@hassangameryt/freeflow 0.2.0 → 0.4.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 (312) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +4 -4
  3. package/{plugins/freeflow/.claude-plugin → .claude-plugin}/plugin.json +5 -2
  4. package/{plugins/freeflow/.codex-plugin → .codex-plugin}/plugin.json +8 -9
  5. package/README.md +194 -127
  6. package/assets/feedback-loop.png +0 -0
  7. package/assets/freeflow-plugin-icon.png +0 -0
  8. package/assets/freeflow_workflow_map.png +0 -0
  9. package/assets/workflow.png +0 -0
  10. package/command-surface.json +129 -0
  11. package/hooks/freeflow-runtime-context.mjs +419 -0
  12. package/hooks/tests/check-runtime-context-hook.sh +223 -0
  13. package/package.json +41 -22
  14. package/pi-extension/dist/index.js +269 -0
  15. package/pi-extension/dist/integrations/mcp-config.js +29 -0
  16. package/pi-extension/dist/output-router/host-producer-identification.js +232 -0
  17. package/pi-extension/dist/output-router/native-safety-net.js +216 -0
  18. package/pi-extension/dist/output-router/observed-tool-routing.js +122 -0
  19. package/pi-extension/dist/output-router/renderers.js +1023 -0
  20. package/pi-extension/dist/output-router/router-tools.js +554 -0
  21. package/pi-extension/dist/output-router/schemas.js +383 -0
  22. package/pi-extension/dist/output-router/status.js +744 -0
  23. package/pi-extension/dist/output-router/utils.js +502 -0
  24. package/pi-extension/dist/runtime/runtime-context.js +798 -0
  25. package/pi-extension/dist/settings/settings-tui.js +314 -0
  26. package/pi-extension/dist/settings/settings-ui.js +1487 -0
  27. package/pi-extension/freeflow/index.js +1 -0
  28. package/pi-extension/src/index.ts +295 -0
  29. package/pi-extension/src/integrations/mcp-config.ts +32 -0
  30. package/pi-extension/src/output-router/host-producer-identification.ts +256 -0
  31. package/pi-extension/src/output-router/native-safety-net.ts +248 -0
  32. package/pi-extension/src/output-router/observed-tool-routing.ts +134 -0
  33. package/pi-extension/src/output-router/renderers.ts +1108 -0
  34. package/pi-extension/src/output-router/router-tools.ts +579 -0
  35. package/pi-extension/src/output-router/schemas.ts +387 -0
  36. package/pi-extension/src/output-router/status.ts +791 -0
  37. package/pi-extension/src/output-router/utils.ts +554 -0
  38. package/pi-extension/src/runtime/runtime-context.ts +895 -0
  39. package/pi-extension/src/settings/settings-tui.ts +408 -0
  40. package/pi-extension/src/settings/settings-ui.ts +1730 -0
  41. package/pi-extension/tests/integration/pi-extension.test.js +4018 -0
  42. package/pi-extension/tests/output-router/observed-producers.test.js +132 -0
  43. package/pi-extension/tests/output-router/search.test.js +722 -0
  44. package/pi-extension/tests/settings/settings-tui.test.js +243 -0
  45. package/pi-extension/tsconfig.json +16 -0
  46. package/{plugins/freeflow/router/dist → router/dist/benchmarks}/benchmark-harness.d.ts +16 -13
  47. package/router/dist/benchmarks/benchmark-harness.js +92 -0
  48. package/router/dist/benchmarks/benchmarks.d.ts +109 -0
  49. package/router/dist/benchmarks/benchmarks.js +979 -0
  50. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +95 -0
  51. package/router/dist/benchmarks/codex-qa-benchmarks.js +500 -0
  52. package/router/dist/benchmarks/command-benchmarks.d.ts +143 -0
  53. package/router/dist/benchmarks/command-benchmarks.js +698 -0
  54. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +82 -0
  55. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +773 -0
  56. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +132 -0
  57. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +1380 -0
  58. package/router/dist/benchmarks/context-saving-baseline.d.ts +60 -0
  59. package/router/dist/benchmarks/context-saving-baseline.js +470 -0
  60. package/router/dist/benchmarks/index-benchmarks.d.ts +103 -0
  61. package/router/dist/benchmarks/index-benchmarks.js +862 -0
  62. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +101 -0
  63. package/router/dist/benchmarks/storage-policy-benchmarks.js +618 -0
  64. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +56 -0
  65. package/router/dist/benchmarks/vault-index-benchmarks.js +322 -0
  66. package/router/dist/config/config.d.ts +103 -0
  67. package/router/dist/config/config.js +632 -0
  68. package/{plugins/freeflow/router/dist → router/dist/config}/router-contract.d.ts +2 -2
  69. package/router/dist/config/router-contract.js +54 -0
  70. package/{plugins/freeflow/router/dist → router/dist/config}/schema.d.ts +19 -10
  71. package/router/dist/config/schema.js +648 -0
  72. package/router/dist/config/types.d.ts +573 -0
  73. package/router/dist/config/types.js +41 -0
  74. package/router/dist/evidence/bounded-evidence.d.ts +40 -0
  75. package/router/dist/evidence/bounded-evidence.js +323 -0
  76. package/router/dist/evidence/evidence-range-selector.d.ts +24 -0
  77. package/router/dist/evidence/evidence-range-selector.js +232 -0
  78. package/router/dist/evidence/evidence-search.d.ts +27 -0
  79. package/router/dist/evidence/evidence-search.js +490 -0
  80. package/{plugins/freeflow/router/dist → router/dist/evidence}/evidence.d.ts +18 -18
  81. package/router/dist/evidence/evidence.js +200 -0
  82. package/router/dist/evidence/failure-contracts.d.ts +37 -0
  83. package/router/dist/evidence/failure-contracts.js +166 -0
  84. package/router/dist/evidence/line-ranges.d.ts +24 -0
  85. package/router/dist/evidence/line-ranges.js +22 -0
  86. package/router/dist/experiments/local-index.d.ts +64 -0
  87. package/router/dist/experiments/local-index.js +581 -0
  88. package/router/dist/index.d.ts +30 -0
  89. package/router/dist/index.js +32 -0
  90. package/router/dist/local/local-traversal.d.ts +18 -0
  91. package/router/dist/local/local-traversal.js +334 -0
  92. package/router/dist/processing/engine.d.ts +132 -0
  93. package/router/dist/processing/engine.js +533 -0
  94. package/router/dist/processing/reducers.d.ts +280 -0
  95. package/router/dist/processing/reducers.js +1673 -0
  96. package/router/dist/processing/renderers.d.ts +31 -0
  97. package/router/dist/processing/renderers.js +195 -0
  98. package/router/dist/processing/scripts.d.ts +91 -0
  99. package/router/dist/processing/scripts.js +386 -0
  100. package/{plugins/freeflow/router/dist → router/dist/repo}/repo-traversal.d.ts +9 -9
  101. package/router/dist/repo/repo-traversal.js +245 -0
  102. package/router/dist/routing/observed-reducers.d.ts +41 -0
  103. package/router/dist/routing/observed-reducers.js +661 -0
  104. package/router/dist/routing/observed-routing.d.ts +26 -0
  105. package/router/dist/routing/observed-routing.js +378 -0
  106. package/router/dist/routing/parsers.d.ts +16 -0
  107. package/router/dist/routing/parsers.js +521 -0
  108. package/router/dist/routing/run-filters.d.ts +60 -0
  109. package/router/dist/routing/run-filters.js +238 -0
  110. package/router/dist/routing/run-reducers.d.ts +49 -0
  111. package/router/dist/routing/run-reducers.js +122 -0
  112. package/router/dist/sandbox/adapter-roots.d.ts +46 -0
  113. package/router/dist/sandbox/adapter-roots.js +66 -0
  114. package/router/dist/sandbox/eryx-python-adapter.d.ts +13 -0
  115. package/router/dist/sandbox/eryx-python-adapter.js +868 -0
  116. package/router/dist/sandbox/jq-wasm-adapter.d.ts +9 -0
  117. package/router/dist/sandbox/jq-wasm-adapter.js +429 -0
  118. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +13 -0
  119. package/router/dist/sandbox/quickjs-wasi-adapter.js +384 -0
  120. package/router/dist/sandbox/script-sandbox.d.ts +134 -0
  121. package/router/dist/sandbox/script-sandbox.js +337 -0
  122. package/router/dist/setup/script-transform-adapters.d.ts +32 -0
  123. package/router/dist/setup/script-transform-adapters.js +278 -0
  124. package/router/dist/tools/batch.d.ts +33 -0
  125. package/router/dist/tools/batch.js +586 -0
  126. package/router/dist/tools/run.d.ts +62 -0
  127. package/router/dist/tools/run.js +1304 -0
  128. package/router/dist/tools/search.d.ts +66 -0
  129. package/router/dist/tools/search.js +1364 -0
  130. package/router/dist/transform/engine.d.ts +168 -0
  131. package/router/dist/transform/engine.js +2072 -0
  132. package/router/dist/vault/vault-index.d.ts +125 -0
  133. package/router/dist/vault/vault-index.js +416 -0
  134. package/router/dist/vault/vault.d.ts +138 -0
  135. package/router/dist/vault/vault.js +630 -0
  136. package/runtime/interaction-contract.md +16 -0
  137. package/skills/bypass/SKILL.md +82 -0
  138. package/skills/commit-work/SKILL.md +106 -0
  139. package/skills/commit-work/references/staging-decisions.md +73 -0
  140. package/skills/decision-gate/SKILL.md +48 -0
  141. package/skills/design-for-depth/SKILL.md +106 -0
  142. package/skills/design-for-depth/references/design-pressure-signals.md +314 -0
  143. package/skills/design-for-depth/references/interface-design-loop.md +156 -0
  144. package/skills/design-for-depth/references/software-design-philosophy.md +248 -0
  145. package/skills/diagnose-failure/SKILL.md +105 -0
  146. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +36 -0
  147. package/skills/diagnose-failure/references/flaky-and-performance.md +43 -0
  148. package/skills/discuss/SKILL.md +121 -0
  149. package/skills/discuss/references/checkpoints.md +66 -0
  150. package/skills/evaluate-skill/SKILL.md +105 -0
  151. package/skills/evaluate-skill/references/definition-schema.md +345 -0
  152. package/skills/evaluate-skill/references/evaluation-design.md +86 -0
  153. package/skills/evaluate-skill/references/execution-and-evidence.md +85 -0
  154. package/skills/evaluate-skill/references/review-and-revision.md +51 -0
  155. package/skills/evaluate-skill/scripts/lib/definitions.mjs +296 -0
  156. package/skills/evaluate-skill/scripts/lib/evidence.mjs +46 -0
  157. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +60 -0
  158. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +148 -0
  159. package/skills/evaluate-skill/scripts/lib/grade-support.mjs +126 -0
  160. package/skills/evaluate-skill/scripts/lib/grade-workspace.mjs +192 -0
  161. package/skills/evaluate-skill/scripts/lib/grade.mjs +56 -0
  162. package/skills/evaluate-skill/scripts/lib/pi.mjs +489 -0
  163. package/skills/evaluate-skill/scripts/lib/process.mjs +79 -0
  164. package/skills/evaluate-skill/scripts/lib/rpc.mjs +444 -0
  165. package/skills/evaluate-skill/scripts/lib/runner.mjs +346 -0
  166. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +409 -0
  167. package/skills/evaluate-skill/scripts/lib/view.mjs +337 -0
  168. package/skills/evaluate-skill/scripts/pi-guard.mjs +164 -0
  169. package/skills/evaluate-skill/scripts/skill-eval.mjs +101 -0
  170. package/skills/execute-work/SKILL.md +99 -0
  171. package/skills/execute-work/references/code-practices.md +78 -0
  172. package/skills/execute-work/references/execution-loop.md +77 -0
  173. package/skills/finish-branch/SKILL.md +117 -0
  174. package/skills/finish-branch/references/integration-options.md +80 -0
  175. package/skills/handoff/SKILL.md +92 -0
  176. package/skills/handoff/references/templates.md +141 -0
  177. package/skills/launch-work/SKILL.md +116 -0
  178. package/skills/launch-work/references/launch-readiness.md +85 -0
  179. package/skills/migration-work/SKILL.md +115 -0
  180. package/skills/migration-work/references/migration-lifecycle.md +82 -0
  181. package/skills/mode-contract/SKILL.md +82 -0
  182. package/skills/output-router/SKILL.md +206 -0
  183. package/skills/output-router/references/safety-policy.md +40 -0
  184. package/skills/release-work/SKILL.md +118 -0
  185. package/skills/release-work/references/release-evidence.md +78 -0
  186. package/skills/review-artifact/SKILL.md +126 -0
  187. package/skills/review-artifact/references/reviewer-prompt.md +148 -0
  188. package/skills/review-work/SKILL.md +126 -0
  189. package/skills/review-work/references/reviewer-prompt.md +134 -0
  190. package/skills/review-work/references/security-risk-lens.md +67 -0
  191. package/skills/setup-freeflow/SKILL.md +98 -0
  192. package/skills/setup-freeflow/references/activation-contract.md +120 -0
  193. package/skills/setup-freeflow/references/host-setup.md +72 -0
  194. package/skills/setup-freeflow/references/output-router-setup.md +110 -0
  195. package/skills/simplify-code/SKILL.md +80 -0
  196. package/skills/simplify-code/references/simplification-patterns.md +70 -0
  197. package/skills/tdd/SKILL.md +96 -0
  198. package/skills/tdd/references/test-design.md +96 -0
  199. package/skills/track-work/SKILL.md +84 -0
  200. package/skills/track-work/references/working-record-schema.md +378 -0
  201. package/skills/verify-work/SKILL.md +85 -0
  202. package/skills/verify-work/references/browser-runtime-evidence.md +55 -0
  203. package/skills/verify-work/references/integration-evidence.md +51 -0
  204. package/skills/verify-work/references/performance-evidence.md +57 -0
  205. package/skills/workflow/SKILL.md +91 -0
  206. package/skills/workflow/references/domain-skill-composition.md +57 -0
  207. package/skills/workflow/references/workflow-loop.md +92 -0
  208. package/skills/write-plan/SKILL.md +123 -0
  209. package/skills/write-plan/references/plan-shapes.md +156 -0
  210. package/skills/write-skill/SKILL.md +119 -0
  211. package/skills/write-skill/references/activation-boundaries.md +62 -0
  212. package/skills/write-skill/references/agent-first-instructions.md +87 -0
  213. package/skills/write-skill/references/progressive-disclosure.md +63 -0
  214. package/skills/write-skill/scripts/lib/skill-author.mjs +653 -0
  215. package/skills/write-skill/scripts/skill-author.mjs +104 -0
  216. package/skills/write-spec/SKILL.md +104 -0
  217. package/skills/write-spec/references/artifact-standards.md +86 -0
  218. package/skills/write-spec/references/decision-records.md +77 -0
  219. package/skills/write-spec/references/spec-shapes.md +128 -0
  220. package/plugins/freeflow/command-surface.json +0 -77
  221. package/plugins/freeflow/docs/README.md +0 -40
  222. package/plugins/freeflow/docs/adr/0001-three-modes.md +0 -27
  223. package/plugins/freeflow/docs/adr/0002-evals-before-hooks.md +0 -17
  224. package/plugins/freeflow/docs/adr/0003-release-boundary.md +0 -21
  225. package/plugins/freeflow/docs/adr/0004-research-replaces-shallow-discovery-skills.md +0 -26
  226. package/plugins/freeflow/docs/adr/README.md +0 -8
  227. package/plugins/freeflow/docs/architecture.md +0 -86
  228. package/plugins/freeflow/docs/output-router.md +0 -212
  229. package/plugins/freeflow/docs/release-evidence.md +0 -69
  230. package/plugins/freeflow/docs/skills.md +0 -43
  231. package/plugins/freeflow/docs/workflow.md +0 -57
  232. package/plugins/freeflow/hooks/freeflow-runtime-context.mjs +0 -257
  233. package/plugins/freeflow/pi-extension/index.js +0 -1176
  234. package/plugins/freeflow/router/dist/benchmark-harness.js +0 -92
  235. package/plugins/freeflow/router/dist/benchmarks.d.ts +0 -102
  236. package/plugins/freeflow/router/dist/benchmarks.js +0 -880
  237. package/plugins/freeflow/router/dist/bounded-evidence.d.ts +0 -40
  238. package/plugins/freeflow/router/dist/bounded-evidence.js +0 -310
  239. package/plugins/freeflow/router/dist/codex-qa-benchmarks.d.ts +0 -91
  240. package/plugins/freeflow/router/dist/codex-qa-benchmarks.js +0 -461
  241. package/plugins/freeflow/router/dist/command-benchmarks.d.ts +0 -128
  242. package/plugins/freeflow/router/dist/command-benchmarks.js +0 -652
  243. package/plugins/freeflow/router/dist/config.d.ts +0 -22
  244. package/plugins/freeflow/router/dist/config.js +0 -114
  245. package/plugins/freeflow/router/dist/evidence-range-selector.d.ts +0 -22
  246. package/plugins/freeflow/router/dist/evidence-range-selector.js +0 -180
  247. package/plugins/freeflow/router/dist/evidence-search.d.ts +0 -25
  248. package/plugins/freeflow/router/dist/evidence-search.js +0 -450
  249. package/plugins/freeflow/router/dist/evidence.js +0 -196
  250. package/plugins/freeflow/router/dist/experimental-local-index.d.ts +0 -1
  251. package/plugins/freeflow/router/dist/experimental-local-index.js +0 -1
  252. package/plugins/freeflow/router/dist/experiments/local-index.d.ts +0 -58
  253. package/plugins/freeflow/router/dist/experiments/local-index.js +0 -562
  254. package/plugins/freeflow/router/dist/index-benchmarks.d.ts +0 -84
  255. package/plugins/freeflow/router/dist/index-benchmarks.js +0 -458
  256. package/plugins/freeflow/router/dist/index.d.ts +0 -12
  257. package/plugins/freeflow/router/dist/index.js +0 -12
  258. package/plugins/freeflow/router/dist/line-ranges.d.ts +0 -22
  259. package/plugins/freeflow/router/dist/line-ranges.js +0 -22
  260. package/plugins/freeflow/router/dist/parsers.d.ts +0 -16
  261. package/plugins/freeflow/router/dist/parsers.js +0 -473
  262. package/plugins/freeflow/router/dist/repo-traversal.js +0 -242
  263. package/plugins/freeflow/router/dist/retrieve.d.ts +0 -33
  264. package/plugins/freeflow/router/dist/retrieve.js +0 -855
  265. package/plugins/freeflow/router/dist/router-contract.js +0 -54
  266. package/plugins/freeflow/router/dist/run.d.ts +0 -26
  267. package/plugins/freeflow/router/dist/run.js +0 -356
  268. package/plugins/freeflow/router/dist/schema.js +0 -354
  269. package/plugins/freeflow/router/dist/types.d.ts +0 -234
  270. package/plugins/freeflow/router/dist/types.js +0 -9
  271. package/plugins/freeflow/router/dist/vault.d.ts +0 -80
  272. package/plugins/freeflow/router/dist/vault.js +0 -484
  273. package/plugins/freeflow/skills/bypass/SKILL.md +0 -68
  274. package/plugins/freeflow/skills/commit-work/SKILL.md +0 -98
  275. package/plugins/freeflow/skills/commit-work/references/staging-decisions.md +0 -51
  276. package/plugins/freeflow/skills/diagnose-failure/SKILL.md +0 -95
  277. package/plugins/freeflow/skills/diagnose-failure/references/feedback-loop-catalog.md +0 -25
  278. package/plugins/freeflow/skills/diagnose-failure/references/flaky-and-performance.md +0 -31
  279. package/plugins/freeflow/skills/evaluate-skill/SKILL.md +0 -96
  280. package/plugins/freeflow/skills/evaluate-skill/references/eval-patterns.md +0 -41
  281. package/plugins/freeflow/skills/evaluate-skill/references/grading-priority.md +0 -33
  282. package/plugins/freeflow/skills/execute-plan/SKILL.md +0 -132
  283. package/plugins/freeflow/skills/execute-plan/references/execution-map.md +0 -122
  284. package/plugins/freeflow/skills/handoff/SKILL.md +0 -66
  285. package/plugins/freeflow/skills/handoff/references/templates.md +0 -64
  286. package/plugins/freeflow/skills/interview-gate/SKILL.md +0 -90
  287. package/plugins/freeflow/skills/mode-contract/SKILL.md +0 -79
  288. package/plugins/freeflow/skills/output-router/SKILL.md +0 -69
  289. package/plugins/freeflow/skills/output-router/references/safety-policy.md +0 -33
  290. package/plugins/freeflow/skills/research/ARTIFACT-DESTINATIONS.md +0 -58
  291. package/plugins/freeflow/skills/research/CHECKPOINTS.md +0 -74
  292. package/plugins/freeflow/skills/research/LANGUAGE.md +0 -61
  293. package/plugins/freeflow/skills/research/SKILL.md +0 -128
  294. package/plugins/freeflow/skills/review-artifact/SKILL.md +0 -115
  295. package/plugins/freeflow/skills/review-artifact/references/reviewer-prompt.md +0 -56
  296. package/plugins/freeflow/skills/review-work/SKILL.md +0 -96
  297. package/plugins/freeflow/skills/review-work/references/reviewer-prompt.md +0 -97
  298. package/plugins/freeflow/skills/setup-freeflow/SKILL.md +0 -94
  299. package/plugins/freeflow/skills/setup-freeflow/references/activation-contract.md +0 -99
  300. package/plugins/freeflow/skills/setup-freeflow/references/host-setup.md +0 -35
  301. package/plugins/freeflow/skills/setup-freeflow/references/output-router-setup.md +0 -52
  302. package/plugins/freeflow/skills/verify-work/SKILL.md +0 -62
  303. package/plugins/freeflow/skills/workflow/SKILL.md +0 -95
  304. package/plugins/freeflow/skills/workflow/references/workflow-map.md +0 -102
  305. package/plugins/freeflow/skills/write-plan/SKILL.md +0 -88
  306. package/plugins/freeflow/skills/write-plan/references/plan-shapes.md +0 -70
  307. package/plugins/freeflow/skills/write-skill/SKILL.md +0 -88
  308. package/plugins/freeflow/skills/write-spec/SKILL.md +0 -90
  309. package/plugins/freeflow/skills/write-spec/references/artifact-standards.md +0 -67
  310. package/plugins/freeflow/skills/write-spec/references/spec-shapes.md +0 -69
  311. /package/{plugins/freeflow/assets → assets}/freeflow-plugin-icon-transparent.png +0 -0
  312. /package/{plugins/freeflow/hooks → hooks}/hooks.json +0 -0
@@ -0,0 +1,419 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import process from "node:process";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
9
+ const PLUGIN_ROOT = path.resolve(SCRIPT_DIR, "..");
10
+ const VALID_MODES = new Set(["conversation", "workflow", "strict-workflow"]);
11
+
12
+ function readStdinJson() {
13
+ const raw = fs.readFileSync(0, "utf8").trim();
14
+ if (!raw) {
15
+ return { input: {}, raw: "" };
16
+ }
17
+
18
+ try {
19
+ return { input: JSON.parse(raw), raw };
20
+ } catch {
21
+ return { input: {}, raw };
22
+ }
23
+ }
24
+
25
+ function readText(filePath) {
26
+ try {
27
+ return fs.readFileSync(filePath, "utf8");
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ function findWorkspaceRoot(cwd) {
34
+ let current = path.resolve(cwd || process.cwd());
35
+
36
+ while (true) {
37
+ if (fs.existsSync(path.join(current, ".git"))) {
38
+ return current;
39
+ }
40
+
41
+ const parent = path.dirname(current);
42
+ if (parent === current) {
43
+ return path.resolve(cwd || process.cwd());
44
+ }
45
+ current = parent;
46
+ }
47
+ }
48
+
49
+ function loadRuntimeContext(options = {}) {
50
+ const includeInteractionContract = options.interactionContract === true;
51
+ const includeSkills = options.skills === true;
52
+ const includeOutputRouter = options.outputRouter === true;
53
+ const interactionContract = includeInteractionContract
54
+ ? readText(path.join(PLUGIN_ROOT, "runtime", "interaction-contract.md"))
55
+ : null;
56
+ const workflowSkill = includeSkills ? readText(path.join(PLUGIN_ROOT, "skills", "workflow", "SKILL.md")) : null;
57
+ const outputRouterSkill = includeOutputRouter
58
+ ? readText(path.join(PLUGIN_ROOT, "skills", "output-router", "SKILL.md"))
59
+ : null;
60
+
61
+ if (
62
+ (includeInteractionContract && !interactionContract) ||
63
+ (includeSkills && !workflowSkill) ||
64
+ (includeOutputRouter && !outputRouterSkill)
65
+ ) {
66
+ throw new Error("Freeflow runtime context files are missing.");
67
+ }
68
+
69
+ return { interactionContract, workflowSkill, outputRouterSkill };
70
+ }
71
+
72
+ function readConfig(root) {
73
+ const repository = readConfigLayer(path.join(root, ".freeflow", "config.json"), isValidSetupConfig);
74
+ const local = readConfigLayer(path.join(root, ".freeflow", "local.json"), isValidLocalConfig);
75
+ const localValid = !local.exists || local.valid;
76
+ const configured = repository.valid && localValid;
77
+ const core = resolveCoreConfig(repository.valid ? repository.parsed : {}, local.valid ? local.parsed : {});
78
+ const enabled = configured && core.config.enabled;
79
+ const repositoryConfig = repository.valid ? repository.parsed : {};
80
+
81
+ return {
82
+ exists: repository.exists,
83
+ valid: configured,
84
+ configured,
85
+ repositoryValid: repository.valid,
86
+ localExists: local.exists,
87
+ localValid,
88
+ error: !repository.valid ? repository.error : local.error,
89
+ enabled,
90
+ interactionContractEnabled: enabled && core.config.interactionContract,
91
+ skillsEnabled: enabled && core.config.skills.enabled,
92
+ defaultMode: core.config.defaultMode,
93
+ sources: core.sources,
94
+ outputRouterEnabled: enabled && repositoryConfig?.outputRouter?.enabled === true,
95
+ };
96
+ }
97
+
98
+ function readConfigLayer(filePath, validate) {
99
+ if (!fs.existsSync(filePath)) {
100
+ return { path: filePath, exists: false, valid: false, parsed: {}, error: null };
101
+ }
102
+
103
+ try {
104
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
105
+ const error = validate(parsed);
106
+ return {
107
+ path: filePath,
108
+ exists: true,
109
+ valid: error === null,
110
+ parsed: error === null ? parsed : {},
111
+ error,
112
+ };
113
+ } catch (error) {
114
+ return {
115
+ path: filePath,
116
+ exists: true,
117
+ valid: false,
118
+ parsed: {},
119
+ error: error instanceof Error ? error.message : String(error),
120
+ };
121
+ }
122
+ }
123
+
124
+ function isRecord(value) {
125
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
126
+ }
127
+
128
+ function validateCoreConfigFields(value) {
129
+ if (Object.prototype.hasOwnProperty.call(value, "defaultMode") && !VALID_MODES.has(value.defaultMode)) {
130
+ return "defaultMode must be conversation, workflow, or strict-workflow";
131
+ }
132
+ if (Object.prototype.hasOwnProperty.call(value, "enabled") && typeof value.enabled !== "boolean") {
133
+ return "enabled must be a boolean";
134
+ }
135
+ if (
136
+ Object.prototype.hasOwnProperty.call(value, "interactionContract") &&
137
+ typeof value.interactionContract !== "boolean"
138
+ ) {
139
+ return "interactionContract must be a boolean";
140
+ }
141
+ if (Object.prototype.hasOwnProperty.call(value, "skills")) {
142
+ if (!isRecord(value.skills)) {
143
+ return "skills must be an object";
144
+ }
145
+ if (Object.prototype.hasOwnProperty.call(value.skills, "enabled") && typeof value.skills.enabled !== "boolean") {
146
+ return "skills.enabled must be a boolean";
147
+ }
148
+ }
149
+ return null;
150
+ }
151
+
152
+ function isValidSetupConfig(value) {
153
+ if (!isRecord(value)) {
154
+ return "repository config must be a JSON object";
155
+ }
156
+
157
+ const allowedKeys = new Set([
158
+ "enabled",
159
+ "defaultMode",
160
+ "interactionContract",
161
+ "skills",
162
+ "outputRouter",
163
+ "observedRouting",
164
+ "scriptTransform",
165
+ ]);
166
+ if (!Object.keys(value).every((key) => allowedKeys.has(key))) {
167
+ return "repository config contains unsupported top-level keys";
168
+ }
169
+
170
+ const coreError = validateCoreConfigFields(value);
171
+ if (coreError) {
172
+ return coreError;
173
+ }
174
+
175
+ for (const key of ["outputRouter", "observedRouting", "scriptTransform"]) {
176
+ if (Object.prototype.hasOwnProperty.call(value, key) && !isRecord(value[key])) {
177
+ return `${key} must be an object`;
178
+ }
179
+ }
180
+ return null;
181
+ }
182
+
183
+ function isValidLocalConfig(value) {
184
+ if (!isRecord(value)) {
185
+ return "local config must be a JSON object";
186
+ }
187
+
188
+ const allowedKeys = new Set(["enabled", "defaultMode", "interactionContract", "skills", "processing"]);
189
+ if (!Object.keys(value).every((key) => allowedKeys.has(key))) {
190
+ return "local config contains unsupported top-level keys";
191
+ }
192
+ return validateCoreConfigFields(value);
193
+ }
194
+
195
+ function hasOwn(value, key) {
196
+ return Object.prototype.hasOwnProperty.call(value, key);
197
+ }
198
+
199
+ function resolveLayeredValue(repository, local, key, fallback) {
200
+ if (hasOwn(local, key)) {
201
+ return { value: local[key], source: "local" };
202
+ }
203
+ if (hasOwn(repository, key)) {
204
+ return { value: repository[key], source: "repository" };
205
+ }
206
+ return { value: fallback, source: "builtin" };
207
+ }
208
+
209
+ function resolveCoreConfig(repository, local) {
210
+ const enabled = resolveLayeredValue(repository, local, "enabled", true);
211
+ const interactionContract = resolveLayeredValue(repository, local, "interactionContract", true);
212
+ const defaultMode = resolveLayeredValue(repository, local, "defaultMode", "workflow");
213
+ const repositorySkills = isRecord(repository.skills) ? repository.skills : {};
214
+ const localSkills = isRecord(local.skills) ? local.skills : {};
215
+ const skillsEnabled = resolveLayeredValue(repositorySkills, localSkills, "enabled", true);
216
+
217
+ return {
218
+ config: {
219
+ enabled: enabled.value,
220
+ interactionContract: interactionContract.value,
221
+ skills: { enabled: skillsEnabled.value },
222
+ defaultMode: defaultMode.value,
223
+ },
224
+ sources: {
225
+ enabled: enabled.source,
226
+ interactionContract: interactionContract.source,
227
+ skillsEnabled: skillsEnabled.source,
228
+ defaultMode: defaultMode.source,
229
+ },
230
+ };
231
+ }
232
+
233
+ function inspectSetup(root) {
234
+ return { config: readConfig(root) };
235
+ }
236
+
237
+ function configStatus(config) {
238
+ if (!config.valid) {
239
+ return "invalid layered configuration";
240
+ }
241
+ const source = config.sources.defaultMode;
242
+ const personal = config.localExists ? "; personal overrides present" : "";
243
+ return `defaultMode \`${config.defaultMode}\` from ${source}${personal}`;
244
+ }
245
+
246
+ function modeOverlayGuidance(mode) {
247
+ if (mode === "conversation") {
248
+ return [
249
+ "",
250
+ "## Conversation Mode Boundary",
251
+ "",
252
+ "Conversation mode is active and read-only.",
253
+ "",
254
+ "Do not call write, edit, or mutating tools. Do not create, delete, commit, push, or change repository, system, external, or durable state.",
255
+ "",
256
+ "A mutation request does not switch mode, and an execution skill does not override this boundary. Explain that conversation mode is read-only and ask the user to switch to workflow or strict-workflow.",
257
+ ];
258
+ }
259
+
260
+ if (mode === "strict-workflow") {
261
+ return [
262
+ "",
263
+ "## Strict Workflow Overlay",
264
+ "",
265
+ "Strict Workflow is active. Use the adaptive Workflow, but increase decision, evidence, verification, and checkpoint pressure at high-risk or hard-to-reverse boundaries.",
266
+ "",
267
+ "For work affecting security, privacy, billing, data loss, migrations, public interfaces, compatibility, deployment, or architecture:",
268
+ "",
269
+ "- stop for any user-owned choice or source conflict;",
270
+ "- inspect the relevant risk surface before crossing the boundary;",
271
+ "- select only artifacts, checkpoints, and independent review that materially reduce risk;",
272
+ "- verify at the affected boundary before claiming success.",
273
+ "",
274
+ "Do not manufacture ceremony for low-risk, reversible work. Strict Workflow does not authorize mutation, bypass safety, or make every implementation detail a user decision.",
275
+ ];
276
+ }
277
+
278
+ return [];
279
+ }
280
+
281
+ function modeGuidance(mode) {
282
+ return [
283
+ `Current Freeflow default mode: \`${mode}\`.`,
284
+ "Treat this as the resolved default at session start, resume, clear, and compact.",
285
+ "Mode behavior:",
286
+ "- `conversation`: answer, discuss, critique, and inspect read-only; switch modes before mutating state.",
287
+ "- `workflow`: use the adaptive workflow for normal consequential work.",
288
+ "- `strict-workflow`: strengthen user-owned decisions, review, and verification for high-risk or hard-to-reverse work.",
289
+ "Do not announce the current mode on every reply. Mention it when the user asks, setup/config is discussed, or the mode changes the next action.",
290
+ ...modeOverlayGuidance(mode),
291
+ ];
292
+ }
293
+
294
+ function buildSetupStatus(root) {
295
+ const { config } = inspectSetup(root);
296
+ if (!config.valid) {
297
+ return "Setup status: Freeflow is not configured for this repo.";
298
+ }
299
+
300
+ const modeStatus = config.skillsEnabled
301
+ ? modeGuidance(config.defaultMode)
302
+ : [`Resolved default mode: \`${config.defaultMode}\` (dormant because Skills are disabled).`];
303
+ return [
304
+ `Setup status: configured by \`.freeflow/config.json\` with ${configStatus(config)}.`,
305
+ "Runtime delivery: confirmed for this lifecycle-hook invocation.",
306
+ ...modeStatus,
307
+ ].join("\n");
308
+ }
309
+
310
+ function shouldInject(eventName) {
311
+ if (process.env.FREEFLOW_DISABLE_RUNTIME_CONTEXT === "1") {
312
+ return false;
313
+ }
314
+
315
+ return eventName === "SessionStart";
316
+ }
317
+
318
+ function capabilityStatus(config) {
319
+ return [
320
+ "## Freeflow Capabilities",
321
+ "",
322
+ `- Interaction Contract: ${config.interactionContractEnabled ? "enabled" : "disabled"}.`,
323
+ `- Skills: ${config.skillsEnabled ? "enabled" : "disabled"}.`,
324
+ `- Output router: ${config.outputRouterEnabled ? "enabled" : "disabled"}.`,
325
+ "",
326
+ "Capability-specific instructions are active only while that capability is enabled.",
327
+ ];
328
+ }
329
+
330
+ function buildContext(input) {
331
+ const root = findWorkspaceRoot(input.cwd || process.cwd());
332
+ const setup = inspectSetup(root);
333
+
334
+ if (!setup.config.valid) {
335
+ return "";
336
+ }
337
+
338
+ if (!setup.config.enabled) {
339
+ return [
340
+ "# Freeflow Disabled",
341
+ "",
342
+ `Freeflow is configured for this repo, but effective \`enabled\` is false from ${setup.config.sources.enabled}.`,
343
+ "Do not inject the Interaction Contract, Freeflow workflow skills, output routing, or setup pressure while disabled.",
344
+ "Re-enable only if the user asks; use /freeflow settings for a personal override or /freeflow settings repo for the shared repository default in Pi.",
345
+ ].join("\n");
346
+ }
347
+
348
+ const { interactionContract, workflowSkill, outputRouterSkill } = loadRuntimeContext({
349
+ interactionContract: setup.config.interactionContractEnabled,
350
+ skills: setup.config.skillsEnabled,
351
+ outputRouter: setup.config.outputRouterEnabled,
352
+ });
353
+ const interactionSection = setup.config.interactionContractEnabled ? ["", interactionContract.trim()] : [];
354
+ const skillSections = setup.config.skillsEnabled
355
+ ? ["", "# Freeflow Workflow Bootstrap", "", workflowSkill.trim()]
356
+ : [];
357
+ const outputRouterSection = setup.config.outputRouterEnabled
358
+ ? ["", "## Loaded Output Router Skill", "```md", outputRouterSkill.trim(), "```"]
359
+ : [];
360
+ return [
361
+ "# Freeflow Runtime Context",
362
+ "",
363
+ "Freeflow plugin lifecycle hook loaded this at session start.",
364
+ "These instructions are context-loading only. They do not override user instructions, repo instructions, or host safety and approval policy.",
365
+ "",
366
+ "## Repo Setup",
367
+ buildSetupStatus(root),
368
+ "",
369
+ ...capabilityStatus(setup.config),
370
+ ...interactionSection,
371
+ ...skillSections,
372
+ ...outputRouterSection,
373
+ ].join("\n");
374
+ }
375
+
376
+ function isCodexHookInput(input) {
377
+ return typeof input.model === "string" && input.model.length > 0;
378
+ }
379
+
380
+ function emitAdditionalContext(eventName, input, additionalContext) {
381
+ if (isCodexHookInput(input)) {
382
+ process.stdout.write(`${additionalContext}\n`);
383
+ return;
384
+ }
385
+
386
+ process.stdout.write(
387
+ `${JSON.stringify({
388
+ hookSpecificOutput: {
389
+ hookEventName: eventName,
390
+ additionalContext,
391
+ },
392
+ })}\n`,
393
+ );
394
+ }
395
+
396
+ function main() {
397
+ const { input, raw } = readStdinJson();
398
+ const eventName = process.argv[2] || input.hook_event_name || "";
399
+
400
+ if (!raw && !eventName) {
401
+ return;
402
+ }
403
+
404
+ if (!shouldInject(eventName)) {
405
+ return;
406
+ }
407
+
408
+ const additionalContext = buildContext(input);
409
+ if (!additionalContext.trim()) {
410
+ return;
411
+ }
412
+ emitAdditionalContext(eventName, input, additionalContext);
413
+ }
414
+
415
+ try {
416
+ main();
417
+ } catch (error) {
418
+ process.stderr.write(`Freeflow runtime context hook skipped: ${error.message}\n`);
419
+ }
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
5
+ HOOK_PATH="$ROOT_DIR/hooks/freeflow-runtime-context.mjs"
6
+ HOOKS_JSON="$ROOT_DIR/hooks/hooks.json"
7
+ WORKFLOW_SKILL="$ROOT_DIR/skills/workflow/SKILL.md"
8
+
9
+ fail() {
10
+ echo "runtime-context hook check failed: $*" >&2
11
+ exit 1
12
+ }
13
+
14
+ assert_contains() {
15
+ local text="$1"
16
+ local expected="$2"
17
+ local label="$3"
18
+ grep -Fq -- "$expected" <<<"$text" || fail "$label missing '$expected'"
19
+ }
20
+
21
+ 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
28
+ }
29
+
30
+ [[ -f "$HOOK_PATH" ]] || fail "missing hook script"
31
+ [[ -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"
37
+ node --check "$HOOK_PATH"
38
+ node -e '
39
+ const fs = require("fs");
40
+ const hooks = JSON.parse(fs.readFileSync(process.argv[1], "utf8")).hooks || {};
41
+ if (!Array.isArray(hooks.SessionStart) || hooks.SessionStart.length === 0) process.exit(1);
42
+ if (hooks.SessionStart[0]?.matcher !== "startup|resume|clear|compact") process.exit(1);
43
+ ' "$HOOKS_JSON" || fail "hooks.json does not register the expected SessionStart lifecycle matcher"
44
+
45
+ tmp_dir="$(mktemp -d)"
46
+ trap 'rm -rf "$tmp_dir"' EXIT
47
+ mkdir -p "$tmp_dir/.freeflow"
48
+ printf '{"defaultMode":"workflow"}\n' >"$tmp_dir/.freeflow/config.json"
49
+
50
+ codex_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$tmp_dir" | node "$HOOK_PATH" SessionStart)"
51
+ disabled_by_env="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$tmp_dir" | FREEFLOW_DISABLE_RUNTIME_CONTEXT=1 node "$HOOK_PATH" SessionStart)"
52
+ [[ -z "$disabled_by_env" ]] || fail "FREEFLOW_DISABLE_RUNTIME_CONTEXT=1 should suppress injection"
53
+
54
+ post_tool_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$tmp_dir" | node "$HOOK_PATH" PostToolUse)"
55
+ [[ -z "$post_tool_output" ]] || fail "PostToolUse should not inject runtime context"
56
+ user_prompt_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$tmp_dir" | node "$HOOK_PATH" UserPromptSubmit)"
57
+ [[ -z "$user_prompt_output" ]] || fail "UserPromptSubmit should not duplicate session-start runtime context"
58
+
59
+ for expected in \
60
+ "# Freeflow Runtime Context" \
61
+ 'Setup status: configured by `.freeflow/config.json`' \
62
+ "Runtime delivery: confirmed for this lifecycle-hook invocation." \
63
+ "# Freeflow Interaction Contract" \
64
+ "Answer questions without inferring action." \
65
+ "# Freeflow Workflow Bootstrap" \
66
+ "Use feedback to choose the smallest useful next action." \
67
+ "$workflow_owner_line" \
68
+ "$workflow_self_review_line" \
69
+ 'Current Freeflow default mode: `workflow`.' \
70
+ 'Interaction Contract: enabled' \
71
+ 'Skills: enabled' \
72
+ 'Output router: disabled'; do
73
+ assert_contains "$codex_output" "$expected" "Codex config-only context"
74
+ done
75
+ for excluded_heading in \
76
+ "# Freeflow Runtime Kernel" \
77
+ "## Freeflow Runtime Priority" \
78
+ "## Loaded Mode Contract Skill" \
79
+ "## Loaded Workflow Skill" \
80
+ "## Loaded Decision Gate Skill" \
81
+ "## Discovery-light" \
82
+ "## Loaded Output Router Skill"; do
83
+ assert_not_contains "$codex_output" "$excluded_heading" "default context"
84
+ done
85
+ workflow_bootstrap_count="$(grep -Fo '# Freeflow Workflow Bootstrap' <<<"$codex_output" | wc -l | tr -d ' ')"
86
+ [[ "$workflow_bootstrap_count" == "1" ]] || fail "Codex SessionStart should load Workflow exactly once"
87
+ assert_not_contains "$codex_output" "## Conversation Mode Boundary" "workflow context"
88
+ assert_not_contains "$codex_output" "## Strict Workflow Overlay" "workflow context"
89
+
90
+ claude_output="$(printf '{"hook_event_name":"SessionStart","source":"startup","cwd":"%s"}\n' "$tmp_dir" | node "$HOOK_PATH" SessionStart)"
91
+ assert_contains "$claude_output" '"hookEventName":"SessionStart"' "Claude wrapper"
92
+ assert_contains "$claude_output" "# Freeflow Interaction Contract" "Claude config-only context"
93
+ assert_contains "$claude_output" "# Freeflow Workflow Bootstrap" "Claude first-turn context"
94
+ assert_contains "$claude_output" "Answer questions without inferring action." "Claude config-only context"
95
+
96
+ for source in startup resume clear compact; do
97
+ event_output="$(printf '{"hook_event_name":"SessionStart","source":"%s","cwd":"%s","model":"gpt-5"}\n' "$source" "$tmp_dir" | node "$HOOK_PATH" SessionStart)"
98
+ assert_contains "$event_output" "# Freeflow Interaction Contract" "$source SessionStart context"
99
+ done
100
+
101
+ # Repo-owned host instructions are neither required for activation nor modified by the read-only hook.
102
+ printf '# Repo agents\n\nKeep this guidance.\n' >"$tmp_dir/AGENTS.md"
103
+ printf '# Repo Claude\n\nKeep this guidance too.\n' >"$tmp_dir/CLAUDE.md"
104
+ cp "$tmp_dir/AGENTS.md" "$tmp_dir/AGENTS.before"
105
+ cp "$tmp_dir/CLAUDE.md" "$tmp_dir/CLAUDE.before"
106
+ host_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$tmp_dir" | node "$HOOK_PATH" SessionStart)"
107
+ assert_contains "$host_output" "# Freeflow Interaction Contract" "host-independent activation"
108
+ cmp -s "$tmp_dir/AGENTS.before" "$tmp_dir/AGENTS.md" || fail "hook modified AGENTS.md"
109
+ cmp -s "$tmp_dir/CLAUDE.before" "$tmp_dir/CLAUDE.md" || fail "hook modified CLAUDE.md"
110
+
111
+ # Repository config remains the only activation boundary; local config only overrides it.
112
+ missing_dir="$(mktemp -d)"
113
+ invalid_dir="$(mktemp -d)"
114
+ mkdir -p "$invalid_dir/.freeflow"
115
+ printf '{invalid\n' >"$invalid_dir/.freeflow/config.json"
116
+ missing_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$missing_dir" | node "$HOOK_PATH" SessionStart)"
117
+ invalid_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$invalid_dir" | node "$HOOK_PATH" SessionStart)"
118
+ [[ -z "$missing_output" ]] || fail "missing config should not activate runtime context"
119
+ [[ -z "$invalid_output" ]] || fail "invalid config should not activate runtime context"
120
+ rm -rf "$missing_dir" "$invalid_dir"
121
+
122
+ local_dir="$(mktemp -d)"
123
+ mkdir -p "$local_dir/.freeflow"
124
+ printf '{"defaultMode":"workflow"}\n' >"$local_dir/.freeflow/config.json"
125
+ printf '{"defaultMode":"strict-workflow"}\n' >"$local_dir/.freeflow/local.json"
126
+ local_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$local_dir" | node "$HOOK_PATH" SessionStart)"
127
+ assert_contains "$local_output" 'Current Freeflow default mode: `strict-workflow`.' "local default override"
128
+ assert_contains "$local_output" 'defaultMode `strict-workflow` from local' "local default source"
129
+ assert_contains "$local_output" "## Strict Workflow Overlay" "local strict overlay"
130
+ assert_contains "$local_output" "security, privacy, billing, data loss, migrations, public interfaces" "local strict overlay"
131
+ assert_contains "$local_output" "# Freeflow Interaction Contract" "local default override"
132
+ assert_contains "$local_output" "# Freeflow Workflow Bootstrap" "local default override"
133
+ rm -rf "$local_dir"
134
+
135
+ local_interaction_dir="$(mktemp -d)"
136
+ mkdir -p "$local_interaction_dir/.freeflow"
137
+ printf '{"defaultMode":"workflow"}\n' > "$local_interaction_dir/.freeflow/config.json"
138
+ printf '{"interactionContract":false}\n' > "$local_interaction_dir/.freeflow/local.json"
139
+ local_interaction_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$local_interaction_dir" | node "$HOOK_PATH" SessionStart)"
140
+ assert_contains "$local_interaction_output" 'Interaction Contract: disabled' "independent local interaction override"
141
+ assert_contains "$local_interaction_output" 'Skills: enabled' "independent local interaction override"
142
+ assert_not_contains "$local_interaction_output" "# Freeflow Interaction Contract" "independent local interaction override"
143
+ assert_contains "$local_interaction_output" "# Freeflow Workflow Bootstrap" "independent local interaction override"
144
+ rm -rf "$local_interaction_dir"
145
+
146
+ local_skills_dir="$(mktemp -d)"
147
+ mkdir -p "$local_skills_dir/.freeflow"
148
+ printf '{"defaultMode":"workflow"}\n' >"$local_skills_dir/.freeflow/config.json"
149
+ printf '{"defaultMode":"conversation","interactionContract":false,"skills":{"enabled":false}}\n' >"$local_skills_dir/.freeflow/local.json"
150
+ local_skills_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$local_skills_dir" | node "$HOOK_PATH" SessionStart)"
151
+ assert_contains "$local_skills_output" 'Resolved default mode: `conversation` (dormant because Skills are disabled).' "local dormant mode"
152
+ assert_contains "$local_skills_output" 'Interaction Contract: disabled' "local interaction override"
153
+ assert_contains "$local_skills_output" 'Skills: disabled' "local skills override"
154
+ assert_not_contains "$local_skills_output" "# Freeflow Interaction Contract" "local interaction override"
155
+ assert_not_contains "$local_skills_output" "# Freeflow Workflow Bootstrap" "local skills override"
156
+ rm -rf "$local_skills_dir"
157
+
158
+ invalid_local_dir="$(mktemp -d)"
159
+ mkdir -p "$invalid_local_dir/.freeflow"
160
+ printf '{"defaultMode":"workflow"}\n' >"$invalid_local_dir/.freeflow/config.json"
161
+ printf '{"skills":{"enabled":"no"}}\n' >"$invalid_local_dir/.freeflow/local.json"
162
+ invalid_local_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$invalid_local_dir" | node "$HOOK_PATH" SessionStart)"
163
+ [[ -z "$invalid_local_output" ]] || fail "invalid local config should fail closed"
164
+ rm -rf "$invalid_local_dir"
165
+
166
+ local_disabled_dir="$(mktemp -d)"
167
+ mkdir -p "$local_disabled_dir/.freeflow"
168
+ printf '{"enabled":true,"defaultMode":"workflow"}\n' >"$local_disabled_dir/.freeflow/config.json"
169
+ printf '{"enabled":false,"processing":{"unsafeUnsandboxed":{"enabled":true}}}\n' >"$local_disabled_dir/.freeflow/local.json"
170
+ local_disabled_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$local_disabled_dir" | node "$HOOK_PATH" SessionStart)"
171
+ assert_contains "$local_disabled_output" "# Freeflow Disabled" "local master override"
172
+ assert_contains "$local_disabled_output" "effective \`enabled\` is false from local" "local master source"
173
+ assert_not_contains "$local_disabled_output" "# Freeflow Interaction Contract" "local master override"
174
+ rm -rf "$local_disabled_dir"
175
+
176
+ disabled_dir="$(mktemp -d)"
177
+ mkdir -p "$disabled_dir/.freeflow"
178
+ printf '{"enabled":false,"defaultMode":"workflow"}\n' >"$disabled_dir/.freeflow/config.json"
179
+ disabled_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$disabled_dir" | node "$HOOK_PATH" SessionStart)"
180
+ assert_contains "$disabled_output" "# Freeflow Disabled" "disabled context"
181
+ assert_not_contains "$disabled_output" "# Freeflow Runtime Kernel" "disabled context"
182
+ rm -rf "$disabled_dir"
183
+
184
+ router_dir="$(mktemp -d)"
185
+ mkdir -p "$router_dir/.freeflow"
186
+ printf '{"defaultMode":"workflow","skills":{"enabled":false},"outputRouter":{"enabled":true}}\n' >"$router_dir/.freeflow/config.json"
187
+ router_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$router_dir" | node "$HOOK_PATH" SessionStart)"
188
+ assert_contains "$router_output" "Skills: disabled" "router-only context"
189
+ assert_contains "$router_output" "## Loaded Output Router Skill" "router-only context"
190
+ assert_contains "$router_output" "# Freeflow Interaction Contract" "router-only context"
191
+ assert_not_contains "$router_output" "# Freeflow Workflow Bootstrap" "router-only context"
192
+ rm -rf "$router_dir"
193
+
194
+ all_dir="$(mktemp -d)"
195
+ mkdir -p "$all_dir/.freeflow"
196
+ printf '{"defaultMode":"strict-workflow","outputRouter":{"enabled":true}}\n' >"$all_dir/.freeflow/config.json"
197
+ all_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$all_dir" | node "$HOOK_PATH" SessionStart)"
198
+ assert_contains "$all_output" "# Freeflow Interaction Contract" "all-capabilities context"
199
+ assert_contains "$all_output" "## Loaded Output Router Skill" "all-capabilities context"
200
+ assert_contains "$all_output" 'Current Freeflow default mode: `strict-workflow`.' "all-capabilities context"
201
+ assert_contains "$all_output" "## Strict Workflow Overlay" "all-capabilities strict overlay"
202
+ rm -rf "$all_dir"
203
+
204
+ conversation_dir="$(mktemp -d)"
205
+ mkdir -p "$conversation_dir/.freeflow"
206
+ printf '{"defaultMode":"conversation"}\n' >"$conversation_dir/.freeflow/config.json"
207
+ conversation_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$conversation_dir" | node "$HOOK_PATH" SessionStart)"
208
+ assert_contains "$conversation_output" 'Current Freeflow default mode: `conversation`.' "conversation context"
209
+ assert_contains "$conversation_output" '`conversation`: answer, discuss, critique, and inspect read-only' "conversation context"
210
+ assert_contains "$conversation_output" "## Conversation Mode Boundary" "conversation overlay"
211
+ assert_contains "$conversation_output" "Do not call write, edit, or mutating tools" "conversation overlay"
212
+ assert_contains "$conversation_output" "an execution skill does not override this boundary" "conversation overlay"
213
+ rm -rf "$conversation_dir"
214
+
215
+ observed_dir="$(mktemp -d)"
216
+ mkdir -p "$observed_dir/.freeflow"
217
+ printf '{"defaultMode":"workflow","outputRouter":{"enabled":true,"observedRouting":{"enabled":true}},"scriptTransform":{"enabled":false}}\n' >"$observed_dir/.freeflow/config.json"
218
+ observed_output="$(printf '{"cwd":"%s","model":"gpt-5"}\n' "$observed_dir" | node "$HOOK_PATH" SessionStart)"
219
+ assert_contains "$observed_output" "# Freeflow Interaction Contract" "observed-routing config"
220
+ assert_contains "$observed_output" "## Loaded Output Router Skill" "observed-routing config"
221
+ rm -rf "$observed_dir"
222
+
223
+ echo "runtime-context hook checks passed"