@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.
- package/CHANGELOG.md +46 -5
- package/README.md +57 -1
- package/aesop.config.example.json +8 -1
- package/bin/CLAUDE.md +30 -2
- package/bin/cli.js +286 -73
- package/daemons/CLAUDE.md +4 -1
- package/daemons/run-watchdog.sh +5 -2
- package/docs/RELEASING.md +159 -0
- package/docs/archive/README.md +3 -0
- package/docs/{spikes → archive/spikes}/tiered-cognition/README.md +1 -3
- package/docs/case-study-portfolio.md +61 -0
- package/docs/self-stats-data.json +11 -0
- package/docs/templates/FLEET-OPS-ANALYSIS.example.md +27 -0
- package/docs/templates/FLEET-OPS-RECOMMENDATIONS.example.md +24 -0
- package/docs/templates/PROPOSALS-LOG.example.md +64 -0
- package/hooks/CLAUDE.md +23 -0
- package/mcp/CLAUDE.md +213 -0
- package/mcp/package.json +26 -0
- package/mcp/server.mjs +543 -0
- package/monitor/CHARTER.md +76 -110
- package/monitor/collect-signals.mjs +85 -0
- package/package.json +4 -1
- package/scan/fleet-scan.example.mjs +292 -0
- package/skills/healthcheck/SKILL.md +44 -0
- package/state_store/CLAUDE.md +39 -0
- package/state_store/__init__.py +24 -0
- package/state_store/__pycache__/__init__.cpython-314.pyc +0 -0
- package/state_store/__pycache__/api.cpython-314.pyc +0 -0
- package/state_store/__pycache__/export.cpython-314.pyc +0 -0
- package/state_store/__pycache__/ingest.cpython-314.pyc +0 -0
- package/state_store/__pycache__/projections.cpython-314.pyc +0 -0
- package/state_store/__pycache__/store.cpython-314.pyc +0 -0
- package/state_store/api.py +35 -0
- package/state_store/export.py +19 -0
- package/state_store/ingest.py +24 -0
- package/state_store/projections.py +52 -0
- package/state_store/store.py +102 -0
- package/tools/CLAUDE.md +30 -149
- package/tools/__pycache__/alert_bridge.cpython-314.pyc +0 -0
- package/tools/__pycache__/ci_merge_wait.cpython-314.pyc +0 -0
- package/tools/__pycache__/fleet_prompt_extractor.cpython-314.pyc +0 -0
- package/tools/__pycache__/healthcheck.cpython-314.pyc +0 -0
- package/tools/__pycache__/metrics_gate.cpython-314.pyc +0 -0
- package/tools/__pycache__/secret_scan.cpython-314.pyc +0 -0
- package/tools/__pycache__/self_stats.cpython-314.pyc +0 -0
- package/tools/__pycache__/session_usage_summary.cpython-314.pyc +0 -0
- package/tools/__pycache__/stall_check.cpython-314.pyc +0 -0
- package/tools/__pycache__/transcript_replay.cpython-314.pyc +0 -0
- package/tools/__pycache__/transcript_timeline.cpython-314.pyc +0 -0
- package/tools/__pycache__/verify_dash.cpython-314.pyc +0 -0
- package/tools/__pycache__/verify_submit_encoding.cpython-314.pyc +0 -0
- package/tools/alert_bridge.py +449 -0
- package/tools/ci_merge_wait.py +121 -8
- package/tools/fleet_prompt_extractor.py +134 -0
- package/tools/healthcheck.py +296 -0
- package/tools/secret_scan.py +8 -1
- package/tools/self_stats.py +509 -0
- package/tools/session_usage_summary.py +198 -0
- package/tools/svg_to_png.mjs +50 -0
- package/tools/transcript_replay.py +236 -0
- package/tools/transcript_timeline.py +184 -0
- package/tools/verify_dash.py +361 -542
- package/tools/verify_submit_encoding.py +12 -4
- package/ui/CLAUDE.md +57 -41
- package/ui/__pycache__/agents.cpython-314.pyc +0 -0
- package/ui/__pycache__/collectors.cpython-314.pyc +0 -0
- package/ui/__pycache__/config.cpython-314.pyc +0 -0
- package/ui/__pycache__/cost.cpython-314.pyc +0 -0
- package/ui/__pycache__/csrf.cpython-314.pyc +0 -0
- package/ui/__pycache__/handler.cpython-314.pyc +0 -0
- package/ui/__pycache__/render.cpython-314.pyc +0 -0
- package/ui/__pycache__/serve.cpython-314.pyc +0 -0
- package/ui/__pycache__/sse.cpython-314.pyc +0 -0
- package/ui/agents.py +9 -5
- package/ui/api/__pycache__/submit.cpython-314.pyc +0 -0
- package/ui/api/submit.py +44 -5
- package/ui/collectors.py +184 -35
- package/ui/config.py +9 -0
- package/ui/cost.py +260 -0
- package/ui/csrf.py +7 -3
- package/ui/handler.py +254 -4
- package/ui/render.py +26 -6
- package/ui/sse.py +16 -1
- package/ui/web/.gitattributes +13 -0
- package/ui/web/dist/assets/index-2LZDQirC.js +9 -0
- package/ui/web/dist/assets/index-D4M1qyOv.css +1 -0
- package/ui/web/dist/index.html +14 -0
- package/ui/web/index.html +13 -0
- package/ui/web/package-lock.json +2225 -0
- package/ui/web/package.json +26 -0
- package/ui/web/src/App.test.tsx +74 -0
- package/ui/web/src/App.tsx +142 -0
- package/ui/web/src/CONTRIBUTING-UI.md +49 -0
- package/ui/web/src/components/AgentRow.css +187 -0
- package/ui/web/src/components/AgentRow.test.tsx +209 -0
- package/ui/web/src/components/AgentRow.tsx +207 -0
- package/ui/web/src/components/AgentsPanel.css +108 -0
- package/ui/web/src/components/AgentsPanel.test.tsx +41 -0
- package/ui/web/src/components/AgentsPanel.tsx +58 -0
- package/ui/web/src/components/AlertsPanel.css +88 -0
- package/ui/web/src/components/AlertsPanel.test.tsx +51 -0
- package/ui/web/src/components/AlertsPanel.tsx +67 -0
- package/ui/web/src/components/BacklogPanel.test.tsx +126 -0
- package/ui/web/src/components/BacklogPanel.tsx +122 -0
- package/ui/web/src/components/CostChart.css +110 -0
- package/ui/web/src/components/CostChart.test.tsx +144 -0
- package/ui/web/src/components/CostChart.tsx +152 -0
- package/ui/web/src/components/CostTable.css +93 -0
- package/ui/web/src/components/CostTable.test.tsx +165 -0
- package/ui/web/src/components/CostTable.tsx +94 -0
- package/ui/web/src/components/EventsFeed.css +68 -0
- package/ui/web/src/components/EventsFeed.test.tsx +36 -0
- package/ui/web/src/components/EventsFeed.tsx +31 -0
- package/ui/web/src/components/HealthHeader.css +137 -0
- package/ui/web/src/components/HealthHeader.test.tsx +278 -0
- package/ui/web/src/components/HealthHeader.tsx +281 -0
- package/ui/web/src/components/InboxForm.css +135 -0
- package/ui/web/src/components/InboxForm.test.tsx +208 -0
- package/ui/web/src/components/InboxForm.tsx +116 -0
- package/ui/web/src/components/MessagesTail.module.css +144 -0
- package/ui/web/src/components/MessagesTail.test.tsx +176 -0
- package/ui/web/src/components/MessagesTail.tsx +94 -0
- package/ui/web/src/components/ReposPanel.css +90 -0
- package/ui/web/src/components/ReposPanel.test.tsx +45 -0
- package/ui/web/src/components/ReposPanel.tsx +67 -0
- package/ui/web/src/components/Scorecard.css +106 -0
- package/ui/web/src/components/Scorecard.test.tsx +117 -0
- package/ui/web/src/components/Scorecard.tsx +85 -0
- package/ui/web/src/components/Timeline.module.css +151 -0
- package/ui/web/src/components/Timeline.test.tsx +215 -0
- package/ui/web/src/components/Timeline.tsx +99 -0
- package/ui/web/src/components/TrackerBoard.test.tsx +121 -0
- package/ui/web/src/components/TrackerBoard.tsx +107 -0
- package/ui/web/src/components/TrackerCard.test.tsx +180 -0
- package/ui/web/src/components/TrackerCard.tsx +160 -0
- package/ui/web/src/components/TrackerForm.test.tsx +189 -0
- package/ui/web/src/components/TrackerForm.tsx +144 -0
- package/ui/web/src/lib/api.ts +218 -0
- package/ui/web/src/lib/format.test.ts +89 -0
- package/ui/web/src/lib/format.ts +103 -0
- package/ui/web/src/lib/sanitizeUrl.test.ts +84 -0
- package/ui/web/src/lib/sanitizeUrl.ts +38 -0
- package/ui/web/src/lib/types.ts +230 -0
- package/ui/web/src/lib/useHashRoute.test.ts +60 -0
- package/ui/web/src/lib/useHashRoute.ts +23 -0
- package/ui/web/src/lib/useSSE.ts +175 -0
- package/ui/web/src/main.tsx +10 -0
- package/ui/web/src/styles/global.css +179 -0
- package/ui/web/src/styles/theme.css +184 -0
- package/ui/web/src/styles/work.css +572 -0
- package/ui/web/src/test/fixtures.ts +385 -0
- package/ui/web/src/test/setup.ts +49 -0
- package/ui/web/src/views/Activity.module.css +43 -0
- package/ui/web/src/views/Activity.test.tsx +89 -0
- package/ui/web/src/views/Activity.tsx +31 -0
- package/ui/web/src/views/Cost.css +87 -0
- package/ui/web/src/views/Cost.test.tsx +142 -0
- package/ui/web/src/views/Cost.tsx +54 -0
- package/ui/web/src/views/Overview.css +51 -0
- package/ui/web/src/views/Overview.test.tsx +76 -0
- package/ui/web/src/views/Overview.tsx +46 -0
- package/ui/web/src/views/Work.test.tsx +82 -0
- package/ui/web/src/views/Work.tsx +79 -0
- package/ui/web/src/vite-env.d.ts +10 -0
- package/ui/web/tsconfig.json +22 -0
- package/ui/web/vite.config.ts +25 -0
- package/ui/web/vitest.config.ts +12 -0
- package/ui/templates/dashboard.html +0 -1202
- /package/docs/{spikes → archive/spikes}/tiered-cognition/ACTIVATION.md +0 -0
- /package/docs/{spikes → archive/spikes}/tiered-cognition/DESIGN.md +0 -0
- /package/docs/{spikes → archive/spikes}/tiered-cognition/FINDINGS.md +0 -0
- /package/docs/{spikes → archive/spikes}/tiered-cognition/aesop-cognition.example.md +0 -0
- /package/docs/{spikes → archive/spikes}/tiered-cognition/force-model-policy.merged.mjs +0 -0
- /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
|
-
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
//
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
console.log(
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
package/daemons/run-watchdog.sh
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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/)
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# Tiered Cognition/Execution — spike + staged wave-12 artifact
|
|
2
2
|
|
|
3
|
-
> **STATUS:
|
|
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
|
|