@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.
Files changed (130) hide show
  1. package/CHANGELOG.md +117 -2
  2. package/README.md +59 -218
  3. package/bin/cli.js +168 -41
  4. package/daemons/run-watchdog.sh +16 -4
  5. package/daemons/selfheal.sh +231 -0
  6. package/docs/ANY-REPO.md +427 -0
  7. package/docs/CONTRIBUTING.md +72 -0
  8. package/docs/DEMO.md +334 -0
  9. package/docs/HOOK-INSTALL.md +15 -56
  10. package/docs/INSTALL.md +74 -4
  11. package/docs/PORTING.md +166 -0
  12. package/docs/README.md +33 -3
  13. package/docs/TEAM-STATE.md +372 -0
  14. package/docs/reproduce.md +33 -3
  15. package/driver/CLAUDE.md +150 -0
  16. package/driver/README.md +383 -0
  17. package/driver/aesop.config.example.json +80 -0
  18. package/driver/agent_driver.py +355 -0
  19. package/driver/backend_config.py +253 -0
  20. package/driver/claude_code_driver.py +198 -0
  21. package/driver/codex_driver.py +673 -0
  22. package/driver/openai_compatible_driver.py +249 -0
  23. package/driver/openai_transport.py +179 -0
  24. package/driver/verification_policy.py +75 -0
  25. package/driver/wave_bridge.py +254 -0
  26. package/driver/wave_loop.py +1408 -0
  27. package/driver/wave_scheduler.py +890 -0
  28. package/hooks/pre-push-policy.sh +131 -33
  29. package/mcp/server.mjs +320 -4
  30. package/monitor/collect-signals.mjs +69 -0
  31. package/package.json +22 -14
  32. package/skills/CLAUDE.md +132 -2
  33. package/skills/buildsystem/SKILL.md +330 -0
  34. package/skills/buildsystem/wave-flat-dispatch.template.mjs +658 -0
  35. package/skills/fleet/SKILL.md +113 -0
  36. package/skills/power/SKILL.md +246 -131
  37. package/state_store/__init__.py +4 -2
  38. package/state_store/api.py +19 -4
  39. package/state_store/coordination.py +209 -0
  40. package/state_store/identity.py +51 -0
  41. package/state_store/projections.py +63 -0
  42. package/state_store/read_api.py +156 -0
  43. package/state_store/store.py +185 -73
  44. package/state_store/write_api.py +462 -0
  45. package/templates/wave-presets/data.json +65 -0
  46. package/templates/wave-presets/library.json +65 -0
  47. package/templates/wave-presets/saas.json +64 -0
  48. package/tools/audit_report.py +388 -0
  49. package/tools/bench_runner.py +100 -3
  50. package/tools/ci_merge_wait.py +256 -35
  51. package/tools/ci_workflow_lint.py +430 -0
  52. package/tools/claudemd_drift.py +394 -0
  53. package/tools/claudemd_lint.py +359 -0
  54. package/tools/common.py +39 -3
  55. package/tools/cost_ceiling.py +166 -43
  56. package/tools/cost_econ.py +480 -0
  57. package/tools/cost_projection.py +559 -0
  58. package/tools/crossos_drift.py +394 -0
  59. package/tools/defect_escape.py +252 -0
  60. package/tools/doctor.js +1 -1
  61. package/tools/eod_sweep.py +188 -26
  62. package/tools/fleet.js +260 -0
  63. package/tools/fleet_ledger.py +209 -7
  64. package/tools/git_identity_check.py +315 -0
  65. package/tools/health-score.js +40 -0
  66. package/tools/health_score.py +361 -0
  67. package/tools/metrics_gate.py +13 -4
  68. package/tools/mutation_test.py +523 -0
  69. package/tools/portability_check.py +206 -0
  70. package/tools/proposals.mjs +47 -2
  71. package/tools/reconcile.py +7 -4
  72. package/tools/reproduce.js +405 -0
  73. package/tools/secret_scan.py +207 -65
  74. package/tools/self_stats.py +20 -0
  75. package/tools/stall_check.py +247 -16
  76. package/tools/stateapi_lint.py +325 -0
  77. package/tools/test_battery.py +173 -0
  78. package/tools/transcript_digest.py +380 -0
  79. package/tools/verify_activity_filter.py +437 -0
  80. package/tools/verify_agent_inspector.py +2 -0
  81. package/tools/verify_cost_panel.py +345 -0
  82. package/tools/verify_dash.py +2 -0
  83. package/tools/verify_dispatch_panel.py +301 -0
  84. package/tools/verify_failure_drilldown.py +188 -0
  85. package/tools/verify_prboard.py +2 -0
  86. package/tools/verify_scorecards.py +281 -0
  87. package/tools/verify_submit_encoding.py +2 -0
  88. package/tools/verify_ui_trio.py +409 -0
  89. package/tools/verify_wave_telemetry.py +268 -0
  90. package/tools/wave_backlog_analyzer.py +490 -0
  91. package/tools/wave_ledger_hook.py +150 -0
  92. package/tools/wave_preflight.py +779 -0
  93. package/tools/wave_resume.py +215 -0
  94. package/tools/wave_templates.py +340 -0
  95. package/ui/agents.py +68 -14
  96. package/ui/collectors.py +81 -55
  97. package/ui/config.py +7 -2
  98. package/ui/cost.py +231 -12
  99. package/ui/handler.py +383 -55
  100. package/ui/quality_scorecard.py +232 -0
  101. package/ui/sse.py +3 -3
  102. package/ui/wave_audit_tail.py +213 -0
  103. package/ui/wave_dispatch.py +280 -0
  104. package/ui/wave_failure.py +288 -0
  105. package/ui/wave_gantt.py +152 -0
  106. package/ui/wave_reasoning_tail.py +176 -0
  107. package/ui/wave_telemetry.py +383 -0
  108. package/ui/web/dist/assets/index-CNQxaiOW.css +1 -0
  109. package/ui/web/dist/assets/index-CP68RIh3.js +9 -0
  110. package/ui/web/dist/index.html +2 -2
  111. package/bin/CLAUDE.md +0 -76
  112. package/daemons/CLAUDE.md +0 -36
  113. package/dash/CLAUDE.md +0 -32
  114. package/docs/archive/README.md +0 -3
  115. package/docs/archive/spikes/tiered-cognition/ACTIVATION.md +0 -125
  116. package/docs/archive/spikes/tiered-cognition/DESIGN.md +0 -287
  117. package/docs/archive/spikes/tiered-cognition/FINDINGS.md +0 -113
  118. package/docs/archive/spikes/tiered-cognition/README.md +0 -27
  119. package/docs/archive/spikes/tiered-cognition/aesop-cognition.example.md +0 -32
  120. package/docs/archive/spikes/tiered-cognition/force-model-policy.merged.mjs +0 -673
  121. package/docs/archive/spikes/tiered-cognition/strip-tools-hook.mjs +0 -434
  122. package/hooks/CLAUDE.md +0 -89
  123. package/mcp/CLAUDE.md +0 -213
  124. package/monitor/CLAUDE.md +0 -40
  125. package/scan/CLAUDE.md +0 -30
  126. package/state_store/CLAUDE.md +0 -39
  127. package/tools/CLAUDE.md +0 -79
  128. package/ui/CLAUDE.md +0 -127
  129. package/ui/web/dist/assets/index-0qQYnvMC.js +0 -9
  130. package/ui/web/dist/assets/index-BdIlFieV.css +0 -1
@@ -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
- conn = sqlite3.connect(self.db_path)
33
- try:
34
- conn.execute("PRAGMA journal_mode=WAL")
35
- conn.execute("PRAGMA busy_timeout=5000")
36
- conn.execute(
37
- """
38
- CREATE TABLE IF NOT EXISTS events (
39
- id INTEGER PRIMARY KEY AUTOINCREMENT,
40
- ts REAL NOT NULL,
41
- actor TEXT NOT NULL,
42
- stream TEXT NOT NULL,
43
- type TEXT NOT NULL,
44
- payload TEXT NOT NULL,
45
- version INTEGER NOT NULL
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
- conn.execute(
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
- conn.execute(
66
- "CREATE INDEX IF NOT EXISTS idx_snapshots_stream_version "
67
- "ON snapshots(stream, event_version)"
68
- )
69
- conn.commit()
70
- finally:
71
- conn.close()
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
- def append(self, stream: str, event_type: str, payload: dict, actor: str = "system") -> int:
74
- """Append one event to ``stream``; return its new per-stream version (1-based)."""
75
- conn = sqlite3.connect(self.db_path)
76
- try:
77
- conn.execute("PRAGMA busy_timeout=5000")
78
- # BEGIN IMMEDIATE takes the write lock up front so the
79
- # read-max-then-insert below is atomic under contention.
80
- conn.execute("BEGIN IMMEDIATE")
81
- row = conn.execute(
82
- "SELECT COALESCE(MAX(version), 0) FROM events WHERE stream = ?",
83
- (stream,),
84
- ).fetchone()
85
- version = row[0] + 1
86
- conn.execute(
87
- "INSERT INTO events (ts, actor, stream, type, payload, version) "
88
- "VALUES (?, ?, ?, ?, ?, ?)",
89
- (time.time(), actor, stream, event_type, json.dumps(payload), version),
90
- )
91
- conn.commit()
92
- return version
93
- finally:
94
- conn.close()
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
- conn = sqlite3.connect(self.db_path)
142
- try:
143
- conn.execute("PRAGMA busy_timeout=5000")
144
- projection_json = json.dumps(projection, separators=(",", ":"), sort_keys=True)
145
- checksum = hashlib.sha256(projection_json.encode()).hexdigest()
146
- conn.execute(
147
- "INSERT INTO snapshots (ts, stream, event_version, projection, checksum) "
148
- "VALUES (?, ?, ?, ?, ?)",
149
- (time.time(), stream, event_version, projection_json, checksum),
150
- )
151
- conn.commit()
152
- finally:
153
- conn.close()
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.