@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/state_store/store.py
CHANGED
|
@@ -16,6 +16,60 @@ import sqlite3
|
|
|
16
16
|
import sys
|
|
17
17
|
import time
|
|
18
18
|
|
|
19
|
+
# Retry configuration for database lock contention (especially under parallel CI shards)
|
|
20
|
+
_MAX_DB_LOCK_RETRIES = 3
|
|
21
|
+
_DB_LOCK_RETRY_BASE_DELAY = 0.05 # 50ms base, exponential backoff
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ConcurrencyConflict(Exception):
|
|
25
|
+
"""Raised when an optimistic concurrency control check fails on append.
|
|
26
|
+
|
|
27
|
+
Signifies that the event stream's current version does not match the
|
|
28
|
+
expected version provided to append(), indicating that another writer
|
|
29
|
+
has extended the stream since the caller last read it. No event is
|
|
30
|
+
written when this exception is raised (fail-closed).
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
expected_version: The version the caller expected
|
|
34
|
+
actual_version: The version found in the database
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, expected_version: int, actual_version: int):
|
|
38
|
+
self.expected_version = expected_version
|
|
39
|
+
self.actual_version = actual_version
|
|
40
|
+
super().__init__(
|
|
41
|
+
f"Concurrency conflict: expected version {expected_version}, "
|
|
42
|
+
f"but stream is at version {actual_version}"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _retry_on_db_lock(func, max_retries=_MAX_DB_LOCK_RETRIES, base_delay=_DB_LOCK_RETRY_BASE_DELAY):
|
|
47
|
+
"""Retry a callable up to max_retries times if it hits 'database is locked' error.
|
|
48
|
+
|
|
49
|
+
Used for defense-in-depth when multiple EventStore instances or CI shards
|
|
50
|
+
briefly contend on WAL locks. Implements exponential backoff.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
func: A callable that may raise sqlite3.OperationalError('database is locked').
|
|
54
|
+
max_retries: Number of attempts (default 3).
|
|
55
|
+
base_delay: Base delay in seconds for exponential backoff (default 0.05s).
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
The return value of func().
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
sqlite3.OperationalError: If all retries are exhausted or non-lock error occurs.
|
|
62
|
+
"""
|
|
63
|
+
for attempt in range(max_retries):
|
|
64
|
+
try:
|
|
65
|
+
return func()
|
|
66
|
+
except sqlite3.OperationalError as e:
|
|
67
|
+
if "database is locked" not in str(e):
|
|
68
|
+
raise
|
|
69
|
+
if attempt == max_retries - 1:
|
|
70
|
+
raise
|
|
71
|
+
time.sleep(base_delay * (2 ** attempt)) # exponential: 0.05s, 0.1s, 0.2s
|
|
72
|
+
|
|
19
73
|
|
|
20
74
|
class EventStore:
|
|
21
75
|
"""Append-only event log stored at ``db_path``.
|
|
@@ -25,73 +79,128 @@ class EventStore:
|
|
|
25
79
|
``PRAGMA busy_timeout`` and assigns the per-stream version inside a
|
|
26
80
|
``BEGIN IMMEDIATE`` transaction, so the read-max-version-then-insert is
|
|
27
81
|
atomic and two writers can never collide or duplicate a version.
|
|
82
|
+
|
|
83
|
+
Implements defense-in-depth retry logic for 'database is locked' errors that
|
|
84
|
+
can occur under heavy parallel contention (e.g., CI shards).
|
|
28
85
|
"""
|
|
29
86
|
|
|
30
87
|
def __init__(self, db_path: str):
|
|
31
88
|
self.db_path = db_path
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
conn.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
89
|
+
|
|
90
|
+
def _init_db():
|
|
91
|
+
conn = sqlite3.connect(self.db_path)
|
|
92
|
+
try:
|
|
93
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
94
|
+
conn.execute("PRAGMA busy_timeout=5000")
|
|
95
|
+
conn.execute(
|
|
96
|
+
"""
|
|
97
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
98
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
99
|
+
ts REAL NOT NULL,
|
|
100
|
+
actor TEXT NOT NULL,
|
|
101
|
+
stream TEXT NOT NULL,
|
|
102
|
+
type TEXT NOT NULL,
|
|
103
|
+
payload TEXT NOT NULL,
|
|
104
|
+
version INTEGER NOT NULL
|
|
105
|
+
)
|
|
106
|
+
"""
|
|
46
107
|
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"CREATE INDEX IF NOT EXISTS idx_events_stream_version "
|
|
51
|
-
"ON events(stream, version)"
|
|
52
|
-
)
|
|
53
|
-
conn.execute(
|
|
54
|
-
"""
|
|
55
|
-
CREATE TABLE IF NOT EXISTS snapshots (
|
|
56
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
57
|
-
ts REAL NOT NULL,
|
|
58
|
-
stream TEXT NOT NULL,
|
|
59
|
-
event_version INTEGER NOT NULL,
|
|
60
|
-
projection TEXT NOT NULL,
|
|
61
|
-
checksum TEXT NOT NULL
|
|
108
|
+
conn.execute(
|
|
109
|
+
"CREATE INDEX IF NOT EXISTS idx_events_stream_version "
|
|
110
|
+
"ON events(stream, version)"
|
|
62
111
|
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
112
|
+
conn.execute(
|
|
113
|
+
"""
|
|
114
|
+
CREATE TABLE IF NOT EXISTS snapshots (
|
|
115
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
116
|
+
ts REAL NOT NULL,
|
|
117
|
+
stream TEXT NOT NULL,
|
|
118
|
+
event_version INTEGER NOT NULL,
|
|
119
|
+
projection TEXT NOT NULL,
|
|
120
|
+
checksum TEXT NOT NULL
|
|
121
|
+
)
|
|
122
|
+
"""
|
|
123
|
+
)
|
|
124
|
+
conn.execute(
|
|
125
|
+
"CREATE INDEX IF NOT EXISTS idx_snapshots_stream_version "
|
|
126
|
+
"ON snapshots(stream, event_version)"
|
|
127
|
+
)
|
|
128
|
+
conn.commit()
|
|
129
|
+
finally:
|
|
130
|
+
conn.close()
|
|
72
131
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
132
|
+
_retry_on_db_lock(_init_db)
|
|
133
|
+
|
|
134
|
+
def append(
|
|
135
|
+
self,
|
|
136
|
+
stream: str,
|
|
137
|
+
event_type: str,
|
|
138
|
+
payload: dict,
|
|
139
|
+
actor: str = "system",
|
|
140
|
+
expected_version: int | None = None,
|
|
141
|
+
) -> int:
|
|
142
|
+
"""Append one event to ``stream``; return its new per-stream version (1-based).
|
|
143
|
+
|
|
144
|
+
Supports optimistic concurrency control via the optional ``expected_version``
|
|
145
|
+
parameter. If provided, the append succeeds ONLY if the stream's current
|
|
146
|
+
maximum version equals ``expected_version``. If the versions do not match,
|
|
147
|
+
raises ConcurrencyConflict WITHOUT writing any event (fail-closed, atomic).
|
|
148
|
+
|
|
149
|
+
The version check and append are both performed under BEGIN IMMEDIATE so
|
|
150
|
+
they are atomic (no TOCTOU window).
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
stream: The stream name (e.g. "tracker", "claims")
|
|
154
|
+
event_type: The event type (e.g. "claim_requested", "claim_released")
|
|
155
|
+
payload: The event payload dict (will be JSON-serialized)
|
|
156
|
+
actor: The actor performing the append (default "system")
|
|
157
|
+
expected_version: Optional OCC check: if provided, append only if the
|
|
158
|
+
stream's current max version equals this value.
|
|
159
|
+
If None (default), OCC is disabled (backward-compatible).
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
The new per-stream version assigned to this event (1-based).
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
ConcurrencyConflict: If expected_version is provided and the stream's
|
|
166
|
+
current max version does not match. The exception
|
|
167
|
+
carries expected_version and actual_version for retry.
|
|
168
|
+
No event is written when this exception is raised.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
def _do_append():
|
|
172
|
+
conn = sqlite3.connect(self.db_path)
|
|
173
|
+
try:
|
|
174
|
+
conn.execute("PRAGMA busy_timeout=5000")
|
|
175
|
+
# BEGIN IMMEDIATE takes the write lock up front so the
|
|
176
|
+
# read-max-then-insert below is atomic under contention.
|
|
177
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
178
|
+
row = conn.execute(
|
|
179
|
+
"SELECT COALESCE(MAX(version), 0) FROM events WHERE stream = ?",
|
|
180
|
+
(stream,),
|
|
181
|
+
).fetchone()
|
|
182
|
+
current_version = row[0]
|
|
183
|
+
|
|
184
|
+
# Perform OCC check (if expected_version provided)
|
|
185
|
+
if expected_version is not None:
|
|
186
|
+
if current_version != expected_version:
|
|
187
|
+
# Mismatch: abort transaction and raise
|
|
188
|
+
conn.rollback()
|
|
189
|
+
raise ConcurrencyConflict(expected_version, current_version)
|
|
190
|
+
|
|
191
|
+
# OCC check passed or not enabled: proceed with append
|
|
192
|
+
version = current_version + 1
|
|
193
|
+
conn.execute(
|
|
194
|
+
"INSERT INTO events (ts, actor, stream, type, payload, version) "
|
|
195
|
+
"VALUES (?, ?, ?, ?, ?, ?)",
|
|
196
|
+
(time.time(), actor, stream, event_type, json.dumps(payload), version),
|
|
197
|
+
)
|
|
198
|
+
conn.commit()
|
|
199
|
+
return version
|
|
200
|
+
finally:
|
|
201
|
+
conn.close()
|
|
202
|
+
|
|
203
|
+
return _retry_on_db_lock(_do_append)
|
|
95
204
|
|
|
96
205
|
def read(self, stream: str) -> list:
|
|
97
206
|
"""Return all events for ``stream`` ascending by version (empty if none)."""
|
|
@@ -138,19 +247,22 @@ class EventStore:
|
|
|
138
247
|
event_version: the per-stream event version this snapshot was computed through
|
|
139
248
|
projection: the materialized state dict (e.g. the full tracker projection)
|
|
140
249
|
"""
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
250
|
+
def _do_save_snapshot():
|
|
251
|
+
conn = sqlite3.connect(self.db_path)
|
|
252
|
+
try:
|
|
253
|
+
conn.execute("PRAGMA busy_timeout=5000")
|
|
254
|
+
projection_json = json.dumps(projection, separators=(",", ":"), sort_keys=True)
|
|
255
|
+
checksum = hashlib.sha256(projection_json.encode()).hexdigest()
|
|
256
|
+
conn.execute(
|
|
257
|
+
"INSERT INTO snapshots (ts, stream, event_version, projection, checksum) "
|
|
258
|
+
"VALUES (?, ?, ?, ?, ?)",
|
|
259
|
+
(time.time(), stream, event_version, projection_json, checksum),
|
|
260
|
+
)
|
|
261
|
+
conn.commit()
|
|
262
|
+
finally:
|
|
263
|
+
conn.close()
|
|
264
|
+
|
|
265
|
+
_retry_on_db_lock(_do_save_snapshot)
|
|
154
266
|
|
|
155
267
|
def read_snapshot(self, stream: str) -> tuple | None:
|
|
156
268
|
"""Read the most recent snapshot for a stream.
|