@kontourai/flow-agents 2.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/.github/CODEOWNERS +16 -0
  2. package/.github/workflows/ci.yml +44 -0
  3. package/.github/workflows/trust-reconcile.yml +62 -4
  4. package/AGENTS.md +4 -4
  5. package/CHANGELOG.md +62 -0
  6. package/CONTEXT.md +35 -0
  7. package/README.md +9 -3
  8. package/agents/dev.json +1 -1
  9. package/agents/tool-code-reviewer.json +1 -1
  10. package/agents/tool-planner.json +3 -3
  11. package/agents/tool-verifier.json +3 -3
  12. package/build/src/cli/assignment-provider.d.ts +1 -0
  13. package/build/src/cli/assignment-provider.js +748 -0
  14. package/build/src/cli/console-learning-projection.js +3 -2
  15. package/build/src/cli/effective-assignment-provider-settings.d.ts +1 -0
  16. package/build/src/cli/effective-assignment-provider-settings.js +125 -0
  17. package/build/src/cli/init.js +104 -21
  18. package/build/src/cli/kit.js +37 -6
  19. package/build/src/cli/pull-work-provider.js +1 -1
  20. package/build/src/cli/usage-feedback.js +3 -3
  21. package/build/src/cli/validate-hook-influence.js +1 -0
  22. package/build/src/cli/validate-workflow-artifacts.js +27 -7
  23. package/build/src/cli/workflow-artifact-cleanup-audit.js +36 -2
  24. package/build/src/cli/workflow-sidecar.d.ts +14 -7
  25. package/build/src/cli/workflow-sidecar.js +938 -168
  26. package/build/src/cli.js +6 -0
  27. package/build/src/flow-kit/validate.d.ts +23 -0
  28. package/build/src/flow-kit/validate.js +67 -1
  29. package/build/src/index.d.ts +1 -1
  30. package/build/src/index.js +1 -1
  31. package/build/src/lib/flow-resolver.d.ts +6 -4
  32. package/build/src/lib/flow-resolver.js +125 -58
  33. package/build/src/lib/local-artifact-root.d.ts +14 -2
  34. package/build/src/lib/local-artifact-root.js +22 -5
  35. package/build/src/lib/workflow-learning-projection.js +2 -2
  36. package/build/src/runtime-adapters.d.ts +12 -0
  37. package/build/src/runtime-adapters.js +56 -16
  38. package/build/src/tools/build-universal-bundles.js +49 -16
  39. package/build/src/tools/generate-context-map.js +10 -8
  40. package/build/src/tools/validate-source-tree.js +24 -1
  41. package/console.telemetry.json +22 -0
  42. package/context/contracts/artifact-contract.md +40 -7
  43. package/context/contracts/assignment-provider-contract.md +239 -0
  44. package/context/contracts/builder-kit-workflow-state-contract.md +5 -2
  45. package/context/contracts/decision-registry-contract.md +265 -0
  46. package/context/contracts/delivery-contract.md +3 -1
  47. package/context/contracts/execution-contract.md +25 -0
  48. package/context/contracts/governance-adapter-contract.md +2 -0
  49. package/context/contracts/knowledge-store-contract.md +197 -0
  50. package/context/contracts/planning-contract.md +4 -1
  51. package/context/contracts/review-contract.md +7 -3
  52. package/context/contracts/sandbox-policy.md +2 -0
  53. package/context/contracts/standing-directives.md +13 -0
  54. package/context/contracts/verification-contract.md +11 -1
  55. package/context/contracts/work-item-contract.md +3 -1
  56. package/context/deferred/parallelization.md +1 -1
  57. package/context/gate-awareness.md +4 -4
  58. package/context/scripts/git-status.sh +2 -2
  59. package/context/scripts/hooks/config-protection.js +502 -2
  60. package/context/scripts/hooks/lib/config-protection-remedies.js +69 -0
  61. package/context/scripts/hooks/run-hook.js +14 -1
  62. package/context/scripts/hooks/stop-goal-fit.js +1140 -99
  63. package/context/scripts/hooks/workflow-steering.js +212 -6
  64. package/context/settings/assignment-provider-settings.json +33 -0
  65. package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
  66. package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +35 -0
  67. package/docs/adr/0019-kit-dependency-ownership.md +62 -0
  68. package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +290 -0
  69. package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +221 -0
  70. package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +463 -0
  71. package/docs/agent-system-guidebook.md +3 -3
  72. package/docs/agent-usage-feedback-loop.md +6 -6
  73. package/docs/context-map.md +10 -7
  74. package/docs/decisions/decision-records.md +57 -0
  75. package/docs/decisions/index.md +17 -0
  76. package/docs/decisions/knowledge-store-provider.md +51 -0
  77. package/docs/decisions/model-routing.md +63 -0
  78. package/docs/decisions/promotion-gate.md +52 -0
  79. package/docs/decisions/standing-directives.md +66 -0
  80. package/docs/developer-architecture.md +1 -1
  81. package/docs/fixture-ownership.md +4 -0
  82. package/docs/flow-kit-repository-contract.md +5 -5
  83. package/docs/getting-started.md +12 -2
  84. package/docs/integrations/harness-install.md +2 -1
  85. package/docs/kit-authoring-guide.md +64 -5
  86. package/docs/knowledge-kit.md +5 -1
  87. package/docs/learnings/2026-07-improvement-program.md +203 -0
  88. package/docs/migrations.md +6 -5
  89. package/docs/north-star.md +2 -2
  90. package/docs/operating-layers.md +4 -3
  91. package/docs/repository-structure.md +5 -5
  92. package/docs/skills-map.md +8 -8
  93. package/docs/spec/runtime-hook-surface.md +19 -15
  94. package/docs/spikes/graph-provider-2026-07.md +155 -0
  95. package/docs/standards-register.md +7 -7
  96. package/docs/trust-anchor-adoption.md +105 -4
  97. package/docs/work-item-adapters.md +1 -1
  98. package/docs/workflow-artifact-lifecycle.md +72 -9
  99. package/docs/workflow-eval-strategy.md +3 -3
  100. package/docs/workflow-shared-contracts.md +3 -2
  101. package/docs/workflow-usage-guide.md +260 -21
  102. package/evals/acceptance/DEMO-false-completion.md +20 -11
  103. package/evals/acceptance/prove-capture-teeth-declared.sh +15 -15
  104. package/evals/acceptance/prove-capture-teeth.sh +11 -11
  105. package/evals/acceptance/test_kiro_harness.sh +1 -1
  106. package/evals/ci/antigaming-suite.sh +5 -0
  107. package/evals/ci/run-baseline.sh +71 -1
  108. package/evals/fixtures/assignment-provider/actor-a.json +6 -0
  109. package/evals/fixtures/assignment-provider/actor-b.json +6 -0
  110. package/evals/fixtures/assignment-provider/github-issue-claimed.json +27 -0
  111. package/evals/fixtures/assignment-provider/github-issue-unassigned.json +7 -0
  112. package/evals/fixtures/assignment-provider/liveness-fresh.json +9 -0
  113. package/evals/fixtures/assignment-provider/liveness-stale.json +9 -0
  114. package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/flows/review.flow.json +26 -0
  115. package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/kit.json +13 -0
  116. package/evals/fixtures/flow-kit-repository/valid-with-dependency/flows/review.flow.json +26 -0
  117. package/evals/fixtures/flow-kit-repository/valid-with-dependency/kit.json +13 -0
  118. package/evals/fixtures/hook-influence/cases.json +32 -0
  119. package/evals/fixtures/pull-work-provider/github-issues.json +6 -6
  120. package/evals/fixtures/trust-reconcile-exploits/fabricated-attestation.json +59 -0
  121. package/evals/fixtures/trust-reconcile-exploits/no-label-bypass.json +36 -0
  122. package/evals/fixtures/trust-reconcile-exploits/skip-assumed-bypass.json +59 -0
  123. package/evals/fixtures/trust-reconcile-exploits/status-misassertion.json +48 -0
  124. package/evals/fixtures/trust-reconcile-exploits/waived-command-check.json +44 -0
  125. package/evals/fixtures/trust-reconcile-mixed-bundle/mixed-bundle.json +159 -0
  126. package/evals/fixtures/trust-reconcile-ws3/ws3-bundle.json +920 -0
  127. package/evals/integration/test_actor_identity.sh +254 -0
  128. package/evals/integration/test_assignment_provider_github.sh +318 -0
  129. package/evals/integration/test_assignment_provider_local_file.sh +222 -0
  130. package/evals/integration/test_builder_step_producers.sh +33 -23
  131. package/evals/integration/test_bundle_install.sh +22 -16
  132. package/evals/integration/test_bundle_lifecycle.sh +11 -11
  133. package/evals/integration/test_captured_fail_reconciliation.sh +50 -50
  134. package/evals/integration/test_checkpoint_signing.sh +43 -0
  135. package/evals/integration/test_claim_lookup.sh +4 -4
  136. package/evals/integration/test_codex_hook_resolution.sh +114 -0
  137. package/evals/integration/test_command_log_concurrency.sh +8 -8
  138. package/evals/integration/test_command_log_fork_classification.sh +3 -3
  139. package/evals/integration/test_command_log_integrity.sh +16 -16
  140. package/evals/integration/test_console_learning_projection.sh +1 -1
  141. package/evals/integration/test_critique_supersession_roundtrip.sh +182 -0
  142. package/evals/integration/test_dual_emit_flow_step.sh +145 -0
  143. package/evals/integration/test_enforcer_expects_driven.sh +12 -12
  144. package/evals/integration/test_evidence_capture_hook.sh +14 -13
  145. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  146. package/evals/integration/test_flow_agents_statusline.sh +2 -2
  147. package/evals/integration/test_flow_kit_install_git.sh +52 -0
  148. package/evals/integration/test_flow_kit_repository.sh +2 -0
  149. package/evals/integration/test_flowdef_session_activation.sh +6 -6
  150. package/evals/integration/test_flowdef_union_floor_regression.sh +429 -0
  151. package/evals/integration/test_gate_bypass_chain.sh +52 -40
  152. package/evals/integration/test_gate_lockdown.sh +97 -73
  153. package/evals/integration/test_gate_review_inquiry_records.sh +3 -3
  154. package/evals/integration/test_goal_fit_escape_hatch.sh +10 -10
  155. package/evals/integration/test_goal_fit_ghost_session.sh +104 -0
  156. package/evals/integration/test_goal_fit_hook.sh +32 -32
  157. package/evals/integration/test_goal_fit_rederive.sh +9 -9
  158. package/evals/integration/test_hook_category_behaviors.sh +103 -0
  159. package/evals/integration/test_hook_influence_cases.sh +1 -0
  160. package/evals/integration/test_install_merge.sh +262 -1
  161. package/evals/integration/test_kit_identity_trust.sh +6 -6
  162. package/evals/integration/test_liveness_conflict_injection.sh +587 -0
  163. package/evals/integration/test_liveness_heartbeat.sh +677 -0
  164. package/evals/integration/test_liveness_verdict.sh +394 -0
  165. package/evals/integration/test_local_flow_kit_install.sh +15 -0
  166. package/evals/integration/test_phase_map_and_gate_claim.sh +63 -11
  167. package/evals/integration/test_promote_gate.sh +178 -0
  168. package/evals/integration/test_publish_delivery.sh +23 -6
  169. package/evals/integration/test_pull_work_assignment_join.sh +132 -0
  170. package/evals/integration/test_pull_work_liveness_preflight.sh +394 -0
  171. package/evals/integration/test_pull_work_provider.sh +2 -2
  172. package/evals/integration/test_reconcile_soundness.sh +37 -13
  173. package/evals/integration/test_resolvefirststep_security.sh +22 -0
  174. package/evals/integration/test_runtime_adapter_activation.sh +87 -18
  175. package/evals/integration/test_session_resume_roundtrip.sh +147 -0
  176. package/evals/integration/test_sidecar_field_preservation.sh +249 -0
  177. package/evals/integration/test_trust_reconcile.sh +11 -10
  178. package/evals/integration/test_trust_reconcile_manifest.sh +79 -0
  179. package/evals/integration/test_trust_reconcile_mixed_bundle.sh +96 -0
  180. package/evals/integration/test_trust_reconcile_negatives.sh +759 -0
  181. package/evals/integration/test_usage_feedback_global.sh +5 -5
  182. package/evals/integration/test_validate_artifacts_portability.sh +60 -0
  183. package/evals/integration/test_verify_cli.sh +2 -2
  184. package/evals/integration/test_veritas_governance_kit.sh +117 -0
  185. package/evals/integration/test_workflow_artifact_cleanup_audit.sh +26 -1
  186. package/evals/integration/test_workflow_artifacts.sh +33 -0
  187. package/evals/integration/test_workflow_sidecar_writer.sh +802 -74
  188. package/evals/integration/test_workflow_steering_hook.sh +175 -5
  189. package/evals/lib/codex-provider.sh +1 -1
  190. package/evals/lib/node.sh +19 -1
  191. package/evals/run.sh +32 -0
  192. package/evals/static/test_decisions.sh +150 -0
  193. package/evals/static/test_flowdef_codeowners_coverage.sh +62 -0
  194. package/evals/static/test_knowledge_providers.sh +23 -0
  195. package/evals/static/test_package.sh +19 -10
  196. package/evals/static/test_universal_bundles.sh +48 -3
  197. package/evals/static/test_workflow_skills.sh +41 -8
  198. package/kits/builder/flows/build.flow.json +3 -74
  199. package/kits/builder/flows/publish-learn.flow.json +90 -0
  200. package/kits/builder/kit.json +11 -0
  201. package/kits/builder/skills/builder-shape/SKILL.md +1 -1
  202. package/kits/builder/skills/deliver/SKILL.md +32 -11
  203. package/kits/builder/skills/design-probe/SKILL.md +4 -4
  204. package/kits/builder/skills/evidence-gate/SKILL.md +26 -1
  205. package/kits/builder/skills/execute-plan/SKILL.md +2 -2
  206. package/kits/builder/skills/fix-bug/SKILL.md +2 -0
  207. package/kits/builder/skills/gate-review/SKILL.md +6 -6
  208. package/kits/builder/skills/idea-to-backlog/SKILL.md +1 -1
  209. package/kits/builder/skills/learning-review/SKILL.md +7 -7
  210. package/kits/builder/skills/pickup-probe/SKILL.md +5 -4
  211. package/kits/builder/skills/plan-work/SKILL.md +27 -5
  212. package/kits/builder/skills/pull-work/SKILL.md +148 -5
  213. package/kits/builder/skills/release-readiness/SKILL.md +4 -4
  214. package/kits/builder/skills/review-work/SKILL.md +24 -1
  215. package/kits/builder/skills/tdd-workflow/SKILL.md +2 -0
  216. package/kits/builder/skills/verify-work/SKILL.md +8 -1
  217. package/kits/catalog.json +6 -0
  218. package/kits/knowledge/kit.json +35 -0
  219. package/kits/knowledge/providers/conformance/fixtures/git-repo/CONTEXT.md +12 -0
  220. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/old-sprocket-shape.md +13 -0
  221. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/sprocket-shape.md +14 -0
  222. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/widget-format.md +14 -0
  223. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/learnings/fixture-learning.md +7 -0
  224. package/kits/knowledge/providers/conformance/fixtures/work-item/issues.json +30 -0
  225. package/kits/knowledge/providers/conformance/suite.test.js +125 -0
  226. package/kits/knowledge/providers/git-repo/index.js +236 -0
  227. package/kits/knowledge/providers/health/health-pass.test.js +99 -0
  228. package/kits/knowledge/providers/health/index.js +153 -0
  229. package/kits/knowledge/providers/index.js +24 -0
  230. package/kits/knowledge/providers/lib/model.js +91 -0
  231. package/kits/knowledge/providers/lib/schema-validate.js +119 -0
  232. package/kits/knowledge/providers/markdown-vault/index.js +169 -0
  233. package/kits/knowledge/providers/work-item/index.js +204 -0
  234. package/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +178 -0
  235. package/kits/veritas-governance/docs/README.md +75 -0
  236. package/kits/veritas-governance/fixtures/readiness/not-ready.readiness-report.json +4645 -0
  237. package/kits/veritas-governance/fixtures/readiness/ready.readiness-report.json +4403 -0
  238. package/kits/veritas-governance/flows/readiness-check.flow.json +35 -0
  239. package/kits/veritas-governance/kit.json +16 -0
  240. package/package.json +11 -5
  241. package/packaging/README.md +2 -2
  242. package/packaging/conformance/fixtures/config-protection--allow-read-state-json.json +20 -0
  243. package/packaging/conformance/fixtures/config-protection--block-write-state-json.json +20 -0
  244. package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +1 -1
  245. package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +1 -1
  246. package/packaging/manifest.json +9 -10
  247. package/schemas/assignment-provider-settings.schema.json +125 -0
  248. package/schemas/decision-record.schema.json +109 -0
  249. package/schemas/knowledge/edge.schema.json +54 -0
  250. package/schemas/knowledge/health-report.schema.json +45 -0
  251. package/schemas/knowledge/node.schema.json +49 -0
  252. package/schemas/knowledge/proposal.schema.json +53 -0
  253. package/schemas/workflow-evidence.schema.json +1 -1
  254. package/schemas/workflow-state.schema.json +5 -0
  255. package/scripts/README.md +8 -3
  256. package/scripts/check-content-boundary.cjs +1 -1
  257. package/scripts/check-decisions.cjs +356 -0
  258. package/scripts/ci/derive-claim-status.mjs +74 -0
  259. package/scripts/ci/trust-reconcile.js +957 -103
  260. package/scripts/git-status.sh +2 -2
  261. package/scripts/hooks/claude-telemetry-hook.js +37 -3
  262. package/scripts/hooks/codex-telemetry-hook.js +36 -2
  263. package/scripts/hooks/config-protection.js +102 -30
  264. package/scripts/hooks/evidence-capture.js +3 -3
  265. package/scripts/hooks/lib/actor-identity.js +368 -0
  266. package/scripts/hooks/lib/config-protection-remedies.js +69 -0
  267. package/scripts/hooks/lib/liveness-heartbeat.js +361 -0
  268. package/scripts/hooks/lib/liveness-policy.js +127 -0
  269. package/scripts/hooks/lib/liveness-read.js +105 -12
  270. package/scripts/hooks/lib/liveness-write.js +46 -0
  271. package/scripts/hooks/lib/local-artifact-paths.js +12 -6
  272. package/scripts/hooks/opencode-telemetry-hook.js +27 -1
  273. package/scripts/hooks/pi-telemetry-hook.js +27 -1
  274. package/scripts/hooks/stop-goal-fit.js +157 -26
  275. package/scripts/hooks/workflow-steering.js +77 -3
  276. package/scripts/install-codex-home.sh +138 -38
  277. package/scripts/statusline/flow-agents-statusline.js +2 -2
  278. package/src/cli/assignment-provider.ts +845 -0
  279. package/src/cli/console-learning-projection.ts +3 -2
  280. package/src/cli/effective-assignment-provider-settings.ts +112 -0
  281. package/src/cli/init.ts +101 -21
  282. package/src/cli/kit.ts +37 -6
  283. package/src/cli/public-api.test.mjs +58 -6
  284. package/src/cli/pull-work-provider.ts +1 -1
  285. package/src/cli/trust-bundle-policy-order.test.mjs +87 -0
  286. package/src/cli/usage-feedback.ts +3 -3
  287. package/src/cli/validate-hook-influence.ts +1 -0
  288. package/src/cli/validate-workflow-artifacts.ts +27 -7
  289. package/src/cli/veritas-readiness-adapter.test.mjs +267 -0
  290. package/src/cli/workflow-artifact-cleanup-audit.ts +35 -2
  291. package/src/cli/workflow-sidecar.ts +933 -168
  292. package/src/cli.ts +6 -0
  293. package/src/flow-kit/validate.ts +74 -1
  294. package/src/index.ts +6 -2
  295. package/src/lib/flow-resolver.ts +123 -55
  296. package/src/lib/local-artifact-root.ts +24 -5
  297. package/src/lib/workflow-learning-projection.ts +2 -2
  298. package/src/runtime-adapters.ts +61 -15
  299. package/src/tools/build-universal-bundles.ts +49 -16
  300. package/src/tools/generate-context-map.ts +10 -8
  301. package/src/tools/validate-source-tree.ts +24 -1
@@ -1,6 +1,8 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { readJson, relPath, writeJson } from "./lib/fs.js";
4
+ import { flowAgentsArtifactRoot } from "./lib/local-artifact-root.js";
5
+ import { parseKitDependencies } from "./flow-kit/validate.js";
4
6
  const ASSET_CLASSES = ["flows", "skills", "docs", "adapters", "evals", "assets"];
5
7
  function assetPath(root, value) {
6
8
  if (path.isAbsolute(value))
@@ -13,7 +15,7 @@ function loadKitAssets(kitRoot, sourceKind, warnings, errors) {
13
15
  const manifestPath = path.join(kitRoot, "kit.json");
14
16
  if (!fs.existsSync(manifestPath)) {
15
17
  errors.push(`${kitRoot}: missing kit.json`);
16
- return [];
18
+ return { kit_id: "", assets: [], dependencies: [] };
17
19
  }
18
20
  let manifest;
19
21
  try {
@@ -21,13 +23,13 @@ function loadKitAssets(kitRoot, sourceKind, warnings, errors) {
21
23
  }
22
24
  catch (error) {
23
25
  errors.push(`${manifestPath}: ${error.message}`);
24
- return [];
26
+ return { kit_id: "", assets: [], dependencies: [] };
25
27
  }
26
28
  const kitId = typeof manifest.id === "string" ? manifest.id : "";
27
29
  const kitName = typeof manifest.name === "string" ? manifest.name : kitId;
28
30
  if (!kitId) {
29
31
  errors.push(`${manifestPath}: missing kit id`);
30
- return [];
32
+ return { kit_id: "", assets: [], dependencies: [] };
31
33
  }
32
34
  const assets = [];
33
35
  for (const assetClass of ASSET_CLASSES) {
@@ -66,12 +68,20 @@ function loadKitAssets(kitRoot, sourceKind, warnings, errors) {
66
68
  assets.push({ kit_id: kitId, kit_name: kitName, asset_class: assetClass, asset_id: assetId, relative_path: rel, source_path: source, source_kind: sourceKind, description });
67
69
  }
68
70
  }
69
- return assets;
71
+ // Parse cross-kit dependency declarations (extension-layer metadata; see
72
+ // docs/adr/0019-kit-dependency-ownership.md). Shape errors surface at
73
+ // install/inspect/validate time via validateKitRepository; here we only collect
74
+ // the edges so activation can enforce presence against the full inventory.
75
+ const dependencies = parseKitDependencies(manifest, manifestPath).entries
76
+ .map((dep) => ({ from_kit_id: kitId, kit_id: dep.kit_id, ...(dep.reason ? { reason: dep.reason } : {}) }));
77
+ return { kit_id: kitId, assets, dependencies };
70
78
  }
71
79
  export function readKitInventory(sourceRoot, dest) {
72
80
  const warnings = [];
73
81
  const errors = [];
74
82
  const assets = [];
83
+ const dependencies = [];
84
+ const presentKitIds = new Set();
75
85
  const catalogPath = path.join(sourceRoot, "kits", "catalog.json");
76
86
  if (!fs.existsSync(catalogPath))
77
87
  warnings.push(`${catalogPath}: built-in Kit Catalog not found; skipping built-in kits (this is normal when running outside a flow-agents checkout)`);
@@ -88,8 +98,13 @@ export function readKitInventory(sourceRoot, dest) {
88
98
  const kitRoot = assetPath(sourceRoot, rel);
89
99
  if (!kitRoot || !fs.existsSync(kitRoot))
90
100
  warnings.push(`${catalogPath}: kits[${index}].path unavailable: ${rel}`);
91
- else
92
- assets.push(...loadKitAssets(kitRoot, "builtin", warnings, errors));
101
+ else {
102
+ const loaded = loadKitAssets(kitRoot, "builtin", warnings, errors);
103
+ assets.push(...loaded.assets);
104
+ dependencies.push(...loaded.dependencies);
105
+ if (loaded.kit_id)
106
+ presentKitIds.add(loaded.kit_id);
107
+ }
93
108
  });
94
109
  }
95
110
  }
@@ -108,11 +123,24 @@ export function readKitInventory(sourceRoot, dest) {
108
123
  const kitRoot = path.join(dest, "kits", "local", "repositories", id);
109
124
  if (!fs.existsSync(kitRoot))
110
125
  warnings.push(`${id}: installed kit copy missing at ${kitRoot}; skipping`);
111
- else
112
- assets.push(...loadKitAssets(kitRoot, "local", warnings, errors));
126
+ else {
127
+ const loaded = loadKitAssets(kitRoot, "local", warnings, errors);
128
+ assets.push(...loaded.assets);
129
+ dependencies.push(...loaded.dependencies);
130
+ if (loaded.kit_id)
131
+ presentKitIds.add(loaded.kit_id);
132
+ }
113
133
  });
114
134
  }
115
135
  }
136
+ // Activation-time cross-kit dependency enforcement: any declared dependency whose
137
+ // kit_id is not present among the union of built-in catalog kits and locally-installed
138
+ // kits is a hard error (feeds the errors[] -> activation-manifest -> non-zero exit path).
139
+ for (const dep of dependencies) {
140
+ if (!presentKitIds.has(dep.kit_id)) {
141
+ 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
+ }
143
+ }
116
144
  return { assets, warnings, errors };
117
145
  }
118
146
  function safeSegment(value) {
@@ -125,7 +153,7 @@ function safeSegment(value) {
125
153
  const ACTIVATED_ASSET_CLASSES = new Set(["flows", "skills", "docs"]);
126
154
  export function activateCodexLocal(sourceRoot, dest) {
127
155
  const inventory = readKitInventory(sourceRoot, dest);
128
- const runtimeDir = path.join(dest, ".flow-agents", "runtime", "codex");
156
+ const runtimeDir = path.join(flowAgentsArtifactRoot(dest), "projections", "codex");
129
157
  const generated = [];
130
158
  const skipped = [];
131
159
  for (const asset of inventory.assets) {
@@ -144,7 +172,13 @@ export function activateCodexLocal(sourceRoot, dest) {
144
172
  // agent's guidance index (AGENTS.md) can reference them and they are co-located
145
173
  // with flow definitions for the same kit.
146
174
  const filename = path.basename(asset.source_path);
147
- const output = path.join(runtimeDir, asset.asset_class, safeSegment(asset.kit_id), filename);
175
+ // Namespace skills by BOTH kit id AND the skill's own directory name: every skill
176
+ // file is literally named SKILL.md, so keying only on kit id collides all of a
177
+ // kit's skills onto one .../<kit-id>/SKILL.md file. Docs keep the flat
178
+ // <kit-id>/<filename> layout (no per-directory collision found).
179
+ const output = asset.asset_class === "skills"
180
+ ? path.join(runtimeDir, asset.asset_class, safeSegment(asset.kit_id), safeSegment(path.basename(path.dirname(asset.source_path))), filename)
181
+ : path.join(runtimeDir, asset.asset_class, safeSegment(asset.kit_id), filename);
148
182
  fs.mkdirSync(path.dirname(output), { recursive: true });
149
183
  fs.copyFileSync(asset.source_path, output);
150
184
  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,11 +203,11 @@ export function activateCodexLocal(sourceRoot, dest) {
169
203
  // layout knowledge. The Strands steering layer then reads the written runtime files.
170
204
  export function activateStrandsLocal(sourceRoot, dest) {
171
205
  const inventory = readKitInventory(sourceRoot, dest);
172
- // Runtime flows land at .flow-agents/runtime/strands/flows/<kit-id>/<asset-id>.flow.json
206
+ // Runtime flows land at .kontourai/flow-agents/projections/strands/flows/<kit-id>/<asset-id>.flow.json
173
207
  // so the Strands steering context can glob for *.flow.json under this path.
174
- // Runtime skills land at .flow-agents/runtime/strands/skills/<kit-id>/<filename> and
175
- // docs at .flow-agents/runtime/strands/docs/<kit-id>/<filename> for system-prompt injection.
176
- const runtimeDir = path.join(dest, ".flow-agents", "runtime", "strands");
208
+ // Runtime skills land at .kontourai/flow-agents/projections/strands/skills/<kit-id>/<filename> and
209
+ // docs at .kontourai/flow-agents/projections/strands/docs/<kit-id>/<filename> for system-prompt injection.
210
+ const runtimeDir = path.join(flowAgentsArtifactRoot(dest), "projections", "strands");
177
211
  const generated = [];
178
212
  const skipped = [];
179
213
  for (const asset of inventory.assets) {
@@ -190,9 +224,15 @@ export function activateStrandsLocal(sourceRoot, dest) {
190
224
  else if (asset.asset_class === "skills" || asset.asset_class === "docs") {
191
225
  // Mirror the codex-local layout: strands/<class>/<kit-id>/<filename>.
192
226
  // The Strands system-prompt injection layer can glob for all *.md files under
193
- // .flow-agents/runtime/strands/skills/ to include agent guidance in the context.
227
+ // .kontourai/flow-agents/projections/strands/skills/ to include agent guidance in the context.
194
228
  const filename = path.basename(asset.source_path);
195
- const output = path.join(runtimeDir, asset.asset_class, safeSegment(asset.kit_id), filename);
229
+ // Namespace skills by BOTH kit id AND the skill's own directory name: every skill
230
+ // file is literally named SKILL.md, so keying only on kit id collides all of a
231
+ // kit's skills onto one .../<kit-id>/SKILL.md file. Docs keep the flat
232
+ // <kit-id>/<filename> layout (no per-directory collision found).
233
+ const output = asset.asset_class === "skills"
234
+ ? path.join(runtimeDir, asset.asset_class, safeSegment(asset.kit_id), safeSegment(path.basename(path.dirname(asset.source_path))), filename)
235
+ : path.join(runtimeDir, asset.asset_class, safeSegment(asset.kit_id), filename);
196
236
  fs.mkdirSync(path.dirname(output), { recursive: true });
197
237
  fs.copyFileSync(asset.source_path, output);
198
238
  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("/") });
@@ -3,11 +3,19 @@ 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 { DURABLE_FLOW_AGENTS_DIR, FLOW_AGENTS_RUNTIME_DIR } from "../lib/local-artifact-root.js";
6
7
  const dist = process.env.FLOW_AGENTS_DIST_DIR ? path.resolve(process.env.FLOW_AGENTS_DIST_DIR) : path.join(root, "dist");
7
8
  const manifest = loadJson(path.join(root, "packaging/manifest.json"));
8
9
  const pkgVersion = loadJson(path.join(root, "package.json"))["version"] ?? "0.0.0";
10
+ const runtimeTaskDir = FLOW_AGENTS_RUNTIME_DIR;
11
+ const durableInstallRecordRel = `${DURABLE_FLOW_AGENTS_DIR}/install.json`;
9
12
  const textExtensions = new Set([".css", ".html", ".js", ".json", ".md", ".sh", ".toml", ".txt", ".yaml", ".yml", ".ts"]);
10
13
  const dropDiagnostics = [];
14
+ // Set by collectAllSkills() when two kits (or a kit and the top-level skills/
15
+ // dir) declare the same skill directory name. Reported via the same
16
+ // console.error + process.exitCode diagnostic idiom as the rest of this file
17
+ // (see main()) instead of an uncaught thrown Error / stack trace.
18
+ let skillCollisionDiagnostic = null;
11
19
  const printDiagnostics = !["0", "false", "no"].includes(String(process.env.FLOW_AGENTS_EXPORT_DIAGNOSTICS ?? "1").toLowerCase());
12
20
  /**
13
21
  * Collect all skill source paths across skills/ and kit-owned skills.
@@ -18,16 +26,28 @@ const printDiagnostics = !["0", "false", "no"].includes(String(process.env.FLOW_
18
26
  */
19
27
  function collectAllSkills() {
20
28
  const results = [];
21
- const seen = new Set();
29
+ const seen = new Map(); // skill name -> source description of first declaration
30
+ const collisions = [];
31
+ // Compiled host bundles ship skills flat at .claude/skills/<name>/SKILL.md etc., so
32
+ // the install name (skill directory name) must be globally unique across skills/ and
33
+ // every kit. A duplicate is a build-FAILING collision, never a silent drop — the
34
+ // previous Set-based dedupe silently kept only the first and discarded the rest.
35
+ const recordSkill = (name, src, source) => {
36
+ const prior = seen.get(name);
37
+ if (prior !== undefined) {
38
+ collisions.push(` - '${name}' is declared by both ${prior} and ${source}`);
39
+ return;
40
+ }
41
+ seen.set(name, source);
42
+ results.push({ name, src });
43
+ };
22
44
  // 1. Top-level skills/ directory (tools pending reclassification).
23
45
  const skillsDir = path.join(root, "skills");
24
46
  if (fs.existsSync(skillsDir)) {
25
47
  for (const skill of fs.readdirSync(skillsDir).sort()) {
26
48
  const skillPath = path.join(skillsDir, skill, "SKILL.md");
27
- if (fs.existsSync(skillPath) && !seen.has(skill)) {
28
- seen.add(skill);
29
- results.push({ name: skill, src: skillPath });
30
- }
49
+ if (fs.existsSync(skillPath))
50
+ recordSkill(skill, skillPath, "skills/");
31
51
  }
32
52
  }
33
53
  // 2. Kit-owned skills declared in kits/<kit>/kit.json `skills` arrays.
@@ -55,13 +75,18 @@ function collectAllSkills() {
55
75
  // Derive install name from the directory containing SKILL.md (one level up).
56
76
  const absPath = path.resolve(path.join(kitsDir, kitName), relPath);
57
77
  const skillName = path.basename(path.dirname(absPath));
58
- if (fs.existsSync(absPath) && !seen.has(skillName)) {
59
- seen.add(skillName);
60
- results.push({ name: skillName, src: absPath });
61
- }
78
+ if (fs.existsSync(absPath))
79
+ recordSkill(skillName, absPath, `kits/${kitName}`);
62
80
  }
63
81
  }
64
82
  }
83
+ if (collisions.length) {
84
+ skillCollisionDiagnostic =
85
+ "flow-agents: skill name collision — skill directory names must be unique across " +
86
+ "skills/ and all kits/*/kit.json:\n" +
87
+ collisions.join("\n") +
88
+ "\nRename one of the colliding skill directories so every compiled bundle skill has a unique install name.";
89
+ }
65
90
  return results.sort((a, b) => a.name.localeCompare(b.name));
66
91
  }
67
92
  function resetDir(dir) {
@@ -190,7 +215,7 @@ function generatedAgentsSummary(agents) {
190
215
  function exportRootAgentsMd(label, agents, taskDir) {
191
216
  return `# Universal Agent Bundle (${label})\n\nThis bundle was generated from the canonical source in this repo. Treat the repo root as the source of truth and regenerate the bundle instead of editing exported agent files by hand.\n\n## Shared Conventions\n\n- \`skills/\`, \`context/\`, \`powers/\`, \`prompts/\`, \`scripts/\`, and \`evals/\` were copied from the canonical source.\n- Cross-session task artifacts should live under \`${taskDir}\`.\n- Kiro-only hook wiring was stripped from exported non-Kiro agents to keep the package portable.\n\n## Exported Agents\n\n${generatedAgentsSummary(agents)}\n`;
192
217
  }
193
- const CODEX_LIVE_HOOKS_README = `\n## Running with hooks active (live)\n\n\`install.sh\` lays the bundle into a workspace, but a live Codex session needs a \`CODEX_HOME\` that has both the bundle's hooks/scripts AND your real credentials. Use the dedicated installer, which flattens the config to the home root and copies your auth from \`~/.codex\`:\n\n\`\`\`bash\nbash scripts/install-codex-home.sh "$HOME/.flow-agents/codex"\nCODEX_HOME="$HOME/.flow-agents/codex" codex exec --dangerously-bypass-hook-trust -C /path/to/project "<prompt>"\n\`\`\`\n\nThe goal-fit Stop hook then enforces by default (\`FLOW_AGENTS_GOAL_FIT_MODE=block\`); set it to \`warn\` or \`off\` to override.\n`;
218
+ const CODEX_LIVE_HOOKS_README = `\n## Running with hooks active (live)\n\n\`install.sh\` lays the bundle into a workspace, but a live Codex session needs a \`CODEX_HOME\` that has both the bundle's hooks/scripts AND your real credentials. Use the dedicated installer, which defaults to \`CODEX_HOME\` when set and otherwise \`~/.codex\`, flattens the config to the home root, and copies your auth from \`~/.codex\` when installing elsewhere:\n\n\`\`\`bash\nbash scripts/install-codex-home.sh\ncodex exec --dangerously-bypass-hook-trust -C /path/to/project \"<prompt>\"\n\`\`\`\n\nPass a positional destination to install into an explicit alternate Codex home.\n\nThe goal-fit Stop hook then enforces by default (\`FLOW_AGENTS_GOAL_FIT_MODE=block\`); set it to \`warn\` or \`off\` to override.\n`;
194
219
  function exportTargetReadme(label, installHint, extra = "") {
195
220
  return `# ${label} Bundle\n\nGenerated from the canonical source in this repository.\n\n## Install\n\n\`\`\`bash\n${installHint}\n\`\`\`\n\nThe install ships the full standalone base (skills, agents, powers) plus the\nFlow Kits. Kit depth is activated through the Kit Catalog, not at install time.\n\n## Contents\n\n- Harness-specific agents\n- Shared skills\n- Shared context, powers, prompts, scripts, and evals\n${extra}`;
196
221
  }
@@ -282,7 +307,7 @@ function claudePolicy(event, script, envPrefix = "") {
282
307
  return `bash -lc 'root="\${CLAUDE_PROJECT_DIR:-$(pwd)}"; ${envPrefix}node "$root/scripts/hooks/claude-hook-adapter.js" ${event} ${script.replace(/\.js$/, "")} ${script} default'`;
283
308
  }
284
309
  function codexRoot(scriptPath) {
285
- return `root="\${CODEX_HOME:-}"; if [ -z "$root" ] || [ ! -f "$root/${scriptPath}" ]; then root=$(git rev-parse --show-toplevel 2>/dev/null || pwd); fi`;
310
+ return `root="\${CODEX_HOME:-}"; if [ -z "$root" ] || [ ! -f "$root/${scriptPath}" ]; then root=$(git rev-parse --show-toplevel 2>/dev/null || pwd); fi; if [ ! -f "$root/${scriptPath}" ] && [ -f "$HOME/.codex/${scriptPath}" ]; then root="$HOME/.codex"; fi; if [ ! -f "$root/${scriptPath}" ]; then echo "flow-agents: hook script not found at $root/${scriptPath} (checked \\$CODEX_HOME, git toplevel/cwd, and \\$HOME/.codex) - skipping hook" >&2; exit 0; fi`;
286
311
  }
287
312
  function codexTelemetry(event) {
288
313
  if (event === "PermissionRequest")
@@ -358,9 +383,9 @@ function installScript(label, defaultDestDisplay, token, destFallbackShell, merg
358
383
  const requiredCheck = destRequired ? `if [[ -z "$DEST" ]]; then\n usage\n exit 2\nfi\n` : "";
359
384
  const usageDest = destRequired ? "/path/to/workspace" : defaultDestDisplay;
360
385
  const mergeBlock = mergeConfig
361
- ? `\nif command -v node >/dev/null 2>&1; then\n node "$DEST/scripts/install-merge.js" --config "$DEST/${mergeConfig.configRelPath}" --managed-hooks "$SRC/${mergeConfig.managedConfigRelPath}" --version "${mergeConfig.version}" --install-record "$DEST/.flow-agents/install.json" --runtime "${mergeConfig.runtime}" || true\nfi`
386
+ ? `\nif command -v node >/dev/null 2>&1; then\n node "$DEST/scripts/install-merge.js" --config "$DEST/${mergeConfig.configRelPath}" --managed-hooks "$SRC/${mergeConfig.managedConfigRelPath}" --version "${mergeConfig.version}" --install-record "$DEST/${durableInstallRecordRel}" --runtime "${mergeConfig.runtime}" || true\nfi`
362
387
  : stampConfig
363
- ? `\nif command -v node >/dev/null 2>&1; then\n node "$DEST/scripts/install-merge.js" --stamp-only --version "${stampConfig.version}" --install-record "$DEST/.flow-agents/install.json" --runtime "${stampConfig.runtime}" || true\nfi`
388
+ ? `\nif command -v node >/dev/null 2>&1; then\n node "$DEST/scripts/install-merge.js" --stamp-only --version "${stampConfig.version}" --install-record "$DEST/${durableInstallRecordRel}" --runtime "${stampConfig.runtime}" || true\nfi`
364
389
  : "";
365
390
  return `#!/usr/bin/env bash\nset -euo pipefail\n\nusage() {\n cat >&2 <<'EOF'\nusage: bash install.sh ${usageDest} [options]\n\nOptions:\n --telemetry-sink NAME local-files, local-kontour-console,\n kontour-hosted-console, user-hosted-console,\n or legacy aliases. May be repeated.\n --console-url URL Persist Console telemetry base URL.\n --console-endpoint URL Persist full Console telemetry records endpoint URL.\n --console-token-file PATH\n Read Console telemetry bearer token from a file.\n --console-tenant ID Persist Console tenant identifier.\nEOF\n}\n\nDEST=""\nDEST_SET=0\nCONSOLE_CONFIG_ARGS=()\nwhile [[ $# -gt 0 ]]; do\n case "$1" in\n --telemetry-sink|--telemetry-sinks|--console-url|--console-endpoint|--console-endpoint-url|--console-token-file|--console-tenant|--console-tenant-id)\n [[ $# -ge 2 ]] || { echo "install.sh: $1 requires a value" >&2; exit 2; }\n CONSOLE_CONFIG_ARGS+=("$1" "$2")\n shift 2\n ;;\n --help|-h)\n usage\n exit 0\n ;;\n -*)\n echo "install.sh: unknown option: $1" >&2\n usage\n exit 2\n ;;\n *)\n if [[ "$DEST_SET" -eq 1 ]]; then\n echo "install.sh: unexpected argument: $1" >&2\n usage\n exit 2\n fi\n DEST="$1"\n DEST_SET=1\n shift\n ;;\n esac\ndone${destFallback}\n${requiredCheck}SRC="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"\n\nmkdir -p "$DEST"\nrsync -a ${token ? "--delete " : ""}${mergeConfig ? `--exclude="${mergeConfig.configRelPath}" ` : ""}"$SRC"/ "$DEST"/${replaceBlock}${mergeBlock}\nif [[ \${#CONSOLE_CONFIG_ARGS[@]} -gt 0 || -n "\${FLOW_AGENTS_TELEMETRY_SINK:-}" || -n "\${FLOW_AGENTS_TELEMETRY_SINKS:-}" || -n "\${FLOW_AGENTS_CONSOLE_URL:-}" || -n "\${CONSOLE_TELEMETRY_URL:-}" || -n "\${CONSOLE_URL:-}" || -n "\${FLOW_AGENTS_CONSOLE_TOKEN_FILE:-}" || -n "\${CONSOLE_TELEMETRY_TOKEN_FILE:-}" ]]; then\n bash "$DEST/scripts/telemetry/install-console-config.sh" "$DEST/scripts/telemetry/telemetry.conf" "\${CONSOLE_CONFIG_ARGS[@]}"\nfi\necho "Installed ${label} bundle ${token ? "to" : "into"} $DEST"\n`;
366
391
  }
@@ -368,8 +393,8 @@ function buildBase(agents) {
368
393
  const bundle = path.join(dist, "base");
369
394
  resetDir(bundle);
370
395
  copySharedContent(bundle, "base", "<bundle-root>");
371
- writeText(path.join(bundle, ".flow-agents", ".gitkeep"), "");
372
- writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Base", agents, ".flow-agents"));
396
+ writeText(path.join(bundle, runtimeTaskDir, ".gitkeep"), "");
397
+ writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Base", agents, runtimeTaskDir));
373
398
  writeText(path.join(bundle, "README.md"), exportTargetReadme("Base", "bash install.sh /path/to/workspace"));
374
399
  writeText(path.join(bundle, "install.sh"), installScript("Base", "/path/to/workspace", undefined, undefined, undefined, { runtime: "base", version: pkgVersion }));
375
400
  fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
@@ -381,7 +406,7 @@ function buildKiro(agents) {
381
406
  copySharedContent(bundle, "kiro", token);
382
407
  for (const spec of agents)
383
408
  writeText(path.join(bundle, "agents", `${spec.name}.json`), sanitizeText(`${JSON.stringify(sanitizeAgentJson(spec), null, 2)}\n`, "kiro", token));
384
- writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Kiro", agents, ".flow-agents"));
409
+ writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Kiro", agents, runtimeTaskDir));
385
410
  writeText(path.join(bundle, "README.md"), exportTargetReadme("Kiro", "bash install.sh $HOME/.flow-agents"));
386
411
  writeText(path.join(bundle, "install.sh"), installScript("Kiro", "$HOME/.flow-agents", token, '${FLOW_AGENTS_DEST:-$HOME/.flow-agents}', undefined, { runtime: "kiro", version: pkgVersion }));
387
412
  fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
@@ -729,6 +754,14 @@ function buildCatalog(agents) {
729
754
  }
730
755
  export function main() {
731
756
  fs.mkdirSync(dist, { recursive: true });
757
+ // Populate (and, on collision, set) skillCollisionDiagnostic before any
758
+ // build step writes output -- fail fast with a clear diagnostic instead of
759
+ // partially building bundles from a deduped/ambiguous skill set.
760
+ collectAllSkills();
761
+ if (skillCollisionDiagnostic) {
762
+ console.error(skillCollisionDiagnostic);
763
+ return 1;
764
+ }
732
765
  const agents = fs.readdirSync(path.join(root, "agents")).filter((name) => name.endsWith(".json")).sort().map((name) => loadJson(path.join(root, "agents", name)));
733
766
  buildBase(agents);
734
767
  buildKiro(agents);
@@ -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 { exists, loadJson, markdownTable, oneLine, readText, rel, root, writeText } from "./common.js";
6
+ import { DURABLE_FLOW_AGENTS_DIR, FLOW_AGENTS_RUNTIME_DIR } from "../lib/local-artifact-root.js";
6
7
  const defaultOutput = path.join(root, "docs/context-map.md");
7
8
  const dirDescriptions = {
8
9
  agents: "Canonical agent specs and routing prompts.",
@@ -21,7 +22,7 @@ const commands = [
21
22
  ["Source tree", "npm run validate:source"],
22
23
  ["Static suite", "bash evals/run.sh static"],
23
24
  ["Integration suite", "bash evals/run.sh integration"],
24
- ["Workflow artifacts", "npm run workflow:validate-artifacts -- --require-sidecars --require-critique .flow-agents/<slug>"],
25
+ ["Workflow artifacts", `npm run workflow:validate-artifacts -- --require-sidecars --require-critique ${FLOW_AGENTS_RUNTIME_DIR}/<slug>`],
25
26
  ["Workflow sidecars", "npm run workflow:sidecar -- --help"],
26
27
  ["Claim lookup", "npm run workflow:sidecar -- claim <id> <dir>"],
27
28
  ["Context map drift", "npm run context-map:check"],
@@ -73,7 +74,8 @@ function repoShape(manifest) {
73
74
  rows.push([dir, "optional", "Optional local/user pack copied when present."]);
74
75
  }
75
76
  rows.push(["dist", "generated", "Generated bundle exports. Do not edit by hand."]);
76
- rows.push([".flow-agents", "runtime", "Cross-session workflow artifacts and sidecars. Not committed by default."]);
77
+ rows.push([FLOW_AGENTS_RUNTIME_DIR, "runtime", "Non-durable workflow artifacts, sidecars, and generated projections. Not committed by default."]);
78
+ rows.push([DURABLE_FLOW_AGENTS_DIR, "durable local state", "Explicit Flow Agents config/install state. Not a runtime artifact fallback."]);
77
79
  return rows;
78
80
  }
79
81
  /** Collect all skill {name, absPath} pairs from skills/ and kit-owned skills. */
@@ -159,15 +161,15 @@ function latestRuntimeStates(includeRuntime) {
159
161
  if (!includeRuntime) {
160
162
  return [
161
163
  "Runtime workflow state is excluded from the committed map.",
162
- "Regenerate locally with `npm run context-map -- --include-runtime` to include recent `.flow-agents` state.",
164
+ `Regenerate locally with \`npm run context-map -- --include-runtime\` to include recent \`${FLOW_AGENTS_RUNTIME_DIR}\` state.`,
163
165
  ];
164
166
  }
165
- const workflowDir = path.join(root, ".flow-agents");
167
+ const workflowDir = path.join(root, FLOW_AGENTS_RUNTIME_DIR);
166
168
  if (!exists(workflowDir))
167
- return ["No local workflow state found under `.flow-agents`."];
169
+ return [`No local workflow state found under \`${FLOW_AGENTS_RUNTIME_DIR}\`.`];
168
170
  const states = fs.readdirSync(workflowDir).map((name) => path.join(workflowDir, name, "state.json")).filter(exists).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
169
171
  if (!states.length)
170
- return ["No local workflow state found under `.flow-agents`."];
172
+ return [`No local workflow state found under \`${FLOW_AGENTS_RUNTIME_DIR}\`.`];
171
173
  const rows = states.slice(0, 8).map((file) => {
172
174
  const data = loadJson(file);
173
175
  const next = typeof data.next_action === "object" && data.next_action ? data.next_action : {};
@@ -184,11 +186,11 @@ function render(includeRuntime) {
184
186
  "## How To Use This", "",
185
187
  "- Start here when a session is long, resumed, or context-constrained.",
186
188
  "- Load only the specific skill, contract, schema, or doc that matches the task.",
187
- "- Treat `.flow-agents` as runtime state and `dist/` as generated output.", "",
189
+ `- Treat \`${FLOW_AGENTS_RUNTIME_DIR}\` as runtime state and \`dist/\` as generated output.`, "",
188
190
  "## Repository Shape", "", ...markdownTable(["Path", "Role", "Purpose"], repoShape(manifest)), "",
189
191
  "## Core Commands", "", ...markdownTable(["Use", "Command"], commands), "",
190
192
  "## Workflow Sidecars", "",
191
- "Machine-readable workflow state lives beside Markdown artifacts in `.flow-agents/<slug>/`.", "",
193
+ `Machine-readable workflow state lives beside Markdown artifacts in \`${FLOW_AGENTS_RUNTIME_DIR}/<slug>/\`.`, "",
192
194
  ...markdownTable(["Schema", "Title", "ID"], schemas()), "",
193
195
  "Primary tools: `npm run workflow:sidecar`, `npm run workflow:validate-artifacts`, `scripts/hooks/stop-goal-fit.js`, and `scripts/hooks/workflow-steering.js`.", "",
194
196
  "## Workflow Skills", "", ...markdownTable(["Skill", "Source", "When To Load"], workflowRows), "",
@@ -25,6 +25,15 @@ const mirroredFiles = new Map([
25
25
  ["scripts/telemetry/console-presets.sh", { mirror: "context/scripts/telemetry/console-presets.sh", allowedDifferences: [] }],
26
26
  ["scripts/telemetry/install-console-config.sh", { mirror: "context/scripts/telemetry/install-console-config.sh", allowedDifferences: [] }],
27
27
  ["scripts/discover-agents.sh", { mirror: "context/scripts/discover-agents.sh", allowedDifferences: [['ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"', 'ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"']] }],
28
+ // WS8 (AC9): scripts/hooks/* is the actively-maintained source of truth; context/ holds
29
+ // the exported mirror. These four hooks (and the config-protection remedy lib) had drifted
30
+ // silently because nothing validated them. Enforce byte-identical mirrors so future drift
31
+ // fails `npm run validate:source` instead of silently accumulating.
32
+ ["scripts/hooks/config-protection.js", { mirror: "context/scripts/hooks/config-protection.js", allowedDifferences: [] }],
33
+ ["scripts/hooks/lib/config-protection-remedies.js", { mirror: "context/scripts/hooks/lib/config-protection-remedies.js", allowedDifferences: [] }],
34
+ ["scripts/hooks/stop-goal-fit.js", { mirror: "context/scripts/hooks/stop-goal-fit.js", allowedDifferences: [] }],
35
+ ["scripts/hooks/run-hook.js", { mirror: "context/scripts/hooks/run-hook.js", allowedDifferences: [] }],
36
+ ["scripts/hooks/workflow-steering.js", { mirror: "context/scripts/hooks/workflow-steering.js", allowedDifferences: [] }],
28
37
  ]);
29
38
  const publicScriptWrappers = new Map([
30
39
  ["scripts/build-universal-bundles.js", { target: "../build/src/tools/build-universal-bundles.js", significantLines: [
@@ -71,14 +80,20 @@ const hookFilePolicies = new Map([
71
80
  ["scripts/hooks/utterance-check.js", { category: "policy hook", requiredNeedles: ["Utterance Check Hook", "FLOW_AGENTS_UTTERANCE_CHECK_ENABLED"] }],
72
81
  ["scripts/hooks/workflow-steering.js", { category: "policy hook", requiredNeedles: ["Workflow Steering Hook"] }],
73
82
  ["scripts/hooks/desktop-notify.sh", { category: "local notification helper", requiredNeedles: ["desktop-notify.sh", "osascript"] }],
83
+ ["scripts/hooks/lib/actor-identity.js", { category: "shared hook library", requiredNeedles: ["resolveActor", "FLOW_AGENTS_ACTOR"] }],
74
84
  ["scripts/hooks/lib/audit-transport.sh", { category: "shared hook library", requiredNeedles: ["audit_emit"] }],
85
+ ["scripts/hooks/lib/config-protection-remedies.js", { category: "shared hook library", requiredNeedles: ["SANCTIONED_REMEDIES", "REMEDY_COMMAND_CANDIDATES"] }],
75
86
  ["scripts/hooks/lib/hook-flags.js", { category: "shared hook library", requiredNeedles: ["isHookEnabled"] }],
87
+ ["scripts/hooks/lib/liveness-heartbeat.js", { category: "shared hook library", requiredNeedles: ["maybeEmitHeartbeat", "resolveHeartbeatThrottleSeconds"] }],
88
+ ["scripts/hooks/lib/liveness-policy.js", { category: "shared hook library", requiredNeedles: ["isLivenessEnabled", "resolveTtlSeconds"] }],
76
89
  ["scripts/hooks/lib/liveness-read.js", { category: "shared hook library", requiredNeedles: ["freshHolders", "readLivenessEvents"] }],
90
+ ["scripts/hooks/lib/liveness-write.js", { category: "shared hook library", requiredNeedles: ["appendLivenessEvent", "livenessStreamFile"] }],
77
91
  ["scripts/hooks/lib/local-artifact-paths.js", { category: "shared hook library", requiredNeedles: ["flowAgentsArtifactRoot", "defaultArtifactRootForRead"] }],
78
92
  ["scripts/hooks/lib/patterns.sh", { category: "shared hook library", requiredNeedles: ["_detect_secrets"] }],
79
93
  ["scripts/hooks/lib/resolve-formatter.js", { category: "shared hook library", requiredNeedles: ["resolveFormatter"] }],
80
94
  ]);
81
95
  const fixtureOwnerPolicies = new Map([
96
+ ["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"], classification: "AssignmentProvider local-file and GitHub render/status fixtures (#290)" }],
82
97
  ["evals/fixtures/backlog-provider-settings", { owners: ["evals/integration/test_effective_backlog_settings.sh"], classification: "settings precedence fixtures" }],
83
98
  ["evals/fixtures/builder-kit-workflow-state", { owners: ["evals/static/test_workflow_skills.sh"], classification: "Builder Kit workflow-state fixtures" }],
84
99
  ["evals/fixtures/console-learning-projection", { owners: ["evals/integration/test_console_learning_projection.sh"], classification: "console learning projection fixtures" }],
@@ -88,6 +103,9 @@ const fixtureOwnerPolicies = new Map([
88
103
  ["evals/fixtures/pull-work-provider", { owners: ["evals/integration/test_pull_work_provider.sh"], classification: "work item provider normalization fixtures" }],
89
104
  ["evals/fixtures/pull-work-wip-shepherding", { owners: ["evals/static/test_workflow_skills.sh"], classification: "WIP shepherding state fixtures" }],
90
105
  ["evals/fixtures/surface-trust", { owners: ["evals/integration/test_workflow_sidecar_writer.sh"], classification: "Surface trust evidence fixtures" }],
106
+ ["evals/fixtures/trust-reconcile-exploits", { owners: ["evals/integration/test_trust_reconcile_negatives.sh"], classification: "WS8 trust-reconcile anti-gaming exploit fixtures (frozen negative regressions)" }],
107
+ ["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" }],
108
+ ["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" }],
91
109
  ["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" }],
92
110
  ["evals/fixtures/veritas-governance-adapter", { owners: ["evals/integration/test_veritas_governance_adapter.sh"], classification: "Veritas governance adapter fixtures" }],
93
111
  ]);
@@ -98,13 +116,18 @@ const requiredUsageFeedbackFiles = [
98
116
  const fixtureOwnershipSelfAuditRefs = new Set([
99
117
  "evals/integration/test_fixture_retirement_audit.sh",
100
118
  ]);
101
- const pythonInventoryExcludes = new Set([".git", ".flow-agents", "node_modules", ".venv", "dist", "__pycache__", ".pytest_cache", ".cache", "build", "integrations"]);
119
+ const pythonInventoryExcludes = new Set([".git", ".flow-agents", ".kontourai", "node_modules", ".venv", "dist", "__pycache__", ".pytest_cache", ".cache", "build", "integrations"]);
102
120
  const pythonCommandScanRoots = ["README.md", "docs", "context", "skills", "prompts", "agents", "evals", "scripts", "packaging", "package.json"];
103
121
  const allowedPythonCommandFiles = [
104
122
  /^agents\/tool-explore-deps\.json$/,
105
123
  /^evals\/results\//,
106
124
  /^evals\/lib\/python\.sh$/,
107
125
  /(^|\/)telemetry\/lib\/enrich\.sh$/,
126
+ // WS8 (AC10b): stop-goal-fit.js lists python/python3 in its first-token command-RECOGNIZER
127
+ // allowlist (so honest command evidence from a polyglot adopter project is not misclassified
128
+ // as prose). These are literal recognizer tokens, NOT a flow-agents Python dependency — no
129
+ // .py file is ever invoked — so the two byte-identical mirror copies are exempt here.
130
+ /^(context\/)?scripts\/hooks\/stop-goal-fit\.js$/,
108
131
  ];
109
132
  function tryLoadJson(file, reporter) {
110
133
  try {
@@ -217,6 +217,28 @@
217
217
  },
218
218
  "limit": 10
219
219
  },
220
+ {
221
+ "id": "builder.publish-learn",
222
+ "label": "Builder publish and learn",
223
+ "match": {
224
+ "attribute": "flow",
225
+ "includes": "builder.publish-learn"
226
+ },
227
+ "titleAttribute": "title",
228
+ "detailAttributes": {
229
+ "Project": "project",
230
+ "Repository": "repo",
231
+ "Task": "taskSlug",
232
+ "Status": "status",
233
+ "Outcome": "outcome",
234
+ "Phase": "phase",
235
+ "Skill": "skill",
236
+ "Agent": "agent",
237
+ "Model": "model",
238
+ "Observed": "observedAt"
239
+ },
240
+ "limit": 10
241
+ },
220
242
  {
221
243
  "id": "delivery",
222
244
  "label": "Delivery workflows",
@@ -1,14 +1,16 @@
1
1
  # Workflow Artifact Contract
2
2
 
3
- Workflow artifacts are the durable handoff surface between orchestrators, workers, verifiers, hooks, evals, and future sessions.
3
+ > Read [`context/contracts/standing-directives.md`](standing-directives.md) ratified owner directives that override default engineering conservatism.
4
+
5
+ Workflow artifacts are the resumable local handoff surface between orchestrators, workers, verifiers, hooks, evals, and future sessions.
4
6
 
5
7
  ## Artifact Root
6
8
 
7
- Use the task artifact root defined by the active distribution's bundle instructions. This source repo has a historical default task root, but distributions may substitute their own root during export.
9
+ Use the task artifact root defined by the active distribution's bundle instructions. This source repo writes and discovers non-durable task artifacts under `.kontourai/flow-agents/<slug>/` by default.
8
10
 
9
11
  Do not hard-code a different root inside a skill or agent when the distribution has already defined one.
10
12
 
11
- In this source tree, `.flow-agents/<slug>/` is the local runtime/session state root by default. Exported agent bundles may map the runtime root to a distribution-specific path through their bundle instructions; treat those paths as local runtime roots, not durable product documentation.
13
+ In this source tree, `.kontourai/flow-agents/<slug>/` is the local runtime/session state root by default. `.flow-agents/` is reserved for Flow Agents-owned durable config/install state; do not put non-durable workflow sessions there. Exported agent bundles may map the runtime root to a distribution-specific path through their bundle instructions; treat those paths as local runtime roots, not durable product documentation.
12
14
 
13
15
  The artifact root is local working memory unless a workflow explicitly promotes or publishes it:
14
16
 
@@ -17,8 +19,39 @@ The artifact root is local working memory unless a workflow explicitly promotes
17
19
  - Archive completed local records under `<artifact-root>/<slug>/archive/<date>/` when they are useful for audit or recovery but should not remain the active session.
18
20
  - Promote stable decisions, usage guidance, release notes, and accepted architecture changes into durable docs such as `docs/`, ADRs, changelogs, or provider-backed descriptions/comments.
19
21
  - Publish provider records only through the provider adapter or explicit publish-change step. Provider records may link back to local artifacts, but they do not make local runtime files durable by themselves.
20
- - Do not commit local workflow runtime roots such as `.flow-agents/<slug>/` as durable policy unless a repository-specific contract explicitly says that artifact is promoted.
21
- - Do not commit local workflow runtime roots such as `.flow-agents/<slug>/`; final acceptance must promote durable content before merge.
22
+ - Do not commit local workflow runtime roots such as `.kontourai/flow-agents/<slug>/` as durable policy unless a repository-specific contract explicitly says that artifact is promoted.
23
+ - Do not commit local workflow runtime roots such as `.kontourai/flow-agents/<slug>/`; final acceptance must promote durable content before merge.
24
+
25
+ ## Session Archival Policy
26
+
27
+ Two archive locations exist and serve distinct purposes — do not conflate them:
28
+
29
+ - **Per-session, in-place history:** `<artifact-root>/<slug>/archive/<date>/` (above) holds
30
+ point-in-time snapshots of an *active* session's own records for audit/recovery while the
31
+ session remains in the active listing.
32
+ - **Whole-session archival (canonical):** `<artifact-root>/archive/<slug>/` is where an
33
+ entire terminal session is *moved out of the active listing*. This is the root-level
34
+ `archive/` that `src/cli/workflow-artifact-cleanup-audit.ts` already treats as a skipped
35
+ root entry (`SKIPPED_ROOT_ENTRIES`), so an archived session is not re-scanned as active
36
+ WIP. Use this location when relocating a completed session; do not invent a third path.
37
+
38
+ Retention rule (selection, not automatic deletion): a session is an **archival candidate**
39
+ when `workflow-artifact-cleanup-audit` classifies it `terminal_done` (a `delivered`/
40
+ `accepted`/`archived` session in phase `done`, or an `accepted`/`archived` session with no
41
+ open learning routing) **and** its `state.json` `updatedAt` is older than a retention
42
+ window (default 30 days). Use the audit tool's existing read-only classification as the
43
+ candidate selector — do not build new discovery logic. `active_wip`,
44
+ `active_learning_followup`, `cleanup_candidate`, and `invalid` sessions are **never**
45
+ archival candidates on age alone.
46
+
47
+ Procedure (documented; archival itself remains a deliberate, non-automated move — the audit
48
+ tool "does not delete, archive," by design):
49
+
50
+ 1. `node build/src/cli/workflow-artifact-cleanup-audit.js <artifact-root>` to classify.
51
+ 2. From the `terminal_done` bucket, select those whose `state.json` `updatedAt` predates the
52
+ retention window.
53
+ 3. Move each selected `<artifact-root>/<slug>/` to `<artifact-root>/archive/<slug>/`
54
+ (a human-reviewed or explicitly-invoked move — not a silent background job).
22
55
 
23
56
  ## Persistence Integrity
24
57
 
@@ -34,7 +67,7 @@ records under concurrency.)
34
67
 
35
68
  ### Structured Sidecars
36
69
 
37
- Markdown artifacts remain the human-readable handoff surface. JSON sidecars are the machine-readable recovery and gate surface. When a workflow creates or updates the corresponding information, write the sidecar beside the Markdown artifacts in `.flow-agents/<slug>/`.
70
+ Markdown artifacts remain the human-readable handoff surface. JSON sidecars are the machine-readable recovery and gate surface. When a workflow creates or updates the corresponding information, write the sidecar beside the Markdown artifacts in `.kontourai/flow-agents/<slug>/`.
38
71
 
39
72
  Draft sidecars:
40
73
 
@@ -51,7 +84,7 @@ Sidecar rules:
51
84
  - Keep `schema_version` at `1.0` until the schema changes incompatibly.
52
85
  - Keep `task_slug` stable across all sidecars for a workflow.
53
86
  - Prefer `npm run workflow:sidecar --` for creating and updating sidecars. If a harness cannot run the writer, produce equivalent JSON and validate it with `npm run workflow:validate-artifacts --`.
54
- - Use `npm run workflow:sidecar -- ensure-session` when available to create or select the current `.flow-agents/<slug>/` session artifact before substantial work starts.
87
+ - Use `npm run workflow:sidecar -- ensure-session` when available to create or select the current `.kontourai/flow-agents/<slug>/` session artifact before substantial work starts.
55
88
  - Update `state.json` at phase transitions.
56
89
  - Create or update `acceptance.json` when planning defines or changes acceptance criteria.
57
90
  - Create or update `evidence.json` when verification or evidence-gate records proof.