@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,30 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
|
|
9
|
+
function resolveGrammar(name: string): string {
|
|
10
|
+
// Tier A: try tree-sitter-wasms
|
|
11
|
+
try {
|
|
12
|
+
const wasmsBase = path.join(
|
|
13
|
+
path.dirname(require.resolve('tree-sitter-wasms/package.json')),
|
|
14
|
+
'out'
|
|
15
|
+
);
|
|
16
|
+
const wasmPath = path.join(wasmsBase, name);
|
|
17
|
+
if (fs.existsSync(wasmPath)) return wasmPath;
|
|
18
|
+
} catch { /* package not installed */ }
|
|
19
|
+
|
|
20
|
+
// Tier B/C: local grammars directory
|
|
21
|
+
const localPath = path.join(__dirname, 'grammars', name);
|
|
22
|
+
if (fs.existsSync(localPath)) return localPath;
|
|
23
|
+
|
|
24
|
+
throw new Error(
|
|
25
|
+
`WASM grammar "${name}" not found. Run Step 0.2 grammar acquisition protocol.`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const TS_WASM_PATH = resolveGrammar('tree-sitter-typescript.wasm');
|
|
30
|
+
export const TSX_WASM_PATH = resolveGrammar('tree-sitter-tsx.wasm');
|
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
|
|
6
|
+
// [GAP-4 FIX + WATCH-2] Cytoscape.js read from node_modules at generation time.
|
|
7
|
+
// Lazy-cached — read once per process, not once per call.
|
|
8
|
+
let _cytoscapeSource: string | null = null;
|
|
9
|
+
|
|
10
|
+
function getCytoscapeSource(): string {
|
|
11
|
+
if (_cytoscapeSource) return _cytoscapeSource;
|
|
12
|
+
const cytoscapePath = require.resolve('cytoscape/dist/cytoscape.min.js');
|
|
13
|
+
_cytoscapeSource = fs.readFileSync(cytoscapePath, 'utf-8');
|
|
14
|
+
return _cytoscapeSource;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generates a fully self-contained HTML visualization of the dependency graph.
|
|
19
|
+
* Cytoscape.js is inlined from node_modules — no CDN, works offline.
|
|
20
|
+
*
|
|
21
|
+
* @param mapJson JSON.stringify of the full ProjectMap (for panel data)
|
|
22
|
+
* @param cytoscapeNodes JSON.stringify of CytoscapeNode[]
|
|
23
|
+
* @param cytoscapeEdges JSON.stringify of CytoscapeEdge[]
|
|
24
|
+
*/
|
|
25
|
+
export function generateHtml(
|
|
26
|
+
mapJson: string,
|
|
27
|
+
cytoscapeNodes: string,
|
|
28
|
+
cytoscapeEdges: string,
|
|
29
|
+
): string {
|
|
30
|
+
// [AMB-8 FIX] XSS prevention: escape </script> injection and template literal breakout
|
|
31
|
+
const safeMap = mapJson
|
|
32
|
+
.replace(/<\//g, '<\\/')
|
|
33
|
+
.replace(/\$\{/g, '\\${');
|
|
34
|
+
const safeNodes = cytoscapeNodes
|
|
35
|
+
.replace(/<\//g, '<\\/')
|
|
36
|
+
.replace(/\$\{/g, '\\${');
|
|
37
|
+
const safeEdges = cytoscapeEdges
|
|
38
|
+
.replace(/<\//g, '<\\/')
|
|
39
|
+
.replace(/\$\{/g, '\\${');
|
|
40
|
+
|
|
41
|
+
// [GAP-4] noscript fallback: plain-text file listing for offline/no-JS environments
|
|
42
|
+
const textFallback = 'Dependency graph requires JavaScript to render.';
|
|
43
|
+
|
|
44
|
+
const cytoscapeJs = getCytoscapeSource();
|
|
45
|
+
|
|
46
|
+
return `<!DOCTYPE html>
|
|
47
|
+
<html lang="en">
|
|
48
|
+
<head>
|
|
49
|
+
<meta charset="utf-8">
|
|
50
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
51
|
+
<title>nomos-arc Dependency Graph</title>
|
|
52
|
+
<style>
|
|
53
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
54
|
+
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #1a1a2e; }
|
|
55
|
+
#cy { width: 100%; height: 100vh; display: block; }
|
|
56
|
+
|
|
57
|
+
/* ── Search bar ── */
|
|
58
|
+
#search-bar {
|
|
59
|
+
position: fixed;
|
|
60
|
+
top: 0; left: 0;
|
|
61
|
+
width: calc(100% - 320px);
|
|
62
|
+
z-index: 100;
|
|
63
|
+
padding: 8px 12px;
|
|
64
|
+
background: rgba(26,26,46,0.92);
|
|
65
|
+
backdrop-filter: blur(4px);
|
|
66
|
+
}
|
|
67
|
+
#search-input {
|
|
68
|
+
width: 100%;
|
|
69
|
+
padding: 8px 12px;
|
|
70
|
+
border: 1px solid #444;
|
|
71
|
+
border-radius: 6px;
|
|
72
|
+
background: #2a2a4a;
|
|
73
|
+
color: #eee;
|
|
74
|
+
font-size: 14px;
|
|
75
|
+
outline: none;
|
|
76
|
+
}
|
|
77
|
+
#search-input:focus { border-color: #27ae60; }
|
|
78
|
+
#search-input::placeholder { color: #888; }
|
|
79
|
+
|
|
80
|
+
/* ── Info panel (context card) ── */
|
|
81
|
+
#info-panel {
|
|
82
|
+
position: fixed;
|
|
83
|
+
right: 0; top: 0;
|
|
84
|
+
width: 320px;
|
|
85
|
+
height: 100vh;
|
|
86
|
+
overflow-y: auto;
|
|
87
|
+
background: #fff;
|
|
88
|
+
box-shadow: -2px 0 8px rgba(0,0,0,0.1);
|
|
89
|
+
padding: 16px;
|
|
90
|
+
display: none;
|
|
91
|
+
z-index: 200;
|
|
92
|
+
font-size: 13px;
|
|
93
|
+
}
|
|
94
|
+
#info-panel h2 { margin: 0 0 4px; font-size: 15px; word-break: break-all; }
|
|
95
|
+
#info-panel .lang-badge {
|
|
96
|
+
display: inline-block;
|
|
97
|
+
padding: 2px 8px;
|
|
98
|
+
border-radius: 4px;
|
|
99
|
+
background: #e8f4fd;
|
|
100
|
+
color: #2980b9;
|
|
101
|
+
font-size: 11px;
|
|
102
|
+
font-weight: 600;
|
|
103
|
+
margin-bottom: 12px;
|
|
104
|
+
}
|
|
105
|
+
#info-panel .section-title {
|
|
106
|
+
font-size: 11px;
|
|
107
|
+
font-weight: 700;
|
|
108
|
+
text-transform: uppercase;
|
|
109
|
+
color: #888;
|
|
110
|
+
margin: 12px 0 4px;
|
|
111
|
+
}
|
|
112
|
+
#info-panel .symbol-item {
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: baseline;
|
|
115
|
+
gap: 6px;
|
|
116
|
+
padding: 3px 0;
|
|
117
|
+
border-bottom: 1px solid #f0f0f0;
|
|
118
|
+
}
|
|
119
|
+
#info-panel .kind-badge {
|
|
120
|
+
font-size: 10px;
|
|
121
|
+
padding: 1px 5px;
|
|
122
|
+
border-radius: 3px;
|
|
123
|
+
background: #f0f0f0;
|
|
124
|
+
color: #555;
|
|
125
|
+
white-space: nowrap;
|
|
126
|
+
}
|
|
127
|
+
#info-panel .dep-link {
|
|
128
|
+
display: block;
|
|
129
|
+
color: #2980b9;
|
|
130
|
+
text-decoration: none;
|
|
131
|
+
font-size: 12px;
|
|
132
|
+
padding: 2px 0;
|
|
133
|
+
word-break: break-all;
|
|
134
|
+
}
|
|
135
|
+
#info-panel .dep-link:hover { text-decoration: underline; }
|
|
136
|
+
#panel-close {
|
|
137
|
+
position: absolute;
|
|
138
|
+
top: 10px; right: 12px;
|
|
139
|
+
background: none;
|
|
140
|
+
border: none;
|
|
141
|
+
font-size: 18px;
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
color: #888;
|
|
144
|
+
}
|
|
145
|
+
</style>
|
|
146
|
+
</head>
|
|
147
|
+
<body>
|
|
148
|
+
<!-- [GAP-4] noscript fallback -->
|
|
149
|
+
<noscript><pre style="padding:20px;color:#fff;background:#1a1a2e">${textFallback}</pre></noscript>
|
|
150
|
+
|
|
151
|
+
<!-- Search bar (6.2d) -->
|
|
152
|
+
<div id="search-bar">
|
|
153
|
+
<input type="text" id="search-input" placeholder="Search symbols or files..." autocomplete="off" />
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<!-- Graph canvas -->
|
|
157
|
+
<div id="cy"></div>
|
|
158
|
+
|
|
159
|
+
<!-- Context card panel (6.2b) -->
|
|
160
|
+
<div id="info-panel">
|
|
161
|
+
<button id="panel-close" title="Close">✕</button>
|
|
162
|
+
<h2 id="panel-filename"></h2>
|
|
163
|
+
<span id="panel-lang" class="lang-badge"></span>
|
|
164
|
+
<div class="section-title">Overview</div>
|
|
165
|
+
<div id="panel-overview" style="color:#444;line-height:1.5"></div>
|
|
166
|
+
<div class="section-title">Purpose</div>
|
|
167
|
+
<div id="panel-purpose" style="color:#444;line-height:1.5"></div>
|
|
168
|
+
<div class="section-title">Dependents</div>
|
|
169
|
+
<div id="panel-dependents"></div>
|
|
170
|
+
<div class="section-title">Symbols</div>
|
|
171
|
+
<div id="panel-symbols"></div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<!-- Cytoscape.js inlined from node_modules (GAP-4 FIX) -->
|
|
175
|
+
<script>${cytoscapeJs}</script>
|
|
176
|
+
|
|
177
|
+
<script>
|
|
178
|
+
// ── Embedded data ─────────────────────────────────────────────────────────
|
|
179
|
+
const PROJECT_MAP = ${safeMap};
|
|
180
|
+
const CY_NODES = ${safeNodes};
|
|
181
|
+
const CY_EDGES = ${safeEdges};
|
|
182
|
+
|
|
183
|
+
// ── Cytoscape init ────────────────────────────────────────────────────────
|
|
184
|
+
const coreModules = (PROJECT_MAP.stats && PROJECT_MAP.stats.core_modules) || [];
|
|
185
|
+
|
|
186
|
+
const cy = cytoscape({
|
|
187
|
+
container: document.getElementById('cy'),
|
|
188
|
+
elements: {
|
|
189
|
+
nodes: CY_NODES,
|
|
190
|
+
edges: CY_EDGES,
|
|
191
|
+
},
|
|
192
|
+
layout: {
|
|
193
|
+
name: 'breadthfirst',
|
|
194
|
+
directed: true,
|
|
195
|
+
roots: coreModules.length > 0 ? coreModules : undefined,
|
|
196
|
+
padding: 40,
|
|
197
|
+
spacingFactor: 1.4,
|
|
198
|
+
},
|
|
199
|
+
style: [
|
|
200
|
+
{
|
|
201
|
+
selector: 'node',
|
|
202
|
+
style: {
|
|
203
|
+
'label': 'data(label)',
|
|
204
|
+
'background-color': 'data(color)',
|
|
205
|
+
'color': '#fff',
|
|
206
|
+
'text-valign': 'center',
|
|
207
|
+
'text-halign': 'center',
|
|
208
|
+
'font-size': '10px',
|
|
209
|
+
'text-wrap': 'wrap',
|
|
210
|
+
'text-max-width': '80px',
|
|
211
|
+
'width': 'label',
|
|
212
|
+
'height': 'label',
|
|
213
|
+
'padding': '10px',
|
|
214
|
+
'border-width': 0,
|
|
215
|
+
'border-color': '#fff',
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
selector: 'edge',
|
|
220
|
+
style: {
|
|
221
|
+
'width': 1.5,
|
|
222
|
+
'line-color': '#555',
|
|
223
|
+
'target-arrow-color': '#888',
|
|
224
|
+
'target-arrow-shape': 'triangle',
|
|
225
|
+
'curve-style': 'bezier',
|
|
226
|
+
'opacity': 0.7,
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
selector: '.dimmed',
|
|
231
|
+
style: { 'background-color': '#bdc3c7', 'opacity': 0.4 },
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
selector: '.highlighted-direct',
|
|
235
|
+
style: { 'background-color': '#e74c3c' },
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
selector: '.highlighted-transitive',
|
|
239
|
+
style: { 'background-color': '#f39c12' },
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
selector: '.highlighted-selected',
|
|
243
|
+
style: { 'border-width': 3, 'border-color': '#fff' },
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
selector: '.search-match',
|
|
247
|
+
style: { 'background-color': '#27ae60' },
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
selector: '.search-chain',
|
|
251
|
+
style: { 'background-color': '#2ecc71' },
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
/** Reset all nodes to their original depth-based colors and clear classes */
|
|
259
|
+
function resetColors() {
|
|
260
|
+
cy.nodes().forEach(function(n) {
|
|
261
|
+
n.removeClass('dimmed highlighted-direct highlighted-transitive highlighted-selected search-match search-chain');
|
|
262
|
+
n.style('background-color', n.data('color'));
|
|
263
|
+
n.style('opacity', 1);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ── Context card panel (Step 6.2b) ────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
function showPanel(nodeData) {
|
|
270
|
+
document.getElementById('panel-filename').textContent = nodeData.label || nodeData.id;
|
|
271
|
+
document.getElementById('panel-lang').textContent = nodeData.language || 'unknown';
|
|
272
|
+
|
|
273
|
+
var sem = nodeData.semantic;
|
|
274
|
+
document.getElementById('panel-overview').textContent =
|
|
275
|
+
sem ? sem.overview : 'Run \`arc map\` to enrich';
|
|
276
|
+
document.getElementById('panel-purpose').textContent =
|
|
277
|
+
sem ? sem.purpose : 'Run \`arc map\` to enrich';
|
|
278
|
+
|
|
279
|
+
// Dependents as clickable links
|
|
280
|
+
var depsContainer = document.getElementById('panel-dependents');
|
|
281
|
+
depsContainer.innerHTML = '';
|
|
282
|
+
var dependents = nodeData.dependents || [];
|
|
283
|
+
if (dependents.length === 0) {
|
|
284
|
+
depsContainer.textContent = 'None';
|
|
285
|
+
} else {
|
|
286
|
+
dependents.forEach(function(dep) {
|
|
287
|
+
var a = document.createElement('a');
|
|
288
|
+
a.href = '#';
|
|
289
|
+
a.className = 'dep-link';
|
|
290
|
+
a.textContent = dep;
|
|
291
|
+
a.addEventListener('click', function(e) {
|
|
292
|
+
e.preventDefault();
|
|
293
|
+
var target = cy.getElementById(dep);
|
|
294
|
+
if (target.length > 0) {
|
|
295
|
+
cy.animate({ center: { eles: target }, zoom: 1.5 }, { duration: 400 });
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
depsContainer.appendChild(a);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Symbols list
|
|
303
|
+
var symbolsContainer = document.getElementById('panel-symbols');
|
|
304
|
+
symbolsContainer.innerHTML = '';
|
|
305
|
+
var symbols = nodeData.symbols || [];
|
|
306
|
+
if (symbols.length === 0) {
|
|
307
|
+
symbolsContainer.textContent = 'None';
|
|
308
|
+
} else {
|
|
309
|
+
symbols.forEach(function(sym) {
|
|
310
|
+
var item = document.createElement('div');
|
|
311
|
+
item.className = 'symbol-item';
|
|
312
|
+
var badge = document.createElement('span');
|
|
313
|
+
badge.className = 'kind-badge';
|
|
314
|
+
badge.textContent = sym.kind;
|
|
315
|
+
var name = document.createElement('span');
|
|
316
|
+
name.textContent = sym.name + (sym.signature ? ': ' + sym.signature : '');
|
|
317
|
+
name.style.color = '#333';
|
|
318
|
+
item.appendChild(badge);
|
|
319
|
+
item.appendChild(name);
|
|
320
|
+
symbolsContainer.appendChild(item);
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
document.getElementById('info-panel').style.display = 'block';
|
|
325
|
+
// Adjust search bar width when panel opens
|
|
326
|
+
document.getElementById('search-bar').style.width = 'calc(100% - 320px)';
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function hidePanel() {
|
|
330
|
+
document.getElementById('info-panel').style.display = 'none';
|
|
331
|
+
document.getElementById('search-bar').style.width = '100%';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
document.getElementById('panel-close').addEventListener('click', hidePanel);
|
|
335
|
+
|
|
336
|
+
// ── Ripple analysis (Step 6.2c) ───────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
cy.on('tap', 'node', function(evt) {
|
|
339
|
+
var node = evt.target;
|
|
340
|
+
var nodeData = node.data();
|
|
341
|
+
|
|
342
|
+
// Show context card
|
|
343
|
+
showPanel(nodeData);
|
|
344
|
+
|
|
345
|
+
// Reset first
|
|
346
|
+
resetColors();
|
|
347
|
+
|
|
348
|
+
// Mark clicked node
|
|
349
|
+
node.addClass('highlighted-selected');
|
|
350
|
+
node.style('background-color', nodeData.color);
|
|
351
|
+
|
|
352
|
+
// BFS over dependents[] data field (files that import this node)
|
|
353
|
+
var directSet = {};
|
|
354
|
+
var transitiveSet = {};
|
|
355
|
+
var queue = [];
|
|
356
|
+
|
|
357
|
+
// Direct dependents
|
|
358
|
+
var directDeps = nodeData.dependents || [];
|
|
359
|
+
directDeps.forEach(function(depId) {
|
|
360
|
+
if (depId !== nodeData.id) {
|
|
361
|
+
directSet[depId] = true;
|
|
362
|
+
queue.push(depId);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Transitive dependents (BFS)
|
|
367
|
+
while (queue.length > 0) {
|
|
368
|
+
var current = queue.shift();
|
|
369
|
+
var currentNode = cy.getElementById(current);
|
|
370
|
+
if (currentNode.length === 0) continue;
|
|
371
|
+
var nextDeps = currentNode.data('dependents') || [];
|
|
372
|
+
nextDeps.forEach(function(nextId) {
|
|
373
|
+
if (!directSet[nextId] && !transitiveSet[nextId] && nextId !== nodeData.id) {
|
|
374
|
+
transitiveSet[nextId] = true;
|
|
375
|
+
queue.push(nextId);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Apply colors
|
|
381
|
+
cy.nodes().forEach(function(n) {
|
|
382
|
+
var id = n.data('id');
|
|
383
|
+
if (id === nodeData.id) return; // keep original + selected border
|
|
384
|
+
if (directSet[id]) {
|
|
385
|
+
n.removeClass('dimmed');
|
|
386
|
+
n.addClass('highlighted-direct');
|
|
387
|
+
n.style('background-color', '#e74c3c');
|
|
388
|
+
n.style('opacity', 1);
|
|
389
|
+
} else if (transitiveSet[id]) {
|
|
390
|
+
n.removeClass('dimmed');
|
|
391
|
+
n.addClass('highlighted-transitive');
|
|
392
|
+
n.style('background-color', '#f39c12');
|
|
393
|
+
n.style('opacity', 1);
|
|
394
|
+
} else {
|
|
395
|
+
n.addClass('dimmed');
|
|
396
|
+
n.style('background-color', '#bdc3c7');
|
|
397
|
+
n.style('opacity', 0.4);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Canvas click — reset all
|
|
403
|
+
cy.on('tap', function(evt) {
|
|
404
|
+
if (evt.target === cy) {
|
|
405
|
+
resetColors();
|
|
406
|
+
hidePanel();
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// ── Symbol search bar (Step 6.2d) ─────────────────────────────────────────
|
|
411
|
+
|
|
412
|
+
var searchTimer = null;
|
|
413
|
+
|
|
414
|
+
document.getElementById('search-input').addEventListener('input', function(e) {
|
|
415
|
+
clearTimeout(searchTimer);
|
|
416
|
+
var query = e.target.value;
|
|
417
|
+
searchTimer = setTimeout(function() {
|
|
418
|
+
performSearch(query);
|
|
419
|
+
}, 200);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
document.getElementById('search-input').addEventListener('keydown', function(e) {
|
|
423
|
+
if (e.key === 'Escape') {
|
|
424
|
+
e.target.value = '';
|
|
425
|
+
resetColors();
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
function performSearch(query) {
|
|
430
|
+
if (!query || query.trim() === '') {
|
|
431
|
+
resetColors();
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
var q = query.trim().toLowerCase();
|
|
435
|
+
|
|
436
|
+
resetColors();
|
|
437
|
+
|
|
438
|
+
var matchedIds = {};
|
|
439
|
+
var chainIds = {};
|
|
440
|
+
|
|
441
|
+
cy.nodes().forEach(function(n) {
|
|
442
|
+
var data = n.data();
|
|
443
|
+
var fileMatch = data.file && data.file.toLowerCase().includes(q);
|
|
444
|
+
var symbolMatch = (data.symbols || []).some(function(sym) {
|
|
445
|
+
return sym.name && sym.name.toLowerCase().includes(q);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
if (fileMatch || symbolMatch) {
|
|
449
|
+
matchedIds[data.id] = true;
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
if (Object.keys(matchedIds).length === 0) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Trace full importer chain for matched nodes (BFS upward via dependents of dependents)
|
|
458
|
+
// Here "chain" = all files that transitively import a matched file
|
|
459
|
+
var queue = Object.keys(matchedIds);
|
|
460
|
+
while (queue.length > 0) {
|
|
461
|
+
var current = queue.shift();
|
|
462
|
+
var node = cy.getElementById(current);
|
|
463
|
+
if (node.length === 0) continue;
|
|
464
|
+
var deps = node.data('dependents') || [];
|
|
465
|
+
deps.forEach(function(depId) {
|
|
466
|
+
if (!matchedIds[depId] && !chainIds[depId]) {
|
|
467
|
+
chainIds[depId] = true;
|
|
468
|
+
queue.push(depId);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
cy.nodes().forEach(function(n) {
|
|
474
|
+
var id = n.data('id');
|
|
475
|
+
if (matchedIds[id]) {
|
|
476
|
+
n.addClass('search-match');
|
|
477
|
+
n.style('background-color', '#27ae60');
|
|
478
|
+
n.style('opacity', 1);
|
|
479
|
+
} else if (chainIds[id]) {
|
|
480
|
+
n.addClass('search-chain');
|
|
481
|
+
n.style('background-color', '#2ecc71');
|
|
482
|
+
n.style('opacity', 1);
|
|
483
|
+
} else {
|
|
484
|
+
n.addClass('dimmed');
|
|
485
|
+
n.style('background-color', '#bdc3c7');
|
|
486
|
+
n.style('opacity', 0.4);
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
</script>
|
|
491
|
+
</body>
|
|
492
|
+
</html>`;
|
|
493
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import { NomosError } from '../errors.js';
|
|
4
|
+
import type { ProjectMap } from '../../types/index.js';
|
|
5
|
+
|
|
6
|
+
// ─── Schema Version ───────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
export const CURRENT_SCHEMA_VERSION = 1;
|
|
9
|
+
|
|
10
|
+
// ─── Zod Schemas ─────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
const SymbolEntrySchema = z.object({
|
|
13
|
+
name: z.string(),
|
|
14
|
+
kind: z.enum(['class', 'function', 'method', 'interface', 'type', 'enum', 'variable', 'export']),
|
|
15
|
+
line: z.number().int().nonnegative(),
|
|
16
|
+
end_line: z.number().int().nonnegative().nullable(),
|
|
17
|
+
signature: z.string().nullable(),
|
|
18
|
+
exported: z.boolean(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const ImportEntrySchema = z.object({
|
|
22
|
+
source: z.string(),
|
|
23
|
+
resolved: z.string().nullable(),
|
|
24
|
+
symbols: z.array(z.string()),
|
|
25
|
+
is_external: z.boolean(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const SemanticInfoSchema = z.object({
|
|
29
|
+
overview: z.string(),
|
|
30
|
+
purpose: z.string(),
|
|
31
|
+
key_logic: z.array(z.string()),
|
|
32
|
+
usage_context: z.array(z.string()),
|
|
33
|
+
source_hash: z.string(),
|
|
34
|
+
enriched_at: z.string(),
|
|
35
|
+
model: z.string(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const EnrichmentStatusSchema = z.enum(['structural', 'semantic', 'indexed']);
|
|
39
|
+
|
|
40
|
+
const FileNodeSchema = z.object({
|
|
41
|
+
file: z.string(),
|
|
42
|
+
hash: z.string(),
|
|
43
|
+
language: z.string(),
|
|
44
|
+
symbols: z.array(SymbolEntrySchema),
|
|
45
|
+
imports: z.array(ImportEntrySchema),
|
|
46
|
+
dependents: z.array(z.string()),
|
|
47
|
+
dependencies: z.array(z.string()),
|
|
48
|
+
depth: z.number().int().nonnegative(),
|
|
49
|
+
last_parsed_at: z.string().nullable(),
|
|
50
|
+
semantic: SemanticInfoSchema.nullable(),
|
|
51
|
+
// Backward-compatible: old maps without this field default to 'structural'
|
|
52
|
+
enrichment_status: EnrichmentStatusSchema.default('structural'),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const ProjectMapSchema = z.object({
|
|
56
|
+
schema_version: z.literal(1),
|
|
57
|
+
generated_at: z.string(),
|
|
58
|
+
root: z.string(),
|
|
59
|
+
files: z.record(z.string(), FileNodeSchema),
|
|
60
|
+
stats: z.object({
|
|
61
|
+
total_files: z.number().int().nonnegative(),
|
|
62
|
+
total_symbols: z.number().int().nonnegative(),
|
|
63
|
+
total_edges: z.number().int().nonnegative(),
|
|
64
|
+
core_modules: z.array(z.string()),
|
|
65
|
+
// Backward-compatible: old maps without these fields default to 0
|
|
66
|
+
structural_only: z.number().int().nonnegative().default(0),
|
|
67
|
+
semantically_enriched: z.number().int().nonnegative().default(0),
|
|
68
|
+
indexed: z.number().int().nonnegative().default(0),
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ─── Migrations ───────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
// Keyed by the version to migrate FROM. Empty — no migrations needed for v1.
|
|
75
|
+
const migrations: Record<number, (raw: unknown) => unknown> = {};
|
|
76
|
+
|
|
77
|
+
// ─── migrateProjectMap ────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
export function migrateProjectMap(raw: unknown): ProjectMap {
|
|
80
|
+
const asObj: Record<string, unknown> =
|
|
81
|
+
raw !== null && typeof raw === 'object' && !Array.isArray(raw)
|
|
82
|
+
? (raw as Record<string, unknown>)
|
|
83
|
+
: {};
|
|
84
|
+
const rawVersion = typeof asObj['schema_version'] === 'number' ? asObj['schema_version'] : 0;
|
|
85
|
+
|
|
86
|
+
// [GAP-5 FIX] Forward compatibility ceiling check
|
|
87
|
+
if (rawVersion > CURRENT_SCHEMA_VERSION) {
|
|
88
|
+
throw new NomosError(
|
|
89
|
+
'graph_parse_error',
|
|
90
|
+
'Map was created by a newer version of arc. Please upgrade.',
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let current: unknown = raw;
|
|
95
|
+
|
|
96
|
+
for (let v = rawVersion; v < CURRENT_SCHEMA_VERSION; v++) {
|
|
97
|
+
const migrate = migrations[v];
|
|
98
|
+
if (migrate) current = migrate(current);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return ProjectMapSchema.parse(current) as ProjectMap;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── readProjectMap ───────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
export async function readProjectMap(mapPath: string): Promise<ProjectMap | null> {
|
|
107
|
+
let raw: string;
|
|
108
|
+
try {
|
|
109
|
+
raw = await fs.readFile(mapPath, 'utf-8');
|
|
110
|
+
} catch (err: unknown) {
|
|
111
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;
|
|
112
|
+
throw new NomosError(
|
|
113
|
+
'graph_parse_error',
|
|
114
|
+
`Failed to read project map at ${mapPath}: ${String(err)}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let parsed: unknown;
|
|
119
|
+
try {
|
|
120
|
+
parsed = JSON.parse(raw);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
throw new NomosError(
|
|
123
|
+
'graph_parse_error',
|
|
124
|
+
`Project map at ${mapPath} is not valid JSON: ${String(err)}`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
return migrateProjectMap(parsed);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
if (err instanceof NomosError) throw err;
|
|
132
|
+
throw new NomosError(
|
|
133
|
+
'graph_parse_error',
|
|
134
|
+
`Project map at ${mapPath} failed schema validation: ${String(err)}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|