diff --git a/.gitignore b/.gitignore
index 2c370d2d0..9ca3d0d09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -68,8 +68,8 @@ gitnexus-web/test-results/
eval/.coverage
eval/.hypothesis/
-# Design docs (local only)
-docs/plans/
+# Local docs
+docs/
gitnexus/test/fixtures/mini-repo/*.md
gitnexus/test/fixtures/mini-repo/.claude
diff --git a/docs/code-indexing/cobol/README.md b/docs/code-indexing/cobol/README.md
deleted file mode 100644
index c96eb4626..000000000
--- a/docs/code-indexing/cobol/README.md
+++ /dev/null
@@ -1,100 +0,0 @@
-# COBOL Code Indexing
-
-GitNexus indexes COBOL codebases using a **regex-only extraction** strategy, bypassing tree-sitter entirely. This document explains why, how the pipeline works, and links to detailed sub-documents.
-
-## Why Regex-Only?
-
-The tree-sitter-cobol grammar (v0.0.1) has three critical limitations that make it unusable for production indexing:
-
-| Issue | Impact | Severity |
-|-------|--------|----------|
-| External scanner hangs on ~5% of files | No timeout mechanism exists for the C scanner; the process blocks indefinitely | **Blocking** |
-| Only ~15% of paragraph headers detected | Most procedure-division paragraphs are invisible to the grammar | High |
-| Patch markers in cols 1-6 cause parse errors | Enterprise COBOL uses non-standard sequence area content (e.g., `mzADD`, `estero`, `#FIX`) | High |
-
-Because the external scanner hang cannot be interrupted (there is no `setTimeoutMicros` equivalent for tree-sitter), using tree-sitter-cobol would hang the indexing pipeline on a non-trivial fraction of real-world files.
-
-The regex-only approach provides:
-
-- **Speed**: ~1ms per file average extraction time
-- **Reliability**: zero hangs, zero crashes across 13,000+ files
-- **Coverage**: captures all critical symbols -- program name, paragraphs, sections, CALL, PERFORM, COPY, data items (01-77, 88-level), file declarations, FD entries, EXEC SQL/CICS blocks, ENTRY points, and MOVE statements
-
-## Architecture
-
-```mermaid
-flowchart TD
- A[Repository Scan] --> B{File Detection}
- B -->|Extension match| C[COBOL file]
- B -->|GITNEXUS_COBOL_DIRS match| C
- B -->|No match| Z[Skip]
-
- C --> D{Copybook?}
- D -->|Yes| E[Add to Copybook Map]
- D -->|No| F[Source Program]
-
- E --> G[COPY Expansion Engine]
- F --> G
-
- G -->|Inline copybook content| H[Expanded Source]
- H --> I[Patch Marker Cleanup]
- I --> J[Regex State Machine]
-
- J --> K[Extracted Symbols]
- K --> L[Graph Model Builder]
- L --> M[Knowledge Graph]
-
- subgraph "Per-Chunk Processing"
- G
- H
- I
- J
- K
- L
- end
-
- subgraph "Post-Processing"
- M --> N[Community Detection]
- M --> O[Process Detection]
- M --> P[Contract Detection]
- end
-
- style J fill:#e8f5e9,stroke:#2e7d32
- style G fill:#e3f2fd,stroke:#1565c0
-```
-
-## COBOL vs Tree-Sitter Languages
-
-| Feature | COBOL (Regex) | Tree-Sitter Languages |
-|---------|--------------|----------------------|
-| Parser | Single-pass regex state machine | tree-sitter grammar + queries |
-| Speed | ~1ms/file | ~5ms/file |
-| AST available | No | Yes |
-| COPY expansion | Yes (pre-processing step) | N/A |
-| Deep indexing | Data items, SQL, CICS, FD, ENTRY | Type annotations, generics, etc. |
-| Call extraction | PERFORM (intra-file) + CALL (cross-program) | AST-based call site detection |
-| Import extraction | COPY statements | `import`/`require`/`use`/`#include` |
-| Coverage | All critical symbols | Language-dependent query coverage |
-| Failure mode | Never hangs | External scanner can hang (COBOL only) |
-
-## Sub-Documents
-
-| Document | Description |
-|----------|-------------|
-| [File Detection](./file-detection.md) | Extension mapping, `GITNEXUS_COBOL_DIRS`, copybook classification |
-| [COPY Expansion](./copy-expansion.md) | Copybook inlining, REPLACING transformations, cycle detection |
-| [Regex Extraction](./regex-extraction.md) | State machine, regex patterns, line processing |
-| [Deep Indexing](./deep-indexing.md) | Data items, EXEC SQL/CICS, file declarations, FD, ENTRY, MOVE |
-| [Graph Model](./graph-model.md) | COBOL-specific node types, edge types, full annotated example |
-| [Performance](./performance.md) | Benchmarks, worker pool tuning, caps, troubleshooting |
-
-## Key Source Files
-
-| File | Purpose |
-|------|---------|
-| `gitnexus/src/core/ingestion/cobol-preprocessor.ts` | Patch marker cleanup + regex extraction engine |
-| `gitnexus/src/core/ingestion/cobol-copy-expander.ts` | COPY statement expansion with REPLACING |
-| `gitnexus/src/core/ingestion/utils.ts` | `getLanguageFromPath`, `getLanguageFromFilename` |
-| `gitnexus/src/core/ingestion/pipeline.ts` | `isCobolCopybook`, `expandCobolCopies`, `detectCrossProgamContracts` |
-| `gitnexus/src/core/ingestion/workers/parse-worker.ts` | `processCobolRegexOnly` -- graph model builder |
-| `gitnexus/src/core/ingestion/workers/worker-pool.ts` | Configurable sub-batch size for COBOL |
diff --git a/docs/code-indexing/cobol/copy-expansion.md b/docs/code-indexing/cobol/copy-expansion.md
deleted file mode 100644
index 7c6aaa2a3..000000000
--- a/docs/code-indexing/cobol/copy-expansion.md
+++ /dev/null
@@ -1,157 +0,0 @@
-# COBOL COPY Expansion
-
-The COPY statement is COBOL's include mechanism -- analogous to `#include` in C or `import` in modern languages. GitNexus expands COPY statements **before** regex extraction so that symbols defined inside copybooks (data items, paragraphs, etc.) are visible in the program's extracted graph.
-
-## Supported Syntax
-
-### Basic COPY
-
-```cobol
-COPY CPSESP.
-COPY "WORKGRID.CPY".
-```
-
-Inlines the content of the named copybook, replacing the COPY line(s).
-
-### COPY with REPLACING
-
-```cobol
-COPY CPSESP REPLACING "ANAZI-KEY" BY "LK-KEY".
-COPY CPSESP REPLACING LEADING "ESP-" BY "LK-ESP-"
- LEADING "KPSESPL" BY "LK-KPSESPL".
-COPY LINKAGE REPLACING TRAILING "-IN" BY "-OUT".
-```
-
-Three REPLACING types are supported:
-
-| Type | Syntax | Behavior | Example |
-| ------------ | ------------------------------------ | --------------------------------------- | -------------------------------- |
-| **EXACT** | `REPLACING "OLD" BY "NEW"` | Replace exact identifier matches | `ANAZI-KEY` becomes `LK-KEY` |
-| **LEADING** | `REPLACING LEADING "PFX-" BY "NEW-"` | Replace prefix on all COBOL identifiers | `ESP-NAME` becomes `LK-ESP-NAME` |
-| **TRAILING** | `REPLACING TRAILING "-IN" BY "-OUT"` | Replace suffix on all COBOL identifiers | `DATA-IN` becomes `DATA-OUT` |
-
-Multiple REPLACING clauses can appear in a single COPY statement. They are applied in order to each COBOL identifier in the copybook content.
-
-### Multi-Line COPY
-
-COPY statements can span multiple lines (standard COBOL continuation rules apply):
-
-```cobol
- COPY CPSESP REPLACING
- - LEADING "ESP-" BY "LK-ESP-"
- - LEADING "KPSESPL" BY "LK-KPSESPL".
-```
-
-Continuation lines (indicator `-` in column 7) are merged before COPY statement scanning.
-
-## Expansion Flow
-
-```mermaid
-sequenceDiagram
- participant Pipeline
- participant Expander as COPY Expander
- participant Resolver
- participant Reader
-
- Pipeline->>Pipeline: Identify all COBOL files
- Pipeline->>Pipeline: Classify copybooks vs programs
- Pipeline->>Reader: Read all copybook content upfront
- Reader-->>Pipeline: Copybook content map (name -> content)
-
- loop For each source file in chunk
- Pipeline->>Expander: expandCopies(content, filePath, resolveFile, readFile)
- Expander->>Expander: Merge continuation lines
- Expander->>Expander: Detect COPY statements via regex
-
- loop For each COPY statement (reverse order)
- Expander->>Resolver: resolveFile(copyTarget)
- Resolver-->>Expander: Copybook key or null
-
- alt Resolved successfully
- Expander->>Reader: readFile(resolvedKey)
- Reader-->>Expander: Copybook content
-
- Expander->>Expander: Apply REPLACING transformations
- Expander->>Expander: Recurse for nested COPYs (depth + 1)
- Expander->>Expander: Splice expanded content into output
- else Not resolved
- Expander->>Expander: Keep original COPY line
- end
- end
-
- Expander-->>Pipeline: Expanded content + resolution metadata
- Pipeline->>Pipeline: Replace file content with expanded content
- end
-```
-
-The return type `CopyExpansionResult` contains `expandedContent` and `copyResolutions`. The `expansionDepth` field has been removed from the return type (it was unused by callers).
-
-COPY statement line numbers in `CopyResolution` are 1-based (consistent with the preprocessor's line numbering). The splice operation that replaces COPY lines with expanded content adjusts for 0-based array indexing internally.
-
-## Cycle Detection
-
-Circular COPY references (e.g., copybook A includes copybook B which includes copybook A) are detected and handled:
-
-1. Each expansion chain maintains a `visited` set of resolved copybook paths
-2. If a copybook path is already in the visited set, the expansion is skipped
-3. A `warnedCircular` set (internal to `expandCopies()`, not a parameter) deduplicates warning messages within a single file expansion
-
-Known circular copybooks in PROJECT-NAME: `ANAZI`, `ANDIP`, `QDIPE` (self-referential includes).
-
-## Max Depth
-
-Nested COPY expansion is limited to **10 levels** (`DEFAULT_MAX_DEPTH`). If a COPY chain exceeds this depth, a warning is logged and the remaining COPY statements are left unexpanded.
-
-## Max Total Expansions
-
-A breadth amplification guard caps the total number of COPY expansions across all branches within a single file to **500** (`MAX_TOTAL_EXPANSIONS`). This prevents exponential blowup from diamond-shaped COPY graphs where N copybooks each include N other copybooks. Once the limit is reached, further COPY statements in that file are left unexpanded and a single warning is logged.
-
-## REPLACING Application Detail
-
-The REPLACING engine works by scanning all COBOL identifiers (matching `\b[A-Z][A-Z0-9-]*\b`) in the copybook content and applying each replacement rule:
-
-```
-Original copybook content:
- 05 ESP-NAME PIC X(30).
- 05 ESP-CODE PIC X(10).
- 05 KPSESPL-FLAG PIC X(01).
-
-After REPLACING LEADING "ESP-" BY "LK-ESP-" LEADING "KPSESPL" BY "LK-KPSESPL":
- 05 LK-ESP-NAME PIC X(30).
- 05 LK-ESP-CODE PIC X(10).
- 05 LK-KPSESPL-FLAG PIC X(01).
-```
-
-For LEADING replacements, the engine checks if each identifier starts with the `from` prefix (case-insensitive) and replaces only the prefix portion, preserving the rest of the identifier.
-
-For TRAILING replacements, the same logic applies to suffixes.
-
-For EXACT replacements, only identifiers that match the `from` value exactly (case-insensitive) are replaced.
-
-## Copybook Resolution
-
-The resolver tries multiple strategies to match a COPY target name to a copybook file:
-
-1. **Exact match**: `COPY CPSESP` resolves to copybook named `CPSESP`
-2. **Strip extension**: `COPY WORKGRID.CPY` strips `.CPY` and resolves to `WORKGRID`
-3. **Add extension**: `COPY CPSESP` tries `CPSESP.CPY` and `CPSESP.COPY`
-
-If no match is found, the COPY statement is left in place (unexpanded) and a resolution record with `resolvedPath: null` is created.
-
-## Pipeline Integration
-
-The expansion runs **per chunk**, after file content is read but before dispatch to worker threads:
-
-1. All copybook files are read upfront (they are typically small, collectively under 100MB)
-2. Per chunk, the copybook map is merged with chunk content (in case a chunk contains copybooks)
-3. Only programs (not copybooks themselves) undergo expansion
-4. The expanded content replaces the original content in-place before worker dispatch
-
-## Inline Comment Handling
-
-The copy expander's `stripInlineComment()` helper is quote-aware: pipe characters (`|`) inside single- or double-quoted strings are preserved. This matches the same quote-aware logic used by the preprocessor.
-
-## Source Files
-
-- `gitnexus/src/core/ingestion/cobol-copy-expander.ts` -- `expandCopies()`, `parseReplacingClause()`, `applyReplacing()`
-- `gitnexus/src/core/ingestion/pipeline.ts` -- `expandCobolCopies()`, copybook map construction, chunk integration
diff --git a/docs/code-indexing/cobol/deep-indexing.md b/docs/code-indexing/cobol/deep-indexing.md
deleted file mode 100644
index f28376782..000000000
--- a/docs/code-indexing/cobol/deep-indexing.md
+++ /dev/null
@@ -1,312 +0,0 @@
-# COBOL Deep Indexing
-
-Beyond basic symbol extraction (program name, paragraphs, CALL, PERFORM, COPY), GitNexus performs deep indexing of COBOL-specific constructs: data items, EXEC SQL/CICS blocks, file declarations, FD entries, ENTRY points, and MOVE statements.
-
-## Data Items
-
-### Level Numbers
-
-| Level Range | Meaning | Graph Node Type |
-|-------------|---------|-----------------|
-| 01 | Record (group item) | `Record` |
-| 02-49 | Elementary/group items | `Property` |
-| 66 | RENAMES | `Property` |
-| 77 | Independent item | `Property` |
-| 88 | Condition name | `Const` |
-
-FILLER items are skipped (no useful name for the graph).
-
-### Clauses Parsed
-
-The `parseDataItemClauses()` function extracts these clauses from the trailing text of a data item declaration:
-
-| Clause | Pattern | Example |
-|--------|---------|---------|
-| `PIC` / `PICTURE` | `\bPIC(?:TURE)?\s+(?:IS\s+)?(\S+)` | `PIC X(30)`, `PICTURE IS 9(5)V99` |
-| `USAGE` | `\bUSAGE\s+(?:IS\s+)?(COMP\|BINARY\|...)` | `USAGE IS COMP-3`, `BINARY` |
-| `REDEFINES` | `\bREDEFINES\s+([A-Z][A-Z0-9-]+)` | `REDEFINES WK-DATE-NUM` |
-| `OCCURS` | `\bOCCURS\s+(\d+)` | `OCCURS 12 TIMES` |
-
-Standalone COMP variants (without the `USAGE` keyword) are also detected: `COMP`, `COMP-1` through `COMP-6`, `COMP-X`, `BINARY`, `PACKED-DECIMAL`.
-
-### Data Hierarchy
-
-Data items form a hierarchical structure based on level numbers. The extractor uses a **stack algorithm**:
-
-```
-Processing order:
- 01 WK-RECORD -> push {01, WK-RECORD} -> parent: Module
- 05 WK-NAME -> push {05, WK-NAME} -> parent: WK-RECORD (01 < 05)
- 10 WK-FIRST -> push {10, WK-FIRST} -> parent: WK-NAME (05 < 10)
- 10 WK-LAST -> pop WK-FIRST, push -> parent: WK-NAME (05 < 10)
- 05 WK-CODE -> pop WK-LAST, WK-NAME -> parent: WK-RECORD (01 < 05)
- 88 WK-ACTIVE -> (88 handled separately) -> parent: WK-CODE
-```
-
-The stack maintains items where each entry's level is strictly less than the next. When a new item arrives with a level <= the top of stack, items are popped until the stack top has a smaller level. A `CONTAINS` edge is created from the stack top to the new item.
-
-For 88-level condition names, the parent is the immediately preceding non-88 data item (found by scanning backwards).
-
-### Annotated Example
-
-```cobol
- 01 WK-EMPLOYEE.
- 05 WK-EMP-ID PIC 9(6).
- 05 WK-EMP-NAME PIC X(30).
- 05 WK-EMP-STATUS PIC X(01).
- 88 WK-ACTIVE VALUE "A".
- 88 WK-INACTIVE VALUE "I".
- 05 WK-SALARY PIC 9(7)V99 COMP-3.
- 05 WK-DEPT PIC X(04) OCCURS 3 TIMES.
-```
-
-Produces:
-- `Record` node: `WK-EMPLOYEE` (level 01, section: working-storage)
-- `Property` nodes: `WK-EMP-ID`, `WK-EMP-NAME`, `WK-EMP-STATUS`, `WK-SALARY`, `WK-DEPT`
-- `Const` nodes: `WK-ACTIVE` (values: `A`), `WK-INACTIVE` (values: `I`)
-- `CONTAINS` edges: `WK-EMPLOYEE -> WK-EMP-ID`, `WK-EMPLOYEE -> WK-EMP-NAME`, etc.
-- `CONTAINS` edges: `WK-EMP-STATUS -> WK-ACTIVE`, `WK-EMP-STATUS -> WK-INACTIVE`
-
-### Data Item Cap
-
-A maximum of **500 data items per file** (`MAX_DATA_ITEMS_PER_FILE`) are processed. Some COBOL programs (especially after COPY expansion) can have 10,000+ data items, which would cause graph bloat and push the V8 relationship Map past its 16.7M entry limit across thousands of files.
-
-The cap applies after extraction: the first 500 items in source order are kept. Since 01-level records appear first, critical top-level structure is preserved.
-
-## EXEC SQL
-
-EXEC SQL blocks are accumulated across lines between `EXEC SQL` and `END-EXEC`, then parsed as a unit.
-
-### Operation Classification
-
-The first SQL keyword determines the operation:
-
-| First Keyword | Operation |
-|---------------|-----------|
-| `SELECT` | SELECT |
-| `INSERT` | INSERT |
-| `UPDATE` | UPDATE |
-| `DELETE` | DELETE |
-| `DECLARE` | DECLARE |
-| `OPEN` | OPEN |
-| `CLOSE` | CLOSE |
-| `FETCH` | FETCH |
-| *(anything else)* | OTHER |
-
-### Table Extraction
-
-Tables are extracted from SQL clauses:
-
-| Clause Pattern | Example |
-|----------------|---------|
-| `FROM
` | `SELECT * FROM EMPLOYEES` |
-| `INSERT INTO
` | `INSERT INTO EMPLOYEES` |
-| `UPDATE
` | `UPDATE EMPLOYEES SET ...` |
-| `JOIN
` | `LEFT JOIN DEPARTMENTS ON ...` |
-
-Note: The `INTO` pattern is restricted to `INSERT INTO` to avoid false positives from `FETCH ... INTO :host-var` and `SELECT ... INTO :host-var` statements, where `INTO` introduces host variables rather than table names.
-
-### Cursor Detection
-
-```cobol
- EXEC SQL
- DECLARE C-EMPLOYEES CURSOR FOR
- SELECT EMP-ID, EMP-NAME FROM EMPLOYEES
- WHERE DEPT = :WK-DEPT
- END-EXEC
-```
-
-Extracts: cursor `C-EMPLOYEES`, table `EMPLOYEES`, host variable `WK-DEPT`.
-
-### Host Variables
-
-Host variables are COBOL variables referenced in SQL with a `:` prefix. The colon is stripped:
-
-```sql
-WHERE EMP-ID = :WK-EMP-ID AND DEPT = :WK-DEPT
-```
-
-Extracts: `WK-EMP-ID`, `WK-DEPT`.
-
-### Graph Output
-
-- `CodeElement` node per table, with description `sql-table op:{OP}`
-- `CodeElement` node per cursor, with description `sql-cursor`
-- `ACCESSES` edge from Module to each CodeElement
-- Deduplication: if the same table appears in multiple SQL blocks, only one node is created
-
-## EXEC CICS
-
-EXEC CICS blocks are accumulated and parsed similarly to SQL blocks.
-
-### Command Detection
-
-Two-word commands are detected first (matched against the block start):
-
-```
-SEND MAP, RECEIVE MAP, SEND TEXT, SEND CONTROL, READ NEXT, READ PREV
-```
-
-If no two-word command matches, the first word is used (e.g., `LINK`, `XCTL`, `RETURN`, `READ`, `WRITE`).
-
-### Extraction
-
-| Element | Pattern | Example |
-|---------|---------|---------|
-| MAP name | `MAP('name')` or `MAP("name")` | `EXEC CICS SEND MAP('EMPMENU')` |
-| PROGRAM name | `PROGRAM('name')` or `PROGRAM("name")` | `EXEC CICS LINK PROGRAM('BGTABUP')` |
-| TRANSID | `TRANSID('name')` or `TRANSID("name")` | `EXEC CICS START TRANSID('EMP1')` |
-
-### Graph Output
-
-- MAP: `CodeElement` node with description `cics-map cmd:{CMD}` + `ACCESSES` edge from Module
-- PROGRAM: `CALLS` edge (cross-program call via CICS LINK/XCTL)
-- TRANSID: `CodeElement` node with description `cics-transid cmd:{CMD}` + `ACCESSES` edge from Module
-
-### Annotated Example
-
-```cobol
- EXEC CICS
- SEND MAP('EMPMENU')
- MAPSET('EMPSET')
- FROM(WK-MAP-DATA)
- ERASE
- END-EXEC
-```
-
-Produces:
-- `CodeElement` node: `EMPMENU` (description: `cics-map cmd:SEND MAP`)
-- `ACCESSES` edge: Module -> `EMPMENU`
-
-## File Declarations
-
-SELECT statements in the INPUT-OUTPUT SECTION are accumulated across multiple lines (until a period terminator) and parsed for:
-
-| Clause | Pattern | Example |
-|--------|---------|---------|
-| SELECT | `SELECT ` | `SELECT MASTER-FILE` |
-| ASSIGN | `ASSIGN TO ` | `ASSIGN TO "MASTER.DAT"` |
-| ORGANIZATION | `ORGANIZATION IS ` | `ORGANIZATION IS INDEXED` |
-| ACCESS | `ACCESS MODE IS ` | `ACCESS MODE IS DYNAMIC` |
-| RECORD KEY | `RECORD KEY IS ` | `RECORD KEY IS WK-EMP-ID` |
-| FILE STATUS | `FILE STATUS IS ` | `FILE STATUS IS WK-FILE-STATUS` |
-
-### Graph Output
-
-- `CodeElement` node with description containing all parsed clauses (e.g., `select org:INDEXED access:DYNAMIC key:WK-EMP-ID status:WK-FILE-STATUS assign:MASTER.DAT`)
-- `RECORD_KEY_OF` edge: from Property node to CodeElement (confidence 0.8)
-- `FILE_STATUS_OF` edge: from Property node to CodeElement (confidence 0.8)
-
-## FD Entries
-
-FD (File Description) entries associate a file name with its record layout:
-
-```cobol
- FD MASTER-FILE.
- 01 MASTER-RECORD.
- 05 MR-EMP-ID PIC 9(6).
- 05 MR-EMP-NAME PIC X(30).
-```
-
-The extractor tracks `pendingFdName` state: when an `FD` line is seen, the next 01-level data item becomes its record.
-
-### Graph Output
-
-- `CodeElement` node with description `fd record:{recordName}`
-- `CONTAINS` edge: FD CodeElement -> Record node
-- `CONTAINS` edge: SELECT CodeElement -> FD CodeElement (linking file declaration to file description)
-
-## ENTRY Points
-
-The `ENTRY` statement defines additional entry points into a COBOL program (in addition to the main program entry):
-
-```cobol
- ENTRY "SUBPROG" USING WK-PARAM-1 WK-PARAM-2.
-```
-
-### Graph Output
-
-- `Constructor` node with description `entry params:{param1},{param2}` (or just `entry` if no parameters)
-- `CONTAINS` edge: Module -> Constructor
-- Symbol table entry (so the entry point is discoverable by name)
-
-## PROCEDURE DIVISION USING
-
-```cobol
- PROCEDURE DIVISION USING WK-INPUT-REC WK-OUTPUT-REC.
-```
-
-The USING clause identifies parameters received by the program from its caller.
-
-### Graph Output
-
-- `RECEIVES` edge: Module -> Property (for each parameter name, confidence 0.8)
-
-## MOVE Statements
-
-MOVE statements produce `ACCESSES` edges in the graph:
-
-```cobol
- MOVE WK-NAME TO OUT-NAME.
- MOVE CORRESPONDING WK-INPUT TO WK-OUTPUT.
- MOVE CORR WK-IN TO WK-OUT.
-```
-
-### Extraction Details
-
-- Source and target identifiers are captured
-- `CORRESPONDING` and its abbreviation `CORR` are both recognized (bulk field-by-field move)
-- Figurative constants (SPACES, ZEROS, LOW-VALUES, HIGH-VALUES, QUOTES, ALL) are skipped
-- The enclosing paragraph (`caller`) is tracked for context
-
-### MOVE CORRESPONDING / CORR Edge Reasons
-
-MOVE CORRESPONDING (and CORR) produces distinct edge reasons to differentiate from simple MOVE:
-
-| Edge | Reason (simple MOVE) | Reason (CORRESPONDING/CORR) |
-|------|---------------------|-----------------------------|
-| Read (source) | `cobol-move-read` | `cobol-move-corresponding-read` |
-| Write (target) | `cobol-move-write` | `cobol-move-corresponding-write` |
-
-This distinction allows queries to find bulk field-by-field moves separately from simple variable assignments.
-
-## GO TO DEPENDING ON
-
-The `GO TO` statement with multiple targets and a `DEPENDING ON` clause is a computed branch:
-
-```cobol
- GO TO PARA-1 PARA-2 PARA-3
- DEPENDING ON WK-SELECTOR.
-```
-
-All target paragraph names are extracted and emitted as separate `gotos` entries. Each target produces a `CALLS` edge in the graph (same semantics as PERFORM). The `DEPENDING ON` variable is not currently tracked as a data-flow dependency.
-
-## SORT INPUT/OUTPUT PROCEDURE
-
-SORT and MERGE statements can specify procedural entry points instead of file-based I/O:
-
-```cobol
- SORT SORT-FILE ON ASCENDING KEY SORT-KEY
- INPUT PROCEDURE IS PREPARE-INPUT
- OUTPUT PROCEDURE IS FORMAT-OUTPUT.
-```
-
-`INPUT PROCEDURE IS` and `OUTPUT PROCEDURE IS` targets are extracted as control-flow targets (same as PERFORM). They produce `performs` entries and corresponding `CALLS` edges in the graph.
-
-## Fixed-Format Literal Continuation
-
-In fixed-format COBOL, string literals can span multiple lines using the continuation indicator (`-` in column 7). When a continuation line starts with a quote character, the extractor joins it with the predecessor by removing the trailing quote from the previous line and the opening quote from the continuation:
-
-```
-Line N: MOVE "THIS IS A LONG STRI
-Line N+1 (cont): - "NG VALUE" TO WK-FIELD.
-Merged: MOVE "THIS IS A LONG STRING VALUE" TO WK-FIELD.
-```
-
-The trailing `"` on line N and the opening `"` on line N+1 are both removed, producing a seamless literal. If no matching quote is found on the predecessor line, the continuation is appended as-is.
-
-## Source Files
-
-- `gitnexus/src/core/ingestion/cobol-preprocessor.ts` -- All extraction logic, clause parsers, EXEC block parsers
-- `gitnexus/src/core/ingestion/workers/parse-worker.ts` -- `processCobolRegexOnly()`, graph node/edge emission
-- `gitnexus/src/core/ingestion/parsing-processor.ts` -- Sequential fallback with same `MAX_DATA_ITEMS_PER_FILE` cap
diff --git a/docs/code-indexing/cobol/file-detection.md b/docs/code-indexing/cobol/file-detection.md
deleted file mode 100644
index 60b60918a..000000000
--- a/docs/code-indexing/cobol/file-detection.md
+++ /dev/null
@@ -1,126 +0,0 @@
-# COBOL File Detection
-
-GitNexus detects COBOL files through two mechanisms: extension-based mapping and directory-based override for extensionless files. This document covers both, plus the copybook/program classification logic.
-
-## Extension Mapping
-
-### Program Extensions
-
-| Extension | Type |
-|-----------|------|
-| `.cbl` | COBOL program |
-| `.cob` | COBOL program |
-| `.cobol` | COBOL program |
-
-### Copybook Extensions
-
-| Extension | Type | Notes |
-|-----------|------|-------|
-| `.cpy` | Copybook | Standard |
-| `.copy` | Copybook | Standard |
-| `.gnm` / `.GNM` | Copybook | Enterprise (GnuCOBOL naming) |
-| `.fd` / `.FD` | Copybook | File Description fragment |
-| `.wrk` / `.WRK` | Copybook | Working-Storage fragment |
-| `.sel` / `.SEL` | Copybook | SELECT clause fragment |
-| `.open` / `.OPEN` | Copybook | File OPEN fragment |
-| `.close` / `.CLOSE` | Copybook | File CLOSE fragment |
-| `.ini` / `.INI` | Copybook | Initialization fragment |
-| `.def` / `.DEF` | Copybook | Definition fragment |
-
-All extension matching is case-sensitive in `getLanguageFromFilename` (the extensions above are matched as written, including uppercase variants like `.GNM`).
-
-## Extensionless File Detection: `GITNEXUS_COBOL_DIRS`
-
-Many enterprise COBOL repositories use extensionless files -- the filename alone identifies the program (e.g., `s/BGTABFL` is the source for program `BGTABFL`). GitNexus handles this via the `GITNEXUS_COBOL_DIRS` environment variable.
-
-### Configuration
-
-Set `GITNEXUS_COBOL_DIRS` to a comma-separated list of directory names:
-
-```bash
-# Files in s/, c/, and wfproc/ directories (at any depth) are treated as COBOL
-export GITNEXUS_COBOL_DIRS=s,c,wfproc
-```
-
-The matching is **case-insensitive** and checks all path segments:
-
-- `/repo/s/BGTABFL` -- matches segment `s` -- COBOL
-- `/repo/src/c/CPSESP` -- matches segment `c` -- COBOL
-- `/repo/wfproc/WF001` -- matches segment `wfproc` -- COBOL
-- `/repo/docs/README` -- no matching segment -- skipped
-
-### Decision Tree
-
-```mermaid
-flowchart TD
- A[getLanguageFromPath] --> B[getLanguageFromFilename]
- B --> C{Known extension?}
- C -->|Yes .cbl/.cob/.cobol/.cpy/...| D[Return COBOL]
- C -->|Yes .ts/.py/.java/...| E[Return other language]
- C -->|No match| F{Has extension?}
-
- F -->|"Has dot in basename"| G[Return null]
- F -->|"No dot = extensionless"| H{GITNEXUS_COBOL_DIRS set?}
-
- H -->|No| G
- H -->|Yes| I{Any path segment matches a configured dir?}
-
- I -->|Yes| D
- I -->|No| G
-
- style D fill:#e8f5e9,stroke:#2e7d32
- style G fill:#ffebee,stroke:#c62828
-```
-
-### Implementation Detail
-
-The `GITNEXUS_COBOL_DIRS` value is parsed once (on first call) and cached in a `Set`:
-
-```typescript
-// From gitnexus/src/core/ingestion/utils.ts
-const getCobolDirs = (): Set => {
- if (_cobolDirs) return _cobolDirs;
- const raw = process.env.GITNEXUS_COBOL_DIRS;
- _cobolDirs = raw
- ? new Set(raw.split(',').map(d => d.trim().toLowerCase()))
- : new Set();
- return _cobolDirs;
-};
-```
-
-The path segment check splits the full path on `/` and tests each segment against the cached set.
-
-## Copybook vs Program Classification
-
-After a file is identified as COBOL, it must be classified as either a **program** (to be parsed for symbols) or a **copybook** (to be loaded into the copybook map for COPY expansion).
-
-### Classification Rules
-
-A COBOL file is classified as a **copybook** if ANY of these conditions is true:
-
-1. It has a recognized copybook extension (`.cpy`, `.copy`, `.gnm`, `.fd`, `.wrk`, `.sel`, `.open`, `.close`, `.ini`, `.def`)
-2. It is an extensionless file whose path contains a directory segment matching one of: `c`, `copy`, `copybooks`, `copylib`, `cpy`
-
-A file is classified as a **program** if:
-
-1. It has a program extension (`.cbl`, `.cob`, `.cobol`), OR
-2. It is extensionless and does NOT match any copybook directory pattern
-
-### Copybook Name Resolution
-
-Copybook names are derived from the filename:
-
-- Strip the extension (if any)
-- Convert to uppercase
-
-Examples:
-- `c/CPSESP` -- name: `CPSESP`
-- `copy/workgrid.cpy` -- name: `WORKGRID`
-- `c/ANAZI.GNM` -- name: `ANAZI`
-
-This name is used to resolve `COPY CPSESP.` statements during expansion.
-
-## Source Files
-
-- `gitnexus/src/core/ingestion/utils.ts` -- `getLanguageFromPath()`, `getLanguageFromFilename()`, `getCobolDirs()`
-- `gitnexus/src/core/ingestion/pipeline.ts` -- `isCobolCopybook()`, `getCopybookName()`, `COPYBOOK_EXTENSIONS`, `COBOL_PROGRAM_EXTENSIONS`
diff --git a/docs/code-indexing/cobol/graph-model.md b/docs/code-indexing/cobol/graph-model.md
deleted file mode 100644
index de82c0723..000000000
--- a/docs/code-indexing/cobol/graph-model.md
+++ /dev/null
@@ -1,193 +0,0 @@
-# COBOL Graph Model
-
-This document describes the graph nodes and edges that GitNexus creates for COBOL codebases. The COBOL graph model is richer than most tree-sitter languages because it captures domain-specific constructs: file declarations, FD entries, data hierarchies, SQL tables, CICS maps, and cross-program contracts.
-
-## Entity-Relationship Diagram
-
-```mermaid
-erDiagram
- File ||--o{ Module : DEFINES
- File ||--o{ Function : DEFINES
- File ||--o{ Namespace : DEFINES
- File ||--o{ Record : DEFINES
- File ||--o{ Property : DEFINES
- File ||--o{ Const : DEFINES
- File ||--o{ CodeElement : DEFINES
- File ||--o{ Constructor : DEFINES
- File }o--o{ File : IMPORTS
-
- Module ||--o{ Record : CONTAINS
- Module ||--o{ Constructor : CONTAINS
- Module }o--o{ CodeElement : ACCESSES
- Module }o--o{ Module : CALLS
- Module }o--o{ Module : CONTRACTS
- Module }o--o{ Property : RECEIVES
-
- Record ||--o{ Property : CONTAINS
- Record ||--o{ Const : CONTAINS
- Record }o--o{ Record : REDEFINES
-
- Property ||--o{ Property : CONTAINS
- Property ||--o{ Const : CONTAINS
- Property }o--o{ Property : REDEFINES
- Property }o--o{ CodeElement : RECORD_KEY_OF
- Property }o--o{ CodeElement : FILE_STATUS_OF
-
- CodeElement ||--o{ CodeElement : CONTAINS
- CodeElement ||--o{ Record : CONTAINS
-
- Function }o--o{ Function : CALLS
-```
-
-## Node Types
-
-| Node Type | COBOL Concept | Created From | Example |
-|-----------|--------------|--------------|---------|
-| `Module` | PROGRAM-ID | `PROGRAM-ID. BGTABFL` | Name: `BGTABFL`, description may include author and date |
-| `Function` | Paragraph | `PROCESS-RECORD.` at column 8 | Name: `PROCESS-RECORD` |
-| `Namespace` | Procedure section | `MAIN-LOGIC SECTION.` at column 8 | Name: `MAIN-LOGIC` |
-| `Record` | 01-level data item | `01 WK-EMPLOYEE.` | Description: `level:01 section:working-storage` |
-| `Property` | 02-49/66/77 data item | `05 WK-NAME PIC X(30).` | Description: `level:05 pic:X(30) section:working-storage` |
-| `Const` | 88-level condition | `88 WK-ACTIVE VALUE "A".` | Description: `level:88 values:A` |
-| `CodeElement` | SELECT, FD, SQL table, CICS map, cursor, transid | Various | Description varies by subtype |
-| `Constructor` | ENTRY point | `ENTRY "SUBPROG" USING WK-DATA` | Description: `entry params:WK-DATA` |
-
-### CodeElement Subtypes
-
-CodeElement is used for multiple COBOL constructs, distinguished by their description prefix:
-
-| Subtype | ID Pattern | Description Format | Example |
-|---------|-----------|-------------------|---------|
-| File SELECT | `CodeElement:{path}:SELECT:{name}` | `select org:INDEXED access:DYNAMIC ...` | `SELECT MASTER-FILE` |
-| FD entry | `CodeElement:{path}:FD:{name}` | `fd record:{recordName}` | `FD MASTER-FILE` |
-| SQL table | `CodeElement:{path}:sql-table:{name}` | `sql-table op:SELECT` | Table `EMPLOYEES` |
-| SQL cursor | `CodeElement:{path}:sql-cursor:{name}` | `sql-cursor` | Cursor `C-EMPLOYEES` |
-| CICS map | `CodeElement:{path}:cics-map:{name}` | `cics-map cmd:SEND MAP` | Map `EMPMENU` |
-| CICS transid | `CodeElement:{path}:cics-transid:{name}` | `cics-transid cmd:START` | Transid `EMP1` |
-
-## Edge Types
-
-| Edge Type | Source | Target | Created By | Confidence | Example |
-|-----------|--------|--------|-----------|------------|---------|
-| `DEFINES` | File | any node | File defines its symbols | 1.0 | File -> Module `BGTABFL` |
-| `CALLS` | Function | Function | `PERFORM X [THRU Y]` | (via call-processor) | `PROCESS-RECORD` -> `CALC-TAX` |
-| `CALLS` | Module | Module | `CALL "BGTABUP"` | (via call-processor) | `BGTABFL` -> `BGTABUP` |
-| `CALLS` | Module | Module | `EXEC CICS LINK PROGRAM('X')` | (via call-processor) | `BGTABFL` -> `BGTABUP` |
-| `IMPORTS` | File | File | `COPY copybook` | (via import-processor) | Source file -> Copybook file |
-| `CONTAINS` | Module | Record | Data hierarchy root | 1.0 | `BGTABFL` -> `WK-EMPLOYEE` |
-| `CONTAINS` | Record | Property | Data hierarchy | 1.0 | `WK-EMPLOYEE` -> `WK-NAME` |
-| `CONTAINS` | Property | Property | Nested data items | 1.0 | `WK-ADDRESS` -> `WK-CITY` |
-| `CONTAINS` | Record/Property | Const | 88-level parent | 1.0 | `WK-STATUS` -> `WK-ACTIVE` |
-| `CONTAINS` | CodeElement (FD) | Record | FD record link | 1.0 | `FD:MASTER-FILE` -> `MASTER-RECORD` |
-| `CONTAINS` | CodeElement (SELECT) | CodeElement (FD) | SELECT-FD link | 0.9 | `SELECT:MASTER-FILE` -> `FD:MASTER-FILE` |
-| `CONTAINS` | Module | Constructor | ENTRY in module | 1.0 | `BGTABFL` -> `SUBPROG` |
-| `REDEFINES` | Record | Record | `01 X REDEFINES Y` | 1.0 | `WK-DATE-NUM` -> `WK-DATE-ALPHA` |
-| `REDEFINES` | Property | Property | `05 X REDEFINES Y` | 1.0 | `WK-CODE-NUM` -> `WK-CODE-ALPHA` |
-| `RECORD_KEY_OF` | Property | CodeElement (SELECT) | `RECORD KEY IS field` | 0.8 | `WK-EMP-ID` -> `SELECT:MASTER-FILE` |
-| `FILE_STATUS_OF` | Property | CodeElement (SELECT) | `FILE STATUS IS field` | 0.8 | `WK-FS` -> `SELECT:MASTER-FILE` |
-| `ACCESSES` | Module | CodeElement | EXEC SQL/CICS | 0.9 | `BGTABFL` -> `sql-table:EMPLOYEES` |
-| `RECEIVES` | Module | Property | `PROCEDURE USING` | 0.8 | `BGTABFL` -> `WK-INPUT-REC` |
-| `CONTRACTS` | Module | Module | Shared copybook detection | 0.9 | `BGTABFL` -> `BGTABUP` (via `CPSESP`) |
-
-## Full Annotated Example
-
-Given this COBOL program:
-
-```cobol
- IDENTIFICATION DIVISION.
- PROGRAM-ID. EMPMAINT.
- AUTHOR. Development Team.
-
- ENVIRONMENT DIVISION.
- INPUT-OUTPUT SECTION.
- FILE-CONTROL.
- SELECT EMP-FILE
- ASSIGN TO "EMPLOYEE.DAT"
- ORGANIZATION IS INDEXED
- ACCESS MODE IS DYNAMIC
- RECORD KEY IS EMP-ID
- FILE STATUS IS WS-FILE-STATUS.
-
- DATA DIVISION.
- FILE SECTION.
- FD EMP-FILE.
- 01 EMP-RECORD.
- 05 EMP-ID PIC 9(6).
- 05 EMP-NAME PIC X(30).
-
- WORKING-STORAGE SECTION.
- 01 WS-FLAGS.
- 05 WS-FILE-STATUS PIC X(02).
- 05 WS-EOF-FLAG PIC X(01).
- 88 WS-EOF VALUE "Y".
-
- LINKAGE SECTION.
- 01 LK-SEARCH-KEY PIC 9(6).
-
- PROCEDURE DIVISION USING LK-SEARCH-KEY.
- MAIN-LOGIC SECTION.
- MAIN-START.
- PERFORM OPEN-FILE
- PERFORM PROCESS-RECORDS
- PERFORM CLOSE-FILE
- STOP RUN.
-
- OPEN-FILE.
- OPEN I-O EMP-FILE.
-
- PROCESS-RECORDS.
- MOVE LK-SEARCH-KEY TO EMP-ID
- EXEC SQL
- SELECT EMP_SALARY INTO :WS-SALARY
- FROM EMPLOYEES
- WHERE EMP_ID = :EMP-ID
- END-EXEC
- CALL "EMPREPORT".
-
- CLOSE-FILE.
- CLOSE EMP-FILE.
-```
-
-The graph produced contains:
-
-**Nodes:**
-- `Module`: EMPMAINT (description: `author:Development Team`)
-- `Namespace`: MAIN-LOGIC
-- `Function`: MAIN-START, OPEN-FILE, PROCESS-RECORDS, CLOSE-FILE
-- `Record`: EMP-RECORD, WS-FLAGS, LK-SEARCH-KEY
-- `Property`: EMP-ID, EMP-NAME, WS-FILE-STATUS, WS-EOF-FLAG
-- `Const`: WS-EOF (values: Y)
-- `CodeElement`: SELECT:EMP-FILE, FD:EMP-FILE, sql-table:EMPLOYEES
-- (COPY imports, if any, would produce File IMPORTS edges)
-
-**Edges:**
-- `DEFINES`: File -> all nodes
-- `CONTAINS`: EMPMAINT -> EMP-RECORD, EMPMAINT -> WS-FLAGS, EMPMAINT -> LK-SEARCH-KEY
-- `CONTAINS`: EMP-RECORD -> EMP-ID, EMP-RECORD -> EMP-NAME
-- `CONTAINS`: WS-FLAGS -> WS-FILE-STATUS, WS-FLAGS -> WS-EOF-FLAG
-- `CONTAINS`: WS-EOF-FLAG -> WS-EOF
-- `CONTAINS`: FD:EMP-FILE -> EMP-RECORD
-- `CONTAINS`: SELECT:EMP-FILE -> FD:EMP-FILE
-- `CALLS`: MAIN-START -> OPEN-FILE, MAIN-START -> PROCESS-RECORDS, MAIN-START -> CLOSE-FILE
-- `CALLS`: EMPMAINT -> EMPREPORT (external CALL)
-- `ACCESSES`: EMPMAINT -> sql-table:EMPLOYEES
-- `RECEIVES`: EMPMAINT -> LK-SEARCH-KEY (PROCEDURE USING)
-- `RECORD_KEY_OF`: EMP-ID -> SELECT:EMP-FILE
-- `FILE_STATUS_OF`: WS-FILE-STATUS -> SELECT:EMP-FILE
-
-## How COBOL Differs from Tree-Sitter Languages
-
-| Aspect | COBOL | Tree-Sitter Languages |
-|--------|-------|----------------------|
-| Node variety | 8 types (Module, Function, Namespace, Record, Property, Const, CodeElement, Constructor) | Typically 4-6 (Function, Class, Method, Interface, Module, Const) |
-| Domain edges | RECORD_KEY_OF, FILE_STATUS_OF, ACCESSES, RECEIVES, CONTRACTS, REDEFINES | Primarily CALLS, IMPORTS, EXTENDS, IMPLEMENTS |
-| Data hierarchy | Deep CONTAINS chains (01 -> 05 -> 10 -> 88) | Flat class members |
-| Cross-program calls | CALL "name" + CICS LINK PROGRAM | Import-based resolution |
-| Contract detection | Shared COPY copybook between caller/callee | Not applicable |
-| Metadata | AUTHOR, DATE-WRITTEN on Module | JSDoc/docstring (not indexed) |
-
-## Source Files
-
-- `gitnexus/src/core/ingestion/workers/parse-worker.ts` -- `processCobolRegexOnly()`, node/edge emission logic
-- `gitnexus/src/core/ingestion/pipeline.ts` -- `detectCrossProgamContracts()` for CONTRACTS edges
-- `gitnexus/src/core/ingestion/cobol-preprocessor.ts` -- `CobolRegexResults` interface (all extracted data)
diff --git a/docs/code-indexing/cobol/performance.md b/docs/code-indexing/cobol/performance.md
deleted file mode 100644
index b0f69e701..000000000
--- a/docs/code-indexing/cobol/performance.md
+++ /dev/null
@@ -1,261 +0,0 @@
-# COBOL Performance and Tuning
-
-This document covers real-world benchmarks, worker pool configuration, memory management, known limitations, and troubleshooting for COBOL indexing.
-
-## PROJECT-NAME Benchmark
-
-The PROJECT-NAME project is a large Italian payroll system written in COBOL. It serves as the primary benchmark for COBOL indexing performance.
-
-### Input
-
-| Metric | Value |
-| --------------------------- | ---------------------------------------------------------------------------- |
-| Paths scanned | 14,217 |
-| Parseable files | 13,129 |
-| Total source size | 224 MB |
-| Chunks | 12 (at 20 MB budget) |
-| Copybooks loaded | 2,976 |
-| Copybooks used in expansion | 2,955 |
-| Key directories | `s/` (7773 programs), `c/` (3036 copybooks), `wfproc/` (1973 workflow files) |
-
-### Output
-
-| Metric | Value |
-| ---------------------- | ------ |
-| Graph nodes | 2.79M |
-| Graph edges | 5.67M |
-| Clusters (communities) | 16,679 |
-| Execution flows | 300 |
-
-### Timing
-
-| Phase | Duration |
-| ------------------------------- | ----------------- |
-| Total | ~251s |
-| KuzuDB write | 132s |
-| Full-text search indexing | 6.7s |
-| Regex extraction (avg per file) | ~1ms |
-| COPY expansion + deep indexing | Remainder (~112s) |
-
-### Indexing Command
-
-```bash
-cd /path/to/PROJECT-NAME
-GITNEXUS_COBOL_DIRS=s,c,wfproc GITNEXUS_VERBOSE=1 node --max-old-space-size=8192 \
- /path/to/gitnexus/dist/cli/index.js analyze --force
-```
-
-## Open-Source Benchmarks
-
-### CardDemo (AWS)
-
-| Metric | Value |
-| ------ | ----- |
-| Graph nodes | 12,323 |
-| Graph edges | 8,893 |
-| Total time | 7.4s |
-
-### ACAS
-
-| Metric | Value |
-| ------ | ----- |
-| Graph nodes | 14,016 |
-| Graph edges | 15,452 |
-| Total time | 9.3s |
-
-### Micro-Benchmark (Single-File Extraction)
-
-| Metric | Value |
-| ------ | ----- |
-| Per-iteration | 0.65ms |
-| Throughput | ~382K lines/sec |
-
-## Worker Pool Tuning
-
-### Sub-Batch Size
-
-The worker pool splits each worker's chunk into sub-batches to bound peak memory per `postMessage` serialization. COBOL repos use a smaller sub-batch size than the default:
-
-| Parameter | Default | COBOL Mode |
-| --------------------- | ----------- | ------------------- |
-| Sub-batch size | 1,500 files | 200 files |
-| Per sub-batch timeout | 120s | 120s (configurable) |
-
-**Why 200?** COBOL regex extraction + preprocessing takes ~1ms per file on average, but with COPY expansion and deep indexing the effective time is ~150ms per file. At sub-batch size 1500, that would be ~225s per sub-batch, exceeding the 120s timeout.
-
-COBOL mode is activated automatically when `GITNEXUS_COBOL_DIRS` is set:
-
-```typescript
-// From pipeline.ts
-const cobolSubBatch = process.env.GITNEXUS_COBOL_DIRS ? 200 : undefined;
-workerPool = createWorkerPool(workerUrl, undefined, cobolSubBatch);
-```
-
-### Worker Count
-
-Workers default to `min(8, cpus - 1)`. For COBOL repos, this is usually sufficient since regex extraction is CPU-bound but fast. The bottleneck is typically KuzuDB write, not extraction.
-
-### Timeout Configuration
-
-| Environment Variable | Default | Purpose |
-| ------------------------------------ | --------------- | --------------------------------------------------- |
-| `GITNEXUS_WORKER_TIMEOUT_MS` | 120,000 (2 min) | Per sub-batch processing timeout |
-| `GITNEXUS_WORKER_STARTUP_TIMEOUT_MS` | 60,000 (1 min) | Worker initialization timeout (tree-sitter loading) |
-
-For COBOL-only repos, worker startup is faster because tree-sitter native modules are loaded lazily (skipped entirely if only COBOL files are present).
-
-## Data Item Cap
-
-### Configuration
-
-```typescript
-const MAX_DATA_ITEMS_PER_FILE = 500;
-```
-
-This constant appears in both `parse-worker.ts` (worker path) and `parsing-processor.ts` (sequential fallback).
-
-### Rationale
-
-Some COBOL programs, especially after COPY expansion, can have 10,000+ data items. At that scale:
-
-- The in-memory relationship Map (for CONTAINS, REDEFINES, etc.) approaches the V8 16.7M entry limit across thousands of files
-- KuzuDB write time increases linearly with edge count
-- Most deep-nested items (level 20+) are rarely queried individually
-
-### Impact
-
-The cap truncates data items beyond the 500th in source order. Since 01-level Records appear first in COBOL source, the cap preserves:
-
-- All 01-level record definitions
-- The most important 02-49 level items (those closest to the record root)
-- 88-level conditions associated with early items
-
-To increase the cap for specific needs, modify the `MAX_DATA_ITEMS_PER_FILE` constant in both files.
-
-## Memory Management
-
-### COPY Expansion Breadth Guard
-
-A per-file `MAX_TOTAL_EXPANSIONS = 500` limit prevents exponential blowup from diamond-shaped COPY graphs (e.g., N copybooks each containing N COPY statements). Once the limit is reached, further COPY statements in that file are left unexpanded. See [copy-expansion.md](copy-expansion.md) for details.
-
-### COPY Expansion Memory
-
-All copybook content is loaded upfront into a Map before chunk processing begins. For PROJECT-NAME:
-
-- 2,976 copybooks, typically under 100MB total
-- The Map is shared (read-only) across chunk iterations
-- Per-chunk, the copybook map is merged with chunk file content (in case a chunk contains copybooks not in the pre-loaded set)
-- After all chunks are processed, the copybook map is freed (`cobolCopybookContents = undefined`)
-
-### Chunk Budget
-
-Source files are grouped into chunks of max 20MB (`CHUNK_BYTE_BUDGET`). Each chunk's lifecycle:
-
-1. Read file content into memory
-2. Expand COPY statements (mutates content in-place)
-3. Dispatch to workers for extraction
-4. Workers return serialized results
-5. Merge results into graph
-6. Chunk content goes out of scope (GC reclaims)
-
-This ensures only ~20MB of source + ~200-400MB of working memory (ASTs, extracted records, serialization) is active at any time.
-
-### Shared Warning Deduplication
-
-The `warnedCircular` set (used by the COPY expansion engine) is shared across all files in a chunk. This prevents the same circular copybook warning (e.g., `ANAZI includes itself`) from being logged thousands of times.
-
-## Known Limitations
-
-| Limitation | Impact | Workaround |
-| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
-| tree-sitter-cobol hangs on ~5% of files | Cannot use tree-sitter for COBOL | Regex-only extraction (current approach) |
-| Data item cap (500/file) | May miss deeply nested items in large programs | Increase `MAX_DATA_ITEMS_PER_FILE` in source |
-| Circular copybooks (ANAZI, ANDIP, QDIPE) | Self-referential includes cannot be expanded | Detected and skipped with warning |
-| wfproc/ files may not be pure COBOL | Workflow files may produce extraction noise | Exclude `wfproc` from `GITNEXUS_COBOL_DIRS` if problematic |
-| No MOVE DATA_FLOW edges yet | Data flow between variables not in graph | Reserved for future release |
-| Continuation line handling | Some complex multi-line continuations (especially in string literals spanning 3+ lines) may not merge correctly | Known edge case; affects <0.1% of lines |
-| Single-line EXEC blocks | `EXEC SQL SELECT ... END-EXEC` on one line is handled, but pathological nesting is not | Extremely rare in practice |
-| Extension case sensitivity | `.GNM` and `.gnm` are matched differently | Use the exact case from the codebase |
-
-## Troubleshooting
-
-### "COPY expansion failed"
-
-```
-[pipeline] COPY expansion failed for s/BGTABFL: Cannot read properties of null
-```
-
-**Cause:** A copybook referenced by a COPY statement cannot be found.
-
-**Fix:**
-
-1. Verify `GITNEXUS_COBOL_DIRS` includes the directory containing copybooks (typically `c`)
-2. Check that copybook filenames match the COPY target (case-insensitive, after stripping extensions)
-3. Ensure copybook files are not in `.gitignore`
-
-### Worker sub-batch timeout
-
-```
-Worker 3 sub-batch timed out after 120s (chunk: 200 items)
-```
-
-**Cause:** A sub-batch took longer than the timeout. Typically happens when one file is extremely large (50,000+ lines after COPY expansion).
-
-**Fix:** Increase the timeout:
-
-```bash
-GITNEXUS_WORKER_TIMEOUT_MS=300000 gitnexus analyze
-```
-
-### Memory errors (heap out of memory)
-
-```
-FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
-```
-
-**Fix:** Increase Node.js heap size:
-
-```bash
-node --max-old-space-size=16384 /path/to/gitnexus/dist/cli/index.js analyze
-```
-
-For very large repos (>500MB source), consider `--max-old-space-size=32768`.
-
-### Concurrent analyze corruption
-
-**Rule:** Only ONE `gitnexus analyze` process should run at a time per repository. Concurrent writes to KuzuDB corrupt the database.
-
-If corruption occurs:
-
-```bash
-# Remove the KuzuDB directory and re-index
-rm -rf .gitnexus/kuzu
-gitnexus analyze --force
-```
-
-### Slow KuzuDB write phase
-
-The KuzuDB write phase (132s for PROJECT-NAME) is the bottleneck for large COBOL repos. This is proportional to the number of nodes and edges being written. Reducing `MAX_DATA_ITEMS_PER_FILE` or excluding non-essential directories from `GITNEXUS_COBOL_DIRS` can help.
-
-### Verbose output
-
-Enable verbose logging to see per-phase timing and statistics:
-
-```bash
-GITNEXUS_VERBOSE=1 gitnexus analyze
-```
-
-This outputs:
-
-- Scan statistics (paths, parseable files, chunk count)
-- Worker pool configuration (worker count, sub-batch size)
-- COPY expansion statistics (copybooks loaded, files expanded)
-- Community and process detection results
-- Contract detection results
-
-## Source Files
-
-- `gitnexus/src/core/ingestion/workers/worker-pool.ts` -- `DEFAULT_SUB_BATCH_SIZE`, `SUB_BATCH_TIMEOUT_MS`, `WORKER_STARTUP_TIMEOUT_MS`
-- `gitnexus/src/core/ingestion/pipeline.ts` -- `CHUNK_BYTE_BUDGET`, COBOL sub-batch configuration, chunk lifecycle
-- `gitnexus/src/core/ingestion/workers/parse-worker.ts` -- `MAX_DATA_ITEMS_PER_FILE`, `processCobolRegexOnly()`
-- `gitnexus/src/core/ingestion/parsing-processor.ts` -- Sequential fallback `MAX_DATA_ITEMS_PER_FILE`
diff --git a/docs/code-indexing/cobol/regex-extraction.md b/docs/code-indexing/cobol/regex-extraction.md
deleted file mode 100644
index 9f37c10c9..000000000
--- a/docs/code-indexing/cobol/regex-extraction.md
+++ /dev/null
@@ -1,206 +0,0 @@
-# COBOL Regex Extraction
-
-The `extractCobolSymbolsWithRegex()` function in `cobol-preprocessor.ts` performs single-pass, state-machine-driven extraction of all COBOL symbols. This document describes the state machine, line processing flow, and every regex pattern used.
-
-## State Machine: Division Tracking
-
-The extractor tracks which COBOL division is currently being processed. Division transitions are detected by the `RE_DIVISION` pattern.
-
-```mermaid
-stateDiagram-v2
- [*] --> null : Start of file
- null --> identification : IDENTIFICATION DIVISION
- identification --> environment : ENVIRONMENT DIVISION
- environment --> data : DATA DIVISION
- data --> procedure : PROCEDURE DIVISION
-
- note right of identification
- Extracts: PROGRAM-ID, AUTHOR, DATE-WRITTEN
- end note
- note right of environment
- Extracts: SELECT ... ASSIGN ... (file declarations)
- end note
- note right of data
- Extracts: FD entries, data items (01-77, 88), COPY
- end note
- note right of procedure
- Extracts: paragraphs, sections, PERFORM, CALL,
- ENTRY, MOVE, EXEC SQL/CICS
- end note
-```
-
-## State Machine: Data Section Tracking
-
-Within the DATA DIVISION, a secondary state machine tracks the current section to tag data items with their origin.
-
-```mermaid
-stateDiagram-v2
- [*] --> unknown : DATA DIVISION entered
- unknown --> working_storage : WORKING-STORAGE SECTION
- unknown --> linkage : LINKAGE SECTION
- unknown --> file : FILE SECTION
- unknown --> local_storage : LOCAL-STORAGE SECTION
- working_storage --> linkage : LINKAGE SECTION
- working_storage --> file : FILE SECTION
- linkage --> working_storage : WORKING-STORAGE SECTION
- file --> working_storage : WORKING-STORAGE SECTION
- file --> linkage : LINKAGE SECTION
- local_storage --> working_storage : WORKING-STORAGE SECTION
-```
-
-Within the ENVIRONMENT DIVISION, the `currentEnvSection` tracks whether we are in `INPUT-OUTPUT` or `CONFIGURATION` section. SELECT statement accumulation only occurs in `INPUT-OUTPUT`.
-
-## Line Processing Flow
-
-Each raw source line goes through this pipeline:
-
-```
-Raw line
- |
- v
-Length < 7? ---------> Skip (flush pending if any)
- |
- v
-Indicator col 7
- |
- +-- '*' or '/' -----> Comment: skip entirely
- |
- +-- '-' ------------> Continuation: append to pending line
- |
- +-- other ----------> Normal: flush pending, strip inline comments (|),
- buffer as new pending logical line
-```
-
-After all lines are processed, the final pending line is flushed, along with any accumulated SELECT statement, SORT/MERGE accumulator, and any open EXEC block (truncated file without `END-EXEC`).
-
-### Inline Comment Stripping
-
-Enterprise COBOL (particularly Italian dialect) uses the pipe character `|` as an inline comment marker. The `stripInlineComment()` helper is **quote-aware**: it tracks whether the scan position is inside a single- or double-quoted string and only treats `|` as a comment marker when outside quotes. Pipe characters inside string literals are preserved.
-
-Free-format `*>` inline comment stripping uses the same quote-aware approach: the scanner walks character by character, toggling quote state, and only recognizes `*>` as a comment marker when not inside a quoted string.
-
-### Patch Marker Handling
-
-The `preprocessCobolSource()` function (run before extraction in the worker) replaces non-standard content in columns 1-6. Standard COBOL expects spaces or digit sequence numbers in this area. If any letter or `#` character is found, the entire sequence area is replaced with 6 spaces:
-
-```
-Before: mzADD MOVE WK-AMT TO WK-TOTAL
-After: MOVE WK-AMT TO WK-TOTAL
-```
-
-This preserves exact line count for position mapping.
-
-## Regex Pattern Reference
-
-All patterns are compiled once as module-level constants and reused across calls.
-
-### Division and Section Detection
-
-| Constant | Pattern | Purpose | Example Match |
-|----------|---------|---------|---------------|
-| `RE_DIVISION` | `\b(IDENTIFICATION\|ENVIRONMENT\|DATA\|PROCEDURE)\s+DIVISION\b` | Division boundary | `PROCEDURE DIVISION` |
-| `RE_SECTION` | `\b(WORKING-STORAGE\|LINKAGE\|FILE\|LOCAL-STORAGE\|INPUT-OUTPUT\|CONFIGURATION)\s+SECTION\b` | Section boundary | `WORKING-STORAGE SECTION` |
-
-### IDENTIFICATION DIVISION
-
-| Constant | Pattern | Purpose | Example Match |
-|----------|---------|---------|---------------|
-| `RE_PROGRAM_ID` | `\bPROGRAM-ID\.\s*([A-Z][A-Z0-9-]*)` | Program name | `PROGRAM-ID. BGTABFL` |
-| `RE_AUTHOR` | `^\s+AUTHOR\.\s*(.+)` | Author metadata | `AUTHOR. D. Smith` |
-| `RE_DATE_WRITTEN` | `^\s+DATE-WRITTEN\.\s*(.+)` | Date metadata | `DATE-WRITTEN. 2024-01-15` |
-
-### ENVIRONMENT DIVISION
-
-| Constant | Pattern | Purpose | Example Match |
-|----------|---------|---------|---------------|
-| `RE_SELECT_START` | `\bSELECT\s+(?:OPTIONAL\s+)?([A-Z][A-Z0-9-]+)` | File SELECT start (with optional `SELECT OPTIONAL` support) | `SELECT MASTER-FILE`, `SELECT OPTIONAL TRANS-FILE` |
-
-SELECT statements are accumulated across multiple lines until a period terminator is found, then parsed for ASSIGN, ORGANIZATION, ACCESS, RECORD KEY, and FILE STATUS clauses.
-
-### DATA DIVISION
-
-| Constant | Pattern | Purpose | Example Match |
-|----------|---------|---------|---------------|
-| `RE_FD` | `^\s+FD\s+([A-Z][A-Z0-9-]+)` | File description | `FD MASTER-FILE` |
-| `RE_DATA_ITEM` | `^\s+(\d{1,2})\s+([A-Z][A-Z0-9-]+)\s*(.*)` | Data item (01-77) | `05 WK-NAME PIC X(30)` |
-| `RE_ANONYMOUS_REDEFINES` | `^\s+(\d{1,2})\s+REDEFINES\s+([A-Z][A-Z0-9-]+)` | Anonymous REDEFINES | `01 REDEFINES WK-REC` |
-| `RE_88_LEVEL` | `^\s+88\s+([A-Z][A-Z0-9-]+)\s+VALUES?\s+(?:ARE\s+)?(.+)` | Condition name | `88 WK-ACTIVE VALUE "Y"` |
-
-The trailing clauses of `RE_DATA_ITEM` are parsed by `parseDataItemClauses()` for PIC, USAGE, OCCURS, and REDEFINES.
-
-### PROCEDURE DIVISION
-
-| Constant | Pattern | Purpose | Example Match |
-|----------|---------|---------|---------------|
-| `RE_PROC_SECTION` | `^ ([A-Z][A-Z0-9-]+)\s+SECTION\.\s*$` | Procedure section header | ` MAIN-LOGIC SECTION.` |
-| `RE_PROC_PARAGRAPH` | `^ ([A-Z][A-Z0-9-]+)\.\s*$` | Paragraph header | ` PROCESS-RECORD.` |
-| `RE_PERFORM` | `\bPERFORM\s+([A-Z][A-Z0-9-]+)(?:\s+THRU\s+([A-Z][A-Z0-9-]+))?` | PERFORM call | `PERFORM CALC-TAX THRU CALC-TAX-EXIT` |
-| `RE_PROC_USING` | `\bPROCEDURE\s+DIVISION\s+USING\s+([\s\S]*?)(?:\.\|$)` | USING parameters | `PROCEDURE DIVISION USING WK-PARAM` |
-| `RE_ENTRY` | `\bENTRY\s+"([^"]+)"(?:\s+USING\s+([\s\S]*?))?(?:\.\|$)` | ENTRY point | `ENTRY "SUBPROG" USING WK-DATA` |
-| `RE_MOVE` | `\bMOVE\s+((?:CORRESPONDING\|CORR)\s+)?([A-Z][A-Z0-9-]+)\s+TO\s+(.+)` | MOVE statement (supports CORR abbreviation and multi-target) | `MOVE WK-NAME TO OUT-NAME`, `MOVE CORR WK-IN TO WK-OUT` |
-
-The USING parameter list (`RE_PROC_USING`) is split on `\bRETURNING\b` before tokenization -- any RETURNING clause and everything after it is excluded from the parameter list (`.split(/\bRETURNING\b/i)[0]`).
-
-Note: `RE_PROC_SECTION` and `RE_PROC_PARAGRAPH` require exactly 7 spaces of leading indentation (COBOL area A starting at column 8). This is the standard COBOL paragraph indentation.
-
-### All-Division Patterns
-
-These patterns are checked regardless of current division:
-
-| Constant | Pattern | Purpose | Example Match |
-|----------|---------|---------|---------------|
-| `RE_CALL` | `\bCALL\s+"([^"]+)"` | External program call | `CALL "BGTABUP"` |
-| `RE_COPY_UNQUOTED` | `\bCOPY\s+([A-Z][A-Z0-9-]+)(?:\s\|\.)` | COPY (unquoted) | `COPY CPSESP.` |
-| `RE_COPY_QUOTED` | `\bCOPY\s+"([^"]+)"(?:\s\|\.)` | COPY (quoted) | `COPY "WORKGRID.CPY".` |
-
-### SORT/MERGE Support
-
-| Constant | Purpose |
-|----------|---------|
-| `SORT_CLAUSE_NOISE` | Set of SORT/MERGE clause keywords filtered from USING/GIVING file lists: `ON`, `ASCENDING`, `DESCENDING`, `KEY`, `WITH`, `DUPLICATES`, `IN`, `ORDER`, `COLLATING`, `SEQUENCE`, `IS`, `THROUGH`, `THRU`, `INPUT`, `OUTPUT`, `PROCEDURE` |
-
-SORT and MERGE statements are accumulated across multiple lines (like SELECT) until a period terminator is found, then parsed for USING/GIVING file lists and INPUT/OUTPUT PROCEDURE targets. The `flushSort()` helper encapsulates the flush-and-parse logic, mirroring the existing `flushSelect()` pattern. Both helpers are called at EOF to handle truncated files.
-
-### GO TO Multi-Target
-
-`RE_GOTO` captures all paragraph names in a `GO TO` statement, including the multi-target form `GO TO p1 p2 p3 DEPENDING ON x`. The captured group contains all target names (space-separated), which are split into individual targets. Each target produces a separate `gotos` entry.
-
-### PROGRAM-ID Detection
-
-PROGRAM-ID is detected regardless of the current division state. This handles sibling programs that appear after `END PROGRAM` and omit the `IDENTIFICATION DIVISION` header -- the extractor will still capture the PROGRAM-ID and push a new program boundary.
-
-### EXEC Block Patterns
-
-| Constant | Pattern | Purpose | Example Match |
-|----------|---------|---------|---------------|
-| `RE_EXEC_SQL_START` | `\bEXEC\s+SQL\b` | Start of EXEC SQL block | `EXEC SQL` |
-| `RE_EXEC_CICS_START` | `\bEXEC\s+CICS\b` | Start of EXEC CICS block | `EXEC CICS` |
-| `RE_END_EXEC` | `\bEND-EXEC\b` | End of EXEC block | `END-EXEC` |
-
-EXEC blocks accumulate all lines between `EXEC SQL/CICS` and `END-EXEC`, then delegate to `parseExecSqlBlock()` or `parseExecCicsBlock()` for detailed extraction.
-
-## Excluded Paragraph Names
-
-The following names are excluded from paragraph detection to avoid false positives from division/section headers:
-
-```
-DECLARATIVES, END, PROCEDURE, IDENTIFICATION,
-ENVIRONMENT, DATA, WORKING-STORAGE, LINKAGE,
-FILE, LOCAL-STORAGE, COMMUNICATION, REPORT,
-SCREEN, INPUT-OUTPUT, CONFIGURATION
-```
-
-Additionally, paragraph candidates containing `DIVISION` or `SECTION` as substrings are excluded.
-
-## MOVE Skip List (Figurative Constants)
-
-MOVE statements where the source is a figurative constant are skipped:
-
-```
-SPACES, ZEROS, ZEROES, LOW-VALUES, LOW-VALUE,
-HIGH-VALUES, HIGH-VALUE, QUOTES, QUOTE, ALL
-```
-
-## Source Files
-
-- `gitnexus/src/core/ingestion/cobol-preprocessor.ts` -- `preprocessCobolSource()`, `extractCobolSymbolsWithRegex()`, all regex constants
diff --git a/docs/guides/microservices-grpc.md b/docs/guides/microservices-grpc.md
deleted file mode 100644
index b09fd55e3..000000000
--- a/docs/guides/microservices-grpc.md
+++ /dev/null
@@ -1,300 +0,0 @@
-# Using GitNexus across gRPC microservices
-
-## When to use this guide
-
-This guide is for teams whose product lives in **several separate Git repositories** — one per service — and whose services talk to each other over **gRPC** (possibly alongside HTTP and message topics). GitNexus indexes each repo independently, then a _group_ stitches the per-repo indexes into a single cross-repo view that the `impact`, `query`, and `context` tools can traverse. If your services live in one monorepo, much of this still applies — set each service as a member of a group and use the `service` prefix to scope queries — but the walkthrough assumes the harder multi-repo case.
-
-## Mental model
-
-- Each repository has its own `.gitnexus/` index (a LadybugDB graph of symbols, relationships, processes). `gitnexus analyze` in each repo produces that index completely independently.
-- A **group** is a higher-level construct stored at `~/.gitnexus/groups//` that references the per-repo indexes by their registry name.
-- Sync-time extractors walk each member repo and emit **contracts** — provider or consumer records keyed by a canonical `contractId` (`grpc::auth.AuthService/Login`, `http::GET::/orders`, etc.).
-- The sync step matches providers and consumers that share a `contractId` and writes **cross-links** to `/contracts.json`. Those cross-links are what lets `impact({repo: "@", target: "X"})` hop from one repo into another.
-- Contracts come from three places: automatic contract extractors (`grpc-extractor`, `http-route-extractor`, `topic-extractor`), a manifest escape hatch (`config.links` in `group.yaml`), and — for same-name symbol matches where no contract is declared — the exact-match matching cascade in [`matching.ts`](../../gitnexus/src/core/group/matching.ts).
-- Each repo stays editable and re-indexable on its own. Re-run `gitnexus analyze` in a repo when it changes, then `gitnexus group sync ` to refresh `contracts.json`. `gitnexus group status` reports which members are stale.
-
-## Prerequisites
-
-- GitNexus installed and runnable as `gitnexus` or `npx gitnexus` (see the root [README.md](../../README.md)).
-- Each service repository checked out locally. No requirement that they share a parent directory — the group references them by registry name.
-- Write access to `~/.gitnexus/` (the default gitnexus home; see `getDefaultGitnexusDir` in [`storage.ts`](../../gitnexus/src/core/group/storage.ts)).
-
-## Step-by-step walkthrough
-
-The example uses three services — a TypeScript API gateway, a Go orders service, and a Python inventory service — with gRPC between them. The gateway is an `orders` consumer; the orders service is both an `orders` provider and an `inventory` consumer; the inventory service is an `inventory` provider.
-
-### 1. Index each repository
-
-Run `analyze` from inside each service repo (or pass the path). The CLI surface lives in [`gitnexus/src/cli/analyze.ts`](../../gitnexus/src/cli/analyze.ts) and is wired in [`gitnexus/src/cli/index.ts`](../../gitnexus/src/cli/index.ts).
-
-```bash
-cd ~/code/gateway && npx gitnexus analyze
-cd ~/code/orders && npx gitnexus analyze
-cd ~/code/inventory && npx gitnexus analyze
-```
-
-Useful flags:
-
-- `--force` — reindex even if up to date.
-- `--embeddings` — generate embedding vectors (needed only if you want semantic search; the exact-match cross-repo cascade does **not** need them).
-- `--name ` — register the repo under a specific alias when two repos share a basename (e.g. two `api/` folders).
-- `--skip-git` — index a checkout that isn't a git repo.
-
-Each run writes a `.gitnexus/` folder in the repo and registers the repo in `~/.gitnexus/registry.json`. Confirm with `npx gitnexus list`.
-
-### 2. Author `group.yaml`
-
-Create the group directory and edit the config. Either use the CLI scaffolder or write the file directly — both produce the same shape consumed by [`config-parser.ts`](../../gitnexus/src/core/group/config-parser.ts).
-
-```bash
-npx gitnexus group create payments-platform
-# or manually:
-mkdir -p ~/.gitnexus/groups/payments-platform
-$EDITOR ~/.gitnexus/groups/payments-platform/group.yaml
-```
-
-Minimal working `group.yaml`:
-
-```yaml
-version: 1
-name: payments-platform
-description: Gateway + orders + inventory (gRPC)
-
-repos:
- gateway: gateway
- orders: orders
- inventory: inventory
-
-# Only add explicit links when the automatic extractors miss something —
-# see "When automatic extraction isn't enough" below.
-links: []
-
-packages: {}
-
-detect:
- http: true
- grpc: true
- topics: true
- shared_libs: true
- embedding_fallback: false
-
-matching:
- bm25_threshold: 0.7
- embedding_threshold: 0.65
- max_candidates_per_step: 3
- # Exclude noisy paths from cross-link matching (contracts are still extracted)
- exclude_links_paths: [/ping, /health, /healthcheck]
- exclude_links_param_only_paths: true
-```
-
-Field notes (schema in [`types.ts`](../../gitnexus/src/core/group/types.ts)):
-
-- `version` — must be `1`. The parser rejects anything else.
-- `name` — required; used for the group directory name and all CLI / MCP calls.
-- `repos` — a mapping from **group path** (a logical name you choose; can be a hierarchy like `backend/orders`) to **registry name** (the name shown by `npx gitnexus list`). Both sides appear throughout the tooling: contract rows use the group path; `@/` routes tools to a single member.
-- `links` — optional manifest escape hatch, one entry per explicit cross-repo contract. Validated by the parser: `from` and `to` must be known repo paths, `type` must be one of `http | grpc | topic | lib | custom`, and `role` must be `provider | consumer`.
-- `detect` — toggles per extractor family. Defaults (set in `config-parser.ts`) turn `http`, `grpc`, `topics`, and `shared_libs` on; disable the ones you don't use to speed up sync.
-- `matching` — thresholds for the matching cascade. The exact match is always run; other strategies depend on indexer state. Two optional fields reduce false-positive cross-links in large groups:
- - `exclude_links_paths` — list of HTTP paths to exclude from cross-link matching (default `[]`). Contracts at these paths are still extracted and visible in the registry, but they don't produce cross-repo links. Useful for health-check endpoints (`/ping`, `/health`) that every service exposes. Trailing slashes are normalized.
- - `exclude_links_param_only_paths` — when `true`, exclude routes where every segment is `{param}` (e.g. `/{param}`, `/{param}/{param}`) from cross-link matching (default `false`). Mixed routes like `/users/{param}` are not affected.
-
-### 3. Sync the group
-
-```bash
-npx gitnexus group sync payments-platform --verbose
-```
-
-What this does (see [`sync.ts`](../../gitnexus/src/core/group/sync.ts)):
-
-1. Opens each member's per-repo LadybugDB.
-2. Runs the HTTP, gRPC, and topic extractors against the source files.
-3. Applies manifest `links` through [`manifest-extractor.ts`](../../gitnexus/src/core/group/extractors/manifest-extractor.ts).
-4. Runs the exact-match cascade, joining providers and consumers that share a normalized `contractId`.
-5. Writes `contracts.json` in the group directory.
-
-Flags:
-
-- `--exact-only` — stop after the exact cascade; skip BM25 and embedding fallback.
-- `--skip-embeddings` — run exact plus BM25 but not embedding-based matching.
-- `--allow-stale` — don't warn if a member's index is stale.
-- `--json` — machine-readable output.
-
-The same operation is available over MCP as `group_sync({ name: "payments-platform" })` — see [`tools.ts`](../../gitnexus/src/mcp/tools.ts).
-
-### 4. Inspect the registry
-
-Use `gitnexus group contracts` for the CLI view or read the `gitnexus://group//contracts` MCP resource for the same data.
-
-```bash
-npx gitnexus group contracts payments-platform --type grpc --json
-```
-
-A shortened response:
-
-```json
-{
- "contracts": [
- {
- "contractId": "grpc::orders.OrderService/PlaceOrder",
- "type": "grpc",
- "role": "provider",
- "repo": "orders",
- "symbolRef": { "filePath": "internal/grpc/order_server.go", "name": "RegisterOrderServiceServer" },
- "confidence": 0.8,
- "meta": { "service": "OrderService", "method": "PlaceOrder", "source": "go_register" }
- },
- {
- "contractId": "grpc::orders.OrderService/PlaceOrder",
- "type": "grpc",
- "role": "consumer",
- "repo": "gateway",
- "symbolRef": { "filePath": "src/clients/orders.ts", "name": "OrderServiceClient" },
- "confidence": 0.75,
- "meta": { "service": "OrderService", "source": "ts_generated_client" }
- }
- ],
- "crossLinks": [
- {
- "from": { "repo": "gateway", "symbolUid": "…", "symbolRef": { "filePath": "src/clients/orders.ts", "name": "OrderServiceClient" } },
- "to": { "repo": "orders", "symbolUid": "…", "symbolRef": { "filePath": "internal/grpc/order_server.go", "name": "RegisterOrderServiceServer" } },
- "type": "grpc",
- "contractId": "grpc::orders.OrderService/PlaceOrder",
- "matchType": "exact",
- "confidence": 1.0
- }
- ]
-}
-```
-
-Staleness of the underlying indexes shows up in `npx gitnexus group status payments-platform` or the `gitnexus://group//status` resource.
-
-### 5. Run cross-repo impact with `@` routing
-
-From any shell (you do **not** have to `cd` into a member repo), the normal `impact` / `query` / `context` tools accept `repo: "@"` to fan out across all members, or `repo: "@/"` to target one member. Routing is implemented in [`resolve-at-member.ts`](../../gitnexus/src/core/group/resolve-at-member.ts) and described in [`tools.ts`](../../gitnexus/src/mcp/tools.ts).
-
-Example MCP calls:
-
-```json
-{"tool": "impact", "arguments": {
- "repo": "@payments-platform/orders",
- "target": "PlaceOrder",
- "direction": "upstream",
- "crossDepth": 2
-}}
-```
-
-```json
-{"tool": "query", "arguments": {
- "repo": "@payments-platform",
- "query": "retry logic around PlaceOrder"
-}}
-```
-
-The CLI equivalents still exist for scripting:
-
-```bash
-npx gitnexus group impact payments-platform \
- --repo orders --target PlaceOrder --direction upstream --cross-depth 2
-```
-
-Phase 1 walks within the anchor member; Phase 2 hops across the Contract Bridge wherever a cross-link endpoint matches an impacted symbol. See [`cross-impact.ts`](../../gitnexus/src/core/group/cross-impact.ts) for the bridge query.
-
-## How gRPC extraction works
-
-`GrpcExtractor` ([`grpc-extractor.ts`](../../gitnexus/src/core/group/extractors/grpc-extractor.ts)) runs two passes per member repo:
-
-1. **Proto map.** Every `**/*.proto` file is parsed to enumerate `service Foo { rpc Bar(...) }` blocks and (transitively) resolve the package name. Each RPC method becomes a provider contract with `contractId = grpc::./` and `confidence = 0.85`. Parsing uses the vendored `tree-sitter-proto` grammar when available and falls back to a length-preserving manual parser (`extractServiceBlocks`) otherwise, so `.proto` extraction works on platforms where the grammar fails to build.
-2. **Source scan.** Every source file whose extension matches [`GRPC_SCAN_GLOB`](../../gitnexus/src/core/group/extractors/grpc-patterns/index.ts) is parsed by its language plugin:
-
-| Language | Provider signal | Consumer signal |
-|----------|-----------------|-----------------|
-| Go ([`go.ts`](../../gitnexus/src/core/group/extractors/grpc-patterns/go.ts)) | `pb.RegisterXxxServer(...)`, `pb.UnimplementedXxxServer` embedded in struct | `pb.NewXxxClient(conn)` |
-| Java ([`java.ts`](../../gitnexus/src/core/group/extractors/grpc-patterns/java.ts)) | `extends XxxServiceGrpc.XxxServiceImplBase` (with or without `@GrpcService`) | `XxxServiceGrpc.newBlockingStub(...)`, `newStub(...)` |
-| Python ([`python.ts`](../../gitnexus/src/core/group/extractors/grpc-patterns/python.ts)) | `add_XxxServicer_to_server(...)` (bare or `_pb2_grpc.` attribute form) | `XxxStub(channel)` (ignores `Mock`/`Test`/`Fake`/`Stub`) |
-| Node / TS ([`node.ts`](../../gitnexus/src/core/group/extractors/grpc-patterns/node.ts)) | NestJS `@GrpcMethod('Service','Method')` | `@GrpcClient` field typed `XxxServiceClient`, `client.getService('Service')`, `new XxxServiceClient(...)`, `new foo.bar.XxxService(...)` in files that call `loadPackageDefinition` |
-
-For each source-scan detection the extractor looks up the short service name in the proto map and picks:
-
-- `grpc::./` when a method is named and the service resolves against the proto map,
-- `grpc::./*` (wildcard) when only the service is known, or
-- `grpc::/*` when no `.proto` is available at all.
-
-Provider detections land at confidence 0.8 (with proto) or 0.65 (without); consumers at 0.75 or 0.55. NestJS `@GrpcMethod` is fixed at 0.8 because the decorator is self-describing.
-
-### Matching
-
-`matching.ts` lowercases the package/service segment before comparing contract ids, so bindings that capitalize names differently (`auth.AuthService` vs `auth.authservice`) still match. Method names are compared case-sensitively because gRPC's wire path is case-sensitive. Service-only wildcards (`grpc::pkg.Svc/*`) match any method on the same service during cross-linking.
-
-### Known limitations
-
-- **Ambiguous proto resolution.** If a short service name exists in more than one `.proto` file and the source-scan hit can't be narrowed down by shared directory segments (`resolveProtoConflict` refuses to guess), the extractor skips contract emission and logs a warning.
-- **Proto packages must be resolvable locally.** Transitive imports that point outside the repo produce an empty package segment, which means the contract id collapses to `grpc::/`. Cross-repo matches still work as long as both sides agree on the empty package.
-- **Rewrite rules are not implemented.** If the provider repo writes `grpc::orders.OrderService/PlaceOrder` and the consumer repo writes `grpc::orderspb.OrderService/PlaceOrder`, they won't cross-link automatically. Use `config.links` to declare the correspondence (see below).
-- **One sync = one snapshot.** Contracts are extracted against the indexed snapshot of each repo. Re-index first, then re-sync; the `status` command and resource surface staleness.
-
-## When automatic extraction isn't enough
-
-The escape hatch is the `links` list in `group.yaml`, handled by [`ManifestExtractor`](../../gitnexus/src/core/group/extractors/manifest-extractor.ts). Each entry is a **one-directional** provider/consumer declaration:
-
-```yaml
-version: 1
-name: payments-platform
-repos:
- gateway: gateway
- orders: orders
- inventory: inventory
-
-links:
- # Explicit gRPC method: use when naming mismatches stop the
- # automatic matcher from cross-linking.
- - from: gateway
- to: orders
- type: grpc
- contract: OrderService/PlaceOrder
- role: consumer
-
- # Service-level link when you don't want to enumerate methods.
- - from: orders
- to: inventory
- type: grpc
- contract: InventoryService
- role: consumer
-
- # Works for HTTP too — use `METHOD::/path` form for the exact
- # handler, or just `/path` for a method-agnostic wildcard.
- - from: gateway
- to: orders
- type: http
- contract: POST::/orders
- role: consumer
-```
-
-What the manifest extractor does (see [`manifest-extractor.ts`](../../gitnexus/src/core/group/extractors/manifest-extractor.ts)):
-
-1. Builds a canonical `contractId` with `buildContractId` — the same canonicalization used by the automatic extractors, so manifest links cross-match automatic contracts on the other side.
-2. Tries to resolve each side to a real graph symbol (the `Route` node for HTTP, a `Function|Method` / `Class|Interface` for gRPC, a `Package|Module` for `lib`).
-3. If resolution fails, falls back to a deterministic synthetic uid (`manifest::::`) so both sides still line up in cross-impact — name-only links still work when the symbol isn't in the graph.
-4. Emits both a provider and a consumer `StoredContract` (confidence `1.0`, `source: "manifest"`) and a `CrossLink` with `matchType: "manifest"`.
-
-Use `links` for exactly the cases the extractor can't infer: different package names across repos (see #701), hand-rolled transports, cases where the provider repo isn't checked out locally but you still want a record, or any contract whose provider and consumer simply don't share a surface the extractors know how to pattern-match.
-
-History: the manifest extractor used to be silently skipped by the sync pipeline; that was fixed in [#827](https://github.com/abhigyanpatwari/GitNexus/pull/827) (tracking issue #826). If you ever see `config.links` with zero cross-links in `contracts.json`, make sure you're on a build that includes that fix, then re-run `group sync`.
-
-## Troubleshooting
-
-1. **`contracts.json` is empty after a sync.** Either no member repo contained a recognizable gRPC pattern, or the extractors are disabled in `detect`. Confirm `detect.grpc: true` and re-run with `--verbose`.
-2. **A known provider/consumer pair doesn't cross-link.** Most common cause: the package segment differs. Check the raw contract ids with `gitnexus group contracts --unmatched` — if you see two same-method contracts with different package prefixes, add a manifest `links:` entry to bridge them (no automatic rewrite rules yet).
-3. **`matchType: "manifest"` is missing entirely.** The extractor needs `config.links` to be non-empty and the sync pipeline to actually call it — verify you're on a post-#827 build. Empty contract rows for manifest links usually mean `resolveSymbol` couldn't find a graph match; the synthetic uid still lets cross-impact work, it just won't carry a file path.
-4. **Ambiguous proto warnings.** Look for `[grpc-extractor] Ambiguous proto resolution` in the sync logs; that means a service name exists in multiple `.proto` files under the same repo and the path-distance heuristic couldn't pick a winner. Resolve by renaming the service or declaring the intended pairing in `config.links`.
-5. **Cross-impact says "stale".** Both sides need a fresh per-repo index _and_ a fresh group sync. Order matters: `gitnexus analyze` in each changed repo, then `gitnexus group sync `. Use `gitnexus group status ` to see which side is behind.
-
-## Related docs and references
-
-- [AGENTS.md](../../AGENTS.md) — authoritative list of MCP tools and resources, including group-mode routing and the `gitnexus://group/…` resources.
-- [ARCHITECTURE.md](../../ARCHITECTURE.md) — overall data flow and the call-resolution DAG that the per-repo indexer uses.
-- [`gitnexus/src/core/group/`](../../gitnexus/src/core/group/) — `service.ts`, `sync.ts`, `config-parser.ts`, `matching.ts`.
-- [`gitnexus/src/core/group/extractors/grpc-extractor.ts`](../../gitnexus/src/core/group/extractors/grpc-extractor.ts) and [`grpc-patterns/`](../../gitnexus/src/core/group/extractors/grpc-patterns/) — gRPC detection.
-- [`gitnexus/src/core/group/extractors/manifest-extractor.ts`](../../gitnexus/src/core/group/extractors/manifest-extractor.ts) — the `config.links` escape hatch.
-- [`gitnexus/src/mcp/tools.ts`](../../gitnexus/src/mcp/tools.ts) — MCP tool schemas (`group_list`, `group_sync`, plus `@` routing on `impact` / `query` / `context`).
-- [`gitnexus/src/cli/group.ts`](../../gitnexus/src/cli/group.ts) — CLI command definitions and flags.
-- Upstream issues: [#701](https://github.com/abhigyanpatwari/GitNexus/issues/701), [#826](https://github.com/abhigyanpatwari/GitNexus/issues/826), [#906](https://github.com/abhigyanpatwari/GitNexus/issues/906).
diff --git a/docs/guides/microservices-thrift.md b/docs/guides/microservices-thrift.md
deleted file mode 100644
index 7b37680aa..000000000
--- a/docs/guides/microservices-thrift.md
+++ /dev/null
@@ -1,185 +0,0 @@
-# Using GitNexus across Apache Thrift microservices
-
-## When to use this guide
-
-Use this guide when several repositories communicate through Apache Thrift and you want GitNexus to trace impact across provider and consumer boundaries. The walkthrough assumes each service is indexed on its own, then joined through a GitNexus group.
-
-This is not a framework integration guide. GitNexus reads portable Thrift IDL and common Java generated-code shapes. Framework-specific wiring, service discovery, deployment metadata, and private annotations belong outside the open-source core.
-
-## Mental model
-
-- `.thrift` files define the canonical service contract. A method in an IDL service becomes a stable contract id in the form `thrift::./`.
-- Service wildcard ids in the form `thrift::./*` are supported as manifest and matching fallback forms when a service-level link is needed.
-- Java generated-code usage points GitNexus toward implementation and call sites. Providers commonly implement generated `Service.Iface`; consumers commonly hold or construct generated service interfaces or clients.
-- Group sync matches provider and consumer contracts with the same id, then cross-repo impact can hop through those links.
-- Framework-specific wiring should be modeled by extractor plugins, manifest links, or downstream integrations rather than hard-coded into core Thrift support.
-
-## Fictional IDL
-
-```thrift
-namespace java billing.v1
-
-struct PlaceOrderRequest {
- 1: string orderId
- 2: double amount
-}
-
-struct PlaceOrderResponse {
- 1: bool accepted
-}
-
-struct GetOrderRequest {
- 1: string orderId
-}
-
-struct GetOrderResponse {
- 1: string orderId
- 2: string status
-}
-
-service OrderService {
- PlaceOrderResponse PlaceOrder(1: PlaceOrderRequest request)
- GetOrderResponse GetOrder(1: GetOrderRequest request)
-}
-```
-
-The service methods above produce canonical ids:
-
-- `thrift::billing.v1.OrderService/PlaceOrder`
-- `thrift::billing.v1.OrderService/GetOrder`
-- `thrift::billing.v1.OrderService/*` as a service-level manifest or matching fallback form
-
-## Java provider example
-
-Generated Java code usually exposes an `Iface` interface for the service. A provider implementation can be detected when it implements that generated interface.
-
-```java
-package example.billing;
-
-import billing.v1.GetOrderRequest;
-import billing.v1.GetOrderResponse;
-import billing.v1.OrderService;
-import billing.v1.PlaceOrderRequest;
-import billing.v1.PlaceOrderResponse;
-
-public final class OrderServiceHandler implements OrderService.Iface {
- @Override
- public PlaceOrderResponse PlaceOrder(PlaceOrderRequest request) {
- return new PlaceOrderResponse(true);
- }
-
- @Override
- public GetOrderResponse GetOrder(GetOrderRequest request) {
- return new GetOrderResponse(request.getOrderId(), "CREATED");
- }
-}
-```
-
-With the IDL available, GitNexus can connect the implementation to `thrift::billing.v1.OrderService/PlaceOrder` and `thrift::billing.v1.OrderService/GetOrder`.
-
-## Java consumer examples
-
-Consumers are strongest when Java usage can be tied back to the IDL namespace and service.
-
-```java
-package example.checkout;
-
-import billing.v1.OrderService;
-import billing.v1.PlaceOrderRequest;
-
-public final class CheckoutWorkflow {
- private final OrderService.Iface orders;
-
- public CheckoutWorkflow(OrderService.Iface orders) {
- this.orders = orders;
- }
-
- public void submit(String orderId) throws Exception {
- orders.PlaceOrder(new PlaceOrderRequest(orderId, 42.0));
- }
-}
-```
-
-Some generated-code styles use the generated service type directly while keeping enough IDL context through imports and method calls.
-
-```java
-package example.reporting;
-
-import billing.v1.GetOrderRequest;
-import billing.v1.OrderService;
-
-public final class OrderLookup {
- private final OrderService.Client client;
-
- public OrderLookup(OrderService.Client client) {
- this.client = client;
- }
-
- public String status(String orderId) throws Exception {
- return client.GetOrder(new GetOrderRequest(orderId)).getStatus();
- }
-}
-```
-
-When IDL context is missing, GitNexus may still emit a weaker consumer signal for generated `Iface` or `Client` shapes, but confidence is lower.
-
-## Group configuration
-
-New group configs enable Thrift contract detection by default. Keep `detect.thrift: true`
-when a group should scan for Thrift contracts, or set it to `false` to skip Thrift
-extraction for that group.
-
-```yaml
-version: 1
-name: billing-platform
-description: Fictional services connected by Apache Thrift
-
-repos:
- checkout: checkout-service
- billing: billing-service
-
-links: []
-
-detect:
- http: true
- grpc: false
- thrift: true
- topics: false
- shared_libs: true
-```
-
-To disable Thrift extraction explicitly:
-
-```yaml
-detect:
- thrift: false
-```
-
-After indexing each member repository, run group sync to extract contracts and write cross-repo links:
-
-```bash
-npx gitnexus group sync billing-platform
-```
-
-## Manifest escape hatch
-
-Use manifest links when automatic extraction cannot see a provider or consumer, or when generated code is wrapped behind an abstraction. Write the contract without the `thrift::` prefix; GitNexus canonicalizes it to the full Thrift contract id.
-
-```yaml
-links:
- - from: checkout
- to: billing
- type: thrift
- contract: billing.v1.OrderService/PlaceOrder
- role: consumer
-```
-
-GitNexus canonicalizes that manifest entry to `thrift::billing.v1.OrderService/PlaceOrder` and uses it to connect the two repositories.
-
-## Known limitations
-
-- Java detection currently targets v1 generated-code patterns.
-- Maven and POM dependency coordinates are not used for inference.
-- Framework-specific annotations and service discovery metadata are ignored by open-source Thrift extraction.
-- Ambiguous same-name services are skipped instead of guessed.
-- Java consumers without IDL context are lower confidence and limited to generated `Iface` and `Client` shapes.
diff --git a/docs/plans/2026-03-26-feat-cobol-full-language-coverage-plan.md b/docs/plans/2026-03-26-feat-cobol-full-language-coverage-plan.md
deleted file mode 100644
index b1a2e880c..000000000
--- a/docs/plans/2026-03-26-feat-cobol-full-language-coverage-plan.md
+++ /dev/null
@@ -1,326 +0,0 @@
----
-title: "feat: Complete COBOL language feature coverage for maximum knowledge graph value"
-type: feat
-status: active
-date: 2026-03-26
-origin: Feature audit from v3-integration-architect agent (session 8642401e)
----
-
-## Enhancement Summary
-
-**Deepened on:** 2026-03-26
-**Research agents used:** COBOL expert (Phase 1+2), graph value analyst, codebase explorer
-**Sections enhanced:** Phase 1 (5 features), Phase 2 (4 features), graph value ranking
-
-### Key Improvements from Research
-1. **CALL USING** is the #1 highest-value edge type (9.2/10) — fixes ~40% of missing caller references
-2. **EXEC DLI** requires dual-interface support (EXEC DLI + CBLTDLI CALL) for full IMS coverage
-3. **DECLARATIVES** is lowest-risk Phase 2 item — existing section/paragraph detection already captures structure
-4. **SET TO TRUE** accounts for 80-90% of all SET statements — prioritize this form
-5. **INSPECT** needs multi-line accumulator (like SORT) — can span 5+ continuation lines
-6. **Graph value ranking**: cobol-call-using (9.2) > cobol-error-handler (9.0) > dli-gu (8.2) > cobol-string (6.2)
-
-### New Edge Cases Discovered
-- CALL USING supports mixed modes: `USING BY REFERENCE WS-A BY CONTENT WS-B BY VALUE WS-C`
-- CALL USING `ADDRESS OF` and `OMITTED` must be filtered from parameter lists
-- EXEC DLI can have multiple SEGMENT levels in hierarchical retrieval (use matchAll)
-- DECLARATIVES can have multiple USE sections (one per file + catch-all for INPUT/OUTPUT/I-O/EXTEND)
-- INSPECT TALLYING can have multiple counters in a single statement
-- STRING/UNSTRING can span multiple lines (need accumulator pattern)
-
----
-
-# Complete COBOL Language Feature Coverage
-
-## Overview
-
-Implement the remaining 25 unhandled COBOL language features and fix 10 partial features to achieve ~95% coverage (up from 71.9%). The goal is to build the richest possible knowledge graph from COBOL codebases, enabling a future `modernize` MCP command (out of scope for this plan) that would use the graph to assist with COBOL-to-modern-language migration.
-
-## Problem Statement
-
-The COBOL processor currently handles 54 of 89 applicable language features (71.9%). The 25 unhandled features represent real data loss in the knowledge graph:
-- **Cross-program data flow** is invisible (CALL ... USING parameters not extracted)
-- **IMS/DB programs** produce empty graphs (EXEC DLI not recognized)
-- **String transformation logic** is invisible (STRING/UNSTRING/INSPECT not tracked)
-- **SQL copybook dependencies** are missing (EXEC SQL INCLUDE not mapped)
-- **Error handling flows** are lost (DECLARATIVES/USE AFTER not captured)
-
-## Proposed Solution
-
-Implement features in 4 phases, ordered by graph value density (edges created per LOC of implementation). Each phase is independently shippable and testable.
-
-## Technical Approach
-
-### Phase 1: High-Value Data Flow Edges (~150 LOC, ~8 new edge types)
-
-The highest-ROI features: they create new ACCESSES and IMPORTS edges that directly improve impact analysis.
-
-**Critical research finding**: Multi-line statement accumulation is the dominant challenge. CALL USING, STRING/UNSTRING, and multi-line data item clauses all span multiple lines in production COBOL. The free-format path processes each line independently — these features need statement accumulators (like SORT/SELECT) or the free-format path needs multi-line awareness. Estimated LOC increased from 110 to 150 to account for accumulator infrastructure.
-
-#### 1.1 EXEC SQL INCLUDE -> IMPORTS edges
-- **File:** `cobol-preprocessor.ts` (parseExecSqlBlock)
-- **What:** Detect `INCLUDE` as the operation, extract member name, emit as a `copies[]` entry
-- **Graph:** IMPORTS edge from File to included copybook/SQLCA with reason `sql-include`
-- **Tests:** Unit test for `EXEC SQL INCLUDE SQLCA END-EXEC` and `EXEC SQL INCLUDE CUSTCOPY END-EXEC`
-
-**Research insights (EXEC SQL INCLUDE):**
-- DB2 member names can contain underscores: `EXEC SQL INCLUDE CUST_TBL_DCL END-EXEC` — regex must use `[A-Z][A-Z0-9_-]+`
-- Quoted literal form: `EXEC SQL INCLUDE 'DBRMLIB.MEMBER' END-EXEC` (z/OS PDS qualified name)
-- SQLCA/SQLDA are DB2 builtins — won't resolve to repo files. Emit unresolved IMPORTS edge (still valuable)
-- No REPLACING support on EXEC SQL INCLUDE (unlike COPY)
-- Add `INCLUDE` to `OP_MAP` in `parseExecSqlBlock`; extract member via `RE_SQL_INCLUDE = /^INCLUDE\s+(?:'([^']+)'|"([^"]+)"|([A-Z][A-Z0-9_-]+))/i`
-
-#### 1.2 CALL ... USING parameter extraction -> ACCESSES edges (Graph value: 9.2/10)
-- **File:** `cobol-preprocessor.ts` (processLogicalLine CALL section)
-- **What:** After capturing CALL target, scan for USING clause. Extract parameter names (reuse USING_KEYWORDS filter). Store as `calls[].parameters: string[]`
-- **Interface:** Add `parameters?: string[]` to calls array type in CobolRegexResults
-- **File:** `cobol-processor.ts` (CALL edge block)
-- **Graph:** For each USING parameter, create ACCESSES edge from caller to data item Property node with reason `cobol-call-using`
-- **Tests:** `CALL 'AUDITLOG' USING CUST-ID WS-AMOUNT` -> 2 ACCESSES edges
-
-**Research insights (CALL USING forms):**
-- Mixed modes: `CALL 'PGM' USING BY REFERENCE WS-A BY CONTENT WS-B BY VALUE WS-C`
-- Pointer passing: `CALL 'PGM' USING ADDRESS OF WS-A`
-- Placeholder: `CALL 'PGM' USING OMITTED WS-B`
-- Filter keywords: add `ADDRESS`, `OMITTED`, `LENGTH` to USING_KEYWORDS (already has BY/VALUE/REFERENCE/CONTENT)
-- **Impact tool enhancement:** CALL-USING edges enable BFS traversal through parameter data flow — single most impactful edge type for COBOL impact analysis
-
-#### 1.3 STRING/UNSTRING data flow -> ACCESSES edges
-- **File:** `cobol-preprocessor.ts` (new section in extractProcedure)
-- **What:** Accumulate multi-line STRING/UNSTRING until period or END-STRING/END-UNSTRING. Extract sources and INTO targets.
-- **Interface:** Add `strings: Array<{ sources: string[]; target: string; type: 'string' | 'unstring'; line: number; caller: string | null }>` to CobolRegexResults
-- **Graph:** read-ACCESSES on sources, write-ACCESSES on INTO target with reason `cobol-string-read` / `cobol-string-write`
-- **Tests:** 2 unit tests + integration test assertions
-
-**Research insights (STRING/UNSTRING):**
-- **Needs statement accumulator** — STRING/UNSTRING always span multiple lines in production
-- Terminate accumulation at: period, END-STRING/END-UNSTRING, or start of next COBOL verb
-- STRING sources: identifiers before each `DELIMITED BY`. Filter: STRING, DELIMITED, BY, SIZE, ALL, INTO, WITH, POINTER, ON, OVERFLOW, NOT, END-STRING
-- UNSTRING: source is first identifier after UNSTRING; INTO targets are identifiers after INTO. Filter: DELIMITER, IN, COUNT, TALLYING, OR
-- WITH POINTER field is both read AND written (starting position updated)
-- TALLYING IN / COUNT IN fields are write targets
-- Literal sources (`'text'`) must be filtered — quote-aware tokenization needed
-- **Edge case**: STRING terminated by next verb, not period — existing fixture has `STRING ... DISPLAY` without period between them
-
-#### 1.4 OCCURS DEPENDING ON -> ACCESSES edge
-- **File:** `cobol-preprocessor.ts` (parseDataItemClauses)
-- **What:** Extend OCCURS regex to capture DEPENDING ON field, KEY fields, and INDEXED BY names
-- **Interface:** Add `dependingOn?: string`, `occursMax?: number`, `occursKeys?: Array<{direction: string; fields: string[]}>`, `indexedBy?: string[]` to data items
-- **Graph:** ACCESSES edge from table item to controlling field with reason `cobol-depends-on`
-- **Tests:** `05 WS-TABLE OCCURS 100 DEPENDING ON WS-COUNT` -> edge
-
-**Research insights (OCCURS):**
-- IBM allows `OCCURS 0 TO n DEPENDING ON` (zero minimum) and `OCCURS UNBOUNDED DEPENDING ON` (V6.4)
-- Subscripted controlling fields: `DEPENDING ON WS-COUNT(WS-IDX)` — strip subscripts before storing
-- **Pre-existing gap**: Multi-line data item clauses without continuation indicator are NOT captured. `05 WS-TABLE\n OCCURS 100\n DEPENDING ON WS-COUNT.` — the current RE_DATA_ITEM only gets the first line, `rest` is empty. Fixing properly requires a data item accumulator (like SELECT). **Defer full fix to Phase 3; implement same-line capture now.**
-- KEY IS fields: `ASCENDING KEY IS WS-KEY-1 WS-KEY-2` — capture for SEARCH ALL resolution
-- INDEXED BY: `INDEXED BY IDX-1 IDX-2` — capture for SET/SEARCH context
-
-#### 1.5 VALUE clause for standard data items
-- **File:** `cobol-preprocessor.ts` (parseDataItemClauses)
-- **What:** Extract VALUE using a pragmatic function that handles quoted strings, numerics, figurative constants, hex/national literals
-- **Interface:** Already exists as `values?: string[]` on data items (currently only populated for 88-level)
-- **Graph:** Stored in Property node description (no new edges)
-- **Tests:** `01 WS-STATUS PIC X VALUE 'A'` -> values: ['A']
-
-**Research insights (VALUE forms):**
-- Hex literals: `VALUE X'F1F2F3F4'`, National: `VALUE N'text'`, DBCS: `VALUE G'text'`
-- Figurative constants: SPACES, ZEROS, ZEROES, LOW-VALUES, HIGH-VALUES, QUOTES, NULL, NULLS
-- ALL literal: `VALUE ALL '*'`
-- Numeric with sign/decimal: `VALUE -123.45`, `VALUE +1`
-- `VALUE IS` optional — both `VALUE 'A'` and `VALUE IS 'A'` valid
-- **Decimal vs period ambiguity**: `VALUE 100.` — is `.` decimal or terminator? `parseDataItemClauses` already strips trailing period, so this is handled
-- IBM V6.4: floating-point `VALUE 1.0E5` — extend numeric regex if needed
-- Implementation: use a pragmatic `extractValue(rest)` function, not a single complex regex
-
-### Phase 2: EXEC DLI + DECLARATIVES (~90 LOC, ~4 new edge types)
-
-IMS/DB support and error handling flows.
-
-#### 2.1 EXEC DLI (IMS/DB) -> ACCESSES edges (Graph value: 8.2/10)
-- **File:** `cobol-preprocessor.ts` (processLogicalLine — add RE_EXEC_DLI_START check alongside SQL/CICS)
-- **What:** Accumulate EXEC DLI blocks like EXEC SQL. Parse DLI verbs (GU, GN, GNP, GHU, GHN, GHNP, ISRT, DLET, REPL, CHKP, SCHD, TERM). Extract segment name, PCB number, INTO/FROM areas, WHERE fields, PSB name.
-- **Interface:** Add `execDliBlocks: Array<{ line: number; verb: string; pcbNumber?: number; segmentName?: string; intoField?: string; fromField?: string; whereField?: string; psbName?: string }>` to CobolRegexResults
-- **Graph:** CodeElement node + ACCESSES edge to `:` Record node with reason `dli-{verb}`; ACCESSES edges to INTO/FROM data areas; PSB ACCESSES for SCHD
-- **Tests:** `EXEC DLI GU USING PCB(1) SEGMENT(CUSTOMER) INTO(WS-CUST) END-EXEC`
-
-**Research insights (dual IMS interface):**
-- **EXEC DLI**: Embedded command interface for CICS-DL/I programs only
-- **CBLTDLI CALL**: Batch interface via `CALL 'CBLTDLI' USING function-code PCB io-area SSA1..SSA15`
-- CBLTDLI is already captured as a CALL to 'CBLTDLI' — enrich with USING parameter semantics later
-- Multiple SEGMENT levels in hierarchical retrieval — use `matchAll` on segment regex
-- DLI verbs: GU (most common), GN, GNP, GHU, GHN, GHNP, ISRT, REPL, DLET, CHKP, SCHD, TERM, ROLL, ROLB
-- **Edge case**: DLET/REPL have no SEGMENT clause (operate on current position)
-- **Recommended order**: Implement AFTER DECLARATIVES and SET (lower risk, higher frequency)
-
-#### 2.2 DECLARATIVES / USE AFTER STANDARD EXCEPTION (Graph value: 9.0/10)
-- **File:** `cobol-preprocessor.ts` (processLogicalLine — detect DECLARATIVES keyword, track USE AFTER blocks)
-- **What:** When `DECLARATIVES.` is encountered, switch to declaratives mode. Extract USE statements binding sections to files/modes.
-- **Interface:** Add `declaratives: Array<{ sectionName: string; useType: 'error' | 'debug' | 'label' | 'reporting'; target: string; line: number }>` to CobolRegexResults
-- **Graph:** ACCESSES edge from declarative Namespace to file Record with reason `cobol-declarative-error-handler`
-- **Tests:** Unit test with DECLARATIVES section, integration test for error flow
-
-**Research insights (DECLARATIVES syntax):**
-- `USE AFTER STANDARD {EXCEPTION|ERROR} ON {file-name|INPUT|OUTPUT|I-O|EXTEND}`
-- EXCEPTION and ERROR are synonymous; STANDARD is optional in IBM dialects
-- Multiple USE sections allowed (one per file + catch-all for I/O modes)
-- `END DECLARATIVES.` must NOT reset PROCEDURE DIVISION state
-- `DECLARATIVES` is already in EXCLUDED_PARA_NAMES — no false paragraph risk
-- Existing section/paragraph detection already captures structural elements — just need USE binding
-- **Lowest risk Phase 2 item** — implement first
-
-#### 2.3 SET statement -> ACCESSES edges
-- **File:** `cobol-preprocessor.ts` (extractProcedure — new RE_SET regex)
-- **Interface:** Add `sets: Array<{ targets: string[]; form: 'to-true'|'to-value'|'up-by'|'down-by'|'address-of'|'to-null'|'to-entry'; value?: string; entryTarget?: string; entryIsLiteral?: boolean; line: number; caller: string | null }>` to CobolRegexResults
-- **Graph:** ACCESSES write edge with reason `cobol-set-condition` (TO TRUE), `cobol-set-index` (TO/UP/DOWN), `cobol-set-address` (ADDRESS OF). SET ENTRY with literal -> CALLS edge.
-- **Tests:** `SET WS-EOF TO TRUE`, `SET IDX-1 TO 5`, `SET IDX-1 UP BY 1`
-
-**Research insights (SET forms by frequency):**
-- `SET condition TO TRUE` — 80-90% of all SET usage. Multiple targets: `SET COND-A COND-B TO TRUE`
-- `SET index TO/UP BY/DOWN BY` — ~8%. Multiple indices: `SET IDX-1 IDX-2 UP BY 1`
-- `SET pointer TO ADDRESS OF data-item` / `SET ADDRESS OF data-item TO pointer` — ~2%
-- `SET proc-ptr TO ENTRY "PROGNAME"` — rare but creates CALLS edge (like dynamic CALL)
-- Filter OF/IN qualifiers: `SET COND-A OF WS-RECORD TO TRUE` (strip OF WS-RECORD)
-- **Prioritize**: SET TO TRUE alone covers 80-90% — implement this form first
-
-#### 2.4 INSPECT -> ACCESSES edges
-- **File:** `cobol-preprocessor.ts` (extractProcedure — new `inspectAccum` accumulator like SORT)
-- **What:** Accumulate multi-line INSPECT until period. Extract inspected field + tally counters.
-- **Interface:** Add `inspects: Array<{ inspectedField: string; counters: string[]; form: 'tallying'|'replacing'|'converting'|'tallying-replacing'; line: number; caller: string | null }>` to CobolRegexResults
-- **Graph:** ACCESSES read on inspected field always; write if REPLACING/CONVERTING. Write edges for tally counters. Reason: `cobol-inspect-read`/`cobol-inspect-write`/`cobol-inspect-tally`
-- **Tests:** `INSPECT WS-FIELD TALLYING WS-COUNT FOR ALL 'A'` -> read on WS-FIELD, write on WS-COUNT
-
-**Research insights (INSPECT forms by frequency):**
-- REPLACING (~60%): `INSPECT WS-STR REPLACING ALL 'A' BY 'B'`
-- TALLYING (~25%): `INSPECT WS-STR TALLYING WS-CNT FOR ALL 'A'` — multiple counters possible
-- CONVERTING (~10%): `INSPECT WS-STR CONVERTING 'abc' TO 'ABC'`
-- Combined (~5%): TALLYING + REPLACING in single statement
-- **Needs multi-line accumulator** — INSPECT frequently spans 3-5 lines in production
-- Extract tally counters with `([A-Z][A-Z0-9-]+)\s+FOR\b` matchAll pattern
-- Filter figurative constants (SPACES, ZEROS) using existing MOVE_SKIP set
-
-### Phase 3: Completeness Fixes (~60 LOC)
-
-Fix the 10 partial features and small gaps.
-
-#### 3.1 CALL ... RETURNING extraction
-- Extend RE_CALL processing to capture RETURNING target after the USING clause
-- Store as `calls[].returning?: string`
-- Graph: ACCESSES write edge with reason `cobol-call-returning`
-
-#### 3.2 SELECT OPTIONAL flag preservation
-- Store `isOptional: boolean` in FileDeclaration interface
-- Include in Record node description
-
-#### 3.3 ALTERNATE RECORD KEY extraction
-- Add regex in parseSelectStatement: `/\bALTERNATE\s+RECORD\s+KEY\s+(?:IS\s+)?([A-Z][A-Z0-9-]+)/i`
-- Store as `alternateKeys?: string[]`
-
-#### 3.4 COMMON attribute on nested programs
-- Extend RE_PROGRAM_ID: `/\bPROGRAM-ID\.\s*([A-Z][A-Z0-9-]+)(?:\s+IS\s+COMMON)?/i`
-- Store `isCommon: boolean` on Module node
-- Affects cross-program CALL resolution scope
-
-#### 3.5 IS EXTERNAL / IS GLOBAL as first-class properties
-- Change from usage string hack to proper boolean fields on data items
-- Add `isExternal?: boolean`, `isGlobal?: boolean` to data item interface
-
-#### 3.6 AUTHOR / DATE-WRITTEN mapped to Module node
-- Already extracted as programMetadata — map to Module node properties
-- `graph.addNode({ ..., properties: { ..., author, dateWritten } })`
-
-#### 3.7 REPLACE statement
-- Track REPLACE / REPLACE OFF state in preprocessor
-- Apply text substitutions during preprocessing (before regex extraction)
-- Complex: requires careful scoping rules
-
-### Phase 4: Niche Features (~30 LOC)
-
-Low-priority but nice for completeness.
-
-#### 4.1 INITIALIZE statement -> write ACCESSES
-- `/\bINITIALIZE\s+([A-Z][A-Z0-9-]+)/i`
-- ACCESSES write edge with reason `cobol-initialize`
-
-#### 4.2 Remaining IDENTIFICATION DIVISION paragraphs
-- DATE-COMPILED, INSTALLATION, SECURITY, REMARKS
-- Map to Module node description properties
-
-#### 4.3 EXEC SQL INCLUDE -> IMPORTS edge (expansion)
-- For EXEC SQL INCLUDE inside EXEC blocks that reference copybooks containing SQL
-- Create IMPORTS edge similar to COPY
-
-## Acceptance Criteria
-
-### Functional Requirements
-
-- [ ] Phase 1: All 5 features implemented with unit + integration tests
-- [ ] Phase 2: All 4 features implemented with unit + integration tests
-- [ ] Phase 3: All 7 partial features fixed
-- [ ] Phase 4: At least 2 of 3 niche features implemented
-- [ ] All existing 145 tests continue to pass
-- [ ] TypeScript compiles cleanly
-
-### Non-Functional Requirements
-
-- [ ] No performance regression: CardDemo benchmark stays under 8s
-- [ ] No file exceeds 1500 LOC (preprocessor currently 1326)
-- [ ] ACAS benchmark shows increased node/edge counts (more data extracted)
-- [ ] CardDemo benchmark shows increased edge counts (CALL USING, STRING, etc.)
-
-### Quality Gates
-
-- [ ] Each phase has its own commit
-- [ ] Integration test assertions updated with exact counts per phase
-- [ ] Benchmark run after each phase to track graph growth
-
-## Dependencies & Risks
-
-### Dependencies
-- None. All changes are additive to existing COBOL processor code.
-- No LanguageProvider changes needed.
-- No graph schema changes needed (all new constructs map to existing node labels + edge types).
-
-### Risks
-- **preprocessor.ts size**: Currently 1326 LOC. Phase 1+2 adds ~200 LOC -> 1526 LOC. May need to extract helpers into a separate `cobol-data-flow.ts` module if it exceeds 1500.
-- **REPLACE statement** (Phase 3.7) is the most complex feature — requires tracking text substitution state across logical lines. Consider deferring to a separate PR if it takes >100 LOC.
-- **EXEC DLI** (Phase 2.1) is only testable against IMS codebases. Need fixture data or synthetic test cases.
-
-## Graph Value Ranking by MCP Tool Impact
-
-Research agent analyzed all 5 MCP tools (query, context, impact, detect_changes, rename) against planned edge types:
-
-| Edge Type | QUERY | CONTEXT | IMPACT | DETECT | RENAME | **Overall** |
-|-----------|-------|---------|--------|--------|--------|-------------|
-| `cobol-call-using` | 4/5 | 5/5 | 5/5 | 4/5 | 4/5 | **9.2/10** |
-| `cobol-error-handler` | 5/5 | 4/5 | 5/5 | 5/5 | 2/5 | **9.0/10** |
-| `dli-*` (IMS verbs) | 4/5 | 4/5 | 5/5 | 4/5 | 2/5 | **8.2/10** |
-| `cobol-string-*` | 4/5 | 3/5 | 3/5 | 3/5 | 2/5 | **6.2/10** |
-
-**Key finding**: `cobol-call-using` alone would fix ~40% of missing caller references in COBOL graphs.
-
-## Future Considerations
-
-This plan provides the graph data foundation for a future `modernize` MCP command (out of scope) that would:
-- Use CALL USING edges to map data contracts between programs
-- Use STRING/UNSTRING edges to identify data transformation logic
-- Use EXEC SQL/DLI edges to map database access patterns
-- Use DECLARATIVES to understand error handling architecture
-- Use the complete knowledge graph to generate migration plans
-
-**MCP tool enhancements needed** (after this plan ships):
-- Add `cobol-call-using`, `cobol-error-handler`, `dli-*` to IMPACT tool's default `relationTypes` for COBOL repos
-- Add confidence floors for new edge types in `IMPACT_RELATION_CONFIDENCE`
-- Register new edge types in `VALID_RELATION_TYPES` set (`local-backend.ts:52`)
-
-## Sources & References
-
-### Internal References
-- Feature audit: session 8642401e (COBOL expert agent, 123 features audited)
-- Prior plans: `docs/plans/2026-03-25-feat-cobol-100-percent-feature-coverage-plan.md`
-- Architecture: `docs/code-indexing/cobol/` (7 documentation files)
-
-### External References
-- COBOL features reference: mainframestechhelp.com/tutorials/cobol/features.htm
-- COBOL-85 standard: ISO/IEC 1989:1985
-- IBM Enterprise COBOL reference
diff --git a/docs/superpowers/plans/2026-04-02-pr626-high-fixes.md b/docs/superpowers/plans/2026-04-02-pr626-high-fixes.md
deleted file mode 100644
index 0c9204e8c..000000000
--- a/docs/superpowers/plans/2026-04-02-pr626-high-fixes.md
+++ /dev/null
@@ -1,725 +0,0 @@
-# PR #626 HIGH-Priority Fixes Implementation Plan
-
-> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
-
-**Goal:** Fix 4 HIGH-priority issues from PR #626 code review before merge.
-
-**Architecture:** Minimal targeted fixes — each task is independent. TDD: tests first, then implementation. No refactoring beyond what's needed.
-
-**Tech Stack:** TypeScript, Vitest, Node.js fs/path APIs
-
-**Spec:** `docs/superpowers/specs/2026-04-02-pr626-high-fixes-design.md`
-
-**Paths:** All file paths are relative to the monorepo root (`GitNexus/`). Git commands run from the root. The `gitnexus/` prefix is a package subdirectory, not a separate repo.
-
----
-
-### Task 1: Path Traversal — Validate Group Name
-
-**Files:**
-- Modify: `gitnexus/src/core/group/storage.ts:17-19` (getGroupDir) and `:63-68` (createGroupDir)
-- Test: `gitnexus/test/unit/group/storage.test.ts`
-
-- [ ] **Step 1: Write failing tests for validateGroupName**
-
-In `gitnexus/test/unit/group/storage.test.ts`, add `createGroupDir` and `validateGroupName` to the existing import from `'../../../src/core/group/storage.js'` (line 6-11). Then add these describe blocks at the end of the outer `describe('Group storage', ...)`:
-
-```typescript
- describe('validateGroupName', () => {
- it('test_validateGroupName_traversal_path_throws', () => {
- expect(() => validateGroupName('../../evil')).toThrow(/Invalid group name/);
- });
-
- it('test_validateGroupName_slash_in_name_throws', () => {
- expect(() => validateGroupName('foo/bar')).toThrow(/Invalid group name/);
- });
-
- it('test_validateGroupName_empty_string_throws', () => {
- expect(() => validateGroupName('')).toThrow(/Invalid group name/);
- });
-
- it('test_validateGroupName_starts_with_dash_throws', () => {
- expect(() => validateGroupName('-leading-dash')).toThrow(/Invalid group name/);
- });
-
- it('test_validateGroupName_starts_with_underscore_throws', () => {
- expect(() => validateGroupName('_leading')).toThrow(/Invalid group name/);
- });
-
- it('test_validateGroupName_dots_throws', () => {
- expect(() => validateGroupName('com.example')).toThrow(/Invalid group name/);
- });
-
- it('test_validateGroupName_valid_alphanumeric_passes', () => {
- expect(() => validateGroupName('my-group_01')).not.toThrow();
- });
-
- it('test_validateGroupName_single_char_passes', () => {
- expect(() => validateGroupName('A')).not.toThrow();
- });
-
- it('test_validateGroupName_all_digits_passes', () => {
- expect(() => validateGroupName('123')).not.toThrow();
- });
- });
-
- describe('getGroupDir rejects invalid names', () => {
- it('test_getGroupDir_traversal_throws', () => {
- expect(() => getGroupDir(tmpDir, '../../etc')).toThrow(/Invalid group name/);
- });
-
- it('test_getGroupDir_valid_name_returns_path', () => {
- const dir = getGroupDir(tmpDir, 'company');
- expect(dir).toBe(path.join(tmpDir, 'groups', 'company'));
- });
- });
-
- describe('createGroupDir rejects invalid names', () => {
- it('test_createGroupDir_traversal_throws', async () => {
- await expect(createGroupDir(tmpDir, '../evil')).rejects.toThrow(/Invalid group name/);
- });
- });
-```
-
-- [ ] **Step 2: Run tests to verify they fail**
-
-Run: `cd gitnexus && npx vitest run test/unit/group/storage.test.ts`
-Expected: FAIL — `validateGroupName` is not exported, `getGroupDir` does not throw.
-
-- [ ] **Step 3: Implement validateGroupName and wire into getGroupDir and createGroupDir**
-
-In `gitnexus/src/core/group/storage.ts`, add the validation function before `getGroupDir` and call it:
-
-```typescript
-const GROUP_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
-
-export function validateGroupName(name: string): void {
- if (!GROUP_NAME_RE.test(name)) {
- throw new Error(
- `Invalid group name "${name}". Names must start with a letter or digit and contain only [a-zA-Z0-9_-].`,
- );
- }
-}
-
-export function getGroupDir(gitnexusDir: string, groupName: string): string {
- validateGroupName(groupName);
- return path.join(gitnexusDir, 'groups', groupName);
-}
-```
-
-`createGroupDir` already calls `getGroupDir` at line 68, so it inherits validation automatically. No change needed in `createGroupDir`.
-
-- [ ] **Step 4: Run tests to verify they pass**
-
-Run: `cd gitnexus && npx vitest run test/unit/group/storage.test.ts`
-Expected: ALL PASS
-
-- [ ] **Step 5: Commit**
-
-```bash
-cd gitnexus && git add src/core/group/storage.ts test/unit/group/storage.test.ts
-git commit -m "fix(group): validate group name to prevent path traversal
-
-Add validateGroupName() with regex [a-zA-Z0-9][a-zA-Z0-9_-]*.
-Called in getGroupDir (defense in depth) which covers all CLI entry
-points: create, add, remove, status, sync.
-
-Addresses PR #626 review item 1 (HIGH).
-
-Co-Authored-By: Claude Opus 4.6 (1M context) "
-```
-
----
-
-### Task 2: Directory Exclusions in Service Boundary Detector
-
-**Files:**
-- Modify: `gitnexus/src/core/group/service-boundary-detector.ts:24-51` (add constant), `:78` (walkForBoundaries), `:130` (hasSourceFilesInSubdirs)
-- Test: `gitnexus/test/unit/group/service-boundary-detector.test.ts`
-
-- [ ] **Step 1: Write failing tests for excluded directories**
-
-Add this describe block inside the existing `detectServiceBoundaries` describe in `gitnexus/test/unit/group/service-boundary-detector.test.ts`:
-
-```typescript
- it('test_detect_skips_vendor_directory', async () => {
- writeFile('services/auth/package.json', '{}');
- writeFile('services/auth/src/index.ts', '');
- // vendor should be skipped — its contents should not create a boundary
- writeFile('vendor/some-dep/package.json', '{}');
- writeFile('vendor/some-dep/src/lib.go', '');
-
- const boundaries = await detectServiceBoundaries(tmpDir);
-
- const paths = boundaries.map((b) => b.servicePath);
- expect(paths).toContain('services/auth');
- expect(paths).not.toContain('vendor/some-dep');
- });
-
- it('test_detect_skips_target_directory', async () => {
- writeFile('services/api/go.mod', 'module api');
- writeFile('services/api/main.go', '');
- writeFile('target/classes/Main.java', '');
- writeFile('target/pom.xml', '');
-
- const boundaries = await detectServiceBoundaries(tmpDir);
-
- const paths = boundaries.map((b) => b.servicePath);
- expect(paths).toContain('services/api');
- expect(paths).not.toContain('target');
- });
-
- it('test_detect_skips_pycache_directory', async () => {
- writeFile('services/ml/pyproject.toml', '[project]');
- writeFile('services/ml/model.py', '');
- // __pycache__ with a marker + source files — would be detected as
- // a boundary if not excluded, since it has package.json + .py file
- writeFile('__pycache__/package.json', '{}');
- writeFile('__pycache__/cached.py', '');
-
- const boundaries = await detectServiceBoundaries(tmpDir);
-
- const paths = boundaries.map((b) => b.servicePath);
- expect(paths).toContain('services/ml');
- expect(paths.every((p) => !p.includes('__pycache__'))).toBe(true);
- });
-
- it('test_detect_skips_dotfile_directories_regression', async () => {
- writeFile('services/api/package.json', '{}');
- writeFile('services/api/src/index.ts', '');
- writeFile('.hidden/package.json', '{}');
- writeFile('.hidden/src/index.ts', '');
-
- const boundaries = await detectServiceBoundaries(tmpDir);
-
- const paths = boundaries.map((b) => b.servicePath);
- expect(paths).toContain('services/api');
- expect(paths).not.toContain('.hidden');
- });
-
- it('test_detect_does_not_skip_regular_source_directories', async () => {
- writeFile('services/api/package.json', '{}');
- writeFile('services/api/src/index.ts', '');
-
- const boundaries = await detectServiceBoundaries(tmpDir);
-
- expect(boundaries).toHaveLength(1);
- expect(boundaries[0].serviceName).toBe('api');
- });
-```
-
-- [ ] **Step 2: Run tests to verify `vendor` and `target` tests fail**
-
-Run: `cd gitnexus && npx vitest run test/unit/group/service-boundary-detector.test.ts`
-Expected: `test_detect_skips_vendor_directory` and `test_detect_skips_target_directory` FAIL (vendor/target not excluded). Other new tests may pass since dotfile exclusion already exists.
-
-- [ ] **Step 3: Add EXCLUDED_DIRS constant and update both walking functions**
-
-In `gitnexus/src/core/group/service-boundary-detector.ts`:
-
-After `SOURCE_EXTENSIONS` (after line 51), add:
-
-```typescript
-const EXCLUDED_DIRS = new Set([
- 'node_modules',
- 'vendor',
- 'target',
- 'build',
- 'dist',
- '__pycache__',
- '.venv',
- 'venv',
- '.tox',
- '.mypy_cache',
- '.gradle',
- '.mvn',
- 'out',
- 'bin',
-]);
-```
-
-In `walkForBoundaries`, replace line 78:
-```typescript
- if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
-```
-with:
-```typescript
- if (entry.name.startsWith('.') || EXCLUDED_DIRS.has(entry.name)) continue;
-```
-
-In `hasSourceFilesInSubdirs`, replace line 130:
-```typescript
- if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
-```
-with:
-```typescript
- if (entry.isDirectory() && !entry.name.startsWith('.') && !EXCLUDED_DIRS.has(entry.name)) {
-```
-
-- [ ] **Step 4: Run tests to verify they pass**
-
-Run: `cd gitnexus && npx vitest run test/unit/group/service-boundary-detector.test.ts`
-Expected: ALL PASS
-
-- [ ] **Step 5: Commit**
-
-```bash
-cd gitnexus && git add src/core/group/service-boundary-detector.ts test/unit/group/service-boundary-detector.test.ts
-git commit -m "fix(group): add directory exclusions to service boundary detector
-
-Add EXCLUDED_DIRS set: vendor, target, build, dist, __pycache__,
-.venv, venv, .tox, .mypy_cache, .gradle, .mvn, out, bin.
-Applied in walkForBoundaries and hasSourceFilesInSubdirs.
-Replaces inline node_modules check.
-
-Addresses PR #626 review item 3 (HIGH).
-
-Co-Authored-By: Claude Opus 4.6 (1M context) "
-```
-
----
-
-### Task 3: Remove Double-Close of LadybugDB Pools
-
-**Files:**
-- Modify: `gitnexus/src/cli/group.ts:160` (remove import), `:187-189` (remove finally block body)
-- Test: `gitnexus/test/unit/group/sync.test.ts` (add pool cleanup test)
-- Test: `gitnexus/test/integration/group/group-cli.test.ts` (verify no blanket close in source)
-
-- [ ] **Step 1: Write unit tests for per-id pool cleanup in sync.ts**
-
-Add to `gitnexus/test/unit/group/sync.test.ts`, inside the existing `describe('syncGroup', ...)`:
-
-```typescript
- it('test_syncGroup_closes_only_opened_pools', async () => {
- const config = makeConfig({
- 'app/backend': 'backend-repo',
- 'app/frontend': 'frontend-repo',
- });
-
- const closedIds: string[] = [];
-
- // Mock initLbug/closeLbug via per-repo override that tracks pool lifecycle
- const { vi } = await import('vitest');
- const poolAdapter = await import('../../../src/core/lbug/pool-adapter.js');
- const initSpy = vi.spyOn(poolAdapter, 'initLbug').mockResolvedValue(undefined);
- const closeSpy = vi.spyOn(poolAdapter, 'closeLbug').mockImplementation(async (id?: string) => {
- if (id) closedIds.push(id);
- });
-
- try {
- await syncGroup(config, {
- resolveRepoHandle: async (_name, groupPath) => ({
- id: groupPath.replace(/\//g, '-'),
- path: groupPath,
- repoPath: '/tmp/' + groupPath,
- storagePath: '/tmp/' + groupPath + '/.gitnexus',
- }),
- skipWrite: true,
- }).catch(() => {});
- // Regardless of extraction errors, closeLbug should be called per id
- // closeLbug should only receive specific pool ids, never undefined/empty
- for (const id of closedIds) {
- expect(id).toBeTruthy();
- expect(typeof id).toBe('string');
- }
- // No blanket close (no-arg call)
- const blanketCalls = closeSpy.mock.calls.filter((args) => args.length === 0 || !args[0]);
- expect(blanketCalls).toHaveLength(0);
- } finally {
- initSpy.mockRestore();
- closeSpy.mockRestore();
- }
- });
-```
-
-- [ ] **Step 2: Run sync unit test to verify it passes (sync.ts already does per-id cleanup)**
-
-Run: `cd gitnexus && npx vitest run test/unit/group/sync.test.ts`
-Expected: PASS — sync.ts already cleans up correctly. This test locks the behavior.
-
-- [ ] **Step 3: Write test verifying CLI source has no blanket closeLbug()**
-
-Add to `gitnexus/test/integration/group/group-cli.test.ts`:
-
-```typescript
- it('test_sync_command_source_does_not_call_blanket_closeLbug', () => {
- const cliGroupPath = path.join(repoRoot, 'src', 'cli', 'group.ts');
- const source = fs.readFileSync(cliGroupPath, 'utf-8');
-
- // closeLbug() without arguments (blanket close) must not appear.
- // closeLbug(id) with argument is fine (that's in sync.ts, not here).
- // Match closeLbug() but not closeLbug(someArg)
- const blanketClosePattern = /closeLbug\s*\(\s*\)/;
- expect(source).not.toMatch(blanketClosePattern);
- });
-```
-
-- [ ] **Step 4: Run test to verify it fails**
-
-Run: `cd gitnexus && npx vitest run test/integration/group/group-cli.test.ts`
-Expected: FAIL — `closeLbug()` (no args) exists at line 188.
-
-- [ ] **Step 5: Remove blanket closeLbug() from cli/group.ts**
-
-In `gitnexus/src/cli/group.ts`:
-
-Remove the `closeLbug` import at line 160:
-```typescript
- const { closeLbug } = await import('../core/lbug/pool-adapter.js');
-```
-
-Replace the try/finally wrapper (lines 162-189):
-```typescript
- try {
- const groupDir = getGroupDir(getDefaultGitnexusDir(), name);
- const config = await loadGroupConfig(groupDir);
-
- console.log(`Syncing group "${name}" (${Object.keys(config.repos).length} repos)...\n`);
-
- const result = await syncGroup(config, {
- groupDir,
- allowStale: Boolean(opts.allowStale),
- verbose: Boolean(opts.verbose),
- skipEmbeddings: Boolean(opts.skipEmbeddings),
- exactOnly: Boolean(opts.exactOnly),
- });
-
- if (opts.json) {
- console.log(JSON.stringify(result, null, 2));
- } else {
- console.log(`\nMatching cascade:`);
- const exactLinks = result.crossLinks.filter((l) => l.matchType === 'exact');
- console.log(` exact: ${exactLinks.length} cross-links (confidence 1.0)`);
- console.log(` unmatched: ${result.unmatched.length} contracts`);
- console.log(
- `\nWrote contracts.json (${result.contracts.length} contracts, ${result.crossLinks.length} cross-links)`,
- );
- }
- } finally {
- await closeLbug().catch(() => {});
- }
-```
-
-Becomes (remove try/finally entirely, since sync.ts handles its own cleanup):
-```typescript
- const groupDir = getGroupDir(getDefaultGitnexusDir(), name);
- const config = await loadGroupConfig(groupDir);
-
- console.log(`Syncing group "${name}" (${Object.keys(config.repos).length} repos)...\n`);
-
- const result = await syncGroup(config, {
- groupDir,
- allowStale: Boolean(opts.allowStale),
- verbose: Boolean(opts.verbose),
- skipEmbeddings: Boolean(opts.skipEmbeddings),
- exactOnly: Boolean(opts.exactOnly),
- });
-
- if (opts.json) {
- console.log(JSON.stringify(result, null, 2));
- } else {
- console.log(`\nMatching cascade:`);
- const exactLinks = result.crossLinks.filter((l) => l.matchType === 'exact');
- console.log(` exact: ${exactLinks.length} cross-links (confidence 1.0)`);
- console.log(` unmatched: ${result.unmatched.length} contracts`);
- console.log(
- `\nWrote contracts.json (${result.contracts.length} contracts, ${result.crossLinks.length} cross-links)`,
- );
- }
-```
-
-- [ ] **Step 6: Run tests to verify they pass**
-
-Run: `cd gitnexus && npx vitest run test/integration/group/group-cli.test.ts test/unit/group/sync.test.ts`
-Expected: ALL PASS
-
-- [ ] **Step 7: Commit**
-
-```bash
-cd gitnexus && git add src/cli/group.ts test/integration/group/group-cli.test.ts test/unit/group/sync.test.ts
-git commit -m "fix(group): remove blanket closeLbug() from CLI sync command
-
-sync.ts already closes pools per-id in its finally block.
-The blanket closeLbug() in cli/group.ts tears down ALL active pools
-including unrelated ones in MCP server context.
-
-Addresses PR #626 review item 4 (HIGH).
-
-Co-Authored-By: Claude Opus 4.6 (1M context) "
-```
-
----
-
-### Task 4: gRPC Proto Regex — Brace-Depth Counter
-
-**Files:**
-- Modify: `gitnexus/src/core/group/extractors/grpc-extractor.ts:101-130` (parseProtoFile)
-- Test: `gitnexus/test/unit/group/grpc-extractor.test.ts`
-
-- [ ] **Step 1: Write failing tests for nested braces in proto services**
-
-Add this describe block inside the existing `proto file parsing` describe in `gitnexus/test/unit/group/grpc-extractor.test.ts`:
-
-```typescript
- it('test_extract_proto_with_google_api_http_nested_braces', async () => {
- writeFile(
- 'api/gateway.proto',
- `syntax = "proto3";
-package gateway.v1;
-
-import "google/api/annotations.proto";
-
-service GatewayService {
- rpc GetUser (GetUserRequest) returns (UserResponse) {
- option (google.api.http) = {
- get: "/v1/users/{user_id}"
- };
- }
- rpc CreateUser (CreateUserRequest) returns (UserResponse) {
- option (google.api.http) = {
- post: "/v1/users"
- body: "*"
- };
- }
-}`,
- );
-
- const contracts = await extractor.extract(null, tmpDir, makeRepo(tmpDir));
- const providers = contracts.filter(
- (c) => c.role === 'provider' && c.symbolRef.filePath === 'api/gateway.proto',
- );
-
- expect(providers).toHaveLength(2);
- const ids = providers.map((c) => c.contractId).sort();
- expect(ids).toEqual([
- 'grpc::gateway.v1.GatewayService/CreateUser',
- 'grpc::gateway.v1.GatewayService/GetUser',
- ]);
- });
-
- it('test_extract_proto_with_multiple_services', async () => {
- writeFile(
- 'api/multi.proto',
- `syntax = "proto3";
-package multi;
-
-service ServiceA {
- rpc MethodA (Req) returns (Res);
-}
-
-service ServiceB {
- rpc MethodB1 (Req) returns (Res);
- rpc MethodB2 (Req) returns (Res);
-}`,
- );
-
- const contracts = await extractor.extract(null, tmpDir, makeRepo(tmpDir));
- const providers = contracts.filter(
- (c) => c.role === 'provider' && c.symbolRef.filePath === 'api/multi.proto',
- );
-
- expect(providers).toHaveLength(3);
- const ids = providers.map((c) => c.contractId).sort();
- expect(ids).toEqual([
- 'grpc::multi.ServiceA/MethodA',
- 'grpc::multi.ServiceB/MethodB1',
- 'grpc::multi.ServiceB/MethodB2',
- ]);
- });
-
- it('test_extract_proto_with_nested_option_blocks_in_rpc', async () => {
- writeFile(
- 'api/nested.proto',
- `syntax = "proto3";
-package nested;
-
-service DeepService {
- rpc DeepMethod (Req) returns (Res) {
- option (google.api.http) = {
- post: "/v1/deep"
- body: "*"
- additional_bindings {
- get: "/v1/deep/{id}"
- }
- };
- }
-}`,
- );
-
- const contracts = await extractor.extract(null, tmpDir, makeRepo(tmpDir));
- const providers = contracts.filter(
- (c) => c.role === 'provider' && c.symbolRef.filePath === 'api/nested.proto',
- );
-
- expect(providers).toHaveLength(1);
- expect(providers[0].contractId).toBe('grpc::nested.DeepService/DeepMethod');
- });
-
- it('test_extract_proto_malformed_unclosed_brace_skips_service', async () => {
- writeFile(
- 'api/broken.proto',
- `syntax = "proto3";
-package broken;
-
-service IncompleteService {
- rpc SomeMethod (Req) returns (Res);
- // Missing closing brace — EOF before depth returns to 0
-`,
- );
-
- // Should not throw; incomplete service is silently skipped
- const contracts = await extractor.extract(null, tmpDir, makeRepo(tmpDir));
- const providers = contracts.filter(
- (c) => c.role === 'provider' && c.symbolRef.filePath === 'api/broken.proto',
- );
-
- // The old regex would find partial match; the new parser should skip it
- expect(providers).toHaveLength(0);
- });
-```
-
-- [ ] **Step 2: Run tests to verify the nested brace test fails**
-
-Run: `cd gitnexus && npx vitest run test/unit/group/grpc-extractor.test.ts`
-Expected: `test_extract_proto_with_google_api_http_nested_braces` FAIL — regex stops at first `}` inside the `option` block.
-
-- [ ] **Step 3: Replace serviceRe regex with extractServiceBlocks function**
-
-In `gitnexus/src/core/group/extractors/grpc-extractor.ts`, replace the `parseProtoFile` method (lines 101-130):
-
-```typescript
- private parseProtoFile(content: string, filePath: string): ExtractedContract[] {
- const out: ExtractedContract[] = [];
-
- const pkgMatch = content.match(/^package\s+([\w.]+)\s*;/m);
- const pkg = pkgMatch ? pkgMatch[1] : '';
-
- for (const { name: serviceName, body } of extractServiceBlocks(content)) {
- const rpcRe = /rpc\s+(\w+)\s*\(/g;
- let rpcMatch: RegExpExecArray | null;
- while ((rpcMatch = rpcRe.exec(body)) !== null) {
- const methodName = rpcMatch[1];
- const cid = contractId(pkg, serviceName, methodName);
- out.push(
- makeContract(cid, 'provider', filePath, `${serviceName}.${methodName}`, 0.85, {
- package: pkg,
- service: serviceName,
- method: methodName,
- source: 'proto',
- }),
- );
- }
- }
-
- return out;
- }
-```
-
-Add this function before the class (e.g. after `serviceOnlyContractId`, around line 26):
-
-```typescript
-function extractServiceBlocks(content: string): Array<{ name: string; body: string }> {
- const results: Array<{ name: string; body: string }> = [];
- const headerRe = /service\s+(\w+)\s*\{/g;
- let headerMatch: RegExpExecArray | null;
-
- while ((headerMatch = headerRe.exec(content)) !== null) {
- const serviceName = headerMatch[1];
- const bodyStart = headerMatch.index + headerMatch[0].length;
- let depth = 1;
- let pos = bodyStart;
-
- while (pos < content.length && depth > 0) {
- const ch = content[pos];
- if (ch === '{') depth++;
- else if (ch === '}') depth--;
- pos++;
- }
-
- // If EOF before depth returns to 0, skip incomplete service
- if (depth !== 0) continue;
-
- // body is between opening { (consumed by regex) and closing } (pos is one past it)
- const body = content.slice(bodyStart, pos - 1);
- results.push({ name: serviceName, body });
- }
-
- return results;
-}
-```
-
-- [ ] **Step 4: Run tests to verify they pass**
-
-Run: `cd gitnexus && npx vitest run test/unit/group/grpc-extractor.test.ts`
-Expected: ALL PASS (including existing regression tests)
-
-- [ ] **Step 5: Commit**
-
-```bash
-cd gitnexus && git add src/core/group/extractors/grpc-extractor.ts test/unit/group/grpc-extractor.test.ts
-git commit -m "fix(group): replace gRPC proto regex with brace-depth counter
-
-The serviceRe regex used [^}]* which stopped at the first '}'.
-Proto services with google.api.http annotations contain nested {}
-blocks, causing methods to be missed.
-
-New extractServiceBlocks() uses a brace-depth counter (init depth=1
-after opening {, scan char-by-char). Malformed protos with unclosed
-braces are silently skipped.
-
-Addresses PR #626 review item 2 (HIGH).
-
-Co-Authored-By: Claude Opus 4.6 (1M context) "
-```
-
----
-
-### Task 5: Run Full Test Suite
-
-- [ ] **Step 1: Run all group-related tests**
-
-Run: `cd gitnexus && npx vitest run test/unit/group/ test/integration/group/`
-Expected: ALL PASS
-
-- [ ] **Step 2: Run full test suite to catch regressions**
-
-Run: `cd gitnexus && npx vitest run`
-Expected: ALL PASS, 0 failures
-
-- [ ] **Step 3: Run typecheck**
-
-Run: `cd gitnexus && npx tsc --noEmit`
-Expected: No errors
-
----
-
-### Task 6: CLI Integration Smoke Test
-
-- [ ] **Step 1: Add CLI smoke test for path traversal**
-
-Add to `gitnexus/test/integration/group/group-cli.test.ts` inside the existing `group CLI` describe:
-
-```typescript
- it('test_create_with_invalid_name_fails', () => {
- const result = runGroup(['create', '../../evil']);
- expect(result.status).not.toBe(0);
- expect(result.stderr).toContain('Invalid group name');
- });
-```
-
-- [ ] **Step 2: Run test**
-
-Run: `cd gitnexus && npx vitest run test/integration/group/group-cli.test.ts`
-Expected: ALL PASS
-
-- [ ] **Step 3: Commit**
-
-```bash
-cd gitnexus && git add test/integration/group/group-cli.test.ts
-git commit -m "test(group): add CLI smoke test for path traversal rejection
-
-Verifies that 'group create ../../evil' fails with Invalid group name.
-
-Co-Authored-By: Claude Opus 4.6 (1M context) "
-```
diff --git a/docs/superpowers/specs/2026-04-02-pr626-high-fixes-design.md b/docs/superpowers/specs/2026-04-02-pr626-high-fixes-design.md
deleted file mode 100644
index bdd1774e6..000000000
--- a/docs/superpowers/specs/2026-04-02-pr626-high-fixes-design.md
+++ /dev/null
@@ -1,175 +0,0 @@
-# PR #626 HIGH-Priority Fixes Design
-
-**Date:** 2026-04-02
-**PR:** abhigyanpatwari/GitNexus#626 — Intra-repo service communication tracking
-**Scope:** 4 HIGH-priority issues identified by abhigyanpatwari and xkonjin
-**Approach:** Minimal targeted fixes (option A) — no refactoring, no scope creep
-
----
-
-## Fix 1: Path Traversal via Group Name
-
-**File:** `gitnexus/src/core/group/storage.ts`
-**Risk:** A group name like `../../etc` creates directories outside the intended path.
-
-### Solution
-
-Add `validateGroupName(name: string): void` that enforces `/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/`.
-
-- Call in `createGroupDir` (primary entry point)
-- Call in `getGroupDir` (defense in depth)
-- Throw descriptive error on invalid names
-
-**Legacy:** Groups already on disk with names outside this pattern are not auto-renamed; only new `create` / resolved paths are validated.
-
-### Why regex over path.resolve + startsWith
-
-- abhigyanpatwari explicitly requested `[a-zA-Z0-9_-]`
-- Stricter: disallows spaces, dots, Unicode edge cases
-- Simpler to reason about
-
-### Tests
-
-- `../../evil` throws
-- `foo/bar` throws
-- Empty string throws
-- `my-group_01` passes
-- `A` (single char) passes
-- CLI smoke: one integration test that hits `getGroupDir` / `createGroupDir` (e.g. `group create` or `group add`) with an invalid name proves wiring for every subcommand that resolves a group through storage
-
-### CLI/API entry points accepting groupName
-
-All paths flow through `getGroupDir` (which validates), so coverage is implicit. For reference:
-
-| Command | Entry | Calls |
-|---------|-------|-------|
-| `group create` | `cli/group.ts` action | `createGroupDir` -> `getGroupDir` |
-| `group add` | `cli/group.ts` action | `getGroupDir` |
-| `group remove` | `cli/group.ts` action | `getGroupDir` |
-| `group list` | `cli/group.ts` action | reads `groups/` dir directly — no traversal risk (reads, not writes) |
-| `group status` | `cli/group.ts` action | `getGroupDir` |
-| `group sync` | `cli/group.ts` action | `getGroupDir` |
-
-**`listGroups`:** Reads directory names from disk without validation. Not a write path, so no traversal risk. May surface manually-created directories with non-conforming names — accepted as-is, not in scope.
-
----
-
-## Fix 2: gRPC Proto Regex -> Brace-Depth Counter
-
-**File:** `gitnexus/src/core/group/extractors/grpc-extractor.ts`
-**Risk:** `serviceRe = /service\s+(\w+)\s*\{([^}]*)}/gs` stops at first `}`. Proto services with `google.api.http` annotations inside RPCs contain nested `{ }` blocks.
-
-### Solution
-
-Replace `serviceRe` regex with `extractServiceBlocks(content: string): Array<{ name: string; body: string }>`:
-
-1. Use regex only to find `service {` start positions (regex consumes the opening `{`)
-2. Initialise depth to 1 immediately after the opening `{`
-3. Scan forward char by char: `{` -> depth++, `}` -> depth--; collect into body
-4. Stop when depth reaches 0 (the matching closing `}`)
-5. Return name + body pairs
-
-Inner `rpcRe` regex remains unchanged — it operates on the already-extracted body.
-
-**Malformed input:** If EOF is reached before `depth` returns to 0, skip the incomplete service (do not add to results). Lock this in the test.
-
-**Scope limitation (v1):** Brace-depth only — no lexer for string literals or comments containing `{`/`}`. Sufficient for `google.api.http` annotations. Known false positive: braces inside `//` comments or quoted strings within proto options. Accepted for v1; a proper proto lexer is out of scope.
-
-### Tests
-
-- Proto with single service, no nesting (regression)
-- Proto with `google.api.http` nested braces inside RPC options
-- Proto with multiple services
-- Proto with nested `option` blocks inside RPC (e.g. `google.api.http`)
-- Malformed proto with unclosed brace (graceful handling)
-
----
-
-## Fix 3: Directory Exclusions in Service Boundary Detector
-
-**File:** `gitnexus/src/core/group/service-boundary-detector.ts`
-**Risk:** Walks entire repo tree, only skipping dotfiles and `node_modules`. Extremely slow on repos with `vendor/`, `target/`, `__pycache__/`, `.venv/`.
-
-### Solution
-
-Create `EXCLUDED_DIRS` as a `Set` (alongside existing `SERVICE_MARKERS`, `SOURCE_EXTENSIONS`), for example:
-
-```text
-node_modules, vendor, target, build, dist,
-__pycache__, .venv, venv, .tox, .mypy_cache,
-.gradle, .mvn, out, bin
-```
-
-(Implement as `new Set([...])` — the list above is the membership, not a string literal.)
-
-Apply in both:
-- `walkForBoundaries` (line 77-78) — replace current inline `=== 'node_modules'` check with `EXCLUDED_DIRS.has(entry.name)`
-- `hasSourceFilesInSubdirs` (line 130) — replace `entry.name !== 'node_modules'` with `!EXCLUDED_DIRS.has(entry.name)`
-
-Note: remove the old `=== 'node_modules'` literal from both locations — it is covered by `EXCLUDED_DIRS`.
-Dotfile exclusion (`.` prefix) remains as a separate check since it's a pattern, not a name.
-Exclusions apply only to `isDirectory()` entries — file names are never checked against `EXCLUDED_DIRS`.
-
-**Tradeoff:** Rare layouts that keep source under names like `out/` or `bin/` will be skipped; accepted for performance on typical monorepos.
-
-**Case sensitivity:** `Set.has` is case-sensitive (matches current `=== 'node_modules'` behavior). Windows case-insensitive FS not handled — accepted as-is, consistent with existing code.
-
-### Tests
-
-- Directory named `vendor/` is skipped
-- Directory named `target/` is skipped
-- Directory named `__pycache__/` is skipped
-- Regular source directories are NOT skipped
-- Dotfile directories still skipped (regression)
-
----
-
-## Fix 4: Double-Close of LadybugDB Pools
-
-**Files:**
-- `gitnexus/src/core/group/sync.ts` (lines 155-157) — per-id cleanup (KEEP)
-- `gitnexus/src/cli/group.ts` (line 188) — blanket `closeLbug()` (REMOVE)
-
-**Risk:** In MCP server context, `closeLbug()` without arguments tears down ALL active pools, including ones from unrelated operations.
-
-### Solution
-
-Remove the `closeLbug()` call (no arguments) from `cli/group.ts` finally block. The per-id cleanup in `sync.ts` is sufficient:
-
-```typescript
-// sync.ts — KEEP: cleans up only pools opened by this sync
-finally {
- for (const id of [...new Set(openPoolIds)]) {
- await closeLbug(id).catch(() => {});
- }
-}
-```
-
-```typescript
-// cli/group.ts — REMOVE: blanket close that kills all pools
-finally {
- await closeLbug().catch(() => {}); // DELETE THIS
-}
-```
-
-Remove the `closeLbug` import from `cli/group.ts` — after removing the `finally` call it has no remaining usages.
-
-### Tests (unit level — mock pool adapter)
-
-- `syncGroup` closes only the pools it opened (mock `closeLbug`, assert called with specific ids)
-- Two-pool scenario: sync opens pools A and B, both closed in finally; pool C (opened elsewhere) not touched
-- CLI `sync` command does not call blanket `closeLbug()` (verify no zero-arg call in source — static check or grep-based test)
-
----
-
-## Out of Scope
-
-- JSON -> LadybugDB migration (tracked in #606)
-- MEDIUM/LOW issues (items 5-10 from review summary)
-- Test gap coverage beyond what's needed for these 4 fixes
-- Any refactoring or architectural changes
-
-## Execution Order
-
-Fixes are independent — can be implemented in parallel or any order.
-Recommended order for review clarity: 1 -> 3 -> 4 -> 2 (simplest to most complex).
diff --git a/gitnexus-web/e2e/language-switching.spec.ts b/gitnexus-web/e2e/language-switching.spec.ts
new file mode 100644
index 000000000..c8390e081
--- /dev/null
+++ b/gitnexus-web/e2e/language-switching.spec.ts
@@ -0,0 +1,71 @@
+import { test, expect, type Page } from '@playwright/test';
+
+const BACKEND_URL = 'http://localhost:4747';
+const REPO_NAME = 'mock-repo';
+
+async function mockBackend(page: Page) {
+ const repo = {
+ name: REPO_NAME,
+ path: '/tmp/mock-repo',
+ repoPath: '/tmp/mock-repo',
+ indexedAt: new Date().toISOString(),
+ stats: { files: 1, nodes: 0, edges: 0, processes: 0 },
+ };
+
+ await page.route(
+ (url) => url.origin === BACKEND_URL && url.pathname === '/api/repos',
+ (route) => route.fulfill({ json: [repo] }),
+ );
+ await page.route(
+ (url) => url.origin === BACKEND_URL && url.pathname === '/api/repo',
+ (route) => route.fulfill({ json: repo }),
+ );
+ await page.route(
+ (url) => url.origin === BACKEND_URL && url.pathname === '/api/graph',
+ (route) => route.fulfill({ json: { nodes: [], relationships: [] } }),
+ );
+ await page.route(
+ (url) => url.origin === BACKEND_URL && url.pathname === '/api/heartbeat',
+ (route) =>
+ route.fulfill({
+ status: 200,
+ headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' },
+ body: ':ok\n\n',
+ }),
+ );
+}
+
+async function enterExploringView(page: Page) {
+ await page.goto('/');
+ await page.locator('[data-testid="landing-repo-card"]').first().click();
+ await expect(page.getByTestId('language-switcher')).toBeVisible({ timeout: 20_000 });
+}
+
+test.describe('language switching', () => {
+ test('switches Header language, updates document metadata, and persists after reload', async ({
+ page,
+ }) => {
+ await mockBackend(page);
+ await page.goto('/');
+ await page.evaluate(() => window.localStorage.clear());
+
+ await enterExploringView(page);
+
+ await page.getByTestId('language-switcher').selectOption('zh-CN');
+
+ await expect(page.locator('html')).toHaveAttribute('lang', 'zh-CN');
+ await expect(page.getByText('觉得不错就点星')).toBeVisible();
+ await expect
+ .poll(() => page.evaluate(() => window.localStorage.getItem('gitnexus.lng')))
+ .toBe('zh-CN');
+
+ await page.reload();
+
+ await expect(page.getByTestId('language-switcher')).toHaveValue('zh-CN', { timeout: 20_000 });
+ await expect(page.locator('html')).toHaveAttribute('lang', 'zh-CN');
+ await expect(page.getByText('觉得不错就点星')).toBeVisible();
+
+ await page.getByTestId('language-switcher').selectOption('en');
+ await expect(page.locator('html')).toHaveAttribute('lang', 'en');
+ });
+});
diff --git a/gitnexus-web/package-lock.json b/gitnexus-web/package-lock.json
index e718eb7f2..f31a7ef5e 100644
--- a/gitnexus-web/package-lock.json
+++ b/gitnexus-web/package-lock.json
@@ -26,6 +26,8 @@
"graphology-layout-forceatlas2": "^0.10.1",
"graphology-layout-noverlap": "^0.4.2",
"graphology-utils": "^2.3.0",
+ "i18next": "^26.2.0",
+ "i18next-browser-languagedetector": "^8.2.1",
"langchain": "^1.3.5",
"lru-cache": "^11.2.4",
"lucide-react": "^1.14.0",
@@ -34,6 +36,7 @@
"pandemonium": "^2.4.0",
"react": "^19.2.5",
"react-dom": "^19.2.6",
+ "react-i18next": "^17.0.8",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^16.1.1",
"react-zoom-pan-pinch": "^4.0.3",
@@ -437,9 +440,9 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
- "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
+ "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -5266,6 +5269,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "license": "MIT",
+ "dependencies": {
+ "void-elements": "3.1.0"
+ }
+ },
"node_modules/html-url-attributes": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
@@ -5290,6 +5302,43 @@
"node": ">= 14"
}
},
+ "node_modules/i18next": {
+ "version": "26.2.0",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.2.0.tgz",
+ "integrity": "sha512-zwBHldHdTmwN7r6UNc7lC6GWNN+YYg3DrRSeHR5PRRBf5QnJZcYHrQc0uaU26qZeYxR7iFZD+Y315dPnKP47wA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://www.locize.com/i18next"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.locize.com"
+ }
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": "^5 || ^6"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/i18next-browser-languagedetector": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz",
+ "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -7725,6 +7774,33 @@
"react": "^19.2.6"
}
},
+ "node_modules/react-i18next": {
+ "version": "17.0.8",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.8.tgz",
+ "integrity": "sha512-0ooKbGLU8JXhe1zwpQUWIeXSgLPOfwJmgheWRIUpcoA0CpyabpGhayjdG+/eA5esC1AQ8h2jWpXjJfzQzeDOCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.29.2",
+ "html-parse-stringify": "^3.0.1",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "peerDependencies": {
+ "i18next": ">= 26.2.0",
+ "react": ">= 16.8.0",
+ "typescript": "^5 || ^6"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -8464,7 +8540,7 @@
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
+ "devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -8625,6 +8701,15 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/uuid": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz",
@@ -8840,6 +8925,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
diff --git a/gitnexus-web/package.json b/gitnexus-web/package.json
index de51268d0..318624e3e 100644
--- a/gitnexus-web/package.json
+++ b/gitnexus-web/package.json
@@ -36,6 +36,8 @@
"graphology-layout-forceatlas2": "^0.10.1",
"graphology-layout-noverlap": "^0.4.2",
"graphology-utils": "^2.3.0",
+ "i18next": "^26.2.0",
+ "i18next-browser-languagedetector": "^8.2.1",
"langchain": "^1.3.5",
"lru-cache": "^11.2.4",
"lucide-react": "^1.14.0",
@@ -44,6 +46,7 @@
"pandemonium": "^2.4.0",
"react": "^19.2.5",
"react-dom": "^19.2.6",
+ "react-i18next": "^17.0.8",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^16.1.1",
"react-zoom-pan-pinch": "^4.0.3",
diff --git a/gitnexus-web/src/App.tsx b/gitnexus-web/src/App.tsx
index 2ee3569a8..68f7d4968 100644
--- a/gitnexus-web/src/App.tsx
+++ b/gitnexus-web/src/App.tsx
@@ -21,8 +21,11 @@ import {
type BackendRepo,
} from './services/backend-client';
import { ERROR_RESET_DELAY_MS } from './config/ui-constants';
+import { formatBackendError } from './i18n/error-messages';
+import { useTranslation } from 'react-i18next';
const AppContent = () => {
+ const { t } = useTranslation(['common', 'errors']);
const {
viewMode,
setViewMode,
@@ -54,7 +57,6 @@ const AppContent = () => {
async (result: ConnectResult): Promise => {
// Use the canonical repo name from the server response so all subsequent
// backend calls (queries, search, grep, readFile) scope to this repo.
- const repoName = result.repoInfo.name;
const repoPath = result.repoInfo.repoPath ?? result.repoInfo.path;
// Normalize both Windows (\) and Unix (/) path separators before splitting
const projectName =
@@ -104,6 +106,11 @@ const AppContent = () => {
// Auto-connect when ?server or ?project query param is present (bookmarkable shortcut)
const autoConnectRan = useRef(false);
+ const tRef = useRef(t);
+ useEffect(() => {
+ tRef.current = t;
+ }, [t]);
+
useEffect(() => {
if (autoConnectRan.current) return;
const params = new URLSearchParams(window.location.search);
@@ -116,8 +123,8 @@ const AppContent = () => {
setProgress({
phase: 'extracting',
percent: 0,
- message: 'Connecting to server...',
- detail: 'Validating server',
+ message: tRef.current('common:progress.connecting'),
+ detail: tRef.current('common:progress.validatingServer'),
});
setViewMode('loading');
@@ -132,8 +139,8 @@ const AppContent = () => {
setProgress({
phase: 'extracting',
percent: 5,
- message: 'Connecting to server...',
- detail: 'Validating server',
+ message: tRef.current('common:progress.connecting'),
+ detail: tRef.current('common:progress.validatingServer'),
});
} else if (phase === 'downloading') {
const pct = total ? Math.round((downloaded / total) * 90) + 5 : 50;
@@ -141,15 +148,15 @@ const AppContent = () => {
setProgress({
phase: 'extracting',
percent: pct,
- message: 'Downloading graph...',
- detail: `${mb} MB downloaded`,
+ message: tRef.current('common:progress.downloadingGraph'),
+ detail: tRef.current('common:progress.downloadedMb', { mb }),
});
} else if (phase === 'extracting') {
setProgress({
phase: 'extracting',
percent: 97,
- message: 'Processing...',
- detail: 'Extracting file contents',
+ message: tRef.current('common:progress.processing'),
+ detail: tRef.current('common:progress.extractingFileContents'),
});
}
},
@@ -173,8 +180,8 @@ const AppContent = () => {
setProgress({
phase: 'error',
percent: 0,
- message: 'Failed to connect to server',
- detail: err instanceof Error ? err.message : 'Unknown error',
+ message: tRef.current('errors:connectFailed'),
+ detail: formatBackendError(err, tRef.current),
});
setTimeout(() => {
setViewMode('onboarding');
@@ -299,7 +306,7 @@ const AppContent = () => {
{serverDisconnected && (
- Server connection lost — reconnecting…
+ {t('errors:backend.reconnecting')}
)}
diff --git a/gitnexus-web/src/components/AnalyzeOnboarding.tsx b/gitnexus-web/src/components/AnalyzeOnboarding.tsx
index f4147f57c..53260e388 100644
--- a/gitnexus-web/src/components/AnalyzeOnboarding.tsx
+++ b/gitnexus-web/src/components/AnalyzeOnboarding.tsx
@@ -17,6 +17,7 @@
import { Sparkles, Github } from '@/lib/lucide-icons';
import { RepoAnalyzer } from './RepoAnalyzer';
+import { useTranslation } from 'react-i18next';
interface AnalyzeOnboardingProps {
/** Called when analysis finishes and the repo is ready to load. */
@@ -24,6 +25,8 @@ interface AnalyzeOnboardingProps {
}
export const AnalyzeOnboarding = ({ onComplete }: AnalyzeOnboardingProps) => {
+ const { t } = useTranslation('onboarding');
+
return (
- Analyze your first repository
+ {t('analyzeFirst.title')}
- Paste a GitHub URL and GitNexus will clone it, parse the code, and build a live
- knowledge graph — right in your browser.
+ {t('analyzeFirst.description')}
@@ -131,7 +133,7 @@ function TabContent({
letterSpacing: '0.08em',
}}
>
- Getting started
+ {t('overview.gettingStarted')}
- What is GitNexus?
+ {t('overview.whatIsTitle')}
- An interactive graph explorer for your codebase. Every file, function, and import
- becomes a node you can explore, query, and navigate visually.
+ {t('overview.whatIsDescription')}
@@ -160,11 +161,10 @@ function TabContent({
}}
>
- Your current repo
+ {t('overview.currentRepoTitle')}
- Three ways to explore
+ {t('overview.threeWaysTitle')}
- 1. Click nodes to inspect
+ 1.{' '}
+ {t('overview.wayInspect')}
- 2. Search by name or type
+ 2.{' '}
+ {t('overview.waySearch')}
- 3. Ask Nexus AI a natural
- language question
+ 3. {t('overview.wayAsk')}
@@ -198,11 +199,11 @@ function TabContent({
}}
>
- Navigation
+ {t('overview.navigationTitle')}
- · Scroll to zoom
- · Click and drag to pan · Double-click a node to focus its subgraph
+ · {t('overview.navZoom')} · {t('overview.navPan')} ·{' '}
+ {t('overview.navFocus')}
- Node size reflects
- connection count — larger nodes are depended on by more files. Edges point from importer →
- imported.
+ {t('graph.sizeDescription')}
- Click any node to open its detail panel — showing imports, exports, and reverse
- dependencies.
+ {t('graph.detailDescription')}
- Search by filename, function name, or import path. Matching nodes are highlighted live
- in the graph.
+ {t('search.searchDescription')}
@@ -299,12 +299,11 @@ function TabContent({
- Filter panel
+ {t('search.filterPanel')}
- Use the filter icon in the left sidebar to isolate specific node types, hide leaf nodes,
- or focus on a depth range from a selected root.
+ {t('search.filterDescription')}
@@ -355,7 +354,7 @@ function TabContent({
letterSpacing: '0.08em',
}}
>
- Nexus AI
+ {t('ai.title')}
- ✓ Semantic Ready
+ {t('ai.semanticReady')}
- Your repo is indexed and ready for semantic queries. Nexus AI understands code structure
- and relationships, not just file names.
+ {t('ai.description')}
-
Try asking:
+
{t('tryAsking')}
{[
- '"Which files depend on the auth module?"',
- '"Find circular dependencies in this repo"',
- '"What are the most connected components?"',
- '"Show me all files that import useEffect"',
+ t('ai.questions.dependencies'),
+ t('ai.questions.circular'),
+ t('ai.questions.connected'),
+ t('ai.questions.imports'),
].map((q) => (
- Open the prompt via the Nexus AI button
- (top-right).
+ {t('ai.openPrompt')}
@@ -168,6 +171,8 @@ function StepRow({
// ── Polling status bar ────────────────────────────────────────────────────────
function PollingBar() {
+ const { t } = useTranslation('onboarding');
+
return (
- Listening for server
+ {t('guide.listeningForServer')}
...
- Start your local server
+ {t('guide.startServer')}
- {isDev
- ? 'Fire up the Express backend in a separate terminal to unlock the full graph.'
- : 'One command is all it takes. The browser connects automatically.'}
+ {isDev ? t('guide.devDescription') : t('guide.prodDescription')}
- Privacy: Your API keys are
- stored only in your browser's session storage and are cleared when the tab closes.
- They're sent directly to the LLM provider when you chat. Your code never leaves your
- machine.
+
+ {t('settings:privacyLabel')}
+ {' '}
+ {t('settings:privacyFull')}
- Couldn't create embeddings with WebGPU, so semantic search (Graph RAG) won't be as
- smart. The graph still works fine though!
+ {t('embedding.fallback.description')}
- Your options:
+
+ {t('embedding.fallback.options')}
+