spec-gen-cli 1.2.2 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +197 -11
- package/dist/api/generate.d.ts.map +1 -1
- package/dist/api/generate.js +5 -4
- package/dist/api/generate.js.map +1 -1
- package/dist/cli/commands/analyze.d.ts.map +1 -1
- package/dist/cli/commands/analyze.js +101 -41
- package/dist/cli/commands/analyze.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +25 -21
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts +353 -10
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +236 -45
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/view.d.ts.map +1 -1
- package/dist/cli/commands/view.js +33 -4
- package/dist/cli/commands/view.js.map +1 -1
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +10 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/analyzer/ast-chunker.d.ts +24 -0
- package/dist/core/analyzer/ast-chunker.d.ts.map +1 -0
- package/dist/core/analyzer/ast-chunker.js +198 -0
- package/dist/core/analyzer/ast-chunker.js.map +1 -0
- package/dist/core/analyzer/call-graph.d.ts +51 -4
- package/dist/core/analyzer/call-graph.d.ts.map +1 -1
- package/dist/core/analyzer/call-graph.js +634 -44
- package/dist/core/analyzer/call-graph.js.map +1 -1
- package/dist/core/analyzer/code-shaper.d.ts.map +1 -1
- package/dist/core/analyzer/code-shaper.js +5 -0
- package/dist/core/analyzer/code-shaper.js.map +1 -1
- package/dist/core/analyzer/codebase-digest.d.ts +40 -0
- package/dist/core/analyzer/codebase-digest.d.ts.map +1 -0
- package/dist/core/analyzer/codebase-digest.js +194 -0
- package/dist/core/analyzer/codebase-digest.js.map +1 -0
- package/dist/core/analyzer/cpp-header-resolver.d.ts +30 -0
- package/dist/core/analyzer/cpp-header-resolver.d.ts.map +1 -0
- package/dist/core/analyzer/cpp-header-resolver.js +71 -0
- package/dist/core/analyzer/cpp-header-resolver.js.map +1 -0
- package/dist/core/analyzer/function-registry-trie.d.ts +21 -0
- package/dist/core/analyzer/function-registry-trie.d.ts.map +1 -0
- package/dist/core/analyzer/function-registry-trie.js +39 -0
- package/dist/core/analyzer/function-registry-trie.js.map +1 -0
- package/dist/core/analyzer/import-resolver-bridge.d.ts +25 -0
- package/dist/core/analyzer/import-resolver-bridge.d.ts.map +1 -0
- package/dist/core/analyzer/import-resolver-bridge.js +99 -0
- package/dist/core/analyzer/import-resolver-bridge.js.map +1 -0
- package/dist/core/analyzer/signature-extractor.d.ts.map +1 -1
- package/dist/core/analyzer/signature-extractor.js +72 -3
- package/dist/core/analyzer/signature-extractor.js.map +1 -1
- package/dist/core/analyzer/subgraph-extractor.d.ts +10 -2
- package/dist/core/analyzer/subgraph-extractor.d.ts.map +1 -1
- package/dist/core/analyzer/subgraph-extractor.js +25 -7
- package/dist/core/analyzer/subgraph-extractor.js.map +1 -1
- package/dist/core/analyzer/type-inference-engine.d.ts +23 -0
- package/dist/core/analyzer/type-inference-engine.d.ts.map +1 -0
- package/dist/core/analyzer/type-inference-engine.js +130 -0
- package/dist/core/analyzer/type-inference-engine.js.map +1 -0
- package/dist/core/analyzer/vector-index.d.ts +35 -6
- package/dist/core/analyzer/vector-index.d.ts.map +1 -1
- package/dist/core/analyzer/vector-index.js +308 -54
- package/dist/core/analyzer/vector-index.js.map +1 -1
- package/dist/core/generator/spec-pipeline.d.ts +31 -11
- package/dist/core/generator/spec-pipeline.d.ts.map +1 -1
- package/dist/core/generator/spec-pipeline.js +170 -39
- package/dist/core/generator/spec-pipeline.js.map +1 -1
- package/dist/core/generator/stages/stage2-entities.d.ts.map +1 -1
- package/dist/core/generator/stages/stage2-entities.js +2 -1
- package/dist/core/generator/stages/stage2-entities.js.map +1 -1
- package/dist/core/generator/stages/stage3-services.d.ts.map +1 -1
- package/dist/core/generator/stages/stage3-services.js +2 -1
- package/dist/core/generator/stages/stage3-services.js.map +1 -1
- package/dist/core/generator/stages/stage4-api.d.ts.map +1 -1
- package/dist/core/generator/stages/stage4-api.js +2 -1
- package/dist/core/generator/stages/stage4-api.js.map +1 -1
- package/dist/core/generator/stages/stage5-architecture.d.ts +2 -1
- package/dist/core/generator/stages/stage5-architecture.d.ts.map +1 -1
- package/dist/core/generator/stages/stage5-architecture.js +15 -3
- package/dist/core/generator/stages/stage5-architecture.js.map +1 -1
- package/dist/core/services/chat-agent.d.ts +5 -0
- package/dist/core/services/chat-agent.d.ts.map +1 -1
- package/dist/core/services/chat-agent.js +14 -0
- package/dist/core/services/chat-agent.js.map +1 -1
- package/dist/core/services/chat-tools.d.ts.map +1 -1
- package/dist/core/services/chat-tools.js +172 -50
- package/dist/core/services/chat-tools.js.map +1 -1
- package/dist/core/services/mcp-handlers/analysis.d.ts +12 -0
- package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/analysis.js +138 -2
- package/dist/core/services/mcp-handlers/analysis.js.map +1 -1
- package/dist/core/services/mcp-handlers/graph.d.ts +21 -1
- package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/graph.js +142 -2
- package/dist/core/services/mcp-handlers/graph.js.map +1 -1
- package/dist/core/services/mcp-handlers/orient.d.ts +17 -0
- package/dist/core/services/mcp-handlers/orient.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/orient.js +200 -0
- package/dist/core/services/mcp-handlers/orient.js.map +1 -0
- package/dist/core/services/mcp-handlers/semantic.d.ts +18 -4
- package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/semantic.js +161 -17
- package/dist/core/services/mcp-handlers/semantic.js.map +1 -1
- package/dist/core/services/mcp-handlers/utils.d.ts +43 -0
- package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/utils.js +66 -1
- package/dist/core/services/mcp-handlers/utils.js.map +1 -1
- package/dist/core/services/mcp-watcher.d.ts +41 -0
- package/dist/core/services/mcp-watcher.d.ts.map +1 -0
- package/dist/core/services/mcp-watcher.js +177 -0
- package/dist/core/services/mcp-watcher.js.map +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/pipeline.d.ts +7 -0
- package/dist/types/pipeline.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/viewer/InteractiveGraphViewer.jsx +33 -8
- package/src/viewer/components/ChatPanel.jsx +8 -5
- package/src/viewer/components/ClassGraph.jsx +699 -0
- package/src/viewer/utils/graph-helpers.js +1 -1
- package/src/viewer/utils/themes.js +36 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Reverse-engineer [OpenSpec](https://github.com/Fission-AI/OpenSpec) specificatio
|
|
|
6
6
|
|
|
7
7
|
Most software has no specification. The code is the spec, scattered across thousands of files, tribal knowledge, and stale documentation. Tools like `openspec init` create empty scaffolding, but someone still has to write everything. By the time specs are written manually, the code has already changed.
|
|
8
8
|
|
|
9
|
-
spec-gen automates this. It analyzes your codebase through static analysis, generates structured specifications using an LLM, and continuously detects when code and specs fall out of sync.
|
|
9
|
+
spec-gen automates this. It analyzes your codebase through static analysis, generates structured specifications using an LLM, and continuously detects when code and specs fall out of sync. It also exposes the analysis as an MCP server so AI agents can navigate your codebase with GraphRAG-style retrieval — semantic search combined with call-graph expansion and spec-linked peer discovery.
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
@@ -492,7 +492,7 @@ spec-gen analyze [options]
|
|
|
492
492
|
--include <glob> # Additional include patterns
|
|
493
493
|
--exclude <glob> # Additional exclude patterns
|
|
494
494
|
--force # Force re-analysis (bypass 1-hour cache)
|
|
495
|
-
--embed
|
|
495
|
+
--no-embed # Skip building the semantic vector index (index is built by default when embedding is configured)
|
|
496
496
|
--reindex-specs # Re-index OpenSpec specs into the vector index without re-running full analysis
|
|
497
497
|
```
|
|
498
498
|
|
|
@@ -531,6 +531,95 @@ Checks performed:
|
|
|
531
531
|
|
|
532
532
|
Run `spec-gen doctor` whenever setup instructions aren't working — it tells you exactly what to fix and how.
|
|
533
533
|
|
|
534
|
+
## Agent Setup
|
|
535
|
+
|
|
536
|
+
### Passive context vs active tools
|
|
537
|
+
|
|
538
|
+
Agents have two ways to acquire knowledge about your codebase:
|
|
539
|
+
|
|
540
|
+
- **Passive (zero friction):** files referenced in `CLAUDE.md` / `.clinerules` are read automatically at session start, before the agent even processes your first message. No decision required, no tool call to make.
|
|
541
|
+
- **Active (friction):** MCP tools must be consciously selected from a list, called, and their output integrated into context. Even when the information would help, agents often skip this step and read files directly instead — it's always the safe fallback.
|
|
542
|
+
|
|
543
|
+
This means architectural context delivered passively is far more reliably absorbed than context behind a tool call. `spec-gen analyze` generates `.spec-gen/analysis/CODEBASE.md` specifically for this purpose: a compact, agent-optimized digest (~100 lines) that agents absorb at session start without any decision cost.
|
|
544
|
+
|
|
545
|
+
### What CODEBASE.md contains
|
|
546
|
+
|
|
547
|
+
Generated from the static analysis artifacts, it surfaces what an agent most needs before touching code:
|
|
548
|
+
|
|
549
|
+
- **Entry points** — functions with no internal callers (where execution starts)
|
|
550
|
+
- **Critical hubs** — highest fan-in functions (most risky to modify)
|
|
551
|
+
- **Spec domains** — which `openspec/specs/` domains exist and what they cover
|
|
552
|
+
- **Most coupled files** — high in-degree in the dependency graph (touch with care)
|
|
553
|
+
- **God functions / oversized orchestrators** — complexity hotspots
|
|
554
|
+
- **Layer violations** — if any
|
|
555
|
+
|
|
556
|
+
This is structural signal, not prose. It complements `openspec/specs/overview/spec.md`, which provides the functional view (what the system does, what domains exist). Together they give agents both the architectural topology and the business intent.
|
|
557
|
+
|
|
558
|
+
### Setup
|
|
559
|
+
|
|
560
|
+
After running `spec-gen analyze`, wire the generated digest into your agent's context:
|
|
561
|
+
|
|
562
|
+
**Claude Code** — add to `CLAUDE.md`:
|
|
563
|
+
|
|
564
|
+
```markdown
|
|
565
|
+
@.spec-gen/analysis/CODEBASE.md
|
|
566
|
+
@openspec/specs/overview/spec.md
|
|
567
|
+
|
|
568
|
+
## spec-gen MCP tools — when to use them
|
|
569
|
+
|
|
570
|
+
| Situation | Tool |
|
|
571
|
+
|-----------|------|
|
|
572
|
+
| Starting any new task | `orient` — returns functions, files, specs, call paths, and insertion points in one call |
|
|
573
|
+
| Don't know which file/function handles a concept | `search_code` |
|
|
574
|
+
| Need call topology across many files | `get_subgraph` / `analyze_impact` |
|
|
575
|
+
| Planning where to add a feature | `suggest_insertion_points` |
|
|
576
|
+
| Reading a spec before writing code | `get_spec` |
|
|
577
|
+
| Checking if code still matches spec | `check_spec_drift` |
|
|
578
|
+
| Finding spec requirements by meaning | `search_specs` |
|
|
579
|
+
|
|
580
|
+
For all other cases (reading a file, grepping, listing files) use native tools directly.
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
**Cline / Roo Code / Kilocode** — create `.clinerules/spec-gen.md`:
|
|
584
|
+
|
|
585
|
+
```markdown
|
|
586
|
+
# spec-gen
|
|
587
|
+
|
|
588
|
+
spec-gen provides static analysis artifacts and MCP tools to help you navigate this codebase.
|
|
589
|
+
Always use these before writing or modifying code.
|
|
590
|
+
|
|
591
|
+
## Before starting any task
|
|
592
|
+
|
|
593
|
+
- Read `.spec-gen/analysis/CODEBASE.md` — architectural digest: entry points, critical hubs,
|
|
594
|
+
god functions, most-coupled files, and available spec domains. Generated locally by `spec-gen analyze`.
|
|
595
|
+
- Read `openspec/specs/overview/spec.md` — functional domain map: what the system does,
|
|
596
|
+
which domains exist, data-flow requirements.
|
|
597
|
+
|
|
598
|
+
## MCP tools — use these instead of grep/read when exploring
|
|
599
|
+
|
|
600
|
+
- **Orient first**: call `orient` at the start of every task — it returns relevant functions,
|
|
601
|
+
files, specs, call paths, and insertion points in one shot.
|
|
602
|
+
- **Finding code**: use `search_code` when you don't know which file or function handles a concept.
|
|
603
|
+
- **Call topology**: use `get_subgraph` or `analyze_impact` when you need to understand
|
|
604
|
+
how calls flow across multiple files (not just a single file).
|
|
605
|
+
- **Adding a feature**: call `suggest_insertion_points` before deciding where to add code —
|
|
606
|
+
it accounts for the dependency graph, not just filenames.
|
|
607
|
+
- **Reading specs**: call `get_spec <domain>` before writing code in that domain;
|
|
608
|
+
use `search_specs` to find requirements by meaning when you don't know the domain name.
|
|
609
|
+
- **Checking drift**: call `check_spec_drift` after modifying a file to verify it still
|
|
610
|
+
matches its spec — do not skip this step before opening a PR.
|
|
611
|
+
|
|
612
|
+
Use native tools (Read, Grep, Glob) only for cases not covered above.
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
`CODEBASE.md` gives the agent passive architectural context. `overview/spec.md` gives the functional domain map. The table tells it when the passive context isn't enough and an active MCP tool call is warranted.
|
|
616
|
+
|
|
617
|
+
> **Tip:** `spec-gen analyze` prints these snippets after every run as a reminder.
|
|
618
|
+
|
|
619
|
+
> **Note:** `.spec-gen/analysis/` is git-ignored — each developer generates it locally. Re-run `spec-gen analyze` after significant structural changes to keep the digest current.
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
534
623
|
## MCP Server
|
|
535
624
|
|
|
536
625
|
`spec-gen mcp` starts spec-gen as a [Model Context Protocol](https://modelcontextprotocol.io/) server over stdio, exposing static analysis as tools that any MCP-compatible AI agent (Cline, Roo Code, Kilocode, Claude Code, Cursor...) can call directly -- no API key required.
|
|
@@ -565,6 +654,31 @@ or for local development:
|
|
|
565
654
|
|
|
566
655
|
**Cline / Roo Code / Kilocode** -- add the same block under `mcpServers` in the MCP settings JSON of your editor.
|
|
567
656
|
|
|
657
|
+
### Watch mode (keep search_code and orient fresh)
|
|
658
|
+
|
|
659
|
+
By default the MCP server reads `llm-context.json` from the last `analyze` run. With `--watch-auto`, it also watches source files for changes and incrementally re-indexes signatures so `search_code` and `orient` reflect your latest edits without waiting for the next commit.
|
|
660
|
+
|
|
661
|
+
Add `--watch-auto` to your MCP config args:
|
|
662
|
+
|
|
663
|
+
```json
|
|
664
|
+
{
|
|
665
|
+
"mcpServers": {
|
|
666
|
+
"spec-gen": {
|
|
667
|
+
"command": "spec-gen",
|
|
668
|
+
"args": ["mcp", "--watch-auto"]
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
The watcher starts automatically on the first tool call — no hardcoded path needed. It re-extracts signatures for any changed source file and patches `llm-context.json` within ~500 ms of a save. If an embedding server is reachable, it also re-embeds changed functions into the vector index automatically. The call graph is not rebuilt on every change; it stays current via the [post-commit hook](#cicd-integration) (`spec-gen analyze --force`).
|
|
675
|
+
|
|
676
|
+
| Option | Default | Description |
|
|
677
|
+
|---|---|---|
|
|
678
|
+
| `--watch-auto` | off | Auto-detect project root from first tool call |
|
|
679
|
+
| `--watch <dir>` | — | Watch a fixed directory (alternative to `--watch-auto`) |
|
|
680
|
+
| `--watch-debounce <ms>` | 400 | Delay before re-indexing after a file change |
|
|
681
|
+
|
|
568
682
|
### Cline / Roo Code / Kilocode
|
|
569
683
|
|
|
570
684
|
For editors with MCP support, after adding the `mcpServers` block to your settings, download the slash command workflows:
|
|
@@ -640,23 +754,36 @@ All tools run on **pure static analysis** -- no LLM quota consumed.
|
|
|
640
754
|
|
|
641
755
|
| Tool | Description | Requires prior analysis |
|
|
642
756
|
|------|-------------|:---:|
|
|
757
|
+
| `orient` | **Single entry point for any new task.** Given a natural-language task description, returns in one call: relevant functions, source files, spec domains, call neighbourhoods, insertion-point candidates, and matching spec sections. Start here. | Yes (+ embedding) |
|
|
643
758
|
| `get_subgraph` | Depth-limited subgraph centred on a function. Direction: `downstream` (what it calls), `upstream` (who calls it), or `both`. Output as JSON or Mermaid diagram. | Yes |
|
|
644
759
|
| `get_architecture_overview` | High-level cluster map: roles (entry layer, orchestrator, core utilities, API layer, internal), inter-cluster dependencies, global entry points, and critical hubs. No LLM required. | Yes |
|
|
645
760
|
| `get_function_skeleton` | Noise-stripped view of a source file: logs, inline comments, and non-JSDoc block comments removed. Signatures, control flow, return/throw, and call expressions preserved. Returns reduction %. | No |
|
|
646
|
-
| `
|
|
647
|
-
| `
|
|
761
|
+
| `get_function_body` | Return the exact source code of a named function in a file. | No |
|
|
762
|
+
| `get_file_dependencies` | Return the file-level import dependencies for a given source file (imports, imported-by, or both). | Yes |
|
|
763
|
+
| `trace_execution_path` | Find all call-graph paths between two functions (DFS, configurable depth/max-paths). Use this when debugging: "how does request X reach function Y?" Returns shortest path, all paths sorted by hops, and a step-by-step chain per path. | Yes |
|
|
764
|
+
| `suggest_insertion_points` | Semantic search over the vector index to find the best existing functions to extend or hook into when implementing a new feature. Returns ranked candidates with role and strategy. Falls back to BM25 keyword search when no embedding server is configured. | Yes (+ embedding) |
|
|
765
|
+
| `search_code` | Natural-language semantic search over indexed functions. Returns the closest matches by meaning with similarity score, call-graph neighbourhood enrichment, and spec-linked peer functions. Falls back to BM25 keyword search when no embedding server is configured. | Yes (+ embedding) |
|
|
648
766
|
|
|
649
767
|
**Specs**
|
|
650
768
|
|
|
651
769
|
| Tool | Description | Requires prior analysis |
|
|
652
770
|
|------|-------------|:---:|
|
|
771
|
+
| `get_spec` | Read the full content of an OpenSpec domain spec by domain name. | Yes (generate) |
|
|
653
772
|
| `get_mapping` | Requirement->function mapping produced by `spec-gen generate`. Shows which functions implement which spec requirements, confidence level, and orphan functions with no spec coverage. | Yes (generate) |
|
|
773
|
+
| `get_decisions` | List or search Architecture Decision Records (ADRs) stored in `openspec/decisions/`. Optional keyword query. | Yes (generate) |
|
|
654
774
|
| `check_spec_drift` | Detect code changes not reflected in OpenSpec specs. Compares git-changed files against spec coverage maps. Issues: gap / stale / uncovered / orphaned-spec / adr-gap. | Yes (generate) |
|
|
655
|
-
| `search_specs` | Semantic search over OpenSpec specifications to find requirements, design notes, and architecture decisions by meaning. Returns linked source files for graph highlighting. Use this when asked "which spec covers X?" or "where should we implement Z?". Requires a spec index built with `spec-gen analyze
|
|
775
|
+
| `search_specs` | Semantic search over OpenSpec specifications to find requirements, design notes, and architecture decisions by meaning. Returns linked source files for graph highlighting. Use this when asked "which spec covers X?" or "where should we implement Z?". Requires a spec index built with `spec-gen analyze` or `--reindex-specs`. | Yes (generate) |
|
|
656
776
|
| `list_spec_domains` | List all OpenSpec domains available in this project. Use this to discover what domains exist before doing a targeted `search_specs` call. | Yes (generate) |
|
|
657
777
|
|
|
658
778
|
### Parameters
|
|
659
779
|
|
|
780
|
+
**`orient`**
|
|
781
|
+
```
|
|
782
|
+
directory string Absolute path to the project directory
|
|
783
|
+
task string Natural-language description of the task, e.g. "add rate limiting to the API"
|
|
784
|
+
limit number Max relevant functions to return (default: 5, max: 20)
|
|
785
|
+
```
|
|
786
|
+
|
|
660
787
|
**`analyze_codebase`**
|
|
661
788
|
```
|
|
662
789
|
directory string Absolute path to the project directory
|
|
@@ -754,6 +881,41 @@ directory string Absolute path to the project directory
|
|
|
754
881
|
filePath string Path to the file, relative to the project directory
|
|
755
882
|
```
|
|
756
883
|
|
|
884
|
+
**`get_function_body`**
|
|
885
|
+
```
|
|
886
|
+
directory string Absolute path to the project directory
|
|
887
|
+
filePath string Path to the file, relative to the project directory
|
|
888
|
+
functionName string Name of the function to extract
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
**`get_file_dependencies`**
|
|
892
|
+
```
|
|
893
|
+
directory string Absolute path to the project directory
|
|
894
|
+
filePath string Path to the file, relative to the project directory
|
|
895
|
+
direction string "imports" | "importedBy" | "both" (default: "both")
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
**`trace_execution_path`**
|
|
899
|
+
```
|
|
900
|
+
directory string Absolute path to the project directory
|
|
901
|
+
entryFunction string Name of the starting function (case-insensitive partial match)
|
|
902
|
+
targetFunction string Name of the target function (case-insensitive partial match)
|
|
903
|
+
maxDepth number Maximum path length in hops (default: 6)
|
|
904
|
+
maxPaths number Maximum number of paths to return (default: 10, max: 50)
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
**`get_decisions`**
|
|
908
|
+
```
|
|
909
|
+
directory string Absolute path to the project directory
|
|
910
|
+
query string Optional keyword to filter ADRs by title or content
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
**`get_spec`**
|
|
914
|
+
```
|
|
915
|
+
directory string Absolute path to the project directory
|
|
916
|
+
domain string Domain name (e.g. "auth", "user", "api")
|
|
917
|
+
```
|
|
918
|
+
|
|
757
919
|
**`get_god_functions`**
|
|
758
920
|
```
|
|
759
921
|
directory string Absolute path to the project directory
|
|
@@ -813,6 +975,20 @@ section string Filter by section type: "requirements" | "purpose" | "design
|
|
|
813
975
|
2. get_mapping({ directory, orphansOnly: true }) # functions with no spec coverage
|
|
814
976
|
```
|
|
815
977
|
|
|
978
|
+
**Scenario D -- Starting a new task (fastest orientation)**
|
|
979
|
+
```
|
|
980
|
+
1. orient({ directory, task: "add rate limiting to the API" })
|
|
981
|
+
# Returns in one call:
|
|
982
|
+
# - relevant functions (semantic search or BM25 fallback)
|
|
983
|
+
# - source files and spec domains that cover them
|
|
984
|
+
# - call-graph neighbourhood for each top function
|
|
985
|
+
# - best insertion-point candidates
|
|
986
|
+
# - spec-linked peer functions (cross-graph traversal)
|
|
987
|
+
# - matching spec sections
|
|
988
|
+
2. get_spec({ directory, domain: "..." }) # read full spec before writing code
|
|
989
|
+
3. check_spec_drift({ directory }) # verify after implementation
|
|
990
|
+
```
|
|
991
|
+
|
|
816
992
|
## Interactive Graph Viewer
|
|
817
993
|
|
|
818
994
|
`spec-gen view` launches a local React app that visualises your codebase analysis and lets you explore spec requirements side-by-side with the dependency graph.
|
|
@@ -936,9 +1112,19 @@ Static analysis output is stored in `.spec-gen/analysis/`:
|
|
|
936
1112
|
|
|
937
1113
|
`spec-gen analyze` also writes **`ARCHITECTURE.md`** to your project root -- a Markdown overview of module clusters, entry points, and critical hubs, refreshed on every run.
|
|
938
1114
|
|
|
939
|
-
## Semantic Search
|
|
1115
|
+
## Semantic Search & GraphRAG
|
|
940
1116
|
|
|
941
|
-
`spec-gen analyze
|
|
1117
|
+
`spec-gen analyze` builds a vector index over all functions in the call graph, enabling natural-language search via the `search_code`, `orient`, and `suggest_insertion_points` MCP tools, and the search bar in the viewer.
|
|
1118
|
+
|
|
1119
|
+
### GraphRAG retrieval expansion
|
|
1120
|
+
|
|
1121
|
+
Semantic search is only the starting point. spec-gen combines three retrieval layers into every search result — this is what makes it genuinely useful for AI agents navigating unfamiliar codebases:
|
|
1122
|
+
|
|
1123
|
+
1. **Semantic seed** — dense vector search (or BM25 keyword fallback) finds the top-N functions closest in meaning to the query.
|
|
1124
|
+
2. **Call-graph expansion** — BFS up to depth 2 follows callee edges from every seed function, pulling in the files those functions depend on. During `generate`, this ensures the LLM sees the full call neighbourhood, not just the most obvious files.
|
|
1125
|
+
3. **Spec-linked peer functions** — each seed function's spec domain is looked up in the requirement→function mapping. Functions from the same spec domain that live in *different files* are surfaced as `specLinkedFunctions`. This crosses the call-graph boundary: implementations that share a spec requirement but are not directly connected by calls are retrieved automatically.
|
|
1126
|
+
|
|
1127
|
+
The result: a single `orient` or `search_code` call returns not just "functions that mention this concept" but the interconnected cluster of code and specs that collectively implement it. Agents spend less time chasing cross-file references manually and more time making changes with confidence.
|
|
942
1128
|
|
|
943
1129
|
### Embedding configuration
|
|
944
1130
|
|
|
@@ -950,8 +1136,8 @@ EMBED_BASE_URL=https://api.openai.com/v1
|
|
|
950
1136
|
EMBED_MODEL=text-embedding-3-small
|
|
951
1137
|
EMBED_API_KEY=sk-... # optional for local servers
|
|
952
1138
|
|
|
953
|
-
# Then run:
|
|
954
|
-
spec-gen analyze
|
|
1139
|
+
# Then run (embedding is automatic when configured):
|
|
1140
|
+
spec-gen analyze
|
|
955
1141
|
```
|
|
956
1142
|
|
|
957
1143
|
**Config file (`.spec-gen/config.json`):**
|
|
@@ -1146,11 +1332,11 @@ for (const [key, req] of Object.entries(requirements)) {
|
|
|
1146
1332
|
npm install # Install dependencies
|
|
1147
1333
|
npm run dev # Development mode (watch)
|
|
1148
1334
|
npm run build # Build
|
|
1149
|
-
npm run test:run # Run tests (
|
|
1335
|
+
npm run test:run # Run tests (2000+ unit tests)
|
|
1150
1336
|
npm run typecheck # Type check
|
|
1151
1337
|
```
|
|
1152
1338
|
|
|
1153
|
-
|
|
1339
|
+
2000+ unit tests covering static analysis, call graph, refactor analysis, spec mapping, drift detection, LLM enhancement, ADR generation, MCP handlers, and the full CLI.
|
|
1154
1340
|
|
|
1155
1341
|
## Links
|
|
1156
1342
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/api/generate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/api/generate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkBH,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAoB,MAAM,YAAY,CAAC;AA2DvF;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,cAAc,CAAC,CA8M/F"}
|
package/dist/api/generate.js
CHANGED
|
@@ -13,7 +13,7 @@ import { OpenSpecFormatGenerator } from '../core/generator/openspec-format-gener
|
|
|
13
13
|
import { OpenSpecWriter } from '../core/generator/openspec-writer.js';
|
|
14
14
|
import { ADRGenerator } from '../core/generator/adr-generator.js';
|
|
15
15
|
import { MappingGenerator } from '../core/generator/mapping-generator.js';
|
|
16
|
-
import { DEFAULT_ANTHROPIC_MODEL, DEFAULT_OPENAI_MODEL, DEFAULT_OPENAI_COMPAT_MODEL, DEFAULT_GEMINI_MODEL, SPEC_GEN_DIR, SPEC_GEN_ANALYSIS_SUBDIR, SPEC_GEN_LOGS_SUBDIR, SPEC_GEN_ANALYSIS_REL_PATH, SPEC_GEN_GENERATION_SUBDIR, OPENSPEC_DIR, ARTIFACT_REPO_STRUCTURE, ARTIFACT_LLM_CONTEXT, ARTIFACT_DEPENDENCY_GRAPH, } from '../constants.js';
|
|
16
|
+
import { DEFAULT_ANTHROPIC_MODEL, DEFAULT_OPENAI_MODEL, DEFAULT_OPENAI_COMPAT_MODEL, DEFAULT_GEMINI_MODEL, SPEC_GEN_DIR, SPEC_GEN_ANALYSIS_SUBDIR, SPEC_GEN_LOGS_SUBDIR, SPEC_GEN_ANALYSIS_REL_PATH, SPEC_GEN_GENERATION_SUBDIR, OPENSPEC_DIR, ARTIFACT_REPO_STRUCTURE, ARTIFACT_LLM_CONTEXT, ARTIFACT_DEPENDENCY_GRAPH, ARTIFACT_REFACTOR_PRIORITIES, } from '../constants.js';
|
|
17
17
|
function progress(onProgress, step, status, detail) {
|
|
18
18
|
onProgress?.({ phase: 'generate', step, status, detail });
|
|
19
19
|
}
|
|
@@ -27,7 +27,8 @@ async function loadAnalysisData(analysisPath) {
|
|
|
27
27
|
phase3_validation: { purpose: 'Validation', files: [], totalTokens: 0 },
|
|
28
28
|
};
|
|
29
29
|
const depGraph = await readJsonFile(join(analysisPath, ARTIFACT_DEPENDENCY_GRAPH), ARTIFACT_DEPENDENCY_GRAPH) ?? undefined;
|
|
30
|
-
|
|
30
|
+
const refactorReport = await readJsonFile(join(analysisPath, ARTIFACT_REFACTOR_PRIORITIES), ARTIFACT_REFACTOR_PRIORITIES) ?? undefined;
|
|
31
|
+
return { repoStructure, llmContext, depGraph, refactorReport };
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
33
34
|
* Generate OpenSpec specification files from analysis results using LLM.
|
|
@@ -60,7 +61,7 @@ export async function specGenGenerate(options = {}) {
|
|
|
60
61
|
if (!analysisData) {
|
|
61
62
|
throw new Error('No analysis found. Run specGenAnalyze() first.');
|
|
62
63
|
}
|
|
63
|
-
const { repoStructure, llmContext, depGraph } = analysisData;
|
|
64
|
+
const { repoStructure, llmContext, depGraph, refactorReport } = analysisData;
|
|
64
65
|
progress(onProgress, 'Loading analysis', 'complete', `${repoStructure.statistics.analyzedFiles} files`);
|
|
65
66
|
// Resolve provider
|
|
66
67
|
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
@@ -142,7 +143,7 @@ export async function specGenGenerate(options = {}) {
|
|
|
142
143
|
});
|
|
143
144
|
let pipelineResult;
|
|
144
145
|
try {
|
|
145
|
-
pipelineResult = await pipeline.run(repoStructure, llmContext, depGraph);
|
|
146
|
+
pipelineResult = await pipeline.run(repoStructure, llmContext, depGraph, refactorReport);
|
|
146
147
|
}
|
|
147
148
|
catch (error) {
|
|
148
149
|
await llm.saveLogs().catch(() => { });
|
package/dist/api/generate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/api/generate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EACL,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,OAAO,EAAE,uBAAuB,EAAE,MAAM,gDAAgD,CAAC;AACzF,OAAO,EAAE,cAAc,EAAkB,MAAM,sCAAsC,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/api/generate.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/analyze.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBpC,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,0CAA0C,CAAC;AAEhG,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/analyze.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBpC,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,0CAA0C,CAAC;AAEhG,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,2CAA2C,CAAC;AAkBnD,UAAU,cAAc;IACtB,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,qBAAqB,CAAC;IAChC,SAAS,EAAE,iBAAiB,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAiCD;;GAEG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,GACA,OAAO,CAAC,cAAc,CAAC,CAiEzB;AAMD,eAAO,MAAM,cAAc,SA4bvB,CAAC"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Outputs repository map, dependency graph, and file significance scores.
|
|
6
6
|
*/
|
|
7
7
|
import { Command } from 'commander';
|
|
8
|
-
import { stat, writeFile, mkdir } from 'node:fs/promises';
|
|
8
|
+
import { stat, writeFile, mkdir, readFile } from 'node:fs/promises';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { logger } from '../../utils/logger.js';
|
|
11
11
|
import { fileExists, formatDuration, formatAge } from '../../utils/command-helpers.js';
|
|
@@ -16,6 +16,7 @@ import { DependencyGraphBuilder, } from '../../core/analyzer/dependency-graph.js
|
|
|
16
16
|
import { AnalysisArtifactGenerator, } from '../../core/analyzer/artifact-generator.js';
|
|
17
17
|
import { buildArchitectureOverview, writeArchitectureMd, } from '../../core/analyzer/architecture-writer.js';
|
|
18
18
|
import { EmbeddingService } from '../../core/analyzer/embedding-service.js';
|
|
19
|
+
import { generateCodebaseDigest } from '../../core/analyzer/codebase-digest.js';
|
|
19
20
|
// ============================================================================
|
|
20
21
|
// HELPER FUNCTIONS
|
|
21
22
|
// ============================================================================
|
|
@@ -105,7 +106,8 @@ export const analyzeCommand = new Command('analyze')
|
|
|
105
106
|
.option('--include <glob>', 'Additional glob patterns to include (repeatable)', collect, [])
|
|
106
107
|
.option('--exclude <glob>', 'Additional glob patterns to exclude (repeatable)', collect, [])
|
|
107
108
|
.option('--force', 'Force re-analysis even if recent analysis exists', false)
|
|
108
|
-
.option('--embed', 'Build a semantic vector index after analysis (requires EMBED_BASE_URL + EMBED_MODEL)',
|
|
109
|
+
.option('--embed', 'Build a semantic vector index after analysis (requires EMBED_BASE_URL + EMBED_MODEL)', true)
|
|
110
|
+
.option('--no-embed', 'Skip vector index build (overrides default --embed)')
|
|
109
111
|
.option('--reindex-specs', 'Re-index OpenSpec specs into the vector index without re-running full analysis (requires EMBED_BASE_URL + EMBED_MODEL)', false)
|
|
110
112
|
.addHelpText('after', `
|
|
111
113
|
Examples:
|
|
@@ -119,7 +121,7 @@ Examples:
|
|
|
119
121
|
$ spec-gen analyze --output ./my-analysis
|
|
120
122
|
Custom output location
|
|
121
123
|
$ spec-gen analyze --force Force re-analysis
|
|
122
|
-
$ spec-gen analyze --embed
|
|
124
|
+
$ spec-gen analyze --no-embed Skip vector index build
|
|
123
125
|
$ spec-gen analyze --reindex-specs Re-index specs only (no full re-analysis)
|
|
124
126
|
|
|
125
127
|
Output files:
|
|
@@ -214,6 +216,10 @@ After analysis, run 'spec-gen generate' to create OpenSpec files.
|
|
|
214
216
|
logger.info('Domains detected', repoStructure.domains.map((d) => d.name).join(', ') || 'None');
|
|
215
217
|
logger.info('Architecture', repoStructure.architecture.pattern);
|
|
216
218
|
logger.blank();
|
|
219
|
+
// If embed is requested, run the embed step (incremental: only re-embeds changed functions)
|
|
220
|
+
if (opts.embed) {
|
|
221
|
+
await runEmbedStep(rootPath, outputPath, specGenConfig, opts.force ?? false, null);
|
|
222
|
+
}
|
|
217
223
|
logger.info('Next step', "Run 'spec-gen generate' to create OpenSpec files");
|
|
218
224
|
return;
|
|
219
225
|
}
|
|
@@ -406,6 +412,8 @@ After analysis, run 'spec-gen generate' to create OpenSpec files.
|
|
|
406
412
|
catch (archErr) {
|
|
407
413
|
logger.debug(`ARCHITECTURE.md generation skipped: ${archErr.message}`);
|
|
408
414
|
}
|
|
415
|
+
// Generate .spec-gen/analysis/CODEBASE.md — agent-readable architecture digest
|
|
416
|
+
const digestWritten = await generateCodebaseDigest(artifacts.llmContext, depGraph, { rootPath, outputDir: outputPath });
|
|
409
417
|
// Files generated
|
|
410
418
|
console.log(' Output Files:');
|
|
411
419
|
console.log(` ├─ ${opts.output}repo-structure.json`);
|
|
@@ -414,52 +422,35 @@ After analysis, run 'spec-gen generate' to create OpenSpec files.
|
|
|
414
422
|
console.log(` ├─ ${opts.output}dependencies.mermaid`);
|
|
415
423
|
if (architectureMdWritten) {
|
|
416
424
|
console.log(` ├─ ${opts.output}SUMMARY.md`);
|
|
417
|
-
console.log('
|
|
425
|
+
console.log(' ├─ ARCHITECTURE.md');
|
|
418
426
|
}
|
|
419
427
|
else {
|
|
420
|
-
console.log(`
|
|
428
|
+
console.log(` ├─ ${opts.output}SUMMARY.md`);
|
|
429
|
+
}
|
|
430
|
+
if (digestWritten) {
|
|
431
|
+
console.log(` └─ ${opts.output}CODEBASE.md`);
|
|
432
|
+
console.log('');
|
|
433
|
+
console.log(' Agent setup (one-time):');
|
|
434
|
+
console.log(` Add to your CLAUDE.md or .clinerules:`);
|
|
435
|
+
console.log('');
|
|
436
|
+
console.log(` @.spec-gen/analysis/CODEBASE.md`);
|
|
437
|
+
console.log('');
|
|
438
|
+
console.log(' ## spec-gen MCP tools — when to use them');
|
|
439
|
+
console.log(' | Situation | Tool |');
|
|
440
|
+
console.log(' |-------------------------------------------------|-----------------------------------|');
|
|
441
|
+
console.log(" | Don't know which file/function handles a concept | search_code |");
|
|
442
|
+
console.log(' | Need call topology across many files | get_subgraph / analyze_impact |');
|
|
443
|
+
console.log(' | Starting a new task on an unfamiliar codebase | orient |');
|
|
444
|
+
console.log(' | Planning where to add a feature | suggest_insertion_points |');
|
|
445
|
+
console.log(' | Checking if code still matches spec | check_spec_drift |');
|
|
446
|
+
console.log(' | Finding spec requirements by meaning | search_specs |');
|
|
421
447
|
}
|
|
422
448
|
console.log('');
|
|
423
449
|
// ========================================================================
|
|
424
450
|
// PHASE 5 (optional): BUILD VECTOR INDEX
|
|
425
451
|
// ========================================================================
|
|
426
452
|
if (opts.embed) {
|
|
427
|
-
|
|
428
|
-
try {
|
|
429
|
-
const { VectorIndex } = await import('../../core/analyzer/vector-index.js');
|
|
430
|
-
// Resolve embedding config: env vars take priority, then .spec-gen/config.json
|
|
431
|
-
let embedSvc;
|
|
432
|
-
try {
|
|
433
|
-
embedSvc = EmbeddingService.fromEnv();
|
|
434
|
-
}
|
|
435
|
-
catch {
|
|
436
|
-
const cfg = await readSpecGenConfig(rootPath);
|
|
437
|
-
if (!cfg)
|
|
438
|
-
throw new Error('No embedding config found. Set EMBED_BASE_URL and EMBED_MODEL, or add "embedding" to .spec-gen/config.json');
|
|
439
|
-
const svcFromConfig = EmbeddingService.fromConfig(cfg);
|
|
440
|
-
if (!svcFromConfig)
|
|
441
|
-
throw new Error('No embedding config found. Set EMBED_BASE_URL and EMBED_MODEL, or add "embedding" to .spec-gen/config.json');
|
|
442
|
-
embedSvc = svcFromConfig;
|
|
443
|
-
}
|
|
444
|
-
const cg = result.artifacts.llmContext.callGraph;
|
|
445
|
-
const sigs = result.artifacts.llmContext.signatures ?? [];
|
|
446
|
-
if (!cg || cg.nodes.length === 0) {
|
|
447
|
-
console.log(' ⚠ No call graph data — function index skipped');
|
|
448
|
-
}
|
|
449
|
-
else {
|
|
450
|
-
const hubIds = new Set(cg.hubFunctions.map(f => f.id));
|
|
451
|
-
const entryIds = new Set(cg.entryPoints.map(f => f.id));
|
|
452
|
-
await VectorIndex.build(outputPath, cg.nodes, sigs, hubIds, entryIds, embedSvc);
|
|
453
|
-
console.log(` ✓ Function index built (${cg.nodes.length} functions)`);
|
|
454
|
-
console.log(` → ${opts.output}vector-index/`);
|
|
455
|
-
}
|
|
456
|
-
// Also index specs if they exist
|
|
457
|
-
await runSpecIndexing(rootPath, outputPath, specGenConfig);
|
|
458
|
-
}
|
|
459
|
-
catch (embedErr) {
|
|
460
|
-
console.log(` ✗ Vector index failed: ${embedErr.message}`);
|
|
461
|
-
}
|
|
462
|
-
console.log('');
|
|
453
|
+
await runEmbedStep(rootPath, outputPath, specGenConfig, opts.force ?? false, result.artifacts.llmContext);
|
|
463
454
|
}
|
|
464
455
|
// Duration
|
|
465
456
|
const totalDuration = Date.now() - startTime;
|
|
@@ -478,6 +469,75 @@ After analysis, run 'spec-gen generate' to create OpenSpec files.
|
|
|
478
469
|
}
|
|
479
470
|
});
|
|
480
471
|
// ============================================================================
|
|
472
|
+
// EMBED STEP HELPER
|
|
473
|
+
// ============================================================================
|
|
474
|
+
/**
|
|
475
|
+
* Build (or incrementally update) the vector index from a LLMContext.
|
|
476
|
+
* When llmContext is null, reads llm-context.json from outputDir (cache path).
|
|
477
|
+
* Non-fatal: prints a warning on failure without throwing.
|
|
478
|
+
*/
|
|
479
|
+
async function runEmbedStep(rootPath, outputPath, specGenConfig, force, llmContext) {
|
|
480
|
+
console.log(' Building semantic vector index...');
|
|
481
|
+
try {
|
|
482
|
+
const { EmbeddingService } = await import('../../core/analyzer/embedding-service.js');
|
|
483
|
+
const { VectorIndex } = await import('../../core/analyzer/vector-index.js');
|
|
484
|
+
// Resolve embedding service
|
|
485
|
+
let embedSvc;
|
|
486
|
+
try {
|
|
487
|
+
embedSvc = EmbeddingService.fromEnv();
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
const cfg = specGenConfig ?? await readSpecGenConfig(rootPath);
|
|
491
|
+
if (!cfg)
|
|
492
|
+
throw new Error('No embedding config found. Set EMBED_BASE_URL and EMBED_MODEL, or add "embedding" to .spec-gen/config.json');
|
|
493
|
+
const svcFromConfig = EmbeddingService.fromConfig(cfg);
|
|
494
|
+
if (!svcFromConfig)
|
|
495
|
+
throw new Error('No embedding config found. Set EMBED_BASE_URL and EMBED_MODEL, or add "embedding" to .spec-gen/config.json');
|
|
496
|
+
embedSvc = svcFromConfig;
|
|
497
|
+
}
|
|
498
|
+
// Load context from disk if not provided (cache hit path)
|
|
499
|
+
if (!llmContext) {
|
|
500
|
+
try {
|
|
501
|
+
const raw = await readFile(join(outputPath, 'llm-context.json'), 'utf-8');
|
|
502
|
+
llmContext = JSON.parse(raw);
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
console.log(' ⚠ Could not read llm-context.json — run spec-gen analyze --force');
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
const cg = llmContext.callGraph;
|
|
510
|
+
const sigs = llmContext.signatures ?? [];
|
|
511
|
+
if (!cg || cg.nodes.length === 0) {
|
|
512
|
+
console.log(' ⚠ No call graph data — function index skipped');
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
const hubIds = new Set(cg.hubFunctions.map(f => f.id));
|
|
516
|
+
const entryIds = new Set(cg.entryPoints.map(f => f.id));
|
|
517
|
+
const fileContents = new Map();
|
|
518
|
+
const uniquePaths = new Set(cg.nodes.map(n => n.filePath));
|
|
519
|
+
await Promise.all([...uniquePaths].map(async (fp) => {
|
|
520
|
+
try {
|
|
521
|
+
fileContents.set(fp, await readFile(join(rootPath, fp), 'utf-8'));
|
|
522
|
+
}
|
|
523
|
+
catch { /* skip unreadable files */ }
|
|
524
|
+
}));
|
|
525
|
+
const { embedded, reused } = await VectorIndex.build(outputPath, cg.nodes, sigs, hubIds, entryIds, embedSvc, fileContents,
|
|
526
|
+
/* incremental */ !force);
|
|
527
|
+
const total = embedded + reused;
|
|
528
|
+
const cacheNote = reused > 0 ? ` (${embedded} embedded, ${reused} cached)` : '';
|
|
529
|
+
console.log(` ✓ Function index built (${total} functions${cacheNote}, ${fileContents.size} files with skeleton bodies)`);
|
|
530
|
+
console.log(` → ${outputPath.replace(rootPath + '/', '')}vector-index/`);
|
|
531
|
+
}
|
|
532
|
+
// Also index specs if they exist
|
|
533
|
+
await runSpecIndexing(rootPath, outputPath, specGenConfig);
|
|
534
|
+
}
|
|
535
|
+
catch (embedErr) {
|
|
536
|
+
console.log(` ✗ Vector index failed: ${embedErr.message}`);
|
|
537
|
+
}
|
|
538
|
+
console.log('');
|
|
539
|
+
}
|
|
540
|
+
// ============================================================================
|
|
481
541
|
// SPEC INDEXING HELPER
|
|
482
542
|
// ============================================================================
|
|
483
543
|
/**
|