@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
@@ -0,0 +1,405 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Aesop reproduce — offline verification suite
5
+ *
6
+ * Mirrors .github/workflows/reproduce.yml for local execution.
7
+ *
8
+ * Modes:
9
+ * REPO: Full test suites (Node.js, Python, Shell, React, benchmarks)
10
+ * INSTALLED: Shipped self-checks (doctor, health-score, secret-scan selftest, packaging)
11
+ *
12
+ * Exit: 0 = all checks pass, 1 = any check failed
13
+ * Output: ASCII table with per-step timing
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const { execSync, spawnSync } = require('child_process');
19
+
20
+ const CURRENT_DIR = process.cwd();
21
+ const PACKAGE_ROOT = path.join(__dirname, '..');
22
+
23
+ // ANSI colors
24
+ const COLORS = {
25
+ GREEN: '\x1b[32m',
26
+ RED: '\x1b[31m',
27
+ YELLOW: '\x1b[33m',
28
+ RESET: '\x1b[0m',
29
+ BOLD: '\x1b[1m',
30
+ DIM: '\x1b[2m'
31
+ };
32
+
33
+ function colorPass() {
34
+ return `${COLORS.GREEN}PASS${COLORS.RESET}`;
35
+ }
36
+
37
+ function colorFail() {
38
+ return `${COLORS.RED}FAIL${COLORS.RESET}`;
39
+ }
40
+
41
+ function colorSkip() {
42
+ return `${COLORS.YELLOW}SKIP${COLORS.RESET}`;
43
+ }
44
+
45
+ // Detect context: repo checkout vs installed package
46
+ function detectContext() {
47
+ // Repo checkout has .git/config and package.json with npm scripts
48
+ const hasGitConfig = fs.existsSync(path.join(PACKAGE_ROOT, '.git', 'config'));
49
+ const hasPackageJson = fs.existsSync(path.join(PACKAGE_ROOT, 'package.json'));
50
+ const hasTestScripts = hasPackageJson && (() => {
51
+ try {
52
+ const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
53
+ return pkg.scripts && pkg.scripts['test:node'] && pkg.scripts['test:py'];
54
+ } catch {
55
+ return false;
56
+ }
57
+ })();
58
+
59
+ // Installed package is in node_modules with limited structure
60
+ const isInstalled = !hasGitConfig && !hasTestScripts;
61
+
62
+ return isInstalled ? 'installed' : 'repo';
63
+ }
64
+
65
+ // Format timing for display
66
+ function formatTiming(ms) {
67
+ if (ms < 1000) {
68
+ return `${ms}ms`;
69
+ }
70
+ const sec = (ms / 1000).toFixed(1);
71
+ return `${sec}s`;
72
+ }
73
+
74
+ // Format a result row
75
+ function formatRow(label, status, timing, hint) {
76
+ const statusStr = status === 'PASS' ? colorPass() : (status === 'FAIL' ? colorFail() : colorSkip());
77
+ const timingStr = timing ? ` [${formatTiming(timing)}]` : '';
78
+ const hintStr = hint ? ` — ${hint}` : '';
79
+ const paddedLabel = label.padEnd(50);
80
+ return ` ${paddedLabel} ${statusStr}${timingStr}${hintStr}`;
81
+ }
82
+
83
+ // Run a subprocess and return pass/fail
84
+ function runSubprocess(label, args, options = {}) {
85
+ const { cwd = PACKAGE_ROOT, stdio = 'inherit', skipIfMissing = null } = options;
86
+
87
+ if (skipIfMissing && !fs.existsSync(skipIfMissing)) {
88
+ return {
89
+ label,
90
+ status: 'SKIP',
91
+ timing: 0,
92
+ passed: true,
93
+ hint: `${path.basename(skipIfMissing)} not found`
94
+ };
95
+ }
96
+
97
+ const startTime = Date.now();
98
+ try {
99
+ const result = spawnSync(args[0], args.slice(1), {
100
+ stdio,
101
+ cwd,
102
+ encoding: 'utf8'
103
+ });
104
+ const timing = Date.now() - startTime;
105
+
106
+ if (result.status === 0 || result.status === null) {
107
+ return { label, status: 'PASS', timing, passed: true };
108
+ } else {
109
+ let hint;
110
+ if (stdio === 'pipe' && result.stderr) {
111
+ hint = result.stderr.split('\n')[0].slice(0, 80);
112
+ } else if (stdio === 'pipe' && result.stdout) {
113
+ hint = result.stdout.split('\n')[0].slice(0, 80);
114
+ }
115
+ return { label, status: 'FAIL', timing, passed: false, hint };
116
+ }
117
+ } catch (e) {
118
+ const timing = Date.now() - startTime;
119
+ return { label, status: 'FAIL', timing, passed: false, hint: e.message.slice(0, 80) };
120
+ }
121
+ }
122
+
123
+ // Repo mode: full test suite
124
+ function runRepoMode() {
125
+ console.log(`\n${COLORS.BOLD}Running Full Test Suite (Repo Mode)${COLORS.RESET}\n`);
126
+
127
+ const results = [];
128
+
129
+ // Step 1: Node syntax check
130
+ results.push(runSubprocess(
131
+ 'Node syntax check (*.mjs excluding .template.mjs)',
132
+ ['bash', '-c', 'git ls-files "*.mjs" | grep -v ".template.mjs$" | while read f; do [ -n "$f" ] && node --check "$f" || true; done'],
133
+ { stdio: 'pipe' }
134
+ ));
135
+
136
+ // Step 2: Shell syntax check
137
+ results.push(runSubprocess(
138
+ 'Shell syntax check (*.sh)',
139
+ ['bash', '-c', 'git ls-files "*.sh" | while read f; do [ -n "$f" ] && bash -n "$f" || true; done'],
140
+ { stdio: 'pipe' }
141
+ ));
142
+
143
+ // Step 3: Node.js tests
144
+ results.push(runSubprocess(
145
+ 'Node.js tests (npm run test:node)',
146
+ ['npm', 'run', 'test:node'],
147
+ { stdio: 'inherit' }
148
+ ));
149
+
150
+ // Step 4: Shell test suites
151
+ results.push(runSubprocess(
152
+ 'Shell test suites (npm run test:sh)',
153
+ ['npm', 'run', 'test:sh'],
154
+ { stdio: 'inherit' }
155
+ ));
156
+
157
+ // Step 5: React component tests (vitest)
158
+ const uiWebDir = path.join(PACKAGE_ROOT, 'ui', 'web');
159
+ if (fs.existsSync(uiWebDir)) {
160
+ results.push(runSubprocess(
161
+ 'React component tests (vitest)',
162
+ ['bash', '-c', `cd ${JSON.stringify(uiWebDir)} && npm ci && npx vitest run`],
163
+ { stdio: 'inherit' }
164
+ ));
165
+ } else {
166
+ results.push({
167
+ label: 'React component tests (vitest)',
168
+ status: 'SKIP',
169
+ timing: 0,
170
+ passed: true,
171
+ hint: 'ui/web not found'
172
+ });
173
+ }
174
+
175
+ // Step 6: Python tool compile check
176
+ results.push(runSubprocess(
177
+ 'Python tool import/compile smoke gate',
178
+ ['bash', '-c', 'python -m compileall -q tools/ && python -m unittest tests.test_tools_importable -v'],
179
+ { stdio: 'pipe' }
180
+ ));
181
+
182
+ // Step 7: Python tests
183
+ results.push(runSubprocess(
184
+ 'Python tests (unittest discover)',
185
+ ['bash', '-c', 'python -m unittest discover -s tests -p "test_*.py" -v'],
186
+ { stdio: 'inherit' }
187
+ ));
188
+
189
+ // Step 8: Benchmark scorer tests
190
+ results.push(runSubprocess(
191
+ 'Benchmark scorer tests (test_bench_runner)',
192
+ ['python', '-m', 'unittest', 'tests.test_bench_runner', '-v'],
193
+ { stdio: 'pipe' }
194
+ ));
195
+
196
+ // Step 9: Benchmark reproduction
197
+ results.push(runSubprocess(
198
+ 'Offline benchmark reproduction',
199
+ ['python', 'tools/bench_runner.py', '--runner', 'mock'],
200
+ { stdio: 'pipe' }
201
+ ));
202
+
203
+ return results;
204
+ }
205
+
206
+ // Classify doctor failures: expected pre-init findings vs real failures
207
+ // Expected on fresh systems: missing config, missing hooks, missing dirs (if tarball unpack incomplete)
208
+ // Real failures: Node version < 18, Python missing, not in git repo
209
+ function classifyDoctorFailure(output) {
210
+ const lines = output.split('\n');
211
+ const failures = [];
212
+ // Expected pre-init directory names from doctor.js checkDirectories()
213
+ const expectedDirs = ['daemons', 'dash', 'monitor', 'tools', 'ui'];
214
+
215
+ for (const line of lines) {
216
+ if (line.includes('✗') || line.includes('FAIL')) {
217
+ failures.push(line);
218
+ }
219
+ }
220
+
221
+ // Check if all failures match expected pre-init patterns
222
+ const allExpected = failures.every(failure => {
223
+ // Pattern 1: Missing config file
224
+ if (failure.includes('aesop.config.json not found')) {
225
+ return true;
226
+ }
227
+ // Pattern 2: Pre-push hook not installed
228
+ if (failure.includes('Pre-push hook not installed')) {
229
+ return true;
230
+ }
231
+ // Pattern 3: Missing directories — verify line contains only expected directory names
232
+ if (failure.includes('Missing:')) {
233
+ // Extract the part after "Missing:"
234
+ const afterMissing = failure.substring(failure.indexOf('Missing:') + 8);
235
+ // Check if all mentioned items are valid directory names
236
+ const items = afterMissing.split(',').map(item => item.trim());
237
+ return items.every(item => expectedDirs.includes(item));
238
+ }
239
+ return false;
240
+ });
241
+
242
+ return { allExpected, failures };
243
+ }
244
+
245
+ // Installed mode: shipped self-checks
246
+ function runInstalledMode() {
247
+ console.log(`\n${COLORS.BOLD}Running Shipped Self-Checks (Installed Mode)${COLORS.RESET}\n`);
248
+
249
+ const results = [];
250
+
251
+ // Step 1: Doctor check — distinguish expected pre-init findings from real failures
252
+ const doctorJs = path.join(PACKAGE_ROOT, 'tools', 'doctor.js');
253
+ const doctorResult = (() => {
254
+ if (!fs.existsSync(doctorJs)) {
255
+ return {
256
+ label: 'Preflight checks (aesop doctor)',
257
+ status: 'SKIP',
258
+ timing: 0,
259
+ passed: true,
260
+ hint: 'doctor.js not found'
261
+ };
262
+ }
263
+
264
+ const startTime = Date.now();
265
+ try {
266
+ const result = spawnSync('node', [doctorJs], {
267
+ stdio: 'pipe',
268
+ encoding: 'utf8'
269
+ });
270
+ const timing = Date.now() - startTime;
271
+
272
+ if (result.status === 0) {
273
+ return { label: 'Preflight checks (aesop doctor)', status: 'PASS', timing, passed: true };
274
+ } else {
275
+ // Doctor failed; classify whether it's expected pre-init findings or real failure
276
+ const output = result.stdout || result.stderr || '';
277
+ const { allExpected } = classifyDoctorFailure(output);
278
+
279
+ if (allExpected) {
280
+ // Expected pre-init findings (missing config, hooks, etc.) — pass but note it
281
+ return {
282
+ label: 'Preflight checks (aesop doctor)',
283
+ status: 'PASS',
284
+ timing,
285
+ passed: true,
286
+ hint: 'Expected pre-init findings (run `aesop doctor` to initialize)'
287
+ };
288
+ } else {
289
+ // Real failure (e.g., Node/Python missing, not in git repo)
290
+ const hint = result.stderr ? result.stderr.split('\n')[0].slice(0, 80) : 'doctor check failed';
291
+ return { label: 'Preflight checks (aesop doctor)', status: 'FAIL', timing, passed: false, hint };
292
+ }
293
+ }
294
+ } catch (e) {
295
+ const timing = Date.now() - startTime;
296
+ return {
297
+ label: 'Preflight checks (aesop doctor)',
298
+ status: 'FAIL',
299
+ timing,
300
+ passed: false,
301
+ hint: e.message.slice(0, 80)
302
+ };
303
+ }
304
+ })();
305
+ results.push(doctorResult);
306
+
307
+ // Step 2: Health score check
308
+ const healthScoreJs = path.join(PACKAGE_ROOT, 'tools', 'health-score.js');
309
+ results.push(runSubprocess(
310
+ 'Health score check (aesop health-score)',
311
+ ['node', healthScoreJs],
312
+ { stdio: 'pipe', skipIfMissing: healthScoreJs }
313
+ ));
314
+
315
+ // Step 3: Secret-scan selftest
316
+ const scannerSelftest = path.join(PACKAGE_ROOT, 'tools', 'scanner_selftest.py');
317
+ results.push(runSubprocess(
318
+ 'Secret-scan selftest',
319
+ ['python', scannerSelftest],
320
+ { stdio: 'pipe', skipIfMissing: scannerSelftest }
321
+ ));
322
+
323
+ // Step 4: Packaging assertions (check required files are shipped)
324
+ const startTime = Date.now();
325
+ let packagingPassed = true;
326
+ let packagingHint;
327
+ try {
328
+ const requiredDirs = ['tools', 'daemons', 'dash', 'monitor', 'ui', 'docs'];
329
+ const requiredFiles = ['aesop.config.example.json', 'README.md', 'LICENSE'];
330
+
331
+ const missing = [];
332
+ for (const dir of requiredDirs) {
333
+ const dirPath = path.join(PACKAGE_ROOT, dir);
334
+ if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
335
+ missing.push(dir);
336
+ }
337
+ }
338
+
339
+ for (const file of requiredFiles) {
340
+ const filePath = path.join(PACKAGE_ROOT, file);
341
+ if (!fs.existsSync(filePath)) {
342
+ missing.push(file);
343
+ }
344
+ }
345
+
346
+ if (missing.length > 0) {
347
+ packagingPassed = false;
348
+ packagingHint = `Missing: ${missing.join(', ')}`;
349
+ }
350
+ } catch (e) {
351
+ packagingPassed = false;
352
+ packagingHint = e.message.slice(0, 80);
353
+ }
354
+
355
+ const packagingTiming = Date.now() - startTime;
356
+ results.push({
357
+ label: 'Packaging assertions',
358
+ status: packagingPassed ? 'PASS' : 'FAIL',
359
+ timing: packagingTiming,
360
+ passed: packagingPassed,
361
+ hint: packagingHint
362
+ });
363
+
364
+ return results;
365
+ }
366
+
367
+ // Main execution
368
+ (async function main() {
369
+ try {
370
+ const context = detectContext();
371
+ console.log(`${COLORS.DIM}Context: ${context}${COLORS.RESET}`);
372
+
373
+ let results;
374
+ if (context === 'repo') {
375
+ results = runRepoMode();
376
+ } else {
377
+ results = runInstalledMode();
378
+ }
379
+
380
+ // Print results table
381
+ console.log(`\n${COLORS.BOLD}Results${COLORS.RESET}\n`);
382
+ for (const result of results) {
383
+ console.log(formatRow(result.label, result.status, result.timing, result.hint));
384
+ }
385
+
386
+ // Summary
387
+ const passCount = results.filter(r => r.passed).length;
388
+ const failCount = results.filter(r => !r.passed).length;
389
+ const totalTime = results.reduce((sum, r) => sum + (r.timing || 0), 0);
390
+
391
+ console.log(`\n${COLORS.BOLD}Summary: ${passCount}/${results.length} checks passed${COLORS.RESET}`);
392
+ console.log(`${COLORS.DIM}Total time: ${formatTiming(totalTime)}${COLORS.RESET}\n`);
393
+
394
+ if (failCount === 0) {
395
+ console.log(`${COLORS.GREEN}✓ All checks passed${COLORS.RESET}\n`);
396
+ process.exitCode = 0;
397
+ } else {
398
+ console.log(`${COLORS.RED}✗ ${failCount} check(s) failed${COLORS.RESET}\n`);
399
+ process.exitCode = 1;
400
+ }
401
+ } catch (err) {
402
+ console.error(`${COLORS.RED}Error: ${err.message}${COLORS.RESET}`);
403
+ process.exit(1);
404
+ }
405
+ })();