harness-everything 0.11.1 → 0.11.3
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +13 -0
- package/ci/mechanism-2ah-workflow-lifecycle.test.js +22 -1
- package/ci/mechanism-2f-stop-gate.test.js +53 -0
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/scripts/kernel-router.js +0 -1
- package/hooks/scripts/lib/workflow-runtime.js +9 -1
- package/hooks/scripts/workflow-stop-gate.js +9 -1
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/scripts/lib/workflow-runtime.js +9 -1
- package/plugins/harness-everything/hooks/scripts/workflow-stop-gate.js +9 -1
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +0 -1
|
@@ -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.11.
|
|
13
|
+
"version": "0.11.3",
|
|
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.11.
|
|
4
|
+
"version": "0.11.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Miya Daniel"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.11.3](https://github.com/dyphn1/Harness-everything/compare/v0.11.2...v0.11.3) (2026-09-18)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
* **workflow:** Resolve verification-after-edit-missing without a host exit code ([e5dae03](https://github.com/dyphn1/Harness-everything/commit/e5dae03799cca594ac3e4f9d0facca6209dec3c8)), closes [#153](https://github.com/dyphn1/Harness-everything/issues/153)
|
|
10
|
+
|
|
11
|
+
## [0.11.2](https://github.com/dyphn1/Harness-everything/compare/v0.11.1...v0.11.2) (2026-09-18)
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **workflow:** classify major workflow from active plan ([b1eae14](https://github.com/dyphn1/Harness-everything/commit/b1eae14ca5157c73ba9cf7c0f2e4b0ed122e9f77))
|
|
16
|
+
* **workflow:** keep pending plan isolation inert ([8b522c2](https://github.com/dyphn1/Harness-everything/commit/8b522c26589e2140bd0971365ad0569c18da6d3f))
|
|
17
|
+
|
|
5
18
|
## [0.11.1](https://github.com/dyphn1/Harness-everything/compare/v0.11.0...v0.11.1) (2026-09-18)
|
|
6
19
|
|
|
7
20
|
### Bug Fixes
|
|
@@ -16,6 +16,7 @@ fs.mkdirSync(repo);
|
|
|
16
16
|
const env = { ...process.env, HARNESS_STATE_HOME: path.join(temp, 'state'), HARNESS_WORKSPACE_ROOT: repo };
|
|
17
17
|
process.env.HARNESS_STATE_HOME = env.HARNESS_STATE_HOME;
|
|
18
18
|
const state = require(path.join(runtimeRoot, 'hooks/scripts/lib/harness-state'));
|
|
19
|
+
const workflowRuntime = require(path.join(runtimeRoot, 'hooks/scripts/lib/workflow-runtime'));
|
|
19
20
|
const sessionId = 'workflow-lifecycle';
|
|
20
21
|
const payload = { session_id: sessionId, cwd: repo };
|
|
21
22
|
const sessionDir = state.getSessionDir(repo, sessionId);
|
|
@@ -138,6 +139,23 @@ try {
|
|
|
138
139
|
check(gate('Write', { file_path: path.join(linked, 'src.js') }).status === 2 && stop().status === 2, 'partial JSON state cannot bypass workflow gates');
|
|
139
140
|
fs.unlinkSync(file);
|
|
140
141
|
check(route('Fix this checkout bug with a regression test').status === 0 && read(file).strategy === 'iterative-single', 'bounded fix selects iterative lifecycle');
|
|
142
|
+
const activeTier2 = read(file);
|
|
143
|
+
check(activeTier2.tier === 'tier2' && activeTier2.mutationIsolation?.required === false && !workflowRuntime.isMajorWorkflow(activeTier2), 'Tier 2 iterative workflow starts non-major');
|
|
144
|
+
check(route('Refactor the entire repository architecture in dependent stages.').status === 0, 'stronger Tier 3 route is retained for explicit replan');
|
|
145
|
+
const queuedTier3 = read(file);
|
|
146
|
+
check(queuedTier3.state === 'blocked' && queuedTier3.pendingPlan?.tier === 'tier3' && queuedTier3.pendingPlan?.mutationIsolation?.required === true, 'stronger Tier 3 plan is queued as pending only');
|
|
147
|
+
check(queuedTier3.workflowPlan?.tier === 'tier2' && queuedTier3.strategy === 'iterative-single' &&
|
|
148
|
+
queuedTier3.mutationIsolation?.required === false, 'pending Tier 3 plan does not overwrite active Tier 2 fields');
|
|
149
|
+
check(!workflowRuntime.isMajorWorkflow(queuedTier3), 'major-workflow classification follows the authoritative active plan, not pending metadata');
|
|
150
|
+
write(spec, stages);
|
|
151
|
+
check(control('start').status === 0, 'explicit replan activates queued Tier 3 plan');
|
|
152
|
+
const promotedTier3 = read(file);
|
|
153
|
+
check(promotedTier3.workflowPlan?.tier === 'tier3' && promotedTier3.strategy.startsWith('fable-') &&
|
|
154
|
+
promotedTier3.mutationIsolation?.required === true && !promotedTier3.pendingPlan, 'pending Tier 3 isolation becomes active atomically on start');
|
|
155
|
+
check(workflowRuntime.isMajorWorkflow(promotedTier3), 'major-workflow classification flips only after explicit activation');
|
|
156
|
+
check(gate('Write', { file_path: path.join(repo, 'premature-primary.js') }, repo).status === 2, 'activated Tier 3 plan still blocks primary-tree mutation');
|
|
157
|
+
fs.unlinkSync(file);
|
|
158
|
+
check(route('Fix this checkout bug with a regression test').status === 0 && read(file).strategy === 'iterative-single', 'fresh bounded fix returns to Tier 2 iterative lifecycle');
|
|
141
159
|
const iterationLimit = read(file).workflowPlan.limits.maxIterations;
|
|
142
160
|
check(Number.isInteger(iterationLimit) && iterationLimit > 0, 'iterative route exposes a numeric iteration budget');
|
|
143
161
|
for (let i = 0; i < iterationLimit; i++) {
|
|
@@ -157,8 +175,11 @@ try {
|
|
|
157
175
|
check(stop().status === 2, 'direct/iterative route cannot complete unverified mutation');
|
|
158
176
|
check(stop({ stop_hook_active: true }).status === 0 && read(file).state === 'blocked', 'iterative retry reports incomplete state');
|
|
159
177
|
check(control('start').status === 0, 'blocked iterative route can resume without a Fable stage map');
|
|
178
|
+
// Issue #153: a host is not guaranteed to report a numeric exit code at
|
|
179
|
+
// all (state-persist.js's own isFailed logic already treats "no exit code
|
|
180
|
+
// and no stderr" as success), so this must resolve rather than deadlock.
|
|
160
181
|
node('hooks/scripts/state-persist.js', { ...payload, tool_name: 'Bash', tool_input: { command: 'npm test' }, tool_response: { stdout: 'no code' } });
|
|
161
|
-
check(stop().status ===
|
|
182
|
+
check(stop().status === 0 && read(file).state === 'satisfied', 'verification without a host-reported numeric exit status still resolves completion');
|
|
162
183
|
node('hooks/scripts/state-persist.js', { ...payload, tool_name: 'Bash', tool_input: { command: 'npm test' }, tool_response: { exitCode: 0, stdout: 'passed' } });
|
|
163
184
|
check(stop().status === 0 && read(file).state === 'satisfied', 'observed iterative verification resolves completion');
|
|
164
185
|
console.log(`PASS: workflow lifecycle (${passed} assertions)`);
|
|
@@ -20,4 +20,57 @@ helper.check(
|
|
|
20
20
|
);
|
|
21
21
|
fs.unlinkSync(dirtyFile);
|
|
22
22
|
|
|
23
|
+
console.log('\n[2f] workflow-stop-gate (iterative-single) verification signal...');
|
|
24
|
+
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
|
|
27
|
+
// Bug reproduction (issue #153): a verify-classified command that genuinely
|
|
28
|
+
// succeeded but whose host never reported a numeric exit code (observed on
|
|
29
|
+
// at least one live host - state-persist.js writes lastVerifyExitCode: null
|
|
30
|
+
// for any non-failing command when the PostToolUse payload has no numeric
|
|
31
|
+
// exitCode) must not be treated as unverified.
|
|
32
|
+
helper.writeState('workflow-run.json', {
|
|
33
|
+
schemaVersion: 1, sessionId: helper.SESSION_ID, strategy: 'iterative-single',
|
|
34
|
+
state: 'running', lastMutationAt: now - 5000,
|
|
35
|
+
});
|
|
36
|
+
helper.writeState('handoff-state.json', {
|
|
37
|
+
status: 'idle', lastEditAt: now - 5000, lastVerifyAt: now, lastVerifyExitCode: null,
|
|
38
|
+
});
|
|
39
|
+
const hostExitCodeUnavailable = helper.runHook('workflow-stop-gate.js', { session_id: helper.SESSION_ID });
|
|
40
|
+
helper.check(
|
|
41
|
+
'2f. workflow-stop-gate resolves when verify ran after edit even without a numeric exit code',
|
|
42
|
+
hostExitCodeUnavailable.code === 0 && helper.readState('workflow-run.json').state === 'satisfied',
|
|
43
|
+
`Got exit=${hostExitCodeUnavailable.code}, stderr="${hostExitCodeUnavailable.stderr.slice(0, 300)}"`
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Regression guard: explicit exit 0 (host does report it) must still resolve.
|
|
47
|
+
helper.writeState('workflow-run.json', {
|
|
48
|
+
schemaVersion: 1, sessionId: helper.SESSION_ID, strategy: 'iterative-single',
|
|
49
|
+
state: 'running', lastMutationAt: now - 5000,
|
|
50
|
+
});
|
|
51
|
+
helper.writeState('handoff-state.json', {
|
|
52
|
+
status: 'idle', lastEditAt: now - 5000, lastVerifyAt: now, lastVerifyExitCode: 0,
|
|
53
|
+
});
|
|
54
|
+
const hostExitCodeReported = helper.runHook('workflow-stop-gate.js', { session_id: helper.SESSION_ID });
|
|
55
|
+
helper.check(
|
|
56
|
+
'2f. workflow-stop-gate still resolves when the host does report exit code 0',
|
|
57
|
+
hostExitCodeReported.code === 0 && helper.readState('workflow-run.json').state === 'satisfied',
|
|
58
|
+
`Got exit=${hostExitCodeReported.code}`
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Negative control: no verification ran after the edit at all -> stays blocked.
|
|
62
|
+
helper.writeState('workflow-run.json', {
|
|
63
|
+
schemaVersion: 1, sessionId: helper.SESSION_ID, strategy: 'iterative-single',
|
|
64
|
+
state: 'running', lastMutationAt: now,
|
|
65
|
+
});
|
|
66
|
+
helper.writeState('handoff-state.json', {
|
|
67
|
+
status: 'idle', lastEditAt: now, lastVerifyAt: now - 5000, lastVerifyExitCode: null,
|
|
68
|
+
});
|
|
69
|
+
const staleVerify = helper.runHook('workflow-stop-gate.js', { session_id: helper.SESSION_ID });
|
|
70
|
+
helper.check(
|
|
71
|
+
'2f. workflow-stop-gate still blocks when no verification ran after the edit',
|
|
72
|
+
staleVerify.code === 2 && staleVerify.stderr.includes('verification-after-edit-missing'),
|
|
73
|
+
`Got exit=${staleVerify.code}, stderr="${staleVerify.stderr.slice(0, 300)}"`
|
|
74
|
+
);
|
|
75
|
+
|
|
23
76
|
helper.finish();
|
|
@@ -47,7 +47,6 @@ function persistWorkflow(plan, payload, prompt) {
|
|
|
47
47
|
context.workflow.state = 'blocked';
|
|
48
48
|
context.workflow.pendingPlan = plan;
|
|
49
49
|
context.workflow.blockReason = 'stronger-route-requires-replan';
|
|
50
|
-
context.workflow.mutationIsolation = plan.mutationIsolation;
|
|
51
50
|
}
|
|
52
51
|
context.workflow.lastPromptHash = crypto.createHash('sha256').update(prompt).digest('hex').slice(0, 24);
|
|
53
52
|
const memoryCapability = issueMemoryCapability(context.workflow);
|
|
@@ -9,8 +9,16 @@ const OPEN_STATES = new Set(['pending', 'active', 'running', 'failed', 'blocked'
|
|
|
9
9
|
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
10
10
|
const WORKFLOW_CONTROLLER_COMMANDS = new Set(['start', 'revision', 'reset-budget', 'escape', 'block']);
|
|
11
11
|
|
|
12
|
+
function activeWorkflowPlan(workflow) {
|
|
13
|
+
if (!workflow) return null;
|
|
14
|
+
const plan = workflow.workflowPlan;
|
|
15
|
+
if (plan && typeof plan === 'object' && plan.strategy === workflow.strategy) return plan;
|
|
16
|
+
return workflow;
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
function isMajorWorkflow(workflow) {
|
|
13
|
-
|
|
20
|
+
const active = activeWorkflowPlan(workflow);
|
|
21
|
+
return Boolean(active && (active.mutationIsolation?.required || active.tier === 'tier3' || String(active.strategy || '').startsWith('fable-')));
|
|
14
22
|
}
|
|
15
23
|
|
|
16
24
|
function loadWorkflow(payload) {
|
|
@@ -21,7 +21,15 @@ function decide(payload) {
|
|
|
21
21
|
} else {
|
|
22
22
|
const handoff = readJson(path.join(sessionDir, 'handoff-state.json'));
|
|
23
23
|
const lastEdit = Math.max(workflow.lastMutationAt || 0, handoff?.lastEditAt || 0);
|
|
24
|
-
|
|
24
|
+
// state-persist.js only ever records lastVerifyAt/lastVerifyExitCode from
|
|
25
|
+
// its `!isFailed` branch, so reaching this timestamp already proves the
|
|
26
|
+
// command succeeded. Do not additionally require lastVerifyExitCode===0:
|
|
27
|
+
// hosts are not guaranteed to report a numeric exit code at all (some
|
|
28
|
+
// PostToolUse payloads omit it entirely), in which case state-persist.js
|
|
29
|
+
// correctly records success with lastVerifyExitCode: null, and requiring
|
|
30
|
+
// a literal 0 here would make this gate permanently unresolvable on that
|
|
31
|
+
// host after any edit.
|
|
32
|
+
if (lastEdit && (handoff?.lastVerifyAt || 0) < lastEdit) unresolved = ['verification-after-edit-missing'];
|
|
25
33
|
}
|
|
26
34
|
if (unresolved.length) {
|
|
27
35
|
workflow.unresolved = unresolved;
|
package/package.json
CHANGED
|
@@ -9,8 +9,16 @@ const OPEN_STATES = new Set(['pending', 'active', 'running', 'failed', 'blocked'
|
|
|
9
9
|
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
10
10
|
const WORKFLOW_CONTROLLER_COMMANDS = new Set(['start', 'revision', 'reset-budget', 'escape', 'block']);
|
|
11
11
|
|
|
12
|
+
function activeWorkflowPlan(workflow) {
|
|
13
|
+
if (!workflow) return null;
|
|
14
|
+
const plan = workflow.workflowPlan;
|
|
15
|
+
if (plan && typeof plan === 'object' && plan.strategy === workflow.strategy) return plan;
|
|
16
|
+
return workflow;
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
function isMajorWorkflow(workflow) {
|
|
13
|
-
|
|
20
|
+
const active = activeWorkflowPlan(workflow);
|
|
21
|
+
return Boolean(active && (active.mutationIsolation?.required || active.tier === 'tier3' || String(active.strategy || '').startsWith('fable-')));
|
|
14
22
|
}
|
|
15
23
|
|
|
16
24
|
function loadWorkflow(payload) {
|
|
@@ -21,7 +21,15 @@ function decide(payload) {
|
|
|
21
21
|
} else {
|
|
22
22
|
const handoff = readJson(path.join(sessionDir, 'handoff-state.json'));
|
|
23
23
|
const lastEdit = Math.max(workflow.lastMutationAt || 0, handoff?.lastEditAt || 0);
|
|
24
|
-
|
|
24
|
+
// state-persist.js only ever records lastVerifyAt/lastVerifyExitCode from
|
|
25
|
+
// its `!isFailed` branch, so reaching this timestamp already proves the
|
|
26
|
+
// command succeeded. Do not additionally require lastVerifyExitCode===0:
|
|
27
|
+
// hosts are not guaranteed to report a numeric exit code at all (some
|
|
28
|
+
// PostToolUse payloads omit it entirely), in which case state-persist.js
|
|
29
|
+
// correctly records success with lastVerifyExitCode: null, and requiring
|
|
30
|
+
// a literal 0 here would make this gate permanently unresolvable on that
|
|
31
|
+
// host after any edit.
|
|
32
|
+
if (lastEdit && (handoff?.lastVerifyAt || 0) < lastEdit) unresolved = ['verification-after-edit-missing'];
|
|
25
33
|
}
|
|
26
34
|
if (unresolved.length) {
|
|
27
35
|
workflow.unresolved = unresolved;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "harness-everything",
|
|
4
|
-
"version": "0.11.
|
|
4
|
+
"version": "0.11.3",
|
|
5
5
|
"description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Miya Daniel",
|
|
@@ -47,7 +47,6 @@ function persistWorkflow(plan, payload, prompt) {
|
|
|
47
47
|
context.workflow.state = 'blocked';
|
|
48
48
|
context.workflow.pendingPlan = plan;
|
|
49
49
|
context.workflow.blockReason = 'stronger-route-requires-replan';
|
|
50
|
-
context.workflow.mutationIsolation = plan.mutationIsolation;
|
|
51
50
|
}
|
|
52
51
|
context.workflow.lastPromptHash = crypto.createHash('sha256').update(prompt).digest('hex').slice(0, 24);
|
|
53
52
|
const memoryCapability = issueMemoryCapability(context.workflow);
|