harness-everything 0.31.3 → 0.33.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 (52) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/CHANGELOG.md +21 -0
  4. package/README.md +3 -2
  5. package/ci/mechanism-2az-step-type-registry.test.js +923 -0
  6. package/ci/mechanism-2ba-step-type-docs.test.js +166 -0
  7. package/ci/openai-plugin-package.test.js +1 -1
  8. package/ci/release-consistency-check.js +26 -9
  9. package/ci/release-consistency-check.test.js +30 -0
  10. package/ci/router-workflow-plan.test.js +36 -0
  11. package/code-review/SKILL.md +41 -0
  12. package/code-review/references/review-playbook.md +40 -0
  13. package/code-review/templates/finding.template.md +33 -0
  14. package/docs/openai-plugin.md +1 -1
  15. package/docs/workflows/code-review.md +58 -0
  16. package/evals/code-review/eval.yaml +26 -0
  17. package/evals/code-review/tasks/negative-grill-me.yaml +16 -0
  18. package/evals/code-review/tasks/negative-security-review.yaml +16 -0
  19. package/evals/code-review/tasks/negative-tdd.yaml +16 -0
  20. package/evals/code-review/tasks/negative-verification-loop.yaml +16 -0
  21. package/evals/code-review/tasks/positive-pr-review.yaml +16 -0
  22. package/evals/code-review/tasks/positive-revised-head-rereview.yaml +16 -0
  23. package/harness-everything/SKILL.md +1 -1
  24. package/harness-everything/references/router-workflow-plan.md +18 -0
  25. package/harness-everything/references/skill-registry.md +1 -0
  26. package/harness-everything/scripts/kernel-router.js +37 -5
  27. package/harness-everything/scripts/routing-keywords.json +7 -0
  28. package/hooks/scripts/codex-user-prompt.js +5 -2
  29. package/hooks/scripts/lib/binding-resolver.js +1 -0
  30. package/hooks/scripts/lib/step-types.json +37 -0
  31. package/hooks/scripts/lib/workflow-obligations.js +117 -14
  32. package/hooks/scripts/workflow-disposition.js +16 -9
  33. package/hooks/scripts/workflow-stop-gate.js +31 -8
  34. package/opencode-plugin/plugin.json +1 -1
  35. package/package.json +2 -2
  36. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  37. package/plugins/harness-everything/hooks/scripts/codex-user-prompt.js +5 -2
  38. package/plugins/harness-everything/hooks/scripts/lib/binding-resolver.js +1 -0
  39. package/plugins/harness-everything/hooks/scripts/lib/step-types.json +37 -0
  40. package/plugins/harness-everything/hooks/scripts/lib/workflow-obligations.js +117 -14
  41. package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +16 -9
  42. package/plugins/harness-everything/hooks/scripts/workflow-stop-gate.js +31 -8
  43. package/plugins/harness-everything/plugin.json +1 -1
  44. package/plugins/harness-everything/skills/code-review/SKILL.md +41 -0
  45. package/plugins/harness-everything/skills/code-review/references/review-playbook.md +40 -0
  46. package/plugins/harness-everything/skills/code-review/templates/finding.template.md +33 -0
  47. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  48. package/plugins/harness-everything/skills/harness-everything/references/router-workflow-plan.md +18 -0
  49. package/plugins/harness-everything/skills/harness-everything/references/skill-registry.md +1 -0
  50. package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +37 -5
  51. package/plugins/harness-everything/skills/harness-everything/scripts/routing-keywords.json +7 -0
  52. package/sbom.cdx.json +7 -7
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const fs = require('fs');
3
4
  const path = require('path');
4
5
  const { atomicWriteJson, readJson } = require('./fable-contracts');
5
6
  const { resolveBinding, PACKAGED_BINDING_PATHS: BINDING_PATHS } = require('./binding-resolver');
@@ -8,13 +9,102 @@ const OBLIGATION_SCHEMA_VERSION = 1;
8
9
  const OBLIGATION_FILE = 'workflow-obligations.json';
9
10
  const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
10
11
  const ALLOWED_ESCAPE_REASONS = new Set(['workflow-uncovered-scope', 'host-capability-unavailable']);
11
- const STEP_TYPES = new Set(['review', 'behavior-change', 'verification', 'commit']);
12
- const REQUIRED_BINDINGS_BY_STEP = {
13
- review: [],
14
- 'behavior-change': ['tdd'],
15
- verification: ['verification-loop'],
16
- commit: ['git-commit'],
17
- };
12
+ const STEP_TYPE_REGISTRY_FILE = path.join(__dirname, 'step-types.json');
13
+
14
+ function validateStepTypeRegistry(registry) {
15
+ const errors = [];
16
+ if (!Array.isArray(registry) || registry.length === 0) {
17
+ throw new Error('invalid step-type registry: expected a non-empty array');
18
+ }
19
+ const idCounts = new Map();
20
+ const bindingFields = ['requiredBindings', 'optionalBindings', 'forbiddenBindings'];
21
+ for (const entry of registry) {
22
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
23
+ errors.push('entry must be an object');
24
+ continue;
25
+ }
26
+ const id = typeof entry.id === 'string' ? entry.id : '';
27
+ const validId = SAFE_ID.test(id) && id === id.trim();
28
+ const label = validId ? `step type ${id}` : 'step type with invalid id';
29
+ if (!validId) errors.push(`${label} must declare a stable id`);
30
+ else idCounts.set(id, (idCounts.get(id) || 0) + 1);
31
+ if (typeof entry.evidenceHint !== 'string' || !entry.evidenceHint.trim()) {
32
+ errors.push(`${label} evidenceHint must be a non-empty string`);
33
+ }
34
+ const valuesByField = new Map();
35
+ for (const field of bindingFields) {
36
+ if (!Array.isArray(entry[field])) {
37
+ errors.push(`${label} ${field} must be an array`);
38
+ valuesByField.set(field, []);
39
+ continue;
40
+ }
41
+ const values = [];
42
+ for (const binding of entry[field]) {
43
+ if (typeof binding !== 'string' || !SAFE_ID.test(binding) || binding !== binding.trim()) {
44
+ errors.push(`${label} ${field} entries must be stable binding ids`);
45
+ continue;
46
+ }
47
+ values.push(binding);
48
+ }
49
+ const unique = [...new Set(values)].sort();
50
+ if (unique.length !== values.length) errors.push(`${label} ${field} contains duplicate binding ids`);
51
+ valuesByField.set(field, unique);
52
+ }
53
+ for (const [left, right] of [
54
+ ['requiredBindings', 'optionalBindings'],
55
+ ['requiredBindings', 'forbiddenBindings'],
56
+ ['optionalBindings', 'forbiddenBindings'],
57
+ ]) {
58
+ const rightValues = new Set(valuesByField.get(right) || []);
59
+ for (const binding of valuesByField.get(left) || []) {
60
+ if (rightValues.has(binding)) errors.push(`${label} has conflicting ${left} and ${right} default for ${binding}`);
61
+ }
62
+ }
63
+ }
64
+ for (const id of [...idCounts.keys()].sort()) {
65
+ if (idCounts.get(id) > 1) errors.push(`duplicate step type id ${id}`);
66
+ }
67
+ if (errors.length) throw new Error('invalid step-type registry: ' + [...new Set(errors)].sort().join('; '));
68
+ return registry.map(entry => ({
69
+ id: entry.id,
70
+ requiredBindings: [...entry.requiredBindings],
71
+ optionalBindings: [...entry.optionalBindings],
72
+ forbiddenBindings: [...entry.forbiddenBindings],
73
+ evidenceHint: entry.evidenceHint.trim(),
74
+ })).sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0);
75
+ }
76
+
77
+ const STEP_TYPE_REGISTRY_UNAVAILABLE = 'STEP_TYPE_REGISTRY_UNAVAILABLE';
78
+
79
+ function readStepTypeRegistry(registryFile = STEP_TYPE_REGISTRY_FILE) {
80
+ const resolvedRegistryFile = path.resolve(registryFile);
81
+ try {
82
+ const parsed = JSON.parse(fs.readFileSync(resolvedRegistryFile, 'utf8'));
83
+ return validateStepTypeRegistry(parsed);
84
+ } catch (error) {
85
+ const unavailable = new Error(
86
+ `cannot load step-type registry ${resolvedRegistryFile}: ${error.message}. ` +
87
+ `Repair action: reinstall or update the Harness plugin so this file is restored; in a Harness source checkout, restore it and run npm run plugin:sync.`,
88
+ );
89
+ unavailable.code = STEP_TYPE_REGISTRY_UNAVAILABLE;
90
+ throw unavailable;
91
+ }
92
+ }
93
+
94
+ // Loaded on first typed use only, so registry-independent planning and execution never read it.
95
+ let stepTypeRegistry = null;
96
+ function loadStepTypeRegistry() {
97
+ if (!stepTypeRegistry) stepTypeRegistry = readStepTypeRegistry();
98
+ return stepTypeRegistry;
99
+ }
100
+
101
+ function stepTypeIds() {
102
+ return loadStepTypeRegistry().map(entry => entry.id).sort();
103
+ }
104
+
105
+ function stepTypeIndex() {
106
+ return new Map(loadStepTypeRegistry().map(entry => [entry.id, entry]));
107
+ }
18
108
 
19
109
  function obligationPath(sessionDir) {
20
110
  return path.join(sessionDir, OBLIGATION_FILE);
@@ -125,6 +215,14 @@ function initializePlanningContract(context, plan) {
125
215
  return set;
126
216
  }
127
217
 
218
+ function typedStepType(rawStepType) {
219
+ const registered = stepTypeIndex();
220
+ if (!registered.has(rawStepType)) {
221
+ throw new Error(`unsupported requirement stepType: ${rawStepType}; registered step types: ${stepTypeIds().join(', ')}`);
222
+ }
223
+ return registered.get(rawStepType);
224
+ }
225
+
128
226
  function parseRequirements(requirementsJson, workspaceRoot) {
129
227
  let parsed;
130
228
  try { parsed = JSON.parse(String(requirementsJson || '')); }
@@ -145,7 +243,7 @@ function parseRequirements(requirementsJson, workspaceRoot) {
145
243
  ? [...new Set(item.suggestedSkills.filter(value => typeof value === 'string' && value.trim()).map(value => value.trim()))]
146
244
  : [];
147
245
  const rawStepType = item.stepType === undefined ? '' : String(item.stepType).trim();
148
- if (rawStepType && !STEP_TYPES.has(rawStepType)) throw new Error(`unsupported requirement stepType: ${rawStepType}`);
246
+ const stepType = rawStepType ? typedStepType(rawStepType) : null;
149
247
  for (const field of ['requiredBindings', 'optionalBindings']) {
150
248
  if (Object.prototype.hasOwnProperty.call(item, field) && !Array.isArray(item[field])) {
151
249
  throw new Error(`${id}.${field} must be an array`);
@@ -155,17 +253,18 @@ function parseRequirements(requirementsJson, workspaceRoot) {
155
253
  if (!rawStepType) return requirement;
156
254
 
157
255
  const requiredBindings = normalizeBindingDeclarations([
158
- ...(REQUIRED_BINDINGS_BY_STEP[rawStepType] || []),
256
+ ...(stepType.requiredBindings || []),
159
257
  ...(item.requiredBindings || []),
160
258
  ]);
161
259
  const requiredIds = new Set(requiredBindings.map(binding => binding.id));
162
260
  const optionalBindings = normalizeBindingDeclarations([
261
+ ...(stepType.optionalBindings || []),
163
262
  ...(item.optionalBindings || []),
164
263
  ...suggestedSkills,
165
264
  ]).filter(binding => !requiredIds.has(binding.id));
166
- if (rawStepType === 'review' && [...requiredBindings, ...optionalBindings].some(binding => binding.id === 'tdd')) {
167
- throw new Error('review steps cannot bind tdd');
168
- }
265
+ const forbiddenBinding = [...requiredBindings, ...optionalBindings]
266
+ .find(binding => stepType.forbiddenBindings.includes(binding.id));
267
+ if (forbiddenBinding) throw new Error(`${rawStepType} steps cannot bind ${forbiddenBinding.id}`);
169
268
  requirement.stepType = rawStepType;
170
269
  requirement.status = 'pending';
171
270
  requirement.evidence = null;
@@ -268,7 +367,7 @@ function recordPlanning(context, requirementsJson, selectedPlan, requestedStrate
268
367
  }
269
368
  const isFable = String(selectedPlan.strategy || '').startsWith('fable-');
270
369
  if (!isFable && !hasTypedSteps) {
271
- throw new Error('selected single-agent workflows require ordered stepType values: review, behavior-change, verification, or commit');
370
+ throw new Error(`selected single-agent workflows require ordered stepType values: ${stepTypeIds().join(', ')}`);
272
371
  }
273
372
  if (!isFable && !requirements.some(item => item.stepType === 'verification')) {
274
373
  throw new Error('step-bound workflows require an explicit verification step');
@@ -360,7 +459,8 @@ function validateObligationSet(context, set, plan = context.workflow?.pendingPla
360
459
  if (!['pending', 'pass', 'escaped', 'blocked'].includes(obligation.status)) return 'obligation-status-invalid';
361
460
  }
362
461
  if (set.stepBindingsEnabled === true) {
363
- if (set.requirements.some(item => !item || !STEP_TYPES.has(item.stepType) ||
462
+ const registered = stepTypeIndex();
463
+ if (set.requirements.some(item => !item || !registered.has(item.stepType) ||
364
464
  !['pending', 'pass', 'blocked'].includes(item.status) || !Array.isArray(item.requiredBindings) ||
365
465
  !Array.isArray(item.optionalBindings))) return 'steps-invalid';
366
466
  for (const step of set.requirements) {
@@ -585,6 +685,9 @@ function unresolvedObligations(context) {
585
685
  module.exports = {
586
686
  OBLIGATION_FILE,
587
687
  ALLOWED_ESCAPE_REASONS,
688
+ loadStepTypeRegistry,
689
+ stepTypeIds,
690
+ validateStepTypeRegistry,
588
691
  obligationPath,
589
692
  needsPlanningContract,
590
693
  needsGenericExecutionObligations,
@@ -7,15 +7,20 @@ const { loadWorkflow, saveWorkflow, matchingRun, OPEN_STATES, WORKFLOW_CONTROLLE
7
7
  const { getWorkspaceRoot, readCurrentSession } = require('./lib/harness-state');
8
8
  const { atomicWriteJson, readJson } = require('./lib/fable-contracts');
9
9
  const { resolveBinding } = require('./lib/binding-resolver');
10
- const {
11
- planningUnresolved,
12
- recordPlanning,
13
- materializeExecutionObligations,
14
- updateObligation,
15
- updateBinding,
16
- updateStep,
17
- activeStepSnapshot,
18
- } = require('./lib/workflow-obligations');
10
+
11
+ let obligationApi;
12
+ function workflowObligations() {
13
+ if (!obligationApi) obligationApi = require('./lib/workflow-obligations');
14
+ return obligationApi;
15
+ }
16
+
17
+ const planningUnresolved = (...args) => workflowObligations().planningUnresolved(...args);
18
+ const recordPlanning = (...args) => workflowObligations().recordPlanning(...args);
19
+ const materializeExecutionObligations = (...args) => workflowObligations().materializeExecutionObligations(...args);
20
+ const updateObligation = (...args) => workflowObligations().updateObligation(...args);
21
+ const updateBinding = (...args) => workflowObligations().updateBinding(...args);
22
+ const updateStep = (...args) => workflowObligations().updateStep(...args);
23
+ const activeStepSnapshot = (...args) => workflowObligations().activeStepSnapshot(...args);
19
24
 
20
25
  const ALLOWED_ESCAPE_REASONS = new Set(['workflow-uncovered-scope', 'host-capability-unavailable']);
21
26
 
@@ -149,6 +154,8 @@ function activatePlan(workflow, plan) {
149
154
  }
150
155
 
151
156
  function main() {
157
+ // The step-type registry loads lazily through the wrappers above, only for commands that
158
+ // consume typed obligations; the top-level handler turns a load failure into one semantic line.
152
159
  if (process.argv[2] === '--help' || process.argv[2] === '-h') {
153
160
  process.stdout.write(usage() + '\n');
154
161
  return;
@@ -3,35 +3,58 @@
3
3
  const path = require('path');
4
4
  const { readJson } = require('./lib/fable-contracts');
5
5
  const { loadWorkflow, saveWorkflow, matchingRun, unresolvedStages, readHookInput } = require('./lib/workflow-runtime');
6
- const { planningUnresolved, unresolvedObligations } = require('./lib/workflow-obligations');
6
+
7
+ const REGISTRY_UNAVAILABLE = 'step-type-registry-unavailable';
8
+ const STEP_TYPE_REGISTRY_UNAVAILABLE = 'STEP_TYPE_REGISTRY_UNAVAILABLE';
7
9
 
8
10
  function warn(message) { console.error('[Workflow Reminder] ' + message); }
9
11
 
10
12
  function decide(payload) {
11
13
  if (!payload) return;
12
14
  try {
15
+ const { planningUnresolved, unresolvedObligations } = require('./lib/workflow-obligations');
13
16
  const context = loadWorkflow(payload);
14
17
  const { workflow, sessionDir } = context;
15
18
  if (!workflow || workflow.state === 'deferred') return;
16
19
  const handoff = readJson(path.join(sessionDir, 'handoff-state.json'));
17
- let unresolved = planningUnresolved(context);
20
+ const unresolved = [];
21
+ const diagnostics = [];
22
+ let requiredFault = false;
23
+
24
+ // Only a validation that actually reads the registry can fail here. It records the
25
+ // semantic diagnostic and the unresolved token itself; other checks keep running.
26
+ const required = check => {
27
+ try {
28
+ unresolved.push(...check());
29
+ } catch (error) {
30
+ if (error.code !== STEP_TYPE_REGISTRY_UNAVAILABLE) throw error;
31
+ diagnostics.push(error.message);
32
+ unresolved.push(REGISTRY_UNAVAILABLE);
33
+ requiredFault = true;
34
+ }
35
+ };
18
36
 
37
+ required(() => planningUnresolved(context));
19
38
  if (String(workflow.strategy || '').startsWith('fable-')) {
20
39
  const match = matchingRun(context);
21
40
  unresolved.push(...(match ? unresolvedStages(match, workflow, handoff?.lastEditAt || 0) : ['correlated-run-missing']));
22
41
  } else {
23
- unresolved.push(...unresolvedObligations(context));
42
+ required(() => unresolvedObligations(context));
24
43
  const lastEdit = Math.max(workflow.lastMutationAt || 0, handoff?.lastEditAt || 0);
25
44
  if (lastEdit && (handoff?.lastVerifyAt || 0) < lastEdit) unresolved.push('verification-after-edit-missing');
26
45
  }
27
46
 
28
- unresolved = [...new Set(unresolved)];
47
+ const pending = [...new Set(unresolved)];
29
48
  const wasBlocked = workflow.state === 'blocked';
30
49
  if (wasBlocked) warn('Workflow is marked BLOCKED; report or revisit the recorded blocker, but Harness will not trap the session.');
31
- if (unresolved.length) {
32
- workflow.unresolved = unresolved;
33
- saveWorkflow(context);
34
- warn('Unresolved workflow evidence: ' + unresolved.join(', ') + '. Resolve/verify before claiming completion.');
50
+ [...new Set(diagnostics)].forEach(message => warn(message));
51
+ if (pending.length) {
52
+ // A failed required validation leaves the workflow file as recorded; it is never marked satisfied.
53
+ if (!requiredFault) {
54
+ workflow.unresolved = pending;
55
+ saveWorkflow(context);
56
+ }
57
+ warn('Unresolved workflow evidence: ' + pending.join(', ') + '. Resolve/verify before claiming completion.');
35
58
  return;
36
59
  }
37
60
  if (wasBlocked) return;
@@ -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.31.3",
4
+ "version": "0.33.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",
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: code-review
3
+ description: "Independently review a PR, diff or finished implementation for correctness, contract and negative-path defects, with source-anchored findings labelled executed, source-traced or inconclusive; read-only by default. USE FOR: \"review this PR before merge\", \"re-review the fix at the new HEAD\". DO NOT USE FOR: \"security threat modeling\", \"implementing or fixing the code\"."
4
+ license: Apache-2.0
5
+ metadata:
6
+ author: Miya Daniel
7
+ version: 0.33.0
8
+ ---
9
+
10
+ # Code Review
11
+
12
+ Independently review completed changes for correctness, contract violations, and negative-path defects. Default to read-only.
13
+
14
+ ## 📋 Skill Contract
15
+
16
+ | Component | Specification |
17
+ | :--- | :--- |
18
+ | **Trigger / Input** | Requested review of a PR, diff, implementation, or revised HEAD. |
19
+ | **Expected Output** | Source-anchored findings and an evidence-qualified summary. |
20
+ | **State Mutations** | None unless the user explicitly authorizes an action. |
21
+ | **Enforcement Gate** | Apply the checklist in `<this-skill-dir>/references/review-playbook.md`. |
22
+
23
+ ## USE FOR:
24
+ - Review a PR / diff / finished implementation before merge
25
+ - Re-review a revised HEAD: prior-finding disposition + new risks
26
+
27
+ ## DO NOT USE FOR:
28
+ - Security-only threat modeling / secrets / OWASP (use `security-review`)
29
+ - Design or plan stress-testing (use `grill-me`); running gates (use `verification-loop`); writing the fix (use `tdd`)
30
+
31
+ ## Workflow
32
+
33
+ 1. Pin repository, contract, base/head, changed scope, and CI state; inspect relevant callers and consumers.
34
+ 2. Independently trace behavior and test applicable counterexamples. Separate severity from safety conditions.
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.
37
+ 5. Keep review read-only unless explicitly authorized. Do not claim “bug-free.”
38
+
39
+ In the interim, bind explicitly on an existing review step: `{"stepType":"review","optionalBindings":["code-review"]}`.
40
+
41
+ Deep dive: <this-skill-dir>/references/review-playbook.md
@@ -0,0 +1,40 @@
1
+ # Review Playbook
2
+
3
+ Use this checklist for the applicable risks; do not force irrelevant categories.
4
+
5
+ ## Establish scope and evidence
6
+
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
+ - [ ] Make an independent pass. Treat author explanations and tests as leads, not proof; derive expected behavior from contracts and source.
9
+ - [ ] 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
+ - [ ] 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.
11
+ - [ ] Reproduce a candidate finding against the PR base before classifying it as new. Pre-existing defects are follow-ups, not new blockers.
12
+
13
+ ## Try applicable counterexamples
14
+
15
+ - [ ] Missing, null, malformed input; old persisted state; base-to-candidate transitions; tenant/scope boundaries.
16
+ - [ ] Missing or broken dependencies: enumerate every entry point, including help, recovery, and stop paths; inspect fallback behavior.
17
+ - [ ] OS/shell differences; concurrency, crash, retry; user paths and symlinks; API/migration compatibility; downstream or generated views.
18
+ - [ ] Follow each important diagnostic/fact through every consumer and ancestor. Check whether required facts are computed before derived authority, indexes, or output.
19
+ - [ ] Compare new rules with previously accepted rules and controls. Separate display severity from safety prerequisites.
20
+
21
+ ## Check tests and output
22
+
23
+ - [ ] Ask whether each test still passes if the behavior is disabled, bypassed, or broken (mutation thought experiment). Confirm the test is included in CI.
24
+ - [ ] When output text changes, compare the intended changed lines with literal templates; do not exclude changed lines from assertions.
25
+ - [ ] Dedupe findings. Use P1 for severe merge blockers, P2 for meaningful correctness/contract defects, P3 for low-risk issues; avoid speculation.
26
+ - [ ] 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.
27
+
28
+ ## Re-review and conclude
29
+
30
+ - [ ] Pin the new HEAD. Disposition every prior finding separately: resolved, still open, superseded, or inconclusive. Then inspect fix-induced side effects and new changes.
31
+ - [ ] Stay read-only unless the user/workflow explicitly authorizes action. Do not edit, comment, label, or merge on review authority alone.
32
+ - [ ] 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
+
34
+ ## Per-finding format
35
+
36
+ `[P1/P2/P3] Title` — `path:line @ <HEAD SHA>`
37
+
38
+ **Trigger** → **behavior** → **contract violation / impact**. **Evidence:** executed / source-traced / inconclusive. **Minimal correction:** … **Regression:** negative case …; positive control … **Disposition:** blocker / follow-up / owner decision.
39
+
40
+ Use `<this-skill-dir>/templates/finding.template.md` for the complete findings and summary form.
@@ -0,0 +1,33 @@
1
+ # Code Review — <PR or change>
2
+
3
+ ## Scope and evidence
4
+
5
+ - Repository / PR:
6
+ - Base SHA → reviewed HEAD SHA:
7
+ - Requirement / PRD / ADR:
8
+ - Changed files and relevant callers/consumers:
9
+ - CI state and commands observed:
10
+ - Counterexample categories covered:
11
+ - Risks not examined / evidence limits:
12
+
13
+ ## Findings
14
+
15
+ ### [P1/P2/P3] <Short, specific title>
16
+
17
+ - **Location:** `<path>:<line or diff anchor> @ <HEAD SHA>`
18
+ - **Trigger:** <minimal input, state transition, or interleaving>
19
+ - **Behavior → impact:** <observed or source-traced behavior and violated contract>
20
+ - **Evidence tier:** <executed/reproduced | source-traced | inconclusive; include exact command/result or preconditions>
21
+ - **Minimal correction:** <smallest feasible fix>
22
+ - **Regression:** Negative case: <case that fails before fix>; positive control: <valid behavior that remains valid>
23
+ - **Disposition:** <blocker | follow-up | owner decision; for re-review, resolved | still open | superseded | inconclusive>
24
+
25
+ <!-- Duplicate this finding section as needed. Remove headings for which there are no findings. -->
26
+
27
+ ## Summary
28
+
29
+ - **Scope / reviewed SHA:**
30
+ - **CI evidence:**
31
+ - **Covered / unexamined risks:**
32
+ - **Findings:** <fixed / open / new counts on re-review>
33
+ - **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.31.0
7
+ version: 0.33.0
8
8
  ---
9
9
 
10
10
  # Harness Everything
@@ -118,6 +118,24 @@ Examples:
118
118
 
119
119
  There is no universal `TODO → TDD → verification` pipeline.
120
120
 
121
+ ## Step types
122
+
123
+ The allowed step types are the entries of `hooks/scripts/lib/step-types.json`. The list below is rendered from that registry and checked against it by `ci/mechanism-2ba-step-type-docs.test.js`; examples elsewhere may name a subset.
124
+
125
+ <!-- step-types:start -->
126
+ - `behavior-change` — required bindings: `tdd` — forbidden bindings: none
127
+ - `commit` — required bindings: `git-commit` — forbidden bindings: none
128
+ - `doc-change` — required bindings: none — forbidden bindings: none
129
+ - `review` — required bindings: none — forbidden bindings: `tdd`
130
+ - `verification` — required bindings: `verification-loop` — forbidden bindings: none
131
+ <!-- step-types:end -->
132
+
133
+ `doc-change` is declared-only: a plan author chooses it explicitly, and router classification, keyword routing and System One never infer or insert it. It has no default bindings, and an explicitly declared `tdd` binding stays legal. Mixed documentation and code work is written as ordered `doc-change` and `behavior-change` steps, and one shared verification step may verify both.
134
+
135
+ Old runtimes reject a persisted non-Fable `doc-change` plan visibly at the controller and Stop boundaries (`planning:steps-invalid`). The old retained router does not show that rejection: it shows no active step. Persisted bytes are not renamed or reset. Inert Fable planning metadata is not rejected by old runtimes, because step bindings are disabled there.
136
+
137
+ Registry revisions: binding defaults and required/forbidden binding policy are materialized when a plan is recorded and are not reapplied on ordinary load. Existing typed plans must still reference currently registered step types; removing a type can invalidate a plan rather than migrate or waive its stored bindings. A policy fingerprint or migration path is future work and is not implemented today.
138
+
121
139
  ## Binding and disclosure rule
122
140
 
123
141
  Each ordered requirement or Fable stage may declare `requiredBindings` and `optionalBindings` by stable id and concrete repository-relative path. Only the active requirement or dependency-ready stage exposes its paths. Required bindings must resolve before that step/stage passes; unknown required bindings remain visible and unresolved. Future-step paths and keyword-matched documents are never preloaded.
@@ -18,6 +18,7 @@ Every skill in this repository is reachable from this router. If a task matches
18
18
  | `grill-me` | Tier 2 | User asks to be grilled on a plan (pure Q&A). |
19
19
  | `improve-codebase-architecture` | Tier 2 | Deep architectural analysis / refactor planning. |
20
20
  | `eval-harness` | Tier 2 | Scoring, stress testing, benchmark comparison. |
21
+ | `code-review` | Tier 2 | Explicit review requests of a PR/diff/finished implementation: correctness, contract, and negative-path findings with evidence tiers, read-only by default — not threat modeling (`security-review`), plan interrogation (`grill-me`), or gate running (`verification-loop`). |
21
22
  | `verification-loop` | Tier 2/3 gate | Before declaring done or creating a PR. |
22
23
  | `security-review` | Tier 2/3 gate | Changes touching auth, input handling, secrets, network boundaries. |
23
24
  | `fable-mode` | Tier 3 | v3 staged planning, named-agent delegation, model selection, failable gates, and cold review. |
@@ -33,6 +33,14 @@ function issueMemoryCapability(workflow) {
33
33
  return token;
34
34
  }
35
35
 
36
+ function needsStepTypeRegistry(plan) {
37
+ return Boolean(
38
+ plan && plan.strategySelection === 'selected' &&
39
+ ['tier2', 'tier3'].includes(plan.tier) && plan.strategy &&
40
+ !String(plan.strategy).startsWith('fable-')
41
+ );
42
+ }
43
+
36
44
  // Host-generated background-task notifications arrive as UserPromptSubmit
37
45
  // hook payloads but are not user input (#168). Routing them like prompts
38
46
  // replaces the workflow contract mid-turn (e.g. deferred -> a new Tier-2).
@@ -44,11 +52,28 @@ function isHostNotificationPrompt(prompt) {
44
52
  return /^<task-notification[\s>][\s\S]*<\/task-notification>\s*$/i.test(text);
45
53
  }
46
54
 
55
+ function stepTypeIdsFor(hooksRoot) {
56
+ try {
57
+ return require(path.join(hooksRoot, 'lib/workflow-obligations')).stepTypeIds();
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+
47
63
  function persistWorkflow(contract, payload, prompt) {
48
64
  const plan = contract && contract.workflowPlan;
49
65
  if (!payload || !(payload.session_id || payload.sessionId)) return null;
50
66
  const { runtime, hooksRoot } = loadRuntime();
51
67
  const context = runtime.loadWorkflow(payload);
68
+ const hasRetainedWorkflow = Boolean(context.workflow && runtime.OPEN_STATES.has(context.workflow.state));
69
+ const activePlan = hasRetainedWorkflow ? context.workflow.workflowPlan : plan;
70
+ if (needsStepTypeRegistry(activePlan)) {
71
+ try { require(path.join(hooksRoot, 'lib/workflow-obligations')).loadStepTypeRegistry(); }
72
+ catch (error) {
73
+ if (hasRetainedWorkflow) return { ...context, hooksRoot, retained: true, registryError: error };
74
+ throw error;
75
+ }
76
+ }
52
77
  const { writeCurrentSession } = require(path.join(hooksRoot, 'lib/harness-state'));
53
78
  writeCurrentSession(context.root, context.sessionId);
54
79
  if (isHostNotificationPrompt(prompt)) {
@@ -121,8 +146,8 @@ function activeExecutionFor(persisted, plan) {
121
146
  const obligations = require(path.join(persisted.hooksRoot, 'lib/workflow-obligations'));
122
147
  const loaded = obligations.loadObligationSet(persisted, plan);
123
148
  return { activeStep: loaded.set ? obligations.activeStepSnapshot(loaded.set) : null };
124
- } catch (_) {
125
- return null;
149
+ } catch (error) {
150
+ return { error: error && error.message ? error.message : String(error) };
126
151
  }
127
152
  }
128
153
 
@@ -163,10 +188,15 @@ function run(raw) {
163
188
  if (persisted?.retained && persisted.workflow) {
164
189
  if (persisted.hostNotification) console.log('\n=> Host notification: routing unchanged; keeping the active execution contract.');
165
190
  else console.log('\n=> Retaining unresolved workflow; showing its compact active contract.');
166
- const activeExecution = activeExecutionFor(persisted, plan) || {};
191
+ const activeExecution = persisted.registryError
192
+ ? { error: persisted.registryError.message || String(persisted.registryError) }
193
+ : activeExecutionFor(persisted, plan) || {};
167
194
  const activeStep = activeExecution.activeStep || null;
168
195
  const activeStages = activeExecution.activeStages || [];
169
196
  core.printRetainedWorkflow(plan, persisted.workflow, activeStep, activeStages);
197
+ if (activeExecution.error) {
198
+ console.log(' - [Workflow Reminder] Active-step context unavailable: ' + activeExecution.error);
199
+ }
170
200
  if (activeStep) {
171
201
  const controller = path.join(persisted.hooksRoot, 'workflow-disposition.js');
172
202
  console.log(' - Resolve only bindings for this active step, then pass this step with evidence.');
@@ -205,8 +235,10 @@ function run(raw) {
205
235
  if (plan.strategySelection === 'selected' && ['tier2', 'tier3'].includes(plan.tier)) {
206
236
  console.log(' - Planning contract: ' + path.join(persisted.sessionDir, 'workflow-obligations.json'));
207
237
  console.log(' - Requirements first: decompose intent into requirement fragments, then confirm the smallest sufficient workflow.');
208
- console.log(' - Record ordered step types (review, behavior-change, verification, commit) and their bindings before execution.');
209
- console.log(' - Record plan: node "' + controller + '" plan --session-id "' + persisted.sessionId + '" --requirements-json "[{\\"id\\":\\"req-1\\",\\"stepType\\":\\"behavior-change\\",\\"summary\\":\\"...\\",\\"acceptance\\":\\"...\\"},{\\"id\\":\\"req-verify\\",\\"stepType\\":\\"verification\\",\\"summary\\":\\"...\\",\\"acceptance\\":\\"...\\"}]" --strategy "' + plan.strategy + '" --evidence "<why this workflow fits>"');
238
+ const stepTypeIds = stepTypeIdsFor(persisted.hooksRoot);
239
+ console.log(' - Record ordered step types' + (stepTypeIds ? ' (' + stepTypeIds.join(', ') + ')' : '') + ' and their bindings before execution.');
240
+ console.log(' - Replace <step-type-id> with a registered type you choose explicitly; mixed docs+code work uses separate doc-change and behavior-change steps; keep the verification step.');
241
+ console.log(' - Record plan: node "' + controller + '" plan --session-id "' + persisted.sessionId + '" --requirements-json "[{\\"id\\":\\"req-1\\",\\"stepType\\":\\"<step-type-id>\\",\\"summary\\":\\"...\\",\\"acceptance\\":\\"...\\"},{\\"id\\":\\"req-verify\\",\\"stepType\\":\\"verification\\",\\"summary\\":\\"...\\",\\"acceptance\\":\\"...\\"}]" --strategy "' + plan.strategy + '" --evidence "<why this workflow fits>"');
210
242
  }
211
243
  console.log(' - Enter selected workflow after planning: node "' + controller + '" start --session-id "' + persisted.sessionId + '"');
212
244
  console.log(' - Escape one declared stage: node "' + controller + '" escape --session-id "' + persisted.sessionId + '" --stage-id "<id>" --reason-code workflow-uncovered-scope --scope "<uncovered scope>" --evidence "<evidence>"');
@@ -611,6 +611,13 @@
611
611
  "bindings": [
612
612
  "zoom-out"
613
613
  ]
614
+ },
615
+ {
616
+ "id": "review",
617
+ "bindings": [
618
+ "code-review"
619
+ ],
620
+ "regex": "\\b(?:code[- ]review|pr review)\\b|\\b(?:re-)?review\\s+(?:(?:(?:this|the|my)\\s+)?(?:pr\\b|pull request\\b|diff\\b|(?:finished\\s+)?implementation\\b(?!\\s+plan\\b))|the\\s+(?:fix\\b|new\\s+head\\b))|程式碼審查|代码审查"
614
621
  }
615
622
  ]
616
623
  }
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:8fcaff79-dd3a-5e6d-8ae9-f34d935267de",
5
+ "serialNumber": "urn:uuid:52f0d0e2-1bad-500d-83b5-0965e29f6e5d",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-10-16T05:41:55.836Z",
8
+ "timestamp": "2026-10-17T15:47:14.418Z",
9
9
  "tools": [
10
10
  {
11
11
  "vendor": "Harness Everything",
12
12
  "name": "generate-sbom.js",
13
- "version": "0.31.3"
13
+ "version": "0.33.0"
14
14
  }
15
15
  ],
16
16
  "component": {
17
17
  "type": "application",
18
- "bom-ref": "pkg:npm/harness-everything@0.31.3",
18
+ "bom-ref": "pkg:npm/harness-everything@0.33.0",
19
19
  "name": "harness-everything",
20
- "version": "0.31.3",
21
- "purl": "pkg:npm/harness-everything@0.31.3"
20
+ "version": "0.33.0",
21
+ "purl": "pkg:npm/harness-everything@0.33.0"
22
22
  }
23
23
  },
24
24
  "components": [
@@ -1999,7 +1999,7 @@
1999
1999
  ]
2000
2000
  },
2001
2001
  {
2002
- "ref": "pkg:npm/harness-everything@0.31.3",
2002
+ "ref": "pkg:npm/harness-everything@0.33.0",
2003
2003
  "dependsOn": [
2004
2004
  "pkg:npm/%40commitlint/cli@19.8.1",
2005
2005
  "pkg:npm/%40commitlint/config-conventional@19.8.1",