@matt82198/aesop 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +24 -9
- package/aesop.config.example.json +16 -0
- package/bin/cli.js +107 -34
- package/daemons/run-watchdog.sh +6 -2
- package/docs/CONFIGURE.md +31 -20
- package/docs/FIRST-WAVE.md +29 -9
- package/docs/INSTALL.md +181 -26
- package/docs/MICROKERNEL.md +452 -0
- package/docs/PORTING.md +4 -0
- package/docs/README.md +7 -3
- package/docs/THE-AESOP-HYPOTHESIS.md +156 -0
- package/driver/CLAUDE.md +123 -123
- package/driver/README.md +40 -2
- package/driver/adjudication_gate.py +367 -0
- package/driver/aesop.config.example.json +20 -4
- package/driver/backend_config.py +603 -12
- package/driver/claude_code_driver.py +9 -17
- package/driver/codex_driver.py +80 -25
- package/driver/context_pack.py +454 -0
- package/driver/openai_compatible_driver.py +53 -11
- package/driver/openai_transport.py +31 -10
- package/driver/orchestrator_backend.py +332 -0
- package/driver/orchestrator_driver.py +589 -0
- package/driver/proc_util.py +134 -0
- package/driver/wave_loop.py +801 -59
- package/driver/wave_scheduler.py +361 -37
- package/monitor/collect-signals.mjs +83 -0
- package/package.json +1 -1
- package/state_store/coordination.py +65 -5
- package/tools/ci_merge_wait.py +88 -42
- package/tools/ci_shard_runner.py +128 -0
- package/tools/doctor.js +124 -12
- package/tools/reproduce.js +11 -2
- package/tools/seated_shadow_adjudication.py +920 -0
- package/tools/self_stats.py +61 -6
- package/tools/shadow_adjudication.py +1024 -0
- package/tools/verify_test_suite_count.py +250 -0
- package/tools/verify_ui_trio_redaction_proof.py +292 -0
- package/tools/wave_manifest_lint.py +485 -0
- package/tools/wave_scorecard.py +430 -0
- package/ui/config.py +1 -1
- package/ui/cost.py +176 -3
- package/ui/web/dist/assets/{index-CP68RIh3.js → index-4rp6B_ID.js} +1 -1
- package/ui/web/dist/assets/{index-CNQxaiOW.css → index-B2lTtpsH.css} +1 -1
- package/ui/web/dist/index.html +2 -2
package/tools/doctor.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
12
13
|
const { spawnSync } = require('child_process');
|
|
13
14
|
const net = require('net');
|
|
14
15
|
|
|
@@ -34,17 +35,19 @@ function colorFail() {
|
|
|
34
35
|
function checkNodeVersion() {
|
|
35
36
|
const version = parseInt(process.versions.node.split('.')[0], 10);
|
|
36
37
|
const passed = version >= 18;
|
|
37
|
-
const hint = passed ?
|
|
38
|
+
const hint = passed ? `v${process.versions.node}` : `Found Node.js v${process.versions.node}, need >=18`;
|
|
38
39
|
return { passed, hint };
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
// Check Python available
|
|
42
|
+
// Check Python available and version >= 3.10
|
|
42
43
|
function checkPython() {
|
|
43
44
|
// Try python3 first
|
|
44
|
-
|
|
45
|
+
let pythonBin = null;
|
|
46
|
+
let result3 = spawnSync('python3', ['--version'], { encoding: 'utf8', timeout: 5000 });
|
|
47
|
+
|
|
45
48
|
if (result3.error && result3.error.code === 'ENOENT') {
|
|
46
49
|
// python3 not found, try python fallback
|
|
47
|
-
const result = spawnSync('python', ['--version'], {
|
|
50
|
+
const result = spawnSync('python', ['--version'], { encoding: 'utf8', timeout: 5000 });
|
|
48
51
|
if (result.error && result.error.code === 'ENOENT') {
|
|
49
52
|
// Neither python3 nor python found
|
|
50
53
|
return { passed: false, hint: 'python3 or python not found on PATH' };
|
|
@@ -53,13 +56,35 @@ function checkPython() {
|
|
|
53
56
|
// python exists but returned non-zero exit code
|
|
54
57
|
return { passed: false, hint: 'python found but returned non-zero exit code' };
|
|
55
58
|
}
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
if (result3.status !== 0) {
|
|
59
|
+
pythonBin = 'python';
|
|
60
|
+
} else if (result3.status !== 0) {
|
|
59
61
|
// python3 exists but returned non-zero exit code
|
|
60
62
|
return { passed: false, hint: 'python3 found but returned non-zero exit code' };
|
|
63
|
+
} else {
|
|
64
|
+
pythonBin = 'python3';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Extract version from python output
|
|
68
|
+
let versionStr = '';
|
|
69
|
+
try {
|
|
70
|
+
const versionOutput = result3.stdout || result3.stderr || '';
|
|
71
|
+
const match = versionOutput.match(/Python\s+(\d+)\.(\d+)/i);
|
|
72
|
+
if (match) {
|
|
73
|
+
const major = parseInt(match[1], 10);
|
|
74
|
+
const minor = parseInt(match[2], 10);
|
|
75
|
+
versionStr = `${major}.${minor}`;
|
|
76
|
+
if (major > 3 || (major === 3 && minor >= 10)) {
|
|
77
|
+
return { passed: true, hint: `v${versionStr}` };
|
|
78
|
+
} else {
|
|
79
|
+
return { passed: false, hint: `Found Python v${versionStr}, need >=3.10` };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {
|
|
83
|
+
// Version extraction failed but python runs
|
|
84
|
+
return { passed: true, hint: 'Available (version check skipped)' };
|
|
61
85
|
}
|
|
62
|
-
|
|
86
|
+
|
|
87
|
+
return { passed: true, hint: 'Available' };
|
|
63
88
|
}
|
|
64
89
|
|
|
65
90
|
// Check git repo (.git directory exists)
|
|
@@ -78,13 +103,98 @@ function checkConfig() {
|
|
|
78
103
|
return { passed: false, hint: 'aesop.config.json not found' };
|
|
79
104
|
}
|
|
80
105
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
81
|
-
JSON.parse(content);
|
|
106
|
+
const config = JSON.parse(content);
|
|
107
|
+
|
|
108
|
+
// Validate repos array exists and is an array
|
|
109
|
+
if (!('repos' in config)) {
|
|
110
|
+
return { passed: false, hint: 'Missing required field: repos' };
|
|
111
|
+
}
|
|
112
|
+
if (!Array.isArray(config.repos)) {
|
|
113
|
+
return { passed: false, hint: 'repos field must be an array' };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Validate aesop_root exists on disk (if present in config)
|
|
117
|
+
if (config.aesop_root && !fs.existsSync(config.aesop_root)) {
|
|
118
|
+
return { passed: false, hint: `aesop_root does not exist: ${config.aesop_root}` };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Validate repo paths exist (if specified)
|
|
122
|
+
for (const repo of config.repos) {
|
|
123
|
+
if (repo.path && !fs.existsSync(repo.path)) {
|
|
124
|
+
return { passed: false, hint: `Repo path does not exist: ${repo.path}` };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
82
128
|
return { passed: true, hint: '' };
|
|
83
129
|
} catch (e) {
|
|
84
|
-
return { passed: false, hint: `Config
|
|
130
|
+
return { passed: false, hint: `Config error: ${e.message}` };
|
|
85
131
|
}
|
|
86
132
|
}
|
|
87
133
|
|
|
134
|
+
// Check for placeholder repo URLs and warn
|
|
135
|
+
function checkRepoURLs() {
|
|
136
|
+
const configPath = path.join(CURRENT_DIR, 'aesop.config.json');
|
|
137
|
+
try {
|
|
138
|
+
if (!fs.existsSync(configPath)) {
|
|
139
|
+
// Skip this check if config doesn't exist (main checkConfig will fail)
|
|
140
|
+
return { passed: true, hint: '' };
|
|
141
|
+
}
|
|
142
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
143
|
+
const config = JSON.parse(content);
|
|
144
|
+
|
|
145
|
+
// Check for placeholder URLs
|
|
146
|
+
const placeholderPattern = /https:\/\/github\.com\/user\//i;
|
|
147
|
+
if (config.repos && Array.isArray(config.repos)) {
|
|
148
|
+
for (const repo of config.repos) {
|
|
149
|
+
if (repo.url && placeholderPattern.test(repo.url)) {
|
|
150
|
+
return {
|
|
151
|
+
passed: false,
|
|
152
|
+
hint: `Placeholder URL found: ${repo.url}. Replace with actual repo URL or remove the repo entry.`
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { passed: true, hint: '' };
|
|
159
|
+
} catch (e) {
|
|
160
|
+
return { passed: true, hint: '' }; // Skip check on config parse error (main checkConfig handles it)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check if ~/.claude/skills/power/SKILL.md and ~/.claude/skills/buildsystem/SKILL.md exist (WARN only)
|
|
165
|
+
function checkSkillsFiles() {
|
|
166
|
+
const homeDir = os.homedir();
|
|
167
|
+
const powerSkill = path.join(homeDir, '.claude', 'skills', 'power', 'SKILL.md');
|
|
168
|
+
const buildsystemSkill = path.join(homeDir, '.claude', 'skills', 'buildsystem', 'SKILL.md');
|
|
169
|
+
|
|
170
|
+
const powerExists = fs.existsSync(powerSkill);
|
|
171
|
+
const buildsystemExists = fs.existsSync(buildsystemSkill);
|
|
172
|
+
|
|
173
|
+
// Skills are required but we WARN, not FAIL, to allow doctor to proceed on partial setup
|
|
174
|
+
if (!powerExists && !buildsystemExists) {
|
|
175
|
+
return {
|
|
176
|
+
passed: true, // WARN, not fail
|
|
177
|
+
hint: `⚠ Skills not found at ~/.claude/skills/. Required before running orchestrator. Copy: cp -r ~/.claude/skills/ here`
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!powerExists) {
|
|
182
|
+
return {
|
|
183
|
+
passed: true, // WARN, not fail
|
|
184
|
+
hint: `⚠ power/SKILL.md not found at ${powerSkill}. Required before running orchestrator.`
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!buildsystemExists) {
|
|
189
|
+
return {
|
|
190
|
+
passed: true, // WARN, not fail
|
|
191
|
+
hint: `⚠ buildsystem/SKILL.md not found at ${buildsystemSkill}. Required before running orchestrator.`
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { passed: true, hint: 'Both skills present' };
|
|
196
|
+
}
|
|
197
|
+
|
|
88
198
|
// Check required directories exist
|
|
89
199
|
function checkDirectories() {
|
|
90
200
|
const requiredDirs = ['daemons', 'dash', 'monitor', 'tools', 'ui'];
|
|
@@ -174,9 +284,11 @@ function formatRow(label, status, hint) {
|
|
|
174
284
|
|
|
175
285
|
const syncChecks = [
|
|
176
286
|
{ label: 'Node.js version ≥18', fn: checkNodeVersion },
|
|
177
|
-
{ label: 'Python
|
|
287
|
+
{ label: 'Python version ≥3.10', fn: checkPython },
|
|
178
288
|
{ label: 'Git repository', fn: checkGitRepo },
|
|
179
|
-
{ label: 'aesop.config.json (
|
|
289
|
+
{ label: 'aesop.config.json (structure & required fields)', fn: checkConfig },
|
|
290
|
+
{ label: 'Repository URLs (no placeholders)', fn: checkRepoURLs },
|
|
291
|
+
{ label: 'Skills files (power & buildsystem)', fn: checkSkillsFiles },
|
|
180
292
|
{ label: 'Required directories (daemons, dash, monitor, tools, ui)', fn: checkDirectories },
|
|
181
293
|
{ label: 'Git pre-push hook installed', fn: checkPrePushHook }
|
|
182
294
|
];
|
package/tools/reproduce.js
CHANGED
|
@@ -44,9 +44,10 @@ function colorSkip() {
|
|
|
44
44
|
|
|
45
45
|
// Detect context: repo checkout vs installed package
|
|
46
46
|
function detectContext() {
|
|
47
|
-
// Repo checkout has .git/config
|
|
47
|
+
// Repo checkout has .git/config, package.json with npm scripts, AND package-lock.json
|
|
48
48
|
const hasGitConfig = fs.existsSync(path.join(PACKAGE_ROOT, '.git', 'config'));
|
|
49
49
|
const hasPackageJson = fs.existsSync(path.join(PACKAGE_ROOT, 'package.json'));
|
|
50
|
+
const hasPackageLock = fs.existsSync(path.join(PACKAGE_ROOT, 'package-lock.json'));
|
|
50
51
|
const hasTestScripts = hasPackageJson && (() => {
|
|
51
52
|
try {
|
|
52
53
|
const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
|
|
@@ -57,7 +58,8 @@ function detectContext() {
|
|
|
57
58
|
})();
|
|
58
59
|
|
|
59
60
|
// Installed package is in node_modules with limited structure
|
|
60
|
-
|
|
61
|
+
// OR repo-mode detected but package-lock.json is missing (fresh scaffold — degrade gracefully)
|
|
62
|
+
const isInstalled = (!hasGitConfig && !hasTestScripts) || (hasGitConfig && !hasPackageLock);
|
|
61
63
|
|
|
62
64
|
return isInstalled ? 'installed' : 'repo';
|
|
63
65
|
}
|
|
@@ -368,7 +370,14 @@ function runInstalledMode() {
|
|
|
368
370
|
(async function main() {
|
|
369
371
|
try {
|
|
370
372
|
const context = detectContext();
|
|
373
|
+
const hasGitConfig = fs.existsSync(path.join(PACKAGE_ROOT, '.git', 'config'));
|
|
374
|
+
const hasPackageLock = fs.existsSync(path.join(PACKAGE_ROOT, 'package-lock.json'));
|
|
375
|
+
const isDegradedFromRepo = hasGitConfig && !hasPackageLock;
|
|
376
|
+
|
|
371
377
|
console.log(`${COLORS.DIM}Context: ${context}${COLORS.RESET}`);
|
|
378
|
+
if (isDegradedFromRepo) {
|
|
379
|
+
console.log(`${COLORS.YELLOW}Scaffolded project detected — running installed-mode verification; full reproduce is for the aesop repo itself${COLORS.RESET}`);
|
|
380
|
+
}
|
|
372
381
|
|
|
373
382
|
let results;
|
|
374
383
|
if (context === 'repo') {
|