hadara 0.3.2 → 0.3.3-rc.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/README.md +38 -23
- package/dist/cli/context.js +110 -0
- package/dist/cli/help.js +6 -7
- package/dist/cli/init.js +56 -58
- package/dist/cli/main.js +12 -0
- package/dist/cli/session.js +37 -0
- package/dist/cli/task.js +52 -0
- package/dist/context/code-graph-extractor.js +123 -0
- package/dist/context/code-index.js +1154 -0
- package/dist/context/context-cache-store.js +925 -0
- package/dist/context/context-graph-builder.js +341 -0
- package/dist/context/context-graph.js +42 -0
- package/dist/context/context-pack.js +457 -0
- package/dist/context/context-slice-boundary.js +37 -0
- package/dist/context/context-slice.js +487 -0
- package/dist/context/document-extractors.js +343 -0
- package/dist/context/evidence-extractors.js +179 -0
- package/dist/context/extractor-contract.js +166 -0
- package/dist/context/registry-extractors.js +177 -0
- package/dist/context/release-extractors.js +175 -0
- package/dist/context/session-start.js +297 -0
- package/dist/context/source-manifest.js +566 -0
- package/dist/context/state-projection.js +196 -0
- package/dist/context/task-extractors.js +168 -0
- package/dist/core/schema.js +26 -0
- package/dist/harness/validate.js +7 -8
- package/dist/schemas/code-index.schema.json +173 -0
- package/dist/schemas/context-cache-record.schema.json +56 -0
- package/dist/schemas/context-cache-status.schema.json +167 -0
- package/dist/schemas/context-cache-warm.schema.json +222 -0
- package/dist/schemas/context-graph.schema.json +286 -0
- package/dist/schemas/context-pack.schema.json +246 -0
- package/dist/schemas/context-slice.schema.json +94 -0
- package/dist/schemas/context-source-manifest.schema.json +147 -0
- package/dist/schemas/schema-index.json +91 -0
- package/dist/schemas/session-start.schema.json +158 -0
- package/dist/schemas/task-close-repair-plan.schema.json +67 -0
- package/dist/schemas/task-context.schema.json +125 -0
- package/dist/schemas/task-finalize.schema.json +98 -0
- package/dist/schemas/task-lifecycle.schema.json +84 -0
- package/dist/services/capability-registry.js +266 -9
- package/dist/services/lifecycle-guide.js +23 -29
- package/dist/services/protocol-consistency.js +6 -8
- package/dist/task/acceptance.js +171 -0
- package/dist/task/task-close-repair-plan.js +190 -0
- package/dist/task/task-close.js +34 -35
- package/dist/task/task-finalize.js +377 -0
- package/dist/task/task-lifecycle.js +210 -0
- package/dist/task/task-next.js +10 -1
- package/dist/task/task-ready.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CODE_FILE_LANGUAGES = exports.CODE_FILE_KINDS = exports.CODE_INDEX_EDGE_TYPES = exports.CODE_INDEX_IGNORED_PATHS = exports.CODE_INDEX_DEFAULT_BUDGETS = exports.CODE_INDEX_FILE_SUMMARY_CACHE_VERSION = exports.CODE_INDEX_FILE_SUMMARY_CACHE_SCHEMA_ID = exports.CODE_INDEX_FILE_SUMMARY_CACHE_ROOT = exports.CODE_INDEX_CACHE_ROOT = exports.CODE_INDEX_EXTRACTOR_VERSION = exports.CODE_INDEX_COMMAND = exports.CODE_INDEX_SCHEMA_ID = void 0;
|
|
7
|
+
exports.buildCodeIndexReport = buildCodeIndexReport;
|
|
8
|
+
exports.codeIndexFileSummaryCachePath = codeIndexFileSummaryCachePath;
|
|
9
|
+
exports.discoverCodeIndexFiles = discoverCodeIndexFiles;
|
|
10
|
+
exports.createCodeFileNode = createCodeFileNode;
|
|
11
|
+
exports.extractCodeFileReferences = extractCodeFileReferences;
|
|
12
|
+
exports.classifyCodeFile = classifyCodeFile;
|
|
13
|
+
exports.detectCodeFileLanguage = detectCodeFileLanguage;
|
|
14
|
+
exports.shouldIgnoreCodeIndexPath = shouldIgnoreCodeIndexPath;
|
|
15
|
+
exports.createCodeFileNodeId = createCodeFileNodeId;
|
|
16
|
+
exports.createCodeSymbolNodeId = createCodeSymbolNodeId;
|
|
17
|
+
exports.toCodeIndexRelativePath = toCodeIndexRelativePath;
|
|
18
|
+
exports.summarizeCodeIndex = summarizeCodeIndex;
|
|
19
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
20
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
21
|
+
const fs_1 = require("../core/fs");
|
|
22
|
+
const extractor_contract_1 = require("./extractor-contract");
|
|
23
|
+
const capability_registry_1 = require("../services/capability-registry");
|
|
24
|
+
exports.CODE_INDEX_SCHEMA_ID = 'hadara.codeIndex.v1';
|
|
25
|
+
exports.CODE_INDEX_COMMAND = 'code.index';
|
|
26
|
+
exports.CODE_INDEX_EXTRACTOR_VERSION = 'c2-code-index-v1';
|
|
27
|
+
exports.CODE_INDEX_CACHE_ROOT = '.hadara/local/cache/context';
|
|
28
|
+
exports.CODE_INDEX_FILE_SUMMARY_CACHE_ROOT = `${exports.CODE_INDEX_CACHE_ROOT}/code-index-files`;
|
|
29
|
+
exports.CODE_INDEX_FILE_SUMMARY_CACHE_SCHEMA_ID = 'hadara.codeIndex.fileSummaryCacheRecord.v1';
|
|
30
|
+
exports.CODE_INDEX_FILE_SUMMARY_CACHE_VERSION = 'c6.6-code-index-file-summary-v1';
|
|
31
|
+
exports.CODE_INDEX_DEFAULT_BUDGETS = {
|
|
32
|
+
maxIndexedFiles: 2000,
|
|
33
|
+
maxIndexedBytes: 20 * 1024 * 1024,
|
|
34
|
+
maxSingleFileBytes: 1024 * 1024
|
|
35
|
+
};
|
|
36
|
+
exports.CODE_INDEX_IGNORED_PATHS = [
|
|
37
|
+
'node_modules',
|
|
38
|
+
'dist',
|
|
39
|
+
'coverage',
|
|
40
|
+
'.git',
|
|
41
|
+
'.hadara/local',
|
|
42
|
+
'.hadara/tmp',
|
|
43
|
+
'.pytest_cache',
|
|
44
|
+
'.mypy_cache',
|
|
45
|
+
'.ruff_cache',
|
|
46
|
+
'.venv',
|
|
47
|
+
'venv'
|
|
48
|
+
];
|
|
49
|
+
exports.CODE_INDEX_EDGE_TYPES = [
|
|
50
|
+
'IMPORTS',
|
|
51
|
+
'EXPORTS',
|
|
52
|
+
'DEFINES_SYMBOL',
|
|
53
|
+
'TESTS_FILE',
|
|
54
|
+
'IMPLEMENTS_COMMAND',
|
|
55
|
+
'REFERENCED_BY_DOC',
|
|
56
|
+
'VALIDATED_BY_EVIDENCE'
|
|
57
|
+
];
|
|
58
|
+
exports.CODE_FILE_KINDS = ['source', 'test', 'fixture', 'script', 'config', 'unknown'];
|
|
59
|
+
exports.CODE_FILE_LANGUAGES = ['typescript', 'javascript', 'json', 'markdown', 'unknown'];
|
|
60
|
+
const COMMAND_REGISTRY_SOURCE_PATH = 'src/services/capability-registry.ts';
|
|
61
|
+
function buildCodeIndexReport(options) {
|
|
62
|
+
const generatedAt = options.generatedAt ?? new Date().toISOString();
|
|
63
|
+
const budgets = normalizeCodeIndexBudgets(options.budgets);
|
|
64
|
+
const discovered = discoverCodeIndexInputs(options.projectRoot, {
|
|
65
|
+
budgets,
|
|
66
|
+
sourceEntries: options.sourceEntries
|
|
67
|
+
});
|
|
68
|
+
const rawSummaries = [];
|
|
69
|
+
const issues = [...discovered.issues];
|
|
70
|
+
let indexedBytes = 0;
|
|
71
|
+
let skippedFiles = discovered.skippedFiles;
|
|
72
|
+
const cacheStats = createCodeIndexFileSummaryCacheStats();
|
|
73
|
+
const cacheOptions = options.fileSummaryCache;
|
|
74
|
+
const extractorVersion = cacheOptions?.extractorVersion ?? exports.CODE_INDEX_EXTRACTOR_VERSION;
|
|
75
|
+
for (const relativePath of discovered.paths) {
|
|
76
|
+
const absolutePath = node_path_1.default.join(options.projectRoot, relativePath);
|
|
77
|
+
try {
|
|
78
|
+
const sourceEntry = discovered.sourceEntriesByPath.get(relativePath) ?? createCodeIndexSourceEntry(options.projectRoot, relativePath);
|
|
79
|
+
if (sourceEntry.sizeBytes > budgets.maxSingleFileBytes) {
|
|
80
|
+
skippedFiles += 1;
|
|
81
|
+
issues.push(createBudgetIssue({
|
|
82
|
+
message: `Skipped ${relativePath} because it exceeds the single-file code index budget (${sourceEntry.sizeBytes} bytes > ${budgets.maxSingleFileBytes} bytes).`,
|
|
83
|
+
path: relativePath,
|
|
84
|
+
fixHint: 'Reduce the file size or wait for a future configurable code index budget.'
|
|
85
|
+
}));
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (indexedBytes + sourceEntry.sizeBytes > budgets.maxIndexedBytes) {
|
|
89
|
+
skippedFiles += 1;
|
|
90
|
+
issues.push(createBudgetIssue({
|
|
91
|
+
message: `Skipped ${relativePath} because the code index byte budget would be exceeded (${indexedBytes + sourceEntry.sizeBytes} bytes > ${budgets.maxIndexedBytes} bytes).`,
|
|
92
|
+
path: relativePath,
|
|
93
|
+
fixHint: 'Reduce indexed source size or wait for a future configurable code index budget.'
|
|
94
|
+
}));
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const cached = readCodeIndexFileSummaryCache({
|
|
98
|
+
projectRoot: options.projectRoot,
|
|
99
|
+
sourceEntry,
|
|
100
|
+
cacheOptions,
|
|
101
|
+
extractorVersion
|
|
102
|
+
});
|
|
103
|
+
updateCodeIndexFileSummaryCacheStats(cacheStats, cached.status);
|
|
104
|
+
if (cached.issue)
|
|
105
|
+
issues.push(cached.issue);
|
|
106
|
+
if (cached.summary) {
|
|
107
|
+
rawSummaries.push(cached.summary);
|
|
108
|
+
indexedBytes += sourceEntry.sizeBytes;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const content = node_fs_1.default.readFileSync(absolutePath, 'utf8');
|
|
112
|
+
indexedBytes += sourceEntry.sizeBytes;
|
|
113
|
+
const summary = extractCodeFileSummary({
|
|
114
|
+
projectRoot: options.projectRoot,
|
|
115
|
+
path: relativePath,
|
|
116
|
+
content,
|
|
117
|
+
sizeBytes: sourceEntry.sizeBytes
|
|
118
|
+
});
|
|
119
|
+
issues.push(...summary.issues);
|
|
120
|
+
rawSummaries.push(summary);
|
|
121
|
+
if (cacheOptions?.mode === 'read-write') {
|
|
122
|
+
writeCodeIndexFileSummaryCache({
|
|
123
|
+
projectRoot: options.projectRoot,
|
|
124
|
+
sourceEntry,
|
|
125
|
+
summary,
|
|
126
|
+
createdAt: cacheOptions.createdAt ?? generatedAt,
|
|
127
|
+
extractorVersion
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
issues.push({
|
|
133
|
+
severity: 'warning',
|
|
134
|
+
code: 'CODE_INDEX_FILE_READ_FAILED',
|
|
135
|
+
message: `Failed to read code index file ${relativePath}: ${error instanceof Error ? error.message : String(error)}.`,
|
|
136
|
+
path: relativePath,
|
|
137
|
+
fixHint: 'Check file permissions or remove the unreadable file from indexed source paths.'
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const sanitized = sanitizeCodeFileSummaries(rawSummaries);
|
|
142
|
+
const fileSummaries = sanitized.summaries;
|
|
143
|
+
issues.push(...sanitized.issues);
|
|
144
|
+
const commandHints = createCommandHints(fileSummaries);
|
|
145
|
+
const commandFamiliesByPath = createCommandFamiliesByPath(commandHints);
|
|
146
|
+
const files = fileSummaries.map((summary) => createCodeFileNodeFromSummary(summary, {
|
|
147
|
+
imports: summary.imports.map((importReference) => importReference.resolvedPath ?? importReference.specifier),
|
|
148
|
+
exports: summary.exports.map((exportReference) => exportReference.name),
|
|
149
|
+
commandFamilies: commandFamiliesByPath.get(summary.filePath) ?? []
|
|
150
|
+
}));
|
|
151
|
+
const symbols = createCodeSymbolNodes(fileSummaries);
|
|
152
|
+
const edges = [
|
|
153
|
+
...createImportEdges(fileSummaries),
|
|
154
|
+
...createSymbolEdges(fileSummaries),
|
|
155
|
+
...createCommandHintEdges(fileSummaries, commandHints),
|
|
156
|
+
...createTestRelationEdges(options.projectRoot, fileSummaries)
|
|
157
|
+
].sort((a, b) => a.id.localeCompare(b.id));
|
|
158
|
+
return {
|
|
159
|
+
schemaVersion: exports.CODE_INDEX_SCHEMA_ID,
|
|
160
|
+
command: exports.CODE_INDEX_COMMAND,
|
|
161
|
+
ok: !issues.some((issue) => issue.severity === 'error'),
|
|
162
|
+
generatedAt,
|
|
163
|
+
projectRoot: options.projectRoot,
|
|
164
|
+
sourceHash: (0, extractor_contract_1.hashContextGraphSources)(files.map((file) => ({ path: file.path, hash: file.hash }))),
|
|
165
|
+
files,
|
|
166
|
+
symbols,
|
|
167
|
+
edges,
|
|
168
|
+
summary: summarizeCodeIndex(files, symbols, edges, issues),
|
|
169
|
+
budget: {
|
|
170
|
+
...budgets,
|
|
171
|
+
indexedFiles: files.length,
|
|
172
|
+
indexedBytes,
|
|
173
|
+
skippedFiles
|
|
174
|
+
},
|
|
175
|
+
cache: createCodeIndexCacheMetadata(cacheOptions, cacheStats),
|
|
176
|
+
issues
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function codeIndexFileSummaryCachePath(relativePath) {
|
|
180
|
+
const normalizedPath = (0, extractor_contract_1.normalizeContextGraphPath)(relativePath);
|
|
181
|
+
const fingerprint = (0, extractor_contract_1.hashContextGraphText)(normalizedPath).replace(/^sha256:/, '');
|
|
182
|
+
return `${exports.CODE_INDEX_FILE_SUMMARY_CACHE_ROOT}/${fingerprint}.json`;
|
|
183
|
+
}
|
|
184
|
+
function discoverCodeIndexFiles(projectRoot, options = {}) {
|
|
185
|
+
const budgets = normalizeCodeIndexBudgets(options.budgets);
|
|
186
|
+
const paths = [];
|
|
187
|
+
const issues = [];
|
|
188
|
+
let skippedFiles = 0;
|
|
189
|
+
let fileBudgetIssueRecorded = false;
|
|
190
|
+
const root = node_path_1.default.resolve(projectRoot);
|
|
191
|
+
function visit(relativeDir) {
|
|
192
|
+
if (relativeDir && shouldIgnoreCodeIndexPath(relativeDir))
|
|
193
|
+
return;
|
|
194
|
+
const absoluteDir = node_path_1.default.join(root, relativeDir);
|
|
195
|
+
let entries;
|
|
196
|
+
try {
|
|
197
|
+
entries = node_fs_1.default.readdirSync(absoluteDir, { withFileTypes: true });
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
issues.push({
|
|
201
|
+
severity: 'warning',
|
|
202
|
+
code: 'CODE_INDEX_FILE_READ_FAILED',
|
|
203
|
+
message: `Failed to read code index directory ${(0, extractor_contract_1.normalizeContextGraphPath)(relativeDir || '.')}: ${error instanceof Error ? error.message : String(error)}.`,
|
|
204
|
+
path: (0, extractor_contract_1.normalizeContextGraphPath)(relativeDir || '.'),
|
|
205
|
+
fixHint: 'Check directory permissions or remove the unreadable directory from indexed source paths.'
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
210
|
+
const relativePath = (0, extractor_contract_1.normalizeContextGraphPath)(node_path_1.default.join(relativeDir, entry.name));
|
|
211
|
+
if (shouldIgnoreCodeIndexPath(relativePath))
|
|
212
|
+
continue;
|
|
213
|
+
if (entry.isDirectory()) {
|
|
214
|
+
visit(relativePath);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (entry.isFile() && classifyCodeFile(relativePath) !== 'unknown') {
|
|
218
|
+
if (paths.length >= budgets.maxIndexedFiles) {
|
|
219
|
+
skippedFiles += 1;
|
|
220
|
+
if (!fileBudgetIssueRecorded) {
|
|
221
|
+
fileBudgetIssueRecorded = true;
|
|
222
|
+
issues.push(createBudgetIssue({
|
|
223
|
+
message: `Code index exceeded max indexed files budget (${budgets.maxIndexedFiles}); partial results returned.`,
|
|
224
|
+
path: relativePath,
|
|
225
|
+
fixHint: 'Reduce indexed files or wait for a future configurable code index budget.'
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
paths.push(relativePath);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
visit('');
|
|
235
|
+
return { paths: Array.from(new Set(paths)).sort(), issues, skippedFiles };
|
|
236
|
+
}
|
|
237
|
+
function discoverCodeIndexInputs(projectRoot, options) {
|
|
238
|
+
if (!options.sourceEntries) {
|
|
239
|
+
const discovered = discoverCodeIndexFiles(projectRoot, { budgets: options.budgets });
|
|
240
|
+
return {
|
|
241
|
+
...discovered,
|
|
242
|
+
sourceEntriesByPath: new Map()
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const sourceEntriesByPath = new Map();
|
|
246
|
+
const paths = [];
|
|
247
|
+
const issues = [];
|
|
248
|
+
let skippedFiles = 0;
|
|
249
|
+
let fileBudgetIssueRecorded = false;
|
|
250
|
+
for (const sourceEntry of options.sourceEntries) {
|
|
251
|
+
if (!sourceEntry.extractorKeys?.includes('codeIndex'))
|
|
252
|
+
continue;
|
|
253
|
+
const relativePath = (0, extractor_contract_1.normalizeContextGraphPath)(sourceEntry.path);
|
|
254
|
+
if (shouldIgnoreCodeIndexPath(relativePath) || classifyCodeFile(relativePath) === 'unknown')
|
|
255
|
+
continue;
|
|
256
|
+
if (paths.length >= options.budgets.maxIndexedFiles) {
|
|
257
|
+
skippedFiles += 1;
|
|
258
|
+
if (!fileBudgetIssueRecorded) {
|
|
259
|
+
fileBudgetIssueRecorded = true;
|
|
260
|
+
issues.push(createBudgetIssue({
|
|
261
|
+
message: `Code index exceeded max indexed files budget (${options.budgets.maxIndexedFiles}); partial results returned.`,
|
|
262
|
+
path: relativePath,
|
|
263
|
+
fixHint: 'Reduce indexed files or wait for a future configurable code index budget.'
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const normalizedEntry = {
|
|
269
|
+
...sourceEntry,
|
|
270
|
+
path: relativePath
|
|
271
|
+
};
|
|
272
|
+
paths.push(relativePath);
|
|
273
|
+
sourceEntriesByPath.set(relativePath, normalizedEntry);
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
paths: Array.from(new Set(paths)).sort(),
|
|
277
|
+
sourceEntriesByPath,
|
|
278
|
+
issues,
|
|
279
|
+
skippedFiles
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function createCodeIndexSourceEntry(projectRoot, relativePath) {
|
|
283
|
+
const normalizedPath = (0, extractor_contract_1.normalizeContextGraphPath)(relativePath);
|
|
284
|
+
const stats = node_fs_1.default.statSync(node_path_1.default.join(projectRoot, normalizedPath));
|
|
285
|
+
return {
|
|
286
|
+
path: normalizedPath,
|
|
287
|
+
sizeBytes: stats.size,
|
|
288
|
+
mtimeMs: stats.mtimeMs,
|
|
289
|
+
metadataHash: (0, extractor_contract_1.hashContextGraphJson)({
|
|
290
|
+
path: normalizedPath,
|
|
291
|
+
sizeBytes: stats.size,
|
|
292
|
+
mtimeMs: stats.mtimeMs
|
|
293
|
+
}),
|
|
294
|
+
extractorKeys: ['codeIndex']
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function normalizeCodeIndexBudgets(input = {}) {
|
|
298
|
+
return {
|
|
299
|
+
maxIndexedFiles: normalizeBudgetValue(input.maxIndexedFiles, exports.CODE_INDEX_DEFAULT_BUDGETS.maxIndexedFiles),
|
|
300
|
+
maxIndexedBytes: normalizeBudgetValue(input.maxIndexedBytes, exports.CODE_INDEX_DEFAULT_BUDGETS.maxIndexedBytes),
|
|
301
|
+
maxSingleFileBytes: normalizeBudgetValue(input.maxSingleFileBytes, exports.CODE_INDEX_DEFAULT_BUDGETS.maxSingleFileBytes)
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function normalizeBudgetValue(value, fallback) {
|
|
305
|
+
if (value === undefined || !Number.isFinite(value))
|
|
306
|
+
return fallback;
|
|
307
|
+
return Math.max(0, Math.floor(value));
|
|
308
|
+
}
|
|
309
|
+
function createBudgetIssue(input) {
|
|
310
|
+
return {
|
|
311
|
+
severity: 'warning',
|
|
312
|
+
code: 'CODE_INDEX_TOO_LARGE',
|
|
313
|
+
message: input.message,
|
|
314
|
+
...(input.path ? { path: input.path } : {}),
|
|
315
|
+
fixHint: input.fixHint
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function createCodeFileNode(relativePath, content, metadata = {}) {
|
|
319
|
+
const normalizedPath = (0, extractor_contract_1.normalizeContextGraphPath)(relativePath);
|
|
320
|
+
return {
|
|
321
|
+
id: createCodeFileNodeId(normalizedPath),
|
|
322
|
+
path: normalizedPath,
|
|
323
|
+
kind: classifyCodeFile(normalizedPath),
|
|
324
|
+
language: detectCodeFileLanguage(normalizedPath),
|
|
325
|
+
hash: (0, extractor_contract_1.hashContextGraphText)(content),
|
|
326
|
+
lineCount: countLines(content),
|
|
327
|
+
exports: uniqueSorted(metadata.exports ?? []),
|
|
328
|
+
imports: uniqueSorted(metadata.imports ?? []),
|
|
329
|
+
commandFamilies: uniqueSorted(metadata.commandFamilies ?? [])
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function createCodeFileNodeFromSummary(summary, metadata = {}) {
|
|
333
|
+
return {
|
|
334
|
+
id: createCodeFileNodeId(summary.filePath),
|
|
335
|
+
path: (0, extractor_contract_1.normalizeContextGraphPath)(summary.filePath),
|
|
336
|
+
kind: summary.kind,
|
|
337
|
+
language: summary.language,
|
|
338
|
+
hash: summary.hash,
|
|
339
|
+
lineCount: summary.lineCount,
|
|
340
|
+
exports: uniqueSorted(metadata.exports ?? []),
|
|
341
|
+
imports: uniqueSorted(metadata.imports ?? []),
|
|
342
|
+
commandFamilies: uniqueSorted(metadata.commandFamilies ?? [])
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function extractCodeFileReferences(input) {
|
|
346
|
+
const imports = [];
|
|
347
|
+
const exports = [];
|
|
348
|
+
const issues = [];
|
|
349
|
+
const normalizedPath = (0, extractor_contract_1.normalizeContextGraphPath)(input.path);
|
|
350
|
+
const language = detectCodeFileLanguage(normalizedPath);
|
|
351
|
+
if (language !== 'typescript' && language !== 'javascript') {
|
|
352
|
+
return { imports, exports, issues };
|
|
353
|
+
}
|
|
354
|
+
const lines = input.content.split(/\r?\n/);
|
|
355
|
+
lines.forEach((line, index) => {
|
|
356
|
+
const lineNumber = index + 1;
|
|
357
|
+
for (const specifier of extractImportSpecifiersFromLine(line)) {
|
|
358
|
+
const resolvedPath = resolveRelativeCodeImport({
|
|
359
|
+
projectRoot: input.projectRoot,
|
|
360
|
+
fromPath: normalizedPath,
|
|
361
|
+
specifier
|
|
362
|
+
});
|
|
363
|
+
imports.push({
|
|
364
|
+
specifier,
|
|
365
|
+
...(resolvedPath ? { resolvedPath } : {}),
|
|
366
|
+
line: lineNumber
|
|
367
|
+
});
|
|
368
|
+
if (isRelativeImportSpecifier(specifier) && !resolvedPath) {
|
|
369
|
+
issues.push({
|
|
370
|
+
severity: 'warning',
|
|
371
|
+
code: 'CODE_INDEX_IMPORT_UNRESOLVED',
|
|
372
|
+
message: `Could not resolve relative import ${specifier} from ${normalizedPath}.`,
|
|
373
|
+
path: normalizedPath,
|
|
374
|
+
fixHint: 'Check that the import target exists, uses a supported extension, and is not ignored by code index rules.'
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
for (const exportReference of extractExportReferencesFromLine(line)) {
|
|
379
|
+
exports.push({ ...exportReference, line: lineNumber });
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
return {
|
|
383
|
+
imports: dedupeImports(imports),
|
|
384
|
+
exports: dedupeExports(exports),
|
|
385
|
+
issues
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function extractCodeFileSummary(input) {
|
|
389
|
+
const normalizedPath = (0, extractor_contract_1.normalizeContextGraphPath)(input.path);
|
|
390
|
+
const references = extractCodeFileReferences({
|
|
391
|
+
projectRoot: input.projectRoot,
|
|
392
|
+
path: normalizedPath,
|
|
393
|
+
content: input.content
|
|
394
|
+
});
|
|
395
|
+
return {
|
|
396
|
+
filePath: normalizedPath,
|
|
397
|
+
kind: classifyCodeFile(normalizedPath),
|
|
398
|
+
language: detectCodeFileLanguage(normalizedPath),
|
|
399
|
+
hash: (0, extractor_contract_1.hashContextGraphText)(input.content),
|
|
400
|
+
lineCount: countLines(input.content),
|
|
401
|
+
sizeBytes: input.sizeBytes,
|
|
402
|
+
imports: references.imports,
|
|
403
|
+
exports: references.exports,
|
|
404
|
+
commandMentions: extractCommandMentions(input.content),
|
|
405
|
+
...(normalizedPath === COMMAND_REGISTRY_SOURCE_PATH
|
|
406
|
+
? { commandIdLines: Object.fromEntries(createRegistryLineMap(input.content).entries()) }
|
|
407
|
+
: {}),
|
|
408
|
+
issues: references.issues
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function extractCommandMentions(content) {
|
|
412
|
+
const mentions = [];
|
|
413
|
+
for (const entry of (0, capability_registry_1.listCommandRegistryEntries)()) {
|
|
414
|
+
const line = findCommandMentionLine(content, entry.id);
|
|
415
|
+
if (line !== undefined)
|
|
416
|
+
mentions.push({ commandId: entry.id, line });
|
|
417
|
+
}
|
|
418
|
+
return mentions.sort((a, b) => `${a.commandId}:${a.line}`.localeCompare(`${b.commandId}:${b.line}`));
|
|
419
|
+
}
|
|
420
|
+
function classifyCodeFile(inputPath) {
|
|
421
|
+
const filePath = (0, extractor_contract_1.normalizeContextGraphPath)(inputPath);
|
|
422
|
+
if (filePath === 'package.json' || filePath === 'tsconfig.json')
|
|
423
|
+
return 'config';
|
|
424
|
+
if (filePath.startsWith('tests/fixtures/'))
|
|
425
|
+
return 'fixture';
|
|
426
|
+
if (filePath.startsWith('tests/') && (filePath.endsWith('.test.ts') || filePath.endsWith('.spec.ts')))
|
|
427
|
+
return 'test';
|
|
428
|
+
if (filePath.startsWith('src/') && (filePath.endsWith('.ts') || filePath.endsWith('.js')))
|
|
429
|
+
return 'source';
|
|
430
|
+
if (filePath.startsWith('scripts/'))
|
|
431
|
+
return 'script';
|
|
432
|
+
return 'unknown';
|
|
433
|
+
}
|
|
434
|
+
function detectCodeFileLanguage(inputPath) {
|
|
435
|
+
const filePath = (0, extractor_contract_1.normalizeContextGraphPath)(inputPath);
|
|
436
|
+
if (filePath.endsWith('.ts'))
|
|
437
|
+
return 'typescript';
|
|
438
|
+
if (filePath.endsWith('.js'))
|
|
439
|
+
return 'javascript';
|
|
440
|
+
if (filePath.endsWith('.json'))
|
|
441
|
+
return 'json';
|
|
442
|
+
if (filePath.endsWith('.md'))
|
|
443
|
+
return 'markdown';
|
|
444
|
+
return 'unknown';
|
|
445
|
+
}
|
|
446
|
+
function shouldIgnoreCodeIndexPath(inputPath) {
|
|
447
|
+
const normalizedPath = (0, extractor_contract_1.normalizeContextGraphPath)(inputPath);
|
|
448
|
+
if (!normalizedPath || normalizedPath === '.')
|
|
449
|
+
return false;
|
|
450
|
+
return exports.CODE_INDEX_IGNORED_PATHS.some((ignoredPath) => normalizedPath === ignoredPath || normalizedPath.startsWith(`${ignoredPath}/`));
|
|
451
|
+
}
|
|
452
|
+
function createCodeFileNodeId(inputPath) {
|
|
453
|
+
return `file:${(0, extractor_contract_1.normalizeContextGraphPath)(inputPath)}`;
|
|
454
|
+
}
|
|
455
|
+
function createCodeSymbolNodeId(inputPath, name) {
|
|
456
|
+
return `symbol:${(0, extractor_contract_1.normalizeContextGraphPath)(inputPath)}#${name}`;
|
|
457
|
+
}
|
|
458
|
+
function toCodeIndexRelativePath(projectRoot, absoluteOrRelativePath) {
|
|
459
|
+
return (0, extractor_contract_1.toProjectRelativeContextPath)(projectRoot, absoluteOrRelativePath);
|
|
460
|
+
}
|
|
461
|
+
function summarizeCodeIndex(files, symbols, edges, issues) {
|
|
462
|
+
return {
|
|
463
|
+
sourceFiles: files.filter((file) => file.kind === 'source').length,
|
|
464
|
+
testFiles: files.filter((file) => file.kind === 'test').length,
|
|
465
|
+
fixtureFiles: files.filter((file) => file.kind === 'fixture').length,
|
|
466
|
+
configFiles: files.filter((file) => file.kind === 'config').length,
|
|
467
|
+
symbols: symbols.length,
|
|
468
|
+
edges: edges.length,
|
|
469
|
+
degraded: issues.some((issue) => issue.severity === 'warning' || issue.severity === 'error')
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
function sanitizeCodeFileSummaries(summaries) {
|
|
473
|
+
const availablePaths = new Set(summaries.map((summary) => summary.filePath));
|
|
474
|
+
const issues = [];
|
|
475
|
+
return {
|
|
476
|
+
summaries: summaries.map((summary) => {
|
|
477
|
+
const imports = summary.imports.map((importReference) => {
|
|
478
|
+
if (!importReference.resolvedPath || availablePaths.has(importReference.resolvedPath))
|
|
479
|
+
return importReference;
|
|
480
|
+
if (isRelativeImportSpecifier(importReference.specifier)) {
|
|
481
|
+
issues.push({
|
|
482
|
+
severity: 'warning',
|
|
483
|
+
code: 'CODE_INDEX_IMPORT_UNRESOLVED',
|
|
484
|
+
message: `Could not resolve relative import ${importReference.specifier} from ${summary.filePath}.`,
|
|
485
|
+
path: summary.filePath,
|
|
486
|
+
fixHint: 'Check that the import target exists, uses a supported extension, and is not ignored by code index rules.'
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
specifier: importReference.specifier,
|
|
491
|
+
line: importReference.line
|
|
492
|
+
};
|
|
493
|
+
});
|
|
494
|
+
return { ...summary, imports: dedupeImports(imports) };
|
|
495
|
+
}),
|
|
496
|
+
issues
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function createCodeIndexFileSummaryCacheStats() {
|
|
500
|
+
return {
|
|
501
|
+
readCount: 0,
|
|
502
|
+
reusedFileCount: 0,
|
|
503
|
+
recomputedFileCount: 0,
|
|
504
|
+
missingFileCount: 0,
|
|
505
|
+
staleFileCount: 0,
|
|
506
|
+
corruptFileCount: 0,
|
|
507
|
+
schemaMismatchFileCount: 0
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function updateCodeIndexFileSummaryCacheStats(stats, status) {
|
|
511
|
+
if (status === 'disabled')
|
|
512
|
+
return;
|
|
513
|
+
stats.readCount += 1;
|
|
514
|
+
if (status === 'fresh')
|
|
515
|
+
stats.reusedFileCount += 1;
|
|
516
|
+
else {
|
|
517
|
+
stats.recomputedFileCount += 1;
|
|
518
|
+
if (status === 'missing')
|
|
519
|
+
stats.missingFileCount += 1;
|
|
520
|
+
if (status === 'stale')
|
|
521
|
+
stats.staleFileCount += 1;
|
|
522
|
+
if (status === 'corrupt')
|
|
523
|
+
stats.corruptFileCount += 1;
|
|
524
|
+
if (status === 'schema-mismatch')
|
|
525
|
+
stats.schemaMismatchFileCount += 1;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function createCodeIndexCacheMetadata(cacheOptions, stats) {
|
|
529
|
+
if (!cacheOptions)
|
|
530
|
+
return { used: false, hit: false };
|
|
531
|
+
return {
|
|
532
|
+
used: stats.reusedFileCount > 0,
|
|
533
|
+
hit: stats.readCount > 0 && stats.recomputedFileCount === 0,
|
|
534
|
+
mode: 'code-index-file-summaries',
|
|
535
|
+
cachePath: exports.CODE_INDEX_FILE_SUMMARY_CACHE_ROOT,
|
|
536
|
+
readFileSummaryCount: stats.readCount,
|
|
537
|
+
reusedFileSummaryCount: stats.reusedFileCount,
|
|
538
|
+
recomputedFileSummaryCount: stats.recomputedFileCount,
|
|
539
|
+
missingFileSummaryCount: stats.missingFileCount,
|
|
540
|
+
staleFileSummaryCount: stats.staleFileCount,
|
|
541
|
+
corruptFileSummaryCount: stats.corruptFileCount,
|
|
542
|
+
schemaMismatchFileSummaryCount: stats.schemaMismatchFileCount
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function readCodeIndexFileSummaryCache(input) {
|
|
546
|
+
if (!input.cacheOptions)
|
|
547
|
+
return { status: 'disabled' };
|
|
548
|
+
const cachePath = codeIndexFileSummaryCachePath(input.sourceEntry.path);
|
|
549
|
+
const absolutePath = node_path_1.default.join(input.projectRoot, cachePath);
|
|
550
|
+
if (!node_fs_1.default.existsSync(absolutePath))
|
|
551
|
+
return { status: 'missing', path: cachePath };
|
|
552
|
+
let parsed;
|
|
553
|
+
try {
|
|
554
|
+
parsed = JSON.parse(node_fs_1.default.readFileSync(absolutePath, 'utf8'));
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
return {
|
|
558
|
+
status: 'corrupt',
|
|
559
|
+
path: cachePath,
|
|
560
|
+
issue: {
|
|
561
|
+
severity: 'warning',
|
|
562
|
+
code: 'CODE_INDEX_FILE_CACHE_CORRUPT',
|
|
563
|
+
message: `Code index file summary cache at ${cachePath} could not be parsed: ${error instanceof Error ? error.message : String(error)}.`,
|
|
564
|
+
path: input.sourceEntry.path,
|
|
565
|
+
fixHint: 'Refresh the context cache with context cache warm --execute.'
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
if (!isCodeIndexFileSummaryCacheRecord(parsed)) {
|
|
570
|
+
return {
|
|
571
|
+
status: 'schema-mismatch',
|
|
572
|
+
path: cachePath,
|
|
573
|
+
issue: {
|
|
574
|
+
severity: 'warning',
|
|
575
|
+
code: 'CODE_INDEX_FILE_CACHE_SCHEMA_MISMATCH',
|
|
576
|
+
message: `Code index file summary cache at ${cachePath} does not match ${exports.CODE_INDEX_FILE_SUMMARY_CACHE_SCHEMA_ID}.`,
|
|
577
|
+
path: input.sourceEntry.path,
|
|
578
|
+
fixHint: 'Refresh the context cache with context cache warm --execute.'
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
if (!isFreshCodeIndexFileSummaryCacheRecord(parsed, input.sourceEntry, input.extractorVersion)) {
|
|
583
|
+
return { status: 'stale', path: cachePath, record: parsed };
|
|
584
|
+
}
|
|
585
|
+
return { status: 'fresh', path: cachePath, record: parsed, summary: parsed.summary };
|
|
586
|
+
}
|
|
587
|
+
function writeCodeIndexFileSummaryCache(input) {
|
|
588
|
+
const normalizedPath = (0, extractor_contract_1.normalizeContextGraphPath)(input.sourceEntry.path);
|
|
589
|
+
const record = {
|
|
590
|
+
schemaVersion: exports.CODE_INDEX_FILE_SUMMARY_CACHE_SCHEMA_ID,
|
|
591
|
+
cacheRecordVersion: exports.CODE_INDEX_FILE_SUMMARY_CACHE_VERSION,
|
|
592
|
+
createdAt: input.createdAt,
|
|
593
|
+
path: normalizedPath,
|
|
594
|
+
extractorVersion: input.extractorVersion,
|
|
595
|
+
source: {
|
|
596
|
+
path: normalizedPath,
|
|
597
|
+
sizeBytes: input.sourceEntry.sizeBytes,
|
|
598
|
+
...(input.sourceEntry.mtimeMs === undefined ? {} : { mtimeMs: input.sourceEntry.mtimeMs }),
|
|
599
|
+
...(input.sourceEntry.contentHash ? { contentHash: input.sourceEntry.contentHash } : {}),
|
|
600
|
+
...(input.sourceEntry.metadataHash ? { metadataHash: input.sourceEntry.metadataHash } : {})
|
|
601
|
+
},
|
|
602
|
+
summary: input.summary
|
|
603
|
+
};
|
|
604
|
+
(0, fs_1.atomicWriteTextFile)(input.projectRoot, codeIndexFileSummaryCachePath(normalizedPath), `${JSON.stringify(record, null, 2)}\n`);
|
|
605
|
+
return record;
|
|
606
|
+
}
|
|
607
|
+
function isFreshCodeIndexFileSummaryCacheRecord(record, sourceEntry, extractorVersion) {
|
|
608
|
+
const normalizedPath = (0, extractor_contract_1.normalizeContextGraphPath)(sourceEntry.path);
|
|
609
|
+
return record.path === normalizedPath
|
|
610
|
+
&& record.source.path === normalizedPath
|
|
611
|
+
&& record.summary.filePath === normalizedPath
|
|
612
|
+
&& record.extractorVersion === extractorVersion
|
|
613
|
+
&& record.source.sizeBytes === sourceEntry.sizeBytes
|
|
614
|
+
&& record.source.mtimeMs === sourceEntry.mtimeMs
|
|
615
|
+
&& record.source.contentHash === sourceEntry.contentHash
|
|
616
|
+
&& record.source.metadataHash === sourceEntry.metadataHash;
|
|
617
|
+
}
|
|
618
|
+
function isCodeIndexFileSummaryCacheRecord(value) {
|
|
619
|
+
if (!value || typeof value !== 'object')
|
|
620
|
+
return false;
|
|
621
|
+
const record = value;
|
|
622
|
+
return record.schemaVersion === exports.CODE_INDEX_FILE_SUMMARY_CACHE_SCHEMA_ID
|
|
623
|
+
&& record.cacheRecordVersion === exports.CODE_INDEX_FILE_SUMMARY_CACHE_VERSION
|
|
624
|
+
&& typeof record.createdAt === 'string'
|
|
625
|
+
&& typeof record.path === 'string'
|
|
626
|
+
&& typeof record.extractorVersion === 'string'
|
|
627
|
+
&& Boolean(record.source)
|
|
628
|
+
&& typeof record.source === 'object'
|
|
629
|
+
&& typeof record.source.path === 'string'
|
|
630
|
+
&& typeof record.source.sizeBytes === 'number'
|
|
631
|
+
&& isCodeFileExtractionSummary(record.summary);
|
|
632
|
+
}
|
|
633
|
+
function isCodeFileExtractionSummary(value) {
|
|
634
|
+
if (!value || typeof value !== 'object')
|
|
635
|
+
return false;
|
|
636
|
+
const summary = value;
|
|
637
|
+
return typeof summary.filePath === 'string'
|
|
638
|
+
&& exports.CODE_FILE_KINDS.includes(summary.kind)
|
|
639
|
+
&& exports.CODE_FILE_LANGUAGES.includes(summary.language)
|
|
640
|
+
&& typeof summary.hash === 'string'
|
|
641
|
+
&& typeof summary.lineCount === 'number'
|
|
642
|
+
&& typeof summary.sizeBytes === 'number'
|
|
643
|
+
&& Array.isArray(summary.imports)
|
|
644
|
+
&& Array.isArray(summary.exports)
|
|
645
|
+
&& Array.isArray(summary.commandMentions)
|
|
646
|
+
&& Array.isArray(summary.issues);
|
|
647
|
+
}
|
|
648
|
+
function countLines(content) {
|
|
649
|
+
if (content.length === 0)
|
|
650
|
+
return 0;
|
|
651
|
+
return content.endsWith('\n') ? content.split('\n').length - 1 : content.split('\n').length;
|
|
652
|
+
}
|
|
653
|
+
function extractImportSpecifiersFromLine(line) {
|
|
654
|
+
const specifiers = [];
|
|
655
|
+
const importMatch = line.match(/^\s*import\s+(?:type\s+)?(?:.+?\s+from\s+)?['"]([^'"]+)['"]/);
|
|
656
|
+
if (importMatch?.[1])
|
|
657
|
+
specifiers.push(importMatch[1]);
|
|
658
|
+
const exportFromMatch = line.match(/^\s*export\s+(?:type\s+)?\{[^}]*\}\s+from\s+['"]([^'"]+)['"]/);
|
|
659
|
+
if (exportFromMatch?.[1])
|
|
660
|
+
specifiers.push(exportFromMatch[1]);
|
|
661
|
+
const requirePattern = /require\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
662
|
+
for (const requireMatch of line.matchAll(requirePattern)) {
|
|
663
|
+
if (requireMatch[1])
|
|
664
|
+
specifiers.push(requireMatch[1]);
|
|
665
|
+
}
|
|
666
|
+
return specifiers;
|
|
667
|
+
}
|
|
668
|
+
function extractExportReferencesFromLine(line) {
|
|
669
|
+
const trimmed = line.trim();
|
|
670
|
+
const exports = [];
|
|
671
|
+
const declarationMatch = trimmed.match(/^export\s+(?:async\s+)?(function|class|interface|type|const)\s+([A-Za-z_$][\w$]*)/);
|
|
672
|
+
if (declarationMatch?.[1] && declarationMatch[2]) {
|
|
673
|
+
exports.push({
|
|
674
|
+
name: declarationMatch[2],
|
|
675
|
+
kind: declarationMatch[1]
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
const listMatch = trimmed.match(/^export\s+(?:type\s+)?\{([^}]+)\}/);
|
|
679
|
+
if (listMatch?.[1]) {
|
|
680
|
+
for (const part of listMatch[1].split(',')) {
|
|
681
|
+
const candidate = part.trim();
|
|
682
|
+
if (!candidate)
|
|
683
|
+
continue;
|
|
684
|
+
const aliasParts = candidate.split(/\s+as\s+/);
|
|
685
|
+
const exportedName = (aliasParts[aliasParts.length - 1] ?? '').trim();
|
|
686
|
+
if (/^[A-Za-z_$][\w$]*$/.test(exportedName))
|
|
687
|
+
exports.push({
|
|
688
|
+
name: exportedName,
|
|
689
|
+
kind: 'unknown'
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return exports;
|
|
694
|
+
}
|
|
695
|
+
function resolveRelativeCodeImport(input) {
|
|
696
|
+
if (!isRelativeImportSpecifier(input.specifier))
|
|
697
|
+
return undefined;
|
|
698
|
+
const fromDir = node_path_1.default.posix.dirname((0, extractor_contract_1.normalizeContextGraphPath)(input.fromPath));
|
|
699
|
+
const basePath = (0, extractor_contract_1.normalizeContextGraphPath)(node_path_1.default.posix.join(fromDir, input.specifier));
|
|
700
|
+
const candidates = [
|
|
701
|
+
basePath,
|
|
702
|
+
`${basePath}.ts`,
|
|
703
|
+
`${basePath}.js`,
|
|
704
|
+
`${basePath}.json`,
|
|
705
|
+
`${basePath}/index.ts`,
|
|
706
|
+
`${basePath}/index.js`,
|
|
707
|
+
`${basePath}/index.json`
|
|
708
|
+
];
|
|
709
|
+
for (const candidate of candidates) {
|
|
710
|
+
if (shouldIgnoreCodeIndexPath(candidate) || classifyCodeFile(candidate) === 'unknown')
|
|
711
|
+
continue;
|
|
712
|
+
if (node_fs_1.default.existsSync(node_path_1.default.join(input.projectRoot, candidate)))
|
|
713
|
+
return candidate;
|
|
714
|
+
}
|
|
715
|
+
return undefined;
|
|
716
|
+
}
|
|
717
|
+
function createImportEdges(importReferences) {
|
|
718
|
+
const edges = [];
|
|
719
|
+
const seen = new Set();
|
|
720
|
+
for (const sourceFile of importReferences) {
|
|
721
|
+
const from = createCodeFileNodeId(sourceFile.filePath);
|
|
722
|
+
for (const importReference of sourceFile.imports) {
|
|
723
|
+
if (!importReference.resolvedPath)
|
|
724
|
+
continue;
|
|
725
|
+
const source = (0, extractor_contract_1.createContextGraphSourceRef)({
|
|
726
|
+
path: sourceFile.filePath,
|
|
727
|
+
line: importReference.line,
|
|
728
|
+
hash: sourceFile.hash,
|
|
729
|
+
extractor: 'extractCodeImports'
|
|
730
|
+
});
|
|
731
|
+
const edge = {
|
|
732
|
+
id: createCodeEdgeId({
|
|
733
|
+
type: 'IMPORTS',
|
|
734
|
+
from,
|
|
735
|
+
to: createCodeFileNodeId(importReference.resolvedPath),
|
|
736
|
+
source,
|
|
737
|
+
reason: `File ${sourceFile.filePath} imports ${importReference.resolvedPath}.`
|
|
738
|
+
}),
|
|
739
|
+
from,
|
|
740
|
+
to: createCodeFileNodeId(importReference.resolvedPath),
|
|
741
|
+
type: 'IMPORTS',
|
|
742
|
+
confidence: 'explicit',
|
|
743
|
+
reason: `File ${sourceFile.filePath} imports ${importReference.resolvedPath}.`,
|
|
744
|
+
source
|
|
745
|
+
};
|
|
746
|
+
if (seen.has(edge.id))
|
|
747
|
+
continue;
|
|
748
|
+
seen.add(edge.id);
|
|
749
|
+
edges.push(edge);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return edges.sort((a, b) => a.id.localeCompare(b.id));
|
|
753
|
+
}
|
|
754
|
+
function createCodeSymbolNodes(fileReferences) {
|
|
755
|
+
const symbols = [];
|
|
756
|
+
const seen = new Set();
|
|
757
|
+
for (const sourceFile of fileReferences) {
|
|
758
|
+
for (const exportReference of sourceFile.exports) {
|
|
759
|
+
const id = createCodeSymbolNodeId(sourceFile.filePath, exportReference.name);
|
|
760
|
+
if (seen.has(id))
|
|
761
|
+
continue;
|
|
762
|
+
seen.add(id);
|
|
763
|
+
symbols.push({
|
|
764
|
+
id,
|
|
765
|
+
name: exportReference.name,
|
|
766
|
+
kind: exportReference.kind,
|
|
767
|
+
path: (0, extractor_contract_1.normalizeContextGraphPath)(sourceFile.filePath),
|
|
768
|
+
exported: true,
|
|
769
|
+
line: exportReference.line
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return symbols.sort((a, b) => a.id.localeCompare(b.id));
|
|
774
|
+
}
|
|
775
|
+
function createSymbolEdges(fileReferences) {
|
|
776
|
+
const edges = [];
|
|
777
|
+
const seen = new Set();
|
|
778
|
+
for (const sourceFile of fileReferences) {
|
|
779
|
+
const from = createCodeFileNodeId(sourceFile.filePath);
|
|
780
|
+
for (const exportReference of sourceFile.exports) {
|
|
781
|
+
const to = createCodeSymbolNodeId(sourceFile.filePath, exportReference.name);
|
|
782
|
+
const source = (0, extractor_contract_1.createContextGraphSourceRef)({
|
|
783
|
+
path: sourceFile.filePath,
|
|
784
|
+
line: exportReference.line,
|
|
785
|
+
hash: sourceFile.hash,
|
|
786
|
+
extractor: 'extractCodeSymbols'
|
|
787
|
+
});
|
|
788
|
+
for (const type of ['DEFINES_SYMBOL', 'EXPORTS']) {
|
|
789
|
+
const reason = type === 'DEFINES_SYMBOL'
|
|
790
|
+
? `File ${sourceFile.filePath} defines exported symbol ${exportReference.name}.`
|
|
791
|
+
: `File ${sourceFile.filePath} exports symbol ${exportReference.name}.`;
|
|
792
|
+
const edge = {
|
|
793
|
+
id: createCodeEdgeId({ type, from, to, source, reason }),
|
|
794
|
+
from,
|
|
795
|
+
to,
|
|
796
|
+
type,
|
|
797
|
+
confidence: 'explicit',
|
|
798
|
+
reason,
|
|
799
|
+
source
|
|
800
|
+
};
|
|
801
|
+
if (seen.has(edge.id))
|
|
802
|
+
continue;
|
|
803
|
+
seen.add(edge.id);
|
|
804
|
+
edges.push(edge);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return edges.sort((a, b) => a.id.localeCompare(b.id));
|
|
809
|
+
}
|
|
810
|
+
function createCommandHints(fileReferences) {
|
|
811
|
+
const availablePaths = new Set(fileReferences.map((reference) => reference.filePath));
|
|
812
|
+
const registryLineByCommand = new Map(Object.entries(fileReferences.find((reference) => reference.filePath === COMMAND_REGISTRY_SOURCE_PATH)?.commandIdLines ?? {}));
|
|
813
|
+
const hints = [];
|
|
814
|
+
for (const entry of (0, capability_registry_1.listCommandRegistryEntries)()) {
|
|
815
|
+
const explicitImplementationFiles = normalizeExistingHintPaths(entry.implementationFiles ?? [], availablePaths);
|
|
816
|
+
const heuristicImplementationFiles = explicitImplementationFiles.length > 0
|
|
817
|
+
? []
|
|
818
|
+
: normalizeExistingHintPaths(inferCommandImplementationFiles(entry), availablePaths);
|
|
819
|
+
const testFiles = normalizeExistingHintPaths(entry.testFiles ?? [], availablePaths);
|
|
820
|
+
if (explicitImplementationFiles.length === 0 && heuristicImplementationFiles.length === 0 && testFiles.length === 0) {
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
hints.push({
|
|
824
|
+
commandId: entry.id,
|
|
825
|
+
commandFamily: entry.family,
|
|
826
|
+
implementationFiles: explicitImplementationFiles.length > 0 ? explicitImplementationFiles : heuristicImplementationFiles,
|
|
827
|
+
implementationConfidence: explicitImplementationFiles.length > 0 ? 'explicit' : 'heuristic',
|
|
828
|
+
testFiles,
|
|
829
|
+
sourceLine: registryLineByCommand.get(entry.id)
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
return hints.sort((a, b) => a.commandId.localeCompare(b.commandId));
|
|
833
|
+
}
|
|
834
|
+
function createCommandFamiliesByPath(hints) {
|
|
835
|
+
const familiesByPath = new Map();
|
|
836
|
+
for (const hint of hints) {
|
|
837
|
+
for (const filePath of [...hint.implementationFiles, ...hint.testFiles]) {
|
|
838
|
+
const families = familiesByPath.get(filePath) ?? new Set();
|
|
839
|
+
families.add(hint.commandFamily);
|
|
840
|
+
familiesByPath.set(filePath, families);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
return new Map([...familiesByPath.entries()].map(([filePath, families]) => [filePath, [...families].sort()]));
|
|
844
|
+
}
|
|
845
|
+
function createCommandHintEdges(fileReferences, hints) {
|
|
846
|
+
const summaryByPath = new Map(fileReferences.map((reference) => [reference.filePath, reference]));
|
|
847
|
+
const edges = [];
|
|
848
|
+
const seen = new Set();
|
|
849
|
+
for (const hint of hints) {
|
|
850
|
+
const commandNodeId = (0, extractor_contract_1.createCommandNodeId)(hint.commandId);
|
|
851
|
+
const registrySummary = summaryByPath.get(COMMAND_REGISTRY_SOURCE_PATH);
|
|
852
|
+
const source = (0, extractor_contract_1.createContextGraphSourceRef)({
|
|
853
|
+
path: COMMAND_REGISTRY_SOURCE_PATH,
|
|
854
|
+
...(hint.sourceLine === undefined ? {} : { line: hint.sourceLine }),
|
|
855
|
+
...(registrySummary ? { hash: registrySummary.hash } : {}),
|
|
856
|
+
extractor: 'extractCommandHints'
|
|
857
|
+
});
|
|
858
|
+
for (const filePath of hint.implementationFiles) {
|
|
859
|
+
const from = createCodeFileNodeId(filePath);
|
|
860
|
+
const reason = hint.implementationConfidence === 'explicit'
|
|
861
|
+
? `Command registry explicitly maps command ${hint.commandId} to implementation file ${filePath}.`
|
|
862
|
+
: `Command ${hint.commandId} maps heuristically to CLI handler file ${filePath}.`;
|
|
863
|
+
const edge = {
|
|
864
|
+
id: createCodeEdgeId({ type: 'IMPLEMENTS_COMMAND', from, to: commandNodeId, source, reason }),
|
|
865
|
+
from,
|
|
866
|
+
to: commandNodeId,
|
|
867
|
+
type: 'IMPLEMENTS_COMMAND',
|
|
868
|
+
confidence: hint.implementationConfidence,
|
|
869
|
+
reason,
|
|
870
|
+
source
|
|
871
|
+
};
|
|
872
|
+
if (!seen.has(edge.id)) {
|
|
873
|
+
seen.add(edge.id);
|
|
874
|
+
edges.push(edge);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
for (const filePath of hint.testFiles) {
|
|
878
|
+
const from = createCodeFileNodeId(filePath);
|
|
879
|
+
const reason = `Command registry explicitly maps command ${hint.commandId} to test file ${filePath}.`;
|
|
880
|
+
const edge = {
|
|
881
|
+
id: createCodeEdgeId({ type: 'TESTS_FILE', from, to: commandNodeId, source, reason }),
|
|
882
|
+
from,
|
|
883
|
+
to: commandNodeId,
|
|
884
|
+
type: 'TESTS_FILE',
|
|
885
|
+
confidence: 'explicit',
|
|
886
|
+
reason,
|
|
887
|
+
source
|
|
888
|
+
};
|
|
889
|
+
if (!seen.has(edge.id)) {
|
|
890
|
+
seen.add(edge.id);
|
|
891
|
+
edges.push(edge);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return edges.sort((a, b) => a.id.localeCompare(b.id));
|
|
896
|
+
}
|
|
897
|
+
function createTestRelationEdges(projectRoot, fileReferences) {
|
|
898
|
+
const edges = [];
|
|
899
|
+
const seen = new Set();
|
|
900
|
+
const sourceFiles = fileReferences.filter((reference) => classifyCodeFile(reference.filePath) === 'source');
|
|
901
|
+
const testFiles = fileReferences.filter((reference) => classifyCodeFile(reference.filePath) === 'test');
|
|
902
|
+
const sourceFilesByStem = createSourceFilesByStem(sourceFiles);
|
|
903
|
+
const testPaths = new Set(testFiles.map((reference) => reference.filePath));
|
|
904
|
+
for (const testFile of testFiles) {
|
|
905
|
+
const testNodeId = createCodeFileNodeId(testFile.filePath);
|
|
906
|
+
for (const importReference of testFile.imports) {
|
|
907
|
+
if (!importReference.resolvedPath || classifyCodeFile(importReference.resolvedPath) !== 'source')
|
|
908
|
+
continue;
|
|
909
|
+
const source = (0, extractor_contract_1.createContextGraphSourceRef)({
|
|
910
|
+
path: testFile.filePath,
|
|
911
|
+
line: importReference.line,
|
|
912
|
+
hash: testFile.hash,
|
|
913
|
+
extractor: 'extractTestRelations'
|
|
914
|
+
});
|
|
915
|
+
pushUniqueCodeEdge(edges, seen, {
|
|
916
|
+
type: 'TESTS_FILE',
|
|
917
|
+
from: testNodeId,
|
|
918
|
+
to: createCodeFileNodeId(importReference.resolvedPath),
|
|
919
|
+
confidence: 'explicit',
|
|
920
|
+
reason: `Test file ${testFile.filePath} imports source file ${importReference.resolvedPath}.`,
|
|
921
|
+
source
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
for (const sourceFile of sourceFilesByStem.get(testFileStem(testFile.filePath)) ?? []) {
|
|
925
|
+
const source = (0, extractor_contract_1.createContextGraphSourceRef)({
|
|
926
|
+
path: testFile.filePath,
|
|
927
|
+
line: 1,
|
|
928
|
+
hash: testFile.hash,
|
|
929
|
+
extractor: 'extractTestRelations'
|
|
930
|
+
});
|
|
931
|
+
pushUniqueCodeEdge(edges, seen, {
|
|
932
|
+
type: 'TESTS_FILE',
|
|
933
|
+
from: testNodeId,
|
|
934
|
+
to: createCodeFileNodeId(sourceFile.filePath),
|
|
935
|
+
confidence: 'derived',
|
|
936
|
+
reason: `Test file ${testFile.filePath} matches source file ${sourceFile.filePath} by filename stem.`,
|
|
937
|
+
source
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
for (const mention of testFile.commandMentions) {
|
|
941
|
+
const source = (0, extractor_contract_1.createContextGraphSourceRef)({
|
|
942
|
+
path: testFile.filePath,
|
|
943
|
+
line: mention.line,
|
|
944
|
+
hash: testFile.hash,
|
|
945
|
+
extractor: 'extractTestRelations'
|
|
946
|
+
});
|
|
947
|
+
pushUniqueCodeEdge(edges, seen, {
|
|
948
|
+
type: 'TESTS_FILE',
|
|
949
|
+
from: testNodeId,
|
|
950
|
+
to: (0, extractor_contract_1.createCommandNodeId)(mention.commandId),
|
|
951
|
+
confidence: 'heuristic',
|
|
952
|
+
reason: `Test file ${testFile.filePath} mentions command id ${mention.commandId}.`,
|
|
953
|
+
source
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
for (const evidenceReference of readEvidenceTestReferences(projectRoot, testPaths)) {
|
|
958
|
+
const source = (0, extractor_contract_1.createContextGraphSourceRef)({
|
|
959
|
+
path: evidenceReference.evidencePath,
|
|
960
|
+
line: evidenceReference.line,
|
|
961
|
+
content: evidenceReference.content,
|
|
962
|
+
extractor: 'extractEvidenceTestReferences'
|
|
963
|
+
});
|
|
964
|
+
pushUniqueCodeEdge(edges, seen, {
|
|
965
|
+
type: 'VALIDATED_BY_EVIDENCE',
|
|
966
|
+
from: createCodeFileNodeId(evidenceReference.testPath),
|
|
967
|
+
to: evidenceReference.evidenceId,
|
|
968
|
+
confidence: 'explicit',
|
|
969
|
+
reason: `Evidence record ${evidenceReference.evidenceId} references indexed test file ${evidenceReference.testPath}.`,
|
|
970
|
+
source
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
return edges.sort((a, b) => a.id.localeCompare(b.id));
|
|
974
|
+
}
|
|
975
|
+
function pushUniqueCodeEdge(edges, seen, input) {
|
|
976
|
+
const edge = {
|
|
977
|
+
id: createCodeEdgeId(input),
|
|
978
|
+
from: input.from,
|
|
979
|
+
to: input.to,
|
|
980
|
+
type: input.type,
|
|
981
|
+
confidence: input.confidence,
|
|
982
|
+
reason: input.reason,
|
|
983
|
+
source: input.source
|
|
984
|
+
};
|
|
985
|
+
if (seen.has(edge.id))
|
|
986
|
+
return;
|
|
987
|
+
seen.add(edge.id);
|
|
988
|
+
edges.push(edge);
|
|
989
|
+
}
|
|
990
|
+
function createSourceFilesByStem(sourceFiles) {
|
|
991
|
+
const filesByStem = new Map();
|
|
992
|
+
for (const sourceFile of sourceFiles) {
|
|
993
|
+
const stem = sourceFileStem(sourceFile.filePath);
|
|
994
|
+
const files = filesByStem.get(stem) ?? [];
|
|
995
|
+
files.push(sourceFile);
|
|
996
|
+
filesByStem.set(stem, files.sort((a, b) => a.filePath.localeCompare(b.filePath)));
|
|
997
|
+
}
|
|
998
|
+
return filesByStem;
|
|
999
|
+
}
|
|
1000
|
+
function sourceFileStem(filePath) {
|
|
1001
|
+
return node_path_1.default.posix.basename((0, extractor_contract_1.normalizeContextGraphPath)(filePath)).replace(/\.(?:ts|js|json)$/, '');
|
|
1002
|
+
}
|
|
1003
|
+
function testFileStem(filePath) {
|
|
1004
|
+
return node_path_1.default.posix.basename((0, extractor_contract_1.normalizeContextGraphPath)(filePath)).replace(/\.(?:test|spec)\.(?:ts|js)$/, '');
|
|
1005
|
+
}
|
|
1006
|
+
function findCommandMentionLine(content, commandId) {
|
|
1007
|
+
const pattern = new RegExp(`(^|[^A-Za-z0-9_.-])${escapeRegex(commandId)}([^A-Za-z0-9_.-]|$)`);
|
|
1008
|
+
const lines = content.split(/\r?\n/);
|
|
1009
|
+
const index = lines.findIndex((line) => pattern.test(line));
|
|
1010
|
+
return index >= 0 ? index + 1 : undefined;
|
|
1011
|
+
}
|
|
1012
|
+
function readEvidenceTestReferences(projectRoot, testPaths) {
|
|
1013
|
+
const tasksDir = node_path_1.default.join(projectRoot, 'tasks');
|
|
1014
|
+
if (!node_fs_1.default.existsSync(tasksDir))
|
|
1015
|
+
return [];
|
|
1016
|
+
const references = [];
|
|
1017
|
+
const taskDirs = node_fs_1.default.readdirSync(tasksDir, { withFileTypes: true })
|
|
1018
|
+
.filter((entry) => entry.isDirectory())
|
|
1019
|
+
.map((entry) => entry.name)
|
|
1020
|
+
.sort();
|
|
1021
|
+
for (const taskDir of taskDirs) {
|
|
1022
|
+
const evidencePath = (0, extractor_contract_1.normalizeContextGraphPath)(node_path_1.default.posix.join('tasks', taskDir, 'evidence.jsonl'));
|
|
1023
|
+
const absolutePath = node_path_1.default.join(projectRoot, evidencePath);
|
|
1024
|
+
if (!node_fs_1.default.existsSync(absolutePath))
|
|
1025
|
+
continue;
|
|
1026
|
+
let content;
|
|
1027
|
+
try {
|
|
1028
|
+
content = node_fs_1.default.readFileSync(absolutePath, 'utf8');
|
|
1029
|
+
}
|
|
1030
|
+
catch {
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
content.split(/\r?\n/).forEach((line, index) => {
|
|
1034
|
+
if (!line.trim())
|
|
1035
|
+
return;
|
|
1036
|
+
const evidenceId = readEvidenceIdFromLine(line) ?? `evidence:${evidencePath}#L${index + 1}`;
|
|
1037
|
+
for (const testPath of testPaths) {
|
|
1038
|
+
if (!line.includes(testPath))
|
|
1039
|
+
continue;
|
|
1040
|
+
references.push({
|
|
1041
|
+
evidencePath,
|
|
1042
|
+
line: index + 1,
|
|
1043
|
+
content,
|
|
1044
|
+
evidenceId,
|
|
1045
|
+
testPath
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
return references.sort((a, b) => `${a.evidencePath}:${a.line}:${a.testPath}`.localeCompare(`${b.evidencePath}:${b.line}:${b.testPath}`));
|
|
1051
|
+
}
|
|
1052
|
+
function readEvidenceIdFromLine(line) {
|
|
1053
|
+
try {
|
|
1054
|
+
const parsed = JSON.parse(line);
|
|
1055
|
+
return typeof parsed.id === 'string' && parsed.id.length > 0 ? parsed.id : undefined;
|
|
1056
|
+
}
|
|
1057
|
+
catch {
|
|
1058
|
+
return undefined;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
function normalizeExistingHintPaths(paths, availablePaths) {
|
|
1062
|
+
return uniqueSorted(paths.map(extractor_contract_1.normalizeContextGraphPath).filter((filePath) => availablePaths.has(filePath)));
|
|
1063
|
+
}
|
|
1064
|
+
function createRegistryLineMap(content) {
|
|
1065
|
+
const lineByCommand = new Map();
|
|
1066
|
+
const lines = content.split(/\r?\n/);
|
|
1067
|
+
lines.forEach((line, index) => {
|
|
1068
|
+
const match = line.match(/\bid:\s*['"]([^'"]+)['"]/);
|
|
1069
|
+
if (match?.[1] && !lineByCommand.has(match[1]))
|
|
1070
|
+
lineByCommand.set(match[1], index + 1);
|
|
1071
|
+
});
|
|
1072
|
+
return lineByCommand;
|
|
1073
|
+
}
|
|
1074
|
+
function inferCommandImplementationFiles(entry) {
|
|
1075
|
+
const exact = {
|
|
1076
|
+
'ci.gate': ['src/cli/ci.ts'],
|
|
1077
|
+
'context.graph': ['src/cli/context.ts'],
|
|
1078
|
+
'dev.docker-check': ['src/cli/dev.ts'],
|
|
1079
|
+
'docs.managed.list': ['src/cli/docs.ts'],
|
|
1080
|
+
'docs.managed.explain': ['src/cli/docs.ts'],
|
|
1081
|
+
'docs.required-reading': ['src/cli/docs.ts'],
|
|
1082
|
+
'evidence.add-command': ['src/cli/evidence.ts'],
|
|
1083
|
+
'evidence.collect': ['src/cli/evidence.ts'],
|
|
1084
|
+
'install.plan': ['src/cli/install.ts'],
|
|
1085
|
+
'mcp.serve': ['src/cli/mcp.ts'],
|
|
1086
|
+
'ops.status': ['src/cli/status.ts'],
|
|
1087
|
+
'package.smoke': ['src/cli/package-smoke.ts'],
|
|
1088
|
+
'policy.check-shell': ['src/cli/policy.ts'],
|
|
1089
|
+
'policy.preflight-shell': ['src/cli/policy.ts'],
|
|
1090
|
+
'release.artifact': ['src/cli/release-artifact.ts'],
|
|
1091
|
+
'release.dry-run': ['src/cli/release-dry-run.ts'],
|
|
1092
|
+
'release.gate': ['src/cli/release-gate.ts'],
|
|
1093
|
+
'release.publish': ['src/cli/release-publish.ts'],
|
|
1094
|
+
'run.scaffold': ['src/cli/run-scaffold.ts'],
|
|
1095
|
+
'run-state.resume': ['src/cli/run-state.ts'],
|
|
1096
|
+
'run-state.show': ['src/cli/run-state.ts'],
|
|
1097
|
+
'smoke.clean-checkout': ['src/cli/smoke.ts'],
|
|
1098
|
+
'smoke.run': ['src/cli/smoke.ts'],
|
|
1099
|
+
'task.audit-close': ['src/cli/task.ts'],
|
|
1100
|
+
'task.upgrade-scaffold': ['src/cli/task.ts'],
|
|
1101
|
+
'write.preflight': ['src/cli/write-preflight.ts']
|
|
1102
|
+
};
|
|
1103
|
+
if (exact[entry.id])
|
|
1104
|
+
return exact[entry.id];
|
|
1105
|
+
const familyPrefix = entry.id.split('.')[0] ?? entry.id;
|
|
1106
|
+
const familyPath = `src/cli/${familyPrefix}.ts`;
|
|
1107
|
+
return [familyPath];
|
|
1108
|
+
}
|
|
1109
|
+
function createCodeEdgeId(input) {
|
|
1110
|
+
const fingerprint = (0, extractor_contract_1.hashContextGraphText)(JSON.stringify({
|
|
1111
|
+
type: input.type,
|
|
1112
|
+
from: input.from,
|
|
1113
|
+
to: input.to,
|
|
1114
|
+
source: {
|
|
1115
|
+
path: input.source.path,
|
|
1116
|
+
line: input.source.line ?? null,
|
|
1117
|
+
extractor: input.source.extractor
|
|
1118
|
+
},
|
|
1119
|
+
reason: input.reason
|
|
1120
|
+
})).replace(/^sha256:/, '');
|
|
1121
|
+
return `code-edge:${input.type}:${fingerprint}`;
|
|
1122
|
+
}
|
|
1123
|
+
function escapeRegex(value) {
|
|
1124
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1125
|
+
}
|
|
1126
|
+
function isRelativeImportSpecifier(specifier) {
|
|
1127
|
+
return specifier.startsWith('./') || specifier.startsWith('../');
|
|
1128
|
+
}
|
|
1129
|
+
function dedupeImports(imports) {
|
|
1130
|
+
const seen = new Set();
|
|
1131
|
+
const deduped = [];
|
|
1132
|
+
for (const importReference of imports) {
|
|
1133
|
+
const key = `${importReference.specifier}\0${importReference.resolvedPath ?? ''}\0${importReference.line}`;
|
|
1134
|
+
if (seen.has(key))
|
|
1135
|
+
continue;
|
|
1136
|
+
seen.add(key);
|
|
1137
|
+
deduped.push(importReference);
|
|
1138
|
+
}
|
|
1139
|
+
return deduped;
|
|
1140
|
+
}
|
|
1141
|
+
function dedupeExports(exports) {
|
|
1142
|
+
const seen = new Set();
|
|
1143
|
+
const deduped = [];
|
|
1144
|
+
for (const exportReference of exports) {
|
|
1145
|
+
if (seen.has(exportReference.name))
|
|
1146
|
+
continue;
|
|
1147
|
+
seen.add(exportReference.name);
|
|
1148
|
+
deduped.push(exportReference);
|
|
1149
|
+
}
|
|
1150
|
+
return deduped;
|
|
1151
|
+
}
|
|
1152
|
+
function uniqueSorted(values) {
|
|
1153
|
+
return Array.from(new Set(values)).sort();
|
|
1154
|
+
}
|