@kontourai/flow-agents 3.2.0 → 3.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 (281) hide show
  1. package/.github/workflows/add-to-project.yml +15 -0
  2. package/.github/workflows/ci.yml +173 -0
  3. package/CHANGELOG.md +54 -0
  4. package/CONTEXT.md +5 -1
  5. package/README.md +19 -8
  6. package/build/src/builder-flow-run-adapter.d.ts +80 -0
  7. package/build/src/builder-flow-run-adapter.js +241 -0
  8. package/build/src/builder-flow-runtime.d.ts +16 -0
  9. package/build/src/builder-flow-runtime.js +290 -0
  10. package/build/src/cli/assignment-provider.js +10 -1
  11. package/build/src/cli/builder-run.d.ts +1 -0
  12. package/build/src/cli/builder-run.js +27 -0
  13. package/build/src/cli/effective-backlog-settings.js +70 -2
  14. package/build/src/cli/init.d.ts +34 -0
  15. package/build/src/cli/init.js +341 -61
  16. package/build/src/cli/kit.js +55 -12
  17. package/build/src/cli/pull-work-provider.js +346 -5
  18. package/build/src/cli/skill-drift-check.d.ts +1 -0
  19. package/build/src/cli/skill-drift-check.js +165 -0
  20. package/build/src/cli/telemetry-doctor.d.ts +37 -0
  21. package/build/src/cli/telemetry-doctor.js +53 -6
  22. package/build/src/cli/validate-hook-influence.js +37 -7
  23. package/build/src/cli/workflow-artifact-cleanup-audit.js +418 -11
  24. package/build/src/cli/workflow-sidecar.d.ts +310 -1
  25. package/build/src/cli/workflow-sidecar.js +1914 -126
  26. package/build/src/cli.js +5 -0
  27. package/build/src/flow-kit/validate.d.ts +54 -34
  28. package/build/src/flow-kit/validate.js +237 -26
  29. package/build/src/index.d.ts +2 -0
  30. package/build/src/index.js +1 -0
  31. package/build/src/lib/console-connect-options.d.ts +97 -0
  32. package/build/src/lib/console-connect-options.js +199 -0
  33. package/build/src/lib/console-telemetry-validate.d.ts +49 -0
  34. package/build/src/lib/console-telemetry-validate.js +91 -0
  35. package/build/src/lib/flow-resolver.d.ts +54 -1
  36. package/build/src/lib/flow-resolver.js +112 -5
  37. package/build/src/lib/fs.d.ts +17 -0
  38. package/build/src/lib/fs.js +172 -0
  39. package/build/src/lib/local-artifact-root.d.ts +44 -1
  40. package/build/src/lib/local-artifact-root.js +131 -3
  41. package/build/src/runtime-adapters.d.ts +39 -3
  42. package/build/src/runtime-adapters.js +77 -31
  43. package/build/src/tools/build-universal-bundles.js +40 -2
  44. package/build/src/tools/codex-agent-routing.d.ts +2 -0
  45. package/build/src/tools/codex-agent-routing.js +49 -0
  46. package/build/src/tools/generate-context-map.js +1 -0
  47. package/build/src/tools/validate-source-tree.js +30 -3
  48. package/context/contracts/artifact-contract.md +16 -2
  49. package/context/scripts/hooks/lib/kit-catalog.js +235 -0
  50. package/context/scripts/hooks/lib/runnable-command.js +177 -0
  51. package/context/scripts/hooks/stop-goal-fit.js +278 -48
  52. package/context/scripts/hooks/workflow-steering.js +194 -22
  53. package/context/scripts/package.json +3 -0
  54. package/context/scripts/telemetry/install-console-config.sh +25 -4
  55. package/context/scripts/telemetry/lib/config.sh +102 -12
  56. package/context/scripts/telemetry/lib/pricing.sh +50 -0
  57. package/context/scripts/telemetry/lib/session.sh +3 -0
  58. package/context/scripts/telemetry/lib/transport.sh +87 -0
  59. package/context/scripts/telemetry/lib/usage.sh +205 -4
  60. package/context/scripts/telemetry/telemetry.conf +6 -0
  61. package/context/scripts/telemetry/telemetry.sh +48 -0
  62. package/context/settings/workspace-backlog-provider-settings.example.json +48 -0
  63. package/docs/agent-usage-feedback-loop.md +35 -0
  64. package/docs/architecture-engine-and-kits.md +110 -0
  65. package/docs/context-map.md +2 -0
  66. package/docs/coordination-guide.md +370 -0
  67. package/docs/decisions/agent-coordination.md +26 -9
  68. package/docs/decisions/embeddable-engine.md +152 -0
  69. package/docs/decisions/index.md +5 -3
  70. package/docs/decisions/trust-ledger-retention.md +88 -0
  71. package/docs/decisions/trust-reconcile.md +42 -9
  72. package/docs/decisions/workflow-enforcement.md +31 -9
  73. package/docs/fixture-ownership.md +6 -2
  74. package/docs/implementing-trust-reconciliation.md +129 -0
  75. package/docs/index.md +23 -9
  76. package/docs/integrations/flow-agents-console.md +275 -0
  77. package/docs/integrations/index.md +4 -0
  78. package/docs/kit-authoring-guide.md +52 -21
  79. package/docs/spec/builder-flow-runtime.md +80 -0
  80. package/docs/spec/runtime-hook-surface.md +45 -1
  81. package/docs/specs/economics-record-contract.md +270 -0
  82. package/docs/specs/harness-capability-matrix.md +74 -0
  83. package/docs/specs/learning-review-proposals-contract.md +340 -0
  84. package/docs/specs/routing-efficiency-review.md +59 -0
  85. package/docs/verifiable-trust.md +74 -25
  86. package/docs/workflow-artifact-lifecycle.md +38 -1
  87. package/docs/workflow-usage-guide.md +10 -0
  88. package/evals/acceptance/prove-capture-teeth.sh +132 -0
  89. package/evals/ci/antigaming-suite.sh +2 -0
  90. package/evals/ci/run-baseline.sh +78 -4
  91. package/evals/fixtures/economics/acceptance.json +12 -0
  92. package/evals/fixtures/economics/agents/tool-worker-1/events.jsonl +2 -0
  93. package/evals/fixtures/economics/agents/tool-worker-2/events.jsonl +2 -0
  94. package/evals/fixtures/economics/agents/tool-worker-3/events.jsonl +2 -0
  95. package/evals/fixtures/economics/agents/tool-worker-4/events.jsonl +1 -0
  96. package/evals/fixtures/economics/agents/tool-worker-5/events.jsonl +2 -0
  97. package/evals/fixtures/economics/critique.json +22 -0
  98. package/evals/fixtures/economics/expected-record.json +71 -0
  99. package/evals/fixtures/economics/session-usage-event.json +1 -0
  100. package/evals/fixtures/economics/state.json +11 -0
  101. package/evals/fixtures/economics/transcript.jsonl +3 -0
  102. package/evals/fixtures/hook-influence/cases.json +7 -7
  103. package/evals/fixtures/learning-review-proposals/balanced/economics.jsonl +6 -0
  104. package/evals/fixtures/learning-review-proposals/effect-follow-up/economics.jsonl +5 -0
  105. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-1/trust.bundle +21 -0
  106. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-2/trust.bundle +21 -0
  107. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-3/trust.bundle +21 -0
  108. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-4/trust.bundle +21 -0
  109. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-5/trust.bundle +21 -0
  110. package/evals/fixtures/learning-review-proposals/pattern-present/economics.jsonl +6 -0
  111. package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.json +30 -0
  112. package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.md +66 -0
  113. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/gate-review.inquiries.json +26 -0
  114. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/trust.bundle +21 -0
  115. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/gate-review.inquiries.json +26 -0
  116. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/trust.bundle +21 -0
  117. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/gate-review.inquiries.json +26 -0
  118. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/trust.bundle +21 -0
  119. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/gate-review.inquiries.json +26 -0
  120. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/trust.bundle +21 -0
  121. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-5/trust.bundle +21 -0
  122. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-6/trust.bundle +21 -0
  123. package/evals/fixtures/learning-review-proposals/repeat-window/economics.jsonl +6 -0
  124. package/evals/fixtures/learning-review-proposals/under-threshold/economics.jsonl +3 -0
  125. package/evals/fixtures/reconcile-preflight/disputed-critique-unsuperseded.json +48 -0
  126. package/evals/fixtures/reconcile-preflight/standalone-disputed-session-local.json +59 -0
  127. package/evals/fixtures/telemetry/usage-transcript-sample.jsonl +4 -0
  128. package/evals/fixtures/trust-reconcile-exploits/mcp-degrade.json +42 -0
  129. package/evals/integration/test_builder_entry_enforcement.sh +241 -0
  130. package/evals/integration/test_builder_step_producers.sh +18 -10
  131. package/evals/integration/test_bundle_install.sh +172 -0
  132. package/evals/integration/test_checkpoint_signing.sh +10 -2
  133. package/evals/integration/test_ci_actor_identity.sh +221 -0
  134. package/evals/integration/test_console_tenant_isolation.sh +167 -0
  135. package/evals/integration/test_critique_supersession_roundtrip.sh +4 -1
  136. package/evals/integration/test_dual_emit_flow_step.sh +10 -4
  137. package/evals/integration/test_economics_record.sh +674 -0
  138. package/evals/integration/test_effective_backlog_settings.sh +1 -1
  139. package/evals/integration/test_evidence_capture_hook.sh +17 -2
  140. package/evals/integration/test_exemption_usage_review.sh +198 -0
  141. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  142. package/evals/integration/test_flow_kit_install_git.sh +83 -0
  143. package/evals/integration/test_flowdef_session_activation.sh +0 -1
  144. package/evals/integration/test_flowdef_session_history_preservation.sh +13 -3
  145. package/evals/integration/test_gate_lockdown.sh +7 -0
  146. package/evals/integration/test_gate_review_inquiry_records.sh +9 -1
  147. package/evals/integration/test_goal_fit_hook.sh +2031 -0
  148. package/evals/integration/test_hook_category_behaviors.sh +8 -1
  149. package/evals/integration/test_hook_influence_cases.sh +25 -1
  150. package/evals/integration/test_install_merge.sh +227 -2
  151. package/evals/integration/test_kit_conformance_levels.sh +6 -6
  152. package/evals/integration/test_learning_review_proposals.sh +329 -0
  153. package/evals/integration/test_liveness_conflict_injection.sh +26 -22
  154. package/evals/integration/test_liveness_console_relay.sh +166 -0
  155. package/evals/integration/test_liveness_heartbeat.sh +17 -17
  156. package/evals/integration/test_liveness_worktree_root.sh +575 -0
  157. package/evals/integration/test_phase_map_and_gate_claim.sh +6 -1
  158. package/evals/integration/test_publish_delivery.sh +389 -2
  159. package/evals/integration/test_pull_work_board.sh +200 -0
  160. package/evals/integration/test_pull_work_provider.sh +1 -1
  161. package/evals/integration/test_reconcile_preflight.sh +304 -0
  162. package/evals/integration/test_record_check.sh +378 -0
  163. package/evals/integration/test_routing_efficiency.sh +71 -0
  164. package/evals/integration/test_runtime_adapter_activation.sh +28 -0
  165. package/evals/integration/test_session_resume_roundtrip.sh +16 -19
  166. package/evals/integration/test_skill_drift_check.sh +870 -0
  167. package/evals/integration/test_takeover_protocol.sh +340 -0
  168. package/evals/integration/test_telemetry.sh +445 -0
  169. package/evals/integration/test_telemetry_doctor.sh +66 -0
  170. package/evals/integration/test_telemetry_usage_pipeline.sh +228 -0
  171. package/evals/integration/test_trust_reconcile_negatives.sh +121 -13
  172. package/evals/integration/test_trust_reconcile_trailer_diagnostic.sh +247 -0
  173. package/evals/integration/test_usage_cost.sh +61 -0
  174. package/evals/integration/test_verify_hold.sh +910 -0
  175. package/evals/integration/test_veritas_governance_kit.sh +257 -0
  176. package/evals/integration/test_workflow_artifact_cleanup_audit.sh +575 -3
  177. package/evals/integration/test_workflow_sidecar_writer.sh +1395 -0
  178. package/evals/integration/test_workflow_steering_hook.sh +157 -16
  179. package/evals/integration/test_workspace_settings.sh +176 -0
  180. package/evals/lib/env.sh +26 -0
  181. package/evals/lib/node.sh +8 -0
  182. package/evals/run.sh +37 -0
  183. package/evals/static/test_ci_integration_coverage.sh +115 -0
  184. package/evals/static/test_declared_scope_forms_documented.sh +114 -0
  185. package/evals/static/test_universal_bundles.sh +34 -0
  186. package/evals/static/test_validate_source_kit_asset_scope.sh +259 -0
  187. package/evals/static/test_workflow_skills.sh +1 -1
  188. package/kits/builder/flows/build.flow.json +9 -18
  189. package/kits/builder/flows/publish-learn.flow.json +5 -1
  190. package/kits/builder/kit.json +120 -0
  191. package/kits/builder/skills/continue-work/SKILL.md +2 -0
  192. package/kits/builder/skills/deliver/SKILL.md +115 -0
  193. package/kits/builder/skills/evidence-gate/SKILL.md +12 -0
  194. package/kits/builder/skills/execute-plan/SKILL.md +9 -0
  195. package/kits/builder/skills/learning-review/SKILL.md +51 -0
  196. package/kits/builder/skills/plan-work/SKILL.md +17 -20
  197. package/kits/builder/skills/pull-work/SKILL.md +33 -2
  198. package/kits/builder/skills/release-readiness/SKILL.md +12 -0
  199. package/kits/knowledge/kit.json +9 -0
  200. package/kits/veritas-governance/docs/README.md +113 -7
  201. package/kits/veritas-governance/fixtures/exemption/approved.trust-bundle.json +74 -0
  202. package/kits/veritas-governance/fixtures/exemption/not-approved.trust-bundle.json +74 -0
  203. package/kits/veritas-governance/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json +14 -0
  204. package/kits/veritas-governance/flows/exemption-issuance.flow.json +35 -0
  205. package/kits/veritas-governance/kit.json +19 -0
  206. package/kits/veritas-governance/skills/exemption-usage-review/SKILL.md +128 -0
  207. package/kits/veritas-governance/skills/exemption-usage-review/review-exemptions.mjs +231 -0
  208. package/package.json +2 -2
  209. package/packaging/manifest.json +29 -0
  210. package/schemas/backlog-provider-settings.schema.json +13 -0
  211. package/schemas/workflow-state.schema.json +44 -0
  212. package/scripts/README.md +4 -0
  213. package/scripts/check-content-boundary.cjs +8 -1
  214. package/scripts/ci/trust-reconcile.js +214 -253
  215. package/scripts/hooks/codex-hook-adapter.js +77 -2
  216. package/scripts/hooks/evidence-capture.js +38 -5
  217. package/scripts/hooks/lib/actor-identity.js +82 -0
  218. package/scripts/hooks/lib/codex-exit-code.js +316 -0
  219. package/scripts/hooks/lib/kit-catalog.js +235 -0
  220. package/scripts/hooks/lib/liveness-write.js +28 -1
  221. package/scripts/hooks/lib/local-artifact-paths.js +97 -1
  222. package/scripts/hooks/lib/runnable-command.js +177 -0
  223. package/scripts/hooks/lib/skill-drift.js +350 -0
  224. package/scripts/hooks/stop-goal-fit.js +278 -48
  225. package/scripts/hooks/workflow-steering.js +194 -22
  226. package/scripts/install-codex-home.sh +97 -47
  227. package/scripts/install-merge.js +72 -14
  228. package/scripts/install-owned-files.js +178 -0
  229. package/scripts/lib/reconcile-shape.js +381 -0
  230. package/scripts/liveness/relay.sh +84 -0
  231. package/scripts/telemetry/economics-record.schema.json +145 -0
  232. package/scripts/telemetry/economics-record.sh +331 -0
  233. package/scripts/telemetry/install-console-config.sh +25 -4
  234. package/scripts/telemetry/learning-review-decide.sh +124 -0
  235. package/scripts/telemetry/learning-review-proposals.schema.json +161 -0
  236. package/scripts/telemetry/learning-review-proposals.sh +484 -0
  237. package/scripts/telemetry/lib/config.sh +102 -12
  238. package/scripts/telemetry/lib/pricing.sh +14 -6
  239. package/scripts/telemetry/lib/session.sh +3 -0
  240. package/scripts/telemetry/lib/transport.sh +133 -15
  241. package/scripts/telemetry/lib/usage.sh +121 -28
  242. package/scripts/telemetry/routing-efficiency.sh +0 -0
  243. package/scripts/telemetry/telemetry.conf +6 -0
  244. package/scripts/telemetry/telemetry.sh +48 -0
  245. package/src/builder-flow-run-adapter.ts +357 -0
  246. package/src/builder-flow-runtime.ts +348 -0
  247. package/src/cli/assignment-provider.ts +12 -1
  248. package/src/cli/builder-flow-run-adapter.test.mjs +495 -0
  249. package/src/cli/builder-flow-runtime.test.mjs +213 -0
  250. package/src/cli/builder-run.ts +28 -0
  251. package/src/cli/codex-agent-routing.test.mjs +44 -0
  252. package/src/cli/codex-exit-code.test.mjs +207 -0
  253. package/src/cli/console-connect-options.test.mjs +329 -0
  254. package/src/cli/console-telemetry-validate.test.mjs +157 -0
  255. package/src/cli/effective-backlog-settings.ts +68 -2
  256. package/src/cli/flow-resolver-composition.test.mjs +72 -0
  257. package/src/cli/init.test.mjs +161 -0
  258. package/src/cli/init.ts +407 -62
  259. package/src/cli/kit-metadata-security.test.mjs +443 -0
  260. package/src/cli/kit.ts +50 -12
  261. package/src/cli/pull-work-provider.ts +377 -3
  262. package/src/cli/sidecar-pure-helpers.test.mjs +64 -0
  263. package/src/cli/skill-drift-check.ts +196 -0
  264. package/src/cli/telemetry-doctor.test.mjs +53 -0
  265. package/src/cli/telemetry-doctor.ts +50 -7
  266. package/src/cli/validate-hook-influence.ts +37 -6
  267. package/src/cli/workflow-artifact-cleanup-audit.ts +483 -10
  268. package/src/cli/workflow-sidecar.ts +1980 -119
  269. package/src/cli.ts +5 -0
  270. package/src/flow-kit/validate.ts +277 -38
  271. package/src/index.ts +19 -0
  272. package/src/lib/console-connect-options.ts +261 -0
  273. package/src/lib/console-telemetry-validate.ts +88 -0
  274. package/src/lib/flow-resolver.ts +117 -4
  275. package/src/lib/fs.ts +160 -0
  276. package/src/lib/local-artifact-root.ts +129 -3
  277. package/src/runtime-adapters.ts +113 -33
  278. package/src/tools/build-universal-bundles.ts +36 -2
  279. package/src/tools/codex-agent-routing.ts +48 -0
  280. package/src/tools/generate-context-map.ts +1 -0
  281. package/src/tools/validate-source-tree.ts +29 -3
@@ -1,9 +1,10 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { readJson, relPath, writeJson } from "./lib/fs.js";
3
+ import { atomicCopyFile, atomicWriteJson, ensureSafeDirectory, readJson, relPath } from "./lib/fs.js";
4
4
  import { flowAgentsArtifactRoot } from "./lib/local-artifact-root.js";
5
- import { parseKitDependencies } from "./flow-kit/validate.js";
5
+ import { parseKitDependencies, parseKitHookInfluenceExpectations, parseKitWorkflowTriggers } from "./flow-kit/validate.js";
6
6
  const ASSET_CLASSES = ["flows", "skills", "docs", "adapters", "evals", "assets"];
7
+ const KIT_ID_RE = /^[a-z][a-z0-9-]*$/;
7
8
  function assetPath(root, value) {
8
9
  if (path.isAbsolute(value))
9
10
  return null;
@@ -15,7 +16,7 @@ function loadKitAssets(kitRoot, sourceKind, warnings, errors) {
15
16
  const manifestPath = path.join(kitRoot, "kit.json");
16
17
  if (!fs.existsSync(manifestPath)) {
17
18
  errors.push(`${kitRoot}: missing kit.json`);
18
- return { kit_id: "", assets: [], dependencies: [] };
19
+ return { kit_id: "", assets: [], dependencies: [], workflow_triggers: [], hook_influence_expectations: [] };
19
20
  }
20
21
  let manifest;
21
22
  try {
@@ -23,13 +24,13 @@ function loadKitAssets(kitRoot, sourceKind, warnings, errors) {
23
24
  }
24
25
  catch (error) {
25
26
  errors.push(`${manifestPath}: ${error.message}`);
26
- return { kit_id: "", assets: [], dependencies: [] };
27
+ return { kit_id: "", assets: [], dependencies: [], workflow_triggers: [], hook_influence_expectations: [] };
27
28
  }
28
29
  const kitId = typeof manifest.id === "string" ? manifest.id : "";
29
30
  const kitName = typeof manifest.name === "string" ? manifest.name : kitId;
30
- if (!kitId) {
31
- errors.push(`${manifestPath}: missing kit id`);
32
- return { kit_id: "", assets: [], dependencies: [] };
31
+ if (!KIT_ID_RE.test(kitId)) {
32
+ errors.push(`${manifestPath}: kit id must be a kebab-case id (^[a-z][a-z0-9-]*$)`);
33
+ return { kit_id: "", assets: [], dependencies: [], workflow_triggers: [], hook_influence_expectations: [] };
33
34
  }
34
35
  const assets = [];
35
36
  for (const assetClass of ASSET_CLASSES) {
@@ -72,16 +73,55 @@ function loadKitAssets(kitRoot, sourceKind, warnings, errors) {
72
73
  // docs/adr/0019-kit-dependency-ownership.md). Shape errors surface at
73
74
  // install/inspect/validate time via validateKitRepository; here we only collect
74
75
  // the edges so activation can enforce presence against the full inventory.
75
- const dependencies = parseKitDependencies(manifest, manifestPath).entries
76
+ const dependencyResult = parseKitDependencies(manifest, manifestPath);
77
+ if (dependencyResult.errors.length) {
78
+ errors.push(...dependencyResult.errors);
79
+ warnings.push(`${kitId}: invalid dependencies metadata; skipping dependencies`);
80
+ }
81
+ const dependencies = dependencyResult.errors.length ? [] : dependencyResult.entries
76
82
  .map((dep) => ({ from_kit_id: kitId, kit_id: dep.kit_id, ...(dep.reason ? { reason: dep.reason } : {}) }));
77
- return { kit_id: kitId, assets, dependencies };
83
+ const workflowTriggerResult = parseKitWorkflowTriggers(manifest, manifestPath);
84
+ if (workflowTriggerResult.errors.length) {
85
+ errors.push(...workflowTriggerResult.errors);
86
+ warnings.push(`${kitId}: invalid workflow_triggers metadata; skipping workflow_triggers`);
87
+ }
88
+ const workflow_triggers = workflowTriggerResult.errors.length ? [] : workflowTriggerResult.entries
89
+ .map((trigger) => ({ kit_id: kitId, source_kind: sourceKind, ...trigger }));
90
+ const hookInfluenceResult = parseKitHookInfluenceExpectations(manifest, manifestPath);
91
+ if (hookInfluenceResult.errors.length) {
92
+ errors.push(...hookInfluenceResult.errors);
93
+ warnings.push(`${kitId}: invalid hook_influence_expectations metadata; skipping hook_influence_expectations`);
94
+ }
95
+ const hook_influence_expectations = hookInfluenceResult.errors.length ? [] : hookInfluenceResult.entries
96
+ .map((expectation) => ({ kit_id: kitId, source_kind: sourceKind, ...expectation }));
97
+ return { kit_id: kitId, assets, dependencies, workflow_triggers, hook_influence_expectations };
78
98
  }
79
- export function readKitInventory(sourceRoot, dest) {
99
+ function expandKitIdFilter(requested, dependencies, presentKitIds) {
100
+ if (!requested)
101
+ return undefined;
102
+ const selected = new Set(requested);
103
+ let changed = true;
104
+ while (changed) {
105
+ changed = false;
106
+ for (const dep of dependencies) {
107
+ if (!selected.has(dep.from_kit_id) || selected.has(dep.kit_id) || !presentKitIds.has(dep.kit_id))
108
+ continue;
109
+ selected.add(dep.kit_id);
110
+ changed = true;
111
+ }
112
+ }
113
+ return selected;
114
+ }
115
+ export function readKitInventory(sourceRoot, dest, options = {}) {
80
116
  const warnings = [];
81
117
  const errors = [];
82
118
  const assets = [];
83
119
  const dependencies = [];
120
+ const workflow_triggers = [];
121
+ const hook_influence_expectations = [];
122
+ const loadedKits = [];
84
123
  const presentKitIds = new Set();
124
+ const kitIdFilter = options.kitIdFilter ? new Set(options.kitIdFilter) : undefined;
85
125
  const catalogPath = path.join(sourceRoot, "kits", "catalog.json");
86
126
  if (!fs.existsSync(catalogPath))
87
127
  warnings.push(`${catalogPath}: built-in Kit Catalog not found; skipping built-in kits (this is normal when running outside a flow-agents checkout)`);
@@ -100,8 +140,7 @@ export function readKitInventory(sourceRoot, dest) {
100
140
  warnings.push(`${catalogPath}: kits[${index}].path unavailable: ${rel}`);
101
141
  else {
102
142
  const loaded = loadKitAssets(kitRoot, "builtin", warnings, errors);
103
- assets.push(...loaded.assets);
104
- dependencies.push(...loaded.dependencies);
143
+ loadedKits.push(loaded);
105
144
  if (loaded.kit_id)
106
145
  presentKitIds.add(loaded.kit_id);
107
146
  }
@@ -125,14 +164,23 @@ export function readKitInventory(sourceRoot, dest) {
125
164
  warnings.push(`${id}: installed kit copy missing at ${kitRoot}; skipping`);
126
165
  else {
127
166
  const loaded = loadKitAssets(kitRoot, "local", warnings, errors);
128
- assets.push(...loaded.assets);
129
- dependencies.push(...loaded.dependencies);
167
+ loadedKits.push(loaded);
130
168
  if (loaded.kit_id)
131
169
  presentKitIds.add(loaded.kit_id);
132
170
  }
133
171
  });
134
172
  }
135
173
  }
174
+ const allDependencies = loadedKits.flatMap((kit) => kit.dependencies);
175
+ const selectedKitIds = expandKitIdFilter(kitIdFilter, allDependencies, presentKitIds);
176
+ for (const loaded of loadedKits) {
177
+ if (selectedKitIds && !selectedKitIds.has(loaded.kit_id))
178
+ continue;
179
+ assets.push(...loaded.assets);
180
+ dependencies.push(...loaded.dependencies);
181
+ workflow_triggers.push(...loaded.workflow_triggers);
182
+ hook_influence_expectations.push(...loaded.hook_influence_expectations);
183
+ }
136
184
  // Activation-time cross-kit dependency enforcement: any declared dependency whose
137
185
  // kit_id is not present among the union of built-in catalog kits and locally-installed
138
186
  // kits is a hard error (feeds the errors[] -> activation-manifest -> non-zero exit path).
@@ -141,7 +189,7 @@ export function readKitInventory(sourceRoot, dest) {
141
189
  errors.push(`${dep.from_kit_id}: declares a dependency on kit '${dep.kit_id}'${dep.reason ? ` (${dep.reason})` : ""} which is not installed or activated. Install it with 'flow-agents kit install <source>' and re-activate, or remove the dependency declaration if it is no longer needed.`);
142
190
  }
143
191
  }
144
- return { assets, warnings, errors };
192
+ return { assets, dependencies, workflow_triggers, hook_influence_expectations, warnings, errors };
145
193
  }
146
194
  function safeSegment(value) {
147
195
  return value.replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^[.-]+|[.-]+$/g, "") || "asset";
@@ -151,9 +199,11 @@ function safeSegment(value) {
151
199
  // skills: agent guidance markdown copied to skills/<kit-id>/ for agent discovery.
152
200
  // docs: documentation markdown copied to docs/<kit-id>/ for agent reference.
153
201
  const ACTIVATED_ASSET_CLASSES = new Set(["flows", "skills", "docs"]);
154
- export function activateCodexLocal(sourceRoot, dest) {
155
- const inventory = readKitInventory(sourceRoot, dest);
202
+ export function activateCodexLocal(sourceRoot, dest, options = {}) {
203
+ const inventory = readKitInventory(sourceRoot, dest, options);
156
204
  const runtimeDir = path.join(flowAgentsArtifactRoot(dest), "projections", "codex");
205
+ const writeRoot = path.resolve(runtimeDir, "../../..");
206
+ ensureSafeDirectory(writeRoot, runtimeDir);
157
207
  const generated = [];
158
208
  const skipped = [];
159
209
  for (const asset of inventory.assets) {
@@ -163,8 +213,7 @@ export function activateCodexLocal(sourceRoot, dest) {
163
213
  continue;
164
214
  }
165
215
  const output = path.join(runtimeDir, "flows", safeSegment(asset.kit_id), `${safeSegment(asset.asset_id)}.flow.json`);
166
- fs.mkdirSync(path.dirname(output), { recursive: true });
167
- fs.copyFileSync(asset.source_path, output);
216
+ atomicCopyFile(writeRoot, asset.source_path, output);
168
217
  generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id, source_path: asset.source_path.split(path.sep).join("/") });
169
218
  }
170
219
  else if (asset.asset_class === "skills" || asset.asset_class === "docs") {
@@ -179,19 +228,17 @@ export function activateCodexLocal(sourceRoot, dest) {
179
228
  const output = asset.asset_class === "skills"
180
229
  ? path.join(runtimeDir, asset.asset_class, safeSegment(asset.kit_id), safeSegment(path.basename(path.dirname(asset.source_path))), filename)
181
230
  : path.join(runtimeDir, asset.asset_class, safeSegment(asset.kit_id), filename);
182
- fs.mkdirSync(path.dirname(output), { recursive: true });
183
- fs.copyFileSync(asset.source_path, output);
231
+ atomicCopyFile(writeRoot, asset.source_path, output);
184
232
  generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id ?? "", source_path: asset.source_path.split(path.sep).join("/") });
185
233
  }
186
234
  else {
187
235
  skipped.push({ asset_class: asset.asset_class, path: asset.relative_path, kit_id: asset.kit_id, asset_id: asset.asset_id, reason: "asset class is not activated by codex-local" });
188
236
  }
189
237
  }
190
- fs.mkdirSync(runtimeDir, { recursive: true });
191
238
  const supportedClasses = Array.from(ACTIVATED_ASSET_CLASSES);
192
239
  const manifest = { schema_version: "1.0", adapter: "codex-local", supported_asset_classes: supportedClasses, generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
193
240
  const manifestPath = path.join(runtimeDir, "activation.json");
194
- writeJson(manifestPath, manifest);
241
+ atomicWriteJson(writeRoot, manifestPath, manifest);
195
242
  generated.push({ asset_class: "activation-manifest", path: relPath(dest, manifestPath), kit_id: "runtime", asset_id: "codex-local.activation", source_path: manifestPath.split(path.sep).join("/") });
196
243
  return { selected_adapter: "codex-local", supported_asset_classes: supportedClasses, generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
197
244
  }
@@ -201,13 +248,15 @@ export function activateCodexLocal(sourceRoot, dest) {
201
248
  // Keeping it in the CLI adapter layer maximises reuse of readKitInventory and safeSegment,
202
249
  // mirrors the codex-local pattern exactly, and keeps framework adapters free of catalog-
203
250
  // layout knowledge. The Strands steering layer then reads the written runtime files.
204
- export function activateStrandsLocal(sourceRoot, dest) {
205
- const inventory = readKitInventory(sourceRoot, dest);
251
+ export function activateStrandsLocal(sourceRoot, dest, options = {}) {
252
+ const inventory = readKitInventory(sourceRoot, dest, options);
206
253
  // Runtime flows land at .kontourai/flow-agents/projections/strands/flows/<kit-id>/<asset-id>.flow.json
207
254
  // so the Strands steering context can glob for *.flow.json under this path.
208
255
  // Runtime skills land at .kontourai/flow-agents/projections/strands/skills/<kit-id>/<filename> and
209
256
  // docs at .kontourai/flow-agents/projections/strands/docs/<kit-id>/<filename> for system-prompt injection.
210
257
  const runtimeDir = path.join(flowAgentsArtifactRoot(dest), "projections", "strands");
258
+ const writeRoot = path.resolve(runtimeDir, "../../..");
259
+ ensureSafeDirectory(writeRoot, runtimeDir);
211
260
  const generated = [];
212
261
  const skipped = [];
213
262
  for (const asset of inventory.assets) {
@@ -217,8 +266,7 @@ export function activateStrandsLocal(sourceRoot, dest) {
217
266
  continue;
218
267
  }
219
268
  const output = path.join(runtimeDir, "flows", safeSegment(asset.kit_id), `${safeSegment(asset.asset_id)}.flow.json`);
220
- fs.mkdirSync(path.dirname(output), { recursive: true });
221
- fs.copyFileSync(asset.source_path, output);
269
+ atomicCopyFile(writeRoot, asset.source_path, output);
222
270
  generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id, source_path: asset.source_path.split(path.sep).join("/") });
223
271
  }
224
272
  else if (asset.asset_class === "skills" || asset.asset_class === "docs") {
@@ -233,19 +281,17 @@ export function activateStrandsLocal(sourceRoot, dest) {
233
281
  const output = asset.asset_class === "skills"
234
282
  ? path.join(runtimeDir, asset.asset_class, safeSegment(asset.kit_id), safeSegment(path.basename(path.dirname(asset.source_path))), filename)
235
283
  : path.join(runtimeDir, asset.asset_class, safeSegment(asset.kit_id), filename);
236
- fs.mkdirSync(path.dirname(output), { recursive: true });
237
- fs.copyFileSync(asset.source_path, output);
284
+ atomicCopyFile(writeRoot, asset.source_path, output);
238
285
  generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id ?? "", source_path: asset.source_path.split(path.sep).join("/") });
239
286
  }
240
287
  else {
241
288
  skipped.push({ asset_class: asset.asset_class, path: asset.relative_path, kit_id: asset.kit_id, asset_id: asset.asset_id, reason: "asset class is not activated by strands-local" });
242
289
  }
243
290
  }
244
- fs.mkdirSync(runtimeDir, { recursive: true });
245
291
  const supportedClasses = Array.from(ACTIVATED_ASSET_CLASSES);
246
292
  const manifest = { schema_version: "1.0", adapter: "strands-local", supported_asset_classes: supportedClasses, generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
247
293
  const manifestPath = path.join(runtimeDir, "activation.json");
248
- writeJson(manifestPath, manifest);
294
+ atomicWriteJson(writeRoot, manifestPath, manifest);
249
295
  generated.push({ asset_class: "activation-manifest", path: relPath(dest, manifestPath), kit_id: "runtime", asset_id: "strands-local.activation", source_path: manifestPath.split(path.sep).join("/") });
250
296
  return { selected_adapter: "strands-local", supported_asset_classes: supportedClasses, generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
251
297
  }
@@ -3,6 +3,7 @@ import fs from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import path from "node:path";
5
5
  import { loadJson, readText, root, walkFiles, writeText } from "./common.js";
6
+ import { CODEX_REASONING_EFFORTS, codexAgentRoutingErrors } from "./codex-agent-routing.js";
6
7
  import { DURABLE_FLOW_AGENTS_DIR, FLOW_AGENTS_RUNTIME_DIR } from "../lib/local-artifact-root.js";
7
8
  const dist = process.env.FLOW_AGENTS_DIST_DIR ? path.resolve(process.env.FLOW_AGENTS_DIST_DIR) : path.join(root, "dist");
8
9
  const manifest = loadJson(path.join(root, "packaging/manifest.json"));
@@ -196,8 +197,39 @@ function mapped(mapName, value) {
196
197
  const map = manifest[mapName];
197
198
  return map[modelPrefix(String(value ?? ""))] ?? map.default;
198
199
  }
200
+ function codexAgentOverride(spec) {
201
+ const override = manifest.codex_agent_map?.[spec.name];
202
+ if (override === undefined)
203
+ return undefined;
204
+ if (!override || typeof override !== "object" || Array.isArray(override)) {
205
+ throw new Error(`packaging/manifest.json: codex_agent_map.${spec.name} must be an object`);
206
+ }
207
+ const keys = Object.keys(override).sort();
208
+ if (keys.join(",") !== "model,reasoning_effort") {
209
+ throw new Error(`packaging/manifest.json: codex_agent_map.${spec.name} must contain exactly model and reasoning_effort`);
210
+ }
211
+ return override;
212
+ }
213
+ function validateCodexModel(model, context) {
214
+ const allowed = new Set(manifest.codex?.allowed_agent_models ?? []);
215
+ if (!allowed.has(model))
216
+ throw new Error(`packaging/manifest.json: ${context} uses unsupported Codex agent model '${model}'`);
217
+ return model;
218
+ }
199
219
  function resolveCodexModel(spec) {
200
- return ["tool-agent-delegate", "tool-agent-handoff"].includes(spec.name) ? "gpt-5.4-mini" : mapped("codex_model_map", spec.model);
220
+ const override = codexAgentOverride(spec)?.model;
221
+ if (typeof override === "string" && override)
222
+ return validateCodexModel(override, `codex_agent_map.${spec.name}.model`);
223
+ const fallback = ["tool-agent-delegate", "tool-agent-handoff"].includes(spec.name) ? "gpt-5.4-mini" : mapped("codex_model_map", spec.model);
224
+ return validateCodexModel(fallback, `resolved model for ${spec.name}`);
225
+ }
226
+ function resolveCodexReasoningEffort(spec) {
227
+ const override = codexAgentOverride(spec)?.reasoning_effort;
228
+ const effort = typeof override === "string" && override ? override : mapped("codex_reasoning_map", spec.model);
229
+ if (!CODEX_REASONING_EFFORTS.has(effort)) {
230
+ throw new Error(`packaging/manifest.json: resolved reasoning effort for ${spec.name} is invalid: '${effort}'`);
231
+ }
232
+ return effort;
201
233
  }
202
234
  function normalizeCodexText(text) {
203
235
  const replacements = { "—": "-", "–": "-", "→": "->", "←": "<-", "’": "'", "“": '"', "”": '"', "⚡": "[quick]", "🖥️": "[interactive]" };
@@ -239,7 +271,7 @@ function codexNicknames(agentName) {
239
271
  function exportCodexAgent(spec) {
240
272
  const prompt = appendExportNote(normalizeCodexText(sanitizeText(spec.prompt, "codex", "<bundle-root>")), "Exported from a Kiro agent spec. Hooks, resource auto-loading, and tool allowlists were converted into prompt guidance only. Any explicit Kiro scheduler commands are preserved as optional shell workflows.");
241
273
  const description = normalizeCodexText(String(spec.description ?? "")).replace(/"/g, "'");
242
- return `# exported from agents/${spec.name}.json\nname = "${spec.name}"\nnickname_candidates = ${JSON.stringify(codexNicknames(spec.name))}\ndescription = "${description}"\nmodel = "${resolveCodexModel(spec)}"\nmodel_reasoning_effort = "${mapped("codex_reasoning_map", spec.model)}"\ndeveloper_instructions = ${JSON.stringify(prompt)}\n`;
274
+ return `# exported from agents/${spec.name}.json\nname = "${spec.name}"\nnickname_candidates = ${JSON.stringify(codexNicknames(spec.name))}\ndescription = "${description}"\nmodel = "${resolveCodexModel(spec)}"\nmodel_reasoning_effort = "${resolveCodexReasoningEffort(spec)}"\ndeveloper_instructions = ${JSON.stringify(prompt)}\n`;
243
275
  }
244
276
  function tomlValue(value) {
245
277
  if (typeof value === "boolean")
@@ -763,6 +795,12 @@ export function main() {
763
795
  return 1;
764
796
  }
765
797
  const agents = fs.readdirSync(path.join(root, "agents")).filter((name) => name.endsWith(".json")).sort().map((name) => loadJson(path.join(root, "agents", name)));
798
+ const routingErrors = codexAgentRoutingErrors(manifest, agents.map((agent) => agent.name));
799
+ if (routingErrors.length) {
800
+ for (const error of routingErrors)
801
+ console.error(`flow-agents: packaging/manifest.json: ${error}`);
802
+ return 1;
803
+ }
766
804
  buildBase(agents);
767
805
  buildKiro(agents);
768
806
  buildClaudeCode(agents);
@@ -0,0 +1,2 @@
1
+ export declare const CODEX_REASONING_EFFORTS: Set<string>;
2
+ export declare function codexAgentRoutingErrors(manifest: any, agentNames: Iterable<string>): string[];
@@ -0,0 +1,49 @@
1
+ export const CODEX_REASONING_EFFORTS = new Set(["low", "medium", "high", "xhigh", "max", "ultra"]);
2
+ export function codexAgentRoutingErrors(manifest, agentNames) {
3
+ const errors = [];
4
+ const knownAgents = new Set(agentNames);
5
+ const allowedModels = manifest.codex?.allowed_agent_models;
6
+ if (!Array.isArray(allowedModels) || allowedModels.length === 0) {
7
+ errors.push("codex.allowed_agent_models must be a non-empty list");
8
+ }
9
+ const allowedModelSet = new Set(Array.isArray(allowedModels) ? allowedModels : []);
10
+ for (const model of allowedModelSet) {
11
+ if (typeof model !== "string" || !model)
12
+ errors.push("codex.allowed_agent_models entries must be non-empty strings");
13
+ }
14
+ if (allowedModelSet.size !== (Array.isArray(allowedModels) ? allowedModels.length : 0)) {
15
+ errors.push("codex.allowed_agent_models must not contain duplicates");
16
+ }
17
+ for (const [family, model] of Object.entries(manifest.codex_model_map ?? {})) {
18
+ if (!allowedModelSet.has(model))
19
+ errors.push(`codex_model_map.${family} uses unsupported model '${String(model)}'`);
20
+ }
21
+ for (const [family, effort] of Object.entries(manifest.codex_reasoning_map ?? {})) {
22
+ if (!CODEX_REASONING_EFFORTS.has(String(effort)))
23
+ errors.push(`codex_reasoning_map.${family} uses invalid reasoning effort '${String(effort)}'`);
24
+ }
25
+ const agentMap = manifest.codex_agent_map;
26
+ if (!agentMap || typeof agentMap !== "object" || Array.isArray(agentMap)) {
27
+ errors.push("codex_agent_map must be an object");
28
+ return errors;
29
+ }
30
+ for (const [agentName, override] of Object.entries(agentMap)) {
31
+ if (!knownAgents.has(agentName))
32
+ errors.push(`codex_agent_map agent '${agentName}' does not exist`);
33
+ if (!override || typeof override !== "object" || Array.isArray(override)) {
34
+ errors.push(`codex_agent_map.${agentName} must be an object`);
35
+ continue;
36
+ }
37
+ const entry = override;
38
+ if (Object.keys(entry).sort().join(",") !== "model,reasoning_effort") {
39
+ errors.push(`codex_agent_map.${agentName} must contain exactly model and reasoning_effort`);
40
+ }
41
+ if (!allowedModelSet.has(entry.model)) {
42
+ errors.push(`codex_agent_map.${agentName}.model uses unsupported model '${String(entry.model)}'`);
43
+ }
44
+ if (!CODEX_REASONING_EFFORTS.has(String(entry.reasoning_effort))) {
45
+ errors.push(`codex_agent_map.${agentName}.reasoning_effort is invalid: '${String(entry.reasoning_effort)}'`);
46
+ }
47
+ }
48
+ return errors;
49
+ }
@@ -27,6 +27,7 @@ const commands = [
27
27
  ["Claim lookup", "npm run workflow:sidecar -- claim <id> <dir>"],
28
28
  ["Context map drift", "npm run context-map:check"],
29
29
  ["Bundle build", "npm run build:bundles"],
30
+ ["Skill drift check", "flow-agents skill-drift-check"],
30
31
  ];
31
32
  function frontmatter(text) {
32
33
  if (!text.startsWith("---"))
@@ -3,6 +3,7 @@ import fs from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import path from "node:path";
5
5
  import { validateKitRepository as validateFlowKitRepository } from "../flow-kit/validate.js";
6
+ import { codexAgentRoutingErrors } from "./codex-agent-routing.js";
6
7
  import { loadJson, readText, rel, root, walkFiles } from "./common.js";
7
8
  class Reporter {
8
9
  errors = [];
@@ -31,6 +32,8 @@ const mirroredFiles = new Map([
31
32
  // fails `npm run validate:source` instead of silently accumulating.
32
33
  ["scripts/hooks/config-protection.js", { mirror: "context/scripts/hooks/config-protection.js", allowedDifferences: [] }],
33
34
  ["scripts/hooks/lib/config-protection-remedies.js", { mirror: "context/scripts/hooks/lib/config-protection-remedies.js", allowedDifferences: [] }],
35
+ ["scripts/hooks/lib/kit-catalog.js", { mirror: "context/scripts/hooks/lib/kit-catalog.js", allowedDifferences: [] }],
36
+ ["scripts/hooks/lib/runnable-command.js", { mirror: "context/scripts/hooks/lib/runnable-command.js", allowedDifferences: [] }],
34
37
  ["scripts/hooks/stop-goal-fit.js", { mirror: "context/scripts/hooks/stop-goal-fit.js", allowedDifferences: [] }],
35
38
  ["scripts/hooks/run-hook.js", { mirror: "context/scripts/hooks/run-hook.js", allowedDifferences: [] }],
36
39
  ["scripts/hooks/workflow-steering.js", { mirror: "context/scripts/hooks/workflow-steering.js", allowedDifferences: [] }],
@@ -82,9 +85,11 @@ const hookFilePolicies = new Map([
82
85
  ["scripts/hooks/desktop-notify.sh", { category: "local notification helper", requiredNeedles: ["desktop-notify.sh", "osascript"] }],
83
86
  ["scripts/hooks/lib/actor-identity.js", { category: "shared hook library", requiredNeedles: ["resolveActor", "FLOW_AGENTS_ACTOR"] }],
84
87
  ["scripts/hooks/lib/audit-transport.sh", { category: "shared hook library", requiredNeedles: ["audit_emit"] }],
88
+ ["scripts/hooks/lib/codex-exit-code.js", { category: "shared hook library", requiredNeedles: ["extractExitCodeFromBanner", "readExitCodeFromRollout"] }],
85
89
  ["scripts/hooks/lib/config-protection-remedies.js", { category: "shared hook library", requiredNeedles: ["SANCTIONED_REMEDIES", "REMEDY_COMMAND_CANDIDATES"] }],
86
90
  ["scripts/hooks/lib/current-pointer.js", { category: "shared hook library", requiredNeedles: ["readCurrentPointer", "perActorCurrentFile"] }],
87
91
  ["scripts/hooks/lib/hook-flags.js", { category: "shared hook library", requiredNeedles: ["isHookEnabled"] }],
92
+ ["scripts/hooks/lib/kit-catalog.js", { category: "shared hook library", requiredNeedles: ["readKitManifests", "workflowTriggersFor"] }],
88
93
  ["scripts/hooks/lib/liveness-heartbeat.js", { category: "shared hook library", requiredNeedles: ["maybeEmitHeartbeat", "resolveHeartbeatThrottleSeconds"] }],
89
94
  ["scripts/hooks/lib/liveness-policy.js", { category: "shared hook library", requiredNeedles: ["isLivenessEnabled", "resolveTtlSeconds"] }],
90
95
  ["scripts/hooks/lib/liveness-read.js", { category: "shared hook library", requiredNeedles: ["freshHolders", "readLivenessEvents"] }],
@@ -92,20 +97,26 @@ const hookFilePolicies = new Map([
92
97
  ["scripts/hooks/lib/local-artifact-paths.js", { category: "shared hook library", requiredNeedles: ["flowAgentsArtifactRoot", "defaultArtifactRootForRead"] }],
93
98
  ["scripts/hooks/lib/patterns.sh", { category: "shared hook library", requiredNeedles: ["_detect_secrets"] }],
94
99
  ["scripts/hooks/lib/resolve-formatter.js", { category: "shared hook library", requiredNeedles: ["resolveFormatter"] }],
100
+ ["scripts/hooks/lib/runnable-command.js", { category: "shared hook library", requiredNeedles: ["isRunnableCommandText"] }],
101
+ ["scripts/hooks/lib/skill-drift.js", { category: "shared hook library", requiredNeedles: ["compareSkillDrift", "buildManifest"] }],
95
102
  ]);
96
103
  const fixtureOwnerPolicies = new Map([
97
104
  ["evals/fixtures/assignment-provider", { owners: ["evals/integration/test_assignment_provider_local_file.sh", "evals/integration/test_assignment_provider_github.sh", "evals/integration/test_pull_work_assignment_join.sh", "evals/integration/test_ensure_session_ownership_guard.sh"], classification: "AssignmentProvider local-file and GitHub render/status fixtures (#290); hostile-effective-state.json is the #291 ensure-session ownership guard's AC9 sanitization fixture" }],
98
105
  ["evals/fixtures/backlog-provider-settings", { owners: ["evals/integration/test_effective_backlog_settings.sh"], classification: "settings precedence fixtures" }],
99
106
  ["evals/fixtures/builder-kit-workflow-state", { owners: ["evals/static/test_workflow_skills.sh"], classification: "Builder Kit workflow-state fixtures" }],
100
107
  ["evals/fixtures/console-learning-projection", { owners: ["evals/integration/test_console_learning_projection.sh"], classification: "console learning projection fixtures" }],
108
+ ["evals/fixtures/economics", { owners: ["evals/integration/test_economics_record.sh"], classification: "per-run kit-economics record fixtures (#349): a transcript with .message.usage blocks, state.json/acceptance.json/critique.json join sources, a session.usage event, and the golden expected kontour.console.economics record" }],
101
109
  ["evals/fixtures/flow-kit-repository", { owners: ["evals/integration/test_flow_kit_repository.sh", "evals/integration/test_local_flow_kit_install.sh", "evals/integration/test_runtime_adapter_activation.sh", "evals/integration/test_activate_npx_context.sh", "evals/integration/test_flow_kit_install_git.sh", "evals/static/test_workflow_skills.sh"], classification: "Flow Kit repository contract fixtures" }],
102
110
  ["evals/fixtures/kit-conformance-levels", { owners: ["evals/integration/test_kit_conformance_levels.sh"], classification: "K-level conformance and consumer-target derivation fixtures" }],
103
111
  ["evals/fixtures/hook-influence", { owners: ["evals/integration/test_hook_influence_cases.sh", "evals/static/test_workflow_skills.sh", "scripts/validate-hook-influence-cases.js"], classification: "hook influence behavioral cases" }],
112
+ ["evals/fixtures/learning-review-proposals", { owners: ["evals/integration/test_learning_review_proposals.sh"], classification: "learning-review kit/gate tuning proposal fixtures (#352): pattern-present (engineered cost-inflation + gate false-block-rate pattern, with sessions/ trust.bundle + gate-review.inquiries.json joins and a hand-computed expected-aggregates.json), balanced (proportional cost/findings movement -> zero proposals), under-threshold (below LR_MIN_WINDOW_SAMPLE), repeat-window (idempotency), and effect-follow-up (later-window effect-fill pass for a ratified proposal)" }],
104
113
  ["evals/fixtures/pull-work-provider", { owners: ["evals/integration/test_pull_work_provider.sh"], classification: "work item provider normalization fixtures" }],
105
114
  ["evals/fixtures/pull-work-wip-shepherding", { owners: ["evals/static/test_workflow_skills.sh"], classification: "WIP shepherding state fixtures" }],
106
115
  ["evals/fixtures/surface-trust", { owners: ["evals/integration/test_workflow_sidecar_writer.sh"], classification: "Surface trust evidence fixtures" }],
107
- ["evals/fixtures/trust-reconcile-exploits", { owners: ["evals/integration/test_trust_reconcile_negatives.sh"], classification: "WS8 trust-reconcile anti-gaming exploit fixtures (frozen negative regressions)" }],
108
- ["evals/fixtures/trust-reconcile-mixed-bundle", { owners: ["evals/integration/test_trust_reconcile_mixed_bundle.sh"], classification: "WS8 trust-reconcile mixed-evidence end-to-end proof fixture" }],
116
+ ["evals/fixtures/telemetry", { owners: ["evals/integration/test_usage_cost.sh", "evals/integration/test_telemetry_usage_pipeline.sh"], classification: "hermetic Stop-hook usage transcript fixture (#defect-2026-07 telemetry-usage-cost-extraction): a multi-model JSONL transcript with .message.model + .message.usage token blocks proving real token/cost/model extraction end-to-end" }],
117
+ ["evals/fixtures/reconcile-preflight", { owners: ["evals/integration/test_reconcile_preflight.sh"], classification: "#356 reconcile-preflight shape fixtures not already covered by trust-reconcile-exploits (un-superseded disputed critique, standalone disputed session-local claim)" }],
118
+ ["evals/fixtures/trust-reconcile-exploits", { owners: ["evals/integration/test_trust_reconcile_negatives.sh", "evals/integration/test_reconcile_preflight.sh"], classification: "WS8 trust-reconcile anti-gaming exploit fixtures (frozen negative regressions); also reused by the #356 local reconcile-preflight eval (same shapes, no forked copies)" }],
119
+ ["evals/fixtures/trust-reconcile-mixed-bundle", { owners: ["evals/integration/test_trust_reconcile_mixed_bundle.sh", "evals/integration/test_reconcile_preflight.sh"], classification: "WS8 trust-reconcile mixed-evidence end-to-end proof fixture; also reused by the #356 reconcile-preflight eval as its CLEAN-BUNDLE (AC4) case" }],
109
120
  ["evals/fixtures/trust-reconcile-ws3", { owners: ["evals/integration/test_trust_reconcile_negatives.sh"], classification: "WS8 AC6 backward-compat fixture: real ws3-kit-dependencies-namespacing old-style bundle" }],
110
121
  ["evals/fixtures/usage-feedback", { owners: ["evals/integration/test_usage_feedback_import.sh", "evals/integration/test_usage_feedback_outcomes.sh", "evals/integration/test_usage_feedback_report.sh"], classification: "usage feedback import/outcome fixtures" }],
111
122
  ["evals/fixtures/veritas-governance-adapter", { owners: ["evals/integration/test_veritas_governance_adapter.sh"], classification: "Veritas governance adapter fixtures" }],
@@ -129,6 +140,9 @@ const allowedPythonCommandFiles = [
129
140
  // as prose). These are literal recognizer tokens, NOT a flow-agents Python dependency — no
130
141
  // .py file is ever invoked — so the two byte-identical mirror copies are exempt here.
131
142
  /^(context\/)?scripts\/hooks\/stop-goal-fit\.js$/,
143
+ // #412: the recognizer allowlist itself now lives in scripts/hooks/lib/runnable-command.js
144
+ // (single source of truth), so the same literal-token exemption applies there too.
145
+ /^(context\/)?scripts\/hooks\/lib\/runnable-command\.js$/,
132
146
  ];
133
147
  function tryLoadJson(file, reporter) {
134
148
  try {
@@ -216,6 +230,8 @@ function validateManifest(reporter, manifest, agentNames) {
216
230
  console.log(`warning: ${rel(manifestPath)} optional_copy_dirs entry absent: ${dir}`);
217
231
  for (const agent of manifest.codex?.excluded_agents ?? [])
218
232
  reporter.check(agentNames.has(agent), `${rel(manifestPath)}: codex excluded agent '${agent}' does not exist`);
233
+ for (const error of codexAgentRoutingErrors(manifest, agentNames))
234
+ reporter.fail(`${rel(manifestPath)}: ${error}`);
219
235
  }
220
236
  async function validateKitRepository(kitDir, reporter) {
221
237
  if (!fs.existsSync(kitDir) || !fs.statSync(kitDir).isDirectory()) {
@@ -273,6 +289,16 @@ function validateLegacyRefs(reporter) {
273
289
  // Collect all kit-owned asset relative paths so legacy-ref scanning can skip matches
274
290
  // that are subpaths of kit-owned assets. E.g. legacyRefRe matches "skills/plan-work/SKILL.md"
275
291
  // within "kits/builder/skills/plan-work/SKILL.md"; the kit declares and validates these.
292
+ //
293
+ // Deliberately NO directory-level exemption: a ref must be an exact registered path, or a
294
+ // parent directory of one, to be skipped. A kit that ships a helper file living alongside a
295
+ // registered asset (e.g. a skill's own script next to its SKILL.md) must register that
296
+ // helper as its own kit.json entry (e.g. under an "assets" section) rather than relying on
297
+ // sibling-directory proximity — widening the exemption to "any file in the same directory
298
+ // as a registered path" would silence real missing-path detection for every OTHER file that
299
+ // happens to sit next to a registered asset, not just the intended one. See
300
+ // kits/veritas-governance/kit.json's "assets" section for the registration this exact case
301
+ // uses.
276
302
  const kitOwnedSubPaths = new Set();
277
303
  const kitsDir = path.join(root, "kits");
278
304
  if (fs.existsSync(kitsDir)) {
@@ -311,7 +337,8 @@ function validateLegacyRefs(reporter) {
311
337
  continue;
312
338
  // Skip refs that are declared kit-owned asset paths or their parent directories
313
339
  // (e.g. "skills/plan-work/SKILL.md" or "skills/plan-work" matched inside
314
- // "kits/builder/skills/plan-work/SKILL.md" in eval files).
340
+ // "kits/builder/skills/plan-work/SKILL.md" in eval files). No directory-level
341
+ // exemption — see the kitOwnedSubPaths comment above.
315
342
  if (kitOwnedSubPaths.has(ref) || [...kitOwnedSubPaths].some((p) => p.startsWith(ref + "/")))
316
343
  continue;
317
344
  const candidates = [path.join(root, ref), ...(ref.startsWith("evals/") ? [] : [path.join(root, "evals", ref)])];
@@ -44,8 +44,7 @@ candidate selector — do not build new discovery logic. `active_wip`,
44
44
  `active_learning_followup`, `cleanup_candidate`, and `invalid` sessions are **never**
45
45
  archival candidates on age alone.
46
46
 
47
- Procedure (documented; archival itself remains a deliberate, non-automated move — the audit
48
- tool "does not delete, archive," by design):
47
+ Procedure (manual, hand-driven):
49
48
 
50
49
  1. `node build/src/cli/workflow-artifact-cleanup-audit.js <artifact-root>` to classify.
51
50
  2. From the `terminal_done` bucket, select those whose `state.json` `updatedAt` predates the
@@ -53,6 +52,21 @@ tool "does not delete, archive," by design):
53
52
  3. Move each selected `<artifact-root>/<slug>/` to `<artifact-root>/archive/<slug>/`
54
53
  (a human-reviewed or explicitly-invoked move — not a silent background job).
55
54
 
55
+ Automated `--apply` mode: `workflow-artifact-cleanup-audit --apply` (see
56
+ `docs/workflow-artifact-lifecycle.md`'s "Apply Mode" section) automates the
57
+ `cleanup_candidate`/aged-`terminal_done` selection above, but archive-moves to a
58
+ *different*, sibling destination — `.kontourai/flow-agents-archive/<date>-<runid>/<slug>/`,
59
+ next to the artifact root, not `<artifact-root>/archive/<slug>/`. The run-scoped,
60
+ sibling root keeps two same-day `--apply` runs from colliding and keeps the artifact
61
+ root's own scan set stable while a run is in progress; it is not a replacement for the
62
+ in-tree `<artifact-root>/archive/<slug>/` convention above, which remains the right
63
+ destination for a manual, hand-driven move. `--apply` never deletes anything (archive-move
64
+ only) and is gated by hard safety rails (never `active_wip`/`active_learning_followup`,
65
+ never a held liveness claim, never inside the freshness window, never an infrastructure
66
+ directory, never a substantive session misclassified `invalid` by a schema nit) — see the
67
+ doc section for the full model, including the `--apply-ambiguous` second gate and the
68
+ `MANIFEST.md`/`--confirm` evidence record.
69
+
56
70
  ## Persistence Integrity
57
71
 
58
72
  Writing a durable artifact must **fail loud, never fail-open.** If a record (state, evidence, a