@matt82198/aesop 0.2.0 → 0.3.2

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 (46) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/README.md +60 -263
  3. package/bin/cli.js +7 -3
  4. package/daemons/install-tasks.ps1 +193 -0
  5. package/daemons/run-hidden.vbs +39 -0
  6. package/docs/HOOK-INSTALL.md +15 -56
  7. package/docs/INSTALL.md +48 -3
  8. package/docs/PORTING.md +166 -0
  9. package/docs/TEAM-STATE.md +16 -184
  10. package/docs/reproduce.md +33 -3
  11. package/driver/CLAUDE.md +50 -48
  12. package/driver/codex_driver.py +59 -12
  13. package/driver/openai_transport.py +33 -0
  14. package/driver/wave_bridge.py +9 -1
  15. package/driver/wave_loop.py +437 -70
  16. package/driver/wave_scheduler.py +890 -0
  17. package/hooks/pre-push-policy.sh +22 -5
  18. package/monitor/collect-signals.mjs +69 -0
  19. package/package.json +6 -4
  20. package/state_store/__init__.py +2 -1
  21. package/state_store/projections.py +63 -0
  22. package/state_store/read_api.py +156 -0
  23. package/state_store/write_api.py +462 -0
  24. package/tools/cost_ceiling.py +106 -15
  25. package/tools/cost_projection.py +559 -0
  26. package/tools/crossos_drift.py +394 -0
  27. package/tools/eod_sweep.py +137 -33
  28. package/tools/mutation_test.py +130 -8
  29. package/tools/proposals.mjs +47 -2
  30. package/tools/reproduce.js +405 -0
  31. package/tools/secret_scan.py +73 -18
  32. package/tools/stall_check.py +247 -16
  33. package/tools/stateapi_lint.py +325 -0
  34. package/tools/test_battery.py +173 -0
  35. package/tools/verify_cost_panel.py +345 -0
  36. package/tools/wave_preflight.py +296 -29
  37. package/tools/wave_templates.py +170 -45
  38. package/ui/collectors.py +81 -0
  39. package/ui/handler.py +230 -85
  40. package/ui/sse.py +3 -3
  41. package/ui/wave_telemetry.py +24 -18
  42. package/ui/web/dist/assets/{index-DqZLgwNg.css → index-CNQxaiOW.css} +1 -1
  43. package/ui/web/dist/assets/index-CP68RIh3.js +9 -0
  44. package/ui/web/dist/index.html +2 -2
  45. package/docs/QUICKSTART.md +0 -80
  46. package/ui/web/dist/assets/index-BGbgw2Nh.js +0 -9
@@ -0,0 +1,193 @@
1
+ param(
2
+ [string]$BashExe = 'C:\Program Files\Git\bin\bash.exe',
3
+ [string]$WatchdogCommand = '',
4
+ [string]$MonitorCommand = '',
5
+ [int]$WatchdogIntervalMinutes = 5,
6
+ [int]$MonitorIntervalMinutes = 20,
7
+ [string]$TaskPrefix = 'Aesop',
8
+ [switch]$Uninstall,
9
+ [switch]$DryRun
10
+ )
11
+
12
+ # Enable strict error handling
13
+ $ErrorActionPreference = 'Stop'
14
+
15
+ function ConvertTo-PosixPath {
16
+ param([string]$WindowsPath)
17
+ # Convert C:\foo\bar to /c/foo/bar
18
+ # Rejects UNC paths (\\server\share) — error out instead of mangling
19
+ if ($WindowsPath -match '^\\\\') {
20
+ Write-Error "UNC paths are unsupported (got: $WindowsPath). Pass -WatchdogCommand explicitly with a valid path."
21
+ exit 1
22
+ }
23
+ $posixPath = $WindowsPath -replace '\\', '/'
24
+ $posixPath = $posixPath -replace '^([A-Za-z]):', '/$1'
25
+ return $posixPath
26
+ }
27
+
28
+ function Get-WorktreeRoot {
29
+ # Derive worktree root from $PSScriptRoot (daemons/)
30
+ # $PSScriptRoot is C:\...\aesop\daemons
31
+ # Parent is C:\...\aesop
32
+ $daemonsDir = $PSScriptRoot
33
+ $aesopRoot = Split-Path -Parent $daemonsDir
34
+ return $aesopRoot
35
+ }
36
+
37
+ function Register-DaemonTask {
38
+ param(
39
+ [string]$TaskName,
40
+ [string]$Command,
41
+ [int]$IntervalMinutes,
42
+ [string]$RunHiddenVbs,
43
+ [string]$BashExe
44
+ )
45
+
46
+ # Build the action: wscript.exe //B //Nologo "path\to\run-hidden.vbs" "<bash>" -lc "<command>"
47
+ $action = New-ScheduledTaskAction `
48
+ -Execute 'wscript.exe' `
49
+ -Argument "//B //Nologo ""$RunHiddenVbs"" ""$BashExe"" -lc ""$Command"""
50
+
51
+ # Build the trigger: Once, starting in 1 minute, repeating every N minutes for 10 years
52
+ $startTime = (Get-Date).AddMinutes(1)
53
+ $trigger = New-ScheduledTaskTrigger `
54
+ -Once `
55
+ -At $startTime `
56
+ -RepetitionInterval (New-TimeSpan -Minutes $IntervalMinutes) `
57
+ -RepetitionDuration (New-TimeSpan -Days 3650)
58
+
59
+ # Build the settings: Hidden, IgnoreNew for multiple instances, 1-hour timeout
60
+ $settings = New-ScheduledTaskSettingsSet `
61
+ -Hidden `
62
+ -MultipleInstances IgnoreNew `
63
+ -ExecutionTimeLimit (New-TimeSpan -Hours 1) `
64
+ -StartWhenAvailable
65
+
66
+ if ($DryRun) {
67
+ # Print DryRun output
68
+ Write-Host "DRYRUN: $TaskName -> wscript.exe //B //Nologo ""$RunHiddenVbs"" ""$BashExe"" -lc ""$Command"" (interval=$IntervalMinutes`m, Hidden=True)"
69
+ }
70
+ else {
71
+ # Register the task (force overwrite if exists)
72
+ try {
73
+ Register-ScheduledTask `
74
+ -TaskName $TaskName `
75
+ -Action $action `
76
+ -Trigger $trigger `
77
+ -Settings $settings `
78
+ -Force `
79
+ -ErrorAction Stop | Out-Null
80
+ Write-Host "Registered task: $TaskName (interval=$IntervalMinutes minutes)"
81
+ }
82
+ catch {
83
+ Write-Error "Failed to register task $TaskName : $_"
84
+ exit 1
85
+ }
86
+ }
87
+ }
88
+
89
+ function Unregister-DaemonTask {
90
+ param([string]$TaskName)
91
+
92
+ try {
93
+ $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
94
+ if ($task) {
95
+ Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction Stop
96
+ Write-Host "Unregistered task: $TaskName"
97
+ return $true
98
+ }
99
+ else {
100
+ Write-Host "Task not found: $TaskName (already unregistered or never existed)"
101
+ return $true
102
+ }
103
+ }
104
+ catch {
105
+ Write-Error "Failed to unregister $TaskName : $_"
106
+ return $false
107
+ }
108
+ }
109
+
110
+ function Main {
111
+ # Resolve paths
112
+ $aesopRoot = Get-WorktreeRoot
113
+ $runHiddenVbs = Join-Path $PSScriptRoot 'run-hidden.vbs'
114
+
115
+ # VALIDATION: Check for double quotes in commands (contract violation)
116
+ # This check runs early and always, before any operations
117
+ if ($WatchdogCommand -like '*"*') {
118
+ Write-Error "WatchdogCommand contains double quotes, which are not allowed (vbs launcher contract violation)."
119
+ exit 1
120
+ }
121
+ if ($MonitorCommand -like '*"*') {
122
+ Write-Error "MonitorCommand contains double quotes, which are not allowed (vbs launcher contract violation)."
123
+ exit 1
124
+ }
125
+
126
+ # PATH VALIDATION: Only enforce file existence checks if not in DryRun mode
127
+ # In DryRun, downgrade to warnings so preview works on machines without Git Bash
128
+ if (-not $DryRun) {
129
+ if (-not (Test-Path $runHiddenVbs)) {
130
+ Write-Error "run-hidden.vbs not found at: $runHiddenVbs"
131
+ exit 1
132
+ }
133
+ if (-not (Test-Path $BashExe)) {
134
+ Write-Error "bash.exe not found at: $BashExe"
135
+ exit 1
136
+ }
137
+ }
138
+ else {
139
+ if (-not (Test-Path $runHiddenVbs)) {
140
+ Write-Warning "run-hidden.vbs not found at: $runHiddenVbs (DryRun mode)"
141
+ }
142
+ if (-not (Test-Path $BashExe)) {
143
+ Write-Warning "bash.exe not found at: $BashExe (DryRun mode)"
144
+ }
145
+ }
146
+
147
+ # Handle Uninstall mode
148
+ if ($Uninstall) {
149
+ $watchdog_ok = Unregister-DaemonTask -TaskName "${TaskPrefix}WatchdogDaemon"
150
+ $monitor_ok = Unregister-DaemonTask -TaskName "${TaskPrefix}RefinementMonitor"
151
+ if (-not $watchdog_ok -or -not $monitor_ok) {
152
+ exit 1
153
+ }
154
+ exit 0
155
+ }
156
+
157
+ # Derive default commands if not provided
158
+ if (-not $WatchdogCommand) {
159
+ $posixRoot = ConvertTo-PosixPath $aesopRoot
160
+
161
+ # P2: Detect apostrophe in derived path (breaks bash syntax if not escaped)
162
+ if ($posixRoot -like "*'*") {
163
+ Write-Error "Repository path contains apostrophe, which would break the derived command: $posixRoot`nPass -WatchdogCommand explicitly."
164
+ exit 1
165
+ }
166
+
167
+ $WatchdogCommand = "bash '$posixRoot/daemons/run-watchdog.sh' --once >> '$posixRoot/state/cron-watchdog.log' 2>&1"
168
+ }
169
+
170
+ # Register watchdog task
171
+ $watchdogTaskName = "${TaskPrefix}WatchdogDaemon"
172
+ Register-DaemonTask `
173
+ -TaskName $watchdogTaskName `
174
+ -Command $WatchdogCommand `
175
+ -IntervalMinutes $WatchdogIntervalMinutes `
176
+ -RunHiddenVbs $runHiddenVbs `
177
+ -BashExe $BashExe
178
+
179
+ # Register monitor task if command provided
180
+ if ($MonitorCommand) {
181
+ $monitorTaskName = "${TaskPrefix}RefinementMonitor"
182
+ Register-DaemonTask `
183
+ -TaskName $monitorTaskName `
184
+ -Command $MonitorCommand `
185
+ -IntervalMinutes $MonitorIntervalMinutes `
186
+ -RunHiddenVbs $runHiddenVbs `
187
+ -BashExe $BashExe
188
+ }
189
+
190
+ exit 0
191
+ }
192
+
193
+ Main
@@ -0,0 +1,39 @@
1
+ ' run-hidden.vbs — VBScript launcher for Windows Scheduled Tasks
2
+ '
3
+ ' Purpose: Execute a command with a hidden console window (window style 0) and
4
+ ' propagate its exit code.
5
+ '
6
+ ' Usage:
7
+ ' wscript.exe //B //Nologo run-hidden.vbs <bash-exe> -lc "<command>"
8
+ '
9
+ ' The script rebuilds a quoted command line from WScript.Arguments and runs it
10
+ ' via WScript.Shell.Run with window style 0 (hidden). CRITICAL: The task
11
+ ' instance lives as long as the child process (shell.Run waits), so that
12
+ ' - MultipleInstances IgnoreNew can prevent concurrent runs
13
+ ' - ExecutionTimeLimit can kill hung processes
14
+ ' - LastTaskResult reflects the actual child exit code (not always 0)
15
+ '
16
+ ' By contract, arguments never contain double quotes.
17
+
18
+ Dim shell, cmd, i, arg
19
+ Dim windowStyle, rc
20
+
21
+ Set shell = CreateObject("WScript.Shell")
22
+
23
+ ' Build command line from arguments
24
+ cmd = ""
25
+ For i = 0 To WScript.Arguments.Count - 1
26
+ arg = WScript.Arguments(i)
27
+ If i > 0 Then cmd = cmd & " "
28
+ ' Arguments never contain quotes by contract; wrap in quotes for safety
29
+ cmd = cmd & """" & arg & """"
30
+ Next
31
+
32
+ ' Window style 0 = hidden; wait for process exit (True = wait)
33
+ windowStyle = 0
34
+
35
+ ' Execute the command and wait for it to complete, capturing exit code
36
+ rc = shell.Run(cmd, windowStyle, True)
37
+
38
+ ' Exit with the child process's exit code
39
+ WScript.Quit rc
@@ -1,82 +1,41 @@
1
- # Git Pre-Push Hook Installation Guide
1
+ # Git Pre-Push Hook Setup
2
2
 
3
- **Ship a hook, not a memo.** The pre-push policy hook is **auto-installed during scaffold** (see below). This guide explains customization, verification, and org-wide distribution.
4
-
5
- ## Security Model: Local Convenience Defense Only
6
-
7
- **IMPORTANT**: The pre-push hook is a **local-machine convenience defense** — it is **NOT cryptographic protection**. Any developer can bypass it with `git push --no-verify` or by editing/deleting `.git/hooks/pre-push`. The audit log (`SECURITY-AUDIT.log`) is stored locally and can also be edited by a user with file system access.
8
-
9
- **Real enforcement requires server-side branch protection.** See GitHub Configuration below.
3
+ **TL;DR**: The hook is **auto-installed during scaffold**. Real enforcement requires server-side GitHub branch protection.
10
4
 
11
5
  ## What the Hook Does
12
6
 
13
- `hooks/pre-push-policy.sh` enforces two checks at git push time:
7
+ `hooks/pre-push-policy.sh` enforces:
8
+ 1. **Branch Policy** — No direct pushes to `main`/`master` (feature branches only)
9
+ 2. **Secret Scan** — `tools/secret_scan.py --staged` blocks credentials
14
10
 
15
- 1. **Branch Policy**: Blocks direct pushes to `main` or `master` branches (feature branches only).
16
- 2. **Secret Scan**: Runs `tools/secret_scan.py --staged` to detect credentials before they reach the remote.
11
+ ## Security Model
17
12
 
18
- Both blocks append a JSON audit record to `state/SECURITY-AUDIT.log` with timestamp, repo, reason, and user — creating a reviewable trail of policy enforcement.
13
+ **IMPORTANT**: Local hook is a **convenience defense only**—NOT cryptographic protection. Bypass with `git push --no-verify`.
19
14
 
20
- ## Auto-Installation During Scaffold
15
+ **Real enforcement**: Pair with GitHub branch protection (server-side). See below.
21
16
 
22
- **Default Behavior:** When you scaffold a new aesop fleet with `npx @matt82198/aesop [target-dir]`, the CLI automatically installs the pre-push hook into `.git/hooks/pre-push`.
17
+ ## Auto-Installation
23
18
 
24
- - **On Unix/macOS/Git Bash**: Creates a symlink so hook updates are automatic
25
- - **On Windows**: Copies the hook directly (symlinks don't work reliably on all NTFS setups)
26
- - **Idempotent**: Re-running scaffold doesn't clobber a user-customized hook
27
- - **Preserve Existing**: If you have a different pre-push hook, scaffold warns and preserves it
28
- - **Force Replace**: Use `npx @matt82198/aesop [target-dir] --force` to replace any existing hook
29
-
30
- **Example:**
19
+ Scaffold auto-installs the hook (symlink on Unix, copy on Windows):
31
20
 
32
21
  ```bash
33
- # Initial scaffold (creates and installs hook automatically)
34
- npx @matt82198/aesop my-fleet
35
-
36
- # Later: re-scaffold the same directory (preserves customizations)
37
22
  npx @matt82198/aesop my-fleet
38
-
39
- # Force replace (even if hook was customized)
40
- npx @matt82198/aesop my-fleet --force
41
23
  ```
42
24
 
43
- ## Manual Installation
44
-
45
- If you're installing into an existing repo (not scaffolded), or you need to manually add the hook:
46
-
47
- ### Option 1: Symlink (Linux / macOS / Git Bash on Windows)
48
-
49
- The cleanest method — hook stays in sync with repo updates:
50
-
25
+ Manual install:
51
26
  ```bash
27
+ # Option 1: Symlink (Unix/macOS/Git Bash)
52
28
  ln -s ../../hooks/pre-push-policy.sh .git/hooks/pre-push
53
- chmod +x .git/hooks/pre-push
54
- ```
55
-
56
- ### Option 2: Copy (Windows, or to break sync)
57
-
58
- Copy the hook directly into `.git/hooks/`:
59
-
60
- ```powershell
61
- Copy-Item hooks\pre-push-policy.sh .git\hooks\pre-push
62
- ```
63
29
 
64
- On Windows PowerShell, mark it executable if your git respects file mode:
65
-
66
- ```bash
67
- git config core.filemode false
30
+ # Option 2: Copy (Windows)
31
+ cp hooks/pre-push-policy.sh .git/hooks/pre-push
68
32
  ```
69
33
 
70
- ## Testing
71
-
72
- Before committing to org-wide deployment, verify the hook works:
73
-
34
+ Test:
74
35
  ```bash
75
36
  bash hooks/pre-push-policy.sh --test
76
37
  ```
77
38
 
78
- Expected output: **PASS** for all three checks (branch policy, feature branch allowance, audit log format).
79
-
80
39
  ## GitHub Configuration (Server-Side Enforcement)
81
40
 
82
41
  To pair this local hook with real enforcement, enable branch protection on GitHub:
package/docs/INSTALL.md CHANGED
@@ -283,11 +283,56 @@ To bypass during testing: `git push --no-verify` (not recommended for production
283
283
 
284
284
  ---
285
285
 
286
+ ## Windows: Register Daemons as Hidden Scheduled Tasks
287
+
288
+ On Windows, the watchdog and refinement monitor daemons can run silently in the background without flashing a console window. Use the provided PowerShell installer:
289
+
290
+ ```powershell
291
+ # Register watchdog daemon (every 5m)
292
+ powershell -NoProfile -ExecutionPolicy Bypass -File daemons/install-tasks.ps1
293
+
294
+ # Register both watchdog and monitor daemons (monitor script is external, customize path as needed)
295
+ powershell -NoProfile -ExecutionPolicy Bypass -File daemons/install-tasks.ps1 `
296
+ -MonitorCommand "bash '/c/path/to/your/monitor/run-monitor.sh' --once"
297
+
298
+ # Customize intervals and task names
299
+ powershell -NoProfile -ExecutionPolicy Bypass -File daemons/install-tasks.ps1 `
300
+ -TaskPrefix MyFleet `
301
+ -WatchdogIntervalMinutes 10 `
302
+ -MonitorIntervalMinutes 30 `
303
+ -MonitorCommand "bash '/c/path/to/your/monitor/run-monitor.sh' --once"
304
+
305
+ # Uninstall tasks
306
+ powershell -NoProfile -ExecutionPolicy Bypass -File daemons/install-tasks.ps1 -Uninstall
307
+
308
+ # Preview without registering (dry-run mode)
309
+ powershell -NoProfile -ExecutionPolicy Bypass -File daemons/install-tasks.ps1 -DryRun
310
+ ```
311
+
312
+ **How it works**: The installer creates Scheduled Tasks that launch `wscript.exe` with a hidden VBScript launcher (`daemons/run-hidden.vbs`). This avoids the console window that appears when bash.exe is run directly as a Scheduled Task action.
313
+
314
+ **Parameters**:
315
+ - `-TaskPrefix AesopMyFleet` — Task names: `AesopMyFleetWatchdogDaemon`, `AesopMyFleetRefinementMonitor` (default: `Aesop`)
316
+ - `-WatchdogIntervalMinutes N` — Watchdog cycle interval in minutes (default: 5)
317
+ - `-MonitorIntervalMinutes N` — Monitor cycle interval in minutes (default: 20)
318
+ - `-WatchdogCommand "bash '...' ..."` — Custom watchdog command (default: `run-watchdog.sh --once >> state/cron-watchdog.log`)
319
+ - `-MonitorCommand "bash '...' ..."` — Custom monitor command; omit to skip registering the monitor task (default: empty)
320
+ - `-Uninstall` — Remove all registered tasks
321
+ - `-DryRun` — Preview task configuration without registering
322
+
323
+ **Constraints**:
324
+ - Commands (`-WatchdogCommand`, `-MonitorCommand`) must NOT contain double quotes (vbs launcher contract)
325
+ - UNC paths (e.g., `\\server\share`) are not supported; use local Windows or POSIX paths only
326
+ - `-DryRun` mode works even if `bash.exe` or run-hidden.vbs is missing (validation downgraded to warnings for preview)
327
+
328
+ ---
329
+
286
330
  ## Next Steps
287
331
 
288
- 1. **Read [CONFIGURE.md](CONFIGURE.md)** — Customize repos, ports, and brain root
289
- 2. **Run [FIRST-WAVE.md](FIRST-WAVE.md)** — Test a full `/power` `/buildsystem` cycle
290
- 3. **Understand [CONCEPTS.md](CONCEPTS.md)** — Learn the dispatch model and state model
332
+ 1. **Read [PORTING.md](PORTING.md)** — Step-by-step guide for adopting Aesop on a foreign repo (10 common failure modes)
333
+ 2. **Read [CONFIGURE.md](CONFIGURE.md)** — Customize repos, ports, and brain root
334
+ 3. **Run [FIRST-WAVE.md](FIRST-WAVE.md)** — Test a full `/power` `/buildsystem` cycle
335
+ 4. **Understand [CONCEPTS.md](CONCEPTS.md)** — Learn the dispatch model and state model
291
336
  4. **Explore the dashboard** — `python3 ui/serve.py` then open http://localhost:8770
292
337
 
293
338
  For troubleshooting, see the [Aesop README](../README.md#troubleshooting) or [GOVERNANCE.md](GOVERNANCE.md) for operational policies.
@@ -0,0 +1,166 @@
1
+ # Porting Aesop to Your Repository
2
+
3
+ Guide for adopters porting the orchestration harness to a foreign repo. Step-by-step with prerequisites, scaffold, config, and the 10 likeliest failure modes from real deployments.
4
+
5
+ ---
6
+
7
+ ## Prerequisites
8
+
9
+ Ensure you have:
10
+ - **Node.js** ≥18 (from `package.json`, `.nvmrc`: v18.20.5)
11
+ - **Python** ≥3.10 (for daemons, health checks, secret-scan)
12
+ - **Git** ≥2.40 (worktree + pre-push hooks)
13
+ - **Bash** v4+ (or Git Bash on Windows)
14
+ - **Optional**: Playwright (for UI verification testing); jq (for dashboard JSON)
15
+
16
+ Check versions:
17
+ ```bash
18
+ node --version && python3 --version && git --version && bash --version
19
+ ```
20
+
21
+ ### OS Notes
22
+ - **Windows**: Use Git Bash for all shell commands; paths use `/c/Users/...` POSIX style
23
+ - **Linux/macOS**: Standard Bash; ensure `/usr/bin/bash` or equivalent exists
24
+
25
+ ---
26
+
27
+ ## 1. Scaffold & Install
28
+
29
+ ### Step 1: Create your harness
30
+ ```bash
31
+ npx @matt82198/aesop my-fleet \
32
+ --name "my-project" \
33
+ --repos "/path/to/repo1,/path/to/repo2"
34
+ ```
35
+
36
+ Creates `my-fleet/` with daemons, skills, config, and UI.
37
+
38
+ ### Step 2: Copy skills to Claude Code home
39
+ ```bash
40
+ cp -r my-fleet/skills/power ~/.claude/skills/power
41
+ cp -r my-fleet/skills/buildsystem ~/.claude/skills/buildsystem
42
+ ```
43
+
44
+ ### Step 3: Configure & test
45
+ Edit `my-fleet/aesop.config.json`:
46
+ ```json
47
+ {
48
+ "backend": "claude",
49
+ "aesop_root": "/path/to/my-fleet",
50
+ "brain_root": "~/.claude",
51
+ "repos": [
52
+ { "path": "/path/to/repo1", "name": "my-project" }
53
+ ],
54
+ "dashboardPort": 8770
55
+ }
56
+ ```
57
+
58
+ Test dashboard backend (Ollama example):
59
+ ```json
60
+ {
61
+ "backend": "openai-compatible",
62
+ "base_url": "http://localhost:11434/v1",
63
+ "model": "mistral",
64
+ "is_local": true
65
+ }
66
+ ```
67
+
68
+ ### Step 4: Install pre-push hook
69
+ ```bash
70
+ mkdir -p my-fleet/.git/hooks
71
+ cp my-fleet/hooks/pre-push-policy.sh my-fleet/.git/hooks/pre-push
72
+ chmod +x my-fleet/.git/hooks/pre-push
73
+ ```
74
+
75
+ ### Step 5: Verify with health check
76
+ ```bash
77
+ cd my-fleet
78
+ python tools/health_score.py
79
+ ```
80
+ Expected output: All checks ✓; port 8770 available; git hooks installed.
81
+
82
+ ---
83
+
84
+ ## 2. First `/power`, First Wave
85
+
86
+ Run the orchestrator once to prime the brain:
87
+ ```bash
88
+ cd my-fleet
89
+ /power
90
+ ```
91
+ Expected output: State created (STATE.md, BUILDLOG.md, .watchdog-heartbeat).
92
+
93
+ Run one complete wave:
94
+ ```bash
95
+ /buildsystem --one-turn
96
+ ```
97
+ Expected verdicts: All items "PASS" or "REVIEW"; zero code defects. Health score should report ≥85/100.
98
+
99
+ ---
100
+
101
+ ## 3. The 10 Likeliest Failure Modes
102
+
103
+ Each: symptom → cause → fix.
104
+
105
+ ### 1. Secret-scan blocks legit push
106
+ - **Symptom**: `git push` fails: "secret detected" but code has no real secret
107
+ - **Cause**: Test fixtures with dummy secrets (credit card, API key patterns) are parsed as real
108
+ - **Fix**: Assemble secrets at runtime: `"key" + "_" + "123"` not `"key_123"`; use `---` delimiters in comments. Secret-scan only scans staged files, not .git.
109
+
110
+ ### 2. Worktree isolation violated
111
+ - **Symptom**: Editing feature branch X; changes also appear in primary tree
112
+ - **Cause**: Agents not using isolated worktrees; sharing same `.git/index`
113
+ - **Fix**: Each agent task must use `git worktree add ../wt-item-slug -b feature/item-slug origin/main`; verify with `git worktree list`
114
+
115
+ ### 3. Heartbeat stale/missing
116
+ - **Symptom**: Watchdog won't start; error: "unreadable state dir" or "stale heartbeat"
117
+ - **Cause**: `state/.watchdog-heartbeat` missing/corrupted or directory unreadable (permissions)
118
+ - **Fix**: `rm -f state/.watchdog-heartbeat && bash daemons/run-watchdog.sh --once` (re-creates it)
119
+
120
+ ### 4. Port 8770 conflict
121
+ - **Symptom**: `python ui/serve.py` fails: "Address already in use"
122
+ - **Cause**: Old dashboard process still bound, or another service using port 8770
123
+ - **Fix**: `lsof -i :8770 | grep -v PID | awk '{print $2}' | xargs kill -9` (Mac/Linux); Windows: `netstat -ano | grep 8770`, kill the PID. Or change `dashboardPort` in config.
124
+
125
+ ### 5. Git identity placeholder
126
+ - **Symptom**: Pre-push hook fails: "user identity invalid"; commits fail
127
+ - **Cause**: Git config has template defaults (e.g., "John Doe") instead of your name
128
+ - **Fix**: `git config user.name "Your Name" && git config user.email "you@example.com"`
129
+
130
+ ### 6. CRLF line endings corrupt scripts
131
+ - **Symptom**: Bash script fails: "command not found" at line 50 (but line 50 exists)
132
+ - **Cause**: Editor/Windows converted LF to CRLF; bash reads `\r` as part of command
133
+ - **Fix**: `git config core.autocrlf false` locally; convert file: `dos2unix daemons/run-watchdog.sh`
134
+
135
+ ### 7. Test count drift in CI
136
+ - **Symptom**: Health-check fails: "expected 127 tests, got 128"
137
+ - **Cause**: Added new test file but didn't update baseline in tools/health_score.py or CI config
138
+ - **Fix**: Run `python tools/health_score.py --json` to get true count; update baseline in CI gate
139
+
140
+ ### 8. UTF-8 encoding on Windows
141
+ - **Symptom**: Pre-publish gate fails with encoding error in secret_scan.py
142
+ - **Cause**: Python opens files in system encoding (cp1252) instead of UTF-8
143
+ - **Fix**: Aesop tools now force UTF-8 internally; ensure `PYTHONIOENCODING=utf-8` if running external Python scripts
144
+
145
+ ### 9. Cost ceiling never triggers
146
+ - **Symptom**: Cost ceilings configured; spending uncapped; halt never fires
147
+ - **Cause**: Claude Code driver returns `get_tokens_spent()=None`; coerces to 0; ledger fallback skipped
148
+ - **Fix**: For Claude Code, cost tracking integrates live API; for non-Claude backends, ledger fallback logs at end of wave
149
+
150
+ ### 10. Hook TTY behavior blocks CI pushes
151
+ - **Symptom**: `git push` fails in CI/cron: "cannot read stdin"; interactive prompt hangs
152
+ - **Cause**: Hook tries to detect TTY when stdin is `/dev/null` (non-interactive context)
153
+ - **Fix**: Hook now handles empty stdin (rc=2 for delete-only, skip scan + log); use `hooks/pre-push-policy.sh --test` in CI (no-op validation)
154
+
155
+ ---
156
+
157
+ ## Next Steps
158
+
159
+ 1. **Read [INSTALL.md](./INSTALL.md)** — Full setup and environment variables
160
+ 2. **Run [health_score.py](../tools/health_score.py)** — Continuous readiness checks
161
+ 3. **Explore the [README](../README.md)** — Demo walkthrough and proof numbers
162
+ 4. **For troubleshooting**, check [GOVERNANCE.md](./GOVERNANCE.md) or open an issue on GitHub
163
+
164
+ ---
165
+
166
+ **License**: PolyForm Strict 1.0.0 (source-available, noncommercial). See [LICENSE](../LICENSE) for details.