@hassangameryt/freeflow 0.5.0 → 0.6.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 (284) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/README.md +148 -154
  5. package/capabilities/cognitive-routing/SKILL.md +229 -0
  6. package/capabilities/context-virtualization/SKILL.md +103 -0
  7. package/capabilities/conversation-history/SKILL.md +53 -0
  8. package/command-surface.json +6 -13
  9. package/hooks/freeflow-runtime-context.mjs +35 -91
  10. package/hooks/tests/check-runtime-context-hook.sh +56 -39
  11. package/package.json +24 -16
  12. package/pi-extension/dist/cognitive-routing/commands.js +96 -0
  13. package/pi-extension/dist/cognitive-routing/config.js +236 -0
  14. package/pi-extension/dist/cognitive-routing/controller.js +862 -0
  15. package/pi-extension/dist/cognitive-routing/history.js +439 -0
  16. package/pi-extension/dist/cognitive-routing/host.js +126 -0
  17. package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
  18. package/pi-extension/dist/cognitive-routing/tool.js +211 -0
  19. package/pi-extension/dist/cognitive-routing/types.js +6 -0
  20. package/pi-extension/dist/context-virtualization/commands.js +89 -0
  21. package/pi-extension/dist/context-virtualization/projector.js +42 -0
  22. package/pi-extension/dist/context-virtualization/runtime.js +356 -0
  23. package/pi-extension/dist/context-virtualization/state.js +57 -0
  24. package/pi-extension/dist/context-virtualization/types.js +10 -0
  25. package/pi-extension/dist/conversation-history/passages.js +66 -0
  26. package/pi-extension/dist/conversation-history/ranking.js +105 -0
  27. package/pi-extension/dist/conversation-history/runtime.js +226 -0
  28. package/pi-extension/dist/conversation-history/search.js +56 -0
  29. package/pi-extension/dist/conversation-history/types.js +1 -0
  30. package/pi-extension/dist/freeflow-context/resolver.js +120 -0
  31. package/pi-extension/dist/freeflow-context/runtime.js +80 -0
  32. package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
  33. package/pi-extension/dist/freeflow-context/tool.js +308 -0
  34. package/pi-extension/dist/freeflow-context/types.js +12 -0
  35. package/pi-extension/dist/freeflow-context/visibility.js +28 -0
  36. package/pi-extension/dist/index.js +516 -98
  37. package/pi-extension/dist/runtime/runtime-context.js +318 -271
  38. package/pi-extension/dist/runtime/runtime-identity.js +5 -0
  39. package/pi-extension/dist/settings/settings-tui.js +78 -0
  40. package/pi-extension/dist/settings/settings-ui.js +593 -563
  41. package/pi-extension/dist/ui/text-component.js +57 -0
  42. package/pi-extension/src/cognitive-routing/commands.ts +119 -0
  43. package/pi-extension/src/cognitive-routing/config.ts +307 -0
  44. package/pi-extension/src/cognitive-routing/controller.ts +1118 -0
  45. package/pi-extension/src/cognitive-routing/history.ts +638 -0
  46. package/pi-extension/src/cognitive-routing/host.ts +172 -0
  47. package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
  48. package/pi-extension/src/cognitive-routing/tool.ts +272 -0
  49. package/pi-extension/src/cognitive-routing/types.ts +89 -0
  50. package/pi-extension/src/context-virtualization/commands.ts +111 -0
  51. package/pi-extension/src/context-virtualization/projector.ts +62 -0
  52. package/pi-extension/src/context-virtualization/runtime.ts +406 -0
  53. package/pi-extension/src/context-virtualization/state.ts +79 -0
  54. package/pi-extension/src/context-virtualization/types.ts +92 -0
  55. package/pi-extension/src/conversation-history/passages.ts +81 -0
  56. package/pi-extension/src/conversation-history/ranking.ts +155 -0
  57. package/pi-extension/src/conversation-history/runtime.ts +250 -0
  58. package/pi-extension/src/conversation-history/search.ts +79 -0
  59. package/pi-extension/src/conversation-history/types.ts +31 -0
  60. package/pi-extension/src/freeflow-context/resolver.ts +139 -0
  61. package/pi-extension/src/freeflow-context/runtime.ts +100 -0
  62. package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
  63. package/pi-extension/src/freeflow-context/tool.ts +360 -0
  64. package/pi-extension/src/freeflow-context/types.ts +70 -0
  65. package/pi-extension/src/freeflow-context/visibility.ts +53 -0
  66. package/pi-extension/src/index.ts +553 -101
  67. package/pi-extension/src/runtime/runtime-context.ts +339 -281
  68. package/pi-extension/src/runtime/runtime-identity.ts +8 -0
  69. package/pi-extension/src/settings/settings-tui.ts +93 -0
  70. package/pi-extension/src/settings/settings-ui.ts +700 -613
  71. package/pi-extension/src/ui/text-component.ts +66 -0
  72. package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
  73. package/pi-extension/tests/cognitive-routing/controller.test.js +1099 -0
  74. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1071 -0
  75. package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
  76. package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
  77. package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
  78. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +236 -0
  79. package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
  80. package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
  81. package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
  82. package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
  83. package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
  84. package/pi-extension/tests/conversation-history/search.test.js +97 -0
  85. package/pi-extension/tests/integration/pi-extension.test.js +927 -2048
  86. package/pi-extension/tests/integration/prompt-architecture.test.js +305 -0
  87. package/runtime/prompts/cognitive-routing.md +9 -0
  88. package/runtime/prompts/context-virtualization.md +3 -0
  89. package/runtime/prompts/conversation-history.md +3 -0
  90. package/runtime/prompts/core.md +13 -0
  91. package/runtime/prompts/interaction-contract.md +7 -0
  92. package/runtime/prompts/skills.md +65 -0
  93. package/skills/action-selection/SKILL.md +91 -0
  94. package/skills/bypass/SKILL.md +3 -2
  95. package/skills/commit-work/SKILL.md +1 -1
  96. package/skills/commit-work/references/staging-decisions.md +1 -1
  97. package/skills/decision-gate/SKILL.md +1 -1
  98. package/skills/design-for-depth/SKILL.md +14 -33
  99. package/skills/design-for-depth/references/design-pressure-signals.md +1 -1
  100. package/skills/design-for-depth/references/interface-design-loop.md +1 -1
  101. package/skills/design-for-depth/references/software-design-philosophy.md +1 -1
  102. package/skills/diagnose-failure/SKILL.md +20 -14
  103. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +1 -3
  104. package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +1 -1
  105. package/skills/discuss/SKILL.md +34 -22
  106. package/skills/discuss/references/discussion-continuity.md +71 -0
  107. package/skills/evaluate-skill/SKILL.md +5 -5
  108. package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +42 -2
  109. package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
  110. package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
  111. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
  112. package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
  113. package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
  114. package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
  115. package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
  116. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
  117. package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
  118. package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
  119. package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
  120. package/skills/execute-work/SKILL.md +18 -15
  121. package/skills/execute-work/references/code-practices.md +1 -1
  122. package/skills/execute-work/references/execute-work-edges.md +42 -0
  123. package/skills/finish-branch/SKILL.md +2 -2
  124. package/skills/finish-branch/references/integration-options.md +1 -1
  125. package/skills/handoff/SKILL.md +3 -3
  126. package/skills/launch-work/SKILL.md +1 -1
  127. package/skills/launch-work/references/launch-readiness.md +2 -2
  128. package/skills/migration-work/SKILL.md +2 -2
  129. package/skills/migration-work/references/migration-lifecycle.md +1 -1
  130. package/skills/mode-contract/SKILL.md +4 -4
  131. package/skills/release-work/SKILL.md +1 -1
  132. package/skills/release-work/references/release-evidence.md +1 -1
  133. package/skills/review-artifact/SKILL.md +28 -26
  134. package/skills/review-work/SKILL.md +28 -26
  135. package/skills/review-work/references/{reviewer-prompt.md → independent-work-reviewer-contract.md} +1 -1
  136. package/skills/review-work/references/security-risk-lens.md +1 -1
  137. package/skills/setup-freeflow/SKILL.md +3 -1
  138. package/skills/setup-freeflow/references/activation-contract.md +2 -2
  139. package/skills/setup-freeflow/references/host-setup.md +2 -2
  140. package/skills/simplify-code/SKILL.md +1 -1
  141. package/skills/simplify-code/references/simplification-patterns.md +1 -1
  142. package/skills/tdd/SKILL.md +18 -38
  143. package/skills/tdd/references/test-design.md +2 -2
  144. package/skills/track-work/SKILL.md +362 -58
  145. package/skills/track-work/scripts/lib/codec.mjs +570 -0
  146. package/skills/track-work/scripts/lib/commands.mjs +568 -0
  147. package/skills/track-work/scripts/lib/contract.mjs +303 -0
  148. package/skills/track-work/scripts/lib/model.mjs +506 -0
  149. package/skills/track-work/scripts/lib/operations.mjs +965 -0
  150. package/skills/track-work/scripts/lib/record-store.mjs +264 -0
  151. package/skills/track-work/scripts/lib/result.mjs +37 -0
  152. package/skills/track-work/scripts/lib/rewrites.mjs +740 -0
  153. package/skills/track-work/scripts/lib/schema.mjs +277 -0
  154. package/skills/track-work/scripts/lib/source-inventory.mjs +162 -0
  155. package/skills/track-work/scripts/lib/views.mjs +443 -0
  156. package/skills/track-work/scripts/lib/workspace.mjs +134 -0
  157. package/skills/track-work/scripts/working-record.mjs +122 -0
  158. package/skills/verify-work/SKILL.md +14 -2
  159. package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
  160. package/skills/verify-work/references/integration-evidence.md +1 -1
  161. package/skills/verify-work/references/performance-evidence.md +1 -1
  162. package/skills/workflow/SKILL.md +105 -53
  163. package/skills/workflow/references/domain-skill-composition.md +22 -42
  164. package/skills/write-plan/SKILL.md +1 -1
  165. package/skills/write-skill/SKILL.md +3 -2
  166. package/skills/write-skill/references/progressive-disclosure.md +17 -3
  167. package/capabilities/output-router/SKILL.md +0 -206
  168. package/capabilities/output-router/references/safety-policy.md +0 -40
  169. package/capabilities/output-router/references/setup.md +0 -110
  170. package/pi-extension/dist/output-router/host-producer-identification.js +0 -232
  171. package/pi-extension/dist/output-router/native-safety-net.js +0 -216
  172. package/pi-extension/dist/output-router/observed-tool-routing.js +0 -122
  173. package/pi-extension/dist/output-router/renderers.js +0 -1023
  174. package/pi-extension/dist/output-router/router-tools.js +0 -554
  175. package/pi-extension/dist/output-router/schemas.js +0 -383
  176. package/pi-extension/dist/output-router/status.js +0 -744
  177. package/pi-extension/dist/output-router/utils.js +0 -502
  178. package/pi-extension/src/output-router/host-producer-identification.ts +0 -256
  179. package/pi-extension/src/output-router/native-safety-net.ts +0 -248
  180. package/pi-extension/src/output-router/observed-tool-routing.ts +0 -134
  181. package/pi-extension/src/output-router/renderers.ts +0 -1108
  182. package/pi-extension/src/output-router/router-tools.ts +0 -579
  183. package/pi-extension/src/output-router/schemas.ts +0 -387
  184. package/pi-extension/src/output-router/status.ts +0 -791
  185. package/pi-extension/src/output-router/utils.ts +0 -554
  186. package/pi-extension/tests/output-router/observed-producers.test.js +0 -132
  187. package/pi-extension/tests/output-router/search.test.js +0 -722
  188. package/router/dist/benchmarks/benchmark-harness.d.ts +0 -35
  189. package/router/dist/benchmarks/benchmark-harness.js +0 -92
  190. package/router/dist/benchmarks/benchmarks.d.ts +0 -109
  191. package/router/dist/benchmarks/benchmarks.js +0 -979
  192. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +0 -95
  193. package/router/dist/benchmarks/codex-qa-benchmarks.js +0 -500
  194. package/router/dist/benchmarks/command-benchmarks.d.ts +0 -143
  195. package/router/dist/benchmarks/command-benchmarks.js +0 -698
  196. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +0 -82
  197. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +0 -773
  198. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +0 -132
  199. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +0 -1380
  200. package/router/dist/benchmarks/context-saving-baseline.d.ts +0 -60
  201. package/router/dist/benchmarks/context-saving-baseline.js +0 -470
  202. package/router/dist/benchmarks/index-benchmarks.d.ts +0 -103
  203. package/router/dist/benchmarks/index-benchmarks.js +0 -862
  204. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +0 -101
  205. package/router/dist/benchmarks/storage-policy-benchmarks.js +0 -618
  206. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +0 -56
  207. package/router/dist/benchmarks/vault-index-benchmarks.js +0 -322
  208. package/router/dist/config/config.d.ts +0 -103
  209. package/router/dist/config/config.js +0 -632
  210. package/router/dist/config/router-contract.d.ts +0 -10
  211. package/router/dist/config/router-contract.js +0 -54
  212. package/router/dist/config/schema.d.ts +0 -27
  213. package/router/dist/config/schema.js +0 -648
  214. package/router/dist/config/types.d.ts +0 -573
  215. package/router/dist/config/types.js +0 -41
  216. package/router/dist/evidence/bounded-evidence.d.ts +0 -40
  217. package/router/dist/evidence/bounded-evidence.js +0 -323
  218. package/router/dist/evidence/evidence-range-selector.d.ts +0 -24
  219. package/router/dist/evidence/evidence-range-selector.js +0 -232
  220. package/router/dist/evidence/evidence-search.d.ts +0 -27
  221. package/router/dist/evidence/evidence-search.js +0 -490
  222. package/router/dist/evidence/evidence.d.ts +0 -38
  223. package/router/dist/evidence/evidence.js +0 -200
  224. package/router/dist/evidence/failure-contracts.d.ts +0 -37
  225. package/router/dist/evidence/failure-contracts.js +0 -166
  226. package/router/dist/evidence/line-ranges.d.ts +0 -24
  227. package/router/dist/evidence/line-ranges.js +0 -22
  228. package/router/dist/experiments/local-index.d.ts +0 -64
  229. package/router/dist/experiments/local-index.js +0 -581
  230. package/router/dist/index.d.ts +0 -30
  231. package/router/dist/index.js +0 -32
  232. package/router/dist/local/local-traversal.d.ts +0 -18
  233. package/router/dist/local/local-traversal.js +0 -334
  234. package/router/dist/processing/engine.d.ts +0 -132
  235. package/router/dist/processing/engine.js +0 -533
  236. package/router/dist/processing/reducers.d.ts +0 -280
  237. package/router/dist/processing/reducers.js +0 -1673
  238. package/router/dist/processing/renderers.d.ts +0 -31
  239. package/router/dist/processing/renderers.js +0 -195
  240. package/router/dist/processing/scripts.d.ts +0 -91
  241. package/router/dist/processing/scripts.js +0 -386
  242. package/router/dist/repo/repo-traversal.d.ts +0 -18
  243. package/router/dist/repo/repo-traversal.js +0 -245
  244. package/router/dist/routing/observed-reducers.d.ts +0 -41
  245. package/router/dist/routing/observed-reducers.js +0 -661
  246. package/router/dist/routing/observed-routing.d.ts +0 -26
  247. package/router/dist/routing/observed-routing.js +0 -378
  248. package/router/dist/routing/parsers.d.ts +0 -16
  249. package/router/dist/routing/parsers.js +0 -521
  250. package/router/dist/routing/run-filters.d.ts +0 -60
  251. package/router/dist/routing/run-filters.js +0 -238
  252. package/router/dist/routing/run-reducers.d.ts +0 -49
  253. package/router/dist/routing/run-reducers.js +0 -122
  254. package/router/dist/sandbox/adapter-roots.d.ts +0 -46
  255. package/router/dist/sandbox/adapter-roots.js +0 -66
  256. package/router/dist/sandbox/eryx-python-adapter.d.ts +0 -13
  257. package/router/dist/sandbox/eryx-python-adapter.js +0 -868
  258. package/router/dist/sandbox/jq-wasm-adapter.d.ts +0 -9
  259. package/router/dist/sandbox/jq-wasm-adapter.js +0 -429
  260. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +0 -13
  261. package/router/dist/sandbox/quickjs-wasi-adapter.js +0 -384
  262. package/router/dist/sandbox/script-sandbox.d.ts +0 -134
  263. package/router/dist/sandbox/script-sandbox.js +0 -337
  264. package/router/dist/setup/script-transform-adapters.d.ts +0 -32
  265. package/router/dist/setup/script-transform-adapters.js +0 -278
  266. package/router/dist/tools/batch.d.ts +0 -33
  267. package/router/dist/tools/batch.js +0 -586
  268. package/router/dist/tools/run.d.ts +0 -62
  269. package/router/dist/tools/run.js +0 -1304
  270. package/router/dist/tools/search.d.ts +0 -66
  271. package/router/dist/tools/search.js +0 -1364
  272. package/router/dist/transform/engine.d.ts +0 -168
  273. package/router/dist/transform/engine.js +0 -2072
  274. package/router/dist/vault/vault-index.d.ts +0 -125
  275. package/router/dist/vault/vault-index.js +0 -416
  276. package/router/dist/vault/vault.d.ts +0 -138
  277. package/router/dist/vault/vault.js +0 -630
  278. package/runtime/interaction-contract.md +0 -16
  279. package/skills/discuss/references/checkpoints.md +0 -66
  280. package/skills/execute-work/references/execution-loop.md +0 -77
  281. package/skills/track-work/references/working-record-schema.md +0 -378
  282. package/skills/workflow/references/workflow-loop.md +0 -92
  283. /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
  284. /package/skills/review-artifact/references/{reviewer-prompt.md → independent-artifact-reviewer-contract.md} +0 -0
@@ -0,0 +1,274 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { projectCognitiveRoutingHistory } from "../../dist/cognitive-routing/history.js";
4
+
5
+ const standard = { provider: "faux", modelId: "standard", thinkingLevel: "high" };
6
+ const reasoning = { provider: "faux", modelId: "reasoning", thinkingLevel: "max" };
7
+
8
+ function intent(overrides = {}) {
9
+ return {
10
+ version: 2,
11
+ phase: "prepared",
12
+ kind: "profile",
13
+ control: "automatic",
14
+ source: "agent",
15
+ mechanism: "agent-tool",
16
+ decisionCorrelationId: "correlation-1",
17
+ fromPair: standard,
18
+ fromProfile: "standard",
19
+ epoch: "epoch-1",
20
+ correlationId: "correlation-1",
21
+ profile: "reasoning",
22
+ target: reasoning,
23
+ returnTarget: { provider: "faux", modelId: "return", thinkingLevel: "medium" },
24
+ reason: "Need deeper analysis.",
25
+ ...overrides,
26
+ };
27
+ }
28
+
29
+ function hostEntry(overrides = {}) {
30
+ return {
31
+ type: "model_state_change",
32
+ id: "state-1",
33
+ parentId: "intent-1",
34
+ timestamp: "2026-08-23T11:00:00.000Z",
35
+ provider: reasoning.provider,
36
+ modelId: reasoning.modelId,
37
+ thinkingLevel: reasoning.thinkingLevel,
38
+ correlationId: "correlation-1",
39
+ origin: { source: "piflow", operation: "model-state-control" },
40
+ ...overrides,
41
+ };
42
+ }
43
+
44
+ function current(profile = "reasoning", control = "automatic") {
45
+ return { profile, control };
46
+ }
47
+
48
+ test("projects a completed switch from the host representative and captured baseline", () => {
49
+ const entries = [
50
+ { id: "root", type: "message", parentId: null, timestamp: "2026-08-23T10:59:00.000Z" },
51
+ {
52
+ id: "intent-1",
53
+ type: "custom",
54
+ parentId: "root",
55
+ timestamp: "2026-08-23T10:59:01.000Z",
56
+ customType: "freeflow-cognitive-routing-intent",
57
+ data: intent(),
58
+ },
59
+ hostEntry(),
60
+ ];
61
+
62
+ const result = projectCognitiveRoutingHistory(entries, {
63
+ branchEntries: entries,
64
+ current: current(),
65
+ });
66
+
67
+ assert.deepEqual(result.current, current());
68
+ assert.deepEqual(result.summary, {
69
+ latestSemanticEventId: "intent:intent-1",
70
+ latestCompletedEventId: "intent:intent-1",
71
+ unresolvedCount: 0,
72
+ anomalyCount: 0,
73
+ });
74
+ assert.deepEqual(result.events, [
75
+ {
76
+ id: "intent:intent-1",
77
+ timestamp: "2026-08-23T11:00:00.000Z",
78
+ jsonlPosition: 2,
79
+ entryId: "state-1",
80
+ parentId: "intent-1",
81
+ branchAnchor: "state-1",
82
+ classification: "semantic-switch",
83
+ decisionSource: "agent",
84
+ mechanism: "agent-tool",
85
+ outcome: "completed",
86
+ changed: true,
87
+ integrity: "valid",
88
+ from: "standard",
89
+ to: "reasoning",
90
+ control: "automatic",
91
+ reason: "Need deeper analysis.",
92
+ epoch: "epoch-1",
93
+ correlationId: "correlation-1",
94
+ decisionCorrelationId: "correlation-1",
95
+ hostOrigin: { source: "piflow", operation: "model-state-control" },
96
+ },
97
+ ]);
98
+ });
99
+
100
+ test("orders completed events by JSONL position and preserves tri-state change semantics", () => {
101
+ const entries = [
102
+ {
103
+ id: "intent-a",
104
+ type: "custom",
105
+ customType: "freeflow-cognitive-routing-intent",
106
+ data: intent({ correlationId: "correlation-a", decisionCorrelationId: "correlation-a" }),
107
+ },
108
+ hostEntry({ id: "state-a", correlationId: "correlation-a", timestamp: "2026-08-23T11:01:00.000Z" }),
109
+ {
110
+ id: "intent-b",
111
+ type: "custom",
112
+ customType: "freeflow-cognitive-routing-intent",
113
+ data: intent({
114
+ correlationId: "correlation-b",
115
+ decisionCorrelationId: "correlation-b",
116
+ fromPair: standard,
117
+ fromProfile: "standard",
118
+ profile: "standard",
119
+ target: standard,
120
+ }),
121
+ },
122
+ hostEntry({
123
+ id: "state-b",
124
+ correlationId: "correlation-b",
125
+ timestamp: "not-a-timestamp",
126
+ provider: standard.provider,
127
+ modelId: standard.modelId,
128
+ thinkingLevel: standard.thinkingLevel,
129
+ }),
130
+ {
131
+ id: "intent-c",
132
+ type: "custom",
133
+ customType: "freeflow-cognitive-routing-intent",
134
+ data: intent({
135
+ version: 1,
136
+ correlationId: "correlation-c",
137
+ decisionCorrelationId: undefined,
138
+ fromPair: undefined,
139
+ fromProfile: undefined,
140
+ }),
141
+ },
142
+ hostEntry({ id: "state-c", correlationId: "correlation-c", timestamp: undefined }),
143
+ ];
144
+
145
+ const result = projectCognitiveRoutingHistory(entries, { current: current() });
146
+
147
+ assert.deepEqual(
148
+ result.events.map(({ id, jsonlPosition, changed, timestamp }) => ({ id, jsonlPosition, changed, timestamp })),
149
+ [
150
+ { id: "intent:intent-c", jsonlPosition: 5, changed: "unknown", timestamp: undefined },
151
+ { id: "intent:intent-b", jsonlPosition: 3, changed: false, timestamp: undefined },
152
+ { id: "intent:intent-a", jsonlPosition: 1, changed: true, timestamp: "2026-08-23T11:01:00.000Z" },
153
+ ],
154
+ );
155
+ assert.equal(result.summary.latestCompletedEventId, "intent:intent-c");
156
+ });
157
+
158
+ test("keeps unresolved and anomalous evidence distinct from the anomalies view", () => {
159
+ const entries = [
160
+ {
161
+ id: "intent-unresolved",
162
+ type: "custom",
163
+ customType: "freeflow-cognitive-routing-intent",
164
+ data: intent({ correlationId: "correlation-unresolved", decisionCorrelationId: "correlation-unresolved" }),
165
+ },
166
+ {
167
+ id: "intent-mismatch",
168
+ type: "custom",
169
+ customType: "freeflow-cognitive-routing-intent",
170
+ data: intent({ correlationId: "correlation-mismatch", decisionCorrelationId: "correlation-mismatch" }),
171
+ },
172
+ hostEntry({
173
+ id: "state-mismatch",
174
+ correlationId: "correlation-mismatch",
175
+ provider: standard.provider,
176
+ modelId: standard.modelId,
177
+ thinkingLevel: standard.thinkingLevel,
178
+ }),
179
+ ];
180
+
181
+ const result = projectCognitiveRoutingHistory(entries, { current: current() });
182
+ assert.equal(result.summary.unresolvedCount, 2);
183
+ assert.equal(result.summary.anomalyCount, 1);
184
+ assert.deepEqual(
185
+ result.events.map(({ id, outcome, integrity, anomalyReason }) => ({ id, outcome, integrity, anomalyReason })),
186
+ [
187
+ {
188
+ id: "intent:intent-mismatch",
189
+ outcome: "unresolved",
190
+ integrity: "anomaly",
191
+ anomalyReason: "host_target_mismatch",
192
+ },
193
+ { id: "intent:intent-unresolved", outcome: "unresolved", integrity: "unknown", anomalyReason: undefined },
194
+ ],
195
+ );
196
+
197
+ const anomalies = projectCognitiveRoutingHistory(entries, { current: current(), anomaliesOnly: true });
198
+ assert.equal(anomalies.summary.unresolvedCount, 2);
199
+ assert.equal(anomalies.summary.anomalyCount, 1);
200
+ assert.deepEqual(
201
+ anomalies.events.map(({ id }) => id),
202
+ ["intent:intent-mismatch"],
203
+ );
204
+ });
205
+
206
+ test("projects control-only entries without fabricating a profile transition", () => {
207
+ const entries = [
208
+ {
209
+ id: "control-1",
210
+ type: "custom",
211
+ customType: "freeflow-cognitive-routing-control",
212
+ data: {
213
+ version: 2,
214
+ control: "automatic",
215
+ source: "user",
216
+ mechanism: "profile-command",
217
+ epoch: "epoch-1",
218
+ reason: "manual hold release",
219
+ },
220
+ },
221
+ ];
222
+
223
+ const result = projectCognitiveRoutingHistory(entries, { current: current() });
224
+
225
+ assert.deepEqual(result.summary, {
226
+ unresolvedCount: 0,
227
+ anomalyCount: 0,
228
+ });
229
+ assert.deepEqual(result.events, [
230
+ {
231
+ id: "control:control-1",
232
+ jsonlPosition: 0,
233
+ entryId: "control-1",
234
+ branchAnchor: "control-1",
235
+ classification: "control-only",
236
+ decisionSource: "user",
237
+ mechanism: "profile-command",
238
+ outcome: "completed",
239
+ changed: false,
240
+ integrity: "valid",
241
+ control: "automatic",
242
+ reason: "manual hold release",
243
+ epoch: "epoch-1",
244
+ },
245
+ ]);
246
+ });
247
+
248
+ test("latest completed transition ignores later control-only entries", () => {
249
+ const entries = [
250
+ {
251
+ id: "intent-completed",
252
+ type: "custom",
253
+ customType: "freeflow-cognitive-routing-intent",
254
+ data: intent(),
255
+ },
256
+ hostEntry({ id: "state-completed" }),
257
+ {
258
+ id: "control-later",
259
+ type: "custom",
260
+ customType: "freeflow-cognitive-routing-control",
261
+ data: {
262
+ version: 2,
263
+ control: "automatic",
264
+ source: "user",
265
+ mechanism: "profile-command",
266
+ epoch: "epoch-1",
267
+ },
268
+ },
269
+ ];
270
+
271
+ const result = projectCognitiveRoutingHistory(entries, { current: current() });
272
+
273
+ assert.equal(result.summary.latestCompletedEventId, "intent:intent-completed");
274
+ });
@@ -0,0 +1,4 @@
1
+ export const PIFLOW_HOST = Object.freeze({
2
+ distribution: Object.freeze({ id: "piflow", version: "test" }),
3
+ capabilities: Object.freeze({ sessionModelStateControl: 1 }),
4
+ });
@@ -0,0 +1,63 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import freeflowExtension from "../../dist/index.js";
4
+
5
+ const PIFLOW_HOST = Object.freeze({
6
+ distribution: Object.freeze({ id: "piflow", version: "test" }),
7
+ capabilities: Object.freeze({ sessionModelStateControl: 1 }),
8
+ });
9
+
10
+ function loadExtension(host) {
11
+ const tools = [];
12
+ const commands = [];
13
+ const handlers = new Map();
14
+ const pi = {
15
+ host,
16
+ registerTool(tool) {
17
+ tools.push(tool);
18
+ },
19
+ registerCommand(name, definition) {
20
+ commands.push({ name, definition });
21
+ },
22
+ on(event, handler) {
23
+ handlers.set(event, handler);
24
+ },
25
+ };
26
+ freeflowExtension(pi);
27
+ return { tools, commands, handlers };
28
+ }
29
+
30
+ test("does not activate PiFlow behavior from FREEFLOW_RUNTIME without host metadata", () => {
31
+ const previousRuntime = process.env.FREEFLOW_RUNTIME;
32
+ process.env.FREEFLOW_RUNTIME = "piflow";
33
+ try {
34
+ const { tools, commands } = loadExtension(undefined);
35
+ assert.equal(
36
+ tools.some((tool) => tool.name === "freeflow_switch_profile"),
37
+ false,
38
+ );
39
+ const freeflowCommand = commands.find((command) => command.name === "freeflow");
40
+ assert.ok(freeflowCommand);
41
+ assert.equal(
42
+ freeflowCommand.definition.getArgumentCompletions("").some((item) => item.value === "profile"),
43
+ false,
44
+ );
45
+ } finally {
46
+ if (previousRuntime === undefined) delete process.env.FREEFLOW_RUNTIME;
47
+ else process.env.FREEFLOW_RUNTIME = previousRuntime;
48
+ }
49
+ });
50
+
51
+ test("activates PiFlow behavior only with the complete host descriptor", () => {
52
+ const { tools, commands } = loadExtension(PIFLOW_HOST);
53
+ assert.equal(
54
+ tools.some((tool) => tool.name === "freeflow_switch_profile"),
55
+ true,
56
+ );
57
+ const freeflowCommand = commands.find((command) => command.name === "freeflow");
58
+ assert.ok(freeflowCommand);
59
+ assert.equal(
60
+ freeflowCommand.definition.getArgumentCompletions("").some((item) => item.value === "profile"),
61
+ true,
62
+ );
63
+ });
@@ -0,0 +1,236 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import test from "node:test";
6
+ import { resolveCognitiveRoutingConfig } from "../../dist/cognitive-routing/config.js";
7
+ import { resolveCognitiveRoutingState } from "../../dist/cognitive-routing/runtime.js";
8
+ import { readCapabilityState } from "../../dist/runtime/runtime-context.js";
9
+ import { PIFLOW_HOST } from "./host-fixture.js";
10
+
11
+ const standard = {
12
+ provider: "openai-codex",
13
+ model: "gpt-5.6-luna",
14
+ thinkingLevel: "high",
15
+ };
16
+ const reasoning = {
17
+ provider: "openai-codex",
18
+ model: "gpt-5.6-sol",
19
+ thinkingLevel: "max",
20
+ };
21
+
22
+ function configuredRepository(overrides = {}) {
23
+ return {
24
+ cognitiveRouting: {
25
+ enabled: true,
26
+ profiles: { standard, reasoning },
27
+ ...overrides,
28
+ },
29
+ };
30
+ }
31
+
32
+ function createHost({ models = [], unauthenticated = [], clamps = {} } = {}) {
33
+ const modelMap = new Map(models.map((model) => [`${model.provider}/${model.id}`, model]));
34
+ const unauthenticatedSet = new Set(unauthenticated);
35
+ return {
36
+ modelRegistry: {
37
+ find(provider, modelId) {
38
+ return modelMap.get(`${provider}/${modelId}`);
39
+ },
40
+ async getApiKeyAndHeaders(model) {
41
+ return unauthenticatedSet.has(model.provider) ? { ok: false, error: "missing credentials" } : { ok: true };
42
+ },
43
+ clampThinkingLevel(model, level) {
44
+ return clamps[`${model.provider}/${model.id}`]?.[level] ?? level;
45
+ },
46
+ },
47
+ };
48
+ }
49
+
50
+ const standardModel = { provider: standard.provider, id: standard.model };
51
+ const reasoningModel = { provider: reasoning.provider, id: reasoning.model };
52
+
53
+ test("resolves two complete repository profiles", () => {
54
+ const result = resolveCognitiveRoutingConfig(configuredRepository(), {});
55
+
56
+ assert.equal(result.valid, true);
57
+ assert.equal(result.enabled, true);
58
+ assert.equal(result.enabledSource, "repository");
59
+ assert.deepEqual(result.profiles, { standard, reasoning });
60
+ assert.deepEqual(result.profileSources, { standard: "repository", reasoning: "repository" });
61
+ });
62
+
63
+ test("defaults new-session Cognitive Routing to automatic standard control", () => {
64
+ const result = resolveCognitiveRoutingConfig(configuredRepository(), {});
65
+
66
+ assert.deepEqual(result.sessionStart, { control: "automatic", profile: "standard" });
67
+ assert.deepEqual(result.sessionStartSources, { control: "default", profile: "default" });
68
+ });
69
+
70
+ test("layers session-start control and profile independently", () => {
71
+ const result = resolveCognitiveRoutingConfig(
72
+ configuredRepository({ sessionStart: { control: "automatic", profile: "standard" } }),
73
+ { cognitiveRouting: { sessionStart: { control: "manual", profile: "reasoning" } } },
74
+ );
75
+
76
+ assert.deepEqual(result.sessionStart, { control: "manual", profile: "reasoning" });
77
+ assert.deepEqual(result.sessionStartSources, { control: "personal", profile: "personal" });
78
+
79
+ const inheritedProfile = resolveCognitiveRoutingConfig(
80
+ configuredRepository({ sessionStart: { control: "manual" } }),
81
+ { cognitiveRouting: { sessionStart: { profile: "reasoning" } } },
82
+ );
83
+ assert.deepEqual(inheritedProfile.sessionStart, { control: "manual", profile: "reasoning" });
84
+ assert.deepEqual(inheritedProfile.sessionStartSources, { control: "repository", profile: "personal" });
85
+ });
86
+
87
+ test("replaces a profile atomically from the personal layer", () => {
88
+ const personalStandard = {
89
+ provider: "openai-codex",
90
+ model: "gpt-5.6-luna-personal",
91
+ thinkingLevel: "medium",
92
+ };
93
+ const result = resolveCognitiveRoutingConfig(configuredRepository(), {
94
+ cognitiveRouting: { profiles: { standard: personalStandard } },
95
+ });
96
+
97
+ assert.equal(result.valid, true);
98
+ assert.deepEqual(result.profiles.standard, personalStandard);
99
+ assert.deepEqual(result.profiles.reasoning, reasoning);
100
+ assert.deepEqual(result.profileSources, { standard: "personal", reasoning: "repository" });
101
+ });
102
+
103
+ test("rejects a partial personal profile instead of merging fields", () => {
104
+ const result = resolveCognitiveRoutingConfig(configuredRepository(), {
105
+ cognitiveRouting: { profiles: { standard: { model: "personal-only" } } },
106
+ });
107
+
108
+ assert.equal(result.valid, false);
109
+ assert.equal(result.error.code, "invalid_profile");
110
+ assert.equal(result.error.profile, "standard");
111
+ });
112
+
113
+ test("keeps Cognitive Routing disabled by default without a config block", async () => {
114
+ const result = await resolveCognitiveRoutingState({}, {}, createHost({ models: [standardModel, reasoningModel] }));
115
+
116
+ assert.equal(result.configured, false);
117
+ assert.equal(result.enabled, false);
118
+ assert.equal(result.effective, false);
119
+ assert.equal(result.blockingReason.code, "disabled");
120
+ });
121
+
122
+ test("fails only Cognitive Routing closed when its config is invalid", async () => {
123
+ const result = await resolveCognitiveRoutingState(
124
+ { cognitiveRouting: { enabled: true, profiles: { standard: { provider: "only-provider" } } } },
125
+ {},
126
+ createHost({ models: [standardModel, reasoningModel] }),
127
+ );
128
+
129
+ assert.equal(result.configured, true);
130
+ assert.equal(result.configValid, false);
131
+ assert.equal(result.effective, false);
132
+ assert.equal(result.blockingReason.code, "config_invalid");
133
+ });
134
+
135
+ test("reports an unsupported host without weakening core capability state", async () => {
136
+ const result = await resolveCognitiveRoutingState(configuredRepository(), {}, undefined);
137
+
138
+ assert.equal(result.enabled, true);
139
+ assert.equal(result.effective, false);
140
+ assert.equal(result.blockingReason.code, "host_unsupported");
141
+ });
142
+
143
+ test("preflights exact identities, authentication, and effective thinking levels", async () => {
144
+ const host = createHost({
145
+ models: [standardModel, reasoningModel],
146
+ clamps: { "openai-codex/gpt-5.6-luna": { high: "high" }, "openai-codex/gpt-5.6-sol": { max: "max" } },
147
+ });
148
+ const result = await resolveCognitiveRoutingState(configuredRepository(), {}, host);
149
+
150
+ assert.equal(result.effective, true);
151
+ assert.deepEqual(result.resolvedProfiles.standard, {
152
+ ...standard,
153
+ effectiveThinkingLevel: "high",
154
+ });
155
+ assert.deepEqual(result.resolvedProfiles.reasoning, {
156
+ ...reasoning,
157
+ effectiveThinkingLevel: "max",
158
+ });
159
+ });
160
+
161
+ test("runtime context isolates invalid Cognitive Routing from core Freeflow state", async () => {
162
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-cognitive-routing-"));
163
+ await mkdir(join(cwd, ".freeflow"));
164
+ const host = createHost({ models: [standardModel, reasoningModel] });
165
+ try {
166
+ await writeFile(
167
+ join(cwd, ".freeflow", "config.json"),
168
+ JSON.stringify({ defaultMode: "workflow", cognitiveRouting: configuredRepository().cognitiveRouting }),
169
+ );
170
+ let state = await readCapabilityState(cwd, host, PIFLOW_HOST);
171
+ assert.equal(state.configured, true);
172
+ assert.equal(state.enabled, true);
173
+ assert.equal(state.cognitiveRouting.effective, true);
174
+
175
+ await writeFile(
176
+ join(cwd, ".freeflow", "config.json"),
177
+ JSON.stringify({ enabled: false, cognitiveRouting: configuredRepository().cognitiveRouting }),
178
+ );
179
+ state = await readCapabilityState(cwd, host, PIFLOW_HOST);
180
+ assert.equal(state.enabled, false);
181
+ assert.equal(state.cognitiveRouting.enabled, false);
182
+ assert.equal(state.cognitiveRouting.effective, false);
183
+ assert.equal(state.cognitiveRouting.blockingReason.code, "disabled");
184
+
185
+ await writeFile(
186
+ join(cwd, ".freeflow", "config.json"),
187
+ JSON.stringify({
188
+ defaultMode: "workflow",
189
+ cognitiveRouting: { enabled: true, profiles: { standard: { provider: "only-provider" } } },
190
+ }),
191
+ );
192
+ state = await readCapabilityState(cwd, host, PIFLOW_HOST);
193
+ assert.equal(state.configured, true);
194
+ assert.equal(state.enabled, true);
195
+ assert.equal(state.cognitiveRouting.configValid, false);
196
+ assert.equal(state.cognitiveRouting.effective, false);
197
+ } finally {
198
+ await rm(cwd, { recursive: true, force: true });
199
+ }
200
+ });
201
+
202
+ test("rejects unavailable, unauthenticated, weakened, and identical profile pairs", async () => {
203
+ const unavailable = await resolveCognitiveRoutingState(
204
+ configuredRepository(),
205
+ {},
206
+ createHost({ models: [standardModel] }),
207
+ );
208
+ assert.equal(unavailable.blockingReason.code, "profile_unavailable");
209
+ assert.equal(unavailable.blockingReason.profile, "reasoning");
210
+
211
+ const unauthenticated = await resolveCognitiveRoutingState(
212
+ configuredRepository(),
213
+ {},
214
+ createHost({ models: [standardModel, reasoningModel], unauthenticated: ["openai-codex"] }),
215
+ );
216
+ assert.equal(unauthenticated.blockingReason.code, "profile_unauthenticated");
217
+ assert.equal(unauthenticated.blockingReason.profile, "standard");
218
+
219
+ const weakened = await resolveCognitiveRoutingState(
220
+ configuredRepository(),
221
+ {},
222
+ createHost({
223
+ models: [standardModel, reasoningModel],
224
+ clamps: { "openai-codex/gpt-5.6-luna": { high: "medium" } },
225
+ }),
226
+ );
227
+ assert.equal(weakened.blockingReason.code, "profile_clamped");
228
+ assert.equal(weakened.blockingReason.profile, "standard");
229
+
230
+ const identical = await resolveCognitiveRoutingState(
231
+ configuredRepository({ profiles: { standard, reasoning: { ...standard } } }),
232
+ {},
233
+ createHost({ models: [standardModel] }),
234
+ );
235
+ assert.equal(identical.blockingReason.code, "profiles_identical");
236
+ });