harness-everything 0.2.1 → 0.2.2
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/README.md +1 -1
- package/build-multi-agent-system/SKILL.md +68 -68
- package/create-agent-launcher/SKILL.md +48 -48
- package/docs/architecture.md +2 -2
- 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/platforms/claude.js +132 -42
- package/hooks/scripts/lib/platforms/codex.js +96 -46
- package/hooks/scripts/lib/platforms/continue.js +73 -41
- package/hooks/scripts/lib/platforms/copilot.js +91 -41
- package/hooks/scripts/lib/platforms/cursor.js +71 -41
- package/hooks/scripts/lib/platforms/hermes.js +25 -0
- 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 +57 -200
- package/scripts/lib/advisory-text.js +163 -163
- package/scripts/lib/claude-hooks.js +71 -71
- package/scripts/lib/manifest.js +82 -82
- package/scripts/lib/prompts.js +241 -241
- package/scripts/lib/skills.js +174 -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,157 +1,157 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Harness Todo CLI
|
|
4
|
-
* A strict state machine for task management.
|
|
5
|
-
* Designed to slap the LLM with Exit Code 1 if it violates the Todo-Driven Workflow.
|
|
6
|
-
*/
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const { getWorkspaceRoot, getStateRoot } = require('../../hooks/scripts/lib/harness-state');
|
|
10
|
-
|
|
11
|
-
// Shared across sessions (not scoped to one): todo-cli.js is invoked as a
|
|
12
|
-
// plain CLI command by the agent, not a hook, so it never receives a
|
|
13
|
-
// session_id to scope by.
|
|
14
|
-
const harnessDir = getStateRoot(getWorkspaceRoot());
|
|
15
|
-
const stateFile = path.join(harnessDir, 'todo-state.json');
|
|
16
|
-
|
|
17
|
-
if (!fs.existsSync(harnessDir)) {
|
|
18
|
-
fs.mkdirSync(harnessDir, { recursive: true });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function loadState() {
|
|
22
|
-
if (fs.existsSync(stateFile)) {
|
|
23
|
-
return JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
24
|
-
}
|
|
25
|
-
return { tasks: [], activeId: null };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function saveState(state) {
|
|
29
|
-
// Atomic write (temp file + rename): todo-state.json is shared across the
|
|
30
|
-
// whole workspace, not scoped per session_id like the hook state files
|
|
31
|
-
// are, so parallel subagent Task bursts can genuinely call `todo-cli.js`
|
|
32
|
-
// around the same time. A plain writeFileSync lets a reader observe a
|
|
33
|
-
// truncated/partial JSON file if two writes interleave; rename is atomic
|
|
34
|
-
// at the filesystem level, so readers only ever see the old or the new
|
|
35
|
-
// file in full, never a half-written one. This doesn't resolve the
|
|
36
|
-
// separate lost-update race (last writer still wins) - that needs real
|
|
37
|
-
// cross-process locking, out of scope for what a CLI state file needs.
|
|
38
|
-
const tmpFile = `${stateFile}.tmp.${process.pid}`;
|
|
39
|
-
fs.writeFileSync(tmpFile, JSON.stringify(state, null, 2));
|
|
40
|
-
fs.renameSync(tmpFile, stateFile);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function printList(state) {
|
|
44
|
-
console.log('\n📋 Current Todo List:');
|
|
45
|
-
if (state.tasks.length === 0) {
|
|
46
|
-
console.log(' (Empty)');
|
|
47
|
-
}
|
|
48
|
-
state.tasks.forEach(t => {
|
|
49
|
-
const status = t.id === state.activeId ? '[IN-PROGRESS]' : (t.completed ? '[COMPLETED]' : '[NOT-STARTED]');
|
|
50
|
-
console.log(` ${t.id}. ${status} ${t.title}`);
|
|
51
|
-
});
|
|
52
|
-
console.log('');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const args = process.argv.slice(2);
|
|
56
|
-
const command = args[0];
|
|
57
|
-
|
|
58
|
-
if (!command) {
|
|
59
|
-
console.error("❌ Error: Missing command. Use 'init', 'add', 'start', 'complete', or 'status'.");
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const state = loadState();
|
|
64
|
-
|
|
65
|
-
switch (command) {
|
|
66
|
-
case 'init':
|
|
67
|
-
state.tasks = [];
|
|
68
|
-
state.activeId = null;
|
|
69
|
-
args.slice(1).forEach((title, idx) => {
|
|
70
|
-
state.tasks.push({ id: idx + 1, title, completed: false });
|
|
71
|
-
});
|
|
72
|
-
saveState(state);
|
|
73
|
-
console.log("✅ Todo list initialized.");
|
|
74
|
-
printList(state);
|
|
75
|
-
break;
|
|
76
|
-
|
|
77
|
-
case 'add':
|
|
78
|
-
const title = args.slice(1).join(' ');
|
|
79
|
-
if (!title) {
|
|
80
|
-
console.error("❌ Error: Missing task title.");
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
const nextId = state.tasks.length > 0 ? Math.max(...state.tasks.map(t => t.id)) + 1 : 1;
|
|
84
|
-
state.tasks.push({ id: nextId, title, completed: false });
|
|
85
|
-
saveState(state);
|
|
86
|
-
console.log(`✅ Added task ${nextId}.`);
|
|
87
|
-
printList(state);
|
|
88
|
-
break;
|
|
89
|
-
|
|
90
|
-
case 'start':
|
|
91
|
-
const startId = parseInt(args[1], 10);
|
|
92
|
-
if (!startId) {
|
|
93
|
-
console.error("❌ Error: Missing task ID. Usage: start <id>");
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
if (state.activeId !== null && state.activeId !== startId) {
|
|
97
|
-
console.error(`❌ [BLOCKED] Task ${state.activeId} is already IN-PROGRESS.`);
|
|
98
|
-
console.error(`You MUST complete or un-start task ${state.activeId} before starting a new one. No multitasking.`);
|
|
99
|
-
process.exit(1);
|
|
100
|
-
}
|
|
101
|
-
const taskToStart = state.tasks.find(t => t.id === startId);
|
|
102
|
-
if (!taskToStart) {
|
|
103
|
-
console.error(`❌ Error: Task ${startId} not found.`);
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
if (taskToStart.completed) {
|
|
107
|
-
console.error(`❌ Error: Task ${startId} is already completed.`);
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
110
|
-
state.activeId = startId;
|
|
111
|
-
saveState(state);
|
|
112
|
-
console.log(`🚀 Started task ${startId}: ${taskToStart.title}`);
|
|
113
|
-
break;
|
|
114
|
-
|
|
115
|
-
case 'complete':
|
|
116
|
-
const compId = parseInt(args[1], 10);
|
|
117
|
-
if (!compId) {
|
|
118
|
-
console.error("❌ Error: Missing task ID. Usage: complete <id>");
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
if (state.activeId !== compId) {
|
|
122
|
-
console.error(`❌ [BLOCKED] Task ${compId} is NOT currently in-progress.`);
|
|
123
|
-
console.error(`You can only complete the active task. Active task is: ${state.activeId || 'None'}`);
|
|
124
|
-
process.exit(1);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// ACTIONABLE RECOMMENDATION #1: Enforce State Transitions Programmatically
|
|
128
|
-
// Do not rely on prompt obedience for running verify-gate.js. The CLI should block the complete command
|
|
129
|
-
console.log("=== 🛡️ Executing Pre-Completion Verification Gate ===");
|
|
130
|
-
try {
|
|
131
|
-
const verifyScript = path.join(__dirname, 'verify-gate.js');
|
|
132
|
-
if (fs.existsSync(verifyScript)) {
|
|
133
|
-
require('child_process').execSync(`node "${verifyScript}"`, { stdio: 'inherit' });
|
|
134
|
-
}
|
|
135
|
-
} catch (err) {
|
|
136
|
-
console.error(`\n❌ [BLOCKED] Verification Gate Failed (Exit Code 1).`);
|
|
137
|
-
console.error(`You CANNOT complete this task. The code is broken.`);
|
|
138
|
-
console.error(`Please fix the errors shown above and try again.`);
|
|
139
|
-
process.exit(1);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const taskToComp = state.tasks.find(t => t.id === compId);
|
|
143
|
-
taskToComp.completed = true;
|
|
144
|
-
state.activeId = null;
|
|
145
|
-
saveState(state);
|
|
146
|
-
console.log(`✅ Completed task ${compId}: ${taskToComp.title}`);
|
|
147
|
-
printList(state);
|
|
148
|
-
break;
|
|
149
|
-
|
|
150
|
-
case 'status':
|
|
151
|
-
printList(state);
|
|
152
|
-
break;
|
|
153
|
-
|
|
154
|
-
default:
|
|
155
|
-
console.error(`❌ Error: Unknown command '${command}'.`);
|
|
156
|
-
process.exit(1);
|
|
157
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Harness Todo CLI
|
|
4
|
+
* A strict state machine for task management.
|
|
5
|
+
* Designed to slap the LLM with Exit Code 1 if it violates the Todo-Driven Workflow.
|
|
6
|
+
*/
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { getWorkspaceRoot, getStateRoot } = require('../../hooks/scripts/lib/harness-state');
|
|
10
|
+
|
|
11
|
+
// Shared across sessions (not scoped to one): todo-cli.js is invoked as a
|
|
12
|
+
// plain CLI command by the agent, not a hook, so it never receives a
|
|
13
|
+
// session_id to scope by.
|
|
14
|
+
const harnessDir = getStateRoot(getWorkspaceRoot());
|
|
15
|
+
const stateFile = path.join(harnessDir, 'todo-state.json');
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(harnessDir)) {
|
|
18
|
+
fs.mkdirSync(harnessDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function loadState() {
|
|
22
|
+
if (fs.existsSync(stateFile)) {
|
|
23
|
+
return JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
24
|
+
}
|
|
25
|
+
return { tasks: [], activeId: null };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function saveState(state) {
|
|
29
|
+
// Atomic write (temp file + rename): todo-state.json is shared across the
|
|
30
|
+
// whole workspace, not scoped per session_id like the hook state files
|
|
31
|
+
// are, so parallel subagent Task bursts can genuinely call `todo-cli.js`
|
|
32
|
+
// around the same time. A plain writeFileSync lets a reader observe a
|
|
33
|
+
// truncated/partial JSON file if two writes interleave; rename is atomic
|
|
34
|
+
// at the filesystem level, so readers only ever see the old or the new
|
|
35
|
+
// file in full, never a half-written one. This doesn't resolve the
|
|
36
|
+
// separate lost-update race (last writer still wins) - that needs real
|
|
37
|
+
// cross-process locking, out of scope for what a CLI state file needs.
|
|
38
|
+
const tmpFile = `${stateFile}.tmp.${process.pid}`;
|
|
39
|
+
fs.writeFileSync(tmpFile, JSON.stringify(state, null, 2));
|
|
40
|
+
fs.renameSync(tmpFile, stateFile);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function printList(state) {
|
|
44
|
+
console.log('\n📋 Current Todo List:');
|
|
45
|
+
if (state.tasks.length === 0) {
|
|
46
|
+
console.log(' (Empty)');
|
|
47
|
+
}
|
|
48
|
+
state.tasks.forEach(t => {
|
|
49
|
+
const status = t.id === state.activeId ? '[IN-PROGRESS]' : (t.completed ? '[COMPLETED]' : '[NOT-STARTED]');
|
|
50
|
+
console.log(` ${t.id}. ${status} ${t.title}`);
|
|
51
|
+
});
|
|
52
|
+
console.log('');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const args = process.argv.slice(2);
|
|
56
|
+
const command = args[0];
|
|
57
|
+
|
|
58
|
+
if (!command) {
|
|
59
|
+
console.error("❌ Error: Missing command. Use 'init', 'add', 'start', 'complete', or 'status'.");
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const state = loadState();
|
|
64
|
+
|
|
65
|
+
switch (command) {
|
|
66
|
+
case 'init':
|
|
67
|
+
state.tasks = [];
|
|
68
|
+
state.activeId = null;
|
|
69
|
+
args.slice(1).forEach((title, idx) => {
|
|
70
|
+
state.tasks.push({ id: idx + 1, title, completed: false });
|
|
71
|
+
});
|
|
72
|
+
saveState(state);
|
|
73
|
+
console.log("✅ Todo list initialized.");
|
|
74
|
+
printList(state);
|
|
75
|
+
break;
|
|
76
|
+
|
|
77
|
+
case 'add':
|
|
78
|
+
const title = args.slice(1).join(' ');
|
|
79
|
+
if (!title) {
|
|
80
|
+
console.error("❌ Error: Missing task title.");
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
const nextId = state.tasks.length > 0 ? Math.max(...state.tasks.map(t => t.id)) + 1 : 1;
|
|
84
|
+
state.tasks.push({ id: nextId, title, completed: false });
|
|
85
|
+
saveState(state);
|
|
86
|
+
console.log(`✅ Added task ${nextId}.`);
|
|
87
|
+
printList(state);
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
case 'start':
|
|
91
|
+
const startId = parseInt(args[1], 10);
|
|
92
|
+
if (!startId) {
|
|
93
|
+
console.error("❌ Error: Missing task ID. Usage: start <id>");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
if (state.activeId !== null && state.activeId !== startId) {
|
|
97
|
+
console.error(`❌ [BLOCKED] Task ${state.activeId} is already IN-PROGRESS.`);
|
|
98
|
+
console.error(`You MUST complete or un-start task ${state.activeId} before starting a new one. No multitasking.`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
const taskToStart = state.tasks.find(t => t.id === startId);
|
|
102
|
+
if (!taskToStart) {
|
|
103
|
+
console.error(`❌ Error: Task ${startId} not found.`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
if (taskToStart.completed) {
|
|
107
|
+
console.error(`❌ Error: Task ${startId} is already completed.`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
state.activeId = startId;
|
|
111
|
+
saveState(state);
|
|
112
|
+
console.log(`🚀 Started task ${startId}: ${taskToStart.title}`);
|
|
113
|
+
break;
|
|
114
|
+
|
|
115
|
+
case 'complete':
|
|
116
|
+
const compId = parseInt(args[1], 10);
|
|
117
|
+
if (!compId) {
|
|
118
|
+
console.error("❌ Error: Missing task ID. Usage: complete <id>");
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
if (state.activeId !== compId) {
|
|
122
|
+
console.error(`❌ [BLOCKED] Task ${compId} is NOT currently in-progress.`);
|
|
123
|
+
console.error(`You can only complete the active task. Active task is: ${state.activeId || 'None'}`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ACTIONABLE RECOMMENDATION #1: Enforce State Transitions Programmatically
|
|
128
|
+
// Do not rely on prompt obedience for running verify-gate.js. The CLI should block the complete command
|
|
129
|
+
console.log("=== 🛡️ Executing Pre-Completion Verification Gate ===");
|
|
130
|
+
try {
|
|
131
|
+
const verifyScript = path.join(__dirname, 'verify-gate.js');
|
|
132
|
+
if (fs.existsSync(verifyScript)) {
|
|
133
|
+
require('child_process').execSync(`node "${verifyScript}"`, { stdio: 'inherit' });
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.error(`\n❌ [BLOCKED] Verification Gate Failed (Exit Code 1).`);
|
|
137
|
+
console.error(`You CANNOT complete this task. The code is broken.`);
|
|
138
|
+
console.error(`Please fix the errors shown above and try again.`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const taskToComp = state.tasks.find(t => t.id === compId);
|
|
143
|
+
taskToComp.completed = true;
|
|
144
|
+
state.activeId = null;
|
|
145
|
+
saveState(state);
|
|
146
|
+
console.log(`✅ Completed task ${compId}: ${taskToComp.title}`);
|
|
147
|
+
printList(state);
|
|
148
|
+
break;
|
|
149
|
+
|
|
150
|
+
case 'status':
|
|
151
|
+
printList(state);
|
|
152
|
+
break;
|
|
153
|
+
|
|
154
|
+
default:
|
|
155
|
+
console.error(`❌ Error: Unknown command '${command}'.`);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Verification Gatekeeper
|
|
4
|
-
* Forces the AI to run this script to prove quality gates before declaring a task done.
|
|
5
|
-
*/
|
|
6
|
-
const { execSync } = require('child_process');
|
|
7
|
-
|
|
8
|
-
console.log("=== 🛡️ Harness Verification Gate ===");
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
console.log("Running basic checks (simulated for now)...");
|
|
12
|
-
|
|
13
|
-
// In a real project, this would run `npm run lint` and `npm test`
|
|
14
|
-
// We'll simulate a failure if `.verify-fail.tmp` exists just to prove the mechanism.
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
if (fs.existsSync('.verify-fail.tmp')) {
|
|
17
|
-
console.error("❌ Verification FAILED: Found broken tests or lint errors.");
|
|
18
|
-
console.error("Please fix the code and run this gate again.");
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
console.log("✅ Lint passed.");
|
|
23
|
-
console.log("✅ Tests passed.");
|
|
24
|
-
console.log("✅ Verification SUCCESS. You may now complete the task.");
|
|
25
|
-
process.exit(0);
|
|
26
|
-
|
|
27
|
-
} catch (err) {
|
|
28
|
-
console.error("❌ Verification FAILED.");
|
|
29
|
-
console.error(err.message);
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Verification Gatekeeper
|
|
4
|
+
* Forces the AI to run this script to prove quality gates before declaring a task done.
|
|
5
|
+
*/
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
console.log("=== 🛡️ Harness Verification Gate ===");
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
console.log("Running basic checks (simulated for now)...");
|
|
12
|
+
|
|
13
|
+
// In a real project, this would run `npm run lint` and `npm test`
|
|
14
|
+
// We'll simulate a failure if `.verify-fail.tmp` exists just to prove the mechanism.
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
if (fs.existsSync('.verify-fail.tmp')) {
|
|
17
|
+
console.error("❌ Verification FAILED: Found broken tests or lint errors.");
|
|
18
|
+
console.error("Please fix the code and run this gate again.");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log("✅ Lint passed.");
|
|
23
|
+
console.log("✅ Tests passed.");
|
|
24
|
+
console.log("✅ Verification SUCCESS. You may now complete the task.");
|
|
25
|
+
process.exit(0);
|
|
26
|
+
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error("❌ Verification FAILED.");
|
|
29
|
+
console.error(err.message);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Atomic Commit Check (PostToolUse: Edit|Write)
|
|
4
|
-
* PostToolUse can't block - the edit/write already happened. So this counts
|
|
5
|
-
* Edit/Write calls since the last commit and, once the count crosses a
|
|
6
|
-
* threshold, feeds a strong reminder back to Claude (exit 2) to commit the
|
|
7
|
-
* completed chunk before piling on more changes. Resets whenever HEAD moves.
|
|
8
|
-
* Fails open on any error - this is a nudge, never a hard stop.
|
|
9
|
-
*/
|
|
10
|
-
const { execSync } = require('child_process');
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
const { getWorkspaceRoot, getSessionDir } = require('./lib/harness-state');
|
|
14
|
-
|
|
15
|
-
const THRESHOLD = 6;
|
|
16
|
-
const RENUDGE_EVERY = 3;
|
|
17
|
-
|
|
18
|
-
function main(payload) {
|
|
19
|
-
try {
|
|
20
|
-
const root = getWorkspaceRoot();
|
|
21
|
-
const head = execSync('git rev-parse HEAD', { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
22
|
-
|
|
23
|
-
const stateFile = path.join(getSessionDir(root, payload && payload.session_id), 'atomic-commit-state.json');
|
|
24
|
-
|
|
25
|
-
let state = { lastHead: head, editCount: 0 };
|
|
26
|
-
if (fs.existsSync(stateFile)) {
|
|
27
|
-
try { state = JSON.parse(fs.readFileSync(stateFile, 'utf8')); } catch (e) { /* use default */ }
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (state.lastHead !== head) {
|
|
31
|
-
// A commit happened since we last checked - fresh start.
|
|
32
|
-
state.lastHead = head;
|
|
33
|
-
state.editCount = 0;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
state.editCount += 1;
|
|
37
|
-
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf8');
|
|
38
|
-
|
|
39
|
-
const overThreshold = state.editCount >= THRESHOLD;
|
|
40
|
-
const onNudgeBeat = overThreshold && (state.editCount - THRESHOLD) % RENUDGE_EVERY === 0;
|
|
41
|
-
|
|
42
|
-
if (onNudgeBeat) {
|
|
43
|
-
const statusRaw = execSync('git status --porcelain', { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
44
|
-
const changedFiles = statusRaw ? statusRaw.split('\n').length : 0;
|
|
45
|
-
console.error(`[Atomic Commit Check] ${state.editCount} Edit/Write calls since the last commit (${changedFiles} files currently changed).`);
|
|
46
|
-
console.error(`If a logically complete chunk of work is done, commit it now before continuing - large uncommitted diffs are harder to review and harder to recover from.`);
|
|
47
|
-
process.exit(2);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
process.exit(0);
|
|
51
|
-
} catch (err) {
|
|
52
|
-
process.exit(0);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let inputData = '';
|
|
57
|
-
const timeout = setTimeout(() => { main(null); }, 200);
|
|
58
|
-
process.stdin.on('data', chunk => { inputData += chunk; });
|
|
59
|
-
process.stdin.on('end', () => {
|
|
60
|
-
clearTimeout(timeout);
|
|
61
|
-
let payload = null;
|
|
62
|
-
try { payload = JSON.parse(inputData.trim()); } catch (err) { /* no payload */ }
|
|
63
|
-
main(payload);
|
|
64
|
-
});
|
|
65
|
-
process.stdin.on('error', () => {
|
|
66
|
-
clearTimeout(timeout);
|
|
67
|
-
main(null);
|
|
68
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Atomic Commit Check (PostToolUse: Edit|Write)
|
|
4
|
+
* PostToolUse can't block - the edit/write already happened. So this counts
|
|
5
|
+
* Edit/Write calls since the last commit and, once the count crosses a
|
|
6
|
+
* threshold, feeds a strong reminder back to Claude (exit 2) to commit the
|
|
7
|
+
* completed chunk before piling on more changes. Resets whenever HEAD moves.
|
|
8
|
+
* Fails open on any error - this is a nudge, never a hard stop.
|
|
9
|
+
*/
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { getWorkspaceRoot, getSessionDir } = require('./lib/harness-state');
|
|
14
|
+
|
|
15
|
+
const THRESHOLD = 6;
|
|
16
|
+
const RENUDGE_EVERY = 3;
|
|
17
|
+
|
|
18
|
+
function main(payload) {
|
|
19
|
+
try {
|
|
20
|
+
const root = getWorkspaceRoot();
|
|
21
|
+
const head = execSync('git rev-parse HEAD', { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
22
|
+
|
|
23
|
+
const stateFile = path.join(getSessionDir(root, payload && payload.session_id), 'atomic-commit-state.json');
|
|
24
|
+
|
|
25
|
+
let state = { lastHead: head, editCount: 0 };
|
|
26
|
+
if (fs.existsSync(stateFile)) {
|
|
27
|
+
try { state = JSON.parse(fs.readFileSync(stateFile, 'utf8')); } catch (e) { /* use default */ }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (state.lastHead !== head) {
|
|
31
|
+
// A commit happened since we last checked - fresh start.
|
|
32
|
+
state.lastHead = head;
|
|
33
|
+
state.editCount = 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
state.editCount += 1;
|
|
37
|
+
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf8');
|
|
38
|
+
|
|
39
|
+
const overThreshold = state.editCount >= THRESHOLD;
|
|
40
|
+
const onNudgeBeat = overThreshold && (state.editCount - THRESHOLD) % RENUDGE_EVERY === 0;
|
|
41
|
+
|
|
42
|
+
if (onNudgeBeat) {
|
|
43
|
+
const statusRaw = execSync('git status --porcelain', { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
44
|
+
const changedFiles = statusRaw ? statusRaw.split('\n').length : 0;
|
|
45
|
+
console.error(`[Atomic Commit Check] ${state.editCount} Edit/Write calls since the last commit (${changedFiles} files currently changed).`);
|
|
46
|
+
console.error(`If a logically complete chunk of work is done, commit it now before continuing - large uncommitted diffs are harder to review and harder to recover from.`);
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
process.exit(0);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let inputData = '';
|
|
57
|
+
const timeout = setTimeout(() => { main(null); }, 200);
|
|
58
|
+
process.stdin.on('data', chunk => { inputData += chunk; });
|
|
59
|
+
process.stdin.on('end', () => {
|
|
60
|
+
clearTimeout(timeout);
|
|
61
|
+
let payload = null;
|
|
62
|
+
try { payload = JSON.parse(inputData.trim()); } catch (err) { /* no payload */ }
|
|
63
|
+
main(payload);
|
|
64
|
+
});
|
|
65
|
+
process.stdin.on('error', () => {
|
|
66
|
+
clearTimeout(timeout);
|
|
67
|
+
main(null);
|
|
68
|
+
});
|