pantheon-opencode 1.5.2 → 1.6.0-beta.10
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 +4 -2
- package/.pantheon/code-mode/checkpoint_session.py +138 -102
- package/.pantheon/code-mode/compress-inline.py +102 -57
- package/.pantheon/code-mode/manifest.json +2 -8
- package/AGENTS.global.md +182 -0
- package/AGENTS.md +10 -891
- package/CHANGELOG.md +343 -0
- package/README.md +577 -79
- package/README.pt-BR.md +345 -60
- package/bin/pantheon-init.mjs +26 -3
- package/commands/pantheon-audit.md +7 -1
- package/commands/pantheon-consolidate.md +1 -1
- package/commands/pantheon-deepwork.md +14 -3
- package/commands/pantheon-focus.md +17 -0
- package/commands/pantheon-optimize.md +2 -2
- package/commands/pantheon-vision.md +61 -12
- package/package.json +30 -11
- package/plugin.json +2 -2
- package/pyproject.toml +11 -16
- package/scripts/build-agents-md.mjs +376 -58
- package/scripts/changelog-merge.mjs +161 -0
- package/scripts/check-plugin-v2-coverage.mjs +213 -0
- package/scripts/ci-validate-yaml.py +7 -5
- package/scripts/{code_mode_server.py → code_mode.py} +119 -27
- package/scripts/cost.mjs +244 -41
- package/scripts/doctor.mjs +236 -29
- package/scripts/install/agents-md.mjs +4 -4
- package/scripts/install/cli-ui.mjs +1 -1
- package/scripts/install/config-migration.mjs +319 -6
- package/scripts/install/health-check.mjs +26 -37
- package/scripts/install/interactive.mjs +7 -7
- package/scripts/install/migrate.mjs +1 -1
- package/scripts/install/opencode-version.mjs +246 -4
- package/scripts/install/opencode.mjs +627 -179
- package/scripts/install/plugin.mjs +43 -9
- package/scripts/install/shared.mjs +120 -12
- package/scripts/install/vision-prompt.mjs +161 -0
- package/scripts/install-mcp.mjs +28 -9
- package/scripts/{mcp_persistence_server.py → mcp_persistence.py} +392 -78
- package/{src/mcp/mcp_resources_server.py → scripts/mcp_resources.py} +2 -2
- package/scripts/{memory_mcp_server.py → memory_mcp.py} +372 -198
- package/scripts/postinstall.mjs +0 -7
- package/scripts/probe-context-rehydrate.mjs +36 -13
- package/scripts/probe-pantheon-cost.mjs +71 -15
- package/scripts/prune.mjs +2 -2
- package/scripts/run-node-tests.mjs +189 -0
- package/scripts/sync-artifacts.mjs +26 -13
- package/scripts/sync-tui.mjs +24 -3
- package/scripts/test-opencode-v2-sandbox.sh +1913 -0
- package/scripts/validate-routing.mjs +43 -3
- package/scripts/validate-sandbox-install.mjs +138 -0
- package/scripts/versioning.mjs +13 -1
- package/scripts/vision-status.mjs +682 -0
- package/skills-lock.json +15 -16
- package/src/agents/aphrodite.md +20 -37
- package/src/agents/apollo.md +7 -3
- package/src/agents/athena.md +63 -35
- package/src/agents/demeter.md +8 -28
- package/src/agents/gaia.md +1 -1
- package/src/agents/hephaestus.md +10 -21
- package/src/agents/hermes.md +15 -38
- package/src/agents/iris.md +1 -1
- package/src/agents/mnemosyne.md +23 -60
- package/src/agents/nyx.md +1 -1
- package/src/agents/prometheus.md +16 -20
- package/src/agents/talos.md +10 -2
- package/src/agents/themis.md +5 -5
- package/src/agents/zeus.md +99 -130
- package/src/instructions/agent-return-format.instructions.md +18 -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 -110
- package/src/instructions/zeus-anti-stall.instructions.md +14 -61
- 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/mcp/{code_mode_server.py → code_mode.py} +119 -27
- package/src/mcp/eval_store.py +2 -2
- package/src/mcp/mcp_codemap_module.py +559 -129
- package/src/mcp/{mcp_persistence_server.py → mcp_persistence.py} +392 -78
- package/{scripts/mcp_resources_server.py → src/mcp/mcp_resources.py} +2 -2
- package/src/mcp/{memory_mcp_server.py → memory_mcp.py} +373 -250
- package/src/mcp/{pantheon_vision_server.py → pantheon_vision.py} +396 -40
- package/src/mcp/requirements-mcp.txt +10 -6
- package/src/mcp/requirements-vision.txt +4 -2
- package/src/pantheon/cost-command.ts +399 -101
- package/src/pantheon/delegation-enforce.ts +10 -1
- package/src/pantheon/model-command.ts +18 -7
- package/src/pantheon/presets.d.mts +0 -8
- package/src/pantheon/presets.mjs +5 -32
- package/src/pantheon/todo-enforcer.ts +1 -1
- package/src/pantheon/v2-bounded-read.ts +640 -0
- package/src/pantheon/v2-delegation-enforce.ts +214 -0
- package/src/pantheon/v2-execute-after.ts +186 -0
- package/src/pantheon/v2-tools.ts +429 -0
- package/src/pantheon/v2-unsupported.d.mts +11 -0
- package/src/pantheon/v2-unsupported.mjs +90 -0
- package/src/pantheon/v2-vision.ts +622 -0
- package/src/pantheon/vision-local.ts +531 -0
- package/src/pantheon/vision.ts +1161 -116
- package/src/plugin-v2.ts +1244 -288
- package/src/plugin.ts +12 -9
- package/src/plugins/tui/dist/server.d.ts +1 -2
- package/src/plugins/tui/dist/server.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui-v2.d.ts +154 -0
- package/src/plugins/tui/dist/tui-v2.d.ts.map +1 -0
- package/src/plugins/tui/dist/tui-v2.js +659 -0
- package/src/plugins/tui/dist/tui-v2.js.map +1 -0
- package/src/plugins/tui/dist/tui.d.ts +85 -82
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +72 -59
- package/src/plugins/tui/index-v2.ts +6 -0
- package/src/plugins/tui/package-lock.json +5504 -1341
- package/src/plugins/tui/package.json +25 -15
- package/src/plugins/tui/src/index-v2.tsx +734 -0
- package/src/plugins/tui/src/index.tsx +106 -85
- package/src/plugins/tui/src/opencode-v2.d.ts +13 -0
- package/src/plugins/tui/tsconfig.build.json +5 -0
- package/src/plugins/tui/tsconfig.typecheck.json +5 -0
- package/src/plugins/tui/tsdown.config.ts +22 -2
- package/src/plugins/tui/tui-v2.ts +2 -0
- package/src/plugins/tui/tui.ts +3 -0
- package/src/routing.yml +11 -22
- package/src/skills/README.md +34 -39
- package/src/skills/agent-coordination/SKILL.md +7 -2
- package/src/skills/artifact-management/SKILL.md +12 -5
- package/src/skills/auto-continue/SKILL.md +54 -95
- package/src/skills/code-review-checklist/SKILL.md +5 -5
- package/src/skills/context-compression/SKILL.md +26 -879
- package/src/skills/session-goal/SKILL.md +14 -5
- package/src/skills/spec-driven-development/SKILL.md +3 -1
- package/src/skills/tdd-with-agents/SKILL.md +18 -10
- package/.pantheon/code-mode/eval-llm-judge.py +0 -255
- package/.pantheon/code-mode/eval-monte-carlo.py +0 -368
- package/.pantheon/code-mode/eval-run.py +0 -197
- package/.pantheon/code-mode/eval-static.py +0 -321
- package/.pantheon/code-mode/session-end-save.py +0 -186
- package/.pantheon/code-mode/session-end-save.sh +0 -18
- package/opencode.json +0 -58
- package/scripts/recover-zenodo-deposition.mjs +0 -146
- package/scripts/test-opencode-v1-v2-sandbox.sh +0 -862
- package/scripts/validate-zenodo-release.mjs +0 -154
- package/scripts/zenodo-artifact.mjs +0 -94
- package/scripts/zenodo-release-state.mjs +0 -53
- package/src/skills/orchestration-workflow/SKILL.md +0 -311
|
@@ -10,10 +10,12 @@ Orchestration scripts executed via the `pantheon-code-mode` MCP server
|
|
|
10
10
|
| `checkpoint_session.py` | Python | Full checkpoint management: init, save, status, resume, list, health, archive, cleanup |
|
|
11
11
|
| `checkpoint-session.sh` | Bash | Shell wrapper for `checkpoint_session.py` |
|
|
12
12
|
| `compress-inline.py` | Python | Inline context compression — score, compress, stats, batch modes |
|
|
13
|
-
| `session-end-save.py` | Python | Session-end Vector DB backup scaffold |
|
|
14
|
-
| `session-end-save.sh` | Bash | Shell wrapper for `session-end-save.py` |
|
|
15
13
|
| `example-sync.sh` | Bash | Minimal demo script for testing `execute_code_script` |
|
|
16
14
|
|
|
15
|
+
Session persistence is handled by the persistence MCP server. The code-mode
|
|
16
|
+
payload contains orchestration, checkpoint, compression, and evaluation
|
|
17
|
+
helpers only; it does not export or back up a database.
|
|
18
|
+
|
|
17
19
|
## checkpoint_session.py Commands
|
|
18
20
|
|
|
19
21
|
| Command | Description |
|
|
@@ -23,8 +23,8 @@ from __future__ import annotations
|
|
|
23
23
|
|
|
24
24
|
import hashlib
|
|
25
25
|
import json
|
|
26
|
-
import sys
|
|
27
26
|
import shutil
|
|
27
|
+
import sys
|
|
28
28
|
from datetime import UTC, datetime, timedelta
|
|
29
29
|
from pathlib import Path
|
|
30
30
|
|
|
@@ -236,79 +236,100 @@ def cmd_list(slug: str) -> None:
|
|
|
236
236
|
print(f" [{n}] phase={phase} turns={turns} @ {ts}")
|
|
237
237
|
|
|
238
238
|
|
|
239
|
-
def
|
|
240
|
-
"""
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
return
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
239
|
+
def _validate_session_file(path: Path) -> tuple[dict, list[str]]:
|
|
240
|
+
"""Read session metadata and return any consistency issues."""
|
|
241
|
+
if not path.exists():
|
|
242
|
+
return {}, ["Missing session.json"]
|
|
243
|
+
try:
|
|
244
|
+
session = _read_json(path)
|
|
245
|
+
except (json.JSONDecodeError, ValueError):
|
|
246
|
+
return {}, ["session.json is corrupted (invalid JSON)"]
|
|
247
|
+
required = ["task_id", "status", "start_time", "last_activity"]
|
|
248
|
+
issues = [
|
|
249
|
+
f"session.json missing field: {field}"
|
|
250
|
+
for field in required
|
|
251
|
+
if field not in session
|
|
252
|
+
]
|
|
253
|
+
return session, issues
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _validate_heartbeat_file(path: Path) -> list[str]:
|
|
257
|
+
"""Read heartbeat metadata and return any consistency issues."""
|
|
258
|
+
if not path.exists():
|
|
259
|
+
return ["Missing heartbeat.json"]
|
|
260
|
+
try:
|
|
261
|
+
heartbeat = _read_json(path)
|
|
262
|
+
except (json.JSONDecodeError, ValueError):
|
|
263
|
+
return ["heartbeat.json is corrupted (invalid JSON)"]
|
|
264
|
+
if heartbeat.get("status") == "stalled":
|
|
265
|
+
return ["Session is marked as STALLED"]
|
|
266
|
+
return []
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _validate_checkpoints(sdir: Path) -> tuple[list[tuple[int, Path]], list[str]]:
|
|
270
|
+
"""Validate checkpoint contents and sequential numbering."""
|
|
271
|
+
checkpoints = get_checkpoints(sdir)
|
|
272
|
+
issues: list[str] = []
|
|
273
|
+
for number, path in checkpoints:
|
|
270
274
|
try:
|
|
271
|
-
|
|
272
|
-
if
|
|
273
|
-
issues.append("
|
|
275
|
+
checkpoint = _read_json(path)
|
|
276
|
+
if checkpoint.get("version", 1) > SCHEMA_VERSION:
|
|
277
|
+
issues.append(f"checkpoint-{number}.json has newer schema version")
|
|
278
|
+
if "slug" not in checkpoint or "timestamp" not in checkpoint:
|
|
279
|
+
issues.append(f"checkpoint-{number}.json missing required fields")
|
|
274
280
|
except (json.JSONDecodeError, ValueError):
|
|
275
|
-
issues.append("
|
|
281
|
+
issues.append(f"checkpoint-{number}.json is corrupted")
|
|
276
282
|
|
|
277
|
-
# Check checkpoint integrity
|
|
278
|
-
checkpoints = get_checkpoints(sdir)
|
|
279
283
|
if checkpoints:
|
|
280
|
-
for
|
|
281
|
-
try:
|
|
282
|
-
cp = _read_json(path)
|
|
283
|
-
if cp.get("version", 1) > SCHEMA_VERSION:
|
|
284
|
-
issues.append(f"checkpoint-{n}.json has newer schema version")
|
|
285
|
-
if "slug" not in cp or "timestamp" not in cp:
|
|
286
|
-
issues.append(f"checkpoint-{n}.json missing required fields")
|
|
287
|
-
except (json.JSONDecodeError, ValueError):
|
|
288
|
-
issues.append(f"checkpoint-{n}.json is corrupted")
|
|
289
|
-
|
|
290
|
-
# Check sequential numbering
|
|
291
|
-
numbers = [n for n, _ in checkpoints]
|
|
284
|
+
numbers = [number for number, _ in checkpoints]
|
|
292
285
|
expected = list(range(numbers[0], numbers[-1] + 1))
|
|
293
286
|
if numbers != expected:
|
|
294
287
|
missing = set(expected) - set(numbers)
|
|
295
288
|
issues.append(f"Missing checkpoints: {sorted(missing)}")
|
|
289
|
+
return checkpoints, issues
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _stale_activity_issue(
|
|
293
|
+
session_file: Path, heartbeat_file: Path, session: dict
|
|
294
|
+
) -> list[str]:
|
|
295
|
+
"""Return a stale-activity issue when both metadata files are present."""
|
|
296
|
+
if not session_file.exists() or not heartbeat_file.exists() or not session:
|
|
297
|
+
return []
|
|
298
|
+
try:
|
|
299
|
+
last_action = session.get("last_activity", "")
|
|
300
|
+
if last_action:
|
|
301
|
+
last_time = datetime.fromisoformat(last_action)
|
|
302
|
+
if datetime.now(UTC) - last_time > STALE_THRESHOLD:
|
|
303
|
+
return [
|
|
304
|
+
f"No activity for > {STALE_THRESHOLD.total_seconds() / 3600:.0f}h "
|
|
305
|
+
"(stale)"
|
|
306
|
+
]
|
|
307
|
+
except (ValueError, TypeError):
|
|
308
|
+
return ["Could not parse last_activity timestamp"]
|
|
309
|
+
return []
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def cmd_health(slug: str) -> None:
|
|
313
|
+
"""Validate session consistency."""
|
|
314
|
+
sdir = get_slug_dir(slug)
|
|
315
|
+
if not sdir.exists():
|
|
316
|
+
print(f"❌ Session not found: {slug}")
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
session_file = sdir / "session.json"
|
|
320
|
+
heartbeat_file = sdir / "heartbeat.json"
|
|
321
|
+
session, issues = _validate_session_file(session_file)
|
|
322
|
+
issues.extend(_validate_heartbeat_file(heartbeat_file))
|
|
323
|
+
checkpoints, checkpoint_issues = _validate_checkpoints(sdir)
|
|
324
|
+
issues.extend(checkpoint_issues)
|
|
325
|
+
issues.extend(_stale_activity_issue(session_file, heartbeat_file, session))
|
|
296
326
|
|
|
297
|
-
# Check for stale activity
|
|
298
|
-
if session_file.exists() and hb_file.exists() and session:
|
|
299
|
-
try:
|
|
300
|
-
last_action = session.get("last_activity", "")
|
|
301
|
-
if last_action:
|
|
302
|
-
last_time = datetime.fromisoformat(last_action)
|
|
303
|
-
if datetime.now(UTC) - last_time > STALE_THRESHOLD:
|
|
304
|
-
issues.append(f"No activity for > {STALE_THRESHOLD.total_seconds() / 3600:.0f}h (stale)")
|
|
305
|
-
except (ValueError, TypeError):
|
|
306
|
-
issues.append("Could not parse last_activity timestamp")
|
|
307
|
-
|
|
308
|
-
# Report
|
|
309
327
|
if not issues:
|
|
310
328
|
print(f"✅ Session '{slug}' is healthy")
|
|
311
|
-
print(
|
|
329
|
+
print(
|
|
330
|
+
f" Checkpoints: {len(checkpoints)}, "
|
|
331
|
+
f"Status: {session.get('status', 'unknown')}"
|
|
332
|
+
)
|
|
312
333
|
else:
|
|
313
334
|
print(f"⚠️ Session '{slug}' has {len(issues)} issue(s):")
|
|
314
335
|
for issue in issues:
|
|
@@ -324,7 +345,10 @@ def cmd_archive(slug: str) -> None:
|
|
|
324
345
|
|
|
325
346
|
archive_dir = ARCHIVEDIR / slug
|
|
326
347
|
if archive_dir.exists():
|
|
327
|
-
print(
|
|
348
|
+
print(
|
|
349
|
+
f"Session '{slug}' already exists in archive. Remove it first "
|
|
350
|
+
"or use a different slug."
|
|
351
|
+
)
|
|
328
352
|
return
|
|
329
353
|
|
|
330
354
|
archive_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -332,6 +356,53 @@ def cmd_archive(slug: str) -> None:
|
|
|
332
356
|
print(f"📦 Session '{slug}' archived to .pantheon/deepwork/archive/{slug}/")
|
|
333
357
|
|
|
334
358
|
|
|
359
|
+
def _cleanup_stale_session(item: Path, now: datetime, dry_run: bool) -> None:
|
|
360
|
+
"""Report a stale heartbeat for one session, if it can be read."""
|
|
361
|
+
heartbeat_file = item / "heartbeat.json"
|
|
362
|
+
if not heartbeat_file.exists():
|
|
363
|
+
return
|
|
364
|
+
try:
|
|
365
|
+
heartbeat = _read_json(heartbeat_file)
|
|
366
|
+
last_action = heartbeat.get("last_action", "")
|
|
367
|
+
if last_action and now - datetime.fromisoformat(last_action) > STALE_THRESHOLD:
|
|
368
|
+
if dry_run:
|
|
369
|
+
print(
|
|
370
|
+
f" [dry-run] Would flag stale session: {item.name}/ "
|
|
371
|
+
f"(last action: {last_action})"
|
|
372
|
+
)
|
|
373
|
+
else:
|
|
374
|
+
print(f" ⏸️ Session '{item.name}' stale since {last_action}")
|
|
375
|
+
except (ValueError, json.JSONDecodeError):
|
|
376
|
+
pass
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _cleanup_temp_files(item: Path, dry_run: bool) -> int:
|
|
380
|
+
"""Remove temporary files from one session and return the count."""
|
|
381
|
+
removed_files = 0
|
|
382
|
+
for tmp_file in item.glob("*.tmp"):
|
|
383
|
+
if dry_run:
|
|
384
|
+
print(f" [dry-run] Would remove temp file: {tmp_file.name}")
|
|
385
|
+
else:
|
|
386
|
+
tmp_file.unlink()
|
|
387
|
+
removed_files += 1
|
|
388
|
+
print(f" Removed temp file: {item.name}/{tmp_file.name}")
|
|
389
|
+
return removed_files
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _cleanup_item(item: Path, now: datetime, dry_run: bool) -> tuple[int, int]:
|
|
393
|
+
"""Clean one session directory and return (directories, files) removed."""
|
|
394
|
+
session_file = item / "session.json"
|
|
395
|
+
if not session_file.exists():
|
|
396
|
+
if dry_run:
|
|
397
|
+
print(f" [dry-run] Would remove orphan directory: {item.name}/")
|
|
398
|
+
return 0, 0
|
|
399
|
+
shutil.rmtree(item)
|
|
400
|
+
print(f" Removed orphan directory: {item.name}/")
|
|
401
|
+
return 1, 0
|
|
402
|
+
_cleanup_stale_session(item, now, dry_run)
|
|
403
|
+
return 0, _cleanup_temp_files(item, dry_run)
|
|
404
|
+
|
|
405
|
+
|
|
335
406
|
def cmd_cleanup(dry_run: bool = False) -> None:
|
|
336
407
|
"""Clean orphaned sessions and temp files.
|
|
337
408
|
|
|
@@ -347,47 +418,12 @@ def cmd_cleanup(dry_run: bool = False) -> None:
|
|
|
347
418
|
now = datetime.now(UTC)
|
|
348
419
|
removed_dirs = 0
|
|
349
420
|
removed_files = 0
|
|
350
|
-
|
|
351
421
|
for item in sorted(DEEPDIR.iterdir()):
|
|
352
422
|
if not item.is_dir() or item.name == "archive" or item.name.startswith("."):
|
|
353
423
|
continue
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
# Check if it's an orphan (no session.json)
|
|
359
|
-
if not session_file.exists():
|
|
360
|
-
if dry_run:
|
|
361
|
-
print(f" [dry-run] Would remove orphan directory: {item.name}/")
|
|
362
|
-
else:
|
|
363
|
-
shutil.rmtree(item)
|
|
364
|
-
removed_dirs += 1
|
|
365
|
-
print(f" Removed orphan directory: {item.name}/")
|
|
366
|
-
continue
|
|
367
|
-
|
|
368
|
-
# Check for stale sessions
|
|
369
|
-
if hb_file.exists():
|
|
370
|
-
try:
|
|
371
|
-
hb = _read_json(hb_file)
|
|
372
|
-
last_action = hb.get("last_action", "")
|
|
373
|
-
if last_action:
|
|
374
|
-
last_time = datetime.fromisoformat(last_action)
|
|
375
|
-
if now - last_time > STALE_THRESHOLD:
|
|
376
|
-
if dry_run:
|
|
377
|
-
print(f" [dry-run] Would flag stale session: {item.name}/ (last action: {last_action})")
|
|
378
|
-
else:
|
|
379
|
-
print(f" ⏸️ Session '{item.name}' stale since {last_action}")
|
|
380
|
-
except (ValueError, json.JSONDecodeError):
|
|
381
|
-
pass
|
|
382
|
-
|
|
383
|
-
# Clean temp files
|
|
384
|
-
for tmp_file in item.glob("*.tmp"):
|
|
385
|
-
if dry_run:
|
|
386
|
-
print(f" [dry-run] Would remove temp file: {tmp_file.name}")
|
|
387
|
-
else:
|
|
388
|
-
tmp_file.unlink()
|
|
389
|
-
removed_files += 1
|
|
390
|
-
print(f" Removed temp file: {item.name}/{tmp_file.name}")
|
|
424
|
+
dirs, files = _cleanup_item(item, now, dry_run)
|
|
425
|
+
removed_dirs += dirs
|
|
426
|
+
removed_files += files
|
|
391
427
|
|
|
392
428
|
if not dry_run:
|
|
393
429
|
total = removed_dirs + removed_files
|
|
@@ -164,6 +164,62 @@ BLOCKER_KEYWORDS: list[re.Pattern] = [
|
|
|
164
164
|
re.compile(r"depend", re.IGNORECASE),
|
|
165
165
|
re.compile(r"dependency", re.IGNORECASE),
|
|
166
166
|
]
|
|
167
|
+
FILE_NOVELTY_HIGH = 10
|
|
168
|
+
FILE_NOVELTY_MEDIUM = 5
|
|
169
|
+
PRIORITY_CRITICAL = 0.75
|
|
170
|
+
PRIORITY_HIGH = 0.50
|
|
171
|
+
PRIORITY_MEDIUM = 0.25
|
|
172
|
+
SUMMARY_MAX_CHARS = 80
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _keyword_scores(
|
|
176
|
+
text: str,
|
|
177
|
+
) -> tuple[list[float], list[float], list[float], list[str]]:
|
|
178
|
+
"""Return impact, risk, novelty, and matching keyword scores."""
|
|
179
|
+
impact_scores: list[float] = []
|
|
180
|
+
risk_scores: list[float] = []
|
|
181
|
+
novelty_scores: list[float] = []
|
|
182
|
+
keywords_found: list[str] = []
|
|
183
|
+
for name, pattern, impact, risk, novelty in KEYWORD_MAP:
|
|
184
|
+
if pattern.search(text):
|
|
185
|
+
impact_scores.append(impact)
|
|
186
|
+
risk_scores.append(risk)
|
|
187
|
+
novelty_scores.append(novelty)
|
|
188
|
+
keywords_found.append(name)
|
|
189
|
+
return impact_scores, risk_scores, novelty_scores, keywords_found
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _blocker_score(text: str, blocker_hint: bool) -> float:
|
|
193
|
+
"""Return the blocker score detected in text or supplied explicitly."""
|
|
194
|
+
blockers = 0.0
|
|
195
|
+
for pattern in BLOCKER_KEYWORDS:
|
|
196
|
+
if pattern.search(text):
|
|
197
|
+
# unblocked/unblocks = positive blocker resolution
|
|
198
|
+
blockers = max(
|
|
199
|
+
blockers, 0.8 if re.search(r"unblock", text, re.IGNORECASE) else 0.5
|
|
200
|
+
)
|
|
201
|
+
if blocker_hint:
|
|
202
|
+
blockers = max(blockers, 0.8)
|
|
203
|
+
return blockers
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _downstream_score(from_agent: str | None, to_agent: str | None) -> float:
|
|
207
|
+
"""Return downstream relevance for an agent pair."""
|
|
208
|
+
if not from_agent or not to_agent:
|
|
209
|
+
return 0.5
|
|
210
|
+
key = (from_agent.lower().strip(), to_agent.lower().strip())
|
|
211
|
+
return AGENT_PAIR_TABLE.get(key, 0.5)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _priority_band(score: float) -> str:
|
|
215
|
+
"""Map a score to the documented priority band."""
|
|
216
|
+
if score >= PRIORITY_CRITICAL:
|
|
217
|
+
return "CRITICAL"
|
|
218
|
+
if score >= PRIORITY_HIGH:
|
|
219
|
+
return "HIGH"
|
|
220
|
+
if score >= PRIORITY_MEDIUM:
|
|
221
|
+
return "MEDIUM"
|
|
222
|
+
return "LOW"
|
|
167
223
|
|
|
168
224
|
|
|
169
225
|
def compute_score(
|
|
@@ -178,45 +234,20 @@ def compute_score(
|
|
|
178
234
|
Returns dict with:
|
|
179
235
|
score, band, impact, risk, novelty, blockers, downstream, keywords_found
|
|
180
236
|
"""
|
|
181
|
-
impact_scores
|
|
182
|
-
risk_scores: list[float] = []
|
|
183
|
-
novelty_scores: list[float] = []
|
|
184
|
-
keywords_found: list[str] = []
|
|
185
|
-
|
|
186
|
-
for name, pattern, imp, rsk, nov in KEYWORD_MAP:
|
|
187
|
-
if pattern.search(text):
|
|
188
|
-
impact_scores.append(imp)
|
|
189
|
-
risk_scores.append(rsk)
|
|
190
|
-
novelty_scores.append(nov)
|
|
191
|
-
keywords_found.append(name)
|
|
237
|
+
impact_scores, risk_scores, novelty_scores, keywords_found = _keyword_scores(text)
|
|
192
238
|
|
|
193
239
|
impact = max(impact_scores) if impact_scores else 0.0
|
|
194
240
|
risk = max(risk_scores) if risk_scores else 0.0
|
|
195
241
|
novelty = max(novelty_scores) if novelty_scores else 0.0
|
|
196
242
|
|
|
197
243
|
# Novelty bonus for file count (overrides keyword score)
|
|
198
|
-
if files_changed >=
|
|
244
|
+
if files_changed >= FILE_NOVELTY_HIGH:
|
|
199
245
|
novelty = max(novelty, 1.0)
|
|
200
|
-
elif files_changed >=
|
|
246
|
+
elif files_changed >= FILE_NOVELTY_MEDIUM:
|
|
201
247
|
novelty = max(novelty, 0.8)
|
|
202
248
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
for bp in BLOCKER_KEYWORDS:
|
|
206
|
-
if bp.search(text):
|
|
207
|
-
# unblocked/unblocks = positive blocker resolution
|
|
208
|
-
if re.search(r"unblock", text, re.IGNORECASE):
|
|
209
|
-
blockers = max(blockers, 0.8)
|
|
210
|
-
else:
|
|
211
|
-
blockers = max(blockers, 0.5)
|
|
212
|
-
if blocker_hint:
|
|
213
|
-
blockers = max(blockers, 0.8)
|
|
214
|
-
|
|
215
|
-
# Downstream relevance
|
|
216
|
-
downstream: float = 0.5 # default when agent pair unknown
|
|
217
|
-
if from_agent and to_agent:
|
|
218
|
-
key = (from_agent.lower().strip(), to_agent.lower().strip())
|
|
219
|
-
downstream = AGENT_PAIR_TABLE.get(key, 0.5)
|
|
249
|
+
blockers = _blocker_score(text, blocker_hint)
|
|
250
|
+
downstream = _downstream_score(from_agent, to_agent)
|
|
220
251
|
|
|
221
252
|
score = (
|
|
222
253
|
impact * WEIGHTS["impact"]
|
|
@@ -226,19 +257,9 @@ def compute_score(
|
|
|
226
257
|
+ downstream * WEIGHTS["downstream"]
|
|
227
258
|
)
|
|
228
259
|
|
|
229
|
-
# Priority band (from SKILL.md line 125-131)
|
|
230
|
-
if score >= 0.75:
|
|
231
|
-
band = "CRITICAL"
|
|
232
|
-
elif score >= 0.50:
|
|
233
|
-
band = "HIGH"
|
|
234
|
-
elif score >= 0.25:
|
|
235
|
-
band = "MEDIUM"
|
|
236
|
-
else:
|
|
237
|
-
band = "LOW"
|
|
238
|
-
|
|
239
260
|
return {
|
|
240
261
|
"score": round(score, 2),
|
|
241
|
-
"band":
|
|
262
|
+
"band": _priority_band(score),
|
|
242
263
|
"impact": impact,
|
|
243
264
|
"risk": risk,
|
|
244
265
|
"novelty": novelty,
|
|
@@ -285,14 +306,17 @@ def compress(
|
|
|
285
306
|
)
|
|
286
307
|
|
|
287
308
|
# Summary = first meaningful line of scrubbed text, ≤80 chars
|
|
288
|
-
lines = [
|
|
309
|
+
lines = [line.strip() for line in scrubbed.splitlines() if line.strip()]
|
|
289
310
|
summary: str = ""
|
|
290
311
|
if lines:
|
|
291
|
-
summary = lines[0][:
|
|
292
|
-
if len(lines[0]) >
|
|
312
|
+
summary = lines[0][:SUMMARY_MAX_CHARS]
|
|
313
|
+
if len(lines[0]) > SUMMARY_MAX_CHARS:
|
|
293
314
|
summary += "…"
|
|
294
315
|
|
|
295
|
-
output_parts = [
|
|
316
|
+
output_parts = [
|
|
317
|
+
f"- What changed: {summary}",
|
|
318
|
+
f"- Priority: {result['band']} (score: {result['score']:.2f})",
|
|
319
|
+
]
|
|
296
320
|
if files:
|
|
297
321
|
output_parts.append(f"- Files: {files}")
|
|
298
322
|
|
|
@@ -311,14 +335,14 @@ def process_batch(
|
|
|
311
335
|
) -> str:
|
|
312
336
|
"""Read multiple files, scrub + score + compress each."""
|
|
313
337
|
parts: list[str] = []
|
|
314
|
-
for
|
|
315
|
-
fpath =
|
|
338
|
+
for raw_path in file_paths:
|
|
339
|
+
fpath = raw_path.strip()
|
|
316
340
|
if not fpath:
|
|
317
341
|
continue
|
|
318
342
|
try:
|
|
319
343
|
with open(fpath, encoding="utf-8") as fh:
|
|
320
344
|
content = fh.read()
|
|
321
|
-
except
|
|
345
|
+
except OSError as exc:
|
|
322
346
|
parts.append(f"- {fpath}: ERROR — {exc}")
|
|
323
347
|
continue
|
|
324
348
|
|
|
@@ -331,11 +355,11 @@ def process_batch(
|
|
|
331
355
|
)
|
|
332
356
|
|
|
333
357
|
# Summary: first meaningful line
|
|
334
|
-
content_lines = [
|
|
358
|
+
content_lines = [line.strip() for line in scrubbed.splitlines() if line.strip()]
|
|
335
359
|
summary = "(empty)"
|
|
336
360
|
if content_lines:
|
|
337
|
-
summary = content_lines[0][:
|
|
338
|
-
if len(content_lines[0]) >
|
|
361
|
+
summary = content_lines[0][:SUMMARY_MAX_CHARS]
|
|
362
|
+
if len(content_lines[0]) > SUMMARY_MAX_CHARS:
|
|
339
363
|
summary += "…"
|
|
340
364
|
|
|
341
365
|
base = os.path.basename(fpath)
|
|
@@ -356,7 +380,9 @@ def main() -> None:
|
|
|
356
380
|
parser = argparse.ArgumentParser(
|
|
357
381
|
description="Pantheon inline compression — score, compress, or stat text.",
|
|
358
382
|
)
|
|
359
|
-
parser.add_argument(
|
|
383
|
+
parser.add_argument(
|
|
384
|
+
"mode", choices=["score", "compress", "stats", "batch"], help="Operation mode"
|
|
385
|
+
)
|
|
360
386
|
parser.add_argument("--text", type=str, default="", help="Input text to process")
|
|
361
387
|
parser.add_argument(
|
|
362
388
|
"--files",
|
|
@@ -364,9 +390,24 @@ def main() -> None:
|
|
|
364
390
|
default="",
|
|
365
391
|
help="Comma-separated file paths (batch mode, or for file count in scoring)",
|
|
366
392
|
)
|
|
367
|
-
parser.add_argument(
|
|
368
|
-
|
|
369
|
-
|
|
393
|
+
parser.add_argument(
|
|
394
|
+
"--from-agent",
|
|
395
|
+
type=str,
|
|
396
|
+
default=None,
|
|
397
|
+
help="Source agent for downstream relevance",
|
|
398
|
+
)
|
|
399
|
+
parser.add_argument(
|
|
400
|
+
"--to-agent",
|
|
401
|
+
type=str,
|
|
402
|
+
default=None,
|
|
403
|
+
help="Target agent for downstream relevance",
|
|
404
|
+
)
|
|
405
|
+
parser.add_argument(
|
|
406
|
+
"--blocker",
|
|
407
|
+
action="store_true",
|
|
408
|
+
default=False,
|
|
409
|
+
help="Hint that entry was a blocker",
|
|
410
|
+
)
|
|
370
411
|
|
|
371
412
|
args = parser.parse_args()
|
|
372
413
|
|
|
@@ -378,7 +419,9 @@ def main() -> None:
|
|
|
378
419
|
print("Error: --files is required for batch mode", file=sys.stderr)
|
|
379
420
|
sys.exit(1)
|
|
380
421
|
file_list = [f.strip() for f in args.files.split(",") if f.strip()]
|
|
381
|
-
output = process_batch(
|
|
422
|
+
output = process_batch(
|
|
423
|
+
file_list, from_agent=args.from_agent, to_agent=args.to_agent
|
|
424
|
+
)
|
|
382
425
|
print(output)
|
|
383
426
|
return
|
|
384
427
|
|
|
@@ -413,7 +456,9 @@ def main() -> None:
|
|
|
413
456
|
# score mode
|
|
414
457
|
# -----------------------------------------------------------------------
|
|
415
458
|
if args.mode == "score":
|
|
416
|
-
file_count =
|
|
459
|
+
file_count = (
|
|
460
|
+
len([f for f in args.files.split(",") if f.strip()]) if args.files else 0
|
|
461
|
+
)
|
|
417
462
|
result = compute_score(
|
|
418
463
|
text,
|
|
419
464
|
from_agent=args.from_agent,
|
|
@@ -3,15 +3,9 @@
|
|
|
3
3
|
"scripts": {
|
|
4
4
|
"checkpoint-session.py": "b70f6667ba287622d6353ada2612c7f2fb70d4ee170d456ecd7ee2b6ead891aa",
|
|
5
5
|
"checkpoint-session.sh": "5b07e7c6c309e1db5c63f1f409eeb300a446bb9598899d94fdb13d94b56eb5f1",
|
|
6
|
-
"checkpoint_session.py": "
|
|
7
|
-
"compress-inline.py": "
|
|
8
|
-
"eval-llm-judge.py": "6d25df589c0f6cffa4766d0ad8eb86ac958960bc8c0f59e93098c7bf8effbbe8",
|
|
9
|
-
"eval-monte-carlo.py": "64c5eba9b072bec1f3eb713654da7edb9df46041395fdc4cb65730720135f8b1",
|
|
10
|
-
"eval-run.py": "5abfdac7d67d66838dd7ee402383e205ae39c0d912deb8a3a9a9a9c4d05952f5",
|
|
11
|
-
"eval-static.py": "33573295cf82a3c98e5f4c230ac376a40d1286ac16ff3aa031987396cde083b5",
|
|
6
|
+
"checkpoint_session.py": "487b4e3525ba03102fab1cd6283bb95d7489cd1848c72cf875884ac88b448ff7",
|
|
7
|
+
"compress-inline.py": "083ecc446cfe281be8f4aaca678eb2ae93adcbfef165f1746fb5bd364778f9d1",
|
|
12
8
|
"example-sync.sh": "7fce84c8dc5a9d25d441a4cb59549a2731910e72d6ef82df45ac5ca6ea66c729",
|
|
13
|
-
"session-end-save.py": "11088a722642d022e5e2ebad78292db4aa4803087b32e261b6eb069e9b991c48",
|
|
14
|
-
"session-end-save.sh": "5fb410143bf62ed7d7c912805b00ae79171127b1b8ff0825db0eb4bf03b595a8",
|
|
15
9
|
"test-allowed.sh": "723b116490044ffb403092c74b6bb94ee37330fecf453241f4cca22977ae8cdf",
|
|
16
10
|
"test-auto-continue-checkpoint.sh": "de2641a4da0f26e9653f320d44763e5b5ed698cf9c701c103c5c82f91175376f",
|
|
17
11
|
"test-env.sh": "6ea48a044451e5938e28fcb709368a7249a7d1b42615b190e40682ad26d4d1f9",
|