harness-everything 0.30.10 → 0.31.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 (134) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +1 -1
  4. package/CHANGELOG.md +24 -4
  5. package/README.md +8 -9
  6. package/VERIFICATION.md +1 -1
  7. package/benchmarks/results/live-host/claude-2026-10-08-issue-297/README.md +27 -0
  8. package/benchmarks/results/pr-303-review-2026-10-13/README.md +81 -0
  9. package/benchmarks/results/pr-303-review-2026-10-13/ci/run.json +1330 -0
  10. package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/mechanism-35-clean-env.log +1 -0
  11. package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/mechanism-35-inherited-flag.log +34 -0
  12. package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate-captured.json +46 -0
  13. package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate-diagnosis.md +13 -0
  14. package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate.json +46 -0
  15. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/summary.json +57 -0
  16. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-collision.log +8 -0
  17. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-consistency.log +624 -0
  18. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-docs-capabilities.log +131 -0
  19. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-platform-compatibility.log +9 -0
  20. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-plugin-openai.log +6 -0
  21. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-plugin-submission.log +5 -0
  22. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-references.log +6 -0
  23. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-release.log +73 -0
  24. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-repo-contract.log +5 -0
  25. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-routing-invariants.log +785 -0
  26. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-routing-skills.log +44 -0
  27. package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/summary.json +12 -0
  28. package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/test-consistency.log +624 -0
  29. package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/test-release.log +73 -0
  30. package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/summary.json +79 -0
  31. package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test-mechanism.log +1737 -0
  32. package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test-mutations.log +16 -0
  33. package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test.log +3297 -0
  34. package/benchmarks/results/pr-303-review-2026-10-13/manifest.json +319 -0
  35. package/benchmarks/results/pr-303-review-2026-10-13/regression/binding-resolution-green.log +36 -0
  36. package/benchmarks/results/pr-303-review-2026-10-13/regression/determinism.json +174 -0
  37. package/benchmarks/results/pr-303-review-2026-10-13/regression/quality-evidence.json +314 -0
  38. package/benchmarks/results/pr-303-review-2026-10-13/regression/quality-report.json +638 -0
  39. package/benchmarks/results/pr-303-review-2026-10-13/regression/red.log +240 -0
  40. package/benchmarks/results/pr-303-review-2026-10-13/regression/untyped-fable-probe.json +22 -0
  41. package/benchmarks/results/pr-303-review-2026-10-13/regression/workflow-obligations-green.log +254 -0
  42. package/benchmarks/results/pr-303-review-2026-10-13/verification/mechanism-35-clean-env-fixed.log +1 -0
  43. package/benchmarks/results/pr-303-review-2026-10-13/verification/mechanism-35-inherited-flag-fixed.log +1 -0
  44. package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.meta.json +8 -0
  45. package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.stderr.log +90 -0
  46. package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.stdout.log +3186 -0
  47. package/benchmarks/results/pr-303-review-2026-10-13/verification/verify-gate-final.json +46 -0
  48. package/bin/cli.js +1 -1
  49. package/ci/invariant-routing.test.js +7 -4
  50. package/ci/mechanism-2ag-router-worktree-contract.test.js +2 -2
  51. package/ci/mechanism-2ai-workspace-fingerprint-batch.test.js +2 -1
  52. package/ci/mechanism-2an-workflow-obligations.test.js +352 -3
  53. package/ci/mechanism-2aq-fable-stage-bindings.test.js +168 -0
  54. package/ci/mechanism-2ar-binding-resolution.test.js +211 -0
  55. package/ci/mechanism-35-verification-contract.test.js +1 -1
  56. package/ci/mutation-check.js +2 -0
  57. package/ci/mutation-loader.js +4 -0
  58. package/ci/openai-plugin-package.test.js +7 -5
  59. package/ci/prompt-contract-regressions.test.js +7 -6
  60. package/ci/router-workflow-plan.test.js +109 -14
  61. package/ci/skill-routing-check.js +44 -15
  62. package/ci/workflow-plan-consumers.test.js +41 -1
  63. package/docs/architecture.md +10 -10
  64. package/docs/enforcement-locks.md +1 -1
  65. package/docs/mechanism-first-skill-mesh.md +12 -11
  66. package/docs/openai-plugin.md +1 -1
  67. package/docs/philosophy.md +7 -6
  68. package/docs/platform-capabilities.md +7 -5
  69. package/docs/routing.md +16 -17
  70. package/docs/system-one-observations.md +1 -1
  71. package/docs/system-one-routing.md +10 -7
  72. package/docs/system-one-suggestion-gates.md +5 -5
  73. package/docs/workflow-runtime.md +6 -2
  74. package/docs/workflows/harness-everything.md +14 -14
  75. package/evals/harness-everything/tasks/positive-tier1.yaml +1 -1
  76. package/evals/harness-everything/tasks/positive-tier2.yaml +1 -1
  77. package/evals/harness-everything/tasks/positive-tier3.yaml +1 -1
  78. package/fable-mode/CONTRACT-FORMAT.md +4 -1
  79. package/fable-mode/SKILL.md +6 -8
  80. package/fable-mode/agents/fable-orchestrator.md +10 -3
  81. package/fable-mode/execution-guardrails/SKILL.md +1 -1
  82. package/fable-mode/fable-haiku/SKILL.md +1 -1
  83. package/fable-mode/fable-opus/SKILL.md +1 -1
  84. package/fable-mode/fable-sonnet/SKILL.md +1 -1
  85. package/fable-mode/scripts/workflow-plan-consumer.js +101 -2
  86. package/harness-everything/SKILL.md +8 -7
  87. package/harness-everything/references/router-workflow-plan.md +11 -6
  88. package/harness-everything/references/triage-and-tiers.md +21 -23
  89. package/harness-everything/schemas/router-workflow-plan.schema.json +2 -0
  90. package/harness-everything/scripts/kernel-router-core.js +52 -55
  91. package/harness-everything/scripts/kernel-router.js +48 -5
  92. package/harness-everything/scripts/router-contract-core.js +7 -7
  93. package/harness-everything/scripts/router-contract.js +1 -3
  94. package/harness-everything/scripts/routing-keywords.json +417 -121
  95. package/harness-everything/scripts/tier-router.js +64 -113
  96. package/hooks/scripts/contract-test.js +29 -3
  97. package/hooks/scripts/lib/binding-resolver.js +76 -0
  98. package/hooks/scripts/lib/fable-contracts.js +1 -1
  99. package/hooks/scripts/lib/workflow-obligations.js +292 -7
  100. package/hooks/scripts/lib/workflow-runtime.js +8 -1
  101. package/hooks/scripts/workflow-disposition.js +116 -2
  102. package/opencode-plugin/plugin.json +1 -1
  103. package/package.json +1 -1
  104. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  105. package/plugins/harness-everything/hooks/scripts/contract-test.js +29 -3
  106. package/plugins/harness-everything/hooks/scripts/lib/binding-resolver.js +76 -0
  107. package/plugins/harness-everything/hooks/scripts/lib/fable-contracts.js +1 -1
  108. package/plugins/harness-everything/hooks/scripts/lib/workflow-obligations.js +292 -7
  109. package/plugins/harness-everything/hooks/scripts/lib/workflow-runtime.js +8 -1
  110. package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +116 -2
  111. package/plugins/harness-everything/plugin.json +1 -1
  112. package/plugins/harness-everything/scripts/lib/advisory-text.js +2 -2
  113. package/plugins/harness-everything/scripts/lib/generated-skills.js +78 -0
  114. package/plugins/harness-everything/skills/fable-mode/CONTRACT-FORMAT.md +4 -1
  115. package/plugins/harness-everything/skills/fable-mode/SKILL.md +6 -8
  116. package/plugins/harness-everything/skills/fable-mode/agents/fable-orchestrator.md +10 -3
  117. package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +1 -1
  118. package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +1 -1
  119. package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +1 -1
  120. package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +1 -1
  121. package/plugins/harness-everything/skills/fable-mode/scripts/workflow-plan-consumer.js +101 -2
  122. package/plugins/harness-everything/skills/harness-everything/SKILL.md +8 -7
  123. package/plugins/harness-everything/skills/harness-everything/references/router-workflow-plan.md +11 -6
  124. package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +21 -23
  125. package/plugins/harness-everything/skills/harness-everything/schemas/router-workflow-plan.schema.json +2 -0
  126. package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router-core.js +52 -55
  127. package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +48 -5
  128. package/plugins/harness-everything/skills/harness-everything/scripts/router-contract-core.js +7 -7
  129. package/plugins/harness-everything/skills/harness-everything/scripts/router-contract.js +1 -3
  130. package/plugins/harness-everything/skills/harness-everything/scripts/routing-keywords.json +417 -121
  131. package/plugins/harness-everything/skills/harness-everything/scripts/tier-router.js +64 -113
  132. package/sbom.cdx.json +7 -7
  133. package/scripts/lib/advisory-text.js +2 -2
  134. package/scripts/lib/generated-skills.js +78 -0
@@ -63,7 +63,7 @@ function normalizeWritePath(value) {
63
63
  if (typeof value !== 'string' || !value.trim()) throw new Error('writeSet entries must be non-empty strings');
64
64
  let normalized = value.trim().replace(/\\/g, '/').replace(/^\.\//, '');
65
65
  normalized = normalized.replace(/\/+$/g, '');
66
- if (!normalized || normalized.startsWith('/') || /^[A-Za-z]:\//.test(normalized)) {
66
+ if (!normalized || normalized.startsWith('/') || /^[A-Za-z]:/.test(normalized)) {
67
67
  throw new Error(`writeSet paths must be repository-relative: ${value}`);
68
68
  }
69
69
  const parts = normalized.split('/');
@@ -76,6 +76,95 @@ function normalizeWritePath(value) {
76
76
  return normalized;
77
77
  }
78
78
 
79
+ // The resolver ships with the hook runtime. A Skills-only install has no hook
80
+ // runtime, so declared paths stay `unverified` instead of claiming availability.
81
+ function bindingResolver() {
82
+ for (const relative of ['../../hooks/scripts/lib/binding-resolver.js', '../../../hooks/scripts/lib/binding-resolver.js']) {
83
+ const file = path.resolve(__dirname, relative);
84
+ if (fs.existsSync(file)) return require(file);
85
+ }
86
+ return null;
87
+ }
88
+
89
+ function resolveStageBindingAvailability(bindings, workspaceRoot) {
90
+ const resolver = bindingResolver();
91
+ for (const binding of bindings) {
92
+ const resolution = resolver
93
+ ? resolver.resolveBinding(binding, workspaceRoot)
94
+ : { availability: binding.path ? 'unverified' : 'unknown', file: null };
95
+ binding.availability = resolution.availability;
96
+ binding.resolvedPath = resolution.file;
97
+ }
98
+ }
99
+
100
+ function normalizeBindings(values, label, required) {
101
+ if (values === undefined) return [];
102
+ if (!Array.isArray(values)) throw new Error(`${label} must be an array`);
103
+ const seen = new Set();
104
+ return values.map(raw => {
105
+ const value = typeof raw === 'string' ? { id: raw } : raw;
106
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label} entries must be ids or objects`);
107
+ const id = sanitizeId(value.id, `${label}.id`);
108
+ if (seen.has(id)) throw new Error(`${label} contains duplicate binding: ${id}`);
109
+ seen.add(id);
110
+ const bindingPath = value.path === undefined || value.path === null || value.path === ''
111
+ ? null
112
+ : normalizeWritePath(value.path);
113
+ return {
114
+ id,
115
+ required,
116
+ path: bindingPath,
117
+ status: 'pending',
118
+ availability: bindingPath ? 'unverified' : 'unknown',
119
+ resolvedPath: null,
120
+ evidence: null,
121
+ reasonCode: null,
122
+ updatedAt: null,
123
+ };
124
+ });
125
+ }
126
+
127
+ function activeStageBindings(runRoot, escapes = []) {
128
+ const run = readJson(path.join(runRoot, 'run.json'));
129
+ if (!run || !Array.isArray(run.stageIds)) return [];
130
+ const contractsDir = path.join(runRoot, 'contracts');
131
+ const stages = new Map();
132
+ for (const stageId of run.stageIds) {
133
+ const stage = readJson(path.join(contractsDir, `${stageId}.json`));
134
+ if (stage && stage.runId === run.runId && stage.stageId === stageId) stages.set(stageId, stage);
135
+ }
136
+ const escapedIds = new Set((escapes || []).filter(item => item?.runId === run.runId && item?.evidence && item?.uncoveredScope &&
137
+ ['workflow-uncovered-scope', 'host-capability-unavailable'].includes(item.reasonCode))
138
+ .map(item => item.stageId));
139
+ const stagePassed = stage => stage?.status === 'pass' &&
140
+ (stage.requiredBindings || []).every(binding => binding.status === 'loaded') &&
141
+ (stage.optionalBindings || []).every(binding => ['loaded', 'not-needed'].includes(binding.status));
142
+ const bindingsResolved = stage =>
143
+ (stage.requiredBindings || []).every(binding => binding.status === 'loaded') &&
144
+ (stage.optionalBindings || []).every(binding => ['loaded', 'not-needed'].includes(binding.status));
145
+ return run.stageIds
146
+ .map(stageId => stages.get(stageId))
147
+ .filter(stage => stage && !escapedIds.has(stage.stageId) &&
148
+ (['planned', 'running', 'fail', 'binding-unresolved'].includes(stage.status) ||
149
+ (stage.status === 'pass' && !bindingsResolved(stage))) &&
150
+ (stage.dependsOn || []).every(id => stagePassed(stages.get(id)) || escapedIds.has(id)))
151
+ .map(stage => ({
152
+ stageId: stage.stageId,
153
+ goal: stage.goal,
154
+ status: stage.status === 'pass' && !bindingsResolved(stage) ? 'binding-unresolved' : stage.status,
155
+ requiredBindings: (stage.requiredBindings || []).map(binding => ({
156
+ id: binding.id, status: binding.status, availability: binding.availability, path: binding.path, resolvedPath: binding.resolvedPath || null,
157
+ })),
158
+ optionalBindings: (stage.optionalBindings || []).map(binding => ({
159
+ id: binding.id, status: binding.status, availability: binding.availability, path: binding.path, resolvedPath: binding.resolvedPath || null,
160
+ })),
161
+ }));
162
+ }
163
+
164
+ function readJson(filePath) {
165
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch (_) { return null; }
166
+ }
167
+
79
168
  function normalizeStage(raw) {
80
169
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('stage must be an object');
81
170
  const stageId = sanitizeId(raw.stageId, 'stageId');
@@ -87,6 +176,12 @@ function normalizeStage(raw) {
87
176
 
88
177
  const dependsOn = [...new Set(raw.dependsOn.map(value => sanitizeId(value, `${stageId}.dependsOn`)))];
89
178
  const writeSet = [...new Set(raw.writeSet.map(normalizeWritePath))].sort();
179
+ const requiredBindings = normalizeBindings(raw.requiredBindings, `${stageId}.requiredBindings`, true);
180
+ const optionalBindings = normalizeBindings(raw.optionalBindings, `${stageId}.optionalBindings`, false);
181
+ const bindingIds = new Set(requiredBindings.map(binding => binding.id));
182
+ for (const binding of optionalBindings) {
183
+ if (bindingIds.has(binding.id)) throw new Error(`${stageId}: binding cannot be both required and optional: ${binding.id}`);
184
+ }
90
185
 
91
186
  return {
92
187
  stageId,
@@ -98,6 +193,8 @@ function normalizeStage(raw) {
98
193
  outputPath: raw.outputPath === undefined ? null : raw.outputPath,
99
194
  dependsOn,
100
195
  writeSet,
196
+ requiredBindings,
197
+ optionalBindings,
101
198
  checkCommand: typeof raw.checkCommand === 'string' && raw.checkCommand.trim() ? raw.checkCommand.trim() : null,
102
199
  passCondition: typeof raw.passCondition === 'string' && raw.passCondition.trim() ? raw.passCondition.trim() : null,
103
200
  failureReturn: typeof raw.failureReturn === 'string' && raw.failureReturn.trim()
@@ -236,6 +333,7 @@ function prepareRun({ routerContract, stages, workspaceRoot, runId, sessionId =
236
333
  fs.mkdirSync(evidenceDir, { recursive: true });
237
334
 
238
335
  for (const stage of normalizedStages) {
336
+ resolveStageBindingAvailability([...stage.requiredBindings, ...stage.optionalBindings], root);
239
337
  atomicWriteJson(path.join(contractsDir, `${stage.stageId}.json`), {
240
338
  schemaVersion: STAGE_CONTRACT_VERSION,
241
339
  planId,
@@ -319,7 +417,7 @@ if (require.main === module) {
319
417
  runId: args.runId,
320
418
  sessionId: args.sessionId || null,
321
419
  });
322
- process.stdout.write(`${JSON.stringify(manifest)}\n`);
420
+ process.stdout.write(`${JSON.stringify({ ...manifest, activeStages: activeStageBindings(manifest.runRoot) })}\n`);
323
421
  } catch (err) {
324
422
  console.error(`[FABLE PLAN CONSUMER] ${err.message}`);
325
423
  process.exit(2);
@@ -328,6 +426,7 @@ if (require.main === module) {
328
426
 
329
427
  module.exports = {
330
428
  STAGE_CONTRACT_VERSION,
429
+ activeStageBindings,
331
430
  derivePlanId,
332
431
  getWorkspaceKey,
333
432
  getWorkspaceStateRoot,
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: harness-everything
3
- description: "Route software/project work through Harness: classify tier/topology, resolve suggested skills, apply semantic obligations, and keep tactics flexible. Use for software triage/routing/re-routing; not general Q&A or non-software writing."
3
+ description: "Route software/project work through Harness: classify tier/topology, compose active-step knowledge bindings, apply semantic obligations, and keep tactics flexible. Use for software triage/routing/re-routing; not general Q&A or non-software writing."
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.30.6
7
+ version: 0.31.0
8
8
  ---
9
9
 
10
10
  # Harness Everything
@@ -20,16 +20,17 @@ metadata:
20
20
  1. **Route (MUST)** — scope/tier plus selected/deferred topology.
21
21
  2. **Verify (MUST)** — objective evidence via `verify-gate.js --json`.
22
22
  3. **Zoom out (MUST)** — after 3 same-signature failures, use `zoom-out`.
23
- 4. **Resolve suggestions (MUST)** — evaluate each suggested `SKILL.md`; run applicable core contracts or retain a flow-grounded reason.
23
+ 4. **Bind knowledge by step (MUST)** — ordered steps declare required/optional skills/references; only the active step's bindings load and resolve.
24
24
  5. **Surface status (MUST)** — non-trivial work renders `### 🚦 Harness Status` with bold `Current`, `Read / Evidence`, `Next`; `Risk / Blocked` optional.
25
25
  6. **Keep agency** — required obligations are MUST; tactics MAY adapt. Numeric planning MAY guide, never hard-stop; only Rule-of-3 reflection and explicit user/host permission boundaries may block.
26
26
  7. **Windows PowerShell (MUST)** — `Get-Content -Encoding UTF8 -Raw`; npm `.cmd` shims (`npx.cmd`); never weaken `ExecutionPolicy`.
27
27
 
28
28
  ## Workflow
29
29
  1. Reuse kernel output or run `node "<this-skill-dir>/scripts/kernel-router.js" "<prompt summary>"`.
30
- 2. Keep the routing checkpoint internal.
31
- 3. Resolve suggestions as `use`, `not-applicable`, or `unresolved`.
32
- 4. Complete selected-topology invariants/stages/checks.
33
- 5. Tier-3/Fable broad mutation MUST use a linked worktree or explicit degraded fallback.
30
+ 2. Keep the routing checkpoint internal; knowledge signals feed planning, not document selection.
31
+ 3. Decompose ordered requirement steps, with their bindings, before execution.
32
+ 4. Required active-step bindings and applicable core contracts MUST resolve before the step passes; unknown/unavailable stays visible.
33
+ 5. Complete selected-topology invariants/stages/checks; Fable exposes only dependency-ready stage bindings.
34
+ 6. Tier-3/Fable broad mutation MUST use a linked worktree or explicit degraded fallback.
34
35
 
35
36
  Deep dive: <this-skill-dir>/references/triage-and-tiers.md
@@ -104,22 +104,27 @@ Irreversible or external side-effect intent (for example destructive database ch
104
104
 
105
105
  This does not choose a topology and does not itself enforce the gate. Phase 5 owns PreToolUse enforcement. The router only declares the invariant so any eventual executor must preserve it.
106
106
 
107
- ## Invariants vs advisory skills
107
+ ## Invariants vs knowledge signals
108
108
 
109
- `requiredInvariants` and `suggestedSkills` are separate plan fields. The kernel prints both separately and consumes them from the structured plan instead of reconstructing suggestions from tier.
109
+ `requiredInvariants` and `knowledgeSignals` have separate roles. Invariants state semantic MUSTs; normalized knowledge signals help decompose requirements but never select document paths. `suggestedSkills` remains an empty compatibility field and is not populated from topology or keywords.
110
110
 
111
111
  Examples:
112
112
 
113
113
  - `loop-awareness` and `objective-verification` are mandatory for `iterative-single`, on top of the base `scope-lock`, `verify-before-claim`, and `replan-after-repeated-failure`;
114
- - `tdd` and `verification-loop` remain advisory skill suggestions;
114
+ - TDD and verification-loop bindings are declared on behavior-change and verification steps, respectively;
115
115
  - `stage-contracts` and `cold-verification` are mandatory for Fable topologies;
116
- - `multi-agent-workspace` is suggested only when the selected topology actually requires durable workspace behavior.
116
+ - domain skills and references are bound only to steps that need them;
117
+ - `fable-multi-agent-workspace` selects durable workspace behavior without globally loading a skill path.
117
118
 
118
119
  There is no universal `TODO → TDD → verification` pipeline.
119
120
 
120
- ## Guide deduplication
121
+ ## Binding and disclosure rule
121
122
 
122
- Knowledge-guide recommendations are deduplicated by the referenced guide path, not by full line text. If two routing groups describe `security-review/SKILL.md` differently, the path is still emitted once.
123
+ 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.
124
+
125
+ For single-agent steps, `workflow-disposition.js start`, `binding`, and `step` return command templates for resolving the current binding and passing the active step. Retained workflow reminders print the same commands. Record the binding evidence before the step disposition; the next step and its paths are disclosed only after the current step passes.
126
+
127
+ In typed-step mode, the aggregate `execute` and `verify` obligations resolve from their corresponding passed steps. A verification-only plan records an explicit no-execution disposition for `execute`; it still requires the verification binding, step evidence, and objective check.
123
128
 
124
129
  ## Determinism rule
125
130
 
@@ -14,10 +14,9 @@ flowchart TD
14
14
  K --> W{Workflow selected?}
15
15
  W -- No / deferred --> D[Preserve invariants<br/>choose smallest justified workflow]
16
16
  W -- Yes --> A[ACTIVE workflow contract]
17
- A --> G{Suggested skills?}
18
- G -- Yes --> R[Read each suggested SKILL.md<br/>resolve applicability]
19
- G -- No --> E[Execute selected workflow]
20
- R --> E
17
+ A --> P[Decompose requirements<br/>into ordered steps]
18
+ P --> B[Bind skills/references<br/>to each step]
19
+ B --> E[Execute active step only]
21
20
  E --> V{Required checks / stages resolved?}
22
21
  V -- Yes --> O([Claim completion])
23
22
  V -- No --> P[Diagnose / re-plan]
@@ -42,7 +41,7 @@ The kernel protects lifecycle obligations without micromanaging model reasoning.
42
41
  1. **Route before execution** — establish task scope/tier and the smallest justified topology.
43
42
  2. **Verify before claim** — completion requires objective evidence appropriate to the change.
44
43
  3. **Re-plan on repetition** — after three same-signature failures, stop micro-retrying and use a fresh diagnosis / `zoom-out`.
45
- 4. **Evaluate before omission** — when the router suggests a skill, read its complete `SKILL.md` entry/basic flow before deciding applicability.
44
+ 4. **Bind knowledge by step** — compose ordered requirement steps and declare required/optional skills or references before execution.
46
45
  5. **Resolve selected workflow** — selected-topology required obligations are semantic MUSTs; implementation tactics MAY adapt where the contract allows.
47
46
  6. **Surface status** — non-trivial software/project work MUST use the single Harness Status format at required phase boundaries.
48
47
 
@@ -52,10 +51,10 @@ The model remains free to choose tools, implementation technique, decomposition
52
51
 
53
52
  - One universal `TODO → TDD → verification-loop` sequence for every Tier 2/3 task.
54
53
  - Multi-agent execution merely because a task is Tier 3.
55
- - Every router-suggested skill when its evaluated flow is objectively not applicable.
54
+ - Every keyword-matched skill/reference regardless of whether an active requirement needs it.
56
55
  - Every optional deep-dive/reference linked from a skill.
57
56
 
58
- A suggested skill may resolve as `use`, `not-applicable`, or `unresolved/unavailable`. Resolution is a **MUST**. `not-applicable` needs a flow-grounded reason; a metadata-only or confidence-only judgement is insufficient. If disposition is `use`, the skill's core contract is a **MUST**. The selected **topology** is also a semantic execution contract; neither concept implies a persistent runtime lock.
57
+ Keyword/domain matches are normalized planning signals, not document selectors. Each ordered step declares its required and optional skill/reference bindings; only the active step or dependency-ready Fable stage exposes those paths. Read a required skill's complete entry and follow its applicable core contract before the step passes. Required bindings without a known path remain visible and unresolved. Optional bindings resolve as loaded or not-needed. Hooks may remind, but missing bindings do not hard-block ordinary tools or Stop.
59
58
 
60
59
  ## 2. Routing Mechanism
61
60
 
@@ -67,28 +66,27 @@ node "<this-skill-dir>/scripts/kernel-router.js" "<brief prompt summary>"
67
66
  npx github:dyphn1/Harness-everything next "<brief prompt summary>"
68
67
  ```
69
68
 
70
- `kernel-router.js` delegates classification/guide discovery to `tier-router.js` and emits:
69
+ `kernel-router.js` delegates classification/knowledge-signal detection to `tier-router.js` and emits:
71
70
 
72
71
  - classified tier + rationale,
73
72
  - structured workflow plan,
74
73
  - compact **Harness Routing Checkpoint**,
75
74
  - required invariants,
76
- - suggested skills whose applicability must be evaluated,
75
+ - normalized knowledge signals that help compose requirement steps, not paths to read,
76
+ - ordered requirement steps and their active required/optional bindings after planning,
77
77
  - **Workflow Contract** for the selected topology.
78
78
 
79
79
  If `UserPromptSubmit` already ran the kernel this turn, reuse that output. Do not infer a silent Tier 1/direct path from missing or degraded routing.
80
80
 
81
- ### Suggestion applicability contract
81
+ ### Active binding contract
82
82
 
83
- For every router-suggested skill:
83
+ - Before a selected Tier 2/3 workflow starts, decompose the request into ordered requirement steps and confirm the smallest sufficient topology.
84
+ - Put a required skill/reference only on steps whose acceptance criteria need it; put optional knowledge on its actual consumer step.
85
+ - Load/resolve only bindings for the active step. Do not print or preload future-step bindings.
86
+ - A required binding MUST resolve as loaded before the step passes; optional bindings resolve as loaded or not-needed. Unknown/unavailable required bindings stay visible and unresolved.
87
+ - Fable uses its existing dependency/write-set graph as the step graph. Only dependency-ready stage bindings are disclosed; unresolved bindings keep a successful check at `binding-unresolved` until they are resolved and that exact check is rerun.
84
88
 
85
- 1. **MUST** read the complete `SKILL.md` entry.
86
- 2. **MUST** evaluate `USE FOR`, `DO NOT USE FOR`, workflow/basic flow, and hard rules.
87
- 3. Read extra material only when the entry explicitly requires it to decide applicability.
88
- 4. **MUST** resolve the suggestion as `use`, `not-applicable`, or `unresolved/unavailable`.
89
- 5. `use` means the skill's core contract **MUST** be followed; `not-applicable` **MUST** keep a flow-grounded reason.
90
-
91
- A name, description, router summary, tier label, or generic "routine/common task" judgement is not enough to mark a suggestion not applicable. Using one suggestion does not resolve the others automatically.
89
+ The binding disposition is an execution contract, not proof that a real host loaded a file. Preserve a host/session trace before making live-host claims. Missing evidence stays a reminder and does not create a persistent Harness lock.
92
90
 
93
91
  ### Workflow escape contract
94
92
 
@@ -111,7 +109,7 @@ Default topology is normally `direct-single`. Keep the lifecycle minimal, but st
111
109
 
112
110
  Typical shape: specific feature/bug fix, multi-file coordination, behavioral change, focused benchmark/review.
113
111
 
114
- The router normally selects `iterative-single`: bounded reason/act work plus objective verification. It may surface `tdd`, `verification-loop`, `security-review`, `using-git-worktrees`, or other focused skills. Those suggestions are applicability decisions, not a universal sequence.
112
+ The router normally selects `iterative-single`: bounded reason/act work plus objective verification. Compose ordered steps and bind TDD to behavior-change, verification-loop to verification, and domain references only to steps whose acceptance needs them. Keyword matches alone never select or load documents.
115
113
 
116
114
  When an evaluated skill is applicable, follow its workflow rather than reading it and then discarding it because the implementation seems obvious.
117
115
 
@@ -181,16 +179,16 @@ For software/project work, consume this internal runtime state when producing th
181
179
  - Strategy: <selected/deferred strategy>
182
180
  - Workflow state: <pending | running | satisfied | deferred | blocked>
183
181
  - Required invariants: <router invariants>
184
- - Suggested skills: <deduplicated suggestions or none>
185
- - Suggestion applicability: <use | not-applicable + reason | unresolved/unavailable>
182
+ - Knowledge signals: <normalized planning inputs; not document paths>
183
+ - Active step: <ordered requirement id/type, with only its declared bindings>
186
184
  - Escape: <none | reason + uncovered scope + evidence>
187
185
  ```
188
186
 
189
- The checkpoint is runtime/source state, not a competing user-facing template. Render user progress through the single Harness Status contract above. The key distinction is: **skill suggestions MUST resolve applicability; applicable skill core contracts and selected-topology obligations are MUSTs, while tactics MAY adapt.**
187
+ The checkpoint is runtime/source state, not a competing user-facing template. Render user progress through the single Harness Status contract above. The key distinction is: **active required bindings and selected-topology obligations are MUSTs; unrelated future knowledge stays undisclosed, while tactics MAY adapt.**
190
188
 
191
189
  ## 8. Self-Healing and Dynamic Skills
192
190
 
193
- Self-heal, generated-skill discovery, knowledge-guide matching, and fact-audit behavior remain part of the runtime. Dynamic skill suggestions still require applicability evaluation.
191
+ Self-heal and fact-audit behavior remain part of the runtime. Prompt keyword matches emit normalized knowledge signals plus candidate step-binding ids; they do not print skill/reference paths. Self-evolved skills registered in a manifest's `generated` entries are matched on metadata (triggers) only: matching ids surface as `SELF-EVOLVED SKILL SIGNALS`, and a step that declares such an id resolves its `SKILL.md` from the manifest only once that step is active.
194
192
 
195
193
  If bootstrap reports missing integration touchpoints:
196
194
 
@@ -12,6 +12,7 @@
12
12
  "patterns",
13
13
  "requiredInvariants",
14
14
  "suggestedSkills",
15
+ "knowledgeSignals",
15
16
  "actionGate",
16
17
  "limits",
17
18
  "parallelism",
@@ -34,6 +35,7 @@
34
35
  "patterns": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
35
36
  "requiredInvariants": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
36
37
  "suggestedSkills": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
38
+ "knowledgeSignals": { "type": "array", "items": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,63}$" }, "uniqueItems": true },
37
39
  "actionGate": {
38
40
  "type": "object",
39
41
  "required": ["required", "reasonCodes", "approver", "disposition"],
@@ -27,7 +27,6 @@ const INVARIANT_TEXT = {
27
27
  'verify-before-claim': 'MUST verify before claim with objective evidence appropriate to the change.',
28
28
  'visible-status-updates': 'MUST render the single Markdown Harness Status with a visible heading, bullet-aligned bold labels, Current, Read / Evidence, and Next; add Risk / Blocked only when materially applicable.',
29
29
  'replan-after-repeated-failure': 'MUST stop micro-retrying after 3 same-signature failures and zoom out/re-diagnose.',
30
- 'evaluate-suggestions-before-skip': 'MUST read/evaluate each suggested skill\'s complete SKILL.md entry/basic flow before omission; an applicable skill core contract MUST be followed.',
31
30
  'loop-awareness': 'MUST reconsider assumptions/re-plan when evidence shows iterative stagnation; numeric iteration values MAY guide but never hard-stop.',
32
31
  'objective-verification': 'MUST use an objective check for iterative work; self-critique alone is not verification.',
33
32
  'stage-contracts': 'When Fable is selected, stages MUST have explicit stage contracts and pass conditions.',
@@ -43,16 +42,6 @@ const INVARIANT_TEXT = {
43
42
  'isolated-worktree-before-mutation': 'Tier-3/Fable broad mutation MUST resolve isolation: verified linked worktree or explicit degraded fallback.',
44
43
  };
45
44
 
46
- const SKILL_TEXT = {
47
- 'tdd': 'tdd: useful for behavioral changes where executable tests can drive the implementation.',
48
- 'verification-loop': 'verification-loop: useful for systematic build/lint/test/diff evidence before delivery.',
49
- 'using-git-worktrees': 'using-git-worktrees: resolves required Tier-3/Fable isolation disposition before broad mutation.',
50
- 'fable-mode': 'fable-mode / fable-discipline: useful for macro planning or deliberate multi-agent decomposition.',
51
- 'fable-discipline': null,
52
- 'multi-agent-workspace': 'multi-agent-workspace: useful when durable bounded delegation, handoffs, or workspace memory are required.',
53
- 'self-evolve': 'self-evolve: classify an evidence-backed lesson and use the authorized memory persistence path.',
54
- };
55
-
56
45
  function sanitizeClassifierOutput(stdout) {
57
46
  const lines = String(stdout || '').split(/\r?\n/);
58
47
  const filtered = [];
@@ -121,25 +110,12 @@ function printWorkflowPlan(plan) {
121
110
  fallback: plan.fallback,
122
111
  reasonCodes: plan.reasonCodes,
123
112
  requiredInvariants: plan.requiredInvariants,
124
- suggestedSkills: plan.suggestedSkills,
113
+ knowledgeSignals: plan.knowledgeSignals,
125
114
  mutationIsolation: plan.mutationIsolation,
126
115
  };
127
116
  console.log(`\n=> ROUTER WORKFLOW PLAN (JSON): ${JSON.stringify(visible)}`);
128
117
  }
129
118
 
130
- function uniqueSuggestedSkills(plan) {
131
- if (!Array.isArray(plan.suggestedSkills)) return [];
132
- return [...new Set(plan.suggestedSkills.filter(skill => typeof skill === 'string' && skill.trim()))];
133
- }
134
-
135
- function enforceSuggestionEvaluation(plan) {
136
- if (uniqueSuggestedSkills(plan).length === 0) return;
137
- if (!Array.isArray(plan.requiredInvariants)) plan.requiredInvariants = [];
138
- if (!plan.requiredInvariants.includes('evaluate-suggestions-before-skip')) {
139
- plan.requiredInvariants.push('evaluate-suggestions-before-skip');
140
- }
141
- }
142
-
143
119
  function displayTier(tier) {
144
120
  if (tier === 'tier1') return 'Tier 1';
145
121
  if (tier === 'tier2') return 'Tier 2';
@@ -149,19 +125,14 @@ function displayTier(tier) {
149
125
 
150
126
  function printRoutingCheckpoint(plan) {
151
127
  const invariants = Array.isArray(plan.requiredInvariants) ? plan.requiredInvariants : [];
152
- const suggestions = uniqueSuggestedSkills(plan);
128
+ const signals = Array.isArray(plan.knowledgeSignals) ? plan.knowledgeSignals : [];
153
129
 
154
130
  console.log('\n=> HARNESS ROUTING CHECKPOINT (INTERNAL SOURCE STATE — DO NOT RENDER SEPARATELY):');
155
131
  console.log(` - Tier: ${displayTier(plan.tier)}`);
156
132
  console.log(` - Strategy: ${plan.strategy || 'deferred'}`);
157
133
  console.log(` - Required invariants: ${invariants.length ? invariants.join(', ') : 'none'}`);
158
- console.log(` - Suggested skills: ${suggestions.length ? suggestions.join(', ') : 'none'}`);
159
- console.log(' - User-visible progress: use this checkpoint as source state for the single Harness Status contract below; do not invent a per-skill progress format.');
160
- if (suggestions.length > 0) {
161
- console.log(' - Suggestion evaluation: MANDATORY. Before skipping any listed skill, read its complete SKILL.md entry and evaluate USE FOR, DO NOT USE FOR, workflow/basic flow, and hard rules.');
162
- console.log(' - Skip evidence: do not reject from only the skill name, description, router summary, or a generic "routine/common task" judgement. If the entry cannot be resolved/read, mark it unresolved/unavailable rather than skipped.');
163
- console.log(' - Suggestion disposition: applicability is conditional, but resolution is mandatory. If a skill is applicable, follow its core contract; if not-applicable, keep one brief flow-grounded reason. The selected workflow is a semantic execution contract, not optional advice.');
164
- }
134
+ console.log(` - Knowledge signals: ${signals.length ? signals.join(', ') : 'none'} (planning input only; not document selectors)`);
135
+ console.log(' - User-visible progress: use this checkpoint as source state for the single Harness Status contract below.');
165
136
  }
166
137
 
167
138
  function printKernelContract(plan) {
@@ -170,6 +141,11 @@ function printKernelContract(plan) {
170
141
  console.log(` - ${invariant}: ${INVARIANT_TEXT[invariant] || 'Required by the selected workflow plan.'}`);
171
142
  }
172
143
 
144
+ if (plan.strategy && ['tier2', 'tier3'].includes(plan.tier)) {
145
+ console.log('\n=> PLANNING STEP: decompose → compose');
146
+ console.log(' - Define ordered requirement steps and their bindings before starting execution.');
147
+ }
148
+
173
149
  console.log('\n=> USER-VISIBLE HARNESS STATUS CONTRACT (MUST):');
174
150
  console.log(' - For non-trivial software/project work, the agent MUST render this single Markdown status shape:');
175
151
  console.log(' ### 🚦 Harness Status');
@@ -184,26 +160,10 @@ function printKernelContract(plan) {
184
160
  console.log(' - Emit it before substantive execution, after a major phase, when direction materially changes, at meaningful long-running phase boundaries, and before final completion (the final response may merge it naturally).');
185
161
  console.log(' - This is a semantic communication MUST, not a hard execution lock, counter, or reset condition.');
186
162
 
187
- console.log('\n=> WORKFLOW SKILLS (APPLICABILITY MUST BE RESOLVED):');
188
- if (Array.isArray(plan.suggestedSkills) && plan.suggestedSkills.length > 0) {
189
- const emitted = new Set();
190
- for (const skill of plan.suggestedSkills) {
191
- if (emitted.has(skill)) continue;
192
- const text = Object.prototype.hasOwnProperty.call(SKILL_TEXT, skill) ? SKILL_TEXT[skill] : `${skill}: evaluate applicability inside the selected topology.`;
193
- if (text) console.log(` - ${text}`);
194
- emitted.add(skill);
195
- if (skill === 'fable-mode') emitted.add('fable-discipline');
196
- }
197
- if (plan.strategy && plan.strategy.startsWith('fable-')) {
198
- console.log(' - Fable is not a universal pipeline, but a selected Fable topology must be entered and resolved before completion.');
199
- }
200
- } else if (plan.strategy === 'direct-single') {
201
- console.log(' - No domain skill was suggested. The bounded direct path SHOULD remain minimal; a focused skill MAY be loaded when it adds value.');
202
- } else if (plan.strategySelection === 'deferred') {
203
- console.log(' - Strategy is deferred/unclassified. Do not infer triviality; choose the smallest justified approach from task evidence.');
204
- } else {
205
- console.log(' - No additional skill suggestion from the workflow plan.');
206
- }
163
+ console.log('\n=> ACTIVE-STEP KNOWLEDGE BINDINGS (MUST):');
164
+ console.log(' - Compose ordered requirement steps before execution. Each step declares the required/optional skills or references for that step.');
165
+ console.log(' - MUST load/resolve only bindings declared for the active step; future-step bindings remain undisclosed until that step becomes active.');
166
+ console.log(' - A required binding MUST be loaded and its core contract followed before the active step passes. An unknown/unavailable binding stays visible and unresolved; it does not hard-block ordinary tools or Stop.');
207
167
 
208
168
  if (plan.ensemble) {
209
169
  console.log(`\n=> ENSEMBLE REVIEW: bounded to ${plan.ensemble.maxCandidates} candidates; synthesis=${plan.ensemble.synthesis}; verifier=${plan.ensemble.verifier}. Preserve minority positions and do not claim improvement without paired evidence.`);
@@ -224,6 +184,44 @@ function printKernelContract(plan) {
224
184
  console.log('\n=> ORCHESTRATION POLICY: Selected-topology required obligations and applicable skill core contracts are semantic MUSTs; implementation tactics MAY adapt. Lifecycle hooks observe/remind rather than hard-block, except user/host permission boundaries and the Rule-of-3 zoom-out.');
225
185
  }
226
186
 
187
+ function printRetainedWorkflow(plan, workflow, activeStep = null, activeStages = []) {
188
+ const invariants = Array.isArray(workflow?.requiredInvariants) ? workflow.requiredInvariants : plan.requiredInvariants || [];
189
+ const state = workflow?.state || 'unknown';
190
+ console.log('\n=> RETAINED EXECUTION CONTRACT (COMPACT):');
191
+ console.log(` - Workflow: ${workflow?.workflowId || 'unavailable'}; state=${state}; tier=${displayTier(workflow?.tier || plan.tier)}; strategy=${workflow?.strategy || plan.strategy || 'deferred'}.`);
192
+ console.log(` - Invariants remain active: ${invariants.length ? invariants.join(', ') : 'none'}.`);
193
+ console.log(` - Action gate: ${plan.actionGate?.required ? `MUST remain ${plan.actionGate.disposition} (${plan.actionGate.reasonCodes.join(', ')})` : 'not required'}.`);
194
+ console.log(` - Isolation: ${plan.mutationIsolation?.required ? 'MUST be resolved before broad mutation; missing host evidence remains visible, not a tool lock' : 'no topology-level isolation requirement'}.`);
195
+ if (activeStages.length) {
196
+ console.log(` - Active Fable stage${activeStages.length === 1 ? '' : 's'}: ${activeStages.map(stage => `${stage.stageId} [${stage.status}]`).join(', ')}.`);
197
+ for (const stage of activeStages) {
198
+ console.log(` - ${stage.stageId}: ${stage.goal}`);
199
+ for (const kind of ['requiredBindings', 'optionalBindings']) {
200
+ const bindings = stage[kind] || [];
201
+ if (bindings.length) {
202
+ console.log(` - ${kind === 'requiredBindings' ? 'Required' : 'Optional'}: ${bindings.map(binding => `${binding.id} [${binding.status}; ${binding.path || 'path unavailable'}]`).join(', ')}.`);
203
+ }
204
+ }
205
+ }
206
+ console.log(' - Future-stage bindings remain undisclosed until their dependencies pass.');
207
+ } else if (activeStep) {
208
+ console.log(` - Active step: ${activeStep.id} (${activeStep.stepType}) — ${activeStep.summary}; status=${activeStep.status}.`);
209
+ for (const kind of ['requiredBindings', 'optionalBindings']) {
210
+ const bindings = activeStep[kind] || [];
211
+ if (bindings.length) {
212
+ console.log(` - ${kind === 'requiredBindings' ? 'Required' : 'Optional'} active bindings: ${bindings.map(binding => `${binding.id} [${binding.status}; ${binding.path || 'path unavailable'}]`).join(', ')}.`);
213
+ }
214
+ }
215
+ console.log(' - Do not resolve, load, or disclose bindings for future steps yet.');
216
+ } else if (String(workflow?.strategy || plan.strategy || '').startsWith('fable-')) {
217
+ console.log(' - No dependency-ready Fable stage bindings are currently available.');
218
+ } else {
219
+ console.log(' - Active step: planning/execution obligations; step bindings are not composed or started yet.');
220
+ }
221
+ console.log(' - Harness Status stays MUST: one block with Current, Read / Evidence, and Next; add Risk / Blocked only when material. Turn label stays MUST.');
222
+ console.log(' - Permission boundary and Rule-of-3 remain active; this reminder is non-blocking.');
223
+ }
224
+
227
225
  function route(prompt, stdinPayload) {
228
226
  const classifierPath = path.join(__dirname, 'tier-router.js');
229
227
  const contractPath = path.join(os.tmpdir(), `harness-router-${process.pid}-${crypto.randomUUID()}.json`);
@@ -244,7 +242,6 @@ function route(prompt, stdinPayload) {
244
242
 
245
243
  const contract = readStructuredContract(contractPath);
246
244
  applyEnsemblePolicy(contract, resolvePrompt(prompt, stdinPayload));
247
- enforceSuggestionEvaluation(contract.workflowPlan);
248
245
  try {
249
246
  fs.rmSync(contractPath, { force: true });
250
247
  } catch (err) {
@@ -279,4 +276,4 @@ if (prompt) {
279
276
  }
280
277
  }
281
278
 
282
- module.exports = { route, printWorkflowPlan, printRoutingCheckpoint, printKernelContract };
279
+ module.exports = { route, printWorkflowPlan, printRoutingCheckpoint, printKernelContract, printRetainedWorkflow };
@@ -105,6 +105,27 @@ function persistWorkflow(contract, payload, prompt) {
105
105
  return { ...context, hooksRoot, retained: false, memoryCapability };
106
106
  }
107
107
 
108
+ function activeExecutionFor(persisted, plan) {
109
+ if (!persisted?.workflow || !persisted.hooksRoot) return null;
110
+ try {
111
+ if (String(persisted.workflow.strategy || '').startsWith('fable-')) {
112
+ const runtime = require(path.join(persisted.hooksRoot, 'lib/workflow-runtime'));
113
+ const match = runtime.matchingRun(persisted);
114
+ if (!match) return { activeStages: [] };
115
+ for (const relative of ['../../fable-mode/scripts/workflow-plan-consumer.js', '../../skills/fable-mode/scripts/workflow-plan-consumer.js']) {
116
+ const file = path.resolve(__dirname, relative);
117
+ if (fs.existsSync(file)) return { activeStages: require(file).activeStageBindings(match.runRoot, persisted.workflow.escapes) };
118
+ }
119
+ return { activeStages: [] };
120
+ }
121
+ const obligations = require(path.join(persisted.hooksRoot, 'lib/workflow-obligations'));
122
+ const loaded = obligations.loadObligationSet(persisted, plan);
123
+ return { activeStep: loaded.set ? obligations.activeStepSnapshot(loaded.set) : null };
124
+ } catch (_) {
125
+ return null;
126
+ }
127
+ }
128
+
108
129
  // Printed last so it is the nearest instruction when the turn ends (docs/system-one-observations.md#label-line).
109
130
  function printLabelContract() {
110
131
  console.log('\n=> TURN LABEL LINE (MUST, EVERY TURN — short answers, lookups and trivial edits included):');
@@ -134,14 +155,35 @@ function run(raw) {
134
155
  if (persisted?.retained && persisted.workflow) plan = persisted.workflow.workflowPlan;
135
156
  } catch (error) { failure = error; }
136
157
  if (hostNotification && !persisted?.workflow) {
137
- console.log('\n=> Host notification — no active execution contract; ignored for routing.');
158
+ console.log('\n=> Host notification: no active execution contract; ignored for routing.');
159
+ if (failure) console.error('[Workflow Reminder] routing state was not persisted: ' + failure.message);
160
+ printLabelContract();
161
+ return;
162
+ }
163
+ if (persisted?.retained && persisted.workflow) {
164
+ if (persisted.hostNotification) console.log('\n=> Host notification: routing unchanged; keeping the active execution contract.');
165
+ else console.log('\n=> Retaining unresolved workflow; showing its compact active contract.');
166
+ const activeExecution = activeExecutionFor(persisted, plan) || {};
167
+ const activeStep = activeExecution.activeStep || null;
168
+ const activeStages = activeExecution.activeStages || [];
169
+ core.printRetainedWorkflow(plan, persisted.workflow, activeStep, activeStages);
170
+ if (activeStep) {
171
+ const controller = path.join(persisted.hooksRoot, 'workflow-disposition.js');
172
+ console.log(' - Resolve only bindings for this active step, then pass this step with evidence.');
173
+ console.log(' - Controller: ' + controller + '; session: ' + persisted.sessionId + '; step: ' + activeStep.id + '.');
174
+ console.log(' - Resolve binding: node "' + controller + '" binding --session-id "' + persisted.sessionId + '" --step-id "' + activeStep.id + '" --binding-id "<active-binding-id>" --disposition <loaded|not-needed|unavailable> --evidence "<binding evidence>"');
175
+ console.log(' - Pass step: node "' + controller + '" step --session-id "' + persisted.sessionId + '" --step-id "' + activeStep.id + '" --disposition pass --evidence "<completion evidence>"');
176
+ } else if (activeStages.length) {
177
+ const controller = path.join(persisted.hooksRoot, 'workflow-disposition.js');
178
+ console.log(' - Resolve only declared bindings with stage-binding --stage-id <active-stage> --binding-id <binding> --disposition <loaded|not-needed|unavailable> --evidence <evidence>.');
179
+ console.log(' - Controller: ' + controller + '; session: ' + persisted.sessionId + '.');
180
+ }
181
+ printLabelContract();
138
182
  if (failure) console.error('[Workflow Reminder] routing state was not persisted: ' + failure.message);
139
183
  return;
140
184
  }
141
185
  if (result?.sanitized) console.log(result.sanitized);
142
186
  if (result?.stderr) process.stderr.write(result.stderr);
143
- if (persisted?.hostNotification) console.log('\n=> Host notification — routing unchanged; keeping the active execution contract.');
144
- else if (persisted?.retained) console.log('\n=> Retaining unresolved workflow from earlier prompts; the checkpoint below is the active execution contract.');
145
187
  core.printWorkflowPlan(plan);
146
188
  core.printRoutingCheckpoint(plan);
147
189
  core.printKernelContract(plan);
@@ -163,7 +205,8 @@ function run(raw) {
163
205
  if (plan.strategySelection === 'selected' && ['tier2', 'tier3'].includes(plan.tier)) {
164
206
  console.log(' - Planning contract: ' + path.join(persisted.sessionDir, 'workflow-obligations.json'));
165
207
  console.log(' - Requirements first: decompose intent into requirement fragments, then confirm the smallest sufficient workflow.');
166
- console.log(' - Record plan: node "' + controller + '" plan --session-id "' + persisted.sessionId + '" --requirements-json "[{\\\"id\\\":\\\"req-1\\\",\\\"summary\\\":\\\"...\\\",\\\"acceptance\\\":\\\"...\\\"}]" --strategy "' + plan.strategy + '" --evidence "<why this workflow fits>"');
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>"');
167
210
  }
168
211
  console.log(' - Enter selected workflow after planning: node "' + controller + '" start --session-id "' + persisted.sessionId + '"');
169
212
  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>"');
@@ -175,7 +218,7 @@ function run(raw) {
175
218
  if (result && result.status !== 0) console.error('[Workflow Reminder] router returned status ' + result.status + '; contract observation is degraded, not blocking.');
176
219
  }
177
220
 
178
- if (typeof module !== 'undefined') module.exports = { isHostNotificationPrompt };
221
+ if (typeof module !== 'undefined') module.exports = { isHostNotificationPrompt, activeExecutionFor };
179
222
 
180
223
  if (process.argv.length > 2 || process.stdin.isTTY) run('');
181
224
  else { let input = '';