Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
702fc7c
feat: add SVG to text diff mode toggle
flatmax Mar 19, 2026
e435df0
feat: persist viewport state per file during navigation
flatmax Mar 19, 2026
6ed0e6a
docs: add viewport restoration behavior spec
flatmax Mar 19, 2026
809d0b0
docs: add adjacent same-file reuse specification
flatmax Mar 19, 2026
7d0ecaa
feat: add chat message after git reset operation
flatmax Mar 19, 2026
420656a
docs: clarify assistant message on successful hard reset
flatmax Mar 19, 2026
58282b2
docs: clarify system event messages in commit and reset flows
flatmax Mar 19, 2026
54ed804
fix: persist commit events to correct session after restart
flatmax Mar 19, 2026
066661a
docs: update reset flow and system event message specs
flatmax Mar 19, 2026
2910018
docs: add edge wrapping to Alt+Arrow navigation
flatmax Mar 19, 2026
5bf3945
docs: add collab share info and clarify model/tokenizer defaults
flatmax Mar 20, 2026
28bd6c9
docs: expand state snapshot fields and clarify implementation details
flatmax Mar 20, 2026
e37a86b
docs: update search feature with two-panel layout
flatmax Mar 20, 2026
e994e43
docs: clarify directory auto-collapse on scroll sync
flatmax Mar 20, 2026
6554dfe
feat: integrate file search into chat panel action bar
flatmax Mar 20, 2026
f8af1cb
docs: remove legacy search tab documentation and component
flatmax Mar 20, 2026
017baa1
feat: move git actions to dialog header
flatmax Mar 20, 2026
4ad2ebd
refactor: move collab indicator to left of tab buttons
flatmax Mar 20, 2026
c4b385c
refactor: move git actions to center of header
flatmax Mar 20, 2026
bcf62ab
refactor: reorganize header layout and extract collab popover
flatmax Mar 20, 2026
971d3b8
docs: update search mode icons and reorder action bar
flatmax Mar 20, 2026
41f65fb
chore: update AWS region and rename Files tab to Chat
flatmax Mar 20, 2026
63883ac
docs: preserve expand state across search tree swaps
flatmax Mar 20, 2026
ccde21d
feat: add MATLAB language support to diff viewer
flatmax Mar 22, 2026
1cdfb3b
Merge branch 'dev3' of github.com:flatmax/AI-Coder-DeCoder into dev3
flatmax Mar 22, 2026
bfa582b
fix: resolve merge conflict and hide convert tab
flatmax Mar 22, 2026
77cff69
docs: update doc convert tab placement in app shell
flatmax Mar 22, 2026
adaa129
docs: consolidate cache tab into context as sub-view
flatmax Mar 22, 2026
27f593e
docs: update header layout and tab structure
flatmax Mar 22, 2026
5df0b08
docs: remove demo videos from README
flatmax Mar 23, 2026
624080b
Merge branch 'dev3' of github.com:flatmax/AI-Coder-DeCoder into dev3
flatmax Mar 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 1 addition & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@

AC⚡DC is an AI pair-programming tool that runs as a terminal application with a browser-based UI. It helps developers navigate codebases, chat with LLMs, and apply structured file edits — all with intelligent prompt caching to minimize costs.

https://github.com/user-attachments/assets/ece86b13-1d6f-4b1e-a029-f358c50ff858

<details><summary>Slow version</summary>

https://github.com/user-attachments/assets/63e442cf-6d3a-4cbc-a96d-20fe8c4964c8

</details>

## Features
Expand All @@ -25,7 +19,7 @@ https://github.com/user-attachments/assets/63e442cf-6d3a-4cbc-a96d-20fe8c4964c8
- **Voice dictation** via Web Speech API.
- **Math rendering** — LaTeX expressions in LLM responses render as formatted math via KaTeX (`$$...$$` for display blocks, `$...$` for inline).
- **Configurable prompt snippets** for common actions.
- **Full-text search** across the repo with regex, whole-word, and case-insensitive modes.
- **Full-text search** with a two-panel layout — file picker (left) showing matching files with match counts, and a match context panel (right) with highlighted results and bidirectional scroll sync. Supports regex, whole-word, and case-insensitive modes.
- **Session history browser** — search, revisit, and reload past conversations.
- **2D file navigation grid** — open files arrange spatially in a grid overlay. Navigate with `Alt+Arrow` keys for fast directional switching between files without reaching for tabs.
- **Tree-sitter symbol index** across Python, JavaScript/TypeScript, and C/C++ with cross-file references.
Expand Down Expand Up @@ -59,14 +53,6 @@ https://github.com/user-attachments/assets/63e442cf-6d3a-4cbc-a96d-20fe8c4964c8

### Code Review

https://github.com/user-attachments/assets/0e853df6-2d84-4c58-8ea8-95251c4e6822

<details><summary>Slow version</summary>

https://github.com/user-attachments/assets/d923e278-b3ef-46a4-b19e-0d54099bf3a7

</details>

1. Click the review button in the header bar.
2. Select a commit in the git graph to set the review base.
3. Click **Start Review** — the repo enters review mode (soft reset).
Expand Down
4 changes: 3 additions & 1 deletion specs3/1-foundation/communication_layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,14 +336,15 @@ Three top-level service classes, registered via `add_class()`:

| Method | Signature | Description |
|--------|-----------|-------------|
| `LLMService.get_current_state` | `() → {messages, selected_files, excluded_index_files, streaming_active, session_id, repo_name, cross_ref_enabled}` | Full state snapshot |
| `LLMService.get_current_state` | `() → {messages, selected_files, excluded_index_files, streaming_active, session_id, repo_name, init_complete, mode, cross_ref_ready, cross_ref_enabled, doc_convert_available}` | Full state snapshot |
| `LLMService.set_selected_files` | `(files) → [string]` | Update file selection |
| `LLMService.get_selected_files` | `() → [string]` | Current selection |
| `LLMService.chat_streaming` | `(request_id, message, files?, images?) → {status}` | Start streaming chat |
| `LLMService.cancel_streaming` | `(request_id) → {status}` | Cancel active stream |
| `LLMService.new_session` | `() → {session_id}` | Start new session |
| `LLMService.generate_commit_message` | `(diff_text) → string` | Generate commit message |
| `LLMService.commit_all` | `() → {status: "started"}` | Stage, generate message, commit — result via `commitResult` broadcast |
| `LLMService.reset_to_head` | `() → {status, system_event_message}` | Reset git to HEAD, record system event in conversation context and history |
| `LLMService.get_context_breakdown` | `() → {model, total_tokens, blocks, breakdown, ...}` | Token/tier breakdown |
| `LLMService.check_review_ready` | `() → {clean, message?}` | Check for clean tree |
| `LLMService.get_commit_graph` | `(limit?, offset?, include_remote?) → {commits, branches, has_more}` | Delegates to Repo |
Expand Down Expand Up @@ -402,6 +403,7 @@ Three top-level service classes, registered via `add_class()`:
| `Collab.deny_client` | `(client_id) → {ok, client_id}` | Deny and disconnect a pending connection |
| `Collab.get_connected_clients` | `() → [{client_id, ip, role, is_localhost}]` | List all connected clients |
| `Collab.get_collab_role` | `() → {role, is_localhost, client_id}` | Calling client's own role |
| `Collab.get_share_info` | `() → {ips: [string], port: int}` | Routable LAN IPs and WebSocket port for share URL construction |

### Browser Methods (Server → Client)

Expand Down
16 changes: 10 additions & 6 deletions specs3/1-foundation/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ The `min_cacheable_tokens` is model-aware — per Anthropic's prompt caching doc
- **4096 tokens** for Claude Opus 4.5/4.6, Haiku 4.5
- **1024 tokens** for Claude Sonnet and other Claude models

The version matching uses string-contains checks on the lowercased model name, matching both dash-separated and dot-separated version patterns (e.g., `"4-5"` and `"4.5"` both match). Non-Claude models default to 1024.

The `cache_min_tokens` config value (default: 1024) can override upward but never below the model's hard minimum. Example: Opus 4.6 → `max(1024, 4096) × 1.1 = 4505`. Sonnet → `max(1024, 1024) × 1.1 = 1126`.

A fallback `cache_target_tokens` property (without model reference) computes `cache_min_tokens × cache_buffer_multiplier` (default: 1126) for callers that don't have a model reference.
Expand Down Expand Up @@ -149,15 +151,17 @@ Users who customized managed files directly (instead of using `system_extra.md`)

## Token Counter Data Sources

The token counter uses `litellm`'s model registry to determine model-specific limits:
The token counter uses hardcoded model-family defaults for limits and `tiktoken` for tokenization:

| Property | Source | Fallback |
|----------|--------|----------|
| Tokenizer | `tiktoken.get_encoding()` for the configured model | ~4 characters per token estimate |
| `max_input_tokens` | `litellm` model info based on model name | Hardcoded defaults by model family |
| `max_output_tokens` | `litellm` model info | Hardcoded defaults by model family |
| Tokenizer | `tiktoken.get_encoding("cl100k_base")` | ~4 characters per token estimate |
| `max_input_tokens` | Hardcoded: 1,000,000 for all currently supported models (Claude, GPT-4, GPT-3.5) | 1,000,000 |
| `max_output_tokens` | Hardcoded: 8,192 for Claude models, 4,096 for others | 4,096 |
| `max_history_tokens` | Computed: `max_input_tokens / 16` | — |

**Note:** The implementation does not query `litellm`'s model registry at runtime. All limits are hardcoded constants in `token_counter.py`. The `cl100k_base` encoding is used for all models regardless of provider.

## Settings Service (RPC)

A whitelisted set of config types can be read, written, and reloaded:
Expand Down Expand Up @@ -228,12 +232,12 @@ These files can still be edited directly on disk in the config directory.

## `.ac-dc/` Directory

A per-repository working directory at `{repo_root}/.ac-dc/`. Created on first run and added to `.gitignore`.
A per-repository working directory at `{repo_root}/.ac-dc/`. Created on first run by `ConfigManager._init_ac_dc_dir()` and added to `.gitignore`. The `images/` subdirectory is also created at this time (not lazily by the history store).

| File | Purpose | Lifecycle |
|------|---------|-----------|
| `history.jsonl` | Persistent conversation history | Append-only |
| `symbol_map.txt` | Current symbol map | Rebuilt on startup and before each LLM request |
| `snippets.json` | Per-repo prompt snippets override (optional, all modes) | User-managed |
| `images/` | Persisted chat images | Write on paste, read on session load |
| `images/` | Persisted chat images | Created by ConfigManager on init; write on paste, read on session load |
| `doc_cache/` | Disk-persisted document outline cache (keyword-enriched) | Auto-managed |
24 changes: 18 additions & 6 deletions specs3/1-foundation/repository_operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,24 @@ Per-file addition/deletion counts from `git diff --numstat` (both staged and uns

### Commit Flow (UI-Driven)

1. Stage all changes (`stage_all`)
2. Get staged diff (`get_staged_diff`)
3. Send diff to LLM to generate commit message
4. Commit with generated message (`commit`)
5. Display commit message as assistant message in chat
6. Refresh file tree
1. User clicks 💾 in action bar → `LLMService.commit_all()`
2. Server captures current session ID **synchronously before launching the background task**, returns `{status: "started"}` immediately. The session ID is captured early so the commit event is persisted to the correct session even if `_session_id` is replaced by `_restore_last_session()` during a concurrent server restart.
3. Background task: stage all changes (`stage_all`)
4. Get staged diff (`get_staged_diff`)
5. Send diff to LLM to generate commit message
6. Commit with generated message (`commit`)
7. Record a **system event message** (`role: "user"`, `system_event: true`) in conversation context and persistent history, using the captured session ID
8. Broadcast `commitResult` to all clients (displays as system event card in chat)
9. Clients refresh file tree

### Reset Flow (UI-Driven)

1. User clicks ⚠️ in action bar → confirmation dialog
2. On confirm → `LLMService.reset_to_head()`
3. Server delegates to `Repo.reset_hard()`
4. Record a **system event message** (`role: "user"`, `system_event: true`) in conversation context and persistent history
5. Return result with `system_event_message` field
6. Client displays system event card and refreshes file tree

## Search

Expand Down
5 changes: 5 additions & 0 deletions specs3/2-code-analysis/document_mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ KeyBERT depends on `sentence-transformers` which downloads the configured model
- `KeyBERT` is imported inside `__init__` or on first call
- If `keybert` is not installed, a warning is logged and headings are emitted without keywords
- The model is initialized once and reused across all files in an indexing run
- Before loading the model, the enricher probes the Hugging Face local cache via `huggingface_hub.try_to_load_from_cache()` to determine whether the sentence-transformer model needs downloading. If the probe returns `None` (model not cached), a "Downloading…" progress message is shown; otherwise a "Loading from cache…" message is shown. The probe is non-critical — if it fails, initialization proceeds normally with a generic "Loading…" message

### Graceful Degradation in Packaged Releases

Expand Down Expand Up @@ -599,6 +600,8 @@ For comparison, tree-sitter indexing of a full repo takes 1-5s. Document indexin

**Threaded cache writes:** During the background enrichment phase, a `ThreadPoolExecutor(max_workers=4)` overlaps disk sidecar writes with the CPU-bound keyword extraction for the next file. Since enrichment is CPU-bound (sentence-transformer embedding) and cache writes are I/O-bound, this keeps disk I/O off the critical path. The sentence-transformer itself is **not** run in threads — Python's GIL prevents CPU-bound threading from providing speedup, and the model's ~420MB memory footprint makes process-based parallelism impractical. The real speed win comes from batched extraction: `KeywordEnricher.enrich()` sends all sections to KeyBERT in a single `extract_keywords()` call, which lets the underlying transformer batch-encode embeddings in one forward pass (2-4× faster than per-heading calls).

**Structure-only extraction method:** `DocIndex._extract_outlines_structure_only()` is a separate code path from `_extract_outlines()` that accepts any cached outline regardless of keyword model — it passes `keyword_model=None` to the cache `get()` call, which skips the model-match check. This means an outline enriched with an old model, or an unenriched outline, will be accepted and reused. Only files whose mtime has changed are re-parsed. This method is used by mode switching and chat requests (via `_stream_chat`) to avoid blocking on keyword enrichment during user-facing operations.

**Two-phase indexing principle:** Structural extraction (headings, links, section sizes) is always **synchronous and instant** (<5ms per file via regex). Keyword enrichment is always **asynchronous and never blocks** any user-facing operation. This separation eliminates all blocking edge cases:

- Mode switches are instant — unenriched outlines are available immediately
Expand Down Expand Up @@ -851,6 +854,8 @@ The reference index is built in two passes:
1. **Collect**: iterate over all `DocOutline` objects, extracting every `DocLink` with its `source_heading` and `target_heading` fields. Build a mapping: `(source_path, source_heading) → [(target_path, target_heading)]`
2. **Resolve**: for each link, look up the target path's `DocOutline` and resolve the `target_heading` anchor to a `DocHeading` node. Increment that heading's `incoming_ref_count`. Record the resolved link as a `DocSectionRef` on the source heading's `outgoing_refs` list

**Image link resolution shortcut:** Image links (`is_image=True`) whose targets were already resolved to repo-relative paths by the markdown extractor's path-extension scan skip the `_resolve_link()` step entirely — their `target_file_part` is used directly as the resolved path. This avoids double-resolution (the markdown extractor already resolved relative paths against the source file's directory).

This two-pass approach ensures all outlines are available before resolution begins (a link from doc A to doc B requires B's outline to resolve the heading anchor).

## Design Decisions
Expand Down
8 changes: 6 additions & 2 deletions specs3/2-code-analysis/symbol_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,15 @@ The MATLAB extractor (`MatlabExtractor`) uses **regex-based parsing** — no tre

**Function body analysis:**

For each function, the extractor also scans the body text to extract:
Before pattern matching, a copy of the source text is preprocessed to strip line comments (`%...`) and string literals (`'...'` and `"..."`) to avoid false positives in call site and variable detection.

For each function, the extractor scans the preprocessed body text to extract:
- **Call sites** — identifiers followed by `(`, excluding MATLAB keywords. Produced as `CallSite` objects for the reference index
- **Local variables** — LHS identifiers in assignments (`x = ...` or `[a, b] = ...`), excluding parameters, output args, and keywords. Attached as `variable`-kind children of the function symbol
- **Read variables** — identifiers that appear in the body but are never assigned locally and are not parameters, outputs, or MATLAB builtins. Also attached as `variable`-kind children

**Builtin exclusion:** A large set of common MATLAB builtins (`disp`, `fprintf`, `zeros`, `ones`, `plot`, `figure`, `fopen`, `exist`, `isa`, `class`, `double`, etc. — approximately 80 entries) and keywords (`if`, `for`, `end`, etc.) are excluded from both call site detection and read-variable detection to reduce noise in the symbol map.

**Nesting and `end` tracking:**

MATLAB uses `end` to close `function`, `classdef`, `if`, `for`, `while`, `switch`, `try`, and `parfor` blocks. The `_find_end()` helper scans forward from a block-opening line, tracking nesting depth, to find the matching `end`. This determines function/class extent for:
Expand All @@ -148,7 +152,7 @@ MATLAB uses `end` to close `function`, `classdef`, `if`, `for`, `while`, `switch

**Output arguments as return type:** If a function declares output arguments (`function [a, b] = myFunc(...)`), the output names are joined with `, ` and stored as `return_type` on the symbol (e.g., `"a, b"`).

**Comment and string stripping:** Before pattern matching, line comments (`%...`) and string literals (`'...'` and `"..."`) are stripped from a copy of the source text to avoid false positives in call site and variable detection.
**Comment and string stripping:** Line comments (`%...`) and string literals (`'...'` and `"..."`) are stripped at two levels: (1) a global copy of the source text is preprocessed for top-level pattern matching (`_FUNC_RE`, `_CLASS_RE`, `_VAR_RE`), and (2) each function's body text is independently stripped within `_extract_calls`, `_extract_local_vars`, and `_extract_read_vars` before scanning for identifiers. The per-function-body stripping ensures that comments and strings inside function bodies don't produce false positives even when the body text is sliced from the original (unstripped) source.

**Builtin exclusion:** A large set of common MATLAB builtins (`disp`, `fprintf`, `zeros`, `plot`, etc.) and keywords (`if`, `for`, `end`, etc.) are excluded from read-variable detection to reduce noise in the symbol map.

Expand Down
Loading