@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
@@ -2,8 +2,7 @@ import { readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { resolveCognitiveRoutingState } from "../cognitive-routing/runtime.js";
5
- import { isPiFlowHost } from "./runtime-identity.js";
6
- export const VALID_MODES = new Set(["conversation", "workflow", "strict-workflow"]);
5
+ import { supportsCognitiveRoutingModelRegistry } from "../cognitive-routing/host.js";
7
6
  export const WORKFLOW_COMMANDS = [
8
7
  { command: "discuss", skill: "discuss" },
9
8
  { command: "action-selection", skill: "action-selection" },
@@ -38,7 +37,6 @@ export const FREEFLOW_MODEL_SKILL_NAMES = [
38
37
  "execute-work",
39
38
  "finish-branch",
40
39
  "handoff",
41
- "mode-contract",
42
40
  "release-work",
43
41
  "review-artifact",
44
42
  "review-work",
@@ -74,16 +72,8 @@ export function freeflowModelSkillPaths(capabilityState = undefined) {
74
72
  }
75
73
  return paths;
76
74
  }
77
- const MODE_STATE_ENTRY = "freeflow-mode";
78
75
  const SESSION_OVERRIDES_ENTRY = "freeflow-session-overrides";
79
- const SESSION_CORE_KEYS = new Set([
80
- "enabled",
81
- "interactionContract",
82
- "skillsEnabled",
83
- "contextVirtualization",
84
- "conversationHistory",
85
- ]);
86
- const RESET_MODE_ARGS = new Set(["reset"]);
76
+ const SESSION_CORE_KEYS = new Set(["enabled", "contextVirtualization", "conversationHistory"]);
87
77
  export const COGNITIVE_ROUTING_SWITCH_TOOL_NAME = "freeflow_switch_profile";
88
78
  export const FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE = "freeflow-runtime-state";
89
79
  export const COGNITIVE_ROUTING_RUNTIME_STATE_MESSAGE_TYPE = "freeflow-cognitive-routing-runtime-state";
@@ -91,26 +81,31 @@ export const WORKFLOW_BOOTSTRAP_MESSAGE_TYPE = "freeflow-workflow-bootstrap";
91
81
  export const COGNITIVE_ROUTING_BOOTSTRAP_MESSAGE_TYPE = "freeflow-cognitive-routing-bootstrap";
92
82
  export const FREEFLOW_BOOTSTRAP_MESSAGE_TYPE = "freeflow-bootstrap";
93
83
  let runtimeContextCache = null;
94
- let currentModeOverride = null;
95
84
  let currentSessionOverrides = {};
85
+ export function isPromptAvailable(value) {
86
+ return typeof value === "string" && value.trim().length > 0;
87
+ }
88
+ export function hasUsableMandatoryPrompts(freeflowContext) {
89
+ return (
90
+ isPromptAvailable(freeflowContext?.corePrompt) && isPromptAvailable(freeflowContext?.interactionContractPrompt)
91
+ );
92
+ }
96
93
  async function readPromptFile(url) {
97
94
  try {
98
- return await readFile(url, "utf8");
95
+ const prompt = await readFile(url, "utf8");
96
+ return isPromptAvailable(prompt) ? prompt.trim() : null;
99
97
  } catch {
100
98
  return null;
101
99
  }
102
100
  }
103
101
  async function loadRuntimeContext(capabilityState = undefined) {
104
102
  const freeflowEnabled = capabilityState?.enabled === true;
105
- const interactionContractEnabled = capabilityState?.interactionContract?.effective === true;
106
- const skillsEnabled = capabilityState?.skills?.effective === true;
107
103
  const contextVirtualizationEnabled = capabilityState?.contextVirtualization?.effective === true;
108
104
  const conversationHistoryEnabled = capabilityState?.conversationHistory?.effective === true;
109
105
  const cognitiveRoutingEnabled = capabilityState?.cognitiveRouting?.effective === true;
110
106
  const [
111
107
  corePrompt,
112
108
  interactionContractPrompt,
113
- skillsPrompt,
114
109
  cognitiveRoutingPrompt,
115
110
  contextVirtualizationPrompt,
116
111
  conversationHistoryPrompt,
@@ -118,12 +113,9 @@ async function loadRuntimeContext(capabilityState = undefined) {
118
113
  freeflowEnabled
119
114
  ? readPromptFile(new URL("../../../runtime/prompts/core.md", import.meta.url))
120
115
  : Promise.resolve(null),
121
- interactionContractEnabled
116
+ freeflowEnabled
122
117
  ? readPromptFile(new URL("../../../runtime/prompts/interaction-contract.md", import.meta.url))
123
118
  : Promise.resolve(null),
124
- skillsEnabled
125
- ? readPromptFile(new URL("../../../runtime/prompts/skills.md", import.meta.url))
126
- : Promise.resolve(null),
127
119
  cognitiveRoutingEnabled
128
120
  ? readPromptFile(new URL("../../../runtime/prompts/cognitive-routing.md", import.meta.url))
129
121
  : Promise.resolve(null),
@@ -137,7 +129,6 @@ async function loadRuntimeContext(capabilityState = undefined) {
137
129
  return {
138
130
  corePrompt,
139
131
  interactionContractPrompt,
140
- skillsPrompt,
141
132
  cognitiveRoutingPrompt,
142
133
  contextVirtualizationPrompt,
143
134
  conversationHistoryPrompt,
@@ -147,13 +138,12 @@ function runtimeContextCacheSatisfies(capabilityState) {
147
138
  if (!runtimeContextCache) return false;
148
139
  const expected = {
149
140
  corePrompt: capabilityState?.enabled === true,
150
- interactionContractPrompt: capabilityState?.interactionContract?.effective === true,
151
- skillsPrompt: capabilityState?.skills?.effective === true,
141
+ interactionContractPrompt: capabilityState?.enabled === true,
152
142
  cognitiveRoutingPrompt: capabilityState?.cognitiveRouting?.effective === true,
153
143
  contextVirtualizationPrompt: capabilityState?.contextVirtualization?.effective === true,
154
144
  conversationHistoryPrompt: capabilityState?.conversationHistory?.effective === true,
155
145
  };
156
- return Object.entries(expected).every(([key, required]) => !required || Boolean(runtimeContextCache[key]));
146
+ return Object.entries(expected).every(([key, required]) => !required || isPromptAvailable(runtimeContextCache[key]));
157
147
  }
158
148
  export async function refreshRuntimeContext(capabilityState = undefined) {
159
149
  runtimeContextCache = await loadRuntimeContext(capabilityState);
@@ -196,9 +186,6 @@ function isRecord(value) {
196
186
  return typeof value === "object" && value !== null && !Array.isArray(value);
197
187
  }
198
188
  function validateCoreConfigFields(value) {
199
- if (value.defaultMode !== undefined && !VALID_MODES.has(value.defaultMode)) {
200
- return `invalid defaultMode: ${JSON.stringify(value.defaultMode)}`;
201
- }
202
189
  if (value.enabled !== undefined && typeof value.enabled !== "boolean") {
203
190
  return "enabled must be a boolean";
204
191
  }
@@ -208,22 +195,6 @@ function validateCoreConfigFields(value) {
208
195
  if (value.conversationHistory !== undefined && typeof value.conversationHistory !== "boolean") {
209
196
  return "conversationHistory must be a boolean";
210
197
  }
211
- if (value.interactionContract !== undefined && typeof value.interactionContract !== "boolean") {
212
- return "interactionContract must be a boolean";
213
- }
214
- if (value.skills !== undefined) {
215
- if (!isRecord(value.skills)) {
216
- return "skills must be an object";
217
- }
218
- for (const key of Object.keys(value.skills)) {
219
- if (key !== "enabled") {
220
- return `unsupported skills config key: ${key}`;
221
- }
222
- }
223
- if (value.skills.enabled !== undefined && typeof value.skills.enabled !== "boolean") {
224
- return "skills.enabled must be a boolean";
225
- }
226
- }
227
198
  return null;
228
199
  }
229
200
  function validateFreeflowConfigShape(value) {
@@ -233,9 +204,6 @@ function validateFreeflowConfigShape(value) {
233
204
  // Keep retired capability keys shape-tolerated so existing repositories remain activated; runtime behavior ignores them.
234
205
  const allowedKeys = new Set([
235
206
  "enabled",
236
- "defaultMode",
237
- "interactionContract",
238
- "skills",
239
207
  "outputRouter",
240
208
  "observedRouting",
241
209
  "scriptTransform",
@@ -263,9 +231,6 @@ function validateFreeflowLocalConfigShape(value) {
263
231
  }
264
232
  const allowedKeys = new Set([
265
233
  "enabled",
266
- "defaultMode",
267
- "interactionContract",
268
- "skills",
269
234
  "processing",
270
235
  "cognitiveRouting",
271
236
  "contextVirtualization",
@@ -339,29 +304,18 @@ function resolveLayeredValue(repository, local, key, fallback) {
339
304
  }
340
305
  function resolveCoreConfig(repository, local) {
341
306
  const enabled = resolveLayeredValue(repository, local, "enabled", true);
342
- const interactionContract = resolveLayeredValue(repository, local, "interactionContract", true);
343
307
  const contextVirtualization = resolveLayeredValue(repository, local, "contextVirtualization", false);
344
308
  const conversationHistory = resolveLayeredValue(repository, local, "conversationHistory", false);
345
- const defaultMode = resolveLayeredValue(repository, local, "defaultMode", "workflow");
346
- const repositorySkills = isRecord(repository.skills) ? repository.skills : {};
347
- const localSkills = isRecord(local.skills) ? local.skills : {};
348
- const skillsEnabled = resolveLayeredValue(repositorySkills, localSkills, "enabled", true);
349
309
  return {
350
310
  config: {
351
311
  enabled: enabled.value,
352
- interactionContract: interactionContract.value,
353
312
  contextVirtualization: contextVirtualization.value,
354
313
  conversationHistory: conversationHistory.value,
355
- skills: { enabled: skillsEnabled.value },
356
- defaultMode: defaultMode.value,
357
314
  },
358
315
  sources: {
359
316
  enabled: enabled.source,
360
- interactionContract: interactionContract.source,
361
317
  contextVirtualization: contextVirtualization.source,
362
318
  conversationHistory: conversationHistory.source,
363
- skillsEnabled: skillsEnabled.source,
364
- defaultMode: defaultMode.source,
365
319
  },
366
320
  };
367
321
  }
@@ -379,29 +333,20 @@ function resolveSessionCoreConfig(layers) {
379
333
  const configured = layers.coreConfig;
380
334
  const sources = layers.sources;
381
335
  const enabled = currentSessionOverrides.enabled;
382
- const interactionContract = currentSessionOverrides.interactionContract;
383
336
  const contextVirtualization = currentSessionOverrides.contextVirtualization;
384
337
  const conversationHistory = currentSessionOverrides.conversationHistory;
385
- const skillsEnabled = currentSessionOverrides.skillsEnabled;
386
338
  return {
387
339
  config: {
388
340
  enabled: typeof enabled === "boolean" ? enabled : configured.enabled,
389
- interactionContract:
390
- typeof interactionContract === "boolean" ? interactionContract : configured.interactionContract,
391
341
  contextVirtualization:
392
342
  typeof contextVirtualization === "boolean" ? contextVirtualization : configured.contextVirtualization,
393
343
  conversationHistory:
394
344
  typeof conversationHistory === "boolean" ? conversationHistory : configured.conversationHistory,
395
- skills: { enabled: typeof skillsEnabled === "boolean" ? skillsEnabled : configured.skills.enabled },
396
- defaultMode: configured.defaultMode,
397
345
  },
398
346
  sources: {
399
347
  enabled: typeof enabled === "boolean" ? "session" : sources.enabled,
400
- interactionContract: typeof interactionContract === "boolean" ? "session" : sources.interactionContract,
401
348
  contextVirtualization: typeof contextVirtualization === "boolean" ? "session" : sources.contextVirtualization,
402
349
  conversationHistory: typeof conversationHistory === "boolean" ? "session" : sources.conversationHistory,
403
- skillsEnabled: typeof skillsEnabled === "boolean" ? "session" : sources.skillsEnabled,
404
- defaultMode: sources.defaultMode,
405
350
  },
406
351
  };
407
352
  }
@@ -433,55 +378,27 @@ export async function readCapabilityState(cwd, host = undefined, extensionHost =
433
378
  const layers = await readFreeflowConfigLayers(cwd);
434
379
  const effectiveCore = resolveSessionCoreConfig(layers);
435
380
  const enabled = layers.configured && effectiveCore.config.enabled;
436
- const interactionContractConfigEnabled = effectiveCore.config.interactionContract;
437
- const skillsConfigEnabled = effectiveCore.config.skills.enabled;
438
381
  const contextVirtualizationConfigEnabled = effectiveCore.config.contextVirtualization;
439
382
  const conversationHistoryConfigEnabled = effectiveCore.config.conversationHistory;
440
- const skillsEffective = enabled && skillsConfigEnabled;
441
- const hostSupportsCognitiveRouting = isPiFlowHost(extensionHost);
383
+ const hostSupportsCognitiveRouting = supportsCognitiveRoutingModelRegistry(host);
442
384
  const configuredCognitiveRouting = await resolveCognitiveRoutingState(
443
385
  layers.repository.parsed,
444
386
  layers.local.parsed,
445
387
  hostSupportsCognitiveRouting ? host : undefined,
446
388
  );
447
- const disabledReason = enabled
448
- ? skillsEffective
449
- ? undefined
450
- : { code: "disabled", message: "Skills are disabled" }
451
- : { code: "disabled", message: "Freeflow is disabled" };
389
+ const disabledReason = enabled ? undefined : { code: "disabled", message: "Freeflow is disabled" };
452
390
  const childCapability = (configuredEnabled) => ({
453
391
  enabled: configuredEnabled,
454
- effective: skillsEffective && configuredEnabled,
392
+ effective: enabled && configuredEnabled,
455
393
  ...(disabledReason ? { blockingReason: disabledReason } : {}),
456
394
  });
457
395
  const cognitiveRouting = enabled
458
- ? skillsEffective
459
- ? !hostSupportsCognitiveRouting && configuredCognitiveRouting.configValid
460
- ? {
461
- ...configuredCognitiveRouting,
462
- effective: false,
463
- blockingReason: {
464
- code: "runtime_disabled",
465
- message: "Cognitive Routing is disabled outside the PiFlow runtime",
466
- },
467
- }
468
- : configuredCognitiveRouting
469
- : {
470
- ...configuredCognitiveRouting,
471
- effective: false,
472
- blockingReason: {
473
- code: "disabled",
474
- message: "Skills are disabled",
475
- },
476
- }
396
+ ? configuredCognitiveRouting
477
397
  : {
478
398
  ...configuredCognitiveRouting,
479
399
  enabled: false,
480
400
  effective: false,
481
- blockingReason: {
482
- code: "disabled",
483
- message: "Freeflow is disabled",
484
- },
401
+ blockingReason: disabledReason,
485
402
  };
486
403
  return {
487
404
  configured: layers.configured,
@@ -498,16 +415,7 @@ export async function readCapabilityState(cwd, host = undefined, extensionHost =
498
415
  configuredSources: layers.sources,
499
416
  sessionOverrides: { ...currentSessionOverrides },
500
417
  configSources: effectiveCore.sources,
501
- defaultMode: effectiveCore.config.defaultMode,
502
418
  enabled,
503
- interactionContract: {
504
- enabled: interactionContractConfigEnabled,
505
- effective: enabled && interactionContractConfigEnabled,
506
- },
507
- skills: {
508
- enabled: skillsConfigEnabled,
509
- effective: enabled && skillsConfigEnabled,
510
- },
511
419
  contextVirtualization: childCapability(contextVirtualizationConfigEnabled),
512
420
  conversationHistory: childCapability(conversationHistoryConfigEnabled),
513
421
  hostSupportsCognitiveRouting,
@@ -515,48 +423,20 @@ export async function readCapabilityState(cwd, host = undefined, extensionHost =
515
423
  };
516
424
  }
517
425
  export const readRuntimeState = readCapabilityState;
518
- export function restoreModeOverride(ctx) {
519
- currentModeOverride = null;
426
+ export function restoreSessionOverrides(ctx) {
520
427
  currentSessionOverrides = {};
521
428
  const entries = ctx.sessionManager?.getBranch?.() ?? ctx.sessionManager?.getEntries?.() ?? [];
522
429
  for (const entry of entries) {
523
- if (entry.type !== "custom") continue;
524
- if (entry.customType === MODE_STATE_ENTRY) {
525
- const mode = entry.data?.currentMode;
526
- currentModeOverride = VALID_MODES.has(mode) ? mode : null;
527
- } else if (entry.customType === SESSION_OVERRIDES_ENTRY) {
430
+ if (entry.type === "custom" && entry.customType === SESSION_OVERRIDES_ENTRY) {
528
431
  currentSessionOverrides = normalizeSessionOverrides(entry.data?.overrides);
529
432
  }
530
433
  }
531
434
  }
532
- export async function readModeState(cwd) {
533
- const layers = await readFreeflowConfigLayers(cwd);
534
- const effectiveCore = resolveSessionCoreConfig(layers);
535
- const repositoryDefaultMode = VALID_MODES.has(layers.repository.parsed.defaultMode)
536
- ? layers.repository.parsed.defaultMode
537
- : "workflow";
538
- const personalDefaultMode = VALID_MODES.has(layers.local.parsed.defaultMode) ? layers.local.parsed.defaultMode : null;
539
- const sessionMode = VALID_MODES.has(currentModeOverride) ? currentModeOverride : null;
540
- const resolvedMode = sessionMode ?? effectiveCore.config.defaultMode;
541
- const active = layers.configured && effectiveCore.config.enabled && effectiveCore.config.skills.enabled;
542
- return {
543
- repositoryDefaultMode,
544
- repositoryDefaultModeSource: VALID_MODES.has(layers.repository.parsed.defaultMode) ? "repository" : "builtin",
545
- personalDefaultMode,
546
- defaultMode: effectiveCore.config.defaultMode,
547
- defaultModeSource: effectiveCore.sources.defaultMode,
548
- currentMode: sessionMode,
549
- sessionMode,
550
- resolvedMode,
551
- active,
552
- effectiveMode: active ? resolvedMode : null,
553
- };
554
- }
555
- export function setModeStatus(
435
+ export function setFreeflowStatus(
556
436
  ctx,
557
- modeState,
558
437
  capabilityState = undefined,
559
438
  cognitiveRoutingRuntime = undefined,
439
+ freeflowContext = undefined,
560
440
  options = {},
561
441
  ) {
562
442
  if (capabilityState && !capabilityState.configured) {
@@ -568,22 +448,25 @@ export function setModeStatus(
568
448
  ctx.ui.setStatus("freeflow", `freeflow: off${source}`);
569
449
  return;
570
450
  }
571
- const active = [];
572
- if (capabilityState?.interactionContract.effective) {
573
- active.push("interaction");
574
- } else if (capabilityState?.configSources?.interactionContract === "session") {
575
- active.push("interaction off (session)");
576
- }
577
- if (capabilityState?.skills.effective) {
578
- active.push(`${modeState.effectiveMode}${modeState.currentMode ? " (session)" : ""}`);
579
- } else if (capabilityState?.configSources?.skillsEnabled === "session") {
580
- active.push("skills off (session)");
451
+ if (capabilityState?.enabled === true && !hasUsableMandatoryPrompts(freeflowContext)) {
452
+ ctx.ui.setStatus("freeflow", "freeflow: unavailable");
453
+ return;
581
454
  }
455
+ const active = [];
582
456
  const cognitiveRouting = capabilityState?.cognitiveRouting;
457
+ const cognitiveRoutingInactive =
458
+ cognitiveRouting?.enabled === true && cognitiveRoutingRuntime?.runtimeStatus === "inactive";
459
+ const cognitiveRoutingBlocked =
460
+ cognitiveRouting?.enabled === true &&
461
+ (cognitiveRouting?.blockingReason?.code === "runtime_blocked" ||
462
+ cognitiveRoutingRuntime?.runtimeStatus === "blocked");
583
463
  const cognitiveRoutingActive = cognitiveRouting?.effective === true && cognitiveRoutingRuntime?.effective === true;
584
- const startupSelectionSuppressesCognitiveRouting =
585
- ctx?.modelStateProvenance?.explicitModel === true || ctx?.modelStateProvenance?.explicitThinking === true;
586
- if (cognitiveRoutingActive) {
464
+ const startupSelectionSuppressesCognitiveRouting = options.startupSelectionSuppressed === true;
465
+ if (cognitiveRoutingInactive) {
466
+ active.push("cognitive inactive");
467
+ } else if (cognitiveRoutingBlocked) {
468
+ active.push(`cognitive blocked · ${cognitiveRoutingRuntime?.runtimeReason ?? "runtime_blocked"}`);
469
+ } else if (cognitiveRoutingActive) {
587
470
  const profile = cognitiveRoutingRuntime.activeProfile;
588
471
  const control =
589
472
  cognitiveRoutingRuntime.controlMode === "manual-standard" ||
@@ -593,18 +476,21 @@ export function setModeStatus(
593
476
  active.push(`${profile} · ${control}`);
594
477
  } else if (cognitiveRouting?.enabled === true) {
595
478
  if (cognitiveRouting.blockingReason?.code === "runtime_disabled") {
596
- active.push("cognitive disabled · PiFlow only");
479
+ active.push("cognitive blocked · runtime_disabled");
597
480
  } else if (
598
481
  cognitiveRouting.effective === true &&
599
482
  cognitiveRoutingRuntime === undefined &&
600
483
  options.cognitiveRoutingStartupPending === true &&
601
484
  !startupSelectionSuppressesCognitiveRouting
602
485
  ) {
603
- const startupProfile = cognitiveRouting.sessionStart?.profile ?? "standard";
486
+ const startupProfile =
487
+ cognitiveRouting.sessionStart?.control === "manual"
488
+ ? (cognitiveRouting.sessionStart.profile ?? "reasoning")
489
+ : "reasoning";
604
490
  active.push(`${startupProfile} · pending`);
605
491
  } else {
606
492
  const reason =
607
- cognitiveRouting?.effective === true
493
+ cognitiveRouting.effective === true
608
494
  ? (cognitiveRoutingRuntime?.blockingReason?.code ?? "runtime_inactive")
609
495
  : (cognitiveRouting.blockingReason?.code ?? "unavailable");
610
496
  active.push(`cognitive blocked · ${reason}`);
@@ -613,19 +499,7 @@ export function setModeStatus(
613
499
  if (capabilityState?.contextVirtualization?.effective || capabilityState?.conversationHistory?.effective) {
614
500
  active.push("context");
615
501
  }
616
- ctx.ui.setStatus("freeflow", `freeflow: ${active.length > 0 ? active.join(" · ") : "idle"}`);
617
- }
618
- function modeSourceLabel(source) {
619
- if (source === "local") return "personal override";
620
- if (source === "repository") return "repository default";
621
- return "built-in default";
622
- }
623
- function describeModeState(modeState) {
624
- const configuredDefault = `configured default ${modeState.defaultMode} (${modeSourceLabel(modeState.defaultModeSource)})`;
625
- if (modeState.sessionMode) {
626
- return `session ${modeState.sessionMode}; ${configuredDefault}`;
627
- }
628
- return configuredDefault;
502
+ ctx.ui.setStatus("freeflow", `freeflow: ${active.length > 0 ? active.join(" · ") : "active"}`);
629
503
  }
630
504
  export function skillPrompt(skill, args) {
631
505
  const trimmed = args?.trim();
@@ -647,7 +521,15 @@ function publicCapabilityStatus(capability) {
647
521
  if (blockingCode && blockingCode !== "disabled") return "unavailable";
648
522
  return "inactive";
649
523
  }
650
- export function freeflowRuntimeStateMessage(modeState, capabilityState, cognitiveRoutingRuntime = undefined) {
524
+ function publicCognitiveRoutingStatus(capability, runtime) {
525
+ if (runtime?.runtimeStatus === "inactive") return "inactive";
526
+ return publicCapabilityStatus(capability);
527
+ }
528
+ export function freeflowRuntimeStateMessage(
529
+ capabilityState,
530
+ cognitiveRoutingRuntime = undefined,
531
+ freeflowContext = undefined,
532
+ ) {
651
533
  const cognitiveRoutingEffective = capabilityState?.cognitiveRouting?.effective === true;
652
534
  const profile = publicCognitiveRoutingProfile(
653
535
  cognitiveRoutingRuntime?.activeProfile,
@@ -655,8 +537,16 @@ export function freeflowRuntimeStateMessage(modeState, capabilityState, cognitiv
655
537
  );
656
538
  const control =
657
539
  profile === "unavailable" ? "unavailable" : publicCognitiveRoutingControl(cognitiveRoutingRuntime?.controlMode);
658
- const defaultMode = modeState?.defaultMode ?? "workflow";
659
- const activeMode = modeState?.effectiveMode ?? "inactive";
540
+ const mandatoryPromptAvailable = capabilityState?.enabled !== true || hasUsableMandatoryPrompts(freeflowContext);
541
+ const freeflowStatus = capabilityState?.configured
542
+ ? capabilityState.enabled
543
+ ? mandatoryPromptAvailable
544
+ ? "active"
545
+ : "unavailable"
546
+ : "inactive"
547
+ : capabilityState?.configExists
548
+ ? "config error"
549
+ : "setup needed";
660
550
  return {
661
551
  role: "custom",
662
552
  customType: FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE,
@@ -665,15 +555,12 @@ export function freeflowRuntimeStateMessage(modeState, capabilityState, cognitiv
665
555
  "",
666
556
  "This is extension-generated runtime state. Use it to interpret the stable Freeflow guidance.",
667
557
  "",
668
- `Default mode: \`${defaultMode}\``,
669
- `Active mode: \`${activeMode}\``,
558
+ `Freeflow: ${freeflowStatus}`,
670
559
  "",
671
560
  "Capabilities:",
672
- `- Interaction Contract: ${publicCapabilityStatus(capabilityState?.interactionContract)}`,
673
- `- Skills: ${publicCapabilityStatus(capabilityState?.skills)}`,
674
561
  `- Context Virtualization: ${publicCapabilityStatus(capabilityState?.contextVirtualization)}`,
675
562
  `- Conversation History: ${publicCapabilityStatus(capabilityState?.conversationHistory)}`,
676
- `- Cognitive Routing: ${publicCapabilityStatus(capabilityState?.cognitiveRouting)}`,
563
+ `- Cognitive Routing: ${publicCognitiveRoutingStatus(capabilityState?.cognitiveRouting, cognitiveRoutingRuntime)}`,
677
564
  "",
678
565
  "Cognitive Routing:",
679
566
  `- Control: \`${control}\``,
@@ -690,31 +577,68 @@ export function withoutFreeflowRuntimeState(messages) {
690
577
  message?.customType !== COGNITIVE_ROUTING_RUNTIME_STATE_MESSAGE_TYPE,
691
578
  );
692
579
  }
693
- export function withFreeflowRuntimeState(messages, modeState, capabilityState, cognitiveRoutingRuntime = undefined) {
694
- return [
695
- ...withoutFreeflowRuntimeState(messages),
696
- freeflowRuntimeStateMessage(modeState, capabilityState, cognitiveRoutingRuntime),
697
- ];
580
+ function lastUserMessageIndex(messages) {
581
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
582
+ if (messages[index]?.role === "user") return index;
583
+ }
584
+ return -1;
585
+ }
586
+ function insertRuntimeStateBeforeLatestUser(messages, runtimeState) {
587
+ const latestUserIndex = lastUserMessageIndex(messages);
588
+ if (latestUserIndex < 0) return [...messages, runtimeState];
589
+ return [...messages.slice(0, latestUserIndex), runtimeState, ...messages.slice(latestUserIndex)];
590
+ }
591
+ export function withFreeflowRuntimeState(
592
+ messages,
593
+ capabilityState,
594
+ cognitiveRoutingRuntime = undefined,
595
+ freeflowContext = undefined,
596
+ options = {},
597
+ ) {
598
+ const source = Array.isArray(messages) ? messages : [];
599
+ const runtimeState = freeflowRuntimeStateMessage(capabilityState, cognitiveRoutingRuntime, freeflowContext);
600
+ const runtimeStateMessages = source.filter(
601
+ (message) =>
602
+ message?.customType === FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE ||
603
+ message?.customType === COGNITIVE_ROUTING_RUNTIME_STATE_MESSAGE_TYPE,
604
+ );
605
+ const withoutRuntimeState = withoutFreeflowRuntimeState(source);
606
+ const expectedRuntimeStateIndex = lastUserMessageIndex(withoutRuntimeState);
607
+ const runtimeStateIndex = source.findIndex((message) => message?.customType === FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE);
608
+ const unchanged =
609
+ options.force !== true &&
610
+ runtimeStateMessages.length === 1 &&
611
+ runtimeStateMessages[0]?.customType === FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE &&
612
+ runtimeStateMessages[0]?.content === runtimeState.content &&
613
+ runtimeStateIndex === (expectedRuntimeStateIndex < 0 ? withoutRuntimeState.length : expectedRuntimeStateIndex);
614
+ if (unchanged) return source;
615
+ return insertRuntimeStateBeforeLatestUser(withoutRuntimeState, runtimeState);
698
616
  }
699
617
  export function runtimeContext(freeflowContext, capabilityState) {
700
- if (capabilityState?.configured !== true || capabilityState?.enabled !== true || !freeflowContext?.corePrompt) {
618
+ if (
619
+ capabilityState?.configured !== true ||
620
+ capabilityState?.enabled !== true ||
621
+ !hasUsableMandatoryPrompts(freeflowContext)
622
+ ) {
701
623
  return "";
702
624
  }
703
- const blocks = [];
704
- if (freeflowContext?.corePrompt) blocks.push(freeflowContext.corePrompt.trim());
705
- if (capabilityState.interactionContract?.effective === true && freeflowContext?.interactionContractPrompt) {
706
- blocks.push(freeflowContext.interactionContractPrompt.trim());
707
- }
708
- if (capabilityState.skills?.effective === true && freeflowContext?.skillsPrompt) {
709
- blocks.push(freeflowContext.skillsPrompt.trim());
710
- }
711
- if (capabilityState.cognitiveRouting?.effective === true && freeflowContext?.cognitiveRoutingPrompt) {
625
+ const blocks = [freeflowContext.corePrompt.trim(), freeflowContext.interactionContractPrompt.trim()];
626
+ if (
627
+ capabilityState.cognitiveRouting?.effective === true &&
628
+ isPromptAvailable(freeflowContext.cognitiveRoutingPrompt)
629
+ ) {
712
630
  blocks.push(freeflowContext.cognitiveRoutingPrompt.trim());
713
631
  }
714
- if (capabilityState.contextVirtualization?.effective === true && freeflowContext?.contextVirtualizationPrompt) {
632
+ if (
633
+ capabilityState.contextVirtualization?.effective === true &&
634
+ isPromptAvailable(freeflowContext.contextVirtualizationPrompt)
635
+ ) {
715
636
  blocks.push(freeflowContext.contextVirtualizationPrompt.trim());
716
637
  }
717
- if (capabilityState.conversationHistory?.effective === true && freeflowContext?.conversationHistoryPrompt) {
638
+ if (
639
+ capabilityState.conversationHistory?.effective === true &&
640
+ isPromptAvailable(freeflowContext.conversationHistoryPrompt)
641
+ ) {
718
642
  blocks.push(freeflowContext.conversationHistoryPrompt.trim());
719
643
  }
720
644
  return blocks.filter(Boolean).join("\n\n");
@@ -740,106 +664,22 @@ export async function setSessionCoreOverride(key, value, ctx, pi) {
740
664
  pi?.appendEntry?.(SESSION_OVERRIDES_ENTRY, { overrides: { ...currentSessionOverrides } });
741
665
  return {
742
666
  changed: true,
743
- reloadRequired: key === "enabled" || key === "skillsEnabled",
667
+ reloadRequired: key === "enabled",
744
668
  sessionOverrides: { ...currentSessionOverrides },
745
669
  capabilityState: await readCapabilityState(ctx.cwd, ctx, pi?.host),
746
670
  };
747
671
  }
748
672
  export async function resetSessionOverrides(ctx, pi) {
749
673
  const hadCoreOverrides = Object.keys(currentSessionOverrides).length > 0;
750
- const reloadRequired =
751
- Object.hasOwn(currentSessionOverrides, "enabled") || Object.hasOwn(currentSessionOverrides, "skillsEnabled");
752
- const hadModeOverride = VALID_MODES.has(currentModeOverride);
674
+ const reloadRequired = Object.hasOwn(currentSessionOverrides, "enabled");
753
675
  if (hadCoreOverrides) {
754
676
  currentSessionOverrides = {};
755
677
  pi?.appendEntry?.(SESSION_OVERRIDES_ENTRY, { overrides: {} });
756
678
  }
757
- if (hadModeOverride) {
758
- await setSessionMode(null, ctx, pi);
759
- }
760
679
  return {
761
- changed: hadCoreOverrides || hadModeOverride,
680
+ changed: hadCoreOverrides,
762
681
  reloadRequired,
763
682
  sessionOverrides: { ...currentSessionOverrides },
764
- modeState: await readModeState(ctx.cwd),
683
+ capabilityState: await readCapabilityState(ctx.cwd, ctx, pi?.host),
765
684
  };
766
685
  }
767
- export async function setSessionMode(mode, ctx, pi) {
768
- const nextMode = mode === "default" || mode === "reset" || mode === null ? null : mode;
769
- if (nextMode !== null && !VALID_MODES.has(nextMode)) {
770
- throw new Error(`Invalid Freeflow mode: ${String(mode)}`);
771
- }
772
- currentModeOverride = nextMode;
773
- pi?.appendEntry?.(MODE_STATE_ENTRY, { currentMode: nextMode });
774
- return readModeState(ctx.cwd);
775
- }
776
- export async function handleModeCommand(args, ctx, pi) {
777
- const arg = args?.trim();
778
- const capabilityState = await readCapabilityState(ctx.cwd, ctx, pi?.host);
779
- const inactiveModeState = await readModeState(ctx.cwd);
780
- if (!capabilityState.configured) {
781
- setModeStatus(ctx, inactiveModeState, capabilityState);
782
- ctx.ui.notify(
783
- "Freeflow is installed but this repo is not set up. Run /setup-freeflow before changing mode.",
784
- "warning",
785
- );
786
- return { changed: false, error: "not_configured" };
787
- }
788
- if (!capabilityState.enabled) {
789
- setModeStatus(ctx, inactiveModeState, capabilityState);
790
- ctx.ui.notify(
791
- "Freeflow is disabled for this repo. Use /freeflow enable or /freeflow settings to re-enable it.",
792
- "warning",
793
- );
794
- return { changed: false, error: "freeflow_disabled" };
795
- }
796
- if (!capabilityState.skills.effective) {
797
- setModeStatus(ctx, inactiveModeState, capabilityState);
798
- ctx.ui.notify(
799
- `Freeflow modes are inactive because Skills are disabled. Configured default mode: ${inactiveModeState.defaultMode} (${modeSourceLabel(inactiveModeState.defaultModeSource)}). Enable Skills in /freeflow settings before changing mode.`,
800
- "warning",
801
- );
802
- return { changed: false, error: "skills_disabled" };
803
- }
804
- if (VALID_MODES.has(arg)) {
805
- if (inactiveModeState.effectiveMode === arg) {
806
- setModeStatus(ctx, inactiveModeState, capabilityState);
807
- const message = inactiveModeState.sessionMode
808
- ? `Freeflow is already in ${arg} mode for this Pi session. No session entry was added. Configured default remains ${inactiveModeState.defaultMode} (${modeSourceLabel(inactiveModeState.defaultModeSource)}).`
809
- : `Freeflow is already in ${arg} mode from the configured default (${modeSourceLabel(inactiveModeState.defaultModeSource)}). No session override was created.`;
810
- ctx.ui.notify(message, "info");
811
- return { changed: false, modeState: inactiveModeState };
812
- }
813
- const modeState = await setSessionMode(arg, ctx, pi);
814
- setModeStatus(ctx, modeState, capabilityState);
815
- ctx.ui.notify(
816
- `Freeflow mode is now ${modeState.effectiveMode} for this Pi session. Stored in Pi session history; .freeflow/local.json and .freeflow/config.json were not changed. Configured default remains ${modeState.defaultMode} (${modeSourceLabel(modeState.defaultModeSource)}).`,
817
- "info",
818
- );
819
- return { changed: true, modeState };
820
- }
821
- if (RESET_MODE_ARGS.has(arg) || arg === "default") {
822
- if (!inactiveModeState.sessionMode) {
823
- setModeStatus(ctx, inactiveModeState, capabilityState);
824
- ctx.ui.notify(
825
- `Freeflow is already using the configured default: ${inactiveModeState.defaultMode} (${modeSourceLabel(inactiveModeState.defaultModeSource)}). No session override is active.`,
826
- "info",
827
- );
828
- return { changed: false, modeState: inactiveModeState };
829
- }
830
- const modeState = await setSessionMode(null, ctx, pi);
831
- setModeStatus(ctx, modeState, capabilityState);
832
- ctx.ui.notify(
833
- `Freeflow mode reset to configured default: ${modeState.defaultMode} (${modeSourceLabel(modeState.defaultModeSource)}). Session override cleared; .freeflow/local.json and .freeflow/config.json were not changed.`,
834
- "info",
835
- );
836
- return { changed: true, modeState };
837
- }
838
- const modeState = await readModeState(ctx.cwd);
839
- setModeStatus(ctx, modeState, capabilityState);
840
- ctx.ui.notify(
841
- `Freeflow mode is ${modeState.effectiveMode} (${describeModeState(modeState)}). Use /freeflow mode conversation, /freeflow mode workflow, /freeflow mode strict-workflow, or /freeflow mode reset.`,
842
- "info",
843
- );
844
- return { changed: false, modeState, error: arg ? "invalid_mode" : undefined };
845
- }