harness-everything 0.46.2 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +30 -1
- package/benchmarks/results/live-host/claude-341-outline-dispatch-2026-10-28/README.md +33 -8
- package/benchmarks/results/live-host/claude-341-outline-dispatch-2026-10-28/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md +128 -0
- package/benchmarks/results/live-host/claude-341-outline-dispatch-2026-10-28/sanitize-outline-capture.js +105 -0
- package/ci/mechanism-e1-derived-regenerate.test.js +333 -0
- package/ci/mechanism-outline-sanitizer.test.js +79 -0
- package/ci/mechanism-prb-outline-review.test.js +318 -31
- package/docs/doc-workflow-reconciliation.md +12 -8
- package/docs/platform-capabilities.md +1 -1
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/scripts/doc-check.js +106 -8
- package/harness-everything/scripts/doc-lifecycle.js +49 -1
- package/hooks/scripts/lib/doc-reconciliation-events.js +22 -0
- package/hooks/scripts/lib/doc-reconciliation-outline-capture.sanitized.jsonl +24 -0
- package/hooks/scripts/lib/doc-reconciliation-pilot.js +3 -1
- package/hooks/scripts/lib/doc-reconciliation-provider.js +182 -13
- package/hooks/scripts/lib/doc-reconciliation-qualification.json +20 -0
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-events.js +22 -0
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-outline-capture.sanitized.jsonl +24 -0
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-pilot.js +3 -1
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-provider.js +182 -13
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-qualification.json +20 -0
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-check.js +106 -8
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-lifecycle.js +49 -1
- package/sbom.cdx.json +7 -7
|
@@ -47,7 +47,7 @@ const QUALIFICATION_EXPORT_SCHEMA = 'harness.doc-reconciliation.qualification.v1
|
|
|
47
47
|
const HEX64 = /^[0-9a-f]{64}$/;
|
|
48
48
|
const PRE_CAPTURE_TOOLS = ['Bash', 'PowerShell', 'Task', 'Agent', 'Write', 'Edit', 'apply_patch'];
|
|
49
49
|
const STDOUT_CAP_BYTES = 1024 * 1024;
|
|
50
|
-
const DOC_CHECK_FLAGS = new Set(['--workspace', '--config', '--changed-path', '--base']);
|
|
50
|
+
const DOC_CHECK_FLAGS = new Set(['--workspace', '--config', '--changed-path', '--base', '--derived-view']);
|
|
51
51
|
const DOC_CHECK_BOOLEAN_FLAGS = new Set(['--conflict-assessment']);
|
|
52
52
|
const DOC_CHECK_SCHEMA = 'harness.doc-check.check.v1';
|
|
53
53
|
const DOC_CHECK_CONFLICT_SCHEMA = 'harness.doc-check.conflict-assessment.v1';
|
|
@@ -76,6 +76,8 @@ const OUTLINE_INVENTORY_PIN = Object.freeze({
|
|
|
76
76
|
runId: 'e1-inv-1',
|
|
77
77
|
inputSha: '0ef0541c6cae550e8269ba712e03797e0e987dfb',
|
|
78
78
|
approvalRef: { issue: 325, commentId: '6436207729', decision: 1 },
|
|
79
|
+
rowDomainMappings: Object.freeze([Object.freeze({ rowIds: Object.freeze(['15', '16', '17', '18']),
|
|
80
|
+
sourceDomain: 'VERI', domain: 'EVID', approvalRef: '../e1-feasibility-2026-10-28/owner-decision-veri-evid.md' })]),
|
|
79
81
|
retainedArtifactPath: `inventory/${OUTLINE_INVENTORY_SHA256}.md`,
|
|
80
82
|
});
|
|
81
83
|
const OUTLINE_MARKER = 'HARNESS_OUTLINE_DISPATCH_V1';
|
|
@@ -99,7 +101,7 @@ function isUncPathPrefix(token, command, index) {
|
|
|
99
101
|
return token === '' && command[index] === '\\' && command[index + 1] === '\\';
|
|
100
102
|
}
|
|
101
103
|
// Canonical SHA-256 of harness-everything/scripts/doc-check.js (CRLF normalised to LF). Re-pin deliberately when that script changes.
|
|
102
|
-
const SHIPPED_DOC_CHECK_DIGEST = '
|
|
104
|
+
const SHIPPED_DOC_CHECK_DIGEST = '804a093b31afde07ea6ec40e39fc758b501966bfa2a15c53a5275ad3171b0755';
|
|
103
105
|
// This install's own doc-check runtime, relative to hooks/scripts/lib: the source checkout or the packaged plugin.
|
|
104
106
|
const DOC_CHECK_INSTALL_LAYOUTS = [
|
|
105
107
|
['..', '..', '..', 'harness-everything', 'scripts', 'doc-check.js'],
|
|
@@ -405,6 +407,8 @@ function resolveDocCheckInvocation(command, cwd) {
|
|
|
405
407
|
if (flag === '--changed-path') changedPaths.push(value);
|
|
406
408
|
else {
|
|
407
409
|
if (values.has(flag)) return rejected('duplicate-flag');
|
|
410
|
+
if (flag === '--derived-view' && (path.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value)
|
|
411
|
+
|| value.split(/[\\/]/).includes('..') || /[*?\[\]{}]/.test(value))) return rejected('derived-view-invalid');
|
|
408
412
|
values.set(flag, value);
|
|
409
413
|
}
|
|
410
414
|
}
|
|
@@ -425,7 +429,8 @@ function resolveDocCheckInvocation(command, cwd) {
|
|
|
425
429
|
}
|
|
426
430
|
}
|
|
427
431
|
return { kind: 'accepted', reason: null, installed: registered.installed, digest, argv,
|
|
428
|
-
conflictAssessment, base: values.get('--base') || null,
|
|
432
|
+
conflictAssessment, base: values.get('--base') || null, config: values.get('--config'),
|
|
433
|
+
derivedView: values.get('--derived-view') || null, changedPaths };
|
|
429
434
|
}
|
|
430
435
|
|
|
431
436
|
// Observation-time digests of the checked paths, read from the workspace when the check ran. Absent paths are omitted.
|
|
@@ -439,6 +444,29 @@ function observedDigestsFor(root, paths) {
|
|
|
439
444
|
return digests;
|
|
440
445
|
}
|
|
441
446
|
|
|
447
|
+
function observedConfigSourcesFor(root, sources) {
|
|
448
|
+
if (!Array.isArray(sources)) return [];
|
|
449
|
+
return sources.map(source => {
|
|
450
|
+
const relative = source && source.path;
|
|
451
|
+
if (typeof relative !== 'string' || !relative || path.isAbsolute(relative) || relative.split(/[\\/]/).includes('..')) {
|
|
452
|
+
return { path: String(relative || ''), presence: 'unsupported' };
|
|
453
|
+
}
|
|
454
|
+
const file = path.resolve(root, relative);
|
|
455
|
+
const within = path.relative(path.resolve(root), file);
|
|
456
|
+
if (!within || within === '..' || within.startsWith(`..${path.sep}`) || path.isAbsolute(within)) {
|
|
457
|
+
return { path: relative, presence: 'unsupported' };
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
if (!fs.lstatSync(file).isFile()) return { path: relative, presence: 'unsupported' };
|
|
461
|
+
return { path: relative, presence: 'present', digest: crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex') };
|
|
462
|
+
} catch (error) {
|
|
463
|
+
return error.code === 'ENOENT' || error.code === 'ENOTDIR'
|
|
464
|
+
? { path: relative, presence: 'missing' }
|
|
465
|
+
: { path: relative, presence: 'unreadable' };
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
442
470
|
function textOf(value) { return typeof value === 'string' && value.trim().length > 0; }
|
|
443
471
|
function recordOf(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); }
|
|
444
472
|
function compareKeys(a, b) { return a < b ? -1 : a > b ? 1 : 0; }
|
|
@@ -695,7 +723,7 @@ function parseInventoryPinRows(bytes) {
|
|
|
695
723
|
|
|
696
724
|
function validateOutlineInventoryPin({ bytes, pin, expectedPin = OUTLINE_INVENTORY_PIN } = {}) {
|
|
697
725
|
if (!Buffer.isBuffer(bytes) || !recordOf(pin) || !recordOf(expectedPin)) return { state: 'unresolved', reason: 'inventory-unavailable' };
|
|
698
|
-
const fields = ['source', 'sha256', 'runId', 'inputSha', 'approvalRef', 'retainedArtifactPath'];
|
|
726
|
+
const fields = ['source', 'sha256', 'runId', 'inputSha', 'approvalRef', 'rowDomainMappings', 'retainedArtifactPath'];
|
|
699
727
|
const actualPin = Object.fromEntries(fields.map(field => [field, pin[field]]));
|
|
700
728
|
const trustedPin = Object.fromEntries(fields.map(field => [field, expectedPin[field]]));
|
|
701
729
|
if (captureJson(actualPin) !== captureJson(trustedPin)) return { state: 'unresolved', reason: 'inventory-pin-mismatch' };
|
|
@@ -716,10 +744,18 @@ function resolveInventoryRow(inventory, rowId) {
|
|
|
716
744
|
if (!parsed || parsed.state !== 'parsed' || !textOf(rowId)) return { state: 'unresolved', reason: 'inventory-row-missing' };
|
|
717
745
|
const matches = [...parsed.rows.entries()].filter(([id]) => id === rowId);
|
|
718
746
|
const domains = parsed.domains.get(rowId) || [];
|
|
719
|
-
if (matches.length !== 1 || matches[0][1] !== 1
|
|
747
|
+
if (matches.length !== 1 || matches[0][1] !== 1) {
|
|
720
748
|
return { state: 'unresolved', reason: 'inventory-row-ambiguous' };
|
|
721
749
|
}
|
|
722
|
-
|
|
750
|
+
const mappings = (Array.isArray(inventory.pin?.rowDomainMappings) ? inventory.pin.rowDomainMappings : [])
|
|
751
|
+
.filter(item => recordOf(item) && Array.isArray(item.rowIds) && item.rowIds.includes(rowId));
|
|
752
|
+
if (mappings.length > 1) return { state: 'unresolved', reason: 'inventory-row-ambiguous' };
|
|
753
|
+
const mapping = mappings[0];
|
|
754
|
+
if (mapping && (!domains.includes(mapping.sourceDomain) || !textOf(mapping.domain) || !textOf(mapping.approvalRef))) {
|
|
755
|
+
return { state: 'unresolved', reason: 'inventory-row-mapping-invalid' };
|
|
756
|
+
}
|
|
757
|
+
if (!mapping && domains.length !== 1) return { state: 'unresolved', reason: 'inventory-row-ambiguous' };
|
|
758
|
+
return { state: 'accepted', row: { rowId, domain: mapping ? mapping.domain : domains[0] } };
|
|
723
759
|
}
|
|
724
760
|
|
|
725
761
|
function hasSubstantiveDecision(text) {
|
|
@@ -1165,11 +1201,20 @@ function validateOutlineCaptureQualification(input, expectedHost = HOST_ID) {
|
|
|
1165
1201
|
const qualificationError = validateQualificationExport(expectedHost);
|
|
1166
1202
|
if (qualificationError) return { open: false, reason: qualificationError };
|
|
1167
1203
|
const exported = JSON.parse(fs.readFileSync(path.join(__dirname, EXPORT_FILE), 'utf8'));
|
|
1168
|
-
const
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1204
|
+
const wrapper = exported.outlineDispatchCapture;
|
|
1205
|
+
if (!recordOf(wrapper) || !textOf(wrapper.hostVersion) || !textOf(wrapper.run) || !textOf(wrapper.baseline)
|
|
1206
|
+
|| !textOf(wrapper.file) || /[\\/]/.test(wrapper.file) || wrapper.file.includes('..')) {
|
|
1207
|
+
return { open: false, reason: 'outline-live-capture-unqualified' };
|
|
1208
|
+
}
|
|
1209
|
+
const bytes = fs.readFileSync(path.join(__dirname, wrapper.file));
|
|
1210
|
+
if (crypto.createHash('sha256').update(bytes).digest('hex') !== wrapper.sha256) {
|
|
1211
|
+
return { open: false, reason: 'outline-live-capture-unqualified' };
|
|
1212
|
+
}
|
|
1213
|
+
const records = bytes.toString('utf8').split(/\r?\n/).filter(line => line.trim()).map(line => JSON.parse(line));
|
|
1214
|
+
if (!records.every(recordOf) || !outlineCaptureMappingValid(wrapper.mapping, records)) {
|
|
1215
|
+
return { open: false, reason: 'outline-live-capture-unqualified' };
|
|
1216
|
+
}
|
|
1217
|
+
return { open: true, source: 'validated-export', captureFile: wrapper.file };
|
|
1173
1218
|
} catch (error) {
|
|
1174
1219
|
return { open: false, reason: `outline-live-capture-unqualified:${error.code || 'invalid'}` };
|
|
1175
1220
|
}
|
|
@@ -1452,8 +1497,35 @@ function resolveOutlineEvidence({ safeInput, capture, captures, request, originR
|
|
|
1452
1497
|
if (review.reviewerPrincipal === worker.agent_id) return fail('outline-reviewer-is-draft-worker');
|
|
1453
1498
|
const principals = { author: safeInput.sessionId, executors: journalExecutors(captures, safeInput.sessionId) };
|
|
1454
1499
|
if (!reviewRecordIndependent(review.review, captures, principals)) return fail('outline-review-not-independent');
|
|
1455
|
-
|
|
1456
|
-
|
|
1500
|
+
// An agent whose start and unique stop both precede the dispatch cannot write after the seam; earlier writes
|
|
1501
|
+
// are covered by the clean-O seam check and by unattributedWriters below.
|
|
1502
|
+
const journal = Array.isArray(captures) ? captures : [];
|
|
1503
|
+
const dispatchIndex = journal.indexOf(correlation.pre);
|
|
1504
|
+
const uniqueLifecycle = start => {
|
|
1505
|
+
if (!textOf(start.session_id) || !textOf(start.agent_id)) return null;
|
|
1506
|
+
const lifecycle = event => journal.filter(item => recordOf(item) && item.event === event
|
|
1507
|
+
&& item.session_id === start.session_id && textOf(start.agent_id) && item.agent_id === start.agent_id);
|
|
1508
|
+
// Exactly one Start and one Stop per (session_id, agent_id); a duplicate either way stays ambiguous.
|
|
1509
|
+
const starts = lifecycle('SubagentStart');
|
|
1510
|
+
const stops = lifecycle('SubagentStop');
|
|
1511
|
+
if (starts.length !== 1 || starts[0] !== start || stops.length !== 1) return null;
|
|
1512
|
+
const startIndex = journal.indexOf(start);
|
|
1513
|
+
const stopIndex = journal.indexOf(stops[0]);
|
|
1514
|
+
if (startIndex < 0 || stopIndex <= startIndex) return null;
|
|
1515
|
+
return { startIndex, stopIndex };
|
|
1516
|
+
};
|
|
1517
|
+
const stoppedBeforeDispatch = start => {
|
|
1518
|
+
const lifecycle = uniqueLifecycle(start);
|
|
1519
|
+
return Boolean(lifecycle && dispatchIndex >= 0 && lifecycle.stopIndex < dispatchIndex);
|
|
1520
|
+
};
|
|
1521
|
+
const startedAfterWorkerStop = start => {
|
|
1522
|
+
const lifecycle = uniqueLifecycle(start);
|
|
1523
|
+
const workerStopIndex = journal.indexOf(correlation.stop);
|
|
1524
|
+
return Boolean(lifecycle && workerStopIndex >= 0 && lifecycle.startIndex > workerStopIndex);
|
|
1525
|
+
};
|
|
1526
|
+
const competingStarts = journal.filter(item => recordOf(item)
|
|
1527
|
+
&& item.event === 'SubagentStart' && item.ref !== worker.ref && sameCanonicalPath(item.cwd, workerRoot)
|
|
1528
|
+
&& !stoppedBeforeDispatch(item) && !startedAfterWorkerStop(item));
|
|
1457
1529
|
if (competingStarts.length) return fail('outline-competing-writer-start');
|
|
1458
1530
|
const unattributedWriters = (Array.isArray(captures) ? captures : []).filter(item => possibleWriterCapture(item)
|
|
1459
1531
|
&& (item.agent_id !== worker.agent_id || !sameCanonicalPath(item.cwd, workerRoot)));
|
|
@@ -1709,6 +1781,92 @@ const createClaudeOriginProvider = input => {
|
|
|
1709
1781
|
if (!paths.length) return rejected('scope-empty', `${capability} checked no governed paths`);
|
|
1710
1782
|
let corpusBinding;
|
|
1711
1783
|
let baselineArtifactDigests;
|
|
1784
|
+
let derivedBinding;
|
|
1785
|
+
if (capability === 'derived-regenerate') {
|
|
1786
|
+
if (!registered.derivedView || paths.length !== 1 || paths[0] !== registered.derivedView) {
|
|
1787
|
+
return rejected('capability-mismatch', 'derived-regenerate requires the exact registered --derived-view path');
|
|
1788
|
+
}
|
|
1789
|
+
const binding = projection.binding;
|
|
1790
|
+
const corpus = binding && binding.sourceCorpus;
|
|
1791
|
+
const config = binding && binding.config;
|
|
1792
|
+
const generator = binding && binding.generator;
|
|
1793
|
+
const generation = binding && binding.generation;
|
|
1794
|
+
const comparison = binding && binding.comparison;
|
|
1795
|
+
if (!recordOf(binding) || binding.view !== registered.derivedView || !recordOf(corpus) || corpus.complete !== true
|
|
1796
|
+
|| !Array.isArray(corpus.paths) || !corpus.paths.length || !recordOf(corpus.digests)
|
|
1797
|
+
|| JSON.stringify([...new Set(corpus.paths)].sort(compareKeys)) !== JSON.stringify(corpus.paths)
|
|
1798
|
+
|| JSON.stringify(Object.keys(corpus.digests).sort(compareKeys)) !== JSON.stringify(corpus.paths)
|
|
1799
|
+
|| corpus.paths.some(relative => !textOf(relative) || path.isAbsolute(relative) || relative.split(/[\\/]/).includes('..')
|
|
1800
|
+
|| !HEX64.test(String(corpus.digests[relative] || '')))
|
|
1801
|
+
|| !recordOf(config) || !textOf(config.path) || path.isAbsolute(config.path)
|
|
1802
|
+
|| config.path.split(/[\\/]/).includes('..') || !HEX64.test(String(config.digest || ''))
|
|
1803
|
+
|| !Array.isArray(config.sources) || !recordOf(generator) || !textOf(generator.path)
|
|
1804
|
+
|| !HEX64.test(String(generator.digest || '')) || !recordOf(generation) || generation.state !== 'pass'
|
|
1805
|
+
|| !HEX64.test(String(generation.outputDigest || '')) || !recordOf(comparison) || comparison.state !== 'equal'
|
|
1806
|
+
|| comparison.targetDigest !== generation.outputDigest) {
|
|
1807
|
+
return rejected('derived-binding-invalid', 'derived-regenerate stdout lacks complete generation, comparison, and input binding');
|
|
1808
|
+
}
|
|
1809
|
+
const configPath = path.resolve(cap.cwd, registered.config);
|
|
1810
|
+
const configRelative = path.relative(path.resolve(cap.cwd), configPath).split(path.sep).join('/');
|
|
1811
|
+
const observedDerived = recordOf(cap.observedDerivedDigests) ? cap.observedDerivedDigests : {};
|
|
1812
|
+
if (configRelative !== config.path || observedDerived[config.path] !== config.digest
|
|
1813
|
+
|| observedDerived[registered.derivedView] !== comparison.targetDigest) {
|
|
1814
|
+
return rejected('derived-input-changed-after-check', 'the selected config or derived target changed before host capture');
|
|
1815
|
+
}
|
|
1816
|
+
const expectedObservedPaths = [...new Set([...corpus.paths, config.path, registered.derivedView])].sort(compareKeys);
|
|
1817
|
+
if (JSON.stringify(Object.keys(observedDerived).sort(compareKeys)) !== JSON.stringify(expectedObservedPaths)
|
|
1818
|
+
|| corpus.paths.some(relative => observedDerived[relative] !== corpus.digests[relative])) {
|
|
1819
|
+
return rejected('derived-input-changed-after-check', 'a source path is missing or changed before host capture');
|
|
1820
|
+
}
|
|
1821
|
+
const configSources = config.sources;
|
|
1822
|
+
if (JSON.stringify(configSources.map(item => item && item.path)) !== JSON.stringify([...new Set(configSources.map(item => item && item.path))].sort(compareKeys))
|
|
1823
|
+
|| configSources.some(item => !recordOf(item) || !textOf(item.path) || path.isAbsolute(item.path)
|
|
1824
|
+
|| item.path.split(/[\\/]/).includes('..') || !['present', 'missing'].includes(item.presence)
|
|
1825
|
+
|| (item.presence === 'present' ? !HEX64.test(String(item.digest || '')) : item.digest !== undefined))) {
|
|
1826
|
+
return rejected('derived-config-binding-invalid', 'derived-regenerate config source inventory is malformed');
|
|
1827
|
+
}
|
|
1828
|
+
const observedConfigSources = cap.observedDerivedConfigSources;
|
|
1829
|
+
if (!Array.isArray(observedConfigSources) || JSON.stringify(observedConfigSources) !== JSON.stringify(configSources)) {
|
|
1830
|
+
return rejected('config-changed-after-check', 'a config source changed between check and host capture');
|
|
1831
|
+
}
|
|
1832
|
+
const configSource = configSources.find(item => item.path === config.path);
|
|
1833
|
+
if (!configSource || configSource.presence !== 'present' || configSource.digest !== config.digest) {
|
|
1834
|
+
return rejected('derived-config-binding-invalid', 'the selected config is absent from the observed config inventory');
|
|
1835
|
+
}
|
|
1836
|
+
let configData;
|
|
1837
|
+
try { configData = JSON.parse(fs.readFileSync(configPath, 'utf8')); }
|
|
1838
|
+
catch { return rejected('derived-config-binding-invalid', 'the selected config cannot be read during assessment'); }
|
|
1839
|
+
const selectedViews = Array.isArray(configData.derived) ? configData.derived.filter(item => item && item.path === registered.derivedView) : [];
|
|
1840
|
+
const expectedGenerator = `node harness-everything/scripts/doc-lifecycle.js index --workspace . --config ${registered.config} --all`;
|
|
1841
|
+
if (selectedViews.length !== 1 || selectedViews[0].generator !== expectedGenerator) {
|
|
1842
|
+
return rejected('derived-generator-unsupported', 'the selected config does not declare the supported lifecycle index operation');
|
|
1843
|
+
}
|
|
1844
|
+
let generatorPath;
|
|
1845
|
+
try { generatorPath = fs.realpathSync(path.join(path.dirname(registered.installed), 'doc-lifecycle.js')); }
|
|
1846
|
+
catch { return rejected('derived-generator-unavailable', 'the installed lifecycle index generator is missing'); }
|
|
1847
|
+
if (generator.path !== generatorPath || fileDigest(generatorPath) !== generator.digest) {
|
|
1848
|
+
return rejected('derived-generator-mismatch', 'the lifecycle generator differs from the implementation observed in stdout');
|
|
1849
|
+
}
|
|
1850
|
+
let currentConfigSources;
|
|
1851
|
+
try {
|
|
1852
|
+
const lifecycle = require(generatorPath);
|
|
1853
|
+
currentConfigSources = lifecycle.configSourceInventory(cap.cwd, configPath);
|
|
1854
|
+
} catch { return rejected('derived-config-binding-invalid', 'the lifecycle config inventory is unavailable'); }
|
|
1855
|
+
if (!currentConfigSources || currentConfigSources.complete !== true
|
|
1856
|
+
|| JSON.stringify(currentConfigSources.sources) !== JSON.stringify(configSources)) {
|
|
1857
|
+
return rejected('config-changed-after-check', 'the lifecycle config source inventory changed after the check');
|
|
1858
|
+
}
|
|
1859
|
+
if (!recordOf(request.identity) || !HEX64.test(String(request.identity.baselineDigest || ''))
|
|
1860
|
+
|| !HEX64.test(String(request.identity.candidateDigest || '')) || !HEX64.test(String(request.identity.configDigest || ''))) {
|
|
1861
|
+
return rejected('identity-missing', 'derived-regenerate needs the complete snapshot identity');
|
|
1862
|
+
}
|
|
1863
|
+
derivedBinding = {
|
|
1864
|
+
...binding,
|
|
1865
|
+
baselineDigest: request.identity.baselineDigest,
|
|
1866
|
+
candidateDigest: request.identity.candidateDigest,
|
|
1867
|
+
configIdentityDigest: request.identity.configDigest,
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1712
1870
|
if (capability === 'conflict-check') {
|
|
1713
1871
|
const assessment = report.conflictAssessment;
|
|
1714
1872
|
const corpus = assessment && assessment.corpus;
|
|
@@ -1781,6 +1939,7 @@ const createClaudeOriginProvider = input => {
|
|
|
1781
1939
|
artifactDigests,
|
|
1782
1940
|
...(Object.keys(baselineArtifacts).length ? { baselineArtifacts } : {}),
|
|
1783
1941
|
...(corpusBinding ? { corpusBinding } : {}),
|
|
1942
|
+
...(derivedBinding ? { derivedBinding } : {}),
|
|
1784
1943
|
captured: { responseRef: cap.responseRef, responseDigest: cap.responseDigest },
|
|
1785
1944
|
};
|
|
1786
1945
|
return { status: 'accepted', originRef, event, correlation: { toolCorrelationId: cap.tool_use_id, resultRef: cap.responseRef } };
|
|
@@ -2115,6 +2274,16 @@ function buildCaptureRecord(payload, eventName, workspaceRoot) {
|
|
|
2115
2274
|
const report = JSON.parse(stdout);
|
|
2116
2275
|
checkedPaths = ['doc-lint', 'link-check'].flatMap(capability => (recordOf(report.capabilities) && recordOf(report.capabilities[capability])
|
|
2117
2276
|
&& Array.isArray(report.capabilities[capability].checkedPaths) ? report.capabilities[capability].checkedPaths : []));
|
|
2277
|
+
const derived = recordOf(report.capabilities) && report.capabilities['derived-regenerate'];
|
|
2278
|
+
const derivedBinding = recordOf(derived) && recordOf(derived.binding) ? derived.binding : null;
|
|
2279
|
+
if (installedCheck.derivedView && typeof workspaceRoot === 'string' && workspaceRoot && derivedBinding && derivedBinding.view === installedCheck.derivedView
|
|
2280
|
+
&& recordOf(derivedBinding.sourceCorpus) && Array.isArray(derivedBinding.sourceCorpus.paths)
|
|
2281
|
+
&& recordOf(derivedBinding.config) && Array.isArray(derivedBinding.config.sources)) {
|
|
2282
|
+
checkedPaths.push(derivedBinding.view);
|
|
2283
|
+
record.observedDerivedDigests = observedDigestsFor(workspaceRoot,
|
|
2284
|
+
[...derivedBinding.sourceCorpus.paths, derivedBinding.config.path, derivedBinding.view]);
|
|
2285
|
+
record.observedDerivedConfigSources = observedConfigSourcesFor(workspaceRoot, derivedBinding.config.sources);
|
|
2286
|
+
}
|
|
2118
2287
|
const conflict = recordOf(report.capabilities) && report.capabilities['conflict-check'];
|
|
2119
2288
|
if (installedCheck.conflictAssessment === true && report.mode === 'conflict-assessment'
|
|
2120
2289
|
&& recordOf(conflict) && conflict.state === 'pass' && recordOf(conflict.corpus)) {
|
|
@@ -30,5 +30,25 @@
|
|
|
30
30
|
"sanitizedCapture": {
|
|
31
31
|
"file": "doc-reconciliation-capture.sanitized.jsonl",
|
|
32
32
|
"sha256": "db4d8c85d2ba0fd06f53f455f31b125dd194e9a22cb337687bc0e295a16f02e5"
|
|
33
|
+
},
|
|
34
|
+
"outlineDispatchCapture": {
|
|
35
|
+
"mapping": {
|
|
36
|
+
"version": "claude-outline-dispatch-mapping/2",
|
|
37
|
+
"promptField": "tool_input.prompt",
|
|
38
|
+
"dispatchKey": "session_id+tool_use_id",
|
|
39
|
+
"runInBackgroundField": "tool_input.run_in_background",
|
|
40
|
+
"isolationPresenceField": "tool_input.isolation",
|
|
41
|
+
"resultAgentIdField": "tool_response.agentId",
|
|
42
|
+
"resultStatusField": "tool_response.status",
|
|
43
|
+
"workerStartEvent": "SubagentStart",
|
|
44
|
+
"workerStopEvent": "SubagentStop",
|
|
45
|
+
"workerIdentity": "session_id+agent_id",
|
|
46
|
+
"workerRootField": "cwd"
|
|
47
|
+
},
|
|
48
|
+
"file": "doc-reconciliation-outline-capture.sanitized.jsonl",
|
|
49
|
+
"sha256": "6feb440af12aaf878a0bed5cb750f29e27d271a9add93001e151746980230b7f",
|
|
50
|
+
"hostVersion": "2.1.312",
|
|
51
|
+
"run": "run8",
|
|
52
|
+
"baseline": "1bd0f805031c5577a6e7f72a9bc5b41618b71fc1"
|
|
33
53
|
}
|
|
34
54
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "harness-everything",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.47.0",
|
|
5
5
|
"description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Miya Daniel",
|
|
@@ -8,13 +8,14 @@ const fs = require('fs');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const crypto = require('crypto');
|
|
10
10
|
const { execFileSync, spawnSync } = require('child_process');
|
|
11
|
-
const {
|
|
11
|
+
const { isDeepStrictEqual } = require('node:util');
|
|
12
|
+
const { collectDocs, buildIndexSnapshot } = require('./doc-lifecycle');
|
|
12
13
|
|
|
13
14
|
// Logical command IDs (A3 section 3): stdout that repeats an ID is not trusted as credit by itself.
|
|
14
15
|
const CHECK_ID = 'harness.doc-check.check.v1';
|
|
15
16
|
const COMPARE_ID = 'harness.doc-check.compare-derived.v1';
|
|
16
|
-
const USAGE = 'usage: doc-check.js check --workspace <dir> --config <json-file, relative to --workspace> --changed-path <path> [--changed-path <path> ...] [--base <git-rev>] [--conflict-assessment]';
|
|
17
|
-
const VALUE_FLAGS = ['--workspace', '--config', '--changed-path', '--base'];
|
|
17
|
+
const USAGE = 'usage: doc-check.js check --workspace <dir> --config <json-file, relative to --workspace> --changed-path <path> [--changed-path <path> ...] [--base <git-rev>] [--derived-view <path>] [--conflict-assessment]';
|
|
18
|
+
const VALUE_FLAGS = ['--workspace', '--config', '--changed-path', '--base', '--derived-view'];
|
|
18
19
|
const CONFLICT_SCHEMA = 'harness.doc-check.conflict-assessment.v1';
|
|
19
20
|
const EXTERNAL = /^(?:https?:|mailto:)/i;
|
|
20
21
|
const SCHEME = /^[a-z][a-z0-9+.-]*:/i;
|
|
@@ -91,10 +92,11 @@ function parseArgs(argv) {
|
|
|
91
92
|
if (!VALUE_FLAGS.includes(flag) || rest[index + 1] === undefined) throw new Error(`${USAGE}\nunknown or incomplete argument: ${flag}`);
|
|
92
93
|
const value = rest[++index];
|
|
93
94
|
if (value.startsWith('--')) throw new Error(`${USAGE}\n${flag} requires a value, not a flag: ${value}`);
|
|
94
|
-
|
|
95
|
+
if (flag === '--changed-path') options.changedPaths.push(value);
|
|
95
96
|
else {
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
const key = flag === '--derived-view' ? 'derivedView' : flag.slice(2);
|
|
98
|
+
if (options[key] !== undefined) throw new Error(`${USAGE}\nrepeated argument: ${flag}`);
|
|
99
|
+
options[key] = value;
|
|
98
100
|
}
|
|
99
101
|
}
|
|
100
102
|
if (!options.workspace || !options.config) throw new Error(`${USAGE}\n--workspace and --config are required`);
|
|
@@ -310,7 +312,7 @@ function compareDerived(argv) {
|
|
|
310
312
|
}
|
|
311
313
|
|
|
312
314
|
const GLOB_CHARS = /[*?[\]{}]/;
|
|
313
|
-
const VIEW_REASON = 'derived view not verified:
|
|
315
|
+
const VIEW_REASON = 'derived view not verified: request check --derived-view to regenerate and compare it';
|
|
314
316
|
|
|
315
317
|
// Near-miss comparison only (separators, leading ./, trailing /, case); never used to accept a view.
|
|
316
318
|
function normalizeView(value) {
|
|
@@ -366,6 +368,97 @@ function derivedCoverage(config, { workspace, configRelative, configChanged, aff
|
|
|
366
368
|
return entries;
|
|
367
369
|
}
|
|
368
370
|
|
|
371
|
+
function digest(bytes) { return crypto.createHash('sha256').update(bytes).digest('hex'); }
|
|
372
|
+
|
|
373
|
+
function derivedRegenerate(workspace, options, config, configBytes) {
|
|
374
|
+
const view = options.derivedView;
|
|
375
|
+
const unavailable = reason => ({ state: 'unavailable', checkedPaths: [view], reason });
|
|
376
|
+
if (typeof view !== 'string' || !view || path.isAbsolute(view) || view.includes('\\')
|
|
377
|
+
|| view.split('/').some(part => !part || part === '.' || part === '..') || GLOB_CHARS.test(view)) {
|
|
378
|
+
return { state: 'unsupported', checkedPaths: [], reason: 'view must be a literal workspace-relative path' };
|
|
379
|
+
}
|
|
380
|
+
const configRelative = workspaceRelative(workspace, options.config);
|
|
381
|
+
if (!configRelative || path.isAbsolute(options.config) || options.config.includes('\\')
|
|
382
|
+
|| !/^[A-Za-z0-9._/-]+$/.test(options.config)
|
|
383
|
+
|| options.config.split('/').some(part => !part || part === '.' || part === '..')) {
|
|
384
|
+
return { state: 'unsupported', checkedPaths: [view], reason: 'config must be a canonical workspace-relative path' };
|
|
385
|
+
}
|
|
386
|
+
const selected = (Array.isArray(config.derived) ? config.derived : []).filter(item => item && item.path === view);
|
|
387
|
+
if (selected.length !== 1) return { state: 'unsupported', checkedPaths: [view], reason: 'view must name exactly one declared derived path' };
|
|
388
|
+
const governedPatterns = [
|
|
389
|
+
...(Array.isArray(config.governed) ? config.governed.map(item => item && item.path) : []),
|
|
390
|
+
...(Array.isArray(config.legacy) ? config.legacy : [])
|
|
391
|
+
].filter(item => typeof item === 'string');
|
|
392
|
+
if (governedPatterns.some(pattern => globToRegExp(pattern).test(view))) {
|
|
393
|
+
return { state: 'unsupported', checkedPaths: [view], reason: 'a derived view cannot be governed by its own index' };
|
|
394
|
+
}
|
|
395
|
+
const expectedGenerator = `node harness-everything/scripts/doc-lifecycle.js index --workspace . --config ${options.config} --all`;
|
|
396
|
+
if (selected[0].generator !== expectedGenerator) {
|
|
397
|
+
return { state: 'unsupported', checkedPaths: [view], reason: 'configured generator is not the supported lifecycle index operation' };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const target = path.resolve(workspace, view);
|
|
401
|
+
const generatorPath = path.resolve(__dirname, 'doc-lifecycle.js');
|
|
402
|
+
const rootReal = fs.realpathSync(workspace);
|
|
403
|
+
const targetBytes = () => {
|
|
404
|
+
const stat = fs.lstatSync(target);
|
|
405
|
+
if (!stat.isFile()) throw new Error('view is not a regular file');
|
|
406
|
+
const real = fs.realpathSync(target);
|
|
407
|
+
const relative = path.relative(rootReal, real);
|
|
408
|
+
if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
409
|
+
throw new Error('view resolves outside the workspace');
|
|
410
|
+
}
|
|
411
|
+
return readRegularFile(target);
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
try {
|
|
415
|
+
const configPath = path.resolve(workspace, options.config);
|
|
416
|
+
const configReal = fs.realpathSync(configPath);
|
|
417
|
+
const configPathRelative = path.relative(rootReal, configReal);
|
|
418
|
+
if (!configPathRelative || configPathRelative === '..' || configPathRelative.startsWith(`..${path.sep}`)
|
|
419
|
+
|| !fs.lstatSync(configPath).isFile()) return { state: 'unsupported', checkedPaths: [view], reason: 'config is not a workspace-contained regular file' };
|
|
420
|
+
const beforeTarget = targetBytes();
|
|
421
|
+
const generatorDigest = digest(fs.readFileSync(generatorPath, 'utf8').replace(/\r\n/g, '\n'));
|
|
422
|
+
const first = buildIndexSnapshot(workspace, configPath, config, configBytes);
|
|
423
|
+
const currentConfigBytes = fs.readFileSync(configPath);
|
|
424
|
+
const currentConfig = JSON.parse(currentConfigBytes.toString('utf8'));
|
|
425
|
+
const second = buildIndexSnapshot(workspace, configPath, currentConfig, currentConfigBytes);
|
|
426
|
+
const afterTarget = targetBytes();
|
|
427
|
+
const currentGeneratorDigest = digest(fs.readFileSync(generatorPath, 'utf8').replace(/\r\n/g, '\n'));
|
|
428
|
+
const outputDigest = digest(first.bytes);
|
|
429
|
+
const targetDigest = digest(beforeTarget);
|
|
430
|
+
const stable = digest(configBytes) === digest(currentConfigBytes)
|
|
431
|
+
&& first.corpus.complete === true && second.corpus.complete === true
|
|
432
|
+
&& first.configInventory.complete === true && second.configInventory.complete === true
|
|
433
|
+
&& isDeepStrictEqual(first.corpus.paths, second.corpus.paths)
|
|
434
|
+
&& isDeepStrictEqual(first.corpus.digests, second.corpus.digests)
|
|
435
|
+
&& isDeepStrictEqual(first.configInventory.sources, second.configInventory.sources)
|
|
436
|
+
&& isDeepStrictEqual(first.configSources, second.configSources)
|
|
437
|
+
&& Buffer.compare(first.bytes, second.bytes) === 0
|
|
438
|
+
&& generatorDigest === currentGeneratorDigest
|
|
439
|
+
&& digest(afterTarget) === targetDigest;
|
|
440
|
+
const equal = Buffer.compare(first.bytes, beforeTarget) === 0;
|
|
441
|
+
const comparisonState = stable ? (equal ? 'equal' : 'stale') : 'unavailable';
|
|
442
|
+
const binding = {
|
|
443
|
+
view,
|
|
444
|
+
sourceCorpus: { complete: first.corpus.complete === true, paths: first.corpus.paths, digests: first.corpus.digests },
|
|
445
|
+
config: { path: configRelative, digest: digest(configBytes), sources: first.configInventory.sources },
|
|
446
|
+
generator: { path: fs.realpathSync(generatorPath), digest: generatorDigest },
|
|
447
|
+
generation: { state: 'pass', outputDigest },
|
|
448
|
+
comparison: { state: comparisonState, targetDigest },
|
|
449
|
+
};
|
|
450
|
+
return {
|
|
451
|
+
state: stable && equal ? 'pass' : stable ? 'stale' : 'unavailable',
|
|
452
|
+
checkedPaths: [view],
|
|
453
|
+
...(stable ? {} : { reason: 'source, config, generator, or target changed during the check' }),
|
|
454
|
+
...(!stable && !first.corpus.complete ? { reason: 'source corpus is incomplete' } : {}),
|
|
455
|
+
binding,
|
|
456
|
+
};
|
|
457
|
+
} catch (error) {
|
|
458
|
+
return unavailable(error.message || 'generation or comparison unavailable');
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
369
462
|
function check(options) {
|
|
370
463
|
const workspace = path.resolve(options.workspace);
|
|
371
464
|
const configPath = path.resolve(workspace, options.config);
|
|
@@ -422,7 +515,7 @@ function check(options) {
|
|
|
422
515
|
// SHOULD reminders only: they never change the exit code and are never diagnostics.
|
|
423
516
|
const changed = new Set(options.changedPaths.map(value => path.relative(workspace, path.resolve(workspace, value)).split(path.sep).join('/')));
|
|
424
517
|
// F3 applicability (conservative): a governed or legacy source, an unknown or deleted path, a derived output or the config
|
|
425
|
-
// itself marks every declared view unverified
|
|
518
|
+
// itself marks every declared view unverified until the explicit derived-view mode is selected.
|
|
426
519
|
const configRelative = path.relative(workspace, configPath).split(path.sep).join('/');
|
|
427
520
|
const declaredViews = [...new Set((Array.isArray(config.derived) ? config.derived : []).map(entry => entry.path))];
|
|
428
521
|
const affectsViews = [...changed].some(item => {
|
|
@@ -465,6 +558,11 @@ function check(options) {
|
|
|
465
558
|
},
|
|
466
559
|
}
|
|
467
560
|
};
|
|
561
|
+
if (options.derivedView) {
|
|
562
|
+
const derived = derivedRegenerate(workspace, options, config, configBytes);
|
|
563
|
+
result.capabilities['derived-regenerate'] = derived;
|
|
564
|
+
if (derived.state === 'pass') result.coverage.unsupported = result.coverage.unsupported.filter(item => item.path !== options.derivedView || item.state !== 'unverified');
|
|
565
|
+
}
|
|
468
566
|
if (options.conflictAssessment) {
|
|
469
567
|
const scopePaths = [...new Set(options.changedPaths.map(value => workspaceRelative(workspace, value)).filter(Boolean))].sort();
|
|
470
568
|
const corpus = analysis.corpus;
|
|
@@ -1839,6 +1839,24 @@ function readWorkspaceConfigSources(root, explicitConfigPath, fallbackConfig) {
|
|
|
1839
1839
|
return sources.length ? sources : [fallbackConfig];
|
|
1840
1840
|
}
|
|
1841
1841
|
|
|
1842
|
+
function configSourceInventory(root, explicitConfigPath) {
|
|
1843
|
+
const sources = [];
|
|
1844
|
+
let complete = true;
|
|
1845
|
+
for (const relativePath of [...configPathsForWorkspace(root, explicitConfigPath)].sort(compareText)) {
|
|
1846
|
+
const file = path.join(root, ...relativePath.split('/'));
|
|
1847
|
+
try {
|
|
1848
|
+
const stat = fs.lstatSync(file);
|
|
1849
|
+
if (!stat.isFile()) { sources.push({ path: relativePath, presence: 'unsupported' }); complete = false; continue; }
|
|
1850
|
+
const bytes = fs.readFileSync(file);
|
|
1851
|
+
sources.push({ path: relativePath, presence: 'present', digest: crypto.createHash('sha256').update(bytes).digest('hex') });
|
|
1852
|
+
} catch (error) {
|
|
1853
|
+
if (error.code === 'ENOENT' || error.code === 'ENOTDIR') sources.push({ path: relativePath, presence: 'missing' });
|
|
1854
|
+
else { sources.push({ path: relativePath, presence: 'unreadable' }); complete = false; }
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
return { complete, sources };
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1842
1860
|
function readGitBaseInventory(root, baseRef, config, explicitConfigPath = null) {
|
|
1843
1861
|
if (!root || typeof baseRef !== 'string' || !baseRef.trim()) return { available: false, inventory: [], files: [] };
|
|
1844
1862
|
try {
|
|
@@ -2602,6 +2620,27 @@ function buildIndex(docs, config, options = {}) {
|
|
|
2602
2620
|
return `${lines.join('\n').replace(/\n+$/, '')}\n`;
|
|
2603
2621
|
}
|
|
2604
2622
|
|
|
2623
|
+
function buildIndexSnapshot(workspace, configPath, config, configBytes, options = {}) {
|
|
2624
|
+
const root = path.resolve(workspace);
|
|
2625
|
+
const corpus = { complete: false, skipped: [], paths: [], digests: {} };
|
|
2626
|
+
const docs = collectDocs(root, config, corpus);
|
|
2627
|
+
const relativeConfig = path.relative(root, configPath);
|
|
2628
|
+
corpus.config = relativeConfig && relativeConfig !== '..' && !relativeConfig.startsWith(`..${path.sep}`)
|
|
2629
|
+
? { path: relativeConfig.split(path.sep).join('/'), digest: crypto.createHash('sha256').update(configBytes).digest('hex') }
|
|
2630
|
+
: null;
|
|
2631
|
+
const configSources = readWorkspaceConfigSources(root, configPath, config);
|
|
2632
|
+
const configInventory = configSourceInventory(root, configPath);
|
|
2633
|
+
const mode = { all: true, workspace: root, base: options.base, configPath, configSources };
|
|
2634
|
+
const analysis = analyzeDocs(docs, config, mode);
|
|
2635
|
+
const bytes = Buffer.from(buildIndex(docs, config, {
|
|
2636
|
+
...mode,
|
|
2637
|
+
diagnostics: analysis.diagnostics,
|
|
2638
|
+
_graph: analysis._graph,
|
|
2639
|
+
_retirementEligibleSources: analysis._retirementEligibleSources
|
|
2640
|
+
}), 'utf8');
|
|
2641
|
+
return { bytes, corpus, configSources, configInventory };
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2605
2644
|
function parseCli(argv) {
|
|
2606
2645
|
const [command, ...rest] = argv;
|
|
2607
2646
|
const options = { command, changedProvided: false, changedPaths: [], proposedUntracked: [], all: false };
|
|
@@ -2709,6 +2748,15 @@ function main(argv = process.argv.slice(2)) {
|
|
|
2709
2748
|
process.stderr.write(`${error.message}\n`);
|
|
2710
2749
|
return 2;
|
|
2711
2750
|
}
|
|
2751
|
+
if (options.command === 'index' && options.all) {
|
|
2752
|
+
try {
|
|
2753
|
+
process.stdout.write(buildIndexSnapshot(workspacePath, configPath, config, configBytes, options).bytes);
|
|
2754
|
+
return 0;
|
|
2755
|
+
} catch (error) {
|
|
2756
|
+
process.stderr.write(`index generation failed: ${error.message}\n`);
|
|
2757
|
+
return 2;
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2712
2760
|
const corpus = options.all ? { complete: false, skipped: [], paths: [], digests: {} } : null;
|
|
2713
2761
|
const docs = collectDocs(workspacePath, config, corpus);
|
|
2714
2762
|
if (corpus) {
|
|
@@ -2737,6 +2785,6 @@ function main(argv = process.argv.slice(2)) {
|
|
|
2737
2785
|
return analysis.diagnostics.some(item => item.severity === 'error') ? 1 : 0;
|
|
2738
2786
|
}
|
|
2739
2787
|
|
|
2740
|
-
module.exports = { readGitBaseInventory, scanWorkspaceFiles, parseFrontmatter, parseRequirementBlocks, collectDocs, documentCorpusPaths, analyzeDocs, resolveRef, lintDocs, buildIndex, diffRevisions, buildGitDocumentInventory };
|
|
2788
|
+
module.exports = { readGitBaseInventory, scanWorkspaceFiles, parseFrontmatter, parseRequirementBlocks, collectDocs, documentCorpusPaths, analyzeDocs, resolveRef, lintDocs, buildIndex, buildIndexSnapshot, configSourceInventory, diffRevisions, buildGitDocumentInventory };
|
|
2741
2789
|
|
|
2742
2790
|
if (require.main === module) process.exitCode = main();
|
package/sbom.cdx.json
CHANGED
|
@@ -2,23 +2,23 @@
|
|
|
2
2
|
"$schema": "https://cyclonedx.org/schema/bom-1.7.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.7",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:76fc9d39-b249-59ad-bcf8-de01347662b8",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-10-
|
|
8
|
+
"timestamp": "2026-10-29T00:00:39.955Z",
|
|
9
9
|
"tools": [
|
|
10
10
|
{
|
|
11
11
|
"vendor": "Harness Everything",
|
|
12
12
|
"name": "generate-sbom.js",
|
|
13
|
-
"version": "0.
|
|
13
|
+
"version": "0.47.0"
|
|
14
14
|
}
|
|
15
15
|
],
|
|
16
16
|
"component": {
|
|
17
17
|
"type": "application",
|
|
18
|
-
"bom-ref": "pkg:npm/harness-everything@0.
|
|
18
|
+
"bom-ref": "pkg:npm/harness-everything@0.47.0",
|
|
19
19
|
"name": "harness-everything",
|
|
20
|
-
"version": "0.
|
|
21
|
-
"purl": "pkg:npm/harness-everything@0.
|
|
20
|
+
"version": "0.47.0",
|
|
21
|
+
"purl": "pkg:npm/harness-everything@0.47.0"
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"components": [
|
|
@@ -2033,7 +2033,7 @@
|
|
|
2033
2033
|
]
|
|
2034
2034
|
},
|
|
2035
2035
|
{
|
|
2036
|
-
"ref": "pkg:npm/harness-everything@0.
|
|
2036
|
+
"ref": "pkg:npm/harness-everything@0.47.0",
|
|
2037
2037
|
"dependsOn": [
|
|
2038
2038
|
"pkg:npm/%40commitlint/cli@19.8.1",
|
|
2039
2039
|
"pkg:npm/%40commitlint/config-conventional@19.8.1",
|