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.
- package/.pantheon/code-mode/README.md +93 -0
- package/.pantheon/code-mode/checkpoint-session.py +7 -0
- package/.pantheon/code-mode/checkpoint-session.sh +11 -0
- package/.pantheon/code-mode/checkpoint_session.py +481 -0
- package/.pantheon/code-mode/compress-inline.py +450 -0
- package/.pantheon/code-mode/eval-llm-judge.py +255 -0
- package/.pantheon/code-mode/eval-monte-carlo.py +368 -0
- package/.pantheon/code-mode/eval-run.py +197 -0
- package/.pantheon/code-mode/eval-static.py +321 -0
- package/.pantheon/code-mode/example-sync.sh +5 -0
- package/.pantheon/code-mode/session-end-save.py +186 -0
- package/.pantheon/code-mode/session-end-save.sh +18 -0
- package/.pantheon/code-mode/test-allowed.sh +5 -0
- package/.pantheon/code-mode/test-auto-continue-checkpoint.sh +179 -0
- package/.pantheon/code-mode/test-env.sh +7 -0
- package/.pantheon/code-mode/test-prlimit.sh +9 -0
- package/.pantheon/code-mode/test-timeout.sh +6 -0
- package/README.md +161 -1101
- package/README.pt-BR.md +222 -0
- package/bin/pantheon-init.mjs +30 -11
- package/opencode.json +1 -0
- package/package.json +14 -3
- package/plugin.json +15 -0
- package/scripts/_pantheon_paths.py +48 -7
- package/scripts/ci-validate-yaml.py +7 -2
- package/scripts/code_mode_server.py +33 -2
- package/scripts/cost.mjs +30 -14
- package/scripts/doctor.mjs +135 -127
- package/scripts/generate-preset-docs.mjs +127 -66
- package/scripts/generate-prompts.sh +2 -2
- package/scripts/install/cli-ui.mjs +8 -1
- package/scripts/install/config-migration.mjs +401 -0
- package/scripts/install/health-check.mjs +3 -3
- package/scripts/install/model-picker.mjs +72 -23
- package/scripts/install/opencode-version.mjs +47 -0
- package/scripts/install/opencode.mjs +261 -88
- package/scripts/install/plugin.mjs +112 -42
- package/scripts/install/shared.mjs +13 -158
- package/scripts/install-mcp.mjs +44 -232
- package/scripts/mcp_persistence_server.py +382 -16
- package/scripts/mcp_resources_server.py +144 -40
- package/scripts/memory_mcp_server.py +2 -2
- package/scripts/probe-context-rehydrate.mjs +240 -0
- package/scripts/probe-pantheon-cost.mjs +304 -0
- package/scripts/recover-zenodo-deposition.mjs +146 -0
- package/scripts/sync-tui.mjs +32 -7
- package/scripts/test-opencode-v1-v2-sandbox.sh +944 -0
- package/scripts/uninstall.mjs +298 -954
- package/scripts/validate-zenodo-release.mjs +154 -0
- package/scripts/zenodo-artifact.mjs +21 -0
- package/scripts/zenodo-release-state.mjs +53 -0
- package/src/agents/zeus.md +25 -14
- package/src/mcp/_pantheon_paths.py +48 -7
- package/src/mcp/code_mode_server.py +33 -2
- package/src/mcp/eval_store.py +1 -1
- package/src/mcp/mcp_persistence_server.py +382 -16
- package/src/mcp/mcp_resources_server.py +144 -40
- package/src/mcp/pantheon_vision_server.py +1 -1
- package/src/mcp/toon_codec.py +491 -0
- package/src/pantheon/background-job-board.ts +67 -1
- package/src/pantheon/cost-command.ts +60 -39
- package/src/pantheon/delegate-manager.ts +669 -0
- package/src/pantheon/delegation-classifier.ts +307 -0
- package/src/pantheon/delegation-enforce.ts +13 -12
- package/src/pantheon/delegation-finalize.ts +374 -0
- package/src/pantheon/delegation-notify.ts +212 -0
- package/src/pantheon/delegation-toast.ts +73 -0
- package/src/pantheon/delegation.ts +1120 -0
- package/src/pantheon/goal-loop.ts +2 -2
- package/src/pantheon/goal-store.ts +1 -1
- package/src/pantheon/hashline/tool.ts +2 -2
- package/src/pantheon/model-command.ts +2 -1
- package/src/pantheon/permission-eval.ts +284 -0
- package/src/pantheon/step-cap.ts +110 -0
- package/src/pantheon/token-opt.ts +781 -0
- package/src/pantheon/toon-codec.ts +481 -0
- package/src/pantheon/tui-version-check.ts +1 -3
- package/src/pantheon/v2-bridge.ts +129 -0
- package/src/pantheon/v2-events.ts +131 -0
- package/src/pantheon/v2-hooks.ts +287 -0
- package/src/pantheon/v2-tool-definitions.ts +331 -0
- package/src/pantheon/zeus-delegate-with-retry.ts +406 -0
- package/src/plugin-v2/index.ts +21 -0
- package/src/plugin-v2.ts +824 -0
- package/src/plugin.ts +236 -42
- package/src/plugins/tui/dist/server.d.ts +1 -1
- package/src/plugins/tui/dist/server.d.ts.map +1 -1
- package/src/plugins/tui/dist/server.js +1 -1
- package/src/plugins/tui/dist/server.js.map +1 -1
- package/src/plugins/tui/dist/tui.d.ts +10 -7
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +11 -8
- package/src/plugins/tui/dist/tui.js.map +1 -1
- package/src/plugins/tui/dist/tui.tsx +22 -16
- package/src/plugins/tui/package-lock.json +51 -51
- package/src/plugins/tui/package.json +7 -7
- package/src/plugins/tui/src/index.tsx +28 -24
- package/src/skills/README.md +0 -2
- package/src/pantheon/tool-context.ts +0 -41
package/scripts/doctor.mjs
CHANGED
|
@@ -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
|
-
* -
|
|
7
|
+
* - Canonical agent file presence
|
|
8
8
|
* - MCP configuration (gh_grep, Context7, etc.)
|
|
9
9
|
* - Permission/frontmatter mismatches
|
|
10
|
-
* -
|
|
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
|
|
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
|
|
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.
|
|
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(
|
|
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
|
-
|
|
369
|
-
|
|
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
|
-
|
|
499
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
//
|
|
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:
|
|
705
|
+
// Check D: OpenCode Status
|
|
793
706
|
// ---------------------------------------------------------------------------
|
|
794
707
|
|
|
795
|
-
function checkSyncStatus(
|
|
796
|
-
section('D.
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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
|
|
817
|
-
const gitStatus = spawn('git', [
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1289
|
-
const agentsOk = existsSync(join(args.target, 'agents'))
|
|
1290
|
-
const
|
|
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 && !
|
|
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(
|
|
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)
|