@cleocode/cleo 2026.4.5 → 2026.4.7
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/dist/cli/index.js +6620 -5782
- package/dist/cli/index.js.map +4 -4
- package/package.json +9 -8
- package/templates/cleoos-hub/README.md +34 -0
- package/templates/cleoos-hub/global-recipes/README.md +46 -0
- package/templates/cleoos-hub/global-recipes/justfile +68 -0
- package/templates/cleoos-hub/pi-extensions/cant-bridge.ts +989 -0
- package/templates/cleoos-hub/pi-extensions/orchestrator.ts +624 -0
- package/templates/cleoos-hub/pi-extensions/stage-guide.ts +164 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CleoOS stage-guide — before_agent_start hook that injects
|
|
3
|
+
* stage-aware LLM prompt guidance from CLEO into Pi's system prompt.
|
|
4
|
+
*
|
|
5
|
+
* Installed to: $CLEO_HOME/pi-extensions/stage-guide.ts
|
|
6
|
+
* Loaded by: Pi via `-e <path>` or settings.json extensions array
|
|
7
|
+
*
|
|
8
|
+
* Phase 2 — Pi harness registration + stage guidance injection.
|
|
9
|
+
*
|
|
10
|
+
* Protocol:
|
|
11
|
+
* 1. On Pi session start, query `cleo session status` for the active
|
|
12
|
+
* epic/scope.
|
|
13
|
+
* 2. On before_agent_start, shell out to `cleo lifecycle guidance --epicId <id>`
|
|
14
|
+
* to fetch the current stage's protocol prompt.
|
|
15
|
+
* 3. Return the prompt via `{ systemPrompt: ... }` so Pi prepends it
|
|
16
|
+
* to the LLM's effective system prompt for this turn.
|
|
17
|
+
*
|
|
18
|
+
* Fallback: If CLEO CLI is unavailable or no epic is active, the hook
|
|
19
|
+
* silently returns nothing (no injection) so Pi behaves normally.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { spawn } from "node:child_process";
|
|
23
|
+
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
24
|
+
|
|
25
|
+
interface LafsMinimalEnvelope<T = unknown> {
|
|
26
|
+
ok: boolean;
|
|
27
|
+
r?: T;
|
|
28
|
+
error?: { code: string | number; message: string };
|
|
29
|
+
_m?: { op: string; rid: string };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface StageGuidanceResult {
|
|
33
|
+
stage: string;
|
|
34
|
+
name: string;
|
|
35
|
+
order: number;
|
|
36
|
+
prompt: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface SessionStatusResult {
|
|
40
|
+
active: boolean;
|
|
41
|
+
sessionId?: string;
|
|
42
|
+
scope?: string;
|
|
43
|
+
epicId?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Invoke the `cleo` CLI with given args, parse stdout as LAFS envelope.
|
|
48
|
+
* Returns the unwrapped result payload or undefined on any failure.
|
|
49
|
+
*/
|
|
50
|
+
function cleoCli<T = unknown>(args: string[]): Promise<T | undefined> {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
let stdout = "";
|
|
53
|
+
let stderr = "";
|
|
54
|
+
const child = spawn("cleo", args, {
|
|
55
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
56
|
+
shell: false,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
child.stdout.on("data", (chunk) => {
|
|
60
|
+
stdout += chunk.toString();
|
|
61
|
+
});
|
|
62
|
+
child.stderr.on("data", (chunk) => {
|
|
63
|
+
stderr += chunk.toString();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
child.on("error", () => resolve(undefined));
|
|
67
|
+
child.on("exit", (code) => {
|
|
68
|
+
if (code !== 0) {
|
|
69
|
+
resolve(undefined);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Locate the JSON envelope line (CLI may log warnings above)
|
|
73
|
+
const lines = stdout.trim().split("\n");
|
|
74
|
+
const envLine = lines.reverse().find((l) => l.startsWith("{"));
|
|
75
|
+
if (!envLine) {
|
|
76
|
+
resolve(undefined);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const env = JSON.parse(envLine) as LafsMinimalEnvelope<T>;
|
|
81
|
+
if (env.ok && env.r !== undefined) {
|
|
82
|
+
resolve(env.r);
|
|
83
|
+
} else {
|
|
84
|
+
resolve(undefined);
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
resolve(undefined);
|
|
88
|
+
}
|
|
89
|
+
void stderr; // keep eslint happy, we intentionally discard stderr
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Resolve the active epic ID from `cleo session status`.
|
|
96
|
+
* Returns undefined if no session or no epic scope.
|
|
97
|
+
*/
|
|
98
|
+
async function resolveActiveEpicId(): Promise<string | undefined> {
|
|
99
|
+
const status = await cleoCli<SessionStatusResult>(["session", "status"]);
|
|
100
|
+
if (!status || !status.active) return undefined;
|
|
101
|
+
// Scope format examples: "epic:T123", "global", "task:T456"
|
|
102
|
+
if (status.scope && status.scope.startsWith("epic:")) {
|
|
103
|
+
return status.scope.slice("epic:".length);
|
|
104
|
+
}
|
|
105
|
+
return status.epicId;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Pi extension factory.
|
|
110
|
+
*
|
|
111
|
+
* Registers a `before_agent_start` hook that enriches the LLM system prompt
|
|
112
|
+
* with CLEO stage-aware guidance whenever an active epic has a running
|
|
113
|
+
* pipeline stage.
|
|
114
|
+
*/
|
|
115
|
+
export default function (pi: ExtensionAPI): void {
|
|
116
|
+
let activeEpicId: string | undefined;
|
|
117
|
+
let lastStage: string | undefined;
|
|
118
|
+
|
|
119
|
+
pi.on("session_start", async (_event, ctx: ExtensionContext) => {
|
|
120
|
+
try {
|
|
121
|
+
activeEpicId = await resolveActiveEpicId();
|
|
122
|
+
if (activeEpicId && ctx.hasUI) {
|
|
123
|
+
ctx.ui.setStatus("cleo-stage", `⚙ cleo:${activeEpicId}`);
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
// Non-fatal: Pi runs without CLEO injection.
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
pi.on("before_agent_start", async (_event, ctx: ExtensionContext) => {
|
|
131
|
+
// Re-resolve in case the operator switched epics mid-session
|
|
132
|
+
if (!activeEpicId) {
|
|
133
|
+
activeEpicId = await resolveActiveEpicId();
|
|
134
|
+
}
|
|
135
|
+
if (!activeEpicId) return {};
|
|
136
|
+
|
|
137
|
+
const guidance = await cleoCli<StageGuidanceResult>([
|
|
138
|
+
"lifecycle",
|
|
139
|
+
"guidance",
|
|
140
|
+
"--epicId",
|
|
141
|
+
activeEpicId,
|
|
142
|
+
"--format",
|
|
143
|
+
"markdown",
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
if (!guidance || !guidance.prompt) return {};
|
|
147
|
+
|
|
148
|
+
// Surface the stage in the status bar so the operator can see it
|
|
149
|
+
if (lastStage !== guidance.stage && ctx.hasUI) {
|
|
150
|
+
ctx.ui.setStatus(
|
|
151
|
+
"cleo-stage",
|
|
152
|
+
`⚙ cleo:${activeEpicId} [${guidance.name} ${guidance.order}/9]`,
|
|
153
|
+
);
|
|
154
|
+
lastStage = guidance.stage;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { systemPrompt: guidance.prompt };
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
pi.on("session_shutdown", async () => {
|
|
161
|
+
activeEpicId = undefined;
|
|
162
|
+
lastStage = undefined;
|
|
163
|
+
});
|
|
164
|
+
}
|