@kontourai/flow-agents 1.3.0 → 2.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 (214) hide show
  1. package/.github/CODEOWNERS +29 -0
  2. package/.github/actions/trust-verify/action.yml +145 -0
  3. package/.github/workflows/ci.yml +11 -4
  4. package/.github/workflows/kit-gates-demo.yml +2 -2
  5. package/.github/workflows/publish-npm.yml +10 -2
  6. package/.github/workflows/release-please.yml +1 -1
  7. package/.github/workflows/trust-reconcile.yml +113 -0
  8. package/AGENTS.md +13 -0
  9. package/CHANGELOG.md +103 -0
  10. package/CONTRIBUTING.md +4 -4
  11. package/README.md +1 -0
  12. package/agents/tool-planner.json +1 -1
  13. package/build/src/cli/console-learning-projection.d.ts +1 -0
  14. package/build/src/cli/effective-backlog-settings.d.ts +1 -0
  15. package/build/src/cli/fixture-retirement-audit.d.ts +2 -0
  16. package/build/src/cli/init.d.ts +17 -0
  17. package/build/src/cli/init.js +242 -20
  18. package/build/src/cli/kit.d.ts +1 -0
  19. package/build/src/cli/promote-workflow-artifact.d.ts +1 -0
  20. package/build/src/cli/publish-change-helper.d.ts +1 -0
  21. package/build/src/cli/pull-work-provider.d.ts +1 -0
  22. package/build/src/cli/runtime-adapter.d.ts +1 -0
  23. package/build/src/cli/telemetry-doctor.d.ts +1 -0
  24. package/build/src/cli/usage-feedback.d.ts +1 -0
  25. package/build/src/cli/utterance-check.d.ts +1 -0
  26. package/build/src/cli/validate-hook-influence.d.ts +1 -0
  27. package/build/src/cli/validate-source-tree.d.ts +1 -0
  28. package/build/src/cli/validate-workflow-artifacts.d.ts +2 -0
  29. package/build/src/cli/validate-workflow-artifacts.js +19 -2
  30. package/build/src/cli/verify.d.ts +1 -0
  31. package/build/src/cli/verify.js +90 -0
  32. package/build/src/cli/veritas-governance.d.ts +1 -0
  33. package/build/src/cli/workflow-artifact-cleanup-audit.d.ts +1 -0
  34. package/build/src/cli/workflow-sidecar.d.ts +324 -0
  35. package/build/src/cli/workflow-sidecar.js +1973 -90
  36. package/build/src/cli.d.ts +2 -0
  37. package/build/src/cli.js +2 -3
  38. package/build/src/flow-kit/validate.d.ts +81 -0
  39. package/build/src/index.d.ts +5 -0
  40. package/build/src/index.js +36 -0
  41. package/build/src/lib/args.d.ts +8 -0
  42. package/build/src/lib/flow-resolver.d.ts +82 -0
  43. package/build/src/lib/flow-resolver.js +237 -0
  44. package/build/src/lib/fs.d.ts +7 -0
  45. package/build/src/lib/workflow-learning-projection.d.ts +132 -0
  46. package/build/src/runtime-adapters.d.ts +18 -0
  47. package/build/src/tools/build-universal-bundles.d.ts +2 -0
  48. package/build/src/tools/build-universal-bundles.js +34 -22
  49. package/build/src/tools/common.d.ts +9 -0
  50. package/build/src/tools/generate-context-map.d.ts +2 -0
  51. package/build/src/tools/generate-context-map.js +3 -16
  52. package/build/src/tools/validate-package.d.ts +2 -0
  53. package/build/src/tools/validate-source-tree.d.ts +2 -0
  54. package/build/src/tools/validate-source-tree.js +42 -162
  55. package/context/contracts/artifact-contract.md +10 -0
  56. package/context/contracts/delivery-contract.md +1 -0
  57. package/context/contracts/review-contract.md +1 -0
  58. package/context/contracts/verification-contract.md +2 -0
  59. package/context/gate-awareness.md +39 -0
  60. package/context/scripts/hooks/stop-goal-fit.js +632 -70
  61. package/docs/adr/0001-flow-agents-consumes-flow.md +1 -1
  62. package/docs/adr/0002-flow-kits-as-extension-unit.md +1 -1
  63. package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
  64. package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
  65. package/docs/adr/0007-skill-audit.md +1 -1
  66. package/docs/adr/0009-canonical-hook-core-kit-boundary.md +95 -0
  67. package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +139 -0
  68. package/docs/adr/0011-mcp-posture.md +100 -0
  69. package/docs/adr/0012-agent-coordination-as-liveness-claims.md +119 -0
  70. package/docs/adr/0013-context-lifecycle.md +151 -0
  71. package/docs/adr/0014-core-vs-domain-kit-boundary.md +143 -0
  72. package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +120 -0
  73. package/docs/adr/0016-three-hard-boundary-model.md +71 -0
  74. package/docs/adr/0017-anti-gaming-trust-security-model.md +155 -0
  75. package/docs/agent-system-guidebook.md +5 -12
  76. package/docs/context-map.md +4 -10
  77. package/docs/developer-architecture.md +14 -0
  78. package/docs/index.md +3 -2
  79. package/docs/integrations/framework-adapter.md +19 -6
  80. package/docs/integrations/index.md +2 -2
  81. package/docs/north-star.md +4 -4
  82. package/docs/operating-layers.md +3 -3
  83. package/docs/plans/adr-0010-phase2-gate-recompute.md +55 -0
  84. package/docs/repository-structure.md +2 -2
  85. package/docs/skills-map.md +1 -0
  86. package/docs/spec/runtime-hook-surface.md +78 -10
  87. package/docs/standards-register.md +3 -3
  88. package/docs/survey-utterance-check.md +1 -1
  89. package/docs/trust-anchor-adoption.md +197 -0
  90. package/docs/verifiable-trust.md +95 -0
  91. package/docs/veritas-integration.md +2 -2
  92. package/docs/workflow-usage-guide.md +69 -0
  93. package/evals/acceptance/DEMO-false-completion.md +144 -0
  94. package/evals/acceptance/demo-cast.sh +92 -0
  95. package/evals/acceptance/demo-false-completion.sh +72 -0
  96. package/evals/acceptance/demo-real-evidence.sh +104 -0
  97. package/evals/acceptance/demo.tape +29 -0
  98. package/evals/acceptance/prove-capture-teeth-declared.sh +335 -0
  99. package/evals/acceptance/prove-capture-teeth.sh +114 -0
  100. package/evals/acceptance/prove-teeth.sh +105 -0
  101. package/evals/ci/antigaming-suite.sh +54 -0
  102. package/evals/ci/run-baseline.sh +2 -0
  103. package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/flows/review.flow.json +26 -0
  104. package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/kit.json +20 -0
  105. package/evals/fixtures/flow-kit-repository/valid-unknown-extension/flows/review.flow.json +26 -0
  106. package/evals/fixtures/flow-kit-repository/valid-unknown-extension/kit.json +18 -0
  107. package/evals/integration/test_builder_step_producers.sh +379 -0
  108. package/evals/integration/test_bundle_install.sh +35 -71
  109. package/evals/integration/test_bundle_lifecycle.sh +39 -2
  110. package/evals/integration/test_captured_fail_reconciliation.sh +820 -0
  111. package/evals/integration/test_checkpoint_signing.sh +489 -0
  112. package/evals/integration/test_claim_lookup.sh +352 -0
  113. package/evals/integration/test_command_log_integrity.sh +275 -0
  114. package/evals/integration/test_context_map.sh +0 -2
  115. package/evals/integration/test_dual_emit_flow_step.sh +278 -0
  116. package/evals/integration/test_enforcer_expects_driven.sh +281 -0
  117. package/evals/integration/test_evidence_capture_hook.sh +185 -0
  118. package/evals/integration/test_flow_kit_repository.sh +2 -0
  119. package/evals/integration/test_flowdef_session_activation.sh +273 -0
  120. package/evals/integration/test_flowdef_session_history_preservation.sh +250 -0
  121. package/evals/integration/test_gate_bypass_chain.sh +448 -0
  122. package/evals/integration/test_gate_lockdown.sh +1137 -0
  123. package/evals/integration/test_gate_review_inquiry_records.sh +399 -0
  124. package/evals/integration/test_goal_fit_escape_hatch.sh +73 -0
  125. package/evals/integration/test_goal_fit_hook.sh +69 -4
  126. package/evals/integration/test_goal_fit_rederive.sh +263 -0
  127. package/evals/integration/test_hook_category_behaviors.sh +14 -0
  128. package/evals/integration/test_install_merge.sh +1176 -0
  129. package/evals/integration/test_mint_attestation.sh +373 -0
  130. package/evals/integration/test_phase_map_and_gate_claim.sh +365 -0
  131. package/evals/integration/test_publish_delivery.sh +269 -0
  132. package/evals/integration/test_reconcile_soundness.sh +528 -0
  133. package/evals/integration/test_resolvefirststep_security.sh +208 -0
  134. package/evals/integration/test_session_resume_roundtrip.sh +286 -0
  135. package/evals/integration/test_trust_checkpoint.sh +325 -0
  136. package/evals/integration/test_trust_reconcile.sh +293 -0
  137. package/evals/integration/test_verify_cli.sh +208 -0
  138. package/evals/integration/test_workflow_sidecar_writer.sh +549 -34
  139. package/evals/lib/node.sh +0 -6
  140. package/evals/run.sh +47 -0
  141. package/evals/static/test_library_exports.sh +85 -0
  142. package/evals/static/test_universal_bundles.sh +15 -0
  143. package/evals/static/test_workflow_skills.sh +6 -13
  144. package/install.sh +0 -7
  145. package/integrations/strands-ts/README.md +25 -15
  146. package/integrations/veritas/flow-agents.adapter.json +1 -2
  147. package/kits/builder/flows/build.flow.json +59 -12
  148. package/kits/builder/kit.json +85 -15
  149. package/kits/builder/skills/continue-work/SKILL.md +116 -0
  150. package/kits/builder/skills/deliver/SKILL.md +36 -6
  151. package/kits/builder/skills/design-probe/SKILL.md +28 -0
  152. package/kits/builder/skills/execute-plan/SKILL.md +9 -1
  153. package/kits/builder/skills/gate-review/SKILL.md +234 -0
  154. package/kits/builder/skills/learning-review/SKILL.md +30 -0
  155. package/kits/builder/skills/pickup-probe/SKILL.md +29 -0
  156. package/kits/builder/skills/plan-work/SKILL.md +13 -1
  157. package/kits/builder/skills/pull-work/SKILL.md +19 -0
  158. package/kits/knowledge/adapters/default-store/index.js +38 -0
  159. package/kits/knowledge/adapters/flow-runner/index.js +1620 -0
  160. package/kits/knowledge/adapters/obsidian-store/index.js +36 -6
  161. package/kits/knowledge/docs/store-contract.md +314 -0
  162. package/kits/knowledge/evals/audit-freshness/suite.test.js +368 -0
  163. package/kits/knowledge/evals/canonicalize-category/suite.test.js +383 -0
  164. package/kits/knowledge/evals/contract-suite/suite.test.js +111 -0
  165. package/kits/knowledge/evals/detect-contradictions/suite.test.js +324 -0
  166. package/kits/knowledge/evals/entities/suite.test.js +40 -0
  167. package/kits/knowledge/evals/glossary-sync/suite.test.js +416 -0
  168. package/kits/knowledge/evals/hygiene-review/suite.test.js +396 -0
  169. package/kits/knowledge/evals/retirement/suite.test.js +145 -0
  170. package/kits/knowledge/flows/audit-freshness.flow.json +44 -0
  171. package/kits/knowledge/flows/canonicalize-category.flow.json +44 -0
  172. package/kits/knowledge/flows/detect-contradictions.flow.json +44 -0
  173. package/kits/knowledge/flows/glossary-sync.flow.json +61 -0
  174. package/kits/knowledge/flows/hygiene-review.flow.json +43 -0
  175. package/kits/knowledge/kit.json +51 -1
  176. package/package.json +13 -4
  177. package/packaging/conformance/README.md +10 -2
  178. package/packaging/conformance/fixtures/evidence-capture--allow-records-command.json +29 -0
  179. package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +29 -0
  180. package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +30 -0
  181. package/packaging/conformance/fixtures/stop-goal-fit--block-mode.json +23 -0
  182. package/packaging/conformance/fixtures/stop-goal-fit--off-mode.json +24 -0
  183. package/packaging/conformance/fixtures/stop-goal-fit--warn-active-delivery.json +5 -2
  184. package/packaging/conformance/fixtures/stop-goal-fit--warn-no-bundle.json +23 -0
  185. package/packaging/conformance/fixtures/workflow-steering--reground-active-prompt.json +30 -0
  186. package/packaging/conformance/fixtures/workflow-steering--reground-session-start.json +30 -0
  187. package/packaging/conformance/run-conformance.js +1 -1
  188. package/scripts/README.md +2 -1
  189. package/scripts/build-universal-bundles.js +0 -1
  190. package/scripts/ci/mint-attestation.js +221 -0
  191. package/scripts/ci/trust-reconcile.js +545 -0
  192. package/scripts/hooks/config-protection.js +423 -1
  193. package/scripts/hooks/evidence-capture.js +348 -0
  194. package/scripts/hooks/lib/liveness-read.js +113 -0
  195. package/scripts/hooks/run-hook.js +6 -1
  196. package/scripts/hooks/stop-goal-fit.js +1471 -79
  197. package/scripts/hooks/workflow-steering.js +135 -5
  198. package/scripts/install-codex-home.sh +39 -0
  199. package/scripts/install-merge.js +330 -0
  200. package/src/cli/init.ts +218 -20
  201. package/src/cli/validate-workflow-artifacts.ts +18 -2
  202. package/src/cli/verify.ts +100 -0
  203. package/src/cli/workflow-sidecar.ts +2093 -84
  204. package/src/cli.ts +2 -3
  205. package/src/index.ts +53 -0
  206. package/src/lib/flow-resolver.ts +284 -0
  207. package/src/tools/build-universal-bundles.ts +34 -21
  208. package/src/tools/generate-context-map.ts +3 -17
  209. package/src/tools/validate-source-tree.ts +44 -104
  210. package/tsconfig.json +1 -0
  211. package/build/src/tools/filter-installed-packs.js +0 -135
  212. package/packaging/packs.json +0 -49
  213. package/scripts/filter-installed-packs.js +0 -2
  214. package/src/tools/filter-installed-packs.ts +0 -132
@@ -5,7 +5,7 @@ import path from "node:path";
5
5
  import { loadJson, readText, root, walkFiles, writeText } from "./common.js";
6
6
  const dist = process.env.FLOW_AGENTS_DIST_DIR ? path.resolve(process.env.FLOW_AGENTS_DIST_DIR) : path.join(root, "dist");
7
7
  const manifest = loadJson(path.join(root, "packaging/manifest.json"));
8
- const packs = loadJson(path.join(root, "packaging/packs.json"));
8
+ const pkgVersion = loadJson(path.join(root, "package.json"))["version"] ?? "0.0.0";
9
9
  const textExtensions = new Set([".css", ".html", ".js", ".json", ".md", ".sh", ".toml", ".txt", ".yaml", ".yml", ".ts"]);
10
10
  const dropDiagnostics = [];
11
11
  const printDiagnostics = !["0", "false", "no"].includes(String(process.env.FLOW_AGENTS_EXPORT_DIAGNOSTICS ?? "1").toLowerCase());
@@ -190,8 +190,9 @@ function generatedAgentsSummary(agents) {
190
190
  function exportRootAgentsMd(label, agents, taskDir) {
191
191
  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
192
  }
193
- function exportTargetReadme(label, installHint) {
194
- return `# ${label} Bundle\n\nGenerated from the canonical source in this repository.\n\n## Install\n\n\`\`\`bash\n${installHint}\n\`\`\`\n\nOptional pack filtering is available at install time with \`FLOW_AGENTS_PACKS\`.\nThe default pack is always included:\n\n\`\`\`bash\nFLOW_AGENTS_PACKS=development,knowledge ${installHint}\n\`\`\`\n\n## Contents\n\n- Harness-specific agents\n- Shared skills\n- Shared context, powers, prompts, scripts, and evals\n`;
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`;
194
+ function exportTargetReadme(label, installHint, extra = "") {
195
+ 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}`;
195
196
  }
196
197
  function mapClaudeTools(allowedTools) {
197
198
  const ordered = [];
@@ -277,8 +278,8 @@ function shellHook(command, timeout = 10, statusMessage) {
277
278
  function claudeTelemetry(event) {
278
279
  return `bash -lc 'root="\${CLAUDE_PROJECT_DIR:-$(pwd)}"; node "$root/scripts/hooks/claude-telemetry-hook.js" ${event} dev'`;
279
280
  }
280
- function claudePolicy(event, script) {
281
- return `bash -lc 'root="\${CLAUDE_PROJECT_DIR:-$(pwd)}"; node "$root/scripts/hooks/claude-hook-adapter.js" ${event} ${script.replace(/\.js$/, "")} ${script} default'`;
281
+ function claudePolicy(event, script, envPrefix = "") {
282
+ return `bash -lc 'root="\${CLAUDE_PROJECT_DIR:-$(pwd)}"; ${envPrefix}node "$root/scripts/hooks/claude-hook-adapter.js" ${event} ${script.replace(/\.js$/, "")} ${script} default'`;
282
283
  }
283
284
  function codexRoot(scriptPath) {
284
285
  return `root="\${CODEX_HOME:-}"; if [ -z "$root" ] || [ ! -f "$root/${scriptPath}" ]; then root=$(git rev-parse --show-toplevel 2>/dev/null || pwd); fi`;
@@ -288,17 +289,23 @@ function codexTelemetry(event) {
288
289
  return `bash -lc '${codexRoot("scripts/telemetry/telemetry.sh")}; bash "$root/scripts/telemetry/telemetry.sh" permissionRequest dev'`;
289
290
  return `bash -lc '${codexRoot("scripts/hooks/codex-telemetry-hook.js")}; node "$root/scripts/hooks/codex-telemetry-hook.js" ${event} dev'`;
290
291
  }
291
- function codexPolicy(script) {
292
- return `bash -lc '${codexRoot("scripts/hooks/codex-hook-adapter.js")}; node "$root/scripts/hooks/codex-hook-adapter.js" ${script.replace(/\.js$/, "")} ${script} default'`;
292
+ function codexPolicy(script, envPrefix = "") {
293
+ return `bash -lc '${codexRoot("scripts/hooks/codex-hook-adapter.js")}; ${envPrefix}node "$root/scripts/hooks/codex-hook-adapter.js" ${script.replace(/\.js$/, "")} ${script} default'`;
293
294
  }
295
+ // Shipped L2 runtimes enforce goal fit by default (mode=block), while remaining
296
+ // operator-overridable via the FLOW_AGENTS_GOAL_FIT_MODE environment variable.
297
+ // The canonical engine default stays warn so the conformance contract is honest.
298
+ const GOAL_FIT_MODE_PREFIX = 'FLOW_AGENTS_GOAL_FIT_MODE="${FLOW_AGENTS_GOAL_FIT_MODE:-block}" ';
294
299
  function exportClaudeSettings() {
295
300
  const hooks = {};
296
301
  for (const event of ["SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "PostToolUse", "Stop", "SessionEnd"]) {
297
302
  hooks[event] = [{ hooks: [shellHook(claudeTelemetry(event), 10, "Recording Flow Agents telemetry")] }];
298
303
  }
299
- hooks.Stop.push({ hooks: [shellHook(claudePolicy("Stop", "stop-goal-fit.js"), 30, "Running Flow Agents hook policy")] });
304
+ hooks.Stop.push({ hooks: [shellHook(claudePolicy("Stop", "stop-goal-fit.js", GOAL_FIT_MODE_PREFIX), 30, "Running Flow Agents hook policy")] });
305
+ hooks.SessionStart.push({ hooks: [shellHook(claudePolicy("SessionStart", "workflow-steering.js"), 30, "Running Flow Agents hook policy")] });
300
306
  hooks.UserPromptSubmit.push({ hooks: [shellHook(claudePolicy("UserPromptSubmit", "workflow-steering.js"), 30, "Running Flow Agents hook policy")] });
301
307
  hooks.PostToolUse.push({ hooks: [shellHook(claudePolicy("PostToolUse", "quality-gate.js"), 30, "Running Flow Agents hook policy")] });
308
+ hooks.PostToolUse.push({ hooks: [shellHook(claudePolicy("PostToolUse", "evidence-capture.js"), 30, "Capturing Flow Agents command evidence")] });
302
309
  hooks.PreToolUse.push({ hooks: [shellHook(claudePolicy("PreToolUse", "config-protection.js"), 30, "Running Flow Agents hook policy")] });
303
310
  return `${JSON.stringify({
304
311
  statusLine: { type: "command", command: 'bash -lc \'root="${CLAUDE_PROJECT_DIR:-$(pwd)}"; node "$root/scripts/statusline/flow-agents-statusline.js"\'' },
@@ -312,8 +319,10 @@ function exportCodexHooks() {
312
319
  for (const event of ["SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "PostToolUse", "Stop"]) {
313
320
  hooks[event] = [{ hooks: [shellHook(codexTelemetry(event), 10, "Recording Flow Agents telemetry")] }];
314
321
  }
315
- hooks.Stop.push({ hooks: [shellHook(codexPolicy("stop-goal-fit.js"), 30, "Running Flow Agents hook policy")] });
322
+ hooks.Stop.push({ hooks: [shellHook(codexPolicy("stop-goal-fit.js", GOAL_FIT_MODE_PREFIX), 30, "Running Flow Agents hook policy")] });
323
+ hooks.SessionStart.push({ hooks: [shellHook(codexPolicy("workflow-steering.js"), 30, "Running Flow Agents hook policy")] });
316
324
  hooks.UserPromptSubmit.push({ hooks: [shellHook(codexPolicy("workflow-steering.js"), 30, "Running Flow Agents hook policy")] });
325
+ hooks.PostToolUse.push({ hooks: [shellHook(codexPolicy("evidence-capture.js"), 30, "Capturing Flow Agents command evidence")] });
317
326
  return `${JSON.stringify({ hooks }, null, 2)}\n`;
318
327
  }
319
328
  function copySharedContent(targetRoot, targetName, token) {
@@ -337,21 +346,23 @@ function copySharedContent(targetRoot, targetName, token) {
337
346
  for (const dir of manifest.optional_copy_dirs ?? [])
338
347
  copyTree(path.join(root, dir), path.join(targetRoot, dir), targetName, token);
339
348
  writeText(path.join(targetRoot, "build/package.json"), `${JSON.stringify({ type: "module" }, null, 2)}\n`);
340
- const filterBuilt = path.join(root, "build/src/tools/filter-installed-packs.js");
341
349
  const commonBuilt = path.join(root, "build/src/tools/common.js");
342
- if (fs.existsSync(filterBuilt))
343
- writeText(path.join(targetRoot, "scripts/filter-installed-packs.mjs"), readText(filterBuilt).replace("./common.js", "./common.mjs"));
344
350
  if (fs.existsSync(commonBuilt))
345
351
  writeText(path.join(targetRoot, "scripts/common.mjs"), readText(commonBuilt));
346
352
  copyTree(path.join(root, "build/src"), path.join(targetRoot, "build/src"), targetName, token);
347
353
  }
348
- function installScript(label, defaultDestDisplay, token, destFallbackShell) {
354
+ function installScript(label, defaultDestDisplay, token, destFallbackShell, mergeConfig, stampConfig) {
349
355
  const replaceBlock = token ? `\nexport DEST\nfind "$DEST" -type f \\( -name '*.json' -o -name '*.md' -o -name '*.sh' -o -name '*.js' -o -name '*.ts' -o -name '*.yaml' -o -name '*.yml' \\) -print0 | xargs -0 perl -0pi -e 's#${token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}#$ENV{DEST}#g'` : "";
350
356
  const destFallback = destFallbackShell ? `\nif [[ -z "$DEST" ]]; then\n DEST="${destFallbackShell}"\nfi` : "";
351
357
  const destRequired = !destFallbackShell;
352
358
  const requiredCheck = destRequired ? `if [[ -z "$DEST" ]]; then\n usage\n exit 2\nfi\n` : "";
353
359
  const usageDest = destRequired ? "/path/to/workspace" : defaultDestDisplay;
354
- 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 " : ""}"$SRC"/ "$DEST"/\nif [[ -n "\${FLOW_AGENTS_PACKS:-}" ]]; then\n node "$DEST/scripts/filter-installed-packs.mjs" "$DEST" --packs "$FLOW_AGENTS_PACKS"\nfi${replaceBlock}\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`;
360
+ 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`
362
+ : 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`
364
+ : "";
365
+ 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`;
355
366
  }
356
367
  function buildBase(agents) {
357
368
  const bundle = path.join(dist, "base");
@@ -360,7 +371,7 @@ function buildBase(agents) {
360
371
  writeText(path.join(bundle, ".flow-agents", ".gitkeep"), "");
361
372
  writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Base", agents, ".flow-agents"));
362
373
  writeText(path.join(bundle, "README.md"), exportTargetReadme("Base", "bash install.sh /path/to/workspace"));
363
- writeText(path.join(bundle, "install.sh"), installScript("Base", "/path/to/workspace"));
374
+ writeText(path.join(bundle, "install.sh"), installScript("Base", "/path/to/workspace", undefined, undefined, undefined, { runtime: "base", version: pkgVersion }));
364
375
  fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
365
376
  }
366
377
  function buildKiro(agents) {
@@ -372,7 +383,7 @@ function buildKiro(agents) {
372
383
  writeText(path.join(bundle, "agents", `${spec.name}.json`), sanitizeText(`${JSON.stringify(sanitizeAgentJson(spec), null, 2)}\n`, "kiro", token));
373
384
  writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Kiro", agents, ".flow-agents"));
374
385
  writeText(path.join(bundle, "README.md"), exportTargetReadme("Kiro", "bash install.sh $HOME/.flow-agents"));
375
- writeText(path.join(bundle, "install.sh"), installScript("Kiro", "$HOME/.flow-agents", token, '${FLOW_AGENTS_DEST:-$HOME/.flow-agents}'));
386
+ writeText(path.join(bundle, "install.sh"), installScript("Kiro", "$HOME/.flow-agents", token, '${FLOW_AGENTS_DEST:-$HOME/.flow-agents}', undefined, { runtime: "kiro", version: pkgVersion }));
376
387
  fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
377
388
  }
378
389
  function buildClaudeCode(agents) {
@@ -388,7 +399,7 @@ function buildClaudeCode(agents) {
388
399
  writeText(path.join(bundle, ".claude/settings.json"), exportClaudeSettings());
389
400
  writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Claude Code", agents, manifest.claude_code.task_dir));
390
401
  writeText(path.join(bundle, "README.md"), exportTargetReadme("Claude Code", "bash install.sh /path/to/workspace"));
391
- writeText(path.join(bundle, "install.sh"), installScript("Claude Code", "/path/to/workspace"));
402
+ writeText(path.join(bundle, "install.sh"), installScript("Claude Code", "/path/to/workspace", undefined, undefined, { configRelPath: ".claude/settings.json", managedConfigRelPath: ".claude/settings.json", runtime: "claude-code", version: pkgVersion }));
392
403
  fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
393
404
  }
394
405
  function buildCodex(agents) {
@@ -409,8 +420,8 @@ function buildCodex(agents) {
409
420
  writeText(path.join(bundle, ".codex/skills", name, "SKILL.md"), sanitizeText(readText(src), "codex", "<bundle-root>"));
410
421
  }
411
422
  writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Codex", targetAgents, manifest.codex.task_dir));
412
- writeText(path.join(bundle, "README.md"), exportTargetReadme("Codex", "bash install.sh /path/to/workspace"));
413
- writeText(path.join(bundle, "install.sh"), installScript("Codex", "/path/to/workspace"));
423
+ writeText(path.join(bundle, "README.md"), exportTargetReadme("Codex", "bash install.sh /path/to/workspace", CODEX_LIVE_HOOKS_README));
424
+ writeText(path.join(bundle, "install.sh"), installScript("Codex", "/path/to/workspace", undefined, undefined, { configRelPath: ".codex/hooks.json", managedConfigRelPath: ".codex/hooks.json", runtime: "codex", version: pkgVersion }));
414
425
  fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
415
426
  }
416
427
  function exportOpencodeAgent(spec) {
@@ -546,6 +557,7 @@ export const FlowAgentsPlugin = async ({ project, client, $, directory, worktree
546
557
  const detail = { tool: input && input.tool };
547
558
  runTelemetry('tool.execute.after', detail);
548
559
  runAdapter('opencode-hook-adapter.js', 'tool.execute.after', detail, 'quality-gate', 'quality-gate.js', 'default');
560
+ runAdapter('opencode-hook-adapter.js', 'tool.execute.after', detail, 'evidence-capture', 'evidence-capture.js', 'default');
549
561
  },
550
562
  'session.idle': async (_input, _output) => {
551
563
  runTelemetry('session.idle');
@@ -591,7 +603,7 @@ function buildOpencode(agents) {
591
603
  writeText(path.join(bundle, "opencode.json"), exportOpencodeConfig());
592
604
  writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("opencode", agents, manifest.opencode.task_dir));
593
605
  writeText(path.join(bundle, "README.md"), exportTargetReadme("opencode", "bash install.sh /path/to/workspace"));
594
- writeText(path.join(bundle, "install.sh"), installScript("opencode", "/path/to/workspace"));
606
+ writeText(path.join(bundle, "install.sh"), installScript("opencode", "/path/to/workspace", undefined, undefined, { configRelPath: "opencode.json", managedConfigRelPath: "opencode.json", runtime: "opencode", version: pkgVersion }));
595
607
  fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
596
608
  }
597
609
  function exportPiExtension() {
@@ -679,6 +691,7 @@ export default function (pi: ExtensionAPI) {
679
691
  pi.on("tool_result", async (_event, _ctx) => {
680
692
  runTelemetry("tool_result");
681
693
  runAdapter("pi-hook-adapter.js", "tool_result", "quality-gate", "quality-gate.js");
694
+ runAdapter("pi-hook-adapter.js", "tool_result", "evidence-capture", "evidence-capture.js");
682
695
  });
683
696
 
684
697
  pi.on("session_shutdown", async (_event, _ctx) => {
@@ -701,7 +714,7 @@ function buildPi(agents) {
701
714
  writeText(path.join(bundle, ".pi/extensions/flow-agents.ts"), exportPiExtension());
702
715
  writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("pi", agents, manifest.pi.task_dir));
703
716
  writeText(path.join(bundle, "README.md"), exportTargetReadme("pi", "bash install.sh /path/to/workspace"));
704
- writeText(path.join(bundle, "install.sh"), installScript("pi", "/path/to/workspace"));
717
+ writeText(path.join(bundle, "install.sh"), installScript("pi", "/path/to/workspace", undefined, undefined, undefined, { runtime: "pi", version: pkgVersion }));
705
718
  fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
706
719
  }
707
720
  function buildCatalog(agents) {
@@ -711,7 +724,6 @@ function buildCatalog(agents) {
711
724
  agents: agents.slice().sort((a, b) => a.name.localeCompare(b.name)).map((spec) => spec.name),
712
725
  skills: collectAllSkills().map(({ name }) => name),
713
726
  powers: fs.readdirSync(path.join(root, "powers")).filter((name) => fs.existsSync(path.join(root, "powers", name, "mcp.json"))).sort(),
714
- packs: packs.packs ?? [],
715
727
  kits: fs.existsSync(kitsCatalog) ? loadJson(kitsCatalog).kits ?? [] : [],
716
728
  };
717
729
  }
@@ -0,0 +1,9 @@
1
+ export declare const root: string;
2
+ export declare function rel(file: string): string;
3
+ export declare function readText(file: string): string;
4
+ export declare function writeText(file: string, text: string): void;
5
+ export declare function loadJson<T = unknown>(file: string): T;
6
+ export declare function exists(file: string): boolean;
7
+ export declare function walkFiles(dir: string): string[];
8
+ export declare function oneLine(value: string, limit?: number): string;
9
+ export declare function markdownTable(headers: string[], rows: string[][]): string[];
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function main(argv?: string[]): number;
@@ -10,19 +10,20 @@ const dirDescriptions = {
10
10
  context: "Shared contracts, routing notes, templates, and reusable guidance.",
11
11
  docs: "Long-lived project documentation and GitHub Pages content.",
12
12
  evals: "Static, integration, install, and behavioral eval fixtures.",
13
- powers: "Optional MCP/tool integration packs.",
13
+ powers: "Optional MCP/tool capability bundles.",
14
14
  prompts: "Reusable prompt entry points.",
15
15
  schemas: "JSON Schema contracts for machine-readable workflow artifacts.",
16
16
  scripts: "Build, validation, hook, telemetry, workflow, and import/export utilities.",
17
17
  skills: "On-demand capability instructions and workflow primitives.",
18
18
  };
19
- const workflowSkills = new Set(["idea-to-backlog", "pull-work", "plan-work", "execute-plan", "review-work", "verify-work", "evidence-gate", "release-readiness", "learning-review", "deliver", "fix-bug", "tdd-workflow"]);
19
+ const workflowSkills = new Set(["idea-to-backlog", "pull-work", "plan-work", "execute-plan", "review-work", "verify-work", "evidence-gate", "gate-review", "release-readiness", "learning-review", "deliver", "continue-work", "fix-bug", "tdd-workflow"]);
20
20
  const commands = [
21
21
  ["Source tree", "npm run validate:source"],
22
22
  ["Static suite", "bash evals/run.sh static"],
23
23
  ["Integration suite", "bash evals/run.sh integration"],
24
24
  ["Workflow artifacts", "npm run workflow:validate-artifacts -- --require-sidecars --require-critique .flow-agents/<slug>"],
25
25
  ["Workflow sidecars", "npm run workflow:sidecar -- --help"],
26
+ ["Claim lookup", "npm run workflow:sidecar -- claim <id> <dir>"],
26
27
  ["Context map drift", "npm run context-map:check"],
27
28
  ["Bundle build", "npm run build:bundles"],
28
29
  ];
@@ -154,17 +155,6 @@ function powers() {
154
155
  const dir = path.join(root, "powers");
155
156
  return fs.readdirSync(dir).sort().flatMap((name) => exists(path.join(dir, name, "POWER.md")) ? [[name, rel(path.join(dir, name, "POWER.md"))]] : []);
156
157
  }
157
- function packs() {
158
- const data = loadJson(path.join(root, "packaging/packs.json"));
159
- return (data.packs ?? []).map((pack) => [
160
- String(pack.name ?? ""),
161
- pack.default ? "yes" : "no",
162
- String(Array.isArray(pack.skills) ? pack.skills.length : 0),
163
- String(Array.isArray(pack.agents) ? pack.agents.length : 0),
164
- String(Array.isArray(pack.powers) ? pack.powers.length : 0),
165
- oneLine(String(pack.description ?? "")),
166
- ]);
167
- }
168
158
  function latestRuntimeStates(includeRuntime) {
169
159
  if (!includeRuntime) {
170
160
  return [
@@ -205,9 +195,6 @@ function render(includeRuntime) {
205
195
  "## Support Skills", "", ...markdownTable(["Skill", "Source", "When To Load"], supportRows), "",
206
196
  "## Agents", "", ...markdownTable(["Agent", "Model", "Tools", "Role"], agents()), "",
207
197
  "## Optional Powers", "", ...markdownTable(["Power", "Source"], powers()), "",
208
- "## Packs", "",
209
- "Pack composition is defined in `packaging/packs.json`. The current builder exports pack metadata in bundle catalogs, and generated install scripts support opt-in `FLOW_AGENTS_PACKS` filtering while leaving all packs installed by default.", "",
210
- ...markdownTable(["Pack", "Default", "Skills", "Agents", "Powers", "Purpose"], packs()), "",
211
198
  "## Current Workflow State", "", ...latestRuntimeStates(includeRuntime), "",
212
199
  "## Context Loading Rules", "",
213
200
  "- For delivery work, load `deliver`, then the specific primitive skill for the current phase.",
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function main(argv?: string[]): number;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function main(argv?: string[]): Promise<number>;
@@ -2,7 +2,7 @@
2
2
  import fs from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import path from "node:path";
5
- import { spawnSync } from "node:child_process";
5
+ import { validateKitRepository as validateFlowKitRepository } from "../flow-kit/validate.js";
6
6
  import { loadJson, readText, rel, root, walkFiles } from "./common.js";
7
7
  class Reporter {
8
8
  errors = [];
@@ -11,14 +11,10 @@ class Reporter {
11
11
  this.fail(message); }
12
12
  }
13
13
  const manifestPath = path.join(root, "packaging/manifest.json");
14
- const packsPath = path.join(root, "packaging/packs.json");
15
14
  const kitsCatalogPath = path.join(root, "kits/catalog.json");
16
15
  const flowRoot = process.env.FLOW_CLI_ROOT ? path.resolve(process.env.FLOW_CLI_ROOT) : "";
17
16
  const flowSchemaPath = flowRoot ? path.join(flowRoot, "schemas", "flow-definition.schema.json") : "";
18
17
  const flowCliPath = flowRoot ? ["dist/cli.js", "src/cli.js"].map((candidate) => path.join(flowRoot, candidate)).find((candidate) => fs.existsSync(candidate)) ?? path.join(flowRoot, "dist/cli.js") : "";
19
- const kitIdRe = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*$/;
20
- const kitAssetSections = new Set(["skills", "docs", "adapters", "evals", "assets"]);
21
- const kitTopLevelKeys = new Set(["schema_version", "id", "name", "product_name", "description", "flows", ...kitAssetSections]);
22
18
  const textRefExtensions = new Set([".md", ".yaml", ".yml", ".json", ".sh", ".js", ".toml"]);
23
19
  const ignoredRefDirs = new Set(["node_modules", "__pycache__", ".pytest_cache", ".cache"]);
24
20
  const legacyRefRe = /(?<![A-Za-z0-9_.-])(?:agents|agent-cards|context|evals|lib|powers|prompts|scripts|skills)\/[A-Za-z0-9_./@:+-]+/g;
@@ -32,10 +28,8 @@ const mirroredFiles = new Map([
32
28
  ]);
33
29
  const publicScriptWrappers = new Map([
34
30
  ["scripts/build-universal-bundles.js", { target: "../build/src/tools/build-universal-bundles.js", significantLines: [
35
- "// Supports FLOW_AGENTS_PACKS through the TypeScript bundle builder.",
36
31
  'import("../build/src/tools/build-universal-bundles.js").then(({ main }) => process.exit(main()));',
37
32
  ] }],
38
- ["scripts/filter-installed-packs.js", { target: "../build/src/tools/filter-installed-packs.js", significantLines: ['import("../build/src/tools/filter-installed-packs.js").then(({ main }) => process.exit(main(process.argv.slice(2))));'] }],
39
33
  ["scripts/generate-context-map.js", { target: "../build/src/tools/generate-context-map.js", significantLines: ['import("../build/src/tools/generate-context-map.js").then(({ main }) => process.exit(main(process.argv.slice(2))));'] }],
40
34
  ["scripts/kit.js", { target: "../build/src/cli/kit.js", significantLines: ['import("../build/src/cli/kit.js").then(({ main }) => main().then((code) => process.exit(code)));'] }],
41
35
  ["scripts/pull-work-provider.js", { target: "../build/src/cli/pull-work-provider.js", significantLines: ['import("../build/src/cli/pull-work-provider.js").then(({ main }) => process.exit(main()));'] }],
@@ -62,6 +56,7 @@ const hookFilePolicies = new Map([
62
56
  ["scripts/hooks/codex-telemetry-hook.js", { category: "telemetry shim", requiredNeedles: ["codex", "telemetry"] }],
63
57
  ["scripts/hooks/run-hook.js", { category: "hook runner", requiredNeedles: ["isHookEnabled", "Path traversal rejected"] }],
64
58
  ["scripts/hooks/config-protection.js", { category: "policy hook", requiredNeedles: ["Config Protection Hook"] }],
59
+ ["scripts/hooks/evidence-capture.js", { category: "policy hook", requiredNeedles: ["Evidence Capture Hook"] }],
65
60
  ["scripts/hooks/governance-audit.sh", { category: "policy hook", requiredNeedles: ["governance-audit.sh", "audit_emit"] }],
66
61
  ["scripts/hooks/opencode-hook-adapter.js", { category: "runtime adapter", requiredNeedles: ["opencode", "run-hook.js"] }],
67
62
  ["scripts/hooks/opencode-telemetry-hook.js", { category: "telemetry shim", requiredNeedles: ["opencode", "telemetry"] }],
@@ -78,6 +73,7 @@ const hookFilePolicies = new Map([
78
73
  ["scripts/hooks/desktop-notify.sh", { category: "local notification helper", requiredNeedles: ["desktop-notify.sh", "osascript"] }],
79
74
  ["scripts/hooks/lib/audit-transport.sh", { category: "shared hook library", requiredNeedles: ["audit_emit"] }],
80
75
  ["scripts/hooks/lib/hook-flags.js", { category: "shared hook library", requiredNeedles: ["isHookEnabled"] }],
76
+ ["scripts/hooks/lib/liveness-read.js", { category: "shared hook library", requiredNeedles: ["freshHolders", "readLivenessEvents"] }],
81
77
  ["scripts/hooks/lib/patterns.sh", { category: "shared hook library", requiredNeedles: ["_detect_secrets"] }],
82
78
  ["scripts/hooks/lib/resolve-formatter.js", { category: "shared hook library", requiredNeedles: ["resolveFormatter"] }],
83
79
  ]);
@@ -196,81 +192,7 @@ function validateManifest(reporter, manifest, agentNames) {
196
192
  for (const agent of manifest.codex?.excluded_agents ?? [])
197
193
  reporter.check(agentNames.has(agent), `${rel(manifestPath)}: codex excluded agent '${agent}' does not exist`);
198
194
  }
199
- function validatePacksManifest(reporter, agentNames) {
200
- const data = tryLoadJson(packsPath, reporter);
201
- if (!data || typeof data !== "object")
202
- return;
203
- reporter.check(data.schema_version === "1.0", `${rel(packsPath)}: schema_version must be 1.0`);
204
- reporter.check(Array.isArray(data.packs) && data.packs.length > 0, `${rel(packsPath)}: packs must be a non-empty list`);
205
- const skillNames = new Set(fs.readdirSync(path.join(root, "skills")).filter((name) => fs.existsSync(path.join(root, "skills", name, "SKILL.md"))));
206
- const powerNames = new Set(fs.readdirSync(path.join(root, "powers")).filter((name) => fs.existsSync(path.join(root, "powers", name, "POWER.md"))));
207
- const names = new Set();
208
- const defaults = new Set();
209
- const assigned = { skills: new Set(), agents: new Set(), powers: new Set() };
210
- (Array.isArray(data.packs) ? data.packs : []).forEach((pack, index) => {
211
- const name = pack?.name;
212
- if (typeof name !== "string" || !/^[a-z][a-z0-9-]*$/.test(name)) {
213
- reporter.fail(`${rel(packsPath)}: packs[${index}].name must be a kebab-case string`);
214
- return;
215
- }
216
- if (names.has(name))
217
- reporter.fail(`${rel(packsPath)}: duplicate pack name '${name}'`);
218
- names.add(name);
219
- if (pack.default === true)
220
- defaults.add(name);
221
- reporter.check(typeof pack.description === "string" && !!pack.description, `${rel(packsPath)}: pack '${name}' missing description`);
222
- for (const [field, available] of [["skills", skillNames], ["agents", agentNames], ["powers", powerNames]]) {
223
- const values = pack[field] ?? [];
224
- reporter.check(Array.isArray(values), `${rel(packsPath)}: pack '${name}' .${field} must be a list`);
225
- const seen = new Set();
226
- for (const value of Array.isArray(values) ? values : []) {
227
- if (typeof value !== "string") {
228
- reporter.fail(`${rel(packsPath)}: pack '${name}' .${field} entry is not a string`);
229
- continue;
230
- }
231
- if (seen.has(value))
232
- reporter.fail(`${rel(packsPath)}: pack '${name}' has duplicate ${field} entry '${value}'`);
233
- seen.add(value);
234
- assigned[field].add(value);
235
- reporter.check(available.has(value), `${rel(packsPath)}: pack '${name}' references missing ${field.slice(0, -1)} '${value}'`);
236
- }
237
- }
238
- });
239
- reporter.check(defaults.has("core"), `${rel(packsPath)}: core pack must be default`);
240
- const missingSkills = [...skillNames].filter((name) => !assigned.skills.has(name)).sort();
241
- reporter.check(missingSkills.length === 0, `${rel(packsPath)}: skills missing from all packs: ${missingSkills.join(", ")}`);
242
- }
243
- function safeLocalPath(baseDir, pathText, label, reporter) {
244
- if (typeof pathText !== "string" || !pathText) {
245
- reporter.fail(`${label} must be a non-empty relative path`);
246
- return undefined;
247
- }
248
- if (path.isAbsolute(pathText)) {
249
- reporter.fail(`${label} must be relative; absolute paths are not allowed`);
250
- return undefined;
251
- }
252
- if (pathText.split(/[\\/]/).includes("..")) {
253
- reporter.fail(`${label} must stay inside the kit directory; '..' path traversal is not allowed`);
254
- return undefined;
255
- }
256
- return path.join(baseDir, pathText);
257
- }
258
- function validateFlowDefinitionShape(file, data, reporter) {
259
- const localCli = flowCliPath;
260
- if (fs.existsSync(localCli)) {
261
- const result = spawnSync("node", [localCli, "validate-definition", file, "--json"], { encoding: "utf8" });
262
- if (result.status !== 0)
263
- reporter.fail(`${rel(file)}: Flow validation failed: ${(result.stderr || result.stdout).trim()}`);
264
- return;
265
- }
266
- if (!data || typeof data !== "object") {
267
- reporter.fail(`${rel(file)}: Flow Definition must be an object`);
268
- return;
269
- }
270
- for (const key of ["id", "version", "steps", "gates"])
271
- reporter.check(key in data, `${rel(file)}: missing .${key}`);
272
- }
273
- function validateKitRepository(kitDir, reporter) {
195
+ async function validateKitRepository(kitDir, reporter) {
274
196
  if (!fs.existsSync(kitDir) || !fs.statSync(kitDir).isDirectory()) {
275
197
  reporter.fail(`${rel(kitDir)}: kit directory does not exist`);
276
198
  return;
@@ -279,78 +201,10 @@ function validateKitRepository(kitDir, reporter) {
279
201
  reporter.check(fs.existsSync(kitJson), `${rel(kitDir)}: missing kit.json at repository root`);
280
202
  if (!fs.existsSync(kitJson))
281
203
  return;
282
- const data = tryLoadJson(kitJson, reporter);
283
- if (!data || typeof data !== "object")
284
- return;
285
- const unknownKeys = Object.keys(data).filter((key) => !kitTopLevelKeys.has(key)).sort();
286
- if (unknownKeys.length)
287
- reporter.fail(`${rel(kitJson)}: unsupported fields ${unknownKeys.join(", ")}; remove them or add them to the Flow Kit Repository contract`);
288
- reporter.check(data.schema_version === "1.0", `${rel(kitJson)}: .schema_version must be "1.0"`);
289
- reporter.check(typeof data.id === "string" && kitIdRe.test(data.id), `${rel(kitJson)}: .id must be a stable kebab-case string`);
290
- reporter.check(typeof data.name === "string" && !!data.name.trim(), `${rel(kitJson)}: .name must be a non-empty string`);
291
- for (const section of [...kitAssetSections].sort())
292
- if (section in data) {
293
- if (!Array.isArray(data[section])) {
294
- reporter.fail(`${rel(kitJson)}: .${section} must be a list of relative asset paths or objects with path`);
295
- continue;
296
- }
297
- const seenPaths = new Set();
298
- const seenIds = new Set();
299
- data[section].forEach((entry, index) => {
300
- const pathValue = typeof entry === "string" ? entry : entry?.path;
301
- const assetId = typeof entry === "object" ? entry.id : undefined;
302
- if (typeof entry === "object") {
303
- const unknown = Object.keys(entry).filter((key) => !["id", "path", "description"].includes(key)).sort();
304
- if (unknown.length)
305
- reporter.fail(`${rel(kitJson)}: ${section}[${index}] has unsupported fields ${unknown.join(", ")}; use id, path, or description`);
306
- }
307
- if (assetId !== undefined && (typeof assetId !== "string" || !kitIdRe.test(assetId)))
308
- reporter.fail(`${rel(kitJson)}: ${section}[${index}].id must be a stable dot/kebab-case string`);
309
- const assetPath = safeLocalPath(kitDir, pathValue, `${rel(kitJson)}: ${section}[${index}].path`, reporter);
310
- if (!assetPath)
311
- return;
312
- if (seenPaths.has(String(pathValue)))
313
- reporter.fail(`${rel(kitJson)}: ${section}[${index}].path duplicates '${pathValue}'; declare each asset once`);
314
- seenPaths.add(String(pathValue));
315
- if (typeof assetId === "string") {
316
- if (seenIds.has(assetId))
317
- reporter.fail(`${rel(kitJson)}: ${section}[${index}].id duplicates '${assetId}'; use a unique asset id`);
318
- seenIds.add(assetId);
319
- }
320
- reporter.check(fs.existsSync(assetPath), `${rel(kitJson)}: ${section}[${index}].path points at missing asset: ${pathValue}; add the file or remove the entry`);
321
- });
322
- }
323
- if (!Array.isArray(data.flows) || !data.flows.length) {
324
- reporter.fail(`${rel(kitJson)}: .flows must be a non-empty list; add at least one Flow Definition entry`);
325
- return;
326
- }
327
- const seenIds = new Set();
328
- const seenPaths = new Set();
329
- data.flows.forEach((flow, index) => {
330
- if (!flow || typeof flow !== "object") {
331
- reporter.fail(`${rel(kitJson)}: flows[${index}] must be an object with id and path`);
332
- return;
333
- }
334
- if (typeof flow.id !== "string" || !kitIdRe.test(flow.id))
335
- reporter.fail(`${rel(kitJson)}: flows[${index}].id must be a stable dot/kebab-case string`);
336
- else if (seenIds.has(flow.id))
337
- reporter.fail(`${rel(kitJson)}: flows[${index}].id duplicates '${flow.id}'; use a unique Flow id`);
338
- else
339
- seenIds.add(flow.id);
340
- const flowPath = safeLocalPath(kitDir, flow.path, `${rel(kitJson)}: flows[${index}].path`, reporter);
341
- if (!flowPath)
342
- return;
343
- if (seenPaths.has(String(flow.path))) {
344
- reporter.fail(`${rel(kitJson)}: flows[${index}].path duplicates '${flow.path}'; declare each Flow Definition once`);
345
- return;
346
- }
347
- seenPaths.add(String(flow.path));
348
- reporter.check(fs.existsSync(flowPath), `${rel(kitJson)}: flows[${index}].path points at missing Flow Definition: ${flow.path}; add the file or fix the path`);
349
- if (fs.existsSync(flowPath))
350
- validateFlowDefinitionShape(flowPath, tryLoadJson(flowPath, reporter), reporter);
351
- });
204
+ for (const error of await validateFlowKitRepository(kitDir))
205
+ reporter.fail(error);
352
206
  }
353
- function validateKits(reporter) {
207
+ async function validateKits(reporter) {
354
208
  reporter.check(fs.existsSync(path.join(root, "kits")), "kits directory missing");
355
209
  const catalog = tryLoadJson(kitsCatalogPath, reporter);
356
210
  const kits = catalog?.kits;
@@ -362,17 +216,17 @@ function validateKits(reporter) {
362
216
  console.log(fs.existsSync(localCli) ? `info: validating kit Flow Definitions with Flow CLI at ${localCli}` : `warning: Flow validator unavailable; source-tree check only verifies Flow Definition top-level shape`);
363
217
  else
364
218
  console.log("warning: Flow schema not configured; source-tree check only verifies Flow Definition top-level shape. Set FLOW_CLI_ROOT to enable Flow CLI validation. Container validation (kit.json core fields) will delegate to 'flow validate-kit' from @kontourai/flow when FLOW_CLI_ROOT is available.");
365
- kits.forEach((entry, index) => {
219
+ for (const [index, entry] of kits.entries()) {
366
220
  const kitText = typeof entry === "string" ? entry : ["path", "directory", "dir", "id", "name"].map((key) => entry?.[key]).find((value) => typeof value === "string" && value);
367
221
  if (!kitText) {
368
222
  reporter.fail(`${rel(kitsCatalogPath)}: kits[${index}] missing path, directory, dir, id, or name`);
369
- return;
223
+ continue;
370
224
  }
371
225
  const kitRef = String(kitText).startsWith("kits/") ? path.join(root, kitText) : path.join(root, "kits", kitText);
372
226
  const kitDir = path.basename(kitRef) === "kit.json" ? path.dirname(kitRef) : kitRef;
373
227
  reporter.check(fs.existsSync(kitDir) && fs.statSync(kitDir).isDirectory(), `${rel(kitsCatalogPath)}: kits[${index}] points at missing kit folder: ${kitText}`);
374
- validateKitRepository(kitDir, reporter);
375
- });
228
+ await validateKitRepository(kitDir, reporter);
229
+ }
376
230
  }
377
231
  function validateAgentPaths(reporter, manifest) {
378
232
  for (const file of walkFiles(path.join(root, "agents")).filter((item) => item.endsWith(".json"))) {
@@ -480,6 +334,32 @@ function validatePublicScriptWrappers(reporter) {
480
334
  reporter.check(JSON.stringify(significantLines) === JSON.stringify(policy.significantLines), `${file}: public wrapper must match the exact thin launcher body for ${policy.target}`);
481
335
  }
482
336
  }
337
+ function validateAdrNumbers(reporter) {
338
+ // Each ADR (a docs/adr file with an `# ADR NNNN:` heading) must own a unique
339
+ // number, and its filename prefix must match that number. Companion/index docs
340
+ // without an ADR heading (e.g. a numbered skill-audit tied to an ADR) are
341
+ // intentionally skipped. Guards against concurrent number collisions like the
342
+ // duplicate ADR 0014 from PRs #180/#172.
343
+ const adrDir = path.join(root, "docs/adr");
344
+ if (!fs.existsSync(adrDir))
345
+ return;
346
+ const byNumber = new Map();
347
+ for (const file of walkFiles(adrDir)) {
348
+ if (path.extname(file) !== ".md")
349
+ continue;
350
+ const heading = readText(file).match(/^#\s+ADR\s+(\d{4}):/m);
351
+ if (!heading)
352
+ continue; // not an ADR decision doc
353
+ const num = heading[1];
354
+ reporter.check(path.basename(file).startsWith(`${num}-`), `${rel(file)}: ADR heading number ${num} does not match the filename prefix`);
355
+ const list = byNumber.get(num) ?? [];
356
+ list.push(rel(file));
357
+ byNumber.set(num, list);
358
+ }
359
+ for (const [num, files] of byNumber) {
360
+ reporter.check(files.length === 1, `docs/adr: duplicate ADR number ${num} — ${files.join(", ")}. ADR numbers must be unique; renumber one.`);
361
+ }
362
+ }
483
363
  function validateHookInventory(reporter) {
484
364
  const readme = readText(path.join(root, "scripts/README.md"));
485
365
  const hookFiles = walkFiles(path.join(root, "scripts/hooks"))
@@ -605,7 +485,7 @@ function validateNoFirstPartyPythonCommands(reporter) {
605
485
  reporter.fail(`${relative}: direct first-party Python command reference is not allowed; use npm/flow-agents TypeScript commands`);
606
486
  }
607
487
  }
608
- export function main(argv = process.argv.slice(2)) {
488
+ export async function main(argv = process.argv.slice(2)) {
609
489
  const kitIndex = argv.indexOf("--kit");
610
490
  if (kitIndex >= 0) {
611
491
  const kitDir = argv[kitIndex + 1];
@@ -619,7 +499,7 @@ export function main(argv = process.argv.slice(2)) {
619
499
  console.log(`info: validating kit Flow Definitions with Flow CLI at ${localCli}`);
620
500
  else
621
501
  console.log("warning: Flow validation surface unavailable; local kit check uses the minimal Flow Definition fallback");
622
- validateKitRepository(path.resolve(kitDir), reporter);
502
+ await validateKitRepository(path.resolve(kitDir), reporter);
623
503
  if (reporter.errors.length) {
624
504
  console.log("Flow Kit repository validation failed:");
625
505
  for (const error of reporter.errors)
@@ -635,14 +515,14 @@ export function main(argv = process.argv.slice(2)) {
635
515
  validateAgentCards(reporter, agentNames);
636
516
  validatePowers(reporter);
637
517
  validateManifest(reporter, manifest, agentNames);
638
- validatePacksManifest(reporter, agentNames);
639
- validateKits(reporter);
518
+ await validateKits(reporter);
640
519
  validateAgentPaths(reporter, manifest);
641
520
  validateLegacyRefs(reporter);
642
521
  validateMirrors(reporter);
643
522
  validateUsageFeedbackFiles(reporter);
644
523
  validatePublicScriptWrappers(reporter);
645
524
  validateHookInventory(reporter);
525
+ validateAdrNumbers(reporter);
646
526
  validateFixtureOwnership(reporter);
647
527
  validatePackageCommandSurface(reporter);
648
528
  validateNoFirstPartyPythonFiles(reporter);
@@ -672,5 +552,5 @@ catch {
672
552
  return process.argv[1];
673
553
  } })();
674
554
  if (_selfRealPath === _argv1RealPath) {
675
- process.exitCode = main();
555
+ main().then((code) => { process.exitCode = code; });
676
556
  }
@@ -20,6 +20,16 @@ The artifact root is local working memory unless a workflow explicitly promotes
20
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
21
  - Do not commit local workflow runtime roots such as `.flow-agents/<slug>/`; final acceptance must promote durable content before merge.
22
22
 
23
+ ## Persistence Integrity
24
+
25
+ Writing a durable artifact must **fail loud, never fail-open.** If a record (state, evidence, a
26
+ trust.bundle, a claim) cannot be persisted — a missing dependency, a validation failure, an I/O
27
+ error — the operation **fails with the reason**; it must not return success while silently
28
+ dropping the write. A silently-skipped persist is **data loss**, not a degraded mode, and is
29
+ invisible to the caller that depended on it. Callers act on persistence **return values**, not
30
+ just thrown exceptions. (See #160: an ignored `{written:false}` from the bundle writer dropped
31
+ records under concurrency.)
32
+
23
33
  ## Required Artifact Types
24
34
 
25
35
  ### Structured Sidecars
@@ -61,6 +61,7 @@ After CI passes and the work is merged, released, or otherwise accepted:
61
61
  - [ ] durable docs link back to the provider record, archived plan, or session artifact when useful
62
62
  - [ ] local `.flow-agents/` runtime artifacts remain untracked, and durable outcomes are promoted before merge to `main`
63
63
  - [ ] follow-up issues or learning-review items created for deferred work
64
+ - [ ] **workspace cleaned up after a confirmed merge**: the merge is verified from the provider's own merge record (a merge commit / `mergedAt`), not a green check or a command exit code; then the isolated worktree is removed and the now-merged branch is deleted locally and on the remote, honoring the `worktree_lifecycle` (`retain_until: pr_merged`) recorded at selection. Never delete a branch or worktree before the merge is confirmed. A delivery is not complete while it leaves a stale worktree or merged branch behind.
64
65
 
65
66
  ## Distribution Rule
66
67
 
@@ -59,6 +59,7 @@ All reviewers are read-only reporters. They may inspect files, run read-only ana
59
59
  Attempt relevant perspectives and record findings:
60
60
 
61
61
  - Code quality: readability, naming, function/file size, error handling, duplication, maintainability
62
+ - Failure handling: callers act on failure *return values*, not just exceptions — flag fail-open on any data-persisting path (per the persistence-integrity invariant in the artifact contract)
62
63
  - Correctness risks: edge cases, unintended behavior, unsafe assumptions, missing tests
63
64
  - Standards fit: project conventions, local architecture, public contracts, documented decisions
64
65
  - Security: secrets, injection, XSS, path traversal, auth/authz, unsafe external calls, vulnerable dependencies