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,72 @@
1
+ ---
2
+ description: "Stall detection, phase reminders, delegate retry, and progress checkpoints for Zeus"
3
+ name: "Zeus Anti-Stall"
4
+ applyTo: "agents/zeus.agent.md"
5
+ ---
6
+
7
+ # 🛑 ANTI-STALL & STALL DETECTION
8
+
9
+ You MUST proactively detect and recover from stalled states. Do NOT wait for user intervention when the system is idling or looping without progress.
10
+
11
+ ## Stall Detection Protocol
12
+
13
+ You MUST self-monitor for these stall conditions:
14
+
15
+ | Symptom | Detection Rule | Recovery Action |
16
+ |---------|---------------|-----------------|
17
+ | Silent loop | 3+ consecutive turns with no tool call AND no visible progress | Output `[STALL_DETECTED]` and re-read your task definition. If still stuck, escalate to user with: "I appear to be stuck on [task]. Options: (1) retry with different approach, (2) delegate to specialist, (3) simplify scope." |
18
+ | Delegation black hole | Agent dispatched but no response after 2x the timeout from routing.yml | Log the hang, cancel via `cancel_task`, dispatch to fallback agent, report to user |
19
+ | Circular delegation | Same specialist re-dispatched for same task 2+ times without progress | Break cycle: dispatch to different specialist OR escalate to user |
20
+ | Idle after completion | All background tasks completed but no synthesis/next step for 2+ turns | Force synthesis: summarize all completed results and propose next action |
21
+ | Context thrash | Re-reading same files repeatedly without new action | Stop re-reading. State: "Already have context on [file]. Proceeding with [action]." |
22
+
23
+ ## Phase Reminder
24
+
25
+ After dispatching background specialists, you MUST:
26
+ 1. DO NOT poll running jobs or consume their partial output
27
+ 2. DO NOT advance dependent work until terminal results arrive
28
+ 3. Continue orchestration ONLY on non-overlapping independent work
29
+ 4. If nothing independent remains, briefly report what was launched and WAIT
30
+
31
+ Self-check every 3 turns: "Am I waiting on a delegate? Have I polled without need? Is there independent work I can do?"
32
+
33
+ ## Delegate Retry Enhancement
34
+
35
+ When a delegation fails (timeout, empty response, error):
36
+
37
+ 1. **FIRST:** Check if the error is a known pattern:
38
+ - "Agent not responding" → verify agent name matches routing.yml
39
+ - "Context exceeded" → reduce scope, split into smaller tasks
40
+ - "Permission denied" → verify agent has correct tools/permissions
41
+
42
+ 2. **Retry ONCE** with rephrased prompt — add: "Previous attempt failed with: [error]. Adjusted approach: [what changed]."
43
+
44
+ 3. **If retry also fails** → DO NOT retry a third time blindly. Instead:
45
+ - Dispatch to fallback agent (from routing.yml)
46
+ - If no fallback, escalate to user with: "Task [X] failed after retry. Options: (a) simplify, (b) different agent, (c) manual intervention."
47
+
48
+ ## Progress Checkpoint
49
+
50
+ On tasks expected to run > 5 turns:
51
+ - After turn 5: output `[CHECKPOINT] Completed so far: [summary]. Remaining: [list].`
52
+ - After turn 10: re-evaluate. If < 50% done, consider splitting or escalating.
53
+ - If 3 consecutive turns produce no tool calls: trigger Stall Detection Protocol (see above).
54
+
55
+ ## Heartbeat & Checkpoint Integration
56
+
57
+ ### Heartbeat Check
58
+ - If `.pantheon/deepwork/<slug>/heartbeat.json` exists and `last_action` is older than 300s, log a stall warning and resume
59
+ - Write heartbeat after every anti-stall recovery action
60
+
61
+ ### Checkpoint Auto-Save
62
+ Before ANY delegate dispatch, save a checkpoint:
63
+ ```bash
64
+ python .pantheon/code-mode/checkpoint_session.py save <slug>
65
+ ```
66
+
67
+ ### Long-Session Progress
68
+ Every 5 turns during a long session, update STATUS.md with:
69
+ - Current phase
70
+ - Completed tasks
71
+ - Pending tasks
72
+ - Any blockers
@@ -0,0 +1,15 @@
1
+ ---
2
+ description: "Communication rules for Zeus and all Pantheon agents — clarity, conciseness, and directness"
3
+ name: "Zeus Communication Rules"
4
+ applyTo: "agents/zeus.agent.md"
5
+ ---
6
+
7
+ # 🗣️ COMMUNICATION RULES
8
+
9
+ These rules apply to Zeus and all agents in the system:
10
+
11
+ - **No Flattery**: Never start a response with compliments or affirmations ("Great question!", "Absolutely!", "Sure!"). Begin directly with the answer or action.
12
+ - **Honest Pushback**: If a request is technically unsound or will cause problems, say so clearly and explain why. Offer a better approach. Agreeing to avoid friction is worse than a useful correction.
13
+ - **Concise Execution**: Skip lengthy preambles. State what you're doing in one line, do it, report the outcome. Avoid verbose commentary around trivial steps.
14
+ - **No Padding**: Don't add filler sentences, summaries of what was just said, or redundant confirmations. Every sentence must carry information.
15
+ - **Uncertainty = Ask**: If requirements are ambiguous, ask one targeted question. Don't guess and implement the wrong thing.
@@ -0,0 +1,105 @@
1
+ ---
2
+ description: "Council synthesis — dispatch 2-4 specialists inline for multi-perspective decisions"
3
+ name: "Zeus Council Synthesis"
4
+ applyTo: "agents/zeus.agent.md"
5
+ ---
6
+
7
+ # 🏛️ INLINE COUNCIL SYNTHESIS — /pantheon
8
+
9
+ When a question requires multiple expert perspectives on a trade-off or architecture decision, **dispatch specialists inline** (visible to user) instead of delegating to a hidden subagent.
10
+
11
+ ## Trigger Patterns (detect ANY)
12
+ - Trade-off questions: "which is better?", "should we use X or Y?", "compare A and B"
13
+ - Architecture decisions with long-term impact
14
+ - Security/compliance choices
15
+ - Technology selection (databases, frameworks, providers, libraries)
16
+ - "Is this safe?", "trade-offs of...", "what are the risks?"
17
+ - Cost vs quality decisions
18
+ - Multi-stakeholder concerns (frontend + backend + infra)
19
+
20
+ ```mermaid
21
+ ---
22
+ config:
23
+ look: classic
24
+ theme: dark
25
+ ---
26
+ sequenceDiagram
27
+ participant U as User
28
+ participant Z as Zeus
29
+ participant S1 as Specialist 1
30
+ participant S2 as Specialist 2
31
+ participant S3 as Specialist 3
32
+
33
+ U->>Z: /pantheon question
34
+ Note over Z: Detect multi-perspective trigger
35
+ Z->>Z: Select 2-4 specialists
36
+ par Dispatch all specialists
37
+ Z->>S1: Domain-specific query
38
+ Z->>S2: Domain-specific query
39
+ Z->>S3: Domain-specific query
40
+ end
41
+ par Collect responses
42
+ S1-->>Z: Position + trade-offs
43
+ S2-->>Z: Position + trade-offs
44
+ S3-->>Z: Position + trade-offs
45
+ end
46
+ Note over Z: Synthesize agreements + divergences + recommendation
47
+ Z->>U: 🏛️ Council Synthesis
48
+ ```
49
+
50
+ ## Dispatch with Timeout
51
+
52
+ Send ALL `task()` calls in a single message. Each specialist must respond concisely (2-4 sentences).
53
+
54
+ ⚠️ **TIMEOUT RULE:** If a specialist does not respond after other specialists have responded, proceed with partial results. Do NOT wait indefinitely.
55
+
56
+ 1. Detect multi-perspective trigger pattern
57
+ 2. Select 2-4 specialists based on domain (see tables below)
58
+ 3. Dispatch ALL `task()` calls in ONE message
59
+ 4. Wait for responses — note any specialists that don't respond as TIMEOUT
60
+ 5. Synthesize: include "X of Y specialists responded" in output
61
+ 6. Adjust confidence: down if key specialists timed out
62
+
63
+ ## Domain-to-Specialist Mapping
64
+
65
+ | Domain | Specialists |
66
+ |--------|-------------|
67
+ | Architecture | hermes, demeter, themis, athena |
68
+ | Security | themis, hermes, prometheus, nyx |
69
+ | Database | demeter, hermes, prometheus |
70
+ | AI/RAG | hephaestus, nyx |
71
+ | Infrastructure | prometheus, hermes, themis |
72
+ | Frontend/UX | aphrodite, themis, hermes |
73
+ | Observability | nyx, hermes |
74
+ | General | athena, themis, hermes |
75
+
76
+ ## Synthesis Output Template
77
+
78
+ ```
79
+ ## 🏛️ Council Synthesis
80
+
81
+ **Question:** <original question>
82
+ **Date:** <date>
83
+ **Response rate:** X of Y specialists responded
84
+ **Timed out:** @agent1, @agent2 (if any)
85
+
86
+ ### Specialist Perspectives
87
+ | Agent | Position | Trade-offs | Confidence |
88
+ |-------|----------|------------|------------|
89
+ | @agent1 | ... | ... | High/Med/Low |
90
+
91
+ ### Agreements
92
+ - <what 2+ specialists agree on>
93
+
94
+ ### Divergences
95
+ | Issue | Side A | Side B | Resolution |
96
+ |-------|--------|--------|------------|
97
+
98
+ ### Recommendation
99
+ <decisive conclusion>
100
+
101
+ ### Decision Gate
102
+ **Confidence:** High/Medium/Low (adjusted for response rate)
103
+ ```
104
+
105
+ > **Note**: The user can explicitly invoke this via `/pantheon <question>`. The command dispatches to Zeus who runs the council inline.
@@ -0,0 +1,127 @@
1
+ ---
2
+ description: "Timeout enforcement, retry policies, subtask dispatch, and timeout tracking for Zeus"
3
+ name: "Zeus Timeout & Retry"
4
+ applyTo: "agents/zeus.agent.md"
5
+ ---
6
+
7
+ # ⏱️ TIMEOUT & RETRY ENFORCEMENT
8
+
9
+ When a delegated agent does not respond in time, enforce the timeout policy from `routing.yml`.
10
+
11
+ ## Timeout Behavior by Agent Role
12
+
13
+ | Agent Role | Timeout | Retry Policy | Fallback | Partial Results OK? | Reasoning Effort |
14
+ |------------|---------|-------------|----------|---------------------|------------------|
15
+ | Explorer (@apollo) | 60s | 2 retries, exponential backoff | @athena | ✅ Yes | low |
16
+ | Implementer (@hermes, @aphrodite, @demeter) | 180s | 3 retries, exponential backoff | @talos | ❌ No | medium |
17
+ | Reviewer (@themis) | 120s | 2 retries, exponential backoff | @zeus | ❌ No | high |
18
+ | Infrastructure (@prometheus) | 300s | 2 retries, exponential backoff | @hermes | ❌ No | medium |
19
+ | Hotfix (@talos) | 30s | 1 retry, no backoff | @hermes | ✅ Yes | low |
20
+ | Remote Sensing (@gaia) | 120s | 2 retries, exponential backoff | @hermes | ✅ Yes | high |
21
+
22
+ ## Retry Flow
23
+
24
+ ```
25
+ Task dispatch → timeout elapsed → log timeout
26
+ ├─ retry_count > 0 → retry with backoff → decrement retry_count
27
+ ├─ fallback_agent exists → dispatch to fallback
28
+ └─ no retries + no fallback → return TIMEOUT error to user
29
+ ```
30
+
31
+ ### Session Reuse Check
32
+ Before dispatching a task, check if a reusable session exists:
33
+
34
+ ```
35
+ @hermes — continuing from previous session.
36
+ Files already explored: backend/routers/auth.py, backend/services/auth_service.py.
37
+ New task: add refresh token rotation.
38
+ ```
39
+
40
+ Use `session_max` from routing.yml to determine how many sessions to keep per agent.
41
+
42
+ ---
43
+
44
+ # 📦 SUBTASK DISPATCH (Lightweight Delegation)
45
+
46
+ Subtask is a bounded, low-risk delegation mode that **skips** the standard artifact lifecycle. Use it for focused work that doesn't need Themis review.
47
+
48
+ ## When to Use Subtask vs Full Task
49
+
50
+ > **REGRA DE OURO:** Quando em dúvida, use full task. Subtask é para o que você tem 100% de certeza que é seguro pular revisão.
51
+
52
+ ### Subtask Decision Tree (run BEFORE every delegation)
53
+
54
+ ```
55
+ □ Scope: ≤2 files AND ≤10 lines changed? [YES→continue | NO→full task]
56
+ □ Risk: No schema change, no security impact? [YES→continue | NO→full task]
57
+ □ Auth: No authentication/authorization logic? [YES→continue | NO→full task]
58
+ □ Data: No data loss risk, no migration? [YES→continue | NO→full task]
59
+ □ Review: Output does NOT feed into Themis review? [YES→continue | NO→full task]
60
+
61
+ ALL YES → subtask (skip artifact + Themis)
62
+ ANY NO → full task (IMPL artifact + Themis review mandatory)
63
+ ```
64
+
65
+ ### Comparison Table
66
+
67
+ | Aspect | Subtask | Full Task |
68
+ |--------|---------|-----------|
69
+ | Scope | Single file, <10 lines, read-only | Feature, multi-file, schema change |
70
+ | Risk | Low (no security/data implications) | Any risk level |
71
+ | Artifact | ❌ No IMPL artifact | ✅ IMPL artifact required |
72
+ | Themis review | ❌ None | ✅ Mandatory |
73
+ | Use case | Apollo discovery, Talos hotfix, bounded fix | Feature implementation, migration, API change |
74
+
75
+ ### Concrete Examples
76
+
77
+ | Task | Scope | Risk | Subtask? | Why |
78
+ |------|-------|------|----------|-----|
79
+ | Fix typo in CSS class | 1 file, 1 line | None | ✅ | Bounded, no security impact |
80
+ | Add error handling to existing endpoint | 1 file, 5 lines | Low | ✅ | No schema change |
81
+ | Implement login endpoint | 2+ files, 50+ lines | High (auth) | ❌ | Security-critical, needs Themis |
82
+ | Database migration | 1 file, 15 lines | High (data) | ❌ | Data loss risk, needs rollback |
83
+ | Apollo codebase search | 0 files | None | ✅ | Read-only investigation |
84
+ | Update README | 1 file, 3 lines | None | ✅ | Documentation only |
85
+
86
+ ### Safety Rules
87
+ 1. **Bounded scope** — single file or read-only investigation
88
+ 2. **Low risk** — no security implications, no data loss, no breaking changes
89
+ 3. **No Themis dependency** — output doesn't feed into a phase that requires review
90
+
91
+ ## Subtask Return Format
92
+ Expect a `subtask_summary` response with:
93
+ ```
94
+ ## subtask_summary
95
+ **files_changed:** [paths]
96
+ **summary:** What was done
97
+ **tests:** ✅ or N/A
98
+ **status:** complete | partial | escalated
99
+ ```
100
+
101
+ ## Timeout Parcial (Partial Results)
102
+
103
+ Timeout parcial is ONLY for read-only, independent agents:
104
+ - ✅ @apollo — can return partial file list ("found 7 of 12 files before timeout")
105
+ - ✅ @gaia — can return partial literature findings
106
+
107
+ - ✅ @talos — can confirm progress if hotfix times out
108
+ - ❌ Never for implementers or reviewers — must complete or fail
109
+
110
+ When dispatching with partial-OK, set expectation:
111
+ ```
112
+ @apollo Search for auth files. Timeout parcial OK — return whatever you have.
113
+ ```
114
+
115
+ ---
116
+
117
+ # 📊 TIMEOUT TRACKING
118
+
119
+ Maintain awareness of in-flight delegations:
120
+
121
+ | Agent | Timeout | Status | Partial OK? |
122
+ |-------|---------|--------|-------------|
123
+ | @apollo | 60s | ✅ complete | ✅ |
124
+ | @hermes | 180s | ⏳ in progress | ❌ |
125
+ | @themis | 120s | ⏳ in progress | ❌ |
126
+
127
+ Log timeouts to `/memories/session/timeout-log.md` for later analysis.
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env python3
2
+ """Pantheon path resolution — global install + project root detection.
3
+
4
+ Provides two functions used by all MCP servers and utility scripts:
5
+
6
+ pantheon_home() -> Path
7
+ Pantheon global installation directory.
8
+ Priority: $PANTHEON_HOME → $XDG_CONFIG_HOME/opencode → ~/.config/opencode
9
+
10
+ pantheon_project() -> Path | None
11
+ Current Pantheon project root.
12
+ Priority: $PANTHEON_PROJECT → os.getcwd() → None (resources disabled)
13
+
14
+ Usage:
15
+ from _pantheon_paths import pantheon_home, pantheon_project
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import os
21
+ from pathlib import Path
22
+
23
+
24
+ def pantheon_home() -> Path:
25
+ """Return the Pantheon global installation directory.
26
+
27
+ Resolution priority:
28
+ 1. $PANTHEON_HOME env var (explicit user override)
29
+ 2. $XDG_CONFIG_HOME/opencode (XDG Base Directory spec)
30
+ 3. ~/.config/opencode (POSIX default)
31
+
32
+ Returns:
33
+ Absolute Path to the Pantheon global config directory.
34
+ """
35
+ env = os.environ.get("PANTHEON_HOME")
36
+ if env:
37
+ return Path(env).expanduser().resolve()
38
+
39
+ xdg = os.environ.get("XDG_CONFIG_HOME")
40
+ if xdg:
41
+ return Path(xdg).expanduser().resolve() / "opencode"
42
+
43
+ return Path.home() / ".config" / "opencode"
44
+
45
+
46
+ def pantheon_project() -> Path | None:
47
+ """Return the Pantheon project root directory.
48
+
49
+ Resolution priority:
50
+ 1. $PANTHEON_PROJECT env var (explicit override)
51
+ 2. Current working directory (set by MCP client's cwd in opencode.json)
52
+
53
+ Returns:
54
+ Absolute Path to the project root, or None if neither is available
55
+ (project-scoped resources like deepwork/memory-bank are unavailable).
56
+ """
57
+ env = os.environ.get("PANTHEON_PROJECT")
58
+ if env:
59
+ return Path(env).expanduser().resolve()
60
+
61
+ cwd = os.getcwd()
62
+ if cwd:
63
+ return Path(cwd).resolve()
64
+
65
+ return None
66
+
67
+
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env python3
2
+ """Pantheon Code Mode MCP Server.
3
+
4
+ Provides a confined execution environment for orchestration scripts
5
+ via MCP tools and resources.
6
+
7
+ Usage:
8
+ python scripts/code_mode_server.py
9
+
10
+ Or via MCP client (stdio transport):
11
+ pantheon-code-mode:
12
+ command: python
13
+ args: ["scripts/code_mode_server.py"]
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import asyncio
19
+ import os
20
+ import stat
21
+ from contextlib import suppress
22
+ from pathlib import Path
23
+
24
+ from _pantheon_paths import pantheon_home, pantheon_project
25
+ from mcp.server.fastmcp import FastMCP
26
+
27
+ # ── Constants ─────────────────────────────────────────────────────────────────
28
+ ALLOWED_EXTENSIONS: frozenset[str] = frozenset({".sh", ".py"})
29
+ SCRIPT_TIMEOUT: int = 30
30
+
31
+ # ── Scripts Directory Resolution ─────────────────────────────────────────────
32
+ # Priority:
33
+ # 1. /.opencode/.pantheon/code-mode/ (project install)
34
+ # 2. /.pantheon/code-mode/ (legacy fallback)
35
+ # 3. /.pantheon/code-mode/ (global fallback)
36
+ _PANTHEON_HOME: Path = pantheon_home()
37
+ _SCRIPTS_DIR_CANDIDATES: list[Path] = []
38
+ _proj = pantheon_project()
39
+ if _proj is not None:
40
+ _SCRIPTS_DIR_CANDIDATES.append(_proj / ".opencode" / ".pantheon" / "code-mode")
41
+ _SCRIPTS_DIR_CANDIDATES.append(_proj / ".pantheon" / "code-mode")
42
+ _SCRIPTS_DIR_CANDIDATES.append(_PANTHEON_HOME / ".pantheon" / "code-mode")
43
+
44
+ SCRIPTS_DIR: Path = _PANTHEON_HOME / ".pantheon" / "code-mode" # default
45
+ for _candidate in _SCRIPTS_DIR_CANDIDATES:
46
+ if _candidate.is_dir():
47
+ SCRIPTS_DIR = _candidate
48
+ break
49
+
50
+ # ── FastMCP App ───────────────────────────────────────────────────────────────
51
+ mcp = FastMCP(
52
+ "Pantheon Code Mode",
53
+ instructions="Confined script execution for Pantheon orchestration. "
54
+ "Scripts live in .pantheon/code-mode/ and must be .sh or .py files.",
55
+ )
56
+
57
+ # ── Helpers ───────────────────────────────────────────────────────────────────
58
+
59
+
60
+ def _validate_script_name(script_name: str) -> Path:
61
+ """Validate a script name and return its resolved path.
62
+
63
+ Raises ValueError if the name is invalid, traverses paths, or
64
+ has a disallowed extension.
65
+ """
66
+ if not script_name:
67
+ raise ValueError("Script name cannot be empty")
68
+
69
+ name = script_name.strip()
70
+ if name.startswith("."):
71
+ raise ValueError(f"Invalid script name: '{script_name}'")
72
+ if "/" in name or "\\" in name:
73
+ raise ValueError(f"Invalid script name: '{script_name}'")
74
+
75
+ ext = Path(name).suffix.lower()
76
+ if ext not in ALLOWED_EXTENSIONS:
77
+ allowed = ", ".join(sorted(ALLOWED_EXTENSIONS))
78
+ raise ValueError(f"Extension '{ext}' not allowed. Allowed: {allowed}")
79
+
80
+ script_path = (SCRIPTS_DIR / name).resolve()
81
+ if not str(script_path).startswith(str(SCRIPTS_DIR.resolve())):
82
+ raise ValueError(f"Invalid script name: '{script_name}'")
83
+ if not script_path.exists():
84
+ raise ValueError(f"Script '{script_name}' not found")
85
+
86
+ return script_path
87
+
88
+
89
+ def _format_output(
90
+ stdout: str, stderr: str, exit_code: int, timed_out: bool = False
91
+ ) -> str:
92
+ """Format script execution output into a readable string."""
93
+ parts: list[str] = []
94
+ if timed_out:
95
+ parts.append(f"[TIMEOUT] Script exceeded {SCRIPT_TIMEOUT}s limit")
96
+ if stdout:
97
+ parts.append(stdout)
98
+ if stderr:
99
+ parts.append(f"[stderr]\n{stderr}")
100
+ parts.append(f"--- exit code: {exit_code}")
101
+ return "\n".join(parts)
102
+
103
+
104
+ # ── Static Resources ──────────────────────────────────────────────────────────
105
+
106
+
107
+ @mcp.resource(
108
+ "pantheon://code-mode/scripts",
109
+ description="List of available code-mode scripts",
110
+ )
111
+ async def list_code_mode_scripts() -> str:
112
+ """Return a list of available scripts in the code-mode directory."""
113
+ if not SCRIPTS_DIR.is_dir():
114
+ return "Code mode directory not found."
115
+
116
+ scripts: list[str] = []
117
+ for f in sorted(SCRIPTS_DIR.iterdir()):
118
+ if f.is_file() and f.suffix.lower() in ALLOWED_EXTENSIONS:
119
+ scripts.append(f"- {f.name}")
120
+
121
+ return "\n".join(scripts) if scripts else "No scripts found."
122
+
123
+
124
+ # ── Template Resources ────────────────────────────────────────────────────────
125
+
126
+
127
+ @mcp.resource(
128
+ "pantheon://code-mode/scripts/{script_name}",
129
+ description="Content of a code-mode script by name",
130
+ )
131
+ async def get_code_mode_script(script_name: str) -> str:
132
+ """Return the source content of a code-mode script."""
133
+ try:
134
+ script_path = _validate_script_name(script_name)
135
+ return script_path.read_text(encoding="utf-8")
136
+ except ValueError as e:
137
+ return str(e)
138
+
139
+
140
+ # ── Tools ─────────────────────────────────────────────────────────────────────
141
+
142
+
143
+ @mcp.tool(
144
+ name="execute_code_script",
145
+ description="Run a .sh/.py script from .pantheon/code-mode/ "
146
+ "with optional args. 30s timeout.",
147
+ )
148
+ async def execute_code_script(script_name: str, args: list[str] | None = None) -> str:
149
+ """Execute a code-mode script with confinement and timeout.
150
+
151
+ Args:
152
+ script_name: Name of the script in the code-mode directory.
153
+ args: Optional CLI arguments forwarded to the subprocess (e.g.
154
+ ``["compress", "--text", "..."]``). Defaults to no args.
155
+ """
156
+ args = args or []
157
+ try:
158
+ script_path = _validate_script_name(script_name)
159
+ except ValueError as e:
160
+ return str(e)
161
+
162
+ # Ensure script is executable
163
+ if not os.access(script_path, os.X_OK):
164
+ with suppress(OSError):
165
+ mode = script_path.stat().st_mode
166
+ script_path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
167
+
168
+ script_dir = script_path.parent
169
+ try:
170
+ proc = await asyncio.create_subprocess_exec(
171
+ str(script_path),
172
+ *args,
173
+ cwd=str(script_dir),
174
+ stdout=asyncio.subprocess.PIPE,
175
+ stderr=asyncio.subprocess.PIPE,
176
+ )
177
+ try:
178
+ stdout, stderr = await asyncio.wait_for(
179
+ proc.communicate(), timeout=SCRIPT_TIMEOUT
180
+ )
181
+ return _format_output(
182
+ stdout.decode("utf-8", errors="replace"),
183
+ stderr.decode("utf-8", errors="replace"),
184
+ proc.returncode or 0,
185
+ )
186
+ except TimeoutError:
187
+ proc.kill()
188
+ await proc.wait()
189
+ return _format_output("", "", -1, timed_out=True)
190
+ except FileNotFoundError:
191
+ return (
192
+ f"Script '{script_name}' not found "
193
+ f"or interpreter missing.\n--- exit code: -1"
194
+ )
195
+ except OSError as e:
196
+ return f"Failed to execute script: {e}\n--- exit code: -1"
197
+
198
+
199
+ # ── Main Entrypoint ───────────────────────────────────────────────────────────
200
+
201
+ if __name__ == "__main__":
202
+ mcp.run()