@matt82198/aesop 0.1.0-beta.3 → 0.1.0-beta.5
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/CHANGELOG.md +79 -95
- package/CLAUDE-TEMPLATE.md +115 -0
- package/README.md +126 -245
- package/aesop.config.example.json +34 -26
- package/bin/CLAUDE.md +67 -0
- package/bin/cli.js +593 -36
- package/daemons/CLAUDE.md +36 -0
- package/daemons/backup-fleet.sh +92 -21
- package/daemons/run-watchdog.sh +95 -12
- package/dash/CLAUDE.md +32 -0
- package/dash/dash-extra.mjs +194 -21
- package/dash/watchdog-gui.sh +23 -6
- package/docs/BEHAVIORAL-PR-REVIEW.md +86 -0
- package/docs/CHECKPOINTING.md +100 -0
- package/docs/DISPATCH-MODEL.md +11 -7
- package/docs/FORENSICS.md +204 -0
- package/docs/GOVERNANCE.md +4 -2
- package/docs/HOOK-INSTALL.md +307 -0
- package/docs/HOW-THE-LOOP-WORKS.md +175 -0
- package/docs/MEMORY-TEMPLATE.md +61 -0
- package/docs/README.md +44 -0
- package/docs/RELEASING.md +159 -0
- package/docs/RELIABILITY.md +61 -0
- package/docs/RESTORE.md +397 -0
- package/docs/SCRIPTS-POLICY.md +97 -0
- package/docs/archive/README.md +3 -0
- package/docs/archive/spikes/tiered-cognition/ACTIVATION.md +125 -0
- package/docs/archive/spikes/tiered-cognition/DESIGN.md +287 -0
- package/docs/archive/spikes/tiered-cognition/FINDINGS.md +113 -0
- package/docs/archive/spikes/tiered-cognition/README.md +27 -0
- package/docs/archive/spikes/tiered-cognition/aesop-cognition.example.md +32 -0
- package/docs/archive/spikes/tiered-cognition/force-model-policy.merged.mjs +673 -0
- package/docs/archive/spikes/tiered-cognition/strip-tools-hook.mjs +434 -0
- package/docs/case-study-portfolio.md +61 -0
- package/docs/self-stats-data.json +11 -0
- package/docs/templates/FLEET-OPS-ANALYSIS.example.md +27 -0
- package/docs/templates/FLEET-OPS-RECOMMENDATIONS.example.md +24 -0
- package/docs/templates/PROPOSALS-LOG.example.md +64 -0
- package/hooks/CLAUDE.md +61 -0
- package/hooks/claude/force-model-policy.mjs +159 -0
- package/hooks/pre-push-policy.sh +753 -0
- package/mcp/CLAUDE.md +213 -0
- package/mcp/package.json +26 -0
- package/mcp/server.mjs +543 -0
- package/monitor/.signal-state.json +3 -0
- package/monitor/ACTIONS.log +1 -0
- package/monitor/BRIEF.md +24 -0
- package/monitor/CHARTER.md +75 -88
- package/monitor/CLAUDE.md +40 -0
- package/monitor/SIGNALS.json +54 -0
- package/monitor/collect-signals.mjs +613 -61
- package/package.json +15 -3
- package/scan/fleet-scan.example.mjs +292 -0
- package/skills/CLAUDE.md +3 -0
- package/skills/healthcheck/SKILL.md +44 -0
- package/skills/power/SKILL.md +161 -0
- package/state_store/CLAUDE.md +39 -0
- package/state_store/__init__.py +24 -0
- package/state_store/__pycache__/__init__.cpython-314.pyc +0 -0
- package/state_store/__pycache__/api.cpython-314.pyc +0 -0
- package/state_store/__pycache__/export.cpython-314.pyc +0 -0
- package/state_store/__pycache__/ingest.cpython-314.pyc +0 -0
- package/state_store/__pycache__/projections.cpython-314.pyc +0 -0
- package/state_store/__pycache__/store.cpython-314.pyc +0 -0
- package/state_store/api.py +35 -0
- package/state_store/export.py +19 -0
- package/state_store/ingest.py +24 -0
- package/state_store/projections.py +52 -0
- package/state_store/store.py +102 -0
- package/tools/CLAUDE.md +72 -0
- package/tools/__pycache__/alert_bridge.cpython-314.pyc +0 -0
- package/tools/__pycache__/buildlog.cpython-314.pyc +0 -0
- package/tools/__pycache__/ci_merge_wait.cpython-314.pyc +0 -0
- package/tools/__pycache__/ensure_state.cpython-314.pyc +0 -0
- package/tools/__pycache__/eod_sweep.cpython-314.pyc +0 -0
- package/tools/__pycache__/fleet_ledger.cpython-314.pyc +0 -0
- package/tools/__pycache__/fleet_prompt_extractor.cpython-314.pyc +0 -0
- package/tools/__pycache__/healthcheck.cpython-314.pyc +0 -0
- package/tools/__pycache__/heartbeat.cpython-314.pyc +0 -0
- package/tools/__pycache__/inbox_drain.cpython-314.pyc +0 -0
- package/tools/__pycache__/launch_tui.cpython-314.pyc +0 -0
- package/tools/__pycache__/metrics_gate.cpython-314.pyc +0 -0
- package/tools/__pycache__/orchestrator_status.cpython-314.pyc +0 -0
- package/tools/__pycache__/power_selftest.cpython-314.pyc +0 -0
- package/tools/__pycache__/prepublish_scan.cpython-314.pyc +0 -0
- package/tools/__pycache__/rotate_logs.cpython-314.pyc +0 -0
- package/tools/__pycache__/scanner_selftest.cpython-314.pyc +0 -0
- package/tools/__pycache__/secret_scan.cpython-314.pyc +0 -0
- package/tools/__pycache__/self_stats.cpython-314.pyc +0 -0
- package/tools/__pycache__/session_usage_summary.cpython-314.pyc +0 -0
- package/tools/__pycache__/stall_check.cpython-314.pyc +0 -0
- package/tools/__pycache__/transcript_replay.cpython-314.pyc +0 -0
- package/tools/__pycache__/transcript_timeline.cpython-314.pyc +0 -0
- package/tools/__pycache__/verify_dash.cpython-314.pyc +0 -0
- package/tools/__pycache__/verify_submit_encoding.cpython-314.pyc +0 -0
- package/tools/agent-forensics.sh +186 -0
- package/tools/alert_bridge.py +449 -0
- package/tools/buildlog.py +182 -0
- package/tools/ci_merge_wait.py +317 -0
- package/tools/ensure_state.py +94 -0
- package/tools/eod_sweep.py +239 -0
- package/tools/fleet_ledger.py +272 -0
- package/tools/fleet_prompt_extractor.py +134 -0
- package/tools/healthcheck.py +296 -0
- package/tools/heartbeat.py +131 -0
- package/tools/inbox_drain.py +249 -0
- package/tools/launch_tui.py +51 -3
- package/tools/lock.mjs +211 -0
- package/tools/metrics_gate.py +205 -0
- package/tools/orchestrator_status.py +99 -0
- package/tools/power_selftest.py +386 -0
- package/tools/prepublish_scan.py +84 -0
- package/tools/proposals.mjs +248 -0
- package/tools/reconstitute.sh +467 -0
- package/tools/rotate_logs.py +228 -0
- package/tools/scanner_selftest.py +303 -0
- package/tools/secret_scan.py +138 -31
- package/tools/self_stats.py +509 -0
- package/tools/session_usage_summary.py +198 -0
- package/tools/stall_check.py +172 -0
- package/tools/svg_to_png.mjs +50 -0
- package/tools/transcript_replay.py +236 -0
- package/tools/transcript_timeline.py +184 -0
- package/tools/verify_dash.py +513 -0
- package/tools/verify_submit_encoding.py +202 -0
- package/ui/CLAUDE.md +125 -0
- package/ui/__pycache__/agents.cpython-314.pyc +0 -0
- package/ui/__pycache__/collectors.cpython-314.pyc +0 -0
- package/ui/__pycache__/config.cpython-314.pyc +0 -0
- package/ui/__pycache__/cost.cpython-314.pyc +0 -0
- package/ui/__pycache__/csrf.cpython-314.pyc +0 -0
- package/ui/__pycache__/handler.cpython-314.pyc +0 -0
- package/ui/__pycache__/render.cpython-314.pyc +0 -0
- package/ui/__pycache__/serve.cpython-314.pyc +0 -0
- package/ui/__pycache__/sse.cpython-314.pyc +0 -0
- package/ui/agents.py +183 -0
- package/ui/api/__init__.py +84 -0
- package/ui/api/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ui/api/__pycache__/submit.cpython-314.pyc +0 -0
- package/ui/api/__pycache__/tracker.cpython-314.pyc +0 -0
- package/ui/api/submit.py +97 -0
- package/ui/api/tracker.py +116 -0
- package/ui/collectors.py +735 -0
- package/ui/config.py +123 -0
- package/ui/cost.py +260 -0
- package/ui/csrf.py +144 -0
- package/ui/handler.py +676 -0
- package/ui/render.py +49 -0
- package/ui/serve.py +68 -796
- package/ui/sse.py +183 -0
- package/ui/web/.gitattributes +13 -0
- package/ui/web/dist/assets/index-2LZDQirC.js +9 -0
- package/ui/web/dist/assets/index-D4M1qyOv.css +1 -0
- package/ui/web/dist/index.html +14 -0
- package/ui/web/index.html +13 -0
- package/ui/web/package-lock.json +2225 -0
- package/ui/web/package.json +26 -0
- package/ui/web/src/App.test.tsx +74 -0
- package/ui/web/src/App.tsx +142 -0
- package/ui/web/src/CONTRIBUTING-UI.md +49 -0
- package/ui/web/src/components/AgentRow.css +187 -0
- package/ui/web/src/components/AgentRow.test.tsx +209 -0
- package/ui/web/src/components/AgentRow.tsx +207 -0
- package/ui/web/src/components/AgentsPanel.css +108 -0
- package/ui/web/src/components/AgentsPanel.test.tsx +41 -0
- package/ui/web/src/components/AgentsPanel.tsx +58 -0
- package/ui/web/src/components/AlertsPanel.css +88 -0
- package/ui/web/src/components/AlertsPanel.test.tsx +51 -0
- package/ui/web/src/components/AlertsPanel.tsx +67 -0
- package/ui/web/src/components/BacklogPanel.test.tsx +126 -0
- package/ui/web/src/components/BacklogPanel.tsx +122 -0
- package/ui/web/src/components/CostChart.css +110 -0
- package/ui/web/src/components/CostChart.test.tsx +144 -0
- package/ui/web/src/components/CostChart.tsx +152 -0
- package/ui/web/src/components/CostTable.css +93 -0
- package/ui/web/src/components/CostTable.test.tsx +165 -0
- package/ui/web/src/components/CostTable.tsx +94 -0
- package/ui/web/src/components/EventsFeed.css +68 -0
- package/ui/web/src/components/EventsFeed.test.tsx +36 -0
- package/ui/web/src/components/EventsFeed.tsx +31 -0
- package/ui/web/src/components/HealthHeader.css +137 -0
- package/ui/web/src/components/HealthHeader.test.tsx +278 -0
- package/ui/web/src/components/HealthHeader.tsx +281 -0
- package/ui/web/src/components/InboxForm.css +135 -0
- package/ui/web/src/components/InboxForm.test.tsx +208 -0
- package/ui/web/src/components/InboxForm.tsx +116 -0
- package/ui/web/src/components/MessagesTail.module.css +144 -0
- package/ui/web/src/components/MessagesTail.test.tsx +176 -0
- package/ui/web/src/components/MessagesTail.tsx +94 -0
- package/ui/web/src/components/ReposPanel.css +90 -0
- package/ui/web/src/components/ReposPanel.test.tsx +45 -0
- package/ui/web/src/components/ReposPanel.tsx +67 -0
- package/ui/web/src/components/Scorecard.css +106 -0
- package/ui/web/src/components/Scorecard.test.tsx +117 -0
- package/ui/web/src/components/Scorecard.tsx +85 -0
- package/ui/web/src/components/Timeline.module.css +151 -0
- package/ui/web/src/components/Timeline.test.tsx +215 -0
- package/ui/web/src/components/Timeline.tsx +99 -0
- package/ui/web/src/components/TrackerBoard.test.tsx +121 -0
- package/ui/web/src/components/TrackerBoard.tsx +107 -0
- package/ui/web/src/components/TrackerCard.test.tsx +180 -0
- package/ui/web/src/components/TrackerCard.tsx +160 -0
- package/ui/web/src/components/TrackerForm.test.tsx +189 -0
- package/ui/web/src/components/TrackerForm.tsx +144 -0
- package/ui/web/src/lib/api.ts +218 -0
- package/ui/web/src/lib/format.test.ts +89 -0
- package/ui/web/src/lib/format.ts +103 -0
- package/ui/web/src/lib/sanitizeUrl.test.ts +84 -0
- package/ui/web/src/lib/sanitizeUrl.ts +38 -0
- package/ui/web/src/lib/types.ts +230 -0
- package/ui/web/src/lib/useHashRoute.test.ts +60 -0
- package/ui/web/src/lib/useHashRoute.ts +23 -0
- package/ui/web/src/lib/useSSE.ts +175 -0
- package/ui/web/src/main.tsx +10 -0
- package/ui/web/src/styles/global.css +179 -0
- package/ui/web/src/styles/theme.css +184 -0
- package/ui/web/src/styles/work.css +572 -0
- package/ui/web/src/test/fixtures.ts +385 -0
- package/ui/web/src/test/setup.ts +49 -0
- package/ui/web/src/views/Activity.module.css +43 -0
- package/ui/web/src/views/Activity.test.tsx +89 -0
- package/ui/web/src/views/Activity.tsx +31 -0
- package/ui/web/src/views/Cost.css +87 -0
- package/ui/web/src/views/Cost.test.tsx +142 -0
- package/ui/web/src/views/Cost.tsx +54 -0
- package/ui/web/src/views/Overview.css +51 -0
- package/ui/web/src/views/Overview.test.tsx +76 -0
- package/ui/web/src/views/Overview.tsx +46 -0
- package/ui/web/src/views/Work.test.tsx +82 -0
- package/ui/web/src/views/Work.tsx +79 -0
- package/ui/web/src/vite-env.d.ts +10 -0
- package/ui/web/tsconfig.json +22 -0
- package/ui/web/vite.config.ts +25 -0
- package/ui/web/vitest.config.ts +12 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""state_store.export — render a projection back into git-tracked JSON.
|
|
2
|
+
|
|
3
|
+
In the DB-source-of-truth design git stops being *read* for state; this job
|
|
4
|
+
renders human-readable, diffable snapshots (e.g. tracker.json) FROM the
|
|
5
|
+
projections for durability and review. The JSON style (indent=2, ascii-escaped)
|
|
6
|
+
matches the existing state/tracker.json so a future cutover produces
|
|
7
|
+
minimal-diff snapshots.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def export_tracker(api, out_path: str) -> None:
|
|
15
|
+
"""Write the tracker projection to ``out_path`` as pretty JSON."""
|
|
16
|
+
projection = api.project("tracker")
|
|
17
|
+
with open(out_path, "w", encoding="utf-8") as fh:
|
|
18
|
+
json.dump(projection, fh, indent=2)
|
|
19
|
+
fh.write("\n")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""state_store.ingest — backfill events from an existing tracker.json.
|
|
2
|
+
|
|
3
|
+
Migration path for the DB-source-of-truth cutover: read the current
|
|
4
|
+
tracker.json and emit one ``item_created`` event per item (full payload) so the
|
|
5
|
+
event store reproduces the current state. History is not reconstructed (only
|
|
6
|
+
final state exists on disk), but the projection round-trips to the same items,
|
|
7
|
+
so an export re-renders the identical tracker.json.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def ingest_tracker_json(api, tracker_json_path: str, actor: str = "migration") -> int:
|
|
15
|
+
"""Append one ``item_created`` per item in ``tracker_json_path``.
|
|
16
|
+
|
|
17
|
+
Returns the number of items ingested.
|
|
18
|
+
"""
|
|
19
|
+
with open(tracker_json_path, "r", encoding="utf-8") as fh:
|
|
20
|
+
data = json.load(fh)
|
|
21
|
+
items = data.get("items", [])
|
|
22
|
+
for item in items:
|
|
23
|
+
api.append("tracker", "item_created", item, actor)
|
|
24
|
+
return len(items)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""state_store.projections — fold tracker events into current state.
|
|
2
|
+
|
|
3
|
+
``project_tracker`` reconstructs the tracker.json shape
|
|
4
|
+
(``{"version": 1, "items": [...]}``) from the ``tracker`` event stream:
|
|
5
|
+
|
|
6
|
+
- ``item_created`` : payload is the full item dict; establishes the item
|
|
7
|
+
(kept in first-seen order).
|
|
8
|
+
- ``item_updated`` : payload is ``{"id": <id>, ...partial fields}``; merges
|
|
9
|
+
the given fields onto the existing item.
|
|
10
|
+
- ``item_archived`` : payload is ``{"id": <id>}`` (+ optional
|
|
11
|
+
``completed_at``); sets ``status`` to ``"archived"``.
|
|
12
|
+
|
|
13
|
+
Unknown ids on update/archive and unknown event types are ignored, keeping the
|
|
14
|
+
fold tolerant of partial/legacy streams.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
TRACKER_VERSION = 1
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def project_tracker(events: list) -> dict:
|
|
22
|
+
"""Return the current tracker state projected from ``events``."""
|
|
23
|
+
order = [] # item ids in first-seen order (stable output ordering)
|
|
24
|
+
items = {} # id -> item dict
|
|
25
|
+
for ev in events:
|
|
26
|
+
etype = ev.get("type")
|
|
27
|
+
payload = ev.get("payload") or {}
|
|
28
|
+
if etype == "item_created":
|
|
29
|
+
iid = payload.get("id")
|
|
30
|
+
if iid is None:
|
|
31
|
+
continue
|
|
32
|
+
if iid not in items:
|
|
33
|
+
order.append(iid)
|
|
34
|
+
items[iid] = dict(payload)
|
|
35
|
+
elif etype == "item_updated":
|
|
36
|
+
iid = payload.get("id")
|
|
37
|
+
if iid in items:
|
|
38
|
+
merged = dict(items[iid])
|
|
39
|
+
for key, value in payload.items():
|
|
40
|
+
if key != "id":
|
|
41
|
+
merged[key] = value
|
|
42
|
+
items[iid] = merged
|
|
43
|
+
elif etype == "item_archived":
|
|
44
|
+
iid = payload.get("id")
|
|
45
|
+
if iid in items:
|
|
46
|
+
merged = dict(items[iid])
|
|
47
|
+
merged["status"] = "archived"
|
|
48
|
+
if "completed_at" in payload:
|
|
49
|
+
merged["completed_at"] = payload["completed_at"]
|
|
50
|
+
items[iid] = merged
|
|
51
|
+
# any other event type is ignored
|
|
52
|
+
return {"version": TRACKER_VERSION, "items": [items[i] for i in order]}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""state_store.store — SQLite-backed, append-only, concurrency-safe event log.
|
|
2
|
+
|
|
3
|
+
Durable substrate for aesop's event-sourced state layer (the DB-source-of-truth
|
|
4
|
+
design; git becomes a rendered export, not the coordination layer). Backend is
|
|
5
|
+
SQLite in WAL mode so many readers and serialized writers share one file; the
|
|
6
|
+
same interface is meant to swap to Postgres behind ``state_store.api.StateAPI``
|
|
7
|
+
for team scale without touching call sites.
|
|
8
|
+
|
|
9
|
+
Stdlib only (sqlite3, json, time) per aesop's no-external-deps invariant.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sqlite3
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class EventStore:
|
|
19
|
+
"""Append-only event log stored at ``db_path``.
|
|
20
|
+
|
|
21
|
+
Safe under concurrent appends from multiple threads AND multiple EventStore
|
|
22
|
+
instances on the same file: every call opens its own connection with
|
|
23
|
+
``PRAGMA busy_timeout`` and assigns the per-stream version inside a
|
|
24
|
+
``BEGIN IMMEDIATE`` transaction, so the read-max-version-then-insert is
|
|
25
|
+
atomic and two writers can never collide or duplicate a version.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, db_path: str):
|
|
29
|
+
self.db_path = db_path
|
|
30
|
+
conn = sqlite3.connect(self.db_path)
|
|
31
|
+
try:
|
|
32
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
33
|
+
conn.execute("PRAGMA busy_timeout=5000")
|
|
34
|
+
conn.execute(
|
|
35
|
+
"""
|
|
36
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
37
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
38
|
+
ts REAL NOT NULL,
|
|
39
|
+
actor TEXT NOT NULL,
|
|
40
|
+
stream TEXT NOT NULL,
|
|
41
|
+
type TEXT NOT NULL,
|
|
42
|
+
payload TEXT NOT NULL,
|
|
43
|
+
version INTEGER NOT NULL
|
|
44
|
+
)
|
|
45
|
+
"""
|
|
46
|
+
)
|
|
47
|
+
conn.execute(
|
|
48
|
+
"CREATE INDEX IF NOT EXISTS idx_events_stream_version "
|
|
49
|
+
"ON events(stream, version)"
|
|
50
|
+
)
|
|
51
|
+
conn.commit()
|
|
52
|
+
finally:
|
|
53
|
+
conn.close()
|
|
54
|
+
|
|
55
|
+
def append(self, stream: str, event_type: str, payload: dict, actor: str = "system") -> int:
|
|
56
|
+
"""Append one event to ``stream``; return its new per-stream version (1-based)."""
|
|
57
|
+
conn = sqlite3.connect(self.db_path)
|
|
58
|
+
try:
|
|
59
|
+
conn.execute("PRAGMA busy_timeout=5000")
|
|
60
|
+
# BEGIN IMMEDIATE takes the write lock up front so the
|
|
61
|
+
# read-max-then-insert below is atomic under contention.
|
|
62
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
63
|
+
row = conn.execute(
|
|
64
|
+
"SELECT COALESCE(MAX(version), 0) FROM events WHERE stream = ?",
|
|
65
|
+
(stream,),
|
|
66
|
+
).fetchone()
|
|
67
|
+
version = row[0] + 1
|
|
68
|
+
conn.execute(
|
|
69
|
+
"INSERT INTO events (ts, actor, stream, type, payload, version) "
|
|
70
|
+
"VALUES (?, ?, ?, ?, ?, ?)",
|
|
71
|
+
(time.time(), actor, stream, event_type, json.dumps(payload), version),
|
|
72
|
+
)
|
|
73
|
+
conn.commit()
|
|
74
|
+
return version
|
|
75
|
+
finally:
|
|
76
|
+
conn.close()
|
|
77
|
+
|
|
78
|
+
def read(self, stream: str) -> list:
|
|
79
|
+
"""Return all events for ``stream`` ascending by version (empty if none)."""
|
|
80
|
+
return self._rows("WHERE stream = ? ORDER BY version ASC", (stream,))
|
|
81
|
+
|
|
82
|
+
def read_all(self) -> list:
|
|
83
|
+
"""Return all events across all streams ascending by id."""
|
|
84
|
+
return self._rows("ORDER BY id ASC", ())
|
|
85
|
+
|
|
86
|
+
def _rows(self, clause: str, params) -> list:
|
|
87
|
+
conn = sqlite3.connect(self.db_path)
|
|
88
|
+
try:
|
|
89
|
+
conn.execute("PRAGMA busy_timeout=5000")
|
|
90
|
+
cur = conn.execute(
|
|
91
|
+
"SELECT id, ts, actor, stream, type, payload, version FROM events " + clause,
|
|
92
|
+
params,
|
|
93
|
+
)
|
|
94
|
+
return [
|
|
95
|
+
{
|
|
96
|
+
"id": r[0], "ts": r[1], "actor": r[2], "stream": r[3],
|
|
97
|
+
"type": r[4], "payload": json.loads(r[5]), "version": r[6],
|
|
98
|
+
}
|
|
99
|
+
for r in cur.fetchall()
|
|
100
|
+
]
|
|
101
|
+
finally:
|
|
102
|
+
conn.close()
|
package/tools/CLAUDE.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# tools/ — Build utilities
|
|
2
|
+
|
|
3
|
+
Local-only Python (stdlib only, no external deps), bash (POSIX, CRLF-safe). Never print secrets — report by pattern name/location only.
|
|
4
|
+
|
|
5
|
+
## FILES
|
|
6
|
+
|
|
7
|
+
- `lock.mjs` — Fail-closed atomic lock acquisition (exponential backoff + stale-lock detection)
|
|
8
|
+
- `secret_scan.py` — Pre-push secret/credential detection gate
|
|
9
|
+
- `scanner_selftest.py` — Regression harness for secret_scan.py
|
|
10
|
+
- `prepublish_scan.py` — Pre-publish full history + staged-changes scan gate
|
|
11
|
+
- `metrics_gate.py` — PR gate for hard numeric claims in markdown
|
|
12
|
+
- `self_stats.py` — Git-derived metrics counter + README block generator
|
|
13
|
+
- `verify_dash.py` — Browser proof for realtime SSE dashboard
|
|
14
|
+
- `verify_submit_encoding.py` — Browser proof for /submit UTF-8 inbox bootstrap
|
|
15
|
+
- `ci_merge_wait.py` — CI-gated merge helper (polls gh pr view until SUCCESS)
|
|
16
|
+
- `alert_bridge.py` — Slack/Discord webhook bridge for SECURITY-ALERTS
|
|
17
|
+
- `proposals.mjs` — Proposal lifecycle manager (list/accept/reject)
|
|
18
|
+
- `power_selftest.py` — Health check harness for /power bootstrap
|
|
19
|
+
- `healthcheck.py` — Fleet health aggregator (heartbeat/alert/orchestrator status)
|
|
20
|
+
- `buildlog.py` — Uniform BUILDLOG.md appender
|
|
21
|
+
- `ensure_state.py` — Scaffold STATE.md and BUILDLOG.md templates
|
|
22
|
+
- `fleet_ledger.py` — Append-only cost ledger with harvest/rotate
|
|
23
|
+
- `heartbeat.py` — Single-instance loop liveness registry
|
|
24
|
+
- `stall_check.py` — Automated agent transcript stall detector
|
|
25
|
+
- `inbox_drain.py` — Drain UI inbox submissions
|
|
26
|
+
- `orchestrator_status.py` — Atomic orchestrator status updates
|
|
27
|
+
- `reconstitute.sh` — Clone/fetch repos from config with security validation
|
|
28
|
+
- `eod_sweep.py` — End-of-day safety check (dirty trees, unpushed commits)
|
|
29
|
+
- `agent-forensics.sh` — Incident forensics / behavior reconstruction
|
|
30
|
+
- `launch_tui.py` — Spawn bash TUI script in detached terminal
|
|
31
|
+
- `rotate_logs.py` — Log rotation utility with size/line thresholds
|
|
32
|
+
- `session_usage_summary.py` — Aggregate token usage across session transcripts
|
|
33
|
+
- `transcript_replay.py` — Replay post-commit edits from transcripts to recover work
|
|
34
|
+
- `transcript_timeline.py` — Extract Write/Edit/Read timeline from transcripts
|
|
35
|
+
- `fleet_prompt_extractor.py` — Extract and deduplicate Agent/Task spawn prompts
|
|
36
|
+
- `svg_to_png.mjs` — Rasterize SVG to PNG via @resvg/resvg-js (with lazy import error handling)
|
|
37
|
+
|
|
38
|
+
## secret_scan.py — Pre-push secret/credential detection gate
|
|
39
|
+
|
|
40
|
+
Scans staged/history/paths for secrets by regex pattern and credential filenames; blocks pushes on findings.
|
|
41
|
+
|
|
42
|
+
- `secret_scan.py --staged [--repo PATH]` — scan git staged files
|
|
43
|
+
- `secret_scan.py --history [--repo PATH]` — scan all git history blobs
|
|
44
|
+
- `secret_scan.py PATH [PATH...]` — scan files/dirs directly (recurse)
|
|
45
|
+
|
|
46
|
+
Exit: 0=clean, 1=findings, 2=usage error. Output masks secrets as `xxxx...`.
|
|
47
|
+
|
|
48
|
+
**Pragma escape** (pattern findings only; credential filenames always fatal):
|
|
49
|
+
```
|
|
50
|
+
# secretscan: allow-pattern-docs
|
|
51
|
+
```
|
|
52
|
+
Mark file's first 10 lines to report rule-based findings as ALLOWED-DOC (non-fatal). Use only for deliberate pattern documentation.
|
|
53
|
+
|
|
54
|
+
## agent-forensics.sh — Incident forensics / behavior reconstruction
|
|
55
|
+
|
|
56
|
+
Read-only git plumbing; reconstructs agent behavior snapshot or diffs behavior-controlling files.
|
|
57
|
+
|
|
58
|
+
- `bash tools/agent-forensics.sh <commit>` — print commit header, rules snapshot, CLAUDE.md, STATE.md, last 30 lines of BUILDLOG.md
|
|
59
|
+
- `bash tools/agent-forensics.sh --diff <commitA> <commitB>` — diff CLAUDE.md, STATE.md, docs/, monitor/CHARTER.md, hooks/ between commits
|
|
60
|
+
|
|
61
|
+
Exit: 0=success, 1=error (never raw git traces). Requires: git, head, tail, wc, grep.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## Invariants
|
|
65
|
+
|
|
66
|
+
- **Dependency-light**: Python tools must work on base Python 3 (no pip installs).
|
|
67
|
+
- **CRLF-safe shell**: no line continuations in .sh scripts; Git Bash + Linux compatible.
|
|
68
|
+
- **Never print secrets**: mask as pattern name + masked value only.
|
|
69
|
+
- **Config-driven paths**: heartbeat/ledger/logs use AESOP_STATE_ROOT env var (default ./state) or CLI args; no hardcoded personal paths.
|
|
70
|
+
- **Fragment-assembled secrets in tests**: scanner_selftest.py concatenates dummy secrets at runtime so pattern text never appears contiguously (self-scan invariant).
|
|
71
|
+
- **verify_*.py are mandatory CI gates**: verify_dash.py and verify_submit_encoding.py are required pre-push gates; use `--allow-skip` only in truly browserless environments (CI must run both).
|
|
72
|
+
- **lock.mjs is the ONLY lock implementation**: never reimplement locking in proposals.mjs or elsewhere; all proposals/state updates must use fail-closed lock.mjs with exponential backoff + stale-lock breaking.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# agent-forensics.sh — incident forensics tool
|
|
3
|
+
# Reconstruct agent behavior as of a commit using git plumbing.
|
|
4
|
+
# Usage:
|
|
5
|
+
# bash tools/agent-forensics.sh <commit>
|
|
6
|
+
# bash tools/agent-forensics.sh --diff <commitA> <commitB>
|
|
7
|
+
#
|
|
8
|
+
# Prints:
|
|
9
|
+
# - Commit header (hash, date, subject)
|
|
10
|
+
# - Rules snapshot (docs/CARDINAL-RULES.md)
|
|
11
|
+
# - CLAUDE.md (if present)
|
|
12
|
+
# - STATE.md (if present)
|
|
13
|
+
# - Last 30 lines of BUILDLOG.md (if tracked)
|
|
14
|
+
# - For --diff: behavior changes between two commits
|
|
15
|
+
#
|
|
16
|
+
# Exit codes: 0 on success, 1 on error (never raw git stack traces).
|
|
17
|
+
# Requires: git, head, tail, wc, grep
|
|
18
|
+
# CRLF-safe: no line continuations, Git Bash + Linux compatible.
|
|
19
|
+
|
|
20
|
+
set -e
|
|
21
|
+
|
|
22
|
+
show_error() {
|
|
23
|
+
printf 'Error: %s\n' "$1" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
show_usage() {
|
|
28
|
+
cat <<'EOF'
|
|
29
|
+
Usage:
|
|
30
|
+
bash tools/agent-forensics.sh <commit> Forensics at <commit>
|
|
31
|
+
bash tools/agent-forensics.sh --diff <commitA> <commitB> Behavior diff
|
|
32
|
+
EOF
|
|
33
|
+
exit 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# --- Commit header (hash, date, subject) ---
|
|
37
|
+
print_commit_header() {
|
|
38
|
+
local commit="$1"
|
|
39
|
+
|
|
40
|
+
# Verify commit exists
|
|
41
|
+
if ! git cat-file -t "$commit" >/dev/null 2>&1; then
|
|
42
|
+
show_error "unknown commit: $commit"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
local hash
|
|
46
|
+
local date
|
|
47
|
+
local subject
|
|
48
|
+
|
|
49
|
+
hash=$(git rev-parse "$commit" 2>/dev/null || show_error "commit parse failed: $commit")
|
|
50
|
+
date=$(git log -1 --format=%ai "$commit" 2>/dev/null || show_error "failed to read commit date")
|
|
51
|
+
subject=$(git log -1 --format=%s "$commit" 2>/dev/null || show_error "failed to read commit subject")
|
|
52
|
+
|
|
53
|
+
printf '# Forensics @ %s\n\n' "$hash"
|
|
54
|
+
printf 'Commit: %s\n' "$hash"
|
|
55
|
+
printf 'Date: %s\n' "$date"
|
|
56
|
+
printf 'Subject: %s\n\n' "$subject"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# --- Print first ~40 lines of docs/CARDINAL-RULES.md ---
|
|
60
|
+
print_rules() {
|
|
61
|
+
local commit="$1"
|
|
62
|
+
|
|
63
|
+
printf '## Rules (docs/CARDINAL-RULES.md)\n\n'
|
|
64
|
+
|
|
65
|
+
if ! git cat-file -e "$commit:docs/CARDINAL-RULES.md" 2>/dev/null; then
|
|
66
|
+
printf '(file not present at this commit)\n\n'
|
|
67
|
+
return
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
git show "$commit:docs/CARDINAL-RULES.md" 2>/dev/null | head -40
|
|
71
|
+
|
|
72
|
+
# Check if file was truncated
|
|
73
|
+
local total_lines
|
|
74
|
+
total_lines=$(git show "$commit:docs/CARDINAL-RULES.md" 2>/dev/null | wc -l)
|
|
75
|
+
if [ "$total_lines" -gt 40 ]; then
|
|
76
|
+
printf '\n[... %d more lines; see git show %s:docs/CARDINAL-RULES.md]\n' "$((total_lines - 40))" "$commit"
|
|
77
|
+
fi
|
|
78
|
+
printf '\n\n'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# --- Print CLAUDE.md if present ---
|
|
82
|
+
print_claude_md() {
|
|
83
|
+
local commit="$1"
|
|
84
|
+
|
|
85
|
+
printf '## CLAUDE.md\n\n'
|
|
86
|
+
|
|
87
|
+
if ! git cat-file -e "$commit:CLAUDE.md" 2>/dev/null; then
|
|
88
|
+
printf '(not present at this commit)\n\n'
|
|
89
|
+
return
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
git show "$commit:CLAUDE.md" 2>/dev/null
|
|
93
|
+
printf '\n\n'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# --- Print STATE.md if present ---
|
|
97
|
+
print_state_md() {
|
|
98
|
+
local commit="$1"
|
|
99
|
+
|
|
100
|
+
printf '## STATE.md\n\n'
|
|
101
|
+
|
|
102
|
+
if ! git cat-file -e "$commit:STATE.md" 2>/dev/null; then
|
|
103
|
+
printf '(not present at this commit)\n\n'
|
|
104
|
+
return
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
git show "$commit:STATE.md" 2>/dev/null
|
|
108
|
+
printf '\n\n'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# --- Print last 30 lines of BUILDLOG.md ---
|
|
112
|
+
print_buildlog() {
|
|
113
|
+
local commit="$1"
|
|
114
|
+
|
|
115
|
+
printf '## BUILDLOG.md (last 30 lines)\n\n'
|
|
116
|
+
|
|
117
|
+
if ! git cat-file -e "$commit:BUILDLOG.md" 2>/dev/null; then
|
|
118
|
+
printf '(not tracked at this commit)\n\n'
|
|
119
|
+
return
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
git show "$commit:BUILDLOG.md" 2>/dev/null | tail -30
|
|
123
|
+
printf '\n\n'
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# --- Diff behavior between two commits ---
|
|
127
|
+
diff_behavior() {
|
|
128
|
+
local commitA="$1"
|
|
129
|
+
local commitB="$2"
|
|
130
|
+
|
|
131
|
+
# Verify both commits exist
|
|
132
|
+
if ! git cat-file -t "$commitA" >/dev/null 2>&1; then
|
|
133
|
+
show_error "unknown commit: $commitA"
|
|
134
|
+
fi
|
|
135
|
+
if ! git cat-file -t "$commitB" >/dev/null 2>&1; then
|
|
136
|
+
show_error "unknown commit: $commitB"
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
printf '# Behavior Diff\n\n'
|
|
140
|
+
printf '%s -> %s\n\n' "$(git rev-parse "$commitA")" "$(git rev-parse "$commitB")"
|
|
141
|
+
|
|
142
|
+
# Files that affect behavior: rules, CLAUDE.md, STATE.md, hooks, monitor/CHARTER.md
|
|
143
|
+
local behavior_files
|
|
144
|
+
behavior_files="CLAUDE.md STATE.md docs/CARDINAL-RULES.md docs/DISPATCH-MODEL.md docs/GOVERNANCE.md hooks/ monitor/CHARTER.md"
|
|
145
|
+
|
|
146
|
+
printf '## Changes in behavior-controlling files\n\n'
|
|
147
|
+
|
|
148
|
+
local has_changes=0
|
|
149
|
+
for file in $behavior_files; do
|
|
150
|
+
if git diff "$commitA" "$commitB" --name-only -- "$file" 2>/dev/null | grep -q .; then
|
|
151
|
+
has_changes=1
|
|
152
|
+
printf '### %s\n\n' "$file"
|
|
153
|
+
git diff "$commitA" "$commitB" -- "$file" 2>/dev/null | head -100
|
|
154
|
+
printf '\n'
|
|
155
|
+
fi
|
|
156
|
+
done
|
|
157
|
+
|
|
158
|
+
if [ "$has_changes" -eq 0 ]; then
|
|
159
|
+
printf '(no changes in behavior-controlling files)\n\n'
|
|
160
|
+
fi
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# --- Main dispatch ---
|
|
164
|
+
case "${1:-}" in
|
|
165
|
+
--diff)
|
|
166
|
+
if [ $# -lt 3 ]; then
|
|
167
|
+
show_error "--diff requires two commits: --diff <commitA> <commitB>"
|
|
168
|
+
fi
|
|
169
|
+
diff_behavior "$2" "$3"
|
|
170
|
+
;;
|
|
171
|
+
--help|-h)
|
|
172
|
+
show_usage
|
|
173
|
+
;;
|
|
174
|
+
'')
|
|
175
|
+
show_usage
|
|
176
|
+
;;
|
|
177
|
+
*)
|
|
178
|
+
print_commit_header "$1"
|
|
179
|
+
print_rules "$1"
|
|
180
|
+
print_claude_md "$1"
|
|
181
|
+
print_state_md "$1"
|
|
182
|
+
print_buildlog "$1"
|
|
183
|
+
;;
|
|
184
|
+
esac
|
|
185
|
+
|
|
186
|
+
exit 0
|