@kontourai/flow-agents 2.2.0 → 2.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 (250) hide show
  1. package/.github/CODEOWNERS +8 -0
  2. package/.github/workflows/ci.yml +32 -0
  3. package/AGENTS.md +4 -4
  4. package/CHANGELOG.md +46 -0
  5. package/CONTEXT.md +14 -0
  6. package/README.md +9 -3
  7. package/agents/dev.json +1 -1
  8. package/agents/tool-code-reviewer.json +1 -1
  9. package/agents/tool-planner.json +3 -3
  10. package/agents/tool-verifier.json +3 -3
  11. package/build/src/cli/console-learning-projection.js +3 -2
  12. package/build/src/cli/init.js +104 -21
  13. package/build/src/cli/kit.js +37 -6
  14. package/build/src/cli/pull-work-provider.js +1 -1
  15. package/build/src/cli/usage-feedback.js +3 -3
  16. package/build/src/cli/validate-hook-influence.js +1 -0
  17. package/build/src/cli/validate-workflow-artifacts.js +22 -6
  18. package/build/src/cli/workflow-artifact-cleanup-audit.js +36 -2
  19. package/build/src/cli/workflow-sidecar.d.ts +14 -7
  20. package/build/src/cli/workflow-sidecar.js +792 -69
  21. package/build/src/flow-kit/validate.d.ts +23 -0
  22. package/build/src/flow-kit/validate.js +67 -1
  23. package/build/src/index.d.ts +1 -1
  24. package/build/src/index.js +1 -1
  25. package/build/src/lib/flow-resolver.d.ts +6 -4
  26. package/build/src/lib/flow-resolver.js +125 -58
  27. package/build/src/lib/local-artifact-root.d.ts +14 -2
  28. package/build/src/lib/local-artifact-root.js +22 -5
  29. package/build/src/lib/workflow-learning-projection.js +2 -2
  30. package/build/src/runtime-adapters.d.ts +12 -0
  31. package/build/src/runtime-adapters.js +56 -16
  32. package/build/src/tools/build-universal-bundles.js +49 -16
  33. package/build/src/tools/generate-context-map.js +10 -8
  34. package/build/src/tools/validate-source-tree.js +23 -1
  35. package/console.telemetry.json +22 -0
  36. package/context/contracts/artifact-contract.md +38 -7
  37. package/context/contracts/builder-kit-workflow-state-contract.md +3 -2
  38. package/context/contracts/decision-registry-contract.md +263 -0
  39. package/context/contracts/delivery-contract.md +1 -1
  40. package/context/contracts/planning-contract.md +2 -1
  41. package/context/contracts/review-contract.md +5 -3
  42. package/context/contracts/verification-contract.md +9 -1
  43. package/context/contracts/work-item-contract.md +1 -1
  44. package/context/deferred/parallelization.md +1 -1
  45. package/context/gate-awareness.md +4 -4
  46. package/context/scripts/git-status.sh +2 -2
  47. package/context/scripts/hooks/config-protection.js +502 -2
  48. package/context/scripts/hooks/lib/config-protection-remedies.js +69 -0
  49. package/context/scripts/hooks/run-hook.js +14 -1
  50. package/context/scripts/hooks/stop-goal-fit.js +1140 -99
  51. package/context/scripts/hooks/workflow-steering.js +212 -6
  52. package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
  53. package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +35 -0
  54. package/docs/adr/0019-kit-dependency-ownership.md +62 -0
  55. package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +290 -0
  56. package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +221 -0
  57. package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +283 -0
  58. package/docs/agent-system-guidebook.md +3 -3
  59. package/docs/agent-usage-feedback-loop.md +6 -6
  60. package/docs/context-map.md +9 -7
  61. package/docs/decisions/decision-records.md +57 -0
  62. package/docs/decisions/index.md +14 -0
  63. package/docs/decisions/promotion-gate.md +52 -0
  64. package/docs/developer-architecture.md +1 -1
  65. package/docs/fixture-ownership.md +3 -0
  66. package/docs/flow-kit-repository-contract.md +5 -5
  67. package/docs/getting-started.md +12 -2
  68. package/docs/integrations/harness-install.md +2 -1
  69. package/docs/kit-authoring-guide.md +64 -5
  70. package/docs/knowledge-kit.md +5 -1
  71. package/docs/learnings/2026-07-improvement-program.md +203 -0
  72. package/docs/migrations.md +6 -5
  73. package/docs/north-star.md +2 -2
  74. package/docs/operating-layers.md +4 -3
  75. package/docs/repository-structure.md +5 -5
  76. package/docs/skills-map.md +8 -8
  77. package/docs/spec/runtime-hook-surface.md +19 -15
  78. package/docs/spikes/graph-provider-2026-07.md +155 -0
  79. package/docs/standards-register.md +7 -7
  80. package/docs/trust-anchor-adoption.md +105 -4
  81. package/docs/work-item-adapters.md +1 -1
  82. package/docs/workflow-artifact-lifecycle.md +72 -9
  83. package/docs/workflow-eval-strategy.md +3 -3
  84. package/docs/workflow-shared-contracts.md +1 -1
  85. package/docs/workflow-usage-guide.md +254 -21
  86. package/evals/acceptance/DEMO-false-completion.md +20 -11
  87. package/evals/acceptance/prove-capture-teeth-declared.sh +15 -15
  88. package/evals/acceptance/prove-capture-teeth.sh +11 -11
  89. package/evals/acceptance/test_kiro_harness.sh +1 -1
  90. package/evals/ci/antigaming-suite.sh +5 -0
  91. package/evals/ci/run-baseline.sh +65 -1
  92. package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/flows/review.flow.json +26 -0
  93. package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/kit.json +13 -0
  94. package/evals/fixtures/flow-kit-repository/valid-with-dependency/flows/review.flow.json +26 -0
  95. package/evals/fixtures/flow-kit-repository/valid-with-dependency/kit.json +13 -0
  96. package/evals/fixtures/hook-influence/cases.json +32 -0
  97. package/evals/fixtures/pull-work-provider/github-issues.json +6 -6
  98. package/evals/fixtures/trust-reconcile-exploits/fabricated-attestation.json +59 -0
  99. package/evals/fixtures/trust-reconcile-exploits/no-label-bypass.json +36 -0
  100. package/evals/fixtures/trust-reconcile-exploits/skip-assumed-bypass.json +59 -0
  101. package/evals/fixtures/trust-reconcile-exploits/status-misassertion.json +48 -0
  102. package/evals/fixtures/trust-reconcile-exploits/waived-command-check.json +44 -0
  103. package/evals/fixtures/trust-reconcile-mixed-bundle/mixed-bundle.json +159 -0
  104. package/evals/fixtures/trust-reconcile-ws3/ws3-bundle.json +920 -0
  105. package/evals/integration/test_actor_identity.sh +254 -0
  106. package/evals/integration/test_builder_step_producers.sh +33 -23
  107. package/evals/integration/test_bundle_install.sh +22 -16
  108. package/evals/integration/test_bundle_lifecycle.sh +11 -11
  109. package/evals/integration/test_captured_fail_reconciliation.sh +50 -50
  110. package/evals/integration/test_checkpoint_signing.sh +43 -0
  111. package/evals/integration/test_claim_lookup.sh +4 -4
  112. package/evals/integration/test_codex_hook_resolution.sh +114 -0
  113. package/evals/integration/test_command_log_concurrency.sh +8 -8
  114. package/evals/integration/test_command_log_fork_classification.sh +3 -3
  115. package/evals/integration/test_command_log_integrity.sh +16 -16
  116. package/evals/integration/test_console_learning_projection.sh +1 -1
  117. package/evals/integration/test_dual_emit_flow_step.sh +145 -0
  118. package/evals/integration/test_enforcer_expects_driven.sh +12 -12
  119. package/evals/integration/test_evidence_capture_hook.sh +14 -13
  120. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  121. package/evals/integration/test_flow_agents_statusline.sh +2 -2
  122. package/evals/integration/test_flow_kit_install_git.sh +52 -0
  123. package/evals/integration/test_flow_kit_repository.sh +2 -0
  124. package/evals/integration/test_flowdef_session_activation.sh +6 -6
  125. package/evals/integration/test_flowdef_union_floor_regression.sh +429 -0
  126. package/evals/integration/test_gate_bypass_chain.sh +52 -40
  127. package/evals/integration/test_gate_lockdown.sh +97 -73
  128. package/evals/integration/test_gate_review_inquiry_records.sh +3 -3
  129. package/evals/integration/test_goal_fit_escape_hatch.sh +10 -10
  130. package/evals/integration/test_goal_fit_ghost_session.sh +104 -0
  131. package/evals/integration/test_goal_fit_hook.sh +32 -32
  132. package/evals/integration/test_goal_fit_rederive.sh +9 -9
  133. package/evals/integration/test_hook_category_behaviors.sh +103 -0
  134. package/evals/integration/test_hook_influence_cases.sh +1 -0
  135. package/evals/integration/test_install_merge.sh +262 -1
  136. package/evals/integration/test_kit_identity_trust.sh +6 -6
  137. package/evals/integration/test_liveness_conflict_injection.sh +587 -0
  138. package/evals/integration/test_liveness_heartbeat.sh +677 -0
  139. package/evals/integration/test_liveness_verdict.sh +394 -0
  140. package/evals/integration/test_local_flow_kit_install.sh +15 -0
  141. package/evals/integration/test_phase_map_and_gate_claim.sh +63 -11
  142. package/evals/integration/test_promote_gate.sh +178 -0
  143. package/evals/integration/test_publish_delivery.sh +2 -2
  144. package/evals/integration/test_pull_work_liveness_preflight.sh +386 -0
  145. package/evals/integration/test_pull_work_provider.sh +2 -2
  146. package/evals/integration/test_reconcile_soundness.sh +4 -4
  147. package/evals/integration/test_resolvefirststep_security.sh +22 -0
  148. package/evals/integration/test_runtime_adapter_activation.sh +87 -18
  149. package/evals/integration/test_session_resume_roundtrip.sh +147 -0
  150. package/evals/integration/test_sidecar_field_preservation.sh +249 -0
  151. package/evals/integration/test_trust_reconcile.sh +2 -2
  152. package/evals/integration/test_trust_reconcile_manifest.sh +79 -0
  153. package/evals/integration/test_trust_reconcile_mixed_bundle.sh +96 -0
  154. package/evals/integration/test_trust_reconcile_negatives.sh +151 -0
  155. package/evals/integration/test_usage_feedback_global.sh +5 -5
  156. package/evals/integration/test_validate_artifacts_portability.sh +60 -0
  157. package/evals/integration/test_verify_cli.sh +2 -2
  158. package/evals/integration/test_veritas_governance_kit.sh +117 -0
  159. package/evals/integration/test_workflow_artifact_cleanup_audit.sh +26 -1
  160. package/evals/integration/test_workflow_artifacts.sh +33 -0
  161. package/evals/integration/test_workflow_sidecar_writer.sh +723 -74
  162. package/evals/integration/test_workflow_steering_hook.sh +175 -5
  163. package/evals/lib/codex-provider.sh +1 -1
  164. package/evals/lib/node.sh +19 -1
  165. package/evals/run.sh +22 -0
  166. package/evals/static/test_decisions.sh +150 -0
  167. package/evals/static/test_flowdef_codeowners_coverage.sh +56 -0
  168. package/evals/static/test_package.sh +19 -10
  169. package/evals/static/test_universal_bundles.sh +48 -3
  170. package/evals/static/test_workflow_skills.sh +41 -8
  171. package/kits/builder/flows/build.flow.json +3 -74
  172. package/kits/builder/flows/publish-learn.flow.json +90 -0
  173. package/kits/builder/kit.json +11 -0
  174. package/kits/builder/skills/builder-shape/SKILL.md +1 -1
  175. package/kits/builder/skills/deliver/SKILL.md +13 -11
  176. package/kits/builder/skills/design-probe/SKILL.md +4 -4
  177. package/kits/builder/skills/evidence-gate/SKILL.md +26 -1
  178. package/kits/builder/skills/execute-plan/SKILL.md +2 -2
  179. package/kits/builder/skills/fix-bug/SKILL.md +2 -0
  180. package/kits/builder/skills/gate-review/SKILL.md +6 -6
  181. package/kits/builder/skills/idea-to-backlog/SKILL.md +1 -1
  182. package/kits/builder/skills/learning-review/SKILL.md +7 -7
  183. package/kits/builder/skills/pickup-probe/SKILL.md +5 -4
  184. package/kits/builder/skills/plan-work/SKILL.md +27 -5
  185. package/kits/builder/skills/pull-work/SKILL.md +80 -5
  186. package/kits/builder/skills/release-readiness/SKILL.md +4 -4
  187. package/kits/builder/skills/review-work/SKILL.md +24 -1
  188. package/kits/builder/skills/tdd-workflow/SKILL.md +2 -0
  189. package/kits/builder/skills/verify-work/SKILL.md +8 -1
  190. package/kits/catalog.json +6 -0
  191. package/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +178 -0
  192. package/kits/veritas-governance/docs/README.md +75 -0
  193. package/kits/veritas-governance/fixtures/readiness/not-ready.readiness-report.json +4645 -0
  194. package/kits/veritas-governance/fixtures/readiness/ready.readiness-report.json +4403 -0
  195. package/kits/veritas-governance/flows/readiness-check.flow.json +35 -0
  196. package/kits/veritas-governance/kit.json +16 -0
  197. package/package.json +8 -4
  198. package/packaging/README.md +2 -2
  199. package/packaging/conformance/fixtures/config-protection--allow-read-state-json.json +20 -0
  200. package/packaging/conformance/fixtures/config-protection--block-write-state-json.json +20 -0
  201. package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +1 -1
  202. package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +1 -1
  203. package/packaging/manifest.json +9 -10
  204. package/schemas/decision-record.schema.json +109 -0
  205. package/schemas/workflow-evidence.schema.json +1 -1
  206. package/schemas/workflow-state.schema.json +5 -0
  207. package/scripts/README.md +8 -3
  208. package/scripts/check-content-boundary.cjs +1 -1
  209. package/scripts/check-decisions.cjs +356 -0
  210. package/scripts/ci/derive-claim-status.mjs +74 -0
  211. package/scripts/ci/trust-reconcile.js +436 -79
  212. package/scripts/git-status.sh +2 -2
  213. package/scripts/hooks/claude-telemetry-hook.js +37 -3
  214. package/scripts/hooks/codex-telemetry-hook.js +36 -2
  215. package/scripts/hooks/config-protection.js +102 -30
  216. package/scripts/hooks/evidence-capture.js +3 -3
  217. package/scripts/hooks/lib/actor-identity.js +368 -0
  218. package/scripts/hooks/lib/config-protection-remedies.js +69 -0
  219. package/scripts/hooks/lib/liveness-heartbeat.js +361 -0
  220. package/scripts/hooks/lib/liveness-policy.js +127 -0
  221. package/scripts/hooks/lib/liveness-read.js +105 -12
  222. package/scripts/hooks/lib/liveness-write.js +46 -0
  223. package/scripts/hooks/lib/local-artifact-paths.js +12 -6
  224. package/scripts/hooks/opencode-telemetry-hook.js +27 -1
  225. package/scripts/hooks/pi-telemetry-hook.js +27 -1
  226. package/scripts/hooks/stop-goal-fit.js +157 -26
  227. package/scripts/hooks/workflow-steering.js +77 -3
  228. package/scripts/install-codex-home.sh +138 -38
  229. package/scripts/statusline/flow-agents-statusline.js +2 -2
  230. package/src/cli/console-learning-projection.ts +3 -2
  231. package/src/cli/init.ts +101 -21
  232. package/src/cli/kit.ts +37 -6
  233. package/src/cli/public-api.test.mjs +58 -6
  234. package/src/cli/pull-work-provider.ts +1 -1
  235. package/src/cli/trust-bundle-policy-order.test.mjs +87 -0
  236. package/src/cli/usage-feedback.ts +3 -3
  237. package/src/cli/validate-hook-influence.ts +1 -0
  238. package/src/cli/validate-workflow-artifacts.ts +22 -6
  239. package/src/cli/veritas-readiness-adapter.test.mjs +267 -0
  240. package/src/cli/workflow-artifact-cleanup-audit.ts +35 -2
  241. package/src/cli/workflow-sidecar.ts +795 -71
  242. package/src/flow-kit/validate.ts +74 -1
  243. package/src/index.ts +6 -2
  244. package/src/lib/flow-resolver.ts +123 -55
  245. package/src/lib/local-artifact-root.ts +24 -5
  246. package/src/lib/workflow-learning-projection.ts +2 -2
  247. package/src/runtime-adapters.ts +61 -15
  248. package/src/tools/build-universal-bundles.ts +49 -16
  249. package/src/tools/generate-context-map.ts +10 -8
  250. package/src/tools/validate-source-tree.ts +23 -1
@@ -32,11 +32,11 @@ if [ "$WORKTREE_COUNT" -gt 1 ]; then
32
32
  echo "=== Active Worktrees ($WORKTREE_COUNT) ==="
33
33
  git worktree list
34
34
  echo ""
35
- echo "⚠️ Multiple worktrees active — check .flow-agents/ for in-progress tasks before modifying shared files"
35
+ echo "⚠️ Multiple worktrees active — check .kontourai/flow-agents/ for in-progress tasks before modifying shared files"
36
36
  fi
37
37
 
38
38
  # List existing TODOs for awareness
39
- TODO_DIR=".flow-agents"
39
+ TODO_DIR=".kontourai/flow-agents"
40
40
  if [ -d "$TODO_DIR" ]; then
41
41
  TODO_COUNT=$(find "$TODO_DIR" -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
42
42
  if [ "$TODO_COUNT" -gt 0 ]; then
@@ -54,7 +54,7 @@ function canonicalEvent(cliEvent, payload) {
54
54
  return mapping[event] || event;
55
55
  }
56
56
 
57
- function claudeSuccessOutput(event) {
57
+ function claudeSuccessOutput(event, conflict) {
58
58
  if (event === 'SessionStart') {
59
59
  return {
60
60
  continue: true,
@@ -71,6 +71,26 @@ function claudeSuccessOutput(event) {
71
71
  if (event === 'Stop' || event === 'SubagentStop' || event === 'SessionEnd') {
72
72
  return { continue: true, suppressOutput: true };
73
73
  }
74
+ // Mid-turn conflict injection (issue #320, AC4): fold a detected liveness conflict into the
75
+ // real hookSpecificOutput.additionalContext channel on PostToolUse/PostToolUseFailure,
76
+ // matching the precedent claude-hook-adapter.js:70-79 already establishes for policy hooks.
77
+ // Guarded on a well-formed `conflict` shape so a malformed value degrades to the unchanged
78
+ // fixed no-conflict output below, never a thrown error (AC8, fail-open).
79
+ if (
80
+ (event === 'PostToolUse' || event === 'PostToolUseFailure') &&
81
+ conflict &&
82
+ typeof conflict.actor === 'string' &&
83
+ typeof conflict.lastAt === 'string'
84
+ ) {
85
+ return {
86
+ continue: true,
87
+ suppressOutput: false,
88
+ hookSpecificOutput: {
89
+ hookEventName: 'PostToolUse',
90
+ additionalContext: `[LIVENESS CONFLICT] actor "${conflict.actor}" claimed this subject at "${conflict.lastAt}" — run \`liveness verdict\` and coordinate.`,
91
+ },
92
+ };
93
+ }
74
94
  return { continue: true, suppressOutput: true };
75
95
  }
76
96
 
@@ -79,9 +99,23 @@ async function main() {
79
99
  const raw = await readStdinRaw();
80
100
  const payload = parseJson(raw);
81
101
  const hookEvent = payload.hook_event_name || eventArg;
102
+ const canonical = canonicalEvent(eventArg, payload);
82
103
  const telemetryScript = path.resolve(__dirname, '..', 'telemetry', 'telemetry.sh');
83
104
 
84
- const result = spawnSync('bash', [telemetryScript, canonicalEvent(eventArg, payload), agentName], {
105
+ let conflict;
106
+ if (canonical === 'postToolUse') {
107
+ try {
108
+ const heartbeatResult = require('./lib/liveness-heartbeat').maybeEmitHeartbeat({
109
+ cwd: process.cwd(),
110
+ env: process.env,
111
+ });
112
+ conflict = heartbeatResult && heartbeatResult.conflict;
113
+ } catch (err) {
114
+ process.stderr.write(`[ClaudeTelemetryHook] liveness heartbeat error: ${err.message}\n`);
115
+ }
116
+ }
117
+
118
+ const result = spawnSync('bash', [telemetryScript, canonical, agentName], {
85
119
  input: raw,
86
120
  encoding: 'utf8',
87
121
  cwd: process.cwd(),
@@ -106,7 +140,7 @@ async function main() {
106
140
  process.stderr.write(`[ClaudeTelemetryHook] failed open: ${detail}\n`);
107
141
  }
108
142
 
109
- process.stdout.write(`${JSON.stringify(claudeSuccessOutput(hookEvent))}\n`);
143
+ process.stdout.write(`${JSON.stringify(claudeSuccessOutput(hookEvent, conflict))}\n`);
110
144
  }
111
145
 
112
146
  main().catch(err => {
@@ -38,7 +38,7 @@ function hookEventName(raw) {
38
38
  }
39
39
  }
40
40
 
41
- function codexSuccessOutput(event) {
41
+ function codexSuccessOutput(event, conflict) {
42
42
  if (event === 'SessionStart') {
43
43
  return {
44
44
  continue: true,
@@ -48,6 +48,27 @@ function codexSuccessOutput(event) {
48
48
  },
49
49
  };
50
50
  }
51
+ // Mid-turn conflict injection (issue #320, AC4): fold a detected liveness conflict into the
52
+ // real hookSpecificOutput.additionalContext channel on PostToolUse, matching the precedent
53
+ // codex-hook-adapter.js:69-77 already establishes for policy hooks. Guarded on a well-formed
54
+ // `conflict` shape so a malformed value degrades to the unchanged fixed no-conflict output
55
+ // below, never a thrown error (AC8, fail-open). Subject to the "Codex live hook influence"
56
+ // caveat (docs/spec/runtime-hook-surface.md §2.1): the wrapper still emits this; effectiveness
57
+ // is a separate, already-tracked residual.
58
+ if (
59
+ event === 'PostToolUse' &&
60
+ conflict &&
61
+ typeof conflict.actor === 'string' &&
62
+ typeof conflict.lastAt === 'string'
63
+ ) {
64
+ return {
65
+ continue: true,
66
+ hookSpecificOutput: {
67
+ hookEventName: 'PostToolUse',
68
+ additionalContext: `[LIVENESS CONFLICT] actor "${conflict.actor}" claimed this subject at "${conflict.lastAt}" — run \`liveness verdict\` and coordinate.`,
69
+ },
70
+ };
71
+ }
51
72
  if (event === 'UserPromptSubmit' || event === 'Stop') {
52
73
  return { continue: true };
53
74
  }
@@ -60,6 +81,19 @@ async function main() {
60
81
  const event = hookEventName(raw);
61
82
  const telemetryScript = path.resolve(__dirname, '..', 'telemetry', 'telemetry.sh');
62
83
 
84
+ let conflict;
85
+ if (eventType === 'PostToolUse') {
86
+ try {
87
+ const heartbeatResult = require('./lib/liveness-heartbeat').maybeEmitHeartbeat({
88
+ cwd: process.cwd(),
89
+ env: process.env,
90
+ });
91
+ conflict = heartbeatResult && heartbeatResult.conflict;
92
+ } catch (err) {
93
+ process.stderr.write(`[CodexTelemetryHook] liveness heartbeat error: ${err.message}\n`);
94
+ }
95
+ }
96
+
63
97
  const result = spawnSync('bash', [telemetryScript, eventType, agentName], {
64
98
  input: raw,
65
99
  encoding: 'utf8',
@@ -85,7 +119,7 @@ async function main() {
85
119
  process.stderr.write(`[CodexTelemetryHook] failed open: ${detail}\n`);
86
120
  }
87
121
 
88
- const output = codexSuccessOutput(event);
122
+ const output = codexSuccessOutput(event, conflict);
89
123
  if (output) process.stdout.write(`${JSON.stringify(output)}\n`);
90
124
  }
91
125
 
@@ -15,12 +15,12 @@
15
15
  * export FLOW_AGENTS_GOAL_FIT_MODE=off for the next bash -lc hook invocation.
16
16
  * - .claude/settings.json / settings.local.json — where an agent could add an
17
17
  * env block or delete the Stop hook entry entirely.
18
- * - .flow-agents/current.json — where an agent could forge active_flow_id /
18
+ * - .kontourai/flow-agents/current.json — where an agent could forge active_flow_id /
19
19
  * active_step_id to route the gate to a fake permissive FlowDefinition.
20
- * - .flow-agents/<slug>/state.json — where an agent could forge phase=planning
20
+ * - .kontourai/flow-agents/<slug>/state.json — where an agent could forge phase=planning
21
21
  * to downgrade from FULL_BLOCK to HARD_BLOCK (R5a: MEDIUM finding fix).
22
22
  * SAFE: the CLI writes state.json via writeState → writeJson → fs.writeFileSync.
23
- * - .flow-agents/<slug>/trust.bundle — where an agent could forge claims.
23
+ * - .kontourai/flow-agents/<slug>/trust.bundle — where an agent could forge claims.
24
24
  * SAFE: the CLI writes trust.bundle via writeTrustBundle → writeJson → fs.writeFileSync.
25
25
  * (Verified: ensureSession / advanceState / writeCurrent / writeState /
26
26
  * writeTrustBundle all use writeJson which calls fs.writeFileSync directly —
@@ -78,7 +78,7 @@ const PROTECTED_FILES = new Set([
78
78
  // These files need PATH context (not just basename) to avoid over-matching
79
79
  // common file names in unrelated projects.
80
80
  //
81
- // R5a adds .flow-agents/<slug>/state.json and .flow-agents/<slug>/trust.bundle.
81
+ // R5a adds flow-agents runtime state and trust.bundle paths.
82
82
  //
83
83
  // checkProtectedPathPattern(filePath) → { name, reason } | null
84
84
  // ---------------------------------------------------------------------------
@@ -86,8 +86,9 @@ const PROTECTED_FILES = new Set([
86
86
  /**
87
87
  * Check whether a Write/Edit tool file path targets a gate kill-switch file
88
88
  * that requires path-level matching (.claude/settings.json,
89
- * .flow-agents/current.json, .flow-agents/<slug>/state.json,
90
- * .flow-agents/<slug>/trust.bundle).
89
+ * .kontourai/flow-agents/current.json, .kontourai/flow-agents/<slug>/state.json,
90
+ * .kontourai/flow-agents/<slug>/trust.bundle, and deprecated runtime-shaped
91
+ * .flow-agents equivalents).
91
92
  *
92
93
  * Returns { name, reason } when blocked, null when allowed.
93
94
  *
@@ -119,35 +120,45 @@ function checkProtectedPathPattern(filePath) {
119
120
  };
120
121
  }
121
122
 
122
- // .flow-agents/current.json — an agent could forge active_flow_id / active_step_id
123
+ // .kontourai/flow-agents/current.json — an agent could forge active_flow_id / active_step_id
123
124
  // to route the gate to a permissive or empty-expects FlowDefinition.
124
125
  // SAFE: the workflow CLI writes current.json via fs (writeJson → fs.writeFileSync),
125
126
  // NOT via the Write/Edit tool — blocking the tool path does not break legit sidecar.
126
- if (/(?:^|\/)\.flow-agents\/current\.json$/.test(norm)) {
127
+ if (/(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/current\.json$/.test(norm)) {
127
128
  return {
128
- name: '.flow-agents/current.json',
129
+ name: '.kontourai/flow-agents/current.json',
129
130
  reason: 'an agent could forge active_flow_id/active_step_id to route the gate to a permissive FlowDefinition',
130
131
  };
131
132
  }
132
133
 
133
- // .flow-agents/<slug>/state.json an agent could forge phase=planning to
134
+ // .kontourai/flow-agents/.goal-fit-block-streak.json controls soft-block
135
+ // release counting. An agent could force early advisory-gate release by
136
+ // writing a high count.
137
+ if (/(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/\.goal-fit-block-streak\.json$/.test(norm)) {
138
+ return {
139
+ name: '.kontourai/flow-agents/.goal-fit-block-streak.json',
140
+ reason: 'an agent could manipulate goal-fit block streak state to force early soft-block release',
141
+ };
142
+ }
143
+
144
+ // .kontourai/flow-agents/<slug>/state.json — an agent could forge phase=planning to
134
145
  // downgrade the block regime (FULL_BLOCK → HARD_BLOCK) and weaken gate checks.
135
146
  // SAFE: the CLI writes state.json via writeState → writeJson → fs.writeFileSync,
136
147
  // NOT via the Write/Edit tool — blocking the tool path does not break legit sidecar.
137
- if (/(?:^|\/)\.flow-agents\/[^/]+\/state\.json$/.test(norm)) {
148
+ if (/(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/state\.json$/.test(norm)) {
138
149
  return {
139
- name: '.flow-agents/<slug>/state.json',
150
+ name: '.kontourai/flow-agents/<slug>/state.json',
140
151
  reason: 'an agent could forge phase=planning to downgrade the block regime and weaken gate enforcement',
141
152
  };
142
153
  }
143
154
 
144
- // .flow-agents/<slug>/trust.bundle — an agent could forge claims (e.g. status=verified
155
+ // .kontourai/flow-agents/<slug>/trust.bundle — an agent could forge claims (e.g. status=verified
145
156
  // or impactLevel=low) to suppress gate blocks or make disputed evidence appear accepted.
146
157
  // SAFE: the CLI writes trust.bundle via writeTrustBundle → writeJson → fs.writeFileSync,
147
158
  // NOT via the Write/Edit tool — blocking the tool path does not break legit sidecar.
148
- if (/(?:^|\/)\.flow-agents\/[^/]+\/trust\.bundle$/.test(norm)) {
159
+ if (/(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/trust\.bundle$/.test(norm)) {
149
160
  return {
150
- name: '.flow-agents/<slug>/trust.bundle',
161
+ name: '.kontourai/flow-agents/<slug>/trust.bundle',
151
162
  reason: 'an agent could forge trust claims (verified status, impact level) to bypass gate integrity checks',
152
163
  };
153
164
  }
@@ -371,14 +382,14 @@ function checkCommandForBypass(command) {
371
382
  // When the agent uses a Bash tool (tool_input.command), detect redirects that
372
383
  // write to protected paths via shell redirect operators (> >>) or `tee`.
373
384
  //
374
- // R5a extends coverage to .flow-agents/<slug>/state.json and trust.bundle.
385
+ // R5a extends coverage to flow-agents runtime state and trust.bundle.
375
386
  //
376
387
  // HONEST — INCOMPLETE COVERAGE: this catches the obvious forms only:
377
388
  // - `cmd > ~/.bashrc` ✓ caught (> redirect operator)
378
389
  // - `cmd >> .claude/settings.json` ✓ caught (>> redirect operator)
379
- // - `tee .flow-agents/current.json` ✓ caught (tee command)
380
- // - `cmd > .flow-agents/s/state.json` ✓ caught (> redirect operator)
381
- // - `tee .flow-agents/s/trust.bundle` ✓ caught (tee command)
390
+ // - `tee .kontourai/flow-agents/current.json` ✓ caught (tee command)
391
+ // - `cmd > .kontourai/flow-agents/s/state.json` ✓ caught (> redirect operator)
392
+ // - `tee .kontourai/flow-agents/s/trust.bundle` ✓ caught (tee command)
382
393
  // - `sed -i ... .bashrc` ✗ NOT caught (in-place edit)
383
394
  // - `interpreted-lang -c "open(...,'w')" ✗ NOT caught (see interpreter check)
384
395
  // - `/dev/fd` tricks, process substitution ✗ NOT caught
@@ -391,10 +402,13 @@ function checkCommandForBypass(command) {
391
402
  /**
392
403
  * Regex that matches a normalized (forward-slash) path ending with a protected
393
404
  * kill-switch file: shell profiles, .claude/settings*.json,
394
- * .flow-agents/current.json, .flow-agents/<slug>/state.json,
395
- * .flow-agents/<slug>/trust.bundle.
405
+ * .kontourai/flow-agents/current.json,
406
+ * .kontourai/flow-agents/.goal-fit-block-streak.json,
407
+ * .kontourai/flow-agents/<slug>/state.json,
408
+ * .kontourai/flow-agents/<slug>/trust.bundle, and deprecated runtime-shaped
409
+ * .flow-agents equivalents.
396
410
  */
397
- const REDIRECT_PROTECTED_RE = /(?:^|\/|~\/)(\.bash_profile|\.bashrc|\.profile|\.zprofile|\.zshrc)$|(?:^|\/)\.claude\/settings(?:\.local)?\.json$|(?:^|\/)\.flow-agents\/current\.json$|(?:^|\/)\.flow-agents\/[^/]+\/state\.json$|(?:^|\/)\.flow-agents\/[^/]+\/trust\.bundle$|(?:^|\/)delivery\/trust\.bundle$|(?:^|\/)delivery\/trust\.checkpoint\.json$/;
411
+ const REDIRECT_PROTECTED_RE = /(?:^|\/|~\/)(\.bash_profile|\.bashrc|\.profile|\.zprofile|\.zshrc)$|(?:^|\/)\.claude\/settings(?:\.local)?\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/current\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/\.goal-fit-block-streak\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/state\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/trust\.bundle$|(?:^|\/)delivery\/trust\.bundle$|(?:^|\/)delivery\/trust\.checkpoint\.json$/;
398
412
 
399
413
  /**
400
414
  * Return true when a token (an unquoted redirect target or tee argument) matches
@@ -592,6 +606,61 @@ function checkCopyMoveToProtected(command) {
592
606
  return null;
593
607
  }
594
608
 
609
+ // ---------------------------------------------------------------------------
610
+ // Sanctioned remedies for blocked writes.
611
+ //
612
+ // AC7: for sidecar/gate kill-switch paths the block message MUST name the
613
+ // sanctioned `npm run workflow:sidecar -- <command>` writer (or a human maintainer
614
+ // for host-owned files) and MUST NEVER advise disabling the config-protection hook.
615
+ // ---------------------------------------------------------------------------
616
+ const READ_ONLY_TOOL_NAMES = new Set(['read', 'glob', 'grep', 'ls', 'notebookread', 'websearch', 'webfetch']);
617
+
618
+ // WS8 (AC15): the remedy table (SHELL_PROFILE_REMEDY, SANCTIONED_REMEDIES, remedyFor,
619
+ // REMEDY_COMMAND_CANDIDATES) was extracted verbatim to ./lib/config-protection-remedies.js
620
+ // so it has an independently testable surface and this file is smaller. remedyForCommand
621
+ // stays here because it depends on this file's tokenizer (splitSegments/tokenize/
622
+ // checkProtectedPathPattern/PROTECTED_FILES). Behavior-preserving — no message text or
623
+ // matching data changed.
624
+ const { SHELL_PROFILE_REMEDY, remedyFor, REMEDY_COMMAND_CANDIDATES } = require('./lib/config-protection-remedies.js');
625
+
626
+ /**
627
+ * Recover the sanctioned remedy for a blocked Bash command.
628
+ *
629
+ * Pass 1: exact path-pattern match on individual tokens -- handles shell
630
+ * redirects / tee / cp where the protected path is its own clean token
631
+ * (a bare redirect target, or a cp/mv destination argument).
632
+ *
633
+ * Pass 2: substring match against the raw command text -- handles
634
+ * interpreter-write commands where the protected path sits inside a quoted
635
+ * string followed by punctuation, so no token ends at the basename and
636
+ * Pass 1's dollar-anchored regex never matches. Any blocked command that
637
+ * references a path with a SANCTIONED_REMEDIES entry gets that entry's
638
+ * remedy instead of falling through to the generic (and often factually
639
+ * wrong) shell-profile advice.
640
+ *
641
+ * Never returns advice to disable the hook.
642
+ */
643
+ function remedyForCommand(command) {
644
+ if (typeof command !== 'string') return SHELL_PROFILE_REMEDY;
645
+
646
+ const segments = splitSegments(command);
647
+ for (const seg of segments) {
648
+ for (const tok of tokenize(seg)) {
649
+ const match = checkProtectedPathPattern(tok);
650
+ if (match) return remedyFor(match.name);
651
+ if (PROTECTED_FILES.has(path.basename(tok))) return SHELL_PROFILE_REMEDY;
652
+ }
653
+ }
654
+
655
+ for (const candidate of REMEDY_COMMAND_CANDIDATES) {
656
+ if (candidate.needles.some((needle) => command.includes(needle))) {
657
+ return remedyFor(candidate.name);
658
+ }
659
+ }
660
+
661
+ return SHELL_PROFILE_REMEDY;
662
+ }
663
+
595
664
  function run(inputOrRaw, options = {}) {
596
665
  if (options.truncated) {
597
666
  return {
@@ -604,8 +673,11 @@ function run(inputOrRaw, options = {}) {
604
673
  try {
605
674
  input = typeof inputOrRaw === 'string' ? JSON.parse(inputOrRaw) : inputOrRaw;
606
675
  } catch { return { exitCode: 0 }; }
676
+ const toolName = String(input?.tool_name || '').trim().toLowerCase();
607
677
  const filePath = input?.tool_input?.path || input?.tool_input?.file_path || '';
608
- if (filePath) {
678
+ // Read-only tools never mutate a file, so path-based protection must not block them.
679
+ // (Bash is NOT read-only and stays fully covered by the command-based checks below.)
680
+ if (filePath && !READ_ONLY_TOOL_NAMES.has(toolName)) {
609
681
  const basename = path.basename(filePath);
610
682
  if (PROTECTED_FILES.has(basename)) {
611
683
  return {
@@ -623,7 +695,7 @@ function run(inputOrRaw, options = {}) {
623
695
  exitCode: 2,
624
696
  stderr: `BLOCKED: Writing to ${pathMatch.name} is not allowed. ` +
625
697
  `This file is protected because ${pathMatch.reason}. ` +
626
- 'If this is a legitimate change, disable the config-protection hook temporarily and document the reason.',
698
+ remedyFor(pathMatch.name),
627
699
  };
628
700
  }
629
701
  }
@@ -648,8 +720,8 @@ function run(inputOrRaw, options = {}) {
648
720
  exitCode: 2,
649
721
  stderr: `BLOCKED: Detected ${redirect} targeting a protected gate kill-switch file. ` +
650
722
  'Writing to shell profiles or Claude/flow-agents config files via shell redirect could ' +
651
- 'disable or tamper with the gate. If this is a legitimate operation, ' +
652
- 'disable the config-protection hook temporarily and document the reason. ' +
723
+ 'disable or tamper with the gate. Do not disable this hook. ' +
724
+ remedyForCommand(command) + ' ' +
653
725
  'NOTE: This check has incomplete coverage (sed -i and similar forms are not caught).',
654
726
  };
655
727
  }
@@ -663,8 +735,8 @@ function run(inputOrRaw, options = {}) {
663
735
  exitCode: 2,
664
736
  stderr: `BLOCKED: Detected ${interpWrite} in a Bash command. ` +
665
737
  'Interpreter invocations (node -e, py3 -c, sed -i, perl -e) that reference ' +
666
- 'protected gate files could tamper with the gate. If this is a legitimate operation, ' +
667
- 'disable the config-protection hook temporarily and document the reason. ' +
738
+ 'protected gate files could tamper with the gate. Do not disable this hook. ' +
739
+ remedyForCommand(command) + ' ' +
668
740
  'NOTE: This check has INCOMPLETE COVERAGE — runtime path construction evades it.',
669
741
  };
670
742
  }
@@ -677,8 +749,8 @@ function run(inputOrRaw, options = {}) {
677
749
  exitCode: 2,
678
750
  stderr: `BLOCKED: Detected ${copyMove} in a Bash command. ` +
679
751
  'Writing to delivery/trust.bundle or delivery/trust.checkpoint.json via cp/mv/install ' +
680
- 'could forge the CI trust anchor. The legitimate write path is the publishDelivery CLI ' +
681
- '(fs.copyFileSync -- not the Write/Edit tool or bash cp). ' +
752
+ 'could forge the CI trust anchor. Do not disable this hook. ' +
753
+ remedyForCommand(command) + ' ' +
682
754
  'NOTE: This check covers cp/mv/install only -- other copy tools may evade it.',
683
755
  };
684
756
  }
@@ -9,7 +9,7 @@
9
9
  * against this captured truth: a check claiming a command passed while the log
10
10
  * shows it FAILED is a caught false-completion.
11
11
  *
12
- * Records to `.flow-agents/<slug>/command-log.jsonl`, one JSON object per line:
12
+ * Records to `.kontourai/flow-agents/<slug>/command-log.jsonl`, one JSON object per line:
13
13
  * {
14
14
  * "command": "<the command string the agent ran>",
15
15
  * "observedResult": "pass" | "fail", // deterministically inferred
@@ -168,7 +168,7 @@ function readJsonFile(file) {
168
168
  try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return null; }
169
169
  }
170
170
 
171
- // Newest-mtime state.json under .flow-agents/<slug>/, mirroring how
171
+ // Newest-mtime state.json under .kontourai/flow-agents/<slug>/, mirroring how
172
172
  // workflow-steering.js and stop-goal-fit.js locate the active artifact dir.
173
173
  function latestStateDir(flowAgentsDir) {
174
174
  let best = null;
@@ -194,7 +194,7 @@ function latestStateDir(flowAgentsDir) {
194
194
 
195
195
  /**
196
196
  * Resolve the active artifact directory the same way the other hooks do:
197
- * prefer .flow-agents/current.json (active_slug / artifact_dir), then fall back
197
+ * prefer .kontourai/flow-agents/current.json (active_slug / artifact_dir), then fall back
198
198
  * to the newest-mtime state.json directory.
199
199
  */
200
200
  function resolveArtifactDir(root) {