harness-everything 0.5.0 → 0.5.1

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.
@@ -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.5.0",
13
+ "version": "0.5.1",
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.5.0",
4
+ "version": "0.5.1",
5
5
  "author": {
6
6
  "name": "Miya Daniel"
7
7
  },
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [0.5.1](https://github.com/dyphn1/Harness-everything/compare/v0.5.0...v0.5.1) (2026-09-16)
6
+
7
+ ### Bug Fixes
8
+
9
+ * **rule-of-3:** parse Claude failure hook payload ([83d3106](https://github.com/dyphn1/Harness-everything/commit/83d310653d19fd72f597d119eface1c3baf5a7bd))
10
+ * **rule-of-3:** wire Claude failure hook to tracker ([11b3e9e](https://github.com/dyphn1/Harness-everything/commit/11b3e9e7a08b0394d2dd3dac78bd1464ae5881df))
11
+
5
12
  ## [0.5.0](https://github.com/dyphn1/Harness-everything/compare/v0.4.4...v0.5.0) (2026-09-16)
6
13
 
7
14
  ### Features
@@ -4,6 +4,70 @@ const helper = require('./test-helper');
4
4
 
5
5
  console.log('\n[2a] Rule of 3 circuit breaker...');
6
6
 
7
+ const hooksConfig = JSON.parse(
8
+ fs.readFileSync(path.join(helper.root, 'hooks', 'hooks.json'), 'utf8')
9
+ );
10
+ const failureTrackerHook = (hooksConfig.hooks.PostToolUseFailure || [])
11
+ .find(entry => entry.id === 'harness:post-failure:rule-of-3-tracker');
12
+ helper.check(
13
+ '2a-claude-wiring. PostToolUseFailure invokes the Rule-of-3 tracker',
14
+ failureTrackerHook &&
15
+ failureTrackerHook.matcher === 'Bash|PowerShell' &&
16
+ failureTrackerHook.hooks?.some(hook => hook.command.includes('rule-of-3-tracker.js')),
17
+ `Hook=${JSON.stringify(failureTrackerHook)}`
18
+ );
19
+
20
+ const claudeFailurePayload = {
21
+ hook_event_name: 'PostToolUseFailure',
22
+ tool_name: 'Bash',
23
+ tool_input: { command: 'node -e "process.exit(1)"' },
24
+ error: 'Exit code 1\ncommand failed',
25
+ session_id: helper.SESSION_ID,
26
+ };
27
+ const failureRuns = [1, 2, 3].map(() =>
28
+ helper.runHook('rule-of-3-tracker.js', claudeFailurePayload)
29
+ );
30
+ const trackedFailureState = helper.readState('rule-of-3-state.json');
31
+ helper.check(
32
+ '2a-claude-failure. PostToolUseFailure error payload increments the same signature to threshold',
33
+ failureRuns.every(result => result.code === 0) &&
34
+ trackedFailureState.count === 3 &&
35
+ trackedFailureState.threshold === 3 &&
36
+ trackedFailureState.lastHash &&
37
+ trackedFailureState.zoomOutResolved === false,
38
+ `Runs=${JSON.stringify(failureRuns)}, state=${JSON.stringify(trackedFailureState)}`
39
+ );
40
+
41
+ const claudeTripResult = helper.runHook('rule-of-3.js', {
42
+ hook_event_name: 'PreToolUse',
43
+ tool_name: 'Bash',
44
+ tool_input: { command: 'echo retry' },
45
+ session_id: helper.SESSION_ID,
46
+ });
47
+ helper.check(
48
+ '2a-claude-trip. Tracked Claude failures block the next mutation',
49
+ claudeTripResult.code === 2 &&
50
+ claudeTripResult.stderr.includes('RULE OF 3 CIRCUIT BREAKER TRIGGERED'),
51
+ `Got exit=${claudeTripResult.code}, stderr="${claudeTripResult.stderr.slice(0, 200)}"`
52
+ );
53
+
54
+ const successResult = helper.runHook('rule-of-3-tracker.js', {
55
+ hook_event_name: 'PostToolUse',
56
+ tool_name: 'Bash',
57
+ tool_input: { command: 'echo success' },
58
+ tool_response: { stdout: 'success', stderr: '', exitCode: 0 },
59
+ session_id: helper.SESSION_ID,
60
+ });
61
+ const resetState = helper.readState('rule-of-3-state.json');
62
+ helper.check(
63
+ '2a-claude-success. Confirmed PostToolUse success keeps the existing reset behavior',
64
+ successResult.code === 0 &&
65
+ resetState.count === 0 &&
66
+ resetState.zoomOutResolved === true &&
67
+ resetState.zoomOutCycles === 0,
68
+ `Got exit=${successResult.code}, state=${JSON.stringify(resetState)}`
69
+ );
70
+
7
71
  helper.writeState('rule-of-3-state.json', { count: 3, lastHash: 'mech-test', zoomOutResolved: false });
8
72
  const tripResult = helper.runHook('rule-of-3.js', { session_id: helper.SESSION_ID });
9
73
  helper.check(
package/hooks/hooks.json CHANGED
@@ -194,6 +194,17 @@
194
194
  ],
195
195
  "description": "Record that an approved action-gate payload executed but failed",
196
196
  "id": "harness:post-failure:action-gate-audit"
197
+ },
198
+ {
199
+ "matcher": "Bash|PowerShell",
200
+ "hooks": [
201
+ {
202
+ "type": "command",
203
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/scripts/rule-of-3-tracker.js\""
204
+ }
205
+ ],
206
+ "description": "Track failed tool error signatures for Rule of 3",
207
+ "id": "harness:post-failure:rule-of-3-tracker"
197
208
  }
198
209
  ]
199
210
  }
@@ -4,7 +4,7 @@ const path = require('path');
4
4
  const crypto = require('crypto');
5
5
  const { getWorkspaceRoot, getSessionDir } = require('./lib/harness-state');
6
6
 
7
- // PostToolUse hooks in Claude Code typically receive the tool output via stdin
7
+ // PostToolUse/PostToolUseFailure hooks in Claude Code receive their payload via stdin
8
8
  let inputData = '';
9
9
 
10
10
  process.stdin.on('data', chunk => {
@@ -14,18 +14,23 @@ process.stdin.on('data', chunk => {
14
14
  process.stdin.on('end', () => {
15
15
  try {
16
16
  const payload = JSON.parse(inputData);
17
+ const hookEventName = payload.hook_event_name || payload.hookEventName || '';
18
+ const isFailureEvent = hookEventName === 'PostToolUseFailure';
17
19
  const toolResponse = payload.tool_response || {};
18
20
  const stdout = toolResponse.stdout ?? payload.stdout ?? '';
19
21
  const stderr = toolResponse.stderr ?? payload.stderr ?? '';
20
- const rawExitCode = toolResponse.exitCode ?? toolResponse.exit_code ?? payload.exitCode;
22
+ const failureText = typeof payload.error === 'string' ? payload.error : '';
23
+ const rawExitCode = toolResponse.exitCode ?? toolResponse.exit_code ?? payload.exitCode ?? payload.exit_code;
21
24
  const exitCode = typeof rawExitCode === 'number' ? rawExitCode : undefined;
22
25
 
23
26
  const explicitFailure = exitCode !== undefined && exitCode !== 0;
24
27
  const explicitSuccess = exitCode === 0;
25
28
  const stderrSignal = typeof stderr === 'string' && stderr.trim().length > 0;
26
29
  const looksLikeError = /\b(error|fail|failed|failure|exception|fatal|panic|traceback|denied|refused|cannot|unable)\b/i.test(stderr);
27
- const isFailure = explicitFailure || (exitCode === undefined && stderrSignal && looksLikeError);
28
- const errorText = (stderrSignal ? stderr : stdout) || '';
30
+ const isFailure = isFailureEvent || explicitFailure || (exitCode === undefined && stderrSignal && looksLikeError);
31
+ const errorText = isFailureEvent
32
+ ? (failureText || (stderrSignal ? stderr : stdout) || '')
33
+ : ((stderrSignal ? stderr : stdout) || failureText || '');
29
34
 
30
35
  const root = getWorkspaceRoot(payload);
31
36
  const stateFile = path.join(getSessionDir(root, payload.session_id || payload.sessionId), 'rule-of-3-state.json');
@@ -108,7 +113,7 @@ process.stdin.on('end', () => {
108
113
  state.lastFailureAt = Date.now();
109
114
 
110
115
  fs.writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf8');
111
- } else if (explicitSuccess) {
116
+ } else if (!isFailureEvent && explicitSuccess) {
112
117
  // Only reset on a *confirmed* zero exit code, not merely "not a failure".
113
118
  if (state.count > 0 || state.zoomOutCycles > 0) {
114
119
  state.count = 0;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harness-enforcement",
3
3
  "description": "Repository inventory for the opencode enforcement plugin and its standalone helper",
4
- "version": "0.5.0",
4
+ "version": "0.5.1",
5
5
  "runtime": "index.mjs",
6
6
  "hooks": {
7
7
  "verify": "hooks/verify.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Harness OS: An Orchestrated Agent Operating System for AI-driven Development",
5
5
  "main": "harness-everything/scripts/bootstrap.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
5
5
  "author": {
6
6
  "name": "Miya Daniel",
@@ -4,7 +4,7 @@ const path = require('path');
4
4
  const crypto = require('crypto');
5
5
  const { getWorkspaceRoot, getSessionDir } = require('./lib/harness-state');
6
6
 
7
- // PostToolUse hooks in Claude Code typically receive the tool output via stdin
7
+ // PostToolUse/PostToolUseFailure hooks in Claude Code receive their payload via stdin
8
8
  let inputData = '';
9
9
 
10
10
  process.stdin.on('data', chunk => {
@@ -14,18 +14,23 @@ process.stdin.on('data', chunk => {
14
14
  process.stdin.on('end', () => {
15
15
  try {
16
16
  const payload = JSON.parse(inputData);
17
+ const hookEventName = payload.hook_event_name || payload.hookEventName || '';
18
+ const isFailureEvent = hookEventName === 'PostToolUseFailure';
17
19
  const toolResponse = payload.tool_response || {};
18
20
  const stdout = toolResponse.stdout ?? payload.stdout ?? '';
19
21
  const stderr = toolResponse.stderr ?? payload.stderr ?? '';
20
- const rawExitCode = toolResponse.exitCode ?? toolResponse.exit_code ?? payload.exitCode;
22
+ const failureText = typeof payload.error === 'string' ? payload.error : '';
23
+ const rawExitCode = toolResponse.exitCode ?? toolResponse.exit_code ?? payload.exitCode ?? payload.exit_code;
21
24
  const exitCode = typeof rawExitCode === 'number' ? rawExitCode : undefined;
22
25
 
23
26
  const explicitFailure = exitCode !== undefined && exitCode !== 0;
24
27
  const explicitSuccess = exitCode === 0;
25
28
  const stderrSignal = typeof stderr === 'string' && stderr.trim().length > 0;
26
29
  const looksLikeError = /\b(error|fail|failed|failure|exception|fatal|panic|traceback|denied|refused|cannot|unable)\b/i.test(stderr);
27
- const isFailure = explicitFailure || (exitCode === undefined && stderrSignal && looksLikeError);
28
- const errorText = (stderrSignal ? stderr : stdout) || '';
30
+ const isFailure = isFailureEvent || explicitFailure || (exitCode === undefined && stderrSignal && looksLikeError);
31
+ const errorText = isFailureEvent
32
+ ? (failureText || (stderrSignal ? stderr : stdout) || '')
33
+ : ((stderrSignal ? stderr : stdout) || failureText || '');
29
34
 
30
35
  const root = getWorkspaceRoot(payload);
31
36
  const stateFile = path.join(getSessionDir(root, payload.session_id || payload.sessionId), 'rule-of-3-state.json');
@@ -108,7 +113,7 @@ process.stdin.on('end', () => {
108
113
  state.lastFailureAt = Date.now();
109
114
 
110
115
  fs.writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf8');
111
- } else if (explicitSuccess) {
116
+ } else if (!isFailureEvent && explicitSuccess) {
112
117
  // Only reset on a *confirmed* zero exit code, not merely "not a failure".
113
118
  if (state.count > 0 || state.zoomOutCycles > 0) {
114
119
  state.count = 0;
@@ -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.5.0",
4
+ "version": "0.5.1",
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",