harness-everything 0.38.0 → 0.40.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.
Files changed (79) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +79 -63
  4. package/CHANGELOG.md +32 -0
  5. package/benchmarks/results/reviews/pr353-r6-2026-10-22/README.md +76 -0
  6. package/benchmarks/results/reviews/pr353-r6-2026-10-22/a-reviews-b.md +191 -0
  7. package/benchmarks/results/reviews/pr353-r6-2026-10-22/b-reviews-a.md +100 -0
  8. package/benchmarks/results/reviews/pr353-r6-2026-10-22/baseline-probes.log +47 -0
  9. package/benchmarks/results/reviews/pr353-r6-2026-10-22/bom-red.log +85 -0
  10. package/benchmarks/results/reviews/pr353-r6-2026-10-22/bom-transition.txt +25 -0
  11. package/benchmarks/results/reviews/pr353-r6-2026-10-22/coordinator-probes.txt +84 -0
  12. package/benchmarks/results/reviews/pr353-r6-2026-10-22/cross-suite.txt +18 -0
  13. package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-bom.log +3 -0
  14. package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-consistency.log.gz +0 -0
  15. package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-core.log +198 -0
  16. package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-probes.log +27 -0
  17. package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-producer.log +83 -0
  18. package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-review.md +89 -0
  19. package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-transitions.log +5 -0
  20. package/benchmarks/results/reviews/pr353-r6-2026-10-22/implementation-a.md +166 -0
  21. package/benchmarks/results/reviews/pr353-r6-2026-10-22/implementation-b.md +264 -0
  22. package/benchmarks/results/reviews/pr353-r6-2026-10-22/logs.sha256 +15 -0
  23. package/benchmarks/results/reviews/pr353-r6-2026-10-22/peer-a-probe.log +62 -0
  24. package/benchmarks/results/reviews/pr353-r6-2026-10-22/peer-a-probe.txt +313 -0
  25. package/benchmarks/results/reviews/pr353-r6-2026-10-22/peer-b-probe.log +51 -0
  26. package/benchmarks/results/reviews/pr353-r6-2026-10-22/peer-b-probe.txt +94 -0
  27. package/benchmarks/results/reviews/pr353-r6-2026-10-22/semantic-transitions.txt +25 -0
  28. package/benchmarks/results/reviews/pr353-r6-2026-10-22/source.sha256 +9 -0
  29. package/benchmarks/results/reviews/pr353-r6-2026-10-22/tests-a-on-b-core.log +201 -0
  30. package/benchmarks/results/reviews/pr353-r6-2026-10-22/tests-a-on-b-producer.log +82 -0
  31. package/benchmarks/results/reviews/pr353-r6-2026-10-22/tests-b-on-a-core.log +197 -0
  32. package/benchmarks/results/reviews/pr353-r6-2026-10-22/tests-b-on-a-producer.log +82 -0
  33. package/benchmarks/results/reviews/pr353-r6-2026-10-22/verify-all.log.gz +0 -0
  34. package/benchmarks/results/reviews/pr353-r6-2026-10-22/verify-gate-summary.json +22 -0
  35. package/ci/fixtures/doc-obligations/reconciliation-s2a/harness.js +489 -0
  36. package/ci/fixtures/doc-obligations/reconciliation-s2a/vectors.json +617 -0
  37. package/ci/mechanism-325-3b-s2a-core.test.js +840 -0
  38. package/ci/mechanism-325-3b-s2a-producer.test.js +564 -0
  39. package/code-review/SKILL.md +3 -3
  40. package/code-review/references/review-playbook.md +4 -0
  41. package/code-review/templates/finding.template.md +6 -0
  42. package/docs/doc-workflow-reconciliation.md +357 -0
  43. package/evals/zoom-out/tasks/negative-first-edge-case-review.yaml +16 -0
  44. package/evals/zoom-out/tasks/positive-repair-loop.yaml +15 -0
  45. package/harness-everything/SKILL.md +1 -1
  46. package/harness-everything/scripts/project-docs-resolver.js +42 -8
  47. package/harness-everything/scripts/routing-keywords.json +4 -0
  48. package/hooks/scripts/lib/doc-reconciliation-core.js +280 -0
  49. package/hooks/scripts/lib/doc-reconciliation-events.js +141 -0
  50. package/hooks/scripts/lib/doc-reconciliation-producer.js +206 -0
  51. package/multi-agent-workspace/SKILL.md +1 -1
  52. package/multi-agent-workspace/scripts/project-docs-resolver.js +42 -8
  53. package/opencode-plugin/plugin.json +1 -1
  54. package/package.json +2 -1
  55. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  56. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-core.js +280 -0
  57. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-events.js +141 -0
  58. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-producer.js +206 -0
  59. package/plugins/harness-everything/plugin.json +1 -1
  60. package/plugins/harness-everything/skills/code-review/SKILL.md +3 -3
  61. package/plugins/harness-everything/skills/code-review/references/review-playbook.md +4 -0
  62. package/plugins/harness-everything/skills/code-review/templates/finding.template.md +6 -0
  63. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  64. package/plugins/harness-everything/skills/harness-everything/scripts/project-docs-resolver.js +42 -8
  65. package/plugins/harness-everything/skills/harness-everything/scripts/routing-keywords.json +4 -0
  66. package/plugins/harness-everything/skills/multi-agent-workspace/SKILL.md +1 -1
  67. package/plugins/harness-everything/skills/multi-agent-workspace/scripts/project-docs-resolver.js +42 -8
  68. package/plugins/harness-everything/skills/to-spec/SKILL.md +1 -1
  69. package/plugins/harness-everything/skills/to-spec/scripts/project-docs-resolver.js +42 -8
  70. package/plugins/harness-everything/skills/zoom-out/SKILL.md +4 -3
  71. package/plugins/harness-everything/skills/zoom-out/references/circuit-breaker.md +7 -0
  72. package/plugins/harness-everything/skills/zoom-out/templates/zoom-out-report.template.md +4 -2
  73. package/sbom.cdx.json +7 -7
  74. package/scripts/errors-sync.js +90 -0
  75. package/to-spec/SKILL.md +1 -1
  76. package/to-spec/scripts/project-docs-resolver.js +42 -8
  77. package/zoom-out/SKILL.md +4 -3
  78. package/zoom-out/references/circuit-breaker.md +7 -0
  79. package/zoom-out/templates/zoom-out-report.template.md +4 -2
@@ -0,0 +1,206 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execFileSync } = require('child_process');
6
+ const { _internals: U } = require('./doc-reconciliation-core');
7
+ const { scanWorkspaceFiles } = U.runtimeModule('doc-lifecycle.js');
8
+ const { resolveDocsConfigCaptured } = U.runtimeModule('project-docs-resolver.js');
9
+ const { deriveDocFacts, deriveDocObligations } = U.runtimeModule('doc-obligations.js');
10
+
11
+ const MAX_FILES = 10000;
12
+ const MAX_BYTES = 64 * 1024 * 1024;
13
+ const POSIX_MODES = process.platform !== 'win32';
14
+ function fail(code) { throw new Error(code); }
15
+ // The JS realpath keeps Windows 8.3 spellings (RUNNER~1) while Git reports the long name, so
16
+ // directory identity uses native resolution and is case-insensitive on Windows (as workflow-isolation).
17
+ function canonicalPath(value) { return fs.realpathSync.native(path.resolve(value)); }
18
+ function samePath(a, b) {
19
+ const left = canonicalPath(a);
20
+ const right = canonicalPath(b);
21
+ return process.platform === 'win32' ? left.toLowerCase() === right.toLowerCase() : left === right;
22
+ }
23
+ function git(root, args, options = {}) {
24
+ const env = { ...process.env };
25
+ for (const key of Object.keys(env)) if (key.startsWith('GIT_')) delete env[key];
26
+ try {
27
+ return execFileSync('git', ['-C', root, ...args], { env, timeout: 10000, maxBuffer: MAX_BYTES, stdio: ['pipe', 'pipe', 'pipe'], ...options });
28
+ } catch { fail('baseline-unavailable'); }
29
+ }
30
+ function fileRecord(relative, mode, storedBytes, presence = 'present') {
31
+ return { path: relative, presence, type: presence === 'present' ? 'file' : '', mode,
32
+ storedBytes: Buffer.from(storedBytes), digest: U.sha(storedBytes) };
33
+ }
34
+ function validatePaths(records) {
35
+ const names = new Set();
36
+ for (const item of records) {
37
+ const name = item.path;
38
+ if (!name || name.includes('\\') || name.includes('\ufffd') || name.startsWith('/')
39
+ || name.split('/').some(segment => !segment || segment === '..' || segment === '.')) fail('inventory-unsupported');
40
+ const folded = name.normalize('NFC').toLowerCase();
41
+ if (names.has(folded)) fail('inventory-unsupported');
42
+ names.add(folded);
43
+ }
44
+ }
45
+ function captureBaseline(root, pin) {
46
+ if (!U.record(pin) || !/^[a-f0-9]{40,64}$/.test(pin.commit || '') || !U.text(pin.root)
47
+ || !samePath(pin.root, root)) fail('baseline-unavailable');
48
+ const actualRoot = git(root, ['rev-parse', '--show-toplevel']).toString('utf8').trim();
49
+ if (!samePath(actualRoot, root)) fail('baseline-unavailable');
50
+ const commit = git(root, ['rev-parse', '--verify', '--end-of-options', `${pin.commit}^{commit}`]).toString('utf8').trim();
51
+ const treeRef = git(root, ['rev-parse', '--verify', '--end-of-options', `${commit}^{tree}`]).toString('utf8').trim();
52
+ const entries = git(root, ['ls-tree', '-r', '-z', '--full-tree', treeRef]).toString('utf8').split('\0').filter(Boolean);
53
+ if (entries.length > MAX_FILES) fail('inventory-unsupported');
54
+ const records = [];
55
+ let total = 0;
56
+ for (const entry of entries) {
57
+ const match = entry.match(/^(100644|100755) blob ([a-f0-9]{40,64})\t(.+)$/s);
58
+ if (!match) fail('inventory-unsupported');
59
+ const storedBytes = git(root, ['cat-file', 'blob', match[2]]);
60
+ total += storedBytes.length;
61
+ if (total > MAX_BYTES) fail('inventory-unsupported');
62
+ records.push(fileRecord(match[3], match[1], storedBytes));
63
+ }
64
+ validatePaths(records);
65
+ return { commit, treeRef, records };
66
+ }
67
+ function captureCandidate(root, baseline) {
68
+ const raw = [];
69
+ let total = 0;
70
+ let visited = 0;
71
+ function walk(dir, prefix) {
72
+ const entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => U.compare(a.name, b.name));
73
+ for (const entry of entries) {
74
+ if (!prefix && entry.name === '.git') continue;
75
+ if (++visited > MAX_FILES) fail('inventory-unsupported');
76
+ const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
77
+ const absolute = path.join(dir, entry.name);
78
+ const stat = fs.lstatSync(absolute);
79
+ if (stat.isDirectory()) {
80
+ // Windows stats carry no POSIX permission bits (directories report 0o666); an unreadable
81
+ // path there makes readdir/readFile throw instead.
82
+ if (POSIX_MODES && (!(stat.mode & 0o444) || !(stat.mode & 0o111))) fail('inventory-incomplete');
83
+ walk(absolute, relative);
84
+ continue;
85
+ }
86
+ if (!stat.isFile()) fail('inventory-unsupported');
87
+ // Unreadable files must remain unavailable even when the process has elevated read access.
88
+ if (POSIX_MODES && !(stat.mode & 0o444)) fail('inventory-incomplete');
89
+ const bytes = fs.readFileSync(absolute);
90
+ total += bytes.length;
91
+ if (raw.length >= MAX_FILES || total > MAX_BYTES) fail('inventory-unsupported');
92
+ raw.push({ path: relative, raw: bytes, mode: stat.mode & 0o111 ? '100755' : '100644' });
93
+ }
94
+ }
95
+ walk(root, '');
96
+ validatePaths(raw);
97
+ // Refuse filters/encodings that the existing stored-byte reader does not model.
98
+ if (raw.length) {
99
+ const attrs = git(root, ['check-attr', '-z', '--stdin', 'filter', 'working-tree-encoding', 'ident'], { input: Buffer.from(raw.map(r => r.path).join('\0') + '\0') }).toString('utf8').split('\0');
100
+ for (let i = 0; i + 2 < attrs.length; i += 3) {
101
+ if (!['unspecified', 'unset'].includes(attrs[i + 2])) fail('inventory-unsupported');
102
+ }
103
+ }
104
+ // Lifecycle owns text/eol/autocrlf conversion. A completeness walk above detects its skipped paths.
105
+ const stored = new Map(scanWorkspaceFiles(root).map(item => [item.path, item.bytes]));
106
+ const records = raw.map(item => {
107
+ const converted = stored.get(item.path);
108
+ if (!converted) fail('inventory-unsupported');
109
+ // An unexplained non-CR difference means the two readers observed different bytes.
110
+ if (!item.raw.includes(13) && !item.raw.equals(converted)) fail('snapshot-changed');
111
+ return fileRecord(item.path, item.mode, converted);
112
+ });
113
+ for (const item of baseline.records) if (!stored.has(item.path)) records.push(fileRecord(item.path, '', Buffer.alloc(0), 'deleted'));
114
+ return records;
115
+ }
116
+ const progressKeys = new Set(['status', 'state', 'evidence', 'loaded', 'loadedAt', 'updatedAt', 'createdAt', 'completedAt', 'timestamp', 'report', 'reports']);
117
+ // Progress fields are excluded only at their defined record locations (top-level task/workflow, each
118
+ // requirement, each requirement binding). Nested objects keep every key: a nested `status`/`state` there
119
+ // is semantic and must change taskScopeDigest (PR #353 review P2).
120
+ function withoutProgress(value, keys = progressKeys) {
121
+ if (!U.record(value)) return U.clone(value);
122
+ return Object.fromEntries(Object.keys(value).filter(key => !keys.has(key)).map(key => [key, U.clone(value[key])]));
123
+ }
124
+ // A binding's `report`/`reports` object configures the binding; only its load/status records are progress.
125
+ const bindingProgressKeys = new Set([...progressKeys].filter(key => key !== 'report' && key !== 'reports'));
126
+ function taskSemantics(task) {
127
+ const semantic = withoutProgress(task);
128
+ if (Array.isArray(task?.requirements)) {
129
+ semantic.requirements = task.requirements.map(requirement => {
130
+ const item = withoutProgress(requirement);
131
+ if (U.record(requirement) && Array.isArray(requirement.bindings)) item.bindings = requirement.bindings.map(binding => withoutProgress(binding, bindingProgressKeys));
132
+ return item;
133
+ });
134
+ }
135
+ return semantic;
136
+ }
137
+ function contextFor(input, provider) {
138
+ if (!provider || typeof provider.resolveContext !== 'function') fail('origin-unavailable');
139
+ const response = provider.resolveContext({ workspaceRoot: input.workspaceRoot, workflowId: input.workflow.workflowId, sessionId: input.workflow.sessionId });
140
+ if (response?.status !== 'accepted') fail(response?.status === 'rejected' ? 'origin-untrusted' : 'origin-unavailable');
141
+ const c = response.context;
142
+ if (!U.record(c) || !U.text(response.originRef) || !U.text(c.workspaceId) || !U.text(c.baselineReference)
143
+ || !U.text(c.principals?.author) || !Array.isArray(c.principals?.executors) || !c.principals.executors.every(U.text)) fail('origin-untrusted');
144
+ for (const key of ['sessionId', 'workflowId', 'workflowRevision']) if (c[key] !== input.workflow[key]) fail('identity-mismatch');
145
+ if (c.taskId !== input.task.taskId || U.stable(c.activeHost) !== U.stable(input.activeHost)) fail('identity-mismatch');
146
+ if (c.workspaceRoot && !samePath(c.workspaceRoot, input.workspaceRoot)) fail('identity-mismatch');
147
+ // The provider must bind the concrete workflow-start pin; a bare reference cannot authorize a
148
+ // re-anchored baseline (PR #353 review P1).
149
+ if (!U.record(c.baseline) || !U.text(c.baseline.commit) || !U.text(c.baseline.root)
150
+ || !U.record(input.baseline) || c.baseline.commit !== input.baseline.commit || !samePath(c.baseline.root, input.baseline.root)) fail('baseline-unavailable');
151
+ const root = canonicalPath(input.workspaceRoot);
152
+ return U.clone({ ...c, baseline: { ...c.baseline, root }, workspaceRoot: root, originRef: response.originRef });
153
+ }
154
+ function filesOf(records, config) {
155
+ // The inventory is whole-project: binary records keep their stored bytes in the identity framing and are
156
+ // omitted from semantic derivation unless the derive contract can observe them (PR #353 review P2).
157
+ return U.deriveFiles(records, config);
158
+ }
159
+ function produceDocSnapshot(input, dependencies = {}) {
160
+ const originProvider = dependencies?.originProvider;
161
+ try {
162
+ if (!U.record(input) || !U.text(input.workspaceRoot) || !U.record(input.workflow) || !U.record(input.task)
163
+ || !U.text(input.task.taskId) || !Array.isArray(input.task.requirements) || !U.record(input.scope)
164
+ || !Number.isInteger(input.workflow.workflowRevision) || input.workflow.workflowRevision < 0) fail('invalid-input');
165
+ if ((input.prdRoot === undefined ? 'docs/prd/' : input.prdRoot) !== 'docs/prd/' || (input.scope.prdRoot && input.scope.prdRoot !== 'docs/prd/')) fail('unsupported-prd-root');
166
+ const root = canonicalPath(input.workspaceRoot);
167
+ const detached = U.clone(input);
168
+ const originContext = contextFor(detached, originProvider);
169
+ const baseline = captureBaseline(root, detached.baseline);
170
+ const config = resolveDocsConfigCaptured(root, { activeHost: detached.activeHost });
171
+ if (!['project', 'host'].includes(config.resolution.status) || config.capture.sources.some(s => s.presence === 'unreadable')) fail('config-unavailable');
172
+ const coverage = { kind: 'whole-project', excluded: ['.git'], scope: U.clone(detached.scope) };
173
+ const candidate = { records: captureCandidate(root, baseline), coverage };
174
+ // Same owned config bytes must be the bytes covered by the working-tree capture.
175
+ for (const source of config.capture.sources) {
176
+ const file = candidate.records.find(r => r.path === source.path && r.presence === 'present');
177
+ if (source.presence === 'present' ? !file || !U.bytes(source.bytes).equals(file.storedBytes) : Boolean(file)) fail('snapshot-changed');
178
+ }
179
+ config.digest = U.configDigest(config);
180
+ baseline.digest = U.snapshotDigest('baseline', baseline, coverage, baseline.records);
181
+ candidate.digest = U.snapshotDigest('candidate', baseline, coverage, candidate.records);
182
+ const planIdentity = { task: taskSemantics(detached.task), scope: U.clone(detached.scope), workflow: withoutProgress(detached.workflow), coverage, configDigest: config.digest, prdRoot: 'docs/prd/' };
183
+ const identity = { workspaceId: originContext.workspaceId, sessionId: originContext.sessionId, workflowId: originContext.workflowId,
184
+ workflowRevision: originContext.workflowRevision, taskId: originContext.taskId, taskScopeDigest: U.structuredDigest('doc-task-scope-v1', planIdentity),
185
+ baselineDigest: baseline.digest, candidateDigest: candidate.digest, configDigest: config.digest, prdRoot: 'docs/prd/' };
186
+ const derived = deriveDocFacts({ base: { files: filesOf(baseline.records, config.resolution.config) },
187
+ candidate: { files: filesOf(candidate.records, config.resolution.config) }, config: config.resolution.config });
188
+ if (!derived.facts?.inputKnown) fail('inventory-incomplete');
189
+ const facts = derived.facts;
190
+ const obligations = deriveDocObligations(facts);
191
+ const derivationReasons = Object.fromEntries(obligations.map(o => [o.id, { applicability: o.applicability, reason: `captured facts derive ${o.capability}: ${o.applicability}`, factRefs: ['facts', ...(o.scope?.paths || []).map(p => `inventory:${p}`)] }]));
192
+ const again = captureCandidate(root, baseline);
193
+ const configAgain = resolveDocsConfigCaptured(root, { activeHost: detached.activeHost });
194
+ let finalContext;
195
+ try { finalContext = contextFor(detached, originProvider); }
196
+ catch { fail('snapshot-changed'); }
197
+ if (U.snapshotDigest('candidate', baseline, coverage, again) !== candidate.digest || U.configDigest(configAgain) !== config.digest
198
+ || U.stable(originContext) !== U.stable(finalContext)) fail('snapshot-changed');
199
+ return { state: 'ready', snapshot: { identity, baseline, candidate, config, originContext, taskSemantics: planIdentity, facts, obligations, derivationReasons }, diagnostics: [] };
200
+ } catch (error) {
201
+ const codes = ['invalid-input', 'baseline-unavailable', 'inventory-incomplete', 'inventory-unsupported', 'snapshot-changed', 'config-unavailable', 'unsupported-prd-root', 'identity-mismatch', 'origin-unavailable', 'origin-untrusted'];
202
+ const code = codes.includes(error.message) ? error.message : 'inventory-unsupported';
203
+ return { state: 'unavailable', snapshot: null, diagnostics: [U.diagnostic(code)] };
204
+ }
205
+ }
206
+ module.exports = { produceDocSnapshot };
@@ -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.38.0",
4
+ "version": "0.40.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",
@@ -4,7 +4,7 @@ description: "Independently review a PR, diff or finished implementation for cor
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.33.0
7
+ version: 0.40.0
8
8
  ---
9
9
 
10
10
  # Code Review
@@ -30,10 +30,10 @@ Independently review completed changes for correctness, contract violations, and
30
30
 
31
31
  ## Workflow
32
32
 
33
- 1. Pin repository, contract, base/head, changed scope, and CI state; inspect relevant callers and consumers.
33
+ 1. Pin repository, verbatim acceptance contract, base/head, changed scope, and CI state; inspect relevant callers and consumers.
34
34
  2. Independently trace behavior and test applicable counterexamples. Separate severity from safety conditions.
35
35
  3. Report only supported findings with evidence tier, smallest correction, regression, and positive control. Use `<this-skill-dir>/templates/finding.template.md`.
36
- 4. On re-review, disposition every prior finding at the new HEAD, then inspect for regressions.
36
+ 4. On re-review, disposition every prior finding at the new HEAD, re-trace the acceptance path, then inspect for regressions.
37
37
  5. Keep review read-only unless explicitly authorized. Do not claim “bug-free.”
38
38
 
39
39
  In the interim, bind explicitly on an existing review step: `{"stepType":"review","optionalBindings":["code-review"]}`.
@@ -5,6 +5,7 @@ Use this checklist for the applicable risks; do not force irrelevant categories.
5
5
  ## Establish scope and evidence
6
6
 
7
7
  - [ ] Pin repository, PR, base/head SHA, user requirement or PRD/ADR, changed files, relevant callers/callees, and current CI state. Avoid unrelated checkout areas.
8
+ - [ ] Quote the acceptance clauses and owner bounds verbatim, with their source. Keep them apart from the chosen implementation (designs, existing tests) and the evidence: a design choice or a test expectation is not a requirement.
8
9
  - [ ] Make an independent pass. Treat author explanations and tests as leads, not proof; derive expected behavior from contracts and source.
9
10
  - [ ] Label evidence honestly: **executed/reproduced** (command, result, environment, SHA); **source-traced** (concrete path and preconditions); **inconclusive** (missing facts or ambiguous semantics; do not present as a proven blocker).
10
11
  - [ ] Inventory user-facing entry points and configuration handoffs (CLI flags, cwd, path spellings, config loading). Exercise them end-to-end, not only the recently edited internals.
@@ -24,10 +25,13 @@ Use this checklist for the applicable risks; do not force irrelevant categories.
24
25
  - [ ] When output text changes, compare the intended changed lines with literal templates; do not exclude changed lines from assertions.
25
26
  - [ ] Dedupe findings. Use P1 for severe merge blockers, P2 for meaningful correctness/contract defects, P3 for low-risk issues; avoid speculation.
26
27
  - [ ] For each finding, include exact path:line or diff anchor at HEAD SHA, trigger, observed behavior, violated contract and impact, evidence tier, smallest fix, negative regression, positive control, and disposition.
28
+ - [ ] Name the exact acceptance clause each finding violates, with a witness (starting state, input, expected result, actual result) and the trust assumption. A finding that maps to no clause gets scope `unresolved` for the owner; do not drop it, and do not present it as a blocker. Record severity and scope separately.
27
29
 
28
30
  ## Re-review and conclude
29
31
 
30
32
  - [ ] Pin the new HEAD. Disposition every prior finding separately: resolved, still open, superseded, or inconclusive. Then inspect fix-induced side effects and new changes.
33
+ - [ ] Re-trace one normal acceptance path end to end plus the consumers the fix affects, even when the request names only the last fix. A diff-only re-review misses unchanged sites that make the same decision.
34
+ - [ ] If this round and the previous one each found new defects in the same decision class, say so and recommend `zoom-out` (site inventory against the violated clause) before another point fix.
31
35
  - [ ] Stay read-only unless the user/workflow explicitly authorizes action. Do not edit, comment, label, or merge on review authority alone.
32
36
  - [ ] Summarize scope/SHA, CI, covered and unexamined risks, fixed/open/new counts, and evidence-grounded merge recommendation. “No actionable findings in examined scope” does not mean “bug-free.” Distinguish repository tests from live-host evidence.
33
37
 
@@ -5,6 +5,8 @@
5
5
  - Repository / PR:
6
6
  - Base SHA → reviewed HEAD SHA:
7
7
  - Requirement / PRD / ADR:
8
+ - Scope anchor (acceptance clauses and owner bounds, verbatim, with source):
9
+ - Prior rounds (findings per decision class, for re-review):
8
10
  - Changed files and relevant callers/consumers:
9
11
  - CI state and commands observed:
10
12
  - Counterexample categories covered:
@@ -17,6 +19,9 @@
17
19
  - **Location:** `<path>:<line or diff anchor> @ <HEAD SHA>`
18
20
  - **Trigger:** <minimal input, state transition, or interleaving>
19
21
  - **Behavior → impact:** <observed or source-traced behavior and violated contract>
22
+ - **Violates:** <exact acceptance clause from the scope anchor>
23
+ - **Witness:** <starting state → input → expected result → actual result; trust assumption>
24
+ - **Scope:** <in-scope | out-of-scope (known limit) | unresolved (owner decision)>
20
25
  - **Evidence tier:** <executed/reproduced | source-traced | inconclusive; include exact command/result or preconditions>
21
26
  - **Minimal correction:** <smallest feasible fix>
22
27
  - **Regression:** Negative case: <case that fails before fix>; positive control: <valid behavior that remains valid>
@@ -30,4 +35,5 @@
30
35
  - **CI evidence:**
31
36
  - **Covered / unexamined risks:**
32
37
  - **Findings:** <fixed / open / new counts on re-review>
38
+ - **Convergence:** <new findings per decision class this round vs the previous round; recommend `zoom-out` when a class keeps producing new findings>
33
39
  - **Conclusion:** <merge recommendation grounded in evidence; “no actionable findings in examined scope” is not a claim of bug-free code>
@@ -4,7 +4,7 @@ description: "Route software/project work through Harness: classify tier/topolog
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.38.0
7
+ version: 0.40.0
8
8
  ---
9
9
 
10
10
  # Harness Everything
@@ -122,15 +122,17 @@ function readHostProjectDocsConfigs(workspaceRoot) {
122
122
  return manifests;
123
123
  }
124
124
 
125
- function inspectHostProjectDocs(workspaceRoot) {
125
+ function inspectHostProjectDocs(workspaceRoot, readSource = null) {
126
126
  const entries = [];
127
127
  for (const home of PLATFORM_HOMES) {
128
128
  const file = path.join(workspaceRoot, home, 'harness-everything', 'manifest.json');
129
- if (!fs.existsSync(file)) continue;
129
+ const captured = readSource ? readSource(file) : null;
130
+ if (captured ? captured.presence === 'absent' : !fs.existsSync(file)) continue;
130
131
  const entry = { file, home, projectDocs: null, warnings: [], diagnostics: [], hasProjectDocs: false };
131
132
  let manifest;
132
133
  try {
133
- manifest = JSON.parse(fs.readFileSync(file, 'utf8'));
134
+ if (captured && captured.presence !== 'present') throw new Error('unreadable source');
135
+ manifest = JSON.parse(captured ? captured.bytes.toString('utf8') : fs.readFileSync(file, 'utf8'));
134
136
  } catch {
135
137
  entry.diagnostics.push({
136
138
  code: 'invalid-host-config',
@@ -349,16 +351,18 @@ function configSource(workspaceRoot, file) {
349
351
  return relativePath(workspaceRoot, file);
350
352
  }
351
353
 
352
- function resolveDocsConfig(workspaceRoot, options = {}) {
354
+ function resolveDocsConfigSelection(workspaceRoot, options, readSource) {
353
355
  const resolvedRoot = workspaceRoot || getWorkspaceRoot();
354
356
  if (!resolvedRoot) throw new Error('cannot resolve project docs outside a git workspace; pass a workspace root explicitly');
355
357
  const root = path.resolve(resolvedRoot);
356
358
  const projectFile = projectDocsConfigPath(root);
357
- if (fs.existsSync(projectFile)) {
359
+ const capturedProject = readSource(projectFile);
360
+ if (capturedProject.presence !== 'absent') {
358
361
  const source = configSource(root, projectFile);
359
362
  let config;
360
363
  try {
361
- config = JSON.parse(fs.readFileSync(projectFile, 'utf8'));
364
+ if (capturedProject.presence !== 'present') throw new Error('unreadable source');
365
+ config = JSON.parse(capturedProject.bytes.toString('utf8'));
362
366
  } catch {
363
367
  return {
364
368
  status: 'invalid',
@@ -376,7 +380,7 @@ function resolveDocsConfig(workspaceRoot, options = {}) {
376
380
  return { status: 'invalid', sources: [source], config: null, warnings, diagnostics };
377
381
  }
378
382
 
379
- const hosts = inspectHostProjectDocs(root);
383
+ const hosts = inspectHostProjectDocs(root, readSource);
380
384
  const hostDiagnostics = hosts.flatMap(entry => entry.diagnostics);
381
385
  const hostWarnings = hosts.flatMap(entry => entry.warnings);
382
386
  const validHosts = hosts.filter(entry => entry.projectDocs);
@@ -393,7 +397,7 @@ function resolveDocsConfig(workspaceRoot, options = {}) {
393
397
  return { status: 'project', sources: [source], config, warnings: [...warnings, ...hostWarnings], diagnostics: [...diagnostics, ...hostDiagnostics] };
394
398
  }
395
399
 
396
- const hosts = inspectHostProjectDocs(root);
400
+ const hosts = inspectHostProjectDocs(root, readSource);
397
401
  const hostDiagnostics = hosts.flatMap(entry => entry.diagnostics);
398
402
  const hostWarnings = hosts.flatMap(entry => entry.warnings);
399
403
  const activeHome = normalizedHostHome(options.activeHost);
@@ -457,6 +461,35 @@ function resolveDocsConfig(workspaceRoot, options = {}) {
457
461
  };
458
462
  }
459
463
 
464
+ function resolveDocsConfigCaptured(workspaceRoot, options = {}) {
465
+ const resolvedRoot = workspaceRoot || getWorkspaceRoot();
466
+ if (!resolvedRoot) throw new Error('cannot resolve project docs outside a git workspace; pass a workspace root explicitly');
467
+ const root = path.resolve(resolvedRoot);
468
+ const records = new Map();
469
+ const readSource = file => {
470
+ const key = configSource(root, file);
471
+ if (!records.has(key)) {
472
+ let bytes = Buffer.alloc(0);
473
+ let presence = 'present';
474
+ try { bytes = Buffer.from(fs.readFileSync(file)); }
475
+ catch (error) { presence = error.code === 'ENOENT' ? 'absent' : 'unreadable'; }
476
+ records.set(key, { path: key, presence, bytes, digest: crypto.createHash('sha256').update(bytes).digest('hex') });
477
+ }
478
+ return records.get(key);
479
+ };
480
+ const resolution = resolveDocsConfigSelection(root, options, readSource);
481
+ let activeHost = null;
482
+ try { activeHost = JSON.parse(JSON.stringify(options.activeHost)); } catch { /* no serializable host context */ }
483
+ const sources = [...records.values()].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
484
+ const selectionInputs = sources.map(({ path: sourcePath, presence }) => ({ path: sourcePath, presence }));
485
+ selectionInputs.push({ status: resolution.status, sources: [...resolution.sources], activeHost, config: resolution.config });
486
+ return { resolution, capture: { activeHost, sources, selectionInputs } };
487
+ }
488
+
489
+ function resolveDocsConfig(workspaceRoot, options = {}) {
490
+ return resolveDocsConfigCaptured(workspaceRoot, options).resolution;
491
+ }
492
+
460
493
  function cleanConfiguredPath(value) {
461
494
  if (typeof value !== 'string') return null;
462
495
  const trimmed = value.trim();
@@ -769,6 +802,7 @@ module.exports = {
769
802
  getRepoManifestHomes,
770
803
  readProjectDocs,
771
804
  resolveDocsConfig,
805
+ resolveDocsConfigCaptured,
772
806
  validateDocsConfig,
773
807
  normalizeHostProjectDocs,
774
808
  parseContextMap,
@@ -598,6 +598,10 @@
598
598
  "cease fire",
599
599
  "rule-of-3",
600
600
  "rule of 3",
601
+ "every round finds a new edge case",
602
+ "every review round finds a new edge case",
603
+ "又陷入反覆修正",
604
+ "反覆測試地獄",
601
605
  "卡住",
602
606
  "失敗",
603
607
  "重複",
@@ -4,7 +4,7 @@ description: "Scaffold a verified multi-agent workspace and select bounded speci
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.36.3
7
+ version: 0.39.0
8
8
  ---
9
9
 
10
10
  # Multi-Agent Workspace
@@ -122,15 +122,17 @@ function readHostProjectDocsConfigs(workspaceRoot) {
122
122
  return manifests;
123
123
  }
124
124
 
125
- function inspectHostProjectDocs(workspaceRoot) {
125
+ function inspectHostProjectDocs(workspaceRoot, readSource = null) {
126
126
  const entries = [];
127
127
  for (const home of PLATFORM_HOMES) {
128
128
  const file = path.join(workspaceRoot, home, 'harness-everything', 'manifest.json');
129
- if (!fs.existsSync(file)) continue;
129
+ const captured = readSource ? readSource(file) : null;
130
+ if (captured ? captured.presence === 'absent' : !fs.existsSync(file)) continue;
130
131
  const entry = { file, home, projectDocs: null, warnings: [], diagnostics: [], hasProjectDocs: false };
131
132
  let manifest;
132
133
  try {
133
- manifest = JSON.parse(fs.readFileSync(file, 'utf8'));
134
+ if (captured && captured.presence !== 'present') throw new Error('unreadable source');
135
+ manifest = JSON.parse(captured ? captured.bytes.toString('utf8') : fs.readFileSync(file, 'utf8'));
134
136
  } catch {
135
137
  entry.diagnostics.push({
136
138
  code: 'invalid-host-config',
@@ -349,16 +351,18 @@ function configSource(workspaceRoot, file) {
349
351
  return relativePath(workspaceRoot, file);
350
352
  }
351
353
 
352
- function resolveDocsConfig(workspaceRoot, options = {}) {
354
+ function resolveDocsConfigSelection(workspaceRoot, options, readSource) {
353
355
  const resolvedRoot = workspaceRoot || getWorkspaceRoot();
354
356
  if (!resolvedRoot) throw new Error('cannot resolve project docs outside a git workspace; pass a workspace root explicitly');
355
357
  const root = path.resolve(resolvedRoot);
356
358
  const projectFile = projectDocsConfigPath(root);
357
- if (fs.existsSync(projectFile)) {
359
+ const capturedProject = readSource(projectFile);
360
+ if (capturedProject.presence !== 'absent') {
358
361
  const source = configSource(root, projectFile);
359
362
  let config;
360
363
  try {
361
- config = JSON.parse(fs.readFileSync(projectFile, 'utf8'));
364
+ if (capturedProject.presence !== 'present') throw new Error('unreadable source');
365
+ config = JSON.parse(capturedProject.bytes.toString('utf8'));
362
366
  } catch {
363
367
  return {
364
368
  status: 'invalid',
@@ -376,7 +380,7 @@ function resolveDocsConfig(workspaceRoot, options = {}) {
376
380
  return { status: 'invalid', sources: [source], config: null, warnings, diagnostics };
377
381
  }
378
382
 
379
- const hosts = inspectHostProjectDocs(root);
383
+ const hosts = inspectHostProjectDocs(root, readSource);
380
384
  const hostDiagnostics = hosts.flatMap(entry => entry.diagnostics);
381
385
  const hostWarnings = hosts.flatMap(entry => entry.warnings);
382
386
  const validHosts = hosts.filter(entry => entry.projectDocs);
@@ -393,7 +397,7 @@ function resolveDocsConfig(workspaceRoot, options = {}) {
393
397
  return { status: 'project', sources: [source], config, warnings: [...warnings, ...hostWarnings], diagnostics: [...diagnostics, ...hostDiagnostics] };
394
398
  }
395
399
 
396
- const hosts = inspectHostProjectDocs(root);
400
+ const hosts = inspectHostProjectDocs(root, readSource);
397
401
  const hostDiagnostics = hosts.flatMap(entry => entry.diagnostics);
398
402
  const hostWarnings = hosts.flatMap(entry => entry.warnings);
399
403
  const activeHome = normalizedHostHome(options.activeHost);
@@ -457,6 +461,35 @@ function resolveDocsConfig(workspaceRoot, options = {}) {
457
461
  };
458
462
  }
459
463
 
464
+ function resolveDocsConfigCaptured(workspaceRoot, options = {}) {
465
+ const resolvedRoot = workspaceRoot || getWorkspaceRoot();
466
+ if (!resolvedRoot) throw new Error('cannot resolve project docs outside a git workspace; pass a workspace root explicitly');
467
+ const root = path.resolve(resolvedRoot);
468
+ const records = new Map();
469
+ const readSource = file => {
470
+ const key = configSource(root, file);
471
+ if (!records.has(key)) {
472
+ let bytes = Buffer.alloc(0);
473
+ let presence = 'present';
474
+ try { bytes = Buffer.from(fs.readFileSync(file)); }
475
+ catch (error) { presence = error.code === 'ENOENT' ? 'absent' : 'unreadable'; }
476
+ records.set(key, { path: key, presence, bytes, digest: crypto.createHash('sha256').update(bytes).digest('hex') });
477
+ }
478
+ return records.get(key);
479
+ };
480
+ const resolution = resolveDocsConfigSelection(root, options, readSource);
481
+ let activeHost = null;
482
+ try { activeHost = JSON.parse(JSON.stringify(options.activeHost)); } catch { /* no serializable host context */ }
483
+ const sources = [...records.values()].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
484
+ const selectionInputs = sources.map(({ path: sourcePath, presence }) => ({ path: sourcePath, presence }));
485
+ selectionInputs.push({ status: resolution.status, sources: [...resolution.sources], activeHost, config: resolution.config });
486
+ return { resolution, capture: { activeHost, sources, selectionInputs } };
487
+ }
488
+
489
+ function resolveDocsConfig(workspaceRoot, options = {}) {
490
+ return resolveDocsConfigCaptured(workspaceRoot, options).resolution;
491
+ }
492
+
460
493
  function cleanConfiguredPath(value) {
461
494
  if (typeof value !== 'string') return null;
462
495
  const trimmed = value.trim();
@@ -769,6 +802,7 @@ module.exports = {
769
802
  getRepoManifestHomes,
770
803
  readProjectDocs,
771
804
  resolveDocsConfig,
805
+ resolveDocsConfigCaptured,
772
806
  validateDocsConfig,
773
807
  normalizeHostProjectDocs,
774
808
  parseContextMap,
@@ -4,7 +4,7 @@ description: "Turn the conversation into a written spec — feature PRD, CLI/API
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.36.3
7
+ version: 0.39.0
8
8
  ---
9
9
 
10
10
  # To Spec
@@ -122,15 +122,17 @@ function readHostProjectDocsConfigs(workspaceRoot) {
122
122
  return manifests;
123
123
  }
124
124
 
125
- function inspectHostProjectDocs(workspaceRoot) {
125
+ function inspectHostProjectDocs(workspaceRoot, readSource = null) {
126
126
  const entries = [];
127
127
  for (const home of PLATFORM_HOMES) {
128
128
  const file = path.join(workspaceRoot, home, 'harness-everything', 'manifest.json');
129
- if (!fs.existsSync(file)) continue;
129
+ const captured = readSource ? readSource(file) : null;
130
+ if (captured ? captured.presence === 'absent' : !fs.existsSync(file)) continue;
130
131
  const entry = { file, home, projectDocs: null, warnings: [], diagnostics: [], hasProjectDocs: false };
131
132
  let manifest;
132
133
  try {
133
- manifest = JSON.parse(fs.readFileSync(file, 'utf8'));
134
+ if (captured && captured.presence !== 'present') throw new Error('unreadable source');
135
+ manifest = JSON.parse(captured ? captured.bytes.toString('utf8') : fs.readFileSync(file, 'utf8'));
134
136
  } catch {
135
137
  entry.diagnostics.push({
136
138
  code: 'invalid-host-config',
@@ -349,16 +351,18 @@ function configSource(workspaceRoot, file) {
349
351
  return relativePath(workspaceRoot, file);
350
352
  }
351
353
 
352
- function resolveDocsConfig(workspaceRoot, options = {}) {
354
+ function resolveDocsConfigSelection(workspaceRoot, options, readSource) {
353
355
  const resolvedRoot = workspaceRoot || getWorkspaceRoot();
354
356
  if (!resolvedRoot) throw new Error('cannot resolve project docs outside a git workspace; pass a workspace root explicitly');
355
357
  const root = path.resolve(resolvedRoot);
356
358
  const projectFile = projectDocsConfigPath(root);
357
- if (fs.existsSync(projectFile)) {
359
+ const capturedProject = readSource(projectFile);
360
+ if (capturedProject.presence !== 'absent') {
358
361
  const source = configSource(root, projectFile);
359
362
  let config;
360
363
  try {
361
- config = JSON.parse(fs.readFileSync(projectFile, 'utf8'));
364
+ if (capturedProject.presence !== 'present') throw new Error('unreadable source');
365
+ config = JSON.parse(capturedProject.bytes.toString('utf8'));
362
366
  } catch {
363
367
  return {
364
368
  status: 'invalid',
@@ -376,7 +380,7 @@ function resolveDocsConfig(workspaceRoot, options = {}) {
376
380
  return { status: 'invalid', sources: [source], config: null, warnings, diagnostics };
377
381
  }
378
382
 
379
- const hosts = inspectHostProjectDocs(root);
383
+ const hosts = inspectHostProjectDocs(root, readSource);
380
384
  const hostDiagnostics = hosts.flatMap(entry => entry.diagnostics);
381
385
  const hostWarnings = hosts.flatMap(entry => entry.warnings);
382
386
  const validHosts = hosts.filter(entry => entry.projectDocs);
@@ -393,7 +397,7 @@ function resolveDocsConfig(workspaceRoot, options = {}) {
393
397
  return { status: 'project', sources: [source], config, warnings: [...warnings, ...hostWarnings], diagnostics: [...diagnostics, ...hostDiagnostics] };
394
398
  }
395
399
 
396
- const hosts = inspectHostProjectDocs(root);
400
+ const hosts = inspectHostProjectDocs(root, readSource);
397
401
  const hostDiagnostics = hosts.flatMap(entry => entry.diagnostics);
398
402
  const hostWarnings = hosts.flatMap(entry => entry.warnings);
399
403
  const activeHome = normalizedHostHome(options.activeHost);
@@ -457,6 +461,35 @@ function resolveDocsConfig(workspaceRoot, options = {}) {
457
461
  };
458
462
  }
459
463
 
464
+ function resolveDocsConfigCaptured(workspaceRoot, options = {}) {
465
+ const resolvedRoot = workspaceRoot || getWorkspaceRoot();
466
+ if (!resolvedRoot) throw new Error('cannot resolve project docs outside a git workspace; pass a workspace root explicitly');
467
+ const root = path.resolve(resolvedRoot);
468
+ const records = new Map();
469
+ const readSource = file => {
470
+ const key = configSource(root, file);
471
+ if (!records.has(key)) {
472
+ let bytes = Buffer.alloc(0);
473
+ let presence = 'present';
474
+ try { bytes = Buffer.from(fs.readFileSync(file)); }
475
+ catch (error) { presence = error.code === 'ENOENT' ? 'absent' : 'unreadable'; }
476
+ records.set(key, { path: key, presence, bytes, digest: crypto.createHash('sha256').update(bytes).digest('hex') });
477
+ }
478
+ return records.get(key);
479
+ };
480
+ const resolution = resolveDocsConfigSelection(root, options, readSource);
481
+ let activeHost = null;
482
+ try { activeHost = JSON.parse(JSON.stringify(options.activeHost)); } catch { /* no serializable host context */ }
483
+ const sources = [...records.values()].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
484
+ const selectionInputs = sources.map(({ path: sourcePath, presence }) => ({ path: sourcePath, presence }));
485
+ selectionInputs.push({ status: resolution.status, sources: [...resolution.sources], activeHost, config: resolution.config });
486
+ return { resolution, capture: { activeHost, sources, selectionInputs } };
487
+ }
488
+
489
+ function resolveDocsConfig(workspaceRoot, options = {}) {
490
+ return resolveDocsConfigCaptured(workspaceRoot, options).resolution;
491
+ }
492
+
460
493
  function cleanConfiguredPath(value) {
461
494
  if (typeof value !== 'string') return null;
462
495
  const trimmed = value.trim();
@@ -769,6 +802,7 @@ module.exports = {
769
802
  getRepoManifestHomes,
770
803
  readProjectDocs,
771
804
  resolveDocsConfig,
805
+ resolveDocsConfigCaptured,
772
806
  validateDocsConfig,
773
807
  normalizeHostProjectDocs,
774
808
  parseContextMap,