ai-maestro 1.0.0

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 (81) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +15 -0
  3. package/README.md +727 -0
  4. package/bin/maestro.mjs +246 -0
  5. package/package.json +29 -0
  6. package/src/checkpoint.mjs +102 -0
  7. package/src/codex-diagnostics.mjs +682 -0
  8. package/src/codex-home-inspect.mjs +252 -0
  9. package/src/codex-home.mjs +258 -0
  10. package/src/commands.mjs +809 -0
  11. package/src/config.mjs +11 -0
  12. package/src/debug.mjs +164 -0
  13. package/src/encoding-guard.mjs +125 -0
  14. package/src/engines/ai-router-engine.mjs +37 -0
  15. package/src/engines/codex-engine.mjs +21 -0
  16. package/src/engines/engine.mjs +21 -0
  17. package/src/engines/registry.mjs +29 -0
  18. package/src/files.mjs +65 -0
  19. package/src/format.mjs +132 -0
  20. package/src/lock.mjs +439 -0
  21. package/src/memory-log.mjs +18 -0
  22. package/src/memory.mjs +1 -0
  23. package/src/orchestration/attempt-chain-runtime.mjs +627 -0
  24. package/src/orchestration/budget-manager.mjs +121 -0
  25. package/src/orchestration/capability-registry.mjs +108 -0
  26. package/src/orchestration/consolidator.mjs +772 -0
  27. package/src/orchestration/delegation-executor.mjs +262 -0
  28. package/src/orchestration/delegation-manager.mjs +116 -0
  29. package/src/orchestration/deployment-intent.mjs +36 -0
  30. package/src/orchestration/engine-history.mjs +110 -0
  31. package/src/orchestration/engine-policy.mjs +73 -0
  32. package/src/orchestration/engine-selector.mjs +187 -0
  33. package/src/orchestration/execution-context.mjs +45 -0
  34. package/src/orchestration/failure-classifier.mjs +136 -0
  35. package/src/orchestration/failure-evidence.mjs +237 -0
  36. package/src/orchestration/fallback-chain-lock.mjs +217 -0
  37. package/src/orchestration/fallback-chain-trail.mjs +218 -0
  38. package/src/orchestration/fallback-executor.mjs +146 -0
  39. package/src/orchestration/fallback-graph.mjs +106 -0
  40. package/src/orchestration/fallback-recommendation.mjs +73 -0
  41. package/src/orchestration/file-conflict-detector.mjs +126 -0
  42. package/src/orchestration/host-validation.mjs +241 -0
  43. package/src/orchestration/orchestration-loop.mjs +1971 -0
  44. package/src/orchestration/orchestration-runtime.mjs +1019 -0
  45. package/src/orchestration/orchestration-scheduler.mjs +135 -0
  46. package/src/orchestration/orchestration-trail.mjs +192 -0
  47. package/src/orchestration/preflight.mjs +212 -0
  48. package/src/orchestration/provider-health.mjs +566 -0
  49. package/src/orchestration/provider-router.mjs +352 -0
  50. package/src/orchestration/rc-check-adapters.mjs +817 -0
  51. package/src/orchestration/rc-check.mjs +544 -0
  52. package/src/orchestration/rc-policy.mjs +200 -0
  53. package/src/orchestration/runtime-gate.mjs +146 -0
  54. package/src/orchestration/sector-managers.mjs +215 -0
  55. package/src/orchestration/self-hosting-canary.mjs +231 -0
  56. package/src/orchestration/self-hosting-readiness.mjs +244 -0
  57. package/src/orchestration/spec-planner.mjs +877 -0
  58. package/src/orchestration/subtask-planner.mjs +176 -0
  59. package/src/orchestration/task-classifier.mjs +241 -0
  60. package/src/orchestration/verifier.mjs +1608 -0
  61. package/src/orchestration-commands.mjs +279 -0
  62. package/src/planner.mjs +38 -0
  63. package/src/project.mjs +1 -0
  64. package/src/run-diagnostics.mjs +641 -0
  65. package/src/runner.mjs +243 -0
  66. package/src/safety.mjs +116 -0
  67. package/src/schema.mjs +371 -0
  68. package/src/session-commands.mjs +521 -0
  69. package/src/shell.mjs +93 -0
  70. package/src/smart-planner.mjs +249 -0
  71. package/src/task-commands.mjs +1182 -0
  72. package/src/tasks.mjs +134 -0
  73. package/src/ui/commands.mjs +76 -0
  74. package/src/ui/events.mjs +45 -0
  75. package/src/ui/public/app.js +600 -0
  76. package/src/ui/public/index.html +88 -0
  77. package/src/ui/public/style.css +460 -0
  78. package/src/ui/server.mjs +112 -0
  79. package/src/ui/state.mjs +504 -0
  80. package/src/usage.mjs +178 -0
  81. package/src/workspace-diff.mjs +228 -0
@@ -0,0 +1,231 @@
1
+ import fs from 'node:fs/promises';
2
+ import fsSync from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ import { execFileRaw } from '../shell.mjs';
7
+ import { redactObject, redactCredentials } from '../format.mjs';
8
+ import { validateOrchestrationEvent } from '../schema.mjs';
9
+
10
+ export const CANARY_OUTCOMES = Object.freeze({ PASSED: 'PASSED', INFRASTRUCTURE_UNAVAILABLE: 'INFRASTRUCTURE_UNAVAILABLE', FAILED: 'FAILED' });
11
+ export const CANARY_EXPECTED_TEXT = 'AI-MAESTRO-CANARY-OK';
12
+
13
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
14
+ const projectRoot = path.resolve(moduleDir, '..', '..');
15
+ const maestroBin = path.join('bin', 'maestro.mjs');
16
+
17
+ function key(value) {
18
+ const resolved = path.resolve(String(value));
19
+ return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
20
+ }
21
+
22
+ function sameOrDescendant(candidate, boundary) {
23
+ const candidateKey = key(candidate);
24
+ const boundaryKey = key(boundary);
25
+ if (candidateKey === boundaryKey) return true;
26
+ const prefix = boundaryKey.endsWith(path.sep) ? boundaryKey : boundaryKey + path.sep;
27
+ return candidateKey.startsWith(prefix);
28
+ }
29
+
30
+ async function exists(filePath) {
31
+ try { await fs.access(filePath); return true; } catch (error) { if (error.code === 'ENOENT') return false; throw error; }
32
+ }
33
+
34
+ async function realpathOrResolved(filePath) {
35
+ try { return await fs.realpath(filePath); } catch (error) { if (error.code === 'ENOENT') return path.resolve(filePath); throw error; }
36
+ }
37
+
38
+ async function parentWritable(parentDir) {
39
+ try { await fs.access(parentDir, fsSync.constants.W_OK); return true; } catch { return false; }
40
+ }
41
+
42
+ export async function preflightControlledCanary({ targetPath } = {}) {
43
+ if (!targetPath || String(targetPath).trim().length === 0) return { ok: false, reason: 'targetPath is required' };
44
+ const resolvedTarget = path.resolve(String(targetPath));
45
+ const resolvedProjectRoot = path.resolve(projectRoot);
46
+ if (sameOrDescendant(resolvedTarget, resolvedProjectRoot)) return { ok: false, reason: 'targetPath must be outside projectRoot', targetPath: resolvedTarget, projectRoot: resolvedProjectRoot };
47
+
48
+ const projectRootReal = await realpathOrResolved(resolvedProjectRoot);
49
+ const parentDir = path.dirname(resolvedTarget);
50
+ const parentReal = await realpathOrResolved(parentDir);
51
+ const realTargetCandidate = path.join(parentReal, path.basename(resolvedTarget));
52
+ if (sameOrDescendant(realTargetCandidate, projectRootReal)) return { ok: false, reason: 'targetPath realpath containment conflicts with projectRoot', targetPath: realTargetCandidate, projectRoot: projectRootReal };
53
+ if (await exists(resolvedTarget)) return { ok: false, reason: 'targetPath already exists', targetPath: resolvedTarget };
54
+
55
+ let stat;
56
+ try { stat = await fs.stat(parentDir); } catch (error) { if (error.code === 'ENOENT') return { ok: false, reason: 'targetPath parent does not exist', parentDir }; throw error; }
57
+ if (!stat.isDirectory()) return { ok: false, reason: 'targetPath parent is not a directory', parentDir };
58
+ if (!await parentWritable(parentDir)) return { ok: false, reason: 'targetPath parent is not writable', parentDir };
59
+ return { ok: true, targetPath: resolvedTarget, projectRoot: resolvedProjectRoot, parentDir };
60
+ }
61
+
62
+ async function textOrNull(filePath) {
63
+ try { return await fs.readFile(filePath, 'utf-8'); } catch (error) { if (error.code === 'ENOENT') return null; throw error; }
64
+ }
65
+
66
+ async function readJsonl(filePath) {
67
+ const text = await textOrNull(filePath);
68
+ return text ? text.trim().split(/\r?\n/).filter(Boolean).map(line => JSON.parse(line)) : [];
69
+ }
70
+
71
+ function evidence(name, result) {
72
+ return redactObject({ name, code: result.code, timedOut: result.timedOut === true, stdout: result.stdout || '', stderr: result.stderr || '' });
73
+ }
74
+
75
+ function infraEvidence(result) {
76
+ const text = ((result && result.stdout) || '') + '\n' + ((result && result.stderr) || '');
77
+ return result && (result.timedOut === true || /ENOENT|not found|missing_tool|provider_unavailable|network_error|authentication_error|engine_resolution_cmd_only|Codex nao encontrado|Reconnecting\.\.\.\s*\d+\/\d+|stream disconnected before completion/i.test(text));
78
+ }
79
+
80
+ function isUnknownCommandResult(result) {
81
+ const text = ((result && result.stdout) || '') + '\n' + ((result && result.stderr) || '');
82
+ return !!(result && result.code !== 0 && /Unknown command/i.test(text));
83
+ }
84
+
85
+ function trimOneTrailingNewline(text) {
86
+ return String(text || '').replace(/\r?\n$/, '');
87
+ }
88
+
89
+ async function runStep(name, command, args, options, steps) {
90
+ const result = await (options.execFileRawFn || execFileRaw)(command, args, options);
91
+ steps.push(evidence(name, result));
92
+ return result;
93
+ }
94
+
95
+ async function createCanaryTask(targetPath, execOptions, steps) {
96
+ const script = [
97
+ "import { addTask } from './src/tasks.mjs';",
98
+ "const task = await addTask('Create CANARY_RESULT.txt containing exactly AI-MAESTRO-CANARY-OK.', {",
99
+ " title: '03G controlled canary task',",
100
+ " engine: 'codex9',",
101
+ " files: ['CANARY_RESULT.txt'],",
102
+ " acceptance: ['CANARY_RESULT.txt contains exactly AI-MAESTRO-CANARY-OK']",
103
+ "});",
104
+ "console.log(String(task.id));"
105
+ ].join('\n');
106
+ const result = await runStep('create-canary-task', process.execPath, ['--input-type=module', '-e', script], { ...execOptions, cwd: targetPath }, steps);
107
+ if (result.code !== 0) return { ok: false, result };
108
+ const taskId = String(result.stdout || '').trim().split(/\r?\n/).pop();
109
+ return { ok: /^\d+$/.test(taskId), taskId, result };
110
+ }
111
+
112
+ async function validateClone(targetPath, execOptions, steps) {
113
+ const schema = await runStep('schema-check', process.execPath, [maestroBin, 'schema-check'], { ...execOptions, cwd: targetPath }, steps);
114
+ if (schema.code !== 0) return { ok: false, reason: 'schema-check failed' };
115
+ const events = await readJsonl(path.join(targetPath, '.maestro', 'ORCHESTRATIONS.jsonl'));
116
+ if (events.length === 0) return { ok: false, reason: 'ORCHESTRATIONS.jsonl is empty' };
117
+ for (const event of events) {
118
+ const validation = validateOrchestrationEvent(event);
119
+ if (!validation.ok) return { ok: false, reason: 'invalid orchestration event: ' + validation.errors.join('; ') };
120
+ }
121
+ return { ok: true, events: events.length };
122
+ }
123
+
124
+ async function latestRunForTask(targetPath, taskId) {
125
+ if (!taskId) return null;
126
+ try {
127
+ const runs = await readJsonl(path.join(targetPath, '.maestro', 'RUNS.jsonl'));
128
+ for (let index = runs.length - 1; index >= 0; index -= 1) {
129
+ if (String(runs[index]?.taskId) === String(taskId)) return runs[index];
130
+ }
131
+ return null;
132
+ } catch {
133
+ return null;
134
+ }
135
+ }
136
+
137
+ function runEntryInfraEvidence(entry) {
138
+ if (!entry) return false;
139
+ const text = [
140
+ entry.stdout,
141
+ entry.stderr,
142
+ entry.fullStdout,
143
+ entry.fullStderr,
144
+ entry.error,
145
+ entry.message,
146
+ entry.reason,
147
+ JSON.stringify(entry)
148
+ ].filter(value => value !== undefined && value !== null).join('\n');
149
+ return infraEvidence({ stdout: text, stderr: '', timedOut: entry.timedOut === true });
150
+ }
151
+
152
+ async function cleanup(registeredTargetPath) {
153
+ if (!registeredTargetPath) return null;
154
+ try { await fs.rm(registeredTargetPath, { recursive: true, force: true }); return null; } catch (error) { return 'cleanup failed for registered targetPath: ' + (error.message || String(error)); }
155
+ }
156
+
157
+ function classify({ cloneResult, initResult, readinessResult, doctorResult, runResult, runLogInfrastructureEvidence, artifactOk, validationOk }) {
158
+ if (cloneResult && cloneResult.code !== 0) return CANARY_OUTCOMES.INFRASTRUCTURE_UNAVAILABLE;
159
+ if (initResult && initResult.code !== 0) return CANARY_OUTCOMES.INFRASTRUCTURE_UNAVAILABLE;
160
+ if (readinessResult && readinessResult.code !== 0 && !isUnknownCommandResult(readinessResult)) return CANARY_OUTCOMES.INFRASTRUCTURE_UNAVAILABLE;
161
+ if (doctorResult && doctorResult.code !== 0) return CANARY_OUTCOMES.INFRASTRUCTURE_UNAVAILABLE;
162
+ if (runLogInfrastructureEvidence === true) return CANARY_OUTCOMES.INFRASTRUCTURE_UNAVAILABLE;
163
+ if (runResult && runResult.code !== 0 && infraEvidence(runResult)) return CANARY_OUTCOMES.INFRASTRUCTURE_UNAVAILABLE;
164
+ if (runResult && runResult.code !== 0) return CANARY_OUTCOMES.FAILED;
165
+ if (artifactOk !== true || validationOk !== true) return CANARY_OUTCOMES.FAILED;
166
+ return CANARY_OUTCOMES.PASSED;
167
+ }
168
+
169
+ export async function runControlledCanary({ targetPath, timeoutMs = 180000, execFileRawFn = execFileRaw } = {}) {
170
+ const steps = [];
171
+ const warnings = [];
172
+ const preflight = await preflightControlledCanary({ targetPath });
173
+ if (!preflight.ok) return redactObject({ ok: false, outcome: CANARY_OUTCOMES.FAILED, reason: preflight.reason, preflight, steps, warnings, writesPrevented: true });
174
+
175
+ const registeredTargetPath = preflight.targetPath;
176
+ let cloneResult = null;
177
+ let initResult = null;
178
+ let readinessResult = null;
179
+ let doctorResult = null;
180
+ let runResult = null;
181
+ let artifactOk = false;
182
+ let validationOk = false;
183
+ let runLogInfrastructureEvidence = false;
184
+ let validation = null;
185
+ let taskId = null;
186
+ const execOptions = { quiet: true, timeoutMs, execFileRawFn };
187
+ const officialEnv = { ...process.env, MAESTRO_CODEX_INVOCATION: process.env.MAESTRO_CODEX_INVOCATION || 'codex-js-isolated', MAESTRO_CODEX_STRATEGY: process.env.MAESTRO_CODEX_STRATEGY || 'basic', MAESTRO_CODEX_PROFILE: process.env.MAESTRO_CODEX_PROFILE || 'default' };
188
+
189
+ let finalResult = null;
190
+ try {
191
+ cloneResult = await runStep('git-clone', 'git', ['clone', preflight.projectRoot, registeredTargetPath], execOptions, steps);
192
+ if (cloneResult.code === 0) initResult = await runStep('maestro-init', process.execPath, [maestroBin, 'init'], { ...execOptions, cwd: registeredTargetPath }, steps);
193
+ if (initResult && initResult.code === 0) readinessResult = await runStep('maestro-readiness', process.execPath, [maestroBin, 'readiness'], { ...execOptions, cwd: registeredTargetPath }, steps);
194
+ const readinessOk = readinessResult && readinessResult.code === 0;
195
+ if (readinessResult && isUnknownCommandResult(readinessResult)) {
196
+ warnings.push(redactCredentials('maestro readiness is unavailable in the cloned committed snapshot; falling back to maestro doctor'));
197
+ doctorResult = await runStep('maestro-doctor', process.execPath, [maestroBin, 'doctor'], { ...execOptions, cwd: registeredTargetPath }, steps);
198
+ }
199
+ const healthOk = readinessOk || (doctorResult && doctorResult.code === 0);
200
+ if (healthOk) {
201
+ const created = await createCanaryTask(registeredTargetPath, execOptions, steps);
202
+ if (created.ok) {
203
+ taskId = created.taskId;
204
+ runResult = await runStep('maestro-orchestrate-real', process.execPath, [maestroBin, 'orchestrate', taskId, '--real', '--force', '--quiet'], { ...execOptions, cwd: registeredTargetPath, env: officialEnv, timeoutMs }, steps);
205
+ runLogInfrastructureEvidence = runEntryInfraEvidence(await latestRunForTask(registeredTargetPath, taskId));
206
+ } else {
207
+ runResult = created.result;
208
+ }
209
+ }
210
+ if (runResult && runResult.code === 0) {
211
+ const artifact = await textOrNull(path.join(registeredTargetPath, 'CANARY_RESULT.txt'));
212
+ artifactOk = trimOneTrailingNewline(artifact) === CANARY_EXPECTED_TEXT;
213
+ validation = await validateClone(registeredTargetPath, execOptions, steps);
214
+ validationOk = validation.ok === true;
215
+ }
216
+ const outcome = classify({ cloneResult, initResult, readinessResult, doctorResult, runResult, runLogInfrastructureEvidence, artifactOk, validationOk });
217
+ const ok = outcome === CANARY_OUTCOMES.PASSED;
218
+ finalResult = { ok, outcome, reason: ok ? 'controlled canary passed' : 'controlled canary did not pass', registeredTargetPath, createdResources: [registeredTargetPath], taskId, enginePolicy: { selected: 'codex-official', fallback: 'codex9 only after evidenced official unavailability', concurrency: 1, timeoutMs }, artifactOk, validation, steps, warnings };
219
+ } finally {
220
+ const cleanupWarning = await cleanup(registeredTargetPath);
221
+ if (cleanupWarning) warnings.push(redactCredentials(cleanupWarning));
222
+ }
223
+ return redactObject(finalResult || { ok: false, outcome: CANARY_OUTCOMES.FAILED, reason: 'controlled canary ended before classification', registeredTargetPath, createdResources: [registeredTargetPath], steps, warnings });
224
+ }
225
+
226
+ export async function canaryCommand(targetPath, options = {}) {
227
+ const result = await runControlledCanary({ targetPath, ...options });
228
+ console.log(redactCredentials(JSON.stringify(result, null, 2)));
229
+ if (!result.ok) process.exitCode = 1;
230
+ return result;
231
+ }
@@ -0,0 +1,244 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ import { doctor, init, schemaCheck } from '../session-commands.mjs';
6
+ import { addTask } from '../tasks.mjs';
7
+ import { writeUtf8TextFile } from '../files.mjs';
8
+ import { doctorLine, redactObject } from '../format.mjs';
9
+ import { validateOrchestrationEvent } from '../schema.mjs';
10
+ import { runSerialOrchestration } from './orchestration-runtime.mjs';
11
+ import { DEFAULT_BUDGET } from './budget-manager.mjs';
12
+
13
+ export const READINESS_STAGE_NAMES = Object.freeze([
14
+ 'doctor', 'schema-check-real-project', 'bootstrap-temp-project',
15
+ 'create-parent-and-children-fixture', 'dry-run-no-side-effects',
16
+ 'serial-real-fake-engine', 'controlled-parallel-real-fake-engine',
17
+ 'audit-trail-start-finish-events', 'schema-check-temp-project',
18
+ 'cleanup-temp-project'
19
+ ]);
20
+
21
+ const TERMINAL = new Set(['completed', 'completed_with_warnings', 'failed', 'blocked', 'needs_human']);
22
+ const OK_TEXT = 'AI-MAESTRO-READINESS-OK';
23
+
24
+ export function readinessEngineRecord(id = 'codex9') {
25
+ return {
26
+ id, displayName: 'Readiness Fake Codex', provider: 'test', model: 'fake', enabled: true, validated: true, source: '03g-readiness',
27
+ cost: { class: 'free', inputPerMillion: null, outputPerMillion: null, currency: null },
28
+ capabilities: { reasoning: 3, planning: 3, coding: 3, debugging: 3, review: 3, writing: 3, documentation: 3, vision: false, longContext: null, structuredOutput: null, toolCalling: true },
29
+ tools: { shell: true, filesystemRead: true, filesystemWrite: true, browser: false, imageInput: false, testExecution: true },
30
+ roles: ['worker'], limits: { maxContextTokens: null, maxOutputTokens: null, requestsPerMinute: null, requestsPerDay: null, maxParallel: 1 },
31
+ health: { available: null, lastCheckedAt: null, recentSuccessRate: null, averageLatencyMs: null, consecutiveFailures: 0 },
32
+ confidence: { capabilities: 'confirmed', cost: 'confirmed', tools: 'confirmed' }
33
+ };
34
+ }
35
+
36
+ export function readOnlyReadinessSubtask(id, extra = {}) {
37
+ return { id, title: 'Review child ' + id, description: 'review child ' + id, expectedFiles: [], blockedBy: [], parallelizable: true, requires: { filesystemWrite: false }, parentTaskId: '1', ...extra };
38
+ }
39
+
40
+ export function writeReadinessSubtask(id, file = 'evidence.txt', extra = {}) {
41
+ return { id, title: 'Write child ' + id, description: 'write ' + file + ' for child ' + id, expectedFiles: [file], blockedBy: [], parallelizable: false, requires: { filesystemWrite: true }, parentTaskId: '1', ...extra };
42
+ }
43
+
44
+ export function readinessDelegation(subtasks) {
45
+ return { decision: 'READY', manager: { id: 'readiness-manager' }, subtasks, assignments: subtasks.map(subtask => ({ subtaskId: subtask.id, role: 'worker', candidateEngine: 'codex9', reason: ['03g-readiness'] })), executionMode: 'serial', requiresHuman: false, warnings: [] };
46
+ }
47
+
48
+ export function readinessModuleResults(subtasks) {
49
+ return {
50
+ classification: { taskKind: 'simple-edit', complexity: 1, risk: 'low', canDelegate: true },
51
+ manager: { id: 'maestro', decision: 'decompose' }, engineCandidate: { id: 'codex9' },
52
+ preflight: { decision: 'ACCEPT', reason: '03g readiness fixture' }, budget: { allowed: true, reason: '03g readiness fixture' }, policy: { ok: true, warnings: [] },
53
+ runtimeGate: { decision: 'DELEGATE', reasons: ['03g readiness fixture'] }, delegation: readinessDelegation(subtasks)
54
+ };
55
+ }
56
+
57
+ function subtaskIdFromPrompt(prompt) {
58
+ const match = String(prompt || '').match(/\bchild\s+([A-Za-z0-9_-]+)\b/i);
59
+ return match ? match[1] : null;
60
+ }
61
+
62
+ function evidenceFileFromPrompt(prompt) {
63
+ const match = String(prompt || '').match(/\bwrite\s+([^\s]+)\s+for\s+child\b/i);
64
+ return match ? match[1] : null;
65
+ }
66
+
67
+ export function createReadinessFakeEngine({ delayMs = 0, codes = [0], stdout = '03G readiness fake worker completed', stderr = '', onRun = async () => {} } = {}) {
68
+ const calls = [];
69
+ let active = 0;
70
+ let maxActive = 0;
71
+ return {
72
+ name: 'codex9', calls,
73
+ get count() { return calls.length; },
74
+ get maxActive() { return maxActive; },
75
+ async run(prompt, options) {
76
+ const callNumber = calls.length + 1;
77
+ const subtaskId = subtaskIdFromPrompt(prompt);
78
+ active += 1;
79
+ maxActive = Math.max(maxActive, active);
80
+ calls.push({ callNumber, subtaskId, prompt, options, startedAt: Date.now() });
81
+ try {
82
+ await onRun({ callNumber, subtaskId, prompt, options, active });
83
+ if (delayMs > 0) await new Promise(resolve => setTimeout(resolve, delayMs));
84
+ const file = evidenceFileFromPrompt(prompt);
85
+ if (file) await fs.writeFile(file, OK_TEXT + '\n', { encoding: 'utf8' });
86
+ return { code: codes[Math.min(callNumber - 1, codes.length - 1)], stdout, stderr, strategy: '03g-readiness-fake', signal: null, timedOut: false };
87
+ } finally {
88
+ active -= 1;
89
+ }
90
+ }
91
+ };
92
+ }
93
+
94
+ export function acceptedReadinessVerification(outcome = 'verified') {
95
+ return { schemaVersion: 1, scope: 'consolidated', verificationOutcome: outcome, outcome, warnings: [], failures: [], summary: outcome };
96
+ }
97
+
98
+ async function textOrNull(file) {
99
+ try { return await fs.readFile(file, 'utf-8'); } catch (error) { if (error.code === 'ENOENT') return null; throw error; }
100
+ }
101
+
102
+ async function readJsonl(file) {
103
+ const text = await textOrNull(file);
104
+ return text ? text.trim().split(/\r?\n/).filter(Boolean).map(line => JSON.parse(line)) : [];
105
+ }
106
+
107
+ async function exists(file) {
108
+ try { await fs.access(file); return true; } catch (error) { if (error.code === 'ENOENT') return false; throw error; }
109
+ }
110
+
111
+ async function silence(quiet, fn) {
112
+ if (!quiet) return fn();
113
+ const original = [console.log, console.warn, console.error];
114
+ console.log = () => {}; console.warn = () => {}; console.error = () => {};
115
+ try { return await fn(); } finally { [console.log, console.warn, console.error] = original; }
116
+ }
117
+
118
+ export async function bootstrapReadinessProject(projectDir = process.cwd()) {
119
+ await fs.mkdir(path.join(projectDir, '.memory'), { recursive: true });
120
+ await fs.writeFile(path.join(projectDir, '.memory', 'BRIEF.md'), '03G readiness temp project\n', { encoding: 'utf8' });
121
+ await init();
122
+ await fs.writeFile(path.join(projectDir, '.maestro', 'engines.json'), JSON.stringify({ schemaVersion: 1, engines: [readinessEngineRecord()] }, null, 2), { encoding: 'utf8' });
123
+ await fs.writeFile(path.join(projectDir, '.maestro', 'budget.json'), JSON.stringify(DEFAULT_BUDGET, null, 2), { encoding: 'utf8' });
124
+ }
125
+
126
+ export async function createReadinessParent(title = '03G readiness parent') {
127
+ return addTask('Coordinate 03G self-hosting readiness using deterministic fake workers.', { title, status: 'pending', priority: 'medium', engine: 'codex9', files: [], acceptance: ['All fake child runs complete and the parent is verified.'] });
128
+ }
129
+
130
+ function assertOk(condition, message) {
131
+ if (!condition) throw new Error(message);
132
+ }
133
+
134
+ async function runReadinessPipeline() {
135
+ await bootstrapReadinessProject(process.cwd());
136
+ const parent = await createReadinessParent();
137
+ const serialSubtasks = [readOnlyReadinessSubtask('ro1'), readOnlyReadinessSubtask('ro2'), writeReadinessSubtask('wr1')];
138
+ const beforeTasks = await fs.readFile(path.join('.maestro', 'TASKS.json'), 'utf-8');
139
+ const beforeRuns = await fs.readFile(path.join('.maestro', 'RUNS.jsonl'), 'utf-8');
140
+ const dryEngine = createReadinessFakeEngine();
141
+ const dryRun = await runSerialOrchestration({ taskId: parent.id, mode: 'dry-run', engine: dryEngine, suppressOutput: true, moduleResults: readinessModuleResults(serialSubtasks), concurrency: { enabled: true, maxConcurrent: 2 } });
142
+ assertOk(dryRun.executed === false && dryEngine.count === 0, 'dry-run executed fake engine');
143
+ assertOk(await fs.readFile(path.join('.maestro', 'TASKS.json'), 'utf-8') === beforeTasks, 'dry-run mutated TASKS.json');
144
+ assertOk(await fs.readFile(path.join('.maestro', 'RUNS.jsonl'), 'utf-8') === beforeRuns, 'dry-run mutated RUNS.jsonl');
145
+
146
+ const serialEngine = createReadinessFakeEngine({ delayMs: 5 });
147
+ const serialRun = await runSerialOrchestration({ taskId: parent.id, mode: 'real', engine: serialEngine, suppressOutput: true, moduleResults: readinessModuleResults(serialSubtasks), verifyConsolidatedResult: () => acceptedReadinessVerification() });
148
+ assertOk(serialRun.executed === true && serialEngine.maxActive === 1 && TERMINAL.has(serialRun.parentStatus), 'serial fake orchestration failed');
149
+ assertOk(serialRun.consolidation && serialRun.verification, 'serial consolidation or verification missing');
150
+
151
+ const parallelParent = await createReadinessParent('03G readiness parallel parent');
152
+ const parallelSubtasks = [
153
+ readOnlyReadinessSubtask('pa1', { parentTaskId: String(parallelParent.id) }),
154
+ readOnlyReadinessSubtask('pa2', { parentTaskId: String(parallelParent.id) }),
155
+ writeReadinessSubtask('pw1', 'evidence-parallel.txt', { parentTaskId: String(parallelParent.id) })
156
+ ];
157
+ const parallelEngine = createReadinessFakeEngine({ delayMs: 30 });
158
+ const parallelRun = await runSerialOrchestration({ taskId: parallelParent.id, mode: 'real', engine: parallelEngine, suppressOutput: true, moduleResults: readinessModuleResults(parallelSubtasks), concurrency: { enabled: true, maxConcurrent: 2 }, verifyConsolidatedResult: () => acceptedReadinessVerification() });
159
+ assertOk(parallelRun.executed === true && parallelEngine.maxActive > 1 && TERMINAL.has(parallelRun.parentStatus), 'controlled-parallel fake orchestration failed');
160
+
161
+ const events = await readJsonl(path.join('.maestro', 'ORCHESTRATIONS.jsonl'));
162
+ for (const id of [String(parent.id), String(parallelParent.id)]) {
163
+ assertOk(events.some(event => event.parentTaskId === id && event.eventType === 'orchestration_started'), 'missing start event for ' + id);
164
+ assertOk(events.some(event => event.parentTaskId === id && event.eventType === 'orchestration_finished'), 'missing finish event for ' + id);
165
+ }
166
+ for (const event of events) {
167
+ const validation = validateOrchestrationEvent(event);
168
+ assertOk(validation.ok, 'invalid orchestration event: ' + validation.errors.join('; '));
169
+ }
170
+ const tempSchema = await schemaCheck();
171
+ assertOk(tempSchema.ok, 'schema-check failed in temp project');
172
+ return redactObject({ parentTaskId: parent.id, parallelParentTaskId: parallelParent.id, dryRun, dryEngine: { count: dryEngine.count, maxActive: dryEngine.maxActive }, serialRun, parallelRun, serialEngine: { count: serialEngine.count, maxActive: serialEngine.maxActive }, parallelEngine: { count: parallelEngine.count, maxActive: parallelEngine.maxActive }, events, tempSchema });
173
+ }
174
+
175
+ function reportMarkdown(stages, ok) {
176
+ return '# AI Maestro Readiness Check\n\n- Date: ' + new Date().toISOString() + '\n- Status: ' + (ok ? 'PASS' : 'FAIL') + '\n\n## Checks\n\n' + stages.map(stage => '- ' + (stage.ok ? 'OK' : 'FAIL') + ' ' + stage.name + (stage.error ? ' - ' + stage.error : '')).join('\n') + '\n';
177
+ }
178
+
179
+ async function writeReport(stages, ok, cwd = process.cwd()) {
180
+ await fs.mkdir(path.join(cwd, '.maestro'), { recursive: true });
181
+ const reportPath = path.join(cwd, '.maestro', 'READINESS_CHECK.md');
182
+ await writeUtf8TextFile(reportPath, reportMarkdown(stages, ok));
183
+ return reportPath;
184
+ }
185
+
186
+ function pushStage(stages, name, ok, extra = {}) {
187
+ stages.push(redactObject({ name, ok, ...extra }));
188
+ }
189
+
190
+ export async function runReadinessCheck({ quiet = false } = {}) {
191
+ const invocationCwd = process.cwd();
192
+ const stages = [];
193
+ const details = {};
194
+ let tempDir = null;
195
+ let tempRemoved = false;
196
+
197
+ try { await silence(quiet, () => doctor()); pushStage(stages, 'doctor', true); } catch (error) { pushStage(stages, 'doctor', false, { error: error.message || String(error) }); }
198
+ try { const result = await silence(quiet, () => schemaCheck()); pushStage(stages, 'schema-check-real-project', result.ok === true, { checks: result.checks }); } catch (error) { pushStage(stages, 'schema-check-real-project', false, { error: error.message || String(error) }); }
199
+
200
+ const originalCwd = process.cwd();
201
+ try {
202
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'maestro-03g-readiness-'));
203
+ process.chdir(tempDir);
204
+ pushStage(stages, 'bootstrap-temp-project', true, { tempDir });
205
+ const pipeline = await silence(quiet, () => runReadinessPipeline());
206
+ details.pipeline = pipeline;
207
+ pushStage(stages, 'create-parent-and-children-fixture', true, { parentTaskId: pipeline.parentTaskId, parallelParentTaskId: pipeline.parallelParentTaskId });
208
+ pushStage(stages, 'dry-run-no-side-effects', pipeline.dryRun.executed === false && pipeline.dryEngine.count === 0);
209
+ pushStage(stages, 'serial-real-fake-engine', pipeline.serialRun.executed === true && TERMINAL.has(pipeline.serialRun.parentStatus), { maxActive: pipeline.serialEngine.maxActive });
210
+ pushStage(stages, 'controlled-parallel-real-fake-engine', pipeline.parallelRun.executed === true && pipeline.parallelEngine.maxActive > 1, { maxActive: pipeline.parallelEngine.maxActive });
211
+ const started = pipeline.events.filter(event => event.eventType === 'orchestration_started').length;
212
+ const finished = pipeline.events.filter(event => event.eventType === 'orchestration_finished').length;
213
+ pushStage(stages, 'audit-trail-start-finish-events', started >= 2 && finished >= 2, { started, finished });
214
+ pushStage(stages, 'schema-check-temp-project', pipeline.tempSchema.ok === true);
215
+ } catch (error) {
216
+ const message = error.message || String(error);
217
+ for (const name of READINESS_STAGE_NAMES) {
218
+ if (!stages.some(stage => stage.name === name) && !['doctor', 'schema-check-real-project', 'cleanup-temp-project'].includes(name)) pushStage(stages, name, false, { error: message });
219
+ }
220
+ } finally {
221
+ process.chdir(originalCwd);
222
+ if (tempDir) {
223
+ await fs.rm(tempDir, { recursive: true, force: true });
224
+ tempRemoved = !(await exists(tempDir));
225
+ }
226
+ pushStage(stages, 'cleanup-temp-project', tempDir ? tempRemoved : true, tempDir ? { tempDir } : {});
227
+ }
228
+
229
+ const orderedStages = READINESS_STAGE_NAMES.map(name => stages.find(stage => stage.name === name) || { name, ok: false, error: 'stage not executed' });
230
+ const ok = orderedStages.every(stage => stage.ok);
231
+ const reportPath = await writeReport(orderedStages, ok, invocationCwd);
232
+ if (!quiet) {
233
+ console.log('Running AI Maestro readiness check...');
234
+ for (const stage of orderedStages) doctorLine(stage.ok ? 'OK' : 'FAIL', stage.name, stage.error || 'done');
235
+ console.log(reportMarkdown(orderedStages, ok));
236
+ }
237
+ return redactObject({ ok, stages: orderedStages, reportPath, tempDir, tempRemoved, details });
238
+ }
239
+
240
+ export async function readinessCommand() {
241
+ const result = await runReadinessCheck();
242
+ if (!result.ok) process.exitCode = 1;
243
+ return result;
244
+ }