@kontourai/flow-agents 0.1.1 → 0.2.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 (97) hide show
  1. package/.github/dependabot.yml +23 -0
  2. package/.github/workflows/publish-npm.yml +1 -1
  3. package/.github/workflows/release-please.yml +31 -0
  4. package/.github/workflows/runtime-compat.yml +118 -0
  5. package/CHANGELOG.md +38 -0
  6. package/CONTRIBUTING.md +4 -0
  7. package/README.md +58 -19
  8. package/build/src/cli/init.js +215 -5
  9. package/build/src/cli/utterance-check.js +236 -0
  10. package/build/src/cli.js +3 -0
  11. package/build/src/tools/build-universal-bundles.js +268 -0
  12. package/build/src/tools/filter-installed-packs.js +3 -0
  13. package/build/src/tools/validate-source-tree.js +6 -1
  14. package/context/scripts/telemetry/lib/config.sh +5 -1
  15. package/context/settings/flow-agents-settings.json +7 -0
  16. package/docs/agent-system-guidebook.md +4 -5
  17. package/docs/context-map.md +1 -0
  18. package/docs/index.md +46 -6
  19. package/docs/integrations/conformance.md +246 -0
  20. package/docs/integrations/framework-adapter.md +275 -0
  21. package/docs/integrations/harness-install.md +213 -0
  22. package/docs/integrations/index.md +54 -0
  23. package/docs/north-star.md +3 -3
  24. package/docs/repository-structure.md +1 -1
  25. package/docs/skills-map.md +10 -4
  26. package/docs/spec/runtime-hook-surface.md +472 -0
  27. package/docs/survey-utterance-check.md +308 -0
  28. package/docs/vision.md +45 -0
  29. package/docs/workflow-usage-guide.md +1 -1
  30. package/evals/acceptance/run.sh +4 -2
  31. package/evals/acceptance/test_opencode_harness.sh +121 -0
  32. package/evals/acceptance/test_pi_harness.sh +98 -0
  33. package/evals/integration/test_bundle_install.sh +226 -1
  34. package/evals/integration/test_bundle_lifecycle.sh +641 -0
  35. package/evals/integration/test_utterance_check.sh +518 -0
  36. package/evals/run.sh +2 -0
  37. package/evals/static/test_universal_bundles.sh +137 -2
  38. package/integrations/strands/README.md +256 -0
  39. package/integrations/strands/example.py +74 -0
  40. package/integrations/strands/flow_agents_strands/__init__.py +27 -0
  41. package/integrations/strands/flow_agents_strands/hooks.py +194 -0
  42. package/integrations/strands/flow_agents_strands/policy.py +348 -0
  43. package/integrations/strands/flow_agents_strands/steering.py +172 -0
  44. package/integrations/strands/flow_agents_strands/telemetry.py +238 -0
  45. package/integrations/strands/pyproject.toml +38 -0
  46. package/integrations/strands/tests/__init__.py +0 -0
  47. package/integrations/strands/tests/test_hooks.py +304 -0
  48. package/integrations/strands/tests/test_policy.py +315 -0
  49. package/integrations/strands/tests/test_telemetry.py +184 -0
  50. package/integrations/strands-ts/README.md +224 -0
  51. package/integrations/strands-ts/bin/conformance-shim.mjs +257 -0
  52. package/integrations/strands-ts/package.json +53 -0
  53. package/integrations/strands-ts/src/hooks.ts +208 -0
  54. package/integrations/strands-ts/src/index.ts +22 -0
  55. package/integrations/strands-ts/src/policy.ts +345 -0
  56. package/integrations/strands-ts/src/telemetry.ts +251 -0
  57. package/integrations/strands-ts/test/test-policy.ts +322 -0
  58. package/integrations/strands-ts/test/test-telemetry.ts +226 -0
  59. package/integrations/strands-ts/tsconfig.json +20 -0
  60. package/package.json +7 -2
  61. package/packaging/conformance/README.md +142 -0
  62. package/packaging/conformance/fixtures/config-protection--allow-no-path.json +18 -0
  63. package/packaging/conformance/fixtures/config-protection--allow-safe-file.json +20 -0
  64. package/packaging/conformance/fixtures/config-protection--block-biome.json +20 -0
  65. package/packaging/conformance/fixtures/config-protection--block-eslintrc.json +20 -0
  66. package/packaging/conformance/fixtures/quality-gate--allow-no-path.json +17 -0
  67. package/packaging/conformance/fixtures/quality-gate--allow-nonexistent-file.json +19 -0
  68. package/packaging/conformance/fixtures/stop-goal-fit--allow-clean-cwd.json +17 -0
  69. package/packaging/conformance/fixtures/stop-goal-fit--block-strict-mode.json +23 -0
  70. package/packaging/conformance/fixtures/stop-goal-fit--warn-active-delivery.json +21 -0
  71. package/packaging/conformance/fixtures/workflow-steering--allow-no-state.json +16 -0
  72. package/packaging/conformance/fixtures/workflow-steering--inject-active-state.json +29 -0
  73. package/packaging/conformance/fixtures/workflow-steering--inject-subagent-steering.json +25 -0
  74. package/packaging/conformance/package.json +4 -0
  75. package/packaging/conformance/run-conformance.js +322 -0
  76. package/packaging/manifest.json +59 -0
  77. package/schemas/flow-agents-settings.schema.json +48 -0
  78. package/scripts/README.md +5 -0
  79. package/scripts/dogfood.js +16 -0
  80. package/scripts/hooks/opencode-hook-adapter.js +123 -0
  81. package/scripts/hooks/opencode-telemetry-hook.js +101 -0
  82. package/scripts/hooks/pi-hook-adapter.js +123 -0
  83. package/scripts/hooks/pi-telemetry-hook.js +105 -0
  84. package/scripts/hooks/run-hook.js +8 -0
  85. package/scripts/hooks/utterance-check.js +327 -0
  86. package/scripts/telemetry/lib/config.sh +5 -1
  87. package/skills/idea-to-backlog/SKILL.md +1 -1
  88. package/src/cli/init.ts +219 -6
  89. package/src/cli/utterance-check.ts +324 -0
  90. package/src/cli.ts +3 -0
  91. package/src/tools/build-universal-bundles.ts +266 -0
  92. package/src/tools/filter-installed-packs.ts +3 -0
  93. package/src/tools/validate-source-tree.ts +6 -1
  94. package/build/src/cli/docs-preview.js +0 -39
  95. package/build/src/cli/export-bookmarks.js +0 -38
  96. package/build/src/cli/import-bookmarks.js +0 -50
  97. package/build/src/cli/instinct-cli.js +0 -93
@@ -40,6 +40,10 @@ CODEX_LEGACY_CONSOLE_DEST="$TMPDIR_EVAL/codex-legacy-console-workspace"
40
40
  CODEX_BAD_CONSOLE_DEST="$TMPDIR_EVAL/codex-bad-console-workspace"
41
41
  BASE_INIT_DEST="$TMPDIR_EVAL/base-init-workspace"
42
42
  CODEX_INIT_DEST="$TMPDIR_EVAL/codex-init-workspace"
43
+ OPENCODE_DEST="$TMPDIR_EVAL/opencode-workspace"
44
+ OPENCODE_CONSOLE_DEST="$TMPDIR_EVAL/opencode-console-workspace"
45
+ OPENCODE_CORE_DEST="$TMPDIR_EVAL/opencode-core-workspace"
46
+ PI_DEST="$TMPDIR_EVAL/pi-workspace"
43
47
  CONSOLE_TOKEN_FILE="$TMPDIR_EVAL/console-token"
44
48
  printf 'test-token\n' > "$CONSOLE_TOKEN_FILE"
45
49
  chmod 600 "$CONSOLE_TOKEN_FILE" 2>/dev/null || true
@@ -112,6 +116,30 @@ else
112
116
  _fail "flow-agents init headless Codex install failed"
113
117
  fi
114
118
 
119
+ if (cd "$ROOT_DIR/dist/opencode" && bash install.sh "$OPENCODE_DEST" >/dev/null); then
120
+ _pass "opencode install succeeded"
121
+ else
122
+ _fail "opencode install failed"
123
+ fi
124
+
125
+ if (cd "$ROOT_DIR/dist/opencode" && bash install.sh "$OPENCODE_CONSOLE_DEST" --telemetry-sink local-kontour-console --console-token-file "$CONSOLE_TOKEN_FILE" --console-tenant tenant-oc >/dev/null); then
126
+ _pass "opencode install with Console telemetry config succeeded"
127
+ else
128
+ _fail "opencode install with Console telemetry config failed"
129
+ fi
130
+
131
+ if node "$ROOT_DIR/build/src/cli.js" init --runtime opencode --dest "$OPENCODE_CORE_DEST" --yes >/dev/null; then
132
+ _pass "flow-agents init headless opencode install succeeded"
133
+ else
134
+ _fail "flow-agents init headless opencode install failed"
135
+ fi
136
+
137
+ if (cd "$ROOT_DIR/dist/pi" && bash install.sh "$PI_DEST" >/dev/null); then
138
+ _pass "pi install succeeded"
139
+ else
140
+ _fail "pi install failed"
141
+ fi
142
+
115
143
  USER_SKILLS_DIR="$CODEX_CORE_DEST/.codex/sk""ills/user-skill"
116
144
  mkdir -p "$CODEX_CORE_DEST/.codex/ag""ents" "$USER_SKILLS_DIR"
117
145
  printf 'name = "user-agent"\n' > "$CODEX_CORE_DEST/.codex/ag""ents/user-agent.toml"
@@ -219,6 +247,14 @@ else
219
247
  _fail "flow-agents init default did not install base AGENTS.md workspace contract"
220
248
  fi
221
249
 
250
+ if rg -F -q "console_telemetry_url=$LOCAL_KONTOUR_CONSOLE_URL" "$OPENCODE_CONSOLE_DEST/scripts/telemetry/telemetry.conf" \
251
+ && rg -q '^console_telemetry_token=test-token$' "$OPENCODE_CONSOLE_DEST/scripts/telemetry/telemetry.conf" \
252
+ && rg -q '^console_tenant_id=tenant-oc$' "$OPENCODE_CONSOLE_DEST/scripts/telemetry/telemetry.conf"; then
253
+ _pass "opencode install persists Console telemetry config"
254
+ else
255
+ _fail "opencode install did not persist Console telemetry config"
256
+ fi
257
+
222
258
  for dir in "$KIRO_DEST" "$BASE_DEST" "$CLAUDE_DEST" "$CODEX_DEST"; do
223
259
  if [[ -f "$dir/console.telemetry.json" ]] \
224
260
  && node - "$dir/console.telemetry.json" <<'NODE'
@@ -341,7 +377,7 @@ else
341
377
  _fail "installed Kiro agent JSON parse failed"
342
378
  fi
343
379
 
344
- if rg -n '/Users/[^/]+/\.flow-agents|~/\.flow-agents' "$KIRO_DEST" "$BASE_DEST" "$CLAUDE_DEST" "$CODEX_DEST" --glob '!**/evals/**' >/tmp/installed-bundle-leaks.txt 2>/dev/null; then
380
+ if rg -n '/Users/[^/]+/\.flow-agents|~/\.flow-agents' "$KIRO_DEST" "$BASE_DEST" "$CLAUDE_DEST" "$CODEX_DEST" "$OPENCODE_DEST" "$PI_DEST" --glob '!**/evals/**' >/tmp/installed-bundle-leaks.txt 2>/dev/null; then
345
381
  _fail "installed bundles contain machine-local absolute paths (see /tmp/installed-bundle-leaks.txt)"
346
382
  else
347
383
  _pass "installed bundles are free of machine-local absolute paths"
@@ -359,6 +395,18 @@ else
359
395
  _fail "Codex task dir scaffold missing"
360
396
  fi
361
397
 
398
+ if [[ -f "$OPENCODE_DEST/.flow-agents/.gitkeep" ]]; then
399
+ _pass "opencode task dir scaffold installed"
400
+ else
401
+ _fail "opencode task dir scaffold missing"
402
+ fi
403
+
404
+ if [[ -f "$PI_DEST/.flow-agents/.gitkeep" ]]; then
405
+ _pass "pi task dir scaffold installed"
406
+ else
407
+ _fail "pi task dir scaffold missing"
408
+ fi
409
+
362
410
  if rg -q 'claude-hook-adapter\.js.*stop-goal-fit\.js' "$CLAUDE_DEST/.claude/settings.json" \
363
411
  && rg -q 'claude-hook-adapter\.js.*workflow-steering\.js' "$CLAUDE_DEST/.claude/settings.json" \
364
412
  && rg -q 'claude-hook-adapter\.js.*quality-gate\.js' "$CLAUDE_DEST/.claude/settings.json" \
@@ -418,6 +466,38 @@ else
418
466
  _fail "installed bundles do not wire prompt-submit workflow steering consistently"
419
467
  fi
420
468
 
469
+ if [[ -f "$OPENCODE_DEST/.opencode/plugins/flow-agents.js" ]] && node - "$OPENCODE_DEST/.opencode/plugins/flow-agents.js" <<'NODE'
470
+ const fs = require("node:fs");
471
+ const text = fs.readFileSync(process.argv[2], "utf8");
472
+ if (!text.includes("opencode-hook-adapter.js")) throw new Error("opencode plugin missing opencode-hook-adapter.js");
473
+ if (!text.includes("opencode-telemetry-hook.js")) throw new Error("opencode plugin missing opencode-telemetry-hook.js");
474
+ if (!text.includes("workflow-steering.js")) throw new Error("opencode plugin missing workflow-steering.js");
475
+ if (!text.includes("stop-goal-fit.js")) throw new Error("opencode plugin missing stop-goal-fit.js");
476
+ if (!text.includes("config-protection.js")) throw new Error("opencode plugin missing config-protection.js");
477
+ console.log("ok");
478
+ NODE
479
+ then
480
+ _pass "opencode install wires Flow Agents plugin with policy hooks"
481
+ else
482
+ _fail "opencode install missing or mis-wired Flow Agents plugin"
483
+ fi
484
+
485
+ if [[ -f "$PI_DEST/.pi/extensions/flow-agents.ts" ]] && node - "$PI_DEST/.pi/extensions/flow-agents.ts" <<'NODE'
486
+ const fs = require("node:fs");
487
+ const text = fs.readFileSync(process.argv[2], "utf8");
488
+ if (!text.includes("pi-hook-adapter.js")) throw new Error("pi extension missing pi-hook-adapter.js");
489
+ if (!text.includes("pi-telemetry-hook.js")) throw new Error("pi extension missing pi-telemetry-hook.js");
490
+ if (!text.includes("workflow-steering.js")) throw new Error("pi extension missing workflow-steering.js");
491
+ if (!text.includes("stop-goal-fit.js")) throw new Error("pi extension missing stop-goal-fit.js");
492
+ if (!text.includes("config-protection.js")) throw new Error("pi extension missing config-protection.js");
493
+ console.log("ok");
494
+ NODE
495
+ then
496
+ _pass "pi install wires Flow Agents extension with policy hooks"
497
+ else
498
+ _fail "pi install missing or mis-wired Flow Agents extension"
499
+ fi
500
+
421
501
  KIRO_WORKSPACE="$TMPDIR_EVAL/kiro-workspace"
422
502
  mkdir -p "$KIRO_WORKSPACE"
423
503
  if node - "$CLAUDE_DEST" "$CODEX_DEST" "$KIRO_DEST" "$KIRO_WORKSPACE" <<'NODE'
@@ -504,6 +584,125 @@ else
504
584
  _fail "installed prompt-submit workflow-steering commands did not execute consistently"
505
585
  fi
506
586
 
587
+ # Execute the opencode plugin's workflow-steering command path directly
588
+ OPENCODE_WORKSPACE="$TMPDIR_EVAL/opencode-exec-workspace"
589
+ mkdir -p "$OPENCODE_WORKSPACE"
590
+ if node - "$OPENCODE_DEST" "$OPENCODE_WORKSPACE" <<'NODE'
591
+ const fs = require("node:fs");
592
+ const path = require("node:path");
593
+ const { spawnSync } = require("node:child_process");
594
+ const [opencodeDest, opencodeWorkspace] = process.argv.slice(2);
595
+ const state = {
596
+ schema_version: "1.0",
597
+ task_slug: "opencode-hook-demo",
598
+ status: "not_verified",
599
+ phase: "verification",
600
+ updated_at: "2026-06-01T00:00:00Z",
601
+ next_action: { status: "needs_user", summary: "Opencode hook test.", target_phase: "goal_fit" },
602
+ };
603
+ const critique = {
604
+ schema_version: "1.0",
605
+ task_slug: "opencode-hook-demo",
606
+ status: "fail",
607
+ required: true,
608
+ updated_at: "2026-06-01T00:01:00Z",
609
+ 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." }] }],
610
+ };
611
+ function writeFixture(root) {
612
+ const taskDir = path.join(root, ".flow-agents/opencode-hook-demo");
613
+ fs.mkdirSync(taskDir, { recursive: true });
614
+ fs.writeFileSync(path.join(taskDir, "state.json"), JSON.stringify(state), "utf8");
615
+ fs.writeFileSync(path.join(taskDir, "critique.json"), JSON.stringify(critique), "utf8");
616
+ fs.mkdirSync(path.join(root, "docs"), { recursive: true });
617
+ fs.writeFileSync(path.join(root, "docs/context-map.md"), "# Context Map\n", "utf8");
618
+ }
619
+ function runOpencodeAdapter(bundleDest, cwd) {
620
+ const adapterPath = path.join(bundleDest, "scripts", "hooks", "opencode-hook-adapter.js");
621
+ const payload = JSON.stringify({ hook_event_name: "UserPromptSubmit", cwd });
622
+ const result = spawnSync(process.execPath, [adapterPath, "UserPromptSubmit", "workflow-steering", "workflow-steering.js", "default"], {
623
+ input: payload,
624
+ cwd,
625
+ env: { ...process.env, SA_HOOK_PROFILE: "standard", FLOW_AGENTS_HOOK_RUNTIME: "opencode" },
626
+ encoding: "utf8",
627
+ timeout: 30000,
628
+ });
629
+ if (result.status !== 0) throw new Error("opencode adapter failed: rc=" + result.status + " stderr=" + result.stderr);
630
+ const out = JSON.parse(result.stdout || "{}");
631
+ const ctx = out.context || "";
632
+ if (!ctx.includes("WORKFLOW STATE ATTENTION")) throw new Error("opencode adapter did not emit workflow attention: stdout=" + result.stdout + " stderr=" + result.stderr);
633
+ if (!ctx.includes("STATE: opencode-hook-demo is status:not_verified phase:verification")) throw new Error("opencode adapter missed state guidance: " + ctx);
634
+ if (!ctx.includes("CRITIQUE: required critique is status:fail")) throw new Error("opencode adapter missed critique guidance: " + ctx);
635
+ }
636
+ writeFixture(opencodeWorkspace);
637
+ runOpencodeAdapter(opencodeDest, opencodeWorkspace);
638
+ console.log("ok");
639
+ NODE
640
+ then
641
+ _pass "opencode installed hook adapter executes workflow-steering commands correctly"
642
+ else
643
+ _fail "opencode installed hook adapter did not execute workflow-steering commands correctly"
644
+ fi
645
+
646
+ # Execute the pi extension's hook adapter command path directly
647
+ PI_WORKSPACE="$TMPDIR_EVAL/pi-exec-workspace"
648
+ mkdir -p "$PI_WORKSPACE"
649
+ if node - "$PI_DEST" "$PI_WORKSPACE" <<'NODE'
650
+ const fs = require("node:fs");
651
+ const path = require("node:path");
652
+ const { spawnSync } = require("node:child_process");
653
+ const [piDest, piWorkspace] = process.argv.slice(2);
654
+ const state = {
655
+ schema_version: "1.0",
656
+ task_slug: "pi-hook-demo",
657
+ status: "not_verified",
658
+ phase: "verification",
659
+ updated_at: "2026-06-01T00:00:00Z",
660
+ next_action: { status: "needs_user", summary: "Pi hook test.", target_phase: "goal_fit" },
661
+ };
662
+ const critique = {
663
+ schema_version: "1.0",
664
+ task_slug: "pi-hook-demo",
665
+ status: "fail",
666
+ required: true,
667
+ updated_at: "2026-06-01T00:01:00Z",
668
+ 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." }] }],
669
+ };
670
+ function writeFixture(root) {
671
+ const taskDir = path.join(root, ".flow-agents/pi-hook-demo");
672
+ fs.mkdirSync(taskDir, { recursive: true });
673
+ fs.writeFileSync(path.join(taskDir, "state.json"), JSON.stringify(state), "utf8");
674
+ fs.writeFileSync(path.join(taskDir, "critique.json"), JSON.stringify(critique), "utf8");
675
+ fs.mkdirSync(path.join(root, "docs"), { recursive: true });
676
+ fs.writeFileSync(path.join(root, "docs/context-map.md"), "# Context Map\n", "utf8");
677
+ }
678
+ function runPiAdapter(bundleDest, cwd) {
679
+ const adapterPath = path.join(bundleDest, "scripts", "hooks", "pi-hook-adapter.js");
680
+ const payload = JSON.stringify({ hook_event_name: "UserPromptSubmit", cwd });
681
+ const result = spawnSync(process.execPath, [adapterPath, "UserPromptSubmit", "workflow-steering", "workflow-steering.js", "default"], {
682
+ input: payload,
683
+ cwd,
684
+ env: { ...process.env, SA_HOOK_PROFILE: "standard", FLOW_AGENTS_HOOK_RUNTIME: "pi" },
685
+ encoding: "utf8",
686
+ timeout: 30000,
687
+ });
688
+ if (result.status !== 0) throw new Error("pi adapter failed: rc=" + result.status + " stderr=" + result.stderr);
689
+ const out = JSON.parse(result.stdout || "{}");
690
+ const ctx = out.context || "";
691
+ if (!ctx.includes("WORKFLOW STATE ATTENTION")) throw new Error("pi adapter did not emit workflow attention: stdout=" + result.stdout + " stderr=" + result.stderr);
692
+ if (!ctx.includes("STATE: pi-hook-demo is status:not_verified phase:verification")) throw new Error("pi adapter missed state guidance: " + ctx);
693
+ if (!ctx.includes("CRITIQUE: required critique is status:fail")) throw new Error("pi adapter missed critique guidance: " + ctx);
694
+ }
695
+ writeFixture(piWorkspace);
696
+ runPiAdapter(piDest, piWorkspace);
697
+ console.log("ok");
698
+ NODE
699
+ then
700
+ _pass "pi installed hook adapter executes workflow-steering commands correctly"
701
+ else
702
+ _fail "pi installed hook adapter did not execute workflow-steering commands correctly"
703
+ fi
704
+
705
+
507
706
  echo ""
508
707
  echo "--- Pack Filtering ---"
509
708
  CODEX_AGENTS_DIR="$CODEX_CORE_DEST/.codex/ag""ents"
@@ -533,6 +732,32 @@ else
533
732
  _fail "Codex core-pack install removed unknown user files"
534
733
  fi
535
734
 
735
+ # Pack filtering for opencode
736
+ OPENCODE_AGENTS_DIR="$OPENCODE_CORE_DEST/.opencode/agents"
737
+ if (cd "$ROOT_DIR/dist/opencode" && FLOW_AGENTS_PACKS=core bash install.sh "$OPENCODE_CORE_DEST" >/dev/null); then
738
+ _pass "opencode core-pack filtered install succeeded"
739
+ else
740
+ _fail "opencode core-pack filtered install failed"
741
+ fi
742
+
743
+ if [[ -d "$OPENCODE_AGENTS_DIR/tool-planner.md" ]] || [[ -f "$OPENCODE_AGENTS_DIR/tool-planner.md" ]]; then
744
+ _pass "opencode core-pack install keeps core agents"
745
+ else
746
+ _fail "opencode core-pack agent filtering failed (tool-planner.md missing)"
747
+ fi
748
+
749
+ if [[ -d "$OPENCODE_CORE_DEST/.opencode/skills/plan-work" && ! -d "$OPENCODE_CORE_DEST/.opencode/skills/deliver" ]]; then
750
+ _pass "opencode core-pack install keeps core skills and prunes optional skills"
751
+ else
752
+ _fail "opencode core-pack skill filtering failed"
753
+ fi
754
+
755
+ if [[ -f "$OPENCODE_CORE_DEST/.flow-agents/installed-packs.json" ]]; then
756
+ _pass "opencode core-pack install records selected packs"
757
+ else
758
+ _fail "opencode core-pack install did not record selected packs"
759
+ fi
760
+
536
761
  echo ""
537
762
  echo "==========================="
538
763
  total=$((pass + fail))