@kontourai/flow-agents 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/.github/CODEOWNERS +8 -0
  2. package/.github/workflows/ci.yml +32 -0
  3. package/AGENTS.md +4 -4
  4. package/CHANGELOG.md +39 -0
  5. package/CONTEXT.md +14 -0
  6. package/README.md +9 -3
  7. package/agents/dev.json +1 -1
  8. package/agents/tool-code-reviewer.json +1 -1
  9. package/agents/tool-planner.json +3 -3
  10. package/agents/tool-verifier.json +3 -3
  11. package/build/src/cli/console-learning-projection.js +3 -2
  12. package/build/src/cli/init.js +104 -21
  13. package/build/src/cli/kit.js +37 -6
  14. package/build/src/cli/pull-work-provider.js +1 -1
  15. package/build/src/cli/usage-feedback.js +3 -3
  16. package/build/src/cli/validate-hook-influence.js +1 -0
  17. package/build/src/cli/validate-workflow-artifacts.js +22 -6
  18. package/build/src/cli/workflow-artifact-cleanup-audit.js +36 -2
  19. package/build/src/cli/workflow-sidecar.d.ts +14 -7
  20. package/build/src/cli/workflow-sidecar.js +792 -69
  21. package/build/src/flow-kit/validate.d.ts +23 -0
  22. package/build/src/flow-kit/validate.js +67 -1
  23. package/build/src/index.d.ts +1 -1
  24. package/build/src/index.js +1 -1
  25. package/build/src/lib/flow-resolver.d.ts +6 -4
  26. package/build/src/lib/flow-resolver.js +125 -58
  27. package/build/src/lib/local-artifact-root.d.ts +14 -2
  28. package/build/src/lib/local-artifact-root.js +22 -5
  29. package/build/src/lib/workflow-learning-projection.js +2 -2
  30. package/build/src/runtime-adapters.d.ts +12 -0
  31. package/build/src/runtime-adapters.js +56 -16
  32. package/build/src/tools/build-universal-bundles.js +49 -16
  33. package/build/src/tools/generate-context-map.js +10 -8
  34. package/build/src/tools/validate-source-tree.js +23 -1
  35. package/console.telemetry.json +22 -0
  36. package/context/contracts/artifact-contract.md +38 -7
  37. package/context/contracts/builder-kit-workflow-state-contract.md +3 -2
  38. package/context/contracts/decision-registry-contract.md +263 -0
  39. package/context/contracts/delivery-contract.md +1 -1
  40. package/context/contracts/planning-contract.md +2 -1
  41. package/context/contracts/review-contract.md +5 -3
  42. package/context/contracts/verification-contract.md +9 -1
  43. package/context/contracts/work-item-contract.md +1 -1
  44. package/context/deferred/parallelization.md +1 -1
  45. package/context/gate-awareness.md +4 -4
  46. package/context/scripts/git-status.sh +2 -2
  47. package/context/scripts/hooks/config-protection.js +502 -2
  48. package/context/scripts/hooks/lib/config-protection-remedies.js +69 -0
  49. package/context/scripts/hooks/run-hook.js +14 -1
  50. package/context/scripts/hooks/stop-goal-fit.js +1140 -99
  51. package/context/scripts/hooks/workflow-steering.js +212 -6
  52. package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
  53. package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +35 -0
  54. package/docs/adr/0019-kit-dependency-ownership.md +62 -0
  55. package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +290 -0
  56. package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +221 -0
  57. package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +283 -0
  58. package/docs/agent-system-guidebook.md +3 -3
  59. package/docs/agent-usage-feedback-loop.md +6 -6
  60. package/docs/context-map.md +9 -7
  61. package/docs/decisions/decision-records.md +57 -0
  62. package/docs/decisions/index.md +14 -0
  63. package/docs/decisions/promotion-gate.md +52 -0
  64. package/docs/developer-architecture.md +1 -1
  65. package/docs/fixture-ownership.md +3 -0
  66. package/docs/flow-kit-repository-contract.md +5 -5
  67. package/docs/getting-started.md +12 -2
  68. package/docs/integrations/harness-install.md +2 -1
  69. package/docs/kit-authoring-guide.md +64 -5
  70. package/docs/knowledge-kit.md +5 -1
  71. package/docs/learnings/2026-07-improvement-program.md +203 -0
  72. package/docs/migrations.md +6 -5
  73. package/docs/north-star.md +2 -2
  74. package/docs/operating-layers.md +4 -3
  75. package/docs/repository-structure.md +5 -5
  76. package/docs/skills-map.md +8 -8
  77. package/docs/spec/runtime-hook-surface.md +19 -15
  78. package/docs/spikes/graph-provider-2026-07.md +155 -0
  79. package/docs/standards-register.md +7 -7
  80. package/docs/trust-anchor-adoption.md +105 -4
  81. package/docs/work-item-adapters.md +1 -1
  82. package/docs/workflow-artifact-lifecycle.md +72 -9
  83. package/docs/workflow-eval-strategy.md +3 -3
  84. package/docs/workflow-shared-contracts.md +1 -1
  85. package/docs/workflow-usage-guide.md +254 -21
  86. package/evals/acceptance/DEMO-false-completion.md +20 -11
  87. package/evals/acceptance/prove-capture-teeth-declared.sh +15 -15
  88. package/evals/acceptance/prove-capture-teeth.sh +11 -11
  89. package/evals/acceptance/test_kiro_harness.sh +1 -1
  90. package/evals/ci/antigaming-suite.sh +5 -0
  91. package/evals/ci/run-baseline.sh +65 -1
  92. package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/flows/review.flow.json +26 -0
  93. package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/kit.json +13 -0
  94. package/evals/fixtures/flow-kit-repository/valid-with-dependency/flows/review.flow.json +26 -0
  95. package/evals/fixtures/flow-kit-repository/valid-with-dependency/kit.json +13 -0
  96. package/evals/fixtures/hook-influence/cases.json +32 -0
  97. package/evals/fixtures/pull-work-provider/github-issues.json +6 -6
  98. package/evals/fixtures/trust-reconcile-exploits/fabricated-attestation.json +59 -0
  99. package/evals/fixtures/trust-reconcile-exploits/no-label-bypass.json +36 -0
  100. package/evals/fixtures/trust-reconcile-exploits/skip-assumed-bypass.json +59 -0
  101. package/evals/fixtures/trust-reconcile-exploits/status-misassertion.json +48 -0
  102. package/evals/fixtures/trust-reconcile-exploits/waived-command-check.json +44 -0
  103. package/evals/fixtures/trust-reconcile-mixed-bundle/mixed-bundle.json +159 -0
  104. package/evals/fixtures/trust-reconcile-ws3/ws3-bundle.json +920 -0
  105. package/evals/integration/test_actor_identity.sh +254 -0
  106. package/evals/integration/test_builder_step_producers.sh +33 -23
  107. package/evals/integration/test_bundle_install.sh +22 -16
  108. package/evals/integration/test_bundle_lifecycle.sh +11 -11
  109. package/evals/integration/test_captured_fail_reconciliation.sh +50 -50
  110. package/evals/integration/test_checkpoint_signing.sh +43 -0
  111. package/evals/integration/test_claim_lookup.sh +4 -4
  112. package/evals/integration/test_codex_hook_resolution.sh +114 -0
  113. package/evals/integration/test_command_log_concurrency.sh +8 -8
  114. package/evals/integration/test_command_log_fork_classification.sh +3 -3
  115. package/evals/integration/test_command_log_integrity.sh +16 -16
  116. package/evals/integration/test_console_learning_projection.sh +1 -1
  117. package/evals/integration/test_dual_emit_flow_step.sh +145 -0
  118. package/evals/integration/test_enforcer_expects_driven.sh +12 -12
  119. package/evals/integration/test_evidence_capture_hook.sh +14 -13
  120. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  121. package/evals/integration/test_flow_agents_statusline.sh +2 -2
  122. package/evals/integration/test_flow_kit_install_git.sh +52 -0
  123. package/evals/integration/test_flow_kit_repository.sh +2 -0
  124. package/evals/integration/test_flowdef_session_activation.sh +6 -6
  125. package/evals/integration/test_flowdef_union_floor_regression.sh +429 -0
  126. package/evals/integration/test_gate_bypass_chain.sh +52 -40
  127. package/evals/integration/test_gate_lockdown.sh +97 -73
  128. package/evals/integration/test_gate_review_inquiry_records.sh +3 -3
  129. package/evals/integration/test_goal_fit_escape_hatch.sh +10 -10
  130. package/evals/integration/test_goal_fit_ghost_session.sh +104 -0
  131. package/evals/integration/test_goal_fit_hook.sh +32 -32
  132. package/evals/integration/test_goal_fit_rederive.sh +9 -9
  133. package/evals/integration/test_hook_category_behaviors.sh +103 -0
  134. package/evals/integration/test_hook_influence_cases.sh +1 -0
  135. package/evals/integration/test_install_merge.sh +262 -1
  136. package/evals/integration/test_kit_identity_trust.sh +6 -6
  137. package/evals/integration/test_liveness_conflict_injection.sh +587 -0
  138. package/evals/integration/test_liveness_heartbeat.sh +677 -0
  139. package/evals/integration/test_liveness_verdict.sh +394 -0
  140. package/evals/integration/test_local_flow_kit_install.sh +15 -0
  141. package/evals/integration/test_phase_map_and_gate_claim.sh +63 -11
  142. package/evals/integration/test_promote_gate.sh +178 -0
  143. package/evals/integration/test_publish_delivery.sh +2 -2
  144. package/evals/integration/test_pull_work_liveness_preflight.sh +386 -0
  145. package/evals/integration/test_pull_work_provider.sh +2 -2
  146. package/evals/integration/test_reconcile_soundness.sh +4 -4
  147. package/evals/integration/test_resolvefirststep_security.sh +22 -0
  148. package/evals/integration/test_runtime_adapter_activation.sh +87 -18
  149. package/evals/integration/test_session_resume_roundtrip.sh +147 -0
  150. package/evals/integration/test_sidecar_field_preservation.sh +249 -0
  151. package/evals/integration/test_trust_reconcile.sh +2 -2
  152. package/evals/integration/test_trust_reconcile_manifest.sh +79 -0
  153. package/evals/integration/test_trust_reconcile_mixed_bundle.sh +96 -0
  154. package/evals/integration/test_trust_reconcile_negatives.sh +151 -0
  155. package/evals/integration/test_usage_feedback_global.sh +5 -5
  156. package/evals/integration/test_validate_artifacts_portability.sh +60 -0
  157. package/evals/integration/test_verify_cli.sh +2 -2
  158. package/evals/integration/test_veritas_governance_kit.sh +117 -0
  159. package/evals/integration/test_workflow_artifact_cleanup_audit.sh +26 -1
  160. package/evals/integration/test_workflow_artifacts.sh +33 -0
  161. package/evals/integration/test_workflow_sidecar_writer.sh +723 -74
  162. package/evals/integration/test_workflow_steering_hook.sh +175 -5
  163. package/evals/lib/codex-provider.sh +1 -1
  164. package/evals/lib/node.sh +19 -1
  165. package/evals/run.sh +22 -0
  166. package/evals/static/test_decisions.sh +150 -0
  167. package/evals/static/test_flowdef_codeowners_coverage.sh +56 -0
  168. package/evals/static/test_package.sh +19 -10
  169. package/evals/static/test_universal_bundles.sh +48 -3
  170. package/evals/static/test_workflow_skills.sh +41 -8
  171. package/kits/builder/flows/build.flow.json +3 -74
  172. package/kits/builder/flows/publish-learn.flow.json +90 -0
  173. package/kits/builder/kit.json +11 -0
  174. package/kits/builder/skills/builder-shape/SKILL.md +1 -1
  175. package/kits/builder/skills/deliver/SKILL.md +13 -11
  176. package/kits/builder/skills/design-probe/SKILL.md +4 -4
  177. package/kits/builder/skills/evidence-gate/SKILL.md +26 -1
  178. package/kits/builder/skills/execute-plan/SKILL.md +2 -2
  179. package/kits/builder/skills/fix-bug/SKILL.md +2 -0
  180. package/kits/builder/skills/gate-review/SKILL.md +6 -6
  181. package/kits/builder/skills/idea-to-backlog/SKILL.md +1 -1
  182. package/kits/builder/skills/learning-review/SKILL.md +7 -7
  183. package/kits/builder/skills/pickup-probe/SKILL.md +5 -4
  184. package/kits/builder/skills/plan-work/SKILL.md +27 -5
  185. package/kits/builder/skills/pull-work/SKILL.md +80 -5
  186. package/kits/builder/skills/release-readiness/SKILL.md +4 -4
  187. package/kits/builder/skills/review-work/SKILL.md +24 -1
  188. package/kits/builder/skills/tdd-workflow/SKILL.md +2 -0
  189. package/kits/builder/skills/verify-work/SKILL.md +8 -1
  190. package/kits/catalog.json +6 -0
  191. package/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +178 -0
  192. package/kits/veritas-governance/docs/README.md +75 -0
  193. package/kits/veritas-governance/fixtures/readiness/not-ready.readiness-report.json +4645 -0
  194. package/kits/veritas-governance/fixtures/readiness/ready.readiness-report.json +4403 -0
  195. package/kits/veritas-governance/flows/readiness-check.flow.json +35 -0
  196. package/kits/veritas-governance/kit.json +16 -0
  197. package/package.json +8 -4
  198. package/packaging/README.md +2 -2
  199. package/packaging/conformance/fixtures/config-protection--allow-read-state-json.json +20 -0
  200. package/packaging/conformance/fixtures/config-protection--block-write-state-json.json +20 -0
  201. package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +1 -1
  202. package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +1 -1
  203. package/packaging/manifest.json +9 -10
  204. package/schemas/decision-record.schema.json +109 -0
  205. package/schemas/workflow-evidence.schema.json +1 -1
  206. package/schemas/workflow-state.schema.json +5 -0
  207. package/scripts/README.md +8 -3
  208. package/scripts/check-content-boundary.cjs +1 -1
  209. package/scripts/check-decisions.cjs +356 -0
  210. package/scripts/ci/derive-claim-status.mjs +74 -0
  211. package/scripts/ci/trust-reconcile.js +436 -79
  212. package/scripts/git-status.sh +2 -2
  213. package/scripts/hooks/claude-telemetry-hook.js +37 -3
  214. package/scripts/hooks/codex-telemetry-hook.js +36 -2
  215. package/scripts/hooks/config-protection.js +102 -30
  216. package/scripts/hooks/evidence-capture.js +3 -3
  217. package/scripts/hooks/lib/actor-identity.js +368 -0
  218. package/scripts/hooks/lib/config-protection-remedies.js +69 -0
  219. package/scripts/hooks/lib/liveness-heartbeat.js +361 -0
  220. package/scripts/hooks/lib/liveness-policy.js +127 -0
  221. package/scripts/hooks/lib/liveness-read.js +105 -12
  222. package/scripts/hooks/lib/liveness-write.js +46 -0
  223. package/scripts/hooks/lib/local-artifact-paths.js +12 -6
  224. package/scripts/hooks/opencode-telemetry-hook.js +27 -1
  225. package/scripts/hooks/pi-telemetry-hook.js +27 -1
  226. package/scripts/hooks/stop-goal-fit.js +157 -26
  227. package/scripts/hooks/workflow-steering.js +77 -3
  228. package/scripts/install-codex-home.sh +138 -38
  229. package/scripts/statusline/flow-agents-statusline.js +2 -2
  230. package/src/cli/console-learning-projection.ts +3 -2
  231. package/src/cli/init.ts +101 -21
  232. package/src/cli/kit.ts +37 -6
  233. package/src/cli/public-api.test.mjs +58 -6
  234. package/src/cli/pull-work-provider.ts +1 -1
  235. package/src/cli/trust-bundle-policy-order.test.mjs +87 -0
  236. package/src/cli/usage-feedback.ts +3 -3
  237. package/src/cli/validate-hook-influence.ts +1 -0
  238. package/src/cli/validate-workflow-artifacts.ts +22 -6
  239. package/src/cli/veritas-readiness-adapter.test.mjs +267 -0
  240. package/src/cli/workflow-artifact-cleanup-audit.ts +35 -2
  241. package/src/cli/workflow-sidecar.ts +795 -71
  242. package/src/flow-kit/validate.ts +74 -1
  243. package/src/index.ts +6 -2
  244. package/src/lib/flow-resolver.ts +123 -55
  245. package/src/lib/local-artifact-root.ts +24 -5
  246. package/src/lib/workflow-learning-projection.ts +2 -2
  247. package/src/runtime-adapters.ts +61 -15
  248. package/src/tools/build-universal-bundles.ts +49 -16
  249. package/src/tools/generate-context-map.ts +10 -8
  250. package/src/tools/validate-source-tree.ts +23 -1
@@ -1,3 +1,26 @@
1
+ export interface KitDependencyEntry {
2
+ kit_id: string;
3
+ reason?: string;
4
+ }
5
+ /**
6
+ * Parse and shape-validate a kit manifest's `dependencies` field (Flow Agents
7
+ * extension-layer metadata; see docs/adr/0019-kit-dependency-ownership.md).
8
+ *
9
+ * Shape rules:
10
+ * - `dependencies` (if present) must be an array.
11
+ * - each entry must be an object with a `kit_id` string matching ^[a-z][a-z0-9-]*$.
12
+ * - `kit_id` must not equal the declaring kit's own id (no self-reference).
13
+ * - no duplicate `kit_id` across entries.
14
+ * - `reason` is an optional string.
15
+ *
16
+ * Returns the parsed entries plus any shape errors (empty errors = valid). This is
17
+ * a sibling shape check, not a member of the file-path-oriented EXTENSION_ASSET_CLASSES
18
+ * loop, because dependency entries have no `path`/file to check.
19
+ */
20
+ export declare function parseKitDependencies(manifest: Record<string, unknown>, manifestPath: string): {
21
+ entries: KitDependencyEntry[];
22
+ errors: string[];
23
+ };
1
24
  export type KitTargetConsumer = "flow" | "flow-agents" | string;
2
25
  export interface KitConformanceLevel {
3
26
  /** K0: valid core Flow Kit container with at least one flow (gates evaluable agentlessly). */
@@ -7,6 +7,66 @@ const EXTENSION_ASSET_CLASSES = ["skills", "docs", "adapters", "evals", "assets"
7
7
  // agent-extension fields are skills, docs, adapters, evals, assets.
8
8
  const CORE_CONTAINER_FIELDS = new Set(["schema_version", "id", "name", "description", "product_name", "flows"]);
9
9
  const AGENT_EXTENSION_CLASSES = new Set(["skills", "docs", "adapters", "evals", "assets"]);
10
+ // Flow Agents-recognized metadata fields that are neither core container fields nor
11
+ // agent-extension asset classes. Recognized here so they are never misreported as
12
+ // unknown third-party extension namespaces. `dependencies` declares cross-kit skill
13
+ // dependencies (extension-layer ownership; see docs/adr/0019-kit-dependency-ownership.md).
14
+ const KNOWN_METADATA_FIELDS = new Set(["dependencies"]);
15
+ /**
16
+ * Parse and shape-validate a kit manifest's `dependencies` field (Flow Agents
17
+ * extension-layer metadata; see docs/adr/0019-kit-dependency-ownership.md).
18
+ *
19
+ * Shape rules:
20
+ * - `dependencies` (if present) must be an array.
21
+ * - each entry must be an object with a `kit_id` string matching ^[a-z][a-z0-9-]*$.
22
+ * - `kit_id` must not equal the declaring kit's own id (no self-reference).
23
+ * - no duplicate `kit_id` across entries.
24
+ * - `reason` is an optional string.
25
+ *
26
+ * Returns the parsed entries plus any shape errors (empty errors = valid). This is
27
+ * a sibling shape check, not a member of the file-path-oriented EXTENSION_ASSET_CLASSES
28
+ * loop, because dependency entries have no `path`/file to check.
29
+ */
30
+ export function parseKitDependencies(manifest, manifestPath) {
31
+ const entries = [];
32
+ const errors = [];
33
+ const raw = manifest.dependencies;
34
+ if (raw === undefined)
35
+ return { entries, errors };
36
+ if (!Array.isArray(raw)) {
37
+ errors.push(`${manifestPath}: .dependencies must be a list`);
38
+ return { entries, errors };
39
+ }
40
+ const ownId = typeof manifest.id === "string" ? manifest.id : "";
41
+ const seen = new Set();
42
+ raw.forEach((entry, index) => {
43
+ if (typeof entry !== "object" || entry === null) {
44
+ errors.push(`${manifestPath}: dependencies[${index}] must be an object`);
45
+ return;
46
+ }
47
+ const record = entry;
48
+ const kitId = record.kit_id;
49
+ if (typeof kitId !== "string" || !/^[a-z][a-z0-9-]*$/.test(kitId)) {
50
+ errors.push(`${manifestPath}: dependencies[${index}].kit_id must be a kebab-case kit id (^[a-z][a-z0-9-]*$)`);
51
+ return;
52
+ }
53
+ if (ownId && kitId === ownId) {
54
+ errors.push(`${manifestPath}: dependencies[${index}].kit_id must not reference the declaring kit itself ('${kitId}')`);
55
+ return;
56
+ }
57
+ if (seen.has(kitId)) {
58
+ errors.push(`${manifestPath}: dependencies[${index}].kit_id duplicates '${kitId}'`);
59
+ return;
60
+ }
61
+ seen.add(kitId);
62
+ if (record.reason !== undefined && typeof record.reason !== "string") {
63
+ errors.push(`${manifestPath}: dependencies[${index}].reason must be a string when present`);
64
+ }
65
+ const reason = typeof record.reason === "string" ? record.reason : undefined;
66
+ entries.push({ kit_id: kitId, ...(reason ? { reason } : {}) });
67
+ });
68
+ return { entries, errors };
69
+ }
10
70
  /**
11
71
  * Allowlist of kit IDs that Kontour authors, tests, and ships with the flow-agents package.
12
72
  *
@@ -106,7 +166,7 @@ export async function deriveKitTargets(manifest, kitDir = "") {
106
166
  // Detect third-party extension namespaces: top-level keys that are neither
107
167
  // core fields nor Flow Agents extension classes.
108
168
  const thirdPartyExtensions = Object.keys(manifest)
109
- .filter((key) => !CORE_CONTAINER_FIELDS.has(key) && !AGENT_EXTENSION_CLASSES.has(key))
169
+ .filter((key) => !CORE_CONTAINER_FIELDS.has(key) && !AGENT_EXTENSION_CLASSES.has(key) && !KNOWN_METADATA_FIELDS.has(key))
110
170
  .sort();
111
171
  const targets = [];
112
172
  if (k0)
@@ -187,6 +247,12 @@ export async function validateKitRepository(kitDir) {
187
247
  }
188
248
  });
189
249
  }
250
+ // Flow Agents metadata: cross-kit dependency declarations (extension-layer;
251
+ // see docs/adr/0019-kit-dependency-ownership.md). Shape-only here — presence is
252
+ // checked separately (non-blocking at install, hard error at activation).
253
+ const depResult = parseKitDependencies(manifest, manifestPath);
254
+ for (const err of depResult.errors)
255
+ errors.push(err);
190
256
  return errors;
191
257
  }
192
258
  export async function assertKitRepository(kitDir) {
@@ -1,4 +1,4 @@
1
- export { defaultArtifactRootForRead, defaultTelemetryDirForRead, defaultTelemetryDirsForRead, firstExistingPath, flowAgentsArtifactRoot, KONTOURAI_DIR, legacyFlowAgentsArtifactRoot, LEGACY_FLOW_AGENTS_DIR, legacyTelemetryDataDir, LEGACY_TELEMETRY_DIR, telemetryDataDir, } from "./lib/local-artifact-root.js";
1
+ export { defaultArtifactRootForRead, defaultCodexHome, defaultTelemetryDirForRead, defaultTelemetryDirsForRead, durableFlowAgentsRoot, durableInstallRecordPath, DURABLE_FLOW_AGENTS_DIR, FLOW_AGENTS_RUNTIME_DIR, FLOW_AGENTS_RUNTIME_SUBDIR, firstExistingPath, flowAgentsArtifactRoot, KONTOURAI_DIR, legacyTelemetryDataDir, LEGACY_TELEMETRY_DIR, telemetryDataDir, } from "./lib/local-artifact-root.js";
2
2
  export { validateTrustBundle, normalizeCheck, normalizeFinding, normalizeLearning, normalizeEvidenceRefs, validateEvidenceRef, validateLearningCorrection, loadJson, writeJson, appendJsonl, sidecarBase, writeState, statuses, phases, checkKinds, checkStatuses, verdicts, } from "./cli/workflow-sidecar.js";
3
3
  /** Read a sidecar JSON file from a workflow artifact directory; returns `{}` if absent. */
4
4
  export declare function readSidecar(dir: string, name: string): Record<string, any>;
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import * as path from "node:path";
17
17
  import { loadJson as _loadJson, writeJson as _writeJson } from "./cli/workflow-sidecar.js";
18
- export { defaultArtifactRootForRead, defaultTelemetryDirForRead, defaultTelemetryDirsForRead, firstExistingPath, flowAgentsArtifactRoot, KONTOURAI_DIR, legacyFlowAgentsArtifactRoot, LEGACY_FLOW_AGENTS_DIR, legacyTelemetryDataDir, LEGACY_TELEMETRY_DIR, telemetryDataDir, } from "./lib/local-artifact-root.js";
18
+ export { defaultArtifactRootForRead, defaultCodexHome, defaultTelemetryDirForRead, defaultTelemetryDirsForRead, durableFlowAgentsRoot, durableInstallRecordPath, DURABLE_FLOW_AGENTS_DIR, FLOW_AGENTS_RUNTIME_DIR, FLOW_AGENTS_RUNTIME_SUBDIR, firstExistingPath, flowAgentsArtifactRoot, KONTOURAI_DIR, legacyTelemetryDataDir, LEGACY_TELEMETRY_DIR, telemetryDataDir, } from "./lib/local-artifact-root.js";
19
19
  export {
20
20
  // Trust-bundle (Hachure) validation — the same validator the writer uses.
21
21
  validateTrustBundle,
@@ -17,11 +17,11 @@
17
17
  *
18
18
  * Returns the validated absolute file path, or null when:
19
19
  * - kitId or flowName contains chars outside SLUG_RE (rejects traversal)
20
- * - FLOW_AGENTS_FLOW_DEFS_DIR resolves into a .flow-agents directory
20
+ * - FLOW_AGENTS_FLOW_DEFS_DIR resolves into a runtime artifact directory
21
21
  * - The resolved path escapes the expected root (belt-and-suspenders)
22
22
  *
23
- * When the override is unsafe (points into .flow-agents), falls back silently
24
- * to the repoRoot/kits/ path so the resolver still works for legit flows.
23
+ * When the override is unsafe, it is ignored and the resolver uses the
24
+ * canonical repoRoot/kits/ source for legitimate flows.
25
25
  */
26
26
  export declare function resolveFlowFilePath(kitId: string, flowName: string, flowId: string, repoRoot: string): string | null;
27
27
  /** A single gate expectation from a FlowDefinition expects[] entry. */
@@ -41,6 +41,8 @@ export type ActiveFlowStep = {
41
41
  stepId: string;
42
42
  gateId: string;
43
43
  gateExpects: GateExpectation[];
44
+ /** When resolved through a parent step's uses_flow edge, names the child FlowDefinition that owns the gate. */
45
+ sourceFlowId?: string;
44
46
  };
45
47
  /**
46
48
  * Resolve the gate expects[] for a specific (flowId, stepId) pair.
@@ -76,7 +78,7 @@ export declare function resolvePhaseMap(flowId: string, repoRoot: string): Recor
76
78
  * with a fallback to process.cwd(). This handles temp dirs, CI workspaces, and
77
79
  * subproject layouts without hardcoding the repo structure.
78
80
  *
79
- * @param flowAgentsDir Path to the .flow-agents directory (contains current.json).
81
+ * @param flowAgentsDir Path to the runtime artifact root directory (contains current.json).
80
82
  * @returns ActiveFlowStep or null when fields are absent or resolution fails.
81
83
  */
82
84
  export declare function resolveActiveFlowStep(flowAgentsDir: string): ActiveFlowStep | null;
@@ -18,9 +18,9 @@ import * as path from "node:path";
18
18
  //
19
19
  // Both kitId and flowName originate from agent-writable sources (active_flow_id
20
20
  // in current.json, and FLOW_AGENTS_FLOW_DEFS_DIR set by the runtime). A crafted
21
- // value like "builder.../../../.flow-agents/slug/fake-flow" produces:
21
+ // value like "builder.../../../.kontourai/flow-agents/slug/fake-flow" produces:
22
22
  // kitId = "builder"
23
- // flowName = "../../../.flow-agents/slug/fake-flow"
23
+ // flowName = "../../../.kontourai/flow-agents/slug/fake-flow"
24
24
  // which resolves OUTSIDE kits/ via path.join traversal.
25
25
  //
26
26
  // SLUG_RE closes this: it rejects any value containing path separators, dots,
@@ -32,23 +32,36 @@ import * as path from "node:path";
32
32
  /** Strict slug pattern — allows only URL-safe identifier chars. */
33
33
  const SLUG_RE = /^[a-zA-Z0-9_-]+$/;
34
34
  /**
35
- * Returns true when the given resolved absolute path falls within a .flow-agents
36
- * directory (an agent-writable area). Used to reject FLOW_AGENTS_FLOW_DEFS_DIR
35
+ * Returns true when the given resolved absolute path falls within a Flow Agents
36
+ * runtime artifact directory (an agent-writable area). Used to reject FLOW_AGENTS_FLOW_DEFS_DIR
37
37
  * overrides that point into agent-controlled storage.
38
38
  */
39
+ function hasAgentWritableRuntimeSegment(resolvedDir) {
40
+ const parts = resolvedDir.split(path.sep);
41
+ if (parts.includes(".flow-agents"))
42
+ return true;
43
+ return parts.some((part, index) => part === ".kontourai" && parts[index + 1] === "flow-agents");
44
+ }
39
45
  function isAgentWritableDir(resolvedDir) {
40
- return resolvedDir.split(path.sep).includes(".flow-agents");
46
+ if (hasAgentWritableRuntimeSegment(resolvedDir))
47
+ return true;
48
+ try {
49
+ return hasAgentWritableRuntimeSegment(fs.realpathSync.native(resolvedDir));
50
+ }
51
+ catch {
52
+ return false;
53
+ }
41
54
  }
42
55
  /**
43
56
  * Build and validate the FlowDefinition file path.
44
57
  *
45
58
  * Returns the validated absolute file path, or null when:
46
59
  * - kitId or flowName contains chars outside SLUG_RE (rejects traversal)
47
- * - FLOW_AGENTS_FLOW_DEFS_DIR resolves into a .flow-agents directory
60
+ * - FLOW_AGENTS_FLOW_DEFS_DIR resolves into a runtime artifact directory
48
61
  * - The resolved path escapes the expected root (belt-and-suspenders)
49
62
  *
50
- * When the override is unsafe (points into .flow-agents), falls back silently
51
- * to the repoRoot/kits/ path so the resolver still works for legit flows.
63
+ * When the override is unsafe, it is ignored and the resolver uses the
64
+ * canonical repoRoot/kits/ source for legitimate flows.
52
65
  */
53
66
  export function resolveFlowFilePath(kitId, flowName, flowId, repoRoot) {
54
67
  // Primary defense: reject any slug containing traversal chars or non-identifier chars.
@@ -60,8 +73,8 @@ export function resolveFlowFilePath(kitId, flowName, flowId, repoRoot) {
60
73
  if (override) {
61
74
  const resolvedOverride = path.resolve(override);
62
75
  if (isAgentWritableDir(resolvedOverride)) {
63
- // Override targets an agent-writable .flow-agents path reject it and
64
- // fall back to the kit root. The session will resolve the real kit flow.
76
+ // Override targets an agent-writable runtime path; ignore it and use
77
+ // the canonical kit root. The session will resolve the real kit flow.
65
78
  expectedRoot = path.resolve(repoRoot, "kits");
66
79
  flowFilePath = path.join(repoRoot, "kits", kitId, "flows", `${flowName}.flow.json`);
67
80
  }
@@ -83,7 +96,47 @@ export function resolveFlowFilePath(kitId, flowName, flowId, repoRoot) {
83
96
  if (!resolvedPath.startsWith(expectedRoot + path.sep) && resolvedPath !== expectedRoot) {
84
97
  return null; // traversal still detected — paranoid fallback
85
98
  }
86
- return resolvedPath;
99
+ // If the file exists, resolve final symlinks before returning a readable path.
100
+ // `readFileSync` follows symlinks; this keeps lexical containment from turning
101
+ // into an out-of-root file read through a symlinked FlowDefinition.
102
+ try {
103
+ const realExpectedRoot = fs.existsSync(expectedRoot) ? fs.realpathSync.native(expectedRoot) : expectedRoot;
104
+ const realPath = fs.realpathSync.native(resolvedPath);
105
+ if (!realPath.startsWith(realExpectedRoot + path.sep) && realPath !== realExpectedRoot) {
106
+ return null;
107
+ }
108
+ return realPath;
109
+ }
110
+ catch {
111
+ return resolvedPath;
112
+ }
113
+ }
114
+ function flowIdParts(flowId) {
115
+ if (!flowId)
116
+ return null;
117
+ const dotIdx = flowId.indexOf(".");
118
+ if (dotIdx < 1)
119
+ return null;
120
+ const kitId = flowId.slice(0, dotIdx);
121
+ const flowName = flowId.slice(dotIdx + 1);
122
+ if (!kitId || !flowName)
123
+ return null;
124
+ return { kitId, flowName };
125
+ }
126
+ function readFlowDefinition(flowId, repoRoot) {
127
+ const parts = flowIdParts(flowId);
128
+ if (!parts)
129
+ return null;
130
+ const flowFilePath = resolveFlowFilePath(parts.kitId, parts.flowName, flowId, repoRoot);
131
+ if (!flowFilePath)
132
+ return null;
133
+ try {
134
+ const raw = fs.readFileSync(flowFilePath, "utf8");
135
+ return JSON.parse(raw);
136
+ }
137
+ catch {
138
+ return null; // ENOENT, permission error, or parse error → fail-open
139
+ }
87
140
  }
88
141
  /**
89
142
  * Resolve the gate expects[] for a specific (flowId, stepId) pair.
@@ -95,41 +148,73 @@ export function resolveFlowFilePath(kitId, flowName, flowId, repoRoot) {
95
148
  * @returns ActiveFlowStep with the matched gate's expects[], or null on any error.
96
149
  */
97
150
  export function resolveFlowStep(flowId, stepId, repoRoot) {
151
+ const resolved = resolveFlowStepInternal(flowId, stepId, repoRoot, new Set());
152
+ if (!resolved)
153
+ return null;
154
+ const { flowExports: _flowExports, ...publicStep } = resolved;
155
+ return publicStep;
156
+ }
157
+ function expectationExportKeys(expectation) {
158
+ const keys = [];
159
+ if (typeof expectation.id === "string" && expectation.id)
160
+ keys.push(expectation.id);
161
+ const claimType = expectation.bundle_claim?.claimType;
162
+ if (typeof claimType === "string" && claimType)
163
+ keys.push(claimType);
164
+ return keys;
165
+ }
166
+ function exportedExpectations(expectations, exportsList) {
167
+ if (!Array.isArray(exportsList))
168
+ return null;
169
+ const exported = new Set(exportsList.filter((item) => typeof item === "string" && item.length > 0));
170
+ const allowed = expectations.filter((expectation) => expectationExportKeys(expectation).some((key) => exported.has(key)));
171
+ return allowed.length === expectations.length ? allowed : null;
172
+ }
173
+ function resolveFlowStepInternal(flowId, stepId, repoRoot, seen) {
98
174
  if (!flowId || !stepId)
99
175
  return null;
100
- const dotIdx = flowId.indexOf(".");
101
- if (dotIdx < 1)
102
- return null; // flowId must have at least one "." to derive kitId
103
- const kitId = flowId.slice(0, dotIdx);
104
- // The flow filename is the part after the first "." (e.g. "build" from "builder.build")
105
- const flowName = flowId.slice(dotIdx + 1);
106
- if (!kitId || !flowName)
176
+ if (!flowIdParts(flowId))
107
177
  return null;
108
178
  // Layer 1 defense: validate stepId too — it is matched against gate.step values but
109
179
  // still originates from agent-writable current.json active_step_id.
110
180
  if (!SLUG_RE.test(stepId))
111
181
  return null;
112
- // Determine the FlowDefinition file path with slug validation + path containment check.
113
- // Returns null for traversal attempts (e.g. flowName = "../../../.flow-agents/fake").
114
- const flowFilePath = resolveFlowFilePath(kitId, flowName, flowId, repoRoot);
115
- if (!flowFilePath)
182
+ const seenKey = `${flowId}:${stepId}`;
183
+ if (seen.has(seenKey))
116
184
  return null;
117
- let flowDef;
118
- try {
119
- const raw = fs.readFileSync(flowFilePath, "utf8");
120
- flowDef = JSON.parse(raw);
121
- }
122
- catch {
123
- return null; // ENOENT, permission error, or parse error → fail-open
124
- }
125
- if (!flowDef || typeof flowDef !== "object" || !flowDef.gates)
185
+ seen.add(seenKey);
186
+ const flowDef = readFlowDefinition(flowId, repoRoot);
187
+ if (!flowDef)
188
+ return null;
189
+ if (!flowDef || typeof flowDef !== "object")
126
190
  return null;
127
191
  // Find the gate whose .step matches stepId.
128
- for (const [gateId, gate] of Object.entries(flowDef.gates)) {
129
- if (!gate || gate.step !== stepId)
130
- continue;
131
- const expects = Array.isArray(gate.expects) ? gate.expects : [];
132
- return { flowId, stepId, gateId, gateExpects: expects };
192
+ if (flowDef.gates) {
193
+ for (const [gateId, gate] of Object.entries(flowDef.gates)) {
194
+ if (!gate || gate.step !== stepId)
195
+ continue;
196
+ const expects = Array.isArray(gate.expects) ? gate.expects : [];
197
+ return { flowId, stepId, gateId, gateExpects: expects, flowExports: flowDef.exports };
198
+ }
199
+ }
200
+ const composedStep = Array.isArray(flowDef.steps)
201
+ ? flowDef.steps.find((step) => step && step.id === stepId && typeof step.uses_flow === "string" && step.uses_flow.trim())
202
+ : null;
203
+ if (composedStep?.uses_flow) {
204
+ const child = resolveFlowStepInternal(composedStep.uses_flow, stepId, repoRoot, seen);
205
+ if (child) {
206
+ const childGateExpects = exportedExpectations(child.gateExpects, child.flowExports);
207
+ if (!childGateExpects)
208
+ return null;
209
+ return {
210
+ flowId,
211
+ stepId,
212
+ gateId: `${child.flowId}:${child.gateId}`,
213
+ gateExpects: childGateExpects,
214
+ sourceFlowId: child.flowId,
215
+ flowExports: flowDef.exports,
216
+ };
217
+ }
133
218
  }
134
219
  return null; // no gate matched the given stepId
135
220
  }
@@ -148,27 +233,9 @@ export function resolveFlowStep(flowId, stepId, repoRoot) {
148
233
  * @returns Record<string,string> phase→stepId map, or null on absence/error.
149
234
  */
150
235
  export function resolvePhaseMap(flowId, repoRoot) {
151
- if (!flowId)
152
- return null;
153
- const dotIdx = flowId.indexOf(".");
154
- if (dotIdx < 1)
155
- return null;
156
- const kitId = flowId.slice(0, dotIdx);
157
- const flowName = flowId.slice(dotIdx + 1);
158
- if (!kitId || !flowName)
236
+ const flowDef = readFlowDefinition(flowId, repoRoot);
237
+ if (!flowDef)
159
238
  return null;
160
- // Layer 1 defense: same slug validation + path containment as resolveFlowStep.
161
- const flowFilePath = resolveFlowFilePath(kitId, flowName, flowId, repoRoot);
162
- if (!flowFilePath)
163
- return null;
164
- let flowDef;
165
- try {
166
- const raw = fs.readFileSync(flowFilePath, "utf8");
167
- flowDef = JSON.parse(raw);
168
- }
169
- catch {
170
- return null; // ENOENT, permission error, or parse error → fail-open
171
- }
172
239
  if (!flowDef || typeof flowDef !== "object")
173
240
  return null;
174
241
  const pm = flowDef.phase_map;
@@ -186,7 +253,7 @@ export function resolvePhaseMap(flowId, repoRoot) {
186
253
  * the nearest ancestor that contains a `kits/` subdirectory. If none is found,
187
254
  * falls back to `process.cwd()` so the default "run from repo root" case still works.
188
255
  *
189
- * This is required because the .flow-agents directory can live anywhere (temp dirs,
256
+ * This is required because the runtime artifact directory can live anywhere (temp dirs,
190
257
  * subprojects, CI workspaces) while the kits/ directory is always at the repo root.
191
258
  */
192
259
  function findRepoRoot(startDir) {
@@ -212,7 +279,7 @@ function findRepoRoot(startDir) {
212
279
  * with a fallback to process.cwd(). This handles temp dirs, CI workspaces, and
213
280
  * subproject layouts without hardcoding the repo structure.
214
281
  *
215
- * @param flowAgentsDir Path to the .flow-agents directory (contains current.json).
282
+ * @param flowAgentsDir Path to the runtime artifact root directory (contains current.json).
216
283
  * @returns ActiveFlowStep or null when fields are absent or resolution fails.
217
284
  */
218
285
  export function resolveActiveFlowStep(flowAgentsDir) {
@@ -1,8 +1,20 @@
1
1
  export declare const KONTOURAI_DIR = ".kontourai";
2
- export declare const LEGACY_FLOW_AGENTS_DIR = ".flow-agents";
2
+ export declare const FLOW_AGENTS_RUNTIME_SUBDIR = "flow-agents";
3
+ export declare const FLOW_AGENTS_RUNTIME_DIR = ".kontourai/flow-agents";
4
+ export declare const DURABLE_FLOW_AGENTS_DIR = ".flow-agents";
3
5
  export declare const LEGACY_TELEMETRY_DIR = ".telemetry";
6
+ /**
7
+ * Default Codex home: `$CODEX_HOME` when set, else `~/.codex`.
8
+ *
9
+ * This is the Codex CLI's own config/state root and is conceptually
10
+ * distinct from the Flow Agents global bundle install root (`~/.flow-agents`)
11
+ * and the durable per-destination install record root
12
+ * (`DURABLE_FLOW_AGENTS_DIR`, i.e. `.flow-agents`).
13
+ */
14
+ export declare function defaultCodexHome(): string;
4
15
  export declare function flowAgentsArtifactRoot(cwd?: string): string;
5
- export declare function legacyFlowAgentsArtifactRoot(cwd?: string): string;
16
+ export declare function durableFlowAgentsRoot(cwd?: string): string;
17
+ export declare function durableInstallRecordPath(cwd?: string): string;
6
18
  export declare function telemetryDataDir(cwd?: string): string;
7
19
  export declare function legacyTelemetryDataDir(cwd?: string): string;
8
20
  export declare function firstExistingPath(candidates: string[]): string;
@@ -1,13 +1,30 @@
1
1
  import * as fs from "node:fs";
2
+ import * as os from "node:os";
2
3
  import * as path from "node:path";
3
4
  export const KONTOURAI_DIR = ".kontourai";
4
- export const LEGACY_FLOW_AGENTS_DIR = ".flow-agents";
5
+ export const FLOW_AGENTS_RUNTIME_SUBDIR = "flow-agents";
6
+ export const FLOW_AGENTS_RUNTIME_DIR = `${KONTOURAI_DIR}/${FLOW_AGENTS_RUNTIME_SUBDIR}`;
7
+ export const DURABLE_FLOW_AGENTS_DIR = ".flow-agents";
5
8
  export const LEGACY_TELEMETRY_DIR = ".telemetry";
9
+ /**
10
+ * Default Codex home: `$CODEX_HOME` when set, else `~/.codex`.
11
+ *
12
+ * This is the Codex CLI's own config/state root and is conceptually
13
+ * distinct from the Flow Agents global bundle install root (`~/.flow-agents`)
14
+ * and the durable per-destination install record root
15
+ * (`DURABLE_FLOW_AGENTS_DIR`, i.e. `.flow-agents`).
16
+ */
17
+ export function defaultCodexHome() {
18
+ return process.env["CODEX_HOME"] || path.join(os.homedir(), ".codex");
19
+ }
6
20
  export function flowAgentsArtifactRoot(cwd = process.cwd()) {
7
- return path.resolve(cwd, KONTOURAI_DIR, "flow-agents");
21
+ return path.resolve(cwd, FLOW_AGENTS_RUNTIME_DIR);
22
+ }
23
+ export function durableFlowAgentsRoot(cwd = process.cwd()) {
24
+ return path.resolve(cwd, DURABLE_FLOW_AGENTS_DIR);
8
25
  }
9
- export function legacyFlowAgentsArtifactRoot(cwd = process.cwd()) {
10
- return path.resolve(cwd, LEGACY_FLOW_AGENTS_DIR);
26
+ export function durableInstallRecordPath(cwd = process.cwd()) {
27
+ return path.join(durableFlowAgentsRoot(cwd), "install.json");
11
28
  }
12
29
  export function telemetryDataDir(cwd = process.cwd()) {
13
30
  return path.resolve(cwd, KONTOURAI_DIR, "telemetry");
@@ -19,7 +36,7 @@ export function firstExistingPath(candidates) {
19
36
  return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0];
20
37
  }
21
38
  export function defaultArtifactRootForRead(cwd = process.cwd()) {
22
- return firstExistingPath([flowAgentsArtifactRoot(cwd), legacyFlowAgentsArtifactRoot(cwd)]);
39
+ return flowAgentsArtifactRoot(cwd);
23
40
  }
24
41
  export function defaultTelemetryDirForRead(cwd = process.cwd()) {
25
42
  return firstExistingPath([telemetryDataDir(cwd), legacyTelemetryDataDir(cwd)]);
@@ -26,7 +26,7 @@ const KNOWN_CORRECTION_TYPES = new Set([
26
26
  "provider",
27
27
  "none",
28
28
  ]);
29
- const SKIPPED_ROOT_ENTRIES = new Set(["archive", "changes", "delivery-history"]);
29
+ const SKIPPED_ROOT_ENTRIES = new Set(["archive", "changes", "delivery-history", "liveness"]);
30
30
  const MAX_SIDECAR_BYTES = 1024 * 1024;
31
31
  export function readWorkflowLearningSources(artifactRoot) {
32
32
  const root = path.resolve(artifactRoot);
@@ -75,7 +75,7 @@ export function buildWorkflowLearningProjection(sources, options) {
75
75
  sourceRef: {
76
76
  product: "flow-agents",
77
77
  kind: "workflow-learning",
78
- id: ".flow-agents/*/learning.json",
78
+ id: ".kontourai/flow-agents/*/learning.json",
79
79
  label: "Local workflow learning sidecars",
80
80
  },
81
81
  },
@@ -13,6 +13,18 @@ export type KitInventory = {
13
13
  warnings: string[];
14
14
  errors: string[];
15
15
  };
16
+ /** A cross-kit dependency edge discovered while loading a kit manifest. */
17
+ export type KitDependencyRef = {
18
+ from_kit_id: string;
19
+ kit_id: string;
20
+ reason?: string;
21
+ };
22
+ /** The parsed result of a single kit manifest: its id, activatable assets, and declared dependencies. */
23
+ export type LoadedKit = {
24
+ kit_id: string;
25
+ assets: KitAsset[];
26
+ dependencies: KitDependencyRef[];
27
+ };
16
28
  export declare function readKitInventory(sourceRoot: string, dest: string): KitInventory;
17
29
  export declare function activateCodexLocal(sourceRoot: string, dest: string): Record<string, unknown>;
18
30
  export declare function activateStrandsLocal(sourceRoot: string, dest: string): Record<string, unknown>;