@cleocode/cleo-os 2026.5.130 → 2026.5.132
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.
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Stop-hook — goal-continuation loop adapter (E4-GOAL-LOOP).
|
|
3
|
+
*
|
|
4
|
+
* ## What this does
|
|
5
|
+
*
|
|
6
|
+
* Claude Code fires the `Stop` event every time Claude finishes a response
|
|
7
|
+
* turn. When a CLEO goal is active, this hook advances the goal one turn
|
|
8
|
+
* (via `cleo goal advance <goalId>`) and — if the goal is not yet terminal —
|
|
9
|
+
* emits a `{ decision: "block", reason: "<continuation>" }` JSON response that
|
|
10
|
+
* Claude Code interprets as "do NOT stop; inject this message instead". This
|
|
11
|
+
* re-nudges Claude to keep working toward the goal, closing the
|
|
12
|
+
* self-renudging loop (AC2).
|
|
13
|
+
*
|
|
14
|
+
* ## Decision protocol (Claude Code Stop-hook contract)
|
|
15
|
+
*
|
|
16
|
+
* - Output `{ "decision": "block", "reason": "<user message>" }` → Claude Code
|
|
17
|
+
* does not stop; it injects `reason` as a fresh user message and continues.
|
|
18
|
+
* - Output nothing (or anything not parseable as `{ decision: "block" }`) →
|
|
19
|
+
* Claude Code stops normally.
|
|
20
|
+
*
|
|
21
|
+
* ## Safety
|
|
22
|
+
*
|
|
23
|
+
* - Best-effort: any error exits 0 (Claude Code stops normally — no crash loop).
|
|
24
|
+
* - Terminal goals (satisfied/abandoned/impossible) emit nothing → stop.
|
|
25
|
+
* - Missing/no active goal → stop normally.
|
|
26
|
+
*
|
|
27
|
+
* ## Installation
|
|
28
|
+
*
|
|
29
|
+
* This script is registered as a Claude Code Stop hook via
|
|
30
|
+
* `ClaudeCodeHookProvider.registerGoalLoopHook()` or by running
|
|
31
|
+
* `cleo goal arm` (AC3). The hook entry in `~/.claude/settings.json`:
|
|
32
|
+
*
|
|
33
|
+
* ```json
|
|
34
|
+
* {
|
|
35
|
+
* "Stop": [{
|
|
36
|
+
* "matcher": "",
|
|
37
|
+
* "hooks": [{
|
|
38
|
+
* "type": "command",
|
|
39
|
+
* "command": "node /path/to/goal-stop-hook.js # cleo-goal-loop"
|
|
40
|
+
* }]
|
|
41
|
+
* }]
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @module @cleocode/cleo-os/harnesses/goal-stop-hook
|
|
46
|
+
*
|
|
47
|
+
* @task T11496 E4-GOAL-LOOP
|
|
48
|
+
* @epic T11492 SG-AUTOPILOT
|
|
49
|
+
* @saga T11283 SG-COGNITIVE-SUBSTRATE
|
|
50
|
+
* @adr ADR-051
|
|
51
|
+
*/
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=goal-stop-hook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goal-stop-hook.d.ts","sourceRoot":"","sources":["../../src/harnesses/goal-stop-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Stop-hook — goal-continuation loop adapter (E4-GOAL-LOOP).
|
|
3
|
+
*
|
|
4
|
+
* ## What this does
|
|
5
|
+
*
|
|
6
|
+
* Claude Code fires the `Stop` event every time Claude finishes a response
|
|
7
|
+
* turn. When a CLEO goal is active, this hook advances the goal one turn
|
|
8
|
+
* (via `cleo goal advance <goalId>`) and — if the goal is not yet terminal —
|
|
9
|
+
* emits a `{ decision: "block", reason: "<continuation>" }` JSON response that
|
|
10
|
+
* Claude Code interprets as "do NOT stop; inject this message instead". This
|
|
11
|
+
* re-nudges Claude to keep working toward the goal, closing the
|
|
12
|
+
* self-renudging loop (AC2).
|
|
13
|
+
*
|
|
14
|
+
* ## Decision protocol (Claude Code Stop-hook contract)
|
|
15
|
+
*
|
|
16
|
+
* - Output `{ "decision": "block", "reason": "<user message>" }` → Claude Code
|
|
17
|
+
* does not stop; it injects `reason` as a fresh user message and continues.
|
|
18
|
+
* - Output nothing (or anything not parseable as `{ decision: "block" }`) →
|
|
19
|
+
* Claude Code stops normally.
|
|
20
|
+
*
|
|
21
|
+
* ## Safety
|
|
22
|
+
*
|
|
23
|
+
* - Best-effort: any error exits 0 (Claude Code stops normally — no crash loop).
|
|
24
|
+
* - Terminal goals (satisfied/abandoned/impossible) emit nothing → stop.
|
|
25
|
+
* - Missing/no active goal → stop normally.
|
|
26
|
+
*
|
|
27
|
+
* ## Installation
|
|
28
|
+
*
|
|
29
|
+
* This script is registered as a Claude Code Stop hook via
|
|
30
|
+
* `ClaudeCodeHookProvider.registerGoalLoopHook()` or by running
|
|
31
|
+
* `cleo goal arm` (AC3). The hook entry in `~/.claude/settings.json`:
|
|
32
|
+
*
|
|
33
|
+
* ```json
|
|
34
|
+
* {
|
|
35
|
+
* "Stop": [{
|
|
36
|
+
* "matcher": "",
|
|
37
|
+
* "hooks": [{
|
|
38
|
+
* "type": "command",
|
|
39
|
+
* "command": "node /path/to/goal-stop-hook.js # cleo-goal-loop"
|
|
40
|
+
* }]
|
|
41
|
+
* }]
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @module @cleocode/cleo-os/harnesses/goal-stop-hook
|
|
46
|
+
*
|
|
47
|
+
* @task T11496 E4-GOAL-LOOP
|
|
48
|
+
* @epic T11492 SG-AUTOPILOT
|
|
49
|
+
* @saga T11283 SG-COGNITIVE-SUBSTRATE
|
|
50
|
+
* @adr ADR-051
|
|
51
|
+
*/
|
|
52
|
+
import { execFile } from 'node:child_process';
|
|
53
|
+
import { promisify } from 'node:util';
|
|
54
|
+
const execFileAsync = promisify(execFile);
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Main
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
/**
|
|
59
|
+
* Run the goal-loop Stop-hook logic.
|
|
60
|
+
*
|
|
61
|
+
* 1. Query `cleo goal status` to find the active goal.
|
|
62
|
+
* 2. If no active goal → stop normally (no output).
|
|
63
|
+
* 3. Call `cleo goal advance <goalId>` to advance one turn.
|
|
64
|
+
* 4. If the advance result carries a continuation (goal still active) →
|
|
65
|
+
* emit a `{ decision: "block", reason: content }` JSON block to stdout.
|
|
66
|
+
* 5. If terminal (satisfied/abandoned/impossible) → stop normally.
|
|
67
|
+
*/
|
|
68
|
+
async function main() {
|
|
69
|
+
// 1. Resolve the active goal (per-agent scoped).
|
|
70
|
+
let statusEnvelope;
|
|
71
|
+
try {
|
|
72
|
+
const { stdout } = await execFileAsync('cleo', ['goal', 'status'], {
|
|
73
|
+
timeout: 10_000,
|
|
74
|
+
env: process.env,
|
|
75
|
+
});
|
|
76
|
+
statusEnvelope = JSON.parse(stdout.trim());
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// cleo unavailable or no project — stop normally.
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const goalData = statusEnvelope?.data;
|
|
83
|
+
// `active: null` means no active goal; an absent or null `id` similarly.
|
|
84
|
+
if (!goalData || 'active' in goalData || !goalData.id) {
|
|
85
|
+
// No active goal — stop normally.
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const goalId = goalData.id;
|
|
89
|
+
// 2. Advance one turn.
|
|
90
|
+
let advanceEnvelope;
|
|
91
|
+
try {
|
|
92
|
+
const { stdout } = await execFileAsync('cleo', ['goal', 'advance', goalId], {
|
|
93
|
+
timeout: 30_000,
|
|
94
|
+
env: process.env,
|
|
95
|
+
});
|
|
96
|
+
advanceEnvelope = JSON.parse(stdout.trim());
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Advance failed — stop normally rather than crashing.
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (!advanceEnvelope?.success) {
|
|
103
|
+
// Advance returned an error envelope — stop normally.
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const continuation = advanceEnvelope?.data?.continuation;
|
|
107
|
+
// 3. If there is a continuation nudge, emit the block decision.
|
|
108
|
+
if (continuation && typeof continuation.content === 'string') {
|
|
109
|
+
const decision = {
|
|
110
|
+
decision: 'block',
|
|
111
|
+
reason: continuation.content,
|
|
112
|
+
};
|
|
113
|
+
process.stdout.write(JSON.stringify(decision) + '\n'); // stdout-discipline-allowed: Stop-hook contract — Claude Code reads decision JSON from stdout to determine whether to continue // stdout-write-allowed: Stop-hook protocol output (not render layer — Claude Code reads raw stdout)
|
|
114
|
+
}
|
|
115
|
+
// Otherwise (terminal goal) — no output → Claude Code stops normally.
|
|
116
|
+
}
|
|
117
|
+
// Run and exit cleanly on any error (never crash the hook).
|
|
118
|
+
main().catch(() => {
|
|
119
|
+
process.exit(0);
|
|
120
|
+
});
|
|
121
|
+
//# sourceMappingURL=goal-stop-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goal-stop-hook.js","sourceRoot":"","sources":["../../src/harnesses/goal-stop-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAoC1C,8EAA8E;AAC9E,OAAO;AACP,8EAA8E;AAE9E;;;;;;;;;GASG;AACH,KAAK,UAAU,IAAI;IACjB,iDAAiD;IACjD,IAAI,cAAkC,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE;YACjE,OAAO,EAAE,MAAM;YACf,GAAG,EAAE,OAAO,CAAC,GAAG;SACjB,CAAC,CAAC;QACH,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAuB,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,kDAAkD;QAClD,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GAAG,cAAc,EAAE,IAAI,CAAC;IACtC,yEAAyE;IACzE,IAAI,CAAC,QAAQ,IAAI,QAAQ,IAAI,QAAQ,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACtD,kCAAkC;QAClC,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC;IAE3B,uBAAuB;IACvB,IAAI,eAAoC,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE;YAC1E,OAAO,EAAE,MAAM;YACf,GAAG,EAAE,OAAO,CAAC,GAAG;SACjB,CAAC,CAAC;QACH,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAwB,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,uDAAuD;QACvD,OAAO;IACT,CAAC;IAED,IAAI,CAAC,eAAe,EAAE,OAAO,EAAE,CAAC;QAC9B,sDAAsD;QACtD,OAAO;IACT,CAAC;IAED,MAAM,YAAY,GAAG,eAAe,EAAE,IAAI,EAAE,YAAY,CAAC;IAEzD,gEAAgE;IAChE,IAAI,YAAY,IAAI,OAAO,YAAY,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC7D,MAAM,QAAQ,GAAiB;YAC7B,QAAQ,EAAE,OAAO;YACjB,MAAM,EAAE,YAAY,CAAC,OAAO;SAC7B,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,oOAAoO;IAC7R,CAAC;IACD,sEAAsE;AACxE,CAAC;AAED,4DAA4D;AAC5D,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;IAChB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleocode/cleo-os",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.132",
|
|
4
4
|
"description": "CleoOS — the batteries-included agentic development environment wrapping Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cli.js",
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
"@mariozechner/pi-coding-agent": ">=0.60.0",
|
|
12
12
|
"@sinclair/typebox": "^0.34.49",
|
|
13
13
|
"env-paths": "^4.0.0",
|
|
14
|
-
"@cleocode/agents": "2026.5.
|
|
15
|
-
"@cleocode/cant": "2026.5.
|
|
16
|
-
"@cleocode/
|
|
17
|
-
"@cleocode/
|
|
18
|
-
"@cleocode/
|
|
14
|
+
"@cleocode/agents": "2026.5.132",
|
|
15
|
+
"@cleocode/cant": "2026.5.132",
|
|
16
|
+
"@cleocode/contracts": "2026.5.132",
|
|
17
|
+
"@cleocode/paths": "2026.5.132",
|
|
18
|
+
"@cleocode/core": "2026.5.132"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"typescript": "^6.0.2",
|