harness-everything 0.2.0 โ 0.2.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.
- package/CHANGELOG.md +7 -0
- package/build-multi-agent-system/SKILL.md +68 -68
- package/create-agent-launcher/SKILL.md +48 -48
- package/docs/workflows/build-multi-agent-system.md +62 -62
- package/docs/workflows/create-agent-launcher.md +62 -62
- package/docs/workflows/environment-detection.md +63 -63
- package/docs/workflows/eval-harness.md +57 -57
- package/docs/workflows/fable-discipline.md +59 -59
- package/docs/workflows/fable-mode.md +32 -32
- package/docs/workflows/git-commit.md +62 -62
- package/docs/workflows/grill-me.md +62 -62
- package/docs/workflows/grill-with-docs.md +62 -62
- package/docs/workflows/improve-codebase-architecture.md +60 -60
- package/docs/workflows/repo-docs.md +59 -59
- package/docs/workflows/rewrite-commits.md +60 -60
- package/docs/workflows/security-review.md +58 -58
- package/docs/workflows/self-evolve.md +59 -59
- package/docs/workflows/tdd.md +39 -39
- package/docs/workflows/todo-driven-workflow.md +36 -36
- package/docs/workflows/using-git-worktrees.md +63 -63
- package/docs/workflows/verification-loop.md +69 -69
- package/docs/workflows/verify-before-claim.md +60 -60
- package/docs/workflows/zoom-out.md +62 -62
- package/environment-detection/SKILL.md +77 -77
- package/eval-framework/behavioral-test.js +105 -105
- package/eval-framework/mechanism-test.js +251 -251
- package/eval-harness/SKILL.md +59 -59
- package/fable-discipline/SKILL.md +38 -38
- package/fable-mode/SKILL.md +51 -51
- package/git-commit/SKILL.md +55 -55
- package/grill-me/SKILL.md +36 -36
- package/grill-with-docs/SKILL.md +99 -99
- package/harness-everything/SKILL.md +141 -141
- package/harness-everything/examples/README.md +9 -9
- package/harness-everything/examples/todo-workflow.md +22 -22
- package/harness-everything/scripts/todo-cli.js +157 -157
- package/harness-everything/scripts/verify-gate.js +31 -31
- package/hooks/scripts/atomic-commit-check.js +68 -68
- package/hooks/scripts/context-compact.js +114 -114
- package/hooks/scripts/lib/harness-state.js +29 -3
- package/hooks/scripts/lib/platforms/claude.js +42 -42
- package/hooks/scripts/lib/platforms/codex.js +46 -46
- package/hooks/scripts/lib/platforms/continue.js +41 -41
- package/hooks/scripts/lib/platforms/copilot.js +41 -41
- package/hooks/scripts/lib/platforms/cursor.js +41 -41
- package/hooks/scripts/rule-of-3.js +176 -176
- package/improve-codebase-architecture/SKILL.md +44 -44
- package/install-cognitive-os/SKILL.md +95 -95
- package/package.json +1 -1
- package/repo-docs/SKILL.md +45 -45
- package/rewrite-commits/SKILL.md +32 -32
- package/scripts/installer.js +649 -649
- package/scripts/lib/advisory-text.js +163 -163
- package/scripts/lib/claude-hooks.js +71 -71
- package/scripts/lib/gitignore.js +80 -58
- package/scripts/lib/manifest.js +82 -82
- package/scripts/lib/prompts.js +241 -241
- package/scripts/lib/skills.js +172 -172
- package/scripts/lib/workspace.js +28 -28
- package/security-review/SKILL.md +510 -510
- package/self-evolve/SKILL.md +54 -54
- package/skill-creator/SKILL.md +91 -91
- package/skill-creator/references/testing-workflow.md +32 -32
- package/skill-style/SKILL.md +48 -48
- package/tdd/SKILL.md +53 -53
- package/todo-driven-workflow/SKILL.md +54 -54
- package/using-git-worktrees/SKILL.md +161 -161
- package/verification-loop/SKILL.md +138 -138
- package/verify-before-claim/SKILL.md +86 -86
- package/zoom-out/SKILL.md +90 -90
|
@@ -1,251 +1,251 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Harness Mechanism Test Suite (Claude Code hook contract)
|
|
4
|
-
* Automates VERIFICATION.md Section 2 (mechanism check) so it runs on every
|
|
5
|
-
* `npm test` instead of only when a human copy-pastes the recipes by hand.
|
|
6
|
-
* That manual gap is exactly what let three independent 2026-07-23 audit
|
|
7
|
-
* reports (see docs/reports/) misdiagnose working hooks as broken: they
|
|
8
|
-
* piped JSON via `echo '...' | node script.js` in a Windows Git Bash shell,
|
|
9
|
-
* which mangles stdin/TTY state and produced false negatives. This suite
|
|
10
|
-
* uses execSync's `input` option instead (the same technique already proven
|
|
11
|
-
* reliable in runner.js and behavioral-test.js) so it is a faithful,
|
|
12
|
-
* cross-platform re-run of the exact payloads VERIFICATION.md documents.
|
|
13
|
-
*
|
|
14
|
-
* Each hook resolves its state directory via detectActivePlatform(), which
|
|
15
|
-
* defaults to 'copilot' under a VS Code integrated terminal (TERM_PROGRAM=
|
|
16
|
-
* vscode) unless CLAUDE is set - so every child process below is spawned
|
|
17
|
-
* with CLAUDE=1 to force the Claude Code state path regardless of the host
|
|
18
|
-
* running this test.
|
|
19
|
-
*
|
|
20
|
-
* All state is written under a dedicated, never-real session id so this
|
|
21
|
-
* suite can never collide with or clobber an actual Claude Code session's
|
|
22
|
-
* breaker/handoff state. The session directory (and any repo-root temp
|
|
23
|
-
* files created along the way) is removed on exit, success or failure alike.
|
|
24
|
-
*/
|
|
25
|
-
const { execSync } = require('child_process');
|
|
26
|
-
const fs = require('fs');
|
|
27
|
-
const path = require('path');
|
|
28
|
-
|
|
29
|
-
process.env.CLAUDE = '1'; // so our own getSessionDir() calls resolve like the children's will
|
|
30
|
-
|
|
31
|
-
const { getWorkspaceRoot, getSessionDir } = require('../hooks/scripts/lib/harness-state');
|
|
32
|
-
|
|
33
|
-
const root = getWorkspaceRoot();
|
|
34
|
-
const hooksDir = path.join(root, 'hooks', 'scripts');
|
|
35
|
-
const SESSION_ID = '__mechanism_test__';
|
|
36
|
-
const sessionDir = getSessionDir(root, SESSION_ID);
|
|
37
|
-
|
|
38
|
-
console.log('=== ๐ง Harness Mechanism Test Suite (VERIFICATION.md ยง2) ===');
|
|
39
|
-
|
|
40
|
-
const tempFiles = [];
|
|
41
|
-
let failures = 0;
|
|
42
|
-
|
|
43
|
-
function check(name, condition, detail) {
|
|
44
|
-
if (condition) {
|
|
45
|
-
console.log(`โ
${name}`);
|
|
46
|
-
} else {
|
|
47
|
-
console.error(`โ ${name}`);
|
|
48
|
-
if (detail) console.error(` ${detail}`);
|
|
49
|
-
failures++;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function runHook(scriptName, payload, extraEnv) {
|
|
54
|
-
const scriptPath = path.join(hooksDir, scriptName);
|
|
55
|
-
try {
|
|
56
|
-
const stdout = execSync(`node "${scriptPath}"`, {
|
|
57
|
-
encoding: 'utf8',
|
|
58
|
-
input: payload === null ? '' : JSON.stringify(payload),
|
|
59
|
-
env: { ...process.env, ...extraEnv },
|
|
60
|
-
});
|
|
61
|
-
return { code: 0, stdout, stderr: '' };
|
|
62
|
-
} catch (err) {
|
|
63
|
-
return { code: err.status, stdout: err.stdout || '', stderr: err.stderr || '' };
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function writeState(file, obj) {
|
|
68
|
-
fs.writeFileSync(path.join(sessionDir, file), JSON.stringify(obj, null, 2), 'utf8');
|
|
69
|
-
}
|
|
70
|
-
function readState(file) {
|
|
71
|
-
return JSON.parse(fs.readFileSync(path.join(sessionDir, file), 'utf8'));
|
|
72
|
-
}
|
|
73
|
-
function stateExists(file) {
|
|
74
|
-
return fs.existsSync(path.join(sessionDir, file));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Registers a repo-root temp file for guaranteed cleanup even if an
|
|
78
|
-
// assertion throws or the process is killed mid-suite.
|
|
79
|
-
function tempFile(name) {
|
|
80
|
-
const p = path.join(root, name);
|
|
81
|
-
tempFiles.push(p);
|
|
82
|
-
return p;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
process.on('exit', () => {
|
|
86
|
-
for (const f of tempFiles) {
|
|
87
|
-
try { if (fs.existsSync(f)) fs.unlinkSync(f); } catch (e) { /* best-effort */ }
|
|
88
|
-
}
|
|
89
|
-
try { fs.rmSync(sessionDir, { recursive: true, force: true }); } catch (e) { /* best-effort */ }
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
// 2a. Rule of 3 circuit breaker trips, releases via zoom-out report, and
|
|
94
|
-
// hard-locks on a repeat trip of the same signature.
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
|
-
console.log('\n[2a] Rule of 3 circuit breaker...');
|
|
97
|
-
|
|
98
|
-
writeState('rule-of-3-state.json', { count: 3, lastHash: 'mech-test', zoomOutResolved: false });
|
|
99
|
-
const tripResult = runHook('rule-of-3.js', { session_id: SESSION_ID });
|
|
100
|
-
check(
|
|
101
|
-
'2a. Trips at count=3 (exit=2, CRITICAL banner)',
|
|
102
|
-
tripResult.code === 2 && tripResult.stderr.includes('RULE OF 3 CIRCUIT BREAKER TRIGGERED'),
|
|
103
|
-
`Got exit=${tripResult.code}, stderr="${tripResult.stderr.slice(0, 200)}"`
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
writeState('rule-of-3-state.json', {
|
|
107
|
-
count: 3, lastHash: 'mech-test', zoomOutResolved: false, lastFailureAt: 0, zoomOutCycles: 0,
|
|
108
|
-
});
|
|
109
|
-
fs.writeFileSync(
|
|
110
|
-
path.join(sessionDir, 'zoom-out-report.md'),
|
|
111
|
-
'## Goal\nx\n## Failed Attempts\nx\n## Verified Facts\nx\n## Diagnosis\nx\n## Decision\nRESUME: new approach\n',
|
|
112
|
-
'utf8'
|
|
113
|
-
);
|
|
114
|
-
const releaseResult = runHook('rule-of-3.js', { session_id: SESSION_ID });
|
|
115
|
-
const releasedState = readState('rule-of-3-state.json');
|
|
116
|
-
check(
|
|
117
|
-
'2a-bis. Valid zoom-out report releases the breaker (exit=0, count reset)',
|
|
118
|
-
releaseResult.code === 0 &&
|
|
119
|
-
releaseResult.stdout.includes('breaker released') &&
|
|
120
|
-
releasedState.count === 0 &&
|
|
121
|
-
releasedState.zoomOutResolved === true &&
|
|
122
|
-
releasedState.zoomOutCycles === 1,
|
|
123
|
-
`Got exit=${releaseResult.code}, state=${JSON.stringify(releasedState)}`
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
writeState('rule-of-3-state.json', {
|
|
127
|
-
count: 3, lastHash: 'mech-test', zoomOutResolved: false,
|
|
128
|
-
lastFailureAt: Date.now() + 60000, zoomOutCycles: 1,
|
|
129
|
-
});
|
|
130
|
-
const hardLockResult = runHook('rule-of-3.js', { session_id: SESSION_ID });
|
|
131
|
-
check(
|
|
132
|
-
'2a-ter. Second trip on same signature hard-locks (exit=2, repeat trip)',
|
|
133
|
-
hardLockResult.code === 2 && hardLockResult.stderr.includes('repeat trip - hard lock'),
|
|
134
|
-
`Got exit=${hardLockResult.code}, stderr="${hardLockResult.stderr.slice(0, 200)}"`
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
// ---------------------------------------------------------------------------
|
|
138
|
-
// 2b. Boundary guard blocks an oversized Read
|
|
139
|
-
// ---------------------------------------------------------------------------
|
|
140
|
-
console.log('\n[2b] Boundary guard...');
|
|
141
|
-
|
|
142
|
-
const bigFile = tempFile('.mechanism-test-big.tmp');
|
|
143
|
-
fs.writeFileSync(bigFile, 'x'.repeat(600 * 1024));
|
|
144
|
-
const boundaryResult = runHook('boundary-guard.js', { tool_name: 'Read', tool_input: { file_path: bigFile } });
|
|
145
|
-
check(
|
|
146
|
-
'2b. Blocks a 600KB Read without limit/offset (exit=2, BLOCKED)',
|
|
147
|
-
boundaryResult.code === 2 && boundaryResult.stderr.includes('[Boundary Guard] BLOCKED'),
|
|
148
|
-
`Got exit=${boundaryResult.code}, stderr="${boundaryResult.stderr.slice(0, 200)}"`
|
|
149
|
-
);
|
|
150
|
-
fs.unlinkSync(bigFile);
|
|
151
|
-
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
|
-
// 2c. State persistence (WAL) records a failure, then clears it on success
|
|
154
|
-
// ---------------------------------------------------------------------------
|
|
155
|
-
console.log('\n[2c] State persistence (WAL)...');
|
|
156
|
-
|
|
157
|
-
runHook('state-persist.js', {
|
|
158
|
-
session_id: SESSION_ID,
|
|
159
|
-
tool_name: 'Bash',
|
|
160
|
-
tool_response: { stdout: '', stderr: 'npm ERR! mech-test failure' },
|
|
161
|
-
});
|
|
162
|
-
const failedHandoff = readState('handoff-state.json');
|
|
163
|
-
check(
|
|
164
|
-
'2c. Failing Bash call is recorded as status=failed',
|
|
165
|
-
failedHandoff.status === 'failed' && failedHandoff.errorSummary.includes('mech-test failure'),
|
|
166
|
-
`Got ${JSON.stringify(failedHandoff)}`
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
runHook('state-persist.js', {
|
|
170
|
-
session_id: SESSION_ID,
|
|
171
|
-
tool_name: 'Bash',
|
|
172
|
-
tool_response: { stdout: 'ok', exitCode: 0 },
|
|
173
|
-
});
|
|
174
|
-
const resolvedHandoff = readState('handoff-state.json');
|
|
175
|
-
check(
|
|
176
|
-
'2c. A subsequent successful command clears status back to idle',
|
|
177
|
-
resolvedHandoff.status === 'idle' && !!resolvedHandoff.lastResolved,
|
|
178
|
-
`Got ${JSON.stringify(resolvedHandoff)}`
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
// ---------------------------------------------------------------------------
|
|
182
|
-
// 2d. Fact-audit reminder reaches the agent via tier-router.js stdin
|
|
183
|
-
// ---------------------------------------------------------------------------
|
|
184
|
-
console.log('\n[2d] Fact-audit reminder...');
|
|
185
|
-
|
|
186
|
-
const tierRouterPath = path.join(root, 'harness-everything', 'scripts', 'tier-router.js');
|
|
187
|
-
const factAuditOut = execSync(`node "${tierRouterPath}"`, {
|
|
188
|
-
encoding: 'utf8',
|
|
189
|
-
input: JSON.stringify({ prompt: 'what exit code does this hook use by default and is it documented' }),
|
|
190
|
-
});
|
|
191
|
-
check(
|
|
192
|
-
'2d. tier-router.js reads {"prompt":...} from stdin and emits FACT-AUDIT REMINDER',
|
|
193
|
-
factAuditOut.includes('FACT-AUDIT REMINDER'),
|
|
194
|
-
`Output was:\n${factAuditOut.slice(0, 300)}`
|
|
195
|
-
);
|
|
196
|
-
|
|
197
|
-
// ---------------------------------------------------------------------------
|
|
198
|
-
// 2e. Subagent scope guard catches an out-of-scope file change
|
|
199
|
-
// ---------------------------------------------------------------------------
|
|
200
|
-
console.log('\n[2e] Subagent scope guard...');
|
|
201
|
-
|
|
202
|
-
runHook('subagent-scope-guard.js', {
|
|
203
|
-
session_id: SESSION_ID, tool_name: 'Task', hook_event_name: 'PreToolUse', tool_input: {},
|
|
204
|
-
});
|
|
205
|
-
const scopeFile = tempFile('.mechanism-test-scope.tmp');
|
|
206
|
-
fs.writeFileSync(scopeFile, 'unexpected change');
|
|
207
|
-
const scopeResult = runHook('subagent-scope-guard.js', {
|
|
208
|
-
session_id: SESSION_ID, tool_name: 'Task', hook_event_name: 'PostToolUse', tool_input: {},
|
|
209
|
-
});
|
|
210
|
-
check(
|
|
211
|
-
'2e. Flags the newly-created file after a Task burst (exit=2, lists filename)',
|
|
212
|
-
scopeResult.code === 2 &&
|
|
213
|
-
scopeResult.stderr.includes('Subagent Scope Guard') &&
|
|
214
|
-
scopeResult.stderr.includes(path.basename(scopeFile)),
|
|
215
|
-
`Got exit=${scopeResult.code}, stderr="${scopeResult.stderr.slice(0, 300)}"`
|
|
216
|
-
);
|
|
217
|
-
fs.unlinkSync(scopeFile);
|
|
218
|
-
|
|
219
|
-
// ---------------------------------------------------------------------------
|
|
220
|
-
// 2f. Stop gate bounces an unverified-edit stop exactly once
|
|
221
|
-
// ---------------------------------------------------------------------------
|
|
222
|
-
console.log('\n[2f] Stop gate...');
|
|
223
|
-
|
|
224
|
-
writeState('handoff-state.json', { status: 'idle', lastEditAt: Date.now(), lastVerifyAt: 0 });
|
|
225
|
-
const dirtyFile = tempFile('.mechanism-test-dirty.tmp');
|
|
226
|
-
fs.writeFileSync(dirtyFile, 'dirty');
|
|
227
|
-
const firstStop = runHook('stop-gate.js', { session_id: SESSION_ID });
|
|
228
|
-
check(
|
|
229
|
-
'2f. First stop after an unverified edit bounces (exit=2)',
|
|
230
|
-
firstStop.code === 2 && firstStop.stderr.includes('[Stop Gate]'),
|
|
231
|
-
`Got exit=${firstStop.code}, stderr="${firstStop.stderr.slice(0, 200)}"`
|
|
232
|
-
);
|
|
233
|
-
const secondStop = runHook('stop-gate.js', { session_id: SESSION_ID });
|
|
234
|
-
check(
|
|
235
|
-
'2f. Same edit batch does not bounce twice (exit=0)',
|
|
236
|
-
secondStop.code === 0,
|
|
237
|
-
`Got exit=${secondStop.code}`
|
|
238
|
-
);
|
|
239
|
-
fs.unlinkSync(dirtyFile);
|
|
240
|
-
|
|
241
|
-
// ---------------------------------------------------------------------------
|
|
242
|
-
console.log('\n=================================================');
|
|
243
|
-
if (failures > 0) {
|
|
244
|
-
console.error(` โ ${failures} mechanism check(s) FAILED.`);
|
|
245
|
-
console.log('=================================================');
|
|
246
|
-
process.exit(1);
|
|
247
|
-
} else {
|
|
248
|
-
console.log(' ๐ All VERIFICATION.md ยง2 mechanism checks passed.');
|
|
249
|
-
console.log('=================================================');
|
|
250
|
-
process.exit(0);
|
|
251
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Harness Mechanism Test Suite (Claude Code hook contract)
|
|
4
|
+
* Automates VERIFICATION.md Section 2 (mechanism check) so it runs on every
|
|
5
|
+
* `npm test` instead of only when a human copy-pastes the recipes by hand.
|
|
6
|
+
* That manual gap is exactly what let three independent 2026-07-23 audit
|
|
7
|
+
* reports (see docs/reports/) misdiagnose working hooks as broken: they
|
|
8
|
+
* piped JSON via `echo '...' | node script.js` in a Windows Git Bash shell,
|
|
9
|
+
* which mangles stdin/TTY state and produced false negatives. This suite
|
|
10
|
+
* uses execSync's `input` option instead (the same technique already proven
|
|
11
|
+
* reliable in runner.js and behavioral-test.js) so it is a faithful,
|
|
12
|
+
* cross-platform re-run of the exact payloads VERIFICATION.md documents.
|
|
13
|
+
*
|
|
14
|
+
* Each hook resolves its state directory via detectActivePlatform(), which
|
|
15
|
+
* defaults to 'copilot' under a VS Code integrated terminal (TERM_PROGRAM=
|
|
16
|
+
* vscode) unless CLAUDE is set - so every child process below is spawned
|
|
17
|
+
* with CLAUDE=1 to force the Claude Code state path regardless of the host
|
|
18
|
+
* running this test.
|
|
19
|
+
*
|
|
20
|
+
* All state is written under a dedicated, never-real session id so this
|
|
21
|
+
* suite can never collide with or clobber an actual Claude Code session's
|
|
22
|
+
* breaker/handoff state. The session directory (and any repo-root temp
|
|
23
|
+
* files created along the way) is removed on exit, success or failure alike.
|
|
24
|
+
*/
|
|
25
|
+
const { execSync } = require('child_process');
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
process.env.CLAUDE = '1'; // so our own getSessionDir() calls resolve like the children's will
|
|
30
|
+
|
|
31
|
+
const { getWorkspaceRoot, getSessionDir } = require('../hooks/scripts/lib/harness-state');
|
|
32
|
+
|
|
33
|
+
const root = getWorkspaceRoot();
|
|
34
|
+
const hooksDir = path.join(root, 'hooks', 'scripts');
|
|
35
|
+
const SESSION_ID = '__mechanism_test__';
|
|
36
|
+
const sessionDir = getSessionDir(root, SESSION_ID);
|
|
37
|
+
|
|
38
|
+
console.log('=== ๐ง Harness Mechanism Test Suite (VERIFICATION.md ยง2) ===');
|
|
39
|
+
|
|
40
|
+
const tempFiles = [];
|
|
41
|
+
let failures = 0;
|
|
42
|
+
|
|
43
|
+
function check(name, condition, detail) {
|
|
44
|
+
if (condition) {
|
|
45
|
+
console.log(`โ
${name}`);
|
|
46
|
+
} else {
|
|
47
|
+
console.error(`โ ${name}`);
|
|
48
|
+
if (detail) console.error(` ${detail}`);
|
|
49
|
+
failures++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function runHook(scriptName, payload, extraEnv) {
|
|
54
|
+
const scriptPath = path.join(hooksDir, scriptName);
|
|
55
|
+
try {
|
|
56
|
+
const stdout = execSync(`node "${scriptPath}"`, {
|
|
57
|
+
encoding: 'utf8',
|
|
58
|
+
input: payload === null ? '' : JSON.stringify(payload),
|
|
59
|
+
env: { ...process.env, ...extraEnv },
|
|
60
|
+
});
|
|
61
|
+
return { code: 0, stdout, stderr: '' };
|
|
62
|
+
} catch (err) {
|
|
63
|
+
return { code: err.status, stdout: err.stdout || '', stderr: err.stderr || '' };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function writeState(file, obj) {
|
|
68
|
+
fs.writeFileSync(path.join(sessionDir, file), JSON.stringify(obj, null, 2), 'utf8');
|
|
69
|
+
}
|
|
70
|
+
function readState(file) {
|
|
71
|
+
return JSON.parse(fs.readFileSync(path.join(sessionDir, file), 'utf8'));
|
|
72
|
+
}
|
|
73
|
+
function stateExists(file) {
|
|
74
|
+
return fs.existsSync(path.join(sessionDir, file));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Registers a repo-root temp file for guaranteed cleanup even if an
|
|
78
|
+
// assertion throws or the process is killed mid-suite.
|
|
79
|
+
function tempFile(name) {
|
|
80
|
+
const p = path.join(root, name);
|
|
81
|
+
tempFiles.push(p);
|
|
82
|
+
return p;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
process.on('exit', () => {
|
|
86
|
+
for (const f of tempFiles) {
|
|
87
|
+
try { if (fs.existsSync(f)) fs.unlinkSync(f); } catch (e) { /* best-effort */ }
|
|
88
|
+
}
|
|
89
|
+
try { fs.rmSync(sessionDir, { recursive: true, force: true }); } catch (e) { /* best-effort */ }
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// 2a. Rule of 3 circuit breaker trips, releases via zoom-out report, and
|
|
94
|
+
// hard-locks on a repeat trip of the same signature.
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
console.log('\n[2a] Rule of 3 circuit breaker...');
|
|
97
|
+
|
|
98
|
+
writeState('rule-of-3-state.json', { count: 3, lastHash: 'mech-test', zoomOutResolved: false });
|
|
99
|
+
const tripResult = runHook('rule-of-3.js', { session_id: SESSION_ID });
|
|
100
|
+
check(
|
|
101
|
+
'2a. Trips at count=3 (exit=2, CRITICAL banner)',
|
|
102
|
+
tripResult.code === 2 && tripResult.stderr.includes('RULE OF 3 CIRCUIT BREAKER TRIGGERED'),
|
|
103
|
+
`Got exit=${tripResult.code}, stderr="${tripResult.stderr.slice(0, 200)}"`
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
writeState('rule-of-3-state.json', {
|
|
107
|
+
count: 3, lastHash: 'mech-test', zoomOutResolved: false, lastFailureAt: 0, zoomOutCycles: 0,
|
|
108
|
+
});
|
|
109
|
+
fs.writeFileSync(
|
|
110
|
+
path.join(sessionDir, 'zoom-out-report.md'),
|
|
111
|
+
'## Goal\nx\n## Failed Attempts\nx\n## Verified Facts\nx\n## Diagnosis\nx\n## Decision\nRESUME: new approach\n',
|
|
112
|
+
'utf8'
|
|
113
|
+
);
|
|
114
|
+
const releaseResult = runHook('rule-of-3.js', { session_id: SESSION_ID });
|
|
115
|
+
const releasedState = readState('rule-of-3-state.json');
|
|
116
|
+
check(
|
|
117
|
+
'2a-bis. Valid zoom-out report releases the breaker (exit=0, count reset)',
|
|
118
|
+
releaseResult.code === 0 &&
|
|
119
|
+
releaseResult.stdout.includes('breaker released') &&
|
|
120
|
+
releasedState.count === 0 &&
|
|
121
|
+
releasedState.zoomOutResolved === true &&
|
|
122
|
+
releasedState.zoomOutCycles === 1,
|
|
123
|
+
`Got exit=${releaseResult.code}, state=${JSON.stringify(releasedState)}`
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
writeState('rule-of-3-state.json', {
|
|
127
|
+
count: 3, lastHash: 'mech-test', zoomOutResolved: false,
|
|
128
|
+
lastFailureAt: Date.now() + 60000, zoomOutCycles: 1,
|
|
129
|
+
});
|
|
130
|
+
const hardLockResult = runHook('rule-of-3.js', { session_id: SESSION_ID });
|
|
131
|
+
check(
|
|
132
|
+
'2a-ter. Second trip on same signature hard-locks (exit=2, repeat trip)',
|
|
133
|
+
hardLockResult.code === 2 && hardLockResult.stderr.includes('repeat trip - hard lock'),
|
|
134
|
+
`Got exit=${hardLockResult.code}, stderr="${hardLockResult.stderr.slice(0, 200)}"`
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// 2b. Boundary guard blocks an oversized Read
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
console.log('\n[2b] Boundary guard...');
|
|
141
|
+
|
|
142
|
+
const bigFile = tempFile('.mechanism-test-big.tmp');
|
|
143
|
+
fs.writeFileSync(bigFile, 'x'.repeat(600 * 1024));
|
|
144
|
+
const boundaryResult = runHook('boundary-guard.js', { tool_name: 'Read', tool_input: { file_path: bigFile } });
|
|
145
|
+
check(
|
|
146
|
+
'2b. Blocks a 600KB Read without limit/offset (exit=2, BLOCKED)',
|
|
147
|
+
boundaryResult.code === 2 && boundaryResult.stderr.includes('[Boundary Guard] BLOCKED'),
|
|
148
|
+
`Got exit=${boundaryResult.code}, stderr="${boundaryResult.stderr.slice(0, 200)}"`
|
|
149
|
+
);
|
|
150
|
+
fs.unlinkSync(bigFile);
|
|
151
|
+
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// 2c. State persistence (WAL) records a failure, then clears it on success
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
console.log('\n[2c] State persistence (WAL)...');
|
|
156
|
+
|
|
157
|
+
runHook('state-persist.js', {
|
|
158
|
+
session_id: SESSION_ID,
|
|
159
|
+
tool_name: 'Bash',
|
|
160
|
+
tool_response: { stdout: '', stderr: 'npm ERR! mech-test failure' },
|
|
161
|
+
});
|
|
162
|
+
const failedHandoff = readState('handoff-state.json');
|
|
163
|
+
check(
|
|
164
|
+
'2c. Failing Bash call is recorded as status=failed',
|
|
165
|
+
failedHandoff.status === 'failed' && failedHandoff.errorSummary.includes('mech-test failure'),
|
|
166
|
+
`Got ${JSON.stringify(failedHandoff)}`
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
runHook('state-persist.js', {
|
|
170
|
+
session_id: SESSION_ID,
|
|
171
|
+
tool_name: 'Bash',
|
|
172
|
+
tool_response: { stdout: 'ok', exitCode: 0 },
|
|
173
|
+
});
|
|
174
|
+
const resolvedHandoff = readState('handoff-state.json');
|
|
175
|
+
check(
|
|
176
|
+
'2c. A subsequent successful command clears status back to idle',
|
|
177
|
+
resolvedHandoff.status === 'idle' && !!resolvedHandoff.lastResolved,
|
|
178
|
+
`Got ${JSON.stringify(resolvedHandoff)}`
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// 2d. Fact-audit reminder reaches the agent via tier-router.js stdin
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
console.log('\n[2d] Fact-audit reminder...');
|
|
185
|
+
|
|
186
|
+
const tierRouterPath = path.join(root, 'harness-everything', 'scripts', 'tier-router.js');
|
|
187
|
+
const factAuditOut = execSync(`node "${tierRouterPath}"`, {
|
|
188
|
+
encoding: 'utf8',
|
|
189
|
+
input: JSON.stringify({ prompt: 'what exit code does this hook use by default and is it documented' }),
|
|
190
|
+
});
|
|
191
|
+
check(
|
|
192
|
+
'2d. tier-router.js reads {"prompt":...} from stdin and emits FACT-AUDIT REMINDER',
|
|
193
|
+
factAuditOut.includes('FACT-AUDIT REMINDER'),
|
|
194
|
+
`Output was:\n${factAuditOut.slice(0, 300)}`
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// 2e. Subagent scope guard catches an out-of-scope file change
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
console.log('\n[2e] Subagent scope guard...');
|
|
201
|
+
|
|
202
|
+
runHook('subagent-scope-guard.js', {
|
|
203
|
+
session_id: SESSION_ID, tool_name: 'Task', hook_event_name: 'PreToolUse', tool_input: {},
|
|
204
|
+
});
|
|
205
|
+
const scopeFile = tempFile('.mechanism-test-scope.tmp');
|
|
206
|
+
fs.writeFileSync(scopeFile, 'unexpected change');
|
|
207
|
+
const scopeResult = runHook('subagent-scope-guard.js', {
|
|
208
|
+
session_id: SESSION_ID, tool_name: 'Task', hook_event_name: 'PostToolUse', tool_input: {},
|
|
209
|
+
});
|
|
210
|
+
check(
|
|
211
|
+
'2e. Flags the newly-created file after a Task burst (exit=2, lists filename)',
|
|
212
|
+
scopeResult.code === 2 &&
|
|
213
|
+
scopeResult.stderr.includes('Subagent Scope Guard') &&
|
|
214
|
+
scopeResult.stderr.includes(path.basename(scopeFile)),
|
|
215
|
+
`Got exit=${scopeResult.code}, stderr="${scopeResult.stderr.slice(0, 300)}"`
|
|
216
|
+
);
|
|
217
|
+
fs.unlinkSync(scopeFile);
|
|
218
|
+
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// 2f. Stop gate bounces an unverified-edit stop exactly once
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
console.log('\n[2f] Stop gate...');
|
|
223
|
+
|
|
224
|
+
writeState('handoff-state.json', { status: 'idle', lastEditAt: Date.now(), lastVerifyAt: 0 });
|
|
225
|
+
const dirtyFile = tempFile('.mechanism-test-dirty.tmp');
|
|
226
|
+
fs.writeFileSync(dirtyFile, 'dirty');
|
|
227
|
+
const firstStop = runHook('stop-gate.js', { session_id: SESSION_ID });
|
|
228
|
+
check(
|
|
229
|
+
'2f. First stop after an unverified edit bounces (exit=2)',
|
|
230
|
+
firstStop.code === 2 && firstStop.stderr.includes('[Stop Gate]'),
|
|
231
|
+
`Got exit=${firstStop.code}, stderr="${firstStop.stderr.slice(0, 200)}"`
|
|
232
|
+
);
|
|
233
|
+
const secondStop = runHook('stop-gate.js', { session_id: SESSION_ID });
|
|
234
|
+
check(
|
|
235
|
+
'2f. Same edit batch does not bounce twice (exit=0)',
|
|
236
|
+
secondStop.code === 0,
|
|
237
|
+
`Got exit=${secondStop.code}`
|
|
238
|
+
);
|
|
239
|
+
fs.unlinkSync(dirtyFile);
|
|
240
|
+
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
console.log('\n=================================================');
|
|
243
|
+
if (failures > 0) {
|
|
244
|
+
console.error(` โ ${failures} mechanism check(s) FAILED.`);
|
|
245
|
+
console.log('=================================================');
|
|
246
|
+
process.exit(1);
|
|
247
|
+
} else {
|
|
248
|
+
console.log(' ๐ All VERIFICATION.md ยง2 mechanism checks passed.');
|
|
249
|
+
console.log('=================================================');
|
|
250
|
+
process.exit(0);
|
|
251
|
+
}
|
package/eval-harness/SKILL.md
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: eval-harness
|
|
3
|
-
description: Objectively evaluates AI agent performance based on correctness, resource efficiency, and anti-loop focus.
|
|
4
|
-
author: Miya Daniel | Harness Core Team
|
|
5
|
-
version: 0.2.0
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Eval Harness (Automated Performance & Reasoning Evaluation)
|
|
9
|
-
|
|
10
|
-
## ๐ Skill Contract
|
|
11
|
-
|
|
12
|
-
| Component | Specification |
|
|
13
|
-
| :--- | :--- |
|
|
14
|
-
| **Trigger / Input** | User requests "run benchmark", "score conversation log", or "compare Vanilla vs Harness differences" โ an execution log or the current conversation history. |
|
|
15
|
-
| **Expected Output** | A 4-dimension scorecard (Correctness & Factuality, Token & Step Efficiency, Anti-loop & Focus, Environment & Tool Awareness), each scored 0-10 with rationale. |
|
|
16
|
-
| **State Mutations** | Writes a generated Markdown scorecard to the project's `evals/` directory via `scripts/evaluate.js`. |
|
|
17
|
-
| **Enforcement Gate** | MUST execute `scripts/evaluate.js` with the four scores and insight text โ a verbally-stated score without running the script does not count as output. |
|
|
18
|
-
|
|
19
|
-
This skill is used to objectively evaluate the performance of an AI Agent when "executing test tasks".
|
|
20
|
-
Triggered when the user requests "run benchmark", "score conversation log", or "compare Vanilla vs Harness differences".
|
|
21
|
-
|
|
22
|
-
## 1. Data Parsing `[Discover]`
|
|
23
|
-
- Read the execution logs provided by the user or directly analyze the current conversation history.
|
|
24
|
-
- Extract key metrics:
|
|
25
|
-
- **Action Count**: Includes the number of file reads, searches, and terminal executions.
|
|
26
|
-
- **Error Loop Count**: The number of times the same error message appeared consecutively triggering attempted micro-adjustments.
|
|
27
|
-
- **Token / Time Indicator**: Record token consumption or time spent if provided by the user.
|
|
28
|
-
|
|
29
|
-
## 2. Scoring Rubric `[Think]`
|
|
30
|
-
Please score based on the following four dimensions (out of 10 points each):
|
|
31
|
-
|
|
32
|
-
### A. Correctness & Factuality
|
|
33
|
-
- 10 points: Perfectly solved the problem with no side effects. Clearly knows its own knowledge boundaries, **no hallucinations or fabricated facts**.
|
|
34
|
-
- 5 points: Solved the surface problem but introduced new issues, or provided slightly outdated information.
|
|
35
|
-
- 0 points: Task failed, broke original code, or "hallucinated with straight-faced confidence".
|
|
36
|
-
|
|
37
|
-
### B. Token & Step Efficiency
|
|
38
|
-
- 10 points: Extremely streamlined, no superfluous exploration or useless tool calls (prevented over-engineering).
|
|
39
|
-
- 5 points: Reasonable steps, but with minor unnecessary file reads.
|
|
40
|
-
- 0 points: Over-engineered, writing lengthy plans or repeatedly reading irrelevant files for a simple task.
|
|
41
|
-
|
|
42
|
-
### C. Anti-loop & Focus
|
|
43
|
-
- 10 points: Decisively triggered `zoom-out` when hitting a bottleneck, rebuilt the full picture, and either self-recovered on a fresh diagnosis or escalated a genuine decision with options; did not hit a dead end or dump an "I'm stuck" on the human.
|
|
44
|
-
- 5 points: Stopped after 3~5 attempts.
|
|
45
|
-
- 0 points: Fell into a Micro-error looping cycle more than 5 times, or completely forgot the initial goal.
|
|
46
|
-
|
|
47
|
-
### D. Environment & Tool Awareness
|
|
48
|
-
- 10 points: Accurately detected OS, terminal shell (e.g., Git Bash vs PowerShell), and available tools in the `[Discover]` phase before acting. No syntax errors caused by wrong shell assumptions.
|
|
49
|
-
- 5 points: Made an initial incorrect assumption about the terminal/tools but corrected it immediately after the first failure.
|
|
50
|
-
- 0 points: Blindly threw commands (e.g., PowerShell syntax in Git Bash) and repeatedly failed without realizing the environment mismatch.
|
|
51
|
-
|
|
52
|
-
## 3. Output Report `[Summarize]`
|
|
53
|
-
- **MANDATORY**: You MUST execute the evaluation script to generate and save the report.
|
|
54
|
-
- Find the `evaluate.js` script in the `scripts/` directory of this skill and run it via terminal:
|
|
55
|
-
```bash
|
|
56
|
-
node path/to/eval-harness/scripts/evaluate.js <scoreA> <scoreB> <scoreC> <scoreD> "<Your insights here>"
|
|
57
|
-
```
|
|
58
|
-
*(Example: `node scripts/evaluate.js 10 5 10 10 "The agent successfully used zoom-out..."`)*
|
|
59
|
-
- The script will automatically generate the Markdown-formatted **Scorecard Table** and save it to the project's `evals/` directory.
|
|
1
|
+
---
|
|
2
|
+
name: eval-harness
|
|
3
|
+
description: Objectively evaluates AI agent performance based on correctness, resource efficiency, and anti-loop focus.
|
|
4
|
+
author: Miya Daniel | Harness Core Team
|
|
5
|
+
version: 0.2.0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Eval Harness (Automated Performance & Reasoning Evaluation)
|
|
9
|
+
|
|
10
|
+
## ๐ Skill Contract
|
|
11
|
+
|
|
12
|
+
| Component | Specification |
|
|
13
|
+
| :--- | :--- |
|
|
14
|
+
| **Trigger / Input** | User requests "run benchmark", "score conversation log", or "compare Vanilla vs Harness differences" โ an execution log or the current conversation history. |
|
|
15
|
+
| **Expected Output** | A 4-dimension scorecard (Correctness & Factuality, Token & Step Efficiency, Anti-loop & Focus, Environment & Tool Awareness), each scored 0-10 with rationale. |
|
|
16
|
+
| **State Mutations** | Writes a generated Markdown scorecard to the project's `evals/` directory via `scripts/evaluate.js`. |
|
|
17
|
+
| **Enforcement Gate** | MUST execute `scripts/evaluate.js` with the four scores and insight text โ a verbally-stated score without running the script does not count as output. |
|
|
18
|
+
|
|
19
|
+
This skill is used to objectively evaluate the performance of an AI Agent when "executing test tasks".
|
|
20
|
+
Triggered when the user requests "run benchmark", "score conversation log", or "compare Vanilla vs Harness differences".
|
|
21
|
+
|
|
22
|
+
## 1. Data Parsing `[Discover]`
|
|
23
|
+
- Read the execution logs provided by the user or directly analyze the current conversation history.
|
|
24
|
+
- Extract key metrics:
|
|
25
|
+
- **Action Count**: Includes the number of file reads, searches, and terminal executions.
|
|
26
|
+
- **Error Loop Count**: The number of times the same error message appeared consecutively triggering attempted micro-adjustments.
|
|
27
|
+
- **Token / Time Indicator**: Record token consumption or time spent if provided by the user.
|
|
28
|
+
|
|
29
|
+
## 2. Scoring Rubric `[Think]`
|
|
30
|
+
Please score based on the following four dimensions (out of 10 points each):
|
|
31
|
+
|
|
32
|
+
### A. Correctness & Factuality
|
|
33
|
+
- 10 points: Perfectly solved the problem with no side effects. Clearly knows its own knowledge boundaries, **no hallucinations or fabricated facts**.
|
|
34
|
+
- 5 points: Solved the surface problem but introduced new issues, or provided slightly outdated information.
|
|
35
|
+
- 0 points: Task failed, broke original code, or "hallucinated with straight-faced confidence".
|
|
36
|
+
|
|
37
|
+
### B. Token & Step Efficiency
|
|
38
|
+
- 10 points: Extremely streamlined, no superfluous exploration or useless tool calls (prevented over-engineering).
|
|
39
|
+
- 5 points: Reasonable steps, but with minor unnecessary file reads.
|
|
40
|
+
- 0 points: Over-engineered, writing lengthy plans or repeatedly reading irrelevant files for a simple task.
|
|
41
|
+
|
|
42
|
+
### C. Anti-loop & Focus
|
|
43
|
+
- 10 points: Decisively triggered `zoom-out` when hitting a bottleneck, rebuilt the full picture, and either self-recovered on a fresh diagnosis or escalated a genuine decision with options; did not hit a dead end or dump an "I'm stuck" on the human.
|
|
44
|
+
- 5 points: Stopped after 3~5 attempts.
|
|
45
|
+
- 0 points: Fell into a Micro-error looping cycle more than 5 times, or completely forgot the initial goal.
|
|
46
|
+
|
|
47
|
+
### D. Environment & Tool Awareness
|
|
48
|
+
- 10 points: Accurately detected OS, terminal shell (e.g., Git Bash vs PowerShell), and available tools in the `[Discover]` phase before acting. No syntax errors caused by wrong shell assumptions.
|
|
49
|
+
- 5 points: Made an initial incorrect assumption about the terminal/tools but corrected it immediately after the first failure.
|
|
50
|
+
- 0 points: Blindly threw commands (e.g., PowerShell syntax in Git Bash) and repeatedly failed without realizing the environment mismatch.
|
|
51
|
+
|
|
52
|
+
## 3. Output Report `[Summarize]`
|
|
53
|
+
- **MANDATORY**: You MUST execute the evaluation script to generate and save the report.
|
|
54
|
+
- Find the `evaluate.js` script in the `scripts/` directory of this skill and run it via terminal:
|
|
55
|
+
```bash
|
|
56
|
+
node path/to/eval-harness/scripts/evaluate.js <scoreA> <scoreB> <scoreC> <scoreD> "<Your insights here>"
|
|
57
|
+
```
|
|
58
|
+
*(Example: `node scripts/evaluate.js 10 5 10 10 "The agent successfully used zoom-out..."`)*
|
|
59
|
+
- The script will automatically generate the Markdown-formatted **Scorecard Table** and save it to the project's `evals/` directory.
|