harness-everything 0.47.4 → 0.47.5

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 (35) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +17 -0
  4. package/behavioral-evals/README.md +17 -0
  5. package/behavioral-evals/case-validator.js +15 -1
  6. package/behavioral-evals/cases/baseline-tdd-workflow.yaml +4 -0
  7. package/behavioral-evals/run.js +172 -0
  8. package/behavioral-evals/transcript-parser.js +43 -0
  9. package/ci/mechanism-2s-tdd-quality.test.js +149 -6
  10. package/ci/test-behavioral-case-validation.js +215 -1
  11. package/code-review/SKILL.md +1 -1
  12. package/code-review/references/review-playbook.md +9 -0
  13. package/opencode-plugin/plugin.json +1 -1
  14. package/package.json +1 -1
  15. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  16. package/plugins/harness-everything/plugin.json +1 -1
  17. package/plugins/harness-everything/skills/code-review/SKILL.md +1 -1
  18. package/plugins/harness-everything/skills/code-review/references/review-playbook.md +9 -0
  19. package/plugins/harness-everything/skills/tdd/SKILL.md +1 -1
  20. package/plugins/harness-everything/skills/tdd/fixtures/quality-scenarios.json +10 -2
  21. package/plugins/harness-everything/skills/tdd/references/quality-model.md +2 -0
  22. package/plugins/harness-everything/skills/tdd/schemas/quality-evidence.schema.json +21 -0
  23. package/plugins/harness-everything/skills/tdd/schemas/quality-report.schema.json +2 -0
  24. package/plugins/harness-everything/skills/tdd/scripts/quality-gate.js +165 -1
  25. package/plugins/harness-everything/skills/zoom-out/SKILL.md +1 -1
  26. package/plugins/harness-everything/skills/zoom-out/references/circuit-breaker.md +1 -0
  27. package/sbom.cdx.json +7 -7
  28. package/tdd/SKILL.md +1 -1
  29. package/tdd/fixtures/quality-scenarios.json +10 -2
  30. package/tdd/references/quality-model.md +2 -0
  31. package/tdd/schemas/quality-evidence.schema.json +21 -0
  32. package/tdd/schemas/quality-report.schema.json +2 -0
  33. package/tdd/scripts/quality-gate.js +165 -1
  34. package/zoom-out/SKILL.md +1 -1
  35. package/zoom-out/references/circuit-breaker.md +1 -0
@@ -230,4 +230,218 @@ try {
230
230
  fs.rmSync(jsonOnlyDir, { recursive: true, force: true });
231
231
  }
232
232
 
233
- console.log(`Behavioral case validation passed (${fs.readdirSync(casesDir).filter(name => name.endsWith('.yaml')).length} cases + negative controls + ordered execution evidence + json-only final output).`);
233
+ // Skill-core completion: passing project tests is artifact evidence only; the
234
+ // TDD case also requires an executed quality gate command.
235
+ const tddCase = parseSimpleYaml(fs.readFileSync(path.join(casesDir, 'baseline-tdd-workflow.yaml'), 'utf8'));
236
+ const qualityGate = tddCase.expectations.find(expectation => expectation.type === 'tdd_quality_gate');
237
+ assert.ok(qualityGate, 'baseline TDD case must require a passed TDD quality gate');
238
+ // The gate is graded by independently re-evaluating retained evidence, so the
239
+ // workspace holds a real passing evidence file and a failing variant.
240
+ const PASS_EVIDENCE = fs.readFileSync(path.join(__dirname, '..', 'tdd', 'fixtures', 'pass-evidence.json'), 'utf8');
241
+ const FAIL_EVIDENCE = (() => {
242
+ const evidence = JSON.parse(PASS_EVIDENCE);
243
+ evidence.requirements = evidence.requirements.map(requirement => ({ ...requirement, sourceConformance: 'FAIL' }));
244
+ return JSON.stringify(evidence);
245
+ })();
246
+ const gateDir = fs.mkdtempSync(path.join(os.tmpdir(), 'behavioral-tdd-quality-gate-'));
247
+ try {
248
+ fs.writeFileSync(path.join(gateDir, 'evidence.json'), PASS_EVIDENCE);
249
+ fs.writeFileSync(path.join(gateDir, 'failing-evidence.json'), FAIL_EVIDENCE);
250
+ fs.writeFileSync(path.join(gateDir, 'broken-evidence.json'), '{ not json');
251
+ // A string command defaults to the gate's own success line when it is a
252
+ // quality-gate invocation, so existing positives stay meaningful. An object
253
+ // command states the observed output and exit code explicitly.
254
+ const QUALITY_GATE_COMMAND_RE = /quality-gate\.js|tdd:quality|test:tdd-quality/;
255
+ const gradeCommands = (name, commands) => {
256
+ const transcriptPath = path.join(gateDir, `${name}.jsonl`);
257
+ const events = commands.map((entry, index) => {
258
+ const { command, output, exit } = typeof entry === 'string'
259
+ ? { command: entry, output: QUALITY_GATE_COMMAND_RE.test(entry) ? 'TDD Quality Gate: PASS' : '', exit: undefined }
260
+ : entry;
261
+ const state = { status: 'completed', input: { command } };
262
+ if (output !== undefined) state.output = output;
263
+ if (exit !== undefined) state.metadata = { exit };
264
+ return { type: 'tool_use', part: { type: 'tool', tool: 'Bash', callID: `${name}-${index}`, state } };
265
+ });
266
+ events.push({ type: 'text', part: { type: 'text', text: 'done' } });
267
+ fs.writeFileSync(transcriptPath, events.map(event => JSON.stringify(event)).join('\n'));
268
+ return grade({ expectations: [qualityGate] }, gateDir, transcriptPath);
269
+ };
270
+ const testsOnly = gradeCommands('tests-only', ['npm test']);
271
+ assert.strictEqual(testsOnly.passed, false, 'passing project tests without the quality gate must fail the TDD case');
272
+ assert.strictEqual(testsOnly.results[0].status, 'fail', 'missing quality gate must be a definitive fail, not inconclusive evidence');
273
+ assert.strictEqual(gradeCommands('npm-gate', ['npm test', 'npm run tdd:quality -- evidence.json']).passed, true, 'npm run tdd:quality with evidence must satisfy the gate');
274
+ assert.strictEqual(gradeCommands('node-gate', ['npm test', 'node tdd/scripts/quality-gate.js evidence.json']).passed, true, 'node quality-gate.js with evidence must satisfy the gate');
275
+ assert.strictEqual(gradeCommands('installed-skill-gate', ['npm test', 'node .claude/skills/tdd/scripts/quality-gate.js evidence.json']).passed, true, 'quality gate under the installed skill dir must satisfy the gate');
276
+ assert.strictEqual(gradeCommands('absolute-gate', ['npm test', 'node /tmp/ws/.harness/tdd/scripts/quality-gate.js evidence.json']).passed, true, 'absolute quality-gate path must satisfy the gate');
277
+ for (const [name, commands] of [
278
+ ['echo-mention', ['echo npm run tdd:quality']],
279
+ ['compound-gate', ['npm run tdd:quality -- evidence.json && rm -rf build']],
280
+ ['similar-name', ['npm run tdd:quality-notes']],
281
+ ]) {
282
+ assert.strictEqual(gradeCommands(name, commands).passed, false, `${name} must not count as an executed quality gate`);
283
+ }
284
+ // A completed tool call is not completion evidence: the gate outcome is
285
+ // re-evaluated from the retained evidence, independent of transcript claims.
286
+ const gateFailures = [
287
+ ['nonzero-exit', ['npm test', 'npm run tdd:quality -- failing-evidence.json'], /re-evaluation of failing-evidence\.json is FAIL/],
288
+ ['missing-evidence', ['npm test', 'npm run tdd:quality -- absent.json'], /not retained/],
289
+ ['invalid-evidence', ['npm test', 'node tdd/scripts/quality-gate.js broken-evidence.json'], /not valid JSON/],
290
+ ['no-evidence-arg', ['npm test', 'npm run tdd:quality'], /without an evidence file/],
291
+ ['self-test-alias', ['npm test', 'npm run test:tdd-quality'], /repository self-test/],
292
+ ['escaping-evidence', ['npm test', 'npm run tdd:quality -- ../evidence.json'], /outside the workspace/],
293
+ ['missing-output-report', ['npm test', 'npm run tdd:quality -- evidence.json --output reports/quality.json'], /quality report reports\/quality\.json was not retained/],
294
+ ['last-run-counts', ['npm run tdd:quality -- evidence.json', 'npm run tdd:quality -- failing-evidence.json'], /FAIL/],
295
+ ];
296
+ for (const [name, commands, reason] of gateFailures) {
297
+ const graded = gradeCommands(name, commands);
298
+ assert.strictEqual(graded.passed, false, `${name} must not satisfy the quality gate`);
299
+ assert.strictEqual(graded.results[0].status, 'fail', `${name} must be a definitive fail`);
300
+ assert.match(graded.results[0].reason, reason, `${name} should explain why`);
301
+ }
302
+ // OpenCode reports state.status "completed" even when the gate exits 1.
303
+ const opencodeExit1 = path.join(gateDir, 'opencode-exit1.jsonl');
304
+ fs.writeFileSync(opencodeExit1, [
305
+ { type: 'tool_use', part: { type: 'tool', tool: 'bash', callID: 'gate', state: { status: 'completed', input: { command: 'npm run tdd:quality -- failing-evidence.json' }, output: 'TDD Quality Gate: FAIL\nexit code 1' } } },
306
+ { type: 'text', part: { type: 'text', text: 'Quality gate done.' } },
307
+ ].map(event => JSON.stringify(event)).join('\n'));
308
+ assert.strictEqual(grade({ expectations: [qualityGate] }, gateDir, opencodeExit1).passed, false, 'completed OpenCode event with exit 1 must fail');
309
+ // Fixing evidence and re-running is allowed: the last completed run counts.
310
+ assert.strictEqual(gradeCommands('rerun-after-fix', ['npm run tdd:quality -- failing-evidence.json', 'npm run tdd:quality -- evidence.json']).passed, true, 'a passing re-run after a failing run should satisfy the gate');
311
+ // Observed command outcome: the transcript must show the gate's own success
312
+ // line and no error or non-zero exit before the evidence is trusted.
313
+ const { evaluateEvidence } = require('../tdd/scripts/quality-gate.js');
314
+ const observedGate = 'npm run tdd:quality -- evidence.json';
315
+ const observedFailures = [
316
+ ['failed-command-pass-evidence', { command: observedGate, output: 'npm ERR! Missing script: "tdd:quality"', exit: 1 }, /quality gate command failed/],
317
+ ['nonzero-exit-no-error-text', { command: observedGate, output: 'TDD Quality Gate: PASS', exit: 1 }, /exited with code 1/],
318
+ ['gate-printed-fail-evidence-now-pass', { command: observedGate, output: 'TDD Quality Gate: FAIL' }, /printed TDD Quality Gate: FAIL/],
319
+ ];
320
+ for (const [name, command, reason] of observedFailures) {
321
+ const graded = gradeCommands(name, [command]);
322
+ assert.strictEqual(graded.passed, false, `${name} must not satisfy the quality gate`);
323
+ assert.strictEqual(graded.results[0].status, 'fail', `${name} must be a definitive fail`);
324
+ assert.match(graded.results[0].reason, reason, `${name} should explain why`);
325
+ }
326
+ const unobservable = gradeCommands('unobservable-outcome', [{ command: observedGate, output: '' }]);
327
+ assert.strictEqual(unobservable.results[0].status, 'inconclusive', 'a gate run with no observable output or exit code must be inconclusive');
328
+ assert.strictEqual(unobservable.passed, false, 'an inconclusive gate run must not pass');
329
+ assert.match(unobservable.results[0].reason, /not observable in the transcript/, 'inconclusive reason should say the outcome is not observable');
330
+ // Definitive workspace failures win over an unobservable outcome.
331
+ const missingNoOutput = gradeCommands('missing-evidence-no-output', [{ command: 'npm run tdd:quality -- nope.json', output: '' }]);
332
+ assert.strictEqual(missingNoOutput.results[0].status, 'fail', 'missing evidence must be a definitive fail even with no observable output');
333
+ assert.match(missingNoOutput.results[0].reason, /not retained/, 'missing evidence should be named');
334
+ // Only the error signal is line-anchored: prose mentioning npm errors passes,
335
+ // while a real npm error line still fails.
336
+ assert.strictEqual(gradeCommands('prose-npm-error', [{ command: observedGate, output: 'TDD Quality Gate: PASS\n- note: avoid npm error handling', exit: 0 }]).passed, true, 'prose mentioning npm error must not fail a passing gate');
337
+ const npmErrorLine = gradeCommands('npm-error-line', [{ command: observedGate, output: 'npm error Missing script: "tdd:quality"' }]);
338
+ assert.strictEqual(npmErrorLine.results[0].status, 'fail', 'an npm error line must be a definitive fail');
339
+ assert.match(npmErrorLine.results[0].reason, /npm error/, 'npm error line should be named');
340
+ // npm ERR!/error lines after a PASS line must fail even without exit metadata;
341
+ // the boundary after the token must match whitespace or end of line.
342
+ const npmTrailer = (name, output) => gradeCommands(name, [{ command: observedGate, output }]);
343
+ for (const [name, output] of [
344
+ ['npm-err-bang-eacces', 'TDD Quality Gate: PASS\nnpm ERR! code EACCES'],
345
+ ['npm-err-bang-eresolve', 'TDD Quality Gate: PASS\nnpm ERR! code ERESOLVE'],
346
+ ['npm-error-elifecycle', 'TDD Quality Gate: PASS\nnpm error code ELIFECYCLE'],
347
+ ['npm-err-bang-eol', 'TDD Quality Gate: PASS\nnpm ERR!'],
348
+ ]) {
349
+ const graded = npmTrailer(name, output);
350
+ assert.strictEqual(graded.results[0].status, 'fail', `${name} must be a definitive fail`);
351
+ assert.strictEqual(graded.passed, false, `${name} must not satisfy the quality gate`);
352
+ assert.match(graded.results[0].reason, /npm error/i, `${name} should name the npm error`);
353
+ }
354
+ assert.strictEqual(npmTrailer('npm-errors-word', 'TDD Quality Gate: PASS\nnpm ERRORS are documented').passed, true, 'a word that only starts with ERR must not be an npm error');
355
+ const { GATE_COMMAND_ERROR_RE } = require('../behavioral-evals/run');
356
+ assert.strictEqual(GATE_COMMAND_ERROR_RE.test('npm ERR! code EACCES'), true, 'npm ERR! line must match the error regex');
357
+ assert.strictEqual(GATE_COMMAND_ERROR_RE.test('npm ERRORS'), false, 'npm ERRORS must not match the error regex');
358
+
359
+ // Claude stream-json: is_error on the tool_result is the host's failure
360
+ // signal; the call is recorded as failed, so it never counts as executed.
361
+ const claudeGate = (name, toolResult) => {
362
+ const transcriptPath = path.join(gateDir, `${name}.jsonl`);
363
+ fs.writeFileSync(transcriptPath, [
364
+ { type: 'assistant', message: { content: [{ type: 'tool_use', id: 'bash-1', name: 'Bash', input: { command: observedGate } }] } },
365
+ { type: 'user', message: { content: [{ type: 'tool_result', tool_use_id: 'bash-1', ...toolResult }] } },
366
+ { type: 'result', subtype: 'success', result: 'done' },
367
+ ].map(event => JSON.stringify(event)).join('\n'));
368
+ return grade({ expectations: [qualityGate] }, gateDir, transcriptPath);
369
+ };
370
+ const claudeError = claudeGate('claude-error', { is_error: true, content: 'npm ERR! Missing script' });
371
+ assert.strictEqual(claudeError.passed, false, 'Claude tool_result with is_error must not satisfy the gate');
372
+ assert.strictEqual(claudeError.results[0].status, 'fail', 'Claude tool_result error must be a definitive fail');
373
+ assert.match(claudeError.results[0].reason, /last quality gate run \(bash-1\) failed/, 'Claude errored gate call should be the failing last run');
374
+ assert.strictEqual(claudeGate('claude-success', { is_error: false, content: [{ type: 'text', text: 'TDD Quality Gate: PASS' }] }).passed, true, 'Claude tool_result carrying the gate success line should pass');
375
+ // Runs are graded in transcript order: the last matching attempt decides, so an
376
+ // earlier PASS never hides a later failed or unresolved run.
377
+ const claudeRuns = (name, runs) => {
378
+ const transcriptPath = path.join(gateDir, `${name}.jsonl`);
379
+ const events = [];
380
+ runs.forEach(({ command, result }, index) => {
381
+ const id = `${name}-bash-${index}`;
382
+ events.push({ type: 'assistant', message: { content: [{ type: 'tool_use', id, name: 'Bash', input: { command } }] } });
383
+ if (result) events.push({ type: 'user', message: { content: [{ type: 'tool_result', tool_use_id: id, ...result }] } });
384
+ });
385
+ events.push({ type: 'assistant', message: { content: [{ type: 'text', text: 'done' }] } });
386
+ events.push({ type: 'result', subtype: 'success', result: 'done' });
387
+ fs.writeFileSync(transcriptPath, events.map(event => JSON.stringify(event)).join('\n'));
388
+ return grade({ expectations: [qualityGate] }, gateDir, transcriptPath);
389
+ };
390
+ const gatePass = { is_error: false, content: [{ type: 'text', text: 'TDD Quality Gate: PASS' }] };
391
+ const gateFail = { is_error: true, content: 'TDD Quality Gate: FAIL' };
392
+ const lastRunError = claudeRuns('claude-pass-then-error', [
393
+ { command: observedGate, result: gatePass },
394
+ { command: observedGate, result: gateFail },
395
+ ]);
396
+ assert.strictEqual(lastRunError.results[0].status, 'fail', 'a failed last run must be a definitive fail even after an earlier PASS');
397
+ assert.strictEqual(lastRunError.passed, false, 'an earlier PASS must not satisfy the gate after a failed last run');
398
+ assert.match(lastRunError.results[0].reason, /last quality gate run/, 'the failed last run should be named');
399
+ const lastRunUnresolved = claudeRuns('claude-pass-then-unresolved', [
400
+ { command: observedGate, result: gatePass },
401
+ { command: observedGate },
402
+ ]);
403
+ assert.strictEqual(lastRunUnresolved.results[0].status, 'inconclusive', 'an unresolved last run must be inconclusive, not a pass');
404
+ assert.strictEqual(lastRunUnresolved.passed, false, 'an unresolved last run must not pass the gate');
405
+ assert.match(lastRunUnresolved.results[0].reason, /no observable result/, 'the unresolved last run should be named');
406
+ const otherEvidenceError = claudeRuns('claude-pass-then-other-evidence', [
407
+ { command: observedGate, result: gatePass },
408
+ { command: 'node tdd/scripts/quality-gate.js failing-evidence.json', result: gateFail },
409
+ ]);
410
+ assert.strictEqual(otherEvidenceError.results[0].status, 'fail', 'a failed run against other evidence must fail the gate');
411
+ assert.match(otherEvidenceError.results[0].reason, /last quality gate run/, 'the failed last run against other evidence should be named');
412
+ assert.strictEqual(claudeRuns('claude-fail-then-retry', [
413
+ { command: observedGate, result: gateFail },
414
+ { command: observedGate, result: gatePass },
415
+ ]).passed, true, 'a failed run followed by a successful retry must pass');
416
+ // Retained --output report: a full report must be present and must equal the
417
+ // fresh re-evaluation over every top-level key.
418
+ const realReport = evaluateEvidence(JSON.parse(PASS_EVIDENCE));
419
+ assert.strictEqual(realReport.result, 'PASS', 'the pass fixture must re-evaluate to PASS');
420
+ fs.mkdirSync(path.join(gateDir, 'reports'), { recursive: true });
421
+ const reportPath = path.join(gateDir, 'reports', 'quality.json');
422
+ fs.writeFileSync(reportPath, JSON.stringify({ result: 'PASS' }));
423
+ const markerOnly = gradeCommands('marker-output-report', ['npm run tdd:quality -- evidence.json --output reports/quality.json']);
424
+ assert.strictEqual(markerOnly.passed, false, 'a {"result":"PASS"} marker must not satisfy a retained report');
425
+ assert.match(markerOnly.results[0].reason, /missing required keys: .*requirements/, 'a marker-only retained report should name the missing keys');
426
+ fs.writeFileSync(reportPath, JSON.stringify(realReport, null, 2));
427
+ assert.strictEqual(gradeCommands('retained-output-report', ['npm run tdd:quality -- evidence.json --output reports/quality.json']).passed, true, 'retained --output report equal to the re-evaluation should pass');
428
+ fs.writeFileSync(reportPath, JSON.stringify({ ...realReport, score: realReport.score - 1 }, null, 2));
429
+ const staleScore = gradeCommands('stale-output-report', ['npm run tdd:quality -- evidence.json --output reports/quality.json']);
430
+ assert.strictEqual(staleScore.passed, false, 'a retained report with a stale score must fail');
431
+ assert.match(staleScore.results[0].reason, /differs from re-evaluation at "score"/, 'a stale retained report should name the differing key');
432
+ // Without --output the gate prints its report to stdout; that printed report
433
+ // must equal the fresh re-evaluation of the evidence the command names.
434
+ const printedText = report => `TDD Quality Gate: PASS\n${JSON.stringify(report, null, 2)}`;
435
+ assert.strictEqual(gradeCommands('stdout-report-equal', [{ command: observedGate, output: printedText(realReport) }]).passed, true, 'a printed report equal to the re-evaluation should pass');
436
+ const otherEvidence = JSON.parse(PASS_EVIDENCE);
437
+ otherEvidence.requirements[0].requirementId = 'REQ-OTHER';
438
+ fs.writeFileSync(path.join(gateDir, 'other-evidence.json'), JSON.stringify(otherEvidence));
439
+ const otherReport = evaluateEvidence(otherEvidence);
440
+ const stdoutMismatch = gradeCommands('stdout-report-other', [{ command: observedGate, output: printedText(otherReport) }]);
441
+ assert.strictEqual(stdoutMismatch.passed, false, 'a printed report from different evidence must fail');
442
+ assert.match(stdoutMismatch.results[0].reason, /gate output report differs from re-evaluation at "[a-zA-Z]+"/, 'a mismatched printed report should name the differing key');
443
+ } finally {
444
+ fs.rmSync(gateDir, { recursive: true, force: true });
445
+ }
446
+
447
+ console.log(`Behavioral case validation passed (${fs.readdirSync(casesDir).filter(name => name.endsWith('.yaml')).length} cases + negative controls + ordered execution evidence + json-only final output + tdd quality gate).`);
@@ -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.40.0
7
+ version: 0.47.5
8
8
  ---
9
9
 
10
10
  # Code Review
@@ -19,6 +19,15 @@ Use this checklist for the applicable risks; do not force irrelevant categories.
19
19
  - [ ] Follow each important diagnostic/fact through every consumer and ancestor. Check whether required facts are computed before derived authority, indexes, or output.
20
20
  - [ ] Compare new rules with previously accepted rules and controls. Separate display severity from safety prerequisites.
21
21
 
22
+ ## Check evidence a gate trusts
23
+
24
+ Apply these when the change grades, gates or labels evidence. Authors SHOULD run them as a self-review before push.
25
+
26
+ - [ ] **Claimed vs observed.** Separate what the subject reports about itself (a tool's `completed` status, a result marker, an agent-supplied expected value, a status label) from what the checker observes independently (exit code, output, a recomputed result, the quoted source). A self-report alone never passes; when nothing independent is observable, the result is inconclusive.
27
+ - [ ] **Select, then judge.** When the last, first or any item of a sequence decides (last run, final message, latest attempt), select from every attempt in order before filtering by outcome. Test both orders: failure then success, and success then failure or no result.
28
+ - [ ] **One witness per rule.** Give each detection rule a negative case that triggers only that rule; a case that also trips an exit code or another pattern cannot show the rule works. Probe positive cases at structural boundaries (inside a token, string or quote), not only at safe separators.
29
+ - [ ] **Comparable subjects, honest labels.** A comparison or conflict needs both sides to describe the same target. A status name claims only what was checked: a consistent quote is not a validated diagnosis.
30
+
22
31
  ## Check tests and output
23
32
 
24
33
  - [ ] Ask whether each test still passes if the behavior is disabled, bypassed, or broken (mutation thought experiment). Confirm the test is included in CI.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harness-enforcement",
3
3
  "description": "Repository inventory for the opencode enforcement plugin and its standalone helper",
4
- "version": "0.47.4",
4
+ "version": "0.47.5",
5
5
  "runtime": "index.mjs",
6
6
  "hooks": {
7
7
  "verify": "hooks/verify.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.47.4",
3
+ "version": "0.47.5",
4
4
  "description": "Harness OS: An Orchestrated Agent Operating System for AI-driven Development",
5
5
  "main": "harness-everything/scripts/bootstrap.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.47.4",
3
+ "version": "0.47.5",
4
4
  "description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
5
5
  "author": {
6
6
  "name": "Miya Daniel",
@@ -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.47.4",
4
+ "version": "0.47.5",
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.40.0
7
+ version: 0.47.5
8
8
  ---
9
9
 
10
10
  # Code Review
@@ -19,6 +19,15 @@ Use this checklist for the applicable risks; do not force irrelevant categories.
19
19
  - [ ] Follow each important diagnostic/fact through every consumer and ancestor. Check whether required facts are computed before derived authority, indexes, or output.
20
20
  - [ ] Compare new rules with previously accepted rules and controls. Separate display severity from safety prerequisites.
21
21
 
22
+ ## Check evidence a gate trusts
23
+
24
+ Apply these when the change grades, gates or labels evidence. Authors SHOULD run them as a self-review before push.
25
+
26
+ - [ ] **Claimed vs observed.** Separate what the subject reports about itself (a tool's `completed` status, a result marker, an agent-supplied expected value, a status label) from what the checker observes independently (exit code, output, a recomputed result, the quoted source). A self-report alone never passes; when nothing independent is observable, the result is inconclusive.
27
+ - [ ] **Select, then judge.** When the last, first or any item of a sequence decides (last run, final message, latest attempt), select from every attempt in order before filtering by outcome. Test both orders: failure then success, and success then failure or no result.
28
+ - [ ] **One witness per rule.** Give each detection rule a negative case that triggers only that rule; a case that also trips an exit code or another pattern cannot show the rule works. Probe positive cases at structural boundaries (inside a token, string or quote), not only at safe separators.
29
+ - [ ] **Comparable subjects, honest labels.** A comparison or conflict needs both sides to describe the same target. A status name claims only what was checked: a consistent quote is not a validated diagnosis.
30
+
22
31
  ## Check tests and output
23
32
 
24
33
  - [ ] Ask whether each test still passes if the behavior is disabled, bypassed, or broken (mutation thought experiment). Confirm the test is included in CI.
@@ -4,7 +4,7 @@ description: "Drive Standard Tasks (Tier 2) with Test-Driven Development (RED/GR
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.13.0
7
+ version: 0.47.5
8
8
  ---
9
9
 
10
10
  # Test-Driven Development
@@ -8,8 +8,16 @@
8
8
  { "name": "weighted integration failure", "mutation": "the 0.15 positive dimension scores zero", "expectedScore": 85, "expectedResult": "FAIL" },
9
9
  { "name": "weighted unit failure", "mutation": "unit determinism is N/A and the 0.15 positive dimension scores zero", "expectedScore": 83.33, "expectedResult": "FAIL" },
10
10
  { "name": "NON_CONFORMANT", "mutation": "sourceConformance = NON_CONFORMANT", "expectedResult": "FAIL" },
11
- { "name": "SOURCE_DEFECT", "mutation": "sourceValidity = SOURCE_DEFECT and sourceNotes is present", "expectedResult": "PASS" },
12
- { "name": "SOURCE_CONFLICT", "mutation": "sourceValidity = SOURCE_CONFLICT with a traced additional source", "expectedResult": "FAIL" },
11
+ { "name": "SOURCE_DEFECT", "mutation": "sourceValidity = SOURCE_DEFECT, sourceNotes is present, and sourceEvidence quotes the defective assertion against the observed value; sourceEvidenceStatus is REVIEWER_REQUIRED, never VERIFIED, because a single-source mismatch does not prove the source is wrong", "expectedResult": "PASS" },
12
+ { "name": "SOURCE_DEFECT without sourceEvidence", "mutation": "sourceValidity = SOURCE_DEFECT with sourceNotes but no sourceEvidence", "expectedResult": "FAIL" },
13
+ { "name": "SOURCE_CONFLICT", "mutation": "sourceValidity = SOURCE_CONFLICT with a traced additional source and sourceEvidence quoting both sources", "expectedResult": "FAIL" },
14
+ { "name": "SOURCE_CONFLICT without sourceEvidence", "mutation": "sourceValidity = SOURCE_CONFLICT with two authoritative sources but no sourceEvidence", "expectedResult": "FAIL" },
15
+ { "name": "SOURCE_CONFLICT node-versus-value", "mutation": "sourceValidity = SOURCE_CONFLICT citing only expect(list.head.value).toEqual(1) from one spec, observed value is an Element object", "expectedEvidence": "REJECTED", "expectedResult": "FAIL" },
16
+ { "name": "SOURCE_CONFLICT agreeing expectations", "mutation": "sourceValidity = SOURCE_CONFLICT with two sources expecting the same value and a failing observed value; the mismatch is implementation behaviour", "expectedEvidence": "REJECTED", "expectedResult": "FAIL" },
17
+ { "name": "SOURCE_CONFLICT two-source evidence", "mutation": "sourceValidity = SOURCE_CONFLICT with two authoritative sources quoting disagreeing expectations for the same subject list.head and a failing observed value; one quote (toBeInstanceOf) is not mechanically checkable, so sourceEvidenceStatus is REVIEWER_REQUIRED and the unresolved conflict gate stays closed", "expectedEvidence": "ACCEPTED", "expectedResult": "FAIL" },
18
+ { "name": "SOURCE_CONFLICT relabelled expected", "mutation": "sourceValidity = SOURCE_CONFLICT with two authoritative sources quoting the identical assertion expect(list.head).toEqual(1) but expected values 1 and 3; each expected must match its quoted literal, so the second entry is rejected", "expectedEvidence": "REJECTED", "expectedResult": "FAIL" },
19
+ { "name": "SOURCE_CONFLICT verified literal disagreement", "mutation": "sourceValidity = SOURCE_CONFLICT with two authoritative sources quoting expect(list.head.value).toBe(1) and expect(list.head.value).toBe('1') with matching expected values 1 and '1' and a failing observed value; sourceEvidenceStatus is VERIFIED and the unresolved conflict gate stays closed", "expectedEvidence": "ACCEPTED", "expectedResult": "FAIL" },
20
+ { "name": "SOURCE_CONFLICT unrelated assertions", "mutation": "sourceValidity = SOURCE_CONFLICT quoting expect(foo).toEqual(1) from one source and expect(bar).toEqual(3) from another; unequal expectations about different subjects", "expectedEvidence": "REJECTED", "expectedResult": "FAIL" },
13
21
  { "name": "malformed input", "mutation": "requirements is not an array", "expectedResult": "FAIL" },
14
22
  { "name": "unexpected input", "mutation": "one unexpectedInput check is FAIL", "expectedResult": "FAIL" },
15
23
  { "name": "error handling", "mutation": "one errorHandling check is SKIPPED", "expectedResult": "FAIL" },
@@ -2,6 +2,8 @@
2
2
 
3
3
  This model turns each source requirement into auditable evidence. The authoritative source remains the contract even when it appears wrong. Record a suspected documentation problem as `SOURCE_DEFECT`; record disagreement between traced sources as `SOURCE_CONFLICT`. Never reinterpret either into a passing implementation. If no authoritative source exists, use `INFERRED` and state the inference basis.
4
4
 
5
+ Before declaring either outcome, `sourceEvidence` quotes the exact conflicting assertions, names each declared source by path, and compares each `expected` value with the `observed` value from the run. A `SOURCE_CONFLICT` needs at least two distinct sources whose expected values disagree about the same `subject` — the contract target each quoted assertion constrains, written as it appears in the quote (`list.head` is not `list.head.value`). Unequal expectations about unrelated targets are not a conflict. If the quoted expectations agree and the run still fails, the mismatch is in the implementation: record `NON_CONFORMANT`, not `SOURCE_CONFLICT`. Each `expected` must match a mechanically readable literal in the quoted assertion (`toEqual`, `toStrictEqual`, `toBe`, or `assert.*` with a JSON or single-quoted literal); otherwise the claim is marked `REVIEWER_REQUIRED` and the gate does not treat it as an established disagreement. `VERIFIED` applies only to a `SOURCE_CONFLICT` whose two quoted literals disagree. A `SOURCE_DEFECT` is always `REVIEWER_REQUIRED`, because a mismatch with the observed value does not prove the source is wrong; the implementation may be the one at fault.
6
+
5
7
  ## Profile contract
6
8
 
7
9
  Every requirement declares exactly one `testProfile`: `unit` or `integration`. The selected profile defines which case classes are applicable; the common scoring and source rules do not make every test use identical evidence.
@@ -149,6 +149,18 @@
149
149
  }
150
150
  ]
151
151
  },
152
+ "sourceEvidence": {
153
+ "type": "object",
154
+ "additionalProperties": false,
155
+ "required": ["source", "assertion", "expected", "observed"],
156
+ "properties": {
157
+ "source": { "type": "string", "minLength": 1 },
158
+ "subject": { "type": "string", "minLength": 1 },
159
+ "assertion": { "type": "string", "minLength": 1 },
160
+ "expected": {},
161
+ "observed": {}
162
+ }
163
+ },
152
164
  "requirement": {
153
165
  "type": "object",
154
166
  "additionalProperties": false,
@@ -161,6 +173,7 @@
161
173
  "sourceConformance": { "enum": ["PASS", "NON_CONFORMANT"] },
162
174
  "sourceValidity": { "enum": ["VALID", "SOURCE_DEFECT", "SOURCE_CONFLICT"] },
163
175
  "sourceNotes": { "type": "string", "minLength": 1 },
176
+ "sourceEvidence": { "type": "array", "items": { "$ref": "#/$defs/sourceEvidence" } },
164
177
  "skippedTests": { "type": "array" },
165
178
  "flakyTests": { "type": "array" },
166
179
  "naDeclarations": { "type": "array" },
@@ -186,6 +199,14 @@
186
199
  }
187
200
  },
188
201
  "allOf": [
202
+ {
203
+ "if": { "required": ["sourceValidity"], "properties": { "sourceValidity": { "enum": ["SOURCE_DEFECT", "SOURCE_CONFLICT"] } } },
204
+ "then": { "required": ["sourceEvidence"], "properties": { "sourceEvidence": { "minItems": 1 } } }
205
+ },
206
+ {
207
+ "if": { "required": ["sourceValidity"], "properties": { "sourceValidity": { "const": "SOURCE_CONFLICT" } } },
208
+ "then": { "properties": { "sourceEvidence": { "items": { "required": ["subject"] } } } }
209
+ },
189
210
  {
190
211
  "if": {
191
212
  "required": ["testProfile"],
@@ -96,6 +96,8 @@
96
96
  "sourceConformance": { "enum": ["PASS", "NON_CONFORMANT", null] },
97
97
  "sourceValidity": { "enum": ["VALID", "SOURCE_DEFECT", "SOURCE_CONFLICT", null] },
98
98
  "sourceNotes": { "type": "string" },
99
+ "sourceEvidence": { "type": "array" },
100
+ "sourceEvidenceStatus": { "enum": ["VERIFIED", "REVIEWER_REQUIRED"], "description": "VERIFIED only for a SOURCE_CONFLICT whose two quoted literals disagree; SOURCE_DEFECT is always REVIEWER_REQUIRED." },
99
101
  "dimensions": { "$ref": "#/$defs/dimensions" },
100
102
  "skippedTests": { "type": "array" },
101
103
  "flakyTests": { "type": "array" },
@@ -239,6 +239,163 @@ function evaluateSource(source, errors, prefix) {
239
239
  return { source: clone(source), traced: valid ? 1 : 0, total: 1, percentage: valid ? 100 : 0 };
240
240
  }
241
241
 
242
+ // SOURCE_DEFECT and SOURCE_CONFLICT must quote the assertions they blame and
243
+ // compare each with the observed value from the run. Sources are identified by
244
+ // their declared authoritative path.
245
+ const EXPECT_ASSERTION = /^expect\((.*?)\)\s*\.\s*(?:toEqual|toStrictEqual|toBe)\s*\((.*)\)$/;
246
+ const ASSERT_ASSERTION = /^assert\s*\.\s*(?:equal|strictEqual|deepEqual|deepStrictEqual)\s*\((.*)\)$/;
247
+
248
+ function jsonLiteral(text) {
249
+ const literal = text.trim();
250
+ const single = /^'([^'"\\]*)'$/.exec(literal);
251
+ try {
252
+ return { checkable: true, value: JSON.parse(single ? `"${single[1]}"` : literal) };
253
+ } catch (error) {
254
+ return { checkable: false };
255
+ }
256
+ }
257
+
258
+ // Splits an argument list on commas at depth 0, outside string literals.
259
+ // Returns null when brackets or strings are unbalanced. Not a general parser.
260
+ function splitTopLevelArguments(text) {
261
+ const parts = [];
262
+ let depth = 0;
263
+ let quote = null;
264
+ let current = '';
265
+ for (let index = 0; index < text.length; index += 1) {
266
+ const ch = text[index];
267
+ if (quote) {
268
+ current += ch;
269
+ if (ch === '\\') {
270
+ current += text[index + 1] ?? '';
271
+ index += 1;
272
+ } else if (ch === quote) {
273
+ quote = null;
274
+ }
275
+ continue;
276
+ }
277
+ if (ch === '"' || ch === "'" || ch === '`') {
278
+ quote = ch;
279
+ } else if ('([{'.includes(ch)) {
280
+ depth += 1;
281
+ } else if (')]}'.includes(ch)) {
282
+ depth -= 1;
283
+ if (depth < 0) return null;
284
+ } else if (ch === ',' && depth === 0) {
285
+ parts.push(current);
286
+ current = '';
287
+ continue;
288
+ }
289
+ current += ch;
290
+ }
291
+ if (quote !== null || depth !== 0) return null;
292
+ parts.push(current);
293
+ return parts;
294
+ }
295
+
296
+ // The literal expected value pinned by a quoted assertion, for the recognised
297
+ // shapes only. Anything else (identifiers, constructors, prose, negated or
298
+ // resolved modifiers) is not checkable. One trailing ';' is tolerated.
299
+ function quotedExpectedValue(assertion) {
300
+ if (typeof assertion !== 'string') return { checkable: false };
301
+ const text = assertion.trim().replace(/;$/, '').trim();
302
+ const expectCall = EXPECT_ASSERTION.exec(text);
303
+ if (expectCall) return jsonLiteral(expectCall[2]);
304
+ const assertCall = ASSERT_ASSERTION.exec(text);
305
+ if (!assertCall) return { checkable: false };
306
+ // assert.equal(actual, expected[, message]): the expected value is the second argument.
307
+ const args = splitTopLevelArguments(assertCall[1]);
308
+ if (!args || (args.length !== 2 && args.length !== 3)) return { checkable: false };
309
+ return jsonLiteral(args[1]);
310
+ }
311
+
312
+ // Returns the sourceEvidence status. VERIFIED means two quoted literals from
313
+ // different sources disagree (SOURCE_CONFLICT only). A quote/observed mismatch
314
+ // alone shows the implementation differs from the source, not that the source
315
+ // is defective, so SOURCE_DEFECT is always REVIEWER_REQUIRED.
316
+ function evaluateSourceEvidence(requirement, declaredSources, errors, prefix) {
317
+ const label = requirement.sourceValidity;
318
+ const evidence = requirement.sourceEvidence;
319
+ if (!Array.isArray(evidence) || evidence.length === 0) {
320
+ errors.push(`${prefix}.sourceEvidence must quote the assertions for ${label}`);
321
+ return undefined;
322
+ }
323
+ const declaredPaths = declaredSources.filter(isObject).map(source => source.path);
324
+ const entries = [];
325
+ const checkable = new Map();
326
+ evidence.forEach((entry, index) => {
327
+ const entryPrefix = `${prefix}.sourceEvidence[${index}]`;
328
+ if (!isObject(entry)) {
329
+ errors.push(`${entryPrefix} must be an object`);
330
+ return;
331
+ }
332
+ if (!declaredPaths.includes(entry.source)) errors.push(`${entryPrefix}.source must name a declared authoritative source path`);
333
+ if (!nonEmpty(entry.assertion)) errors.push(`${entryPrefix}.assertion must quote the exact assertion text`);
334
+ if (!('expected' in entry)) errors.push(`${entryPrefix}.expected is required`);
335
+ if (!('observed' in entry)) errors.push(`${entryPrefix}.observed is required`);
336
+ if (declaredPaths.includes(entry.source) && nonEmpty(entry.assertion) && 'expected' in entry && 'observed' in entry) {
337
+ const quoted = quotedExpectedValue(entry.assertion);
338
+ if (quoted.checkable && !equal(quoted.value, entry.expected)) {
339
+ errors.push(`${entryPrefix}.expected does not match the value in the quoted assertion`);
340
+ return;
341
+ }
342
+ checkable.set(entry, quoted.checkable);
343
+ entries.push(entry);
344
+ }
345
+ });
346
+ let status;
347
+ const mismatched = entries.filter(entry => !equal(entry.expected, entry.observed));
348
+ if (mismatched.length === 0) {
349
+ errors.push(`${prefix}.sourceEvidence must compare a quoted assertion with a failing observed value before blaming the source`);
350
+ } else if (label === 'SOURCE_DEFECT') {
351
+ // No independent corroboration exists for a single source, so the reviewer decides.
352
+ status = 'REVIEWER_REQUIRED';
353
+ }
354
+ if (label === 'SOURCE_CONFLICT') {
355
+ if (new Set(entries.map(entry => entry.source)).size < 2) {
356
+ errors.push(`${prefix}.sourceEvidence must quote assertions from at least two distinct authoritative sources`);
357
+ }
358
+ // A conflict needs two sources constraining the same contract subject;
359
+ // unequal expectations about unrelated targets are not a conflict.
360
+ const subjects = new Map();
361
+ evidence.forEach((entry, index) => {
362
+ if (!entries.includes(entry)) return;
363
+ const entryPrefix = `${prefix}.sourceEvidence[${index}]`;
364
+ if (!nonEmpty(entry.subject)) {
365
+ errors.push(`${entryPrefix}.subject must name the contract target the assertion constrains`);
366
+ } else if (!assertionNamesSubject(entry.assertion, entry.subject.trim())) {
367
+ errors.push(`${entryPrefix}.subject must appear as a whole target in the quoted assertion`);
368
+ } else {
369
+ subjects.set(entry, entry.subject.trim());
370
+ }
371
+ });
372
+ const sameSubjectPairs = [];
373
+ for (const a of subjects.keys()) {
374
+ for (const b of subjects.keys()) {
375
+ if (a !== b && a.source !== b.source && subjects.get(a) === subjects.get(b)) sameSubjectPairs.push([a, b]);
376
+ }
377
+ }
378
+ const disagreeing = sameSubjectPairs.filter(([a, b]) => !equal(a.expected, b.expected));
379
+ if (subjects.size >= 2 && sameSubjectPairs.length === 0) {
380
+ errors.push(`${prefix}.sourceEvidence must quote assertions from two sources about the same subject; unequal expectations about unrelated targets are not a SOURCE_CONFLICT`);
381
+ } else if (sameSubjectPairs.length && disagreeing.length === 0) {
382
+ errors.push(`${prefix}.sourceEvidence expected values must disagree across sources; agreeing expectations with a failing observed value are NON_CONFORMANT`);
383
+ } else if (disagreeing.length) {
384
+ // Only a disagreement between two literal quotes is verified; a quote that
385
+ // cannot be read mechanically goes to the reviewer without an error.
386
+ status = disagreeing.some(([a, b]) => checkable.get(a) && checkable.get(b)) ? 'VERIFIED' : 'REVIEWER_REQUIRED';
387
+ }
388
+ }
389
+ return status;
390
+ }
391
+
392
+ // True when `subject` appears in `assertion` as a whole target: `list.head`
393
+ // matches `expect(list.head)` but not `expect(list.head.value)`.
394
+ function assertionNamesSubject(assertion, subject) {
395
+ const escaped = subject.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
396
+ return new RegExp(`(?<![\\w$.])${escaped}(?![\\w$.\\[])`).test(assertion);
397
+ }
398
+
242
399
  function scoreDimensions(dimensions) {
243
400
  let weighted = 0;
244
401
  let appliedWeight = 0;
@@ -323,6 +480,10 @@ function evaluateRequirement(requirement, index, errors) {
323
480
  if (requirement.sourceValidity === 'SOURCE_CONFLICT' && declaredSources.some(source => !isObject(source) || source.status !== 'AUTHORITATIVE')) {
324
481
  errors.push(`${prefix}.SOURCE_CONFLICT may only identify authoritative sources`);
325
482
  }
483
+ let sourceEvidenceStatus;
484
+ if (['SOURCE_DEFECT', 'SOURCE_CONFLICT'].includes(requirement.sourceValidity)) {
485
+ sourceEvidenceStatus = evaluateSourceEvidence(requirement, declaredSources, errors, prefix);
486
+ }
326
487
 
327
488
  const scoring = scoringDetails(dimensions);
328
489
  const score = scoreDimensions(dimensions);
@@ -347,6 +508,8 @@ function evaluateRequirement(requirement, index, errors) {
347
508
  sourceConformance: requirement.sourceConformance || null,
348
509
  sourceValidity: requirement.sourceValidity || null,
349
510
  ...(requirement.sourceNotes ? { sourceNotes: requirement.sourceNotes } : {}),
511
+ ...(requirement.sourceEvidence ? { sourceEvidence: clone(requirement.sourceEvidence) } : {}),
512
+ ...(sourceEvidenceStatus ? { sourceEvidenceStatus } : {}),
350
513
  dimensions,
351
514
  skippedTests,
352
515
  flakyTests: Array.isArray(requirement.flakyTests) ? clone(requirement.flakyTests) : [],
@@ -432,6 +595,7 @@ function printSummary(report) {
432
595
  console.log('Requirements:');
433
596
  for (const requirement of report.requirements) {
434
597
  console.log(`- ${requirement.requirementId} [${requirement.testProfile}]: ${requirement.result} ${requirement.score}%`);
598
+ if (requirement.sourceEvidenceStatus) console.log(`- ${requirement.requirementId} sourceEvidence: ${requirement.sourceEvidenceStatus}`);
435
599
  }
436
600
  console.log('Dimensions:');
437
601
  for (const name of DIMENSIONS) {
@@ -472,4 +636,4 @@ function runCli(argv) {
472
636
 
473
637
  if (require.main === module) process.exit(runCli(process.argv));
474
638
 
475
- module.exports = { DIMENSIONS, REQUIRED_CLASSES, VERSION, evaluateEvidence, runCli };
639
+ module.exports = { DIMENSIONS, REQUIRED_CLASSES, VERSION, evaluateEvidence, quotedExpectedValue, runCli };
@@ -4,7 +4,7 @@ description: "Reflect-first circuit breaker - after 3 failures, stop edits, rebu
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.40.0
7
+ version: 0.47.5
8
8
  ---
9
9
 
10
10
  # Zoom Out (Circuit Breaker)