@hassangameryt/freeflow 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/.codex-plugin/plugin.json +4 -5
  4. package/README.md +141 -163
  5. package/capabilities/cognitive-routing/SKILL.md +387 -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 -37
  9. package/hooks/freeflow-runtime-context.mjs +50 -541
  10. package/hooks/hooks.json +1 -26
  11. package/hooks/tests/check-runtime-context-hook.sh +114 -308
  12. package/package.json +27 -17
  13. package/pi-extension/dist/cognitive-routing/commands.js +96 -0
  14. package/pi-extension/dist/cognitive-routing/config.js +232 -0
  15. package/pi-extension/dist/cognitive-routing/controller.js +1292 -0
  16. package/pi-extension/dist/cognitive-routing/history.js +451 -0
  17. package/pi-extension/dist/cognitive-routing/host.js +188 -0
  18. package/pi-extension/dist/cognitive-routing/pi-routing-session.js +414 -0
  19. package/pi-extension/dist/cognitive-routing/pi-session-control.js +320 -0
  20. package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
  21. package/pi-extension/dist/cognitive-routing/tool.js +212 -0
  22. package/pi-extension/dist/cognitive-routing/types.js +6 -0
  23. package/pi-extension/dist/context-virtualization/commands.js +89 -0
  24. package/pi-extension/dist/context-virtualization/projector.js +42 -0
  25. package/pi-extension/dist/context-virtualization/runtime.js +356 -0
  26. package/pi-extension/dist/context-virtualization/state.js +57 -0
  27. package/pi-extension/dist/context-virtualization/types.js +10 -0
  28. package/pi-extension/dist/conversation-history/passages.js +66 -0
  29. package/pi-extension/dist/conversation-history/ranking.js +105 -0
  30. package/pi-extension/dist/conversation-history/runtime.js +226 -0
  31. package/pi-extension/dist/conversation-history/search.js +56 -0
  32. package/pi-extension/dist/conversation-history/types.js +1 -0
  33. package/pi-extension/dist/freeflow-context/resolver.js +120 -0
  34. package/pi-extension/dist/freeflow-context/runtime.js +80 -0
  35. package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
  36. package/pi-extension/dist/freeflow-context/tool.js +308 -0
  37. package/pi-extension/dist/freeflow-context/types.js +12 -0
  38. package/pi-extension/dist/freeflow-context/visibility.js +28 -0
  39. package/pi-extension/dist/index.js +564 -129
  40. package/pi-extension/dist/runtime/runtime-context.js +357 -470
  41. package/pi-extension/dist/runtime/runtime-identity.js +5 -0
  42. package/pi-extension/dist/settings/settings-tui.js +78 -0
  43. package/pi-extension/dist/settings/settings-ui.js +572 -826
  44. package/pi-extension/dist/ui/text-component.js +57 -0
  45. package/pi-extension/src/cognitive-routing/commands.ts +129 -0
  46. package/pi-extension/src/cognitive-routing/config.ts +303 -0
  47. package/pi-extension/src/cognitive-routing/controller.ts +1628 -0
  48. package/pi-extension/src/cognitive-routing/history.ts +650 -0
  49. package/pi-extension/src/cognitive-routing/host.ts +266 -0
  50. package/pi-extension/src/cognitive-routing/pi-routing-session.ts +538 -0
  51. package/pi-extension/src/cognitive-routing/pi-session-control.ts +444 -0
  52. package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
  53. package/pi-extension/src/cognitive-routing/tool.ts +273 -0
  54. package/pi-extension/src/cognitive-routing/types.ts +89 -0
  55. package/pi-extension/src/context-virtualization/commands.ts +111 -0
  56. package/pi-extension/src/context-virtualization/projector.ts +62 -0
  57. package/pi-extension/src/context-virtualization/runtime.ts +406 -0
  58. package/pi-extension/src/context-virtualization/state.ts +79 -0
  59. package/pi-extension/src/context-virtualization/types.ts +92 -0
  60. package/pi-extension/src/conversation-history/passages.ts +81 -0
  61. package/pi-extension/src/conversation-history/ranking.ts +155 -0
  62. package/pi-extension/src/conversation-history/runtime.ts +250 -0
  63. package/pi-extension/src/conversation-history/search.ts +79 -0
  64. package/pi-extension/src/conversation-history/types.ts +31 -0
  65. package/pi-extension/src/freeflow-context/resolver.ts +139 -0
  66. package/pi-extension/src/freeflow-context/runtime.ts +100 -0
  67. package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
  68. package/pi-extension/src/freeflow-context/tool.ts +360 -0
  69. package/pi-extension/src/freeflow-context/types.ts +70 -0
  70. package/pi-extension/src/freeflow-context/visibility.ts +53 -0
  71. package/pi-extension/src/index.ts +597 -135
  72. package/pi-extension/src/runtime/runtime-context.ts +373 -491
  73. package/pi-extension/src/runtime/runtime-identity.ts +8 -0
  74. package/pi-extension/src/settings/settings-tui.ts +93 -0
  75. package/pi-extension/src/settings/settings-ui.ts +679 -910
  76. package/pi-extension/src/ui/text-component.ts +66 -0
  77. package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
  78. package/pi-extension/tests/cognitive-routing/controller.test.js +1128 -0
  79. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1142 -0
  80. package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
  81. package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
  82. package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
  83. package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +434 -0
  84. package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +174 -0
  85. package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +91 -0
  86. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +279 -0
  87. package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
  88. package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
  89. package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
  90. package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
  91. package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
  92. package/pi-extension/tests/conversation-history/search.test.js +97 -0
  93. package/pi-extension/tests/integration/mode-free-core.test.js +218 -0
  94. package/pi-extension/tests/integration/pi-extension.test.js +532 -3701
  95. package/pi-extension/tests/integration/prompt-architecture.test.js +350 -0
  96. package/pi-extension/tests/integration/unaffected-regressions.test.js +411 -0
  97. package/runtime/prompts/cognitive-routing.md +10 -0
  98. package/runtime/prompts/context-virtualization.md +3 -0
  99. package/runtime/prompts/conversation-history.md +3 -0
  100. package/runtime/prompts/core.md +84 -0
  101. package/runtime/prompts/interaction-contract.md +11 -0
  102. package/skills/action-selection/SKILL.md +140 -0
  103. package/skills/action-selection/references/trajectory-stalls.md +71 -0
  104. package/skills/bypass/SKILL.md +109 -38
  105. package/skills/commit-work/SKILL.md +94 -55
  106. package/skills/commit-work/references/git-state-edges.md +94 -0
  107. package/skills/commit-work/references/simple-push.md +92 -0
  108. package/skills/commit-work/references/staging-decisions.md +79 -46
  109. package/skills/decision-gate/SKILL.md +80 -22
  110. package/skills/design-for-depth/SKILL.md +206 -60
  111. package/skills/design-for-depth/references/design-pressure-signals.md +108 -240
  112. package/skills/design-for-depth/references/interface-design-loop.md +77 -97
  113. package/skills/design-for-depth/references/module-and-dependency-design.md +147 -0
  114. package/skills/design-for-depth/references/software-design-philosophy.md +128 -174
  115. package/skills/design-for-depth/references/state-and-failure-boundaries.md +136 -0
  116. package/skills/diagnose-failure/SKILL.md +200 -48
  117. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +18 -6
  118. package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +13 -5
  119. package/skills/discuss/SKILL.md +119 -65
  120. package/skills/discuss/references/discussion-continuity.md +123 -0
  121. package/skills/evaluate-skill/SKILL.md +166 -52
  122. package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +43 -3
  123. package/skills/evaluate-skill/references/evaluation-design.md +56 -30
  124. package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
  125. package/skills/evaluate-skill/references/review-and-revision.md +62 -21
  126. package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
  127. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
  128. package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
  129. package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
  130. package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
  131. package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
  132. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
  133. package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
  134. package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
  135. package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
  136. package/skills/execute-work/SKILL.md +99 -57
  137. package/skills/execute-work/references/code-practices.md +4 -4
  138. package/skills/execute-work/references/execute-work-edges.md +62 -0
  139. package/skills/finish-branch/SKILL.md +2 -2
  140. package/skills/finish-branch/references/integration-options.md +1 -1
  141. package/skills/handoff/SKILL.md +3 -3
  142. package/skills/launch-work/SKILL.md +1 -1
  143. package/skills/launch-work/references/launch-readiness.md +2 -2
  144. package/skills/migration-work/SKILL.md +2 -2
  145. package/skills/migration-work/references/migration-lifecycle.md +1 -1
  146. package/skills/release-work/SKILL.md +1 -1
  147. package/skills/release-work/references/release-evidence.md +1 -1
  148. package/skills/review-artifact/SKILL.md +129 -66
  149. package/skills/review-artifact/references/adjudicate-artifact-review.md +101 -0
  150. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +159 -0
  151. package/skills/review-work/SKILL.md +113 -68
  152. package/skills/review-work/references/adjudicate-work-review.md +97 -0
  153. package/skills/review-work/references/independent-work-reviewer-contract.md +139 -0
  154. package/skills/review-work/references/security-risk-lens.md +18 -16
  155. package/skills/setup-freeflow/SKILL.md +16 -17
  156. package/skills/setup-freeflow/references/activation-contract.md +24 -30
  157. package/skills/setup-freeflow/references/host-setup.md +6 -7
  158. package/skills/simplify-code/SKILL.md +97 -39
  159. package/skills/simplify-code/references/simplification-patterns.md +1 -1
  160. package/skills/tdd/SKILL.md +100 -53
  161. package/skills/tdd/references/test-design.md +3 -3
  162. package/skills/track-work/SKILL.md +195 -58
  163. package/skills/track-work/references/working-record-format.md +515 -0
  164. package/skills/track-work/scripts/lib/document.mjs +713 -0
  165. package/skills/track-work/scripts/lib/format.mjs +166 -0
  166. package/skills/track-work/scripts/lib/store.mjs +233 -0
  167. package/skills/track-work/scripts/lib/transitions.mjs +790 -0
  168. package/skills/track-work/scripts/lib/views.mjs +40 -0
  169. package/skills/track-work/scripts/working-record.mjs +316 -0
  170. package/skills/verify-work/SKILL.md +57 -19
  171. package/skills/verify-work/references/browser-runtime-evidence.md +2 -2
  172. package/skills/verify-work/references/integration-evidence.md +1 -7
  173. package/skills/verify-work/references/performance-evidence.md +2 -2
  174. package/skills/workflow/SKILL.md +137 -55
  175. package/skills/workflow/references/domain-skill-composition.md +26 -40
  176. package/skills/write-plan/SKILL.md +1 -1
  177. package/skills/write-skill/SKILL.md +218 -61
  178. package/skills/write-skill/references/activation-boundaries.md +63 -21
  179. package/skills/write-skill/references/agent-first-instructions.md +61 -27
  180. package/skills/write-skill/references/progressive-disclosure.md +97 -33
  181. package/skills/write-skill/references/skill-dependency-graphs.md +190 -0
  182. package/capabilities/output-router/SKILL.md +0 -206
  183. package/capabilities/output-router/references/safety-policy.md +0 -40
  184. package/capabilities/output-router/references/setup.md +0 -110
  185. package/pi-extension/dist/output-router/host-producer-identification.js +0 -232
  186. package/pi-extension/dist/output-router/native-safety-net.js +0 -216
  187. package/pi-extension/dist/output-router/observed-tool-routing.js +0 -122
  188. package/pi-extension/dist/output-router/renderers.js +0 -1023
  189. package/pi-extension/dist/output-router/router-tools.js +0 -554
  190. package/pi-extension/dist/output-router/schemas.js +0 -383
  191. package/pi-extension/dist/output-router/status.js +0 -744
  192. package/pi-extension/dist/output-router/utils.js +0 -502
  193. package/pi-extension/src/output-router/host-producer-identification.ts +0 -256
  194. package/pi-extension/src/output-router/native-safety-net.ts +0 -248
  195. package/pi-extension/src/output-router/observed-tool-routing.ts +0 -134
  196. package/pi-extension/src/output-router/renderers.ts +0 -1108
  197. package/pi-extension/src/output-router/router-tools.ts +0 -579
  198. package/pi-extension/src/output-router/schemas.ts +0 -387
  199. package/pi-extension/src/output-router/status.ts +0 -791
  200. package/pi-extension/src/output-router/utils.ts +0 -554
  201. package/pi-extension/tests/output-router/observed-producers.test.js +0 -132
  202. package/pi-extension/tests/output-router/search.test.js +0 -722
  203. package/router/dist/benchmarks/benchmark-harness.d.ts +0 -35
  204. package/router/dist/benchmarks/benchmark-harness.js +0 -92
  205. package/router/dist/benchmarks/benchmarks.d.ts +0 -109
  206. package/router/dist/benchmarks/benchmarks.js +0 -979
  207. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +0 -95
  208. package/router/dist/benchmarks/codex-qa-benchmarks.js +0 -500
  209. package/router/dist/benchmarks/command-benchmarks.d.ts +0 -143
  210. package/router/dist/benchmarks/command-benchmarks.js +0 -698
  211. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +0 -82
  212. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +0 -773
  213. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +0 -132
  214. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +0 -1380
  215. package/router/dist/benchmarks/context-saving-baseline.d.ts +0 -60
  216. package/router/dist/benchmarks/context-saving-baseline.js +0 -470
  217. package/router/dist/benchmarks/index-benchmarks.d.ts +0 -103
  218. package/router/dist/benchmarks/index-benchmarks.js +0 -862
  219. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +0 -101
  220. package/router/dist/benchmarks/storage-policy-benchmarks.js +0 -618
  221. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +0 -56
  222. package/router/dist/benchmarks/vault-index-benchmarks.js +0 -322
  223. package/router/dist/config/config.d.ts +0 -103
  224. package/router/dist/config/config.js +0 -632
  225. package/router/dist/config/router-contract.d.ts +0 -10
  226. package/router/dist/config/router-contract.js +0 -54
  227. package/router/dist/config/schema.d.ts +0 -27
  228. package/router/dist/config/schema.js +0 -648
  229. package/router/dist/config/types.d.ts +0 -573
  230. package/router/dist/config/types.js +0 -41
  231. package/router/dist/evidence/bounded-evidence.d.ts +0 -40
  232. package/router/dist/evidence/bounded-evidence.js +0 -323
  233. package/router/dist/evidence/evidence-range-selector.d.ts +0 -24
  234. package/router/dist/evidence/evidence-range-selector.js +0 -232
  235. package/router/dist/evidence/evidence-search.d.ts +0 -27
  236. package/router/dist/evidence/evidence-search.js +0 -490
  237. package/router/dist/evidence/evidence.d.ts +0 -38
  238. package/router/dist/evidence/evidence.js +0 -200
  239. package/router/dist/evidence/failure-contracts.d.ts +0 -37
  240. package/router/dist/evidence/failure-contracts.js +0 -166
  241. package/router/dist/evidence/line-ranges.d.ts +0 -24
  242. package/router/dist/evidence/line-ranges.js +0 -22
  243. package/router/dist/experiments/local-index.d.ts +0 -64
  244. package/router/dist/experiments/local-index.js +0 -581
  245. package/router/dist/index.d.ts +0 -30
  246. package/router/dist/index.js +0 -32
  247. package/router/dist/local/local-traversal.d.ts +0 -18
  248. package/router/dist/local/local-traversal.js +0 -334
  249. package/router/dist/processing/engine.d.ts +0 -132
  250. package/router/dist/processing/engine.js +0 -533
  251. package/router/dist/processing/reducers.d.ts +0 -280
  252. package/router/dist/processing/reducers.js +0 -1673
  253. package/router/dist/processing/renderers.d.ts +0 -31
  254. package/router/dist/processing/renderers.js +0 -195
  255. package/router/dist/processing/scripts.d.ts +0 -91
  256. package/router/dist/processing/scripts.js +0 -386
  257. package/router/dist/repo/repo-traversal.d.ts +0 -18
  258. package/router/dist/repo/repo-traversal.js +0 -245
  259. package/router/dist/routing/observed-reducers.d.ts +0 -41
  260. package/router/dist/routing/observed-reducers.js +0 -661
  261. package/router/dist/routing/observed-routing.d.ts +0 -26
  262. package/router/dist/routing/observed-routing.js +0 -378
  263. package/router/dist/routing/parsers.d.ts +0 -16
  264. package/router/dist/routing/parsers.js +0 -521
  265. package/router/dist/routing/run-filters.d.ts +0 -60
  266. package/router/dist/routing/run-filters.js +0 -238
  267. package/router/dist/routing/run-reducers.d.ts +0 -49
  268. package/router/dist/routing/run-reducers.js +0 -122
  269. package/router/dist/sandbox/adapter-roots.d.ts +0 -46
  270. package/router/dist/sandbox/adapter-roots.js +0 -66
  271. package/router/dist/sandbox/eryx-python-adapter.d.ts +0 -13
  272. package/router/dist/sandbox/eryx-python-adapter.js +0 -868
  273. package/router/dist/sandbox/jq-wasm-adapter.d.ts +0 -9
  274. package/router/dist/sandbox/jq-wasm-adapter.js +0 -429
  275. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +0 -13
  276. package/router/dist/sandbox/quickjs-wasi-adapter.js +0 -384
  277. package/router/dist/sandbox/script-sandbox.d.ts +0 -134
  278. package/router/dist/sandbox/script-sandbox.js +0 -337
  279. package/router/dist/setup/script-transform-adapters.d.ts +0 -32
  280. package/router/dist/setup/script-transform-adapters.js +0 -278
  281. package/router/dist/tools/batch.d.ts +0 -33
  282. package/router/dist/tools/batch.js +0 -586
  283. package/router/dist/tools/run.d.ts +0 -62
  284. package/router/dist/tools/run.js +0 -1304
  285. package/router/dist/tools/search.d.ts +0 -66
  286. package/router/dist/tools/search.js +0 -1364
  287. package/router/dist/transform/engine.d.ts +0 -168
  288. package/router/dist/transform/engine.js +0 -2072
  289. package/router/dist/vault/vault-index.d.ts +0 -125
  290. package/router/dist/vault/vault-index.js +0 -416
  291. package/router/dist/vault/vault.d.ts +0 -138
  292. package/router/dist/vault/vault.js +0 -630
  293. package/runtime/interaction-contract.md +0 -16
  294. package/skills/discuss/references/checkpoints.md +0 -66
  295. package/skills/execute-work/references/execution-loop.md +0 -77
  296. package/skills/mode-contract/SKILL.md +0 -104
  297. package/skills/review-artifact/references/reviewer-prompt.md +0 -148
  298. package/skills/review-work/references/reviewer-prompt.md +0 -134
  299. package/skills/track-work/references/working-record-schema.md +0 -378
  300. package/skills/workflow/references/workflow-loop.md +0 -92
  301. /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
package/hooks/hooks.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "Load Freeflow runtime context and manage session mode.",
2
+ "description": "Load Freeflow core runtime context.",
3
3
  "hooks": {
4
4
  "SessionStart": [
5
5
  {
@@ -13,31 +13,6 @@
13
13
  }
14
14
  ]
15
15
  }
16
- ],
17
- "UserPromptSubmit": [
18
- {
19
- "hooks": [
20
- {
21
- "type": "command",
22
- "command": "node \"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT}}/hooks/freeflow-runtime-context.mjs\" UserPromptSubmit",
23
- "timeout": 5,
24
- "statusMessage": "Checking Freeflow mode"
25
- }
26
- ]
27
- }
28
- ],
29
- "SessionEnd": [
30
- {
31
- "matcher": "clear",
32
- "hooks": [
33
- {
34
- "type": "command",
35
- "command": "node \"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT}}/hooks/freeflow-runtime-context.mjs\" SessionEnd",
36
- "timeout": 3,
37
- "statusMessage": "Preserving Freeflow mode"
38
- }
39
- ]
40
- }
41
16
  ]
42
17
  }
43
18
  }
@@ -4,258 +4,136 @@ set -euo pipefail
4
4
  ROOT_DIR="$(CDPATH='' cd -- "$(dirname -- "$0")/../.." && pwd)"
5
5
  HOOK_PATH="$ROOT_DIR/hooks/freeflow-runtime-context.mjs"
6
6
  HOOKS_JSON="$ROOT_DIR/hooks/hooks.json"
7
- WORKFLOW_SKILL="$ROOT_DIR/skills/workflow/SKILL.md"
7
+ CORE_PROMPT="$ROOT_DIR/runtime/prompts/core.md"
8
+ INTERACTION_PROMPT="$ROOT_DIR/runtime/prompts/interaction-contract.md"
8
9
 
9
10
  fail() {
10
- echo "runtime-context hook check failed: $*" >&2
11
- exit 1
11
+ echo "runtime-context hook check failed: $*" >&2
12
+ exit 1
12
13
  }
13
14
 
14
15
  assert_contains() {
15
- local text="$1"
16
- local expected="$2"
17
- local label="$3"
18
- grep -Fq -- "$expected" <<<"$text" || fail "$label missing '$expected'"
16
+ local text="$1"
17
+ local expected="$2"
18
+ local label="$3"
19
+ grep -Fq -- "$expected" <<<"$text" || fail "$label missing '$expected'"
19
20
  }
20
21
 
21
22
  assert_not_contains() {
22
- local text="$1"
23
- local unexpected="$2"
24
- local label="$3"
25
- if grep -Fq -- "$unexpected" <<<"$text"; then
26
- fail "$label unexpectedly contains '$unexpected'"
27
- fi
23
+ local text="$1"
24
+ local unexpected="$2"
25
+ local label="$3"
26
+ if grep -Fq -- "$unexpected" <<<"$text"; then
27
+ fail "$label unexpectedly contains '$unexpected'"
28
+ fi
28
29
  }
29
30
 
30
31
  [[ -f "$HOOK_PATH" ]] || fail "missing hook script"
31
32
  [[ -f "$HOOKS_JSON" ]] || fail "missing hooks manifest"
32
- [[ -f "$WORKFLOW_SKILL" ]] || fail "missing canonical Workflow skill"
33
- workflow_owner_line="$(grep -m1 '^The active agent owns ' "$WORKFLOW_SKILL" || true)"
34
- workflow_self_review_line="$(grep -m1 '^Self-review is silent ' "$WORKFLOW_SKILL" || true)"
35
- [[ -n "$workflow_owner_line" ]] || fail "canonical Workflow owner sentinel missing"
36
- [[ -n "$workflow_self_review_line" ]] || fail "canonical Workflow self-review sentinel missing"
33
+ [[ -f "$CORE_PROMPT" ]] || fail "missing core prompt"
34
+ [[ -f "$INTERACTION_PROMPT" ]] || fail "missing interaction contract prompt"
35
+ [[ ! -f "$ROOT_DIR/runtime/prompts/skills.md" ]] || fail "retired skills prompt remains"
37
36
  node --check "$HOOK_PATH"
38
37
  node -e '
39
38
  const fs = require("fs");
40
39
  const hooks = JSON.parse(fs.readFileSync(process.argv[1], "utf8")).hooks || {};
41
- if (!Array.isArray(hooks.SessionStart) || hooks.SessionStart.length === 0) process.exit(1);
40
+ if (!Array.isArray(hooks.SessionStart) || hooks.SessionStart.length !== 1) process.exit(1);
42
41
  if (hooks.SessionStart[0]?.matcher !== "startup|resume|clear|compact") process.exit(1);
43
- if (!Array.isArray(hooks.UserPromptSubmit) || hooks.UserPromptSubmit.length !== 1) process.exit(1);
44
- if (hooks.UserPromptSubmit[0]?.matcher != null) process.exit(1);
45
- if (!Array.isArray(hooks.SessionEnd) || hooks.SessionEnd.length !== 1) process.exit(1);
46
- if (hooks.SessionEnd[0]?.matcher !== "clear") process.exit(1);
47
- ' "$HOOKS_JSON" || fail "hooks.json does not register the expected non-overlapping runtime events"
42
+ if (hooks.UserPromptSubmit || hooks.SessionEnd) process.exit(1);
43
+ ' "$HOOKS_JSON" || fail "hooks.json does not expose only SessionStart"
48
44
 
49
45
  tmp_dir="$(mktemp -d)"
50
46
  trap 'rm -rf "$tmp_dir"' EXIT
51
47
  mkdir -p "$tmp_dir/.freeflow"
52
- printf '{"defaultMode":"workflow"}\n' >"$tmp_dir/.freeflow/config.json"
48
+ printf '{}\n' >"$tmp_dir/.freeflow/config.json"
53
49
 
54
50
  codex_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$tmp_dir" | node "$HOOK_PATH" SessionStart)"
55
51
  disabled_by_env="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$tmp_dir" | FREEFLOW_DISABLE_RUNTIME_CONTEXT=1 node "$HOOK_PATH" SessionStart)"
56
52
  [[ -z "$disabled_by_env" ]] || fail "FREEFLOW_DISABLE_RUNTIME_CONTEXT=1 should suppress injection"
57
53
 
58
- post_tool_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$tmp_dir" | node "$HOOK_PATH" PostToolUse)"
59
- [[ -z "$post_tool_output" ]] || fail "PostToolUse should not inject runtime context"
60
- user_prompt_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$tmp_dir" | node "$HOOK_PATH" UserPromptSubmit)"
61
- [[ -z "$user_prompt_output" ]] || fail "UserPromptSubmit should not duplicate session-start runtime context"
54
+ for unsupported_event in PostToolUse UserPromptSubmit SessionEnd; do
55
+ unsupported_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$tmp_dir" | node "$HOOK_PATH" "$unsupported_event")"
56
+ [[ -z "$unsupported_output" ]] || fail "$unsupported_event should not inject runtime context"
57
+ done
62
58
 
63
59
  for expected in \
64
- '"hookEventName":"SessionStart"' \
65
- "# Freeflow Runtime Context" \
66
- 'Runtime delivery: confirmed for this lifecycle-hook invocation.' \
67
- 'Configured default: `workflow` (repository)' \
68
- 'Effective mode: `workflow` (configured default, active)' \
69
- "# Freeflow Interaction Contract" \
70
- "Answer questions without inferring action." \
71
- "# Freeflow Workflow Bootstrap" \
72
- "Use feedback to choose the smallest useful next action." \
73
- "$workflow_owner_line" \
74
- "$workflow_self_review_line" \
75
- 'Interaction Contract: enabled' \
76
- 'Skills: enabled'; do
77
- assert_contains "$codex_output" "$expected" "Codex config-only context"
78
- done
79
- assert_not_contains "$codex_output" "Output Router" "Codex host context"
80
- assert_not_contains "$codex_output" "Output router:" "Codex host context"
81
- for excluded_heading in \
82
- "# Freeflow Runtime Kernel" \
83
- "## Freeflow Runtime Priority" \
84
- "## Loaded Mode Contract Skill" \
85
- "## Loaded Workflow Skill" \
86
- "## Loaded Decision Gate Skill" \
87
- "## Discovery-light" \
88
- "## Loaded Output Router Skill"; do
89
- assert_not_contains "$codex_output" "$excluded_heading" "default context"
60
+ '"hookEventName":"SessionStart"' \
61
+ "# Freeflow Stable Guidance" \
62
+ "## Shared Terms" \
63
+ "## Recover After Context Loss" \
64
+ "## Three Nested Loops" \
65
+ "## Workflow Cue" \
66
+ "## Action Selection Cue" \
67
+ "## Supported Exit" \
68
+ "# Freeflow Interaction Contract" \
69
+ "Questions, criticism, examples, hypotheses, and tentative ideas do not authorize action by themselves." \
70
+ "Freeflow: active"; do
71
+ assert_contains "$codex_output" "$expected" "configured core context"
90
72
  done
91
- workflow_bootstrap_count="$(grep -Fo '# Freeflow Workflow Bootstrap' <<<"$codex_output" | wc -l | tr -d ' ')"
92
- [[ "$workflow_bootstrap_count" == "1" ]] || fail "Codex SessionStart should load Workflow exactly once"
93
- assert_not_contains "$codex_output" "## Conversation Mode Boundary" "workflow context"
94
- assert_not_contains "$codex_output" "## Strict Workflow Overlay" "workflow context"
73
+ assert_not_contains "$codex_output" "## Mode" "configured core context"
74
+ assert_not_contains "$codex_output" "Default mode" "configured core context"
75
+ assert_not_contains "$codex_output" "Active mode" "configured core context"
76
+ assert_not_contains "$codex_output" "Interaction Contract: enabled" "configured core context"
77
+ assert_not_contains "$codex_output" "Skills: enabled" "configured core context"
78
+ assert_not_contains "$codex_output" "session-modes" "configured core context"
79
+
80
+ # Empty mandatory fragments fail closed rather than emitting a partial active surface.
81
+ whitespace_root="$(mktemp -d)"
82
+ mkdir -p "$whitespace_root/hooks" "$whitespace_root/runtime/prompts" "$whitespace_root/repo/.freeflow"
83
+ cp "$HOOK_PATH" "$whitespace_root/hooks/freeflow-runtime-context.mjs"
84
+ cp -R "$ROOT_DIR/runtime/prompts/." "$whitespace_root/runtime/prompts/"
85
+ printf ' \n\t\n' >"$whitespace_root/runtime/prompts/core.md"
86
+ printf '{}\n' >"$whitespace_root/repo/.freeflow/config.json"
87
+ whitespace_output="$(printf '{\"cwd\":\"%s\"}\n' "$whitespace_root/repo" | node "$whitespace_root/hooks/freeflow-runtime-context.mjs" SessionStart)"
88
+ [[ -z "$whitespace_output" ]] || fail "whitespace-only mandatory prompt should fail closed"
89
+ rm -rf "$whitespace_root"
95
90
 
96
91
  claude_output="$(printf '{"hook_event_name":"SessionStart","source":"startup","cwd":"%s"}\n' "$tmp_dir" | node "$HOOK_PATH" SessionStart)"
97
92
  assert_contains "$claude_output" '"hookEventName":"SessionStart"' "Claude wrapper"
98
- assert_contains "$claude_output" "# Freeflow Interaction Contract" "Claude config-only context"
99
- assert_contains "$claude_output" "# Freeflow Workflow Bootstrap" "Claude first-turn context"
100
- assert_contains "$claude_output" "Answer questions without inferring action." "Claude config-only context"
101
-
102
- # Session mode changes are host-managed state: they apply before the same model request,
103
- # survive lifecycle restoration, remain isolated by session id, and never edit repo config.
104
- session_data_dir="$(mktemp -d)"
105
- session_id="session/with unsafe path characters"
106
- config_before="$(shasum -a 256 "$tmp_dir/.freeflow/config.json" | awk '{print $1}')"
107
- mode_change_output="$(printf '{"hook_event_name":"UserPromptSubmit","session_id":"%s","cwd":"%s","prompt":"Switch to conversation mode."}\n' "$session_id" "$tmp_dir" | PLUGIN_DATA="$session_data_dir" node "$HOOK_PATH" UserPromptSubmit)"
108
- assert_contains "$mode_change_output" '"hookEventName":"UserPromptSubmit"' "same-turn mode change wrapper"
109
- assert_contains "$mode_change_output" 'Session override: `conversation`' "same-turn mode change"
110
- assert_contains "$mode_change_output" 'Effective mode: `conversation` (session override, active)' "same-turn mode change"
111
- assert_contains "$mode_change_output" "## Conversation Mode Boundary" "same-turn mode change"
112
-
113
- restored_mode_output="$(printf '{"hook_event_name":"SessionStart","source":"compact","session_id":"%s","cwd":"%s","model":"gpt-5"}\n' "$session_id" "$tmp_dir" | PLUGIN_DATA="$session_data_dir" node "$HOOK_PATH" SessionStart)"
114
- assert_contains "$restored_mode_output" 'Configured default: `workflow` (repository)' "restored session mode"
115
- assert_contains "$restored_mode_output" 'Session override: `conversation`' "restored session mode"
116
- assert_contains "$restored_mode_output" 'Effective mode: `conversation` (session override, active)' "restored session mode"
117
-
118
- other_session_output="$(printf '{"hook_event_name":"SessionStart","source":"resume","session_id":"other-session","cwd":"%s","model":"gpt-5"}\n' "$tmp_dir" | PLUGIN_DATA="$session_data_dir" node "$HOOK_PATH" SessionStart)"
119
- assert_contains "$other_session_output" 'Session override: none' "session mode isolation"
120
- assert_contains "$other_session_output" 'Effective mode: `workflow` (configured default, active)' "session mode isolation"
121
-
122
- ordinary_prompt_output="$(printf '{"hook_event_name":"UserPromptSubmit","session_id":"%s","cwd":"%s","prompt":"Please explain this function."}\n' "$session_id" "$tmp_dir" | PLUGIN_DATA="$session_data_dir" node "$HOOK_PATH" UserPromptSubmit)"
123
- [[ -z "$ordinary_prompt_output" ]] || fail "ordinary prompts should not duplicate runtime context"
124
- question_prompt_output="$(printf '{"hook_event_name":"UserPromptSubmit","session_id":"%s","cwd":"%s","prompt":"Should we switch to strict-workflow mode?"}\n' "$session_id" "$tmp_dir" | PLUGIN_DATA="$session_data_dir" node "$HOOK_PATH" UserPromptSubmit)"
125
- [[ -z "$question_prompt_output" ]] || fail "mode questions should not mutate session state"
126
- hypothetical_prompt_output="$(printf '{"hook_event_name":"UserPromptSubmit","session_id":"%s","cwd":"%s","prompt":"Suppose the mode were workflow."}\n' "$session_id" "$tmp_dir" | PLUGIN_DATA="$session_data_dir" node "$HOOK_PATH" UserPromptSubmit)"
127
- [[ -z "$hypothetical_prompt_output" ]] || fail "mode hypotheticals should not mutate session state"
128
- default_prompt_output="$(printf '{"hook_event_name":"UserPromptSubmit","session_id":"%s","cwd":"%s","prompt":"Change the default mode to strict-workflow."}\n' "$session_id" "$tmp_dir" | PLUGIN_DATA="$session_data_dir" node "$HOOK_PATH" UserPromptSubmit)"
129
- [[ -z "$default_prompt_output" ]] || fail "default-mode requests should remain agent-routed configuration decisions"
130
-
131
- native_change_output="$(printf '{"hook_event_name":"UserPromptSubmit","session_id":"%s","cwd":"%s","prompt":"$mode-contract strict-workflow"}\n' "$session_id" "$tmp_dir" | PLUGIN_DATA="$session_data_dir" node "$HOOK_PATH" UserPromptSubmit)"
132
- assert_contains "$native_change_output" 'Session override: `strict-workflow`' "Codex native skill mode control"
133
- assert_contains "$native_change_output" "## Strict Workflow Overlay" "Codex native skill mode control"
134
-
135
- claude_data_dir="$(mktemp -d)"
136
- claude_mode_output="$(printf '{"hook_event_name":"UserPromptSubmit","session_id":"claude-session","cwd":"%s","prompt":"/freeflow:mode-contract conversation"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" node "$HOOK_PATH" UserPromptSubmit)"
137
- assert_contains "$claude_mode_output" '"hookEventName":"UserPromptSubmit"' "Claude native skill mode control"
138
- assert_contains "$claude_mode_output" 'Session override: `conversation`' "Claude native skill mode control"
139
- claude_restored_output="$(printf '{"hook_event_name":"SessionStart","source":"resume","session_id":"claude-session","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" node "$HOOK_PATH" SessionStart)"
140
- assert_contains "$claude_restored_output" 'Effective mode: `conversation` (session override, active)' "Claude restored session mode"
141
-
142
- # Claude /clear ends one session and starts another. SessionEnd transfers only the
143
- # active override for this host process/workspace, and SessionStart consumes it exactly once.
144
- claude_clear_end_output="$(printf '{"hook_event_name":"SessionEnd","reason":"clear","session_id":"claude-session","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-a" node "$HOOK_PATH" SessionEnd)"
145
- [[ -z "$claude_clear_end_output" ]] || fail "SessionEnd clear should not emit model context"
146
- claude_clear_output="$(printf '{"hook_event_name":"SessionStart","source":"clear","session_id":"claude-session-after-clear","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-a" node "$HOOK_PATH" SessionStart)"
147
- assert_contains "$claude_clear_output" 'Session override: `conversation`' "Claude clear session transfer"
148
- assert_contains "$claude_clear_output" 'Effective mode: `conversation` (session override, active)' "Claude clear session transfer"
149
- claude_unrelated_output="$(printf '{"hook_event_name":"SessionStart","source":"startup","session_id":"claude-unrelated-session","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" node "$HOOK_PATH" SessionStart)"
150
- assert_contains "$claude_unrelated_output" 'Session override: none' "Claude clear transfer isolation"
151
- claude_second_clear_output="$(printf '{"hook_event_name":"SessionStart","source":"clear","session_id":"claude-second-clear","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" node "$HOOK_PATH" SessionStart)"
152
- assert_contains "$claude_second_clear_output" 'Session override: none' "Claude clear transfer one-shot consumption"
153
-
154
- # A stale clear handoff is consumed without applying it.
155
- claude_stale_mode_output="$(printf '{"hook_event_name":"UserPromptSubmit","session_id":"claude-stale-source","cwd":"%s","prompt":"Switch to strict-workflow mode."}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" node "$HOOK_PATH" UserPromptSubmit)"
156
- assert_contains "$claude_stale_mode_output" 'Session override: `strict-workflow`' "Claude stale clear setup"
157
- printf '{"hook_event_name":"SessionEnd","reason":"clear","session_id":"claude-stale-source","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-stale" node "$HOOK_PATH" SessionEnd >/dev/null
158
- clear_transfer_file="$(find "$claude_data_dir/session-modes/claude-clear" -type f -name '*.json' -print -quit)"
159
- node - "$clear_transfer_file" <<'NODE'
160
- const fs = require("fs");
161
- const path = process.argv[2];
162
- const state = JSON.parse(fs.readFileSync(path, "utf8"));
163
- state.createdAt = new Date(Date.now() - 120000).toISOString();
164
- fs.writeFileSync(path, `${JSON.stringify(state)}\n`);
165
- NODE
166
- claude_stale_clear_output="$(printf '{"hook_event_name":"SessionStart","source":"clear","session_id":"claude-stale-target","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-stale" node "$HOOK_PATH" SessionStart)"
167
- assert_contains "$claude_stale_clear_output" 'Session override: none' "Claude stale clear transfer"
168
- [[ ! -e "$clear_transfer_file" ]] || fail "stale Claude clear transfer should be consumed"
169
-
170
- # Malformed plugin-owned transfer content fails closed without suppressing the
171
- # normal SessionStart context and is consumed rather than retried forever.
172
- printf '{"hook_event_name":"SessionEnd","reason":"clear","session_id":"claude-stale-source","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-malformed" node "$HOOK_PATH" SessionEnd >/dev/null
173
- malformed_transfer_file="$(find "$claude_data_dir/session-modes/claude-clear" -type f -name '*.json' -print -quit)"
174
- printf 'null\n' >"$malformed_transfer_file"
175
- claude_malformed_clear_output="$(printf '{"hook_event_name":"SessionStart","source":"clear","session_id":"claude-malformed-target","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-malformed" node "$HOOK_PATH" SessionStart)"
176
- assert_contains "$claude_malformed_clear_output" '# Freeflow Runtime Context' "Claude malformed clear transfer"
177
- assert_contains "$claude_malformed_clear_output" 'Session override: none' "Claude malformed clear transfer"
178
- [[ ! -e "$malformed_transfer_file" ]] || fail "malformed Claude clear transfer should be consumed"
179
-
180
- # Overlapping clears in one workspace remain isolated by the host process ID.
181
- printf '{"hook_event_name":"UserPromptSubmit","session_id":"claude-overlap-a","cwd":"%s","prompt":"Switch to conversation mode."}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" node "$HOOK_PATH" UserPromptSubmit >/dev/null
182
- printf '{"hook_event_name":"UserPromptSubmit","session_id":"claude-overlap-b","cwd":"%s","prompt":"Switch to strict-workflow mode."}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" node "$HOOK_PATH" UserPromptSubmit >/dev/null
183
- printf '{"hook_event_name":"SessionEnd","reason":"clear","session_id":"claude-overlap-a","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-overlap-a" node "$HOOK_PATH" SessionEnd >/dev/null
184
- printf '{"hook_event_name":"SessionEnd","reason":"clear","session_id":"claude-overlap-b","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-overlap-b" node "$HOOK_PATH" SessionEnd >/dev/null
185
- claude_overlap_b_output="$(printf '{"hook_event_name":"SessionStart","source":"clear","session_id":"claude-overlap-b-new","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-overlap-b" node "$HOOK_PATH" SessionStart)"
186
- assert_contains "$claude_overlap_b_output" 'Session override: `strict-workflow`' "Claude overlapping clear B"
187
- claude_overlap_a_output="$(printf '{"hook_event_name":"SessionStart","source":"clear","session_id":"claude-overlap-a-new","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-overlap-a" node "$HOOK_PATH" SessionStart)"
188
- assert_contains "$claude_overlap_a_output" 'Session override: `conversation`' "Claude overlapping clear A"
189
-
190
- # A no-override clear removes any stale transfer for the same process/workspace.
191
- printf '{"hook_event_name":"SessionEnd","reason":"clear","session_id":"claude-no-override","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-overlap-a" node "$HOOK_PATH" SessionEnd >/dev/null
192
- claude_no_override_clear_output="$(printf '{"hook_event_name":"SessionStart","source":"clear","session_id":"claude-no-override-new","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-overlap-a" node "$HOOK_PATH" SessionStart)"
193
- assert_contains "$claude_no_override_clear_output" 'Session override: none' "Claude no-override clear"
194
-
195
- # Transfer consumption claims the handoff atomically: two concurrent starts can
196
- # never both receive the same override.
197
- printf '{"hook_event_name":"SessionEnd","reason":"clear","session_id":"claude-overlap-b-new","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-race" node "$HOOK_PATH" SessionEnd >/dev/null
198
- race_output_a="$(mktemp)"
199
- race_output_b="$(mktemp)"
200
- printf '{"hook_event_name":"SessionStart","source":"clear","session_id":"claude-race-a","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-race" node "$HOOK_PATH" SessionStart >"$race_output_a" &
201
- race_pid_a=$!
202
- printf '{"hook_event_name":"SessionStart","source":"clear","session_id":"claude-race-b","cwd":"%s"}\n' "$tmp_dir" | CLAUDE_PLUGIN_DATA="$claude_data_dir" CLAUDE_PID="claude-process-race" node "$HOOK_PATH" SessionStart >"$race_output_b" &
203
- race_pid_b=$!
204
- wait "$race_pid_a" "$race_pid_b"
205
- race_override_count="$(grep -Fh 'Session override: `strict-workflow`' "$race_output_a" "$race_output_b" | wc -l | tr -d ' ')"
206
- [[ "$race_override_count" == "1" ]] || fail "Claude clear transfer should have exactly one concurrent consumer"
207
- rm -f "$race_output_a" "$race_output_b"
208
-
209
- # Codex currently reports SessionEnd reason=other, so it must never stage a
210
- # Claude-style clear handoff.
211
- codex_clear_data_dir="$(mktemp -d)"
212
- printf '{"hook_event_name":"UserPromptSubmit","session_id":"codex-source","cwd":"%s","prompt":"Switch to conversation mode."}\n' "$tmp_dir" | PLUGIN_DATA="$codex_clear_data_dir" node "$HOOK_PATH" UserPromptSubmit >/dev/null
213
- printf '{"hook_event_name":"SessionEnd","reason":"other","session_id":"codex-source","cwd":"%s"}\n' "$tmp_dir" | PLUGIN_DATA="$codex_clear_data_dir" node "$HOOK_PATH" SessionEnd >/dev/null
214
- if [[ -d "$codex_clear_data_dir/session-modes/claude-clear" ]]; then
215
- fail "Codex SessionEnd should not stage Claude clear state"
216
- fi
217
- rm -rf "$codex_clear_data_dir" "$claude_data_dir"
218
-
219
- reset_output="$(printf '{"hook_event_name":"UserPromptSubmit","session_id":"%s","cwd":"%s","prompt":"Reset the session mode."}\n' "$session_id" "$tmp_dir" | PLUGIN_DATA="$session_data_dir" node "$HOOK_PATH" UserPromptSubmit)"
220
- assert_contains "$reset_output" 'Session override: cleared' "session mode reset"
221
- assert_contains "$reset_output" 'Effective mode: `workflow` (configured default, active)' "session mode reset"
222
- config_after="$(shasum -a 256 "$tmp_dir/.freeflow/config.json" | awk '{print $1}')"
223
- [[ "$config_before" == "$config_after" ]] || fail "session mode controls modified repository config"
224
- if find "$session_data_dir" -type f -print | grep -Fq "$session_id"; then
225
- fail "raw session id leaked into the session-state path"
226
- fi
227
- rm -rf "$session_data_dir"
93
+ assert_contains "$claude_output" "# Freeflow Interaction Contract" "Claude core context"
94
+ assert_contains "$claude_output" "## Shared Terms" "Claude core context"
95
+ assert_contains "$claude_output" "Freeflow: active" "Claude core context"
228
96
 
97
+ # Every supported lifecycle start receives the same mandatory core surface.
229
98
  for source in startup resume clear compact; do
230
- codex_event_output="$(printf '{"hook_event_name":"SessionStart","source":"%s","cwd":"%s","model":"gpt-5"}\n' "$source" "$tmp_dir" | node "$HOOK_PATH" SessionStart)"
231
- claude_event_output="$(printf '{"hook_event_name":"SessionStart","source":"%s","cwd":"%s"}\n' "$source" "$tmp_dir" | node "$HOOK_PATH" SessionStart)"
232
- for host in codex claude; do
233
- if [[ "$host" == "codex" ]]; then
234
- event_output="$codex_event_output"
235
- else
236
- event_output="$claude_event_output"
237
- assert_contains "$event_output" '"hookEventName":"SessionStart"' "Claude $source wrapper"
238
- fi
239
- assert_contains "$event_output" "# Freeflow Interaction Contract" "$host $source SessionStart context"
240
- assert_contains "$event_output" "# Freeflow Workflow Bootstrap" "$host $source SessionStart context"
241
- contract_count="$(grep -Fo '# Freeflow Interaction Contract' <<<"$event_output" | wc -l | tr -d ' ')"
242
- bootstrap_count="$(grep -Fo '# Freeflow Workflow Bootstrap' <<<"$event_output" | wc -l | tr -d ' ')"
243
- [[ "$contract_count" == "1" ]] || fail "$host $source SessionStart should load the Interaction Contract exactly once"
244
- [[ "$bootstrap_count" == "1" ]] || fail "$host $source SessionStart should load Workflow exactly once"
245
- done
99
+ output="$(printf '{"hook_event_name":"SessionStart","source":"%s","cwd":"%s"}\n' "$source" "$tmp_dir" | node "$HOOK_PATH" SessionStart)"
100
+ assert_contains "$output" "# Freeflow Stable Guidance" "$source SessionStart context"
101
+ assert_contains "$output" "# Freeflow Interaction Contract" "$source SessionStart context"
102
+ assert_contains "$output" "Freeflow: active" "$source SessionStart context"
246
103
  done
247
104
 
248
- # Repo-owned host instructions are neither required for activation nor modified by the read-only hook.
249
- printf '# Repo agents\n\nKeep this guidance.\n' >"$tmp_dir/AGENTS.md"
250
- printf '# Repo Claude\n\nKeep this guidance too.\n' >"$tmp_dir/CLAUDE.md"
251
- cp "$tmp_dir/AGENTS.md" "$tmp_dir/AGENTS.before"
252
- cp "$tmp_dir/CLAUDE.md" "$tmp_dir/CLAUDE.before"
253
- host_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$tmp_dir" | node "$HOOK_PATH" SessionStart)"
254
- assert_contains "$host_output" "# Freeflow Interaction Contract" "host-independent activation"
255
- cmp -s "$tmp_dir/AGENTS.before" "$tmp_dir/AGENTS.md" || fail "hook modified AGENTS.md"
256
- cmp -s "$tmp_dir/CLAUDE.before" "$tmp_dir/CLAUDE.md" || fail "hook modified CLAUDE.md"
105
+ # Pi-only configuration remains isolated from the shared non-Pi hook.
106
+ cognitive_dir="$(mktemp -d)"
107
+ mkdir -p "$cognitive_dir/.freeflow"
108
+ printf '{"cognitiveRouting":{"enabled":true}}\n' >"$cognitive_dir/.freeflow/config.json"
109
+ cognitive_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$cognitive_dir" | node "$HOOK_PATH" SessionStart)"
110
+ assert_contains "$cognitive_output" "# Freeflow Stable Guidance" "Pi-only capability tolerance"
111
+ assert_not_contains "$cognitive_output" "Cognitive Routing" "non-Pi capability isolation"
112
+ rm -rf "$cognitive_dir"
113
+
114
+ # Old mode and core-toggle configuration is a breaking invalidation, not a compatibility path.
115
+ for obsolete_config in \
116
+ '{"defaultMode":"workflow"}' \
117
+ '{"interactionContract":false}' \
118
+ '{"skills":{"enabled":false}}'; do
119
+ obsolete_dir="$(mktemp -d)"
120
+ mkdir -p "$obsolete_dir/.freeflow"
121
+ printf '%s\n' "$obsolete_config" >"$obsolete_dir/.freeflow/config.json"
122
+ obsolete_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$obsolete_dir" | node "$HOOK_PATH" SessionStart)"
123
+ [[ -z "$obsolete_output" ]] || fail "obsolete config should not activate: $obsolete_config"
124
+ rm -rf "$obsolete_dir"
125
+ done
126
+
127
+ # No prompt-submit or clear-transfer path may create or consume legacy session state.
128
+ state_dir="$(mktemp -d)"
129
+ printf '{"hook_event_name":"UserPromptSubmit","session_id":"old-session","cwd":"%s","prompt":"Switch to conversation mode."}\n' "$tmp_dir" |
130
+ PLUGIN_DATA="$state_dir" node "$HOOK_PATH" UserPromptSubmit >/dev/null
131
+ printf '{"hook_event_name":"SessionEnd","reason":"clear","session_id":"old-session","cwd":"%s"}\n' "$tmp_dir" |
132
+ PLUGIN_DATA="$state_dir" node "$HOOK_PATH" SessionEnd >/dev/null
133
+ [[ ! -e "$state_dir/session-modes" ]] || fail "legacy session-mode state directory was created"
134
+ rm -rf "$state_dir"
257
135
 
258
- # Repository config remains the only activation boundary; local config only overrides it.
136
+ # Missing, invalid, disabled, and invalid-local activation remain inert.
259
137
  missing_dir="$(mktemp -d)"
260
138
  invalid_dir="$(mktemp -d)"
261
139
  mkdir -p "$invalid_dir/.freeflow"
@@ -266,110 +144,38 @@ invalid_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$invalid_dir" | node
266
144
  [[ -z "$invalid_output" ]] || fail "invalid config should not activate runtime context"
267
145
  rm -rf "$missing_dir" "$invalid_dir"
268
146
 
269
- local_dir="$(mktemp -d)"
270
- mkdir -p "$local_dir/.freeflow"
271
- printf '{"defaultMode":"workflow"}\n' >"$local_dir/.freeflow/config.json"
272
- printf '{"defaultMode":"strict-workflow"}\n' >"$local_dir/.freeflow/local.json"
273
- local_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$local_dir" | node "$HOOK_PATH" SessionStart)"
274
- assert_contains "$local_output" 'Configured default: `strict-workflow` (personal)' "local default override"
275
- assert_contains "$local_output" 'Effective mode: `strict-workflow` (configured default, active)' "local default source"
276
- assert_contains "$local_output" "## Strict Workflow Overlay" "local strict overlay"
277
- assert_contains "$local_output" "security, privacy, billing, data loss, migrations, public interfaces" "local strict overlay"
278
- assert_contains "$local_output" "# Freeflow Interaction Contract" "local default override"
279
- assert_contains "$local_output" "# Freeflow Workflow Bootstrap" "local default override"
280
- rm -rf "$local_dir"
281
-
282
- local_interaction_dir="$(mktemp -d)"
283
- mkdir -p "$local_interaction_dir/.freeflow"
284
- printf '{"defaultMode":"workflow"}\n' >"$local_interaction_dir/.freeflow/config.json"
285
- printf '{"interactionContract":false}\n' >"$local_interaction_dir/.freeflow/local.json"
286
- local_interaction_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$local_interaction_dir" | node "$HOOK_PATH" SessionStart)"
287
- assert_contains "$local_interaction_output" 'Interaction Contract: disabled' "independent local interaction override"
288
- assert_contains "$local_interaction_output" 'Skills: enabled' "independent local interaction override"
289
- assert_not_contains "$local_interaction_output" "# Freeflow Interaction Contract" "independent local interaction override"
290
- assert_contains "$local_interaction_output" "# Freeflow Workflow Bootstrap" "independent local interaction override"
291
- rm -rf "$local_interaction_dir"
292
-
293
- local_skills_dir="$(mktemp -d)"
294
- mkdir -p "$local_skills_dir/.freeflow"
295
- printf '{"defaultMode":"workflow"}\n' >"$local_skills_dir/.freeflow/config.json"
296
- printf '{"defaultMode":"conversation","interactionContract":false,"skills":{"enabled":false}}\n' >"$local_skills_dir/.freeflow/local.json"
297
- local_skills_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$local_skills_dir" | node "$HOOK_PATH" SessionStart)"
298
- assert_contains "$local_skills_output" 'Resolved mode: `conversation` (dormant because Skills are disabled)' "local dormant mode"
299
- assert_contains "$local_skills_output" 'Interaction Contract: disabled' "local interaction override"
300
- assert_contains "$local_skills_output" 'Skills: disabled' "local skills override"
301
- assert_not_contains "$local_skills_output" "# Freeflow Interaction Contract" "local interaction override"
302
- assert_not_contains "$local_skills_output" "# Freeflow Workflow Bootstrap" "local skills override"
303
- rm -rf "$local_skills_dir"
147
+ disabled_dir="$(mktemp -d)"
148
+ mkdir -p "$disabled_dir/.freeflow"
149
+ printf '{"enabled":false}\n' >"$disabled_dir/.freeflow/config.json"
150
+ disabled_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$disabled_dir" | node "$HOOK_PATH" SessionStart)"
151
+ [[ -z "$disabled_output" ]] || fail "disabled Freeflow should suppress hook prompt delivery"
152
+ rm -rf "$disabled_dir"
304
153
 
305
154
  invalid_local_dir="$(mktemp -d)"
306
155
  mkdir -p "$invalid_local_dir/.freeflow"
307
- printf '{"defaultMode":"workflow"}\n' >"$invalid_local_dir/.freeflow/config.json"
308
- printf '{"skills":{"enabled":"no"}}\n' >"$invalid_local_dir/.freeflow/local.json"
156
+ printf '{}\n' >"$invalid_local_dir/.freeflow/config.json"
157
+ printf '{"enabled":"no"}\n' >"$invalid_local_dir/.freeflow/local.json"
309
158
  invalid_local_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$invalid_local_dir" | node "$HOOK_PATH" SessionStart)"
310
159
  [[ -z "$invalid_local_output" ]] || fail "invalid local config should fail closed"
311
160
  rm -rf "$invalid_local_dir"
312
161
 
162
+ # Personal disablement remains separate from shared activation.
313
163
  local_disabled_dir="$(mktemp -d)"
314
164
  mkdir -p "$local_disabled_dir/.freeflow"
315
- printf '{"enabled":true,"defaultMode":"workflow"}\n' >"$local_disabled_dir/.freeflow/config.json"
316
- printf '{"enabled":false,"processing":{"unsafeUnsandboxed":{"enabled":true}}}\n' >"$local_disabled_dir/.freeflow/local.json"
165
+ printf '{}\n' >"$local_disabled_dir/.freeflow/config.json"
166
+ printf '{"enabled":false}\n' >"$local_disabled_dir/.freeflow/local.json"
317
167
  local_disabled_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$local_disabled_dir" | node "$HOOK_PATH" SessionStart)"
318
- assert_contains "$local_disabled_output" "# Freeflow Disabled" "local master override"
319
- assert_contains "$local_disabled_output" 'Configured but inactive: `enabled` is false (personal).' "local master source"
320
- assert_not_contains "$local_disabled_output" "# Freeflow Interaction Contract" "local master override"
168
+ [[ -z "$local_disabled_output" ]] || fail "local master override should suppress hook prompt delivery"
321
169
  rm -rf "$local_disabled_dir"
322
170
 
323
- disabled_dir="$(mktemp -d)"
324
- mkdir -p "$disabled_dir/.freeflow"
325
- printf '{"enabled":false,"defaultMode":"workflow"}\n' >"$disabled_dir/.freeflow/config.json"
326
- disabled_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$disabled_dir" | node "$HOOK_PATH" SessionStart)"
327
- assert_contains "$disabled_output" "# Freeflow Disabled" "disabled context"
328
- assert_not_contains "$disabled_output" "# Freeflow Runtime Kernel" "disabled context"
329
- rm -rf "$disabled_dir"
330
-
331
- router_dir="$(mktemp -d)"
332
- mkdir -p "$router_dir/.freeflow"
333
- printf '{"defaultMode":"workflow","skills":{"enabled":false},"outputRouter":{"enabled":true}}\n' >"$router_dir/.freeflow/config.json"
334
- router_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$router_dir" | node "$HOOK_PATH" SessionStart)"
335
- assert_contains "$router_output" "Skills: disabled" "Codex context with legacy router config"
336
- assert_contains "$router_output" "# Freeflow Interaction Contract" "Codex context with legacy router config"
337
- assert_not_contains "$router_output" "# Freeflow Workflow Bootstrap" "Codex context with disabled skills"
338
- assert_not_contains "$router_output" "Output Router" "Codex context with legacy router config"
339
- assert_not_contains "$router_output" "Output router:" "Codex context with legacy router config"
340
- rm -rf "$router_dir"
341
-
342
- all_dir="$(mktemp -d)"
343
- mkdir -p "$all_dir/.freeflow"
344
- printf '{"defaultMode":"strict-workflow","outputRouter":{"enabled":true}}\n' >"$all_dir/.freeflow/config.json"
345
- all_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$all_dir" | node "$HOOK_PATH" SessionStart)"
346
- assert_contains "$all_output" "# Freeflow Interaction Contract" "Codex strict context"
347
- assert_contains "$all_output" "# Freeflow Workflow Bootstrap" "Codex strict context"
348
- assert_not_contains "$all_output" "Output Router" "Codex strict context"
349
- assert_not_contains "$all_output" "Output router:" "Codex strict context"
350
- assert_contains "$all_output" 'Effective mode: `strict-workflow` (configured default, active)' "Codex strict context"
351
- assert_contains "$all_output" "## Strict Workflow Overlay" "all-capabilities strict overlay"
352
- rm -rf "$all_dir"
353
-
354
- conversation_dir="$(mktemp -d)"
355
- mkdir -p "$conversation_dir/.freeflow"
356
- printf '{"defaultMode":"conversation"}\n' >"$conversation_dir/.freeflow/config.json"
357
- conversation_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$conversation_dir" | node "$HOOK_PATH" SessionStart)"
358
- assert_contains "$conversation_output" 'Configured default: `conversation` (repository)' "conversation context"
359
- assert_contains "$conversation_output" 'Effective mode: `conversation` (configured default, active)' "conversation context"
360
- assert_contains "$conversation_output" "## Conversation Mode Boundary" "conversation overlay"
361
- assert_contains "$conversation_output" "Do not call write, edit, or mutating tools" "conversation overlay"
362
- assert_contains "$conversation_output" "an execution skill does not override this boundary" "conversation overlay"
363
- rm -rf "$conversation_dir"
364
-
365
- observed_dir="$(mktemp -d)"
366
- mkdir -p "$observed_dir/.freeflow"
367
- printf '{"defaultMode":"workflow","outputRouter":{"enabled":true,"observedRouting":{"enabled":true}},"scriptTransform":{"enabled":false}}\n' >"$observed_dir/.freeflow/config.json"
368
- observed_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$observed_dir" | node "$HOOK_PATH" SessionStart)"
369
- assert_contains "$observed_output" "# Freeflow Interaction Contract" "Codex context with observed-routing config"
370
- assert_contains "$observed_output" "# Freeflow Workflow Bootstrap" "Codex context with observed-routing config"
371
- assert_not_contains "$observed_output" "Output Router" "Codex context with observed-routing config"
372
- assert_not_contains "$observed_output" "Output router:" "Codex context with observed-routing config"
373
- rm -rf "$observed_dir"
171
+ # Repo-owned host instructions are not activation markers and are not modified.
172
+ printf '# Repo agents\n\nKeep this guidance.\n' >"$tmp_dir/AGENTS.md"
173
+ printf '# Repo Claude\n\nKeep this guidance too.\n' >"$tmp_dir/CLAUDE.md"
174
+ cp "$tmp_dir/AGENTS.md" "$tmp_dir/AGENTS.before"
175
+ cp "$tmp_dir/CLAUDE.md" "$tmp_dir/CLAUDE.before"
176
+ host_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$tmp_dir" | node "$HOOK_PATH" SessionStart)"
177
+ assert_contains "$host_output" "# Freeflow Interaction Contract" "host-independent activation"
178
+ cmp -s "$tmp_dir/AGENTS.before" "$tmp_dir/AGENTS.md" || fail "hook modified AGENTS.md"
179
+ cmp -s "$tmp_dir/CLAUDE.before" "$tmp_dir/CLAUDE.md" || fail "hook modified CLAUDE.md"
374
180
 
375
181
  echo "runtime-context hook checks passed"