@hassangameryt/freeflow 0.6.0 → 0.7.1

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 (136) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +3 -2
  3. package/.codex-plugin/plugin.json +5 -6
  4. package/.cursor-plugin/plugin.json +25 -0
  5. package/README.md +66 -56
  6. package/capabilities/cognitive-routing/SKILL.md +288 -130
  7. package/com.github.copilot/hooks/hooks.json +11 -0
  8. package/command-surface.json +0 -24
  9. package/gemini-extension.json +5 -0
  10. package/hooks/adapters/claude-session-start.mjs +16 -0
  11. package/hooks/adapters/codex-session-start.mjs +16 -0
  12. package/hooks/adapters/copilot-session-start.mjs +13 -0
  13. package/hooks/adapters/cursor-session-start.mjs +13 -0
  14. package/hooks/adapters/gemini-session-start.mjs +15 -0
  15. package/hooks/claude/hooks.json +18 -0
  16. package/hooks/codex/hooks.json +18 -0
  17. package/hooks/cursor/hooks.json +9 -0
  18. package/hooks/freeflow-runtime-context.mjs +3 -650
  19. package/hooks/hooks.json +4 -30
  20. package/hooks/shared/runtime-context.mjs +210 -0
  21. package/hooks/tests/check-host-adapters.test.mjs +82 -0
  22. package/hooks/tests/check-runtime-context-hook.sh +140 -320
  23. package/package.json +20 -4
  24. package/pi-extension/dist/cognitive-routing/commands.js +3 -3
  25. package/pi-extension/dist/cognitive-routing/config.js +16 -20
  26. package/pi-extension/dist/cognitive-routing/controller.js +478 -48
  27. package/pi-extension/dist/cognitive-routing/history.js +12 -0
  28. package/pi-extension/dist/cognitive-routing/host.js +74 -12
  29. package/pi-extension/dist/cognitive-routing/pi-routing-session.js +414 -0
  30. package/pi-extension/dist/cognitive-routing/pi-session-control.js +320 -0
  31. package/pi-extension/dist/cognitive-routing/tool.js +1 -0
  32. package/pi-extension/dist/cognitive-routing/types.js +1 -1
  33. package/pi-extension/dist/index.js +236 -219
  34. package/pi-extension/dist/runtime/runtime-context.js +126 -286
  35. package/pi-extension/dist/settings/settings-ui.js +52 -336
  36. package/pi-extension/src/cognitive-routing/commands.ts +15 -5
  37. package/pi-extension/src/cognitive-routing/config.ts +16 -20
  38. package/pi-extension/src/cognitive-routing/controller.ts +561 -51
  39. package/pi-extension/src/cognitive-routing/history.ts +12 -0
  40. package/pi-extension/src/cognitive-routing/host.ts +110 -16
  41. package/pi-extension/src/cognitive-routing/pi-routing-session.ts +538 -0
  42. package/pi-extension/src/cognitive-routing/pi-session-control.ts +444 -0
  43. package/pi-extension/src/cognitive-routing/tool.ts +1 -0
  44. package/pi-extension/src/cognitive-routing/types.ts +1 -1
  45. package/pi-extension/src/index.ts +249 -239
  46. package/pi-extension/src/runtime/runtime-context.ts +129 -305
  47. package/pi-extension/src/settings/settings-ui.ts +53 -371
  48. package/pi-extension/tests/cognitive-routing/commands.test.js +1 -1
  49. package/pi-extension/tests/cognitive-routing/controller.test.js +69 -40
  50. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +128 -57
  51. package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +434 -0
  52. package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +174 -0
  53. package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +91 -0
  54. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +47 -4
  55. package/pi-extension/tests/integration/mode-free-core.test.js +218 -0
  56. package/pi-extension/tests/integration/pi-extension.test.js +430 -2478
  57. package/pi-extension/tests/integration/prompt-architecture.test.js +124 -79
  58. package/pi-extension/tests/integration/unaffected-regressions.test.js +411 -0
  59. package/plugin.json +33 -0
  60. package/runtime/prompts/cognitive-routing.md +5 -4
  61. package/runtime/prompts/conversation-history.md +1 -1
  62. package/runtime/prompts/core.md +77 -6
  63. package/runtime/prompts/interaction-contract.md +6 -2
  64. package/skills/action-selection/SKILL.md +93 -44
  65. package/skills/action-selection/references/trajectory-stalls.md +71 -0
  66. package/skills/bypass/SKILL.md +109 -39
  67. package/skills/commit-work/SKILL.md +94 -55
  68. package/skills/commit-work/references/git-state-edges.md +94 -0
  69. package/skills/commit-work/references/simple-push.md +92 -0
  70. package/skills/commit-work/references/staging-decisions.md +79 -46
  71. package/skills/decision-gate/SKILL.md +80 -22
  72. package/skills/design-for-depth/SKILL.md +208 -43
  73. package/skills/design-for-depth/references/design-pressure-signals.md +108 -240
  74. package/skills/design-for-depth/references/interface-design-loop.md +77 -97
  75. package/skills/design-for-depth/references/module-and-dependency-design.md +147 -0
  76. package/skills/design-for-depth/references/software-design-philosophy.md +127 -173
  77. package/skills/design-for-depth/references/state-and-failure-boundaries.md +136 -0
  78. package/skills/diagnose-failure/SKILL.md +198 -52
  79. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +19 -5
  80. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +13 -5
  81. package/skills/discuss/SKILL.md +118 -76
  82. package/skills/discuss/references/discussion-continuity.md +84 -32
  83. package/skills/evaluate-skill/SKILL.md +166 -52
  84. package/skills/evaluate-skill/references/evaluation-definition-schema.md +2 -2
  85. package/skills/evaluate-skill/references/evaluation-design.md +56 -30
  86. package/skills/evaluate-skill/references/execution-and-evidence.md +1 -1
  87. package/skills/evaluate-skill/references/review-and-revision.md +62 -21
  88. package/skills/execute-work/SKILL.md +98 -59
  89. package/skills/execute-work/references/code-practices.md +4 -4
  90. package/skills/execute-work/references/execute-work-edges.md +42 -22
  91. package/skills/review-artifact/SKILL.md +132 -71
  92. package/skills/review-artifact/references/adjudicate-artifact-review.md +101 -0
  93. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +52 -41
  94. package/skills/review-work/SKILL.md +115 -72
  95. package/skills/review-work/references/adjudicate-work-review.md +97 -0
  96. package/skills/review-work/references/independent-work-reviewer-contract.md +40 -35
  97. package/skills/review-work/references/security-risk-lens.md +17 -15
  98. package/skills/setup-freeflow/SKILL.md +14 -17
  99. package/skills/setup-freeflow/references/activation-contract.md +24 -30
  100. package/skills/setup-freeflow/references/host-setup.md +6 -7
  101. package/skills/simplify-code/SKILL.md +97 -39
  102. package/skills/simplify-code/references/simplification-patterns.md +1 -1
  103. package/skills/tdd/SKILL.md +108 -41
  104. package/skills/tdd/references/test-design.md +1 -1
  105. package/skills/track-work/SKILL.md +133 -300
  106. package/skills/track-work/references/working-record-format.md +515 -0
  107. package/skills/track-work/scripts/lib/document.mjs +713 -0
  108. package/skills/track-work/scripts/lib/format.mjs +166 -0
  109. package/skills/track-work/scripts/lib/store.mjs +233 -0
  110. package/skills/track-work/scripts/lib/transitions.mjs +790 -0
  111. package/skills/track-work/scripts/lib/views.mjs +27 -430
  112. package/skills/track-work/scripts/working-record.mjs +292 -98
  113. package/skills/verify-work/SKILL.md +46 -20
  114. package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
  115. package/skills/verify-work/references/integration-evidence.md +1 -7
  116. package/skills/verify-work/references/performance-evidence.md +1 -1
  117. package/skills/workflow/SKILL.md +110 -80
  118. package/skills/workflow/references/domain-skill-composition.md +24 -18
  119. package/skills/write-skill/SKILL.md +218 -62
  120. package/skills/write-skill/references/activation-boundaries.md +63 -21
  121. package/skills/write-skill/references/agent-first-instructions.md +61 -27
  122. package/skills/write-skill/references/progressive-disclosure.md +91 -41
  123. package/skills/write-skill/references/skill-dependency-graphs.md +190 -0
  124. package/runtime/prompts/skills.md +0 -65
  125. package/skills/mode-contract/SKILL.md +0 -104
  126. package/skills/track-work/scripts/lib/codec.mjs +0 -570
  127. package/skills/track-work/scripts/lib/commands.mjs +0 -568
  128. package/skills/track-work/scripts/lib/contract.mjs +0 -303
  129. package/skills/track-work/scripts/lib/model.mjs +0 -506
  130. package/skills/track-work/scripts/lib/operations.mjs +0 -965
  131. package/skills/track-work/scripts/lib/record-store.mjs +0 -264
  132. package/skills/track-work/scripts/lib/result.mjs +0 -37
  133. package/skills/track-work/scripts/lib/rewrites.mjs +0 -740
  134. package/skills/track-work/scripts/lib/schema.mjs +0 -277
  135. package/skills/track-work/scripts/lib/source-inventory.mjs +0 -162
  136. package/skills/track-work/scripts/lib/workspace.mjs +0 -134
@@ -89,7 +89,7 @@ test("releases manual ownership through auto without a model transition", async
89
89
 
90
90
  assert.equal(await handleCognitiveRoutingProfileCommand("profile auto", context, controller), true);
91
91
  assert.equal(calls, 1);
92
- assert.match(context.notifications[0].message, /automatic control is active/);
92
+ assert.match(context.notifications[0].message, /automatic Reasoning control is active/);
93
93
  });
94
94
 
95
95
  test("rejects manual profile changes while Pi is running", async () => {
@@ -103,21 +103,21 @@ test("prepares activation before acquiring and applying the host pair", async ()
103
103
  });
104
104
  assert.deepEqual(host.calls[0][2].target, {
105
105
  provider: "faux",
106
- modelId: "standard",
107
- thinkingLevel: "high",
106
+ modelId: "reasoning",
107
+ thinkingLevel: "max",
108
108
  });
109
- assert.equal(controller.state().activeProfile, "standard");
109
+ assert.equal(controller.state().activeProfile, "reasoning");
110
110
  });
111
111
 
112
112
  test("activates each configured session-start control and profile combination", async () => {
113
113
  const combinations = [
114
- ["automatic", "standard", "automatic"],
115
- ["automatic", "reasoning", "automatic"],
116
- ["manual", "standard", "manual-standard"],
117
- ["manual", "reasoning", "manual-reasoning"],
114
+ ["automatic", "standard", "automatic", "reasoning"],
115
+ ["automatic", "reasoning", "automatic", "reasoning"],
116
+ ["manual", "standard", "manual-standard", "standard"],
117
+ ["manual", "reasoning", "manual-reasoning", "reasoning"],
118
118
  ];
119
119
 
120
- for (const [control, profile, expectedControlMode] of combinations) {
120
+ for (const [control, profile, expectedControlMode, expectedProfile] of combinations) {
121
121
  const host = createHost({ onAcquire: appliedLease });
122
122
  const controller = new CognitiveRoutingController({
123
123
  capabilityState: {
@@ -136,9 +136,9 @@ test("activates each configured session-start control and profile combination",
136
136
  const result = await controller.activate();
137
137
 
138
138
  assert.equal(result.status, "active");
139
- assert.equal(controller.state().activeProfile, profile);
139
+ assert.equal(controller.state().activeProfile, expectedProfile);
140
140
  assert.equal(controller.state().controlMode, expectedControlMode);
141
- assert.equal(host.calls[2][1].modelId, profile);
141
+ assert.equal(host.calls[2][1].modelId, expectedProfile);
142
142
  }
143
143
  });
144
144
 
@@ -201,14 +201,14 @@ test("does not persist or reapply an automatic request for the active profile",
201
201
  await controller.activate();
202
202
  const callsBefore = host.calls.length;
203
203
 
204
- const result = await controller.switchAutomaticProfile("standard", "Keep the current profile.");
204
+ const result = await controller.switchAutomaticProfile("reasoning", "Keep the current profile.");
205
205
 
206
206
  assert.deepEqual(result, {
207
207
  status: "active",
208
208
  changed: false,
209
- from: "standard",
210
- to: "standard",
211
- profile: "standard",
209
+ from: "reasoning",
210
+ to: "reasoning",
211
+ profile: "reasoning",
212
212
  });
213
213
  assert.equal(host.calls.length, callsBefore);
214
214
  });
@@ -226,19 +226,19 @@ test("switches profiles through the automatic owner with bounded agent evidence"
226
226
  });
227
227
  await controller.activate();
228
228
 
229
- const result = await controller.switchAutomaticProfile("reasoning", "Need a deeper analysis.");
229
+ const result = await controller.switchAutomaticProfile("standard", "Need a lighter execution pass.");
230
230
 
231
231
  assert.deepEqual(result, {
232
232
  status: "active",
233
233
  changed: true,
234
- from: "standard",
235
- to: "reasoning",
236
- profile: "reasoning",
234
+ from: "reasoning",
235
+ to: "standard",
236
+ profile: "standard",
237
237
  });
238
238
  assert.equal(controller.state().controlMode, "automatic");
239
239
  assert.equal(host.calls.at(-1)[0], "setState");
240
240
  assert.equal(host.calls[3][2].source, "agent");
241
- assert.equal(host.calls[3][2].reason, "Need a deeper analysis.");
241
+ assert.equal(host.calls[3][2].reason, "Need a lighter execution pass.");
242
242
  });
243
243
 
244
244
  test("blocks an automatic switch while a manual hold owns the controller", async () => {
@@ -285,6 +285,21 @@ test("releases manual control as a durable control-only entry without switching
285
285
  );
286
286
  });
287
287
 
288
+ test("releases a manual Standard hold by switching to automatic Reasoning", async () => {
289
+ const host = createHost({ onAcquire: appliedLease });
290
+ const controller = new CognitiveRoutingController({ capabilityState, pi: host.pi, ctx: host.ctx });
291
+ await controller.activate();
292
+ await controller.setManualProfile("standard");
293
+
294
+ const result = await controller.setAutomaticControl("profile-command");
295
+
296
+ assert.deepEqual(result, { status: "automatic" });
297
+ assert.equal(controller.state().activeProfile, "reasoning");
298
+ assert.equal(controller.state().controlMode, "automatic");
299
+ assert.equal(host.calls.at(-1)[0], "setState");
300
+ assert.equal(host.calls.at(-1)[1].modelId, "reasoning");
301
+ });
302
+
288
303
  test("does not acquire or switch when durable activation preparation fails", async () => {
289
304
  let acquireCalls = 0;
290
305
  const host = createHost({
@@ -422,7 +437,7 @@ test("serializes deactivation behind an in-flight activation", async () => {
422
437
  lease: {
423
438
  async setState(request) {
424
439
  calls.push(["setState", request]);
425
- if (request.modelId === "standard") {
440
+ if (request.modelId === "reasoning") {
426
441
  resolveTransition();
427
442
  await new Promise((resolve) => {
428
443
  releaseTransition = resolve;
@@ -1003,14 +1018,14 @@ test("writes v2 mechanism, causal, and baseline metadata before transitions", as
1003
1018
  pi: host.pi,
1004
1019
  ctx: host.ctx,
1005
1020
  idFactory: (() => {
1006
- const ids = ["epoch-1", "activation-correlation", "profile-correlation"];
1021
+ const ids = ["epoch-1", "activation-correlation", "standard-correlation", "automatic-reasoning-correlation"];
1007
1022
  return () => ids.shift();
1008
1023
  })(),
1009
1024
  });
1010
1025
 
1011
1026
  await controller.activate();
1012
- await controller.switchAutomaticProfile("reasoning", "Need a deeper analysis.");
1013
- await controller.setAutomaticControl();
1027
+ await controller.switchAutomaticProfile("standard", "Need a lighter execution pass.");
1028
+ await controller.setAutomaticControl("profile-command");
1014
1029
 
1015
1030
  const intents = host.sessionEntries.filter(
1016
1031
  (entry) => entry.type === "custom" && entry.customType === COGNITIVE_ROUTING_INTENT_ENTRY,
@@ -1025,10 +1040,10 @@ test("writes v2 mechanism, causal, and baseline metadata before transitions", as
1025
1040
  branchId: null,
1026
1041
  epoch: "epoch-1",
1027
1042
  correlationId: "activation-correlation",
1028
- target: { provider: "faux", modelId: "standard", thinkingLevel: "high" },
1043
+ target: { provider: "faux", modelId: "reasoning", thinkingLevel: "max" },
1029
1044
  returnTarget: { provider: "faux", modelId: "return", thinkingLevel: "medium" },
1030
1045
  fromPair: { provider: "faux", modelId: "return", thinkingLevel: "medium" },
1031
- profile: "standard",
1046
+ profile: "reasoning",
1032
1047
  });
1033
1048
  assert.deepEqual(intents[1].data, {
1034
1049
  version: 2,
@@ -1037,30 +1052,44 @@ test("writes v2 mechanism, causal, and baseline metadata before transitions", as
1037
1052
  control: "automatic",
1038
1053
  source: "agent",
1039
1054
  mechanism: "agent-tool",
1040
- decisionCorrelationId: "profile-correlation",
1055
+ decisionCorrelationId: "standard-correlation",
1056
+ fromControl: "automatic",
1041
1057
  branchId: null,
1042
1058
  epoch: "epoch-1",
1043
- correlationId: "profile-correlation",
1044
- profile: "reasoning",
1045
- target: { provider: "faux", modelId: "reasoning", thinkingLevel: "max" },
1059
+ correlationId: "standard-correlation",
1060
+ profile: "standard",
1061
+ target: { provider: "faux", modelId: "standard", thinkingLevel: "high" },
1046
1062
  returnTarget: { provider: "faux", modelId: "return", thinkingLevel: "medium" },
1047
- fromPair: { provider: "faux", modelId: "standard", thinkingLevel: "high" },
1048
- fromProfile: "standard",
1049
- reason: "Need a deeper analysis.",
1063
+ fromPair: { provider: "faux", modelId: "reasoning", thinkingLevel: "max" },
1064
+ fromProfile: "reasoning",
1065
+ reason: "Need a lighter execution pass.",
1050
1066
  });
1051
-
1052
- const control = host.sessionEntries.find(
1053
- (entry) => entry.type === "custom" && entry.customType === COGNITIVE_ROUTING_CONTROL_ENTRY,
1054
- );
1055
- assert.deepEqual(control.data, {
1067
+ assert.deepEqual(intents[2].data, {
1056
1068
  version: 2,
1069
+ phase: "prepared",
1070
+ kind: "profile",
1057
1071
  control: "automatic",
1058
1072
  source: "user",
1059
1073
  mechanism: "profile-command",
1060
- reason: "manual hold release",
1074
+ decisionCorrelationId: "automatic-reasoning-correlation",
1075
+ fromControl: "automatic",
1061
1076
  branchId: null,
1062
1077
  epoch: "epoch-1",
1078
+ correlationId: "automatic-reasoning-correlation",
1079
+ profile: "reasoning",
1080
+ target: { provider: "faux", modelId: "reasoning", thinkingLevel: "max" },
1081
+ returnTarget: { provider: "faux", modelId: "return", thinkingLevel: "medium" },
1082
+ fromPair: { provider: "faux", modelId: "standard", thinkingLevel: "high" },
1083
+ fromProfile: "standard",
1084
+ reason: "automatic control selects the Reasoning profile",
1063
1085
  });
1086
+
1087
+ assert.equal(
1088
+ host.sessionEntries.some(
1089
+ (entry) => entry.type === "custom" && entry.customType === COGNITIVE_ROUTING_CONTROL_ENTRY,
1090
+ ),
1091
+ false,
1092
+ );
1064
1093
  });
1065
1094
 
1066
1095
  test("fails closed before a profile transition when no current baseline is observable", async () => {
@@ -1072,7 +1101,7 @@ test("fails closed before a profile transition when no current baseline is obser
1072
1101
  host.ctx.model = undefined;
1073
1102
  host.ctx.thinkingLevel = undefined;
1074
1103
 
1075
- const result = await controller.switchAutomaticProfile("reasoning", "Need a deeper analysis.");
1104
+ const result = await controller.switchAutomaticProfile("standard", "Need a deeper analysis.");
1076
1105
 
1077
1106
  assert.deepEqual(result, { status: "inactive", reason: "current_state_unavailable" });
1078
1107
  assert.equal(host.calls.length, callsBefore);
@@ -1094,6 +1123,6 @@ test("restores the return target without guessing a missing closing baseline", a
1094
1123
  (entry) => entry.type === "custom" && entry.customType === COGNITIVE_ROUTING_INTENT_ENTRY,
1095
1124
  );
1096
1125
  assert.equal(closingIntent.data.fromPair, undefined);
1097
- assert.equal(closingIntent.data.fromProfile, "standard");
1126
+ assert.equal(closingIntent.data.fromProfile, "reasoning");
1098
1127
  assert.equal(host.sessionEntries.at(-1).type, "model_state_change");
1099
1128
  });
@@ -137,7 +137,7 @@ function createContext(cwd, host) {
137
137
  };
138
138
  }
139
139
 
140
- test("registered shortcuts cycle manual holds and automatic profiles", async () => {
140
+ test("registered shortcuts cycle manual holds and establish automatic Reasoning", async () => {
141
141
  const cwd = await mkdtemp(join(tmpdir(), "freeflow-cognitive-routing-shortcuts-"));
142
142
  await mkdir(join(cwd, ".freeflow"));
143
143
  await writeFile(
@@ -157,38 +157,29 @@ test("registered shortcuts cycle manual holds and automatic profiles", async ()
157
157
  ctx.isIdle = () => true;
158
158
  try {
159
159
  await host.handlers.get("session_start")({ type: "session_start", reason: "startup" }, ctx);
160
+ await host.handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
160
161
  const cycle = host.shortcuts.find(({ shortcut }) => shortcut === "ctrl+shift+r");
161
162
  const automatic = host.shortcuts.find(({ shortcut }) => shortcut === "ctrl+shift+a");
162
163
  assert.ok(cycle);
163
164
  assert.ok(automatic);
164
165
 
165
166
  await cycle.definition.handler(ctx);
166
- assert.equal(host.entries.at(-1).modelId, "reasoning");
167
+ assert.equal(host.entries.filter((entry) => entry.modelId).at(-1).modelId, "standard");
168
+
167
169
  await automatic.definition.handler(ctx);
168
- assert.equal(host.entries.at(-1).customType, "freeflow-cognitive-routing-control");
169
170
  assert.equal(host.entries.filter((entry) => entry.modelId).at(-1).modelId, "reasoning");
171
+ const modelEntriesAfterAutomatic = host.entries.filter((entry) => entry.modelId).length;
172
+
170
173
  await automatic.definition.handler(ctx);
171
- assert.equal(host.entries.at(-1).modelId, "standard");
172
- await automatic.definition.handler(ctx);
173
- assert.equal(host.entries.at(-1).modelId, "reasoning");
174
+ assert.equal(host.entries.filter((entry) => entry.modelId).at(-1).modelId, "reasoning");
175
+ assert.equal(host.entries.filter((entry) => entry.modelId).length, modelEntriesAfterAutomatic);
176
+
174
177
  const automaticIntent = host.entries
175
178
  .filter((entry) => entry.customType === "freeflow-cognitive-routing-intent")
176
179
  .at(-1);
177
180
  assert.equal(automaticIntent.data.source, "user");
178
181
  assert.equal(automaticIntent.data.mechanism, "profile-shortcut");
179
- assert.equal(automaticIntent.data.reason, "Cycle automatic profile to reasoning.");
180
- assert.deepEqual(host.operations, [
181
- "prepare",
182
- "acquire",
183
- "setState",
184
- "prepare",
185
- "setState",
186
- "prepare",
187
- "prepare",
188
- "setState",
189
- "prepare",
190
- "setState",
191
- ]);
182
+ assert.equal(automaticIntent.data.reason, "automatic control selects the Reasoning profile");
192
183
  } finally {
193
184
  await rm(cwd, { recursive: true, force: true });
194
185
  }
@@ -295,7 +286,7 @@ test("Cognitive Routing retries activation after a transient failure", async ()
295
286
  const providerContext = await host.handlers.get("context")({ messages: [] }, ctx);
296
287
  assert.match(providerContext.messages.at(-1).content, /Cognitive Routing: active/);
297
288
  assert.match(providerContext.messages.at(-1).content, /Control: `automatic`/);
298
- assert.match(providerContext.messages.at(-1).content, /Profile: `standard`/);
289
+ assert.match(providerContext.messages.at(-1).content, /Profile: `reasoning`/);
299
290
  assert.ok(host.operations.includes("setState"));
300
291
  } finally {
301
292
  await host.handlers.get("session_shutdown")({ type: "session_shutdown", reason: "test-cleanup" }, ctx);
@@ -385,13 +376,13 @@ test("new empty sessions do not persist Cognitive Routing state until the first
385
376
 
386
377
  test("new sessions honor each configured Cognitive Routing startup combination", async () => {
387
378
  const combinations = [
388
- ["automatic", "standard"],
389
- ["automatic", "reasoning"],
390
- ["manual", "standard"],
391
- ["manual", "reasoning"],
379
+ ["automatic", "standard", "reasoning"],
380
+ ["automatic", "reasoning", "reasoning"],
381
+ ["manual", "standard", "standard"],
382
+ ["manual", "reasoning", "reasoning"],
392
383
  ];
393
384
 
394
- for (const [control, profile] of combinations) {
385
+ for (const [control, profile, expectedProfile] of combinations) {
395
386
  const cwd = await mkdtemp(join(tmpdir(), `freeflow-cognitive-routing-start-${control}-${profile}-`));
396
387
  await mkdir(join(cwd, ".freeflow"));
397
388
  await writeFile(
@@ -414,11 +405,11 @@ test("new sessions honor each configured Cognitive Routing startup combination",
414
405
  await host.handlers.get("session_start")({ type: "session_start", reason: "startup" }, ctx);
415
406
  await host.handlers.get("before_agent_start")({ prompt: "hello", systemPrompt: "base prompt" }, ctx);
416
407
 
417
- assert.equal(host.entries.filter((entry) => entry.modelId).at(-1).modelId, profile);
408
+ assert.equal(host.entries.filter((entry) => entry.modelId).at(-1).modelId, expectedProfile);
418
409
  const activation = host.entries.find(
419
410
  (entry) => entry.customType === "freeflow-cognitive-routing-intent" && entry.data.kind === "activation",
420
411
  );
421
- assert.equal(activation.data.profile, profile);
412
+ assert.equal(activation.data.profile, expectedProfile);
422
413
  assert.equal(activation.data.control, control);
423
414
  } finally {
424
415
  await host.handlers.get("session_shutdown")({ type: "session_shutdown", reason: "test-cleanup" }, ctx);
@@ -461,7 +452,7 @@ test("editing session-start settings does not activate the current empty session
461
452
  const component = factory({ requestRender() {} }, theme, {}, (value) => {
462
453
  result = value;
463
454
  });
464
- for (let index = 0; index < 5; index += 1) component.handleInput("\u001b[B");
455
+ component.handleInput("\u001b[B");
465
456
  component.handleInput("\r");
466
457
  for (let index = 0; index < 3; index += 1) component.handleInput("\u001b[B");
467
458
  component.handleInput("\r");
@@ -513,11 +504,11 @@ test("child extension activation does not deactivate the parent routing controll
513
504
  try {
514
505
  await parent.handlers.get("session_start")({ type: "session_start", reason: "startup" }, parentContext);
515
506
  await parent.handlers.get("before_agent_start")({ prompt: "hello", systemPrompt: "base prompt" }, parentContext);
516
- assert.deepEqual(parent.state, { model: { provider: "faux", id: "standard" }, thinkingLevel: "high" });
507
+ assert.deepEqual(parent.state, { model: { provider: "faux", id: "reasoning" }, thinkingLevel: "max" });
517
508
 
518
509
  await child.handlers.get("session_start")({ type: "session_start", reason: "startup" }, childContext);
519
510
 
520
- assert.deepEqual(parent.state, { model: { provider: "faux", id: "standard" }, thinkingLevel: "high" });
511
+ assert.deepEqual(parent.state, { model: { provider: "faux", id: "reasoning" }, thinkingLevel: "max" });
521
512
  assert.equal(
522
513
  parent.entries.some((entry) => entry.kind === "closing"),
523
514
  false,
@@ -548,6 +539,7 @@ test("registered profile commands create manual holds and release them without a
548
539
  const ctx = createContext(cwd, host);
549
540
  try {
550
541
  await host.handlers.get("session_start")({ type: "session_start", reason: "startup" }, ctx);
542
+ await host.handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
551
543
  const command = host.commands.find(({ name }) => name === "freeflow");
552
544
  assert.ok(command);
553
545
  assert.deepEqual(
@@ -665,6 +657,7 @@ test("reload preserves a manual reasoning hold after lease rotation", async () =
665
657
  ctx.isIdle = () => true;
666
658
  try {
667
659
  await host.handlers.get("session_start")({ type: "session_start", reason: "startup" }, ctx);
660
+ await host.handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
668
661
  await host.commands.find(({ name }) => name === "freeflow").definition.handler("profile reasoning", ctx);
669
662
  await host.handlers.get("session_shutdown")({ type: "session_shutdown", reason: "reload" }, ctx);
670
663
 
@@ -705,6 +698,7 @@ test("tree navigation retains the current profile without a supported branch cho
705
698
  const ctx = createContext(cwd, host);
706
699
  try {
707
700
  await host.handlers.get("session_start")({ type: "session_start", reason: "startup" }, ctx);
701
+ await host.handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
708
702
  const standardBranch = [...host.entries];
709
703
  const command = host.commands.find(({ name }) => name === "freeflow");
710
704
  await command.definition.handler("profile reasoning", ctx);
@@ -748,7 +742,7 @@ test("compaction reconciles without changing an active routing pair", async () =
748
742
  await host.handlers.get("session_compact")({ type: "session_compact" }, ctx);
749
743
 
750
744
  assert.deepEqual(host.operations, operationsBeforeCompaction);
751
- assert.deepEqual(host.state, { model: { provider: "faux", id: "standard" }, thinkingLevel: "high" });
745
+ assert.deepEqual(host.state, { model: { provider: "faux", id: "reasoning" }, thinkingLevel: "max" });
752
746
  assert.ok(host.activeToolNames().includes("freeflow_switch_profile"));
753
747
  } finally {
754
748
  await host.handlers.get("session_shutdown")({ type: "session_shutdown", reason: "test-cleanup" }, ctx);
@@ -865,7 +859,7 @@ test("reload recovery stays inactive after an unmatched closing intent", async (
865
859
  }
866
860
  });
867
861
 
868
- test("Pi delivers a compact Cognitive Routing cue and volatile state per turn", async () => {
862
+ test("Pi delivers stable Cognitive Routing cues and refreshes runtime state only when needed", async () => {
869
863
  const cwd = await mkdtemp(join(tmpdir(), "freeflow-cognitive-routing-compact-delivery-"));
870
864
  await mkdir(join(cwd, ".freeflow"));
871
865
  await writeFile(
@@ -889,14 +883,17 @@ test("Pi delivers a compact Cognitive Routing cue and volatile state per turn",
889
883
 
890
884
  assert.equal(first.message, undefined);
891
885
  assert.match(first.systemPrompt, /## Cognitive Routing Cue/);
892
- assert.match(
893
- first.systemPrompt,
894
- /governs compute placement across Freeflow methods.*skill has not been read in the current context.*read it before applying another Freeflow skill or taking task Act/s,
895
- );
896
- assert.match(
897
- first.systemPrompt,
898
- /every authorized execution-bearing bounded activity is Reasoning-led.*Standard takes task Act only inside delegation/s,
899
- );
886
+ assert.match(first.systemPrompt, /each user interaction begins in Reasoning/);
887
+ assert.match(first.systemPrompt, /If the full Cognitive Routing skill is not visible/);
888
+ assert.match(first.systemPrompt, /Make that read the only environment call/);
889
+ assert.match(first.systemPrompt, /it is not a route transition/);
890
+ assert.match(first.systemPrompt, /YIELD/);
891
+ assert.match(first.systemPrompt, /Delegate/);
892
+ assert.match(first.systemPrompt, /ACT_BOUNDED/);
893
+ assert.match(first.systemPrompt, /Automatic Standard only executes active Yield or Delegate contracts/);
894
+ assert.match(first.systemPrompt, /writes `YIELD HANDOFF` or `RETURN` and switches to Reasoning/);
895
+ assert.match(first.systemPrompt, /Standard never handles substantive user-facing interaction/);
896
+ assert.doesNotMatch(first.systemPrompt, /TASK ACT|OBSERVE/);
900
897
  assert.ok(
901
898
  first.systemPrompt.indexOf("## Action Selection Cue") < first.systemPrompt.indexOf("## Cognitive Routing Cue"),
902
899
  );
@@ -909,7 +906,22 @@ test("Pi delivers a compact Cognitive Routing cue and volatile state per turn",
909
906
  assert.equal(firstContext.messages[0].customType, "freeflow-runtime-state");
910
907
  assert.match(firstContext.messages[0].content, /Cognitive Routing: active/);
911
908
  assert.match(firstContext.messages[0].content, /Control: `automatic`/);
912
- assert.match(firstContext.messages[0].content, /Profile: `standard`/);
909
+ assert.match(firstContext.messages[0].content, /Profile: `reasoning`/);
910
+
911
+ const unchangedContext = await contextHandler({ messages: firstContext.messages }, ctx);
912
+ assert.equal(unchangedContext, undefined);
913
+
914
+ await host.handlers.get("session_tree")({ type: "session_tree", reason: "branch" }, ctx);
915
+ const branchContext = await contextHandler({ messages: firstContext.messages }, ctx);
916
+ assert.ok(branchContext);
917
+ assert.equal(branchContext.messages.length, 1);
918
+ assert.match(branchContext.messages[0].content, /Profile: `reasoning`/);
919
+
920
+ await host.handlers.get("session_compact")({ type: "session_compact", reason: "compaction" }, ctx);
921
+ const compactContext = await contextHandler({ messages: branchContext.messages }, ctx);
922
+ assert.ok(compactContext);
923
+ assert.equal(compactContext.messages.length, 1);
924
+ assert.match(compactContext.messages[0].content, /Profile: `reasoning`/);
913
925
 
914
926
  const second = await beforeAgentStart({ systemPrompt: "base prompt" }, ctx);
915
927
  assert.equal(second.message, undefined);
@@ -920,6 +932,65 @@ test("Pi delivers a compact Cognitive Routing cue and volatile state per turn",
920
932
  }
921
933
  });
922
934
 
935
+ test("Pi preserves runtime state after a no-op profile switch", async () => {
936
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-cognitive-routing-no-op-state-"));
937
+ await mkdir(join(cwd, ".freeflow"));
938
+ await writeFile(
939
+ join(cwd, ".freeflow", "config.json"),
940
+ JSON.stringify({
941
+ cognitiveRouting: {
942
+ enabled: true,
943
+ profiles: {
944
+ standard: { provider: "faux", model: "standard", thinkingLevel: "high" },
945
+ reasoning: { provider: "faux", model: "reasoning", thinkingLevel: "max" },
946
+ },
947
+ },
948
+ }),
949
+ );
950
+ const host = createExtensionHost();
951
+ const ctx = createContext(cwd, host);
952
+ try {
953
+ await host.handlers.get("session_start")({ type: "session_start", reason: "startup" }, ctx);
954
+ await host.handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
955
+
956
+ const contextHandler = host.handlers.get("context");
957
+ const reasoningContext = await contextHandler({ messages: [] }, ctx);
958
+ const runtimeStateMessages = reasoningContext.messages.filter(
959
+ (message) =>
960
+ message.customType === "freeflow-runtime-state" ||
961
+ message.customType === "freeflow-cognitive-routing-runtime-state",
962
+ );
963
+ assert.equal(runtimeStateMessages.length, 1);
964
+ assert.equal(runtimeStateMessages[0].customType, "freeflow-runtime-state");
965
+
966
+ const switchTool = host.tools.find((tool) => tool.name === "freeflow_switch_profile");
967
+ const result = await switchTool.execute(
968
+ "call-no-op-volatile-state",
969
+ { target: "reasoning", reason: "Remain on Reasoning for this check." },
970
+ undefined,
971
+ undefined,
972
+ ctx,
973
+ );
974
+ assert.equal(result.details.result.status, "active");
975
+ assert.equal(result.details.result.changed, false);
976
+
977
+ await host.handlers.get("tool_result")({ type: "tool_result", toolName: "freeflow_switch_profile" }, ctx);
978
+ const unchangedContext = await contextHandler({ messages: reasoningContext.messages }, ctx);
979
+ assert.equal(unchangedContext, undefined);
980
+ assert.equal(
981
+ reasoningContext.messages.filter(
982
+ (message) =>
983
+ message.customType === "freeflow-runtime-state" ||
984
+ message.customType === "freeflow-cognitive-routing-runtime-state",
985
+ ).length,
986
+ 1,
987
+ );
988
+ } finally {
989
+ await host.handlers.get("session_shutdown")({ type: "session_shutdown", reason: "test-cleanup" }, ctx);
990
+ await rm(cwd, { recursive: true, force: true });
991
+ }
992
+ });
993
+
923
994
  test("Pi refreshes volatile Cognitive Routing state after a profile switch", async () => {
924
995
  const cwd = await mkdtemp(join(tmpdir(), "freeflow-cognitive-routing-volatile-state-"));
925
996
  await mkdir(join(cwd, ".freeflow"));
@@ -942,31 +1013,31 @@ test("Pi refreshes volatile Cognitive Routing state after a profile switch", asy
942
1013
  await host.handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
943
1014
 
944
1015
  const contextHandler = host.handlers.get("context");
945
- const standardContext = await contextHandler({ messages: [] }, ctx);
946
- assert.match(standardContext.messages[0].content, /Profile: `standard`/);
1016
+ const reasoningContext = await contextHandler({ messages: [] }, ctx);
1017
+ assert.match(reasoningContext.messages[0].content, /Profile: `reasoning`/);
947
1018
 
948
1019
  const switchTool = host.tools.find((tool) => tool.name === "freeflow_switch_profile");
949
1020
  const result = await switchTool.execute(
950
1021
  "call-volatile-state",
951
- { target: "reasoning", reason: "Need a deeper analysis." },
1022
+ { target: "standard", reason: "Use Standard for bounded execution." },
952
1023
  undefined,
953
1024
  undefined,
954
1025
  ctx,
955
1026
  );
956
- assert.equal(result.details.result.to, "reasoning");
1027
+ assert.equal(result.details.result.to, "standard");
957
1028
 
958
- const reasoningContext = await contextHandler({ messages: standardContext.messages }, ctx);
959
- assert.equal(reasoningContext.messages.length, 1);
960
- assert.match(reasoningContext.messages[0].content, /Control: `automatic`/);
961
- assert.match(reasoningContext.messages[0].content, /Profile: `reasoning`/);
962
- assert.doesNotMatch(reasoningContext.messages[0].content, /Profile: `standard`/);
1029
+ const standardContext = await contextHandler({ messages: reasoningContext.messages }, ctx);
1030
+ assert.equal(standardContext.messages.length, 1);
1031
+ assert.match(standardContext.messages[0].content, /Control: `automatic`/);
1032
+ assert.match(standardContext.messages[0].content, /Profile: `standard`/);
1033
+ assert.doesNotMatch(standardContext.messages[0].content, /Profile: `reasoning`/);
963
1034
  } finally {
964
1035
  await host.handlers.get("session_shutdown")({ type: "session_shutdown", reason: "test-cleanup" }, ctx);
965
1036
  await rm(cwd, { recursive: true, force: true });
966
1037
  }
967
1038
  });
968
1039
 
969
- test("Pi keeps Cognitive Routing behind the Skills parent gate", async () => {
1040
+ test("Pi rejects the removed Skills configuration instead of gating Cognitive Routing", async () => {
970
1041
  const cwd = await mkdtemp(join(tmpdir(), "freeflow-cognitive-routing-skills-gate-"));
971
1042
  await mkdir(join(cwd, ".freeflow"));
972
1043
  await writeFile(
@@ -992,8 +1063,8 @@ test("Pi keeps Cognitive Routing behind the Skills parent gate", async () => {
992
1063
  assert.doesNotMatch(result.systemPrompt, /^# Cognitive Routing$/m);
993
1064
  assert.ok(!host.activeToolNames().includes("freeflow_switch_profile"));
994
1065
  const providerContext = await host.handlers.get("context")({ messages: [] }, ctx);
995
- assert.match(providerContext.messages[0].content, /Skills: inactive/);
996
- assert.match(providerContext.messages[0].content, /Cognitive Routing: inactive/);
1066
+ assert.match(providerContext.messages[0].content, /Freeflow: config error/);
1067
+ assert.doesNotMatch(providerContext.messages[0].content, /Skills:/);
997
1068
  } finally {
998
1069
  await host.handlers.get("session_shutdown")({ type: "session_shutdown", reason: "test-cleanup" }, ctx);
999
1070
  await rm(cwd, { recursive: true, force: true });
@@ -1026,7 +1097,7 @@ test("Pi lifecycle prepares, activates, restores, and releases Cognitive Routing
1026
1097
  ctx,
1027
1098
  );
1028
1099
  assert.deepEqual(host.operations.slice(0, 3), ["prepare", "acquire", "setState"]);
1029
- assert.deepEqual(host.state, { model: { provider: "faux", id: "standard" }, thinkingLevel: "high" });
1100
+ assert.deepEqual(host.state, { model: { provider: "faux", id: "reasoning" }, thinkingLevel: "max" });
1030
1101
  assert.equal(host.entries[0].customType, "freeflow-cognitive-routing-intent");
1031
1102
  assert.ok(host.activeToolNames().includes("freeflow_switch_profile"));
1032
1103
  assert.match(beforeAgentStart.systemPrompt, /## Cognitive Routing Cue/);
@@ -1037,12 +1108,12 @@ test("Pi lifecycle prepares, activates, restores, and releases Cognitive Routing
1037
1108
  const switchTool = host.tools.find((tool) => tool.name === "freeflow_switch_profile");
1038
1109
  await switchTool.execute(
1039
1110
  "call-1",
1040
- { target: "reasoning", reason: "Continue the unresolved architecture decision." },
1111
+ { target: "standard", reason: "Use Standard for the bounded execution pass." },
1041
1112
  undefined,
1042
1113
  undefined,
1043
1114
  ctx,
1044
1115
  );
1045
- assert.deepEqual(host.state, { model: { provider: "faux", id: "reasoning" }, thinkingLevel: "max" });
1116
+ assert.deepEqual(host.state, { model: { provider: "faux", id: "standard" }, thinkingLevel: "high" });
1046
1117
 
1047
1118
  await host.handlers.get("session_shutdown")({ type: "session_shutdown", reason: "reload" }, ctx);
1048
1119
 
@@ -1062,7 +1133,7 @@ test("Pi lifecycle prepares, activates, restores, and releases Cognitive Routing
1062
1133
 
1063
1134
  await host.handlers.get("session_start")({ type: "session_start", reason: "reload" }, ctx);
1064
1135
  assert.deepEqual(host.operations.slice(-3), ["prepare", "acquire", "setState"]);
1065
- assert.deepEqual(host.state, { model: { provider: "faux", id: "reasoning" }, thinkingLevel: "max" });
1136
+ assert.deepEqual(host.state, { model: { provider: "faux", id: "standard" }, thinkingLevel: "high" });
1066
1137
  assert.ok(host.activeToolNames().includes("freeflow_switch_profile"));
1067
1138
  await host.handlers.get("session_shutdown")({ type: "session_shutdown", reason: "test-cleanup" }, ctx);
1068
1139
  } finally {