hadara 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +49 -34
  2. package/dist/cli/context.js +110 -0
  3. package/dist/cli/help.js +6 -7
  4. package/dist/cli/init.js +56 -58
  5. package/dist/cli/main.js +12 -0
  6. package/dist/cli/session.js +37 -0
  7. package/dist/cli/task.js +52 -0
  8. package/dist/context/code-graph-extractor.js +123 -0
  9. package/dist/context/code-index.js +1154 -0
  10. package/dist/context/context-cache-store.js +925 -0
  11. package/dist/context/context-graph-builder.js +341 -0
  12. package/dist/context/context-graph.js +42 -0
  13. package/dist/context/context-pack.js +457 -0
  14. package/dist/context/context-slice-boundary.js +37 -0
  15. package/dist/context/context-slice.js +487 -0
  16. package/dist/context/document-extractors.js +343 -0
  17. package/dist/context/evidence-extractors.js +179 -0
  18. package/dist/context/extractor-contract.js +166 -0
  19. package/dist/context/registry-extractors.js +177 -0
  20. package/dist/context/release-extractors.js +175 -0
  21. package/dist/context/session-start.js +297 -0
  22. package/dist/context/source-manifest.js +566 -0
  23. package/dist/context/state-projection.js +209 -0
  24. package/dist/context/task-extractors.js +168 -0
  25. package/dist/core/schema.js +26 -0
  26. package/dist/harness/validate.js +7 -8
  27. package/dist/schemas/code-index.schema.json +173 -0
  28. package/dist/schemas/context-cache-record.schema.json +56 -0
  29. package/dist/schemas/context-cache-status.schema.json +167 -0
  30. package/dist/schemas/context-cache-warm.schema.json +222 -0
  31. package/dist/schemas/context-graph.schema.json +286 -0
  32. package/dist/schemas/context-pack.schema.json +246 -0
  33. package/dist/schemas/context-slice.schema.json +94 -0
  34. package/dist/schemas/context-source-manifest.schema.json +147 -0
  35. package/dist/schemas/schema-index.json +91 -0
  36. package/dist/schemas/session-start.schema.json +158 -0
  37. package/dist/schemas/task-close-repair-plan.schema.json +67 -0
  38. package/dist/schemas/task-context.schema.json +125 -0
  39. package/dist/schemas/task-finalize.schema.json +98 -0
  40. package/dist/schemas/task-lifecycle.schema.json +84 -0
  41. package/dist/services/capability-registry.js +266 -9
  42. package/dist/services/lifecycle-guide.js +23 -29
  43. package/dist/services/protocol-consistency.js +6 -8
  44. package/dist/services/workbench-next-actions.js +2 -0
  45. package/dist/task/acceptance.js +171 -0
  46. package/dist/task/task-close-repair-plan.js +190 -0
  47. package/dist/task/task-close.js +34 -35
  48. package/dist/task/task-finalize.js +377 -0
  49. package/dist/task/task-lifecycle.js +210 -0
  50. package/dist/task/task-next.js +10 -1
  51. package/dist/task/task-ready.js +4 -0
  52. 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
+ }