@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
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import * as path from "node:path";
4
4
  import { flagBool, flagString, parseArgs } from "../lib/args.js";
5
+ import { defaultArtifactRootForRead } from "../lib/local-artifact-root.js";
5
6
  import { buildWorkflowLearningProjection, readWorkflowLearningSources } from "../lib/workflow-learning-projection.js";
6
7
  const SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/;
7
8
  function printHelp() {
@@ -10,7 +11,7 @@ function printHelp() {
10
11
  console.log("Build an inert Console learning projection from local workflow learning sidecars.");
11
12
  console.log("");
12
13
  console.log("Options:");
13
- console.log(" --artifact-root <path> Workflow artifact root to scan (default: .flow-agents)");
14
+ console.log(" --artifact-root <path> Workflow artifact root to scan (default: .kontourai/flow-agents)");
14
15
  console.log(" --kontour-root <path> Local Kontour root to write under (default: .kontour)");
15
16
  console.log(" --scope <id> Projection scope id (default: current directory name)");
16
17
  console.log(" --scope-kind <kind> Projection scope kind (default: repo)");
@@ -84,7 +85,7 @@ export function main(argv = process.argv.slice(2)) {
84
85
  return 0;
85
86
  }
86
87
  try {
87
- const artifactRoot = path.resolve(flagString(flags, "artifact-root", ".flow-agents") ?? ".flow-agents");
88
+ const artifactRoot = path.resolve(flagString(flags, "artifact-root") ?? defaultArtifactRootForRead());
88
89
  const kontourRoot = path.resolve(flagString(flags, "kontour-root", ".kontour") ?? ".kontour");
89
90
  const producer = requireSafeSegment(flagString(flags, "producer", "flow-agents-learning") ?? "flow-agents-learning", "--producer");
90
91
  const scope = {
@@ -0,0 +1 @@
1
+ export declare function main(argv?: string[]): number;
@@ -0,0 +1,125 @@
1
+ import * as child_process from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { parseArgs, flagString, flagBool } from "../lib/args.js";
7
+ import { readJson } from "../lib/fs.js";
8
+ // Mirrors src/cli/effective-backlog-settings.ts's loadSettings/currentRepo/merge/findProject/
9
+ // effective structure and its ask_user/configured result envelope exactly, pointed at the
10
+ // AssignmentProvider settings schema/example instead of the backlog equivalents (#290 Wave 2
11
+ // Task B). Deliberately NOT extracted into a shared helper with effective-backlog-settings.ts —
12
+ // see the #290 plan artifact's Unresolved Questions #3 for the recorded rationale (duplicating
13
+ // this file's small merge/repo-detection logic is lower-risk than refactoring a file outside
14
+ // this issue's declared scope).
15
+ const PROJECT_SETTINGS_RELATIVE_PATH = path.join("context", "settings", "assignment-provider-settings.json");
16
+ function loadSettings(file) {
17
+ if (!fs.existsSync(file))
18
+ return null;
19
+ const data = readJson(file);
20
+ if (data.schema_version !== "1.0")
21
+ throw new Error(`${file}: unsupported schema_version ${String(data.schema_version)}`);
22
+ return data;
23
+ }
24
+ function repoFromText(text) {
25
+ const match = text.trim().match(/github\.com[:/]([^/]+)\/([^/.]+)(?:\.git)?$/);
26
+ return match ? { owner: match[1], name: match[2] } : null;
27
+ }
28
+ function currentRepo(repoPath) {
29
+ try {
30
+ const out = child_process.execFileSync("git", ["-C", repoPath, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
31
+ const repo = repoFromText(out);
32
+ if (repo)
33
+ return repo;
34
+ }
35
+ catch { }
36
+ const packagePath = path.join(repoPath, "package.json");
37
+ if (fs.existsSync(packagePath)) {
38
+ const data = readJson(packagePath);
39
+ const repository = data.repository;
40
+ const url = typeof repository === "object" && repository !== null ? repository.url : repository;
41
+ if (typeof url === "string")
42
+ return repoFromText(url);
43
+ }
44
+ return null;
45
+ }
46
+ function merge(base, override) {
47
+ if (!base && !override)
48
+ return null;
49
+ if (!base)
50
+ return structuredClone(override);
51
+ if (!override)
52
+ return structuredClone(base);
53
+ const out = structuredClone(base);
54
+ for (const [key, value] of Object.entries(override)) {
55
+ out[key] = typeof value === "object" && value !== null && typeof out[key] === "object" && out[key] !== null && !Array.isArray(value)
56
+ ? merge(out[key], value)
57
+ : structuredClone(value);
58
+ }
59
+ return out;
60
+ }
61
+ function findProject(settings, repo) {
62
+ const projects = settings?.projects;
63
+ if (!Array.isArray(projects))
64
+ return null;
65
+ return projects.find((project) => {
66
+ const projectRepo = (project.project?.repo ?? {});
67
+ return projectRepo.owner === repo.owner && projectRepo.name === repo.name;
68
+ }) ?? null;
69
+ }
70
+ function defaultProjectSettingsPath() {
71
+ let cursor = path.dirname(fileURLToPath(import.meta.url));
72
+ while (true) {
73
+ const candidate = path.join(cursor, PROJECT_SETTINGS_RELATIVE_PATH);
74
+ if (fs.existsSync(candidate))
75
+ return candidate;
76
+ const parent = path.dirname(cursor);
77
+ if (parent === cursor)
78
+ return path.resolve(PROJECT_SETTINGS_RELATIVE_PATH);
79
+ cursor = parent;
80
+ }
81
+ }
82
+ function effective(repoPath, projectSettings, globalSettings) {
83
+ const repo = currentRepo(repoPath);
84
+ const projectDoc = loadSettings(projectSettings);
85
+ const globalDoc = loadSettings(globalSettings);
86
+ if (!repo)
87
+ return [{ status: "ask_user", reason: "could_not_identify_current_repo", message: "Ask the user which assignment AssignmentProvider to use for this workspace.", resolution: { project_settings_path: projectSettings, global_settings_path: globalSettings } }, 2];
88
+ const effectiveSettings = merge(merge(merge(globalDoc?.defaults, findProject(globalDoc, repo)), projectDoc?.defaults), findProject(projectDoc, repo));
89
+ if (!effectiveSettings)
90
+ return [{ status: "ask_user", reason: "no_assignment_provider_settings", message: "Ask the user which assignment AssignmentProvider to use before claiming work.", current_repo: repo, resolution: { project_settings_path: projectSettings, global_settings_path: globalSettings, checked: ["project", "global"] } }, 2];
91
+ return [{ status: "configured", current_repo: repo, source: findProject(projectDoc, repo) || projectDoc?.defaults ? "project" : "global", precedence: ["project.projects match", "project.defaults", "global.projects match", "global.defaults"], settings: effectiveSettings }, 0];
92
+ }
93
+ export function main(argv = process.argv.slice(2)) {
94
+ const args = parseArgs(argv);
95
+ try {
96
+ const [result, code] = effective(path.resolve(flagString(args.flags, "repo-path", ".") ?? "."), path.resolve(flagString(args.flags, "project-settings", defaultProjectSettingsPath()) ?? ""), path.resolve(flagString(args.flags, "global-settings", path.join(os.homedir(), ".config", "flow-agents", "assignment-provider-settings.json")) ?? ""));
97
+ if (flagBool(args.flags, "json"))
98
+ console.log(JSON.stringify(result, null, 2));
99
+ else
100
+ console.log(`status: ${String(result.status)}`);
101
+ return code;
102
+ }
103
+ catch (error) {
104
+ console.error(`error: ${error.message}`);
105
+ return 1;
106
+ }
107
+ }
108
+ // Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
109
+ // Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
110
+ // entry-point guard fires correctly when the module is loaded directly as a script.
111
+ const _selfRealPath = (() => { try {
112
+ return fs.realpathSync(fileURLToPath(import.meta.url));
113
+ }
114
+ catch {
115
+ return fileURLToPath(import.meta.url);
116
+ } })();
117
+ const _argv1RealPath = (() => { try {
118
+ return fs.realpathSync(process.argv[1]);
119
+ }
120
+ catch {
121
+ return process.argv[1];
122
+ } })();
123
+ if (_selfRealPath === _argv1RealPath) {
124
+ process.exitCode = main();
125
+ }
@@ -10,6 +10,7 @@ import { parseArgs, flagBool, flagList, flagString } from "../lib/args.js";
10
10
  import { activateCodexLocal } from "../runtime-adapters.js";
11
11
  import { main as buildBundles } from "../tools/build-universal-bundles.js";
12
12
  import { root } from "../tools/common.js";
13
+ import { defaultCodexHome, durableInstallRecordPath } from "../lib/local-artifact-root.js";
13
14
  const runtimeBundles = {
14
15
  base: "base",
15
16
  codex: "codex",
@@ -82,8 +83,8 @@ Options:
82
83
  Honors FLOW_AGENTS_USER_CLAUDE_SETTINGS for test isolation.
83
84
  opencode: merges opencode.json into ~/.config/opencode/opencode.json
84
85
  (honors XDG_CONFIG_HOME; test isolation via FLOW_AGENTS_USER_OPENCODE_CONFIG).
85
- codex: runs install-codex-home.sh into ~/.flow-agents/codex
86
- (the isolated Codex HOME; hooks merged, not overwritten).
86
+ codex: runs install-codex-home.sh into CODEX_HOME or ~/.codex
87
+ (hooks merged, not overwritten).
87
88
  pi: NOT_VERIFIED (no documented global dir); warns and falls back to workspace default.
88
89
  --telemetry-sink local-files|local-kontour-console|kontour-hosted-console|user-hosted-console
89
90
  --console-url URL
@@ -174,9 +175,8 @@ function globalDest(runtime) {
174
175
  return path.join(process.env["XDG_CONFIG_HOME"] ?? path.join(os.homedir(), ".config"), "opencode");
175
176
  }
176
177
  if (runtime === "codex") {
177
- // codex --global routes to the isolated Codex HOME at ~/.flow-agents/codex.
178
- // This is the same path used by install-codex-home.sh; --dest overrides it for sandbox testing.
179
- return path.join(os.homedir(), ".flow-agents", "codex");
178
+ // codex --global routes to the standard Codex home. --dest remains an explicit override.
179
+ return defaultCodexHome();
180
180
  }
181
181
  if (runtime === "pi") {
182
182
  // pi has no documented global config dir.
@@ -268,6 +268,75 @@ function ensureBundle(runtime) {
268
268
  throw new Error(`bundle installer missing: ${bundle}`);
269
269
  return bundle;
270
270
  }
271
+ // The bundle's hook commands resolve the flow-agents scripts directory via
272
+ // ${CLAUDE_PROJECT_DIR:-$(pwd)}. That is correct for a project-scoped install
273
+ // (installBundle rsyncs scripts/ alongside .claude/, so CLAUDE_PROJECT_DIR ==
274
+ // the install destination). It is NOT correct for a --global install: there
275
+ // is no per-destination copy of scripts/, and CLAUDE_PROJECT_DIR varies with
276
+ // whichever project happens to be open, so the hook resolves to a path that
277
+ // exists in at most one project (and never for most sessions). Global
278
+ // installs need an absolute, session-independent path instead.
279
+ const GLOBAL_INSTALL_PROJECT_DIR_PREFIX = /root="\$\{CLAUDE_PROJECT_DIR:-\$\(pwd\)\}";\s*/g;
280
+ const GLOBAL_INSTALL_PROJECT_DIR_VAR = /"\$root\//g;
281
+ function rewriteCommandForGlobalInstall(command, sourceRoot) {
282
+ return command
283
+ .replace(GLOBAL_INSTALL_PROJECT_DIR_PREFIX, "")
284
+ .replace(GLOBAL_INSTALL_PROJECT_DIR_VAR, `"${sourceRoot}/`);
285
+ }
286
+ /** Recursively rewrite every `command` string found under `value` in place. */
287
+ function rewriteCommandsForGlobalInstall(value, sourceRoot) {
288
+ if (Array.isArray(value)) {
289
+ for (const item of value)
290
+ rewriteCommandsForGlobalInstall(item, sourceRoot);
291
+ return;
292
+ }
293
+ if (!value || typeof value !== "object")
294
+ return;
295
+ const obj = value;
296
+ for (const key of Object.keys(obj)) {
297
+ if (key === "command" && typeof obj[key] === "string") {
298
+ obj[key] = rewriteCommandForGlobalInstall(obj[key], sourceRoot);
299
+ continue;
300
+ }
301
+ rewriteCommandsForGlobalInstall(obj[key], sourceRoot);
302
+ }
303
+ }
304
+ /**
305
+ * Additively copy every file under srcDir into destDir, creating directories
306
+ * as needed and overwriting files whose content changed. Never deletes files
307
+ * in destDir that srcDir does not own — destDir may contain unrelated content
308
+ * (other kits, other tools) that this sync must not touch.
309
+ */
310
+ function copyDirMerge(srcDir, destDir) {
311
+ let added = 0;
312
+ let updated = 0;
313
+ if (!fs.existsSync(srcDir))
314
+ return { added, updated };
315
+ for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
316
+ const srcPath = path.join(srcDir, entry.name);
317
+ const destPath = path.join(destDir, entry.name);
318
+ if (entry.isDirectory()) {
319
+ const nested = copyDirMerge(srcPath, destPath);
320
+ added += nested.added;
321
+ updated += nested.updated;
322
+ continue;
323
+ }
324
+ if (!entry.isFile())
325
+ continue;
326
+ const content = fs.readFileSync(srcPath);
327
+ if (fs.existsSync(destPath)) {
328
+ if (Buffer.compare(fs.readFileSync(destPath), content) === 0)
329
+ continue;
330
+ updated += 1;
331
+ }
332
+ else {
333
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
334
+ added += 1;
335
+ }
336
+ fs.writeFileSync(destPath, content);
337
+ }
338
+ return { added, updated };
339
+ }
271
340
  function installBundle(bundle, options) {
272
341
  const args = ["install.sh", options.dest];
273
342
  for (const sink of options.telemetrySinks)
@@ -334,10 +403,13 @@ export async function main(argv = process.argv.slice(2)) {
334
403
  if (!options.global)
335
404
  checkScopeCollision();
336
405
  }
337
- // --global for claude-code: merge only into the global/user-level settings dir.
338
- // This writes only the hook-wiring config (merge into ~/.claude/settings.json),
339
- // not the full workspace bundle. The global settings dir is the claude config root,
340
- // so the settings.json lives directly in dest (not dest/.claude/).
406
+ // --global for claude-code: merge hook-wiring into the global/user-level
407
+ // settings dir, plus additively sync skills/agents so a global install
408
+ // does not silently go stale as new Builder Kit skills ship. This does
409
+ // NOT rsync the full workspace bundle (no context/, powers/, prompts/,
410
+ // etc.) — only the parts a global Claude Code user-config needs. The
411
+ // global settings dir is the claude config root, so the settings.json
412
+ // lives directly in dest (not dest/.claude/).
341
413
  if (options.global && options.runtime === "claude-code") {
342
414
  const bundle = ensureBundle(options.runtime);
343
415
  // For --global, dest is ~/.claude/ (the global settings dir).
@@ -353,6 +425,10 @@ export async function main(argv = process.argv.slice(2)) {
353
425
  // Remove permissive defaults (not appropriate for global user settings).
354
426
  delete managed["permissions"];
355
427
  delete managed["skipDangerousModePermissionPrompt"];
428
+ // See rewriteCommandsForGlobalInstall: bundle hook commands assume
429
+ // CLAUDE_PROJECT_DIR points at this package; a global install must not
430
+ // depend on which project is currently open, so pin to an absolute path.
431
+ rewriteCommandsForGlobalInstall(managed, root);
356
432
  fs.mkdirSync(options.dest, { recursive: true });
357
433
  const destSettingsPath = path.join(options.dest, "settings.json");
358
434
  const installMergePath = path.join(root, "scripts", "install-merge.js");
@@ -371,16 +447,21 @@ export async function main(argv = process.argv.slice(2)) {
371
447
  const tmp = `${destSettingsPath}.tmp.${process.pid}`;
372
448
  fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
373
449
  fs.renameSync(tmp, destSettingsPath);
450
+ // Additive skills/agents sync: adds new files and updates changed ones,
451
+ // never deletes — dest may hold unrelated content from other kits/tools.
452
+ const skillsSync = copyDirMerge(path.join(bundle, ".claude", "skills"), path.join(options.dest, "skills"));
453
+ const agentsSync = copyDirMerge(path.join(bundle, ".claude", "agents"), path.join(options.dest, "agents"));
374
454
  // Write version stamp.
375
- const installRecordDir = path.join(options.dest, ".flow-agents");
455
+ const recordPath = durableInstallRecordPath(options.dest);
456
+ const installRecordDir = path.dirname(recordPath);
376
457
  fs.mkdirSync(installRecordDir, { recursive: true });
377
458
  const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
378
459
  const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "claude-code", global: true };
379
- const recordPath = path.join(installRecordDir, "install.json");
380
460
  const recordTmp = `${recordPath}.tmp.${process.pid}`;
381
461
  fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}\n`, "utf8");
382
462
  fs.renameSync(recordTmp, recordPath);
383
463
  console.log(`Flow Agents global hooks merged for claude-code in ${options.dest}`);
464
+ console.log(`Synced skills (+${skillsSync.added} new, ~${skillsSync.updated} updated) and agents (+${agentsSync.added} new, ~${agentsSync.updated} updated) in ${options.dest}`);
384
465
  return 0;
385
466
  }
386
467
  // --global for opencode: merge FA opencode.json into the global opencode config dir.
@@ -415,11 +496,11 @@ export async function main(argv = process.argv.slice(2)) {
415
496
  `, "utf8");
416
497
  fs.renameSync(tmp, destConfigPath);
417
498
  // Write version stamp.
418
- const installRecordDir = path.join(options.dest, ".flow-agents");
499
+ const recordPath = durableInstallRecordPath(options.dest);
500
+ const installRecordDir = path.dirname(recordPath);
419
501
  fs.mkdirSync(installRecordDir, { recursive: true });
420
502
  const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
421
503
  const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "opencode", global: true };
422
- const recordPath = path.join(installRecordDir, "install.json");
423
504
  const recordTmp = `${recordPath}.tmp.${process.pid}`;
424
505
  fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}
425
506
  `, "utf8");
@@ -427,9 +508,8 @@ export async function main(argv = process.argv.slice(2)) {
427
508
  console.log(`Flow Agents global config merged for opencode in ${options.dest}`);
428
509
  return 0;
429
510
  }
430
- // --global for codex: run install-codex-home.sh to install into the isolated Codex HOME.
431
- // The codex --global path is ~/.flow-agents/codex (the isolated home IS codex's global).
432
- // Pass through telemetry/console args and honor --dest override for sandbox testing.
511
+ // --global for codex: run install-codex-home.sh to install into the Codex home.
512
+ // Defaults to CODEX_HOME or ~/.codex. --dest remains an explicit override.
433
513
  if (options.global && options.runtime === "codex") {
434
514
  const codexHomeScript = path.join(root, "scripts", "install-codex-home.sh");
435
515
  if (!fs.existsSync(codexHomeScript)) {
@@ -452,7 +532,10 @@ export async function main(argv = process.argv.slice(2)) {
452
532
  console.error(`flow-agents init: unable to run install-codex-home.sh: ${result.error.message}`);
453
533
  return 1;
454
534
  }
455
- return result.status ?? 1;
535
+ const installed = result.status ?? 1;
536
+ if (installed !== 0)
537
+ return installed;
538
+ return activateKits(options);
456
539
  }
457
540
  // --global for pi: NOT_VERIFIED (no documented global dir). Warn and fall through to workspace install.
458
541
  if (options.global && options.runtime === "pi") {
@@ -541,11 +624,11 @@ function dogfoodClaudeCode(bundleRoot, dest) {
541
624
  fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
542
625
  fs.renameSync(tmp, destSettingsPath);
543
626
  // Write version stamp.
544
- const installRecordDir = path.join(dest, ".flow-agents");
627
+ const recordPath = durableInstallRecordPath(dest);
628
+ const installRecordDir = path.dirname(recordPath);
545
629
  fs.mkdirSync(installRecordDir, { recursive: true });
546
630
  const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
547
631
  const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "claude-code" };
548
- const recordPath = path.join(installRecordDir, "install.json");
549
632
  const recordTmp = `${recordPath}.tmp.${process.pid}`;
550
633
  fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}\n`, "utf8");
551
634
  fs.renameSync(recordTmp, recordPath);
@@ -584,11 +667,11 @@ function dogfoodCodex(bundleRoot, dest) {
584
667
  fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
585
668
  fs.renameSync(tmp, destHooksPath);
586
669
  // Write version stamp.
587
- const installRecordDir = path.join(dest, ".flow-agents");
670
+ const recordPath = durableInstallRecordPath(dest);
671
+ const installRecordDir = path.dirname(recordPath);
588
672
  fs.mkdirSync(installRecordDir, { recursive: true });
589
673
  const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
590
674
  const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "codex" };
591
- const recordPath = path.join(installRecordDir, "install.json");
592
675
  const recordTmp = `${recordPath}.tmp.${process.pid}`;
593
676
  fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}\n`, "utf8");
594
677
  fs.renameSync(recordTmp, recordPath);
@@ -6,12 +6,17 @@ import * as path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { parseArgs, flagBool, flagString } from "../lib/args.js";
8
8
  import { assertPathContained, copyDir, isoNow, readJson, walkFiles, writeJson } from "../lib/fs.js";
9
- import { assertKitRepository, deriveKitTargets } from "../flow-kit/validate.js";
9
+ import { assertKitRepository, deriveKitTargets, parseKitDependencies } from "../flow-kit/validate.js";
10
10
  import { activateCodexLocal, activateStrandsLocal } from "../runtime-adapters.js";
11
+ import { defaultCodexHome } from "../lib/local-artifact-root.js";
11
12
  const REGISTRY_REL = path.join("kits", "local", "installed-kits.json");
12
13
  const REPOSITORIES_REL = path.join("kits", "local", "repositories");
13
14
  function registryPath(dest) { return path.join(dest, REGISTRY_REL); }
14
15
  function installedPath(dest, kitId) { return path.join(dest, REPOSITORIES_REL, kitId); }
16
+ function resolveDest(flags) {
17
+ const explicit = flagString(flags, "dest");
18
+ return path.resolve(explicit ?? defaultCodexHome());
19
+ }
15
20
  function loadRegistry(dest) {
16
21
  const file = registryPath(dest);
17
22
  if (!fs.existsSync(file))
@@ -19,6 +24,28 @@ function loadRegistry(dest) {
19
24
  const data = readJson(file);
20
25
  return { schema_version: data.schema_version ?? "1.0", kits: Array.isArray(data.kits) ? data.kits.filter((item) => typeof item === "object" && item !== null) : [] };
21
26
  }
27
+ /**
28
+ * Emit a non-blocking warning for each declared kit dependency that is not present
29
+ * in the destination's LOCAL registry.
30
+ *
31
+ * Scope limitation (v1, accepted): this check only sees the local installed-kits
32
+ * registry at `dest`, NOT the built-in Kit Catalog. A dependency satisfied solely by
33
+ * a built-in catalog kit will still warn here. Presence is enforced authoritatively
34
+ * (hard error) at activation time against the full discovered inventory
35
+ * (built-in catalog + local registry) — see src/runtime-adapters.ts.
36
+ */
37
+ function warnUninstalledDependencies(manifest, manifestPath, dest) {
38
+ const { entries } = parseKitDependencies(manifest, manifestPath);
39
+ if (!entries.length)
40
+ return;
41
+ const installed = new Set(loadRegistry(dest).kits.map((entry) => String(entry.id ?? "")));
42
+ const kitId = String(manifest.id);
43
+ for (const dep of entries) {
44
+ if (!installed.has(dep.kit_id)) {
45
+ console.log(`warning: kit '${kitId}' declares a dependency on '${dep.kit_id}'${dep.reason ? ` (${dep.reason})` : ""} which is not installed at ${dest}; install it first with 'flow-agents kit install <source>' or activation will fail`);
46
+ }
47
+ }
48
+ }
22
49
  function contentHash(root) {
23
50
  const hash = crypto.createHash("sha256");
24
51
  for (const file of walkFiles(root)) {
@@ -73,7 +100,7 @@ async function install(argv) {
73
100
  }
74
101
  async function installLocalSource(source, argv) {
75
102
  const args = parseArgs(argv);
76
- const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
103
+ const dest = resolveDest(args.flags);
77
104
  let manifest;
78
105
  try {
79
106
  manifest = await assertKitRepository(source);
@@ -85,10 +112,12 @@ async function installLocalSource(source, argv) {
85
112
  return 1;
86
113
  }
87
114
  const kitId = String(manifest.id);
115
+ warnUninstalledDependencies(manifest, path.join(source, "kit.json"), dest);
88
116
  const hash = contentHash(source);
89
117
  const registry = loadRegistry(dest);
90
118
  const existing = registry.kits.find((entry) => entry.id === kitId);
91
119
  const target = installedPath(dest, kitId);
120
+ fs.mkdirSync(dest, { recursive: true });
92
121
  assertPathContained(dest, target);
93
122
  const sourceText = source;
94
123
  if (existing && existing.source !== sourceText && !flagBool(args.flags, "update")) {
@@ -127,7 +156,7 @@ async function installGitSource(rawUrl, argv) {
127
156
  }
128
157
  if (!ref)
129
158
  ref = flagString(args.flags, "ref") ?? null;
130
- const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
159
+ const dest = resolveDest(args.flags);
131
160
  const force = flagBool(args.flags, "force") ?? false;
132
161
  const update = flagBool(args.flags, "update") ?? false;
133
162
  // Shallow-clone into a temporary directory.
@@ -161,10 +190,12 @@ async function installGitSource(rawUrl, argv) {
161
190
  }
162
191
  // Delegate to the shared install logic (copy + registry update).
163
192
  const kitId = String(manifest.id);
193
+ warnUninstalledDependencies(manifest, path.join(tmpBase, "kit.json"), dest);
164
194
  const hash = kitContentHash(tmpBase);
165
195
  const registry = loadRegistry(dest);
166
196
  const existing = registry.kits.find((entry) => entry.id === kitId);
167
197
  const target = installedPath(dest, kitId);
198
+ fs.mkdirSync(dest, { recursive: true });
168
199
  assertPathContained(dest, target);
169
200
  const sourceText = repoUrl + (ref ? `#${ref}` : "");
170
201
  if (existing && existing.source !== sourceText && !update) {
@@ -197,7 +228,7 @@ async function installGitSource(rawUrl, argv) {
197
228
  }
198
229
  function list(argv) {
199
230
  const args = parseArgs(argv);
200
- const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
231
+ const dest = resolveDest(args.flags);
201
232
  const entries = loadRegistry(dest).kits;
202
233
  if (!entries.length) {
203
234
  console.log("No local Flow Kits installed.");
@@ -210,7 +241,7 @@ function list(argv) {
210
241
  }
211
242
  function status(argv) {
212
243
  const args = parseArgs(argv);
213
- const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
244
+ const dest = resolveDest(args.flags);
214
245
  let entries = loadRegistry(dest).kits;
215
246
  const kitId = args.positionals[0];
216
247
  if (kitId) {
@@ -233,7 +264,7 @@ function status(argv) {
233
264
  const AVAILABLE_ADAPTERS = ["codex-local", "strands-local"];
234
265
  function activate(argv) {
235
266
  const args = parseArgs(argv);
236
- const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
267
+ const dest = resolveDest(args.flags);
237
268
  const sourceRoot = path.resolve(flagString(args.flags, "source-root", path.resolve(path.dirname(process.argv[1]), "..")) ?? ".");
238
269
  const adapter = flagString(args.flags, "adapter");
239
270
  if (adapter && !AVAILABLE_ADAPTERS.includes(adapter)) {
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { parseArgs, flagList, flagString } from "../lib/args.js";
4
- const FLOW_ARTIFACT_PATTERN = /(?<path>\.flow-agents\/[^\s`'")]+)/g;
4
+ const FLOW_ARTIFACT_PATTERN = /(?<path>\.kontourai\/flow-agents\/[^\s`'")]+)/g;
5
5
  const GITHUB_ISSUE_PATTERN = /(?:(?<owner>[A-Za-z0-9_.-]+)\/(?<repo>[A-Za-z0-9_.-]+))?#(?<number>\d+)/g;
6
6
  const GITHUB_URL_PATTERN = /https:\/\/github\.com\/(?<owner>[^/\s]+)\/(?<repo>[^/\s]+)\/(?:issues|pull)\/(?<number>\d+)/g;
7
7
  const BLOCKER_PATTERN = /\b(?:blocked by|depends on|waiting on|requires|should follow|coordinate with)\b[:\s-]*(?<refs>[^\n]+)/gi;
@@ -3,7 +3,7 @@ import { fileURLToPath } from "node:url";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { parseArgs, flagBool, flagList, flagString } from "../lib/args.js";
6
- import { defaultArtifactRootForRead, defaultTelemetryDirForRead, defaultTelemetryDirsForRead, telemetryDataDir, flowAgentsArtifactRoot, legacyFlowAgentsArtifactRoot } from "../lib/local-artifact-root.js";
6
+ import { defaultArtifactRootForRead, defaultTelemetryDirForRead, defaultTelemetryDirsForRead, telemetryDataDir, flowAgentsArtifactRoot } from "../lib/local-artifact-root.js";
7
7
  const VALID_RESULTS = new Set(["success", "partial", "failure", "not_verified"]);
8
8
  function telemetryDir(flags) {
9
9
  const explicit = flagString(flags, "telemetry-dir") ?? process.env.TELEMETRY_DATA_DIR;
@@ -152,7 +152,7 @@ function syncArtifacts(argv) {
152
152
  const dir = telemetryDir(flags);
153
153
  ensureSafeDir(dir);
154
154
  const artifacts = flagList(flags, "artifact-dir");
155
- const records = (artifacts.length ? artifacts : [flowAgentsArtifactRoot(), legacyFlowAgentsArtifactRoot()]).flatMap((item) => fs.existsSync(item) ? artifactOutcomes(item, flags) : []);
155
+ const records = (artifacts.length ? artifacts : [flowAgentsArtifactRoot()]).flatMap((item) => fs.existsSync(item) ? artifactOutcomes(item, flags) : []);
156
156
  writeJsonlUpsert(path.join(dir, "outcomes.jsonl"), records, "outcome_id");
157
157
  if (!flagBool(flags, "quiet"))
158
158
  console.log(`synced ${records.length} artifact outcome(s) to ${path.join(dir, "outcomes.jsonl")}`);
@@ -382,7 +382,7 @@ function discoverProjects(root) {
382
382
  if (!fs.existsSync(root))
383
383
  return [];
384
384
  const candidates = [root, ...fs.readdirSync(root).map((name) => path.join(root, name))];
385
- return candidates.filter((candidate) => fs.existsSync(flowAgentsArtifactRoot(candidate)) || fs.existsSync(legacyFlowAgentsArtifactRoot(candidate))).map((repoRoot) => {
385
+ return candidates.filter((candidate) => fs.existsSync(flowAgentsArtifactRoot(candidate))).map((repoRoot) => {
386
386
  const name = path.basename(repoRoot);
387
387
  return { name, repo: name, repo_root: repoRoot, artifact_dir: defaultArtifactRootForRead(repoRoot), input_telemetry_dir: defaultTelemetryDirForRead(repoRoot), runtime: "codex", skill_ids: [] };
388
388
  });
@@ -7,6 +7,7 @@ const validEvents = new Set(["UserPromptSubmit", "PostToolUse", "Stop"]);
7
7
  const requiredCases = {
8
8
  "dev-builder-build-requires-pickup-probe-before-plan": { description: "missing pickup Probe before plan", tier: "design-target", must_include: ["design-probe", "accepted gaps", "provider_state", "conflict_risks"], agent_must_do: ["route decision_gap back to design-probe/pickup Probe"] },
9
9
  "dev-builder-review-before-verify-after-execute": { description: "review-before-verify after execute", tier: "adapter", event: "PostToolUse", must_include: ["Next: review", "then verify", "report only"], agent_must_do: ["review-work for report-only critique before verify-work", "not count critique.json as verification evidence"] },
10
+ "dev-builder-route-fresh-coding-prompt": { description: "fresh coding prompt routes into Builder workflow", tier: "adapter", event: "UserPromptSubmit", must_include: ["BUILDER WORKFLOW ROUTE", "activate `deliver`", "--flow-id builder.build", "plan-work -> execute-plan -> review-work -> verify-work", "publish/release-readiness and learning-review"], agent_must_do: ["activate Builder Kit delivery workflow before source edits", "use deliver by default for coding/build work", "do not bypass plan-work -> execute-plan -> review-work -> verify-work", "publish, release readiness, and learning feedback"] },
10
11
  "dev-verify-fail-preserves-trace-before-rework": { description: "verify failure route-back with preserved FAIL evidence", tier: "design-target", must_include: ["If verdict=FAIL: record the FAIL artifact"], agent_must_do: ["write the FAIL artifact before routing back", "route to plan"] },
11
12
  "codex-claude-strict-stop-adapter-contract": { description: "Goal Fit stop adapter behavior", tier: "adapter", hook: "stop-goal-fit", event: "Stop", must_include: ["Goal Fit warning"], agent_must_do: ["treat strict Stop guidance as a blocker"] },
12
13
  "codex-live-context-gap": { description: "Codex documented runtime gap", tier: "documented-runtime-gap", must_include: ["STATE:", "not_verified"], agent_must_do: ["record NOT_VERIFIED for live influence"] },
@@ -1,7 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
- const root = path.resolve(".");
4
+ import { fileURLToPath } from "node:url";
5
+ // Resolve bundled JSON Schemas relative to this compiled script's own package
6
+ // location (build/src/cli/validate-workflow-artifacts.js -> ../../../schemas), NOT
7
+ // process.cwd(), so `flow-agents-validate-artifacts` works from any repo (the artifact
8
+ // paths to validate are still taken from argv/cwd). Mirrors the package-relative pattern
9
+ // used for liveness-read.js in workflow-sidecar.ts.
10
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
5
11
  const statusRe = /\[(PASS|FAIL|NOT_VERIFIED|SKIP|PARTIAL)\]/i;
6
12
  const dateTimeRe = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
7
13
  const sidecarSchemas = {
@@ -311,11 +317,12 @@ function readJson(file) {
311
317
  }
312
318
  function validateSidecar(file) {
313
319
  const { value, issues } = readJson(file);
320
+ const warnings = [];
314
321
  if (value === undefined)
315
- return issues;
322
+ return { issues, warnings };
316
323
  const schemaFile = sidecarSchemas[path.basename(file)];
317
324
  if (schemaFile) {
318
- const schema = JSON.parse(readText(path.join(root, schemaFile)));
325
+ const schema = JSON.parse(readText(path.join(packageRoot, schemaFile)));
319
326
  validateSchemaValue(file, value, schema, path.basename(file), issues, schema);
320
327
  }
321
328
  if (path.basename(file) === "evidence.json") {
@@ -359,7 +366,10 @@ function validateSidecar(file) {
359
366
  issues.push({ path: file, message: "deploy decision requires post_deploy_checks to be planned or pass" });
360
367
  }
361
368
  }
362
- return issues;
369
+ if (path.basename(file) === "state.json" && value && typeof value === "object" && !Array.isArray(value) && !("branch" in value)) {
370
+ warnings.push({ path: file, message: "state.json has no branch field (legacy/pre-#289 session) — re-run `ensure-session` to backfill agent/<actor>/<slug>." });
371
+ }
372
+ return { issues, warnings };
363
373
  }
364
374
  function validateLearningCorrections(file, status, records, issues) {
365
375
  records.forEach((record, index) => {
@@ -457,7 +467,11 @@ function validateSidecarGroup(inputs, markdown, requireSidecars, requireCritique
457
467
  if (bundleValue) {
458
468
  const claims = Array.isArray(bundleValue.claims) ? bundleValue.claims : [];
459
469
  const critiqueClaims = claims.filter((c) => c && c.claimType === "workflow.critique.review");
460
- if (critiqueClaims.some((c) => c.value === "fail" || c.status === "disputed"))
470
+ // #282: a historical fail/disputed critique that has been explicitly superseded by a later
471
+ // resolving write (metadata.superseded_by) is retained structurally as history and does NOT
472
+ // block a top-level pass — only a LIVE (non-superseded) fail/disputed critique blocks.
473
+ const isSuperseded = (c) => c && c.metadata && typeof c.metadata === "object" && c.metadata.superseded_by;
474
+ if (critiqueClaims.some((c) => (c.value === "fail" || c.status === "disputed") && !isSuperseded(c)))
461
475
  issues.push({ path: trustBundlePath, message: "required critique must pass" });
462
476
  }
463
477
  }
@@ -486,12 +500,18 @@ function main() {
486
500
  const markdown = artifactPaths(pathsIn);
487
501
  const sidecars = sidecarPaths(pathsIn);
488
502
  const issues = [];
503
+ const warnings = [];
489
504
  if (!skipMarkdown)
490
505
  for (const file of markdown)
491
506
  issues.push(...validateArtifact(file));
492
- for (const file of sidecars)
493
- issues.push(...validateSidecar(file));
507
+ for (const file of sidecars) {
508
+ const result = validateSidecar(file);
509
+ issues.push(...result.issues);
510
+ warnings.push(...result.warnings);
511
+ }
494
512
  issues.push(...validateSidecarGroup(pathsIn, markdown, requireSidecars, requireCritique));
513
+ for (const w of warnings)
514
+ console.error(`WARN ${w.path}: ${w.message}`);
495
515
  if (issues.length) {
496
516
  for (const issue of issues)
497
517
  console.error(`${issue.path}: ${issue.message}`);