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,566 @@
|
|
|
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.CONTEXT_SOURCE_MANIFEST_IGNORED_PATHS = exports.CONTEXT_SOURCE_MANIFEST_DEFAULT_BUDGETS = exports.CONTEXT_SOURCE_MANIFEST_CACHE_VERSION = exports.CONTEXT_SOURCE_MANIFEST_CACHE_PATH = exports.CONTEXT_SOURCE_MANIFEST_CACHE_ROOT = exports.CONTEXT_SOURCE_MANIFEST_SCHEMA_ID = void 0;
|
|
7
|
+
exports.buildContextSourceManifest = buildContextSourceManifest;
|
|
8
|
+
exports.checkContextSourceManifestFastFreshness = checkContextSourceManifestFastFreshness;
|
|
9
|
+
exports.compareContextSourceManifests = compareContextSourceManifests;
|
|
10
|
+
exports.createContextSourceSubsetHash = createContextSourceSubsetHash;
|
|
11
|
+
exports.classifyContextSourcePath = classifyContextSourcePath;
|
|
12
|
+
exports.extractorKeysForContextSource = extractorKeysForContextSource;
|
|
13
|
+
exports.shouldIgnoreContextSourcePath = shouldIgnoreContextSourcePath;
|
|
14
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
15
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
16
|
+
const node_child_process_1 = require("node:child_process");
|
|
17
|
+
const code_index_1 = require("./code-index");
|
|
18
|
+
const extractor_contract_1 = require("./extractor-contract");
|
|
19
|
+
exports.CONTEXT_SOURCE_MANIFEST_SCHEMA_ID = 'hadara.context.sourceManifest.v1';
|
|
20
|
+
exports.CONTEXT_SOURCE_MANIFEST_CACHE_ROOT = '.hadara/local/cache/context';
|
|
21
|
+
exports.CONTEXT_SOURCE_MANIFEST_CACHE_PATH = `${exports.CONTEXT_SOURCE_MANIFEST_CACHE_ROOT}/source-manifest.json`;
|
|
22
|
+
exports.CONTEXT_SOURCE_MANIFEST_CACHE_VERSION = 'c6.1-source-manifest-v1';
|
|
23
|
+
exports.CONTEXT_SOURCE_MANIFEST_DEFAULT_BUDGETS = {
|
|
24
|
+
maxSourceFiles: 5000,
|
|
25
|
+
maxSourceBytes: 40 * 1024 * 1024,
|
|
26
|
+
maxSingleSourceBytes: 2 * 1024 * 1024
|
|
27
|
+
};
|
|
28
|
+
exports.CONTEXT_SOURCE_MANIFEST_IGNORED_PATHS = Array.from(new Set([
|
|
29
|
+
...code_index_1.CODE_INDEX_IGNORED_PATHS,
|
|
30
|
+
'.hadara/local',
|
|
31
|
+
'.hadara/tmp',
|
|
32
|
+
'.hadara/run',
|
|
33
|
+
'.dashboard-visual'
|
|
34
|
+
])).sort();
|
|
35
|
+
const DEFAULT_EXTRACTOR_VERSIONS = {
|
|
36
|
+
codeIndex: code_index_1.CODE_INDEX_EXTRACTOR_VERSION,
|
|
37
|
+
extractAgentHandoff: 'c1-agent-handoff-v1',
|
|
38
|
+
extractCommandRegistry: 'c1-command-registry-v1',
|
|
39
|
+
extractDecisions: 'c1-decisions-v1',
|
|
40
|
+
extractDocsRegistry: 'c1-docs-registry-v1',
|
|
41
|
+
extractEvidence: 'c1-evidence-v1',
|
|
42
|
+
extractManagedSections: 'c1-managed-sections-v1',
|
|
43
|
+
extractProjectState: 'c1-project-state-v1',
|
|
44
|
+
extractReleaseReadiness: 'c1-release-readiness-v1',
|
|
45
|
+
extractTaskBoard: 'c1-task-board-v1',
|
|
46
|
+
extractTaskCapsules: 'c1-task-capsules-v1'
|
|
47
|
+
};
|
|
48
|
+
function buildContextSourceManifest(options) {
|
|
49
|
+
const generatedAt = options.generatedAt ?? new Date().toISOString();
|
|
50
|
+
const budgets = normalizeContextSourceManifestBudgets(options.budgets);
|
|
51
|
+
const extractorVersions = { ...DEFAULT_EXTRACTOR_VERSIONS, ...(options.extractorVersions ?? {}) };
|
|
52
|
+
const ignoreConfigHash = (0, extractor_contract_1.hashContextGraphJson)(exports.CONTEXT_SOURCE_MANIFEST_IGNORED_PATHS);
|
|
53
|
+
const previousByPath = new Map((options.previousManifest?.sources ?? []).map((entry) => [entry.path, entry]));
|
|
54
|
+
const sources = [];
|
|
55
|
+
const issues = [];
|
|
56
|
+
let discoveredBytes = 0;
|
|
57
|
+
let skippedSourceCount = 0;
|
|
58
|
+
let fileBudgetIssueRecorded = false;
|
|
59
|
+
const root = node_path_1.default.resolve(options.projectRoot);
|
|
60
|
+
function addSource(relativePath, input = {}) {
|
|
61
|
+
if (shouldIgnoreContextSourcePath(relativePath))
|
|
62
|
+
return;
|
|
63
|
+
const kind = classifyContextSourcePath(relativePath);
|
|
64
|
+
if (!kind)
|
|
65
|
+
return;
|
|
66
|
+
if (sources.length >= budgets.maxSourceFiles) {
|
|
67
|
+
skippedSourceCount += 1;
|
|
68
|
+
if (!fileBudgetIssueRecorded) {
|
|
69
|
+
fileBudgetIssueRecorded = true;
|
|
70
|
+
issues.push(createPartialIssue({
|
|
71
|
+
message: `Context source manifest exceeded max source file budget (${budgets.maxSourceFiles}); partial manifest returned.`,
|
|
72
|
+
path: relativePath,
|
|
73
|
+
fixHint: 'Reduce context source count or increase the future configurable source manifest budget.'
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const absolutePath = node_path_1.default.join(root, relativePath);
|
|
79
|
+
let stats;
|
|
80
|
+
try {
|
|
81
|
+
stats = node_fs_1.default.statSync(absolutePath);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (input.skipMissing && isMissingFileError(error))
|
|
85
|
+
return;
|
|
86
|
+
skippedSourceCount += 1;
|
|
87
|
+
issues.push({
|
|
88
|
+
severity: 'warning',
|
|
89
|
+
code: 'SOURCE_MANIFEST_READ_FAILED',
|
|
90
|
+
message: `Failed to stat context source ${relativePath}: ${error instanceof Error ? error.message : String(error)}.`,
|
|
91
|
+
path: relativePath,
|
|
92
|
+
fixHint: 'Check file permissions or remove the unreadable file from context source paths.'
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!stats.isFile())
|
|
97
|
+
return;
|
|
98
|
+
if (stats.size > budgets.maxSingleSourceBytes) {
|
|
99
|
+
skippedSourceCount += 1;
|
|
100
|
+
issues.push(createPartialIssue({
|
|
101
|
+
message: `Skipped ${relativePath} because it exceeds the single-source manifest budget (${stats.size} bytes > ${budgets.maxSingleSourceBytes} bytes).`,
|
|
102
|
+
path: relativePath,
|
|
103
|
+
fixHint: 'Reduce the file size or wait for a future configurable source manifest budget.'
|
|
104
|
+
}));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (discoveredBytes + stats.size > budgets.maxSourceBytes) {
|
|
108
|
+
skippedSourceCount += 1;
|
|
109
|
+
issues.push(createPartialIssue({
|
|
110
|
+
message: `Skipped ${relativePath} because the source manifest byte budget would be exceeded (${discoveredBytes + stats.size} bytes > ${budgets.maxSourceBytes} bytes).`,
|
|
111
|
+
path: relativePath,
|
|
112
|
+
fixHint: 'Reduce context source size or wait for a future configurable source manifest budget.'
|
|
113
|
+
}));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
discoveredBytes += stats.size;
|
|
117
|
+
const extractorKeys = extractorKeysForContextSource(relativePath, kind);
|
|
118
|
+
const metadataHash = createContextSourceMetadataHash({
|
|
119
|
+
path: relativePath,
|
|
120
|
+
kind,
|
|
121
|
+
sizeBytes: stats.size,
|
|
122
|
+
mtimeMs: stats.mtimeMs,
|
|
123
|
+
ignoreConfigHash,
|
|
124
|
+
extractorKeys,
|
|
125
|
+
extractorVersions
|
|
126
|
+
});
|
|
127
|
+
const previous = previousByPath.get(relativePath);
|
|
128
|
+
const contentHash = previous && canCarryForwardContentHash(previous, {
|
|
129
|
+
kind,
|
|
130
|
+
sizeBytes: stats.size,
|
|
131
|
+
mtimeMs: stats.mtimeMs
|
|
132
|
+
}) ? previous.contentHash : undefined;
|
|
133
|
+
sources.push({
|
|
134
|
+
path: relativePath,
|
|
135
|
+
kind,
|
|
136
|
+
sizeBytes: stats.size,
|
|
137
|
+
mtimeMs: stats.mtimeMs,
|
|
138
|
+
metadataHash,
|
|
139
|
+
extractorKeys,
|
|
140
|
+
...(contentHash ? { contentHash } : {}),
|
|
141
|
+
parseState: 'ok'
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function visit(relativeDir) {
|
|
145
|
+
if (relativeDir && shouldIgnoreContextSourcePath(relativeDir))
|
|
146
|
+
return;
|
|
147
|
+
const absoluteDir = node_path_1.default.join(root, relativeDir);
|
|
148
|
+
let entries;
|
|
149
|
+
try {
|
|
150
|
+
entries = node_fs_1.default.readdirSync(absoluteDir, { withFileTypes: true });
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
issues.push({
|
|
154
|
+
severity: 'warning',
|
|
155
|
+
code: 'SOURCE_MANIFEST_READ_FAILED',
|
|
156
|
+
message: `Failed to read context source directory ${(0, extractor_contract_1.normalizeContextGraphPath)(relativeDir || '.')}: ${error instanceof Error ? error.message : String(error)}.`,
|
|
157
|
+
path: (0, extractor_contract_1.normalizeContextGraphPath)(relativeDir || '.'),
|
|
158
|
+
fixHint: 'Check directory permissions or exclude the unreadable directory from context source discovery.'
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
163
|
+
const relativePath = (0, extractor_contract_1.normalizeContextGraphPath)(node_path_1.default.join(relativeDir, entry.name));
|
|
164
|
+
if (shouldIgnoreContextSourcePath(relativePath))
|
|
165
|
+
continue;
|
|
166
|
+
if (entry.isDirectory()) {
|
|
167
|
+
visit(relativePath);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (!entry.isFile())
|
|
171
|
+
continue;
|
|
172
|
+
addSource(relativePath);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const gitCandidatePaths = listGitContextSourceCandidatePaths(root);
|
|
176
|
+
if (gitCandidatePaths) {
|
|
177
|
+
for (const relativePath of gitCandidatePaths)
|
|
178
|
+
addSource(relativePath, { skipMissing: true });
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
visit('');
|
|
182
|
+
}
|
|
183
|
+
const sortedSources = sources.sort((a, b) => a.path.localeCompare(b.path));
|
|
184
|
+
const projectFingerprint = (0, extractor_contract_1.hashContextGraphJson)({ rootName: node_path_1.default.basename(root) });
|
|
185
|
+
const fingerprint = createContextSourceManifestFingerprint({
|
|
186
|
+
projectRoot: root,
|
|
187
|
+
projectFingerprint,
|
|
188
|
+
cacheVersion: exports.CONTEXT_SOURCE_MANIFEST_CACHE_VERSION,
|
|
189
|
+
ignoreConfigHash,
|
|
190
|
+
extractorVersions
|
|
191
|
+
});
|
|
192
|
+
const manifestWithoutHash = {
|
|
193
|
+
schemaVersion: exports.CONTEXT_SOURCE_MANIFEST_SCHEMA_ID,
|
|
194
|
+
generatedAt,
|
|
195
|
+
projectFingerprint,
|
|
196
|
+
cacheVersion: exports.CONTEXT_SOURCE_MANIFEST_CACHE_VERSION,
|
|
197
|
+
ignoreConfigHash,
|
|
198
|
+
extractorVersions,
|
|
199
|
+
...(fingerprint ? { fingerprint } : {}),
|
|
200
|
+
sources: sortedSources,
|
|
201
|
+
summary: {
|
|
202
|
+
sourceCount: sortedSources.length,
|
|
203
|
+
totalBytes: discoveredBytes,
|
|
204
|
+
hashedSourceCount: sortedSources.filter((source) => Boolean(source.contentHash)).length,
|
|
205
|
+
skippedSourceCount,
|
|
206
|
+
...(options.generatedByCommand ? { generatedByCommand: options.generatedByCommand } : {})
|
|
207
|
+
},
|
|
208
|
+
budget: {
|
|
209
|
+
...budgets,
|
|
210
|
+
discoveredSourceCount: sortedSources.length,
|
|
211
|
+
discoveredBytes,
|
|
212
|
+
skippedSourceCount
|
|
213
|
+
},
|
|
214
|
+
issues
|
|
215
|
+
};
|
|
216
|
+
return {
|
|
217
|
+
...manifestWithoutHash,
|
|
218
|
+
manifestHash: createStableContextSourceManifestHash(manifestWithoutHash)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function checkContextSourceManifestFastFreshness(projectRoot, manifest) {
|
|
222
|
+
if (!manifest.fingerprint) {
|
|
223
|
+
return { ok: false, reason: 'missing-fingerprint' };
|
|
224
|
+
}
|
|
225
|
+
const currentFingerprint = createContextSourceManifestFingerprint({
|
|
226
|
+
projectRoot,
|
|
227
|
+
projectFingerprint: manifest.projectFingerprint,
|
|
228
|
+
cacheVersion: manifest.cacheVersion,
|
|
229
|
+
ignoreConfigHash: manifest.ignoreConfigHash,
|
|
230
|
+
extractorVersions: manifest.extractorVersions
|
|
231
|
+
});
|
|
232
|
+
if (!currentFingerprint) {
|
|
233
|
+
return { ok: false, reason: 'fingerprint-unavailable' };
|
|
234
|
+
}
|
|
235
|
+
if (sameContextSourceManifestFingerprint(manifest.fingerprint, currentFingerprint)) {
|
|
236
|
+
return {
|
|
237
|
+
ok: true,
|
|
238
|
+
strategy: currentFingerprint.strategy,
|
|
239
|
+
reason: 'fresh',
|
|
240
|
+
currentFingerprint
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
ok: false,
|
|
245
|
+
strategy: currentFingerprint.strategy,
|
|
246
|
+
reason: 'fingerprint-mismatch',
|
|
247
|
+
currentFingerprint
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function compareContextSourceManifests(previous, next) {
|
|
251
|
+
const previousByPath = new Map((previous?.sources ?? []).map((entry) => [entry.path, entry]));
|
|
252
|
+
const nextByPath = new Map(next.sources.map((entry) => [entry.path, entry]));
|
|
253
|
+
const addedPaths = [];
|
|
254
|
+
const removedPaths = [];
|
|
255
|
+
const changedPaths = [];
|
|
256
|
+
const unchangedPaths = [];
|
|
257
|
+
const staleExtractorKeys = new Set();
|
|
258
|
+
for (const nextEntry of next.sources) {
|
|
259
|
+
const previousEntry = previousByPath.get(nextEntry.path);
|
|
260
|
+
if (!previousEntry) {
|
|
261
|
+
addedPaths.push(nextEntry.path);
|
|
262
|
+
nextEntry.extractorKeys.forEach((key) => staleExtractorKeys.add(key));
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (previousEntry.metadataHash === nextEntry.metadataHash && previousEntry.contentHash === nextEntry.contentHash) {
|
|
266
|
+
unchangedPaths.push(nextEntry.path);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
changedPaths.push(nextEntry.path);
|
|
270
|
+
Array.from(new Set([...previousEntry.extractorKeys, ...nextEntry.extractorKeys])).forEach((key) => staleExtractorKeys.add(key));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
for (const previousEntry of previousByPath.values()) {
|
|
274
|
+
if (nextByPath.has(previousEntry.path))
|
|
275
|
+
continue;
|
|
276
|
+
removedPaths.push(previousEntry.path);
|
|
277
|
+
previousEntry.extractorKeys.forEach((key) => staleExtractorKeys.add(key));
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
addedPaths: addedPaths.sort(),
|
|
281
|
+
removedPaths: removedPaths.sort(),
|
|
282
|
+
changedPaths: changedPaths.sort(),
|
|
283
|
+
unchangedPaths: unchangedPaths.sort(),
|
|
284
|
+
staleExtractorKeys: Array.from(staleExtractorKeys).sort()
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function createContextSourceSubsetHash(manifest, input = {}) {
|
|
288
|
+
const kindSet = input.kinds ? new Set(input.kinds) : undefined;
|
|
289
|
+
return (0, extractor_contract_1.hashContextGraphJson)(manifest.sources
|
|
290
|
+
.filter((source) => !input.extractorKey || source.extractorKeys.includes(input.extractorKey))
|
|
291
|
+
.filter((source) => !kindSet || kindSet.has(source.kind))
|
|
292
|
+
.map((source) => ({
|
|
293
|
+
path: source.path,
|
|
294
|
+
kind: source.kind,
|
|
295
|
+
metadataHash: source.metadataHash,
|
|
296
|
+
contentHash: source.contentHash ?? null
|
|
297
|
+
}))
|
|
298
|
+
.sort((a, b) => a.path.localeCompare(b.path)));
|
|
299
|
+
}
|
|
300
|
+
function classifyContextSourcePath(inputPath) {
|
|
301
|
+
const filePath = (0, extractor_contract_1.normalizeContextGraphPath)(inputPath);
|
|
302
|
+
if (filePath === 'docs/TASK_BOARD.md')
|
|
303
|
+
return 'task-board';
|
|
304
|
+
if (filePath === '.hadara/docs-registry.json' || filePath === 'docs/DOC_REGISTRY.md')
|
|
305
|
+
return 'docs-registry';
|
|
306
|
+
if (filePath === 'src/services/capability-registry.ts')
|
|
307
|
+
return 'command-registry';
|
|
308
|
+
if (filePath === 'docs/PROJECT_STATE.md')
|
|
309
|
+
return 'project-state-doc';
|
|
310
|
+
if (filePath === 'docs/AGENT_HANDOFF.md')
|
|
311
|
+
return 'handoff-doc';
|
|
312
|
+
if (filePath === 'docs/RELEASE_READINESS.md')
|
|
313
|
+
return 'release-doc';
|
|
314
|
+
if (filePath.startsWith('docs/specs/') && filePath.endsWith('.md'))
|
|
315
|
+
return 'spec-doc';
|
|
316
|
+
if (filePath.startsWith('tasks/') && filePath.endsWith('/evidence.jsonl'))
|
|
317
|
+
return 'evidence';
|
|
318
|
+
if (filePath.startsWith('tasks/') && filePath.endsWith('.md'))
|
|
319
|
+
return 'task-capsule';
|
|
320
|
+
const codeKind = (0, code_index_1.classifyCodeFile)(filePath);
|
|
321
|
+
if (codeKind === 'source' || codeKind === 'script')
|
|
322
|
+
return 'source-file';
|
|
323
|
+
if (codeKind === 'test')
|
|
324
|
+
return 'test-file';
|
|
325
|
+
if (codeKind === 'fixture')
|
|
326
|
+
return 'fixture-file';
|
|
327
|
+
if (codeKind === 'config')
|
|
328
|
+
return 'config-file';
|
|
329
|
+
if (filePath.startsWith('docs/') && filePath.endsWith('.md'))
|
|
330
|
+
return 'managed-section-source';
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
333
|
+
function extractorKeysForContextSource(relativePath, kind) {
|
|
334
|
+
switch (kind) {
|
|
335
|
+
case 'task-board':
|
|
336
|
+
return ['extractTaskBoard'];
|
|
337
|
+
case 'task-capsule':
|
|
338
|
+
return ['extractTaskCapsules'];
|
|
339
|
+
case 'evidence':
|
|
340
|
+
return ['extractEvidence'];
|
|
341
|
+
case 'docs-registry':
|
|
342
|
+
return ['extractDocsRegistry'];
|
|
343
|
+
case 'command-registry':
|
|
344
|
+
return ['extractCommandRegistry', 'codeIndex'];
|
|
345
|
+
case 'managed-section-source':
|
|
346
|
+
return ['extractManagedSections', 'extractDecisions'];
|
|
347
|
+
case 'release-doc':
|
|
348
|
+
return ['extractReleaseReadiness', 'extractManagedSections'];
|
|
349
|
+
case 'handoff-doc':
|
|
350
|
+
return ['extractAgentHandoff', 'extractManagedSections'];
|
|
351
|
+
case 'project-state-doc':
|
|
352
|
+
return ['extractProjectState', 'extractManagedSections'];
|
|
353
|
+
case 'spec-doc':
|
|
354
|
+
return ['extractDocsRegistry', 'extractManagedSections'];
|
|
355
|
+
case 'source-file':
|
|
356
|
+
case 'test-file':
|
|
357
|
+
case 'fixture-file':
|
|
358
|
+
case 'config-file':
|
|
359
|
+
return ['codeIndex'];
|
|
360
|
+
case 'other-doc':
|
|
361
|
+
return ['extractManagedSections'];
|
|
362
|
+
default:
|
|
363
|
+
return relativePath.startsWith('docs/') ? ['extractManagedSections'] : [];
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function shouldIgnoreContextSourcePath(inputPath) {
|
|
367
|
+
const normalizedPath = (0, extractor_contract_1.normalizeContextGraphPath)(inputPath);
|
|
368
|
+
if (!normalizedPath || normalizedPath === '.')
|
|
369
|
+
return false;
|
|
370
|
+
return exports.CONTEXT_SOURCE_MANIFEST_IGNORED_PATHS.some((ignoredPath) => normalizedPath === ignoredPath || normalizedPath.startsWith(`${ignoredPath}/`));
|
|
371
|
+
}
|
|
372
|
+
function listGitContextSourceCandidatePaths(root) {
|
|
373
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(root, '.git')))
|
|
374
|
+
return undefined;
|
|
375
|
+
try {
|
|
376
|
+
const output = (0, node_child_process_1.execFileSync)('git', ['-C', root, 'ls-files', '-z', '--cached', '--others', '--exclude-standard'], {
|
|
377
|
+
encoding: 'utf8',
|
|
378
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
379
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
380
|
+
timeout: 5000
|
|
381
|
+
});
|
|
382
|
+
return Array.from(new Set(output
|
|
383
|
+
.split('\0')
|
|
384
|
+
.map((entry) => (0, extractor_contract_1.normalizeContextGraphPath)(entry))
|
|
385
|
+
.filter((entry) => entry && !shouldIgnoreContextSourcePath(entry))))
|
|
386
|
+
.sort((a, b) => a.localeCompare(b));
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function createContextSourceManifestFingerprint(input) {
|
|
393
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(input.projectRoot, '.git')))
|
|
394
|
+
return undefined;
|
|
395
|
+
const gitHead = readGitOutput(input.projectRoot, ['rev-parse', 'HEAD']) ?? 'UNBORN';
|
|
396
|
+
const gitStatus = readGitOutput(input.projectRoot, ['status', '--porcelain=v1', '-z', '--untracked-files=all']);
|
|
397
|
+
if (gitStatus === undefined)
|
|
398
|
+
return undefined;
|
|
399
|
+
const relevantStatusEntries = contextRelevantGitStatusEntriesFromGitStatus(gitStatus);
|
|
400
|
+
if (!relevantStatusEntries)
|
|
401
|
+
return undefined;
|
|
402
|
+
const dirtyMetadataHash = createDirtyContextSourceMetadataHash(input.projectRoot, gitStatus);
|
|
403
|
+
if (!dirtyMetadataHash)
|
|
404
|
+
return undefined;
|
|
405
|
+
return {
|
|
406
|
+
strategy: 'git-worktree-v1',
|
|
407
|
+
projectFingerprint: input.projectFingerprint,
|
|
408
|
+
cacheVersion: input.cacheVersion,
|
|
409
|
+
ignoreConfigHash: input.ignoreConfigHash,
|
|
410
|
+
extractorVersionsHash: (0, extractor_contract_1.hashContextGraphJson)(input.extractorVersions),
|
|
411
|
+
gitHead,
|
|
412
|
+
gitStatusHash: (0, extractor_contract_1.hashContextGraphJson)(relevantStatusEntries),
|
|
413
|
+
dirtyContextSourceMetadataHash: dirtyMetadataHash
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function readGitOutput(root, args) {
|
|
417
|
+
try {
|
|
418
|
+
return (0, node_child_process_1.execFileSync)('git', ['-C', root, ...args], {
|
|
419
|
+
encoding: 'utf8',
|
|
420
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
421
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
422
|
+
timeout: 5000
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function createDirtyContextSourceMetadataHash(root, gitStatus) {
|
|
430
|
+
const dirtyPaths = dirtyContextSourcePathsFromGitStatus(gitStatus);
|
|
431
|
+
if (!dirtyPaths)
|
|
432
|
+
return undefined;
|
|
433
|
+
const metadata = [];
|
|
434
|
+
for (const relativePath of dirtyPaths) {
|
|
435
|
+
try {
|
|
436
|
+
const stats = node_fs_1.default.statSync(node_path_1.default.join(root, relativePath));
|
|
437
|
+
if (stats.isFile()) {
|
|
438
|
+
metadata.push({
|
|
439
|
+
path: relativePath,
|
|
440
|
+
sizeBytes: stats.size,
|
|
441
|
+
mtimeMs: stats.mtimeMs
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
if (!isMissingFileError(error))
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return (0, extractor_contract_1.hashContextGraphJson)(metadata.sort((a, b) => a.path.localeCompare(b.path)));
|
|
451
|
+
}
|
|
452
|
+
function dirtyContextSourcePathsFromGitStatus(gitStatus) {
|
|
453
|
+
const entries = contextRelevantGitStatusEntriesFromGitStatus(gitStatus);
|
|
454
|
+
if (!entries)
|
|
455
|
+
return undefined;
|
|
456
|
+
return entries.map((entry) => entry.path).sort((a, b) => a.localeCompare(b));
|
|
457
|
+
}
|
|
458
|
+
function contextRelevantGitStatusEntriesFromGitStatus(gitStatus) {
|
|
459
|
+
const paths = new Set();
|
|
460
|
+
const entriesByPath = new Map();
|
|
461
|
+
const entries = gitStatus.split('\0').filter(Boolean);
|
|
462
|
+
for (const entry of entries) {
|
|
463
|
+
if (entry.length < 4)
|
|
464
|
+
continue;
|
|
465
|
+
const indexStatus = entry[0];
|
|
466
|
+
const worktreeStatus = entry[1];
|
|
467
|
+
if (indexStatus === 'R' || worktreeStatus === 'R' || indexStatus === 'C' || worktreeStatus === 'C') {
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
const relativePath = (0, extractor_contract_1.normalizeContextGraphPath)(entry.slice(3));
|
|
471
|
+
if (!relativePath || shouldIgnoreContextSourcePath(relativePath))
|
|
472
|
+
continue;
|
|
473
|
+
const kind = classifyContextSourcePath(relativePath);
|
|
474
|
+
if (!kind)
|
|
475
|
+
continue;
|
|
476
|
+
paths.add(relativePath);
|
|
477
|
+
entriesByPath.set(relativePath, {
|
|
478
|
+
status: `${indexStatus}${worktreeStatus}`,
|
|
479
|
+
path: relativePath
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return Array.from(paths)
|
|
483
|
+
.sort((a, b) => a.localeCompare(b))
|
|
484
|
+
.map((relativePath) => entriesByPath.get(relativePath))
|
|
485
|
+
.filter((entry) => Boolean(entry));
|
|
486
|
+
}
|
|
487
|
+
function sameContextSourceManifestFingerprint(left, right) {
|
|
488
|
+
return left.strategy === right.strategy
|
|
489
|
+
&& left.projectFingerprint === right.projectFingerprint
|
|
490
|
+
&& left.cacheVersion === right.cacheVersion
|
|
491
|
+
&& left.ignoreConfigHash === right.ignoreConfigHash
|
|
492
|
+
&& left.extractorVersionsHash === right.extractorVersionsHash
|
|
493
|
+
&& left.gitHead === right.gitHead
|
|
494
|
+
&& left.gitStatusHash === right.gitStatusHash
|
|
495
|
+
&& left.dirtyContextSourceMetadataHash === right.dirtyContextSourceMetadataHash;
|
|
496
|
+
}
|
|
497
|
+
function isMissingFileError(error) {
|
|
498
|
+
return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
|
|
499
|
+
}
|
|
500
|
+
function normalizeContextSourceManifestBudgets(input = {}) {
|
|
501
|
+
return {
|
|
502
|
+
maxSourceFiles: normalizeBudgetValue(input.maxSourceFiles, exports.CONTEXT_SOURCE_MANIFEST_DEFAULT_BUDGETS.maxSourceFiles),
|
|
503
|
+
maxSourceBytes: normalizeBudgetValue(input.maxSourceBytes, exports.CONTEXT_SOURCE_MANIFEST_DEFAULT_BUDGETS.maxSourceBytes),
|
|
504
|
+
maxSingleSourceBytes: normalizeBudgetValue(input.maxSingleSourceBytes, exports.CONTEXT_SOURCE_MANIFEST_DEFAULT_BUDGETS.maxSingleSourceBytes)
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
function normalizeBudgetValue(value, fallback) {
|
|
508
|
+
if (value === undefined || !Number.isFinite(value))
|
|
509
|
+
return fallback;
|
|
510
|
+
return Math.max(0, Math.floor(value));
|
|
511
|
+
}
|
|
512
|
+
function createPartialIssue(input) {
|
|
513
|
+
return {
|
|
514
|
+
severity: 'warning',
|
|
515
|
+
code: 'SOURCE_MANIFEST_PARTIAL',
|
|
516
|
+
message: input.message,
|
|
517
|
+
...(input.path ? { path: input.path } : {}),
|
|
518
|
+
fixHint: input.fixHint
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function createContextSourceMetadataHash(input) {
|
|
522
|
+
return (0, extractor_contract_1.hashContextGraphJson)({
|
|
523
|
+
path: input.path,
|
|
524
|
+
kind: input.kind,
|
|
525
|
+
sizeBytes: input.sizeBytes,
|
|
526
|
+
mtimeMs: input.mtimeMs ?? null,
|
|
527
|
+
ignoreConfigHash: input.ignoreConfigHash,
|
|
528
|
+
extractorKeys: input.extractorKeys,
|
|
529
|
+
extractorVersions: Object.fromEntries(input.extractorKeys.map((key) => [key, input.extractorVersions[key] ?? 'unknown']).sort())
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
function createStableContextSourceManifestHash(input) {
|
|
533
|
+
return (0, extractor_contract_1.hashContextGraphJson)({
|
|
534
|
+
schemaVersion: input.schemaVersion,
|
|
535
|
+
projectFingerprint: input.projectFingerprint,
|
|
536
|
+
cacheVersion: input.cacheVersion,
|
|
537
|
+
ignoreConfigHash: input.ignoreConfigHash,
|
|
538
|
+
extractorVersions: input.extractorVersions,
|
|
539
|
+
sources: input.sources.map((source) => ({
|
|
540
|
+
path: source.path,
|
|
541
|
+
kind: source.kind,
|
|
542
|
+
sizeBytes: source.sizeBytes,
|
|
543
|
+
mtimeMs: source.mtimeMs ?? null,
|
|
544
|
+
mtimeNs: source.mtimeNs ?? null,
|
|
545
|
+
contentHash: source.contentHash ?? null,
|
|
546
|
+
metadataHash: source.metadataHash,
|
|
547
|
+
extractorKeys: source.extractorKeys,
|
|
548
|
+
parseState: source.parseState ?? null,
|
|
549
|
+
issueCodes: source.issueCodes ?? []
|
|
550
|
+
})),
|
|
551
|
+
summary: {
|
|
552
|
+
sourceCount: input.summary.sourceCount,
|
|
553
|
+
totalBytes: input.summary.totalBytes,
|
|
554
|
+
hashedSourceCount: input.summary.hashedSourceCount,
|
|
555
|
+
skippedSourceCount: input.summary.skippedSourceCount
|
|
556
|
+
},
|
|
557
|
+
budget: input.budget,
|
|
558
|
+
issues: input.issues
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
function canCarryForwardContentHash(previous, next) {
|
|
562
|
+
return Boolean(previous.contentHash)
|
|
563
|
+
&& previous.kind === next.kind
|
|
564
|
+
&& previous.sizeBytes === next.sizeBytes
|
|
565
|
+
&& previous.mtimeMs === next.mtimeMs;
|
|
566
|
+
}
|