@cleocode/cleo 2026.4.15 → 2026.4.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/cleo",
3
- "version": "2026.4.15",
3
+ "version": "2026.4.16",
4
4
  "description": "CLEO CLI — the assembled product consuming @cleocode/core",
5
5
  "type": "module",
6
6
  "main": "./dist/cli/index.js",
@@ -13,12 +13,12 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "citty": "^0.2.1",
16
- "@cleocode/caamp": "2026.4.15",
17
- "@cleocode/cant": "2026.4.15",
18
- "@cleocode/core": "2026.4.15",
19
- "@cleocode/contracts": "2026.4.15",
20
- "@cleocode/lafs": "2026.4.15",
21
- "@cleocode/runtime": "2026.4.15"
16
+ "@cleocode/caamp": "2026.4.16",
17
+ "@cleocode/cant": "2026.4.16",
18
+ "@cleocode/contracts": "2026.4.16",
19
+ "@cleocode/lafs": "2026.4.16",
20
+ "@cleocode/core": "2026.4.16",
21
+ "@cleocode/runtime": "2026.4.16"
22
22
  },
23
23
  "engines": {
24
24
  "node": ">=24.0.0"
@@ -11,11 +11,19 @@
11
11
  * of all declared agents, teams, and tools without hand-authored
12
12
  * protocol text.
13
13
  *
14
- * MVP scope (Wave 2):
14
+ * Wave 2 scope:
15
15
  * - Scans project tier only: `<cwd>/.cleo/cant/` (recursive)
16
16
  * - Three-tier resolution (global, user, project) is Wave 5
17
17
  * - Prompt strategy: APPEND (per ULTRAPLAN L6, never replace)
18
18
  *
19
+ * Wave 8 additions (T420):
20
+ * - validate-on-load mental-model injection
21
+ * - When the spawned agent's CANT definition has a `mentalModel` block,
22
+ * fetches prior mental-model observations via memoryFind and injects
23
+ * them into the Pi system prompt with VALIDATE_ON_LOAD_PREAMBLE.
24
+ * - Exports `VALIDATE_ON_LOAD_PREAMBLE` and `buildMentalModelInjection`
25
+ * for testability (T421).
26
+ *
19
27
  * Requirements:
20
28
  * - `@cleocode/cant` must be installed (provides `compileBundle`)
21
29
  * - Pi coding agent runtime (`@mariozechner/pi-coding-agent`)
@@ -34,6 +42,69 @@ import type {
34
42
  ExtensionContext,
35
43
  } from "@mariozechner/pi-coding-agent";
36
44
 
45
+ // ============================================================================
46
+ // T420: validate-on-load constants and pure helpers
47
+ // ============================================================================
48
+
49
+ /**
50
+ * Preamble text injected into the Pi system prompt when an agent has a
51
+ * `mental_model:` CANT block. The agent MUST re-evaluate each observation
52
+ * against the current project state before acting.
53
+ *
54
+ * Exported so empirical tests (T421) can assert on its presence.
55
+ */
56
+ export const VALIDATE_ON_LOAD_PREAMBLE =
57
+ "===== MENTAL MODEL (validate-on-load) =====\n" +
58
+ "These are your prior observations, patterns, and learnings for this project.\n" +
59
+ "Before acting, you MUST re-evaluate each entry against current project state.\n" +
60
+ "If an entry is stale, note it and proceed with fresh understanding.";
61
+
62
+ /** Minimal observation shape returned by memoryFind / searchBrainCompact. */
63
+ export interface MentalModelObservation {
64
+ id: string;
65
+ type: string;
66
+ title: string;
67
+ date?: string;
68
+ }
69
+
70
+ /**
71
+ * Build the validate-on-load mental-model injection string.
72
+ *
73
+ * Pure function — no I/O, safe to call in tests without a real DB.
74
+ *
75
+ * @param agentName - Name of the spawned agent (used in the header line).
76
+ * @param observations - Prior mental-model observations to list.
77
+ * @returns System-prompt block containing the preamble and numbered observations,
78
+ * or an empty string when `observations` is empty.
79
+ */
80
+ export function buildMentalModelInjection(
81
+ agentName: string,
82
+ observations: MentalModelObservation[],
83
+ ): string {
84
+ if (observations.length === 0) return "";
85
+
86
+ const lines: string[] = [
87
+ "",
88
+ `// Agent: ${agentName}`,
89
+ VALIDATE_ON_LOAD_PREAMBLE,
90
+ "",
91
+ ];
92
+
93
+ for (let i = 0; i < observations.length; i++) {
94
+ const obs = observations[i];
95
+ const datePart = obs.date ? ` [${obs.date}]` : "";
96
+ lines.push(`${i + 1}. [${obs.id}] (${obs.type})${datePart}: ${obs.title}`);
97
+ }
98
+
99
+ lines.push("===== END MENTAL MODEL =====");
100
+
101
+ return lines.join("\n");
102
+ }
103
+
104
+ // ============================================================================
105
+ // Internal state
106
+ // ============================================================================
107
+
37
108
  /** Cached system prompt addendum from the last session_start compilation. */
38
109
  let bundlePrompt: string | null = null;
39
110
 
@@ -63,12 +134,80 @@ function discoverCantFiles(dir: string): string[] {
63
134
  }
64
135
  }
65
136
 
137
+ // ============================================================================
138
+ // T420: mental-model injection helper (async, calls memoryFind)
139
+ // ============================================================================
140
+
141
+ /**
142
+ * Fetch prior mental-model observations for an agent and build the
143
+ * validate-on-load injection block.
144
+ *
145
+ * Called in `before_agent_start` when the agent has a `mentalModel` CANT block.
146
+ * Best-effort: returns empty string on any failure so Pi is never blocked.
147
+ *
148
+ * @param agentName - Name of the spawned agent.
149
+ * @param projectRoot - Project root directory for brain.db access.
150
+ * @returns The validate-on-load system-prompt block, or "" on failure/empty.
151
+ */
152
+ async function fetchMentalModelInjection(
153
+ agentName: string,
154
+ projectRoot: string,
155
+ ): Promise<string> {
156
+ try {
157
+ // Lazy import: @cleocode/core may not be present in all environments.
158
+ // memoryFind is the engine-compat wrapper (T418) that accepts `agent`.
159
+ const coreModule = await import("@cleocode/core") as {
160
+ memoryFind?: (
161
+ params: {
162
+ query: string;
163
+ agent?: string;
164
+ limit?: number;
165
+ tables?: string[];
166
+ },
167
+ projectRoot?: string,
168
+ ) => Promise<{
169
+ success: boolean;
170
+ data?: {
171
+ results?: MentalModelObservation[];
172
+ };
173
+ }>;
174
+ };
175
+
176
+ if (typeof coreModule.memoryFind !== "function") return "";
177
+
178
+ // Fetch the 10 most recent mental-model observations for this agent.
179
+ // Use tables filter to avoid decisions/patterns/learnings which are
180
+ // not agent-scoped in the current schema.
181
+ const result = await coreModule.memoryFind(
182
+ {
183
+ query: agentName,
184
+ agent: agentName,
185
+ limit: 10,
186
+ tables: ["observations"],
187
+ },
188
+ projectRoot,
189
+ );
190
+
191
+ if (!result.success || !result.data?.results?.length) return "";
192
+
193
+ return buildMentalModelInjection(agentName, result.data.results);
194
+ } catch {
195
+ // Best-effort — never crash Pi
196
+ return "";
197
+ }
198
+ }
199
+
200
+ // ============================================================================
201
+ // Pi extension factory
202
+ // ============================================================================
203
+
66
204
  /**
67
205
  * Pi extension factory for the CleoOS CANT bridge.
68
206
  *
69
207
  * Registers event handlers for `session_start` (compile `.cant` files)
70
- * and `before_agent_start` (append compiled bundle to system prompt).
71
- * Also registers a `/cant:bundle-info` command for introspection.
208
+ * and `before_agent_start` (append compiled bundle + mental-model injection
209
+ * to system prompt). Also registers a `/cant:bundle-info` command for
210
+ * introspection.
72
211
  *
73
212
  * @param pi - The Pi extension API instance.
74
213
  */
@@ -141,16 +280,58 @@ export default function (pi: ExtensionAPI): void {
141
280
  }
142
281
  });
143
282
 
144
- // before_agent_start: APPEND compiled bundle prompt to system prompt
145
- pi.on("before_agent_start", async (event: { systemPrompt?: string }) => {
146
- if (!bundlePrompt) return {};
283
+ // before_agent_start: APPEND compiled bundle prompt + mental-model injection
284
+ // to system prompt (per ULTRAPLAN L6, never replace)
285
+ pi.on(
286
+ "before_agent_start",
287
+ async (
288
+ event: {
289
+ systemPrompt?: string;
290
+ agentName?: string;
291
+ /** T420: agent CANT definition, if resolved by Pi runtime. */
292
+ agentDef?: {
293
+ /** mentalModel block presence signals validate-on-load injection. */
294
+ mentalModel?: unknown;
295
+ };
296
+ /** Project root injected by Pi when available. */
297
+ projectRoot?: string;
298
+ },
299
+ ctx?: ExtensionContext,
300
+ ) => {
301
+ const existingPrompt = event.systemPrompt ?? "";
302
+ let appendix = "";
147
303
 
148
- // APPEND never replace (per ULTRAPLAN L6)
149
- const existingPrompt = event.systemPrompt ?? "";
150
- return {
151
- systemPrompt: existingPrompt + "\n\n" + bundlePrompt,
152
- };
153
- });
304
+ // APPEND CANT bundle prompt
305
+ if (bundlePrompt) {
306
+ appendix += "\n\n" + bundlePrompt;
307
+ }
308
+
309
+ // T420: validate-on-load mental-model injection.
310
+ // Inject when the agent has a `mentalModel` CANT block.
311
+ const agentName = event.agentName;
312
+ const hasMentalModel =
313
+ agentName !== undefined &&
314
+ agentName !== "" &&
315
+ event.agentDef?.mentalModel !== undefined;
316
+
317
+ if (hasMentalModel && agentName) {
318
+ // Resolve project root: prefer explicit field, fall back to ctx.cwd
319
+ const projectRoot = event.projectRoot ?? ctx?.cwd ?? "";
320
+ if (projectRoot) {
321
+ const mentalModelBlock = await fetchMentalModelInjection(agentName, projectRoot);
322
+ if (mentalModelBlock) {
323
+ appendix += mentalModelBlock;
324
+ }
325
+ }
326
+ }
327
+
328
+ if (!appendix) return {};
329
+
330
+ return {
331
+ systemPrompt: existingPrompt + appendix,
332
+ };
333
+ },
334
+ );
154
335
 
155
336
  // /cant:bundle-info — introspection command
156
337
  pi.registerCommand("cant:bundle-info", {