pantheon-opencode 1.4.3 → 1.5.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pantheon/code-mode/README.md +93 -0
- package/.pantheon/code-mode/checkpoint-session.py +7 -0
- package/.pantheon/code-mode/checkpoint-session.sh +11 -0
- package/.pantheon/code-mode/checkpoint_session.py +481 -0
- package/.pantheon/code-mode/compress-inline.py +450 -0
- package/.pantheon/code-mode/eval-llm-judge.py +255 -0
- package/.pantheon/code-mode/eval-monte-carlo.py +368 -0
- package/.pantheon/code-mode/eval-run.py +197 -0
- package/.pantheon/code-mode/eval-static.py +321 -0
- package/.pantheon/code-mode/example-sync.sh +5 -0
- package/.pantheon/code-mode/session-end-save.py +186 -0
- package/.pantheon/code-mode/session-end-save.sh +18 -0
- package/.pantheon/code-mode/test-allowed.sh +5 -0
- package/.pantheon/code-mode/test-auto-continue-checkpoint.sh +179 -0
- package/.pantheon/code-mode/test-env.sh +7 -0
- package/.pantheon/code-mode/test-prlimit.sh +9 -0
- package/.pantheon/code-mode/test-timeout.sh +6 -0
- package/README.md +161 -1101
- package/README.pt-BR.md +222 -0
- package/bin/pantheon-init.mjs +30 -11
- package/opencode.json +1 -0
- package/package.json +14 -3
- package/plugin.json +15 -0
- package/scripts/_pantheon_paths.py +48 -7
- package/scripts/ci-validate-yaml.py +7 -2
- package/scripts/code_mode_server.py +33 -2
- package/scripts/cost.mjs +30 -14
- package/scripts/doctor.mjs +135 -127
- package/scripts/generate-preset-docs.mjs +127 -66
- package/scripts/generate-prompts.sh +2 -2
- package/scripts/install/cli-ui.mjs +8 -1
- package/scripts/install/config-migration.mjs +401 -0
- package/scripts/install/health-check.mjs +3 -3
- package/scripts/install/model-picker.mjs +72 -23
- package/scripts/install/opencode-version.mjs +47 -0
- package/scripts/install/opencode.mjs +261 -88
- package/scripts/install/plugin.mjs +112 -42
- package/scripts/install/shared.mjs +13 -158
- package/scripts/install-mcp.mjs +44 -232
- package/scripts/mcp_persistence_server.py +382 -16
- package/scripts/mcp_resources_server.py +144 -40
- package/scripts/memory_mcp_server.py +2 -2
- package/scripts/probe-context-rehydrate.mjs +240 -0
- package/scripts/probe-pantheon-cost.mjs +304 -0
- package/scripts/recover-zenodo-deposition.mjs +146 -0
- package/scripts/sync-tui.mjs +32 -7
- package/scripts/test-opencode-v1-v2-sandbox.sh +944 -0
- package/scripts/uninstall.mjs +298 -954
- package/scripts/validate-zenodo-release.mjs +154 -0
- package/scripts/zenodo-artifact.mjs +21 -0
- package/scripts/zenodo-release-state.mjs +53 -0
- package/src/agents/zeus.md +25 -14
- package/src/mcp/_pantheon_paths.py +48 -7
- package/src/mcp/code_mode_server.py +33 -2
- package/src/mcp/eval_store.py +1 -1
- package/src/mcp/mcp_persistence_server.py +382 -16
- package/src/mcp/mcp_resources_server.py +144 -40
- package/src/mcp/pantheon_vision_server.py +1 -1
- package/src/mcp/toon_codec.py +491 -0
- package/src/pantheon/background-job-board.ts +67 -1
- package/src/pantheon/cost-command.ts +60 -39
- package/src/pantheon/delegate-manager.ts +669 -0
- package/src/pantheon/delegation-classifier.ts +307 -0
- package/src/pantheon/delegation-enforce.ts +13 -12
- package/src/pantheon/delegation-finalize.ts +374 -0
- package/src/pantheon/delegation-notify.ts +212 -0
- package/src/pantheon/delegation-toast.ts +73 -0
- package/src/pantheon/delegation.ts +1120 -0
- package/src/pantheon/goal-loop.ts +2 -2
- package/src/pantheon/goal-store.ts +1 -1
- package/src/pantheon/hashline/tool.ts +2 -2
- package/src/pantheon/model-command.ts +2 -1
- package/src/pantheon/permission-eval.ts +284 -0
- package/src/pantheon/step-cap.ts +110 -0
- package/src/pantheon/token-opt.ts +781 -0
- package/src/pantheon/toon-codec.ts +481 -0
- package/src/pantheon/tui-version-check.ts +1 -3
- package/src/pantheon/v2-bridge.ts +129 -0
- package/src/pantheon/v2-events.ts +131 -0
- package/src/pantheon/v2-hooks.ts +287 -0
- package/src/pantheon/v2-tool-definitions.ts +331 -0
- package/src/pantheon/zeus-delegate-with-retry.ts +406 -0
- package/src/plugin-v2/index.ts +21 -0
- package/src/plugin-v2.ts +824 -0
- package/src/plugin.ts +236 -42
- package/src/plugins/tui/dist/server.d.ts +1 -1
- package/src/plugins/tui/dist/server.d.ts.map +1 -1
- package/src/plugins/tui/dist/server.js +1 -1
- package/src/plugins/tui/dist/server.js.map +1 -1
- package/src/plugins/tui/dist/tui.d.ts +10 -7
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +11 -8
- package/src/plugins/tui/dist/tui.js.map +1 -1
- package/src/plugins/tui/dist/tui.tsx +22 -16
- package/src/plugins/tui/package-lock.json +51 -51
- package/src/plugins/tui/package.json +7 -7
- package/src/plugins/tui/src/index.tsx +28 -24
- package/src/skills/README.md +0 -2
- package/src/pantheon/tool-context.ts +0 -41
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Session-end save — exports Vector DB entries to timestamped backup."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from datetime import UTC, datetime
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# Bootstrap: resolve PANTHEON_HOME before _pantheon_paths is available
|
|
15
|
+
_home_env = os.environ.get("PANTHEON_HOME")
|
|
16
|
+
if _home_env:
|
|
17
|
+
_BOOT_HOME = Path(_home_env).expanduser().resolve()
|
|
18
|
+
else:
|
|
19
|
+
_xdg = os.environ.get("XDG_CONFIG_HOME")
|
|
20
|
+
_BOOT_HOME = (
|
|
21
|
+
Path(_xdg).expanduser().resolve() / "opencode"
|
|
22
|
+
if _xdg
|
|
23
|
+
else Path.home() / ".config" / "opencode"
|
|
24
|
+
)
|
|
25
|
+
sys.path.insert(0, str(_BOOT_HOME / "scripts"))
|
|
26
|
+
|
|
27
|
+
from _pantheon_paths import pantheon_home, pantheon_project
|
|
28
|
+
_PROJECT = pantheon_project()
|
|
29
|
+
if _PROJECT is None:
|
|
30
|
+
print("ERROR: PANTHEON_PROJECT not set and CWD unavailable")
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
PROJECT_ROOT: Path = _PROJECT
|
|
33
|
+
OUTPUT_DIR: Path = PROJECT_ROOT / ".pantheon" / "memory-bank" / ".tmp"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def parse_args() -> argparse.Namespace:
|
|
37
|
+
parser = argparse.ArgumentParser(description="Session-end memory save")
|
|
38
|
+
parser.add_argument("--dry-run", action="store_true", help="Preview without saving")
|
|
39
|
+
parser.add_argument("--agent", default="unknown", help="Agent name triggering save")
|
|
40
|
+
return parser.parse_args()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def detect_agent(args_agent: str) -> str:
|
|
44
|
+
"""Return agent name: CLI arg first, then env/fallback."""
|
|
45
|
+
if args_agent and args_agent != "unknown":
|
|
46
|
+
return args_agent
|
|
47
|
+
# Fall back to env var
|
|
48
|
+
from_env = sys.argv[0]
|
|
49
|
+
for candidate in ("zeus", "hermes", "aphrodite", "demeter",
|
|
50
|
+
"themis", "apollo", "athena", "talos",
|
|
51
|
+
"prometheus", "hephaestus", "iris",
|
|
52
|
+
"nyx", "gaia", "mnemosyne"):
|
|
53
|
+
if candidate in from_env.lower():
|
|
54
|
+
return candidate
|
|
55
|
+
return "unknown"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def try_export_memory() -> list[dict] | None:
|
|
59
|
+
"""Try to export entries from pantheon-memory via MCP.
|
|
60
|
+
|
|
61
|
+
Tries three strategies:
|
|
62
|
+
1. Import memory_mcp_server module and call export directly
|
|
63
|
+
2. If ChromaDB unavailable, create placeholder (no crash)
|
|
64
|
+
|
|
65
|
+
Returns list of dicts with keys: content, importance, category, session_id
|
|
66
|
+
Returns None if absolutely unavailable (MCP down, no data).
|
|
67
|
+
Client-side filtering by importance >= 0.4.
|
|
68
|
+
"""
|
|
69
|
+
# Strategy 1: Try direct module import
|
|
70
|
+
sys.path.insert(0, str(pantheon_home() / "scripts"))
|
|
71
|
+
try:
|
|
72
|
+
import vector_memory.index as vmi
|
|
73
|
+
# Use memory_sessions-like approach via ChromaDB
|
|
74
|
+
import chromadb
|
|
75
|
+
db_path = pantheon_home() / "memory" / "chroma_db"
|
|
76
|
+
if db_path.exists():
|
|
77
|
+
client = chromadb.PersistentClient(str(db_path))
|
|
78
|
+
collection = client.get_or_create_collection("pantheon_memory")
|
|
79
|
+
results = collection.get()
|
|
80
|
+
if results["ids"]:
|
|
81
|
+
entries = []
|
|
82
|
+
for i, doc_id in enumerate(results["ids"]):
|
|
83
|
+
meta = results["metadatas"][i] if results["metadatas"] else {}
|
|
84
|
+
doc = results["documents"][i] if results["documents"] else ""
|
|
85
|
+
entries.append({
|
|
86
|
+
"id": doc_id,
|
|
87
|
+
"content": doc[:200] if doc else "",
|
|
88
|
+
"importance": meta.get("importance", 0.5),
|
|
89
|
+
"category": meta.get("category", "memory"),
|
|
90
|
+
"session_id": meta.get("session_id", ""),
|
|
91
|
+
"agent": meta.get("agent", "unknown"),
|
|
92
|
+
})
|
|
93
|
+
# Client-side filter by importance >= 0.4
|
|
94
|
+
filtered = [e for e in entries if e["importance"] >= 0.4]
|
|
95
|
+
if filtered:
|
|
96
|
+
return filtered
|
|
97
|
+
# If we have entries but all below threshold, don't return None
|
|
98
|
+
# (means MCP is working, just no high-importance entries yet)
|
|
99
|
+
return []
|
|
100
|
+
except ImportError:
|
|
101
|
+
pass
|
|
102
|
+
except Exception as e:
|
|
103
|
+
print(f" (MCP export note: {e})")
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
# Strategy 2: MCP unavailable — return None (caller handles placeholder)
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def write_session_save(entries: list[dict], agent: str, dry_run: bool = False) -> Path | None:
|
|
111
|
+
"""Write session save file. Returns path if written, None if dry-run."""
|
|
112
|
+
timestamp = datetime.now(UTC).strftime("%Y%m%d-%H%M%S")
|
|
113
|
+
filename = f"session-save-{timestamp}.md"
|
|
114
|
+
filepath = OUTPUT_DIR / filename
|
|
115
|
+
|
|
116
|
+
if dry_run:
|
|
117
|
+
print(f"[dry-run] Would save to: {filepath}")
|
|
118
|
+
print(f"[dry-run] Entries: {len(entries)} from agent '{agent}'")
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
122
|
+
|
|
123
|
+
lines = [
|
|
124
|
+
f"# Session Save — {timestamp}",
|
|
125
|
+
f"**Agent:** {agent}",
|
|
126
|
+
f"**Entries:** {len(entries)}",
|
|
127
|
+
"",
|
|
128
|
+
"## Entries",
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
if not entries:
|
|
132
|
+
lines.append("*(No entries with importance >= 0.4)*")
|
|
133
|
+
else:
|
|
134
|
+
for entry in entries:
|
|
135
|
+
content = entry.get("content", "—")
|
|
136
|
+
importance = entry.get("importance", 0)
|
|
137
|
+
category = entry.get("category", "memory")
|
|
138
|
+
lines.append(f"- [{importance:.1f}] ({category}) {content[:200]}")
|
|
139
|
+
|
|
140
|
+
filepath.write_text("\n".join(lines) + "\n")
|
|
141
|
+
print(f" Saved {len(entries)} entries to {filepath}")
|
|
142
|
+
return filepath
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def update_deepwork_status(entries_count: int, agent: str) -> None:
|
|
146
|
+
"""Append save record to active deepwork STATUS.md if found."""
|
|
147
|
+
deepwork_dir = PROJECT_ROOT / ".pantheon" / "deepwork"
|
|
148
|
+
if not deepwork_dir.exists():
|
|
149
|
+
return
|
|
150
|
+
for status_file in sorted(deepwork_dir.glob("*/STATUS.md")):
|
|
151
|
+
timestamp = datetime.now(UTC).strftime("%Y-%m-%d %H:%M")
|
|
152
|
+
line = f"- {timestamp}: Session save — {entries_count} entries (agent: {agent})"
|
|
153
|
+
with open(status_file, "a") as f:
|
|
154
|
+
f.write(f"{line}\n")
|
|
155
|
+
print(f" Updated {status_file}")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def main() -> None:
|
|
159
|
+
args = parse_args()
|
|
160
|
+
agent = detect_agent(args.agent)
|
|
161
|
+
|
|
162
|
+
print(f"Running session-end save (agent: {agent})")
|
|
163
|
+
|
|
164
|
+
if args.dry_run:
|
|
165
|
+
print(" --dry-run mode — no files will be written")
|
|
166
|
+
|
|
167
|
+
entries = try_export_memory()
|
|
168
|
+
|
|
169
|
+
if entries is None:
|
|
170
|
+
print(" pantheon-memory unavailable. Writing placeholder.")
|
|
171
|
+
entries = []
|
|
172
|
+
|
|
173
|
+
path = write_session_save(entries, agent, dry_run=args.dry_run)
|
|
174
|
+
|
|
175
|
+
if path and not args.dry_run:
|
|
176
|
+
update_deepwork_status(len(entries), agent)
|
|
177
|
+
|
|
178
|
+
print()
|
|
179
|
+
print(f" Agent: {agent}")
|
|
180
|
+
print(f" Entries: {len(entries)}")
|
|
181
|
+
print(f" Dry-run: {args.dry_run}")
|
|
182
|
+
print("Session-end save complete.")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
main()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Wrapper for session-end-save.py
|
|
3
|
+
# Usage: ./session-end-save.sh [agent_name] [--dry-run]
|
|
4
|
+
#
|
|
5
|
+
# Persists Vector DB entries to a timestamped backup file in
|
|
6
|
+
# .pantheon/memory-bank/.tmp/ with client-side importance filtering.
|
|
7
|
+
#
|
|
8
|
+
# Arguments:
|
|
9
|
+
# agent_name — name of the agent triggering the save (default: unknown)
|
|
10
|
+
# --dry-run — preview without writing
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
AGENT="${1:-unknown}"
|
|
16
|
+
shift || true
|
|
17
|
+
|
|
18
|
+
python3 "$SCRIPT_DIR/session-end-save.py" --agent "$AGENT" "$@"
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Test: auto-continue-checkpoint.sh
|
|
3
|
+
# Validates the checkpoint script creates checkpoints and heartbeat files correctly.
|
|
4
|
+
# Usage: bash .pantheon/code-mode/test-auto-continue-checkpoint.sh
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
TEST_DIR="$(mktemp -d)/test-auto-continue"
|
|
9
|
+
trap 'rm -rf "$TEST_DIR"' EXIT
|
|
10
|
+
SCRIPT=".pantheon/code-mode/auto-continue-checkpoint.sh"
|
|
11
|
+
|
|
12
|
+
# Ensure script exists
|
|
13
|
+
if [ ! -f "$SCRIPT" ]; then
|
|
14
|
+
echo "❌ FAIL: $SCRIPT not found"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
echo "=== RED: Testing checkpoint script ==="
|
|
19
|
+
|
|
20
|
+
# 1. Create a deepwork directory with a STATUS.md
|
|
21
|
+
DEEPDIR="$TEST_DIR/test-slug"
|
|
22
|
+
mkdir -p "$DEEPDIR"
|
|
23
|
+
cat > "$DEEPDIR/STATUS.md" << 'EOF'
|
|
24
|
+
# STATUS — test-slug
|
|
25
|
+
**Date:** 2026-07-16T20:00:00Z
|
|
26
|
+
**Phase:** 2
|
|
27
|
+
**Turns:** 15
|
|
28
|
+
**Status:** In Progress
|
|
29
|
+
EOF
|
|
30
|
+
|
|
31
|
+
# 2. Run the checkpoint script targeting our test directory
|
|
32
|
+
# We need to run from repo root and use SLUG that maps to our test dir
|
|
33
|
+
# The script uses .pantheon/deepwork/<slug>, so we need to symlink or override
|
|
34
|
+
# Strategy: create a temporary .pantheon/deepwork/test-slug dir
|
|
35
|
+
# Resolve the repo/runtime root from this script's location so the test never
|
|
36
|
+
# depends on a hardcoded checkout path (the tarball must be machine-path-free).
|
|
37
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/../.." && pwd)"
|
|
38
|
+
mkdir -p "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
39
|
+
|
|
40
|
+
# Copy STATUS.md into the real deepwork path
|
|
41
|
+
cp "$DEEPDIR/STATUS.md" "$REPO_ROOT/.pantheon/deepwork/test-slug/STATUS.md"
|
|
42
|
+
|
|
43
|
+
# Run the actual script
|
|
44
|
+
bash "$REPO_ROOT/$SCRIPT" "test-slug"
|
|
45
|
+
|
|
46
|
+
# 3. VERIFY: checkpoint file created in the real deepwork path
|
|
47
|
+
if [ ! -f "$REPO_ROOT/.pantheon/deepwork/test-slug/checkpoint-1.json" ]; then
|
|
48
|
+
echo "❌ FAIL: checkpoint-1.json not created"
|
|
49
|
+
# Cleanup
|
|
50
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
51
|
+
rm -rf "$TEST_DIR"
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
echo " ✅ checkpoint-1.json created"
|
|
55
|
+
|
|
56
|
+
# 4. VERIFY: heartbeat file created
|
|
57
|
+
if [ ! -f "$REPO_ROOT/.pantheon/deepwork/test-slug/heartbeat.json" ]; then
|
|
58
|
+
echo "❌ FAIL: heartbeat.json not created"
|
|
59
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
60
|
+
rm -rf "$TEST_DIR"
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
echo " ✅ heartbeat.json created"
|
|
64
|
+
|
|
65
|
+
# 5. VERIFY: checkpoint content is valid JSON
|
|
66
|
+
if ! jq empty "$REPO_ROOT/.pantheon/deepwork/test-slug/checkpoint-1.json" 2>/dev/null; then
|
|
67
|
+
echo "❌ FAIL: checkpoint-1.json is not valid JSON"
|
|
68
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
69
|
+
rm -rf "$TEST_DIR"
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
echo " ✅ checkpoint-1.json is valid JSON"
|
|
73
|
+
|
|
74
|
+
# 6. VERIFY: heartbeat content is valid JSON
|
|
75
|
+
if ! jq empty "$REPO_ROOT/.pantheon/deepwork/test-slug/heartbeat.json" 2>/dev/null; then
|
|
76
|
+
echo "❌ FAIL: heartbeat.json is not valid JSON"
|
|
77
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
78
|
+
rm -rf "$TEST_DIR"
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
echo " ✅ heartbeat.json is valid JSON"
|
|
82
|
+
|
|
83
|
+
# 7. VERIFY: checkpoint fields
|
|
84
|
+
SLUG_CHECK=$(jq -r '.slug' "$REPO_ROOT/.pantheon/deepwork/test-slug/checkpoint-1.json")
|
|
85
|
+
PHASE_CHECK=$(jq -r '.phase' "$REPO_ROOT/.pantheon/deepwork/test-slug/checkpoint-1.json")
|
|
86
|
+
TURNS_CHECK=$(jq -r '.turn_count' "$REPO_ROOT/.pantheon/deepwork/test-slug/checkpoint-1.json")
|
|
87
|
+
|
|
88
|
+
if [ "$SLUG_CHECK" != "test-slug" ]; then
|
|
89
|
+
echo "❌ FAIL: slug mismatch: got '$SLUG_CHECK'"
|
|
90
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
91
|
+
rm -rf "$TEST_DIR"
|
|
92
|
+
exit 1
|
|
93
|
+
fi
|
|
94
|
+
echo " ✅ checkpoint slug = '$SLUG_CHECK'"
|
|
95
|
+
|
|
96
|
+
if [ "$PHASE_CHECK" != "2" ]; then
|
|
97
|
+
echo "❌ FAIL: phase mismatch: got '$PHASE_CHECK'"
|
|
98
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
99
|
+
rm -rf "$TEST_DIR"
|
|
100
|
+
exit 1
|
|
101
|
+
fi
|
|
102
|
+
echo " ✅ checkpoint phase = $PHASE_CHECK"
|
|
103
|
+
|
|
104
|
+
if [ "$TURNS_CHECK" != "15" ]; then
|
|
105
|
+
echo "❌ FAIL: turn_count mismatch: got '$TURNS_CHECK'"
|
|
106
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
107
|
+
rm -rf "$TEST_DIR"
|
|
108
|
+
exit 1
|
|
109
|
+
fi
|
|
110
|
+
echo " ✅ checkpoint turn_count = $TURNS_CHECK"
|
|
111
|
+
|
|
112
|
+
# 8. VERIFY: heartbeat fields
|
|
113
|
+
HB_SLUG=$(jq -r '.slug' "$REPO_ROOT/.pantheon/deepwork/test-slug/heartbeat.json")
|
|
114
|
+
HB_STATUS=$(jq -r '.status' "$REPO_ROOT/.pantheon/deepwork/test-slug/heartbeat.json")
|
|
115
|
+
|
|
116
|
+
if [ "$HB_SLUG" != "test-slug" ]; then
|
|
117
|
+
echo "❌ FAIL: heartbeat slug mismatch: got '$HB_SLUG'"
|
|
118
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
119
|
+
rm -rf "$TEST_DIR"
|
|
120
|
+
exit 1
|
|
121
|
+
fi
|
|
122
|
+
echo " ✅ heartbeat slug = '$HB_SLUG'"
|
|
123
|
+
|
|
124
|
+
if [ "$HB_STATUS" != "alive" ]; then
|
|
125
|
+
echo "❌ FAIL: heartbeat status mismatch: got '$HB_STATUS'"
|
|
126
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
127
|
+
rm -rf "$TEST_DIR"
|
|
128
|
+
exit 1
|
|
129
|
+
fi
|
|
130
|
+
echo " ✅ heartbeat status = '$HB_STATUS'"
|
|
131
|
+
|
|
132
|
+
# 9. VERIFY: running a second time creates checkpoint-2.json
|
|
133
|
+
bash "$REPO_ROOT/$SCRIPT" "test-slug"
|
|
134
|
+
|
|
135
|
+
if [ ! -f "$REPO_ROOT/.pantheon/deepwork/test-slug/checkpoint-2.json" ]; then
|
|
136
|
+
echo "❌ FAIL: checkpoint-2.json not created on second run"
|
|
137
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
138
|
+
rm -rf "$TEST_DIR"
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
echo " ✅ checkpoint-2.json created (auto-increment works)"
|
|
142
|
+
|
|
143
|
+
# 10. VERIFY: second checkpoint has updated heartbeat
|
|
144
|
+
HB_TIMESTAMP=$(jq -r '.last_action' "$REPO_ROOT/.pantheon/deepwork/test-slug/heartbeat.json")
|
|
145
|
+
if [ -z "$HB_TIMESTAMP" ]; then
|
|
146
|
+
echo "❌ FAIL: heartbeat timestamp empty after second run"
|
|
147
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
148
|
+
rm -rf "$TEST_DIR"
|
|
149
|
+
exit 1
|
|
150
|
+
fi
|
|
151
|
+
echo " ✅ heartbeat timestamp updated = '$HB_TIMESTAMP'"
|
|
152
|
+
|
|
153
|
+
# 11. VERIFY: without STATUS.md, script still works (defaults to 0)
|
|
154
|
+
bash "$REPO_ROOT/$SCRIPT" "empty-test"
|
|
155
|
+
if [ ! -f "$REPO_ROOT/.pantheon/deepwork/empty-test/checkpoint-1.json" ]; then
|
|
156
|
+
echo "❌ FAIL: checkpoint not created for slug without STATUS.md"
|
|
157
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/empty-test"
|
|
158
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
159
|
+
rm -rf "$TEST_DIR"
|
|
160
|
+
exit 1
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
PHASE_DEFAULT=$(jq -r '.phase' "$REPO_ROOT/.pantheon/deepwork/empty-test/checkpoint-1.json")
|
|
164
|
+
if [ "$PHASE_DEFAULT" != "0" ]; then
|
|
165
|
+
echo "❌ FAIL: phase should default to 0 without STATUS.md, got '$PHASE_DEFAULT'"
|
|
166
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/empty-test"
|
|
167
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
168
|
+
rm -rf "$TEST_DIR"
|
|
169
|
+
exit 1
|
|
170
|
+
fi
|
|
171
|
+
echo " ✅ checkpoint works without STATUS.md (phase=0 default)"
|
|
172
|
+
|
|
173
|
+
# Cleanup all test artifacts
|
|
174
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/test-slug"
|
|
175
|
+
rm -rf "$REPO_ROOT/.pantheon/deepwork/empty-test"
|
|
176
|
+
rm -rf "$TEST_DIR"
|
|
177
|
+
|
|
178
|
+
echo ""
|
|
179
|
+
echo "=== ✅ ALL CHECKPOINT TESTS PASSED ==="
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ---
|
|
3
|
+
# timeout: 5
|
|
4
|
+
# ---
|
|
5
|
+
echo "pid: $$"
|
|
6
|
+
cat /proc/self/limits | grep -E "Max cpu|Max address|Max processes" 2>&1 | head -n 5
|
|
7
|
+
echo "--- prlimit in cmd check ---"
|
|
8
|
+
# Show that prlimit limits are applied: prlimit --cpu=10 should show cpu 10
|
|
9
|
+
prlimit --output RESOURCE,SOFT,HARD 2>&1 | head -n 20
|