@hassangameryt/freeflow 0.7.1 → 0.7.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.
Files changed (122) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor-plugin/plugin.json +1 -1
  5. package/README.md +28 -23
  6. package/capabilities/cognitive-routing/SKILL.md +94 -311
  7. package/command-surface.json +7 -1
  8. package/gemini-extension.json +1 -1
  9. package/package.json +4 -4
  10. package/pi-extension/dist/cognitive-routing-v2/budget.js +42 -0
  11. package/pi-extension/dist/cognitive-routing-v2/config.js +120 -0
  12. package/pi-extension/dist/cognitive-routing-v2/projection.js +321 -0
  13. package/pi-extension/dist/cognitive-routing-v2/render.js +231 -0
  14. package/pi-extension/dist/cognitive-routing-v2/runtime.js +1585 -0
  15. package/pi-extension/dist/cognitive-routing-v2/schemas.js +107 -0
  16. package/pi-extension/dist/cognitive-routing-v2/state.js +612 -0
  17. package/pi-extension/dist/cognitive-routing-v2/tools.js +91 -0
  18. package/pi-extension/dist/cognitive-routing-v2/types.js +44 -0
  19. package/pi-extension/dist/freeflow-context/tool.js +5 -0
  20. package/pi-extension/dist/index.js +279 -624
  21. package/pi-extension/dist/runtime/runtime-context.js +63 -13
  22. package/pi-extension/dist/session-sources/events.js +195 -0
  23. package/pi-extension/dist/session-sources/read-only-session.js +148 -0
  24. package/pi-extension/dist/session-sources/sources.js +233 -0
  25. package/pi-extension/dist/settings/settings-ui.js +44 -117
  26. package/pi-extension/src/cognitive-routing-v2/budget.ts +42 -0
  27. package/pi-extension/src/cognitive-routing-v2/config.ts +143 -0
  28. package/pi-extension/src/cognitive-routing-v2/projection.ts +365 -0
  29. package/pi-extension/src/cognitive-routing-v2/render.ts +256 -0
  30. package/pi-extension/src/cognitive-routing-v2/runtime.ts +1634 -0
  31. package/pi-extension/src/cognitive-routing-v2/schemas.ts +109 -0
  32. package/pi-extension/src/cognitive-routing-v2/state.ts +622 -0
  33. package/pi-extension/src/cognitive-routing-v2/tools.ts +93 -0
  34. package/pi-extension/src/cognitive-routing-v2/types.ts +222 -0
  35. package/pi-extension/src/freeflow-context/tool.ts +5 -0
  36. package/pi-extension/src/index.ts +285 -674
  37. package/pi-extension/src/runtime/runtime-context.ts +75 -14
  38. package/pi-extension/src/session-sources/events.ts +208 -0
  39. package/pi-extension/src/session-sources/read-only-session.ts +170 -0
  40. package/pi-extension/src/session-sources/sources.ts +263 -0
  41. package/pi-extension/src/settings/settings-ui.ts +54 -131
  42. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +484 -0
  43. package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +183 -0
  44. package/pi-extension/tests/cognitive-routing-v2/native.test.js +441 -0
  45. package/pi-extension/tests/cognitive-routing-v2/performance.test.js +87 -0
  46. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +245 -0
  47. package/pi-extension/tests/cognitive-routing-v2/projection.test.js +241 -0
  48. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +236 -0
  49. package/pi-extension/tests/cognitive-routing-v2/render.test.js +230 -0
  50. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +153 -0
  51. package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +521 -0
  52. package/pi-extension/tests/cognitive-routing-v2/state.test.js +275 -0
  53. package/pi-extension/tests/context-virtualization/extension.test.js +16 -0
  54. package/pi-extension/tests/fixtures/routing-native.js +188 -0
  55. package/pi-extension/tests/integration/mode-free-core.test.js +3 -2
  56. package/pi-extension/tests/integration/pi-extension.test.js +154 -37
  57. package/pi-extension/tests/integration/prompt-architecture.test.js +87 -6
  58. package/pi-extension/tests/integration/unaffected-regressions.test.js +77 -10
  59. package/plugin.json +1 -1
  60. package/runtime/prompts/cognitive-routing.md +4 -5
  61. package/runtime/prompts/core.md +51 -45
  62. package/runtime/prompts/interaction-contract.md +5 -5
  63. package/skills/action-selection/SKILL.md +57 -97
  64. package/skills/design-for-depth/SKILL.md +58 -193
  65. package/skills/design-for-depth/references/implementation-decisions.md +76 -0
  66. package/skills/diagnose-failure/SKILL.md +64 -196
  67. package/skills/discuss/SKILL.md +64 -123
  68. package/skills/discuss/references/discussion-continuity.md +7 -3
  69. package/skills/execute-work/SKILL.md +69 -88
  70. package/skills/execute-work/references/execute-work-edges.md +9 -3
  71. package/skills/handoff/SKILL.md +44 -54
  72. package/skills/handoff/references/handoff-templates.md +15 -6
  73. package/skills/review-artifact/SKILL.md +80 -127
  74. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
  75. package/skills/review-work/SKILL.md +65 -115
  76. package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
  77. package/skills/simplify-code/SKILL.md +47 -100
  78. package/skills/track-work/SKILL.md +128 -119
  79. package/skills/track-work/references/working-record-format.md +14 -2
  80. package/skills/verify-work/SKILL.md +58 -62
  81. package/skills/verify-work/references/integration-evidence.md +4 -2
  82. package/skills/verify-work/references/test-design.md +89 -0
  83. package/skills/workflow/SKILL.md +73 -119
  84. package/skills/write-plan/SKILL.md +46 -77
  85. package/skills/write-plan/references/plan-shapes.md +5 -5
  86. package/skills/write-spec/SKILL.md +51 -63
  87. package/skills/write-spec/references/artifact-standards.md +2 -2
  88. package/skills/write-spec/references/decision-records.md +3 -2
  89. package/skills/write-spec/references/spec-shapes.md +15 -0
  90. package/pi-extension/dist/cognitive-routing/commands.js +0 -96
  91. package/pi-extension/dist/cognitive-routing/config.js +0 -232
  92. package/pi-extension/dist/cognitive-routing/controller.js +0 -1292
  93. package/pi-extension/dist/cognitive-routing/history.js +0 -451
  94. package/pi-extension/dist/cognitive-routing/host.js +0 -188
  95. package/pi-extension/dist/cognitive-routing/pi-routing-session.js +0 -414
  96. package/pi-extension/dist/cognitive-routing/pi-session-control.js +0 -320
  97. package/pi-extension/dist/cognitive-routing/runtime.js +0 -43
  98. package/pi-extension/dist/cognitive-routing/tool.js +0 -212
  99. package/pi-extension/dist/cognitive-routing/types.js +0 -6
  100. package/pi-extension/src/cognitive-routing/commands.ts +0 -129
  101. package/pi-extension/src/cognitive-routing/config.ts +0 -303
  102. package/pi-extension/src/cognitive-routing/controller.ts +0 -1628
  103. package/pi-extension/src/cognitive-routing/history.ts +0 -650
  104. package/pi-extension/src/cognitive-routing/host.ts +0 -266
  105. package/pi-extension/src/cognitive-routing/pi-routing-session.ts +0 -538
  106. package/pi-extension/src/cognitive-routing/pi-session-control.ts +0 -444
  107. package/pi-extension/src/cognitive-routing/runtime.ts +0 -57
  108. package/pi-extension/src/cognitive-routing/tool.ts +0 -273
  109. package/pi-extension/src/cognitive-routing/types.ts +0 -89
  110. package/pi-extension/tests/cognitive-routing/commands.test.js +0 -134
  111. package/pi-extension/tests/cognitive-routing/controller.test.js +0 -1128
  112. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +0 -1142
  113. package/pi-extension/tests/cognitive-routing/history.test.js +0 -274
  114. package/pi-extension/tests/cognitive-routing/host-identity.test.js +0 -63
  115. package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +0 -434
  116. package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +0 -174
  117. package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +0 -91
  118. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +0 -279
  119. package/pi-extension/tests/cognitive-routing/tool.test.js +0 -230
  120. package/skills/tdd/SKILL.md +0 -143
  121. package/skills/tdd/references/test-design.md +0 -96
  122. /package/pi-extension/tests/{cognitive-routing/host-fixture.js → fixtures/pi-host.js} +0 -0
@@ -5,6 +5,7 @@ import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import test from "node:test";
7
7
  import { promisify } from "node:util";
8
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
8
9
 
9
10
  import freeflowExtension from "../../dist/index.js";
10
11
  import {
@@ -15,7 +16,7 @@ import {
15
16
  setFreeflowStatus,
16
17
  setSessionCoreOverride,
17
18
  } from "../../dist/runtime/runtime-context.js";
18
- import { PIFLOW_HOST } from "../cognitive-routing/host-fixture.js";
19
+ import { PIFLOW_HOST } from "../fixtures/pi-host.js";
19
20
 
20
21
  const execFileAsync = promisify(execFile);
21
22
 
@@ -153,7 +154,7 @@ test("keeps Runtime State before the latest user message during context refreshe
153
154
  const conversation = [
154
155
  { role: "user", content: "release 0.6.1" },
155
156
  { role: "assistant", content: "delegating" },
156
- { role: "toolResult", toolName: "freeflow_switch_profile", content: [] },
157
+ { role: "toolResult", toolName: "freeflow_delegate", content: [] },
157
158
  ];
158
159
  const first = await handlers.get("context")({ messages: conversation }, ctx);
159
160
  const firstUserIndex = first.messages.findIndex((message) => message.role === "user");
@@ -187,6 +188,90 @@ test("keeps Runtime State before the latest user message during context refreshe
187
188
  }
188
189
  });
189
190
 
191
+ test("new routing tools and projection state follow the configured contract", async () => {
192
+ const cases = [
193
+ { projection: false, expectedMode: "disabled", exposesProjection: false },
194
+ // Projection can be effective while the Coordinator correctly lacks Executor-only tools.
195
+ { projection: true, expectedMode: "enabled", exposesProjection: false },
196
+ ];
197
+
198
+ for (const scenario of cases) {
199
+ const cwd = await configuredRepo({
200
+ cognitiveRouting: {
201
+ enabled: true,
202
+ projection: scenario.projection,
203
+ profiles: {
204
+ coordinator: { provider: "test", model: "model-a", thinking: "low" },
205
+ executor: { provider: "test", model: "model-b", thinking: "high" },
206
+ },
207
+ },
208
+ });
209
+ try {
210
+ let liveContext;
211
+ const loaded = loadExtension(freeflowExtension, null, {
212
+ appendEntry(customType, data) {
213
+ liveContext.sessionManager.appendCustomEntry(customType, data);
214
+ },
215
+ async setModel(model) {
216
+ liveContext.model = model;
217
+ return true;
218
+ },
219
+ setThinkingLevel(level) {
220
+ liveContext.thinkingLevel = level;
221
+ },
222
+ });
223
+ const ctx = context(cwd);
224
+ liveContext = ctx;
225
+ ctx.model = { provider: "test", id: "return" };
226
+ ctx.thinkingLevel = "medium";
227
+ ctx.modelRegistry = cognitiveRoutingModelRegistry();
228
+ ctx.isIdle = () => true;
229
+ ctx.sessionManager = SessionManager.create(cwd, join(cwd, "sessions"));
230
+ // Reconciliation needs genuine persisted ancestry, not a fake mutable branch array.
231
+ ctx.sessionManager.appendMessage({
232
+ role: "assistant",
233
+ content: [{ type: "text", text: "fixture seed" }],
234
+ timestamp: 1,
235
+ stopReason: "stop",
236
+ });
237
+ await loaded.handlers.get("session_start")({ type: "session_start", reason: "startup" }, ctx);
238
+ await loaded.handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
239
+
240
+ const toolNames = loaded.tools.map((tool) => tool.name);
241
+ assert.deepEqual(
242
+ toolNames.filter((name) =>
243
+ ["freeflow_delegate", "freeflow_return", "freeflow_unit", "freeflow_project"].includes(name),
244
+ ),
245
+ ["freeflow_delegate", "freeflow_return", "freeflow_unit", "freeflow_project"],
246
+ );
247
+ const delegate = loaded.tools.find((tool) => tool.name === "freeflow_delegate");
248
+ const returning = loaded.tools.find((tool) => tool.name === "freeflow_return");
249
+ assert.deepEqual(
250
+ delegate.parameters.oneOf.map((branch) => branch.properties.operation.enum[0]),
251
+ ["assign", "replace"],
252
+ );
253
+ assert.deepEqual(
254
+ returning.parameters.oneOf.map((branch) => branch.properties.operation.enum[0]),
255
+ ["submit", "retry"],
256
+ );
257
+ assert.equal(loaded.activeToolNames().includes("freeflow_project"), scenario.exposesProjection);
258
+
259
+ const providerContext = await loaded.handlers.get("context")({ messages: [] }, ctx);
260
+ assert.match(providerContext.messages[0].content, new RegExp("Projection: `" + scenario.expectedMode + "`"));
261
+
262
+ await loaded.commands.find((command) => command.name === "freeflow").definition.handler("profile executor", ctx);
263
+ const manualContext = await loaded.handlers.get("context")({ messages: [] }, ctx);
264
+ assert.match(
265
+ manualContext.messages[0].content,
266
+ new RegExp("Projection: `" + (scenario.projection ? "manual-bypass" : "disabled") + "`"),
267
+ );
268
+ assert.equal(loaded.activeToolNames().includes("freeflow_project"), false);
269
+ } finally {
270
+ await rm(cwd, { recursive: true, force: true });
271
+ }
272
+ }
273
+ });
274
+
190
275
  test("Pi registers the remaining Freeflow commands without mode controls or retired router tools", () => {
191
276
  const { commands, shortcuts, tools } = loadExtension();
192
277
  const commandNames = commands.map((command) => command.name);
@@ -199,12 +284,14 @@ test("Pi registers the remaining Freeflow commands without mode controls or reti
199
284
  assert.ok(!commandNames.includes("discover"));
200
285
  assert.ok(!commandNames.includes("execute-plan"));
201
286
  assert.ok(!commandNames.includes("workflow"));
287
+ assert.deepEqual(shortcuts, []);
288
+ assert.ok(tools.some((tool) => tool.name === "freeflow_context"));
202
289
  assert.deepEqual(
203
- shortcuts.map(({ shortcut }) => shortcut),
204
- ["ctrl+shift+r", "ctrl+shift+a"],
290
+ toolNames.filter((name) => name.startsWith("freeflow_")),
291
+ ["freeflow_delegate", "freeflow_return", "freeflow_unit", "freeflow_project", "freeflow_context"],
205
292
  );
206
- assert.ok(tools.some((tool) => tool.name === "freeflow_context"));
207
- assert.ok(tools.some((tool) => tool.name === "freeflow_switch_profile"));
293
+ assert.ok(!toolNames.includes("freeflow_switch_profile"));
294
+ assert.ok(!toolNames.includes("freeflow_cognitive_routing_history"));
208
295
  assert.ok(freeflowCommand);
209
296
  assert.ok(!freeflowCommand.definition.getArgumentCompletions("").some((item) => item.value === "mode"));
210
297
  assert.deepEqual(freeflowCommand.definition.getArgumentCompletions("mode "), []);
@@ -213,22 +300,27 @@ test("Pi registers the remaining Freeflow commands without mode controls or reti
213
300
  );
214
301
  });
215
302
 
216
- test("unsupported Pi hosts keep Cognitive Routing unavailable while exposing its configuration", async () => {
303
+ test("PiFlow keeps Cognitive Routing unavailable while exposing its configuration", async () => {
217
304
  const cwd = await configuredRepo({
218
305
  cognitiveRouting: {
219
306
  enabled: true,
220
307
  profiles: {
221
- standard: { provider: "test", model: "standard", thinkingLevel: "high" },
222
- reasoning: { provider: "test", model: "reasoning", thinkingLevel: "max" },
308
+ coordinator: { provider: "test", model: "coordinator", thinking: "high" },
309
+ executor: { provider: "test", model: "executor", thinking: "max" },
223
310
  },
224
311
  },
225
312
  });
226
313
  try {
227
- const { commands, handlers, shortcuts, tools } = loadExtension(freeflowExtension, null);
314
+ const { commands, handlers, shortcuts, tools, activeToolNames } = loadExtension(freeflowExtension, PIFLOW_HOST);
228
315
  const freeflowCommand = commands.find((command) => command.name === "freeflow");
229
316
  assert.ok(freeflowCommand);
230
317
  assert.deepEqual(shortcuts, []);
318
+ assert.deepEqual(
319
+ tools.filter((tool) => tool.name.startsWith("freeflow_")).map((tool) => tool.name),
320
+ ["freeflow_delegate", "freeflow_return", "freeflow_unit", "freeflow_project", "freeflow_context"],
321
+ );
231
322
  assert.ok(!tools.some((tool) => tool.name === "freeflow_switch_profile"));
323
+ assert.ok(!tools.some((tool) => tool.name === "freeflow_cognitive_routing_history"));
232
324
  assert.ok(!freeflowCommand.definition.getArgumentCompletions("").some((item) => item.value === "profile"));
233
325
  const capabilityState = await readCapabilityState(cwd, undefined, undefined);
234
326
  assert.equal(capabilityState.cognitiveRouting.enabled, true);
@@ -237,7 +329,8 @@ test("unsupported Pi hosts keep Cognitive Routing unavailable while exposing its
237
329
 
238
330
  const ctx = context(cwd);
239
331
  await handlers.get("session_start")({ type: "session_start" }, ctx);
240
- assert.match(ctx.statuses.at(-1).value, /cognitive blocked · host_unsupported/);
332
+ assert.match(ctx.statuses.at(-1).value, /cognitive blocked ·/);
333
+ assert.ok(!activeToolNames().some((name) => name.startsWith("freeflow_delegate") || name === "freeflow_return"));
241
334
  const before = await handlers.get("before_agent_start")({ systemPrompt: "base" }, ctx);
242
335
  assert.doesNotMatch(before.systemPrompt, /Cognitive Routing/);
243
336
  } finally {
@@ -250,8 +343,8 @@ test("normal Pi settings expose active Cognitive Routing configuration", async (
250
343
  cognitiveRouting: {
251
344
  enabled: true,
252
345
  profiles: {
253
- standard: { provider: "test", model: "model-a", thinkingLevel: "low" },
254
- reasoning: { provider: "test", model: "model-b", thinkingLevel: "high" },
346
+ coordinator: { provider: "test", model: "model-a", thinking: "low" },
347
+ executor: { provider: "test", model: "model-b", thinking: "high" },
255
348
  },
256
349
  },
257
350
  });
@@ -273,7 +366,7 @@ test("normal Pi settings expose active Cognitive Routing configuration", async (
273
366
  settingsCtx.ui.custom = async (factory) => {
274
367
  const component = factory({ requestRender() {} }, testTheme, {}, () => {});
275
368
  const rootText = renderText(component);
276
- assert.match(rootText, /Cognitive Routing\s+enabled \(5\) active/);
369
+ assert.match(rootText, /Cognitive Routing\s+enabled \(4\) active/);
277
370
  assert.doesNotMatch(rootText, /PiFlow only/);
278
371
  return undefined;
279
372
  };
@@ -296,14 +389,14 @@ test("Pi statusline reports only dynamic Cognitive Routing and context state", (
296
389
  conversationHistory: { effective: false },
297
390
  cognitiveRouting: { enabled: true, effective: true, blockingReason: null },
298
391
  },
299
- { effective: true, activeProfile: "reasoning", controlMode: "automatic" },
392
+ { effective: true, activeProfile: "executor", controlMode: "automatic" },
300
393
  readyFreeflowContext,
301
394
  );
302
- assert.equal(ctx.statuses.at(-1).value, "freeflow: reasoning · automatic · context");
395
+ assert.equal(ctx.statuses.at(-1).value, "freeflow: executor · automatic · context");
303
396
  assert.doesNotMatch(ctx.statuses.at(-1).value, /interaction|workflow|mode|skills/i);
304
397
  });
305
398
 
306
- test("Pi statusline shows the configured Cognitive Routing profile while activation is pending", () => {
399
+ test("Pi statusline defaults pending activation to the Coordinator profile", () => {
307
400
  const ctx = context();
308
401
  setFreeflowStatus(
309
402
  ctx,
@@ -316,7 +409,6 @@ test("Pi statusline shows the configured Cognitive Routing profile while activat
316
409
  cognitiveRouting: {
317
410
  enabled: true,
318
411
  effective: true,
319
- sessionStart: { profile: "reasoning" },
320
412
  blockingReason: null,
321
413
  },
322
414
  },
@@ -324,7 +416,7 @@ test("Pi statusline shows the configured Cognitive Routing profile while activat
324
416
  readyFreeflowContext,
325
417
  { cognitiveRoutingStartupPending: true },
326
418
  );
327
- assert.equal(ctx.statuses.at(-1).value, "freeflow: reasoning · pending");
419
+ assert.equal(ctx.statuses.at(-1).value, "freeflow: coordinator · pending");
328
420
  });
329
421
 
330
422
  test("Pi statusline keeps an inactive Cognitive Routing runtime blocked", () => {
@@ -339,20 +431,20 @@ test("Pi statusline keeps an inactive Cognitive Routing runtime blocked", () =>
339
431
  conversationHistory: { effective: false },
340
432
  cognitiveRouting: { enabled: true, effective: true, blockingReason: null },
341
433
  },
342
- { effective: false, activeProfile: "standard", controlMode: "automatic" },
434
+ { effective: false, activeProfile: "coordinator", controlMode: "automatic" },
343
435
  readyFreeflowContext,
344
436
  { cognitiveRoutingStartupPending: true },
345
437
  );
346
438
  assert.equal(ctx.statuses.at(-1).value, "freeflow: cognitive blocked · runtime_inactive");
347
439
  });
348
440
 
349
- test("PiFlow empty configured sessions show a pending profile before first prompt", async () => {
441
+ test("PiFlow keeps configured routing unavailable before the first prompt", async () => {
350
442
  const cwd = await configuredRepo({
351
443
  cognitiveRouting: {
352
444
  enabled: true,
353
445
  profiles: {
354
- standard: { provider: "test", model: "model-a", thinkingLevel: "low" },
355
- reasoning: { provider: "test", model: "model-b", thinkingLevel: "high" },
446
+ coordinator: { provider: "test", model: "model-a", thinking: "low" },
447
+ executor: { provider: "test", model: "model-b", thinking: "high" },
356
448
  },
357
449
  },
358
450
  });
@@ -361,7 +453,7 @@ test("PiFlow empty configured sessions show a pending profile before first promp
361
453
  const ctx = context(cwd);
362
454
  ctx.modelRegistry = cognitiveRoutingModelRegistry();
363
455
  await handlers.get("session_start")({ type: "session_start", reason: "startup" }, ctx);
364
- assert.equal(ctx.statuses.at(-1).value, "freeflow: reasoning · automatic");
456
+ assert.match(ctx.statuses.at(-1).value, /cognitive blocked ·/);
365
457
  } finally {
366
458
  await rm(cwd, { recursive: true, force: true });
367
459
  }
@@ -377,8 +469,16 @@ test("Pi exposes bypass scope argument completions", () => {
377
469
  ]);
378
470
  });
379
471
 
380
- test("Pi describes the mode-free Freeflow argument surface", () => {
381
- const { commands } = loadExtension();
472
+ test("Pi describes the mode-free Freeflow argument surface and manual profile controls", () => {
473
+ const { commands } = loadExtension(
474
+ freeflowExtension,
475
+ {},
476
+ {
477
+ appendEntry() {},
478
+ setModel() {},
479
+ setThinkingLevel() {},
480
+ },
481
+ );
382
482
  const freeflowCommand = commands.find((command) => command.name === "freeflow");
383
483
  assert.ok(freeflowCommand);
384
484
  assert.deepEqual(freeflowCommand.definition.getArgumentCompletions(""), [
@@ -386,9 +486,16 @@ test("Pi describes the mode-free Freeflow argument surface", () => {
386
486
  { value: "status", label: "status", description: "Show effective Freeflow state" },
387
487
  { value: "context", label: "context", description: "Inspect Freeflow Context" },
388
488
  { value: "profile", label: "profile", description: "Hold or release Cognitive Routing profile control" },
489
+ { value: "resume", label: "resume", description: "Resume the current saved routing responsibility" },
389
490
  { value: "enable", label: "enable", description: "Enable Freeflow for this repository" },
390
491
  { value: "disable", label: "disable", description: "Disable Freeflow for this repository" },
391
492
  ]);
493
+ assert.deepEqual(freeflowCommand.definition.getArgumentCompletions("profile "), [
494
+ { value: "profile coordinator", label: "coordinator", description: "Hold Coordinator manually" },
495
+ { value: "profile executor", label: "executor", description: "Hold Executor manually" },
496
+ { value: "profile auto", label: "auto", description: "Return to automatic Coordinator reconciliation" },
497
+ { value: "profile history", label: "history", description: "Read routing observations" },
498
+ ]);
392
499
  assert.deepEqual(freeflowCommand.definition.getArgumentCompletions("mode "), []);
393
500
  assert.deepEqual(freeflowCommand.definition.getArgumentCompletions("context "), [
394
501
  { value: "context status", label: "status", description: "Show Freeflow Context state" },
@@ -398,7 +505,7 @@ test("Pi describes the mode-free Freeflow argument surface", () => {
398
505
  ]);
399
506
  });
400
507
 
401
- test("Pi exposes 25 base skills without a mode skill or compatibility aliases", async () => {
508
+ test("Pi exposes 24 base skills without TDD, a mode skill, or compatibility aliases", async () => {
402
509
  const cwd = await configuredRepo();
403
510
  try {
404
511
  const { handlers, commands, sentMessages, sentMessageOptions } = loadExtension();
@@ -408,8 +515,9 @@ test("Pi exposes 25 base skills without a mode skill or compatibility aliases",
408
515
  assert.ok(match, `unexpected skill path: ${path}`);
409
516
  return match[1];
410
517
  });
411
- assert.equal(skillNames.length, 25);
518
+ assert.equal(skillNames.length, 24);
412
519
  assert.ok(skillNames.includes("workflow"));
520
+ assert.ok(!skillNames.includes("tdd"));
413
521
  assert.ok(!skillNames.includes("mode-contract"));
414
522
  assert.ok(!skillNames.includes("discover"));
415
523
  assert.ok(!skillNames.includes("execute-plan"));
@@ -738,13 +846,23 @@ test("Pi Cognitive Routing settings preserve complete presets", async () => {
738
846
  cognitiveRouting: {
739
847
  enabled: true,
740
848
  profiles: {
741
- standard: { provider: "test", model: "model-a", thinkingLevel: "low" },
742
- reasoning: { provider: "test", model: "model-b", thinkingLevel: "high" },
849
+ coordinator: { provider: "test", model: "model-a", thinking: "low" },
850
+ executor: { provider: "test", model: "model-b", thinking: "high" },
743
851
  },
744
852
  },
745
853
  });
746
854
  try {
747
- const { commands } = loadExtension();
855
+ const { commands } = loadExtension(
856
+ freeflowExtension,
857
+ {},
858
+ {
859
+ appendEntry() {},
860
+ async setModel() {
861
+ return true;
862
+ },
863
+ setThinkingLevel() {},
864
+ },
865
+ );
748
866
  const freeflowCommand = commands.find((command) => command.name === "freeflow");
749
867
  assert.ok(freeflowCommand);
750
868
  const settingsCtx = context(cwd);
@@ -759,9 +877,8 @@ test("Pi Cognitive Routing settings preserve complete presets", async () => {
759
877
  component.handleInput("\r");
760
878
  component.handleInput("\u001b[B");
761
879
  component.handleInput("\r");
762
- component.handleInput("\u001b[B");
763
880
  component.handleInput("\r");
764
- for (let index = 0; index < 4; index++) component.handleInput("\u001b[B");
881
+ for (let index = 0; index < 1; index++) component.handleInput("\u001b[B");
765
882
  component.handleInput("\r");
766
883
  component.handleInput("\r");
767
884
  await component.waitForWrites();
@@ -769,10 +886,10 @@ test("Pi Cognitive Routing settings preserve complete presets", async () => {
769
886
  };
770
887
  await freeflowCommand.definition.handler("settings repo", settingsCtx);
771
888
  const saved = JSON.parse(await readFile(join(cwd, ".freeflow/config.json"), "utf8"));
772
- assert.deepEqual(saved.cognitiveRouting.profiles.standard, {
889
+ assert.deepEqual(saved.cognitiveRouting.profiles.executor, {
773
890
  provider: "test",
774
891
  model: "model-b",
775
- thinkingLevel: "max",
892
+ thinking: "max",
776
893
  });
777
894
  assert.equal(settingsCtx.reloads.length, 1);
778
895
  } finally {
@@ -827,8 +944,8 @@ test("Pi preserves the host prompt when a mandatory prompt file is missing", asy
827
944
 
828
945
  function cognitiveRoutingModelRegistry() {
829
946
  const models = [
830
- { provider: "test", id: "model-a", name: "Model A", reasoning: true },
831
- { provider: "test", id: "model-b", name: "Model B", reasoning: true },
947
+ { provider: "test", id: "model-a", name: "Model A", reasoning: true, contextWindow: 128000 },
948
+ { provider: "test", id: "model-b", name: "Model B", reasoning: true, contextWindow: 128000 },
832
949
  ];
833
950
  const supported = new Map([
834
951
  ["model-a", new Set(["off", "low", "medium", "high"])],
@@ -6,11 +6,12 @@ import { tmpdir } from "node:os";
6
6
  import test from "node:test";
7
7
 
8
8
  import freeflowExtension from "../../dist/index.js";
9
- import { PIFLOW_HOST } from "../cognitive-routing/host-fixture.js";
9
+ import { PIFLOW_HOST } from "../fixtures/pi-host.js";
10
10
 
11
- function context(cwd) {
11
+ function context(cwd, systemPrompt = "") {
12
12
  return {
13
13
  cwd,
14
+ getSystemPrompt: () => systemPrompt,
14
15
  sessionManager: {
15
16
  getEntries: () => [],
16
17
  getBranch: () => [],
@@ -65,20 +66,44 @@ function lastRuntimeState(messages) {
65
66
  }
66
67
 
67
68
  test("re-entry recovery is stable and capability-neutral", async () => {
68
- const [core, cognitiveRouting, conversationHistory] = await Promise.all([
69
+ const [core, cognitiveRouting, conversationHistory, routingSkill] = await Promise.all([
69
70
  readFile(join(process.cwd(), "runtime", "prompts", "core.md"), "utf8"),
70
71
  readFile(join(process.cwd(), "runtime", "prompts", "cognitive-routing.md"), "utf8"),
71
72
  readFile(join(process.cwd(), "runtime", "prompts", "conversation-history.md"), "utf8"),
73
+ readFile(join(process.cwd(), "capabilities", "cognitive-routing", "SKILL.md"), "utf8"),
72
74
  ]);
73
75
 
76
+ assert.match(core, /## Load The Selected Method/);
77
+ assert.match(core, /Before applying a selected skill, read its current body/);
74
78
  assert.match(core, /## Recover After Context Loss/);
75
79
  assert.match(core, /latest Freeflow Runtime State/);
76
- assert.doesNotMatch(cognitiveRouting, /When Cognitive Routing is active and its skill is absent/);
77
- assert.match(cognitiveRouting, /A Runtime State refresh is host context, not a user interruption/);
80
+ assert.match(core, /recovery is not a bypass/);
81
+ assert.match(core, /evidence of prior approval; check that the approval still applies/);
82
+ assert.match(core, /choose or retain the current owner/);
83
+ assert.match(core, /apply its method and gather or produce evidence/);
84
+ assert.match(core, /read the complete `full` record/);
85
+ assert.match(core, /Make material gaps, contradictions, deferrals, and user-owned decisions explicit/);
86
+ assert.match(cognitiveRouting, /Before relying on automatic routing, read the complete cognitive-routing skill/);
87
+ assert.match(cognitiveRouting, /This bootstrap read is the only environment call/);
88
+ assert.match(cognitiveRouting, /If unavailable, stop and report the missing method/);
89
+ // The approved compact cue keeps bootstrap/control boundaries; the loaded
90
+ // method owns detailed role and handoff policy. Keep both obligations checked.
78
91
  assert.match(
79
92
  cognitiveRouting,
80
- /`Control` and `Profile` in the latest Runtime State report current compute state, not Yield, Delegate, `ACT_BOUNDED`, or boundary state/,
93
+ /Under Manual or inactive routing, stop applying the automatic split and follow ordinary Workflow/,
81
94
  );
95
+ assert.match(cognitiveRouting, /Use the latest Runtime State for control, profile, and current responsibility/);
96
+ assert.match(cognitiveRouting, /Coordinator and Executor in one agent\/session/);
97
+ assert.match(routingSkill, /Automatic delegation, role restrictions, and projection are bypassed/);
98
+ assert.match(routingSkill, /Put the actual contract inside `freeflow_delegate/);
99
+ assert.match(routingSkill, /submits the actual report and stops ordinary task work/);
100
+ assert.match(cognitiveRouting, /project every completed skill and instructional-reference read/);
101
+ assert.match(routingSkill, /Attach recovery to the existing responsibility/);
102
+ assert.match(
103
+ routingSkill,
104
+ /Returned work may use an attached recovery-only phase only if the runtime actually exposes it; otherwise inspect saved communication and stop if insufficient/,
105
+ );
106
+ assert.match(routingSkill, /Do not simulate recovery by replacing the assignment or report/);
82
107
  assert.doesNotMatch(
83
108
  conversationHistory,
84
109
  /Current user direction, live source truth, and present runtime state remain authoritative/,
@@ -98,8 +123,10 @@ test("composes the mandatory core fragments, optional capabilities, discovery, a
98
123
  const order = [
99
124
  "# Freeflow Stable Guidance",
100
125
  "## Shared Terms",
126
+ "## Load The Selected Method",
101
127
  "## Recover After Context Loss",
102
128
  "## Three Nested Loops",
129
+ "## Evidence And Judgment",
103
130
  "## Workflow Cue",
104
131
  "## Action Selection Cue",
105
132
  "## Supported Exit",
@@ -112,6 +139,18 @@ test("composes the mandatory core fragments, optional capabilities, discovery, a
112
139
  order,
113
140
  [...order].sort((a, b) => a - b),
114
141
  );
142
+ assert.match(
143
+ prompt,
144
+ /Before applying a selected skill, read its current body when its exact method is absent from context/,
145
+ );
146
+ assert.match(prompt, /Verification establishes what direct evidence proves at the observed boundary/);
147
+ assert.match(
148
+ prompt,
149
+ /Review judges whether work or an artifact is aligned, correct, suitable, and sufficiently evidenced/,
150
+ );
151
+ assert.match(prompt, /Interpret requested intent, not sentence form/);
152
+ assert.match(prompt, /answer the question before any action that depends on it/);
153
+ assert.match(prompt, /An unresolved question does not automatically suspend independent, clearly authorized work/);
115
154
  assert.doesNotMatch(prompt, /## Mode\b|strict-workflow|conversation mode|workflow mode/);
116
155
  assert.doesNotMatch(prompt, /Skills prompt/);
117
156
  assert.doesNotMatch(prompt, /# Workflow\n/);
@@ -124,6 +163,8 @@ test("composes the mandatory core fragments, optional capabilities, discovery, a
124
163
  assert.match(runtimeState.content, /Context Virtualization: active/);
125
164
  assert.match(runtimeState.content, /Conversation History: active/);
126
165
  assert.match(runtimeState.content, /Cognitive Routing: inactive/);
166
+ assert.match(runtimeState.content, /Control: `unavailable`/);
167
+ assert.match(runtimeState.content, /Projection: `disabled`/);
127
168
  assert.doesNotMatch(runtimeState.content, /Default mode|Active mode|Interaction Contract|Skills/);
128
169
 
129
170
  const resources = await handlers.get("resources_discover")({ cwd }, ctx);
@@ -137,6 +178,46 @@ test("composes the mandatory core fragments, optional capabilities, discovery, a
137
178
  }
138
179
  });
139
180
 
181
+ test("subagents keep Freeflow core and base skills without optional capabilities", async () => {
182
+ const cwd = await configuredRepo({
183
+ contextVirtualization: true,
184
+ conversationHistory: true,
185
+ cognitiveRouting: {
186
+ enabled: true,
187
+ profiles: {
188
+ coordinator: { provider: "test", model: "coordinator", thinking: "high" },
189
+ executor: { provider: "test", model: "executor", thinking: "max" },
190
+ },
191
+ },
192
+ });
193
+ try {
194
+ const { handlers, activeToolNames } = loadExtension(freeflowExtension, null);
195
+ const ctx = context(cwd, "<!-- freeflow-subagent-capabilities: disabled -->");
196
+ await handlers.get("session_start")({ type: "session_start" }, ctx);
197
+
198
+ const before = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
199
+ assert.match(before.systemPrompt, /# Freeflow Stable Guidance/);
200
+ assert.match(before.systemPrompt, /# Freeflow Interaction Contract/);
201
+ assert.doesNotMatch(before.systemPrompt, /## Cognitive Routing Cue/);
202
+ assert.doesNotMatch(before.systemPrompt, /## Context Virtualization Cue/);
203
+ assert.doesNotMatch(before.systemPrompt, /## Conversation History Cue/);
204
+
205
+ const resources = await handlers.get("resources_discover")({ cwd }, ctx);
206
+ assert.ok(resources.skillPaths.some((path) => path.endsWith("/skills/action-selection/SKILL.md")));
207
+ assert.ok(!resources.skillPaths.some((path) => path.includes("/capabilities/")));
208
+
209
+ const providerContext = await handlers.get("context")({ messages: [] }, ctx);
210
+ const runtimeState = lastRuntimeState(providerContext.messages);
211
+ assert.match(runtimeState.content, /Freeflow: active/);
212
+ assert.match(runtimeState.content, /Context Virtualization: inactive/);
213
+ assert.match(runtimeState.content, /Conversation History: inactive/);
214
+ assert.match(runtimeState.content, /Cognitive Routing: inactive/);
215
+ assert.ok(!activeToolNames().includes("freeflow_context"));
216
+ } finally {
217
+ await rm(cwd, { recursive: true, force: true });
218
+ }
219
+ });
220
+
140
221
  test("provider context reuses the before-agent surface until the next provider turn", async () => {
141
222
  const cwd = await configuredRepo({ contextVirtualization: true });
142
223
  try {