harness-everything 0.16.5 → 0.17.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 (28) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +29 -0
  4. package/ci/mechanism-2ai-workspace-fingerprint-batch.test.js +11 -1
  5. package/ci/mechanism-2am-openai-packaged-helper-runtime.test.js +28 -0
  6. package/ci/mechanism-2an-environment-detection-readonly.test.js +113 -0
  7. package/ci/mechanism-2an-workflow-obligations.test.js +201 -0
  8. package/docs/workflows/environment-detection.md +15 -7
  9. package/environment-detection/SKILL.md +15 -16
  10. package/harness-everything/SKILL.md +1 -1
  11. package/harness-everything/scripts/kernel-router.js +14 -3
  12. package/harness-everything/scripts/self-heal.js +30 -8
  13. package/hooks/scripts/lib/workflow-obligations.js +316 -0
  14. package/hooks/scripts/lib/workflow-runtime.js +1 -1
  15. package/hooks/scripts/workflow-disposition.js +97 -5
  16. package/hooks/scripts/workflow-stop-gate.js +12 -4
  17. package/opencode-plugin/plugin.json +1 -1
  18. package/package.json +1 -1
  19. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  20. package/plugins/harness-everything/hooks/scripts/lib/workflow-obligations.js +316 -0
  21. package/plugins/harness-everything/hooks/scripts/lib/workflow-runtime.js +1 -1
  22. package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +97 -5
  23. package/plugins/harness-everything/hooks/scripts/workflow-stop-gate.js +12 -4
  24. package/plugins/harness-everything/plugin.json +1 -1
  25. package/plugins/harness-everything/skills/environment-detection/SKILL.md +15 -16
  26. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  27. package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +14 -3
  28. package/plugins/harness-everything/skills/harness-everything/scripts/self-heal.js +30 -8
@@ -10,7 +10,7 @@
10
10
  "name": "harness-everything",
11
11
  "source": "./",
12
12
  "description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
13
- "version": "0.16.5",
13
+ "version": "0.17.0",
14
14
  "author": {
15
15
  "name": "Miya Daniel"
16
16
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harness-everything",
3
3
  "description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
4
- "version": "0.16.5",
4
+ "version": "0.17.0",
5
5
  "author": {
6
6
  "name": "Miya Daniel"
7
7
  },
package/CHANGELOG.md CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [0.17.0](https://github.com/dyphn1/Harness-everything/compare/v0.16.6...v0.17.0) (2026-09-21)
6
+
7
+ ### Features
8
+
9
+ * **router:** expose obligation resolution contract ([767f8ef](https://github.com/dyphn1/Harness-everything/commit/767f8ef20b97fe8da2dcbe11a49f00fd03d0976b))
10
+ * **router:** retain task shape for post-decomposition composition ([cff0e48](https://github.com/dyphn1/Harness-everything/commit/cff0e489cdb5cd8a80b48370ab9905e508eac4e7))
11
+ * **router:** surface requirements-first planning ([3ad82ae](https://github.com/dyphn1/Harness-everything/commit/3ad82aedbae7943b66cf686b05a685d247f8c0b0))
12
+ * **workflow:** add non-Fable obligation contract ([d31bb0e](https://github.com/dyphn1/Harness-everything/commit/d31bb0e3b997a597692ae0af8741d5963bddc3aa))
13
+ * **workflow:** add planning transition ([5f5855b](https://github.com/dyphn1/Harness-everything/commit/5f5855bb2219efcc6a0964319324e7d8215a775d))
14
+ * **workflow:** confirm recomposed post-decomposition plan ([a5b2698](https://github.com/dyphn1/Harness-everything/commit/a5b26989da664c8f9e4c276210ba23fc67e2aeda))
15
+ * **workflow:** decompose intent before start ([d1aba6d](https://github.com/dyphn1/Harness-everything/commit/d1aba6d01a2e3e260fc79b996b7e754fa3cd53e8))
16
+ * **workflow:** expose obligation controller command ([4235144](https://github.com/dyphn1/Harness-everything/commit/42351443f315c130b302e120f382929f84cf5896))
17
+ * **workflow:** keep unresolved obligations visible ([926dc2b](https://github.com/dyphn1/Harness-everything/commit/926dc2bb900b5891a3f217989216eeb5fc1917fd))
18
+ * **workflow:** materialize and resolve obligations ([3cc25ad](https://github.com/dyphn1/Harness-everything/commit/3cc25ad18fa6325a6fd93b69749a7dadb605e099))
19
+ * **workflow:** recompose topology after requirement decomposition ([bbb3ed7](https://github.com/dyphn1/Harness-everything/commit/bbb3ed7ca6bf9cfd24b8089428db0eb6b6cd52c0))
20
+ * **workflow:** report unresolved planning obligations ([0c088d7](https://github.com/dyphn1/Harness-everything/commit/0c088d7cc7bf2c75631bf9b9b56dcd8b5851600a))
21
+
22
+ ### Bug Fixes
23
+
24
+ * **workflow:** keep composition pre-execution ([a22e1de](https://github.com/dyphn1/Harness-everything/commit/a22e1de876bcc6a1bc36a6c679c2dcbcac1ce672))
25
+ * **workflow:** keep recomposed pending execution unresolved ([f29672a](https://github.com/dyphn1/Harness-everything/commit/f29672a55d2b764a8e0dd909cd700af851bda415))
26
+ * **workflow:** preserve decomposition before replan ([8400383](https://github.com/dyphn1/Harness-everything/commit/840038349cb11636152f68be560961f75a9859f2))
27
+
28
+ ## [0.16.6](https://github.com/dyphn1/Harness-everything/compare/v0.16.5...v0.16.6) (2026-09-21)
29
+
30
+ ### Bug Fixes
31
+
32
+ * **environment-detection:** keep discovery read-only ([#214](https://github.com/dyphn1/Harness-everything/issues/214)) ([f6fa7ec](https://github.com/dyphn1/Harness-everything/commit/f6fa7ec57fb95072dce1177ee4e93718b77b21ef))
33
+
5
34
  ## [0.16.5](https://github.com/dyphn1/Harness-everything/compare/v0.16.4...v0.16.5) (2026-09-21)
6
35
 
7
36
  ### Bug Fixes
@@ -188,8 +188,18 @@ try {
188
188
  prompt: 'Fix this checkout bug with a regression test',
189
189
  });
190
190
  check(routed.status === 0, '#169 large-workspace Tier-2 fixture routes normally');
191
+ const requirements = JSON.stringify([
192
+ { id: 'req-fingerprint', summary: 'Exercise large-workspace shell admission', acceptance: 'the shell call remains available above the fingerprint observation cap' },
193
+ ]);
194
+ const planned = node('hooks/scripts/workflow-disposition.js', undefined, [
195
+ 'plan', '--session-id', sessionId,
196
+ '--requirements-json', requirements,
197
+ '--strategy', 'iterative-single',
198
+ '--evidence', 'single bounded runtime regression uses the iterative workflow',
199
+ ]);
200
+ check(planned.status === 0, '#169 large-workspace Tier-2 fixture resolves requirements/workflow planning');
191
201
  const started = node('hooks/scripts/workflow-disposition.js', undefined, ['start', '--session-id', sessionId]);
192
- check(started.status === 0, '#169 large-workspace Tier-2 fixture starts normally');
202
+ check(started.status === 0, '#169 large-workspace Tier-2 fixture starts after planning');
193
203
 
194
204
  const admitted = node('hooks/scripts/workflow-gate.js', {
195
205
  ...payload,
@@ -45,6 +45,34 @@ try {
45
45
  });
46
46
  assert.strictEqual(selfHeal.status, 0, `packaged self-heal --check failed:\n${describe(selfHeal)}`);
47
47
 
48
+ // Public OpenAI skills-only submissions intentionally omit the plugin-root
49
+ // installer/runtime. The read-only audit must degrade safely instead of
50
+ // searching arbitrary parent/cache paths or failing skill execution.
51
+ const skillsOnlyRoot = path.join(tempRoot, 'skills-only');
52
+ const skillsOnlyWorkspace = path.join(tempRoot, 'skills-only-workspace');
53
+ fs.cpSync(path.join(pluginRoot, 'skills'), path.join(skillsOnlyRoot, 'skills'), { recursive: true });
54
+ fs.mkdirSync(path.join(skillsOnlyWorkspace, '.git'), { recursive: true });
55
+ fs.writeFileSync(path.join(skillsOnlyWorkspace, 'AGENTS.md'), '# skills-only workspace\n', 'utf8');
56
+ const skillsOnlyAgentsBefore = fs.readFileSync(path.join(skillsOnlyWorkspace, 'AGENTS.md'), 'utf8');
57
+ const skillsOnlyCheck = runNode(
58
+ path.join(skillsOnlyRoot, 'skills', 'harness-everything', 'scripts', 'self-heal.js'),
59
+ ['--check'],
60
+ {
61
+ cwd: skillsOnlyWorkspace,
62
+ env: {
63
+ HARNESS_STATE_HOME: path.join(tempRoot, 'skills-only-state'),
64
+ HARNESS_WORKSPACE_ROOT: skillsOnlyWorkspace,
65
+ },
66
+ }
67
+ );
68
+ assert.strictEqual(skillsOnlyCheck.status, 0, `skills-only self-heal --check failed:\n${describe(skillsOnlyCheck)}`);
69
+ assert.match(describe(skillsOnlyCheck), /Local repair runtime unavailable on this distribution surface/);
70
+ assert.strictEqual(
71
+ fs.readFileSync(path.join(skillsOnlyWorkspace, 'AGENTS.md'), 'utf8'),
72
+ skillsOnlyAgentsBefore,
73
+ 'skills-only read-only audit must not rewrite workspace integration files'
74
+ );
75
+
48
76
  const installer = runNode(path.join(pluginRoot, 'scripts', 'installer.js'), ['--codex', '--no-skills', '--yes'], {
49
77
  cwd: workspaceRoot,
50
78
  env,
@@ -0,0 +1,113 @@
1
+ 'use strict';
2
+
3
+ const assert = require('assert');
4
+ const crypto = require('crypto');
5
+ const fs = require('fs');
6
+ const os = require('os');
7
+ const path = require('path');
8
+ const { spawnSync } = require('child_process');
9
+
10
+ const ROOT = path.resolve(__dirname, '..');
11
+ const SELF_HEAL = path.join(ROOT, 'harness-everything', 'scripts', 'self-heal.js');
12
+ const SKILL = path.join(ROOT, 'environment-detection', 'SKILL.md');
13
+ const OPENCODE = path.join(ROOT, 'opencode-plugin', 'index.mjs');
14
+
15
+ function write(root, relative, content) {
16
+ const target = path.join(root, relative);
17
+ fs.mkdirSync(path.dirname(target), { recursive: true });
18
+ fs.writeFileSync(target, content, 'utf8');
19
+ return target;
20
+ }
21
+
22
+ function treeDigest(root) {
23
+ const hash = crypto.createHash('sha256');
24
+ function walk(dir, relative = '') {
25
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
26
+ const rel = relative ? `${relative}/${entry.name}` : entry.name;
27
+ const full = path.join(dir, entry.name);
28
+ hash.update(`${entry.isDirectory() ? 'D' : 'F'}:${rel}\0`);
29
+ if (entry.isDirectory()) walk(full, rel);
30
+ else hash.update(fs.readFileSync(full));
31
+ }
32
+ }
33
+ walk(root);
34
+ return hash.digest('hex');
35
+ }
36
+
37
+ function run(workspace, home, args) {
38
+ const env = {
39
+ ...process.env,
40
+ HARNESS_WORKSPACE_ROOT: workspace,
41
+ HARNESS_STATE_HOME: path.join(home, 'state'),
42
+ HOME: home,
43
+ USERPROFILE: home,
44
+ APPDATA: path.join(home, 'AppData', 'Roaming'),
45
+ VSCODE_USER_PROMPTS_FOLDER: path.join(home, 'vscode-prompts'),
46
+ };
47
+ for (const key of ['CLAUDE', 'CLAUDECODE', 'CLAUDE_CODE', 'CURSOR_SANDBOX', 'GITHUB_COPILOT_CHAT', 'COPILOT_AGENT', 'AI_AGENT']) {
48
+ delete env[key];
49
+ }
50
+ return spawnSync(process.execPath, [SELF_HEAL, ...args], {
51
+ cwd: workspace,
52
+ env,
53
+ encoding: 'utf8',
54
+ });
55
+ }
56
+
57
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-213-readonly-'));
58
+ try {
59
+ const workspace = path.join(tempRoot, 'workspace');
60
+ const home = path.join(tempRoot, 'home');
61
+ fs.mkdirSync(path.join(workspace, '.git'), { recursive: true });
62
+ fs.mkdirSync(home, { recursive: true });
63
+
64
+ // Seed all six installer/self-heal adapter surfaces with user-owned content.
65
+ write(workspace, '.claude/settings.json', JSON.stringify({ customSetting: true, hooks: {} }, null, 2));
66
+ write(workspace, '.cursorrules', 'user cursor rule\n');
67
+ write(workspace, '.github/copilot-instructions.md', 'user copilot instruction\n');
68
+ write(workspace, 'AGENTS.md', 'user codex instruction\n');
69
+ write(workspace, '.continue/rules/user.md', '# user continue rule\n');
70
+ write(workspace, '.hermes.md', 'user hermes context\n');
71
+
72
+ const beforeAudit = treeDigest(workspace);
73
+ const audit = run(workspace, home, ['--check']);
74
+ assert.strictEqual(audit.status, 0, `read-only audit failed:\n${audit.stdout}\n${audit.stderr}`);
75
+ assert.strictEqual(treeDigest(workspace), beforeAudit, '--check changed the workspace');
76
+ for (const label of ['Claude Code hooks', 'Cursor rules', 'Copilot instructions', 'Codex instructions', 'Continue.dev rules', 'Hermes Agent instructions']) {
77
+ assert.ok(audit.stdout.includes(label), `audit omitted ${label}`);
78
+ }
79
+
80
+ // Explicit repair remains available, preserves user-owned content, and is
81
+ // idempotent on the second run.
82
+ const repair = run(workspace, home, []);
83
+ assert.strictEqual(repair.status, 0, `explicit repair failed:\n${repair.stdout}\n${repair.stderr}`);
84
+ assert.strictEqual(JSON.parse(fs.readFileSync(path.join(workspace, '.claude/settings.json'), 'utf8')).customSetting, true);
85
+ assert.match(fs.readFileSync(path.join(workspace, '.cursorrules'), 'utf8'), /user cursor rule/);
86
+ assert.match(fs.readFileSync(path.join(workspace, '.github', 'copilot-instructions.md'), 'utf8'), /user copilot instruction/);
87
+ assert.match(fs.readFileSync(path.join(workspace, 'AGENTS.md'), 'utf8'), /user codex instruction/);
88
+ assert.match(fs.readFileSync(path.join(workspace, '.hermes.md'), 'utf8'), /user hermes context/);
89
+ assert.strictEqual(fs.readFileSync(path.join(workspace, '.continue', 'rules', 'user.md'), 'utf8'), '# user continue rule\n');
90
+
91
+ const afterRepair = treeDigest(workspace);
92
+ const secondRepair = run(workspace, home, []);
93
+ assert.strictEqual(secondRepair.status, 0, `second repair failed:\n${secondRepair.stdout}\n${secondRepair.stderr}`);
94
+ assert.strictEqual(treeDigest(workspace), afterRepair, 'explicit repair is not idempotent');
95
+
96
+ // The routed skill contract must keep default detection observational and
97
+ // use a portable sibling-skill path rather than a repository/cache root.
98
+ const skill = fs.readFileSync(SKILL, 'utf8');
99
+ assert.ok(skill.includes('self-heal.js" --check'));
100
+ assert.doesNotMatch(skill, /<skills-repo-root>/);
101
+ assert.match(skill, /Only explicit install\/setup\/repair intent may omit/);
102
+ assert.match(skill, /OpenCode has its own plugin runtime/);
103
+ assert.match(skill, /skills-only surfaces may lack repair/);
104
+
105
+ // OpenCode is a separate runtime surface; its session/plugin startup must
106
+ // not import or invoke the six-adapter installer/self-heal path implicitly.
107
+ const opencode = fs.readFileSync(OPENCODE, 'utf8');
108
+ assert.doesNotMatch(opencode, /self-heal\.js|scripts[\\/]installer\.js/);
109
+
110
+ console.log('Issue #213 verified: detection is read-only across six adapters, repair is explicit/idempotent, and OpenCode stays separate.');
111
+ } finally {
112
+ fs.rmSync(tempRoot, { recursive: true, force: true });
113
+ }
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+ const { spawnSync } = require('child_process');
8
+
9
+ const ROOT = path.resolve(__dirname, '..');
10
+ const stateHome = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-issue85-obligations-'));
11
+ process.env.HARNESS_STATE_HOME = stateHome;
12
+ process.env.HARNESS_WORKSPACE_ROOT = ROOT;
13
+
14
+ const { getSessionDir } = require(path.join(ROOT, 'hooks/scripts/lib/harness-state.js'));
15
+ const kernel = path.join(ROOT, 'harness-everything/scripts/kernel-router.js');
16
+ const controller = path.join(ROOT, 'hooks/scripts/workflow-disposition.js');
17
+ const stopGate = path.join(ROOT, 'hooks/scripts/workflow-stop-gate.js');
18
+ const workflowGate = path.join(ROOT, 'hooks/scripts/workflow-gate.js');
19
+ const statePersist = path.join(ROOT, 'hooks/scripts/state-persist.js');
20
+
21
+ let failed = 0;
22
+ function check(condition, message) {
23
+ if (condition) console.log(' PASS ' + message);
24
+ else { console.error(' FAIL ' + message); failed++; }
25
+ }
26
+ function run(script, args = [], input = null) {
27
+ return spawnSync(process.execPath, [script, ...args], {
28
+ cwd: ROOT,
29
+ encoding: 'utf8',
30
+ input: input === null ? undefined : JSON.stringify(input),
31
+ env: { ...process.env, HARNESS_STATE_HOME: stateHome, HARNESS_WORKSPACE_ROOT: ROOT },
32
+ });
33
+ }
34
+ function readJson(file) { return JSON.parse(fs.readFileSync(file, 'utf8')); }
35
+
36
+ console.log('=== Issue #85 requirements-first workflow contract ===');
37
+
38
+ const sessionId = 'issue85-tier2';
39
+ const payload = { session_id: sessionId, cwd: ROOT, prompt: 'Fix this checkout bug and add a regression test.' };
40
+ const routed = run(kernel, [], payload);
41
+ check(routed.status === 0, 'Tier 2 route persists successfully');
42
+
43
+ const sessionDir = getSessionDir(ROOT, sessionId, payload);
44
+ const workflowFile = path.join(sessionDir, 'workflow-run.json');
45
+ const obligationFile = path.join(sessionDir, 'workflow-obligations.json');
46
+ let workflow = readJson(workflowFile);
47
+ let obligations = readJson(obligationFile);
48
+ check(workflow.tier === 'tier2' && workflow.strategy === 'iterative-single', 'Tier 2 receives a preliminary iterative-single route');
49
+ check(workflow.state === 'pending', 'selected Tier 2 workflow begins pending');
50
+ check(obligations.phase === 'planning', 'Tier 2 immediately materializes a planning contract');
51
+ check(obligations.obligations.map(item => item.id).join(',') === 'decompose,compose', 'planning starts with decompose then compose');
52
+ check(obligations.requirements.length === 0, 'requirements begin empty instead of being invented by the router');
53
+ check(obligations.workflowSelection.candidateStrategy === 'iterative-single', 'router topology is recorded only as candidate strategy');
54
+
55
+ const prematureStart = run(controller, ['start', '--session-id', sessionId]);
56
+ check(prematureStart.status !== 0, 'workflow start refuses to pretend planning is complete');
57
+ workflow = readJson(workflowFile);
58
+ check(workflow.state === 'pending', 'failed start leaves workflow pending');
59
+
60
+ const mutationReminder = run(workflowGate, [], {
61
+ session_id: sessionId, cwd: ROOT, hook_event_name: 'PreToolUse',
62
+ tool_name: 'Write', tool_input: { file_path: path.join(ROOT, 'tmp-issue85.txt'), content: 'x' },
63
+ });
64
+ check(mutationReminder.status === 0, 'unresolved planning does not hard-block ordinary tools');
65
+
66
+ const beforePlanStop = run(stopGate, [], { session_id: sessionId, cwd: ROOT, hook_event_name: 'Stop' });
67
+ workflow = readJson(workflowFile);
68
+ check(beforePlanStop.status === 0, 'Stop remains fail-open while planning is unresolved');
69
+ check(workflow.state === 'pending', 'unresolved planning cannot silently become satisfied');
70
+ check((workflow.unresolved || []).includes('decompose:pending'), 'decomposition gap is visible');
71
+ check((workflow.unresolved || []).includes('compose:pending'), 'workflow-composition gap is visible');
72
+
73
+ const requirements = JSON.stringify([
74
+ { id: 'req-fix', summary: 'Correct the checkout failure', acceptance: 'the failing scenario succeeds', suggestedSkills: ['tdd'] },
75
+ { id: 'req-regression', summary: 'Protect the behavior with a regression test', acceptance: 'the test fails before the fix and passes after it', suggestedSkills: ['tdd', 'verification-loop'] },
76
+ ]);
77
+ const planned = run(controller, [
78
+ 'plan', '--session-id', sessionId,
79
+ '--requirements-json', requirements,
80
+ '--strategy', 'iterative-single',
81
+ '--evidence', 'two dependent requirements fit a bounded single-agent test/fix/verify loop',
82
+ ]);
83
+ check(planned.status === 0, 'requirements and workflow selection are recorded before execution');
84
+ obligations = readJson(obligationFile);
85
+ check(obligations.requirements.map(item => item.id).join(',') === 'req-fix,req-regression', 'requirement fragments are machine-readable');
86
+ check(obligations.workflowSelection.disposition === 'confirmed', 'workflow choice is explicitly confirmed after decomposition');
87
+ check(obligations.obligations.find(item => item.id === 'decompose').status === 'pass', 'decompose obligation resolves from requirement fragments');
88
+ check(obligations.obligations.find(item => item.id === 'compose').status === 'pass', 'compose obligation resolves from workflow-selection evidence');
89
+
90
+ const started = run(controller, ['start', '--session-id', sessionId]);
91
+ check(started.status === 0, 'start activates the confirmed non-Fable workflow');
92
+ workflow = readJson(workflowFile);
93
+ obligations = readJson(obligationFile);
94
+ check(workflow.state === 'running', 'confirmed workflow becomes running');
95
+ check(obligations.phase === 'execution', 'planning contract advances to execution phase');
96
+ check(obligations.obligations.map(item => item.id).join(',') === 'decompose,compose,execute,verify', 'execution adds execute/verify without losing planning evidence');
97
+
98
+ check(run(controller, ['obligation', '--session-id', sessionId, '--obligation-id', 'execute', '--disposition', 'escaped', '--reason-code', 'workflow-uncovered-scope', '--scope', 'optional unrelated cleanup', '--evidence', 'outside requested bug-fix requirements']).status === 0,
99
+ 'execution obligation accepts bounded evidence-backed escape');
100
+ check(run(controller, ['obligation', '--session-id', sessionId, '--obligation-id', 'verify', '--disposition', 'escaped', '--reason-code', 'workflow-uncovered-scope', '--scope', 'tests', '--evidence', 'attempt']).status !== 0,
101
+ 'verification obligation cannot be escaped');
102
+ check(run(controller, ['obligation', '--session-id', sessionId, '--obligation-id', 'verify', '--disposition', 'blocked', '--reason-code', 'verification-environment-unavailable', '--evidence', 'fixture intentionally blocks verification']).status === 0,
103
+ 'verification obligation can record explicit blocked evidence');
104
+
105
+ const blockedStop = run(stopGate, [], { session_id: sessionId, cwd: ROOT, hook_event_name: 'Stop' });
106
+ workflow = readJson(workflowFile);
107
+ check(blockedStop.status === 0, 'blocked obligation still reminds without trapping Stop');
108
+ check(workflow.state === 'running', 'blocked obligation keeps workflow unresolved rather than satisfied');
109
+ check((workflow.unresolved || []).includes('verify:blocked'), 'blocked verification remains visible');
110
+
111
+ check(run(controller, ['obligation', '--session-id', sessionId, '--obligation-id', 'verify', '--disposition', 'pass', '--evidence', 'npm test and focused regression passed']).status === 0,
112
+ 'verification can later resolve to pass with evidence');
113
+ check(run(statePersist, [], {
114
+ session_id: sessionId, cwd: ROOT, hook_event_name: 'PostToolUse',
115
+ tool_name: 'Bash', tool_input: { command: 'npm test' },
116
+ tool_response: { stdout: 'pass', stderr: '', exitCode: 0 },
117
+ }).status === 0, 'objective verification milestone is observed after the earlier mutation');
118
+ const resolvedStop = run(stopGate, [], { session_id: sessionId, cwd: ROOT, hook_event_name: 'Stop' });
119
+ workflow = readJson(workflowFile);
120
+ check(resolvedStop.status === 0, 'resolved workflow Stop remains fail-open');
121
+ check(workflow.state === 'satisfied', 'planning + execution + verification allow satisfied');
122
+ check((workflow.unresolved || []).length === 0, 'satisfied workflow has no unresolved obligations');
123
+
124
+ const mismatchSession = 'issue85-replan';
125
+ const mismatchPayload = { session_id: mismatchSession, cwd: ROOT, prompt: 'Fix this checkout bug and add a regression test.' };
126
+ check(run(kernel, [], mismatchPayload).status === 0, 'second Tier 2 route persists for replan discriminator');
127
+ const mismatchDir = getSessionDir(ROOT, mismatchSession, mismatchPayload);
128
+ const mismatchPlan = run(controller, [
129
+ 'plan', '--session-id', mismatchSession,
130
+ '--requirements-json', requirements,
131
+ '--strategy', 'fable-staged',
132
+ '--evidence', 'decomposition indicates dependent multi-stage work instead',
133
+ ]);
134
+ check(mismatchPlan.status === 0, 'post-decomposition workflow can be recomposed through the router contract');
135
+ const mismatchObligations = readJson(path.join(mismatchDir, 'workflow-obligations.json'));
136
+ const mismatchWorkflow = readJson(path.join(mismatchDir, 'workflow-run.json'));
137
+ check(mismatchObligations.workflowSelection.disposition === 'confirmed', 'recomposed workflow receives an explicit confirmed disposition');
138
+ check(mismatchObligations.workflowSelection.requestedStrategy === 'fable-staged' &&
139
+ mismatchObligations.workflowSelection.confirmedStrategy === 'fable-staged',
140
+ 'requested and router-confirmed post-decomposition strategy are preserved');
141
+ check(mismatchWorkflow.pendingPlan?.strategy === 'fable-staged', 'recomposed plan becomes the pending execution contract');
142
+ const mismatchStop = run(stopGate, [], { session_id: mismatchSession, cwd: ROOT, hook_event_name: 'Stop' });
143
+ const mismatchAfterStop = readJson(path.join(mismatchDir, 'workflow-run.json'));
144
+ check(mismatchStop.status === 0 && mismatchAfterStop.state === 'pending' &&
145
+ (mismatchAfterStop.unresolved || []).includes('execution:not-started'),
146
+ 'recomposed workflow cannot become satisfied before its selected topology starts');
147
+ const mismatchStart = run(controller, ['start', '--session-id', mismatchSession]);
148
+ check(mismatchStart.status !== 0 && /workflow-stages\.json/.test(mismatchStart.stderr || ''),
149
+ 'recomposed Fable topology proceeds to its native stage-contract requirement');
150
+ check(run(workflowGate, [], {
151
+ session_id: mismatchSession, cwd: ROOT, hook_event_name: 'PreToolUse',
152
+ tool_name: 'Write', tool_input: { file_path: path.join(ROOT, 'tmp-replan.txt'), content: 'x' },
153
+ }).status === 0, 'recomposed planning state still does not recreate a tool lock');
154
+
155
+ const prohibitedSession = 'issue85-prohibited-recompose';
156
+ const prohibitedPayload = { session_id: prohibitedSession, cwd: ROOT, prompt: 'Fix this checkout bug with a regression test. Do not use fable.' };
157
+ check(run(kernel, [], prohibitedPayload).status === 0, 'prohibition fixture routes successfully');
158
+ const prohibitedRequirements = JSON.stringify([
159
+ { id: 'req-safe', summary: 'Resolve the bug under the user constraints', acceptance: 'the fix is verified without prohibited topology' },
160
+ ]);
161
+ const prohibitedPlan = run(controller, [
162
+ 'plan', '--session-id', prohibitedSession,
163
+ '--requirements-json', prohibitedRequirements,
164
+ '--strategy', 'fable-staged',
165
+ '--evidence', 'candidate considered after decomposition',
166
+ ]);
167
+ check(prohibitedPlan.status === 0, 'prohibited post-decomposition choice is evaluated by router policy');
168
+ const prohibitedDir = getSessionDir(ROOT, prohibitedSession, prohibitedPayload);
169
+ const prohibitedObligations = readJson(path.join(prohibitedDir, 'workflow-obligations.json'));
170
+ check(prohibitedObligations.workflowSelection.disposition === 'blocked', 'original user prohibition survives recomposition');
171
+ check(prohibitedObligations.obligations.find(item => item.id === 'decompose').status === 'pass' &&
172
+ prohibitedObligations.obligations.find(item => item.id === 'compose').status === 'blocked',
173
+ 'requirements remain valid while only the prohibited workflow choice stays unresolved');
174
+
175
+ const fableSession = 'issue85-fable';
176
+ const fablePayload = { session_id: fableSession, cwd: ROOT, prompt: 'Refactor the entire authentication architecture in dependent stages.' };
177
+ check(run(kernel, [], fablePayload).status === 0, 'Tier 3 Fable candidate persists successfully');
178
+ const fableDir = getSessionDir(ROOT, fableSession, fablePayload);
179
+ const fableWorkflow = readJson(path.join(fableDir, 'workflow-run.json'));
180
+ const fablePlanning = readJson(path.join(fableDir, 'workflow-obligations.json'));
181
+ check(fableWorkflow.strategy === 'fable-staged', 'Tier 3 keeps Fable as preliminary candidate');
182
+ check(fablePlanning.obligations.map(item => item.id).join(',') === 'decompose,compose', 'Fable also requires requirements/workflow planning before stage execution');
183
+ check(run(controller, ['start', '--session-id', fableSession]).status !== 0, 'Fable cannot enter stage execution before planning contract resolves');
184
+
185
+ const tier1Session = 'issue85-tier1';
186
+ const tier1Payload = { session_id: tier1Session, cwd: ROOT, prompt: 'Update one README typo' };
187
+ check(run(kernel, [], tier1Payload).status === 0, 'Tier 1 route persists successfully');
188
+ const tier1Dir = getSessionDir(ROOT, tier1Session, tier1Payload);
189
+ const tier1WorkflowFile = path.join(tier1Dir, 'workflow-run.json');
190
+ check(!fs.existsSync(path.join(tier1Dir, 'workflow-obligations.json')), 'clear bounded Tier 1 intent has no decomposition overhead');
191
+ check(run(stopGate, [], { session_id: tier1Session, cwd: ROOT, hook_event_name: 'Stop' }).status === 0, 'Tier 1 direct Stop remains lightweight');
192
+ const tier1Workflow = readJson(tier1WorkflowFile);
193
+ check(tier1Workflow.strategy === 'direct-single' && tier1Workflow.state === 'satisfied', 'Tier 1 remains a minimal direct path');
194
+
195
+ const runtimeSource = fs.readFileSync(path.join(ROOT, 'hooks/scripts/lib/workflow-runtime.js'), 'utf8');
196
+ check(!runtimeSource.includes('.mutation-probes.lock'), '#190 mutation-probe lock remains retired');
197
+ check(!runtimeSource.includes('budget-exhausted'), '#190 hard budget state remains retired');
198
+
199
+ fs.rmSync(stateHome, { recursive: true, force: true });
200
+ console.log('\n' + (failed === 0 ? 'PASS' : 'FAIL') + ': issue #85 requirements-first workflow contract (' + failed + ' failures)');
201
+ process.exit(failed === 0 ? 0 : 1);
@@ -13,8 +13,12 @@ graph TD
13
13
  PreflightOk -->|yes| ParseOut[Parse OS shell PATH and CLIs]
14
14
  PreflightOk -->|no| FallbackProbe[Inspect environment_info and probe shell]
15
15
  FallbackProbe --> ParseOut
16
- ParseOut --> SelfHealAudit[Audit harness touchpoints with self-heal.js]
17
- SelfHealAudit --> AdoptSyntax[Adopt shell-specific syntax]
16
+ ParseOut --> SelfHealAudit[Run self-heal.js --check when supported]
17
+ SelfHealAudit --> Drift{Integration drift found?}
18
+ Drift -->|ordinary detection| ReportOnly[Report only; do not mutate]
19
+ Drift -->|explicit repair intent| Repair[Run supported platform repair]
20
+ ReportOnly --> AdoptSyntax[Adopt shell-specific syntax]
21
+ Repair --> AdoptSyntax
18
22
  AdoptSyntax --> DoneCtx([Session context aligned])
19
23
  ```
20
24
 
@@ -48,10 +52,11 @@ graph LR
48
52
  A session starts on Windows with PowerShell as the active shell. The agent needs to list files and check Node availability before running tests.
49
53
 
50
54
  1. Run `node "<skill-dir>/scripts/preflight.js"` to detect Windows + PowerShell + available CLIs.
51
- 2. Adopt PowerShell syntax for paths and env vars instead of Git Bash assumptions.
52
- 3. If `preflight.js` cannot run, inspect `<environment_info>` and probe with minimal echo/env commands, then adopt syntax.
53
- 4. Stay inside `process.cwd()`; ignore other workspace, history, or temp paths shown by the IDE.
54
- 5. If the same aligned command still fails three times, stop retrying the syntax and trigger `zoom-out`.
55
+ 2. Where the local Harness runtime exists, run self-heal in `--check` mode only; report drift without changing hooks or advisory files.
56
+ 3. If the helper/runtime is absent (for example a skills-only distribution), report that the local repair surface is unavailable and continue without searching parent/cache paths.
57
+ 4. Only an explicit install/setup/repair request may invoke mutating self-heal. OpenCode keeps its separate plugin installation/runtime boundary.
58
+ 5. Adopt PowerShell syntax for paths and env vars instead of Git Bash assumptions. If `preflight.js` cannot run, inspect `<environment_info>` and probe minimally.
59
+ 6. Stay inside `process.cwd()`; if the same aligned command still fails three times, trigger `zoom-out`.
55
60
 
56
61
  ```mermaid
57
62
  graph TD
@@ -68,6 +73,9 @@ graph TD
68
73
  - [ ] Detection ran at session start via `environment-detection/scripts/preflight.js`, with `<environment_info>` inspection and probing used only as fallback
69
74
  - [ ] Adopted syntax matches detected shell: Git Bash uses `/`, `$VAR`, Unix cmds without `dir`/`del`; PowerShell uses `$env:VAR` and cmdlets; CMD uses `\`, `%VAR%`, `dir`/`del`/`copy` without Unix cmds
70
75
  - [ ] All commands stayed inside `process.cwd()`; no commands ran outside the current project root
71
- - [ ] No toolchain installation was attempted; scope stayed to detection, alignment, and touchpoint repair
76
+ - [ ] Ordinary detection changed no workspace integration files; touchpoint inspection used `--check` or safely skipped an unavailable local runtime
77
+ - [ ] Mutating self-heal ran only for explicit install/setup/repair intent and only on a supported local platform surface
78
+ - [ ] OpenCode was kept on its separate plugin runtime; skills-only distributions did not assume a local installer
79
+ - [ ] No toolchain installation was attempted; scope stayed to detection and alignment
72
80
  - [ ] After three failures of the same command, execution stopped blind retry and triggered `zoom-out`
73
81
  - [ ] Shell detail followed `environment-detection/references/shell-syntax-rules.md` where needed
@@ -4,38 +4,37 @@ description: Use at session start (Discover phase) to detect and align with the
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.4.0
7
+ version: 0.16.6
8
8
  ---
9
9
 
10
10
  # Environment Detection & Shell Alignment
11
11
 
12
- Think > Try > Summarize > Record: detect before executing.
13
-
14
12
  ## 📋 Skill Contract
15
13
 
16
14
  | Component | Specification |
17
15
  | :--- | :--- |
18
16
  | **Trigger / Input** | Session start or complex terminal sequences. |
19
- | **Expected Output** | Detected OS, shell, PATH via script or heuristics. |
20
- | **State Mutations** | Session context adopts path/env-var syntax. |
21
- | **Enforcement Gate** | Preflight; fall back to `<environment_info>` inspection and probing. |
17
+ | **Expected Output** | OS, shell, PATH, available CLIs. |
18
+ | **State Mutations** | Read-only by default; explicit repair may update Harness files. |
19
+ | **Enforcement Gate** | Preflight; fall back to `<environment_info>` and minimal probes. |
22
20
 
23
- Boundary: operate solely within `process.cwd()`; ignore other workspaces, history, or temp paths surfaced by the IDE — never run commands outside the current project root.
21
+ Boundary: stay inside `process.cwd()`. Detection never writes integrations or searches parent/cache paths for another runtime.
24
22
 
25
23
  ## Workflow
26
24
 
27
- 1. Preflight from this skill's own directory; parse OS, shell, available CLIs: `node "<this-skill-dir>/scripts/preflight.js"`
28
- 2. Self-heal harness touchpoints (idempotent): `node "<skills-repo-root>/harness-everything/scripts/self-heal.js"` — audits `<workspace>/.claude/settings.json`, `<workspace>/.cursorrules`, `<workspace>/.github/copilot-instructions.md`, `<workspace>/AGENTS.md`.
29
- 3. Adopt syntax: Git Bash → forward `/`, `$VAR`, Unix cmds (`ls`, `rm -rf`), never `dir`/`del`/PowerShell · PowerShell → `$env:VAR`, cmdlets · CMD → `\`, `%VAR%`, `dir`/`del`/`copy`, never Unix.
30
- 4. On failure don't blindly retry — suspect wrong-shell syntax. Three failures → trigger `zoom-out`.
25
+ 1. Run `node "<this-skill-dir>/scripts/preflight.js"`; detect OS, shell, CLIs.
26
+ 2. Where supported, audit read-only: `node "<this-skill-dir>/../harness-everything/scripts/self-heal.js" --check`. If unavailable, report and continue.
27
+ 3. Only explicit install/setup/repair intent may omit `--check`. Repair covers Claude, Codex, Copilot, Cursor, Continue, Hermes. OpenCode has its own plugin runtime; skills-only surfaces may lack repair.
28
+ 4. Adopt shell syntax: Git Bash → `/`, `$VAR`, Unix commands; PowerShell → `$env:VAR`, cmdlets; CMD → `\`, `%VAR%`, Windows commands. Three repeated failures → `zoom-out`.
31
29
 
32
30
  Deep dive: <this-skill-dir>/references/shell-syntax-rules.md
33
31
 
34
32
  ## USE FOR:
35
- - session-start OS/shell/toolchain detection
36
- - per-terminal command/path/env-var alignment
37
- - repairing missing harness touchpoints
33
+ - session-start environment detection
34
+ - shell/path/toolchain alignment
35
+ - read-only audit or explicit repair
38
36
 
39
37
  ## DO NOT USE FOR:
40
- - operating outside the current workspace
41
- - installing toolchains from scratch
38
+ - implicit repair during detection
39
+ - six-adapter repair on OpenCode/skills-only surfaces
40
+ - work outside the workspace or toolchain installation
@@ -4,7 +4,7 @@ description: "Route software/project work through the Harness kernel: classify T
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.16.5
7
+ version: 0.17.0
8
8
  ---
9
9
 
10
10
  # Harness Everything
@@ -44,7 +44,8 @@ function isHostNotificationPrompt(prompt) {
44
44
  return /^<task-notification[\s>][\s\S]*<\/task-notification>\s*$/i.test(text);
45
45
  }
46
46
 
47
- function persistWorkflow(plan, payload, prompt) {
47
+ function persistWorkflow(contract, payload, prompt) {
48
+ const plan = contract && contract.workflowPlan;
48
49
  if (!payload || !(payload.session_id || payload.sessionId)) return null;
49
50
  const { runtime, hooksRoot } = loadRuntime();
50
51
  const context = runtime.loadWorkflow(payload);
@@ -83,6 +84,7 @@ function persistWorkflow(plan, payload, prompt) {
83
84
  verification: plan.verification,
84
85
  mutationIsolation: plan.mutationIsolation || { required: false },
85
86
  workflowPlan: plan,
87
+ taskShape: contract.taskShape || null,
86
88
  state: selected ? 'pending' : 'deferred',
87
89
  planningWarnings: plan.fallback?.disposition === 'none' ? [] : [...(plan.fallback?.reasonCodes || [])],
88
90
  runId: null,
@@ -94,6 +96,10 @@ function persistWorkflow(plan, payload, prompt) {
94
96
  hardBoundaries: ['rule-of-3-reflection', 'user-host-permission'],
95
97
  },
96
98
  };
99
+ if (selected && ['tier2', 'tier3'].includes(plan.tier)) {
100
+ const { initializePlanningContract } = require(path.join(hooksRoot, 'lib/workflow-obligations'));
101
+ initializePlanningContract(context, plan);
102
+ }
97
103
  const memoryCapability = issueMemoryCapability(context.workflow);
98
104
  runtime.saveWorkflow(context);
99
105
  return { ...context, hooksRoot, retained: false, memoryCapability };
@@ -115,7 +121,7 @@ function run(raw) {
115
121
  let persisted = null;
116
122
  let failure = null;
117
123
  try {
118
- persisted = persistWorkflow(plan, payload, prompt);
124
+ persisted = persistWorkflow(result?.contract || null, payload, prompt);
119
125
  if (persisted?.retained && persisted.workflow) plan = persisted.workflow.workflowPlan;
120
126
  } catch (error) { failure = error; }
121
127
  if (hostNotification && !persisted?.workflow) {
@@ -146,7 +152,12 @@ function run(raw) {
146
152
  console.log(' - Memory write disposition: ' + (persisted.workflow.workflowPlan.memory?.write || 'none'));
147
153
  if (persisted.memoryCapability) console.log(' - Memory capability (single-use, workflow/session-bound): ' + persisted.memoryCapability);
148
154
  console.log(' - Stage specification: ' + path.join(persisted.sessionDir, 'workflow-stages.json'));
149
- console.log(' - Enter/replan: node "' + controller + '" start --session-id "' + persisted.sessionId + '"');
155
+ if (plan.strategySelection === 'selected' && ['tier2', 'tier3'].includes(plan.tier)) {
156
+ console.log(' - Planning contract: ' + path.join(persisted.sessionDir, 'workflow-obligations.json'));
157
+ console.log(' - Requirements first: decompose intent into requirement fragments, then confirm the smallest sufficient workflow.');
158
+ 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>"');
159
+ }
160
+ console.log(' - Enter selected workflow after planning: node "' + controller + '" start --session-id "' + persisted.sessionId + '"');
150
161
  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>"');
151
162
  } else {
152
163
  console.log(' - Runtime state was not persisted; reminders may be less precise, but execution remains available.');