pantheon-opencode 1.4.3 → 1.5.0-beta.2

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 (99) hide show
  1. package/.pantheon/code-mode/README.md +93 -0
  2. package/.pantheon/code-mode/checkpoint-session.py +7 -0
  3. package/.pantheon/code-mode/checkpoint-session.sh +11 -0
  4. package/.pantheon/code-mode/checkpoint_session.py +481 -0
  5. package/.pantheon/code-mode/compress-inline.py +450 -0
  6. package/.pantheon/code-mode/eval-llm-judge.py +255 -0
  7. package/.pantheon/code-mode/eval-monte-carlo.py +368 -0
  8. package/.pantheon/code-mode/eval-run.py +197 -0
  9. package/.pantheon/code-mode/eval-static.py +321 -0
  10. package/.pantheon/code-mode/example-sync.sh +5 -0
  11. package/.pantheon/code-mode/session-end-save.py +186 -0
  12. package/.pantheon/code-mode/session-end-save.sh +18 -0
  13. package/.pantheon/code-mode/test-allowed.sh +5 -0
  14. package/.pantheon/code-mode/test-auto-continue-checkpoint.sh +179 -0
  15. package/.pantheon/code-mode/test-env.sh +7 -0
  16. package/.pantheon/code-mode/test-prlimit.sh +9 -0
  17. package/.pantheon/code-mode/test-timeout.sh +6 -0
  18. package/README.md +161 -1101
  19. package/README.pt-BR.md +222 -0
  20. package/bin/pantheon-init.mjs +30 -11
  21. package/opencode.json +1 -0
  22. package/package.json +14 -3
  23. package/plugin.json +15 -0
  24. package/scripts/_pantheon_paths.py +48 -7
  25. package/scripts/ci-validate-yaml.py +7 -2
  26. package/scripts/code_mode_server.py +33 -2
  27. package/scripts/cost.mjs +30 -14
  28. package/scripts/doctor.mjs +135 -127
  29. package/scripts/generate-preset-docs.mjs +127 -66
  30. package/scripts/generate-prompts.sh +2 -2
  31. package/scripts/install/cli-ui.mjs +8 -1
  32. package/scripts/install/config-migration.mjs +401 -0
  33. package/scripts/install/health-check.mjs +3 -3
  34. package/scripts/install/model-picker.mjs +72 -23
  35. package/scripts/install/opencode-version.mjs +47 -0
  36. package/scripts/install/opencode.mjs +261 -88
  37. package/scripts/install/plugin.mjs +112 -42
  38. package/scripts/install/shared.mjs +13 -158
  39. package/scripts/install-mcp.mjs +44 -232
  40. package/scripts/mcp_persistence_server.py +382 -16
  41. package/scripts/mcp_resources_server.py +144 -40
  42. package/scripts/memory_mcp_server.py +2 -2
  43. package/scripts/probe-context-rehydrate.mjs +240 -0
  44. package/scripts/probe-pantheon-cost.mjs +304 -0
  45. package/scripts/recover-zenodo-deposition.mjs +146 -0
  46. package/scripts/sync-tui.mjs +32 -7
  47. package/scripts/test-opencode-v1-v2-sandbox.sh +944 -0
  48. package/scripts/uninstall.mjs +298 -954
  49. package/scripts/validate-zenodo-release.mjs +154 -0
  50. package/scripts/zenodo-artifact.mjs +21 -0
  51. package/scripts/zenodo-release-state.mjs +53 -0
  52. package/src/agents/zeus.md +25 -14
  53. package/src/mcp/_pantheon_paths.py +48 -7
  54. package/src/mcp/code_mode_server.py +33 -2
  55. package/src/mcp/eval_store.py +1 -1
  56. package/src/mcp/mcp_persistence_server.py +382 -16
  57. package/src/mcp/mcp_resources_server.py +144 -40
  58. package/src/mcp/pantheon_vision_server.py +1 -1
  59. package/src/mcp/toon_codec.py +491 -0
  60. package/src/pantheon/background-job-board.ts +67 -1
  61. package/src/pantheon/cost-command.ts +60 -39
  62. package/src/pantheon/delegate-manager.ts +669 -0
  63. package/src/pantheon/delegation-classifier.ts +307 -0
  64. package/src/pantheon/delegation-enforce.ts +13 -12
  65. package/src/pantheon/delegation-finalize.ts +374 -0
  66. package/src/pantheon/delegation-notify.ts +212 -0
  67. package/src/pantheon/delegation-toast.ts +73 -0
  68. package/src/pantheon/delegation.ts +1120 -0
  69. package/src/pantheon/goal-loop.ts +2 -2
  70. package/src/pantheon/goal-store.ts +1 -1
  71. package/src/pantheon/hashline/tool.ts +2 -2
  72. package/src/pantheon/model-command.ts +2 -1
  73. package/src/pantheon/permission-eval.ts +284 -0
  74. package/src/pantheon/step-cap.ts +110 -0
  75. package/src/pantheon/token-opt.ts +781 -0
  76. package/src/pantheon/toon-codec.ts +481 -0
  77. package/src/pantheon/tui-version-check.ts +1 -3
  78. package/src/pantheon/v2-bridge.ts +129 -0
  79. package/src/pantheon/v2-events.ts +131 -0
  80. package/src/pantheon/v2-hooks.ts +287 -0
  81. package/src/pantheon/v2-tool-definitions.ts +331 -0
  82. package/src/pantheon/zeus-delegate-with-retry.ts +406 -0
  83. package/src/plugin-v2/index.ts +21 -0
  84. package/src/plugin-v2.ts +824 -0
  85. package/src/plugin.ts +236 -42
  86. package/src/plugins/tui/dist/server.d.ts +1 -1
  87. package/src/plugins/tui/dist/server.d.ts.map +1 -1
  88. package/src/plugins/tui/dist/server.js +1 -1
  89. package/src/plugins/tui/dist/server.js.map +1 -1
  90. package/src/plugins/tui/dist/tui.d.ts +10 -7
  91. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  92. package/src/plugins/tui/dist/tui.js +11 -8
  93. package/src/plugins/tui/dist/tui.js.map +1 -1
  94. package/src/plugins/tui/dist/tui.tsx +22 -16
  95. package/src/plugins/tui/package-lock.json +51 -51
  96. package/src/plugins/tui/package.json +7 -7
  97. package/src/plugins/tui/src/index.tsx +28 -24
  98. package/src/skills/README.md +0 -2
  99. package/src/pantheon/tool-context.ts +0 -41
@@ -4,16 +4,15 @@
4
4
  * doctor.mjs — Pantheon health check CLI tool
5
5
  *
6
6
  * Validates that a Pantheon installation is working correctly:
7
- * - Agent file presence across canonical → platform
7
+ * - Canonical agent file presence
8
8
  * - MCP configuration (gh_grep, Context7, etc.)
9
9
  * - Permission/frontmatter mismatches
10
- * - Sync freshness
10
+ * - OpenCode artifact freshness
11
11
  * - Git status
12
12
  *
13
13
  * Usage:
14
14
  * node scripts/doctor.mjs # auto-detect, cwd
15
15
  * node scripts/doctor.mjs --target /path/to/project
16
- * node scripts/doctor.mjs --platform opencode
17
16
  * node scripts/doctor.mjs --fix # attempt auto-fixes
18
17
  * node scripts/doctor.mjs --verbose
19
18
  * node scripts/doctor.mjs --help
@@ -23,7 +22,7 @@
23
22
  */
24
23
 
25
24
  import { spawn as spawnAsync, spawnSync } from 'node:child_process'
26
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
25
+ import { existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs'
27
26
  import { homedir } from 'node:os'
28
27
  import { basename, dirname, join, resolve } from 'node:path'
29
28
  import { fileURLToPath } from 'node:url'
@@ -35,15 +34,6 @@ import { fileURLToPath } from 'node:url'
35
34
  const __dirname = dirname(fileURLToPath(import.meta.url))
36
35
  const ROOT = join(__dirname, '..')
37
36
  const AGENTS_DIR = join(ROOT, 'src', 'agents')
38
- const PLATFORM_DIR = join(ROOT, 'platform')
39
-
40
- // All known platforms (must match platform/ subdirectories with adapter.json)
41
- const _ALL_PLATFORMS = ['opencode']
42
-
43
- const PLATFORM_LABELS = {
44
- opencode: 'OpenCode',
45
- }
46
-
47
37
  // ---------------------------------------------------------------------------
48
38
  // State
49
39
  // ---------------------------------------------------------------------------
@@ -73,9 +63,6 @@ function parseArgs(argv) {
73
63
  case '--target':
74
64
  args.target = argv[++i]
75
65
  break
76
- case '--platform':
77
- args.platform = argv[++i]
78
- break
79
66
  case '--fix':
80
67
  args.fix = true
81
68
  break
@@ -157,19 +144,18 @@ function showHelp() {
157
144
  Usage:
158
145
  node scripts/doctor.mjs auto-detect, cwd
159
146
  node scripts/doctor.mjs --target /path/to/project target project
160
- node scripts/doctor.mjs --platform opencode specific platform
161
147
  node scripts/doctor.mjs --profile global|lite|sandbox profile policy
162
148
  node scripts/doctor.mjs --fix attempt auto-fixes
163
149
  node scripts/doctor.mjs --verbose detailed output
164
150
  node scripts/doctor.mjs --help show this help
165
151
 
166
152
  Checks:
167
- A. Agent Files — canonical vs platform sync
153
+ A. Agent Files — canonical agent files
168
154
  B. MCP Configuration — opencode.json MCP settings (Config layer)
169
155
  B.5 MCP Spawn Paths — local MCP exe/args resolve to existing files
170
156
  B.6 MCP Runtime Smoke — spawn each local MCP + JSON-RPC initialize handshake
171
157
  C. Permission Checks — frontmatter validation
172
- D. Sync Status — npm run sync freshness
158
+ D. OpenCode Status — OpenCode artifact layout
173
159
  E. Git Status — uncommitted changes
174
160
  F. Runtime Layer — venv python + pinned pip dependencies
175
161
 
@@ -256,7 +242,7 @@ function spawn(prog, args, cwd = null) {
256
242
  // Check A: Agent Files
257
243
  // ---------------------------------------------------------------------------
258
244
 
259
- function checkAgentFiles(args) {
245
+ function checkAgentFiles() {
260
246
  section('A. Agent Files')
261
247
 
262
248
  const canonical = getCanonicalAgentNames()
@@ -265,86 +251,6 @@ function checkAgentFiles(args) {
265
251
  return
266
252
  }
267
253
  pass(`${canonical.length} canonical agents found in agents/`)
268
-
269
- // Determine which platforms to check
270
- if (!existsSync(PLATFORM_DIR)) {
271
- info(
272
- `Platform component not distributed in this package — platform check skipped (${args.profile} profile)`,
273
- )
274
- return
275
- }
276
- const platformDirs = readdirSync(PLATFORM_DIR, { withFileTypes: true })
277
- .filter((d) => d.isDirectory() && d.name !== '_template' && d.name !== 'plans')
278
- .map((d) => d.name)
279
- .filter((name) => !args.platform || name === args.platform)
280
-
281
- if (platformDirs.length === 0) {
282
- info(
283
- `No platform directories found in platform/ — platform check skipped (${args.profile} profile)`,
284
- )
285
- return
286
- }
287
-
288
- for (const platform of platformDirs) {
289
- const adapterPath = join(PLATFORM_DIR, platform, 'adapter.json')
290
- if (!existsSync(adapterPath)) {
291
- info(`${platform}: no adapter.json — skipping`)
292
- continue
293
- }
294
-
295
- const adapter = readJson(adapterPath)
296
- if (!adapter) {
297
- warn(`${platform}: adapter.json parse error — skipping`)
298
- continue
299
- }
300
-
301
- const label = PLATFORM_LABELS[platform] ?? platform
302
- const outputDir = adapter.outputDir ?? 'agents'
303
- const ext = adapter.fileExtension ?? '.md'
304
- const agentDir = join(PLATFORM_DIR, platform, outputDir)
305
-
306
- if (!existsSync(agentDir)) {
307
- if (adapter.skipAgentSync) {
308
- info(`${label}: agent sync skipped (skipAgentSync) — n/a`)
309
- continue
310
- }
311
- warn(`${label}: output directory missing: ${agentDir}`)
312
- continue
313
- }
314
-
315
- if (adapter.skipAgentSync) {
316
- info(`${label}: agent sync skipped (skipAgentSync) — skipping agent check`)
317
- continue
318
- }
319
-
320
- const platformFiles = readdirSync(agentDir)
321
- .filter((f) => statSync(join(agentDir, f)).isFile() && f.endsWith(ext))
322
- .map((f) => f.replace(new RegExp(`${ext}$`), ''))
323
-
324
- // Check every canonical agent exists in platform output
325
- let allPresent = true
326
- const missing = []
327
- for (const agent of canonical) {
328
- if (!platformFiles.includes(agent)) {
329
- missing.push(agent)
330
- allPresent = false
331
- }
332
- }
333
-
334
- // Detect phantom agents (in platform but not canonical — stale)
335
- const phantom = platformFiles.filter((f) => !canonical.includes(f))
336
-
337
- if (allPresent && phantom.length === 0) {
338
- pass(`${label}: all ${canonical.length} agents present`)
339
- } else {
340
- if (missing.length > 0) {
341
- error(`${label}: ${missing.length} canonical agent(s) missing: ${missing.join(', ')}`)
342
- }
343
- if (phantom.length > 0) {
344
- warn(`${label}: ${phantom.length} stale agent(s) in output: ${phantom.join(', ')}`)
345
- }
346
- }
347
- }
348
254
  }
349
255
 
350
256
  // ---------------------------------------------------------------------------
@@ -364,10 +270,12 @@ export function resolveOpenCodeConfigDir(env = process.env) {
364
270
  * @returns {{label:string, path:string, data:object}[]}
365
271
  */
366
272
  export function collectMcpConfigs(args) {
367
- const candidates = [
368
- { label: 'project root', path: join(args.target, 'opencode.json') },
369
- { label: 'repository root', path: join(ROOT, 'opencode.json') },
370
- ]
273
+ const candidates = [{ label: 'project root', path: join(args.target, 'opencode.json') }]
274
+ // The package's own template is useful when doctor is run from the
275
+ // repository, but must not be treated as a user's config from an installed
276
+ // package. Otherwise every global/project sandbox reports false MCP errors.
277
+ if (resolve(args.target) === resolve(ROOT))
278
+ candidates.push({ label: 'repository root', path: join(ROOT, 'opencode.json') })
371
279
 
372
280
  // OpenCode resolves user configuration independently of cwd. Keep the
373
281
  // project-local .opencode config for repository checks, but always include
@@ -495,11 +403,13 @@ function checkMcpConfig(args) {
495
403
  )
496
404
  continue
497
405
  }
498
- if (!existsSync(exe)) {
499
- error(`${cfg.label}: MCP "${name}" executable does not exist: ${exe}`)
406
+ const effectiveCwd = resolveMcpCwd(mcpEntry, cfg.path)
407
+ const effectiveExe = exe.startsWith('/') ? exe : resolve(effectiveCwd, exe)
408
+ if (!existsSync(effectiveExe)) {
409
+ error(`${cfg.label}: MCP "${name}" executable does not exist: ${effectiveExe}`)
500
410
  continue
501
411
  }
502
- const deadArgs = cmd.slice(1).filter((a) => a.startsWith('/') && !existsSync(a))
412
+ const deadArgs = resolveMcpScriptPaths(mcpEntry, cfg.path).filter((path) => !existsSync(path))
503
413
  if (deadArgs.length > 0) {
504
414
  error(`${cfg.label}: MCP "${name}" has missing script path(s): ${deadArgs.join(', ')}`)
505
415
  }
@@ -622,7 +532,11 @@ async function checkMcpRuntimeSmoke(args) {
622
532
  for (const [name, entry] of Object.entries(cfg.data.mcp ?? {})) {
623
533
  if (!entry || entry.type !== 'local') continue
624
534
  if (!Array.isArray(entry.command) || entry.command.length === 0) continue
625
- local.push({ name, command: entry.command, cwd: dirname(cfg.path), source: cfg.label })
535
+ const cwd = resolveMcpCwd(entry, cfg.path)
536
+ const command = entry.command.map((part, index) =>
537
+ index === 0 || part.startsWith('-') || part.startsWith('/') ? part : resolve(cwd, part),
538
+ )
539
+ local.push({ name, command, cwd, source: cfg.label })
626
540
  }
627
541
  }
628
542
 
@@ -702,8 +616,7 @@ function checkPermissionMismatches(args) {
702
616
  }
703
617
 
704
618
  // Determine which agent directories to check
705
- // Validation script checks ~/.config/opencode/agents and platform/opencode/agents
706
- // Let's run it regardless
619
+ // Validate the canonical agents and the OpenCode installation independently.
707
620
  info('Running validate-agent-frontmatter.py...')
708
621
 
709
622
  const result = spawn('python3', [validator])
@@ -789,14 +702,35 @@ function checkPermissionMismatches(args) {
789
702
  }
790
703
 
791
704
  // ---------------------------------------------------------------------------
792
- // Check D: Sync Status
705
+ // Check D: OpenCode Status
793
706
  // ---------------------------------------------------------------------------
794
707
 
795
- function checkSyncStatus(_args) {
796
- section('D. Sync Status')
797
-
798
- // Platform sync removed in v1.0 (OpenCode-only)
799
- info('Sync check skipped — platform sync was removed in v1.0')
708
+ function checkSyncStatus(args) {
709
+ section('D. OpenCode Status')
710
+ const agentsDir = join(args.target, '.opencode', 'agents')
711
+ if (existsSync(agentsDir)) {
712
+ pass('OpenCode agents directory is present')
713
+ // Content gate: validate pantheon://agents CONTENT (zeus/hermes present),
714
+ // not just connectivity/directory presence.
715
+ const agentFiles = readdirSync(agentsDir).filter((f) => f.endsWith('.md'))
716
+ if (agentFiles.length === 0) {
717
+ warn('OpenCode agents directory is empty — pantheon://agents will serve no agents')
718
+ } else {
719
+ const names = new Set(agentFiles.map((f) => f.replace(/\.md$/, '').toLowerCase()))
720
+ const missingCanonical = ['zeus', 'hermes'].filter((n) => !names.has(n))
721
+ if (missingCanonical.length > 0) {
722
+ error(
723
+ `pantheon://agents content check failed — missing canonical agents in ${agentsDir}: ${missingCanonical.join(', ')}`,
724
+ )
725
+ } else {
726
+ pass(
727
+ `pantheon://agents content validated (zeus, hermes + ${agentFiles.length - 2} more agents)`,
728
+ )
729
+ }
730
+ }
731
+ } else {
732
+ info('OpenCode agents directory not present — project may use global agents')
733
+ }
800
734
  }
801
735
 
802
736
  // ---------------------------------------------------------------------------
@@ -813,8 +747,16 @@ function checkGitStatus(args) {
813
747
  return
814
748
  }
815
749
 
816
- // Check for uncommitted changes in agents/ and platform/
817
- const gitStatus = spawn('git', ['status', '--porcelain', '--', 'agents/', 'platform/'])
750
+ // Check the repository source and OpenCode configuration paths.
751
+ const gitStatus = spawn('git', [
752
+ 'status',
753
+ '--porcelain',
754
+ '--',
755
+ 'src/agents/',
756
+ 'src/instructions/',
757
+ '.opencode/',
758
+ 'opencode.json',
759
+ ])
818
760
 
819
761
  if (gitStatus.status !== 0) {
820
762
  warn('Could not check git status')
@@ -824,7 +766,7 @@ function checkGitStatus(args) {
824
766
  const lines = gitStatus.stdout ? gitStatus.stdout.split('\n').filter(Boolean) : []
825
767
 
826
768
  if (lines.length === 0) {
827
- pass('No uncommitted changes in agents/ or platform/')
769
+ pass('No uncommitted changes in Pantheon/OpenCode paths')
828
770
  return
829
771
  }
830
772
 
@@ -851,7 +793,7 @@ function checkGitStatus(args) {
851
793
  if (deleted.length > 0) details.push(`${deleted.length} deleted`)
852
794
  if (untracked.length > 0) details.push(`${untracked.length} untracked`)
853
795
 
854
- warn(`${total} uncommitted change(s) in agents/ or platform/ (${details.join(', ')})`)
796
+ warn(`${total} uncommitted change(s) in Pantheon/OpenCode paths (${details.join(', ')})`)
855
797
  if (args.verbose) {
856
798
  for (const f of modified.slice(0, 10)) {
857
799
  console.log(` M ${f}`)
@@ -963,8 +905,7 @@ function checkRequirementLine(line, installed) {
963
905
  function checkVenvLayer(args) {
964
906
  section('F. Runtime Layer — Venv, Python & Dependencies')
965
907
 
966
- const configDir = resolveOpenCodeConfigDir()
967
- const venvPython = join(configDir, '.venv', 'bin', 'python3')
908
+ const venvPython = resolveRuntimePython(args)
968
909
 
969
910
  if (!existsSync(venvPython)) {
970
911
  if (args.profile === 'lite') {
@@ -1013,6 +954,70 @@ function checkVenvLayer(args) {
1013
954
  }
1014
955
  }
1015
956
 
957
+ // ---------------------------------------------------------------------------
958
+ // Check F.2: Code-Mode Scripts Directory
959
+ // ---------------------------------------------------------------------------
960
+
961
+ /**
962
+ * Verify the code-mode scripts directory exists for the resolved runtime
963
+ * root (project → <target>/.opencode, otherwise the global config dir).
964
+ * When absent, create it with a warning so the pantheon-code-mode MCP
965
+ * server has a stable home (mirrors the installer's runtime seeding).
966
+ */
967
+ export function checkCodeModeDir(args) {
968
+ section('F.2 Code-Mode Scripts')
969
+ const target = resolve(args.target)
970
+ const isProject = existsSync(join(target, '.opencode'))
971
+ const runtimeRoot = isProject ? target : resolveOpenCodeConfigDir(args.env ?? process.env)
972
+ const codeModeDir = isProject
973
+ ? join(runtimeRoot, '.opencode', '.pantheon', 'code-mode')
974
+ : join(runtimeRoot, '.pantheon', 'code-mode')
975
+ if (existsSync(codeModeDir)) {
976
+ pass(`Code-mode scripts directory exists: ${codeModeDir}`)
977
+ return codeModeDir
978
+ }
979
+ warn(`Code-mode scripts directory missing — creating empty ${codeModeDir}`)
980
+ try {
981
+ mkdirSync(codeModeDir, { recursive: true })
982
+ info(`Created ${codeModeDir} (run \`pantheon-opencode init --components runtime\` to seed it)`)
983
+ } catch (err) {
984
+ error(`Cannot create code-mode directory ${codeModeDir}: ${err.message}`)
985
+ }
986
+ return codeModeDir
987
+ }
988
+
989
+ /** Resolve the venv created by init for either a project or global install. */
990
+ export function resolveRuntimePython(args) {
991
+ const target = resolve(args.target)
992
+ const isProject = existsSync(join(target, '.opencode'))
993
+ const runtimeRoot = isProject ? target : resolveOpenCodeConfigDir(args.env ?? process.env)
994
+ return join(runtimeRoot, '.venv', 'bin', 'python3')
995
+ }
996
+
997
+ /** Return relative script arguments as absolute paths using an MCP entry cwd. */
998
+ export function resolveMcpScriptPaths(entry, configPath = '') {
999
+ const cwd = resolveMcpCwd(entry, configPath)
1000
+ if (!Array.isArray(entry?.command)) return []
1001
+ return entry.command
1002
+ .slice(1)
1003
+ .filter(
1004
+ (arg) =>
1005
+ typeof arg === 'string' &&
1006
+ !arg.startsWith('-') &&
1007
+ (arg.endsWith('.py') || arg.includes('/')),
1008
+ )
1009
+ .map((arg) => (arg.startsWith('/') ? arg : resolve(cwd, arg)))
1010
+ }
1011
+
1012
+ /** Resolve an MCP entry's cwd relative to the config file, as OpenCode does. */
1013
+ export function resolveMcpCwd(entry, configPath = '') {
1014
+ const configured = entry?.cwd
1015
+ if (typeof configured === 'string' && configured !== '') {
1016
+ return configured.startsWith('/') ? configured : resolve(dirname(configPath), configured)
1017
+ }
1018
+ return dirname(configPath)
1019
+ }
1020
+
1016
1021
  // ---------------------------------------------------------------------------
1017
1022
  // Check G: AGENTS.md freshness
1018
1023
  // ---------------------------------------------------------------------------
@@ -1285,16 +1290,18 @@ async function main() {
1285
1290
  process.exit(2)
1286
1291
  }
1287
1292
 
1288
- // Verify it looks like a Pantheon project (has agents/ and platform/)
1289
- const agentsOk = existsSync(join(args.target, 'agents'))
1290
- const platformOk = existsSync(join(args.target, 'platform'))
1293
+ // Verify it looks like a Pantheon project or an OpenCode install target.
1294
+ const agentsOk = existsSync(join(args.target, 'src', 'agents'))
1295
+ const opencodeOk = existsSync(join(args.target, '.opencode'))
1291
1296
 
1292
- if (!agentsOk && !platformOk) {
1297
+ if (!agentsOk && !opencodeOk) {
1293
1298
  // Maybe they pointed at a project root that isn't Pantheon
1294
1299
  // Check if this is a Pantheon install target (has opencode.json with mcp)
1295
1300
  const opencodeJson = readJson(join(args.target, 'opencode.json'))
1296
1301
  if (!opencodeJson) {
1297
- warn(`Target does not appear to be a Pantheon project (no agents/ or platform/ directory)`)
1302
+ warn(
1303
+ 'Target does not appear to be a Pantheon project (no src/agents/ or .opencode/ directory)',
1304
+ )
1298
1305
  }
1299
1306
  }
1300
1307
 
@@ -1306,6 +1313,7 @@ async function main() {
1306
1313
  checkAgentFiles(args)
1307
1314
  checkMcpConfig(args)
1308
1315
  checkVenvLayer(args)
1316
+ checkCodeModeDir(args)
1309
1317
  await checkMcpRuntimeSmoke(args)
1310
1318
  checkPermissionMismatches(args)
1311
1319
  checkSyncStatus(args)