pantheon-opencode 1.5.0-beta.8 → 1.5.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 +14 -63
- package/CHANGELOG.md +320 -0
- package/README.md +14 -27
- package/README.pt-BR.md +5 -6
- package/commands/pantheon.md +1 -35
- package/package.json +1 -2
- package/plugin.json +1 -1
- package/pyproject.toml +2 -5
- package/scripts/doctor.mjs +32 -3
- package/scripts/install/plugin.mjs +9 -12
- package/scripts/mcp_persistence_server.py +1 -1
- package/scripts/sync-tui.mjs +8 -11
- package/src/agents/apollo.md +1 -0
- package/src/agents/gaia.md +1 -0
- package/src/agents/zeus.md +9 -52
- package/src/instructions/memory-protocol.instructions.md +6 -3
- package/src/instructions/zeus-anti-stall.instructions.md +2 -4
- package/src/instructions/zeus-council-synthesis.instructions.md +3 -28
- package/src/instructions/zeus-timeout-retry.instructions.md +3 -28
- package/src/mcp/mcp_persistence_server.py +1 -1
- package/src/mcp/toon_codec.py +2 -4
- package/src/pantheon/background-job-board.ts +4 -4
- package/src/pantheon/{delegation-compaction.ts → compaction-context.ts} +10 -100
- package/src/pantheon/cost-command.ts +1 -2
- package/src/pantheon/delegation-enforce.ts +59 -14
- package/src/pantheon/goal-loop.ts +1 -1
- package/src/pantheon/goal-store.ts +1 -1
- package/src/pantheon/idle-continuation.ts +4 -6
- package/src/pantheon/model-command.ts +1 -2
- package/src/pantheon/native-task-status.ts +2 -2
- package/src/pantheon/presets.d.mts +0 -20
- package/src/pantheon/presets.mjs +0 -64
- package/src/pantheon/session-guard.ts +2 -2
- package/src/pantheon/session-id.ts +30 -0
- package/src/pantheon/task-result-guard.ts +2 -3
- package/src/pantheon/todo-enforcer.ts +3 -3
- package/src/pantheon/tool-context.ts +23 -0
- package/src/pantheon/v2-bridge.ts +1 -4
- package/src/plugin-v2.ts +8 -21
- package/src/plugin.ts +61 -245
- package/src/plugins/pantheon-hooks.ts +1 -1
- package/src/plugins/tui/dist/tui.d.ts +229 -77
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +509 -259
- package/src/plugins/tui/package-lock.json +2 -2
- package/src/plugins/tui/package.json +2 -2
- package/src/plugins/tui/src/index.tsx +753 -279
- package/src/routing.yml +0 -16
- package/src/skills/context-compression/SKILL.md +1 -1
- package/scripts/generate-preset-docs.mjs +0 -300
- package/scripts/generate-prompts.sh +0 -222
- package/scripts/hash_verify.py +0 -192
- package/scripts/install/component.mjs +0 -24
- package/scripts/install/migrate-config.mjs +0 -302
- package/scripts/test-context-compression.sh +0 -169
- package/src/pantheon/auto-wake.ts +0 -85
- package/src/pantheon/chat-reminders.ts +0 -9
- package/src/pantheon/comment-checker.ts +0 -48
- package/src/pantheon/compaction-assert.ts +0 -160
- package/src/pantheon/compaction-handoff.ts +0 -304
- package/src/pantheon/cost-tracker.ts +0 -108
- package/src/pantheon/delegate-manager.ts +0 -766
- package/src/pantheon/delegation-classifier.ts +0 -307
- package/src/pantheon/delegation-finalize.ts +0 -406
- package/src/pantheon/delegation-notify.ts +0 -212
- package/src/pantheon/delegation-toast.ts +0 -73
- package/src/pantheon/delegation.ts +0 -1337
- package/src/pantheon/dispatch-guard.ts +0 -109
- package/src/pantheon/native-probe.ts +0 -234
- package/src/pantheon/permission-eval.ts +0 -284
- package/src/pantheon/preemptive-compact.ts +0 -76
- package/src/pantheon/provider-capabilities.ts +0 -221
- package/src/pantheon/provider-stream-tracker.ts +0 -182
- package/src/pantheon/retry-policy.ts +0 -212
- package/src/pantheon/step-cap.ts +0 -110
- package/src/pantheon/token-opt.ts +0 -689
- package/src/pantheon/toon-codec.ts +0 -481
- package/src/pantheon/v2-events.ts +0 -148
- package/src/pantheon/v2-hooks.ts +0 -287
- package/src/pantheon/v2-tool-definitions.ts +0 -331
- package/src/pantheon/zeus-delegate-with-retry.ts +0 -406
- package/src/plugins/tui/dist/tui.tsx +0 -2777
package/scripts/hash_verify.py
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Hash-Anchored Edit Verification — standalone script.
|
|
3
|
-
|
|
4
|
-
Verifies that file edits actually changed content by comparing SHA-256
|
|
5
|
-
hashes before and after an edit. Detects failed/no-op edits.
|
|
6
|
-
|
|
7
|
-
Usage:
|
|
8
|
-
python3 scripts/hash_verify.py <file> [--before-hash=<sha256>] [--diff-min-lines=10]
|
|
9
|
-
python3 scripts/hash_verify.py --staged # Check all staged files
|
|
10
|
-
python3 scripts/hash_verify.py --batch <file>... # Check multiple files
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import argparse
|
|
14
|
-
import hashlib
|
|
15
|
-
import subprocess
|
|
16
|
-
import sys
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
|
|
19
|
-
EXIT_SUCCESS = 0
|
|
20
|
-
EXIT_FAILURE = 1
|
|
21
|
-
EXIT_WARNING = 2
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def sha256_of(file: Path) -> str:
|
|
25
|
-
"""Return hex SHA-256 of file content."""
|
|
26
|
-
return hashlib.sha256(file.read_bytes()).hexdigest()
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def compute_diff_size(file: Path) -> int:
|
|
30
|
-
"""Return number of changed lines in working-tree diff."""
|
|
31
|
-
try:
|
|
32
|
-
result = subprocess.run(
|
|
33
|
-
["git", "diff", "--", str(file)],
|
|
34
|
-
capture_output=True,
|
|
35
|
-
text=True,
|
|
36
|
-
timeout=5,
|
|
37
|
-
check=False,
|
|
38
|
-
)
|
|
39
|
-
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
40
|
-
return 0
|
|
41
|
-
|
|
42
|
-
diff = result.stdout
|
|
43
|
-
added = len(
|
|
44
|
-
[
|
|
45
|
-
line
|
|
46
|
-
for line in diff.split("\n")
|
|
47
|
-
if line.startswith("+") and not line.startswith("+++")
|
|
48
|
-
]
|
|
49
|
-
)
|
|
50
|
-
removed = len(
|
|
51
|
-
[
|
|
52
|
-
line
|
|
53
|
-
for line in diff.split("\n")
|
|
54
|
-
if line.startswith("-") and not line.startswith("---")
|
|
55
|
-
]
|
|
56
|
-
)
|
|
57
|
-
return added + removed
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def verify_file(file: Path, before_hash: str | None, diff_min_lines: int) -> dict:
|
|
61
|
-
"""Verify a single file edit."""
|
|
62
|
-
if not file.exists():
|
|
63
|
-
return {"file": str(file), "status": "error", "message": "File does not exist"}
|
|
64
|
-
|
|
65
|
-
after_hash = sha256_of(file)
|
|
66
|
-
|
|
67
|
-
if before_hash:
|
|
68
|
-
# Compare explicit before/after hashes
|
|
69
|
-
if after_hash == before_hash:
|
|
70
|
-
return {
|
|
71
|
-
"file": str(file),
|
|
72
|
-
"status": "failed",
|
|
73
|
-
"hash_before": before_hash,
|
|
74
|
-
"hash_after": after_hash,
|
|
75
|
-
"message": "Hash unchanged — edit did not modify file content",
|
|
76
|
-
}
|
|
77
|
-
else:
|
|
78
|
-
# Auto-detect: check git diff
|
|
79
|
-
diff_changes = compute_diff_size(file)
|
|
80
|
-
if diff_changes == 0:
|
|
81
|
-
return {
|
|
82
|
-
"file": str(file),
|
|
83
|
-
"status": "failed",
|
|
84
|
-
"hash_after": after_hash,
|
|
85
|
-
"message": "No git diff detected — edit did not modify file",
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if diff_changes < diff_min_lines:
|
|
89
|
-
return {
|
|
90
|
-
"file": str(file),
|
|
91
|
-
"status": "warning",
|
|
92
|
-
"hash_after": after_hash,
|
|
93
|
-
"diff_lines": diff_changes,
|
|
94
|
-
"message": f"Edit detected but diff is small ({diff_changes} lines vs min {diff_min_lines})",
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
"file": str(file),
|
|
99
|
-
"status": "verified",
|
|
100
|
-
"hash_after": after_hash,
|
|
101
|
-
"message": "Edit verified — file content changed",
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def check_staged_files(diff_min_lines: int) -> list[dict]:
|
|
106
|
-
"""Verify all staged (git-add'ed) files."""
|
|
107
|
-
try:
|
|
108
|
-
result = subprocess.run(
|
|
109
|
-
["git", "diff", "--cached", "--name-only"],
|
|
110
|
-
capture_output=True,
|
|
111
|
-
text=True,
|
|
112
|
-
timeout=5,
|
|
113
|
-
check=False,
|
|
114
|
-
)
|
|
115
|
-
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
116
|
-
return [{"status": "error", "message": "Git not available"}]
|
|
117
|
-
|
|
118
|
-
staged = [f.strip() for f in result.stdout.split("\n") if f.strip()]
|
|
119
|
-
results = []
|
|
120
|
-
for fname in staged[:20]: # limit to 20 files
|
|
121
|
-
fpath = Path(fname)
|
|
122
|
-
if fpath.exists():
|
|
123
|
-
results.append(verify_file(fpath, None, diff_min_lines))
|
|
124
|
-
return results
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def main() -> None:
|
|
128
|
-
parser = argparse.ArgumentParser(
|
|
129
|
-
description="Hash-anchored edit verification — ensures file edits actually changed content."
|
|
130
|
-
)
|
|
131
|
-
parser.add_argument("files", nargs="*", help="File(s) to verify")
|
|
132
|
-
parser.add_argument(
|
|
133
|
-
"--before-hash", help="Expected SHA-256 hash before edit (for single-file mode)"
|
|
134
|
-
)
|
|
135
|
-
parser.add_argument(
|
|
136
|
-
"--diff-min-lines",
|
|
137
|
-
type=int,
|
|
138
|
-
default=3,
|
|
139
|
-
help="Minimum diff lines to consider edit meaningful (default: 3)",
|
|
140
|
-
)
|
|
141
|
-
parser.add_argument(
|
|
142
|
-
"--staged", action="store_true", help="Verify all staged files instead"
|
|
143
|
-
)
|
|
144
|
-
parser.add_argument(
|
|
145
|
-
"--batch", action="store_true", help="Treat positional args as batch file list"
|
|
146
|
-
)
|
|
147
|
-
args = parser.parse_args()
|
|
148
|
-
|
|
149
|
-
if args.staged:
|
|
150
|
-
results = check_staged_files(args.diff_min_lines)
|
|
151
|
-
elif args.batch or len(args.files) > 1:
|
|
152
|
-
results = [verify_file(Path(f), None, args.diff_min_lines) for f in args.files]
|
|
153
|
-
elif args.files:
|
|
154
|
-
results = [
|
|
155
|
-
verify_file(Path(args.files[0]), args.before_hash, args.diff_min_lines)
|
|
156
|
-
]
|
|
157
|
-
else:
|
|
158
|
-
print("Usage: python3 scripts/hash_verify.py <file> [--before-hash=<sha256>]")
|
|
159
|
-
print(" python3 scripts/hash_verify.py --staged")
|
|
160
|
-
print(" python3 scripts/hash_verify.py --batch <file>...")
|
|
161
|
-
sys.exit(EXIT_FAILURE)
|
|
162
|
-
|
|
163
|
-
# Print results
|
|
164
|
-
any_failed = False
|
|
165
|
-
any_warning = False
|
|
166
|
-
|
|
167
|
-
for r in results:
|
|
168
|
-
status_tag = {
|
|
169
|
-
"verified": "✅",
|
|
170
|
-
"warning": "⚠️",
|
|
171
|
-
"failed": "❌",
|
|
172
|
-
"error": "❌",
|
|
173
|
-
}.get(r["status"], "❓")
|
|
174
|
-
print(f"{status_tag} {r['file']}: {r['message']}")
|
|
175
|
-
if r["status"] == "failed" or r["status"] == "error":
|
|
176
|
-
any_failed = True
|
|
177
|
-
elif r["status"] == "warning":
|
|
178
|
-
any_warning = True
|
|
179
|
-
|
|
180
|
-
if any_failed:
|
|
181
|
-
print("\n❌ HASH VERIFY FAILED — some edits did not modify files")
|
|
182
|
-
sys.exit(EXIT_FAILURE)
|
|
183
|
-
elif any_warning:
|
|
184
|
-
print("\n⚠️ HASH VERIFY PASSED WITH WARNINGS — small diffs detected")
|
|
185
|
-
sys.exit(EXIT_WARNING)
|
|
186
|
-
else:
|
|
187
|
-
print("\n✅ HASH VERIFY: all edits verified")
|
|
188
|
-
sys.exit(EXIT_SUCCESS)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if __name__ == "__main__":
|
|
192
|
-
main()
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* component.mjs — Component resolution utilities for the OpenCode installer
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/** Available component identifiers */
|
|
6
|
-
export const COMPONENT_NAMES = [
|
|
7
|
-
'agents',
|
|
8
|
-
'skills',
|
|
9
|
-
'instructions',
|
|
10
|
-
'prompts',
|
|
11
|
-
'commands',
|
|
12
|
-
'plugins',
|
|
13
|
-
'runtime',
|
|
14
|
-
]
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Parse --components flag into a Set.
|
|
18
|
-
* @param {string|string[]} spec - Comma-separated string or array of component names
|
|
19
|
-
* @returns {Set<string>}
|
|
20
|
-
*/
|
|
21
|
-
export function resolveComponents(spec) {
|
|
22
|
-
if (Array.isArray(spec)) return new Set(spec)
|
|
23
|
-
return new Set(spec.split(',').map((s) => s.trim()))
|
|
24
|
-
}
|
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* migrate-config.mjs — Standalone migration CLI with checkpoint crash recovery
|
|
4
|
-
*
|
|
5
|
-
* Runs configuration migrations with an atomic checkpoint system.
|
|
6
|
-
* If the process crashes mid-migration, the checkpoint file persists
|
|
7
|
-
* so that --resume picks up where it left off.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* node scripts/install/migrate-config.mjs --from=1.1.0 --to=1.1.1 --target=~/.config/opencode
|
|
11
|
-
* node scripts/install/migrate-config.mjs --resume --target=~/.config/opencode
|
|
12
|
-
* node scripts/install/migrate-config.mjs --list --target=~/.config/opencode
|
|
13
|
-
* node scripts/install/migrate-config.mjs --dry-run --from=1.1.0 --to=1.1.1
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
17
|
-
import { join } from 'node:path'
|
|
18
|
-
import {
|
|
19
|
-
advanceCheckpoint,
|
|
20
|
-
listAppliedMigrations,
|
|
21
|
-
readCheckpoint,
|
|
22
|
-
removeCheckpoint,
|
|
23
|
-
resumePendingMigration,
|
|
24
|
-
writeCheckpoint,
|
|
25
|
-
} from './migrate.mjs'
|
|
26
|
-
import {
|
|
27
|
-
addMigrationRecord,
|
|
28
|
-
createInitialState,
|
|
29
|
-
readState,
|
|
30
|
-
updateVersion,
|
|
31
|
-
writeState,
|
|
32
|
-
} from './state.mjs'
|
|
33
|
-
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
// Migration step definitions
|
|
36
|
-
// Each step is an idempotent function(target, state, { dryRun }) => void
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
const MIGRATION_STEPS = {
|
|
40
|
-
/**
|
|
41
|
-
* migrate_state_v2: Upgrade install-state.json from schema v1 to v2.
|
|
42
|
-
* Adds schema_version field, components tracking, and migration history.
|
|
43
|
-
*/
|
|
44
|
-
migrate_state_v2: (target, state, { dryRun }) => {
|
|
45
|
-
if (state.schema_version && state.schema_version >= 2) {
|
|
46
|
-
return
|
|
47
|
-
}
|
|
48
|
-
if (!dryRun) {
|
|
49
|
-
state.schema_version = 2
|
|
50
|
-
state.components = state.components || {
|
|
51
|
-
agents: { version: 1, status: 'pending' },
|
|
52
|
-
skills: { version: 1, status: 'pending' },
|
|
53
|
-
plugins: { version: 1, status: 'pending' },
|
|
54
|
-
runtime: { version: 1, status: 'pending' },
|
|
55
|
-
}
|
|
56
|
-
state.applied_migrations = state.applied_migrations || []
|
|
57
|
-
state.migration_checkpoint = null
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* update_schema: Ensure all v2 schema fields exist with defaults.
|
|
63
|
-
*/
|
|
64
|
-
update_schema: (target, state, { dryRun }) => {
|
|
65
|
-
if (!dryRun) {
|
|
66
|
-
if (!state.components) {
|
|
67
|
-
state.components = {
|
|
68
|
-
agents: { version: 1, status: 'pending' },
|
|
69
|
-
skills: { version: 1, status: 'pending' },
|
|
70
|
-
plugins: { version: 1, status: 'pending' },
|
|
71
|
-
runtime: { version: 1, status: 'pending' },
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
for (const key of ['agents', 'skills', 'plugins', 'runtime']) {
|
|
75
|
-
if (!state.components[key]) {
|
|
76
|
-
state.components[key] = { version: 1, status: 'pending' }
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
state.applied_migrations = state.applied_migrations || []
|
|
80
|
-
if (state.migration_checkpoint === undefined) {
|
|
81
|
-
state.migration_checkpoint = null
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* migrate_plugins: Normalize plugin component tracking.
|
|
88
|
-
*/
|
|
89
|
-
migrate_plugins: (target, state, { dryRun }) => {
|
|
90
|
-
if (!dryRun) {
|
|
91
|
-
if (!state.components) state.components = {}
|
|
92
|
-
const p = state.components.plugins || { version: 1, status: 'pending' }
|
|
93
|
-
p.version = Math.max(p.version || 0, 1)
|
|
94
|
-
state.components.plugins = p
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ---------------------------------------------------------------------------
|
|
100
|
-
// Migration step ordering (a step runs ONLY after all predecessors complete)
|
|
101
|
-
// ---------------------------------------------------------------------------
|
|
102
|
-
|
|
103
|
-
const STEP_ORDER = Object.keys(MIGRATION_STEPS)
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Resolve which steps to run based on from/to version comparison.
|
|
107
|
-
* For now, runs all steps. In the future this can be gated by version.
|
|
108
|
-
* @param {string} fromVersion
|
|
109
|
-
* @param {string} toVersion
|
|
110
|
-
* @returns {string[]} Ordered list of step names
|
|
111
|
-
*/
|
|
112
|
-
function resolveSteps(fromVersion, toVersion) {
|
|
113
|
-
return [...STEP_ORDER]
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ---------------------------------------------------------------------------
|
|
117
|
-
// CLI
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
|
|
120
|
-
function parseArgs() {
|
|
121
|
-
const args = process.argv.slice(2)
|
|
122
|
-
const result = {
|
|
123
|
-
from: null,
|
|
124
|
-
to: null,
|
|
125
|
-
target: process.cwd(),
|
|
126
|
-
dryRun: false,
|
|
127
|
-
resume: false,
|
|
128
|
-
list: false,
|
|
129
|
-
help: false,
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
for (let i = 0; i < args.length; i++) {
|
|
133
|
-
const arg = args[i]
|
|
134
|
-
if (arg === '--dry-run') result.dryRun = true
|
|
135
|
-
else if (arg === '--resume') result.resume = true
|
|
136
|
-
else if (arg === '--list') result.list = true
|
|
137
|
-
else if (arg === '--help' || arg === '-h') result.help = true
|
|
138
|
-
else if (arg.startsWith('--from=')) result.from = arg.split('=', 2)[1]
|
|
139
|
-
else if (arg.startsWith('--to=')) result.to = arg.split('=', 2)[1]
|
|
140
|
-
else if (arg.startsWith('--target=')) result.target = arg.split('=', 2)[1]
|
|
141
|
-
else if (arg === '--from') result.from = args[++i]
|
|
142
|
-
else if (arg === '--to') result.to = args[++i]
|
|
143
|
-
else if (arg === '--target') result.target = args[++i]
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return result
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function showHelp() {
|
|
150
|
-
console.log(`
|
|
151
|
-
migrate-config.mjs — Pantheon configuration migration CLI
|
|
152
|
-
|
|
153
|
-
Usage:
|
|
154
|
-
node scripts/install/migrate-config.mjs --from=1.1.0 --to=1.1.1 --target=~/.config/opencode
|
|
155
|
-
node scripts/install/migrate-config.mjs --resume --target=~/.config/opencode
|
|
156
|
-
node scripts/install/migrate-config.mjs --list --target=~/.config/opencode
|
|
157
|
-
node scripts/install/migrate-config.mjs --dry-run --from=1.1.0 --to=1.1.1
|
|
158
|
-
node scripts/install/migrate-config.mjs --help
|
|
159
|
-
|
|
160
|
-
Options:
|
|
161
|
-
--from=<version> Source version (required for new migrations)
|
|
162
|
-
--to=<version> Target version (required for new migrations)
|
|
163
|
-
--target=<path> Installation directory (default: cwd)
|
|
164
|
-
--dry-run Preview without writing
|
|
165
|
-
--resume Resume from pending checkpoint (crash recovery)
|
|
166
|
-
--list Show applied migration history
|
|
167
|
-
--help Show this help
|
|
168
|
-
|
|
169
|
-
Crash Recovery:
|
|
170
|
-
If the process is interrupted mid-migration, a checkpoint file is left
|
|
171
|
-
at .pantheon/.migration-checkpoint. Run with --resume to continue.
|
|
172
|
-
`)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function main() {
|
|
176
|
-
const config = parseArgs()
|
|
177
|
-
|
|
178
|
-
if (config.help) {
|
|
179
|
-
showHelp()
|
|
180
|
-
return
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// ── List mode ──
|
|
184
|
-
if (config.list) {
|
|
185
|
-
const history = listAppliedMigrations(config.target)
|
|
186
|
-
if (!history.currentVersion) {
|
|
187
|
-
console.log(' \u2139\uFE0F No install state found in: ' + config.target)
|
|
188
|
-
return
|
|
189
|
-
}
|
|
190
|
-
console.log(` Current version: ${history.currentVersion}`)
|
|
191
|
-
console.log(` Previous version: ${history.previousVersion || 'N/A'}`)
|
|
192
|
-
console.log(` Applied migrations: ${history.migrations.length}`)
|
|
193
|
-
for (const m of history.migrations) {
|
|
194
|
-
const icon =
|
|
195
|
-
m.status === 'completed'
|
|
196
|
-
? '\u2705'
|
|
197
|
-
: m.status === 'rolled_back'
|
|
198
|
-
? '\u{1F504}'
|
|
199
|
-
: '\u26A0\uFE0F'
|
|
200
|
-
console.log(` ${icon} ${m.from} \u2192 ${m.to} [${m.status}] (${m.applied_at})`)
|
|
201
|
-
}
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// ── Resume mode ──
|
|
206
|
-
if (config.resume) {
|
|
207
|
-
const cp = resumePendingMigration(config.target)
|
|
208
|
-
if (!cp) {
|
|
209
|
-
console.log(' \u23ED\uFE0F No pending checkpoint found — nothing to resume')
|
|
210
|
-
return
|
|
211
|
-
}
|
|
212
|
-
config.from = cp.from
|
|
213
|
-
config.to = cp.to
|
|
214
|
-
} else {
|
|
215
|
-
if (!config.from || !config.to) {
|
|
216
|
-
console.error('\u274C --from and --to are required (or use --resume)')
|
|
217
|
-
showHelp()
|
|
218
|
-
process.exit(1)
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// ── Read state ──
|
|
223
|
-
let state = readState(config.target)
|
|
224
|
-
if (!state) {
|
|
225
|
-
state = createInitialState(config.from)
|
|
226
|
-
console.log(` \u{1F4DD} Creating new install state (v${state.schema_version})`)
|
|
227
|
-
if (!config.dryRun) writeState(config.target, state)
|
|
228
|
-
} else {
|
|
229
|
-
console.log(
|
|
230
|
-
` \u{1F4CB} Current state: v${state.schema_version}, version ${state.pantheon_version}`,
|
|
231
|
-
)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// ── Resolve checkpoint ──
|
|
235
|
-
let cp = readCheckpoint(config.target)
|
|
236
|
-
|
|
237
|
-
if (!cp) {
|
|
238
|
-
const steps = resolveSteps(config.from, config.to)
|
|
239
|
-
cp = writeCheckpoint(config.target, config.from, config.to, steps)
|
|
240
|
-
console.log(` \u{1F4DD} Checkpoint written: ${config.from} \u2192 ${config.to}`)
|
|
241
|
-
} else {
|
|
242
|
-
console.log(
|
|
243
|
-
` \u{1F504} Resuming from checkpoint (${cp.steps_completed.length} steps done, ${cp.steps_pending.length} remaining)`,
|
|
244
|
-
)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// ── Run pending steps ──
|
|
248
|
-
const stepsToRun = [...cp.steps_pending]
|
|
249
|
-
let failed = false
|
|
250
|
-
|
|
251
|
-
for (const step of stepsToRun) {
|
|
252
|
-
const stepFn = MIGRATION_STEPS[step]
|
|
253
|
-
if (!stepFn) {
|
|
254
|
-
console.warn(` \u26A0\uFE0F Unknown migration step: ${step} — skipping`)
|
|
255
|
-
if (!config.dryRun) {
|
|
256
|
-
advanceCheckpoint(config.target, step)
|
|
257
|
-
}
|
|
258
|
-
continue
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
console.log(` \u25B6\uFE0F Running: ${step}`)
|
|
262
|
-
try {
|
|
263
|
-
stepFn(config.target, state, { dryRun: config.dryRun })
|
|
264
|
-
|
|
265
|
-
if (!config.dryRun) {
|
|
266
|
-
advanceCheckpoint(config.target, step)
|
|
267
|
-
writeState(config.target, state)
|
|
268
|
-
console.log(` \u2705 ${step} completed`)
|
|
269
|
-
} else {
|
|
270
|
-
console.log(` \u2705 ${step} would complete (dry-run)`)
|
|
271
|
-
}
|
|
272
|
-
} catch (err) {
|
|
273
|
-
console.error(` \u274C ${step} failed: ${err.message}`)
|
|
274
|
-
failed = true
|
|
275
|
-
break
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// ── Finalize ──
|
|
280
|
-
if (failed) {
|
|
281
|
-
console.error(`\n \u274C Migration failed at checkpoint.`)
|
|
282
|
-
console.error(
|
|
283
|
-
` Resume with: node scripts/install/migrate-config.mjs --resume --target=${config.target}`,
|
|
284
|
-
)
|
|
285
|
-
process.exit(1)
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (!config.dryRun) {
|
|
289
|
-
updateVersion(state, config.to)
|
|
290
|
-
addMigrationRecord(state, config.from, config.to, 'completed')
|
|
291
|
-
removeCheckpoint(config.target)
|
|
292
|
-
writeState(config.target, state)
|
|
293
|
-
console.log(`\n \u2705 Migration complete: ${config.from} \u2192 ${config.to}`)
|
|
294
|
-
console.log(` State written to: ${join(config.target, '.pantheon', 'install-state.json')}`)
|
|
295
|
-
} else {
|
|
296
|
-
console.log(`\n \u2705 Migration would complete (dry-run): ${config.from} \u2192 ${config.to}`)
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
|
|
301
|
-
main()
|
|
302
|
-
}
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Test: Context Compression Pipeline
|
|
3
|
-
# Simulates Themis APPROVED → triggers compress_context handoff
|
|
4
|
-
#
|
|
5
|
-
# Usage: bash scripts/test-context-compression.sh
|
|
6
|
-
#
|
|
7
|
-
# This script validates the prerequisites for the Level 2 context compression
|
|
8
|
-
# pipeline and creates mock artifacts for end-to-end testing.
|
|
9
|
-
|
|
10
|
-
set -euo pipefail
|
|
11
|
-
|
|
12
|
-
echo "=== Context Compression Test ==="
|
|
13
|
-
echo ""
|
|
14
|
-
|
|
15
|
-
# Step 1: Verify prerequisites
|
|
16
|
-
echo "[1/4] Checking prerequisites..."
|
|
17
|
-
errors=0
|
|
18
|
-
|
|
19
|
-
test -f skills/context-compression/SKILL.md || { echo " ❌ SKILL.md not found"; errors=$((errors + 1)); }
|
|
20
|
-
test -f agents/mnemosyne.agent.md || { echo " ❌ mnemosyne.agent.md not found"; errors=$((errors + 1)); }
|
|
21
|
-
test -f agents/zeus.agent.md || { echo " ❌ zeus.agent.md not found"; errors=$((errors + 1)); }
|
|
22
|
-
test -f routing.yml || { echo " ❌ routing.yml not found"; errors=$((errors + 1)); }
|
|
23
|
-
test -f skills/artifact-management/SKILL.md || { echo " ❌ artifact-protocol.instructions.md not found"; errors=$((errors + 1)); }
|
|
24
|
-
test -f .pantheon/memory-bank/01-active-context.md || { echo " ❌ active-context.md not found"; errors=$((errors + 1)); }
|
|
25
|
-
test -d .pantheon/memory-bank/.tmp || mkdir -p .pantheon/memory-bank/.tmp
|
|
26
|
-
|
|
27
|
-
if [ -f scripts/scrub-secrets.py ]; then
|
|
28
|
-
echo " ✅ scrub-secrets.py found (217 lines, $(grep -c 'def ' scripts/scrub-secrets.py || echo 0) functions)"
|
|
29
|
-
else
|
|
30
|
-
echo " ⚠️ scrub-secrets.py not found (will skip scrubbing)"
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
# Verify compress_context handoff in routing.yml
|
|
34
|
-
if grep -q "compress_context" routing.yml; then
|
|
35
|
-
HANDOFF_LINE=$(grep -n "compress_context" routing.yml | head -1 | cut -d: -f1)
|
|
36
|
-
echo " ✅ compress_context handoff found in routing.yml at line $HANDOFF_LINE"
|
|
37
|
-
else
|
|
38
|
-
echo " ❌ compress_context handoff missing from routing.yml"
|
|
39
|
-
errors=$((errors + 1))
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
# Verify ZZ artifact mention in artifact-protocol
|
|
43
|
-
if grep -q "ZZ-phase" skills/artifact-management/SKILL.md; then
|
|
44
|
-
echo " ✅ ZZ artifact format documented in artifact-protocol.instructions.md"
|
|
45
|
-
else
|
|
46
|
-
echo " ❌ ZZ artifact format missing from artifact-protocol.instructions.md"
|
|
47
|
-
errors=$((errors + 1))
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
# Verify context-compression trigger was added to zeus.agent.md
|
|
51
|
-
if grep -q "Context Compression Trigger" agents/zeus.agent.md; then
|
|
52
|
-
echo " ✅ Context Compression Trigger section present in zeus.agent.md"
|
|
53
|
-
else
|
|
54
|
-
echo " ⚠️ Context Compression Trigger section not yet in zeus.agent.md"
|
|
55
|
-
fi
|
|
56
|
-
|
|
57
|
-
if [ "$errors" -gt 0 ]; then
|
|
58
|
-
echo " ❌ $errors prerequisite(s) missing — aborting"
|
|
59
|
-
exit 1
|
|
60
|
-
fi
|
|
61
|
-
echo " ✅ All prerequisites met"
|
|
62
|
-
echo ""
|
|
63
|
-
|
|
64
|
-
# Step 2: Create a mock IMPL artifact for testing
|
|
65
|
-
echo "[2/4] Creating mock IMPL artifact..."
|
|
66
|
-
|
|
67
|
-
cat > .pantheon/memory-bank/.tmp/IMPL-test-phase-hermes.md << 'ARTIFACT'
|
|
68
|
-
# IMPL-test-phase-hermes
|
|
69
|
-
**Date:** 2026-06-26 **Status:** Awaiting Compression Test
|
|
70
|
-
|
|
71
|
-
## What Was Implemented
|
|
72
|
-
- test/file.py — mock implementation for compression test
|
|
73
|
-
- test/schema.py — mock schema migration for priority scoring test
|
|
74
|
-
|
|
75
|
-
## Tests
|
|
76
|
-
- ✅ 5 tests passing / Coverage: 87%
|
|
77
|
-
|
|
78
|
-
## Notes for Themis
|
|
79
|
-
This is a test artifact to validate the compression pipeline end-to-end.
|
|
80
|
-
ARTIFACT
|
|
81
|
-
|
|
82
|
-
cat > .pantheon/memory-bank/.tmp/IMPL-test-phase-demeter.md << 'ARTIFACT'
|
|
83
|
-
# IMPL-test-phase-demeter
|
|
84
|
-
**Date:** 2026-06-26 **Status:** Awaiting Compression Test
|
|
85
|
-
|
|
86
|
-
## What Was Implemented
|
|
87
|
-
- backend/migrations/0013_add_refresh_tokens.py — new refresh_tokens table
|
|
88
|
-
- backend/models/token.py — Token model with FK to users
|
|
89
|
-
|
|
90
|
-
## Tests
|
|
91
|
-
- ✅ Migration tested: upgrade + downgrade both verified
|
|
92
|
-
|
|
93
|
-
## Notes for Themis
|
|
94
|
-
New table: refresh_tokens with (user_id, token_hash, expires_at, revoked_at)
|
|
95
|
-
ARTIFACT
|
|
96
|
-
|
|
97
|
-
echo " ✅ 2 mock artifacts created (hermes + demeter)"
|
|
98
|
-
echo ""
|
|
99
|
-
|
|
100
|
-
# Step 3: Create mock subtask_summary for compression
|
|
101
|
-
echo "[3/4] Creating mock subtask summaries..."
|
|
102
|
-
|
|
103
|
-
cat > .pantheon/memory-bank/.tmp/subtask-hermes.json << 'JSON'
|
|
104
|
-
{
|
|
105
|
-
"files_changed": ["test/file.py", "test/schema.py"],
|
|
106
|
-
"summary": "Added JWT auth endpoint with refresh token rotation",
|
|
107
|
-
"tests": "✅ All passing",
|
|
108
|
-
"coverage": "87%",
|
|
109
|
-
"status": "complete",
|
|
110
|
-
"blockers": null
|
|
111
|
-
}
|
|
112
|
-
JSON
|
|
113
|
-
|
|
114
|
-
cat > .pantheon/memory-bank/.tmp/subtask-demeter.json << 'JSON'
|
|
115
|
-
{
|
|
116
|
-
"files_changed": ["backend/migrations/0013_add_refresh_tokens.py", "backend/models/token.py"],
|
|
117
|
-
"summary": "Created refresh_tokens table with FK to users, hashed token, expires_at, revoked_at",
|
|
118
|
-
"tests": "✅ Migration upgrade + downgrade verified",
|
|
119
|
-
"coverage": "100%",
|
|
120
|
-
"status": "complete",
|
|
121
|
-
"blockers": null
|
|
122
|
-
}
|
|
123
|
-
JSON
|
|
124
|
-
|
|
125
|
-
echo " ✅ 2 mock subtask summaries created"
|
|
126
|
-
echo ""
|
|
127
|
-
|
|
128
|
-
# Step 4: Verify .tmp structure is writable and correct
|
|
129
|
-
echo "[4/4] Verifying .tmp structure..."
|
|
130
|
-
ls -la .pantheon/memory-bank/.tmp/
|
|
131
|
-
echo ""
|
|
132
|
-
echo " ✅ .tmp structure looks correct"
|
|
133
|
-
echo ""
|
|
134
|
-
|
|
135
|
-
# Step 5: Validate scrub-secrets.py works
|
|
136
|
-
echo "[5/4] Testing scrub-secrets.py (bonus)..."
|
|
137
|
-
if [ -f scripts/scrub-secrets.py ]; then
|
|
138
|
-
# Test with a simple secret. The token is assembled at runtime so the
|
|
139
|
-
# shipped script never contains a literal `Bearer <token>` sequence (the
|
|
140
|
-
# redaction gate flags any such literal as a credential).
|
|
141
|
-
SCRUB_FIXTURE_TOKEN="my-test-token-12345"
|
|
142
|
-
SECRET_TEST=$(echo "Bearer ${SCRUB_FIXTURE_TOKEN}" | python3 scripts/scrub-secrets.py --stdin 2>/dev/null)
|
|
143
|
-
if echo "$SECRET_TEST" | grep -q "REDACTED"; then
|
|
144
|
-
echo " ✅ scrub-secrets.py: secret detection working"
|
|
145
|
-
else
|
|
146
|
-
echo " ⚠️ scrub-secrets.py: did not detect test secret (may need different pattern)"
|
|
147
|
-
fi
|
|
148
|
-
fi
|
|
149
|
-
echo ""
|
|
150
|
-
|
|
151
|
-
echo "=== Test Complete ==="
|
|
152
|
-
echo ""
|
|
153
|
-
echo "Mock artifacts ready for compression pipeline test."
|
|
154
|
-
echo ""
|
|
155
|
-
echo "Next manual step: Trigger compress_context handoff:"
|
|
156
|
-
echo " @mnemosyne Run compression pipeline with test artifacts"
|
|
157
|
-
echo ""
|
|
158
|
-
echo "Expected output:"
|
|
159
|
-
echo " - .pantheon/memory-bank/.tmp/ZZ-test-context.md"
|
|
160
|
-
echo " - Updated 01-active-context.md with compressed entries"
|
|
161
|
-
echo " - Updated 02-progress-log.md with archived mock artifact"
|
|
162
|
-
echo ""
|
|
163
|
-
echo "Scoring verification (expected):"
|
|
164
|
-
echo " - subtask-hermes.json: JWT + endpoint + token keywords → HIGH (score ~0.71)"
|
|
165
|
-
echo " - subtask-demeter.json: migration + new table + FK keywords → CRITICAL (score ~0.83)"
|
|
166
|
-
echo ""
|
|
167
|
-
echo "To clean up test artifacts:"
|
|
168
|
-
echo " rm .pantheon/memory-bank/.tmp/IMPL-test-phase-*.md"
|
|
169
|
-
echo " rm .pantheon/memory-bank/.tmp/subtask-*.json"
|