@matt82198/aesop 0.1.0 → 0.3.1
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 +117 -2
- package/README.md +59 -218
- package/bin/cli.js +168 -41
- package/daemons/run-watchdog.sh +16 -4
- package/daemons/selfheal.sh +231 -0
- package/docs/ANY-REPO.md +427 -0
- package/docs/CONTRIBUTING.md +72 -0
- package/docs/DEMO.md +334 -0
- package/docs/HOOK-INSTALL.md +15 -56
- package/docs/INSTALL.md +74 -4
- package/docs/PORTING.md +166 -0
- package/docs/README.md +33 -3
- package/docs/TEAM-STATE.md +372 -0
- package/docs/reproduce.md +33 -3
- package/driver/CLAUDE.md +150 -0
- package/driver/README.md +383 -0
- package/driver/aesop.config.example.json +80 -0
- package/driver/agent_driver.py +355 -0
- package/driver/backend_config.py +253 -0
- package/driver/claude_code_driver.py +198 -0
- package/driver/codex_driver.py +673 -0
- package/driver/openai_compatible_driver.py +249 -0
- package/driver/openai_transport.py +179 -0
- package/driver/verification_policy.py +75 -0
- package/driver/wave_bridge.py +254 -0
- package/driver/wave_loop.py +1408 -0
- package/driver/wave_scheduler.py +890 -0
- package/hooks/pre-push-policy.sh +131 -33
- package/mcp/server.mjs +320 -4
- package/monitor/collect-signals.mjs +69 -0
- package/package.json +22 -14
- package/skills/CLAUDE.md +132 -2
- package/skills/buildsystem/SKILL.md +330 -0
- package/skills/buildsystem/wave-flat-dispatch.template.mjs +658 -0
- package/skills/fleet/SKILL.md +113 -0
- package/skills/power/SKILL.md +246 -131
- package/state_store/__init__.py +4 -2
- package/state_store/api.py +19 -4
- package/state_store/coordination.py +209 -0
- package/state_store/identity.py +51 -0
- package/state_store/projections.py +63 -0
- package/state_store/read_api.py +156 -0
- package/state_store/store.py +185 -73
- package/state_store/write_api.py +462 -0
- package/templates/wave-presets/data.json +65 -0
- package/templates/wave-presets/library.json +65 -0
- package/templates/wave-presets/saas.json +64 -0
- package/tools/audit_report.py +388 -0
- package/tools/bench_runner.py +100 -3
- package/tools/ci_merge_wait.py +256 -35
- package/tools/ci_workflow_lint.py +430 -0
- package/tools/claudemd_drift.py +394 -0
- package/tools/claudemd_lint.py +359 -0
- package/tools/common.py +39 -3
- package/tools/cost_ceiling.py +166 -43
- package/tools/cost_econ.py +480 -0
- package/tools/cost_projection.py +559 -0
- package/tools/crossos_drift.py +394 -0
- package/tools/defect_escape.py +252 -0
- package/tools/doctor.js +1 -1
- package/tools/eod_sweep.py +188 -26
- package/tools/fleet.js +260 -0
- package/tools/fleet_ledger.py +209 -7
- package/tools/git_identity_check.py +315 -0
- package/tools/health-score.js +40 -0
- package/tools/health_score.py +361 -0
- package/tools/metrics_gate.py +13 -4
- package/tools/mutation_test.py +523 -0
- package/tools/portability_check.py +206 -0
- package/tools/proposals.mjs +47 -2
- package/tools/reconcile.py +7 -4
- package/tools/reproduce.js +405 -0
- package/tools/secret_scan.py +207 -65
- package/tools/self_stats.py +20 -0
- package/tools/stall_check.py +247 -16
- package/tools/stateapi_lint.py +325 -0
- package/tools/test_battery.py +173 -0
- package/tools/transcript_digest.py +380 -0
- package/tools/verify_activity_filter.py +437 -0
- package/tools/verify_agent_inspector.py +2 -0
- package/tools/verify_cost_panel.py +345 -0
- package/tools/verify_dash.py +2 -0
- package/tools/verify_dispatch_panel.py +301 -0
- package/tools/verify_failure_drilldown.py +188 -0
- package/tools/verify_prboard.py +2 -0
- package/tools/verify_scorecards.py +281 -0
- package/tools/verify_submit_encoding.py +2 -0
- package/tools/verify_ui_trio.py +409 -0
- package/tools/verify_wave_telemetry.py +268 -0
- package/tools/wave_backlog_analyzer.py +490 -0
- package/tools/wave_ledger_hook.py +150 -0
- package/tools/wave_preflight.py +779 -0
- package/tools/wave_resume.py +215 -0
- package/tools/wave_templates.py +340 -0
- package/ui/agents.py +68 -14
- package/ui/collectors.py +81 -55
- package/ui/config.py +7 -2
- package/ui/cost.py +231 -12
- package/ui/handler.py +383 -55
- package/ui/quality_scorecard.py +232 -0
- package/ui/sse.py +3 -3
- package/ui/wave_audit_tail.py +213 -0
- package/ui/wave_dispatch.py +280 -0
- package/ui/wave_failure.py +288 -0
- package/ui/wave_gantt.py +152 -0
- package/ui/wave_reasoning_tail.py +176 -0
- package/ui/wave_telemetry.py +383 -0
- package/ui/web/dist/assets/index-CNQxaiOW.css +1 -0
- package/ui/web/dist/assets/index-CP68RIh3.js +9 -0
- package/ui/web/dist/index.html +2 -2
- package/bin/CLAUDE.md +0 -76
- package/daemons/CLAUDE.md +0 -36
- package/dash/CLAUDE.md +0 -32
- package/docs/archive/README.md +0 -3
- package/docs/archive/spikes/tiered-cognition/ACTIVATION.md +0 -125
- package/docs/archive/spikes/tiered-cognition/DESIGN.md +0 -287
- package/docs/archive/spikes/tiered-cognition/FINDINGS.md +0 -113
- package/docs/archive/spikes/tiered-cognition/README.md +0 -27
- package/docs/archive/spikes/tiered-cognition/aesop-cognition.example.md +0 -32
- package/docs/archive/spikes/tiered-cognition/force-model-policy.merged.mjs +0 -673
- package/docs/archive/spikes/tiered-cognition/strip-tools-hook.mjs +0 -434
- package/hooks/CLAUDE.md +0 -89
- package/mcp/CLAUDE.md +0 -213
- package/monitor/CLAUDE.md +0 -40
- package/scan/CLAUDE.md +0 -30
- package/state_store/CLAUDE.md +0 -39
- package/tools/CLAUDE.md +0 -79
- package/ui/CLAUDE.md +0 -127
- package/ui/web/dist/assets/index-0qQYnvMC.js +0 -9
- package/ui/web/dist/assets/index-BdIlFieV.css +0 -1
package/tools/cost_ceiling.py
CHANGED
|
@@ -14,19 +14,33 @@ Configuration (aesop.config.json):
|
|
|
14
14
|
|
|
15
15
|
Spend source: an explicit --spent/spent= figure always wins. Otherwise spend
|
|
16
16
|
is read from the cost ledger (tools/fleet_ledger.py's OUTCOMES-LEDGER.md under
|
|
17
|
-
the resolved state dir): sum of tokens_in + tokens_out across all ledger rows
|
|
17
|
+
the resolved state dir): sum of tokens_in + tokens_out across all ledger rows
|
|
18
|
+
within the specified window (or all rows if no window specified).
|
|
18
19
|
Missing/unreadable ledger -> spend of 0 (never trips on a ledger that doesn't
|
|
19
20
|
exist yet).
|
|
20
21
|
|
|
22
|
+
WINDOW CONTRACT (shared with cost_projection.py):
|
|
23
|
+
Both tools filter ledger rows by the SAME window parameters to ensure
|
|
24
|
+
consistent spend calculations. The helper calculate_window_bounds() is
|
|
25
|
+
imported by both modules to ensure identical window calculations.
|
|
26
|
+
|
|
27
|
+
When both cost_projection.project(window_minutes=W) and cost_ceiling.check(window_minutes=W)
|
|
28
|
+
are called with the same W value, they will produce identical spend figures.
|
|
29
|
+
|
|
21
30
|
API:
|
|
22
|
-
check(spent=None, period="wave", config=None, state_dir=None, trip=True
|
|
31
|
+
check(spent=None, period="wave", config=None, state_dir=None, trip=True,
|
|
32
|
+
window_minutes=None) -> dict
|
|
23
33
|
Returns {"period", "ceiling", "spent", "exceeded", "tripped"}.
|
|
24
34
|
When exceeded and trip=True, calls tools/halt.py's halt() with a reason
|
|
25
35
|
describing the breach, and "tripped" is True. When ceiling is None
|
|
26
36
|
(unconfigured), exceeded is always False and nothing is ever tripped.
|
|
27
37
|
|
|
38
|
+
window_minutes: Optional time window in minutes for ledger filtering.
|
|
39
|
+
If None, uses all ledger rows (backward compat). If specified, only rows
|
|
40
|
+
within the window contribute to spend calculation.
|
|
41
|
+
|
|
28
42
|
CLI:
|
|
29
|
-
python tools/cost_ceiling.py --check --spent N [--period wave|daily]
|
|
43
|
+
python tools/cost_ceiling.py --check --spent N [--period wave|daily] [--window MINUTES]
|
|
30
44
|
Exit 0 if not exceeded (or ceiling unconfigured), exit 1 if exceeded
|
|
31
45
|
(and thus tripped, unless already halted).
|
|
32
46
|
"""
|
|
@@ -35,7 +49,7 @@ import argparse
|
|
|
35
49
|
import json
|
|
36
50
|
import sys
|
|
37
51
|
from pathlib import Path
|
|
38
|
-
from datetime import datetime, timezone
|
|
52
|
+
from datetime import datetime, timezone, timedelta
|
|
39
53
|
|
|
40
54
|
try:
|
|
41
55
|
import halt
|
|
@@ -45,6 +59,26 @@ except ImportError:
|
|
|
45
59
|
from tools import fleet_ledger
|
|
46
60
|
|
|
47
61
|
|
|
62
|
+
def calculate_window_bounds(window_minutes):
|
|
63
|
+
"""Calculate UTC window start and end times for ledger filtering.
|
|
64
|
+
|
|
65
|
+
Shared contract between cost_projection.py and cost_ceiling.py to ensure
|
|
66
|
+
consistent ledger windowing. Both tools import and use this helper to
|
|
67
|
+
guarantee agreement on spend figures when using the same window.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
window_minutes: Time window in minutes (e.g., 30 = last 30 minutes)
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Tuple of (window_start_utc, window_end_utc) as datetime objects with UTC timezone.
|
|
74
|
+
window_end_utc is always datetime.now(timezone.utc).
|
|
75
|
+
window_start_utc is window_end_utc minus window_minutes.
|
|
76
|
+
"""
|
|
77
|
+
now_utc = datetime.now(timezone.utc)
|
|
78
|
+
window_start = now_utc - timedelta(minutes=window_minutes)
|
|
79
|
+
return window_start, now_utc
|
|
80
|
+
|
|
81
|
+
|
|
48
82
|
def load_config():
|
|
49
83
|
"""Load aesop.config.json from current directory, return dict (or {} if absent/bad)."""
|
|
50
84
|
config_file = Path("aesop.config.json")
|
|
@@ -73,15 +107,22 @@ def get_ceiling(config, period):
|
|
|
73
107
|
return None
|
|
74
108
|
|
|
75
109
|
|
|
76
|
-
def read_ledger_total_tokens(state_dir, period="wave"):
|
|
77
|
-
"""Sum tokens_in + tokens_out from OUTCOMES-LEDGER.md.
|
|
110
|
+
def read_ledger_total_tokens(state_dir, period="wave", window_minutes=None):
|
|
111
|
+
"""Sum tokens_in + tokens_out from OUTCOMES-LEDGER.md with optional windowing.
|
|
78
112
|
|
|
79
113
|
Args:
|
|
80
114
|
state_dir: path to state directory
|
|
81
115
|
period: "wave" (all rows) or "daily" (today's rows only, filtered by UTC date)
|
|
116
|
+
window_minutes: Optional time window in minutes. If specified, only rows
|
|
117
|
+
within the last window_minutes are included. Default None
|
|
118
|
+
means include all rows (backward compat).
|
|
82
119
|
|
|
83
120
|
Returns 0 if the ledger doesn't exist or is unreadable/empty.
|
|
84
121
|
Uses fleet_ledger.py's shared parser (single source of truth).
|
|
122
|
+
|
|
123
|
+
Window contract: When both cost_projection and cost_ceiling use the same
|
|
124
|
+
window_minutes value, they produce identical spend figures (single source
|
|
125
|
+
of truth is the ledger parse result, and the same filtering is applied).
|
|
85
126
|
"""
|
|
86
127
|
# Set the state root temporarily so fleet_ledger can find the ledger
|
|
87
128
|
import os
|
|
@@ -98,6 +139,12 @@ def read_ledger_total_tokens(state_dir, period="wave"):
|
|
|
98
139
|
if not rows:
|
|
99
140
|
return 0
|
|
100
141
|
|
|
142
|
+
# Calculate window bounds if windowing is requested
|
|
143
|
+
if window_minutes is not None:
|
|
144
|
+
window_start, window_end = calculate_window_bounds(window_minutes)
|
|
145
|
+
else:
|
|
146
|
+
window_start = None # No window filtering
|
|
147
|
+
|
|
101
148
|
total = 0
|
|
102
149
|
|
|
103
150
|
if period == "daily":
|
|
@@ -107,56 +154,125 @@ def read_ledger_total_tokens(state_dir, period="wave"):
|
|
|
107
154
|
# Extract date from ISO timestamp (format: YYYY-MM-DDTHH:MM:SSZ or similar)
|
|
108
155
|
try:
|
|
109
156
|
iso_ts = row['iso_ts']
|
|
110
|
-
# Parse
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
157
|
+
# Parse ISO timestamp
|
|
158
|
+
ts_clean = iso_ts.replace('Z', '+00:00') if 'Z' in iso_ts else iso_ts
|
|
159
|
+
row_dt = datetime.fromisoformat(ts_clean)
|
|
160
|
+
if row_dt.tzinfo is None:
|
|
161
|
+
row_dt = row_dt.replace(tzinfo=timezone.utc)
|
|
162
|
+
|
|
163
|
+
# Check date filter
|
|
164
|
+
row_date = row_dt.date()
|
|
165
|
+
if row_date != today_utc:
|
|
166
|
+
continue
|
|
167
|
+
|
|
168
|
+
# Check window filter (if specified)
|
|
169
|
+
if window_start is not None and row_dt < window_start:
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
total += row['tokens_in'] + row['tokens_out']
|
|
173
|
+
except (ValueError, IndexError, KeyError, AttributeError):
|
|
115
174
|
# Skip malformed timestamps
|
|
116
175
|
continue
|
|
117
176
|
else:
|
|
118
|
-
# period == "wave": sum all
|
|
177
|
+
# period == "wave": sum rows within window (or all if no window)
|
|
119
178
|
for row in rows:
|
|
179
|
+
# Check window filter (if specified)
|
|
180
|
+
if window_start is not None:
|
|
181
|
+
try:
|
|
182
|
+
iso_ts = row['iso_ts']
|
|
183
|
+
ts_clean = iso_ts.replace('Z', '+00:00') if 'Z' in iso_ts else iso_ts
|
|
184
|
+
row_dt = datetime.fromisoformat(ts_clean)
|
|
185
|
+
if row_dt.tzinfo is None:
|
|
186
|
+
row_dt = row_dt.replace(tzinfo=timezone.utc)
|
|
187
|
+
|
|
188
|
+
if row_dt < window_start:
|
|
189
|
+
continue
|
|
190
|
+
except (ValueError, AttributeError):
|
|
191
|
+
# Skip malformed timestamps
|
|
192
|
+
continue
|
|
193
|
+
|
|
120
194
|
total += row['tokens_in'] + row['tokens_out']
|
|
121
195
|
|
|
122
196
|
return total
|
|
123
197
|
|
|
124
198
|
|
|
125
|
-
def check(spent=None, period="wave", config=None, state_dir=None, trip=True):
|
|
199
|
+
def check(spent=None, period="wave", config=None, state_dir=None, trip=True, window_minutes=None):
|
|
126
200
|
"""Check spend against the configured ceiling for `period`.
|
|
127
201
|
|
|
128
|
-
Returns a dict: {"period", "ceiling", "spent", "exceeded", "tripped"}.
|
|
129
|
-
"""
|
|
130
|
-
if config is None:
|
|
131
|
-
config = load_config()
|
|
132
|
-
if state_dir is None:
|
|
133
|
-
state_dir = halt.resolve_state_dir(config=config)
|
|
134
|
-
state_dir = Path(state_dir)
|
|
135
|
-
|
|
136
|
-
ceiling = get_ceiling(config, period)
|
|
137
|
-
|
|
138
|
-
if spent is None:
|
|
139
|
-
spent = read_ledger_total_tokens(state_dir, period=period)
|
|
140
|
-
spent = int(spent)
|
|
202
|
+
Returns a dict: {"period", "ceiling", "spent", "exceeded", "tripped", "reason"}.
|
|
141
203
|
|
|
142
|
-
|
|
143
|
-
|
|
204
|
+
Window contract: when cost_ceiling and cost_projection both pass the same
|
|
205
|
+
window_minutes value, they will compute identical spend figures from the
|
|
206
|
+
ledger, ensuring agreement on cost tracking.
|
|
144
207
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
208
|
+
Distinctions:
|
|
209
|
+
- Genuine ceiling breach (ceiling is configured, spent >= ceiling): when trip=True,
|
|
210
|
+
writes persistent .HALT sentinel via halt.halt() and sets tripped=True.
|
|
211
|
+
- Exception during spend computation (ledger read/create failure, etc.): signals
|
|
212
|
+
abort of current wave (exceeded=True) but does NOT write persistent sentinel
|
|
213
|
+
(tripped=False). This preserves fleet availability across transient I/O errors
|
|
214
|
+
(e.g., momentary file lock, disk full) while still aborting the current wave.
|
|
152
215
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
"
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
216
|
+
Args:
|
|
217
|
+
spent: Optional explicit spend figure in tokens. If provided, overrides ledger.
|
|
218
|
+
period: "wave" (all rows) or "daily" (today's rows only)
|
|
219
|
+
config: aesop.config.json dict, or None to load from disk
|
|
220
|
+
state_dir: path to state directory, or None to resolve from config
|
|
221
|
+
trip: If True and exceeded, trip the .HALT sentinel
|
|
222
|
+
window_minutes: Optional time window in minutes for ledger filtering.
|
|
223
|
+
If None, all ledger rows are included (backward compat).
|
|
224
|
+
When specified, only rows within the last window_minutes
|
|
225
|
+
are included. MUST match the window_minutes used in
|
|
226
|
+
cost_projection.project() to ensure agreement.
|
|
227
|
+
"""
|
|
228
|
+
try:
|
|
229
|
+
if config is None:
|
|
230
|
+
config = load_config()
|
|
231
|
+
if state_dir is None:
|
|
232
|
+
state_dir = halt.resolve_state_dir(config=config)
|
|
233
|
+
state_dir = Path(state_dir)
|
|
234
|
+
|
|
235
|
+
ceiling = get_ceiling(config, period)
|
|
236
|
+
|
|
237
|
+
if spent is None:
|
|
238
|
+
spent = read_ledger_total_tokens(state_dir, period=period, window_minutes=window_minutes)
|
|
239
|
+
spent = int(spent)
|
|
240
|
+
|
|
241
|
+
exceeded = ceiling is not None and spent >= ceiling
|
|
242
|
+
tripped = False
|
|
243
|
+
|
|
244
|
+
if exceeded and trip:
|
|
245
|
+
reason = (
|
|
246
|
+
f"cost ceiling exceeded: {period} spend {spent} tokens >= "
|
|
247
|
+
f"ceiling {ceiling} tokens"
|
|
248
|
+
)
|
|
249
|
+
halt.halt(reason, state_dir=state_dir)
|
|
250
|
+
tripped = True
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
"period": period,
|
|
254
|
+
"ceiling": ceiling,
|
|
255
|
+
"spent": spent,
|
|
256
|
+
"exceeded": exceeded,
|
|
257
|
+
"tripped": tripped,
|
|
258
|
+
"reason": None,
|
|
259
|
+
}
|
|
260
|
+
except Exception as e:
|
|
261
|
+
# Exception during spend computation (e.g., ledger read/create failure,
|
|
262
|
+
# file lock, transient I/O error). This is fail-SAFE: abort the current
|
|
263
|
+
# wave (exceeded=True) to prevent runaway work, but do NOT write the
|
|
264
|
+
# persistent .HALT sentinel (tripped=False). The distinction ensures a
|
|
265
|
+
# transient I/O hiccup does not permanently wedge the fleet.
|
|
266
|
+
reason_text = f"cost_check_error: {type(e).__name__}: {str(e)[:100]}"
|
|
267
|
+
return {
|
|
268
|
+
"period": period,
|
|
269
|
+
"ceiling": None,
|
|
270
|
+
"spent": None,
|
|
271
|
+
"exceeded": True,
|
|
272
|
+
"tripped": False,
|
|
273
|
+
"error": str(e),
|
|
274
|
+
"reason": reason_text,
|
|
275
|
+
}
|
|
160
276
|
|
|
161
277
|
|
|
162
278
|
def main(argv=None):
|
|
@@ -164,13 +280,20 @@ def main(argv=None):
|
|
|
164
280
|
parser.add_argument("--check", action="store_true", help="Run the ceiling check (required).")
|
|
165
281
|
parser.add_argument("--spent", type=int, default=None, help="Explicit spend figure in tokens; defaults to ledger total.")
|
|
166
282
|
parser.add_argument("--period", choices=("wave", "daily"), default="wave", help="Which ceiling to check against (default: wave).")
|
|
283
|
+
parser.add_argument("--window", type=int, default=None, help="Optional time window in minutes for ledger filtering; None means all rows (default, backward compat).")
|
|
167
284
|
args = parser.parse_args(argv)
|
|
168
285
|
|
|
169
286
|
if not args.check:
|
|
170
287
|
parser.print_usage(sys.stderr)
|
|
171
288
|
return 2
|
|
172
289
|
|
|
173
|
-
result = check(spent=args.spent, period=args.period)
|
|
290
|
+
result = check(spent=args.spent, period=args.period, window_minutes=args.window)
|
|
291
|
+
|
|
292
|
+
# Check for errors FIRST (fail-closed): exception during check() means abort the wave
|
|
293
|
+
if "error" in result:
|
|
294
|
+
reason = result.get("reason", result["error"])
|
|
295
|
+
print(f"[cost_ceiling] FATAL: {reason}", file=sys.stderr)
|
|
296
|
+
return 1
|
|
174
297
|
|
|
175
298
|
if result["ceiling"] is None:
|
|
176
299
|
print(f"[cost_ceiling] no {args.period} ceiling configured — skipping (spent={result['spent']})")
|