harness-everything 0.46.3 → 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 +23 -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 +278 -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 +168 -15
- 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 +168 -15
- 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
|
}
|
|
@@ -1456,20 +1501,31 @@ function resolveOutlineEvidence({ safeInput, capture, captures, request, originR
|
|
|
1456
1501
|
// are covered by the clean-O seam check and by unattributedWriters below.
|
|
1457
1502
|
const journal = Array.isArray(captures) ? captures : [];
|
|
1458
1503
|
const dispatchIndex = journal.indexOf(correlation.pre);
|
|
1459
|
-
const
|
|
1504
|
+
const uniqueLifecycle = start => {
|
|
1505
|
+
if (!textOf(start.session_id) || !textOf(start.agent_id)) return null;
|
|
1460
1506
|
const lifecycle = event => journal.filter(item => recordOf(item) && item.event === event
|
|
1461
1507
|
&& item.session_id === start.session_id && textOf(start.agent_id) && item.agent_id === start.agent_id);
|
|
1462
1508
|
// Exactly one Start and one Stop per (session_id, agent_id); a duplicate either way stays ambiguous.
|
|
1463
1509
|
const starts = lifecycle('SubagentStart');
|
|
1464
1510
|
const stops = lifecycle('SubagentStop');
|
|
1465
|
-
if (starts.length !== 1 || starts[0] !== start || stops.length !== 1) return
|
|
1511
|
+
if (starts.length !== 1 || starts[0] !== start || stops.length !== 1) return null;
|
|
1466
1512
|
const startIndex = journal.indexOf(start);
|
|
1467
1513
|
const stopIndex = journal.indexOf(stops[0]);
|
|
1468
|
-
|
|
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);
|
|
1469
1525
|
};
|
|
1470
1526
|
const competingStarts = journal.filter(item => recordOf(item)
|
|
1471
1527
|
&& item.event === 'SubagentStart' && item.ref !== worker.ref && sameCanonicalPath(item.cwd, workerRoot)
|
|
1472
|
-
&& !stoppedBeforeDispatch(item));
|
|
1528
|
+
&& !stoppedBeforeDispatch(item) && !startedAfterWorkerStop(item));
|
|
1473
1529
|
if (competingStarts.length) return fail('outline-competing-writer-start');
|
|
1474
1530
|
const unattributedWriters = (Array.isArray(captures) ? captures : []).filter(item => possibleWriterCapture(item)
|
|
1475
1531
|
&& (item.agent_id !== worker.agent_id || !sameCanonicalPath(item.cwd, workerRoot)));
|
|
@@ -1725,6 +1781,92 @@ const createClaudeOriginProvider = input => {
|
|
|
1725
1781
|
if (!paths.length) return rejected('scope-empty', `${capability} checked no governed paths`);
|
|
1726
1782
|
let corpusBinding;
|
|
1727
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
|
+
}
|
|
1728
1870
|
if (capability === 'conflict-check') {
|
|
1729
1871
|
const assessment = report.conflictAssessment;
|
|
1730
1872
|
const corpus = assessment && assessment.corpus;
|
|
@@ -1797,6 +1939,7 @@ const createClaudeOriginProvider = input => {
|
|
|
1797
1939
|
artifactDigests,
|
|
1798
1940
|
...(Object.keys(baselineArtifacts).length ? { baselineArtifacts } : {}),
|
|
1799
1941
|
...(corpusBinding ? { corpusBinding } : {}),
|
|
1942
|
+
...(derivedBinding ? { derivedBinding } : {}),
|
|
1800
1943
|
captured: { responseRef: cap.responseRef, responseDigest: cap.responseDigest },
|
|
1801
1944
|
};
|
|
1802
1945
|
return { status: 'accepted', originRef, event, correlation: { toolCorrelationId: cap.tool_use_id, resultRef: cap.responseRef } };
|
|
@@ -2131,6 +2274,16 @@ function buildCaptureRecord(payload, eventName, workspaceRoot) {
|
|
|
2131
2274
|
const report = JSON.parse(stdout);
|
|
2132
2275
|
checkedPaths = ['doc-lint', 'link-check'].flatMap(capability => (recordOf(report.capabilities) && recordOf(report.capabilities[capability])
|
|
2133
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
|
+
}
|
|
2134
2287
|
const conflict = recordOf(report.capabilities) && report.capabilities['conflict-check'];
|
|
2135
2288
|
if (installedCheck.conflictAssessment === true && report.mode === 'conflict-assessment'
|
|
2136
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",
|