@hassangameryt/freeflow 0.6.0 → 0.7.0

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 +2 -2
  3. package/.codex-plugin/plugin.json +4 -5
  4. package/README.md +39 -55
  5. package/capabilities/cognitive-routing/SKILL.md +288 -130
  6. package/command-surface.json +0 -24
  7. package/hooks/freeflow-runtime-context.mjs +42 -477
  8. package/hooks/hooks.json +1 -26
  9. package/hooks/tests/check-runtime-context-hook.sh +108 -319
  10. package/package.json +4 -2
  11. package/pi-extension/dist/cognitive-routing/commands.js +3 -3
  12. package/pi-extension/dist/cognitive-routing/config.js +16 -20
  13. package/pi-extension/dist/cognitive-routing/controller.js +478 -48
  14. package/pi-extension/dist/cognitive-routing/history.js +12 -0
  15. package/pi-extension/dist/cognitive-routing/host.js +74 -12
  16. package/pi-extension/dist/cognitive-routing/pi-routing-session.js +414 -0
  17. package/pi-extension/dist/cognitive-routing/pi-session-control.js +320 -0
  18. package/pi-extension/dist/cognitive-routing/tool.js +1 -0
  19. package/pi-extension/dist/cognitive-routing/types.js +1 -1
  20. package/pi-extension/dist/index.js +236 -219
  21. package/pi-extension/dist/runtime/runtime-context.js +126 -286
  22. package/pi-extension/dist/settings/settings-ui.js +52 -336
  23. package/pi-extension/src/cognitive-routing/commands.ts +15 -5
  24. package/pi-extension/src/cognitive-routing/config.ts +16 -20
  25. package/pi-extension/src/cognitive-routing/controller.ts +561 -51
  26. package/pi-extension/src/cognitive-routing/history.ts +12 -0
  27. package/pi-extension/src/cognitive-routing/host.ts +110 -16
  28. package/pi-extension/src/cognitive-routing/pi-routing-session.ts +538 -0
  29. package/pi-extension/src/cognitive-routing/pi-session-control.ts +444 -0
  30. package/pi-extension/src/cognitive-routing/tool.ts +1 -0
  31. package/pi-extension/src/cognitive-routing/types.ts +1 -1
  32. package/pi-extension/src/index.ts +249 -239
  33. package/pi-extension/src/runtime/runtime-context.ts +129 -305
  34. package/pi-extension/src/settings/settings-ui.ts +53 -371
  35. package/pi-extension/tests/cognitive-routing/commands.test.js +1 -1
  36. package/pi-extension/tests/cognitive-routing/controller.test.js +69 -40
  37. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +128 -57
  38. package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +434 -0
  39. package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +174 -0
  40. package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +91 -0
  41. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +47 -4
  42. package/pi-extension/tests/integration/mode-free-core.test.js +218 -0
  43. package/pi-extension/tests/integration/pi-extension.test.js +430 -2478
  44. package/pi-extension/tests/integration/prompt-architecture.test.js +124 -79
  45. package/pi-extension/tests/integration/unaffected-regressions.test.js +411 -0
  46. package/runtime/prompts/cognitive-routing.md +5 -4
  47. package/runtime/prompts/conversation-history.md +1 -1
  48. package/runtime/prompts/core.md +77 -6
  49. package/runtime/prompts/interaction-contract.md +6 -2
  50. package/skills/action-selection/SKILL.md +93 -44
  51. package/skills/action-selection/references/trajectory-stalls.md +71 -0
  52. package/skills/bypass/SKILL.md +109 -39
  53. package/skills/commit-work/SKILL.md +94 -55
  54. package/skills/commit-work/references/git-state-edges.md +94 -0
  55. package/skills/commit-work/references/simple-push.md +92 -0
  56. package/skills/commit-work/references/staging-decisions.md +79 -46
  57. package/skills/decision-gate/SKILL.md +80 -22
  58. package/skills/design-for-depth/SKILL.md +208 -43
  59. package/skills/design-for-depth/references/design-pressure-signals.md +108 -240
  60. package/skills/design-for-depth/references/interface-design-loop.md +77 -97
  61. package/skills/design-for-depth/references/module-and-dependency-design.md +147 -0
  62. package/skills/design-for-depth/references/software-design-philosophy.md +127 -173
  63. package/skills/design-for-depth/references/state-and-failure-boundaries.md +136 -0
  64. package/skills/diagnose-failure/SKILL.md +198 -52
  65. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +19 -5
  66. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +13 -5
  67. package/skills/discuss/SKILL.md +118 -76
  68. package/skills/discuss/references/discussion-continuity.md +84 -32
  69. package/skills/evaluate-skill/SKILL.md +166 -52
  70. package/skills/evaluate-skill/references/evaluation-definition-schema.md +2 -2
  71. package/skills/evaluate-skill/references/evaluation-design.md +56 -30
  72. package/skills/evaluate-skill/references/execution-and-evidence.md +1 -1
  73. package/skills/evaluate-skill/references/review-and-revision.md +62 -21
  74. package/skills/execute-work/SKILL.md +98 -59
  75. package/skills/execute-work/references/code-practices.md +4 -4
  76. package/skills/execute-work/references/execute-work-edges.md +42 -22
  77. package/skills/review-artifact/SKILL.md +132 -71
  78. package/skills/review-artifact/references/adjudicate-artifact-review.md +101 -0
  79. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +52 -41
  80. package/skills/review-work/SKILL.md +115 -72
  81. package/skills/review-work/references/adjudicate-work-review.md +97 -0
  82. package/skills/review-work/references/independent-work-reviewer-contract.md +40 -35
  83. package/skills/review-work/references/security-risk-lens.md +17 -15
  84. package/skills/setup-freeflow/SKILL.md +14 -17
  85. package/skills/setup-freeflow/references/activation-contract.md +24 -30
  86. package/skills/setup-freeflow/references/host-setup.md +6 -7
  87. package/skills/simplify-code/SKILL.md +97 -39
  88. package/skills/simplify-code/references/simplification-patterns.md +1 -1
  89. package/skills/tdd/SKILL.md +108 -41
  90. package/skills/tdd/references/test-design.md +1 -1
  91. package/skills/track-work/SKILL.md +133 -300
  92. package/skills/track-work/references/working-record-format.md +515 -0
  93. package/skills/track-work/scripts/lib/document.mjs +713 -0
  94. package/skills/track-work/scripts/lib/format.mjs +166 -0
  95. package/skills/track-work/scripts/lib/store.mjs +233 -0
  96. package/skills/track-work/scripts/lib/transitions.mjs +790 -0
  97. package/skills/track-work/scripts/lib/views.mjs +27 -430
  98. package/skills/track-work/scripts/working-record.mjs +292 -98
  99. package/skills/verify-work/SKILL.md +46 -20
  100. package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
  101. package/skills/verify-work/references/integration-evidence.md +1 -7
  102. package/skills/verify-work/references/performance-evidence.md +1 -1
  103. package/skills/workflow/SKILL.md +110 -80
  104. package/skills/workflow/references/domain-skill-composition.md +24 -18
  105. package/skills/write-skill/SKILL.md +218 -62
  106. package/skills/write-skill/references/activation-boundaries.md +63 -21
  107. package/skills/write-skill/references/agent-first-instructions.md +61 -27
  108. package/skills/write-skill/references/progressive-disclosure.md +91 -41
  109. package/skills/write-skill/references/skill-dependency-graphs.md +190 -0
  110. package/runtime/prompts/skills.md +0 -65
  111. package/skills/mode-contract/SKILL.md +0 -104
  112. package/skills/track-work/scripts/lib/codec.mjs +0 -570
  113. package/skills/track-work/scripts/lib/commands.mjs +0 -568
  114. package/skills/track-work/scripts/lib/contract.mjs +0 -303
  115. package/skills/track-work/scripts/lib/model.mjs +0 -506
  116. package/skills/track-work/scripts/lib/operations.mjs +0 -965
  117. package/skills/track-work/scripts/lib/record-store.mjs +0 -264
  118. package/skills/track-work/scripts/lib/result.mjs +0 -37
  119. package/skills/track-work/scripts/lib/rewrites.mjs +0 -740
  120. package/skills/track-work/scripts/lib/schema.mjs +0 -277
  121. package/skills/track-work/scripts/lib/source-inventory.mjs +0 -162
  122. package/skills/track-work/scripts/lib/workspace.mjs +0 -134
@@ -1,5 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
- import { cp, mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
2
+ import { cp, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { tmpdir } from "node:os";
@@ -53,7 +53,7 @@ function loadExtension(extension = freeflowExtension, host = PIFLOW_HOST) {
53
53
  return { handlers, activeToolNames: () => activeToolNames ?? tools.map((tool) => tool.name) };
54
54
  }
55
55
 
56
- async function configuredRepo(config) {
56
+ async function configuredRepo(config = {}) {
57
57
  const cwd = await mkdtemp(join(tmpdir(), "freeflow-prompt-architecture-"));
58
58
  await mkdir(join(cwd, ".freeflow"));
59
59
  await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify(config, null, 2), "utf8");
@@ -64,12 +64,29 @@ function lastRuntimeState(messages) {
64
64
  return messages.findLast((message) => message.customType === "freeflow-runtime-state");
65
65
  }
66
66
 
67
- test("composes conditional prompt fragments, discoverable capabilities, and runtime state", async () => {
68
- const cwd = await configuredRepo({
69
- defaultMode: "workflow",
70
- contextVirtualization: true,
71
- conversationHistory: true,
72
- });
67
+ test("re-entry recovery is stable and capability-neutral", async () => {
68
+ const [core, cognitiveRouting, conversationHistory] = await Promise.all([
69
+ readFile(join(process.cwd(), "runtime", "prompts", "core.md"), "utf8"),
70
+ readFile(join(process.cwd(), "runtime", "prompts", "cognitive-routing.md"), "utf8"),
71
+ readFile(join(process.cwd(), "runtime", "prompts", "conversation-history.md"), "utf8"),
72
+ ]);
73
+
74
+ assert.match(core, /## Recover After Context Loss/);
75
+ 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/);
78
+ assert.match(
79
+ cognitiveRouting,
80
+ /`Control` and `Profile` in the latest Runtime State report current compute state, not Yield, Delegate, `ACT_BOUNDED`, or boundary state/,
81
+ );
82
+ assert.doesNotMatch(
83
+ conversationHistory,
84
+ /Current user direction, live source truth, and present runtime state remain authoritative/,
85
+ );
86
+ });
87
+
88
+ test("composes the mandatory core fragments, optional capabilities, discovery, and runtime state", async () => {
89
+ const cwd = await configuredRepo({ contextVirtualization: true, conversationHistory: true });
73
90
  try {
74
91
  const { handlers, activeToolNames } = loadExtension();
75
92
  const ctx = context(cwd);
@@ -80,45 +97,40 @@ test("composes conditional prompt fragments, discoverable capabilities, and runt
80
97
  const prompt = before.systemPrompt;
81
98
  const order = [
82
99
  "# Freeflow Stable Guidance",
83
- "# Freeflow Interaction Contract",
84
100
  "## Shared Terms",
101
+ "## Recover After Context Loss",
85
102
  "## Three Nested Loops",
86
103
  "## Workflow Cue",
87
104
  "## Action Selection Cue",
88
105
  "## Supported Exit",
106
+ "# Freeflow Interaction Contract",
89
107
  "## Context Virtualization Cue",
90
108
  "## Conversation History Cue",
91
109
  ].map((marker) => prompt.indexOf(marker));
92
110
  assert.ok(order.every((index) => index >= 0));
93
- assert.match(
94
- prompt,
95
- /\*\*Slice:\*\* one coherent outcome that can be executed and checked as a unit.*Track Work gives it durable identity/s,
96
- );
97
- assert.match(
98
- prompt,
99
- /After context loss.*current user direction, bounded task memory, and live evidence.*read Track Work before any record operation.*`resume` view and current schema before mutation.*Workflow is unread.*read it before consequential work or owner selection/s,
100
- );
101
111
  assert.deepEqual(
102
112
  order,
103
113
  [...order].sort((a, b) => a - b),
104
114
  );
115
+ assert.doesNotMatch(prompt, /## Mode\b|strict-workflow|conversation mode|workflow mode/);
116
+ assert.doesNotMatch(prompt, /Skills prompt/);
105
117
  assert.doesNotMatch(prompt, /# Workflow\n/);
106
118
  assert.doesNotMatch(prompt, /# Cognitive Routing\n/);
107
- assert.doesNotMatch(prompt, /# Conversation History\n/);
108
119
 
109
120
  const providerContext = await handlers.get("context")({ messages: [] }, ctx);
110
121
  const runtimeState = lastRuntimeState(providerContext.messages);
111
122
  assert.ok(runtimeState);
112
- assert.match(runtimeState.content, /Skills: active/);
123
+ assert.match(runtimeState.content, /Freeflow: active/);
113
124
  assert.match(runtimeState.content, /Context Virtualization: active/);
114
125
  assert.match(runtimeState.content, /Conversation History: active/);
115
126
  assert.match(runtimeState.content, /Cognitive Routing: inactive/);
127
+ assert.doesNotMatch(runtimeState.content, /Default mode|Active mode|Interaction Contract|Skills/);
116
128
 
117
129
  const resources = await handlers.get("resources_discover")({ cwd }, ctx);
118
130
  assert.ok(resources.skillPaths.some((path) => path.endsWith("/skills/action-selection/SKILL.md")));
119
131
  assert.ok(resources.skillPaths.some((path) => path.endsWith("/capabilities/context-virtualization/SKILL.md")));
120
132
  assert.ok(resources.skillPaths.some((path) => path.endsWith("/capabilities/conversation-history/SKILL.md")));
121
- assert.ok(!resources.skillPaths.some((path) => path.endsWith("/capabilities/cognitive-routing/SKILL.md")));
133
+ assert.ok(!resources.skillPaths.some((path) => path.endsWith("/skills/mode-contract/SKILL.md")));
122
134
  assert.ok(activeToolNames().includes("freeflow_context"));
123
135
  } finally {
124
136
  await rm(cwd, { recursive: true, force: true });
@@ -126,7 +138,7 @@ test("composes conditional prompt fragments, discoverable capabilities, and runt
126
138
  });
127
139
 
128
140
  test("provider context reuses the before-agent surface until the next provider turn", async () => {
129
- const cwd = await configuredRepo({ defaultMode: "workflow", contextVirtualization: true });
141
+ const cwd = await configuredRepo({ contextVirtualization: true });
130
142
  try {
131
143
  const { handlers } = loadExtension();
132
144
  const ctx = context(cwd);
@@ -135,59 +147,20 @@ test("provider context reuses the before-agent surface until the next provider t
135
147
  const before = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
136
148
  assert.match(before.systemPrompt, /## Context Virtualization Cue/);
137
149
 
138
- await writeFile(
139
- join(cwd, ".freeflow/config.json"),
140
- JSON.stringify({ defaultMode: "workflow", skills: { enabled: false }, contextVirtualization: true }),
141
- "utf8",
142
- );
150
+ await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({}), "utf8");
143
151
  const sameTurn = await handlers.get("context")({ messages: [] }, ctx);
144
- assert.match(lastRuntimeState(sameTurn.messages).content, /Skills: active/);
145
152
  assert.match(lastRuntimeState(sameTurn.messages).content, /Context Virtualization: active/);
146
153
 
147
154
  const next = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
148
- assert.doesNotMatch(next.systemPrompt, /## Shared Terms/);
149
155
  assert.doesNotMatch(next.systemPrompt, /## Context Virtualization Cue/);
156
+ assert.match(next.systemPrompt, /# Freeflow Interaction Contract/);
157
+ assert.match(next.systemPrompt, /## Shared Terms/);
150
158
  } finally {
151
159
  await rm(cwd, { recursive: true, force: true });
152
160
  }
153
161
  });
154
162
 
155
- test("Skills is the parent gate for capability prompt, discovery, and tools", async () => {
156
- const cwd = await configuredRepo({
157
- defaultMode: "workflow",
158
- skills: { enabled: false },
159
- contextVirtualization: true,
160
- conversationHistory: true,
161
- });
162
- try {
163
- const { handlers, activeToolNames } = loadExtension();
164
- const ctx = context(cwd);
165
- await handlers.get("session_start")({ type: "session_start" }, ctx);
166
-
167
- const before = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
168
- assert.match(before.systemPrompt, /# Freeflow Stable Guidance/);
169
- assert.match(before.systemPrompt, /## Mode/);
170
- assert.match(before.systemPrompt, /# Freeflow Interaction Contract/);
171
- assert.doesNotMatch(before.systemPrompt, /## Shared Terms/);
172
- assert.doesNotMatch(before.systemPrompt, /## Three Nested Loops/);
173
- assert.doesNotMatch(before.systemPrompt, /## Context Virtualization Cue/);
174
- assert.doesNotMatch(before.systemPrompt, /## Conversation History Cue/);
175
-
176
- const providerContext = await handlers.get("context")({ messages: [] }, ctx);
177
- const runtimeState = lastRuntimeState(providerContext.messages);
178
- assert.match(runtimeState.content, /Skills: inactive/);
179
- assert.match(runtimeState.content, /Context Virtualization: inactive/);
180
- assert.match(runtimeState.content, /Conversation History: inactive/);
181
-
182
- const resources = await handlers.get("resources_discover")({ cwd }, ctx);
183
- assert.deepEqual(resources.skillPaths, []);
184
- assert.ok(!activeToolNames().includes("freeflow_context"));
185
- } finally {
186
- await rm(cwd, { recursive: true, force: true });
187
- }
188
- });
189
-
190
- test("missing optional prompt fragments preserve runtime context loading", async () => {
163
+ test("missing optional prompt fragments preserve the mandatory core surface", async () => {
191
164
  const root = await mkdtemp(join(tmpdir(), "freeflow-prompt-fragment-failure-"));
192
165
  try {
193
166
  await cp(join(process.cwd(), "pi-extension", "dist"), join(root, "pi-extension", "dist"), { recursive: true });
@@ -200,8 +173,6 @@ test("missing optional prompt fragments preserve runtime context loading", async
200
173
  const state = {
201
174
  configured: true,
202
175
  enabled: true,
203
- interactionContract: { effective: true },
204
- skills: { effective: true },
205
176
  cognitiveRouting: { effective: true },
206
177
  contextVirtualization: { effective: false },
207
178
  conversationHistory: { effective: false },
@@ -213,6 +184,10 @@ test("missing optional prompt fragments preserve runtime context loading", async
213
184
  runtime.runtimeContext(loaded, { ...state, cognitiveRouting: { effective: false } }),
214
185
  /# Freeflow Stable Guidance/,
215
186
  );
187
+ assert.match(
188
+ runtime.runtimeContext(loaded, { ...state, cognitiveRouting: { effective: false } }),
189
+ /# Freeflow Interaction Contract/,
190
+ );
216
191
  assert.doesNotMatch(
217
192
  runtime.runtimeContext(loaded, { ...state, cognitiveRouting: { effective: false } }),
218
193
  /## Cognitive Routing Cue/,
@@ -222,9 +197,9 @@ test("missing optional prompt fragments preserve runtime context loading", async
222
197
  }
223
198
  });
224
199
 
225
- test("missing child prompt removes that capability from every model-facing surface", async () => {
200
+ test("missing child prompt removes only that capability from every model-facing surface", async () => {
226
201
  const root = await mkdtemp(join(tmpdir(), "freeflow-child-prompt-failure-"));
227
- const cwd = await configuredRepo({ defaultMode: "workflow", contextVirtualization: true });
202
+ const cwd = await configuredRepo({ contextVirtualization: true });
228
203
  try {
229
204
  await cp(join(process.cwd(), "pi-extension", "dist"), join(root, "pi-extension", "dist"), { recursive: true });
230
205
  await cp(join(process.cwd(), "runtime", "prompts"), join(root, "runtime", "prompts"), { recursive: true });
@@ -240,20 +215,76 @@ test("missing child prompt removes that capability from every model-facing surfa
240
215
 
241
216
  const before = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
242
217
  assert.doesNotMatch(before.systemPrompt, /## Context Virtualization Cue/);
218
+ assert.match(before.systemPrompt, /# Freeflow Interaction Contract/);
243
219
  const resources = await handlers.get("resources_discover")({ cwd }, ctx);
244
220
  assert.ok(!resources.skillPaths.some((path) => path.endsWith("/capabilities/context-virtualization/SKILL.md")));
245
221
  assert.ok(!activeToolNames().includes("freeflow_context"));
222
+
223
+ await handlers.get("session_tree")({}, ctx);
224
+ assert.ok(!activeToolNames().includes("freeflow_context"));
225
+ await handlers.get("session_compact")({}, ctx);
226
+ assert.ok(!activeToolNames().includes("freeflow_context"));
227
+
246
228
  const providerContext = await handlers.get("context")({ messages: [] }, ctx);
247
229
  assert.match(providerContext.messages.at(-1).content, /Context Virtualization: unavailable/);
230
+
231
+ await writeFile(join(root, "runtime", "prompts", "context-virtualization.md"), " \n\t", "utf8");
232
+ const whitespaceBefore = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
233
+ assert.doesNotMatch(whitespaceBefore.systemPrompt, /## Context Virtualization Cue/);
234
+ const whitespaceResources = await handlers.get("resources_discover")({ cwd }, ctx);
235
+ assert.ok(
236
+ !whitespaceResources.skillPaths.some((path) => path.endsWith("/capabilities/context-virtualization/SKILL.md")),
237
+ );
238
+ assert.ok(!activeToolNames().includes("freeflow_context"));
239
+ const whitespaceContext = await handlers.get("context")({ messages: [] }, ctx);
240
+ assert.match(whitespaceContext.messages.at(-1).content, /Context Virtualization: unavailable/);
248
241
  } finally {
249
242
  await rm(cwd, { recursive: true, force: true });
250
243
  await rm(root, { recursive: true, force: true });
251
244
  }
252
245
  });
253
246
 
254
- test("missing core prompt preserves the host prompt and marks Freeflow unavailable", async () => {
247
+ test("missing mandatory Interaction Contract preserves the host prompt and hides base skills", async () => {
248
+ const root = await mkdtemp(join(tmpdir(), "freeflow-interaction-contract-failure-"));
249
+ const cwd = await configuredRepo();
250
+ try {
251
+ await cp(join(process.cwd(), "pi-extension", "dist"), join(root, "pi-extension", "dist"), { recursive: true });
252
+ await cp(join(process.cwd(), "runtime", "prompts"), join(root, "runtime", "prompts"), { recursive: true });
253
+ await symlink(join(process.cwd(), "node_modules"), join(root, "node_modules"), "dir");
254
+ await rm(join(root, "runtime", "prompts", "interaction-contract.md"));
255
+
256
+ const extension = (
257
+ await import(
258
+ `${pathToFileURL(join(root, "pi-extension", "dist", "index.js")).href}?missing-contract=${Date.now()}`
259
+ )
260
+ ).default;
261
+ const { handlers, activeToolNames } = loadExtension(extension);
262
+ const ctx = context(cwd);
263
+ await handlers.get("session_start")({}, ctx);
264
+ assert.ok(!activeToolNames().includes("freeflow_context"));
265
+ const before = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
266
+ assert.equal(before.systemPrompt, "base prompt");
267
+ const resources = await handlers.get("resources_discover")({ cwd }, ctx);
268
+ assert.deepEqual(resources.skillPaths, []);
269
+ await handlers.get("session_tree")({}, ctx);
270
+ assert.ok(!activeToolNames().includes("freeflow_context"));
271
+
272
+ await writeFile(join(root, "runtime", "prompts", "interaction-contract.md"), " \n\t", "utf8");
273
+ const whitespaceBefore = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
274
+ assert.equal(whitespaceBefore.systemPrompt, "base prompt");
275
+ const whitespaceResources = await handlers.get("resources_discover")({ cwd }, ctx);
276
+ assert.deepEqual(whitespaceResources.skillPaths, []);
277
+ const whitespaceContext = await handlers.get("context")({ messages: [] }, ctx);
278
+ assert.match(whitespaceContext.messages.at(-1).content, /Freeflow: unavailable/);
279
+ } finally {
280
+ await rm(cwd, { recursive: true, force: true });
281
+ await rm(root, { recursive: true, force: true });
282
+ }
283
+ });
284
+
285
+ test("missing mandatory core prompt preserves the host prompt and hides base skills", async () => {
255
286
  const root = await mkdtemp(join(tmpdir(), "freeflow-core-prompt-failure-"));
256
- const cwd = await configuredRepo({ defaultMode: "workflow" });
287
+ const cwd = await configuredRepo({ contextVirtualization: true });
257
288
  try {
258
289
  await cp(join(process.cwd(), "pi-extension", "dist"), join(root, "pi-extension", "dist"), { recursive: true });
259
290
  await cp(join(process.cwd(), "runtime", "prompts"), join(root, "runtime", "prompts"), { recursive: true });
@@ -263,7 +294,7 @@ test("missing core prompt preserves the host prompt and marks Freeflow unavailab
263
294
  const extension = (
264
295
  await import(`${pathToFileURL(join(root, "pi-extension", "dist", "index.js")).href}?missing-core=${Date.now()}`)
265
296
  ).default;
266
- const { handlers } = loadExtension(extension);
297
+ const { handlers, activeToolNames } = loadExtension(extension);
267
298
  const ctx = context(cwd);
268
299
  await handlers.get("session_start")({ type: "session_start" }, ctx);
269
300
 
@@ -272,17 +303,32 @@ test("missing core prompt preserves the host prompt and marks Freeflow unavailab
272
303
  const resources = await handlers.get("resources_discover")({ cwd }, ctx);
273
304
  assert.deepEqual(resources.skillPaths, []);
274
305
  const providerContext = await handlers.get("context")({ messages: [] }, ctx);
275
- assert.match(providerContext.messages.at(-1).content, /Interaction Contract: unavailable/);
276
- assert.match(providerContext.messages.at(-1).content, /Skills: unavailable/);
277
- assert.match(providerContext.messages.at(-1).content, /Active mode: `inactive`/);
306
+ assert.match(providerContext.messages.at(-1).content, /Freeflow: unavailable/);
307
+ assert.doesNotMatch(
308
+ providerContext.messages.at(-1).content,
309
+ /Default mode|Active mode|Interaction Contract|Skills/,
310
+ );
311
+
312
+ await handlers.get("session_tree")({}, ctx);
313
+ assert.ok(!activeToolNames().includes("freeflow_context"));
314
+ await handlers.get("session_compact")({}, ctx);
315
+ assert.ok(!activeToolNames().includes("freeflow_context"));
316
+
317
+ await writeFile(join(root, "runtime", "prompts", "core.md"), " \n\t", "utf8");
318
+ const whitespaceBefore = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
319
+ assert.equal(whitespaceBefore.systemPrompt, "base prompt");
320
+ const whitespaceResources = await handlers.get("resources_discover")({ cwd }, ctx);
321
+ assert.deepEqual(whitespaceResources.skillPaths, []);
322
+ const whitespaceContext = await handlers.get("context")({ messages: [] }, ctx);
323
+ assert.match(whitespaceContext.messages.at(-1).content, /Freeflow: unavailable/);
278
324
  } finally {
279
325
  await rm(cwd, { recursive: true, force: true });
280
326
  await rm(root, { recursive: true, force: true });
281
327
  }
282
328
  });
283
329
 
284
- test("Runtime State remains present while Freeflow is disabled and prompt surfaces disappear", async () => {
285
- const cwd = await configuredRepo({ enabled: false, skills: { enabled: true } });
330
+ test("Runtime State remains present while Freeflow is disabled without optional core labels", async () => {
331
+ const cwd = await configuredRepo({ enabled: false });
286
332
  try {
287
333
  const { handlers } = loadExtension();
288
334
  const ctx = context(cwd);
@@ -293,9 +339,8 @@ test("Runtime State remains present while Freeflow is disabled and prompt surfac
293
339
  const providerContext = await handlers.get("context")({ messages: [] }, ctx);
294
340
  const runtimeState = lastRuntimeState(providerContext.messages);
295
341
  assert.ok(runtimeState);
296
- assert.match(runtimeState.content, /Active mode: `inactive`/);
297
- assert.match(runtimeState.content, /Skills: inactive/);
298
- assert.match(runtimeState.content, /Interaction Contract: inactive/);
342
+ assert.match(runtimeState.content, /Freeflow: inactive/);
343
+ assert.doesNotMatch(runtimeState.content, /Default mode|Active mode|Interaction Contract|Skills/);
299
344
 
300
345
  const resources = await handlers.get("resources_discover")({ cwd }, ctx);
301
346
  assert.deepEqual(resources.skillPaths, []);