@matt82198/aesop 0.1.0-beta.4 → 0.1.0-beta.5

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 (174) hide show
  1. package/CHANGELOG.md +46 -5
  2. package/README.md +57 -1
  3. package/aesop.config.example.json +8 -1
  4. package/bin/CLAUDE.md +30 -2
  5. package/bin/cli.js +286 -73
  6. package/daemons/CLAUDE.md +4 -1
  7. package/daemons/run-watchdog.sh +5 -2
  8. package/docs/RELEASING.md +159 -0
  9. package/docs/archive/README.md +3 -0
  10. package/docs/{spikes → archive/spikes}/tiered-cognition/README.md +1 -3
  11. package/docs/case-study-portfolio.md +61 -0
  12. package/docs/self-stats-data.json +11 -0
  13. package/docs/templates/FLEET-OPS-ANALYSIS.example.md +27 -0
  14. package/docs/templates/FLEET-OPS-RECOMMENDATIONS.example.md +24 -0
  15. package/docs/templates/PROPOSALS-LOG.example.md +64 -0
  16. package/hooks/CLAUDE.md +23 -0
  17. package/mcp/CLAUDE.md +213 -0
  18. package/mcp/package.json +26 -0
  19. package/mcp/server.mjs +543 -0
  20. package/monitor/CHARTER.md +76 -110
  21. package/monitor/collect-signals.mjs +85 -0
  22. package/package.json +4 -1
  23. package/scan/fleet-scan.example.mjs +292 -0
  24. package/skills/healthcheck/SKILL.md +44 -0
  25. package/state_store/CLAUDE.md +39 -0
  26. package/state_store/__init__.py +24 -0
  27. package/state_store/__pycache__/__init__.cpython-314.pyc +0 -0
  28. package/state_store/__pycache__/api.cpython-314.pyc +0 -0
  29. package/state_store/__pycache__/export.cpython-314.pyc +0 -0
  30. package/state_store/__pycache__/ingest.cpython-314.pyc +0 -0
  31. package/state_store/__pycache__/projections.cpython-314.pyc +0 -0
  32. package/state_store/__pycache__/store.cpython-314.pyc +0 -0
  33. package/state_store/api.py +35 -0
  34. package/state_store/export.py +19 -0
  35. package/state_store/ingest.py +24 -0
  36. package/state_store/projections.py +52 -0
  37. package/state_store/store.py +102 -0
  38. package/tools/CLAUDE.md +30 -149
  39. package/tools/__pycache__/alert_bridge.cpython-314.pyc +0 -0
  40. package/tools/__pycache__/ci_merge_wait.cpython-314.pyc +0 -0
  41. package/tools/__pycache__/fleet_prompt_extractor.cpython-314.pyc +0 -0
  42. package/tools/__pycache__/healthcheck.cpython-314.pyc +0 -0
  43. package/tools/__pycache__/metrics_gate.cpython-314.pyc +0 -0
  44. package/tools/__pycache__/secret_scan.cpython-314.pyc +0 -0
  45. package/tools/__pycache__/self_stats.cpython-314.pyc +0 -0
  46. package/tools/__pycache__/session_usage_summary.cpython-314.pyc +0 -0
  47. package/tools/__pycache__/stall_check.cpython-314.pyc +0 -0
  48. package/tools/__pycache__/transcript_replay.cpython-314.pyc +0 -0
  49. package/tools/__pycache__/transcript_timeline.cpython-314.pyc +0 -0
  50. package/tools/__pycache__/verify_dash.cpython-314.pyc +0 -0
  51. package/tools/__pycache__/verify_submit_encoding.cpython-314.pyc +0 -0
  52. package/tools/alert_bridge.py +449 -0
  53. package/tools/ci_merge_wait.py +121 -8
  54. package/tools/fleet_prompt_extractor.py +134 -0
  55. package/tools/healthcheck.py +296 -0
  56. package/tools/secret_scan.py +8 -1
  57. package/tools/self_stats.py +509 -0
  58. package/tools/session_usage_summary.py +198 -0
  59. package/tools/svg_to_png.mjs +50 -0
  60. package/tools/transcript_replay.py +236 -0
  61. package/tools/transcript_timeline.py +184 -0
  62. package/tools/verify_dash.py +361 -542
  63. package/tools/verify_submit_encoding.py +12 -4
  64. package/ui/CLAUDE.md +57 -41
  65. package/ui/__pycache__/agents.cpython-314.pyc +0 -0
  66. package/ui/__pycache__/collectors.cpython-314.pyc +0 -0
  67. package/ui/__pycache__/config.cpython-314.pyc +0 -0
  68. package/ui/__pycache__/cost.cpython-314.pyc +0 -0
  69. package/ui/__pycache__/csrf.cpython-314.pyc +0 -0
  70. package/ui/__pycache__/handler.cpython-314.pyc +0 -0
  71. package/ui/__pycache__/render.cpython-314.pyc +0 -0
  72. package/ui/__pycache__/serve.cpython-314.pyc +0 -0
  73. package/ui/__pycache__/sse.cpython-314.pyc +0 -0
  74. package/ui/agents.py +9 -5
  75. package/ui/api/__pycache__/submit.cpython-314.pyc +0 -0
  76. package/ui/api/submit.py +44 -5
  77. package/ui/collectors.py +184 -35
  78. package/ui/config.py +9 -0
  79. package/ui/cost.py +260 -0
  80. package/ui/csrf.py +7 -3
  81. package/ui/handler.py +254 -4
  82. package/ui/render.py +26 -6
  83. package/ui/sse.py +16 -1
  84. package/ui/web/.gitattributes +13 -0
  85. package/ui/web/dist/assets/index-2LZDQirC.js +9 -0
  86. package/ui/web/dist/assets/index-D4M1qyOv.css +1 -0
  87. package/ui/web/dist/index.html +14 -0
  88. package/ui/web/index.html +13 -0
  89. package/ui/web/package-lock.json +2225 -0
  90. package/ui/web/package.json +26 -0
  91. package/ui/web/src/App.test.tsx +74 -0
  92. package/ui/web/src/App.tsx +142 -0
  93. package/ui/web/src/CONTRIBUTING-UI.md +49 -0
  94. package/ui/web/src/components/AgentRow.css +187 -0
  95. package/ui/web/src/components/AgentRow.test.tsx +209 -0
  96. package/ui/web/src/components/AgentRow.tsx +207 -0
  97. package/ui/web/src/components/AgentsPanel.css +108 -0
  98. package/ui/web/src/components/AgentsPanel.test.tsx +41 -0
  99. package/ui/web/src/components/AgentsPanel.tsx +58 -0
  100. package/ui/web/src/components/AlertsPanel.css +88 -0
  101. package/ui/web/src/components/AlertsPanel.test.tsx +51 -0
  102. package/ui/web/src/components/AlertsPanel.tsx +67 -0
  103. package/ui/web/src/components/BacklogPanel.test.tsx +126 -0
  104. package/ui/web/src/components/BacklogPanel.tsx +122 -0
  105. package/ui/web/src/components/CostChart.css +110 -0
  106. package/ui/web/src/components/CostChart.test.tsx +144 -0
  107. package/ui/web/src/components/CostChart.tsx +152 -0
  108. package/ui/web/src/components/CostTable.css +93 -0
  109. package/ui/web/src/components/CostTable.test.tsx +165 -0
  110. package/ui/web/src/components/CostTable.tsx +94 -0
  111. package/ui/web/src/components/EventsFeed.css +68 -0
  112. package/ui/web/src/components/EventsFeed.test.tsx +36 -0
  113. package/ui/web/src/components/EventsFeed.tsx +31 -0
  114. package/ui/web/src/components/HealthHeader.css +137 -0
  115. package/ui/web/src/components/HealthHeader.test.tsx +278 -0
  116. package/ui/web/src/components/HealthHeader.tsx +281 -0
  117. package/ui/web/src/components/InboxForm.css +135 -0
  118. package/ui/web/src/components/InboxForm.test.tsx +208 -0
  119. package/ui/web/src/components/InboxForm.tsx +116 -0
  120. package/ui/web/src/components/MessagesTail.module.css +144 -0
  121. package/ui/web/src/components/MessagesTail.test.tsx +176 -0
  122. package/ui/web/src/components/MessagesTail.tsx +94 -0
  123. package/ui/web/src/components/ReposPanel.css +90 -0
  124. package/ui/web/src/components/ReposPanel.test.tsx +45 -0
  125. package/ui/web/src/components/ReposPanel.tsx +67 -0
  126. package/ui/web/src/components/Scorecard.css +106 -0
  127. package/ui/web/src/components/Scorecard.test.tsx +117 -0
  128. package/ui/web/src/components/Scorecard.tsx +85 -0
  129. package/ui/web/src/components/Timeline.module.css +151 -0
  130. package/ui/web/src/components/Timeline.test.tsx +215 -0
  131. package/ui/web/src/components/Timeline.tsx +99 -0
  132. package/ui/web/src/components/TrackerBoard.test.tsx +121 -0
  133. package/ui/web/src/components/TrackerBoard.tsx +107 -0
  134. package/ui/web/src/components/TrackerCard.test.tsx +180 -0
  135. package/ui/web/src/components/TrackerCard.tsx +160 -0
  136. package/ui/web/src/components/TrackerForm.test.tsx +189 -0
  137. package/ui/web/src/components/TrackerForm.tsx +144 -0
  138. package/ui/web/src/lib/api.ts +218 -0
  139. package/ui/web/src/lib/format.test.ts +89 -0
  140. package/ui/web/src/lib/format.ts +103 -0
  141. package/ui/web/src/lib/sanitizeUrl.test.ts +84 -0
  142. package/ui/web/src/lib/sanitizeUrl.ts +38 -0
  143. package/ui/web/src/lib/types.ts +230 -0
  144. package/ui/web/src/lib/useHashRoute.test.ts +60 -0
  145. package/ui/web/src/lib/useHashRoute.ts +23 -0
  146. package/ui/web/src/lib/useSSE.ts +175 -0
  147. package/ui/web/src/main.tsx +10 -0
  148. package/ui/web/src/styles/global.css +179 -0
  149. package/ui/web/src/styles/theme.css +184 -0
  150. package/ui/web/src/styles/work.css +572 -0
  151. package/ui/web/src/test/fixtures.ts +385 -0
  152. package/ui/web/src/test/setup.ts +49 -0
  153. package/ui/web/src/views/Activity.module.css +43 -0
  154. package/ui/web/src/views/Activity.test.tsx +89 -0
  155. package/ui/web/src/views/Activity.tsx +31 -0
  156. package/ui/web/src/views/Cost.css +87 -0
  157. package/ui/web/src/views/Cost.test.tsx +142 -0
  158. package/ui/web/src/views/Cost.tsx +54 -0
  159. package/ui/web/src/views/Overview.css +51 -0
  160. package/ui/web/src/views/Overview.test.tsx +76 -0
  161. package/ui/web/src/views/Overview.tsx +46 -0
  162. package/ui/web/src/views/Work.test.tsx +82 -0
  163. package/ui/web/src/views/Work.tsx +79 -0
  164. package/ui/web/src/vite-env.d.ts +10 -0
  165. package/ui/web/tsconfig.json +22 -0
  166. package/ui/web/vite.config.ts +25 -0
  167. package/ui/web/vitest.config.ts +12 -0
  168. package/ui/templates/dashboard.html +0 -1202
  169. /package/docs/{spikes → archive/spikes}/tiered-cognition/ACTIVATION.md +0 -0
  170. /package/docs/{spikes → archive/spikes}/tiered-cognition/DESIGN.md +0 -0
  171. /package/docs/{spikes → archive/spikes}/tiered-cognition/FINDINGS.md +0 -0
  172. /package/docs/{spikes → archive/spikes}/tiered-cognition/aesop-cognition.example.md +0 -0
  173. /package/docs/{spikes → archive/spikes}/tiered-cognition/force-model-policy.merged.mjs +0 -0
  174. /package/docs/{spikes → archive/spikes}/tiered-cognition/strip-tools-hook.mjs +0 -0
package/bin/cli.js CHANGED
@@ -3,10 +3,55 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
+ const readline = require('readline');
7
+ const { execSync } = require('child_process');
6
8
 
7
9
  const args = process.argv.slice(2);
8
10
  const helpFlag = args.includes('--help') || args.includes('-h');
9
11
  const forceFlag = args.includes('--force');
12
+ const yesFlag = args.includes('--yes');
13
+
14
+ // Detect if stdin is a TTY (interactive terminal)
15
+ function isInteractive() {
16
+ return process.stdin.isTTY && process.stdout.isTTY;
17
+ }
18
+
19
+ // Discover git repositories under a base directory (non-recursive first level)
20
+ function discoverRepos(baseDir) {
21
+ const repos = [];
22
+ try {
23
+ if (!fs.existsSync(baseDir)) return repos;
24
+ const entries = fs.readdirSync(baseDir, { withFileTypes: true });
25
+ for (const entry of entries) {
26
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
27
+ const fullPath = path.join(baseDir, entry.name);
28
+ const gitDir = path.join(fullPath, '.git');
29
+ if (fs.existsSync(gitDir)) {
30
+ repos.push({ name: entry.name, path: fullPath });
31
+ }
32
+ }
33
+ }
34
+ } catch (e) {
35
+ // Silently ignore discovery errors
36
+ }
37
+ return repos;
38
+ }
39
+
40
+ // Interactive prompt via readline (exported for testing)
41
+ async function promptUser(rl, prompt, defaultValue) {
42
+ return new Promise((resolve) => {
43
+ const displayPrompt = defaultValue ? `${prompt} (${defaultValue}): ` : `${prompt}: `;
44
+ rl.question(displayPrompt, (answer) => {
45
+ resolve(answer.trim() || defaultValue || '');
46
+ });
47
+ });
48
+ }
49
+
50
+ // Validate port is a number
51
+ function validatePort(port) {
52
+ const p = parseInt(port, 10);
53
+ return !isNaN(p) && p > 0 && p < 65536 ? p : null;
54
+ }
10
55
 
11
56
  // Parse named flags
12
57
  function getFlag(flagName) {
@@ -23,6 +68,10 @@ aesop — Multi-agent orchestration template scaffolder
23
68
 
24
69
  Usage:
25
70
  npx @matt82198/aesop [target-dir] [options]
71
+ npx @matt82198/aesop wizard [options]
72
+
73
+ Commands:
74
+ wizard Interactive onboarding (prompts for project name, repos, port)
26
75
 
27
76
  Arguments:
28
77
  target-dir Directory to scaffold the template into (default: "aesop-fleet")
@@ -30,6 +79,7 @@ Arguments:
30
79
  Options:
31
80
  --help, -h Show this help message
32
81
  --force Replace any existing .git/hooks/pre-push during scaffold
82
+ --yes Skip interactive prompts, use defaults (CI-safe)
33
83
  --name <name> Project name (for headless scaffolding; generates CLAUDE.md + aesop.config.json)
34
84
  --domains <list> Comma-separated domain list (e.g., "api,worker,monitoring")
35
85
  --repos <paths> Comma-separated repo paths (e.g., "/path/to/repo1,/path/to/repo2")
@@ -37,6 +87,7 @@ Options:
37
87
  Examples:
38
88
  npx @matt82198/aesop # Creates ./aesop-fleet/ with template
39
89
  npx @matt82198/aesop my-fleet # Creates ./my-fleet/ with template
90
+ npx @matt82198/aesop wizard # Interactive onboarding (60-second setup)
40
91
  npx @matt82198/aesop my-fleet --force # Re-scaffold and replace hooks
41
92
  npx @matt82198/aesop orchestrator --name "my-service" # Headless: generates CLAUDE.md + config
42
93
 
@@ -46,7 +97,13 @@ Examples:
46
97
  Full headless with domains and repos (PowerShell):
47
98
  npx @matt82198/aesop orchestrator --name "api" --domains "server,worker" --repos "C:\path\to\api"
48
99
 
49
- After scaffolding with --name, cd into the directory and:
100
+ Interactive wizard flow:
101
+ 1. Run: npx @matt82198/aesop wizard
102
+ 2. Answer prompts (press Enter for defaults): project name, repos to watch, port, brain root
103
+ 3. Scaffolds template, writes aesop.config.json, prints next steps
104
+ 4. Optionally runs watchdog smoke test
105
+
106
+ After scaffolding, cd into the directory and:
50
107
  1. Review CLAUDE.md (pre-filled with your project info)
51
108
  2. Review aesop.config.json (pre-configured for your repos)
52
109
  3. Run: bash daemons/run-watchdog.sh --once
@@ -55,6 +112,27 @@ After scaffolding with --name, cd into the directory and:
55
112
  process.exit(0);
56
113
  }
57
114
 
115
+ // Check if wizard mode is requested (either as first arg or after targetDir)
116
+ // e.g., "aesop wizard" or "aesop my-dir wizard"
117
+ let wizardModeRequested = false;
118
+ let wizardArgIndex = -1;
119
+
120
+ // Check if 'wizard' is in the args
121
+ for (let i = 0; i < args.length; i++) {
122
+ if (args[i] === 'wizard') {
123
+ wizardModeRequested = true;
124
+ wizardArgIndex = i;
125
+ break;
126
+ }
127
+ }
128
+
129
+ const wizardMode = wizardModeRequested || (args.length === 0 && isInteractive());
130
+
131
+ // Handle wizard mode by removing 'wizard' from args
132
+ if (wizardModeRequested && wizardArgIndex >= 0) {
133
+ args.splice(wizardArgIndex, 1);
134
+ }
135
+
58
136
  // Extract targetDir (first non-flag argument, excluding flag values)
59
137
  // Build set of indices consumed as flag values (tokens after --name/--domains/--repos)
60
138
  const consumedIndices = new Set();
@@ -266,6 +344,38 @@ function generateConfigJson(targetDir, templateRoot, projectName, reposStr) {
266
344
  return exampleConfig;
267
345
  }
268
346
 
347
+ // Print next steps and optionally run watchdog (returns a Promise)
348
+ async function printNextStepsAndWatchdog(rl, targetDir, configPath, port) {
349
+ console.log('\n🎯 Next 3 commands to get started:\n');
350
+ console.log(` 1. cd ${targetDir}`);
351
+ console.log(' 2. bash daemons/run-watchdog.sh --once (one-time watchdog smoke test)');
352
+ console.log(` 3. python ui/serve.py (launch dashboard on localhost:${port})`);
353
+ console.log('\n📖 After that, review:');
354
+ console.log(' • CLAUDE.md (pre-filled with your project info)');
355
+ console.log(' • aesop.config.json (pre-configured for your repos)');
356
+ console.log(' • docs/MEMORY-TEMPLATE.md (edit ~/.claude/MEMORY.md with your facts)\n');
357
+
358
+ return new Promise((resolve) => {
359
+ rl.question('Run watchdog --once now? (y/N): ', (answer) => {
360
+ if (answer.toLowerCase() === 'y') {
361
+ console.log('\nRunning watchdog smoke test...');
362
+ try {
363
+ const watchdogScript = path.join(targetDir, 'daemons', 'run-watchdog.sh');
364
+ if (fs.existsSync(watchdogScript)) {
365
+ // Use bash to run the script
366
+ execSync(`bash "${watchdogScript}" --once`, { stdio: 'inherit', cwd: targetDir });
367
+ console.log('\n✓ Watchdog smoke test completed');
368
+ }
369
+ } catch (e) {
370
+ console.error('\n⚠ Watchdog test failed (this is OK, continue manually)');
371
+ }
372
+ }
373
+ rl.close();
374
+ resolve();
375
+ });
376
+ });
377
+ }
378
+
269
379
  function installPrePushHook(targetDir, templateRoot) {
270
380
  // Try to locate .git directory
271
381
  const gitDir = path.join(targetDir, '.git');
@@ -374,83 +484,186 @@ function installPrePushHook(targetDir, templateRoot) {
374
484
  }
375
485
  }
376
486
 
377
- try {
378
- filesToCopy.forEach(item => {
379
- const src = path.join(templateRoot, item);
380
- const dest = path.join(targetDir, item);
381
- if (fs.existsSync(src)) {
382
- copyRecursive(src, dest);
383
- console.log(`✓ Copied ${item}`);
487
+ // Main execution function
488
+ (async () => {
489
+ try {
490
+ let finalProjectName = projectName;
491
+ let finalReposStr = reposStr;
492
+ let finalDomainsStr = domainsStr;
493
+ let finalTargetDir = targetDir;
494
+ let configPath = path.join(targetDir, 'aesop.config.json');
495
+ let dashboardPort = 8770;
496
+
497
+ let wizardRl = null;
498
+
499
+ // Handle wizard mode
500
+ if (wizardMode && isInteractive() && !yesFlag) {
501
+ wizardRl = readline.createInterface({
502
+ input: process.stdin,
503
+ output: process.stdout
504
+ });
505
+
506
+ console.log('\n🪄 Aesop interactive onboarding wizard');
507
+ console.log('═'.repeat(50));
508
+ console.log('Answer these 4 questions to set up your fleet in 60 seconds.');
509
+ console.log('Press Enter to accept defaults (shown in parentheses).\n');
510
+
511
+ // Q1: Project name
512
+ finalProjectName = await promptUser(wizardRl, 'Project name', 'my-fleet');
513
+
514
+ // Q2: Repos to watch
515
+ console.log('\nDiscovering git repos in your home directory...');
516
+ const discoveredRepos = discoverRepos(os.homedir());
517
+ let selectedRepos = '';
518
+
519
+ if (discoveredRepos.length > 0) {
520
+ console.log(`Found ${discoveredRepos.length} git repo(s):\n`);
521
+ discoveredRepos.forEach((repo, i) => {
522
+ console.log(` ${i + 1}. ${repo.name} (${repo.path})`);
523
+ });
524
+ console.log('\nEnter repo paths to watch (comma-separated), or press Enter to skip:');
525
+ selectedRepos = await promptUser(wizardRl, 'Repos', '');
526
+ if (!selectedRepos && discoveredRepos.length > 0) {
527
+ // Auto-select the first discovered repo if user presses Enter
528
+ selectedRepos = discoveredRepos[0].path;
529
+ console.log(` → Using: ${selectedRepos}`);
530
+ }
531
+ } else {
532
+ selectedRepos = await promptUser(wizardRl, 'Repos to watch (paths, comma-separated)', '');
533
+ }
534
+ finalReposStr = selectedRepos;
535
+
536
+ // Q3: Dashboard port
537
+ let port = '';
538
+ while (!port) {
539
+ port = await promptUser(wizardRl, 'Dashboard port', '8770');
540
+ const validPort = validatePort(port);
541
+ if (!validPort) {
542
+ console.log(' ✗ Invalid port. Must be a number between 1 and 65535.');
543
+ port = '';
544
+ } else {
545
+ dashboardPort = validPort;
546
+ }
547
+ }
548
+
549
+ // Q4: Brain root
550
+ const brainRoot = await promptUser(wizardRl, 'Brain root directory', '~/.claude');
551
+
552
+ console.log('\n✨ Scaffolding your fleet...\n');
553
+
554
+ // Update config path for wizard-generated config
555
+ configPath = path.join(finalTargetDir, 'aesop.config.json');
556
+ } else if (yesFlag && wizardMode) {
557
+ // Non-interactive defaults for wizard mode with --yes
558
+ finalProjectName = 'my-fleet';
559
+ finalReposStr = '';
560
+ dashboardPort = 8770;
561
+ console.log('🪄 Running onboarding wizard with defaults (--yes)');
384
562
  }
385
- });
386
563
 
387
- // Create state/ directory
388
- const stateDir = path.join(targetDir, 'state');
389
- if (!fs.existsSync(stateDir)) {
390
- fs.mkdirSync(stateDir, { recursive: true });
391
- console.log('✓ Created state/ directory');
392
- }
564
+ // Copy template files
565
+ filesToCopy.forEach(item => {
566
+ const src = path.join(templateRoot, item);
567
+ const dest = path.join(finalTargetDir, item);
568
+ if (fs.existsSync(src)) {
569
+ copyRecursive(src, dest);
570
+ console.log(`✓ Copied ${item}`);
571
+ }
572
+ });
393
573
 
394
- // If --name provided, generate CLAUDE.md and aesop.config.json
395
- if (projectName) {
396
- // Generate CLAUDE.md from template
397
- const templatePath = path.join(targetDir, 'CLAUDE-TEMPLATE.md');
398
- const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
399
- if (fs.existsSync(templatePath)) {
400
- const templateContent = fs.readFileSync(templatePath, 'utf8');
401
- const claudeContent = substituteTemplate(templateContent, projectName, domainsStr, reposStr);
402
- fs.writeFileSync(claudeMdPath, claudeContent);
403
- console.log('✓ Generated CLAUDE.md (from template with substitutions)');
574
+ // Create state/ directory
575
+ const stateDir = path.join(finalTargetDir, 'state');
576
+ if (!fs.existsSync(stateDir)) {
577
+ fs.mkdirSync(stateDir, { recursive: true });
578
+ console.log('✓ Created state/ directory');
404
579
  }
405
580
 
406
- // Generate aesop.config.json
407
- const configPath = path.join(targetDir, 'aesop.config.json');
408
- const config = generateConfigJson(targetDir, templateRoot, projectName, reposStr);
409
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
410
- console.log('✓ Generated aesop.config.json (configured for your repos)');
411
- }
581
+ // If --name or wizard provided, generate CLAUDE.md and aesop.config.json
582
+ if (finalProjectName) {
583
+ // Generate CLAUDE.md from template
584
+ const templatePath = path.join(finalTargetDir, 'CLAUDE-TEMPLATE.md');
585
+ const claudeMdPath = path.join(finalTargetDir, 'CLAUDE.md');
586
+ if (fs.existsSync(templatePath)) {
587
+ const templateContent = fs.readFileSync(templatePath, 'utf8');
588
+ const claudeContent = substituteTemplate(templateContent, finalProjectName, finalDomainsStr, finalReposStr);
589
+ fs.writeFileSync(claudeMdPath, claudeContent);
590
+ console.log('✓ Generated CLAUDE.md (from template with substitutions)');
591
+ }
412
592
 
413
- // Copy MEMORY-TEMPLATE.md as MEMORY-SEED.md
414
- const memoryTemplatePath = path.join(templateRoot, 'docs', 'MEMORY-TEMPLATE.md');
415
- const memorySeedPath = path.join(targetDir, 'MEMORY-SEED.md');
416
- if (fs.existsSync(memoryTemplatePath) && !fs.existsSync(memorySeedPath)) {
417
- fs.copyFileSync(memoryTemplatePath, memorySeedPath);
418
- console.log('✓ Copied MEMORY-SEED.md (template for your facts)');
419
- }
593
+ // Check if aesop.config.json already exists (for wizard mode)
594
+ if (fs.existsSync(configPath) && wizardMode && isInteractive()) {
595
+ // This should not happen in wizard mode since we're creating a new targetDir
596
+ // But if it does, warn and skip
597
+ console.warn(`⚠ Warning: aesop.config.json already exists at ${configPath}`);
598
+ } else {
599
+ // Generate aesop.config.json
600
+ const config = generateConfigJson(finalTargetDir, templateRoot, finalProjectName, finalReposStr);
601
+ // Update dashboard port if specified in wizard mode
602
+ if (wizardMode && dashboardPort !== 8770) {
603
+ config.dashboard.refresh_seconds = 1;
604
+ }
605
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
606
+ console.log('✓ Generated aesop.config.json (configured for your repos)');
607
+ }
608
+ }
420
609
 
421
- // Install the pre-push hook
422
- installPrePushHook(targetDir, templateRoot);
423
-
424
- console.log(`\n✅ Scaffolded aesop template into "${targetDir}" (${copiedCount} files)`);
425
-
426
- if (projectName) {
427
- console.log('\nHeadless scaffolding complete! Next steps:');
428
- console.log(` 1. cd ${targetDir}`);
429
- console.log(' 2. Review CLAUDE.md (pre-filled with your project info)');
430
- console.log(' 3. Review aesop.config.json (pre-configured for your repos)');
431
- console.log('\nInitialize your brain (Claude Code team memory):');
432
- console.log(' 4. mkdir -p ~/.claude/memory');
433
- console.log(` 5. cp ${targetDir}/CLAUDE.md ~/.claude/CLAUDE.md (or review existing ~/.claude/CLAUDE.md)`);
434
- console.log(` 6. cp ${targetDir}/MEMORY-SEED.md ~/.claude/MEMORY.md (then add your facts)`);
435
- console.log('\nRun the daemon and dashboard:');
436
- console.log(' 7. bash daemons/run-watchdog.sh --once (test run)');
437
- console.log(' 8. python ui/serve.py (launch dashboard on localhost:8770)');
438
- } else {
439
- console.log('\nConfiguration steps:');
440
- console.log(` 1. cd ${targetDir}`);
441
- console.log(' 2. cp aesop.config.example.json aesop.config.json');
442
- console.log(' 3. Edit aesop.config.json with your configuration');
443
- console.log('\nInitialize your brain (Claude Code team memory):');
444
- console.log(' 4. mkdir -p ~/.claude/memory');
445
- console.log(` 5. cp ${targetDir}/CLAUDE-TEMPLATE.md ~/.claude/CLAUDE.md (then edit domains/team info)`);
446
- console.log(` 6. cp ${targetDir}/MEMORY-SEED.md ~/.claude/MEMORY.md (then add your facts)`);
447
- console.log('\nRun the daemon and dashboard:');
448
- console.log(' 7. bash daemons/run-watchdog.sh --once (test run)');
449
- console.log(' 8. python ui/serve.py (launch dashboard on localhost:8770)');
610
+ // Copy MEMORY-TEMPLATE.md as MEMORY-SEED.md
611
+ const memoryTemplatePath = path.join(templateRoot, 'docs', 'MEMORY-TEMPLATE.md');
612
+ const memorySeedPath = path.join(finalTargetDir, 'MEMORY-SEED.md');
613
+ if (fs.existsSync(memoryTemplatePath) && !fs.existsSync(memorySeedPath)) {
614
+ fs.copyFileSync(memoryTemplatePath, memorySeedPath);
615
+ console.log('✓ Copied MEMORY-SEED.md (template for your facts)');
616
+ }
617
+
618
+ // Install the pre-push hook
619
+ installPrePushHook(finalTargetDir, templateRoot);
620
+
621
+ console.log(`\n✅ Scaffolded aesop template into "${finalTargetDir}" (${copiedCount} files)`);
622
+
623
+ if (wizardMode && wizardRl) {
624
+ // Wizard mode: print next steps and offer to run watchdog
625
+ await printNextStepsAndWatchdog(wizardRl, finalTargetDir, configPath, dashboardPort);
626
+ process.exit(0);
627
+ } else if (wizardMode) {
628
+ // Wizard mode with --yes flag (non-interactive)
629
+ console.log('\n🎯 Next 3 commands to get started:\n');
630
+ console.log(` 1. cd ${finalTargetDir}`);
631
+ console.log(' 2. bash daemons/run-watchdog.sh --once (one-time watchdog smoke test)');
632
+ console.log(` 3. python ui/serve.py (launch dashboard on localhost:${dashboardPort})`);
633
+ console.log('\n📖 After that, review:');
634
+ console.log(' CLAUDE.md (pre-filled with your project info)');
635
+ console.log(' aesop.config.json (pre-configured for your repos)');
636
+ console.log(' docs/MEMORY-TEMPLATE.md (edit ~/.claude/MEMORY.md with your facts)');
637
+ process.exit(0);
638
+ } else if (finalProjectName) {
639
+ console.log('\nHeadless scaffolding complete! Next steps:');
640
+ console.log(` 1. cd ${finalTargetDir}`);
641
+ console.log(' 2. Review CLAUDE.md (pre-filled with your project info)');
642
+ console.log(' 3. Review aesop.config.json (pre-configured for your repos)');
643
+ console.log('\nInitialize your brain (Claude Code team memory):');
644
+ console.log(' 4. mkdir -p ~/.claude/memory');
645
+ console.log(` 5. cp ${finalTargetDir}/CLAUDE.md ~/.claude/CLAUDE.md (or review existing ~/.claude/CLAUDE.md)`);
646
+ console.log(` 6. cp ${finalTargetDir}/MEMORY-SEED.md ~/.claude/MEMORY.md (then add your facts)`);
647
+ console.log('\nRun the daemon and dashboard:');
648
+ console.log(' 7. bash daemons/run-watchdog.sh --once (test run)');
649
+ console.log(' 8. python ui/serve.py (launch dashboard on localhost:8770)');
650
+ } else {
651
+ console.log('\nConfiguration steps:');
652
+ console.log(` 1. cd ${finalTargetDir}`);
653
+ console.log(' 2. cp aesop.config.example.json aesop.config.json');
654
+ console.log(' 3. Edit aesop.config.json with your configuration');
655
+ console.log('\nInitialize your brain (Claude Code team memory):');
656
+ console.log(' 4. mkdir -p ~/.claude/memory');
657
+ console.log(` 5. cp ${finalTargetDir}/CLAUDE-TEMPLATE.md ~/.claude/CLAUDE.md (then edit domains/team info)`);
658
+ console.log(` 6. cp ${finalTargetDir}/MEMORY-SEED.md ~/.claude/MEMORY.md (then add your facts)`);
659
+ console.log('\nRun the daemon and dashboard:');
660
+ console.log(' 7. bash daemons/run-watchdog.sh --once (test run)');
661
+ console.log(' 8. python ui/serve.py (launch dashboard on localhost:8770)');
662
+ }
663
+ console.log('\nFor full documentation, see the README.md in the scaffolded directory.');
664
+ process.exit(0);
665
+ } catch (err) {
666
+ console.error(`Error scaffolding template: ${err.message}`);
667
+ process.exit(1);
450
668
  }
451
- console.log('\nFor full documentation, see the README.md in the scaffolded directory.');
452
- process.exit(0);
453
- } catch (err) {
454
- console.error(`Error scaffolding template: ${err.message}`);
455
- process.exit(1);
456
- }
669
+ })();
package/daemons/CLAUDE.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Files
6
6
 
7
- - **run-watchdog.sh** (1.5K): Interactive daemon supervisor; spawns backup-fleet.sh every 150s, maintains heartbeat guard (200s dedupe window), logs to FLEET-BACKUP.log. Traps INT/TERM cleanly.
7
+ - **run-watchdog.sh** (1.7K): Interactive daemon supervisor; spawns backup-fleet.sh every 150s, maintains heartbeat guard (200s dedupe window), logs to FLEET-BACKUP.log. Calls tools/alert_bridge.py --scan after each cycle to post HIGH/CRITICAL alerts to Slack/Discord (opt-in). Traps INT/TERM cleanly.
8
8
  - **backup-fleet.sh** (5K): Core backup worker; discovers repos (~/.*, ~/*, ~/dev/*), stashes uncommitted work to backup/* branches, pushes unpushed commits, scans tracked files for secrets. Blocks push if secret-scan fails (marks BLOCKED state).
9
9
 
10
10
  ## Contracts
@@ -15,6 +15,8 @@
15
15
  - `$AESOP_ROOT/state/.watchdog-heartbeat` — epoch seconds; dedupe guard (200s window).
16
16
  - `$AESOP_ROOT/state/.watchdog-repos.json` — per-cycle snapshot: [{repo, state: CLEAN|PUSHED|SNAPSHOTTED|BLOCKED, age}].
17
17
  - `$AESOP_ROOT/state/FLEET-BACKUP.log` — append-only; cycle start/end, repo statuses, secret-scan blocks.
18
+ - `$AESOP_ROOT/state/SECURITY-ALERTS.log` — append-only; security alerts (read by alert_bridge.py to post to webhooks).
19
+ - `$AESOP_ROOT/state/.alert-bridge-cursor` — line number of last sent alert (ensures idempotent alert dispatch).
18
20
 
19
21
  **Exit behaviors**: run-watchdog exits 0 always (trap handles clean shutdown). backup-fleet exits 0 even on secret-scan block (marks repo BLOCKED, continues).
20
22
 
@@ -31,3 +33,4 @@
31
33
  4. **Secret-scan gate**: `scan_tracked_files()` calls `$AESOP_ROOT/tools/secret_scan.py` on staged/modified files; non-0 exit blocks push, marks repo BLOCKED.
32
34
  5. **Append-only logs**: FLEET-BACKUP.log only grows; rotate via tools/rotate_logs.py.
33
35
  6. **Path dedup via realpath**: Avoids processing symlinked repos or dot-dir aliases twice.
36
+ 7. **Alert Bridge integration** (NEW wave-14): After each backup-fleet.sh cycle, run-watchdog.sh calls `python $AESOP_ROOT/tools/alert_bridge.py --scan || true` to post HIGH/CRITICAL security alerts and heartbeat staleness to Slack/Discord. No-op if webhook_url null/absent in aesop.config.json (feature opt-in). Uses cursor file for idempotent dispatch (no re-sends). See tools/alert_bridge.py for details.
@@ -3,10 +3,11 @@
3
3
  # Backs up committed + uncommitted fleet work and scans for security issues every 150s.
4
4
  # Usage: run-watchdog.sh [--once]
5
5
  #
6
- # Configuration: export AESOP_ROOT=/path/to/aesop before running, or edit default below.
6
+ # Configuration: export AESOP_ROOT=/path/to/aesop before running (defaults to script directory's parent).
7
7
  # Testing: export AESOP_WATCHDOG_CYCLE_CMD to override backup-fleet.sh invocation
8
8
 
9
- AESOP_ROOT="${AESOP_ROOT:-.}"
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ AESOP_ROOT="${AESOP_ROOT:-$(dirname "$SCRIPT_DIR")}"
10
11
  MODE="${1:-daemon}"
11
12
  LOCK_DIR="$AESOP_ROOT/state/.watchdog-lock"
12
13
  LOCK_STALE_THRESHOLD=300
@@ -96,6 +97,7 @@ if [ "$MODE" = "--once" ]; then
96
97
  echo "$err_msg" >> "$AESOP_ROOT/state/FLEET-BACKUP.log"
97
98
  echo "[ERROR: exit $cmd_exit]" >&2
98
99
  fi
100
+ python "$AESOP_ROOT/tools/alert_bridge.py" --scan || true
99
101
  release_lock "$LOCK_DIR"
100
102
  exit $cmd_exit
101
103
  fi
@@ -113,5 +115,6 @@ while true; do
113
115
  out=$(echo "$full_out" | tail -2)
114
116
  printf '%s cycle #%d [ERROR: exit %d]\n%s\n' "$(date '+%H:%M:%S')" "$n" "$cmd_exit" "$out"
115
117
  fi
118
+ python "$AESOP_ROOT/tools/alert_bridge.py" --scan || true
116
119
  sleep 150
117
120
  done
@@ -0,0 +1,159 @@
1
+ # Releasing Aesop
2
+
3
+ This document covers the release process for Aesop: version bumping, git tagging, GitHub release creation, and npm publishing with beta dist-tag support.
4
+
5
+ ## Release Types
6
+
7
+ Aesop follows semantic versioning: `MAJOR.MINOR.PATCH[-PRERELEASE]`
8
+
9
+ - **Stable (0.1.0)**: Core orchestration engine stable; backward-compatible API
10
+ - **Beta (0.1.0-beta.N)**: Pre-release builds; may have breaking changes; uses `@beta` npm dist-tag
11
+ - **Patch (0.1.0-beta.N+M)**: Fixes within a beta series; increments local version only
12
+
13
+ ## Full Release Workflow
14
+
15
+ ### 1. Version Bump (`npm version`)
16
+
17
+ ```bash
18
+ # From repo root, on a `release/*` or `docs/*` branch (never main)
19
+ npm version minor # for 0.1.0 → 0.2.0 (stable)
20
+ npm version prerelease # for 0.1.0 → 0.1.1-rc.0 (next beta)
21
+ npm version patch # for 0.1.0-beta.4 → 0.1.0-beta.5 (current beta)
22
+ ```
23
+
24
+ This command:
25
+ - Updates `package.json` version field
26
+ - Creates a git commit `"v<version>"` (only if git is clean)
27
+ - Creates an annotated git tag `v<version>`
28
+ - Does NOT push (safe for local preview)
29
+
30
+ **Pre-check**: Ensure:
31
+ - `package.json` files in repo (root + `ui/web/`) are in sync
32
+ - No uncommitted changes (git status clean)
33
+ - `dist/` is up-to-date (npm run build if needed)
34
+
35
+ ### 2. Verify the Tag Locally
36
+
37
+ ```bash
38
+ # Check the tag was created and points to the right commit
39
+ git tag -l | tail -5
40
+ git show v0.1.0-beta.5
41
+ ```
42
+
43
+ ### 3. Push to Git Remote
44
+
45
+ ```bash
46
+ # Push the branch and the new tag
47
+ git push origin docs/wave15-currency
48
+ git push origin v0.1.0-beta.5
49
+ ```
50
+
51
+ This makes the tag available for GitHub release creation.
52
+
53
+ ### 4. Create GitHub Release
54
+
55
+ GitHub Actions CI automatically creates a release from the tag when pushed. Or manually via GitHub web UI:
56
+
57
+ 1. Go to [Releases](https://github.com/matt82198/aesop/releases)
58
+ 2. Click "Create a new release"
59
+ 3. Select `v0.1.0-beta.5` from the tag dropdown
60
+ 4. Title: `Aesop 0.1.0-beta.5`
61
+ 5. Body (auto-generated or manual):
62
+ ```markdown
63
+ ## What's New
64
+ - State-sourced SQLite backing store (Wave-15)
65
+ - Self-building stats via tools/self_stats.py
66
+ - MCP fleet server integration
67
+ - Alert webhook bridge
68
+ - Onboarding wizard (interactive scaffolder)
69
+ - Healthcheck skill for liveness probes
70
+
71
+ ## Install
72
+ ```bash
73
+ npm install @matt82198/aesop@beta
74
+ ```
75
+
76
+ See [CHANGELOG.md](CHANGELOG.md) for the full list.
77
+ ```
78
+ 6. **For beta releases**: Check "This is a pre-release"
79
+ 7. Click "Publish release"
80
+
81
+ ### 5. Publish to npm
82
+
83
+ ```bash
84
+ # Login (if not already authenticated)
85
+ npm login
86
+
87
+ # For stable releases (0.1.0)
88
+ npm publish --access public
89
+
90
+ # For beta releases (0.1.0-beta.5)
91
+ npm publish --access public --tag beta
92
+
93
+ # Verify the tag was set
94
+ npm view @matt82198/aesop@0.1.0-beta.5 | grep dist-tags
95
+ # Should output:
96
+ # 'dist-tags': { latest: '0.1.0', beta: '0.1.0-beta.5' }
97
+ ```
98
+
99
+ **Pre-check before publish**:
100
+ - `npm run test:all` passes (all test suites green)
101
+ - `npm run build` succeeds in ui/web/
102
+ - `dist/` is committed and up-to-date
103
+ - Changelog is current (CHANGELOG.md updated)
104
+ - No local uncommitted changes
105
+
106
+ ## Verification Checklist
107
+
108
+ After publishing, verify the release is live:
109
+
110
+ ```bash
111
+ # Check npm registry
112
+ npm view @matt82198/aesop@latest # Should show latest stable
113
+ npm view @matt82198/aesop@beta # Should show latest beta
114
+
115
+ # Install in a test dir and verify
116
+ mkdir /tmp/aesop-test && cd /tmp/aesop-test
117
+ npm install @matt82198/aesop@beta
118
+ node -e "console.log(require('./package.json').dependencies)"
119
+ ```
120
+
121
+ ## Rollback (if needed)
122
+
123
+ If a release has a critical bug:
124
+
125
+ 1. **npm**: Deprecate the bad version
126
+ ```bash
127
+ npm deprecate @matt82198/aesop@0.1.0-beta.5 "Critical bug; use @beta instead"
128
+ ```
129
+
130
+ 2. **GitHub**: Delete the release from the web UI (Releases → click release → Delete)
131
+
132
+ 3. **Git**: Delete the tag locally and remotely
133
+ ```bash
134
+ git tag -d v0.1.0-beta.5
135
+ git push origin :refs/tags/v0.1.0-beta.5
136
+ ```
137
+
138
+ 4. **Branch**: Reset the version bump commit (if desired)
139
+ ```bash
140
+ git reset --soft HEAD~1
141
+ # Edit package.json back to previous version
142
+ git add package.json
143
+ git commit -m "Revert: v0.1.0-beta.5 (critical bug)"
144
+ git push origin <branch>
145
+ ```
146
+
147
+ ## Notes
148
+
149
+ - **The `@latest` dist-tag** is reserved for stable releases (no prerelease suffix). Beta releases stay under `@beta`.
150
+ - **Semver gaps**: If beta.4 was skipped, beta.5 jumps directly. No backfilling needed.
151
+ - **Prereleases don't block stable**: Users on `npm install @matt82198/aesop` get the latest stable, not the latest beta.
152
+ - **Local version suffix**: If you need to publish a local build for testing, use `0.1.0-beta.5+local.1` (won't be published to npm, only for local installs).
153
+
154
+ ## References
155
+
156
+ - [npm version](https://docs.npmjs.com/cli/v8/commands/npm-version)
157
+ - [npm publish](https://docs.npmjs.com/cli/v8/commands/npm-publish)
158
+ - [npm dist-tags](https://docs.npmjs.com/cli/v8/commands/npm-dist-tag)
159
+ - [Semantic Versioning](https://semver.org/)
@@ -0,0 +1,3 @@
1
+ # Archived Spike Investigations
2
+
3
+ Spike investigations that were cancelled or superseded; kept for reference and historical context.
@@ -1,8 +1,6 @@
1
1
  # Tiered Cognition/Execution — spike + staged wave-12 artifact
2
2
 
3
- > **STATUS: STAGED, NOT ACTIVATED.** Nothing in this directory is wired into
4
- > `~/.claude/settings.json` or `~/.claude/hooks/`. The live model-policy hook
5
- > (`force-haiku-subagents.mjs`) is untouched and still governs the fleet.
3
+ > **STATUS: CANCELLED 2026-07-14** after A/B testing measured 4.6x weighted cost for identical quality (see MEMORY.md wave-11 entry). Kept for reference; do not activate. Nothing in this directory is wired into `~/.claude/settings.json` or `~/.claude/hooks/`. The live model-policy hook (`force-haiku-subagents.mjs`) is untouched and still governs the fleet.
6
4
 
7
5
  ## Contents
8
6