@matt82198/aesop 0.1.0-rc.1 → 0.2.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.
Files changed (114) hide show
  1. package/CHANGELOG.md +101 -2
  2. package/README.md +66 -15
  3. package/bin/cli.js +164 -41
  4. package/daemons/run-watchdog.sh +16 -4
  5. package/daemons/selfheal.sh +231 -0
  6. package/docs/ANY-REPO.md +427 -0
  7. package/docs/CONTRIBUTING.md +72 -0
  8. package/docs/DEMO.md +334 -0
  9. package/docs/INSTALL.md +70 -1
  10. package/docs/QUICKSTART.md +80 -0
  11. package/docs/README.md +33 -3
  12. package/docs/TEAM-STATE.md +540 -0
  13. package/driver/CLAUDE.md +148 -0
  14. package/driver/README.md +383 -0
  15. package/driver/aesop.config.example.json +80 -0
  16. package/driver/agent_driver.py +355 -0
  17. package/driver/backend_config.py +253 -0
  18. package/driver/claude_code_driver.py +198 -0
  19. package/driver/codex_driver.py +626 -0
  20. package/driver/openai_compatible_driver.py +249 -0
  21. package/driver/openai_transport.py +146 -0
  22. package/driver/verification_policy.py +75 -0
  23. package/driver/wave_bridge.py +246 -0
  24. package/driver/wave_loop.py +1041 -0
  25. package/hooks/pre-push-policy.sh +109 -28
  26. package/mcp/server.mjs +320 -4
  27. package/package.json +23 -15
  28. package/skills/CLAUDE.md +132 -2
  29. package/skills/buildsystem/SKILL.md +330 -0
  30. package/skills/buildsystem/wave-flat-dispatch.template.mjs +658 -0
  31. package/skills/fleet/SKILL.md +113 -0
  32. package/skills/power/SKILL.md +246 -131
  33. package/state_store/__init__.py +2 -1
  34. package/state_store/api.py +19 -4
  35. package/state_store/coordination.py +209 -0
  36. package/state_store/identity.py +51 -0
  37. package/state_store/store.py +185 -73
  38. package/templates/wave-presets/data.json +65 -0
  39. package/templates/wave-presets/library.json +65 -0
  40. package/templates/wave-presets/saas.json +64 -0
  41. package/tools/audit_report.py +388 -0
  42. package/tools/bench_runner.py +100 -3
  43. package/tools/ci_merge_wait.py +256 -35
  44. package/tools/ci_workflow_lint.py +430 -0
  45. package/tools/claudemd_drift.py +394 -0
  46. package/tools/claudemd_lint.py +359 -0
  47. package/tools/common.py +39 -3
  48. package/tools/cost_ceiling.py +63 -31
  49. package/tools/cost_econ.py +480 -0
  50. package/tools/defect_escape.py +252 -0
  51. package/tools/doctor.js +1 -1
  52. package/tools/eod_sweep.py +58 -0
  53. package/tools/fleet.js +260 -0
  54. package/tools/fleet_ledger.py +209 -7
  55. package/tools/git_identity_check.py +315 -0
  56. package/tools/health-score.js +40 -0
  57. package/tools/health_score.py +361 -0
  58. package/tools/metrics_gate.py +13 -4
  59. package/tools/mutation_test.py +401 -0
  60. package/tools/portability_check.py +206 -0
  61. package/tools/reconcile.py +7 -4
  62. package/tools/secret_scan.py +137 -50
  63. package/tools/self_stats.py +20 -0
  64. package/tools/transcript_digest.py +380 -0
  65. package/tools/verify_activity_filter.py +437 -0
  66. package/tools/verify_agent_inspector.py +2 -0
  67. package/tools/verify_dash.py +2 -0
  68. package/tools/verify_dispatch_panel.py +301 -0
  69. package/tools/verify_failure_drilldown.py +188 -0
  70. package/tools/verify_prboard.py +2 -0
  71. package/tools/verify_scorecards.py +281 -0
  72. package/tools/verify_submit_encoding.py +2 -0
  73. package/tools/verify_ui_trio.py +409 -0
  74. package/tools/verify_wave_telemetry.py +268 -0
  75. package/tools/wave_backlog_analyzer.py +490 -0
  76. package/tools/wave_ledger_hook.py +150 -0
  77. package/tools/wave_preflight.py +512 -0
  78. package/tools/wave_resume.py +215 -0
  79. package/tools/wave_templates.py +215 -0
  80. package/ui/agents.py +68 -14
  81. package/ui/collectors.py +0 -55
  82. package/ui/config.py +7 -2
  83. package/ui/cost.py +231 -12
  84. package/ui/handler.py +183 -0
  85. package/ui/quality_scorecard.py +232 -0
  86. package/ui/wave_audit_tail.py +213 -0
  87. package/ui/wave_dispatch.py +280 -0
  88. package/ui/wave_failure.py +288 -0
  89. package/ui/wave_gantt.py +152 -0
  90. package/ui/wave_reasoning_tail.py +176 -0
  91. package/ui/wave_telemetry.py +377 -0
  92. package/ui/web/dist/assets/index-BGbgw2Nh.js +9 -0
  93. package/ui/web/dist/assets/index-DqZLgwNg.css +1 -0
  94. package/ui/web/dist/index.html +2 -2
  95. package/bin/CLAUDE.md +0 -76
  96. package/daemons/CLAUDE.md +0 -36
  97. package/dash/CLAUDE.md +0 -32
  98. package/docs/archive/README.md +0 -3
  99. package/docs/archive/spikes/tiered-cognition/ACTIVATION.md +0 -125
  100. package/docs/archive/spikes/tiered-cognition/DESIGN.md +0 -287
  101. package/docs/archive/spikes/tiered-cognition/FINDINGS.md +0 -113
  102. package/docs/archive/spikes/tiered-cognition/README.md +0 -27
  103. package/docs/archive/spikes/tiered-cognition/aesop-cognition.example.md +0 -32
  104. package/docs/archive/spikes/tiered-cognition/force-model-policy.merged.mjs +0 -673
  105. package/docs/archive/spikes/tiered-cognition/strip-tools-hook.mjs +0 -434
  106. package/hooks/CLAUDE.md +0 -89
  107. package/mcp/CLAUDE.md +0 -213
  108. package/monitor/CLAUDE.md +0 -40
  109. package/scan/CLAUDE.md +0 -30
  110. package/state_store/CLAUDE.md +0 -39
  111. package/tools/CLAUDE.md +0 -79
  112. package/ui/CLAUDE.md +0 -127
  113. package/ui/web/dist/assets/index-0qQYnvMC.js +0 -9
  114. package/ui/web/dist/assets/index-BdIlFieV.css +0 -1
package/mcp/CLAUDE.md DELETED
@@ -1,213 +0,0 @@
1
- # mcp/ — Read-Only Fleet State MCP Server
2
-
3
- **Purpose**: Expose Aesop fleet operational status as a Model Context Protocol (MCP) server for Claude Code and other MCP clients.
4
-
5
- ## Server
6
-
7
- **File**: `server.mjs` — Stdio-transport MCP server implementing the JSON-RPC 2.0 protocol.
8
-
9
- **Configuration**:
10
- - `AESOP_ROOT` environment variable or `--root <path>` command-line flag (no hardcoded paths)
11
- - `AESOP_STATE_ROOT` environment variable for state directory override (default: `AESOP_ROOT/state`)
12
- - `AESOP_TRANSCRIPTS_ROOT` environment variable for transcript directory override (default: `~/.claude/projects`)
13
- - Reads `aesop.config.json` from AESOP_ROOT for optional config file overrides
14
-
15
- **Launch**:
16
- ```bash
17
- node mcp/server.mjs [--root /path/to/aesop]
18
- ```
19
-
20
- The server reads from stdin, writes JSON-RPC responses to stdout. Suitable for direct MCP integration or debugging via manual JSON-RPC frames.
21
-
22
- ## Tools
23
-
24
- All tools are **strictly read-only**: no state mutations, no file writes, no shell-outs except the minimal dash-extra.mjs passthrough for fleet_agents.
25
-
26
- ### fleet_status
27
-
28
- **Description**: Get fleet operational status — daemon heartbeats, monitor heartbeat, orchestrator activity, alert count.
29
-
30
- **Input**: No arguments.
31
-
32
- **Output** (JSON):
33
- ```json
34
- {
35
- "watchdog": {
36
- "alive": "ALIVE" | "STALE",
37
- "age_seconds": <bucketed>,
38
- "threshold_seconds": 300
39
- },
40
- "monitor": {
41
- "alive": "ALIVE" | "STALE",
42
- "age_seconds": <bucketed>,
43
- "threshold_seconds": 3600
44
- },
45
- "orchestrator": <parsed orchestrator-status.json> or null,
46
- "alerts": {
47
- "count": <int>,
48
- "sample_lines": [<last 3 unreviewed lines>]
49
- }
50
- }
51
- ```
52
-
53
- **Missing files**: If a file doesn't exist, the corresponding field is `null`. The response never crashes; missing state files are reported gracefully with `absent` markers where applicable.
54
-
55
- ### fleet_agents
56
-
57
- **Description**: List active Claude agents from transcript directory. Invokes `dash-extra.mjs --json` with environment variables for path portability.
58
-
59
- **Input**: No arguments.
60
-
61
- **Output** (JSON):
62
- ```json
63
- {
64
- "absent": <bool>,
65
- "agents": [
66
- {
67
- "project": <string>,
68
- "taskLabel": <string>,
69
- "promptFull": <string>,
70
- "runtimeSeconds": <int>,
71
- "tokensUsed": <int>,
72
- "startedAt": <ISO string>,
73
- "lastActivity": <ISO string>
74
- },
75
- ...
76
- ]
77
- }
78
- ```
79
-
80
- **Design notes**:
81
- - Passes through `dash-extra.mjs --json` output (no reimplementation).
82
- - Timeout: 5 seconds. On timeout or error, returns empty agents list.
83
- - Environment variables (`AESOP_ROOT`, `AESOP_STATE_ROOT`, `AESOP_TRANSCRIPTS_ROOT`) are set before invoking dash-extra to ensure portable transcript discovery.
84
-
85
- ### fleet_tracker
86
-
87
- **Description**: Get fleet work items from `tracker.json`, grouped by lane.
88
-
89
- **Input**: No arguments.
90
-
91
- **Output** (JSON):
92
- ```json
93
- {
94
- "absent": <bool>,
95
- "by_lane": {
96
- "ranked": [
97
- {
98
- "id": <string>,
99
- "title": <string>,
100
- "priority": "P0" | "P1" | "P2",
101
- "status": "todo" | "in-progress" | "done",
102
- "tags": [<string>, ...]
103
- },
104
- ...
105
- ],
106
- "in-progress": [ ... ],
107
- "done": [ ... ],
108
- ...
109
- }
110
- }
111
- ```
112
-
113
- **Lanes**: Each lane is optional and present only if items exist in it. Common lanes: `ranked`, `proposed`, `in-progress`, `done`.
114
-
115
- ### fleet_cost
116
-
117
- **Description**: Get per-model token usage totals from the fleet outcomes ledger (`state/ledger/OUTCOMES-LEDGER.md`).
118
-
119
- **Input**: No arguments.
120
-
121
- **Output** (JSON):
122
- ```json
123
- {
124
- "absent": <bool>,
125
- "by_model": {
126
- "claude-haiku-4": {
127
- "tokens_in": <int>,
128
- "tokens_out": <int>,
129
- "total_tokens": <int>,
130
- "count": <int>
131
- },
132
- "claude-opus": { ... },
133
- ...
134
- },
135
- "total_tokens_in": <int>,
136
- "total_tokens_out": <int>
137
- }
138
- ```
139
-
140
- **Design notes**:
141
- - Parses markdown table rows from the ledger file.
142
- - Reports **token counts only** — no invented dollar figures.
143
- - Gracefully handles missing or malformed lines (skips with no error).
144
-
145
- ## MCP Integration
146
-
147
- ### Claude Code (claude.json)
148
-
149
- To register the server in Claude Code:
150
-
151
- ```json
152
- {
153
- "mcp": {
154
- "aesop-fleet": {
155
- "command": "node",
156
- "args": [
157
- "/path/to/aesop/mcp/server.mjs",
158
- "--root",
159
- "/path/to/aesop"
160
- ],
161
- "env": {
162
- "AESOP_ROOT": "/path/to/aesop"
163
- }
164
- }
165
- }
166
- }
167
- ```
168
-
169
- ### Portable Setup
170
-
171
- For portable configuration (no hardcoded user paths), use environment variables:
172
-
173
- ```bash
174
- export AESOP_ROOT=/path/to/aesop
175
- export AESOP_STATE_ROOT=/path/to/state # optional override
176
- export AESOP_TRANSCRIPTS_ROOT=/path/to/transcripts # optional override
177
- node /path/to/aesop/mcp/server.mjs
178
- ```
179
-
180
- Or use the `--root` flag:
181
-
182
- ```bash
183
- node /path/to/aesop/mcp/server.mjs --root /path/to/aesop
184
- ```
185
-
186
- ## Invariants & Guarantees
187
-
188
- 1. **Strictly read-only**: All tools are read-only. Zero state mutations, zero file writes.
189
- 2. **No shell execution** (except dash-extra.mjs invocation with timeout): Minimizes surface area for injection or performance issues.
190
- 3. **Graceful degradation**: Missing state files → empty results with `absent: true` markers, never a crash.
191
- 4. **No external dependencies**: Plain Node.js (no npm packages). Server.mjs uses only stdlib modules: `fs`, `path`, `os`, `child_process`, `readline`.
192
- 5. **JSON-RPC 2.0 compliant**: Standard request/response format with proper error codes.
193
- 6. **Portable paths**: All paths resolved from AESOP_ROOT, AESOP_STATE_ROOT, AESOP_TRANSCRIPTS_ROOT env vars or config file. No hardcoded paths like `/home/user/aesop`.
194
-
195
- ## Testing
196
-
197
- See `tests/mcp-fleet.test.mjs` for end-to-end tests:
198
- - Spawn server over stdio
199
- - Drive JSON-RPC initialize + tools/list + tools/call round-trips
200
- - Validate read-only behavior (no state mutations after calls)
201
- - Verify fixture isolation (temp state root)
202
-
203
- Run:
204
- ```bash
205
- npm test # or
206
- node tests/mcp-fleet.test.mjs
207
- ```
208
-
209
- ## Files
210
-
211
- - **server.mjs** — Main MCP server implementation (stdio, JSON-RPC 2.0)
212
- - **package.json** — Minimal metadata; no external dependencies
213
- - **CLAUDE.md** — This file
package/monitor/CLAUDE.md DELETED
@@ -1,40 +0,0 @@
1
- # monitor/ — Orchestration monitor
2
-
3
- **Purpose**: Continuous background signal collector and refinement proposer — watches fleet machinery health deterministically (Node.js, no LLM), emits cycle snapshots, and proposes rule changes via append-only PROPOSALS.md; GOAL IS FIXED (improve machinery, never mission).
4
-
5
- ## Files
6
-
7
- - **CHARTER.md** — Governance document; defines 11 signal checks (4 of them extended/opt-in), action tiers (AUTO/PROPOSE), outputs, single-instance guard, single-writer discipline. Read-only; behavior changes only via PROPOSALS/behavioral-PR flow.
8
- - **collect-signals.mjs** — Deterministic signal collector (Node.js built-ins only); emits BRIEF.md + SIGNALS.json each cycle; reads config from env or aesop.config.json.
9
- - **BRIEF.md** — Human-readable cycle snapshot (heartbeats, git state, memory freshness, log rotation, junk sprawl, stray scripts, security alerts, respawn watch, cost cadence, unreviewed prompts); overwritten each cycle; runtime.
10
- - **SIGNALS.json** — Machine-readable metrics (same signal keys as BRIEF); JSON; overwritten each cycle; runtime.
11
- - **PROPOSALS.md** — Structured inbox for user approval (idempotent per signal key, append-only); never edited by monitor after emission; tracks isolation-violation-detected, respawn-watch-breach, stray-repo-scripts, security-alerts-high-med, stale-memory-files; gitignored.
12
- - **ACTIONS.log** — Append-only log of AUTO tier actions taken (heartbeat updates, log rotation invokes, junk quarantine); runtime; gitignored.
13
- - **.monitor-heartbeat** — Epoch timestamp (line 1) for single-instance liveness check (<300s = skip cycle); runtime; gitignored.
14
- - **.signal-state.json** — Sidecar state (cycleCount, etc.); runtime; gitignored.
15
-
16
- ## Contracts
17
-
18
- **Signal keys collected**: heartbeats, git, memory, logs, junk, strayRepo, alerts, respawnWatch, costTick, unreviewedPrompts, isolationViolations. Outputs: BRIEF.md (human), SIGNALS.json (machine); both runtime/gitignored. PROPOSALS.md (tracked, append-only); ACTIONS.log (runtime/gitignored).
19
-
20
- **Extended signals (opt-in, default OFF)**: checks 5 (junk), 6 (strayRepo), 8 (respawnWatch), and 10 (unreviewedPrompts) are extended — disabled by default.
21
- - Config key: `monitor.extended_signals` (boolean, default `false`) in aesop.config.json.
22
- - Env override: `AESOP_EXTENDED_SIGNALS` (`'true'` or `'1'` to enable).
23
- - Precedence: env var > config file > default (false).
24
- - When disabled, extended checks emit `{"skipped": true}` in SIGNALS.json and BRIEF.md notes them as "extended (off)"; their dirs are not walked. PROPOSE-tier signals for extended checks (respawn-watch-breach, stray-repo-scripts) are only emitted when extended_signals is ON.
25
-
26
- **AUTO tier actions** (apply immediately, log to ACTIONS.log): heartbeat checks (read-only); log rotation (invoke rotate_logs.py if available, fail-open); heartbeat write (.monitor-heartbeat update); junk script quarantine (move old temp .py/.mjs to monitor/quarantine/ + manifest — only when extended_signals is ON).
27
-
28
- **PROPOSE tier actions** (write to PROPOSALS.md, await user approval): rule changes, agent config changes, deletions outside monitor/quarantine/, orchestration policy changes.
29
-
30
- **Idempotency rule**: Proposal emission keyed on signal key (e.g., 'respawn-watch-breach'); only emitted if not already present in PROPOSALS.md (check: `**Signal:** <key>` exists). Idempotent + append-only; safe to run repeatedly.
31
-
32
- ## Invariants & Gotchas
33
-
34
- - CHARTER.md is authority; monitor logic is faithful to it; never edit CHARTER.md or collect-signals.mjs directly — propose via PROPOSALS.md.
35
- - Goal is FIXED (improve machinery, never mission); if monitor thinks goal should change, it writes note to PROPOSALS.md and stops.
36
- - Single-instance guard via .monitor-heartbeat; if <300s old, skip cycle.
37
- - Single-writer discipline: only monitor writes BRIEF.md, SIGNALS.json, ACTIONS.log, .monitor-heartbeat, .signal-state.json.
38
- - Config from env (AESOP_ROOT, BRAIN_ROOT, SCRIPTS_ROOT, TEMP_ROOT, AESOP_EXTENDED_SIGNALS) or aesop.config.json; falls back to safe defaults.
39
- - Robust to missing files; treats missing dirs/logs as empty, never crashes.
40
- - Node.js only (no Python, no LLM, no cloud); deterministic + cheap.
package/scan/CLAUDE.md DELETED
@@ -1,30 +0,0 @@
1
- # scan/ — Example IOC/secret scanner template
2
-
3
- The `fleet-scan.example.mjs` is a config-driven template for scanning committed code and fleet transcripts for security and alignment red-flags during each watchdog cycle.
4
-
5
- ## Setup
6
-
7
- 1. Copy `fleet-scan.example.mjs` → `fleet-scan.mjs` (in the same directory)
8
- 2. Edit the configuration section to match your fleet setup (REPOS array, PROJECT_ROOTS paths)
9
- 3. Configure paths via `aesop.config.json` or environment variables:
10
- - `AESOP_FLEET_ROOT`: root directory containing your project repositories
11
- - `AESOP_TRANSCRIPTS_ROOT`: root directory containing `~/.claude/projects` transcripts
12
- 4. Ensure `aesop.config.json` contains repo definitions (see `aesop.config.example.json`)
13
-
14
- ## How it works
15
-
16
- When enabled (by copying to `fleet-scan.mjs`), the scanner:
17
- - Runs every watchdog cycle (default: every 150s)
18
- - Scans git repositories and transcript directories for patterns matching your configured IOC/secret rules
19
- - Logs findings to `SECURITY-ALERTS.log` with severity (HIGH/MED/LOW)
20
- - Never blocks the fleet (non-fatal, append-only)
21
- - Marks reviewed findings with `NOTE:` or `RESOLVED-FP` prefix to skip future alerts
22
-
23
- ## Customization
24
-
25
- Edit the `fleet-scan.mjs` rules section to add domain-specific checks:
26
- - Pattern-based secret/credential detection
27
- - Alignment violations (e.g., non-approved libraries, unclosed TODO markers)
28
- - Transcript red-flags (e.g., cost anomalies, stalled agents)
29
-
30
- Output findings via the `add(severity, kind, where, detail)` function; the scanner handles deduplication and logging.
@@ -1,39 +0,0 @@
1
- # state_store/ — event-sourced state layer (DB source of truth, git as export)
2
-
3
- **Purpose**: the durable substrate to move aesop's coordination/state off git —
4
- which cannot scale to a team (single-writer control files, hot-file merge
5
- conflicts, no transactions/concurrency/real-time). State becomes an append-only
6
- event log with per-stream versioning; current state is a projection; git is
7
- demoted to a rendered, diffable **export**.
8
-
9
- **Status (2026-07-14)**: additive prototype. The live `ui/` tracker path
10
- (`collectors.py` → `state/tracker.json`) is UNCHANGED. This package ships the
11
- store + tracker projection + backfill + export, ready for a later dual-read
12
- cutover. Design: `conductor3 plans/aesop-scaling-rearchitecture.md`.
13
-
14
- ## Files (stdlib only — sqlite3/json/threading/time)
15
- - **store.py** — `EventStore(db_path)`: append-only SQLite (WAL) log.
16
- Concurrency-safe across threads and processes via `busy_timeout` +
17
- `BEGIN IMMEDIATE` (atomic read-max-version-then-insert). `append/read/read_all`.
18
- - **projections.py** — `project_tracker(events)`: folds `item_created` /
19
- `item_updated` / `item_archived` into the full `tracker.json` shape,
20
- preserving first-seen order.
21
- - **api.py** — `StateAPI(db_path)`: the swap seam (`append`/`get`/`project`).
22
- Backend swaps SQLite→Postgres here without touching callers.
23
- - **export.py** — `export_tracker(api, out_path)`: render the projection back to
24
- a git-tracked JSON snapshot (indent=2, ascii-escaped to match the live file).
25
- - **ingest.py** — `ingest_tracker_json(api, path)`: backfill one `item_created`
26
- per existing item (the migration/backfill path).
27
-
28
- ## Invariants
29
- - **Append-only**: never mutate/delete events; state changes are new events.
30
- - **Per-stream version is 1-based and gapless** (enforced atomically).
31
- - **git as export, not source**: nothing here reads git for state.
32
- - **Round-trip fidelity**: ingest → project → export reproduces the same items
33
- (tested against the real `state/tracker.json`).
34
-
35
- ## Next (cutover, follow-up — NOT this increment)
36
- Point tracker CRUD at `StateAPI` (create→`item_created`, update→`item_updated`,
37
- move→lane update, delete→`item_archived`); add a `subscribe()` real-time path to
38
- replace the SSE file-watch; run the `export_tracker` job to keep `tracker.json`
39
- rendered during dual-read; then flip readers to the API.
package/tools/CLAUDE.md DELETED
@@ -1,79 +0,0 @@
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
- - `alert_bridge.py` — Slack/Discord webhook bridge for SECURITY-ALERTS
8
- - `bench_runner.py` — Held-out benchmark runner + scorer with accuracy + cost axis (offline mock runner; pluggable Haiku/Sonnet/Opus runners return text or (text, usage))
9
- - `buildlog.py` — Uniform BUILDLOG.md appender
10
- - `ci_merge_wait.py` — CI-gated merge helper (polls gh pr view until SUCCESS)
11
- - `common.py` — Shared utilities (state directory resolution, heartbeat staleness checks)
12
- - `cost_ceiling.py` — Cost-ceiling checker: trips the HALT kill-switch when configured token limits are exceeded
13
- - `ensure_state.py` — Scaffold STATE.md and BUILDLOG.md templates
14
- - `eod_sweep.py` — End-of-day safety check (dirty trees, unpushed commits)
15
- - `fleet_ledger.py` — Append-only cost ledger with harvest/rotate
16
- - `fleet_prompt_extractor.py` — Extract and deduplicate Agent/Task spawn prompts
17
- - `halt.py` — Kill-switch: writes/reads/clears the `.HALT` sentinel that daemons/dispatch check to stop the fleet
18
- - `healthcheck.py` — Fleet health aggregator (heartbeat/alert/orchestrator status)
19
- - `heartbeat.py` — Single-instance loop liveness registry
20
- - `inbox_drain.py` — Drain UI inbox submissions
21
- - `launch_tui.py` — Spawn bash TUI script in detached terminal
22
- - `lock.mjs` — Fail-closed atomic lock acquisition (exponential backoff + stale-lock detection)
23
- - `metrics_gate.py` — PR gate for hard numeric claims in markdown
24
- - `orchestrator_status.py` — Atomic orchestrator status updates
25
- - `power_selftest.py` — Health check harness for /power bootstrap
26
- - `prepublish_scan.py` — Pre-publish full history + staged-changes scan gate
27
- - `proposals.mjs` — Proposal lifecycle manager (list/accept/reject)
28
- - `reconcile.py` — Detect/resolve drift between git STATE.md (phase) and the state_store projection (git-authoritative; --resolve appends to SQLite only)
29
- - `reconstitute.sh` — Clone/fetch repos from config with security validation
30
- - `rotate_logs.py` — Log rotation utility with size/line thresholds
31
- - `scanner_selftest.py` — Regression harness for secret_scan.py
32
- - `secret_scan.py` — Pre-push secret/credential detection gate
33
- - `self_stats.py` — Git-derived metrics counter + README block generator
34
- - `session_usage_summary.py` — Aggregate token usage across session transcripts
35
- - `stall_check.py` — Automated agent transcript stall detector
36
- - `svg_to_png.mjs` — Rasterize SVG to PNG via @resvg/resvg-js (with lazy import error handling)
37
- - `transcript_replay.py` — Replay post-commit edits from transcripts to recover work
38
- - `transcript_timeline.py` — Extract Write/Edit/Read timeline from transcripts
39
- - `verify_dash.py` — Browser proof for realtime SSE dashboard
40
- - `verify_submit_encoding.py` — Browser proof for /submit UTF-8 inbox bootstrap
41
- - `verify_prboard.py` — Browser proof for the Wave PR Board (/api/wave/prs), gh stubbed via AESOP_GH_BIN
42
- - `verify_agent_inspector.py` — Browser proof for the Agent Inspector drawer (/api/agent?id=), agents + transcript stubbed via a temp AESOP_ROOT
43
- - `agent-forensics.sh` — Incident forensics / behavior reconstruction
44
-
45
- ## secret_scan.py — Pre-push secret/credential detection gate
46
-
47
- Scans staged/history/paths for secrets by regex pattern and credential filenames; blocks pushes on findings.
48
-
49
- - `secret_scan.py --staged [--repo PATH]` — scan git staged files
50
- - `secret_scan.py --history [--repo PATH]` — scan all git history blobs
51
- - `secret_scan.py PATH [PATH...]` — scan files/dirs directly (recurse)
52
-
53
- Exit: 0=clean, 1=findings, 2=usage error. Output masks secrets as `xxxx...`.
54
-
55
- **Pragma escape** (pattern findings only; credential filenames always fatal):
56
- ```
57
- # secretscan: allow-pattern-docs
58
- ```
59
- Mark file's first 10 lines to report rule-based findings as ALLOWED-DOC (non-fatal). Use only for deliberate pattern documentation.
60
-
61
- ## agent-forensics.sh — Incident forensics / behavior reconstruction
62
-
63
- Read-only git plumbing; reconstructs agent behavior snapshot or diffs behavior-controlling files.
64
-
65
- - `bash tools/agent-forensics.sh <commit>` — print commit header, rules snapshot, CLAUDE.md, STATE.md, last 30 lines of BUILDLOG.md
66
- - `bash tools/agent-forensics.sh --diff <commitA> <commitB>` — diff CLAUDE.md, STATE.md, docs/, monitor/CHARTER.md, hooks/ between commits
67
-
68
- Exit: 0=success, 1=error (never raw git traces). Requires: git, head, tail, wc, grep.
69
-
70
-
71
- ## Invariants
72
-
73
- - **Dependency-light**: Python tools must work on base Python 3 (no pip installs).
74
- - **CRLF-safe shell**: no line continuations in .sh scripts; Git Bash + Linux compatible.
75
- - **Never print secrets**: mask as pattern name + masked value only.
76
- - **Config-driven paths**: heartbeat/ledger/logs use AESOP_STATE_ROOT env var (default ./state) or CLI args; no hardcoded personal paths.
77
- - **Fragment-assembled secrets in tests**: scanner_selftest.py concatenates dummy secrets at runtime so pattern text never appears contiguously (self-scan invariant).
78
- - **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).
79
- - **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.
package/ui/CLAUDE.md DELETED
@@ -1,127 +0,0 @@
1
- # ui/ — Web dashboard
2
-
3
- **Purpose**: Stdlib-only local observability dashboard. Serves a dark-theme HTML dashboard on a configurable port (realtime via Server-Sent Events), enabling real-time fleet monitoring without external dependencies.
4
-
5
- ## Files (wave-14 U9 cutover: new React+Vite app structure)
6
-
7
- **Backend (Python, unchanged from wave-9 split)**:
8
- - **serve.py** — Thin (~65-line) entry point + composition layer. Wires the sibling modules, calls `config.reload()` / `csrf.init()` / `sse.reset_state()`, and re-exports their symbols so `serve.X` keeps resolving for the test suite (which loads serve.py by path) and for `python ui/serve.py`.
9
- - **config.py** — Path / env / `aesop.config.json` resolution. `reload()` recomputes all path globals from the current environment. **Load-bearing rule: every other module reads `config.X` at call time (`import config`), never `from config import <path>` — a frozen import would go stale after reload() (breaks test-fixture isolation).** Added in wave-14: `WEB_DIST` path pointing to `ui/web/dist/`.
10
- - **csrf.py** — Session-token generation (atomic O_EXCL 0600) + `validate_csrf_request()`; `init()` sets `SESSION_TOKEN`.
11
- - **collectors.py** — Read-only data collectors (heartbeats, repos, events, alerts, messages, backlog parse), tracker CRUD, and SSE section snapshots (incl. `read`-style tracker/orchestrator-status snapshots + inbox drain).
12
- - **agents.py** — Agent transcript reading (`get_fleet_agents`, `extract_agent_dispatch_prompt`) and path-traversal-safe agent-id handling (`_AGENT_ID_FORBIDDEN`).
13
- - **sse.py** — SSE client registry, bounded broadcast, hash-gated `_maybe_emit`, and the background `collector_loop`. `reset_state()` restores per-import collector isolation (the `sse` module is cached across test re-imports). Extended in wave-14: emits "cost" as a 6th SSE section.
14
- - **render.py** — Renders `ui/web/dist/index.html` (wave-14 U9 cutover: no fallback), substituting the CSRF token via a unique sentinel (the sentinel persists through Vite's build). Requires `template_path` parameter; legacy fallback removed.
15
- - **handler.py** — `DashboardHandler` (HTTP routing + all GET/POST endpoints incl. /api/tracker) and `run_server()`. Wave-14 additions: static serving (`GET /assets/*` from `ui/web/dist/assets/`), `/api/state` consolidated snapshot, `/api/session` for Vite dev server, `/api/cost` scorecard. Reads `config.X` / `csrf.SESSION_TOKEN` at call time.
16
- - **cost.py** — Parser for `state/ledger/OUTCOMES-LEDGER.md` (markdown table); returns per-model and per-day aggregates, verdict scorecards, and optional dollar estimates (only if `aesop.config.json` supplies `pricing` map).
17
- - **wave_prs.py** — Wave PR board collector: `get_wave_prs()` gathers open PRs (`gh pr list`) + PR-less `feat/*` branches (`git for-each-ref`), rolls check runs into passing/failing/pending/none, derives the top blocker, caches ~5s. Read-only; subprocess reads use `encoding='utf-8', errors='replace'`; degrades to `{available:false, error}` when gh is missing/un-authed. `AESOP_GH_BIN` overrides the gh binary.
18
-
19
- ## State Store Integration (Wave-15)
20
-
21
- The tracker uses a **dual-path mutation model** backed by an event-sourced SQLite WAL store:
22
-
23
- - **Write path**: Mutations append events to `tracker_events.db` (WAL mode) via the StateAPI layer (`state_store/api.py`). Each mutation (create/update/delete) produces an immutable event in the event log.
24
- - **Read path**: `tracker.json` is re-rendered as an export (projection) from the event log on every read. This export is committed to git for checkpoint durability.
25
- - **Location**: `state/tracker_events.db` (SQLite WAL, never committed). `tracker.json` (rendered export, committed).
26
- - **Render-failure recovery**: If projection rendering fails, the UI falls back to the last-known good `tracker.json`; no data loss. Stale renders are repaired on the next successful mutation (idempotent re-render).
27
-
28
- This design provides:
29
- - **Durable event audit trail** (append-only events in WAL)
30
- - **Git-friendly checkpoints** (rendered tracker.json snapshots)
31
- - **Atomic mutations** (events append, projection re-renders)
32
- - **Graceful degradation** (fallback to last-known-good on render failure)
33
-
34
- **Frontend (React 18 + Vite + TypeScript, wave-14 U1–U8)**:
35
- - **ui/web/** — Complete React application (TypeScript, Vite, TypeScript strict mode).
36
- - **src/main.tsx** — Vite entry point; renders `<App />` to `#root`.
37
- - **src/App.tsx** — App shell: header + hash-routed view slots (/#/, /#/work, /#/activity, /#/cost).
38
- - **src/styles/tokens.css** + **src/styles/global.css** — Design tokens (light/dark color palettes, spacing, typography) + base resets.
39
- - **src/views/** — Page-level components (Overview, Work, Activity, Cost) with SSE bindings.
40
- - **src/components/** — Reusable UI components (HealthHeader, AgentsPanel, TrackerBoard, Timeline, CostChart, etc.).
41
- - **src/lib/api.ts** — Typed fetch helpers + CSRF header injection + `/api/session` fallback for dev server.
42
- - **src/lib/useSSE.ts** — EventSource hook with reconnect logic, per-section state, connection status.
43
- - **src/lib/types.ts** — TypeScript types for all API payloads (contract with backend).
44
- - **src/lib/sanitizeUrl.ts** — XSS-safe URL parsing (PR links inert on bad schemes).
45
- - **vite.config.ts** — Vite config with API proxy for `/data`, `/api`, `/events`, `/agent`, `/submit` to :8770.
46
- - **dist/** — Built static files (committed to git; served by Python handler). Filenames are content-hashed by Vite.
47
- - **README.md** — User guide and configuration reference (updated for wave-14).
48
-
49
- ## Configuration & Path Precedence
50
-
51
- Configuration is resolved in this order (first match wins):
52
-
53
- 1. **Environment variables** (highest priority):
54
- - `PORT` — HTTP server port (default: 8770)
55
- - `AESOP_ROOT` — aesop installation root (default: `$HOME/aesop`)
56
- - `AESOP_STATE_ROOT` — state directory (overrides config state_root)
57
- - `AESOP_TRANSCRIPTS_ROOT` — Claude transcript directory (overrides config transcripts_root)
58
- - `AESOP_UI_COLLECT_INTERVAL` — collector thread poll cadence in seconds (default: 1.0)
59
-
60
- 2. **Config file** (`aesop.config.json`):
61
- - `state_root` — path to state/ directory
62
- - `transcripts_root` — path to Claude transcript directory
63
-
64
- 3. **Built-in defaults** (lowest priority):
65
- - `AESOP_ROOT/state` for state directory
66
- - `~/.claude/projects` for transcripts
67
-
68
- ## CSRF & Session Protection
69
-
70
- **Token model**:
71
- - Per-session CSRF token generated at startup, persisted to `state/.ui-session-token` (mode 0600, readable only by owner)
72
- - Token is 43-character URL-safe base64 string (256 bits)
73
- - Persistent across server restarts (read from file if exists, generate fresh if not)
74
- - Used to validate mutations via /submit endpoint
75
-
76
- **Validation on /submit POST**:
77
- 1. **Origin/Referer check**: if present, must be local (http://127.0.0.1:<port>, http://localhost:<port>, or http://[::1]:<port>)
78
- 2. **X-Aesop-Token header check**: must match SESSION_TOKEN exactly
79
- - Both checks fail-closed (missing header = rejection)
80
- - GET /events (SSE, read-only) requires no token
81
-
82
- **Local CLI access**:
83
- - CLI tools read token from `state/.ui-session-token` (0600) to submit to /submit endpoint
84
- - Legitimate browser clients: token injected into HTML template, sent by JavaScript
85
-
86
- ## API Endpoints (wave-14 wave additions)
87
-
88
- **Read-only (no CSRF required)**:
89
- - `GET /` — Renders `ui/web/dist/index.html` with CSRF token substituted (hard 500 if dist missing).
90
- - `GET /assets/*` — Static files from `ui/web/dist/assets/` (content-hashed, immutable cache headers, path-traversal-safe).
91
- - `GET /api/state` — Consolidated first-paint snapshot: `{data, backlog, agents, tracker, status, cost}` in one round trip (reuses latest snapshots).
92
- - `GET /api/session` — Returns `{token}` for Vite dev server; Origin-checked fail-closed (only local origins).
93
- - `GET /api/cost` — Cost/scorecard summary from `state/ledger/OUTCOMES-LEDGER.md` (per-model, per-day, verdicts, optional pricing estimates).
94
- - `GET /api/wave/prs` — Wave PR board: open PRs (`gh pr list`) + PR-less `feat/*` branches (`git for-each-ref`), each with CI rollup / mergeable / age / top blocker. Cached ~5s; degrades to `{available:false, error}` when gh is missing/un-authenticated (never a 500). `wave_prs.py` collector; polled by the frontend (not an SSE section — `gh` is too slow for the collector tick). Set `AESOP_GH_BIN` to override the gh binary path.
95
- - `GET /events` — Server-Sent Events stream (read-only, no CSRF).
96
-
97
- **Mutations (CSRF-gated)**:
98
- - `POST /submit` — Append to inbox.
99
- - `POST /api/tracker`, `POST /api/tracker/<id>` — Tracker CRUD.
100
-
101
- ## Server-Sent Events (SSE) Model
102
-
103
- **Realtime streaming via GET /events**:
104
- - ThreadingHTTPServer required (SSE holds one connection per client)
105
- - Streams JSON frames: `event: <section>` / `data: <json>`
106
- - Keepalive comment-line (`: keepalive`) sent every ~15s to prevent timeout
107
- - Read-only stream; no mutations
108
-
109
- **Sections emitted** (only on content change, in order):
110
- 1. **data** — heartbeat status, daemon state, log tail
111
- 2. **backlog** — AUDIT-BACKLOG.md parsed into tier buckets (P0/P1/P2/Needs decision)
112
- 3. **agents** — fleet agent activity (from Claude transcripts directory)
113
- 4. **tracker** — tracker items (CRUD mutations reflected in realtime)
114
- 5. **status** — orchestrator phase + activity
115
- 6. **cost** — cost/scorecard summary (wave-14 addition)
116
-
117
- ## Background Collector Thread
118
-
119
- Daemon thread polling heartbeats/logs via mtime/fingerprint gates; re-derives SSE sections only on input change, broadcasts only when content-hash differs (avoids expensive operations on every tick). Started idempotently on first HTTP request via `start_collector_thread()`, runs with single-instance guard `_collector_lock`, wakes every `COLLECTOR_INTERVAL` (default 1.0s, `AESOP_UI_COLLECT_INTERVAL`).
120
-
121
- ## Invariants & Gotchas
122
-
123
- - **Stdlib only**: No external dependencies (requests, flask, etc.). Uses only `http.server`, `json`, `subprocess`, `threading`.
124
- - **ThreadingHTTPServer required**: SSE model requires one thread per client connection. Standard HTTPServer (processes) cannot hold SSE connections open.
125
- - **Collector fail-open**: If collector thread crashes, server continues serving; realtime updates stop but dashboard remains accessible.
126
- - **Token file permissions**: On Unix-like systems, token file is chmod 0600 (user-only). Windows ignores mode bits but respects file permissions via ACLs.
127
- - **Paths git-ignored**: `state/.ui-session-token` is ephemeral (regenerated if missing), never committed.