@matt82198/aesop 0.1.0-beta.4 → 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.
Files changed (174) hide show
  1. package/CHANGELOG.md +46 -5
  2. package/README.md +57 -1
  3. package/aesop.config.example.json +8 -1
  4. package/bin/CLAUDE.md +30 -2
  5. package/bin/cli.js +286 -73
  6. package/daemons/CLAUDE.md +4 -1
  7. package/daemons/run-watchdog.sh +5 -2
  8. package/docs/RELEASING.md +159 -0
  9. package/docs/archive/README.md +3 -0
  10. package/docs/{spikes → archive/spikes}/tiered-cognition/README.md +1 -3
  11. package/docs/case-study-portfolio.md +61 -0
  12. package/docs/self-stats-data.json +11 -0
  13. package/docs/templates/FLEET-OPS-ANALYSIS.example.md +27 -0
  14. package/docs/templates/FLEET-OPS-RECOMMENDATIONS.example.md +24 -0
  15. package/docs/templates/PROPOSALS-LOG.example.md +64 -0
  16. package/hooks/CLAUDE.md +23 -0
  17. package/mcp/CLAUDE.md +213 -0
  18. package/mcp/package.json +26 -0
  19. package/mcp/server.mjs +543 -0
  20. package/monitor/CHARTER.md +76 -110
  21. package/monitor/collect-signals.mjs +85 -0
  22. package/package.json +4 -1
  23. package/scan/fleet-scan.example.mjs +292 -0
  24. package/skills/healthcheck/SKILL.md +44 -0
  25. package/state_store/CLAUDE.md +39 -0
  26. package/state_store/__init__.py +24 -0
  27. package/state_store/__pycache__/__init__.cpython-314.pyc +0 -0
  28. package/state_store/__pycache__/api.cpython-314.pyc +0 -0
  29. package/state_store/__pycache__/export.cpython-314.pyc +0 -0
  30. package/state_store/__pycache__/ingest.cpython-314.pyc +0 -0
  31. package/state_store/__pycache__/projections.cpython-314.pyc +0 -0
  32. package/state_store/__pycache__/store.cpython-314.pyc +0 -0
  33. package/state_store/api.py +35 -0
  34. package/state_store/export.py +19 -0
  35. package/state_store/ingest.py +24 -0
  36. package/state_store/projections.py +52 -0
  37. package/state_store/store.py +102 -0
  38. package/tools/CLAUDE.md +30 -149
  39. package/tools/__pycache__/alert_bridge.cpython-314.pyc +0 -0
  40. package/tools/__pycache__/ci_merge_wait.cpython-314.pyc +0 -0
  41. package/tools/__pycache__/fleet_prompt_extractor.cpython-314.pyc +0 -0
  42. package/tools/__pycache__/healthcheck.cpython-314.pyc +0 -0
  43. package/tools/__pycache__/metrics_gate.cpython-314.pyc +0 -0
  44. package/tools/__pycache__/secret_scan.cpython-314.pyc +0 -0
  45. package/tools/__pycache__/self_stats.cpython-314.pyc +0 -0
  46. package/tools/__pycache__/session_usage_summary.cpython-314.pyc +0 -0
  47. package/tools/__pycache__/stall_check.cpython-314.pyc +0 -0
  48. package/tools/__pycache__/transcript_replay.cpython-314.pyc +0 -0
  49. package/tools/__pycache__/transcript_timeline.cpython-314.pyc +0 -0
  50. package/tools/__pycache__/verify_dash.cpython-314.pyc +0 -0
  51. package/tools/__pycache__/verify_submit_encoding.cpython-314.pyc +0 -0
  52. package/tools/alert_bridge.py +449 -0
  53. package/tools/ci_merge_wait.py +121 -8
  54. package/tools/fleet_prompt_extractor.py +134 -0
  55. package/tools/healthcheck.py +296 -0
  56. package/tools/secret_scan.py +8 -1
  57. package/tools/self_stats.py +509 -0
  58. package/tools/session_usage_summary.py +198 -0
  59. package/tools/svg_to_png.mjs +50 -0
  60. package/tools/transcript_replay.py +236 -0
  61. package/tools/transcript_timeline.py +184 -0
  62. package/tools/verify_dash.py +361 -542
  63. package/tools/verify_submit_encoding.py +12 -4
  64. package/ui/CLAUDE.md +57 -41
  65. package/ui/__pycache__/agents.cpython-314.pyc +0 -0
  66. package/ui/__pycache__/collectors.cpython-314.pyc +0 -0
  67. package/ui/__pycache__/config.cpython-314.pyc +0 -0
  68. package/ui/__pycache__/cost.cpython-314.pyc +0 -0
  69. package/ui/__pycache__/csrf.cpython-314.pyc +0 -0
  70. package/ui/__pycache__/handler.cpython-314.pyc +0 -0
  71. package/ui/__pycache__/render.cpython-314.pyc +0 -0
  72. package/ui/__pycache__/serve.cpython-314.pyc +0 -0
  73. package/ui/__pycache__/sse.cpython-314.pyc +0 -0
  74. package/ui/agents.py +9 -5
  75. package/ui/api/__pycache__/submit.cpython-314.pyc +0 -0
  76. package/ui/api/submit.py +44 -5
  77. package/ui/collectors.py +184 -35
  78. package/ui/config.py +9 -0
  79. package/ui/cost.py +260 -0
  80. package/ui/csrf.py +7 -3
  81. package/ui/handler.py +254 -4
  82. package/ui/render.py +26 -6
  83. package/ui/sse.py +16 -1
  84. package/ui/web/.gitattributes +13 -0
  85. package/ui/web/dist/assets/index-2LZDQirC.js +9 -0
  86. package/ui/web/dist/assets/index-D4M1qyOv.css +1 -0
  87. package/ui/web/dist/index.html +14 -0
  88. package/ui/web/index.html +13 -0
  89. package/ui/web/package-lock.json +2225 -0
  90. package/ui/web/package.json +26 -0
  91. package/ui/web/src/App.test.tsx +74 -0
  92. package/ui/web/src/App.tsx +142 -0
  93. package/ui/web/src/CONTRIBUTING-UI.md +49 -0
  94. package/ui/web/src/components/AgentRow.css +187 -0
  95. package/ui/web/src/components/AgentRow.test.tsx +209 -0
  96. package/ui/web/src/components/AgentRow.tsx +207 -0
  97. package/ui/web/src/components/AgentsPanel.css +108 -0
  98. package/ui/web/src/components/AgentsPanel.test.tsx +41 -0
  99. package/ui/web/src/components/AgentsPanel.tsx +58 -0
  100. package/ui/web/src/components/AlertsPanel.css +88 -0
  101. package/ui/web/src/components/AlertsPanel.test.tsx +51 -0
  102. package/ui/web/src/components/AlertsPanel.tsx +67 -0
  103. package/ui/web/src/components/BacklogPanel.test.tsx +126 -0
  104. package/ui/web/src/components/BacklogPanel.tsx +122 -0
  105. package/ui/web/src/components/CostChart.css +110 -0
  106. package/ui/web/src/components/CostChart.test.tsx +144 -0
  107. package/ui/web/src/components/CostChart.tsx +152 -0
  108. package/ui/web/src/components/CostTable.css +93 -0
  109. package/ui/web/src/components/CostTable.test.tsx +165 -0
  110. package/ui/web/src/components/CostTable.tsx +94 -0
  111. package/ui/web/src/components/EventsFeed.css +68 -0
  112. package/ui/web/src/components/EventsFeed.test.tsx +36 -0
  113. package/ui/web/src/components/EventsFeed.tsx +31 -0
  114. package/ui/web/src/components/HealthHeader.css +137 -0
  115. package/ui/web/src/components/HealthHeader.test.tsx +278 -0
  116. package/ui/web/src/components/HealthHeader.tsx +281 -0
  117. package/ui/web/src/components/InboxForm.css +135 -0
  118. package/ui/web/src/components/InboxForm.test.tsx +208 -0
  119. package/ui/web/src/components/InboxForm.tsx +116 -0
  120. package/ui/web/src/components/MessagesTail.module.css +144 -0
  121. package/ui/web/src/components/MessagesTail.test.tsx +176 -0
  122. package/ui/web/src/components/MessagesTail.tsx +94 -0
  123. package/ui/web/src/components/ReposPanel.css +90 -0
  124. package/ui/web/src/components/ReposPanel.test.tsx +45 -0
  125. package/ui/web/src/components/ReposPanel.tsx +67 -0
  126. package/ui/web/src/components/Scorecard.css +106 -0
  127. package/ui/web/src/components/Scorecard.test.tsx +117 -0
  128. package/ui/web/src/components/Scorecard.tsx +85 -0
  129. package/ui/web/src/components/Timeline.module.css +151 -0
  130. package/ui/web/src/components/Timeline.test.tsx +215 -0
  131. package/ui/web/src/components/Timeline.tsx +99 -0
  132. package/ui/web/src/components/TrackerBoard.test.tsx +121 -0
  133. package/ui/web/src/components/TrackerBoard.tsx +107 -0
  134. package/ui/web/src/components/TrackerCard.test.tsx +180 -0
  135. package/ui/web/src/components/TrackerCard.tsx +160 -0
  136. package/ui/web/src/components/TrackerForm.test.tsx +189 -0
  137. package/ui/web/src/components/TrackerForm.tsx +144 -0
  138. package/ui/web/src/lib/api.ts +218 -0
  139. package/ui/web/src/lib/format.test.ts +89 -0
  140. package/ui/web/src/lib/format.ts +103 -0
  141. package/ui/web/src/lib/sanitizeUrl.test.ts +84 -0
  142. package/ui/web/src/lib/sanitizeUrl.ts +38 -0
  143. package/ui/web/src/lib/types.ts +230 -0
  144. package/ui/web/src/lib/useHashRoute.test.ts +60 -0
  145. package/ui/web/src/lib/useHashRoute.ts +23 -0
  146. package/ui/web/src/lib/useSSE.ts +175 -0
  147. package/ui/web/src/main.tsx +10 -0
  148. package/ui/web/src/styles/global.css +179 -0
  149. package/ui/web/src/styles/theme.css +184 -0
  150. package/ui/web/src/styles/work.css +572 -0
  151. package/ui/web/src/test/fixtures.ts +385 -0
  152. package/ui/web/src/test/setup.ts +49 -0
  153. package/ui/web/src/views/Activity.module.css +43 -0
  154. package/ui/web/src/views/Activity.test.tsx +89 -0
  155. package/ui/web/src/views/Activity.tsx +31 -0
  156. package/ui/web/src/views/Cost.css +87 -0
  157. package/ui/web/src/views/Cost.test.tsx +142 -0
  158. package/ui/web/src/views/Cost.tsx +54 -0
  159. package/ui/web/src/views/Overview.css +51 -0
  160. package/ui/web/src/views/Overview.test.tsx +76 -0
  161. package/ui/web/src/views/Overview.tsx +46 -0
  162. package/ui/web/src/views/Work.test.tsx +82 -0
  163. package/ui/web/src/views/Work.tsx +79 -0
  164. package/ui/web/src/vite-env.d.ts +10 -0
  165. package/ui/web/tsconfig.json +22 -0
  166. package/ui/web/vite.config.ts +25 -0
  167. package/ui/web/vitest.config.ts +12 -0
  168. package/ui/templates/dashboard.html +0 -1202
  169. /package/docs/{spikes → archive/spikes}/tiered-cognition/ACTIVATION.md +0 -0
  170. /package/docs/{spikes → archive/spikes}/tiered-cognition/DESIGN.md +0 -0
  171. /package/docs/{spikes → archive/spikes}/tiered-cognition/FINDINGS.md +0 -0
  172. /package/docs/{spikes → archive/spikes}/tiered-cognition/aesop-cognition.example.md +0 -0
  173. /package/docs/{spikes → archive/spikes}/tiered-cognition/force-model-policy.merged.mjs +0 -0
  174. /package/docs/{spikes → archive/spikes}/tiered-cognition/strip-tools-hook.mjs +0 -0
@@ -1,137 +1,103 @@
1
- # Aesop Orchestration Monitor — Charter
1
+ # Orchestration Refinement Monitor — Charter
2
2
 
3
- A standing background monitor (Haiku subagent) that watches how the multi-agent system operates and acts on points of refinement. It improves the *machinery*, never the *mission*.
3
+ A standing background monitor (Haiku) that watches how the multi-agent system operates and
4
+ acts on points of refinement. It improves the *machinery*, never the *mission*.
4
5
 
5
6
  ## FIXED GOAL (never changes)
7
+ > Ship and maintain your projects correctly, cheaply, and autonomously under the cardinal rules
8
+ > (`~/.claude/CLAUDE.md`). The monitor's job is to make that machine run better — it must NOT
9
+ > redirect, re-scope, or invent new project goals. If it ever thinks the goal should change, it
10
+ > writes a note in `PROPOSALS.md` and stops there.
11
+
12
+ ## What it watches (via `collect-signals.mjs`, refreshed each cycle)
13
+ 1. **Junk-script sprawl** — one-off Python/JS scripts Claude Code writes to temp/scratch/cloud and
14
+ never reuses. Repeated throwaway `.py` to remote agents is a known cost/clutter sink.
15
+ 2. **Memory gaps** — projects/decisions/gotchas with no durable memory file; stale or duplicate memories.
16
+ 3. **Rule friction** — cardinal-rule violations in recent activity (non-Haiku subagents, main-thread
17
+ big reads, unpushed green work) and rules that are ambiguous, contradicted, or repeatedly ignored.
18
+ 4. **Orchestration health** — hung/stale agents, drift, repeated failures, git state across repos.
6
19
 
7
- > Maintain your projects correctly, cheaply, and autonomously under your cardinal rules.
8
- > The monitor's job is to make that machine run better — it must NOT redirect, re-scope,
9
- > or invent new project goals. If it ever thinks the goal should change, it writes a note
10
- > in `PROPOSALS.md` and stops there.
20
+ ## Action tiers
21
+ - **AUTO (apply immediately, then log to `ACTIONS.log`):**
22
+ - Persist/refresh project memory files (one fact each) + update `MEMORY.md` index.
23
+ - Quarantine confirmed-dead one-off scripts: move junk temp `.py`/`.mjs` (older than 24h, not
24
+ referenced by any repo, not the active session's scratchpad) into `orchestration-monitor/quarantine/`
25
+ with a manifest — never delete outright, never touch a live session scratchpad or repo `src/`.
26
+ - Clarifying, non-behavioral edits to a memory/log the monitor owns.
27
+ - **PROPOSE (write to `PROPOSALS.md`, do NOT apply — needs user OK):**
28
+ - Any change to `~/.claude/CLAUDE.md`, `~/.claude/skills/**`, or a project `CLAUDE.md`.
29
+ - Anything that alters agent behavior, model choice, push targets, or goal scope.
30
+ - Deleting anything outside the monitor's own quarantine.
11
31
 
12
- ## Standing checks (deterministic signal collection)
32
+ ## Hard guardrails (inviolable)
33
+ - Preserve the FIXED GOAL. Improve rules; never rewrite intent.
34
+ - Subagents are ALWAYS Haiku (this monitor included). Opus orchestrates on the main thread only.
35
+ - NEVER work around a classifier denial (cross-repo private-source push = exfil; reusing a
36
+ credential meant for another service = credential exploration). If seen, log + propose, don't act.
37
+ - NEVER print secret values. NEVER push to a remote or run a destructive command as an AUTO action.
38
+ - Idempotent + additive: safe to run repeatedly; prefer append/quarantine over overwrite/delete.
39
+ - Stay cheap: read the signal brief, not raw code/logs. One tight cycle, then sleep.
13
40
 
14
- Each cycle, `collect-signals.mjs` runs these checks and emits `BRIEF.md` + `SIGNALS.json`:
41
+ ## End-of-day wipe-survival sweep
15
42
 
16
- 1. **Heartbeat liveness check** read `.heartbeats/*` (epoch on line 1) + legacy beat files
17
- (`.monitor-heartbeat`, `.watchdog-heartbeat`); flag any older than threshold (watchdog 300s,
18
- monitor 3600s, others 1800s default) as stale loops; append warning to BRIEF.md.
43
+ The monitor may run `python ~/scripts/eod_sweep.py` in its final cycle of the day.
44
+ This verifies all known git repos are safe (no data loss risk):
45
+ - working trees clean OR dirty-file list logged
46
+ - current branch pushed to origin (ahead-count 0)
47
+ - untracked files not silently .gitignore'd
48
+ - SECURITY-ALERTS.log has no unreviewed HIGH/MED items
49
+ - memory INBOX consumed (0 pending items)
50
+ - no heartbeat file claims a dead loop (>1h stale)
19
51
 
20
- 2. **Git state across repos** for each repo in config: branch, last commit, uncommitted files,
21
- commits ahead of remote. Identifies unpushed work and dirty state.
52
+ Output: single verdict line (`EOD-SWEEP: SAFE` or `EOD-SWEEP: AT-RISK <n> findings`) + one finding per line;
53
+ appends to handoff log. Exit code 0 only if SAFE.
22
54
 
23
- 3. **Memory freshness** scan project memory files (excluding index/inbox); flag any >30 days old;
24
- append summary to BRIEF.md ("N memories stale — keeper should re-verify").
55
+ If findings detected, the monitor stages an alert: `EOD-SWEEP AT-RISK <n> findings` (each finding on bullet).
25
56
 
26
- 4. **Log rotation** check append-only logs against max-lines/max-kb thresholds (configurable,
27
- default 500 lines / 40 KB). If a log exceeds threshold, emit rotation signal (AUTO tier can
28
- invoke rotate_logs.py if available); append rotation summary to BRIEF.md.
57
+ ## Standing checks (trimmed for solo operation)
29
58
 
30
- 5. **Junk-script sprawl detection** *(extended, opt-in via `monitor.extended_signals`)* — scan temp/scratch roots for throwaway `.py`/.mjs`/`.js` files
31
- older than 24 hours, not in a live session directory (avoid false-positives during active work).
32
- Count total, estimate quarantinable, list oldest. AUTO tier quarantines confirmed junk into
33
- `monitor/quarantine/` with manifest.
59
+ **KEPT (durable value):**
34
60
 
35
- 6. **Stray scripts in repo roots** *(extended, opt-in via `monitor.extended_signals`)* scan recent commits (7d) to detect one-off `.py`/`.mjs`/`.sql`
36
- scripts committed directly to repo root (not under proper src/scripts paths). Flag for cleanup.
61
+ 1. **Scripts outside `~/scripts`**flag `.py`/`.mjs`/`.sh` accumulating outside `~/scripts`
62
+ (scratchpads, temp, repo roots). Quarantine per existing tiers; when one looks genuinely reusable,
63
+ suggest promotion to `~/scripts` (indexed in `~/scripts/CLAUDE.md`).
37
64
 
38
- 7. **Security alert review loop** — tail SECURITY-ALERTS.log for new HIGH/MED entries (skip suppressed);
39
- each new alert noted in BRIEF.md. Monitor agent does semantic review (REAL vs FP) each cycle.
65
+ 2. **Remote Python execution = HIGH-tier finding** — any scheduled/cloud agent or workflow that would
66
+ execute Python remotely violates Rule 5 (local-only execution). Flag immediately; never create one.
40
67
 
41
- 8. **Respawn watch (Rule 6 retry cap)** *(extended, opt-in via `monitor.extended_signals`)* parse agent spawn records; normalize descriptions to
42
- prompt-signatures (first ~40 chars, lowercased); count occurrences in recent window (last 50 rows).
43
- Flag any signature appearing >3 times as probable hung-agent loop → rule 6 cap breached.
44
- Limitation: heuristic may have false positives for intentional similar-task fan-outs.
68
+ 3. **Memory freshness** scan `~/.claude/projects/*/memory/*.md` (excluding INBOX.md, MEMORY.md); emit `staleMemories` signal in SIGNALS.json listing any file >30d old; append to BRIEF.md: "N memories >30d — keeper should re-verify: <names>".
45
69
 
46
- 9. **Cost cadence tracking** — every 3rd cycle, harvest agent spawn ledger and append cost tick
47
- to COST-LOG.md (cycle count, timestamp, model distribution). Flags non-haiku specializations.
70
+ 4. **SECURITY ALERT REVIEW LOOP** — on each /power cycle, arm a persistent Monitor tailing
71
+ SECURITY-ALERTS.log filtered to new 'HIGH'/'MED' lines (exclude SUPPRESSED-FP). Each new alert spawns a read-only fleet-auditor agent to classify REAL vs FALSE-POSITIVE (mask any secret: first 4 chars + ***), verdict appended to SECURITY-ALERTS.log as a REVIEW note and, if REAL, surfaced to the user immediately. Rationale: the append-only alert log is only useful if alerts are triaged; unreviewed HIGHs rot into ignored noise.
48
72
 
49
- 10. **Unreviewed agent prompts** *(extended, opt-in via `monitor.extended_signals`)* run fleet-prompt-extractor.py (if available) to collect NEW
50
- spawns since last review; emit count in SIGNALS.json + note in BRIEF.md.
73
+ 5. **Fleet ledger harvest** — each cycle, run `python ~/scripts/fleet_ledger.py harvest` to scan session task outputs (JSONL format) and append missing agent outcomes to OUTCOMES-LEDGER.md. Tracks last-harvest state in .fleet-ledger-harvest.json sidecar. Deterministic, local-only, append-only; feeds cost analysis and agent respawn detection. Then invoke `python ~/scripts/fleet_ledger.py rotate` to archive old rows if ledger exceeds 200 lines.
51
74
 
52
- ## Extended signals (opt-in)
75
+ 6. **Log rotation** — after ledger operations, invoke `python ~/scripts/rotate_logs.py <file> --max-lines 500 --max-kb 40` over: hook-activity.log, SECURITY-ALERTS.log, FLEET-BACKUP.log (BUILDLOG.md at 1000-line threshold — historical record). Emit `logRotations` list in SIGNALS.json and summary line in BRIEF.md.
53
76
 
54
- Checks 5, 6, 8, and 10 are marked as "extended" they are disabled by default but can be
55
- enabled via configuration:
77
+ 7. **Heartbeat liveness check** — read state/.heartbeats/* (epoch on line 1) + legacy beat files (.monitor-heartbeat, .watchdog-heartbeat, etc.); flag any older than threshold (watchdog 300s, monitor 3600s, others 1800s default) as `staleLoops` signal in SIGNALS.json; append warning to BRIEF.md. Continuous watchdog complement to /power selftest.
56
78
 
57
- - **Config key:** `monitor.extended_signals` (boolean, default: `false`) in aesop.config.json
58
- - **Env override:** `AESOP_EXTENDED_SIGNALS` (string `'true'` or `'1'` to enable)
59
- - **Precedence:** env var > config file > default (false)
79
+ 8. **Respawn watch (Rule 6 machine-enforcement)** — parse OUTCOMES-LEDGER.md for agent descriptions; normalize each to prompt-signature (first ~40 chars, lowercased, trimmed); count occurrences per signature within recent window (last 50 rows OR since marker in .signal-state.json). Any signature appearing >3 times = probable hung-agent respawn loop → add to `respawnWatch` in SIGNALS.json + warning line in BRIEF.md. *Limitation:* heuristic may have false positives for intentional similar-task fan-outs (e.g., per-file processing with distinct file names in description); BRIEF includes note to manually review flagged signatures. Pure node parsing (no LLM).
60
80
 
61
- When disabled, extended checks emit `{"skipped": true}` in SIGNALS.json and BRIEF.md notes them
62
- as "extended (off)". AUTO junk-quarantine action only runs when the junk check is enabled.
63
- PROPOSE-tier signals for extended checks (respawn-watch-breach, stray-repo-scripts) are only
64
- emitted when extended_signals is ON.
81
+ 9. **Semantic prompt review** — each cycle, collect-signals.mjs runs `fleet_prompt_extractor.py` to identify NEW unreviewed agent-spawn prompts (tracks seen-hash state in `.fleet-prompts-seen.json`); count is emitted as `unreviewedPrompts` in SIGNALS.json + noted in BRIEF.md. If count > 0, the monitor agent does semantic review (read each prompt, classify: OK / DRIFT / SUSPICIOUS) and runs `drift_escalator.py` to write verdicts to DRIFT-REVIEW.log + escalate any DRIFT/SUSPICIOUS findings to SECURITY-ALERTS.log + DRIFT-ESCALATIONS.md. This closes the gap: regex fleet-scan is NOT semantic review; LLM judgment gates malformed/off-goal/injection prompts. Completes before any flagged prompt's agent runs.
65
82
 
66
- ## Action tiers
83
+ 10. **Mirror refresh cadence (throttled)** — each monitor cycle mirrors scripts and docs to your fleet-ops directory and regenerates asset indexes. If either produces diffs: check `.last-mirror-pr-time` in state/. Only open a PR if >1 hour has passed since last PR; otherwise log silently as no-op to ACTIONS.log with timestamp. Ensures mirrors stay in sync and asset lists reflect tier-1 changes, but prevents PR spam. Pure Python automation; runs in monitor context only.
67
84
 
68
- - **AUTO (apply immediately, then log to `ACTIONS.log`):**
69
- - Heartbeat freshness checks (read-only).
70
- - Log rotation (invoke rotate_logs.py if available; fail-open if not).
71
- - Persist heartbeat write (`.monitor-heartbeat` update after cycle).
72
- - Quarantine confirmed-junk scripts: move old temp `.py`/`.mjs` (not live session) into
73
- `monitor/quarantine/` with manifest — never delete outright (only when extended_signals is ON).
74
-
75
- - **PROPOSE (write to `PROPOSALS.md`, do NOT apply — needs user approval):**
76
- - Changes to cardinal rules or agent configuration.
77
- - Alterations to agent behavior, model choice, or push targets.
78
- - Deletions of anything outside monitor's own quarantine.
79
- - Any change to orchestration policy.
85
+ 11. **UI inbox drain (session-independent work queue)** — each cycle, run `python ~/scripts/inbox_drain.py pending` to surface queued submissions that arrived while no session was running. If items exist, write a one-line `QUEUED WORK PENDING` flag to BRIEF.md so an active monitor also surfaces it. This complements /power's inbox drain (step 5); the monitor ensures work doesn't rot in the queue even between sessions.
80
86
 
81
- ## Hard guardrails (inviolable)
87
+ **CUT or DEMOTED (no durable value for solo operator):**
82
88
 
83
- - Preserve the FIXED GOAL. Improve rules; never rewrite intent.
84
- - **Subagents are ALWAYS Haiku** (cost optimization). Orchestrator on main thread only.
85
- - NEVER push destructively or to restricted remotes as an AUTO action.
86
- - NEVER edit cardinal-rules files (CLAUDE.md, skills/, agent configs) — only PROPOSE changes.
87
- - Idempotent + additive: safe to run repeatedly; prefer append over overwrite.
88
- - Stay cheap: read the signal brief, not raw logs. One tight cycle, then sleep.
89
- - Robust to missing files: treat missing dirs/logs as empty, never crash.
89
+ - **Suppression audit** — (CUT) A solo operator watching the dashboard notices suppression count manually; meta-audits add ceremony without signal.
90
+ - **Cost cadence** (DEMOTED; blocked) Ledger parsing incomplete; once cost meter is proven, restore. For now, flag as blocked in PROPOSALS.md if touched.
91
+
92
+ **Rationale & changes:**
93
+
94
+ The original 12-check charter was ceremony-heavy for a one-person system. Cuts preserve the critical signals (liveness, security triage, hung loops, prompt safety, cost data acquisition) and demote/cut only checks whose value is marginal or already visible to a solo operator. Check #10 was throttled instead of cut because the mirror sync is critical, but PR-per-cycle wastes bandwidth — now it only PRs when changes exist AND at least 1 hour has passed.
90
95
 
91
- ## Outputs
96
+ Total standing checks: **11** (reflecting genericization; some checks are environment-specific).
92
97
 
93
- - `BRIEF.md` — human-readable status snapshot (overwrite each cycle).
94
- - `SIGNALS.json` — machine-readable metrics and findings (overwrite each cycle).
98
+ ## Outputs (all under orchestration-monitor/)
99
+ - `BRIEF.md` / `SIGNALS.json` — the deterministic snapshot (written by the collector).
95
100
  - `ACTIONS.log` — append-only record of AUTO actions taken (timestamped).
96
- - `PROPOSALS.md` — staged changes awaiting user approval.
97
- - `quarantine/` — parked junk scripts + `MANIFEST.tsv` (append-only).
98
- - `.monitor-heartbeat` — epoch timestamp (line 1) for liveness.
99
- - `.signal-state.json` — sidecar state (cycle count, seen prompts, etc.).
100
-
101
- ## Operating it
102
-
103
- 1. **Deploy the monitor** as a continuous background task (or invoke manually each cycle).
104
- 2. **Customize via config**: populate `aesop.config.json` with your repo paths, thresholds.
105
- 3. **Review PROPOSALS.md** periodically; approve changes or reject them.
106
- 4. **Check ACTIONS.log** to see what was automated.
107
- 5. **Monitor BRIEF.md** for drift signals; respond to findings.
108
-
109
- ## Customization
110
-
111
- Edit `collect-signals.mjs` to:
112
- - Add/remove checks based on your project needs.
113
- - Configure paths via environment variables (AESOP_ROOT, BRAIN_ROOT, SCRIPTS_ROOT, TEMP_ROOT)
114
- or load from aesop.config.json.
115
- - Set heartbeat thresholds appropriate to your workflow.
116
- - Integrate custom signal collectors (e.g., compliance checks, custom linters).
117
-
118
- See comments in `collect-signals.mjs` for extension points. **Keep it CRLF-safe** (no line
119
- continuations; maintain Windows+POSIX compatibility).
120
-
121
- ## Single-instance guard
122
-
123
- Before running, check `.monitor-heartbeat` — if <300s old, skip cycle (another monitor is running).
124
- After cycle completes, update `.monitor-heartbeat` with current epoch.
125
- Override with `AESOP_MONITOR_FORCE=true` or `AESOP_MONITOR_FORCE=1` for manual runs or tests (explicit comparison, not truthiness: strings like `'0'` or `'false'` do NOT bypass the guard).
126
-
127
- ## Single-writer discipline with atomic operations
128
-
129
- - Only the monitor edits `BRIEF.md`, `SIGNALS.json`, `ACTIONS.log`, `.monitor-heartbeat`, `.signal-state.json`.
130
- - Writes are atomic (temp file + rename) to prevent mid-write corruption if a kill occurs.
131
- - Rename is retried on EPERM/EBUSY (Windows readers holding file) with exponential backoff; .tmp is cleaned up on final failure; prior file is preserved (degrade gracefully).
132
- - `PROPOSALS.md` uses atomic operations (mkdir-style lockfile) to prevent race conditions between concurrent appends (emitProposal) and full rewrites (accept/reject).
133
- - **Both emitProposal and moveProposal (accept/reject) acquire the PROPOSALS.md.lock before read-check-append/write**.
134
- - Lock contains pid+timestamp for staleness detection (>60s stale locks are reclaimed).
135
- - Lock is acquired, held during read-modify-write, released after write completes.
136
- - **Fail-closed (P0 wave-8 fix)**: If lock acquisition fails after timeout (default 30s, configurable via AESOP_LOCK_TIMEOUT_MS env var or config), the operation throws an error instead of proceeding. In emitProposal (monitor context), lock failures skip proposal emission for that cycle and retry next cycle. In proposals.mjs CLI (accept/reject), lock failures exit with error code 1 (caller should retry).
137
- - Quarantine manifest is append-only; never edit entries, only add new ones.
101
+ - `PROPOSALS.md` — staged rule/behavior changes awaiting user approval.
102
+ - `quarantine/` — parked junk scripts + `MANIFEST.tsv`.
103
+ - `.monitor-heartbeat` — liveness for any dashboard.
@@ -455,6 +455,70 @@ function checkUnreviewedPrompts() {
455
455
  return 0;
456
456
  }
457
457
 
458
+ // 11) Isolation violation detection (rec #5: git-tracked dirs don't flag)
459
+ // Detects worktrees created INSIDE repo root (violation) vs sanctioned ../sibling-wt-* (OK)
460
+ // FP fix: git-tracked source directories (new modules) do NOT flag as violations
461
+ function detectIsolationViolations() {
462
+ if (!fs.existsSync(AESOP_ROOT)) return { violations: [], count: 0 };
463
+
464
+ const violations = [];
465
+ const trackedFiles = new Set();
466
+
467
+ // Get list of git-tracked files to exclude legitimate source dirs
468
+ try {
469
+ const tracked = sh('git ls-files', AESOP_ROOT);
470
+ tracked.split('\n').forEach(f => {
471
+ if (f) {
472
+ const dir = f.split('/')[0];
473
+ trackedFiles.add(dir);
474
+ }
475
+ });
476
+ } catch {
477
+ // If git ls-files fails, proceed without tracked set (less precise but fail-open)
478
+ }
479
+
480
+ // Walk AESOP_ROOT (shallow) looking for dirs with nested .git
481
+ try {
482
+ const entries = fs.readdirSync(AESOP_ROOT, { withFileTypes: true });
483
+ for (const entry of entries) {
484
+ if (!entry.isDirectory()) continue;
485
+ if (entry.name.startsWith('.')) continue;
486
+ if (entry.name === 'node_modules') continue;
487
+
488
+ const dirPath = path.join(AESOP_ROOT, entry.name);
489
+ const gitPath = path.join(dirPath, '.git');
490
+
491
+ // Check if this directory has a .git file or .git dir (worktree indicator)
492
+ try {
493
+ const gitStat = fs.statSync(gitPath);
494
+ // It has .git — check if it's a worktree (contains "gitdir:" or is a symlink)
495
+ const isWorktree = gitStat.isFile() || gitStat.isSymbolicLink();
496
+
497
+ if (isWorktree) {
498
+ // This looks like a worktree. Check if it's git-tracked (FP fix)
499
+ const isTracked = trackedFiles.has(entry.name);
500
+
501
+ if (!isTracked) {
502
+ // Untracked worktree inside repo root = VIOLATION
503
+ violations.push({
504
+ path: entry.name,
505
+ type: 'untracked-worktree-in-root',
506
+ message: `Worktree '${entry.name}' found inside repo root (untracked). Should use sanctioned ../aesop-wt-* sibling instead.`,
507
+ });
508
+ }
509
+ // If tracked, it's a legitimate source directory; don't flag
510
+ }
511
+ } catch (e) {
512
+ // Entry doesn't have .git or is inaccessible; skip
513
+ }
514
+ }
515
+ } catch (e) {
516
+ // Directory read failed; fail-open with no violations
517
+ }
518
+
519
+ return { violations, count: violations.length };
520
+ }
521
+
458
522
  // === AUTO Actions ===
459
523
  // Log rotation: invoke rotate_logs.py if available and log needs rotation
460
524
  function performAutoLogRotation(logFiles, actionsLogPath) {
@@ -661,6 +725,7 @@ const staleLoops = checkHeartbeats();
661
725
  const gitState = checkGitState();
662
726
  const memory = checkMemoryFreshness();
663
727
  const logFiles = checkLogFiles();
728
+ const isolationViolations = detectIsolationViolations();
664
729
 
665
730
  // Extended signal checks (5, 6, 8, 10) — skipped if extended_signals is OFF
666
731
  const junk = extendedSignals ? detectJunkScripts() : { skipped: true };
@@ -709,6 +774,7 @@ const signals = {
709
774
  respawnWatch,
710
775
  costTick,
711
776
  unreviewedPrompts,
777
+ isolationViolations,
712
778
  };
713
779
 
714
780
  const brief = [];
@@ -756,6 +822,17 @@ if (needsRotation.length === 0) {
756
822
  }
757
823
  brief.push('');
758
824
 
825
+ brief.push('## Isolation violations');
826
+ if (isolationViolations.count === 0) {
827
+ brief.push('✓ No worktrees detected inside repo root (sanctioned siblings OK).');
828
+ } else {
829
+ brief.push(`🚨 **${isolationViolations.count} isolation violation(s)** detected (worktrees inside repo root):`);
830
+ for (const v of isolationViolations.violations) {
831
+ brief.push(` - ${v.path}: ${v.message}`);
832
+ }
833
+ }
834
+ brief.push('');
835
+
759
836
  // Extended signals section (if disabled, just note they're off; if enabled, show details)
760
837
  if (extendedSignals) {
761
838
  brief.push('## Junk-script sprawl (temp/scratch)');
@@ -839,6 +916,14 @@ if (extendedSignals) {
839
916
  }
840
917
 
841
918
  // Core proposals (always emitted)
919
+ if (isolationViolations.count > 0) {
920
+ emitProposal(
921
+ 'isolation-violation-detected',
922
+ `${isolationViolations.count} worktree(s) detected inside repo root. Agents should create worktrees as sanctioned siblings (../aesop-wt-*), not inside the repo root to maintain isolation.`,
923
+ `Review the violation(s): ${isolationViolations.violations.map(v => v.path).join(', ')}. Move worktrees to ../aesop-wt-<name> location outside repo root. Add pre-commit hook or monitoring to prevent future in-root worktrees.`
924
+ );
925
+ }
926
+
842
927
  if (alerts.highMedCount > 0) {
843
928
  emitProposal(
844
929
  'security-alerts-high-med',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matt82198/aesop",
3
- "version": "0.1.0-beta.4",
3
+ "version": "0.1.0-beta.5",
4
4
  "description": "Multi-agent orchestration for AI coding agents, designed to survive failure \u00e2\u20ac\u201d a plain-file brain, git as the only durable layer, cheap subagent fleets, and guardrails enforced in code.",
5
5
  "bin": {
6
6
  "aesop": "bin/cli.js"
@@ -11,11 +11,14 @@
11
11
  "daemons/",
12
12
  "dash/",
13
13
  "monitor/",
14
+ "mcp/",
14
15
  "tools/",
15
16
  "ui/",
16
17
  "docs/",
17
18
  "hooks/",
18
19
  "skills/",
20
+ "state_store/",
21
+ "scan/",
19
22
  "aesop.config.example.json",
20
23
  "CLAUDE-TEMPLATE.md",
21
24
  "README.md",