@kontourai/flow-agents 3.6.0 → 3.8.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 (96) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/build/src/builder-flow-run-adapter.d.ts +22 -2
  3. package/build/src/builder-flow-run-adapter.js +93 -28
  4. package/build/src/builder-flow-runtime.d.ts +8 -3
  5. package/build/src/builder-flow-runtime.js +407 -51
  6. package/build/src/cli/assignment-provider.d.ts +4 -7
  7. package/build/src/cli/assignment-provider.js +80 -14
  8. package/build/src/cli/builder-run.js +14 -18
  9. package/build/src/cli/workflow-sidecar.d.ts +28 -4
  10. package/build/src/cli/workflow-sidecar.js +825 -103
  11. package/build/src/cli/workflow.js +301 -43
  12. package/build/src/flow-kit/validate.d.ts +17 -0
  13. package/build/src/flow-kit/validate.js +340 -2
  14. package/build/src/index.js +5 -5
  15. package/build/src/lib/observed-command.d.ts +7 -0
  16. package/build/src/lib/observed-command.js +100 -0
  17. package/build/src/tools/build-universal-bundles.js +53 -3
  18. package/build/src/tools/generate-context-map.js +5 -3
  19. package/context/scripts/hooks/lib/kit-catalog.js +1 -1
  20. package/context/scripts/hooks/stop-goal-fit.js +78 -9
  21. package/docs/context-map.md +22 -20
  22. package/docs/developer-architecture.md +1 -1
  23. package/docs/getting-started.md +9 -1
  24. package/docs/public-workflow-cli.md +76 -7
  25. package/docs/skills-map.md +51 -27
  26. package/docs/spec/builder-flow-runtime.md +75 -40
  27. package/docs/workflow-usage-guide.md +109 -42
  28. package/evals/fixtures/hook-influence/cases.json +2 -2
  29. package/evals/integration/test_builder_entry_enforcement.sh +52 -41
  30. package/evals/integration/test_builder_step_producers.sh +330 -6
  31. package/evals/integration/test_bundle_install.sh +318 -65
  32. package/evals/integration/test_bundle_lifecycle.sh +4 -6
  33. package/evals/integration/test_critique_supersession_roundtrip.sh +21 -8
  34. package/evals/integration/test_current_json_per_actor.sh +12 -0
  35. package/evals/integration/test_dual_emit_flow_step.sh +62 -43
  36. package/evals/integration/test_flowdef_session_activation.sh +145 -25
  37. package/evals/integration/test_flowdef_session_history_preservation.sh +23 -21
  38. package/evals/integration/test_gate_lockdown.sh +3 -5
  39. package/evals/integration/test_goal_fit_hook.sh +60 -2
  40. package/evals/integration/test_install_merge.sh +18 -8
  41. package/evals/integration/test_liveness_verdict.sh +14 -17
  42. package/evals/integration/test_phase_map_and_gate_claim.sh +63 -38
  43. package/evals/integration/test_public_workflow_cli.sh +334 -14
  44. package/evals/integration/test_pull_work_liveness_preflight.sh +22 -66
  45. package/evals/integration/test_sidecar_field_preservation.sh +36 -11
  46. package/evals/integration/test_workflow_sidecar_writer.sh +277 -44
  47. package/evals/integration/test_workflow_steering_hook.sh +15 -38
  48. package/evals/run.sh +2 -0
  49. package/evals/static/test_builder_skill_coherence.sh +247 -0
  50. package/evals/static/test_library_exports.sh +5 -2
  51. package/evals/static/test_universal_bundles.sh +44 -1
  52. package/evals/static/test_workflow_skills.sh +13 -325
  53. package/kits/builder/flows/build.flow.json +22 -0
  54. package/kits/builder/flows/shape.flow.json +9 -9
  55. package/kits/builder/kit.json +70 -16
  56. package/kits/builder/skills/builder-shape/SKILL.md +75 -75
  57. package/kits/builder/skills/continue-work/SKILL.md +45 -106
  58. package/kits/builder/skills/deliver/SKILL.md +96 -442
  59. package/kits/builder/skills/design-probe/SKILL.md +40 -139
  60. package/kits/builder/skills/evidence-gate/SKILL.md +59 -201
  61. package/kits/builder/skills/execute-plan/SKILL.md +54 -125
  62. package/kits/builder/skills/fix-bug/SKILL.md +42 -132
  63. package/kits/builder/skills/gate-review/SKILL.md +60 -211
  64. package/kits/builder/skills/idea-to-backlog/SKILL.md +63 -244
  65. package/kits/builder/skills/learning-review/SKILL.md +63 -170
  66. package/kits/builder/skills/pickup-probe/SKILL.md +54 -111
  67. package/kits/builder/skills/plan-work/SKILL.md +51 -185
  68. package/kits/builder/skills/pull-work/SKILL.md +136 -485
  69. package/kits/builder/skills/release-readiness/SKILL.md +66 -107
  70. package/kits/builder/skills/review-work/SKILL.md +89 -176
  71. package/kits/builder/skills/tdd-workflow/SKILL.md +53 -147
  72. package/kits/builder/skills/verify-work/SKILL.md +101 -113
  73. package/package.json +2 -2
  74. package/packaging/README.md +1 -1
  75. package/scripts/README.md +1 -1
  76. package/scripts/hooks/lib/kit-catalog.js +1 -1
  77. package/scripts/hooks/stop-goal-fit.js +78 -9
  78. package/scripts/install-codex-home.sh +63 -23
  79. package/scripts/install-owned-files.js +18 -2
  80. package/src/builder-flow-run-adapter.ts +118 -32
  81. package/src/builder-flow-runtime.ts +426 -64
  82. package/src/cli/assignment-provider-lock.test.mjs +83 -0
  83. package/src/cli/assignment-provider.ts +75 -14
  84. package/src/cli/builder-flow-run-adapter.test.mjs +4 -2
  85. package/src/cli/builder-flow-runtime.test.mjs +436 -18
  86. package/src/cli/builder-run.ts +3 -9
  87. package/src/cli/kit-metadata-security.test.mjs +232 -6
  88. package/src/cli/public-api.test.mjs +15 -0
  89. package/src/cli/workflow-sidecar-execution-proof.test.mjs +90 -0
  90. package/src/cli/workflow-sidecar.ts +746 -103
  91. package/src/cli/workflow.ts +288 -41
  92. package/src/flow-kit/validate.ts +320 -2
  93. package/src/index.ts +6 -5
  94. package/src/lib/observed-command.ts +96 -0
  95. package/src/tools/build-universal-bundles.ts +51 -3
  96. package/src/tools/generate-context-map.ts +5 -3
@@ -285,7 +285,7 @@ else
285
285
  _fail "pi install failed"
286
286
  fi
287
287
 
288
- USER_SKILLS_DIR="$CODEX_FULL_DEST/.codex/sk""ills/user-skill"
288
+ USER_SKILLS_DIR="$CODEX_FULL_DEST/.agents/sk""ills/user-skill"
289
289
  mkdir -p "$CODEX_FULL_DEST/.codex/ag""ents" "$USER_SKILLS_DIR"
290
290
  printf 'name = "user-agent"\n' > "$CODEX_FULL_DEST/.codex/ag""ents/user-agent.toml"
291
291
  printf '# user skill\n' > "$USER_SKILLS_DIR/SKILL.md"
@@ -309,7 +309,7 @@ for dir in \
309
309
  "$CLAUDE_DEST/.flow-agents" \
310
310
  "$CLAUDE_DEST/.kontourai/flow-agents" \
311
311
  "$CODEX_DEST/.codex/agents" \
312
- "$CODEX_DEST/.codex/skills" \
312
+ "$CODEX_DEST/.agents/skills" \
313
313
  "$CODEX_DEST/.flow-agents" \
314
314
  "$CODEX_DEST/.kontourai/flow-agents" \
315
315
  "$CODEX_FULL_DEST/.flow-agents" \
@@ -653,26 +653,12 @@ const state = {
653
653
  target_phase: "goal_fit",
654
654
  },
655
655
  };
656
- const critique = {
657
- schema_version: "1.0",
658
- task_slug: "installed-hook-demo",
659
- status: "fail",
660
- required: true,
661
- updated_at: "2026-05-09T00:01:00Z",
662
- critiques: [{
663
- id: "installed-hook-review",
664
- reviewer: "tool-code-reviewer",
665
- reviewed_at: "2026-05-09T00:01:00Z",
666
- verdict: "fail",
667
- summary: "Blocking installed hook verification remains.",
668
- findings: [{ id: "missing-installed-exec", severity: "high", status: "open", description: "Execute the installed hook command." }],
669
- }],
670
- };
656
+ const trustBundle = { schema_version: "1.0", claims: [] };
671
657
  function writeFixture(root) {
672
658
  const taskDir = path.join(root, ".kontourai/flow-agents/installed-hook-demo");
673
659
  fs.mkdirSync(taskDir, { recursive: true });
674
660
  fs.writeFileSync(path.join(taskDir, "state.json"), JSON.stringify(state), "utf8");
675
- fs.writeFileSync(path.join(taskDir, "critique.json"), JSON.stringify(critique), "utf8");
661
+ fs.writeFileSync(path.join(taskDir, "trust.bundle"), JSON.stringify(trustBundle), "utf8");
676
662
  fs.mkdirSync(path.join(root, "docs"), { recursive: true });
677
663
  fs.writeFileSync(path.join(root, "docs/context-map.md"), "# Context Map\n", "utf8");
678
664
  }
@@ -704,7 +690,7 @@ function runCommand(label, command, cwd, runtimeJson) {
704
690
  const ctx = runtimeJson ? (JSON.parse(result.stdout).hookSpecificOutput?.additionalContext || "") : result.stdout;
705
691
  if (!ctx.includes("WORKFLOW STATE ATTENTION")) throw new Error(`${label} installed hook did not emit workflow attention: ${result.stdout} ${result.stderr}`);
706
692
  if (!ctx.includes("STATE: installed-hook-demo is status:not_verified phase:verification")) throw new Error(`${label} installed hook missed state guidance: ${ctx}`);
707
- if (!ctx.includes("CRITIQUE: required critique is status:fail")) throw new Error(`${label} installed hook missed critique guidance: ${ctx}`);
693
+ if (ctx.includes("CRITIQUE:")) throw new Error(`${label} installed hook read retired critique sidecar guidance: ${ctx}`);
708
694
  }
709
695
  writeFixture(claudeDest);
710
696
  writeFixture(codexDest);
@@ -736,19 +722,12 @@ const state = {
736
722
  updated_at: "2026-06-01T00:00:00Z",
737
723
  next_action: { status: "needs_user", summary: "Opencode hook test.", target_phase: "goal_fit" },
738
724
  };
739
- const critique = {
740
- schema_version: "1.0",
741
- task_slug: "opencode-hook-demo",
742
- status: "fail",
743
- required: true,
744
- updated_at: "2026-06-01T00:01:00Z",
745
- critiques: [{ id: "oc-review", reviewer: "tool-code-reviewer", reviewed_at: "2026-06-01T00:01:00Z", verdict: "fail", summary: "Blocking.", findings: [{ id: "oc-open", severity: "high", status: "open", description: "Open finding." }] }],
746
- };
725
+ const trustBundle = { schema_version: "1.0", claims: [] };
747
726
  function writeFixture(root) {
748
727
  const taskDir = path.join(root, ".kontourai/flow-agents/opencode-hook-demo");
749
728
  fs.mkdirSync(taskDir, { recursive: true });
750
729
  fs.writeFileSync(path.join(taskDir, "state.json"), JSON.stringify(state), "utf8");
751
- fs.writeFileSync(path.join(taskDir, "critique.json"), JSON.stringify(critique), "utf8");
730
+ fs.writeFileSync(path.join(taskDir, "trust.bundle"), JSON.stringify(trustBundle), "utf8");
752
731
  fs.mkdirSync(path.join(root, "docs"), { recursive: true });
753
732
  fs.writeFileSync(path.join(root, "docs/context-map.md"), "# Context Map\n", "utf8");
754
733
  }
@@ -767,7 +746,7 @@ function runOpencodeAdapter(bundleDest, cwd) {
767
746
  const ctx = out.context || "";
768
747
  if (!ctx.includes("WORKFLOW STATE ATTENTION")) throw new Error("opencode adapter did not emit workflow attention: stdout=" + result.stdout + " stderr=" + result.stderr);
769
748
  if (!ctx.includes("STATE: opencode-hook-demo is status:not_verified phase:verification")) throw new Error("opencode adapter missed state guidance: " + ctx);
770
- if (!ctx.includes("CRITIQUE: required critique is status:fail")) throw new Error("opencode adapter missed critique guidance: " + ctx);
749
+ if (ctx.includes("CRITIQUE:")) throw new Error("opencode adapter read retired critique sidecar guidance: " + ctx);
771
750
  }
772
751
  writeFixture(opencodeWorkspace);
773
752
  runOpencodeAdapter(opencodeDest, opencodeWorkspace);
@@ -795,19 +774,12 @@ const state = {
795
774
  updated_at: "2026-06-01T00:00:00Z",
796
775
  next_action: { status: "needs_user", summary: "Pi hook test.", target_phase: "goal_fit" },
797
776
  };
798
- const critique = {
799
- schema_version: "1.0",
800
- task_slug: "pi-hook-demo",
801
- status: "fail",
802
- required: true,
803
- updated_at: "2026-06-01T00:01:00Z",
804
- critiques: [{ id: "pi-review", reviewer: "tool-code-reviewer", reviewed_at: "2026-06-01T00:01:00Z", verdict: "fail", summary: "Blocking.", findings: [{ id: "pi-open", severity: "high", status: "open", description: "Open finding." }] }],
805
- };
777
+ const trustBundle = { schema_version: "1.0", claims: [] };
806
778
  function writeFixture(root) {
807
779
  const taskDir = path.join(root, ".kontourai/flow-agents/pi-hook-demo");
808
780
  fs.mkdirSync(taskDir, { recursive: true });
809
781
  fs.writeFileSync(path.join(taskDir, "state.json"), JSON.stringify(state), "utf8");
810
- fs.writeFileSync(path.join(taskDir, "critique.json"), JSON.stringify(critique), "utf8");
782
+ fs.writeFileSync(path.join(taskDir, "trust.bundle"), JSON.stringify(trustBundle), "utf8");
811
783
  fs.mkdirSync(path.join(root, "docs"), { recursive: true });
812
784
  fs.writeFileSync(path.join(root, "docs/context-map.md"), "# Context Map\n", "utf8");
813
785
  }
@@ -826,7 +798,7 @@ function runPiAdapter(bundleDest, cwd) {
826
798
  const ctx = out.context || "";
827
799
  if (!ctx.includes("WORKFLOW STATE ATTENTION")) throw new Error("pi adapter did not emit workflow attention: stdout=" + result.stdout + " stderr=" + result.stderr);
828
800
  if (!ctx.includes("STATE: pi-hook-demo is status:not_verified phase:verification")) throw new Error("pi adapter missed state guidance: " + ctx);
829
- if (!ctx.includes("CRITIQUE: required critique is status:fail")) throw new Error("pi adapter missed critique guidance: " + ctx);
801
+ if (ctx.includes("CRITIQUE:")) throw new Error("pi adapter read retired critique sidecar guidance: " + ctx);
830
802
  }
831
803
  writeFixture(piWorkspace);
832
804
  runPiAdapter(piDest, piWorkspace);
@@ -886,7 +858,9 @@ echo "--- Packed Package Builder Entry ---"
886
858
  PACKAGE_CONSUMER="$TMPDIR_EVAL/package-consumer"
887
859
  PACKAGE_PROJECT="$TMPDIR_EVAL/package-project"
888
860
  PACKAGE_AMBIENT="$TMPDIR_EVAL/package-ambient"
889
- mkdir -p "$PACKAGE_CONSUMER" "$PACKAGE_PROJECT/.kontourai/flow-agents" "$PACKAGE_AMBIENT/kits/builder/flows"
861
+ mkdir -p "$PACKAGE_CONSUMER" "$PACKAGE_PROJECT/.kontourai/flow-agents" "$PACKAGE_PROJECT/checks" "$PACKAGE_AMBIENT/kits/builder/flows"
862
+ printf '#!/usr/bin/env bash\nset -eu\ntest -f "$1"\nprintf "1..1\\nok 1 - session exists\\n"\n' > "$PACKAGE_PROJECT/checks/check-packed-workflow.sh"
863
+ chmod +x "$PACKAGE_PROJECT/checks/check-packed-workflow.sh"
890
864
  cat >"$PACKAGE_AMBIENT/kits/builder/flows/build.flow.json" <<'JSON'
891
865
  {
892
866
  "id": "builder.build",
@@ -897,35 +871,152 @@ cat >"$PACKAGE_AMBIENT/kits/builder/flows/build.flow.json" <<'JSON'
897
871
  JSON
898
872
  PACKAGE_PACK_LOG="$TMPDIR_EVAL/package-pack.log"
899
873
  PACKAGE_CLI="$PACKAGE_CONSUMER/node_modules/@kontourai/flow-agents/build/src/cli.js"
900
- PACKAGE_SESSION="$PACKAGE_PROJECT/.kontourai/flow-agents/packed-builder-entry"
874
+ PACKAGE_SESSION="$PACKAGE_PROJECT/.kontourai/flow-agents/acme-builder-901"
875
+ PACKAGE_LIFECYCLE_SESSION="$PACKAGE_PROJECT/.kontourai/flow-agents/acme-builder-902"
876
+ PACKAGE_TEST_COMMAND="bash checks/check-packed-workflow.sh .kontourai/flow-agents/acme-builder-901/state.json"
877
+ PACKAGE_CRITERION_JSON="$(node - "$PACKAGE_TEST_COMMAND" <<'NODE'
878
+ const command = process.argv[2];
879
+ process.stdout.write(JSON.stringify({
880
+ id: 'AC-1',
881
+ status: 'pass',
882
+ evidence_refs: [{ kind: 'command', excerpt: command, summary: 'The packed public workflow fixture executed this exact assertion for AC-1.' }],
883
+ }));
884
+ NODE
885
+ )"
886
+ PACKAGE_COMMAND_REF="$(node - "$PACKAGE_TEST_COMMAND" <<'NODE'
887
+ const command = process.argv[2];
888
+ process.stdout.write(JSON.stringify({ kind: 'command', excerpt: command, summary: 'Runs the packed consumer workflow assertion.' }));
889
+ NODE
890
+ )"
891
+
892
+ package_flow() {
893
+ (cd "$PACKAGE_PROJECT" && CODEX_SESSION_ID=packed-package-consumer node "$PACKAGE_CLI" workflow "$@")
894
+ }
895
+
896
+ package_review() {
897
+ (cd "$PACKAGE_PROJECT" && CODEX_SESSION_ID=packed-package-reviewer node "$PACKAGE_CLI" workflow "$@")
898
+ }
899
+
901
900
  if (cd "$ROOT_DIR" && npm pack --silent --pack-destination "$TMPDIR_EVAL" >"$PACKAGE_PACK_LOG") \
902
901
  && PACKAGE_TARBALL="$(find "$TMPDIR_EVAL" -maxdepth 1 -type f -name 'kontourai-flow-agents-*.tgz' -print -quit)" \
903
902
  && [[ -n "$PACKAGE_TARBALL" ]] \
904
903
  && npm install --silent --no-audit --no-fund --ignore-scripts --prefix "$PACKAGE_CONSUMER" "$PACKAGE_TARBALL" \
905
- && (cd "$PACKAGE_AMBIENT" && CODEX_SESSION_ID=packed-package-consumer node "$PACKAGE_CONSUMER/node_modules/@kontourai/flow-agents/build/src/cli/workflow-sidecar.js" ensure-session \
906
- --artifact-root "$PACKAGE_PROJECT/.kontourai/flow-agents" \
907
- --task-slug packed-builder-entry \
908
- --title "Packed Builder entry" \
909
- --summary "Installed package should project pickup-probe." \
910
- --flow-id builder.build >/dev/null 2>&1) \
911
- && node - "$PACKAGE_PROJECT" <<'NODE' &&
904
+ && mkdir -p "$PACKAGE_SESSION" "$PACKAGE_LIFECYCLE_SESSION" \
905
+ && printf 'Selected Work Item: acme/builder#901\n' > "$PACKAGE_SESSION/acme-builder-901--pull-work.md" \
906
+ && package_flow start --artifact-root "$PACKAGE_PROJECT/.kontourai/flow-agents" \
907
+ --flow builder.build --work-item acme/builder#901 --assignment-provider local-file --summary "Packed public workflow contract fixture." >/dev/null \
908
+ && node - "$PACKAGE_SESSION" <<'NODE' &&
909
+ const fs = require('node:fs');
910
+ const path = require('node:path');
911
+ const session = process.argv[2];
912
+ const slug = path.basename(session);
913
+ const write = (name, body) => fs.writeFileSync(path.join(session, name), body, 'utf8');
914
+ write(`${slug}--pull-work.md`, '# Pull And Probe Report\n\nSelected Work Item: acme/builder#901\n\nPickup readiness and recorded decisions are reviewable here.\n');
915
+ write(`${slug}--plan-work.md`, '# Plan\n\n## Definition Of Done\n\n- AC-1: The packed workflow fixture records criterion-backed command evidence.\n');
916
+ write(`${slug}--deliver.md`, '# Execute\n\nThe packed fixture changed only its declared fixture artifacts.\n');
917
+ write(`${slug}--evidence-gate.md`, '# Evidence Gate\n\nScope and acceptance coverage are reviewable here.\n');
918
+ write('acceptance.json', JSON.stringify({ schema_version: '1.0', task_slug: slug, criteria: [{ id: 'AC-1', description: 'The packed workflow fixture records criterion-backed command evidence.', status: 'pending', evidence_refs: [] }], goal_fit: { status: 'pending', summary: 'Fixture has not completed Goal Fit review.' } }, null, 2));
919
+ write('release.json', JSON.stringify({ schema_version: '1.0', task_slug: slug, decision: 'hold', updated_at: '2026-07-11T00:00:00Z', scope: 'packed fixture', evidence_ref: `${slug}--evidence-gate.md`, gates: [{ name: 'merge', status: 'hold', summary: 'Fixture never publishes.' }], rollback_plan: { status: 'not_required', summary: 'No release.', owner: 'fixture' }, observability_plan: { status: 'not_required', summary: 'No release.' }, post_deploy_checks: [], docs: { status: 'not_needed', summary: 'Fixture.' } }, null, 2));
920
+ write('learning.json', JSON.stringify({ schema_version: '1.0', task_slug: slug, status: 'learned', updated_at: '2026-07-11T00:00:00Z', records: [{ id: 'packed-fixture-learning', recorded_at: '2026-07-11T00:00:00Z', source_refs: [`${slug}--evidence-gate.md`], outcome: 'success', facts: ['Packed consumer produced declared durable artifacts.'], interpretation: 'The public workflow contract remained usable after npm install.', routing: [{ target: 'none', action: 'No follow-up required.', status: 'completed' }], correction: { needed: false, evidence: 'Fixture contract passes.' } }] }, null, 2));
921
+ NODE
922
+ node - "$PACKAGE_PROJECT" <<'NODE' &&
912
923
  const fs = require('node:fs');
913
924
  const path = require('node:path');
914
925
  const project = process.argv[2];
915
- const state = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow-agents', 'packed-builder-entry', 'state.json'), 'utf8'));
916
- const flow = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow', 'runs', 'packed-builder-entry', 'state.json'), 'utf8'));
917
- const bundle = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow-agents', 'packed-builder-entry', 'trust.bundle'), 'utf8'));
926
+ const state = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow-agents', 'acme-builder-901', 'state.json'), 'utf8'));
927
+ const flow = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow', 'runs', 'acme-builder-901', 'state.json'), 'utf8'));
928
+ const bundle = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow-agents', 'acme-builder-901', 'trust.bundle'), 'utf8'));
918
929
  if (flow.current_step !== 'design-probe' || state.flow_run?.current_step !== 'design-probe') process.exit(1);
919
930
  if (JSON.stringify(state.next_action?.skills) !== JSON.stringify(['pickup-probe'])) process.exit(1);
920
931
  if (!(bundle.claims || []).some((claim) => claim.claimType === 'builder.pull-work.selected' && claim.status === 'verified')) process.exit(1);
921
932
  NODE
922
- node --input-type=module - "$PACKAGE_PROJECT" "$PACKAGE_CONSUMER" <<'NODE' &&
933
+ package_flow evidence --session-dir "$PACKAGE_SESSION" --expectation pickup-probe-readiness --status pass \
934
+ --summary "Packed fixture records the declared pickup readiness artifact." \
935
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$PACKAGE_SESSION/acme-builder-901--pull-work.md\",\"summary\":\"Durable pickup readiness artifact.\"}" >/dev/null \
936
+ && package_flow evidence --session-dir "$PACKAGE_SESSION" --expectation probe-decisions-or-accepted-gaps --status pass \
937
+ --summary "Packed fixture records the declared probe decisions artifact." \
938
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$PACKAGE_SESSION/acme-builder-901--pull-work.md\",\"summary\":\"Durable probe decisions artifact.\"}" >/dev/null \
939
+ && package_flow evidence --session-dir "$PACKAGE_SESSION" --expectation implementation-plan --status pass \
940
+ --summary "Packed fixture records the declared implementation plan artifact." \
941
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$PACKAGE_SESSION/acme-builder-901--plan-work.md\",\"summary\":\"Durable implementation plan artifact.\"}" >/dev/null \
942
+ && package_flow evidence --session-dir "$PACKAGE_SESSION" --expectation implementation-scope --status pass \
943
+ --summary "Packed fixture records the declared execution scope artifact." \
944
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$PACKAGE_SESSION/acme-builder-901--deliver.md\",\"summary\":\"Durable execution scope artifact.\"}" >/dev/null \
945
+ && ! package_flow evidence --session-dir "$PACKAGE_SESSION" --expectation tests-evidence --status pass \
946
+ --command "$PACKAGE_TEST_COMMAND" --criterion-json "$PACKAGE_CRITERION_JSON" \
947
+ --summary "This must reject tests evidence before a clean review." \
948
+ --evidence-ref-json "$PACKAGE_COMMAND_REF" >/dev/null 2>&1 \
949
+ && package_review critique --session-dir "$PACKAGE_SESSION" --verdict pass \
950
+ --summary "Authenticated packed review found no blocking fixture findings." \
951
+ --artifact-ref "$PACKAGE_SESSION/acme-builder-901--deliver.md" \
952
+ --lane-json "{\"id\":\"code-review\",\"status\":\"pass\",\"summary\":\"Reviewed the packed consumer delivery.\",\"evidence_refs\":[{\"kind\":\"artifact\",\"file\":\"$PACKAGE_SESSION/acme-builder-901--deliver.md\",\"summary\":\"Reviewed packed delivery artifact.\"}]}" >/dev/null \
953
+ && package_flow evidence --session-dir "$PACKAGE_SESSION" --expectation tests-evidence --status pass \
954
+ --command "$PACKAGE_TEST_COMMAND" --criterion-json "$PACKAGE_CRITERION_JSON" \
955
+ --summary "Packed fixture records criterion-backed command evidence after review." \
956
+ --evidence-ref-json "$PACKAGE_COMMAND_REF" >/dev/null \
957
+ && node - "$PACKAGE_SESSION" <<'NODE' &&
958
+ const fs = require('node:fs');
959
+ const path = require('node:path');
960
+ const session = process.argv[2];
961
+ const bundle = JSON.parse(fs.readFileSync(path.join(session, 'trust.bundle'), 'utf8'));
962
+ const criterionClaim = (bundle.claims || []).find((claim) =>
963
+ claim.metadata?.origin === 'acceptance'
964
+ && claim.metadata?.criterion?.id === 'AC-1'
965
+ && claim.metadata.criterion.status === 'pass'
966
+ && claim.metadata.criterion.evidence_refs?.some((ref) => ref.kind === 'command'));
967
+ if (!criterionClaim) {
968
+ console.error('AC-1 is missing criterion-backed command evidence');
969
+ process.exit(1);
970
+ }
971
+ NODE
972
+ package_flow evidence --session-dir "$PACKAGE_SESSION" --expectation merge-readiness --status pass \
973
+ --summary "Packed fixture records the declared merge-readiness artifact." \
974
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$PACKAGE_SESSION/acme-builder-901--evidence-gate.md\",\"summary\":\"Durable merge-readiness artifact.\"}" >/dev/null \
975
+ && package_flow evidence --session-dir "$PACKAGE_SESSION" --expectation pull-request-opened --status pass \
976
+ --summary "Packed fixture records the declared release artifact." \
977
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$PACKAGE_SESSION/release.json\",\"summary\":\"Durable release artifact.\"}" >/dev/null \
978
+ && package_flow evidence --session-dir "$PACKAGE_SESSION" --expectation ci-merge-readiness --status pass \
979
+ --summary "Packed fixture records the declared CI readiness artifact." \
980
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$PACKAGE_SESSION/release.json\",\"summary\":\"Durable CI readiness artifact.\"}" >/dev/null \
981
+ && package_flow evidence --session-dir "$PACKAGE_SESSION" --expectation decision-evidence --status pass \
982
+ --summary "Packed fixture records the declared learning decision artifact." \
983
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$PACKAGE_SESSION/learning.json\",\"summary\":\"Durable learning decision artifact.\"}" >/dev/null \
984
+ && package_flow evidence --session-dir "$PACKAGE_SESSION" --expectation learning-evidence --status pass \
985
+ --summary "Packed fixture records the declared learning evidence artifact." \
986
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$PACKAGE_SESSION/learning.json\",\"summary\":\"Durable learning evidence artifact.\"}" >/dev/null \
987
+ && node - "$PACKAGE_SESSION" <<'NODE' &&
988
+ const fs = require('node:fs');
989
+ const path = require('node:path');
990
+ const session = process.argv[2];
991
+ const bundle = JSON.parse(fs.readFileSync(path.join(session, 'trust.bundle'), 'utf8'));
992
+ const types = new Set((bundle.claims || []).filter((claim) => claim.status === 'verified').map((claim) => claim.claimType));
993
+ const requiredTypes = [
994
+ 'builder.pull-work.selected',
995
+ 'builder.design-probe.pickup-readiness',
996
+ 'builder.design-probe.decisions',
997
+ 'builder.plan.implementation',
998
+ 'builder.execute.scope',
999
+ 'builder.verify.tests',
1000
+ 'builder.merge-ready.readiness',
1001
+ 'builder.learn.decisions',
1002
+ 'builder.learn.evidence',
1003
+ ];
1004
+ const missingTypes = requiredTypes.filter((type) => !types.has(type));
1005
+ if (missingTypes.length > 0) {
1006
+ console.error(`missing verified public workflow claims: ${missingTypes.join(', ')}`);
1007
+ process.exit(1);
1008
+ }
1009
+ NODE
1010
+ printf 'Selected Work Item: acme/builder#902\n' > "$PACKAGE_LIFECYCLE_SESSION/acme-builder-902--pull-work.md" \
1011
+ && package_flow start --artifact-root "$PACKAGE_PROJECT/.kontourai/flow-agents" \
1012
+ --flow builder.build --work-item acme/builder#902 --assignment-provider local-file --summary "Packed public lifecycle fixture." >/dev/null \
1013
+ && node --input-type=module - "$PACKAGE_PROJECT" "$PACKAGE_LIFECYCLE_SESSION" "$PACKAGE_CONSUMER" <<'NODE' &&
923
1014
  import fs from 'node:fs';
924
1015
  import path from 'node:path';
925
1016
  import { generateKeyPairSync, sign } from 'node:crypto';
926
1017
  import { pathToFileURL } from 'node:url';
927
- const [project, consumer] = process.argv.slice(2);
928
- const slug = 'packed-builder-entry';
1018
+ const [project, session, consumer] = process.argv.slice(2);
1019
+ const slug = path.basename(session);
929
1020
  const assignment = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow-agents', 'assignment', `${slug}.json`), 'utf8'));
930
1021
  const state = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow-agents', slug, 'state.json'), 'utf8'));
931
1022
  const packageEntry = path.join(consumer, 'node_modules', '@kontourai', 'flow-agents', 'build', 'src', 'index.js');
@@ -957,15 +1048,15 @@ for (const operation of ['cancel', 'archive']) {
957
1048
  fs.writeFileSync(path.join(project, `${operation}.authorization.json`), JSON.stringify(authorization, null, 2));
958
1049
  }
959
1050
  NODE
960
- (cd "$PACKAGE_PROJECT" && CODEX_SESSION_ID=packed-package-consumer node "$PACKAGE_CLI" builder-run pause --session-dir "$PACKAGE_SESSION" --reason "packed pause" >/dev/null) \
961
- && (cd "$PACKAGE_PROJECT" && CODEX_SESSION_ID=packed-package-consumer node "$PACKAGE_CLI" builder-run resume --session-dir "$PACKAGE_SESSION" --reason "packed resume" >/dev/null) \
962
- && (cd "$PACKAGE_PROJECT" && node "$PACKAGE_CLI" builder-run cancel --session-dir "$PACKAGE_SESSION" --authorization-file "$PACKAGE_PROJECT/cancel.authorization.json" >/dev/null) \
963
- && (cd "$PACKAGE_PROJECT" && node "$PACKAGE_CLI" builder-run archive --session-dir "$PACKAGE_SESSION" --authorization-file "$PACKAGE_PROJECT/archive.authorization.json" >/dev/null) \
1051
+ package_flow pause --session-dir "$PACKAGE_LIFECYCLE_SESSION" --reason "packed pause" >/dev/null \
1052
+ && package_flow resume --session-dir "$PACKAGE_LIFECYCLE_SESSION" --reason "packed resume" >/dev/null \
1053
+ && package_flow cancel --session-dir "$PACKAGE_LIFECYCLE_SESSION" --authorization-file "$PACKAGE_PROJECT/cancel.authorization.json" >/dev/null \
1054
+ && package_flow archive --session-dir "$PACKAGE_LIFECYCLE_SESSION" --authorization-file "$PACKAGE_PROJECT/archive.authorization.json" >/dev/null \
964
1055
  && node - "$PACKAGE_PROJECT" <<'NODE'
965
1056
  const fs = require('node:fs');
966
1057
  const path = require('node:path');
967
1058
  const project = process.argv[2];
968
- const slug = 'packed-builder-entry';
1059
+ const slug = 'acme-builder-902';
969
1060
  const archived = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow-agents', 'archive', slug, 'state.json'), 'utf8'));
970
1061
  const flow = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow', 'runs', slug, 'state.json'), 'utf8'));
971
1062
  const consumed = fs.readdirSync(path.join(project, '.kontourai', 'flow-agents', 'lifecycle-authority', 'consumed'));
@@ -973,13 +1064,47 @@ if (archived.status !== 'archived' || flow.status !== 'canceled' || consumed.len
973
1064
  if (fs.existsSync(path.join(project, '.kontourai', 'flow-agents', slug))) process.exit(1);
974
1065
  NODE
975
1066
  then
976
- _pass "packed npm consumer projects Builder entry and executes lifecycle commands"
1067
+ _pass "packed npm consumer follows the public Builder workflow contract and lifecycle commands"
977
1068
  else
978
- _fail "packed npm consumer did not execute canonical Builder entry and lifecycle commands"
1069
+ _fail "packed npm consumer did not complete the public Builder workflow contract and lifecycle commands"
979
1070
  fi
980
1071
 
981
- if [[ -d "$CODEX_FULL_DEST/.codex/skills/plan-work" && -d "$CODEX_FULL_DEST/.codex/skills/deliver" && -d "$CODEX_FULL_DEST/.codex/skills/agentic-engineering" ]]; then
982
- _pass "Codex full install ships kit-skills and standalone skills together"
1072
+ if node - "$CODEX_FULL_DEST/.agents/skills" "$CODEX_FULL_DEST" <<'NODE'
1073
+ const fs = require('node:fs');
1074
+ const path = require('node:path');
1075
+ const [root, installRoot] = process.argv.slice(2);
1076
+ const builder = [
1077
+ 'builder-shape', 'continue-work', 'deliver', 'design-probe', 'evidence-gate',
1078
+ 'execute-plan', 'fix-bug', 'gate-review', 'idea-to-backlog', 'learning-review',
1079
+ 'pickup-probe', 'plan-work', 'pull-work', 'release-readiness', 'review-work',
1080
+ 'tdd-workflow', 'verify-work',
1081
+ ];
1082
+ for (const skill of [...builder, 'agentic-engineering']) {
1083
+ const file = path.join(root, skill, 'SKILL.md');
1084
+ if (!fs.existsSync(file)) throw new Error(`missing installed skill ${skill}`);
1085
+ if (builder.includes(skill)) {
1086
+ const text = fs.readFileSync(file, 'utf8');
1087
+ if (/npm run workflow:sidecar|flow-agents-workflow-sidecar|flow-agents builder-run/.test(text)) {
1088
+ throw new Error(`${skill} exposes a private workflow command`);
1089
+ }
1090
+ if (/--reviewer/.test(text)) {
1091
+ throw new Error(`${skill} exposes a caller-selected review identity`);
1092
+ }
1093
+ if (skill === 'verify-work' && (!/--criterion-json/.test(text) || /--command\s+"(?:true|node --version)"/.test(text))) {
1094
+ throw new Error('verify-work must ship criterion-backed substantive verification evidence guidance');
1095
+ }
1096
+ const refs = [...text.matchAll(/\b(?:context|docs|kits)\/[A-Za-z0-9_./-]+\.(?:md|json)\b/g)].map((match) => match[0]);
1097
+ for (const ref of refs) {
1098
+ const skillLocal = path.join(path.dirname(file), ref);
1099
+ if (!fs.existsSync(skillLocal) && !fs.existsSync(path.join(installRoot, ref))) {
1100
+ throw new Error(`${skill} has unresolved installed resource ${ref}`);
1101
+ }
1102
+ }
1103
+ }
1104
+ }
1105
+ NODE
1106
+ then
1107
+ _pass "Codex full install ships all 17 coherent Builder skills and standalone skills"
983
1108
  else
984
1109
  _fail "Codex full install is missing skills"
985
1110
  fi
@@ -990,6 +1115,105 @@ else
990
1115
  _fail "Codex full install removed unknown user files"
991
1116
  fi
992
1117
 
1118
+ echo ""
1119
+ echo "--- Dedicated Codex Home Universal Skills ---"
1120
+ GLOBAL_HOME="$TMPDIR_EVAL/global-home"
1121
+ GLOBAL_CODEX="$TMPDIR_EVAL/global-codex"
1122
+ GLOBAL_SKILLS="$TMPDIR_EVAL/global-agents/skills"
1123
+ mkdir -p "$GLOBAL_SKILLS/user-skill"
1124
+ printf '# user skill\n' > "$GLOBAL_SKILLS/user-skill/SKILL.md"
1125
+ GLOBAL_OUTPUT="$TMPDIR_EVAL/global-install.out"
1126
+ if HOME="$GLOBAL_HOME" CODEX_REAL_HOME="$GLOBAL_CODEX" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$GLOBAL_CODEX" --skills-dir "$GLOBAL_SKILLS" >"$GLOBAL_OUTPUT"; then
1127
+ _pass "dedicated Codex installer accepts independent hermetic runtime and skill roots"
1128
+ else
1129
+ _fail "dedicated Codex installer failed with independent hermetic roots"
1130
+ fi
1131
+ if [[ -f "$GLOBAL_SKILLS/plan-work/SKILL.md" && -f "$GLOBAL_SKILLS/plan-work/context/contracts/planning-contract.md" && -f "$GLOBAL_SKILLS/user-skill/SKILL.md" && ! -e "$GLOBAL_CODEX/skills/plan-work" ]]; then
1132
+ _pass "global install uses self-contained universal skills and preserves user skills"
1133
+ else
1134
+ _fail "global universal skill layout, resource closure, or user preservation is wrong"
1135
+ fi
1136
+ if rg -q 'Installed Flow Agents into Codex home at .+/global-codex$' "$GLOBAL_OUTPUT" && rg -q 'Installed portable skills at .+/global-agents/skills$' "$GLOBAL_OUTPUT"; then
1137
+ _pass "installer reports both runtime and portable skill destinations"
1138
+ else
1139
+ _fail "installer output does not report both destinations"
1140
+ fi
1141
+ if HOME="$GLOBAL_HOME" CODEX_REAL_HOME="$GLOBAL_CODEX" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$GLOBAL_CODEX" --skills-dir "$GLOBAL_SKILLS" >/dev/null; then
1142
+ _pass "split-root reinstall is idempotent"
1143
+ else
1144
+ _fail "split-root reinstall is not idempotent"
1145
+ fi
1146
+
1147
+ CONFLICT_CODEX="$TMPDIR_EVAL/conflict-codex"
1148
+ CONFLICT_SKILLS="$TMPDIR_EVAL/conflict-agents/skills"
1149
+ mkdir -p "$CONFLICT_SKILLS/plan-work" "$CONFLICT_CODEX/skills/plan-work" "$CONFLICT_CODEX/.flow-agents"
1150
+ printf '# user collision\n' > "$CONFLICT_SKILLS/plan-work/SKILL.md"
1151
+ cp "$ROOT_DIR/dist/codex/.agents/skills/plan-work/SKILL.md" "$CONFLICT_CODEX/skills/plan-work/SKILL.md"
1152
+ printf 'runtime sentinel\n' > "$CONFLICT_CODEX/runtime-sentinel"
1153
+ node - "$CONFLICT_CODEX" <<'NODE'
1154
+ const fs=require('node:fs'), path=require('node:path'), crypto=require('node:crypto'); const root=process.argv[2];
1155
+ const rel='skills' + '/plan-work/SKILL.md'; const sha256=crypto.createHash('sha256').update(fs.readFileSync(path.join(root,rel))).digest('hex');
1156
+ fs.writeFileSync(path.join(root,'.flow-agents/codex-install-manifest.json'), JSON.stringify({schema_version:'1.0',files:[{path:rel,sha256}]},null,2)+'\n');
1157
+ NODE
1158
+ CONFLICT_RUNTIME_BEFORE="$(shasum -a 256 "$CONFLICT_CODEX/skills/plan-work/SKILL.md" "$CONFLICT_CODEX/.flow-agents/codex-install-manifest.json" "$CONFLICT_CODEX/runtime-sentinel")"
1159
+ if HOME="$GLOBAL_HOME" CODEX_REAL_HOME="$CONFLICT_CODEX" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CONFLICT_CODEX" --skills-dir "$CONFLICT_SKILLS" >/dev/null 2>&1; then
1160
+ _fail "global installer overwrote a user-owned skill collision"
1161
+ else
1162
+ CONFLICT_RUNTIME_AFTER="$(shasum -a 256 "$CONFLICT_CODEX/skills/plan-work/SKILL.md" "$CONFLICT_CODEX/.flow-agents/codex-install-manifest.json" "$CONFLICT_CODEX/runtime-sentinel")"
1163
+ if [[ "$(cat "$CONFLICT_SKILLS/plan-work/SKILL.md")" == "# user collision" && "$CONFLICT_RUNTIME_BEFORE" == "$CONFLICT_RUNTIME_AFTER" && ! -e "$CONFLICT_SKILLS/.flow-agents" ]]; then
1164
+ _pass "cross-root collision preflight preserves universal content, legacy skill, runtime sentinel, and manifests"
1165
+ else
1166
+ _fail "cross-root collision failure partially mutated an install root"
1167
+ fi
1168
+ fi
1169
+
1170
+ SYMLINK_TARGET="$TMPDIR_EVAL/symlink-target"
1171
+ SYMLINK_PARENT="$TMPDIR_EVAL/symlink-parent"
1172
+ mkdir -p "$SYMLINK_TARGET" "$SYMLINK_PARENT"
1173
+ ln -s "$SYMLINK_TARGET" "$SYMLINK_PARENT/catalog"
1174
+ if HOME="$GLOBAL_HOME" CODEX_REAL_HOME="$TMPDIR_EVAL/symlink-codex" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$TMPDIR_EVAL/symlink-codex" --skills-dir "$SYMLINK_PARENT/catalog/skills" >/dev/null 2>&1; then
1175
+ _fail "global installer followed a symlinked skill destination component"
1176
+ else
1177
+ [[ ! -e "$SYMLINK_TARGET/skills" ]] && _pass "global installer refuses symlinked skill destination components without mutation" || _fail "symlink rejection mutated its target"
1178
+ fi
1179
+
1180
+ SOURCE_ANCESTOR="$(dirname "$ROOT_DIR")"
1181
+ SOURCE_DESCENDANT="$ROOT_DIR/.fa-550-forbidden-skills"
1182
+ for unsafe in / "$SOURCE_ANCESTOR" "$SOURCE_DESCENDANT"; do
1183
+ rm -rf "$SOURCE_DESCENDANT"
1184
+ if HOME="$GLOBAL_HOME" CODEX_REAL_HOME="$TMPDIR_EVAL/unsafe-codex" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$TMPDIR_EVAL/unsafe-codex" --skills-dir "$unsafe" >/dev/null 2>&1; then
1185
+ _fail "global installer accepted unsafe skill namespace root: $unsafe"
1186
+ elif [[ ! -e "$SOURCE_DESCENDANT" && ! -e "$TMPDIR_EVAL/unsafe-codex" ]]; then
1187
+ _pass "unsafe root/overlap rejection occurs before destination mutation: $unsafe"
1188
+ else
1189
+ _fail "unsafe root/overlap rejection left destination mutations: $unsafe"
1190
+ fi
1191
+ done
1192
+
1193
+ MIGRATE_CODEX="$TMPDIR_EVAL/migrate-codex"
1194
+ MIGRATE_SKILLS="$TMPDIR_EVAL/migrate-agents/skills"
1195
+ LEGACY_SKILLS_SEGMENT="skills"
1196
+ LEGACY_USER_DIR="$MIGRATE_CODEX/$LEGACY_SKILLS_SEGMENT/user-legacy"
1197
+ mkdir -p "$MIGRATE_CODEX/skills/plan-work" "$LEGACY_USER_DIR" "$MIGRATE_CODEX/.flow-agents"
1198
+ cp "$ROOT_DIR/dist/codex/.agents/skills/plan-work/SKILL.md" "$MIGRATE_CODEX/skills/plan-work/SKILL.md"
1199
+ printf '# modified legacy user content\n' > "$LEGACY_USER_DIR/SKILL.md"
1200
+ node - "$MIGRATE_CODEX" <<'NODE'
1201
+ const fs = require('node:fs'); const path = require('node:path'); const crypto = require('node:crypto');
1202
+ const root = process.argv[2];
1203
+ const rels = ['skills' + '/plan-work/SKILL.md', 'skills' + '/user-legacy/SKILL.md'];
1204
+ const files = rels.map(p => ({path:p, sha256:crypto.createHash('sha256').update(fs.readFileSync(path.join(root,p))).digest('hex')}));
1205
+ fs.writeFileSync(path.join(root,'.flow-agents/codex-install-manifest.json'), JSON.stringify({schema_version:'1.0',files},null,2)+'\n');
1206
+ fs.appendFileSync(path.join(root,'skills' + '/user-legacy/SKILL.md'), 'kept modification\n');
1207
+ NODE
1208
+ if HOME="$GLOBAL_HOME" CODEX_REAL_HOME="$MIGRATE_CODEX" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$MIGRATE_CODEX" --skills-dir "$MIGRATE_SKILLS" >/dev/null 2>"$TMPDIR_EVAL/migrate.err" \
1209
+ && [[ ! -e "$MIGRATE_CODEX/skills/plan-work/SKILL.md" ]] \
1210
+ && rg -q 'kept modification' "$LEGACY_USER_DIR/SKILL.md" \
1211
+ && [[ -f "$MIGRATE_SKILLS/plan-work/SKILL.md" ]]; then
1212
+ _pass "legacy migration removes unchanged owned skills and preserves modified legacy content"
1213
+ else
1214
+ _fail "legacy Flow Agents skill migration was destructive or incomplete"
1215
+ fi
1216
+
993
1217
  OPENCODE_AGENTS_DIR="$OPENCODE_FULL_DEST/.opencode/agents"
994
1218
  if [[ -f "$OPENCODE_AGENTS_DIR/tool-planner.md" && -f "$OPENCODE_AGENTS_DIR/dev.md" ]]; then
995
1219
  _pass "opencode full install ships the complete agent base"
@@ -997,8 +1221,37 @@ else
997
1221
  _fail "opencode full install is missing base agents"
998
1222
  fi
999
1223
 
1000
- if [[ -d "$OPENCODE_FULL_DEST/.opencode/skills/plan-work" && -d "$OPENCODE_FULL_DEST/.opencode/skills/deliver" && -d "$OPENCODE_FULL_DEST/.opencode/skills/agentic-engineering" ]]; then
1001
- _pass "opencode full install ships kit-skills and standalone skills together"
1224
+ if node - "$OPENCODE_FULL_DEST/.opencode/skills" "$OPENCODE_FULL_DEST" <<'NODE'
1225
+ const fs = require('node:fs');
1226
+ const path = require('node:path');
1227
+ const [root, installRoot] = process.argv.slice(2);
1228
+ const builder = [
1229
+ 'builder-shape', 'continue-work', 'deliver', 'design-probe', 'evidence-gate',
1230
+ 'execute-plan', 'fix-bug', 'gate-review', 'idea-to-backlog', 'learning-review',
1231
+ 'pickup-probe', 'plan-work', 'pull-work', 'release-readiness', 'review-work',
1232
+ 'tdd-workflow', 'verify-work',
1233
+ ];
1234
+ for (const skill of [...builder, 'agentic-engineering']) {
1235
+ const file = path.join(root, skill, 'SKILL.md');
1236
+ if (!fs.existsSync(file)) throw new Error(`missing installed skill ${skill}`);
1237
+ if (builder.includes(skill)) {
1238
+ const text = fs.readFileSync(file, 'utf8');
1239
+ if (/npm run workflow:sidecar|flow-agents-workflow-sidecar|flow-agents builder-run/.test(text)) {
1240
+ throw new Error(`${skill} exposes a private workflow command`);
1241
+ }
1242
+ if (/--reviewer/.test(text)) {
1243
+ throw new Error(`${skill} exposes a caller-selected review identity`);
1244
+ }
1245
+ if (skill === 'verify-work' && (!/--criterion-json/.test(text) || /--command\s+"(?:true|node --version)"/.test(text))) {
1246
+ throw new Error('verify-work must ship criterion-backed substantive verification evidence guidance');
1247
+ }
1248
+ const refs = [...text.matchAll(/\b(?:context|docs|kits)\/[A-Za-z0-9_./-]+\.(?:md|json)\b/g)].map((match) => match[0]);
1249
+ for (const ref of refs) if (!fs.existsSync(path.join(installRoot, ref))) throw new Error(`${skill} has unresolved installed resource ${ref}`);
1250
+ }
1251
+ }
1252
+ NODE
1253
+ then
1254
+ _pass "opencode full install ships all 17 coherent Builder skills and standalone skills"
1002
1255
  else
1003
1256
  _fail "opencode full install is missing skills"
1004
1257
  fi
@@ -164,7 +164,7 @@ OPENCODE_UPGRADE="$TMPDIR_EVAL/upgrade-opencode"
164
164
  # Touch a marker into skill files in the COPIES (not dist/ originals)
165
165
  UPGRADE_MARKER="# flow-agents-upgrade-test-marker"
166
166
  CLAUDE_SKILL_FILE="$CLAUDE_BUNDLE_COPY/.claude/skills/plan-work/SKILL.md"
167
- CODEX_SKILL_FILE="$CODEX_BUNDLE_COPY/.codex/skills/plan-work/SKILL.md"
167
+ CODEX_SKILL_FILE="$CODEX_BUNDLE_COPY/.agents/skills/plan-work/SKILL.md"
168
168
  OPENCODE_SKILL_FILE="$OPENCODE_BUNDLE_COPY/.opencode/skills/plan-work/SKILL.md"
169
169
 
170
170
  if [[ -f "$CLAUDE_SKILL_FILE" ]]; then
@@ -191,12 +191,10 @@ else
191
191
  _fail "claude-code upgrade: skill change did not propagate to workspace"
192
192
  fi
193
193
 
194
- if [[ -f "$CODEX_SKILL_FILE" ]] && grep -qF "$UPGRADE_MARKER" "$CODEX_UPGRADE/.codex/skills/plan-work/SKILL.md" 2>/dev/null; then
194
+ if [[ -f "$CODEX_SKILL_FILE" ]] && grep -qF "$UPGRADE_MARKER" "$CODEX_UPGRADE/.agents/skills/plan-work/SKILL.md" 2>/dev/null; then
195
195
  _pass "codex upgrade: modified skill file propagated to workspace"
196
- elif [[ ! -f "$CODEX_SKILL_FILE" ]]; then
197
- _pass "codex upgrade: skill file not in bundle (skipped)"
198
196
  else
199
- _fail "codex upgrade: skill change did not propagate to workspace"
197
+ _fail "codex upgrade: required universal skill missing or change did not propagate"
200
198
  fi
201
199
 
202
200
  if [[ -f "$OPENCODE_SKILL_FILE" ]] && grep -qF "$UPGRADE_MARKER" "$OPENCODE_UPGRADE/.opencode/skills/plan-work/SKILL.md" 2>/dev/null; then
@@ -246,7 +244,7 @@ echo '{"custom":"data"}' > "$OPENCODE_USER/.kontourai/flow-agents/my-session/sta
246
244
 
247
245
  # Modify an installed skill file to simulate user edits
248
246
  CLAUDE_INSTALLED_SKILL="$CLAUDE_USER/.claude/skills/plan-work/SKILL.md"
249
- CODEX_INSTALLED_SKILL="$CODEX_USER/.codex/skills/plan-work/SKILL.md"
247
+ CODEX_INSTALLED_SKILL="$CODEX_USER/.agents/skills/plan-work/SKILL.md"
250
248
  OPENCODE_INSTALLED_SKILL="$OPENCODE_USER/.opencode/skills/plan-work/SKILL.md"
251
249
 
252
250
  USER_EDIT_MARKER="# USER EDIT - should be overwritten by re-install"