pantheon-opencode 1.6.0-beta.6 → 1.6.0-beta.8
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 +19 -769
- package/CHANGELOG.md +53 -0
- package/README.md +78 -9
- package/README.pt-BR.md +10 -4
- package/package.json +5 -1
- package/plugin.json +2 -2
- package/pyproject.toml +3 -1
- package/scripts/build-agents-md.mjs +159 -26
- package/scripts/install/agents-md.mjs +2 -2
- package/scripts/install/config-migration.mjs +115 -1
- package/scripts/install/interactive.mjs +1 -1
- package/scripts/install/opencode.mjs +245 -12
- package/scripts/install/shared.mjs +115 -7
- package/scripts/sync-artifacts.mjs +2 -2
- package/scripts/test-opencode-v2-sandbox.sh +155 -4
- package/scripts/validate-sandbox-install.mjs +138 -0
- package/skills-lock.json +5 -5
- package/src/agents/aphrodite.md +18 -36
- package/src/agents/demeter.md +8 -27
- package/src/agents/gaia.md +1 -0
- package/src/agents/hephaestus.md +10 -20
- package/src/agents/hermes.md +15 -37
- package/src/agents/mnemosyne.md +14 -42
- package/src/agents/prometheus.md +16 -19
- package/src/agents/talos.md +4 -1
- package/src/agents/themis.md +4 -4
- package/src/agents/zeus.md +24 -127
- package/src/instructions/agent-return-format.instructions.md +2 -3
- package/src/instructions/backend-standards.instructions.md +2 -0
- package/src/instructions/frontend-standards.instructions.md +3 -1
- package/src/instructions/memory-protocol.instructions.md +11 -121
- package/src/instructions/zeus-anti-stall.instructions.md +14 -69
- package/src/instructions/zeus-communication-rules.instructions.md +0 -1
- package/src/instructions/zeus-council-synthesis.instructions.md +32 -260
- package/src/instructions/zeus-memory-operations.instructions.md +26 -0
- package/src/instructions/zeus-timeout-retry.instructions.md +40 -87
- package/src/pantheon/v2-delegation-enforce.ts +87 -0
- package/src/pantheon/v2-execute-after.ts +186 -0
- package/src/pantheon/v2-unsupported.mjs +0 -23
- package/src/plugin-v2.ts +271 -36
- package/src/plugins/tui/dist/tui.d.ts +11 -8
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +53 -37
- package/src/plugins/tui/package-lock.json +30 -33
- package/src/plugins/tui/package.json +5 -1
- package/src/plugins/tui/src/index.tsx +103 -82
- package/src/plugins/tui/tsdown.config.ts +17 -0
- package/src/routing.yml +10 -5
- package/src/skills/README.md +3 -3
- package/src/skills/artifact-management/SKILL.md +12 -5
- package/src/skills/auto-continue/SKILL.md +37 -28
- package/src/skills/code-review-checklist/SKILL.md +5 -5
- package/src/skills/context-compression/SKILL.md +26 -879
- package/src/skills/tdd-with-agents/SKILL.md +18 -10
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs'
|
|
21
21
|
import { join } from 'node:path'
|
|
22
|
+
import { copyAgentPrompts } from './build-agents-md.mjs'
|
|
22
23
|
|
|
23
24
|
import {
|
|
24
25
|
collectSkillNames,
|
|
25
|
-
copyFiles,
|
|
26
26
|
installSkills,
|
|
27
27
|
ROOT,
|
|
28
28
|
syncDir,
|
|
@@ -59,7 +59,7 @@ export function syncCopyArtifacts(configDir) {
|
|
|
59
59
|
const dstAgents = join(configDir, 'agents')
|
|
60
60
|
if (existsSync(join(ROOT, 'src', 'agents'))) {
|
|
61
61
|
mkdirSync(dstAgents, { recursive: true })
|
|
62
|
-
bump(
|
|
62
|
+
bump(copyAgentPrompts(join(ROOT, 'src', 'agents'), dstAgents, false))
|
|
63
63
|
const routing = join(ROOT, 'src', 'routing.yml')
|
|
64
64
|
if (existsSync(routing)) {
|
|
65
65
|
if (
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
# Env overrides:
|
|
35
35
|
# PANTHEON_SANDBOX_ROOT Sandbox root (default: ~/pantheon-sandbox)
|
|
36
36
|
# OPENCODE_V2_SPEC npm spec providing the V2 binary
|
|
37
|
-
# (default: @opencode
|
|
37
|
+
# (default: @opencode/cli@latest). NOTE: this is an
|
|
38
38
|
# install spec only — it is not a dependency of this
|
|
39
39
|
# package, and no code imports it.
|
|
40
40
|
# PANTHEON_REPO Repository the sandbox is built from (default: the
|
|
@@ -63,7 +63,7 @@ SANDBOX_ROOT="${PANTHEON_SANDBOX_ROOT:-$HOME/pantheon-sandbox}"
|
|
|
63
63
|
SANDBOX_HOME="$SANDBOX_ROOT/home"
|
|
64
64
|
NPM_PREFIX="$SANDBOX_HOME/.npm-global"
|
|
65
65
|
|
|
66
|
-
OPENCODE_V2_SPEC="${OPENCODE_V2_SPEC:-@opencode
|
|
66
|
+
OPENCODE_V2_SPEC="${OPENCODE_V2_SPEC:-@opencode/cli@latest}"
|
|
67
67
|
PANTHEON_SANDBOX_MODEL="${PANTHEON_SANDBOX_MODEL:-opencode-go/mimo-v2.5}"
|
|
68
68
|
PANTHEON_PROMPT_TIMEOUT="${PANTHEON_PROMPT_TIMEOUT:-300}"
|
|
69
69
|
|
|
@@ -418,6 +418,7 @@ V2_DB="${PANTHEON_V2_DB:-$SANDBOX_DIR/opencode-v2.db}"
|
|
|
418
418
|
V2_PORT="${PANTHEON_V2_PORT:-49376}"
|
|
419
419
|
V2_LOOPBACK_HOST="${PANTHEON_V2_HOST:-127.0.0.1}"
|
|
420
420
|
V2_HANDSHAKE_TIMEOUT="${PANTHEON_V2_HANDSHAKE_TIMEOUT:-60}"
|
|
421
|
+
V2_MCP_LIST_TIMEOUT="${PANTHEON_V2_MCP_LIST_TIMEOUT:-15}"
|
|
421
422
|
V2_SERVER_LOG="$SANDBOX_DIR/v2-server.log"
|
|
422
423
|
V2_SERVICE_STATE="$SANDBOX_HOME/.local/state/opencode/service.json"
|
|
423
424
|
V2_SERVER_PID=""
|
|
@@ -735,7 +736,8 @@ check_mcp() { # label binary project
|
|
|
735
736
|
if ! wait_for_v2_mcp_registrations "$project/.opencode" "$project/.venv/bin/python3"; then
|
|
736
737
|
kill "$list_pid" 2>/dev/null || true
|
|
737
738
|
wait "$list_pid" 2>/dev/null || true
|
|
738
|
-
|
|
739
|
+
echo "--- mcp list output (preserved at $list_file) ---" >&2
|
|
740
|
+
cat "$list_file" >&2 || true
|
|
739
741
|
record_status "$label MCP" "FAIL" "MCP registrations did not match the active sandbox"
|
|
740
742
|
return 0
|
|
741
743
|
fi
|
|
@@ -744,7 +746,8 @@ check_mcp() { # label binary project
|
|
|
744
746
|
else
|
|
745
747
|
kill "$list_pid" 2>/dev/null || true
|
|
746
748
|
wait "$list_pid" 2>/dev/null || true
|
|
747
|
-
|
|
749
|
+
echo "--- mcp list output (preserved at $list_file) ---" >&2
|
|
750
|
+
cat "$list_file" >&2 || true
|
|
748
751
|
record_status "$label MCP" "FAIL" "MCP handshakes did not become ready after mcp list started"
|
|
749
752
|
return 0
|
|
750
753
|
fi
|
|
@@ -809,6 +812,11 @@ if ! git -C "$REPO_DIR" ls-files --error-unmatch -- "$TGZ" >/dev/null 2>&1; then
|
|
|
809
812
|
echo "--- Init (headless) ---"
|
|
810
813
|
pantheon-opencode init --headless -y
|
|
811
814
|
rewrite_v2_mcp_config
|
|
815
|
+
node "$REPO_DIR/scripts/validate-sandbox-install.mjs" \
|
|
816
|
+
--repo "$REPO_DIR" \
|
|
817
|
+
--install "$NPM_PREFIX/lib/node_modules/pantheon-opencode" \
|
|
818
|
+
--project-config "$V2_CONFIG" \
|
|
819
|
+
--config "$PANTHEON_GLOBAL/opencode.json"
|
|
812
820
|
|
|
813
821
|
echo "--- MCP Validation ---"
|
|
814
822
|
check_mcp "V2" opencode2 "$SANDBOX_DIR/project-v2"
|
|
@@ -871,6 +879,139 @@ fi
|
|
|
871
879
|
echo "Next: open the isolated TUI with: $SANDBOX_DIR/start-pantheon.sh"
|
|
872
880
|
RUNTEST
|
|
873
881
|
chmod +x "$SANDBOX_ROOT/run-test.sh"
|
|
882
|
+
|
|
883
|
+
cat > "$SANDBOX_ROOT/start-pantheon.sh" <<'STARTPANTHEON'
|
|
884
|
+
#!/usr/bin/env bash
|
|
885
|
+
# Generated by pantheon-opencode scripts/test-opencode-v2-sandbox.sh --prepare
|
|
886
|
+
# Launches the installed OpenCode beta with only this sandbox's HOME/config/db.
|
|
887
|
+
set -euo pipefail
|
|
888
|
+
|
|
889
|
+
SANDBOX_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
890
|
+
SANDBOX_HOME="$SANDBOX_DIR/home"
|
|
891
|
+
NPM_PREFIX="$SANDBOX_HOME/.npm-global"
|
|
892
|
+
SANDBOX_VENV="$SANDBOX_HOME/.config/opencode/.venv"
|
|
893
|
+
V2_PORT="${PANTHEON_V2_PORT:-49376}"
|
|
894
|
+
if [[ ! "$V2_PORT" =~ ^[0-9]{1,5}$ ]]; then
|
|
895
|
+
echo "ERROR: PANTHEON_V2_PORT must be an integer from 1 through 65535 (got '$V2_PORT')" >&2
|
|
896
|
+
exit 1
|
|
897
|
+
fi
|
|
898
|
+
V2_PORT=$((10#$V2_PORT))
|
|
899
|
+
if [ "$V2_PORT" -lt 1 ] || [ "$V2_PORT" -gt 65535 ]; then
|
|
900
|
+
echo "ERROR: PANTHEON_V2_PORT must be an integer from 1 through 65535 (got '$V2_PORT')" >&2
|
|
901
|
+
exit 1
|
|
902
|
+
fi
|
|
903
|
+
if ! python3 - "$V2_PORT" "$NPM_PREFIX" <<'CHECKPORT'
|
|
904
|
+
import errno
|
|
905
|
+
import os
|
|
906
|
+
import socket
|
|
907
|
+
import sys
|
|
908
|
+
from urllib.request import urlopen
|
|
909
|
+
|
|
910
|
+
port = int(sys.argv[1])
|
|
911
|
+
cli_prefix = os.path.realpath(
|
|
912
|
+
os.path.join(sys.argv[2], "lib", "node_modules", "@opencode", "cli")
|
|
913
|
+
)
|
|
914
|
+
try:
|
|
915
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
|
916
|
+
# A just-stopped service can leave a harmless TIME_WAIT socket behind.
|
|
917
|
+
# SO_REUSEADDR permits the new service to bind that port without
|
|
918
|
+
# masking a live listener or an unrelated server.
|
|
919
|
+
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
920
|
+
probe.bind(("127.0.0.1", port))
|
|
921
|
+
except OSError as exc:
|
|
922
|
+
if exc.errno != errno.EADDRINUSE:
|
|
923
|
+
print(f"ERROR: cannot reserve dedicated V2 port 127.0.0.1:{port}: {exc}", file=sys.stderr)
|
|
924
|
+
raise SystemExit(1)
|
|
925
|
+
|
|
926
|
+
# OpenCode intentionally keeps its managed service alive after the TUI exits.
|
|
927
|
+
# Reuse it only when /proc proves the listener is this sandbox's CLI binary.
|
|
928
|
+
inodes = set()
|
|
929
|
+
for table in ("/proc/net/tcp", "/proc/net/tcp6"):
|
|
930
|
+
try:
|
|
931
|
+
with open(table, encoding="ascii") as entries:
|
|
932
|
+
next(entries, None)
|
|
933
|
+
for line in entries:
|
|
934
|
+
fields = line.split()
|
|
935
|
+
if len(fields) >= 10 and int(fields[1].rsplit(":", 1)[1], 16) == port and fields[3] == "0A":
|
|
936
|
+
inodes.add(fields[9])
|
|
937
|
+
except OSError:
|
|
938
|
+
pass
|
|
939
|
+
|
|
940
|
+
listener_pids = set()
|
|
941
|
+
for pid in (entry for entry in os.listdir("/proc") if entry.isdigit()):
|
|
942
|
+
fd_dir = f"/proc/{pid}/fd"
|
|
943
|
+
try:
|
|
944
|
+
for fd in os.listdir(fd_dir):
|
|
945
|
+
try:
|
|
946
|
+
target = os.readlink(os.path.join(fd_dir, fd))
|
|
947
|
+
except OSError:
|
|
948
|
+
continue
|
|
949
|
+
if target.startswith("socket:[") and target[8:-1] in inodes:
|
|
950
|
+
listener_pids.add(pid)
|
|
951
|
+
break
|
|
952
|
+
except OSError:
|
|
953
|
+
continue
|
|
954
|
+
|
|
955
|
+
owns_listener = False
|
|
956
|
+
for pid in listener_pids:
|
|
957
|
+
try:
|
|
958
|
+
with open(f"/proc/{pid}/cmdline", "rb") as command_line:
|
|
959
|
+
args = [arg.decode(errors="replace") for arg in command_line.read().split(b"\0") if arg]
|
|
960
|
+
except OSError:
|
|
961
|
+
continue
|
|
962
|
+
executable = os.path.realpath(args[0]) if args else ""
|
|
963
|
+
if (
|
|
964
|
+
executable.startswith(cli_prefix + os.sep)
|
|
965
|
+
and "serve" in args
|
|
966
|
+
and "--service" in args
|
|
967
|
+
and len(listener_pids) == 1
|
|
968
|
+
):
|
|
969
|
+
owns_listener = True
|
|
970
|
+
break
|
|
971
|
+
|
|
972
|
+
healthy = False
|
|
973
|
+
if owns_listener:
|
|
974
|
+
try:
|
|
975
|
+
scheme = "http"
|
|
976
|
+
loopback = ".".join(("127", "0", "0", "1"))
|
|
977
|
+
with urlopen(f"{scheme}://{loopback}:{port}/global/health", timeout=2) as response:
|
|
978
|
+
healthy = response.status == 200
|
|
979
|
+
except Exception:
|
|
980
|
+
pass
|
|
981
|
+
|
|
982
|
+
if not healthy:
|
|
983
|
+
print(
|
|
984
|
+
f"ERROR: dedicated V2 port 127.0.0.1:{port} is occupied by a different or unverifiable service; no service was stopped",
|
|
985
|
+
file=sys.stderr,
|
|
986
|
+
)
|
|
987
|
+
raise SystemExit(1)
|
|
988
|
+
print(f"Reusing the existing beta sandbox OpenCode service on 127.0.0.1:{port} (PID {next(iter(listener_pids))}).")
|
|
989
|
+
CHECKPORT
|
|
990
|
+
then
|
|
991
|
+
exit 1
|
|
992
|
+
fi
|
|
993
|
+
|
|
994
|
+
export HOME="$SANDBOX_HOME"
|
|
995
|
+
export PATH="$NPM_PREFIX/bin:$SANDBOX_VENV/bin:$PATH"
|
|
996
|
+
export npm_config_prefix="$NPM_PREFIX"
|
|
997
|
+
export XDG_CONFIG_HOME="$SANDBOX_HOME/.config"
|
|
998
|
+
export XDG_CONFIG_DIRS="$SANDBOX_HOME/.config/xdg"
|
|
999
|
+
export XDG_DATA_HOME="$SANDBOX_HOME/.local/share"
|
|
1000
|
+
export XDG_DATA_DIRS="$SANDBOX_HOME/.local/share/xdg"
|
|
1001
|
+
export XDG_STATE_HOME="$SANDBOX_HOME/.local/state"
|
|
1002
|
+
export XDG_CACHE_HOME="$SANDBOX_HOME/.cache"
|
|
1003
|
+
export OPENCODE_CONFIG_DIR="$SANDBOX_DIR/project-v2"
|
|
1004
|
+
export OPENCODE_DB="${PANTHEON_V2_DB:-$SANDBOX_DIR/opencode-v2.db}"
|
|
1005
|
+
export PANTHEON_V2_PORT="$V2_PORT"
|
|
1006
|
+
export PORT="$PANTHEON_V2_PORT"
|
|
1007
|
+
unset OPENCODE_CONFIG OPENCODE_CONFIG_CONTENT OPENCODE_CONFIG_PROJECT_DISABLE \
|
|
1008
|
+
OPENCODE_DATA_DIR OPENCODE_STATE_DIR OPENCODE_CACHE_DIR OPENCODE_STORAGE_PATH
|
|
1009
|
+
|
|
1010
|
+
"$NPM_PREFIX/bin/opencode2" service set port "$V2_PORT"
|
|
1011
|
+
cd "$SANDBOX_DIR/project-v2"
|
|
1012
|
+
exec "$NPM_PREFIX/bin/opencode2" "$@"
|
|
1013
|
+
STARTPANTHEON
|
|
1014
|
+
chmod +x "$SANDBOX_ROOT/start-pantheon.sh"
|
|
874
1015
|
}
|
|
875
1016
|
|
|
876
1017
|
# ── Reset / Prepare ───────────────────────────────────────────────────────────
|
|
@@ -990,6 +1131,7 @@ prepare_project() {
|
|
|
990
1131
|
(cd "$dir" && "$pan" init --project --version "$TARGET_VERSION" --model "$PANTHEON_SANDBOX_MODEL" -y) \
|
|
991
1132
|
|| die "init --project --version $TARGET_VERSION failed in $dir"
|
|
992
1133
|
rewrite_v2_mcp_config
|
|
1134
|
+
validate_sandbox_install
|
|
993
1135
|
}
|
|
994
1136
|
|
|
995
1137
|
# The installer merges an existing project config. A reused sandbox can
|
|
@@ -1077,6 +1219,15 @@ for name, script in managed.items():
|
|
|
1077
1219
|
PY
|
|
1078
1220
|
}
|
|
1079
1221
|
|
|
1222
|
+
validate_sandbox_install() {
|
|
1223
|
+
node "$REPO_DIR/scripts/validate-sandbox-install.mjs" \
|
|
1224
|
+
--repo "$REPO_DIR" \
|
|
1225
|
+
--install "$NPM_PREFIX/lib/node_modules/pantheon-opencode" \
|
|
1226
|
+
--project-config "$V2_CONFIG" \
|
|
1227
|
+
--config "$SANDBOX_HOME/.config/opencode/opencode.json" \
|
|
1228
|
+
|| die "sandbox package/config validation failed"
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1080
1231
|
cmd_prepare() {
|
|
1081
1232
|
mkdir -p "$SANDBOX_HOME" "$NPM_PREFIX"
|
|
1082
1233
|
write_run_test_sh
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from 'node:crypto'
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
import { join, resolve } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
|
|
7
|
+
const PACKAGE_REF = /^(?:npm:)?pantheon-opencode(?:@[^/]+)?(?:\/(?:plugin|plugin-v2))?$/
|
|
8
|
+
export const SANDBOX_SOURCE_FILES = [
|
|
9
|
+
'bin/pantheon-init.mjs',
|
|
10
|
+
'scripts/install/config-migration.mjs',
|
|
11
|
+
'scripts/install/opencode.mjs',
|
|
12
|
+
'scripts/validate-sandbox-install.mjs',
|
|
13
|
+
'src/plugin-v2.ts',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
function readJson(path, label) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(readFileSync(path, 'utf8'))
|
|
19
|
+
} catch (error) {
|
|
20
|
+
throw new Error(`cannot read ${label} at ${path}: ${error.message}`)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function pluginReferences(config) {
|
|
25
|
+
const refs = []
|
|
26
|
+
for (const key of ['plugin', 'plugins']) {
|
|
27
|
+
const value = config?.[key]
|
|
28
|
+
const entries = Array.isArray(value) ? value : value === undefined ? [] : [value]
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
if (typeof entry === 'string') refs.push(entry)
|
|
31
|
+
else if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
32
|
+
for (const field of ['package', 'id', 'name', 'path', 'source']) {
|
|
33
|
+
if (typeof entry[field] === 'string') refs.push(entry[field])
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return refs
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Prove that the sandbox is executing the package built from this checkout and
|
|
43
|
+
* that the project config cannot silently load an older npm-cached Pantheon.
|
|
44
|
+
*/
|
|
45
|
+
export function validateSandboxInstall({ repoDir, installDir, projectConfig, configPaths = [] }) {
|
|
46
|
+
const sourcePackagePath = join(resolve(repoDir), 'package.json')
|
|
47
|
+
const installedPackagePath = join(resolve(installDir), 'package.json')
|
|
48
|
+
const sourcePackage = readJson(sourcePackagePath, 'checkout package manifest')
|
|
49
|
+
const installedPackage = readJson(installedPackagePath, 'sandbox package manifest')
|
|
50
|
+
if (sourcePackage.version !== installedPackage.version) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`sandbox package version ${installedPackage.version} does not match checkout ${sourcePackage.version}`,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const sourceHash = createHash('sha256')
|
|
57
|
+
for (const relativePath of SANDBOX_SOURCE_FILES) {
|
|
58
|
+
let source
|
|
59
|
+
let installed
|
|
60
|
+
try {
|
|
61
|
+
source = readFileSync(join(resolve(repoDir), relativePath))
|
|
62
|
+
installed = readFileSync(join(resolve(installDir), relativePath))
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw new Error(`cannot compare package source ${relativePath}: ${error.message}`)
|
|
65
|
+
}
|
|
66
|
+
if (!source.equals(installed)) {
|
|
67
|
+
throw new Error(`installed package source differs from checkout: ${relativePath}`)
|
|
68
|
+
}
|
|
69
|
+
sourceHash.update(relativePath).update('\0').update(installed).update('\0')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const expectedPluginPath = resolve(installDir, 'src', 'plugin-v2')
|
|
73
|
+
const allConfigPaths = [...new Set([projectConfig, ...configPaths].map((path) => resolve(path)))]
|
|
74
|
+
let activeProjectPluginCount = 0
|
|
75
|
+
for (const configPath of allConfigPaths) {
|
|
76
|
+
const config = readJson(configPath, 'active OpenCode config')
|
|
77
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
78
|
+
throw new Error(`active OpenCode config must be an object: ${configPath}`)
|
|
79
|
+
}
|
|
80
|
+
for (const ref of pluginReferences(config)) {
|
|
81
|
+
const normalized = ref.replaceAll('\\', '/').replace(/\/$/, '')
|
|
82
|
+
if (PACKAGE_REF.test(normalized)) {
|
|
83
|
+
throw new Error(`stale Pantheon package reference in ${configPath}: ${ref}`)
|
|
84
|
+
}
|
|
85
|
+
if (resolve(ref) === expectedPluginPath) {
|
|
86
|
+
if (configPath === resolve(projectConfig)) activeProjectPluginCount += 1
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (activeProjectPluginCount !== 1) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`project config must reference the sandbox V2 plugin exactly once; found ${activeProjectPluginCount}: ${resolve(projectConfig)}`,
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
status: 'PASS',
|
|
98
|
+
version: installedPackage.version,
|
|
99
|
+
sourceSha256: sourceHash.digest('hex'),
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function parseArgs(args) {
|
|
104
|
+
const options = { configPaths: [] }
|
|
105
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
106
|
+
const arg = args[i]
|
|
107
|
+
if (arg === '--config') options.configPaths.push(args[++i])
|
|
108
|
+
else if (['--repo', '--install', '--project-config'].includes(arg))
|
|
109
|
+
options[arg.slice(2).replace('-', '')] = args[++i]
|
|
110
|
+
else throw new Error(`unknown argument: ${arg}`)
|
|
111
|
+
}
|
|
112
|
+
for (const required of ['repo', 'install', 'projectconfig']) {
|
|
113
|
+
if (!options[required])
|
|
114
|
+
throw new Error(
|
|
115
|
+
`missing required option --${required.replace('projectconfig', 'project-config')}`,
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
repoDir: options.repo,
|
|
120
|
+
installDir: options.install,
|
|
121
|
+
projectConfig: options.projectconfig,
|
|
122
|
+
configPaths: options.configPaths,
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function main() {
|
|
127
|
+
try {
|
|
128
|
+
const result = validateSandboxInstall(parseArgs(process.argv.slice(2)))
|
|
129
|
+
process.stdout.write(
|
|
130
|
+
`Sandbox install proof: ${result.status} (version ${result.version}, source sha256 ${result.sourceSha256})\n`,
|
|
131
|
+
)
|
|
132
|
+
} catch (error) {
|
|
133
|
+
process.stderr.write(`Sandbox install proof: FAIL — ${error.message}\n`)
|
|
134
|
+
process.exitCode = 1
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) main()
|
package/skills-lock.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"src/skills/agent-coordination/SKILL.md": "cfd040ef29e3dd0fd5e0e67773874facf7c06e3284898afe1fddba4456c3f1aa",
|
|
3
|
-
"src/skills/artifact-management/SKILL.md": "
|
|
4
|
-
"src/skills/auto-continue/SKILL.md": "
|
|
3
|
+
"src/skills/artifact-management/SKILL.md": "91d68c8f2d1b642725adfc6df11e55865a8491aca690dc27d02ea7c778d49d41",
|
|
4
|
+
"src/skills/auto-continue/SKILL.md": "79aa1786ab62332abc45986d147ee944836e6f7a496c289e793639add4211229",
|
|
5
5
|
"src/skills/clonedeps/SKILL.md": "3e0b43cf96da98c83699dc48db3fd58fbfda84b9022bb70d3747875c81c3e124",
|
|
6
|
-
"src/skills/code-review-checklist/SKILL.md": "
|
|
6
|
+
"src/skills/code-review-checklist/SKILL.md": "6445513ab880efdb1005861ae13cb9a215f80b9d260f6278499f60771e07858c",
|
|
7
7
|
"src/skills/codemap/SKILL.md": "87af5271e1576e154cadff313eab16264b726e43d094520736b7a5b03b409a4a",
|
|
8
|
-
"src/skills/context-compression/SKILL.md": "
|
|
8
|
+
"src/skills/context-compression/SKILL.md": "346ed8697b8db42cc49403ee3dd38f9b775fe93cb55395d73c81b55cc77442d6",
|
|
9
9
|
"src/skills/git-workflow-and-versioning/SKILL.md": "685a94041f1b4ce2a430301f517b89102055696267c369a7e8fb191606659276",
|
|
10
10
|
"src/skills/incremental-implementation/SKILL.md": "7065ddb45da3c6b6751de07f290e92e6cb072e949a2e3aaae13ee7762f0a70fc",
|
|
11
11
|
"src/skills/loop-engineering/SKILL.md": "f804e545821b9cca8fe0ec517307ed902446f28c2ca8ff9194c0f987cf7229e3",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"src/skills/session-goal/SKILL.md": "793498265f70253c20524f6adc88bcc86db11d7160fe8232a26aedc6b1c5489f",
|
|
16
16
|
"src/skills/simplify/SKILL.md": "aa739e9f75962a77d6574557d674ea01b5aea95e580b721d66c058753161b053",
|
|
17
17
|
"src/skills/spec-driven-development/SKILL.md": "e93b9103d6cd1848ab01328efde82c1daf564d1cabbdc2278254b346fcf55d02",
|
|
18
|
-
"src/skills/tdd-with-agents/SKILL.md": "
|
|
18
|
+
"src/skills/tdd-with-agents/SKILL.md": "9d20607ea34fc3e02c7e3ef5186b103d84196bd9a13aaadd55a44f41339125da",
|
|
19
19
|
"src/skills/verification-planning/SKILL.md": "14186446ef51e1ba2c87b337e8d5f86e566d805156ab21fba2a75f8c4a99df3f",
|
|
20
20
|
"src/skills/visual-review-pipeline/SKILL.md": "f6e65ff75a9325c38e0e9dde8e87f09b873e14e490936ef48aa80201bb68fe10",
|
|
21
21
|
"src/skills/worktrees/SKILL.md": "0033c5f8a50f210f31f0256ba8cc0fd99c65674a2aabedb05d8e69447b5ec218"
|
package/src/agents/aphrodite.md
CHANGED
|
@@ -23,6 +23,7 @@ mcp_tools:
|
|
|
23
23
|
pantheon-resources: all
|
|
24
24
|
pantheon-memory: [memory_search]
|
|
25
25
|
pantheon-code-mode: [execute_code_script]
|
|
26
|
+
pantheon-vision: [vision_describe, vision_ocr, vision_analyze]
|
|
26
27
|
---
|
|
27
28
|
|
|
28
29
|
## When NOT to Use Aphrodite
|
|
@@ -37,7 +38,7 @@ You are a frontend implementation specialist. You BUILD UI. You do NOT design ar
|
|
|
37
38
|
|
|
38
39
|
**You MUST:**
|
|
39
40
|
- Implement React components with TypeScript strict mode
|
|
40
|
-
-
|
|
41
|
+
- For testable behavior changes, follow RED→GREEN→REFACTOR; use focused verification for non-behavioral edits
|
|
41
42
|
- Ensure WCAG AA accessibility on every component
|
|
42
43
|
- Use mobile-first responsive design
|
|
43
44
|
|
|
@@ -50,17 +51,17 @@ You are a frontend implementation specialist. You BUILD UI. You do NOT design ar
|
|
|
50
51
|
## Workflow
|
|
51
52
|
|
|
52
53
|
### Before Implementation
|
|
53
|
-
1.
|
|
54
|
-
2. Read relevant
|
|
55
|
-
3. Plan component
|
|
54
|
+
1. Inspect relevant component/test/API files directly; use @apollo only when broad or unfamiliar discovery materially reduces risk
|
|
55
|
+
2. Read relevant frontend standards; load `skill: visual-review-pipeline` only when visual behavior changed
|
|
56
|
+
3. Plan component/data flow when scope or risk warrants it; do not require a design phase for a bounded fix
|
|
56
57
|
|
|
57
58
|
### Implementation (TDD)
|
|
58
59
|
See `skill: tdd-with-agents` for the full TDD cycle.
|
|
59
60
|
|
|
60
61
|
### Post-Implementation
|
|
61
|
-
1.
|
|
62
|
-
2. Send to @themis
|
|
63
|
-
3. Report
|
|
62
|
+
1. Verify the changed behavior with focused tests; use Playwright screenshots when the change affects visible layout or interaction
|
|
63
|
+
2. Send auth/security-sensitive or material UI changes to @themis; a trivial isolated edit needs no separate review phase
|
|
64
|
+
3. Report changed components and checks run; report coverage only when measured and relevant
|
|
64
65
|
|
|
65
66
|
## Anti-Stall Rules
|
|
66
67
|
|
|
@@ -73,12 +74,9 @@ See `skill: tdd-with-agents` for the full TDD cycle.
|
|
|
73
74
|
| 3 turns no progress | No new code or test in 3 turns | Output \`[APHRODITE_STALL]\`. Escalate to @zeus with: "Stuck on [component]. Last progress: [description]." |
|
|
74
75
|
|
|
75
76
|
## Pre-Implementation Recall
|
|
76
|
-
|
|
77
|
-
1. Run: @mnemosyne Recall "<feature>" --top-k 3 --agent aphrodite
|
|
78
|
-
2. Review past UI patterns and component decisions
|
|
79
|
-
3. Check for existing similar implementations
|
|
77
|
+
Use relevant supplied memory/ADR context when available. Inspect nearby components for reuse; do not block a bounded fix on a separate recall or broad discovery task.
|
|
80
78
|
|
|
81
|
-
## Visual Review Pipeline
|
|
79
|
+
## Visual Review Pipeline (when visual behavior changes)
|
|
82
80
|
|
|
83
81
|
After implementing UI components:
|
|
84
82
|
1. Capture screenshot via Playwright: `browser_navigate` to component, `browser_screenshotPage`
|
|
@@ -88,41 +86,25 @@ After implementing UI components:
|
|
|
88
86
|
|
|
89
87
|
## Handoff Rules
|
|
90
88
|
|
|
91
|
-
- **To @apollo:**
|
|
92
|
-
- **To @themis:**
|
|
89
|
+
- **To @apollo:** For broad/unfamiliar discovery only; otherwise inspect the relevant local files.
|
|
90
|
+
- **To @themis:** For auth/security-sensitive or material UI changes; include focused accessibility and behavior checks.
|
|
93
91
|
- **To @zeus:** Only for escalations (stuck, conflicting requirements, scope change)
|
|
94
92
|
|
|
95
93
|
## Efficiency Rules
|
|
96
94
|
|
|
97
|
-
-
|
|
95
|
+
- Search/read relevant files directly; delegate broad discovery to @apollo only when useful
|
|
98
96
|
- Use Context7 only for React/Next.js/TypeScript library docs
|
|
99
|
-
- Run
|
|
100
|
-
-
|
|
97
|
+
- Run focused tests for each changed behavior; run the broader suite when the change crosses integration boundaries or risk warrants it
|
|
98
|
+
- Read only as much context as the task needs; a fixed file-count limit must not trigger unnecessary delegation
|
|
101
99
|
|
|
102
100
|
## Auto-Continue (Embedded: UI TDD Cycles)
|
|
103
101
|
|
|
104
|
-
- Auto-continue through component test cycles (RED→GREEN→REFACTOR)
|
|
105
|
-
-
|
|
102
|
+
- Auto-continue through relevant component test cycles (RED→GREEN→REFACTOR)
|
|
103
|
+
- Capture screenshots only for changes to visual layout or interaction, not every test iteration
|
|
106
104
|
- After max 3 visual review iterations, stop for accessibility audit
|
|
107
|
-
- Stop for Themis review
|
|
105
|
+
- Stop for required Themis review on sensitive/material changes
|
|
108
106
|
- Do NOT auto-continue on visual regression — stop and diagnose
|
|
109
107
|
- Partial results NOT allowed — must complete or fail
|
|
110
108
|
|
|
111
|
-
## Inline Compression
|
|
112
|
-
|
|
113
|
-
Compress working context with the `context-compression` skill (L1, Pantheon-native) when:
|
|
114
|
-
- > Inline compression: See `skill: context-compression` (C8, C9, C11)
|
|
115
|
-
|
|
116
|
-
**How**: call `execute_code_script("compress-inline.py", args=["compress", "--text", "<content>"])`. Use `score` to preview priority, `batch` for multiple files. See the `context-compression` skill for the full protocol.
|
|
117
|
-
|
|
118
|
-
**Note**: scrubbing is automatic in the MCP layer; never embed raw secrets in the `--text` argument beyond what the tool scrubs.
|
|
119
|
-
|
|
120
109
|
## Skills
|
|
121
110
|
Frontend: `tdd-with-agents`, `visual-review-pipeline`, `incremental-implementation`
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
## Session Context Retrieval
|
|
125
|
-
When dispatched by Zeus, call context_get(slug=slug, key="latest", session_id=SESSION_ID)
|
|
126
|
-
if session_slug is provided in dispatch metadata. Apply any "remaining_tasks",
|
|
127
|
-
"current_phase", or "gotchas" from the retrieved context.
|
|
128
|
-
If context_get returns None, proceed fresh (first phase or expired session).
|
package/src/agents/demeter.md
CHANGED
|
@@ -45,24 +45,21 @@ You are a database specialist. You design schemas, write migrations, and optimiz
|
|
|
45
45
|
## Workflow
|
|
46
46
|
|
|
47
47
|
### Before Migration
|
|
48
|
-
1.
|
|
49
|
-
2. Read
|
|
48
|
+
1. Inspect relevant schema/migrations directly; use @apollo only when the schema is broad or unfamiliar enough that parallel discovery reduces risk
|
|
49
|
+
2. Read the relevant model/migration context to understand relationships
|
|
50
50
|
3. Plan migration: what changes, impact on existing data, rollback strategy
|
|
51
51
|
|
|
52
52
|
### Migration Development (TDD)
|
|
53
53
|
See `skill: tdd-with-agents` for the full TDD cycle.
|
|
54
54
|
|
|
55
|
-
### Post-Migration
|
|
55
|
+
### Post-Migration (required for schema/data changes)
|
|
56
56
|
1. Run EXPLAIN on new queries to verify index usage
|
|
57
57
|
2. Check for N+1 patterns in any new relationships
|
|
58
58
|
3. Send to @themis for quality gate review
|
|
59
59
|
4. Report: "Migration complete. Tables: [list]. Indexes: [list]. Rollback tested: [OK]."
|
|
60
60
|
|
|
61
61
|
## Pre-Migration Recall
|
|
62
|
-
|
|
63
|
-
1. Run: @mnemosyne Recall "<schema change>" --top-k 3 --agent demeter
|
|
64
|
-
2. Review past migration patterns and rollback strategies
|
|
65
|
-
3. Check for existing schema decisions in ADRs
|
|
62
|
+
Use supplied ADR/memory context when relevant. For every schema/data migration, document impact and rollback and keep both upgrade/downgrade verification and Themis review; do not require a separate recall task when context is already available.
|
|
66
63
|
|
|
67
64
|
## Anti-Stall Rules
|
|
68
65
|
|
|
@@ -82,36 +79,20 @@ Before creating a new migration:
|
|
|
82
79
|
|
|
83
80
|
## Efficiency Rules
|
|
84
81
|
|
|
85
|
-
-
|
|
82
|
+
- Inspect the relevant schema directly; delegate only broad/unfamiliar discovery that materially reduces migration risk
|
|
86
83
|
- Use Context7 only for SQLAlchemy/Alembic/PostgreSQL library docs
|
|
87
84
|
- Always write the rollback BEFORE testing the upgrade
|
|
88
|
-
-
|
|
85
|
+
- Read the model/migration files needed to establish affected relationships; do not delegate solely to satisfy an arbitrary file-count limit
|
|
89
86
|
- Batch multiple related schema changes into ONE migration (not one per column)
|
|
90
87
|
|
|
91
88
|
## Auto-Continue (Embedded: Migration Cycles)
|
|
92
89
|
|
|
93
|
-
- Auto-continue through migration + downgrade tests (upgrade → verify → downgrade → verify)
|
|
94
|
-
-
|
|
90
|
+
- Auto-continue through migration + downgrade tests for an authorized schema change (upgrade → verify → downgrade → verify)
|
|
91
|
+
- Do not require a checkpoint script for a one-off command; checkpoint only long-running/multi-phase work when context-loss risk warrants it
|
|
95
92
|
- Stop for data integrity review before finalizing
|
|
96
93
|
- Do NOT auto-continue when migration fails — stop and diagnose
|
|
97
94
|
- Always test both upgrade AND downgrade before marking complete
|
|
98
95
|
- Partial results NOT allowed — must complete or fail
|
|
99
96
|
|
|
100
|
-
## Inline Compression
|
|
101
|
-
|
|
102
|
-
Compress working context with the `context-compression` skill (L1, Pantheon-native) when:
|
|
103
|
-
- > Inline compression: See `skill: context-compression` (C8, C9, C11)
|
|
104
|
-
|
|
105
|
-
**How**: call `execute_code_script("compress-inline.py", args=["compress", "--text", "<content>"])`. Use `score` to preview priority, `batch` for multiple files. See the `context-compression` skill for the full protocol.
|
|
106
|
-
|
|
107
|
-
**Note**: scrubbing is automatic in the MCP layer; never embed raw secrets in the `--text` argument beyond what the tool scrubs.
|
|
108
|
-
|
|
109
97
|
## Skills
|
|
110
98
|
`tdd-with-agents`, `incremental-implementation`
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
## Session Context Retrieval
|
|
114
|
-
When dispatched by Zeus, call context_get(slug=slug, key="latest", session_id=SESSION_ID)
|
|
115
|
-
if session_slug is provided in dispatch metadata. Apply any "remaining_tasks",
|
|
116
|
-
"current_phase", or "gotchas" from the retrieved context.
|
|
117
|
-
If context_get returns None, proceed fresh (first phase or expired session).
|
package/src/agents/gaia.md
CHANGED
package/src/agents/hephaestus.md
CHANGED
|
@@ -42,29 +42,19 @@ mcp_tools:
|
|
|
42
42
|
- Guardrails and safety checks
|
|
43
43
|
|
|
44
44
|
## Handoffs
|
|
45
|
-
- **@apollo**: For RAG research
|
|
46
|
-
- **@themis**: For
|
|
45
|
+
- **@apollo**: For broad/unfamiliar RAG research or library patterns when direct local inspection is insufficient
|
|
46
|
+
- **@themis**: For auth/security-sensitive or material pipeline changes
|
|
47
|
+
|
|
48
|
+
## Proportional Execution
|
|
49
|
+
|
|
50
|
+
- Inspect only the relevant chain/config/tests; implement a clear bounded task directly without a mandatory planning or discovery phase.
|
|
51
|
+
- Delegate research only when external or broad context is genuinely needed; verify changed behavior with focused tests/evaluation, not a full pipeline for an unrelated micro-edit.
|
|
52
|
+
- Preserve review and human approval for auth/security, data-integrity, production-impacting, destructive, or global changes. Full-auto cannot authorize those actions.
|
|
47
53
|
|
|
48
54
|
## Auto-Continue (Embedded: Pipeline)
|
|
49
55
|
|
|
50
|
-
- Auto-continue through
|
|
51
|
-
-
|
|
56
|
+
- Auto-continue through authorized pipeline stages when the task requires them; do not run unrelated stages for a bounded fix
|
|
57
|
+
- Do not require a checkpoint script for a one-off command; checkpoint only long-running/multi-phase work when context-loss risk warrants it
|
|
52
58
|
- Stop for evaluation before marking pipeline as production-ready
|
|
53
59
|
- If a stage fails, stop and diagnose — re-run with adjusted parameters
|
|
54
60
|
- Partial results NOT allowed — pipeline must be verified end-to-end
|
|
55
|
-
|
|
56
|
-
## Inline Compression
|
|
57
|
-
|
|
58
|
-
Compress working context with the `context-compression` skill (L1, Pantheon-native) when:
|
|
59
|
-
- > Inline compression: See `skill: context-compression` (C8, C9, C11)
|
|
60
|
-
|
|
61
|
-
**How**: call `execute_code_script("compress-inline.py", args=["compress", "--text", "<content>"])`. Use `score` to preview priority, `batch` for multiple files. See the `context-compression` skill for the full protocol.
|
|
62
|
-
|
|
63
|
-
**Note**: scrubbing is automatic in the MCP layer; never embed raw secrets in the `--text` argument beyond what the tool scrubs.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
## Session Context Retrieval
|
|
67
|
-
When dispatched by Zeus, call context_get(slug=slug, key="latest", session_id=SESSION_ID)
|
|
68
|
-
if session_slug is provided in dispatch metadata. Apply any "remaining_tasks",
|
|
69
|
-
"current_phase", or "gotchas" from the retrieved context.
|
|
70
|
-
If context_get returns None, proceed fresh (first phase or expired session).
|