@nomos-arc/arc 0.1.0
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/.claude/settings.local.json +10 -0
- package/.nomos-config.json +5 -0
- package/CLAUDE.md +108 -0
- package/LICENSE +190 -0
- package/README.md +569 -0
- package/dist/cli.js +21120 -0
- package/docs/auth/googel_plan.yaml +1093 -0
- package/docs/auth/google_task.md +235 -0
- package/docs/auth/hardened_blueprint.yaml +1658 -0
- package/docs/auth/red_team_report.yaml +336 -0
- package/docs/auth/session_state.yaml +162 -0
- package/docs/certificate/cer_enhance_plan.md +605 -0
- package/docs/certificate/certificate_report.md +338 -0
- package/docs/dev_overview.md +419 -0
- package/docs/feature_assessment.md +156 -0
- package/docs/how_it_works.md +78 -0
- package/docs/infrastructure/map.md +867 -0
- package/docs/init/master_plan.md +3581 -0
- package/docs/init/red_team_report.md +215 -0
- package/docs/init/report_phase_1a.md +304 -0
- package/docs/integrity-gate/enhance_drift.md +703 -0
- package/docs/integrity-gate/overview.md +108 -0
- package/docs/management/manger-task.md +99 -0
- package/docs/management/scafffold.md +76 -0
- package/docs/map/ATOMIC_BLUEPRINT.md +1349 -0
- package/docs/map/RED_TEAM_REPORT.md +159 -0
- package/docs/map/map_task.md +147 -0
- package/docs/map/semantic_graph_task.md +792 -0
- package/docs/map/semantic_master_plan.md +705 -0
- package/docs/phase7/TEAM_RED.md +249 -0
- package/docs/phase7/plan.md +1682 -0
- package/docs/phase7/task.md +275 -0
- package/docs/prompts/USAGE.md +312 -0
- package/docs/prompts/architect.md +165 -0
- package/docs/prompts/executer.md +190 -0
- package/docs/prompts/hardener.md +190 -0
- package/docs/prompts/red_team.md +146 -0
- package/docs/verification/goveranance-overview.md +396 -0
- package/docs/verification/governance-overview.md +245 -0
- package/docs/verification/verification-arc-ar.md +560 -0
- package/docs/verification/verification-architecture.md +560 -0
- package/docs/very_next.md +52 -0
- package/docs/whitepaper.md +89 -0
- package/overview.md +1469 -0
- package/package.json +63 -0
- package/src/adapters/__tests__/git.test.ts +296 -0
- package/src/adapters/__tests__/stdio.test.ts +70 -0
- package/src/adapters/git.ts +226 -0
- package/src/adapters/pty.ts +159 -0
- package/src/adapters/stdio.ts +113 -0
- package/src/cli.ts +83 -0
- package/src/commands/apply.ts +47 -0
- package/src/commands/auth.ts +301 -0
- package/src/commands/certificate.ts +89 -0
- package/src/commands/discard.ts +24 -0
- package/src/commands/drift.ts +116 -0
- package/src/commands/index.ts +78 -0
- package/src/commands/init.ts +121 -0
- package/src/commands/list.ts +75 -0
- package/src/commands/map.ts +55 -0
- package/src/commands/plan.ts +30 -0
- package/src/commands/review.ts +58 -0
- package/src/commands/run.ts +63 -0
- package/src/commands/search.ts +147 -0
- package/src/commands/show.ts +63 -0
- package/src/commands/status.ts +59 -0
- package/src/core/__tests__/budget.test.ts +213 -0
- package/src/core/__tests__/certificate.test.ts +385 -0
- package/src/core/__tests__/config.test.ts +191 -0
- package/src/core/__tests__/preflight.test.ts +24 -0
- package/src/core/__tests__/prompt.test.ts +358 -0
- package/src/core/__tests__/review.test.ts +161 -0
- package/src/core/__tests__/state.test.ts +362 -0
- package/src/core/auth/__tests__/manager.test.ts +166 -0
- package/src/core/auth/__tests__/server.test.ts +220 -0
- package/src/core/auth/gcp-projects.ts +160 -0
- package/src/core/auth/manager.ts +114 -0
- package/src/core/auth/server.ts +141 -0
- package/src/core/budget.ts +119 -0
- package/src/core/certificate.ts +502 -0
- package/src/core/config.ts +212 -0
- package/src/core/errors.ts +54 -0
- package/src/core/factory.ts +49 -0
- package/src/core/graph/__tests__/builder.test.ts +272 -0
- package/src/core/graph/__tests__/contract-writer.test.ts +175 -0
- package/src/core/graph/__tests__/enricher.test.ts +299 -0
- package/src/core/graph/__tests__/parser.test.ts +200 -0
- package/src/core/graph/__tests__/pipeline.test.ts +202 -0
- package/src/core/graph/__tests__/renderer.test.ts +128 -0
- package/src/core/graph/__tests__/resolver.test.ts +185 -0
- package/src/core/graph/__tests__/scanner.test.ts +231 -0
- package/src/core/graph/__tests__/show.test.ts +134 -0
- package/src/core/graph/builder.ts +303 -0
- package/src/core/graph/constraints.ts +94 -0
- package/src/core/graph/contract-writer.ts +93 -0
- package/src/core/graph/drift/__tests__/classifier.test.ts +215 -0
- package/src/core/graph/drift/__tests__/comparator.test.ts +335 -0
- package/src/core/graph/drift/__tests__/drift.test.ts +453 -0
- package/src/core/graph/drift/__tests__/reporter.test.ts +203 -0
- package/src/core/graph/drift/classifier.ts +165 -0
- package/src/core/graph/drift/comparator.ts +205 -0
- package/src/core/graph/drift/reporter.ts +77 -0
- package/src/core/graph/enricher.ts +251 -0
- package/src/core/graph/grammar-paths.ts +30 -0
- package/src/core/graph/html-template.ts +493 -0
- package/src/core/graph/map-schema.ts +137 -0
- package/src/core/graph/parser.ts +336 -0
- package/src/core/graph/pipeline.ts +209 -0
- package/src/core/graph/renderer.ts +92 -0
- package/src/core/graph/resolver.ts +195 -0
- package/src/core/graph/scanner.ts +145 -0
- package/src/core/logger.ts +46 -0
- package/src/core/orchestrator.ts +792 -0
- package/src/core/plan-file-manager.ts +66 -0
- package/src/core/preflight.ts +64 -0
- package/src/core/prompt.ts +173 -0
- package/src/core/review.ts +95 -0
- package/src/core/state.ts +294 -0
- package/src/core/worktree-coordinator.ts +77 -0
- package/src/search/__tests__/chunk-extractor.test.ts +339 -0
- package/src/search/__tests__/embedder-auth.test.ts +124 -0
- package/src/search/__tests__/embedder.test.ts +267 -0
- package/src/search/__tests__/graph-enricher.test.ts +178 -0
- package/src/search/__tests__/indexer.test.ts +518 -0
- package/src/search/__tests__/integration.test.ts +649 -0
- package/src/search/__tests__/query-engine.test.ts +334 -0
- package/src/search/__tests__/similarity.test.ts +78 -0
- package/src/search/__tests__/vector-store.test.ts +281 -0
- package/src/search/chunk-extractor.ts +167 -0
- package/src/search/embedder.ts +209 -0
- package/src/search/graph-enricher.ts +95 -0
- package/src/search/indexer.ts +483 -0
- package/src/search/lexical-searcher.ts +190 -0
- package/src/search/query-engine.ts +225 -0
- package/src/search/vector-store.ts +311 -0
- package/src/types/index.ts +572 -0
- package/src/utils/__tests__/ansi.test.ts +54 -0
- package/src/utils/__tests__/frontmatter.test.ts +79 -0
- package/src/utils/__tests__/sanitize.test.ts +229 -0
- package/src/utils/ansi.ts +19 -0
- package/src/utils/context.ts +44 -0
- package/src/utils/frontmatter.ts +27 -0
- package/src/utils/sanitize.ts +78 -0
- package/test/e2e/lifecycle.test.ts +330 -0
- package/test/fixtures/mock-planner-hang.ts +5 -0
- package/test/fixtures/mock-planner.ts +26 -0
- package/test/fixtures/mock-reviewer-bad.ts +8 -0
- package/test/fixtures/mock-reviewer-retry.ts +34 -0
- package/test/fixtures/mock-reviewer.ts +18 -0
- package/test/fixtures/sample-project/src/circular-a.ts +6 -0
- package/test/fixtures/sample-project/src/circular-b.ts +6 -0
- package/test/fixtures/sample-project/src/config.ts +15 -0
- package/test/fixtures/sample-project/src/main.ts +19 -0
- package/test/fixtures/sample-project/src/services/product-service.ts +20 -0
- package/test/fixtures/sample-project/src/services/user-service.ts +18 -0
- package/test/fixtures/sample-project/src/types.ts +14 -0
- package/test/fixtures/sample-project/src/utils/index.ts +14 -0
- package/test/fixtures/sample-project/src/utils/validate.ts +12 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
# Semantic Graph Master Execution Plan
|
|
2
|
+
|
|
3
|
+
## 1. Context & Objectives
|
|
4
|
+
- **Source Task:** `docs/map/semantic_graph_task.md`
|
|
5
|
+
- **End Goal:** Implement the `arc map` and `arc show map` commands that transform a raw codebase into a self-aware knowledge base via `project_map.json` — a semantically enriched dependency graph with static analysis (Phases 1–3), optional AI enrichment (Phase 4), architectural constraint injection into `arc plan` (Phase 5), and interactive Cytoscape.js visualization (Phase 6).
|
|
6
|
+
|
|
7
|
+
## 2. Prerequisite Check
|
|
8
|
+
- [ ] Check 1: Node.js >= 20 is installed — run `node --version` and verify output starts with `v20` or higher.
|
|
9
|
+
- [ ] Check 2: Project root is `/home/ibrahim@creativeft.local/apps/nomos-arc.ai` — run `ls package.json src/cli.ts` and verify both exist.
|
|
10
|
+
- [ ] Check 3: Existing dependencies install cleanly — run `npm install` and verify exit code 0.
|
|
11
|
+
- [ ] Check 4: Existing tests pass before any modifications — run `npx vitest run` and verify 0 failures.
|
|
12
|
+
- [ ] Check 5: Directory `src/core/graph/` does not yet exist — run `ls src/core/graph/ 2>&1` and verify "No such file or directory".
|
|
13
|
+
- [ ] Check 6: Directory `src/core/graph/__tests__/` does not yet exist — same check.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 3. Phased Atomic Execution Plan
|
|
18
|
+
|
|
19
|
+
### Phase 0: Prerequisites — Dependency & Configuration Setup
|
|
20
|
+
**Dependencies:** `package.json`, `src/core/errors.ts`, `.gitignore`
|
|
21
|
+
|
|
22
|
+
#### Step 0.1: Install runtime dependencies (`fast-glob`, `gitignore-parser`, `p-limit`, `open`)
|
|
23
|
+
- **Target File:** `package.json`
|
|
24
|
+
- **Action:** Run `npm install fast-glob gitignore-parser p-limit open`
|
|
25
|
+
- **Constraints:** Do not modify any other file. Do not touch devDependencies.
|
|
26
|
+
- **Verification Criteria:** Run `node -e "import('fast-glob').then(()=>console.log('ok'))"` — output is `ok`. Run `cat package.json | grep fast-glob` — line exists under `dependencies`.
|
|
27
|
+
- **Rollback/Failure Action:** Run `npm uninstall fast-glob gitignore-parser p-limit open` and re-evaluate.
|
|
28
|
+
|
|
29
|
+
#### Step 0.2: Install parsing & AI dependencies + update esbuild externals
|
|
30
|
+
- **Target File:** `package.json`
|
|
31
|
+
- **Action:**
|
|
32
|
+
1. Run `npm install tree-sitter tree-sitter-typescript @google/generative-ai`
|
|
33
|
+
2. In `package.json`, update the `"build"` script to append these flags to the existing esbuild command: `--external:tree-sitter --external:tree-sitter-typescript --external:@google/generative-ai --external:open`
|
|
34
|
+
- **Constraints:** Only modify the `"build"` script value. Keep all existing `--external:` flags intact. Do not change any other script.
|
|
35
|
+
- **Verification Criteria:**
|
|
36
|
+
1. Run `node -e "import('tree-sitter').then(()=>console.log('ok'))"` — output is `ok`.
|
|
37
|
+
2. Run `grep 'external:tree-sitter' package.json` — matches found.
|
|
38
|
+
3. Run `grep 'external:open' package.json` — match found.
|
|
39
|
+
4. Run `npm run build` — exit code 0, `dist/cli.js` is produced.
|
|
40
|
+
- **Rollback/Failure Action:** Revert `package.json` to git HEAD (`git checkout package.json`), then `npm install`.
|
|
41
|
+
|
|
42
|
+
#### Step 0.3: Add new `NomosErrorCode` values
|
|
43
|
+
- **Target File:** `src/core/errors.ts`
|
|
44
|
+
- **Action:** Add four new error code literals to the `NomosErrorCode` union type, immediately before the closing semicolon:
|
|
45
|
+
```
|
|
46
|
+
| 'graph_map_not_found'
|
|
47
|
+
| 'graph_parse_error'
|
|
48
|
+
| 'graph_ai_key_missing'
|
|
49
|
+
| 'graph_write_failed'
|
|
50
|
+
```
|
|
51
|
+
- **Constraints:** Do not modify the `NomosError` class. Do not remove or rename any existing error codes. Insert the new codes after `'certificate_invalid'`.
|
|
52
|
+
- **Verification Criteria:** Run `grep 'graph_map_not_found' src/core/errors.ts` — exactly 1 match. Run `grep 'graph_write_failed' src/core/errors.ts` — exactly 1 match. Run `npx tsc --noEmit` — exit code 0.
|
|
53
|
+
- **Rollback/Failure Action:** Revert file: `git checkout src/core/errors.ts`.
|
|
54
|
+
|
|
55
|
+
#### Step 0.4: Update `.gitignore` with semantic graph entries
|
|
56
|
+
- **Target File:** `.gitignore`
|
|
57
|
+
- **Action:** Append the following two lines at the end of the file:
|
|
58
|
+
```
|
|
59
|
+
*.semantic.md
|
|
60
|
+
tasks-management/graph/
|
|
61
|
+
```
|
|
62
|
+
- **Constraints:** Do not remove or modify any existing gitignore entries.
|
|
63
|
+
- **Verification Criteria:** Run `grep '*.semantic.md' .gitignore` — exactly 1 match. Run `grep 'tasks-management/graph/' .gitignore` — exactly 1 match.
|
|
64
|
+
- **Rollback/Failure Action:** Remove the appended lines manually.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### Phase 1: Foundation — Types, Schema, and File Discovery
|
|
69
|
+
**Dependencies:** Phase 0 complete (all 4 steps verified).
|
|
70
|
+
|
|
71
|
+
#### Step 1.1: Define all Semantic Graph types in `src/types/index.ts`
|
|
72
|
+
- **Target File:** `src/types/index.ts`
|
|
73
|
+
- **Action:** Append the following type definitions at the end of the file (before any final closing comment, if present). Use the exact interfaces from the task specification:
|
|
74
|
+
- `SymbolEntry` — `{ name: string; kind: 'class' | 'function' | 'method' | 'interface' | 'type' | 'enum' | 'variable' | 'export'; line: number; end_line: number | null; signature: string | null; exported: boolean; }`
|
|
75
|
+
- `ImportEntry` — `{ source: string; resolved: string | null; symbols: string[]; is_external: boolean; }`
|
|
76
|
+
- `SemanticInfo` — `{ overview: string; purpose: string; key_logic: string[]; usage_context: string[]; source_hash: string; enriched_at: string; model: string; }`
|
|
77
|
+
- `FileNode` — `{ file: string; hash: string; language: string; symbols: SymbolEntry[]; imports: ImportEntry[]; dependents: string[]; dependencies: string[]; depth: number; last_parsed_at: string | null; semantic: SemanticInfo | null; }`
|
|
78
|
+
- `ProjectMap` — `{ schema_version: 1; generated_at: string; root: string; files: Record<string, FileNode>; stats: { total_files: number; total_symbols: number; total_edges: number; core_modules: string[]; }; }`
|
|
79
|
+
- **Constraints:** Export all interfaces. Do not modify any existing types. Place under a clear section comment `// ─── Semantic Graph Types ───`.
|
|
80
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'export interface FileNode' src/types/index.ts` — exactly 1 match. Run `grep 'export interface ProjectMap' src/types/index.ts` — exactly 1 match.
|
|
81
|
+
- **Rollback/Failure Action:** Remove the appended type block and retry.
|
|
82
|
+
|
|
83
|
+
#### Step 1.2: Add `graph` section to NomosConfig and GraphConfigSchema
|
|
84
|
+
- **Target Files:** `src/types/index.ts`, `src/core/config.ts`
|
|
85
|
+
- **Action:**
|
|
86
|
+
1. In `src/types/index.ts`, add a `graph` property to the `NomosConfig` interface:
|
|
87
|
+
```typescript
|
|
88
|
+
graph: {
|
|
89
|
+
exclude_patterns: string[];
|
|
90
|
+
ai_enrichment: boolean;
|
|
91
|
+
ai_model: string;
|
|
92
|
+
ai_concurrency: number;
|
|
93
|
+
ai_requests_per_minute: number;
|
|
94
|
+
max_file_chars: number;
|
|
95
|
+
core_modules_count: number;
|
|
96
|
+
output_dir: string;
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
2. In `src/core/config.ts`, define `GraphConfigSchema` as a Zod object with defaults matching the task spec:
|
|
100
|
+
```typescript
|
|
101
|
+
const GraphConfigSchema = z.object({
|
|
102
|
+
exclude_patterns: z.array(z.string()).default(['node_modules', 'dist', '*.test.*', '*.spec.*', '*.semantic.md']),
|
|
103
|
+
ai_enrichment: z.boolean().default(true),
|
|
104
|
+
ai_model: z.string().default('gemini-1.5-flash'),
|
|
105
|
+
ai_concurrency: z.number().int().positive().default(5),
|
|
106
|
+
ai_requests_per_minute: z.number().int().positive().default(14),
|
|
107
|
+
max_file_chars: z.number().positive().default(4000),
|
|
108
|
+
core_modules_count: z.number().int().positive().default(10),
|
|
109
|
+
output_dir: z.string().default('tasks-management/graph'),
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
3. Add `graph: GraphConfigSchema.default(() => GraphConfigSchema.parse({}))` to the `NomosConfigSchema` Zod object.
|
|
113
|
+
- **Constraints:** Do not modify existing schema fields. Follow the same `.default(() => Schema.parse({}))` pattern used for other nested objects in `config.ts`.
|
|
114
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'GraphConfigSchema' src/core/config.ts` — at least 2 matches (definition + usage). Run `npx vitest run` — all existing tests still pass.
|
|
115
|
+
- **Rollback/Failure Action:** Revert both files: `git checkout src/types/index.ts src/core/config.ts`.
|
|
116
|
+
|
|
117
|
+
#### Step 1.3: Implement `ProjectMapSchema`, `migrateProjectMap()`, and `readProjectMap()`
|
|
118
|
+
- **Target File:** `src/core/graph/map-schema.ts` (new file)
|
|
119
|
+
- **Action:** Create the file with:
|
|
120
|
+
1. Zod schemas for `SymbolEntrySchema`, `ImportEntrySchema`, `SemanticInfoSchema`, `FileNodeSchema`, `ProjectMapSchema` — each matching the TypeScript interfaces from Step 1.1.
|
|
121
|
+
2. `migrations` record (`Record<number, (raw: unknown) => unknown>`) — empty for now.
|
|
122
|
+
3. `migrateProjectMap(raw: unknown): ProjectMap` — applies migrations from `schema_version` to current, then validates with `ProjectMapSchema.parse()`.
|
|
123
|
+
4. `readProjectMap(mapPath: string): Promise<ProjectMap | null>` — reads JSON from `mapPath`, returns `null` if file does not exist (`ENOENT`), throws `NomosError('graph_parse_error', ...)` on parse/validation failure.
|
|
124
|
+
- **Constraints:** Use ESM imports with `.js` extensions. Import `NomosError` from `../errors.js`. Import types from `../../types/index.js`. Use `fs/promises` for file I/O.
|
|
125
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'export const ProjectMapSchema' src/core/graph/map-schema.ts` — exactly 1 match. Run `grep 'export function migrateProjectMap' src/core/graph/map-schema.ts` — exactly 1 match. Run `grep 'export async function readProjectMap' src/core/graph/map-schema.ts` — exactly 1 match.
|
|
126
|
+
- **Rollback/Failure Action:** Delete `src/core/graph/map-schema.ts` and retry.
|
|
127
|
+
|
|
128
|
+
#### Step 1.4: Implement `FileScanner`
|
|
129
|
+
- **Target File:** `src/core/graph/scanner.ts` (new file)
|
|
130
|
+
- **Action:** Create a `FileScanner` class with:
|
|
131
|
+
1. Constructor: `(projectRoot: string, config: NomosConfig['graph'], logger: Logger)`
|
|
132
|
+
2. `LANGUAGE_MAP` constant: maps file extensions (`.ts`, `.tsx`, `.mts`, `.cts`, `.d.ts`, `.js`, `.mjs`, `.cjs`, `.jsx`, `.py`, `.go`, `.rs`) to language strings. Defined as module-level constant.
|
|
133
|
+
3. `scan(existingMap: ProjectMap | null, force: boolean, globPatterns?: string[]): Promise<ScanResult>` where `ScanResult = { files: Map<string, { file: string; hash: string; language: string; content: string }>; carried: Map<string, FileNode> }`.
|
|
134
|
+
4. Logic:
|
|
135
|
+
a. Read `.gitignore` at project root using `gitignore-parser`. If file missing, use empty ignore list.
|
|
136
|
+
b. Use `fast-glob` with patterns `**/*` (or user-provided globs), applying both gitignore patterns and `config.exclude_patterns` as `ignore` option.
|
|
137
|
+
c. For each matched file: check extension against `LANGUAGE_MAP` — skip if unknown. Compute `sha256:<hex>` hash using `node:crypto`. If file unreadable (`EACCES`, `ENOENT`), log warning and skip.
|
|
138
|
+
d. If `existingMap` is not null and `force` is false: compare hash. If unchanged, add to `carried` map (existing `FileNode` carried forward). Otherwise add to `files` map (needs parsing).
|
|
139
|
+
e. If `existingMap` is null or `force` is true: add all to `files` map.
|
|
140
|
+
- **Constraints:** Do not import `tree-sitter`. Do not perform AST parsing. Export `LANGUAGE_MAP` for use by other modules. All file paths must be relative to project root.
|
|
141
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'export class FileScanner' src/core/graph/scanner.ts` — exactly 1 match. Run `grep 'LANGUAGE_MAP' src/core/graph/scanner.ts` — at least 1 match.
|
|
142
|
+
- **Rollback/Failure Action:** Delete `src/core/graph/scanner.ts` and retry.
|
|
143
|
+
|
|
144
|
+
#### Step 1.5: Test `FileScanner`
|
|
145
|
+
- **Target File:** `src/core/graph/__tests__/scanner.test.ts` (new file)
|
|
146
|
+
- **Action:** Write vitest tests covering:
|
|
147
|
+
1. Finds `.ts` and `.js` files in a temporary directory.
|
|
148
|
+
2. Computes correct SHA-256 hashes.
|
|
149
|
+
3. Detects correct language from extension.
|
|
150
|
+
4. Excludes `node_modules` directory.
|
|
151
|
+
5. Handles unreadable file gracefully (no throw).
|
|
152
|
+
6. Skips files with unknown extensions (e.g., `.png`).
|
|
153
|
+
7. Carries forward unchanged `FileNode` from existing map when `force=false`.
|
|
154
|
+
8. Re-scans all files when `force=true` even if hashes match.
|
|
155
|
+
- **Constraints:** Use `vitest` (`describe`, `it`, `expect`). Create temporary fixture files using `fs/promises` in a `beforeEach` / `afterEach` with `os.tmpdir()`. Mock the logger with `{ warn: vi.fn(), info: vi.fn(), error: vi.fn(), debug: vi.fn() }`.
|
|
156
|
+
- **Verification Criteria:** Run `npx vitest run src/core/graph/__tests__/scanner.test.ts` — all tests pass, exit code 0.
|
|
157
|
+
- **Rollback/Failure Action:** Fix failing tests. If fundamentally broken, delete test file and re-evaluate Step 1.4.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
### Phase 2: AST Parsing & Symbol Extraction
|
|
162
|
+
**Dependencies:** Phase 1 complete (Steps 1.1–1.5 verified).
|
|
163
|
+
|
|
164
|
+
#### Step 2.1: Implement `ASTParser` — grammar selection + symbol extraction
|
|
165
|
+
- **Target File:** `src/core/graph/parser.ts` (new file)
|
|
166
|
+
- **Action:** Create an `ASTParser` class with:
|
|
167
|
+
1. Constructor: `(logger: Logger)` — initializes `tree-sitter` `Parser` instance.
|
|
168
|
+
2. `parse(filePath: string, content: string, language: string): ParseResult` where `ParseResult = { symbols: SymbolEntry[]; imports: ImportEntry[] }`.
|
|
169
|
+
3. Grammar selection: Use `tree-sitter-typescript`'s `tsx` grammar for `.tsx` files; `typescript` grammar for all other TS variants. Use `javascript` grammar for JS files. For unsupported languages (`python`, `go`, `rust`), return empty `symbols` and `imports` with a debug log (Phase 1 only supports TS/JS).
|
|
170
|
+
4. Error-node threshold: After parsing, count total character length of all `ERROR` nodes. If `errorChars / totalChars > 0.20`, log `[nomos:graph:warn] Parse errors in {filePath} — symbols may be incomplete` and return empty symbols + imports.
|
|
171
|
+
5. Symbol extraction via tree walk:
|
|
172
|
+
- `function_declaration`, `arrow_function` (when parent is `variable_declarator` or `export_statement`) → `kind: 'function'`
|
|
173
|
+
- `class_declaration` → `kind: 'class'`; walk child `method_definition` nodes → `kind: 'method'`, `name: 'ClassName.methodName'`
|
|
174
|
+
- `interface_declaration` → `kind: 'interface'`
|
|
175
|
+
- `type_alias_declaration` → `kind: 'type'`
|
|
176
|
+
- `enum_declaration` → `kind: 'enum'`
|
|
177
|
+
- Top-level `lexical_declaration` with `export` parent → `kind: 'variable'`
|
|
178
|
+
6. For each symbol: extract `line` (1-indexed from `node.startPosition.row + 1`), `end_line` (`node.endPosition.row + 1`), `exported` (parent or grandparent is `export_statement`).
|
|
179
|
+
7. Signature capture: For functions, extract text from name through closing `)` of params + return type annotation. For methods, include `async`/access modifiers.
|
|
180
|
+
- **Constraints:** Import `tree-sitter` and `tree-sitter-typescript` as external ESM modules. Do not parse imports in this step — that is Step 2.2.
|
|
181
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'export class ASTParser' src/core/graph/parser.ts` — exactly 1 match.
|
|
182
|
+
- **Rollback/Failure Action:** Delete `src/core/graph/parser.ts` and retry.
|
|
183
|
+
|
|
184
|
+
#### Step 2.2: Implement import extraction in `ASTParser`
|
|
185
|
+
- **Target File:** `src/core/graph/parser.ts` (modify existing)
|
|
186
|
+
- **Action:** In the `parse()` method's tree walk, add extraction of:
|
|
187
|
+
1. `import_statement` nodes: extract `source` from the string literal child, extract `symbols` from named import specifiers (e.g., `{ z, ZodSchema }` → `['z', 'ZodSchema']`). Default/namespace imports record an empty `symbols` array.
|
|
188
|
+
2. `call_expression` with callee name `require`: extract `source` from the first argument string literal.
|
|
189
|
+
3. For each extracted import: create `ImportEntry` with `source`, `symbols`, `resolved: null` (filled by Phase 3 resolver), `is_external: false` (tentative — resolver will correct this).
|
|
190
|
+
- **Constraints:** Only modify the `parse()` method. Do not resolve import paths — leave `resolved: null`.
|
|
191
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'import_statement' src/core/graph/parser.ts` — at least 1 match.
|
|
192
|
+
- **Rollback/Failure Action:** Revert changes to `src/core/graph/parser.ts` (keep Step 2.1 work intact).
|
|
193
|
+
|
|
194
|
+
#### Step 2.3: Test `ASTParser`
|
|
195
|
+
- **Target File:** `src/core/graph/__tests__/parser.test.ts` (new file)
|
|
196
|
+
- **Action:** Write vitest tests covering:
|
|
197
|
+
1. Extracts function declarations (name, line, end_line, exported flag, signature).
|
|
198
|
+
2. Extracts class declarations and their methods as `kind: 'method'` with `ClassName.methodName` naming.
|
|
199
|
+
3. Extracts interface, type, and enum declarations.
|
|
200
|
+
4. Extracts `export const` variable declarations.
|
|
201
|
+
5. Selects `tsx` grammar for `.tsx` extension — test with JSX content.
|
|
202
|
+
6. Error-node threshold: feed malformed TypeScript content (> 20% errors) → returns empty arrays.
|
|
203
|
+
7. Extracts import statements with named symbols.
|
|
204
|
+
8. Extracts `require()` calls.
|
|
205
|
+
9. Import `resolved` field is always `null`.
|
|
206
|
+
- **Constraints:** Use inline TypeScript strings as test input (no fixture files needed). Create a real `ASTParser` instance (tree-sitter is a real dependency, not mocked).
|
|
207
|
+
- **Verification Criteria:** Run `npx vitest run src/core/graph/__tests__/parser.test.ts` — all tests pass, exit code 0.
|
|
208
|
+
- **Rollback/Failure Action:** Fix failing tests. If tree-sitter loading fails, verify Step 0.2 externals configuration.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
### Phase 3: Import Resolution & Dependency Graph
|
|
213
|
+
**Dependencies:** Phase 2 complete (Steps 2.1–2.3 verified).
|
|
214
|
+
|
|
215
|
+
#### Step 3.1: Implement `ImportResolver`
|
|
216
|
+
- **Target File:** `src/core/graph/resolver.ts` (new file)
|
|
217
|
+
- **Action:** Create an `ImportResolver` class with:
|
|
218
|
+
1. Constructor: `(projectRoot: string, logger: Logger)` — reads `tsconfig.json` from project root to extract `compilerOptions.paths`. If `tsconfig.json` missing or has no `paths`, stores empty alias map.
|
|
219
|
+
2. `resolve(importSource: string, importerFile: string, knownFiles: Set<string>): ResolveResult` where `ResolveResult = { resolved: string | null; is_external: boolean }`.
|
|
220
|
+
3. Resolution logic (in order):
|
|
221
|
+
a. **External check:** If `importSource` does not start with `.`, `/`, or a matched tsconfig alias prefix → `{ resolved: null, is_external: true }`.
|
|
222
|
+
b. **Tsconfig alias resolution:** Match `importSource` against `compilerOptions.paths` keys (supporting `*` wildcard). On match, substitute the capture into the first value pattern, then continue to relative resolution below.
|
|
223
|
+
c. **Relative path resolution:** Compute absolute path from `importerFile`'s directory. Try extensions in order: `.ts`, `.tsx`, `.js`. Then try `/index.ts`, `/index.tsx`, `/index.js`. Check each against `knownFiles` set (relative paths). First match wins → `{ resolved: matchedRelativePath, is_external: false }`.
|
|
224
|
+
d. **Path traversal guard:** If resolved absolute path falls outside `projectRoot`, log warning → `{ resolved: null, is_external: true }`.
|
|
225
|
+
e. **Unresolvable:** Log `[nomos:graph:warn] Cannot resolve import '{importSource}' from '{importerFile}'` → `{ resolved: null, is_external: false }`.
|
|
226
|
+
- **Constraints:** Do not use any external package — pure Node.js path manipulation + `fs.existsSync` for alias resolution file checks. All returned paths must be relative to project root using forward slashes.
|
|
227
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'export class ImportResolver' src/core/graph/resolver.ts` — exactly 1 match.
|
|
228
|
+
- **Rollback/Failure Action:** Delete `src/core/graph/resolver.ts` and retry.
|
|
229
|
+
|
|
230
|
+
#### Step 3.2: Implement `GraphBuilder`
|
|
231
|
+
- **Target File:** `src/core/graph/builder.ts` (new file)
|
|
232
|
+
- **Action:** Create a `GraphBuilder` class with:
|
|
233
|
+
1. Constructor: `(config: NomosConfig['graph'], logger: Logger)`
|
|
234
|
+
2. `build(fileNodes: Map<string, FileNode>): void` — mutates `FileNode` objects in-place to populate `dependencies`, `dependents`, and `depth` fields, and returns the `stats` object.
|
|
235
|
+
3. Logic:
|
|
236
|
+
a. **Adjacency list:** For each file, iterate its `imports[]`. For each import with `resolved !== null` and `is_external === false`: add `resolved` to the file's `dependencies[]`; add the file to the resolved target's `dependents[]`.
|
|
237
|
+
b. **Kahn's BFS topological sort:**
|
|
238
|
+
- Compute in-degree for each node = `dependents.length` (after populating dependents).
|
|
239
|
+
- Initialize queue with all nodes having in-degree 0 → assign `depth = 0`.
|
|
240
|
+
- BFS: for each dequeued node, decrement in-degree of all its dependents; if in-degree reaches 0, enqueue and assign `depth = currentDepth + 1`.
|
|
241
|
+
c. **Cycle detection:** After BFS, any node with remaining in-degree > 0 is in a cycle. Find the max depth among nodes that have edges entering the cycle, assign cyclic nodes `depth = maxDepth + 1`. Log: `[nomos:graph:warn] Circular dependency detected: {cycle path}`.
|
|
242
|
+
d. **Stats:** `total_files`, `total_symbols` (sum of all `symbols.length`), `total_edges` (count of non-external resolved imports), `core_modules` (top N files sorted by `depth` descending, N = `config.core_modules_count`).
|
|
243
|
+
4. Return type: `{ total_files: number; total_symbols: number; total_edges: number; core_modules: string[] }`.
|
|
244
|
+
- **Constraints:** Mutate `FileNode` objects in-place (they are already in the map). Do not clone. `dependencies` and `dependents` arrays must be deduped (use `Set` during construction, convert to array at end).
|
|
245
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'export class GraphBuilder' src/core/graph/builder.ts` — exactly 1 match. Run `grep 'Kahn' src/core/graph/builder.ts` — at least 1 match (in comment).
|
|
246
|
+
- **Rollback/Failure Action:** Delete `src/core/graph/builder.ts` and retry.
|
|
247
|
+
|
|
248
|
+
#### Step 3.3: Test `ImportResolver`
|
|
249
|
+
- **Target File:** `src/core/graph/__tests__/resolver.test.ts` (new file)
|
|
250
|
+
- **Action:** Write vitest tests covering:
|
|
251
|
+
1. Resolves `./utils/hash` → `src/utils/hash.ts` (appends `.ts` extension).
|
|
252
|
+
2. Resolves `./utils` → `src/utils/index.ts` (index file resolution).
|
|
253
|
+
3. Resolves tsconfig alias `@/utils` → `src/utils/index.ts` (wildcard substitution).
|
|
254
|
+
4. Identifies `zod` as external (`is_external: true`, `resolved: null`).
|
|
255
|
+
5. Identifies `commander` as external.
|
|
256
|
+
6. Path traversal: `../../outside-project` → treated as external with warning.
|
|
257
|
+
7. Unresolvable relative import → `resolved: null`, `is_external: false`.
|
|
258
|
+
8. Missing `tsconfig.json` → alias resolution skipped gracefully.
|
|
259
|
+
- **Constraints:** Use a temporary fixture directory with actual files for resolution checks. Mock the logger.
|
|
260
|
+
- **Verification Criteria:** Run `npx vitest run src/core/graph/__tests__/resolver.test.ts` — all tests pass, exit code 0.
|
|
261
|
+
- **Rollback/Failure Action:** Fix failing tests or re-evaluate Step 3.1 logic.
|
|
262
|
+
|
|
263
|
+
#### Step 3.4: Test `GraphBuilder`
|
|
264
|
+
- **Target File:** `src/core/graph/__tests__/builder.test.ts` (new file)
|
|
265
|
+
- **Action:** Write vitest tests covering:
|
|
266
|
+
1. Simple chain: A imports B, B imports C → depths: A=0, B=1, C=2.
|
|
267
|
+
2. Fan-in: A imports C, B imports C → C has highest depth, A and B are depth 0.
|
|
268
|
+
3. Forward/reverse edges: `A.dependencies` includes `B.file`, `B.dependents` includes `A.file`.
|
|
269
|
+
4. 2-node cycle: A imports B, B imports A → both assigned `depth = max + 1`, warning logged.
|
|
270
|
+
5. 3-node cycle: A→B→C→A → all three assigned cycle depth, warning logged.
|
|
271
|
+
6. `core_modules` extraction: returns top N files by depth.
|
|
272
|
+
7. Stats: `total_files`, `total_symbols`, `total_edges` are correct.
|
|
273
|
+
- **Constraints:** Create `FileNode` objects directly in test code (no disk I/O needed). Mock the logger.
|
|
274
|
+
- **Verification Criteria:** Run `npx vitest run src/core/graph/__tests__/builder.test.ts` — all tests pass, exit code 0.
|
|
275
|
+
- **Rollback/Failure Action:** Fix failing tests or re-evaluate Step 3.2 logic.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### Phase 4: AI Semantic Enrichment
|
|
280
|
+
**Dependencies:** Phase 3 complete (Steps 3.1–3.4 verified).
|
|
281
|
+
|
|
282
|
+
#### Step 4.1: Implement `SemanticEnricher`
|
|
283
|
+
- **Target File:** `src/core/graph/enricher.ts` (new file)
|
|
284
|
+
- **Action:** Create a `SemanticEnricher` class with:
|
|
285
|
+
1. Constructor: `(config: NomosConfig['graph'], logger: Logger)` — validates `process.env.GEMINI_API_KEY` exists; if absent and `config.ai_enrichment` is true, throws `NomosError('graph_ai_key_missing', 'GEMINI_API_KEY environment variable is not set.')`. Initializes `GoogleGenerativeAI` client and `p-limit` limiter with `config.ai_concurrency`.
|
|
286
|
+
2. `enrich(fileNodes: Map<string, FileNode>): Promise<void>` — mutates `FileNode.semantic` in-place.
|
|
287
|
+
3. Logic per file:
|
|
288
|
+
a. **Staleness check:** Skip if `semantic !== null` AND `semantic.source_hash === hash`.
|
|
289
|
+
b. **Prompt construction:** Truncate file content at last complete line boundary before `config.max_file_chars` characters. Build prompt per task spec format: File, Language, Exports, Used by, file content, JSON schema instruction.
|
|
290
|
+
c. **Gemini API call:** Use `model.generateContent()` with `generationConfig.responseSchema` for structured JSON output.
|
|
291
|
+
d. **Rate limiting:** Use `p-limit` for concurrency. Enforce minimum inter-request gap of `Math.ceil(60000 / config.ai_requests_per_minute)` ms using a timestamp tracker.
|
|
292
|
+
e. **Retry policy:** On HTTP 429 or 503, retry up to 3 times with exponential backoff (2s, 4s, 8s). After 3 failures, set `semantic: null` and log error.
|
|
293
|
+
f. **Zod validation:** Validate Gemini response against `SemanticInfoSchema` (from `map-schema.ts`). If validation fails, log raw response (truncated to 200 chars), set `semantic: null`.
|
|
294
|
+
g. **Populate `SemanticInfo`:** `{ overview, purpose, key_logic, usage_context, source_hash: fileNode.hash, enriched_at: new Date().toISOString(), model: config.ai_model }`.
|
|
295
|
+
- **Constraints:** Never throw on a per-file failure — only log and set `semantic: null`. The only throw is in constructor for missing API key.
|
|
296
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'export class SemanticEnricher' src/core/graph/enricher.ts` — exactly 1 match.
|
|
297
|
+
- **Rollback/Failure Action:** Delete `src/core/graph/enricher.ts` and retry.
|
|
298
|
+
|
|
299
|
+
#### Step 4.2: Implement `ContractWriter`
|
|
300
|
+
- **Target File:** `src/core/graph/contract-writer.ts` (new file)
|
|
301
|
+
- **Action:** Create a `ContractWriter` class with:
|
|
302
|
+
1. Constructor: `(projectRoot: string, logger: Logger)`
|
|
303
|
+
2. `writeContracts(fileNodes: Map<string, FileNode>): Promise<void>`
|
|
304
|
+
3. Logic per file:
|
|
305
|
+
a. Skip if `semantic === null`.
|
|
306
|
+
b. Compute output path: replace source extension with `.semantic.md` (e.g., `src/core/state.ts` → `src/core/state.semantic.md`).
|
|
307
|
+
c. **Overwrite skip:** If `.semantic.md` file already exists, read its content and check if it contains the current `source_hash`. If hash matches, skip writing.
|
|
308
|
+
d. Write markdown using the template from the task spec:
|
|
309
|
+
```markdown
|
|
310
|
+
# {filename} — Semantic Contract
|
|
311
|
+
> Auto-generated by `arc map` — do not edit manually.
|
|
312
|
+
|
|
313
|
+
## Overview
|
|
314
|
+
{overview}
|
|
315
|
+
|
|
316
|
+
## Purpose
|
|
317
|
+
{purpose}
|
|
318
|
+
|
|
319
|
+
## Key Logic
|
|
320
|
+
1. {key_logic[0]}
|
|
321
|
+
2. {key_logic[1]}
|
|
322
|
+
...
|
|
323
|
+
|
|
324
|
+
## Usage Context
|
|
325
|
+
Used by: {dependents}
|
|
326
|
+
- {usage_context[0]}
|
|
327
|
+
- {usage_context[1]}
|
|
328
|
+
...
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
*Enriched at: {enriched_at} | Model: {model}*
|
|
332
|
+
```
|
|
333
|
+
- **Constraints:** Use `fs/promises`. Do not create directories — source files already exist so their parent directories exist. Log each write at debug level.
|
|
334
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'export class ContractWriter' src/core/graph/contract-writer.ts` — exactly 1 match.
|
|
335
|
+
- **Rollback/Failure Action:** Delete `src/core/graph/contract-writer.ts` and retry.
|
|
336
|
+
|
|
337
|
+
#### Step 4.3: Test `SemanticEnricher`
|
|
338
|
+
- **Target File:** `src/core/graph/__tests__/enricher.test.ts` (new file)
|
|
339
|
+
- **Action:** Write vitest tests with `vi.mock('@google/generative-ai')` covering:
|
|
340
|
+
1. Enriches a file with null semantic — mock returns valid JSON → `semantic` populated.
|
|
341
|
+
2. Staleness check: file with matching `source_hash` → skipped (no API call).
|
|
342
|
+
3. Zod validation failure: mock returns `{ overview: 123 }` (wrong type) → `semantic: null`.
|
|
343
|
+
4. Retry on 429: mock throws 429 twice then succeeds → `semantic` populated.
|
|
344
|
+
5. Key missing: `GEMINI_API_KEY` unset → constructor throws `NomosError('graph_ai_key_missing')`.
|
|
345
|
+
6. All retries exhausted: mock throws 429 four times → `semantic: null`, error logged.
|
|
346
|
+
- **Constraints:** Mock `@google/generative-ai` at module level with `vi.mock()`. Save/restore `process.env.GEMINI_API_KEY` in `beforeEach`/`afterEach`.
|
|
347
|
+
- **Verification Criteria:** Run `npx vitest run src/core/graph/__tests__/enricher.test.ts` — all tests pass, exit code 0.
|
|
348
|
+
- **Rollback/Failure Action:** Fix failing tests or re-evaluate Step 4.1.
|
|
349
|
+
|
|
350
|
+
#### Step 4.4: Test `ContractWriter`
|
|
351
|
+
- **Target File:** `src/core/graph/__tests__/contract-writer.test.ts` (new file)
|
|
352
|
+
- **Action:** Write vitest tests covering:
|
|
353
|
+
1. Writes `.semantic.md` with correct markdown structure and all four sections.
|
|
354
|
+
2. Skips writing when `source_hash` in existing `.semantic.md` matches current hash.
|
|
355
|
+
3. Overwrites when hash has changed.
|
|
356
|
+
4. Skips files with `semantic: null`.
|
|
357
|
+
- **Constraints:** Use temporary directory for output. Verify file contents with `fs/promises.readFile`.
|
|
358
|
+
- **Verification Criteria:** Run `npx vitest run src/core/graph/__tests__/contract-writer.test.ts` — all tests pass, exit code 0.
|
|
359
|
+
- **Rollback/Failure Action:** Fix failing tests or re-evaluate Step 4.2.
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
### Phase 5: Command, Pipeline, and Integration
|
|
364
|
+
**Dependencies:** Phase 4 complete (Steps 4.1–4.4 verified).
|
|
365
|
+
|
|
366
|
+
#### Step 5.1: Implement `MapPipeline`
|
|
367
|
+
- **Target File:** `src/core/graph/pipeline.ts` (new file)
|
|
368
|
+
- **Action:** Create a `MapPipeline` class that chains all phases:
|
|
369
|
+
1. Constructor: `(config: NomosConfig, projectRoot: string, logger: Logger)`
|
|
370
|
+
2. `run(options: { noAi: boolean; force: boolean; patterns?: string[] }): Promise<{ map: ProjectMap; aiFailures: number }>` — the main execution method.
|
|
371
|
+
3. Pipeline flow:
|
|
372
|
+
a. Read existing `project_map.json` via `readProjectMap()` (returns null if missing).
|
|
373
|
+
b. Call `FileScanner.scan()` → get `files` (need parsing) and `carried` (unchanged).
|
|
374
|
+
c. If `files` is empty and `carried` is empty: log warning `No files matched`, return early with empty map.
|
|
375
|
+
d. For each file in `files`: call `ASTParser.parse()` → build `FileNode` with `symbols`, `imports`, `hash`, `language`, `last_parsed_at: new Date().toISOString()`.
|
|
376
|
+
e. Merge parsed `FileNode` objects with `carried` `FileNode` objects into a single `Map<string, FileNode>`.
|
|
377
|
+
f. Initialize `ImportResolver`, call `resolve()` for every `ImportEntry` in every `FileNode` → populate `resolved` and `is_external`.
|
|
378
|
+
g. Call `GraphBuilder.build()` → mutates `dependencies`, `dependents`, `depth` → returns `stats`.
|
|
379
|
+
h. If `noAi` is false: call `SemanticEnricher.enrich()` → mutates `semantic` fields. Then call `ContractWriter.writeContracts()`.
|
|
380
|
+
i. Construct `ProjectMap` object with `schema_version: 1`, `generated_at`, `root`, `files`, `stats`.
|
|
381
|
+
j. Atomic write to `{config.graph.output_dir}/project_map.json` using `tmp → fsync → rename` pattern. Create output directory with `fs.mkdir(outDir, { recursive: true })` first. On write failure, throw `NomosError('graph_write_failed', ...)`.
|
|
382
|
+
k. Count AI failures (files with `semantic: null` that were attempted) and return.
|
|
383
|
+
- **Constraints:** Do not duplicate atomic write logic from `state.ts` — implement it inline following the same pattern. The pipeline must not throw on partial AI failures — only on I/O failures.
|
|
384
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'export class MapPipeline' src/core/graph/pipeline.ts` — exactly 1 match.
|
|
385
|
+
- **Rollback/Failure Action:** Delete `src/core/graph/pipeline.ts` and retry.
|
|
386
|
+
|
|
387
|
+
#### Step 5.2: Implement `arc map` command
|
|
388
|
+
- **Target File:** `src/commands/map.ts` (new file)
|
|
389
|
+
- **Action:** Create a `registerMapCommand(program: Command): void` function that registers `arc map`:
|
|
390
|
+
1. Options: `--no-ai` (boolean flag), `--force` (boolean flag), variadic `[patterns...]` argument.
|
|
391
|
+
2. Action handler:
|
|
392
|
+
a. Load config via `loadConfig()`.
|
|
393
|
+
b. If `--no-ai` is set, override `config.graph.ai_enrichment = false`.
|
|
394
|
+
c. Create `MapPipeline` and call `run()`.
|
|
395
|
+
d. Print summary to stdout: `Mapped {N} files, enriched {M}, {K} skipped`.
|
|
396
|
+
e. Exit codes: `0` on full success, `1` on fatal error (catch `NomosError`), `2` if `aiFailures > 0` but map was written.
|
|
397
|
+
3. Progress output to stderr: files scanned count, AI per-file status.
|
|
398
|
+
- **Constraints:** Follow the same pattern as existing commands in `src/commands/`. Import `loadConfig` from `../core/config.js`. Use `process.stderr.write()` for progress, `console.log()` for final summary.
|
|
399
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'registerMapCommand' src/commands/map.ts` — exactly 1 match.
|
|
400
|
+
- **Rollback/Failure Action:** Delete `src/commands/map.ts` and retry.
|
|
401
|
+
|
|
402
|
+
#### Step 5.3: Register `arc map` in CLI entry point
|
|
403
|
+
- **Target File:** `src/cli.ts` (modify existing)
|
|
404
|
+
- **Action:**
|
|
405
|
+
1. Add import: `import { registerMapCommand } from './commands/map.js';`
|
|
406
|
+
2. Add `registerMapCommand` to the command registration array (the `[...].forEach(fn => fn(program))` block).
|
|
407
|
+
- **Constraints:** Do not modify any other imports or registrations. Place the import alphabetically among existing command imports.
|
|
408
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'registerMapCommand' src/cli.ts` — exactly 2 matches (import + usage). Run `npx tsx src/cli.ts map --help` — shows help text for `arc map` with `--no-ai` and `--force` options.
|
|
409
|
+
- **Rollback/Failure Action:** Revert `src/cli.ts` changes.
|
|
410
|
+
|
|
411
|
+
#### Step 5.4a: Extend `PromptOptions` with `architecturalConstraints`
|
|
412
|
+
- **Target File:** `src/types/index.ts`
|
|
413
|
+
- **Action:** Add `architecturalConstraints: string | null;` property to the `PromptOptions` interface, after the `mode` field.
|
|
414
|
+
- **Constraints:** Do not modify any other field. Add a comment: `// Injected from project_map.json by arc map`.
|
|
415
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — will show errors in files that construct `PromptOptions` without the new field. This is expected — Step 5.4c will fix them. Verify with `grep 'architecturalConstraints' src/types/index.ts` — exactly 1 match.
|
|
416
|
+
- **Rollback/Failure Action:** Remove the added line.
|
|
417
|
+
|
|
418
|
+
#### Step 5.4b: Implement `readArchitecturalConstraints`
|
|
419
|
+
- **Target File:** `src/core/graph/constraints.ts` (new file)
|
|
420
|
+
- **Action:** Create and export `readArchitecturalConstraints(projectRoot: string, outputDir: string, contextFiles: string[]): Promise<string | null>`:
|
|
421
|
+
1. Read `project_map.json` from `path.join(projectRoot, outputDir, 'project_map.json')`. If file does not exist, return `null`.
|
|
422
|
+
2. Parse with `migrateProjectMap()`.
|
|
423
|
+
3. For each `contextFile`:
|
|
424
|
+
a. Look up in `map.files`. If absent, log `[nomos:graph:warn] {file} not in project map — run arc map to update` and skip.
|
|
425
|
+
b. For each found `FileNode`, collect `dependents[]` that have non-null `semantic`.
|
|
426
|
+
c. For each dependent, identify which `symbols[]` from the context file they consume (by checking the dependent's `imports[].symbols` where `imports[].resolved` matches the context file).
|
|
427
|
+
4. Build constraint string with format:
|
|
428
|
+
```
|
|
429
|
+
- {contextFile} → symbol: {signature}
|
|
430
|
+
Consumed by: {dependent1}, {dependent2}
|
|
431
|
+
Contract: "{semantic.purpose or overview from dependent}"
|
|
432
|
+
```
|
|
433
|
+
5. Return the assembled string, or `null` if no constraints found.
|
|
434
|
+
- **Constraints:** Import `readProjectMap` and `migrateProjectMap` from `./map-schema.js`. Do not read `.semantic.md` files — all data comes from `project_map.json`.
|
|
435
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0 (assuming 5.4a is done). Run `grep 'readArchitecturalConstraints' src/core/graph/constraints.ts` — exactly 1 match.
|
|
436
|
+
- **Rollback/Failure Action:** Delete `src/core/graph/constraints.ts` and retry.
|
|
437
|
+
|
|
438
|
+
#### Step 5.4c: Wire constraints into `Orchestrator.plan()` and `assemblePrompt()`
|
|
439
|
+
- **Target Files:** `src/core/orchestrator.ts`, `src/core/prompt.ts`
|
|
440
|
+
- **Action:**
|
|
441
|
+
1. In `src/core/prompt.ts` → `assemblePrompt()` function: After the existing sections, add:
|
|
442
|
+
```typescript
|
|
443
|
+
if (options.architecturalConstraints !== null) {
|
|
444
|
+
sections.push(
|
|
445
|
+
`[ARCHITECTURAL CONSTRAINTS]\n` +
|
|
446
|
+
`The following contracts MUST NOT be broken. Changing the listed symbol signatures ` +
|
|
447
|
+
`or return types will break dependent modules.\n\n` +
|
|
448
|
+
options.architecturalConstraints
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
2. In `src/core/orchestrator.ts` → `plan()` method: Before the call to `assemblePrompt(...)`:
|
|
453
|
+
a. Import `readArchitecturalConstraints` from `./graph/constraints.js`.
|
|
454
|
+
b. Call `const constraints = await readArchitecturalConstraints(this.projectRoot, this.config.graph.output_dir, contextFiles)`.
|
|
455
|
+
c. Pass `architecturalConstraints: constraints` into the `PromptOptions` object.
|
|
456
|
+
3. Fix any other call sites that construct `PromptOptions` to include `architecturalConstraints: null` (check for compilation errors).
|
|
457
|
+
- **Constraints:** Do not modify the `ReviewPromptOptions` interface or `assembleReviewPrompt()`. The `[ARCHITECTURAL CONSTRAINTS]` section must appear AFTER all other sections in the prompt.
|
|
458
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `npx vitest run` — all existing tests pass (may need to update test fixtures that construct `PromptOptions` to include the new field as `null`). Run `grep 'ARCHITECTURAL CONSTRAINTS' src/core/prompt.ts` — exactly 1 match.
|
|
459
|
+
- **Rollback/Failure Action:** Revert changes to both files.
|
|
460
|
+
|
|
461
|
+
#### Step 5.5: Test `MapPipeline` end-to-end
|
|
462
|
+
- **Target File:** `src/core/graph/__tests__/pipeline.test.ts` (new file)
|
|
463
|
+
- **Precondition:** Create the test fixture directory `test/fixtures/sample-project/` with these files BEFORE writing the test:
|
|
464
|
+
|
|
465
|
+
**Sub-step 5.5a: Create test fixture**
|
|
466
|
+
- **Target Directory:** `test/fixtures/sample-project/`
|
|
467
|
+
- **Action:** Create the following files:
|
|
468
|
+
- `tsconfig.json`: `{ "compilerOptions": { "paths": { "@/utils": ["src/utils/index.ts"] } } }`
|
|
469
|
+
- `src/types.ts`: exports `interface User { name: string }` and `type UserId = string`
|
|
470
|
+
- `src/utils/index.ts`: exports `function hash(s: string): string { return s; }` — imports nothing
|
|
471
|
+
- `src/utils/format.ts`: exports `function formatUser(u: User): string { return u.name; }` — imports `../types` and `@/utils`
|
|
472
|
+
- `src/service.ts`: exports `class UserService { getUser(id: UserId): User { return { name: '' }; } }` — imports `./types` and `./utils/index`
|
|
473
|
+
- `src/main.ts`: imports `./service` — no exports (entry point)
|
|
474
|
+
- `src/circular-a.ts`: exports `const a = 1;` — imports `./circular-b`
|
|
475
|
+
- `src/circular-b.ts`: exports `const b = 2;` — imports `./circular-a`
|
|
476
|
+
- **Verification Criteria:** Run `ls test/fixtures/sample-project/src/` — shows 6 `.ts` files + `utils/` directory.
|
|
477
|
+
|
|
478
|
+
**Sub-step 5.5b: Write pipeline tests**
|
|
479
|
+
- **Target File:** `src/core/graph/__tests__/pipeline.test.ts`
|
|
480
|
+
- **Action:** Write vitest tests covering:
|
|
481
|
+
1. Full pipeline on `test/fixtures/sample-project/` with `noAi: true` → produces `project_map.json` with correct file count (8 files).
|
|
482
|
+
2. `src/types.ts` and `src/utils/index.ts` have the highest depth values.
|
|
483
|
+
3. `src/main.ts` has depth 0.
|
|
484
|
+
4. `circular-a.ts` and `circular-b.ts` are detected as a cycle (warning logged).
|
|
485
|
+
5. Incremental run: run pipeline twice without changes → second run carries forward all nodes (0 files re-parsed).
|
|
486
|
+
6. `--force` flag: run pipeline twice with force=true → all files re-parsed on second run.
|
|
487
|
+
7. `stats.core_modules` contains the expected high-depth files.
|
|
488
|
+
8. `stats.total_edges` counts only internal resolved imports.
|
|
489
|
+
- **Constraints:** Use `noAi: true` for all tests (avoid needing Gemini API key in CI). Write `project_map.json` to a temp directory, not the fixture itself.
|
|
490
|
+
- **Verification Criteria:** Run `npx vitest run src/core/graph/__tests__/pipeline.test.ts` — all tests pass, exit code 0.
|
|
491
|
+
- **Rollback/Failure Action:** Fix failing tests or trace issue back to the specific phase component.
|
|
492
|
+
|
|
493
|
+
#### Step 5.6: Test Architectural Constraint injection
|
|
494
|
+
- **Target File:** `src/core/__tests__/prompt.test.ts` (modify existing)
|
|
495
|
+
- **Action:** Add new test cases:
|
|
496
|
+
1. When `architecturalConstraints` is a non-null string → `assemblePrompt()` output contains `[ARCHITECTURAL CONSTRAINTS]` section with the exact constraint text.
|
|
497
|
+
2. When `architecturalConstraints` is `null` → `assemblePrompt()` output does NOT contain `[ARCHITECTURAL CONSTRAINTS]`.
|
|
498
|
+
3. Integration: write a minimal `project_map.json` to a temp directory with known symbols, call `readArchitecturalConstraints()` → verify constraint string mentions the expected symbol signatures.
|
|
499
|
+
4. Missing-from-map file: call `readArchitecturalConstraints()` with a `contextFiles` entry that does not exist in the map → warning logged, file skipped gracefully.
|
|
500
|
+
- **Constraints:** Update any existing test fixtures that construct `PromptOptions` to include `architecturalConstraints: null`.
|
|
501
|
+
- **Verification Criteria:** Run `npx vitest run src/core/__tests__/prompt.test.ts` — all tests pass (both new and existing), exit code 0.
|
|
502
|
+
- **Rollback/Failure Action:** Fix failing tests.
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
### Phase 6: Visualization — `arc show map`
|
|
507
|
+
**Dependencies:** Phase 5 complete (Steps 5.1–5.6 verified).
|
|
508
|
+
|
|
509
|
+
#### Step 6.1: Implement `MapRenderer`
|
|
510
|
+
- **Target File:** `src/core/graph/renderer.ts` (new file)
|
|
511
|
+
- **Action:** Create a `MapRenderer` class with:
|
|
512
|
+
1. `render(map: ProjectMap): { nodes: CytoscapeNode[]; edges: CytoscapeEdge[] }` — transforms `ProjectMap` into Cytoscape.js format.
|
|
513
|
+
2. Each `FileNode` → node: `{ data: { id: relativePath, label: filename, ...fullFileNode } }`.
|
|
514
|
+
3. Each dependency link → edge: `{ data: { source: importerPath, target: dependencyPath } }`.
|
|
515
|
+
4. `computeColor(depth: number, maxDepth: number): string` — maps depth to hex color gradient:
|
|
516
|
+
- Depth 0: `#a8d8ea` (cool blue)
|
|
517
|
+
- Mid depth: `#f9c784` (warm amber)
|
|
518
|
+
- Max depth: `#c0392b` (deep red)
|
|
519
|
+
- Interpolate linearly between these three stops.
|
|
520
|
+
- **Constraints:** Export `CytoscapeNode` and `CytoscapeEdge` types. Do not import Cytoscape.js — the renderer produces plain JSON that the HTML template consumes.
|
|
521
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'export class MapRenderer' src/core/graph/renderer.ts` — exactly 1 match.
|
|
522
|
+
- **Rollback/Failure Action:** Delete `src/core/graph/renderer.ts` and retry.
|
|
523
|
+
|
|
524
|
+
#### Step 6.2a: Create HTML template — static shell with BFS layout
|
|
525
|
+
- **Target File:** `src/core/graph/html-template.ts` (new file)
|
|
526
|
+
- **Action:** Create an `export function generateHtml(mapJson: string, cytoscapeNodes: string, cytoscapeEdges: string): string` that returns a complete HTML document string:
|
|
527
|
+
1. `<!DOCTYPE html>` with `<meta charset="utf-8">`.
|
|
528
|
+
2. `<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.28.1/cytoscape.min.js"></script>` — pinned CDN.
|
|
529
|
+
3. Inline `<style>` for full-viewport layout: `body { margin: 0; } #cy { width: 100%; height: 100vh; }`.
|
|
530
|
+
4. `<div id="cy"></div>` container.
|
|
531
|
+
5. Inline `<script>`:
|
|
532
|
+
- `const PROJECT_MAP = ${mapJson};`
|
|
533
|
+
- Initialize Cytoscape with BFS layout: `layout: { name: 'breadthfirst', directed: true, roots: coreModules }`.
|
|
534
|
+
- Apply node colors from embedded data.
|
|
535
|
+
- Style: `node { label: data(label), background-color: data(color), ... }; edge { target-arrow-shape: triangle, curve-style: bezier }`.
|
|
536
|
+
- **Constraints:** The HTML must be completely self-contained — no external files other than the pinned Cytoscape CDN. Template literal must properly escape embedded JSON.
|
|
537
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'cytoscape/3.28.1' src/core/graph/html-template.ts` — exactly 1 match.
|
|
538
|
+
- **Rollback/Failure Action:** Delete `src/core/graph/html-template.ts` and retry.
|
|
539
|
+
|
|
540
|
+
#### Step 6.2b: Add context card panel to HTML template
|
|
541
|
+
- **Target File:** `src/core/graph/html-template.ts` (modify existing)
|
|
542
|
+
- **Action:** Add to the HTML template:
|
|
543
|
+
1. A `<div id="info-panel">` fixed to the right side, 320px wide, hidden by default.
|
|
544
|
+
2. CSS: `#info-panel { position: fixed; right: 0; top: 0; width: 320px; height: 100vh; overflow-y: auto; background: #fff; box-shadow: -2px 0 8px rgba(0,0,0,0.1); padding: 16px; display: none; }`.
|
|
545
|
+
3. JavaScript: On node click (`cy.on('tap', 'node', ...)`), populate the panel with:
|
|
546
|
+
- Filename + language badge.
|
|
547
|
+
- `semantic.overview` and `semantic.purpose` (or "Run `arc map` to enrich" if null).
|
|
548
|
+
- `dependents[]` list as `<a>` elements that re-center the graph on the clicked dependent.
|
|
549
|
+
- `symbols[]` list with kind badges and signatures.
|
|
550
|
+
4. Show panel on node click, hide on canvas click.
|
|
551
|
+
- **Constraints:** Modify only the template string content. Do not change the function signature.
|
|
552
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'info-panel' src/core/graph/html-template.ts` — at least 1 match.
|
|
553
|
+
- **Rollback/Failure Action:** Revert to Step 6.2a state and retry.
|
|
554
|
+
|
|
555
|
+
#### Step 6.2c: Add ripple analysis to HTML template
|
|
556
|
+
- **Target File:** `src/core/graph/html-template.ts` (modify existing)
|
|
557
|
+
- **Action:** Add to the node click handler:
|
|
558
|
+
1. On node click: find all direct dependents (`node.data('dependents')`) → highlight in red (`#e74c3c`).
|
|
559
|
+
2. Find transitive dependents (dependents of dependents, BFS traversal) → highlight in yellow (`#f39c12`).
|
|
560
|
+
3. Dim all other nodes to grey (`#bdc3c7`).
|
|
561
|
+
4. Clicked node keeps its original color but gets a border highlight.
|
|
562
|
+
5. On canvas click (`cy.on('tap', function(evt) { if (evt.target === cy) ... })`): reset all nodes to their default depth-based colors.
|
|
563
|
+
- **Constraints:** BFS traversal must use the `dependents` data field, not Cytoscape graph traversal (to match the task spec's direction: dependents = files that import this node).
|
|
564
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'e74c3c' src/core/graph/html-template.ts` — at least 1 match (red color). Run `grep 'f39c12' src/core/graph/html-template.ts` — at least 1 match (yellow color).
|
|
565
|
+
- **Rollback/Failure Action:** Revert ripple analysis code only, keep Steps 6.2a and 6.2b intact.
|
|
566
|
+
|
|
567
|
+
#### Step 6.2d: Add symbol search bar to HTML template
|
|
568
|
+
- **Target File:** `src/core/graph/html-template.ts` (modify existing)
|
|
569
|
+
- **Action:** Add to the HTML template:
|
|
570
|
+
1. A `<div id="search-bar">` at the top of the page: `<input type="text" id="search-input" placeholder="Search symbols or files..." />`.
|
|
571
|
+
2. CSS: fixed position, top: 0, left: 0, width: `calc(100% - 320px)`, z-index above graph.
|
|
572
|
+
3. JavaScript: On input change (debounced 200ms):
|
|
573
|
+
a. Match input against all `FileNode.symbols[].name` and `FileNode.file` strings (case-insensitive substring).
|
|
574
|
+
b. Highlight matched nodes in green (`#27ae60`).
|
|
575
|
+
c. For each matched node, trace the full dependency chain (all importers recursively) and highlight those in lighter green.
|
|
576
|
+
d. Dim all other nodes to grey.
|
|
577
|
+
4. On Escape key or empty input: reset to default depth coloring.
|
|
578
|
+
- **Constraints:** Debounce search to avoid lag on large graphs. Search is client-side against the embedded `PROJECT_MAP` data.
|
|
579
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'search-input' src/core/graph/html-template.ts` — at least 1 match. Run `grep '27ae60' src/core/graph/html-template.ts` — at least 1 match (green color).
|
|
580
|
+
- **Rollback/Failure Action:** Revert search bar code only, keep Steps 6.2a–6.2c intact.
|
|
581
|
+
|
|
582
|
+
#### Step 6.3: Implement `arc show map` command
|
|
583
|
+
- **Target File:** `src/commands/show.ts` (new file)
|
|
584
|
+
- **Action:** Create a `registerShowCommand(program: Command): void` function:
|
|
585
|
+
1. Register a parent `show` command group: `program.command('show').description('Display visual outputs')`.
|
|
586
|
+
2. Register `map` subcommand: `.command('map').description('Open the interactive dependency graph in your browser')`.
|
|
587
|
+
3. Option: `--no-open` (boolean flag) — generate `index.html` without opening browser.
|
|
588
|
+
4. Action handler:
|
|
589
|
+
a. Load config via `loadConfig()`.
|
|
590
|
+
b. Read `project_map.json` via `readProjectMap()`. If null, print `[nomos:error] No project map found. Run: arc map first.` to stderr and exit 1.
|
|
591
|
+
c. Run `migrateProjectMap()` on loaded data.
|
|
592
|
+
d. Call `MapRenderer.render()` to get nodes/edges.
|
|
593
|
+
e. Call `generateHtml()` to produce the HTML string.
|
|
594
|
+
f. Write `index.html` to `{config.graph.output_dir}/index.html`.
|
|
595
|
+
g. If `--no-open` is not set: import `open` package and call `open(htmlPath)`.
|
|
596
|
+
h. Print path to stdout: `Graph visualization written to: {htmlPath}`.
|
|
597
|
+
- **Constraints:** Use the `open` npm package for browser launch (not `child_process.exec`). Follow the same command registration pattern as existing commands.
|
|
598
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'registerShowCommand' src/commands/show.ts` — exactly 1 match.
|
|
599
|
+
- **Rollback/Failure Action:** Delete `src/commands/show.ts` and retry.
|
|
600
|
+
|
|
601
|
+
#### Step 6.4: Register `arc show map` in CLI entry point
|
|
602
|
+
- **Target File:** `src/cli.ts` (modify existing)
|
|
603
|
+
- **Action:**
|
|
604
|
+
1. Add import: `import { registerShowCommand } from './commands/show.js';`
|
|
605
|
+
2. Add `registerShowCommand` to the command registration array.
|
|
606
|
+
- **Constraints:** Do not modify any other imports or registrations.
|
|
607
|
+
- **Verification Criteria:** Run `npx tsc --noEmit` — exit code 0. Run `grep 'registerShowCommand' src/cli.ts` — exactly 2 matches. Run `npx tsx src/cli.ts show map --help` — shows help text with `--no-open` option.
|
|
608
|
+
- **Rollback/Failure Action:** Revert `src/cli.ts` changes.
|
|
609
|
+
|
|
610
|
+
#### Step 6.5: Test `MapRenderer`
|
|
611
|
+
- **Target File:** `src/core/graph/__tests__/renderer.test.ts` (new file)
|
|
612
|
+
- **Action:** Write vitest tests covering:
|
|
613
|
+
1. Correct node count matches `ProjectMap.files` count.
|
|
614
|
+
2. Correct edge count matches total internal dependency links.
|
|
615
|
+
3. Color at depth 0 = `#a8d8ea` (blue).
|
|
616
|
+
4. Color at max depth = `#c0392b` (red).
|
|
617
|
+
5. Color at mid depth is between blue and red (amber range).
|
|
618
|
+
6. Node data includes all `FileNode` fields.
|
|
619
|
+
7. Edge source/target match dependency relationships.
|
|
620
|
+
8. Empty map → 0 nodes, 0 edges (no crash).
|
|
621
|
+
- **Constraints:** Create minimal `ProjectMap` objects directly in test code.
|
|
622
|
+
- **Verification Criteria:** Run `npx vitest run src/core/graph/__tests__/renderer.test.ts` — all tests pass, exit code 0.
|
|
623
|
+
- **Rollback/Failure Action:** Fix failing tests.
|
|
624
|
+
|
|
625
|
+
#### Step 6.6: Test `arc show map` command
|
|
626
|
+
- **Target File:** `src/core/graph/__tests__/show.test.ts` (new file)
|
|
627
|
+
- **Action:** Write vitest tests covering:
|
|
628
|
+
1. Map missing → function/command errors with `graph_map_not_found` or exits with code 1 and prints correct error message.
|
|
629
|
+
2. Map present → `index.html` is written to the correct output path.
|
|
630
|
+
3. Generated `index.html` contains `cytoscape/3.28.1` CDN link.
|
|
631
|
+
4. Generated `index.html` contains embedded `PROJECT_MAP` data.
|
|
632
|
+
5. `--no-open` flag: browser open function is NOT called (mock the `open` package with `vi.mock('open')`).
|
|
633
|
+
- **Constraints:** Mock the `open` package. Use temp directories for output. Create a minimal valid `project_map.json` for test input.
|
|
634
|
+
- **Verification Criteria:** Run `npx vitest run src/core/graph/__tests__/show.test.ts` — all tests pass, exit code 0.
|
|
635
|
+
- **Rollback/Failure Action:** Fix failing tests.
|
|
636
|
+
|
|
637
|
+
---
|
|
638
|
+
|
|
639
|
+
## 4. Final System Integration & Validation
|
|
640
|
+
|
|
641
|
+
### Full Test Suite
|
|
642
|
+
- **Command:** `npx vitest run`
|
|
643
|
+
- **Expected Output:** All tests pass — 0 failures. This includes all 9 new test files plus all existing tests.
|
|
644
|
+
|
|
645
|
+
### Build Verification
|
|
646
|
+
- **Command:** `npm run build`
|
|
647
|
+
- **Expected Output:** `dist/cli.js` produced, exit code 0. The new `--external` flags prevent bundling of native modules.
|
|
648
|
+
|
|
649
|
+
### Type Check
|
|
650
|
+
- **Command:** `npx tsc --noEmit`
|
|
651
|
+
- **Expected Output:** Exit code 0, no type errors.
|
|
652
|
+
|
|
653
|
+
### End-to-End Smoke Test (Structural Only)
|
|
654
|
+
- **Command:** `npx tsx src/cli.ts map --no-ai`
|
|
655
|
+
- **Expected Output:** Map generated at `tasks-management/graph/project_map.json`. Summary printed to stdout: `Mapped N files, enriched 0, 0 skipped`. Exit code 0.
|
|
656
|
+
- **Verification:** Run `cat tasks-management/graph/project_map.json | node -e "const m=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); console.log('files:', Object.keys(m.files).length, 'edges:', m.stats.total_edges)"` — prints file and edge counts.
|
|
657
|
+
|
|
658
|
+
### Visualization Smoke Test
|
|
659
|
+
- **Command:** `npx tsx src/cli.ts show map --no-open`
|
|
660
|
+
- **Expected Output:** `Graph visualization written to: tasks-management/graph/index.html`. Exit code 0.
|
|
661
|
+
- **Verification:** Run `grep 'cytoscape/3.28.1' tasks-management/graph/index.html` — match found.
|
|
662
|
+
|
|
663
|
+
### Incremental Run Verification
|
|
664
|
+
- **Command:** Run `npx tsx src/cli.ts map --no-ai` twice in succession.
|
|
665
|
+
- **Expected Output:** Second run completes in under 1 second (all files carried forward, zero re-parsing). Summary indicates 0 files re-parsed.
|
|
666
|
+
|
|
667
|
+
### File Inventory Check
|
|
668
|
+
- **Expected new files (20 total):**
|
|
669
|
+
```
|
|
670
|
+
src/core/graph/map-schema.ts
|
|
671
|
+
src/core/graph/scanner.ts
|
|
672
|
+
src/core/graph/parser.ts
|
|
673
|
+
src/core/graph/resolver.ts
|
|
674
|
+
src/core/graph/builder.ts
|
|
675
|
+
src/core/graph/enricher.ts
|
|
676
|
+
src/core/graph/contract-writer.ts
|
|
677
|
+
src/core/graph/constraints.ts
|
|
678
|
+
src/core/graph/pipeline.ts
|
|
679
|
+
src/core/graph/renderer.ts
|
|
680
|
+
src/core/graph/html-template.ts
|
|
681
|
+
src/core/graph/__tests__/scanner.test.ts
|
|
682
|
+
src/core/graph/__tests__/parser.test.ts
|
|
683
|
+
src/core/graph/__tests__/resolver.test.ts
|
|
684
|
+
src/core/graph/__tests__/builder.test.ts
|
|
685
|
+
src/core/graph/__tests__/enricher.test.ts
|
|
686
|
+
src/core/graph/__tests__/contract-writer.test.ts
|
|
687
|
+
src/core/graph/__tests__/pipeline.test.ts
|
|
688
|
+
src/core/graph/__tests__/renderer.test.ts
|
|
689
|
+
src/core/graph/__tests__/show.test.ts
|
|
690
|
+
src/commands/map.ts
|
|
691
|
+
src/commands/show.ts
|
|
692
|
+
test/fixtures/sample-project/ (directory with 8 files)
|
|
693
|
+
```
|
|
694
|
+
- **Expected modified files (6 total):**
|
|
695
|
+
```
|
|
696
|
+
package.json (new deps + esbuild externals)
|
|
697
|
+
src/core/errors.ts (4 new error codes)
|
|
698
|
+
src/types/index.ts (graph types + config section + PromptOptions field)
|
|
699
|
+
src/core/config.ts (GraphConfigSchema)
|
|
700
|
+
src/core/prompt.ts (architectural constraints section)
|
|
701
|
+
src/core/orchestrator.ts (constraint injection in plan())
|
|
702
|
+
src/cli.ts (register map + show commands)
|
|
703
|
+
.gitignore (semantic.md + graph dir)
|
|
704
|
+
```
|
|
705
|
+
- **Verification:** Run `git diff --stat HEAD` and verify only these files are modified/added.
|