@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.
- package/CHANGELOG.md +39 -1
- package/README.md +60 -263
- package/bin/cli.js +7 -3
- package/daemons/install-tasks.ps1 +193 -0
- package/daemons/run-hidden.vbs +39 -0
- package/docs/HOOK-INSTALL.md +15 -56
- package/docs/INSTALL.md +48 -3
- package/docs/PORTING.md +166 -0
- package/docs/TEAM-STATE.md +16 -184
- package/docs/reproduce.md +33 -3
- package/driver/CLAUDE.md +50 -48
- package/driver/codex_driver.py +59 -12
- package/driver/openai_transport.py +33 -0
- package/driver/wave_bridge.py +9 -1
- package/driver/wave_loop.py +437 -70
- package/driver/wave_scheduler.py +890 -0
- package/hooks/pre-push-policy.sh +22 -5
- package/monitor/collect-signals.mjs +69 -0
- package/package.json +6 -4
- package/state_store/__init__.py +2 -1
- package/state_store/projections.py +63 -0
- package/state_store/read_api.py +156 -0
- package/state_store/write_api.py +462 -0
- package/tools/cost_ceiling.py +106 -15
- package/tools/cost_projection.py +559 -0
- package/tools/crossos_drift.py +394 -0
- package/tools/eod_sweep.py +137 -33
- package/tools/mutation_test.py +130 -8
- package/tools/proposals.mjs +47 -2
- package/tools/reproduce.js +405 -0
- package/tools/secret_scan.py +73 -18
- package/tools/stall_check.py +247 -16
- package/tools/stateapi_lint.py +325 -0
- package/tools/test_battery.py +173 -0
- package/tools/verify_cost_panel.py +345 -0
- package/tools/wave_preflight.py +296 -29
- package/tools/wave_templates.py +170 -45
- package/ui/collectors.py +81 -0
- package/ui/handler.py +230 -85
- package/ui/sse.py +3 -3
- package/ui/wave_telemetry.py +24 -18
- package/ui/web/dist/assets/{index-DqZLgwNg.css → index-CNQxaiOW.css} +1 -1
- package/ui/web/dist/assets/index-CP68RIh3.js +9 -0
- package/ui/web/dist/index.html +2 -2
- package/docs/QUICKSTART.md +0 -80
- 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
|
package/docs/HOOK-INSTALL.md
CHANGED
|
@@ -1,82 +1,41 @@
|
|
|
1
|
-
# Git Pre-Push Hook
|
|
1
|
+
# Git Pre-Push Hook Setup
|
|
2
2
|
|
|
3
|
-
**
|
|
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
|
|
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
|
-
|
|
16
|
-
2. **Secret Scan**: Runs `tools/secret_scan.py --staged` to detect credentials before they reach the remote.
|
|
11
|
+
## Security Model
|
|
17
12
|
|
|
18
|
-
|
|
13
|
+
**IMPORTANT**: Local hook is a **convenience defense only**—NOT cryptographic protection. Bypass with `git push --no-verify`.
|
|
19
14
|
|
|
20
|
-
|
|
15
|
+
**Real enforcement**: Pair with GitHub branch protection (server-side). See below.
|
|
21
16
|
|
|
22
|
-
|
|
17
|
+
## Auto-Installation
|
|
23
18
|
|
|
24
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 [
|
|
289
|
-
2. **
|
|
290
|
-
3. **
|
|
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.
|
package/docs/PORTING.md
ADDED
|
@@ -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.
|