pantheon-opencode 1.0.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 (128) hide show
  1. package/AGENTS.md +37 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1013 -0
  4. package/bin/pantheon-init.mjs +183 -0
  5. package/commands/pantheon-audit.md +25 -0
  6. package/commands/pantheon-bg.md +10 -0
  7. package/commands/pantheon-consolidate.md +11 -0
  8. package/commands/pantheon-deepwork.md +128 -0
  9. package/commands/pantheon-doc.md +10 -0
  10. package/commands/pantheon-focus.md +9 -0
  11. package/commands/pantheon-forget.md +58 -0
  12. package/commands/pantheon-hash.md +11 -0
  13. package/commands/pantheon-optimize.md +79 -0
  14. package/commands/pantheon-remember.md +44 -0
  15. package/commands/pantheon-search.md +48 -0
  16. package/commands/pantheon-status.md +71 -0
  17. package/commands/pantheon-todo.md +11 -0
  18. package/commands/pantheon.md +49 -0
  19. package/docs/AGENT-MCP.md +194 -0
  20. package/docs/ARCHITECTURE.md +384 -0
  21. package/docs/BRANCH-PROTECTION.md +142 -0
  22. package/docs/INDEX.md +81 -0
  23. package/docs/INSTALLATION.md +217 -0
  24. package/docs/MCP.md +238 -0
  25. package/docs/MEMORY.md +471 -0
  26. package/docs/MIGRATION-MEMORY-BANK.md +139 -0
  27. package/docs/PLATFORMS.md +5 -0
  28. package/docs/QUICKSTART.md +49 -0
  29. package/docs/README.md +18 -0
  30. package/docs/RELEASING.md +256 -0
  31. package/docs/SETUP.md +5 -0
  32. package/docs/UPGRADING.md +41 -0
  33. package/docs/mcp-recommendations.md +439 -0
  34. package/docs/mcp-tools.md +156 -0
  35. package/docs/mcp-user-guide.md +204 -0
  36. package/docs/persistence-mcp.md +111 -0
  37. package/package.json +72 -0
  38. package/pantheon.schema.json +158 -0
  39. package/scripts/__init__.py +0 -0
  40. package/scripts/_pantheon_paths.py +68 -0
  41. package/scripts/check-version-consistency.sh +23 -0
  42. package/scripts/code_mode_server.py +202 -0
  43. package/scripts/doctor.mjs +763 -0
  44. package/scripts/generate-prompts.sh +222 -0
  45. package/scripts/generate-routing-docs.mjs +104 -0
  46. package/scripts/hash_verify.py +192 -0
  47. package/scripts/init-pantheon-mcp.sh +118 -0
  48. package/scripts/install/agents-md.mjs +214 -0
  49. package/scripts/install/health-check.mjs +196 -0
  50. package/scripts/install/migrate.mjs +209 -0
  51. package/scripts/install/opencode.mjs +645 -0
  52. package/scripts/install/shared.mjs +655 -0
  53. package/scripts/install/venv.mjs +116 -0
  54. package/scripts/install-mcp.mjs +885 -0
  55. package/scripts/install.mjs +26 -0
  56. package/scripts/manifest.mjs +622 -0
  57. package/scripts/mcp_persistence_server.py +459 -0
  58. package/scripts/mcp_resources_server.py +206 -0
  59. package/scripts/memory_cache.py +78 -0
  60. package/scripts/memory_mcp_server.py +605 -0
  61. package/scripts/paths.py +64 -0
  62. package/scripts/prune_context.py +72 -0
  63. package/scripts/release-bundle.mjs +109 -0
  64. package/scripts/scrub-secrets.py +282 -0
  65. package/scripts/scrub_secrets.py +281 -0
  66. package/scripts/test-context-compression.sh +166 -0
  67. package/scripts/themis_heuristic_scan.py +287 -0
  68. package/scripts/todo_enforcer.py +242 -0
  69. package/scripts/uninstall.mjs +1057 -0
  70. package/scripts/validate-routing.mjs +160 -0
  71. package/scripts/validate_agent_frontmatter.py +226 -0
  72. package/scripts/versioning.mjs +254 -0
  73. package/skills-lock.json +16 -0
  74. package/src/agents/aphrodite.md +162 -0
  75. package/src/agents/apollo.md +109 -0
  76. package/src/agents/athena.md +226 -0
  77. package/src/agents/demeter.md +146 -0
  78. package/src/agents/gaia.md +82 -0
  79. package/src/agents/hephaestus.md +105 -0
  80. package/src/agents/hermes.md +302 -0
  81. package/src/agents/iris.md +99 -0
  82. package/src/agents/mnemosyne.md +226 -0
  83. package/src/agents/nyx.md +87 -0
  84. package/src/agents/prometheus.md +199 -0
  85. package/src/agents/talos.md +93 -0
  86. package/src/agents/themis.md +187 -0
  87. package/src/agents/zeus.md +209 -0
  88. package/src/instructions/agent-return-format.instructions.md +26 -0
  89. package/src/instructions/backend-standards.instructions.md +45 -0
  90. package/src/instructions/documentation-standards.instructions.md +53 -0
  91. package/src/instructions/frontend-standards.instructions.md +46 -0
  92. package/src/instructions/memory-protocol.instructions.md +67 -0
  93. package/src/instructions/yagni.instructions.md +21 -0
  94. package/src/instructions/zeus-anti-stall.instructions.md +72 -0
  95. package/src/instructions/zeus-communication-rules.instructions.md +15 -0
  96. package/src/instructions/zeus-council-synthesis.instructions.md +105 -0
  97. package/src/instructions/zeus-timeout-retry.instructions.md +127 -0
  98. package/src/mcp/_pantheon_paths.py +67 -0
  99. package/src/mcp/code_mode_server.py +202 -0
  100. package/src/mcp/init-pantheon-mcp.sh +118 -0
  101. package/src/mcp/install-mcp.mjs +885 -0
  102. package/src/mcp/mcp_persistence_server.py +458 -0
  103. package/src/mcp/mcp_resources_server.py +205 -0
  104. package/src/mcp/memory_mcp_server.py +604 -0
  105. package/src/mcp/requirements-mcp-core.txt +5 -0
  106. package/src/mcp/requirements-mcp.txt +5 -0
  107. package/src/plugin.ts +19 -0
  108. package/src/plugins/tui/dist/tui.js +143 -0
  109. package/src/plugins/tui/dist/tui.tsx +144 -0
  110. package/src/plugins/tui/package.json +32 -0
  111. package/src/plugins/tui/src/index.tsx +144 -0
  112. package/src/plugins/tui/tsdown.config.ts +22 -0
  113. package/src/routing.yml +499 -0
  114. package/src/skills/README.md +230 -0
  115. package/src/skills/agent-coordination/SKILL.md +95 -0
  116. package/src/skills/artifact-management/SKILL.md +118 -0
  117. package/src/skills/auto-continue/SKILL.md +280 -0
  118. package/src/skills/code-review-checklist/SKILL.md +139 -0
  119. package/src/skills/context-compression/SKILL.md +861 -0
  120. package/src/skills/git-workflow-and-versioning/SKILL.md +32 -0
  121. package/src/skills/incremental-implementation/SKILL.md +27 -0
  122. package/src/skills/memory-bank/SKILL.md +165 -0
  123. package/src/skills/orchestration-workflow/SKILL.md +311 -0
  124. package/src/skills/security-hardening/SKILL.md +36 -0
  125. package/src/skills/session-goal/SKILL.md +138 -0
  126. package/src/skills/spec-driven-development/SKILL.md +23 -0
  127. package/src/skills/tdd-with-agents/SKILL.md +170 -0
  128. package/src/skills/visual-review-pipeline/SKILL.md +200 -0
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * install.mjs — Pantheon v5.0 OpenCode installer
4
+ * Simple delegator that installs agents + config to ~/.config/opencode/
5
+ */
6
+ import { join } from "node:path";
7
+ import { existsSync } from "node:fs";
8
+ import { installOpenCode } from "./install/opencode.mjs";
9
+
10
+ const ROOT = new URL("..", import.meta.url).pathname;
11
+
12
+ function main() {
13
+ const args = process.argv.slice(2);
14
+ const positional = args.filter(a => !a.startsWith("--"));
15
+ const target = positional[0] || join(process.env.HOME || "~", ".config", "opencode");
16
+ const dryRun = args.includes("--dry-run");
17
+ const clean = args.includes("--clean");
18
+
19
+ console.log(`Pantheon v5.0 — OpenCode installer`);
20
+ console.log(`Target: ${target}${dryRun ? " (DRY RUN)" : ""}`);
21
+
22
+ installOpenCode(target, dryRun, clean);
23
+ console.log("\n✅ Done. Run 'npx pantheon-orchestrator init' to verify.");
24
+ }
25
+
26
+ main();
@@ -0,0 +1,622 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * manifest.mjs — /pantheon-manifest command
4
+ *
5
+ * One-command pipeline: sync canonical agents → install to target → verify integrity
6
+ *
7
+ * Steps:
8
+ * 1. SYNC: Generate platform-specific agent files from canonical sources
9
+ * 2. INSTALL: Copy generated files + config to target OpenCode directory
10
+ * 3. VERIFY: Agent file count, MCP connectivity, config integrity, agent sources
11
+ * 4. REPORT: Structured pass/fail output with colored status
12
+ *
13
+ * Usage:
14
+ * node scripts/manifest.mjs # default: ~/.config/opencode, opencode platform
15
+ * node scripts/manifest.mjs --check-only # verify only, no sync/install
16
+ * node scripts/manifest.mjs --target ~/.config/opencode
17
+ * node scripts/manifest.mjs --platforms opencode,claude
18
+ * node scripts/manifest.mjs --skip-sync # sync already done, just install+verify
19
+ * node scripts/manifest.mjs --skip-install # sync+verify, no install
20
+ * node scripts/manifest.mjs --verbose
21
+ * node scripts/manifest.mjs --help
22
+ *
23
+ * Exit codes:
24
+ * 0 = all checks pass
25
+ * 1 = warnings (non-blocking)
26
+ * 2 = errors (blocking failures)
27
+ */
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Imports
31
+ // ---------------------------------------------------------------------------
32
+
33
+ import { execSync, spawnSync } from 'node:child_process'
34
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
35
+ import { homedir } from 'node:os'
36
+ import { join, resolve } from 'node:path'
37
+ import { fileURLToPath } from 'node:url'
38
+
39
+ const __dirname = fileURLToPath(new URL('.', import.meta.url))
40
+ const ROOT = join(__dirname, '..')
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // State
44
+ // ---------------------------------------------------------------------------
45
+
46
+ const counts = { pass: 0, warn: 0, error: 0, info: 0 }
47
+ const results = []
48
+ let exitCode = 0
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // ANSI color helpers
52
+ // ---------------------------------------------------------------------------
53
+
54
+ const C = {
55
+ reset: '\x1b[0m',
56
+ bold: '\x1b[1m',
57
+ green: '\x1b[32m',
58
+ yellow: '\x1b[33m',
59
+ red: '\x1b[31m',
60
+ cyan: '\x1b[36m',
61
+ dim: '\x1b[2m',
62
+ gray: '\x1b[90m',
63
+ }
64
+
65
+ function colored(text, color) {
66
+ return `${color}${text}${C.reset}`
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // CLI arg parsing
71
+ // ---------------------------------------------------------------------------
72
+
73
+ function parseArgs(argv) {
74
+ const args = {
75
+ target: null,
76
+ platforms: ['opencode'],
77
+ checkOnly: false,
78
+ skipSync: false,
79
+ skipInstall: false,
80
+ verbose: false,
81
+ help: false,
82
+ }
83
+
84
+ for (let i = 2; i < argv.length; i++) {
85
+ switch (argv[i]) {
86
+ case '--target':
87
+ args.target = argv[++i]
88
+ break
89
+ case '--platforms':
90
+ args.platforms = argv[++i].split(',').map((s) => s.trim().toLowerCase())
91
+ break
92
+ case '--check-only':
93
+ args.checkOnly = true
94
+ break
95
+ case '--skip-sync':
96
+ args.skipSync = true
97
+ break
98
+ case '--skip-install':
99
+ args.skipInstall = true
100
+ break
101
+ case '--verbose':
102
+ args.verbose = true
103
+ break
104
+ case '--help':
105
+ case '-h':
106
+ args.help = true
107
+ break
108
+ default:
109
+ console.warn(`⚠️ Unknown option: ${argv[i]}`)
110
+ break
111
+ }
112
+ }
113
+
114
+ // Resolve target
115
+ if (!args.target) {
116
+ const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), '.config')
117
+ args.target = join(xdgConfig, 'opencode')
118
+ }
119
+ if (!args.target.startsWith('/')) {
120
+ args.target = resolve(args.target)
121
+ }
122
+
123
+ // --check-only implies --skip-sync --skip-install
124
+ if (args.checkOnly) {
125
+ args.skipSync = true
126
+ args.skipInstall = true
127
+ }
128
+
129
+ return args
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Output helpers
134
+ // ---------------------------------------------------------------------------
135
+
136
+ function emit(category, message) {
137
+ counts[category]++
138
+ results.push({ category, message })
139
+ const icon = {
140
+ pass: colored('\u2713', C.green),
141
+ warn: colored('\u26A0', C.yellow),
142
+ error: colored('\u2717', C.red),
143
+ info: colored('\u2139', C.cyan),
144
+ }[category]
145
+ const label = {
146
+ pass: colored('PASS', C.green),
147
+ warn: colored('WARN', C.yellow),
148
+ error: colored('FAIL', C.red),
149
+ info: colored('INFO', C.cyan),
150
+ }[category]
151
+ if (category === 'pass' && !process.argv.includes('--verbose') && !process.argv.includes('-v')) {
152
+ return // quiet pass unless verbose
153
+ }
154
+ console.log(` ${icon} ${label} ${message}`)
155
+ }
156
+
157
+ function pass(msg) {
158
+ emit('pass', msg)
159
+ }
160
+ function warn(msg) {
161
+ emit('warn', msg)
162
+ if (exitCode < 1) exitCode = 1
163
+ }
164
+ function fail(msg) {
165
+ emit('error', msg)
166
+ exitCode = 2
167
+ }
168
+ function info(msg) {
169
+ emit('info', msg)
170
+ }
171
+
172
+ function showHelp() {
173
+ console.log(`
174
+ ${colored('/pantheon-manifest', C.bold)} — Sync, install, and verify Pantheon agents
175
+
176
+ ${colored('USAGE', C.cyan)}
177
+ node scripts/manifest.mjs default pipeline
178
+ node scripts/manifest.mjs --check-only verify only
179
+ node scripts/manifest.mjs --target PATH custom target
180
+ node scripts/manifest.mjs --platforms opencode,claude multi-platform
181
+ node scripts/manifest.mjs --skip-sync skip sync step
182
+ node scripts/manifest.mjs --skip-install skip install step
183
+ node scripts/manifest.mjs --verbose show all output
184
+ node scripts/manifest.mjs --help this help
185
+
186
+ ${colored('PIPELINE', C.cyan)}
187
+ 1. SYNC — Generate platform agent files via sync-platforms.mjs
188
+ 2. INSTALL — Deploy agents, config, skills to target directory
189
+ 3. VERIFY — Check agent count, MCP servers, config integrity
190
+ 4. REPORT — Structured pass/fail summary
191
+
192
+ ${colored('DEFAULTS', C.cyan)}
193
+ --target ~/.config/opencode
194
+ --platforms opencode
195
+
196
+ ${colored('EXIT CODES', C.cyan)}
197
+ 0 = all checks pass
198
+ 1 = warnings (non-blocking)
199
+ 2 = errors (blocking failures)
200
+ `)
201
+ }
202
+
203
+ // ---------------------------------------------------------------------------
204
+ // Step helpers
205
+ // ---------------------------------------------------------------------------
206
+
207
+ function section(title) {
208
+ console.log(`\n${colored('='.repeat(60), C.dim)}`)
209
+ console.log(` ${colored(title, C.bold)}`)
210
+ console.log(`${colored('='.repeat(60), C.dim)}`)
211
+ }
212
+
213
+ function _step(label, fn) {
214
+ console.log(`\n${colored('>>>', C.cyan)} ${colored(label, C.bold)}`)
215
+ return fn()
216
+ }
217
+
218
+ // ---------------------------------------------------------------------------
219
+ // Step 1: Sync
220
+ // ---------------------------------------------------------------------------
221
+
222
+ function syncPlatforms(targetPlatforms) {
223
+ const syncScript = join(ROOT, 'scripts', 'sync-platforms.mjs')
224
+ if (!existsSync(syncScript)) {
225
+ fail(`sync-platforms.mjs not found at ${syncScript}`)
226
+ return false
227
+ }
228
+
229
+ let allOk = true
230
+ for (const platform of targetPlatforms) {
231
+ const label = platform.charAt(0).toUpperCase() + platform.slice(1)
232
+ console.log(` ${colored('Syncing', C.cyan)} ${colored(label, C.bold)}...`)
233
+ try {
234
+ const result = spawnSync(process.execPath, [syncScript, platform], {
235
+ cwd: ROOT,
236
+ stdio: 'inherit',
237
+ timeout: 60_000,
238
+ })
239
+ if (result.status === 0) {
240
+ pass(`${label} sync completed`)
241
+ } else {
242
+ fail(`${label} sync failed (exit ${result.status})`)
243
+ allOk = false
244
+ }
245
+ } catch (err) {
246
+ fail(`Sync error: ${err.message}`)
247
+ allOk = false
248
+ }
249
+ }
250
+ return allOk
251
+ }
252
+
253
+ // ---------------------------------------------------------------------------
254
+ // Step 2: Install
255
+ // ---------------------------------------------------------------------------
256
+
257
+ function installManifest(target, targetPlatforms) {
258
+ const installScript = join(ROOT, 'scripts', 'install.mjs')
259
+ if (!existsSync(installScript)) {
260
+ fail(`install.mjs not found at ${installScript}`)
261
+ return false
262
+ }
263
+
264
+ const platformArg = targetPlatforms.join(',')
265
+ console.log(
266
+ ` ${colored('Installing to', C.cyan)} ${colored(target, C.bold)} ${colored(`(platforms: ${platformArg})`, C.dim)}...`,
267
+ )
268
+
269
+ try {
270
+ const result = spawnSync(
271
+ process.execPath,
272
+ [installScript, '--target', target, '--platforms', platformArg],
273
+ {
274
+ cwd: ROOT,
275
+ stdio: 'inherit',
276
+ timeout: 120_000,
277
+ },
278
+ )
279
+ if (result.status === 0) {
280
+ pass(`Install completed`)
281
+ return true
282
+ } else {
283
+ fail(`Install failed (exit ${result.status})`)
284
+ // Print stderr if available
285
+ if (result.stderr && result.stderr.length > 0) {
286
+ const lines = result.stderr.toString().split('\n').filter(Boolean)
287
+ for (const line of lines.slice(-5)) {
288
+ console.error(` ${colored(line, C.red)}`)
289
+ }
290
+ }
291
+ return false
292
+ }
293
+ } catch (err) {
294
+ fail(`Install error: ${err.message}`)
295
+ return false
296
+ }
297
+ }
298
+
299
+ // ---------------------------------------------------------------------------
300
+ // Step 3: Verify
301
+ // ---------------------------------------------------------------------------
302
+
303
+ /**
304
+ * Get canonical agent names from pantheon agents/ directory.
305
+ */
306
+ function getCanonicalAgentNames() {
307
+ const agentsDir = join(ROOT, 'agents')
308
+ if (!existsSync(agentsDir)) return []
309
+ return readdirSync(agentsDir)
310
+ .filter((f) => f.endsWith('.agent.md'))
311
+ .map((f) => f.replace(/\.agent\.md$/, ''))
312
+ .sort()
313
+ }
314
+
315
+ /**
316
+ * Get installed agent file names from target platform agents directory.
317
+ */
318
+ function getInstalledAgentNames(target, _platform) {
319
+ // Global install: agents/ directory at target root
320
+ // Project install: .opencode/agents/ at target root
321
+ const candidates = [join(target, 'agents'), join(target, '.opencode', 'agents')]
322
+ for (const dir of candidates) {
323
+ if (existsSync(dir)) {
324
+ return readdirSync(dir)
325
+ .filter((f) => f.endsWith('.md') && statSync(join(dir, f)).isFile())
326
+ .map((f) => f.replace(/\.md$/, ''))
327
+ .sort()
328
+ }
329
+ }
330
+ return []
331
+ }
332
+
333
+ /**
334
+ * Count agents with mcp_tools in frontmatter.
335
+ */
336
+ function countAgentsWithMcpTools(target) {
337
+ const candidates = [join(target, 'agents'), join(target, '.opencode', 'agents')]
338
+ let count = 0
339
+ for (const dir of candidates) {
340
+ if (!existsSync(dir)) continue
341
+ const files = readdirSync(dir).filter((f) => f.endsWith('.md'))
342
+ for (const f of files) {
343
+ const content = readFileSync(join(dir, f), 'utf8')
344
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/)
345
+ if (fmMatch?.[1].includes('mcp_tools')) {
346
+ count++
347
+ }
348
+ }
349
+ }
350
+ return count
351
+ }
352
+
353
+ /**
354
+ * Check MCP server connectivity via `opencode mcp list`.
355
+ */
356
+ function checkMCPConnectivity() {
357
+ try {
358
+ const result = execSync('opencode mcp list', {
359
+ encoding: 'utf8',
360
+ timeout: 15_000,
361
+ stdio: ['pipe', 'pipe', 'pipe'],
362
+ })
363
+ const output = result.trim()
364
+
365
+ // Count server lines: lines starting with "●" containing ✓ or ✗
366
+ const serverLines = output.split('\n').filter((l) => l.includes('\u25CF'))
367
+ const connectedLines = serverLines.filter((l) => l.includes('\u2713'))
368
+ const failedLines = serverLines.filter((l) => l.includes('\u2717'))
369
+ const totalServers = serverLines.length
370
+ const connectedCount = connectedLines.length
371
+ const failedCount = failedLines.length
372
+
373
+ if (failedCount === 0 && connectedCount > 0) {
374
+ pass(`${connectedCount} MCP server(s) connected`)
375
+ if (process.argv.includes('--verbose')) {
376
+ console.log(`\n${output}\n`)
377
+ }
378
+ } else if (connectedCount > 0 && failedCount > 0) {
379
+ warn(`${connectedCount} connected, ${failedCount} failed MCP server(s)`)
380
+ if (process.argv.includes('--verbose')) {
381
+ console.log(`\n${output}\n`)
382
+ }
383
+ } else if (totalServers === 0) {
384
+ info('No MCP servers found in `opencode mcp list`')
385
+ } else {
386
+ warn(`0 of ${totalServers} MCP server(s) connected`)
387
+ if (process.argv.includes('--verbose')) {
388
+ console.log(`\n${output}\n`)
389
+ }
390
+ }
391
+ return { connected: connectedCount, failed: failedCount, total: totalServers }
392
+ } catch (err) {
393
+ // opencode not installed or mcp list not available
394
+ warn(`Could not check MCP connectivity: ${err.message}`)
395
+ return { connected: 0, failed: 0, total: 0 }
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Check target opencode.json config integrity.
401
+ */
402
+ function checkConfigIntegrity(target) {
403
+ const configPath = join(target, 'opencode.json')
404
+ if (!existsSync(configPath)) {
405
+ fail(`opencode.json not found at ${configPath}`)
406
+ return false
407
+ }
408
+
409
+ try {
410
+ const config = JSON.parse(readFileSync(configPath, 'utf8'))
411
+ const allOk = true
412
+
413
+ // Check default agent
414
+ if (config.default_agent) {
415
+ pass(`default_agent: ${config.default_agent}`)
416
+ } else {
417
+ warn('No default_agent set in opencode.json')
418
+ }
419
+
420
+ // Check MCP config
421
+ if (config.mcp && typeof config.mcp === 'object') {
422
+ const mcpCount = Object.keys(config.mcp).length
423
+ const pantheonMcps = Object.keys(config.mcp).filter((k) => k.startsWith('pantheon-'))
424
+ pass(`${mcpCount} MCP server(s) configured, ${pantheonMcps.length} pantheon-*`)
425
+ } else {
426
+ warn('No MCP section in opencode.json — run install')
427
+ }
428
+
429
+ // Check permission
430
+ if (config.permission?.mcp) {
431
+ const mcpPermCount = Object.keys(config.permission.mcp).length
432
+ pass(`${mcpPermCount} MCP permission(s) configured`)
433
+ } else {
434
+ warn('No permission.mcp section in opencode.json')
435
+ }
436
+
437
+ // Check instructions
438
+ if (Array.isArray(config.instructions) && config.instructions.length > 0) {
439
+ pass(`${config.instructions.length} instruction path(s) configured`)
440
+ } else {
441
+ warn('No instructions paths in opencode.json')
442
+ }
443
+
444
+ return allOk
445
+ } catch (err) {
446
+ fail(`opencode.json parse error: ${err.message}`)
447
+ return false
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Verify agent count consistency and mcp_tools coverage.
453
+ */
454
+ function verifyAgentFiles(target) {
455
+ const canonical = getCanonicalAgentNames()
456
+ const installed = getInstalledAgentNames(target, 'opencode')
457
+
458
+ if (canonical.length === 0) {
459
+ fail('No canonical agent files found in agents/')
460
+ return false
461
+ }
462
+
463
+ if (installed.length === 0) {
464
+ fail(`No installed agent files found in ${target}/agents/`)
465
+ return false
466
+ }
467
+
468
+ // Count match
469
+ if (installed.length === canonical.length) {
470
+ pass(`${installed.length} of ${canonical.length} agent files installed`)
471
+ } else {
472
+ warn(`${installed.length} installed vs ${canonical.length} canonical agent files`)
473
+ }
474
+
475
+ // Check for missing agents
476
+ const missing = canonical.filter((a) => !installed.includes(a))
477
+ if (missing.length > 0) {
478
+ fail(`${missing.length} agent(s) missing from install: ${missing.join(', ')}`)
479
+ }
480
+
481
+ // Check for phantom agents (in install but not canonical)
482
+ const phantom = installed.filter((a) => !canonical.includes(a))
483
+ if (phantom.length > 0) {
484
+ warn(`${phantom.length} stale/phantom agent(s) in install: ${phantom.join(', ')}`)
485
+ }
486
+
487
+ // Verify each installed file has valid frontmatter
488
+ let invalidCount = 0
489
+ const candidateDirs = [join(target, 'agents'), join(target, '.opencode', 'agents')]
490
+ for (const dir of candidateDirs) {
491
+ if (!existsSync(dir)) continue
492
+ for (const name of installed) {
493
+ const filePath = join(dir, `${name}.md`)
494
+ if (!existsSync(filePath)) continue
495
+ const content = readFileSync(filePath, 'utf8')
496
+ if (!content.startsWith('---')) {
497
+ warn(`${name}.md: missing frontmatter`)
498
+ invalidCount++
499
+ }
500
+ }
501
+ }
502
+ if (invalidCount === 0 && installed.length > 0) {
503
+ pass('All installed agents have valid frontmatter')
504
+ }
505
+
506
+ // mcp_tools coverage
507
+ const mcpCount = countAgentsWithMcpTools(target)
508
+ if (mcpCount > 0) {
509
+ info(`${mcpCount} of ${installed.length} agent files have mcp_tools configured`)
510
+ }
511
+
512
+ return missing.length === 0
513
+ }
514
+
515
+ // ---------------------------------------------------------------------------
516
+ // Step 4: Report
517
+ // ---------------------------------------------------------------------------
518
+
519
+ function printSummary() {
520
+ console.log(`\n${colored('='.repeat(60), C.dim)}`)
521
+ console.log(` ${colored('MANIFEST SUMMARY', C.bold)}`)
522
+ console.log(`${colored('='.repeat(60), C.dim)}`)
523
+ console.log(` ${colored('\u2713', C.green)} ${counts.pass} passed`)
524
+ console.log(` ${colored('\u26A0', C.yellow)} ${counts.warn} warnings`)
525
+ console.log(` ${colored('\u2717', C.red)} ${counts.error} errors`)
526
+ console.log(` ${colored('\u2139', C.cyan)} ${counts.info} info`)
527
+ console.log('')
528
+
529
+ if (exitCode === 0) {
530
+ console.log(
531
+ ` ${colored('\u2713', C.green)} ${colored('MANIFEST COMPLETE', C.bold)} — all checks passed`,
532
+ )
533
+ } else if (exitCode === 1) {
534
+ console.log(
535
+ ` ${colored('\u26A0', C.yellow)} ${colored('MANIFEST COMPLETE WITH WARNINGS', C.bold)} — review above`,
536
+ )
537
+ } else {
538
+ console.log(
539
+ ` ${colored('\u2717', C.red)} ${colored('MANIFEST FAILED', C.bold)} — errors must be resolved`,
540
+ )
541
+ }
542
+ console.log('')
543
+ }
544
+
545
+ // ---------------------------------------------------------------------------
546
+ // Main
547
+ // ---------------------------------------------------------------------------
548
+
549
+ function main() {
550
+ const args = parseArgs(process.argv)
551
+
552
+ if (args.help) {
553
+ showHelp()
554
+ process.exit(0)
555
+ }
556
+
557
+ // Validate target directory exists
558
+ if (!existsSync(args.target)) {
559
+ console.error(`${colored('ERROR', C.red)}: Target directory does not exist: ${args.target}`)
560
+ console.error(` Run: mkdir -p ${args.target}`)
561
+ process.exit(2)
562
+ }
563
+
564
+ // Validate we're in the Pantheon root
565
+ if (!existsSync(join(ROOT, 'AGENTS.md')) || !existsSync(join(ROOT, 'agents'))) {
566
+ console.error(
567
+ `${colored('ERROR', C.red)}: Not in Pantheon repository root — AGENTS.md or agents/ not found`,
568
+ )
569
+ console.error(` Expected root at: ${ROOT}`)
570
+ process.exit(2)
571
+ }
572
+
573
+ const mode = args.checkOnly
574
+ ? colored('CHECK-ONLY MODE', C.yellow)
575
+ : colored('FULL PIPELINE', C.green)
576
+ console.log(`\n${colored('Pantheon Manifest', C.bold)} ${colored('v3', C.dim)} — ${mode}`)
577
+ console.log(` ${colored('Target:', C.dim)} ${args.target}`)
578
+ console.log(` ${colored('Platforms:', C.dim)} ${args.platforms.join(', ')}`)
579
+
580
+ // ── Step 1: Sync ──
581
+ if (!args.skipSync) {
582
+ section('STEP 1: SYNC')
583
+ syncPlatforms(args.platforms)
584
+ } else if (!args.checkOnly) {
585
+ info('Sync step skipped (--skip-sync)')
586
+ }
587
+
588
+ // ── Step 2: Install ──
589
+ if (!args.skipInstall) {
590
+ section('STEP 2: INSTALL')
591
+ installManifest(args.target, args.platforms)
592
+ } else if (!args.checkOnly) {
593
+ info('Install step skipped (--skip-install)')
594
+ }
595
+
596
+ // ── Step 3: Verify ──
597
+ section('STEP 3: VERIFY')
598
+ verifyAgentFiles(args.target)
599
+ checkMCPConnectivity()
600
+ checkConfigIntegrity(args.target)
601
+
602
+ // ── Step 4: Report ──
603
+ section('STEP 4: REPORT')
604
+ printSummary()
605
+
606
+ // ── Final status line ──
607
+ if (exitCode === 0) {
608
+ console.log(
609
+ ` ${colored('\u2728', C.green)} Pantheon manifest is ${colored('UP TO DATE', C.bold + C.green)}`,
610
+ )
611
+ } else if (exitCode === 1) {
612
+ console.log(` ${colored('\u26A0', C.yellow)} Pantheon manifest has warnings (review above)`)
613
+ } else {
614
+ console.log(
615
+ ` ${colored('\u26D4', C.red)} Pantheon manifest has errors — run with --verbose for details`,
616
+ )
617
+ }
618
+
619
+ process.exit(exitCode)
620
+ }
621
+
622
+ main()