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.
- package/AGENTS.md +37 -0
- package/LICENSE +21 -0
- package/README.md +1013 -0
- package/bin/pantheon-init.mjs +183 -0
- package/commands/pantheon-audit.md +25 -0
- package/commands/pantheon-bg.md +10 -0
- package/commands/pantheon-consolidate.md +11 -0
- package/commands/pantheon-deepwork.md +128 -0
- package/commands/pantheon-doc.md +10 -0
- package/commands/pantheon-focus.md +9 -0
- package/commands/pantheon-forget.md +58 -0
- package/commands/pantheon-hash.md +11 -0
- package/commands/pantheon-optimize.md +79 -0
- package/commands/pantheon-remember.md +44 -0
- package/commands/pantheon-search.md +48 -0
- package/commands/pantheon-status.md +71 -0
- package/commands/pantheon-todo.md +11 -0
- package/commands/pantheon.md +49 -0
- package/docs/AGENT-MCP.md +194 -0
- package/docs/ARCHITECTURE.md +384 -0
- package/docs/BRANCH-PROTECTION.md +142 -0
- package/docs/INDEX.md +81 -0
- package/docs/INSTALLATION.md +217 -0
- package/docs/MCP.md +238 -0
- package/docs/MEMORY.md +471 -0
- package/docs/MIGRATION-MEMORY-BANK.md +139 -0
- package/docs/PLATFORMS.md +5 -0
- package/docs/QUICKSTART.md +49 -0
- package/docs/README.md +18 -0
- package/docs/RELEASING.md +256 -0
- package/docs/SETUP.md +5 -0
- package/docs/UPGRADING.md +41 -0
- package/docs/mcp-recommendations.md +439 -0
- package/docs/mcp-tools.md +156 -0
- package/docs/mcp-user-guide.md +204 -0
- package/docs/persistence-mcp.md +111 -0
- package/package.json +72 -0
- package/pantheon.schema.json +158 -0
- package/scripts/__init__.py +0 -0
- package/scripts/_pantheon_paths.py +68 -0
- package/scripts/check-version-consistency.sh +23 -0
- package/scripts/code_mode_server.py +202 -0
- package/scripts/doctor.mjs +763 -0
- package/scripts/generate-prompts.sh +222 -0
- package/scripts/generate-routing-docs.mjs +104 -0
- package/scripts/hash_verify.py +192 -0
- package/scripts/init-pantheon-mcp.sh +118 -0
- package/scripts/install/agents-md.mjs +214 -0
- package/scripts/install/health-check.mjs +196 -0
- package/scripts/install/migrate.mjs +209 -0
- package/scripts/install/opencode.mjs +645 -0
- package/scripts/install/shared.mjs +655 -0
- package/scripts/install/venv.mjs +116 -0
- package/scripts/install-mcp.mjs +885 -0
- package/scripts/install.mjs +26 -0
- package/scripts/manifest.mjs +622 -0
- package/scripts/mcp_persistence_server.py +459 -0
- package/scripts/mcp_resources_server.py +206 -0
- package/scripts/memory_cache.py +78 -0
- package/scripts/memory_mcp_server.py +605 -0
- package/scripts/paths.py +64 -0
- package/scripts/prune_context.py +72 -0
- package/scripts/release-bundle.mjs +109 -0
- package/scripts/scrub-secrets.py +282 -0
- package/scripts/scrub_secrets.py +281 -0
- package/scripts/test-context-compression.sh +166 -0
- package/scripts/themis_heuristic_scan.py +287 -0
- package/scripts/todo_enforcer.py +242 -0
- package/scripts/uninstall.mjs +1057 -0
- package/scripts/validate-routing.mjs +160 -0
- package/scripts/validate_agent_frontmatter.py +226 -0
- package/scripts/versioning.mjs +254 -0
- package/skills-lock.json +16 -0
- package/src/agents/aphrodite.md +162 -0
- package/src/agents/apollo.md +109 -0
- package/src/agents/athena.md +226 -0
- package/src/agents/demeter.md +146 -0
- package/src/agents/gaia.md +82 -0
- package/src/agents/hephaestus.md +105 -0
- package/src/agents/hermes.md +302 -0
- package/src/agents/iris.md +99 -0
- package/src/agents/mnemosyne.md +226 -0
- package/src/agents/nyx.md +87 -0
- package/src/agents/prometheus.md +199 -0
- package/src/agents/talos.md +93 -0
- package/src/agents/themis.md +187 -0
- package/src/agents/zeus.md +209 -0
- package/src/instructions/agent-return-format.instructions.md +26 -0
- package/src/instructions/backend-standards.instructions.md +45 -0
- package/src/instructions/documentation-standards.instructions.md +53 -0
- package/src/instructions/frontend-standards.instructions.md +46 -0
- package/src/instructions/memory-protocol.instructions.md +67 -0
- package/src/instructions/yagni.instructions.md +21 -0
- package/src/instructions/zeus-anti-stall.instructions.md +72 -0
- package/src/instructions/zeus-communication-rules.instructions.md +15 -0
- package/src/instructions/zeus-council-synthesis.instructions.md +105 -0
- package/src/instructions/zeus-timeout-retry.instructions.md +127 -0
- package/src/mcp/_pantheon_paths.py +67 -0
- package/src/mcp/code_mode_server.py +202 -0
- package/src/mcp/init-pantheon-mcp.sh +118 -0
- package/src/mcp/install-mcp.mjs +885 -0
- package/src/mcp/mcp_persistence_server.py +458 -0
- package/src/mcp/mcp_resources_server.py +205 -0
- package/src/mcp/memory_mcp_server.py +604 -0
- package/src/mcp/requirements-mcp-core.txt +5 -0
- package/src/mcp/requirements-mcp.txt +5 -0
- package/src/plugin.ts +19 -0
- package/src/plugins/tui/dist/tui.js +143 -0
- package/src/plugins/tui/dist/tui.tsx +144 -0
- package/src/plugins/tui/package.json +32 -0
- package/src/plugins/tui/src/index.tsx +144 -0
- package/src/plugins/tui/tsdown.config.ts +22 -0
- package/src/routing.yml +499 -0
- package/src/skills/README.md +230 -0
- package/src/skills/agent-coordination/SKILL.md +95 -0
- package/src/skills/artifact-management/SKILL.md +118 -0
- package/src/skills/auto-continue/SKILL.md +280 -0
- package/src/skills/code-review-checklist/SKILL.md +139 -0
- package/src/skills/context-compression/SKILL.md +861 -0
- package/src/skills/git-workflow-and-versioning/SKILL.md +32 -0
- package/src/skills/incremental-implementation/SKILL.md +27 -0
- package/src/skills/memory-bank/SKILL.md +165 -0
- package/src/skills/orchestration-workflow/SKILL.md +311 -0
- package/src/skills/security-hardening/SKILL.md +36 -0
- package/src/skills/session-goal/SKILL.md +138 -0
- package/src/skills/spec-driven-development/SKILL.md +23 -0
- package/src/skills/tdd-with-agents/SKILL.md +170 -0
- 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()
|