@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
@@ -3,9 +3,7 @@ import { join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
5
  import { resolveCognitiveRoutingState } from "../cognitive-routing/runtime.js";
6
- import { isPiFlowHost } from "./runtime-identity.js";
7
-
8
- export const VALID_MODES = new Set(["conversation", "workflow", "strict-workflow"]);
6
+ import { supportsCognitiveRoutingModelRegistry } from "../cognitive-routing/host.js";
9
7
 
10
8
  export const WORKFLOW_COMMANDS = [
11
9
  { command: "discuss", skill: "discuss" },
@@ -43,7 +41,6 @@ export const FREEFLOW_MODEL_SKILL_NAMES = [
43
41
  "execute-work",
44
42
  "finish-branch",
45
43
  "handoff",
46
- "mode-contract",
47
44
  "release-work",
48
45
  "review-artifact",
49
46
  "review-work",
@@ -84,20 +81,11 @@ export function freeflowModelSkillPaths(capabilityState = undefined) {
84
81
  return paths;
85
82
  }
86
83
 
87
- type SessionCoreKey =
88
- "enabled" | "interactionContract" | "skillsEnabled" | "contextVirtualization" | "conversationHistory";
84
+ type SessionCoreKey = "enabled" | "contextVirtualization" | "conversationHistory";
89
85
  type SessionCoreOverrides = Partial<Record<SessionCoreKey, boolean>>;
90
86
 
91
- const MODE_STATE_ENTRY = "freeflow-mode";
92
87
  const SESSION_OVERRIDES_ENTRY = "freeflow-session-overrides";
93
- const SESSION_CORE_KEYS = new Set<SessionCoreKey>([
94
- "enabled",
95
- "interactionContract",
96
- "skillsEnabled",
97
- "contextVirtualization",
98
- "conversationHistory",
99
- ]);
100
- const RESET_MODE_ARGS = new Set(["reset"]);
88
+ const SESSION_CORE_KEYS = new Set<SessionCoreKey>(["enabled", "contextVirtualization", "conversationHistory"]);
101
89
 
102
90
  export const COGNITIVE_ROUTING_SWITCH_TOOL_NAME = "freeflow_switch_profile";
103
91
  export const FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE = "freeflow-runtime-state";
@@ -107,12 +95,22 @@ export const COGNITIVE_ROUTING_BOOTSTRAP_MESSAGE_TYPE = "freeflow-cognitive-rout
107
95
  export const FREEFLOW_BOOTSTRAP_MESSAGE_TYPE = "freeflow-bootstrap";
108
96
 
109
97
  let runtimeContextCache = null;
110
- let currentModeOverride = null;
111
98
  let currentSessionOverrides: SessionCoreOverrides = {};
112
99
 
100
+ export function isPromptAvailable(value: unknown): value is string {
101
+ return typeof value === "string" && value.trim().length > 0;
102
+ }
103
+
104
+ export function hasUsableMandatoryPrompts(freeflowContext: any): boolean {
105
+ return (
106
+ isPromptAvailable(freeflowContext?.corePrompt) && isPromptAvailable(freeflowContext?.interactionContractPrompt)
107
+ );
108
+ }
109
+
113
110
  async function readPromptFile(url: URL): Promise<string | null> {
114
111
  try {
115
- return await readFile(url, "utf8");
112
+ const prompt = await readFile(url, "utf8");
113
+ return isPromptAvailable(prompt) ? prompt.trim() : null;
116
114
  } catch {
117
115
  return null;
118
116
  }
@@ -120,15 +118,12 @@ async function readPromptFile(url: URL): Promise<string | null> {
120
118
 
121
119
  async function loadRuntimeContext(capabilityState = undefined) {
122
120
  const freeflowEnabled = capabilityState?.enabled === true;
123
- const interactionContractEnabled = capabilityState?.interactionContract?.effective === true;
124
- const skillsEnabled = capabilityState?.skills?.effective === true;
125
121
  const contextVirtualizationEnabled = capabilityState?.contextVirtualization?.effective === true;
126
122
  const conversationHistoryEnabled = capabilityState?.conversationHistory?.effective === true;
127
123
  const cognitiveRoutingEnabled = capabilityState?.cognitiveRouting?.effective === true;
128
124
  const [
129
125
  corePrompt,
130
126
  interactionContractPrompt,
131
- skillsPrompt,
132
127
  cognitiveRoutingPrompt,
133
128
  contextVirtualizationPrompt,
134
129
  conversationHistoryPrompt,
@@ -136,12 +131,9 @@ async function loadRuntimeContext(capabilityState = undefined) {
136
131
  freeflowEnabled
137
132
  ? readPromptFile(new URL("../../../runtime/prompts/core.md", import.meta.url))
138
133
  : Promise.resolve(null),
139
- interactionContractEnabled
134
+ freeflowEnabled
140
135
  ? readPromptFile(new URL("../../../runtime/prompts/interaction-contract.md", import.meta.url))
141
136
  : Promise.resolve(null),
142
- skillsEnabled
143
- ? readPromptFile(new URL("../../../runtime/prompts/skills.md", import.meta.url))
144
- : Promise.resolve(null),
145
137
  cognitiveRoutingEnabled
146
138
  ? readPromptFile(new URL("../../../runtime/prompts/cognitive-routing.md", import.meta.url))
147
139
  : Promise.resolve(null),
@@ -156,7 +148,6 @@ async function loadRuntimeContext(capabilityState = undefined) {
156
148
  return {
157
149
  corePrompt,
158
150
  interactionContractPrompt,
159
- skillsPrompt,
160
151
  cognitiveRoutingPrompt,
161
152
  contextVirtualizationPrompt,
162
153
  conversationHistoryPrompt,
@@ -167,13 +158,12 @@ function runtimeContextCacheSatisfies(capabilityState) {
167
158
  if (!runtimeContextCache) return false;
168
159
  const expected = {
169
160
  corePrompt: capabilityState?.enabled === true,
170
- interactionContractPrompt: capabilityState?.interactionContract?.effective === true,
171
- skillsPrompt: capabilityState?.skills?.effective === true,
161
+ interactionContractPrompt: capabilityState?.enabled === true,
172
162
  cognitiveRoutingPrompt: capabilityState?.cognitiveRouting?.effective === true,
173
163
  contextVirtualizationPrompt: capabilityState?.contextVirtualization?.effective === true,
174
164
  conversationHistoryPrompt: capabilityState?.conversationHistory?.effective === true,
175
165
  };
176
- return Object.entries(expected).every(([key, required]) => !required || Boolean(runtimeContextCache[key]));
166
+ return Object.entries(expected).every(([key, required]) => !required || isPromptAvailable(runtimeContextCache[key]));
177
167
  }
178
168
 
179
169
  export async function refreshRuntimeContext(capabilityState = undefined) {
@@ -223,10 +213,6 @@ function isRecord(value) {
223
213
  }
224
214
 
225
215
  function validateCoreConfigFields(value) {
226
- if (value.defaultMode !== undefined && !VALID_MODES.has(value.defaultMode)) {
227
- return `invalid defaultMode: ${JSON.stringify(value.defaultMode)}`;
228
- }
229
-
230
216
  if (value.enabled !== undefined && typeof value.enabled !== "boolean") {
231
217
  return "enabled must be a boolean";
232
218
  }
@@ -239,24 +225,6 @@ function validateCoreConfigFields(value) {
239
225
  return "conversationHistory must be a boolean";
240
226
  }
241
227
 
242
- if (value.interactionContract !== undefined && typeof value.interactionContract !== "boolean") {
243
- return "interactionContract must be a boolean";
244
- }
245
-
246
- if (value.skills !== undefined) {
247
- if (!isRecord(value.skills)) {
248
- return "skills must be an object";
249
- }
250
- for (const key of Object.keys(value.skills)) {
251
- if (key !== "enabled") {
252
- return `unsupported skills config key: ${key}`;
253
- }
254
- }
255
- if (value.skills.enabled !== undefined && typeof value.skills.enabled !== "boolean") {
256
- return "skills.enabled must be a boolean";
257
- }
258
- }
259
-
260
228
  return null;
261
229
  }
262
230
 
@@ -268,9 +236,6 @@ function validateFreeflowConfigShape(value) {
268
236
  // Keep retired capability keys shape-tolerated so existing repositories remain activated; runtime behavior ignores them.
269
237
  const allowedKeys = new Set([
270
238
  "enabled",
271
- "defaultMode",
272
- "interactionContract",
273
- "skills",
274
239
  "outputRouter",
275
240
  "observedRouting",
276
241
  "scriptTransform",
@@ -303,9 +268,6 @@ function validateFreeflowLocalConfigShape(value) {
303
268
 
304
269
  const allowedKeys = new Set([
305
270
  "enabled",
306
- "defaultMode",
307
- "interactionContract",
308
- "skills",
309
271
  "processing",
310
272
  "cognitiveRouting",
311
273
  "contextVirtualization",
@@ -387,30 +349,19 @@ function resolveLayeredValue(repository, local, key, fallback) {
387
349
 
388
350
  function resolveCoreConfig(repository, local) {
389
351
  const enabled = resolveLayeredValue(repository, local, "enabled", true);
390
- const interactionContract = resolveLayeredValue(repository, local, "interactionContract", true);
391
352
  const contextVirtualization = resolveLayeredValue(repository, local, "contextVirtualization", false);
392
353
  const conversationHistory = resolveLayeredValue(repository, local, "conversationHistory", false);
393
- const defaultMode = resolveLayeredValue(repository, local, "defaultMode", "workflow");
394
- const repositorySkills = isRecord(repository.skills) ? repository.skills : {};
395
- const localSkills = isRecord(local.skills) ? local.skills : {};
396
- const skillsEnabled = resolveLayeredValue(repositorySkills, localSkills, "enabled", true);
397
354
 
398
355
  return {
399
356
  config: {
400
357
  enabled: enabled.value,
401
- interactionContract: interactionContract.value,
402
358
  contextVirtualization: contextVirtualization.value,
403
359
  conversationHistory: conversationHistory.value,
404
- skills: { enabled: skillsEnabled.value },
405
- defaultMode: defaultMode.value,
406
360
  },
407
361
  sources: {
408
362
  enabled: enabled.source,
409
- interactionContract: interactionContract.source,
410
363
  contextVirtualization: contextVirtualization.source,
411
364
  conversationHistory: conversationHistory.source,
412
- skillsEnabled: skillsEnabled.source,
413
- defaultMode: defaultMode.source,
414
365
  },
415
366
  };
416
367
  }
@@ -430,30 +381,21 @@ function resolveSessionCoreConfig(layers) {
430
381
  const configured = layers.coreConfig;
431
382
  const sources = layers.sources;
432
383
  const enabled = currentSessionOverrides.enabled;
433
- const interactionContract = currentSessionOverrides.interactionContract;
434
384
  const contextVirtualization = currentSessionOverrides.contextVirtualization;
435
385
  const conversationHistory = currentSessionOverrides.conversationHistory;
436
- const skillsEnabled = currentSessionOverrides.skillsEnabled;
437
386
 
438
387
  return {
439
388
  config: {
440
389
  enabled: typeof enabled === "boolean" ? enabled : configured.enabled,
441
- interactionContract:
442
- typeof interactionContract === "boolean" ? interactionContract : configured.interactionContract,
443
390
  contextVirtualization:
444
391
  typeof contextVirtualization === "boolean" ? contextVirtualization : configured.contextVirtualization,
445
392
  conversationHistory:
446
393
  typeof conversationHistory === "boolean" ? conversationHistory : configured.conversationHistory,
447
- skills: { enabled: typeof skillsEnabled === "boolean" ? skillsEnabled : configured.skills.enabled },
448
- defaultMode: configured.defaultMode,
449
394
  },
450
395
  sources: {
451
396
  enabled: typeof enabled === "boolean" ? "session" : sources.enabled,
452
- interactionContract: typeof interactionContract === "boolean" ? "session" : sources.interactionContract,
453
397
  contextVirtualization: typeof contextVirtualization === "boolean" ? "session" : sources.contextVirtualization,
454
398
  conversationHistory: typeof conversationHistory === "boolean" ? "session" : sources.conversationHistory,
455
- skillsEnabled: typeof skillsEnabled === "boolean" ? "session" : sources.skillsEnabled,
456
- defaultMode: sources.defaultMode,
457
399
  },
458
400
  };
459
401
  }
@@ -488,55 +430,27 @@ export async function readCapabilityState(cwd, host = undefined, extensionHost =
488
430
  const layers = await readFreeflowConfigLayers(cwd);
489
431
  const effectiveCore = resolveSessionCoreConfig(layers);
490
432
  const enabled = layers.configured && effectiveCore.config.enabled;
491
- const interactionContractConfigEnabled = effectiveCore.config.interactionContract;
492
- const skillsConfigEnabled = effectiveCore.config.skills.enabled;
493
433
  const contextVirtualizationConfigEnabled = effectiveCore.config.contextVirtualization;
494
434
  const conversationHistoryConfigEnabled = effectiveCore.config.conversationHistory;
495
- const skillsEffective = enabled && skillsConfigEnabled;
496
- const hostSupportsCognitiveRouting = isPiFlowHost(extensionHost);
435
+ const hostSupportsCognitiveRouting = supportsCognitiveRoutingModelRegistry(host);
497
436
  const configuredCognitiveRouting = await resolveCognitiveRoutingState(
498
437
  layers.repository.parsed,
499
438
  layers.local.parsed,
500
439
  hostSupportsCognitiveRouting ? host : undefined,
501
440
  );
502
- const disabledReason = enabled
503
- ? skillsEffective
504
- ? undefined
505
- : { code: "disabled" as const, message: "Skills are disabled" }
506
- : { code: "disabled" as const, message: "Freeflow is disabled" };
441
+ const disabledReason = enabled ? undefined : { code: "disabled" as const, message: "Freeflow is disabled" };
507
442
  const childCapability = (configuredEnabled) => ({
508
443
  enabled: configuredEnabled,
509
- effective: skillsEffective && configuredEnabled,
444
+ effective: enabled && configuredEnabled,
510
445
  ...(disabledReason ? { blockingReason: disabledReason } : {}),
511
446
  });
512
447
  const cognitiveRouting = enabled
513
- ? skillsEffective
514
- ? !hostSupportsCognitiveRouting && configuredCognitiveRouting.configValid
515
- ? {
516
- ...configuredCognitiveRouting,
517
- effective: false,
518
- blockingReason: {
519
- code: "runtime_disabled" as const,
520
- message: "Cognitive Routing is disabled outside the PiFlow runtime",
521
- },
522
- }
523
- : configuredCognitiveRouting
524
- : {
525
- ...configuredCognitiveRouting,
526
- effective: false,
527
- blockingReason: {
528
- code: "disabled" as const,
529
- message: "Skills are disabled",
530
- },
531
- }
448
+ ? configuredCognitiveRouting
532
449
  : {
533
450
  ...configuredCognitiveRouting,
534
451
  enabled: false,
535
452
  effective: false,
536
- blockingReason: {
537
- code: "disabled" as const,
538
- message: "Freeflow is disabled",
539
- },
453
+ blockingReason: disabledReason,
540
454
  };
541
455
  return {
542
456
  configured: layers.configured,
@@ -553,16 +467,7 @@ export async function readCapabilityState(cwd, host = undefined, extensionHost =
553
467
  configuredSources: layers.sources,
554
468
  sessionOverrides: { ...currentSessionOverrides },
555
469
  configSources: effectiveCore.sources,
556
- defaultMode: effectiveCore.config.defaultMode,
557
470
  enabled,
558
- interactionContract: {
559
- enabled: interactionContractConfigEnabled,
560
- effective: enabled && interactionContractConfigEnabled,
561
- },
562
- skills: {
563
- enabled: skillsConfigEnabled,
564
- effective: enabled && skillsConfigEnabled,
565
- },
566
471
  contextVirtualization: childCapability(contextVirtualizationConfigEnabled),
567
472
  conversationHistory: childCapability(conversationHistoryConfigEnabled),
568
473
  hostSupportsCognitiveRouting,
@@ -572,53 +477,23 @@ export async function readCapabilityState(cwd, host = undefined, extensionHost =
572
477
 
573
478
  export const readRuntimeState = readCapabilityState;
574
479
 
575
- export function restoreModeOverride(ctx) {
576
- currentModeOverride = null;
480
+ export function restoreSessionOverrides(ctx) {
577
481
  currentSessionOverrides = {};
578
482
  const entries = ctx.sessionManager?.getBranch?.() ?? ctx.sessionManager?.getEntries?.() ?? [];
579
483
 
580
484
  for (const entry of entries) {
581
- if (entry.type !== "custom") continue;
582
- if (entry.customType === MODE_STATE_ENTRY) {
583
- const mode = entry.data?.currentMode;
584
- currentModeOverride = VALID_MODES.has(mode) ? mode : null;
585
- } else if (entry.customType === SESSION_OVERRIDES_ENTRY) {
485
+ if (entry.type === "custom" && entry.customType === SESSION_OVERRIDES_ENTRY) {
586
486
  currentSessionOverrides = normalizeSessionOverrides(entry.data?.overrides);
587
487
  }
588
488
  }
589
489
  }
590
490
 
591
- export async function readModeState(cwd) {
592
- const layers = await readFreeflowConfigLayers(cwd);
593
- const effectiveCore = resolveSessionCoreConfig(layers);
594
- const repositoryDefaultMode = VALID_MODES.has(layers.repository.parsed.defaultMode)
595
- ? layers.repository.parsed.defaultMode
596
- : "workflow";
597
- const personalDefaultMode = VALID_MODES.has(layers.local.parsed.defaultMode) ? layers.local.parsed.defaultMode : null;
598
- const sessionMode = VALID_MODES.has(currentModeOverride) ? currentModeOverride : null;
599
- const resolvedMode = sessionMode ?? effectiveCore.config.defaultMode;
600
- const active = layers.configured && effectiveCore.config.enabled && effectiveCore.config.skills.enabled;
601
-
602
- return {
603
- repositoryDefaultMode,
604
- repositoryDefaultModeSource: VALID_MODES.has(layers.repository.parsed.defaultMode) ? "repository" : "builtin",
605
- personalDefaultMode,
606
- defaultMode: effectiveCore.config.defaultMode,
607
- defaultModeSource: effectiveCore.sources.defaultMode,
608
- currentMode: sessionMode,
609
- sessionMode,
610
- resolvedMode,
611
- active,
612
- effectiveMode: active ? resolvedMode : null,
613
- };
614
- }
615
-
616
- export function setModeStatus(
491
+ export function setFreeflowStatus(
617
492
  ctx,
618
- modeState,
619
493
  capabilityState = undefined,
620
494
  cognitiveRoutingRuntime = undefined,
621
- options: { cognitiveRoutingStartupPending?: boolean } = {},
495
+ freeflowContext = undefined,
496
+ options: { cognitiveRoutingStartupPending?: boolean; startupSelectionSuppressed?: boolean } = {},
622
497
  ) {
623
498
  if (capabilityState && !capabilityState.configured) {
624
499
  ctx.ui.setStatus("freeflow", capabilityState.configExists ? "freeflow: config error" : "freeflow: setup needed");
@@ -629,23 +504,26 @@ export function setModeStatus(
629
504
  ctx.ui.setStatus("freeflow", `freeflow: off${source}`);
630
505
  return;
631
506
  }
507
+ if (capabilityState?.enabled === true && !hasUsableMandatoryPrompts(freeflowContext)) {
508
+ ctx.ui.setStatus("freeflow", "freeflow: unavailable");
509
+ return;
510
+ }
632
511
 
633
512
  const active: string[] = [];
634
- if (capabilityState?.interactionContract.effective) {
635
- active.push("interaction");
636
- } else if (capabilityState?.configSources?.interactionContract === "session") {
637
- active.push("interaction off (session)");
638
- }
639
- if (capabilityState?.skills.effective) {
640
- active.push(`${modeState.effectiveMode}${modeState.currentMode ? " (session)" : ""}`);
641
- } else if (capabilityState?.configSources?.skillsEnabled === "session") {
642
- active.push("skills off (session)");
643
- }
644
513
  const cognitiveRouting = capabilityState?.cognitiveRouting;
514
+ const cognitiveRoutingInactive =
515
+ cognitiveRouting?.enabled === true && cognitiveRoutingRuntime?.runtimeStatus === "inactive";
516
+ const cognitiveRoutingBlocked =
517
+ cognitiveRouting?.enabled === true &&
518
+ (cognitiveRouting?.blockingReason?.code === "runtime_blocked" ||
519
+ cognitiveRoutingRuntime?.runtimeStatus === "blocked");
645
520
  const cognitiveRoutingActive = cognitiveRouting?.effective === true && cognitiveRoutingRuntime?.effective === true;
646
- const startupSelectionSuppressesCognitiveRouting =
647
- ctx?.modelStateProvenance?.explicitModel === true || ctx?.modelStateProvenance?.explicitThinking === true;
648
- if (cognitiveRoutingActive) {
521
+ const startupSelectionSuppressesCognitiveRouting = options.startupSelectionSuppressed === true;
522
+ if (cognitiveRoutingInactive) {
523
+ active.push("cognitive inactive");
524
+ } else if (cognitiveRoutingBlocked) {
525
+ active.push(`cognitive blocked · ${cognitiveRoutingRuntime?.runtimeReason ?? "runtime_blocked"}`);
526
+ } else if (cognitiveRoutingActive) {
649
527
  const profile = cognitiveRoutingRuntime.activeProfile;
650
528
  const control =
651
529
  cognitiveRoutingRuntime.controlMode === "manual-standard" ||
@@ -655,18 +533,21 @@ export function setModeStatus(
655
533
  active.push(`${profile} · ${control}`);
656
534
  } else if (cognitiveRouting?.enabled === true) {
657
535
  if (cognitiveRouting.blockingReason?.code === "runtime_disabled") {
658
- active.push("cognitive disabled · PiFlow only");
536
+ active.push("cognitive blocked · runtime_disabled");
659
537
  } else if (
660
538
  cognitiveRouting.effective === true &&
661
539
  cognitiveRoutingRuntime === undefined &&
662
540
  options.cognitiveRoutingStartupPending === true &&
663
541
  !startupSelectionSuppressesCognitiveRouting
664
542
  ) {
665
- const startupProfile = cognitiveRouting.sessionStart?.profile ?? "standard";
543
+ const startupProfile =
544
+ cognitiveRouting.sessionStart?.control === "manual"
545
+ ? (cognitiveRouting.sessionStart.profile ?? "reasoning")
546
+ : "reasoning";
666
547
  active.push(`${startupProfile} · pending`);
667
548
  } else {
668
549
  const reason =
669
- cognitiveRouting?.effective === true
550
+ cognitiveRouting.effective === true
670
551
  ? (cognitiveRoutingRuntime?.blockingReason?.code ?? "runtime_inactive")
671
552
  : (cognitiveRouting.blockingReason?.code ?? "unavailable");
672
553
  active.push(`cognitive blocked · ${reason}`);
@@ -675,21 +556,7 @@ export function setModeStatus(
675
556
  if (capabilityState?.contextVirtualization?.effective || capabilityState?.conversationHistory?.effective) {
676
557
  active.push("context");
677
558
  }
678
- ctx.ui.setStatus("freeflow", `freeflow: ${active.length > 0 ? active.join(" · ") : "idle"}`);
679
- }
680
-
681
- function modeSourceLabel(source) {
682
- if (source === "local") return "personal override";
683
- if (source === "repository") return "repository default";
684
- return "built-in default";
685
- }
686
-
687
- function describeModeState(modeState) {
688
- const configuredDefault = `configured default ${modeState.defaultMode} (${modeSourceLabel(modeState.defaultModeSource)})`;
689
- if (modeState.sessionMode) {
690
- return `session ${modeState.sessionMode}; ${configuredDefault}`;
691
- }
692
- return configuredDefault;
559
+ ctx.ui.setStatus("freeflow", `freeflow: ${active.length > 0 ? active.join(" · ") : "active"}`);
693
560
  }
694
561
 
695
562
  export function skillPrompt(skill, args) {
@@ -699,6 +566,8 @@ export function skillPrompt(skill, args) {
699
566
 
700
567
  type CognitiveRoutingRuntimeSnapshot = {
701
568
  effective?: boolean;
569
+ runtimeStatus?: "active" | "inactive" | "blocked";
570
+ runtimeReason?: unknown;
702
571
  activeProfile?: unknown;
703
572
  controlMode?: unknown;
704
573
  };
@@ -727,10 +596,15 @@ function publicCapabilityStatus(capability) {
727
596
  return "inactive";
728
597
  }
729
598
 
599
+ function publicCognitiveRoutingStatus(capability, runtime) {
600
+ if (runtime?.runtimeStatus === "inactive") return "inactive";
601
+ return publicCapabilityStatus(capability);
602
+ }
603
+
730
604
  export function freeflowRuntimeStateMessage(
731
- modeState,
732
605
  capabilityState,
733
606
  cognitiveRoutingRuntime: CognitiveRoutingRuntimeSnapshot | undefined = undefined,
607
+ freeflowContext = undefined,
734
608
  ) {
735
609
  const cognitiveRoutingEffective = capabilityState?.cognitiveRouting?.effective === true;
736
610
  const profile = publicCognitiveRoutingProfile(
@@ -739,8 +613,16 @@ export function freeflowRuntimeStateMessage(
739
613
  );
740
614
  const control =
741
615
  profile === "unavailable" ? "unavailable" : publicCognitiveRoutingControl(cognitiveRoutingRuntime?.controlMode);
742
- const defaultMode = modeState?.defaultMode ?? "workflow";
743
- const activeMode = modeState?.effectiveMode ?? "inactive";
616
+ const mandatoryPromptAvailable = capabilityState?.enabled !== true || hasUsableMandatoryPrompts(freeflowContext);
617
+ const freeflowStatus = capabilityState?.configured
618
+ ? capabilityState.enabled
619
+ ? mandatoryPromptAvailable
620
+ ? "active"
621
+ : "unavailable"
622
+ : "inactive"
623
+ : capabilityState?.configExists
624
+ ? "config error"
625
+ : "setup needed";
744
626
 
745
627
  return {
746
628
  role: "custom",
@@ -750,15 +632,12 @@ export function freeflowRuntimeStateMessage(
750
632
  "",
751
633
  "This is extension-generated runtime state. Use it to interpret the stable Freeflow guidance.",
752
634
  "",
753
- `Default mode: \`${defaultMode}\``,
754
- `Active mode: \`${activeMode}\``,
635
+ `Freeflow: ${freeflowStatus}`,
755
636
  "",
756
637
  "Capabilities:",
757
- `- Interaction Contract: ${publicCapabilityStatus(capabilityState?.interactionContract)}`,
758
- `- Skills: ${publicCapabilityStatus(capabilityState?.skills)}`,
759
638
  `- Context Virtualization: ${publicCapabilityStatus(capabilityState?.contextVirtualization)}`,
760
639
  `- Conversation History: ${publicCapabilityStatus(capabilityState?.conversationHistory)}`,
761
- `- Cognitive Routing: ${publicCapabilityStatus(capabilityState?.cognitiveRouting)}`,
640
+ `- Cognitive Routing: ${publicCognitiveRoutingStatus(capabilityState?.cognitiveRouting, cognitiveRoutingRuntime)}`,
762
641
  "",
763
642
  "Cognitive Routing:",
764
643
  `- Control: \`${control}\``,
@@ -777,38 +656,75 @@ export function withoutFreeflowRuntimeState(messages: readonly FreeflowContextMe
777
656
  );
778
657
  }
779
658
 
659
+ function lastUserMessageIndex(messages: readonly FreeflowContextMessage[]): number {
660
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
661
+ if (messages[index]?.role === "user") return index;
662
+ }
663
+ return -1;
664
+ }
665
+
666
+ function insertRuntimeStateBeforeLatestUser(
667
+ messages: readonly FreeflowContextMessage[],
668
+ runtimeState: FreeflowContextMessage,
669
+ ): FreeflowContextMessage[] {
670
+ const latestUserIndex = lastUserMessageIndex(messages);
671
+ if (latestUserIndex < 0) return [...messages, runtimeState];
672
+ return [...messages.slice(0, latestUserIndex), runtimeState, ...messages.slice(latestUserIndex)];
673
+ }
674
+
780
675
  export function withFreeflowRuntimeState(
781
676
  messages: readonly FreeflowContextMessage[] | undefined,
782
- modeState,
783
677
  capabilityState,
784
678
  cognitiveRoutingRuntime: CognitiveRoutingRuntimeSnapshot | undefined = undefined,
679
+ freeflowContext = undefined,
680
+ options: { force?: boolean } = {},
785
681
  ) {
786
- return [
787
- ...withoutFreeflowRuntimeState(messages),
788
- freeflowRuntimeStateMessage(modeState, capabilityState, cognitiveRoutingRuntime),
789
- ];
682
+ const source = Array.isArray(messages) ? messages : [];
683
+ const runtimeState = freeflowRuntimeStateMessage(capabilityState, cognitiveRoutingRuntime, freeflowContext);
684
+ const runtimeStateMessages = source.filter(
685
+ (message) =>
686
+ message?.customType === FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE ||
687
+ message?.customType === COGNITIVE_ROUTING_RUNTIME_STATE_MESSAGE_TYPE,
688
+ );
689
+ const withoutRuntimeState = withoutFreeflowRuntimeState(source);
690
+ const expectedRuntimeStateIndex = lastUserMessageIndex(withoutRuntimeState);
691
+ const runtimeStateIndex = source.findIndex((message) => message?.customType === FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE);
692
+ const unchanged =
693
+ options.force !== true &&
694
+ runtimeStateMessages.length === 1 &&
695
+ runtimeStateMessages[0]?.customType === FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE &&
696
+ runtimeStateMessages[0]?.content === runtimeState.content &&
697
+ runtimeStateIndex === (expectedRuntimeStateIndex < 0 ? withoutRuntimeState.length : expectedRuntimeStateIndex);
698
+ if (unchanged) return source;
699
+ return insertRuntimeStateBeforeLatestUser(withoutRuntimeState, runtimeState);
790
700
  }
791
701
 
792
702
  export function runtimeContext(freeflowContext, capabilityState) {
793
- if (capabilityState?.configured !== true || capabilityState?.enabled !== true || !freeflowContext?.corePrompt) {
703
+ if (
704
+ capabilityState?.configured !== true ||
705
+ capabilityState?.enabled !== true ||
706
+ !hasUsableMandatoryPrompts(freeflowContext)
707
+ ) {
794
708
  return "";
795
709
  }
796
710
 
797
- const blocks = [];
798
- if (freeflowContext?.corePrompt) blocks.push(freeflowContext.corePrompt.trim());
799
- if (capabilityState.interactionContract?.effective === true && freeflowContext?.interactionContractPrompt) {
800
- blocks.push(freeflowContext.interactionContractPrompt.trim());
801
- }
802
- if (capabilityState.skills?.effective === true && freeflowContext?.skillsPrompt) {
803
- blocks.push(freeflowContext.skillsPrompt.trim());
804
- }
805
- if (capabilityState.cognitiveRouting?.effective === true && freeflowContext?.cognitiveRoutingPrompt) {
711
+ const blocks = [freeflowContext.corePrompt.trim(), freeflowContext.interactionContractPrompt.trim()];
712
+ if (
713
+ capabilityState.cognitiveRouting?.effective === true &&
714
+ isPromptAvailable(freeflowContext.cognitiveRoutingPrompt)
715
+ ) {
806
716
  blocks.push(freeflowContext.cognitiveRoutingPrompt.trim());
807
717
  }
808
- if (capabilityState.contextVirtualization?.effective === true && freeflowContext?.contextVirtualizationPrompt) {
718
+ if (
719
+ capabilityState.contextVirtualization?.effective === true &&
720
+ isPromptAvailable(freeflowContext.contextVirtualizationPrompt)
721
+ ) {
809
722
  blocks.push(freeflowContext.contextVirtualizationPrompt.trim());
810
723
  }
811
- if (capabilityState.conversationHistory?.effective === true && freeflowContext?.conversationHistoryPrompt) {
724
+ if (
725
+ capabilityState.conversationHistory?.effective === true &&
726
+ isPromptAvailable(freeflowContext.conversationHistoryPrompt)
727
+ ) {
812
728
  blocks.push(freeflowContext.conversationHistoryPrompt.trim());
813
729
  }
814
730
  return blocks.filter(Boolean).join("\n\n");
@@ -837,7 +753,7 @@ export async function setSessionCoreOverride(key: SessionCoreKey, value: boolean
837
753
  pi?.appendEntry?.(SESSION_OVERRIDES_ENTRY, { overrides: { ...currentSessionOverrides } });
838
754
  return {
839
755
  changed: true,
840
- reloadRequired: key === "enabled" || key === "skillsEnabled",
756
+ reloadRequired: key === "enabled",
841
757
  sessionOverrides: { ...currentSessionOverrides },
842
758
  capabilityState: await readCapabilityState(ctx.cwd, ctx, pi?.host),
843
759
  };
@@ -845,109 +761,17 @@ export async function setSessionCoreOverride(key: SessionCoreKey, value: boolean
845
761
 
846
762
  export async function resetSessionOverrides(ctx, pi) {
847
763
  const hadCoreOverrides = Object.keys(currentSessionOverrides).length > 0;
848
- const reloadRequired =
849
- Object.hasOwn(currentSessionOverrides, "enabled") || Object.hasOwn(currentSessionOverrides, "skillsEnabled");
850
- const hadModeOverride = VALID_MODES.has(currentModeOverride);
764
+ const reloadRequired = Object.hasOwn(currentSessionOverrides, "enabled");
851
765
 
852
766
  if (hadCoreOverrides) {
853
767
  currentSessionOverrides = {};
854
768
  pi?.appendEntry?.(SESSION_OVERRIDES_ENTRY, { overrides: {} });
855
769
  }
856
- if (hadModeOverride) {
857
- await setSessionMode(null, ctx, pi);
858
- }
859
770
 
860
771
  return {
861
- changed: hadCoreOverrides || hadModeOverride,
772
+ changed: hadCoreOverrides,
862
773
  reloadRequired,
863
774
  sessionOverrides: { ...currentSessionOverrides },
864
- modeState: await readModeState(ctx.cwd),
775
+ capabilityState: await readCapabilityState(ctx.cwd, ctx, pi?.host),
865
776
  };
866
777
  }
867
-
868
- export async function setSessionMode(mode, ctx, pi) {
869
- const nextMode = mode === "default" || mode === "reset" || mode === null ? null : mode;
870
- if (nextMode !== null && !VALID_MODES.has(nextMode)) {
871
- throw new Error(`Invalid Freeflow mode: ${String(mode)}`);
872
- }
873
- currentModeOverride = nextMode;
874
- pi?.appendEntry?.(MODE_STATE_ENTRY, { currentMode: nextMode });
875
- return readModeState(ctx.cwd);
876
- }
877
-
878
- export async function handleModeCommand(args, ctx, pi) {
879
- const arg = args?.trim();
880
- const capabilityState = await readCapabilityState(ctx.cwd, ctx, pi?.host);
881
- const inactiveModeState = await readModeState(ctx.cwd);
882
-
883
- if (!capabilityState.configured) {
884
- setModeStatus(ctx, inactiveModeState, capabilityState);
885
- ctx.ui.notify(
886
- "Freeflow is installed but this repo is not set up. Run /setup-freeflow before changing mode.",
887
- "warning",
888
- );
889
- return { changed: false, error: "not_configured" };
890
- }
891
- if (!capabilityState.enabled) {
892
- setModeStatus(ctx, inactiveModeState, capabilityState);
893
- ctx.ui.notify(
894
- "Freeflow is disabled for this repo. Use /freeflow enable or /freeflow settings to re-enable it.",
895
- "warning",
896
- );
897
- return { changed: false, error: "freeflow_disabled" };
898
- }
899
- if (!capabilityState.skills.effective) {
900
- setModeStatus(ctx, inactiveModeState, capabilityState);
901
- ctx.ui.notify(
902
- `Freeflow modes are inactive because Skills are disabled. Configured default mode: ${inactiveModeState.defaultMode} (${modeSourceLabel(inactiveModeState.defaultModeSource)}). Enable Skills in /freeflow settings before changing mode.`,
903
- "warning",
904
- );
905
- return { changed: false, error: "skills_disabled" };
906
- }
907
-
908
- if (VALID_MODES.has(arg)) {
909
- if (inactiveModeState.effectiveMode === arg) {
910
- setModeStatus(ctx, inactiveModeState, capabilityState);
911
- const message = inactiveModeState.sessionMode
912
- ? `Freeflow is already in ${arg} mode for this Pi session. No session entry was added. Configured default remains ${inactiveModeState.defaultMode} (${modeSourceLabel(inactiveModeState.defaultModeSource)}).`
913
- : `Freeflow is already in ${arg} mode from the configured default (${modeSourceLabel(inactiveModeState.defaultModeSource)}). No session override was created.`;
914
- ctx.ui.notify(message, "info");
915
- return { changed: false, modeState: inactiveModeState };
916
- }
917
-
918
- const modeState = await setSessionMode(arg, ctx, pi);
919
- setModeStatus(ctx, modeState, capabilityState);
920
- ctx.ui.notify(
921
- `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)}).`,
922
- "info",
923
- );
924
- return { changed: true, modeState };
925
- }
926
-
927
- if (RESET_MODE_ARGS.has(arg) || arg === "default") {
928
- if (!inactiveModeState.sessionMode) {
929
- setModeStatus(ctx, inactiveModeState, capabilityState);
930
- ctx.ui.notify(
931
- `Freeflow is already using the configured default: ${inactiveModeState.defaultMode} (${modeSourceLabel(inactiveModeState.defaultModeSource)}). No session override is active.`,
932
- "info",
933
- );
934
- return { changed: false, modeState: inactiveModeState };
935
- }
936
-
937
- const modeState = await setSessionMode(null, ctx, pi);
938
- setModeStatus(ctx, modeState, capabilityState);
939
- ctx.ui.notify(
940
- `Freeflow mode reset to configured default: ${modeState.defaultMode} (${modeSourceLabel(modeState.defaultModeSource)}). Session override cleared; .freeflow/local.json and .freeflow/config.json were not changed.`,
941
- "info",
942
- );
943
- return { changed: true, modeState };
944
- }
945
-
946
- const modeState = await readModeState(ctx.cwd);
947
- setModeStatus(ctx, modeState, capabilityState);
948
- ctx.ui.notify(
949
- `Freeflow mode is ${modeState.effectiveMode} (${describeModeState(modeState)}). Use /freeflow mode conversation, /freeflow mode workflow, /freeflow mode strict-workflow, or /freeflow mode reset.`,
950
- "info",
951
- );
952
- return { changed: false, modeState, error: arg ? "invalid_mode" : undefined };
953
- }