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,279 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { CONFIG } from './config.mjs';
4
+ import { getTaskById, getTasks, getNextTask } from './tasks.mjs';
5
+ import { getEngine } from './engines/registry.mjs';
6
+ import { classifyTask } from './orchestration/task-classifier.mjs';
7
+ import {
8
+ listEngineRecords, getEngineRecord, describeEngine, getEnginesFilePath
9
+ } from './orchestration/capability-registry.mjs';
10
+ import { runPreflight } from './orchestration/preflight.mjs';
11
+ import { selectEngine, selectEngineWithPreflight } from './orchestration/engine-selector.mjs';
12
+ import { planSectorWork } from './orchestration/sector-managers.mjs';
13
+ import { buildDelegationPlan, DELEGATION_LIMITS } from './orchestration/delegation-manager.mjs';
14
+ import { loadFallbackGraph } from './orchestration/fallback-graph.mjs';
15
+ import { loadBudget, evaluateBudget } from './orchestration/budget-manager.mjs';
16
+ import { checkEnginePolicy } from './orchestration/engine-policy.mjs';
17
+ import { summarizeHistoryByEngine } from './orchestration/engine-history.mjs';
18
+ import { getUsageSummary } from './usage.mjs';
19
+
20
+ function printJson(value) {
21
+ console.log(JSON.stringify(value, null, 2));
22
+ }
23
+
24
+ // ---- maestro engines ... ----------------------------------------------
25
+
26
+ export async function enginesList() {
27
+ const engines = await listEngineRecords();
28
+ if (engines.length === 0) {
29
+ console.log('No engines found in ' + getEnginesFilePath() + '.');
30
+ return;
31
+ }
32
+ for (const engine of engines) {
33
+ const costClass = engine.cost ? engine.cost.class : 'unknown';
34
+ console.log(
35
+ '- ' + engine.id + ' [' + (engine.enabled ? 'enabled' : 'disabled') + ', ' +
36
+ (engine.validated ? 'validated' : 'unvalidated') + ', cost=' + costClass + ', roles=' +
37
+ (engine.roles && engine.roles.length ? engine.roles.join('/') : 'none') + '] ' + (engine.displayName || '')
38
+ );
39
+ }
40
+ }
41
+
42
+ export async function enginesShow(id) {
43
+ if (!id) {
44
+ console.log('Usage: maestro engines show <id>');
45
+ return;
46
+ }
47
+ const described = await describeEngine(id);
48
+ if (!described.found) {
49
+ console.log('Engine "' + id + '" not found in ' + getEnginesFilePath() + '.');
50
+ return;
51
+ }
52
+ printJson(described.engineRecord);
53
+ }
54
+
55
+ export async function enginesHealth() {
56
+ const engines = await listEngineRecords();
57
+ for (const engine of engines) {
58
+ const health = engine.health || {};
59
+ console.log(
60
+ '- ' + engine.id + ': available=' + (health.available === null || health.available === undefined ? 'unknown' : health.available) +
61
+ ', recentSuccessRate=' + (typeof health.recentSuccessRate === 'number' ? Math.round(health.recentSuccessRate * 100) + '%' : 'unknown') +
62
+ ', consecutiveFailures=' + (health.consecutiveFailures || 0) +
63
+ ', lastCheckedAt=' + (health.lastCheckedAt || 'never')
64
+ );
65
+ }
66
+ const historySummary = await summarizeHistoryByEngine();
67
+ if (historySummary.length > 0) {
68
+ console.log('\nFrom .maestro/engine-history.jsonl:');
69
+ for (const entry of historySummary) {
70
+ console.log(
71
+ '- ' + entry.engine + ': runs=' + entry.totalRuns +
72
+ ', successRate=' + (entry.successRate === null ? 'unknown' : Math.round(entry.successRate * 100) + '%') +
73
+ ', lastRunAt=' + (entry.lastRunAt || 'never')
74
+ );
75
+ }
76
+ }
77
+ }
78
+
79
+ // Safe, limited validation: for engines with a real invocation module
80
+ // (codex9, ai-router), call the engine's own non-mutating describe() (the
81
+ // same contract `maestro doctor` already uses — it checks CLI/PATH
82
+ // presence, it does NOT call a real model). Catalog-only entries with no
83
+ // engineModule cannot be validated this way and are reported as such,
84
+ // never silently marked validated.
85
+ export async function enginesValidate(id) {
86
+ if (!id) {
87
+ console.log('Usage: maestro engines validate <id>');
88
+ return;
89
+ }
90
+ const record = await getEngineRecord(id);
91
+ if (!record) {
92
+ console.log('Engine "' + id + '" not found in ' + getEnginesFilePath() + '.');
93
+ return;
94
+ }
95
+ if (!record.runnable || !record.engineModule) {
96
+ console.log('Engine "' + id + '" has no invocation module in this repo (runnable=' + !!record.runnable + '). Cannot validate without building one first — see .maestro/V0_5_CURRENT_ARCHITECTURE_AUDIT.md.');
97
+ return;
98
+ }
99
+ try {
100
+ const engineModule = getEngine(id);
101
+ const description = await engineModule.describe();
102
+ console.log('Validation result for "' + id + '" (via engine.describe(), non-mutating, no real model call):');
103
+ printJson(description);
104
+ } catch (error) {
105
+ console.log('Could not validate "' + id + '": ' + error.message);
106
+ }
107
+ }
108
+
109
+ // Regenerates the redacted 9Router model inventory via a short, read-only
110
+ // GET to /v1/models. Never calls /v1/chat/completions (would trigger a real,
111
+ // possibly paid, model invocation). If 9Router isn't reachable, this reports
112
+ // that clearly instead of failing the whole command or inventing data.
113
+ export async function enginesResearch() {
114
+ const base = process.env.MAESTRO_NINEROUTER_BASE_URL || 'http://localhost:20128';
115
+ console.log('Probing ' + base + '/v1/models (read-only; no chat/completions call is made)...');
116
+ let response;
117
+ try {
118
+ const controller = new AbortController();
119
+ const timeout = setTimeout(() => controller.abort(), 5000);
120
+ response = await fetch(base + '/v1/models', { signal: controller.signal });
121
+ clearTimeout(timeout);
122
+ } catch (error) {
123
+ console.log('9Router not reachable at ' + base + ' (' + error.message + '). Existing .maestro/NINEROUTER_*.md/json files were left unchanged.');
124
+ return;
125
+ }
126
+ if (!response.ok) {
127
+ console.log('9Router responded with HTTP ' + response.status + ' for /v1/models. Existing inventory files were left unchanged.');
128
+ return;
129
+ }
130
+ const data = await response.json();
131
+ const models = Array.isArray(data.data) ? data.data : [];
132
+ const byOwner = {};
133
+ for (const model of models) {
134
+ const owner = model.owned_by || 'unknown';
135
+ (byOwner[owner] = byOwner[owner] || []).push(model.id);
136
+ }
137
+ const inventoryPath = path.join(CONFIG.maestroPath, 'NINEROUTER_RAW_INVENTORY_REDACTED.json');
138
+ let existing = {};
139
+ try {
140
+ existing = JSON.parse(await fs.readFile(inventoryPath, 'utf-8'));
141
+ } catch (error) {
142
+ existing = {};
143
+ }
144
+ const updated = {
145
+ ...existing,
146
+ inspectedAt: new Date().toISOString(),
147
+ inspectionMethod: 'local-http-probe (maestro engines research)',
148
+ modelsByOwnerPrefix: byOwner,
149
+ modelCount: models.length
150
+ };
151
+ await fs.writeFile(inventoryPath, JSON.stringify(updated, null, 2) + '\n');
152
+ console.log('Refreshed ' + inventoryPath + ': ' + models.length + ' models across ' + Object.keys(byOwner).length + ' owner prefixes.');
153
+ console.log('This does not change .maestro/engines.json automatically — review new/changed models and update engine fiches manually, per project rule "não afirmar capacidade sem evidência".');
154
+ }
155
+
156
+ // ---- routing / preflight / dry-run --------------------------------------
157
+
158
+ async function resolveTaskForCommand(id) {
159
+ if (id) {
160
+ const task = await getTaskById(id);
161
+ if (!task) {
162
+ console.log('Task #' + id + ' not found.');
163
+ return null;
164
+ }
165
+ return task;
166
+ }
167
+ const next = getNextTask(await getTasks());
168
+ if (!next) {
169
+ console.log('No task id given and no pending task found. Usage: maestro route-task <id>');
170
+ return null;
171
+ }
172
+ return next;
173
+ }
174
+
175
+ export async function routeTask(id) {
176
+ const task = await resolveTaskForCommand(id);
177
+ if (!task) return;
178
+ const classification = classifyTask(task);
179
+ const { selection, preflight } = await selectEngineWithPreflight(task, classification);
180
+ console.log('Task #' + task.id + ' "' + (task.title || task.description) + '"');
181
+ printJson({ classification, selection, preflight });
182
+ }
183
+
184
+ export async function preflightCommand(id) {
185
+ const task = await resolveTaskForCommand(id);
186
+ if (!task) return;
187
+ const classification = classifyTask(task);
188
+ const engines = await listEngineRecords();
189
+ const results = engines.map(engine => ({
190
+ engine: engine.id,
191
+ enabled: engine.enabled === true,
192
+ preflight: runPreflight(task, engine, classification),
193
+ policy: checkEnginePolicy(engine, { role: 'worker' })
194
+ }));
195
+ printJson({ taskId: task.id, classification, results });
196
+ }
197
+
198
+ export async function orchestraPlan(id, { dryRun = false } = {}) {
199
+ const task = await resolveTaskForCommand(id);
200
+ if (!task) return;
201
+ const classification = classifyTask(task);
202
+ const managerPlan = planSectorWork(task, classification);
203
+ const delegationPlan = buildDelegationPlan(task, classification, { depth: 0 });
204
+ const { selection, preflight } = await selectEngineWithPreflight(task, classification);
205
+ const budget = await loadBudget();
206
+ const budgetResult = await evaluateBudget({
207
+ engineRecord: selection.selectedEngine ? await getEngineRecord(selection.selectedEngine) : null
208
+ });
209
+ const fallbackGraph = await loadFallbackGraph();
210
+
211
+ const plan = {
212
+ mode: dryRun ? 'dry-run (no engine will be called)' : 'plan',
213
+ task: { id: task.id, title: task.title || task.description, status: task.status },
214
+ classification,
215
+ manager: managerPlan,
216
+ delegation: {
217
+ ...delegationPlan,
218
+ limits: DELEGATION_LIMITS
219
+ },
220
+ engineSelection: selection,
221
+ preflight,
222
+ budget: {
223
+ policy: budget,
224
+ evaluation: budgetResult
225
+ },
226
+ fallbackRulesAvailable: Object.keys(fallbackGraph.rules || {}),
227
+ verifier: {
228
+ required: classification.verificationRequired === true,
229
+ note: 'Deterministic checks (syntax/workspace-diff/tool-call detection) always run; a model-backed second opinion only runs if the caller opts in and budget allows it (src/orchestration/verifier.mjs).'
230
+ }
231
+ };
232
+
233
+ printJson(plan);
234
+ if (dryRun) {
235
+ console.log('\n(dry-run: no engine, worker, or model was actually called)');
236
+ }
237
+ }
238
+
239
+ // ---- budget ---------------------------------------------------------------
240
+
241
+ export async function budgetCommand() {
242
+ const budget = await loadBudget();
243
+ printJson(budget);
244
+ console.log('\nEdit .maestro/budget.json directly to change policy (see .maestro/BUDGET_POLICY.md). There is no "maestro budget set" writer in v0.5.0.');
245
+ }
246
+
247
+ // ---- stats variants ---------------------------------------------------------
248
+
249
+ export async function statsByEngine() {
250
+ const summary = await getUsageSummary();
251
+ console.log('Usage rows by engine (.maestro/usage.jsonl):');
252
+ printJson(summary.engines || {});
253
+ const history = await summarizeHistoryByEngine();
254
+ if (history.length > 0) {
255
+ console.log('\nOutcome history (.maestro/engine-history.jsonl):');
256
+ printJson(history);
257
+ }
258
+ }
259
+
260
+ export async function statsByRunKind() {
261
+ const summary = await getUsageSummary();
262
+ console.log('Usage rows by runKind (.maestro/usage.jsonl):');
263
+ printJson(summary.byRunKind || {});
264
+ }
265
+
266
+ export async function statsCost() {
267
+ const summary = await getUsageSummary();
268
+ console.log('ESTIMATED execution split by cost class (never real billing data):');
269
+ printJson(summary.executionByCost || {});
270
+ console.log('\nestimatedTotalTokens: ' + (summary.estimatedTotalTokens || 0));
271
+ }
272
+
273
+ export async function statsByGateDecision() {
274
+ const summary = await getUsageSummary();
275
+ console.log('Runtime gate decisions (.maestro/usage.jsonl, since v0.5.1):');
276
+ printJson(summary.byGateDecision || {});
277
+ console.log('\nBLOCK breakdown by cause (preflight / budget / policy / no-candidate):');
278
+ printJson(summary.byBlockReason || {});
279
+ }
@@ -0,0 +1,38 @@
1
+ import path from 'path';
2
+ import { CONFIG } from './config.mjs';
3
+ import { addTask, archiveActiveTasksBeforePlan } from './tasks.mjs';
4
+ import { writeTextWithBackup } from './files.mjs';
5
+ import { buildSmartTasks } from './smart-planner.mjs';
6
+ import { tryBuildSpecTasks } from './orchestration/spec-planner.mjs';
7
+
8
+ const planFilePath = path.join(CONFIG.maestroPath, 'PLAN.md');
9
+
10
+ export async function generatePlan(request, briefContent = '') {
11
+ let planContent = '# AI Maestro Plan\n\n';
12
+ planContent += '## Request\n' + request + '\n\n';
13
+
14
+ if (briefContent) {
15
+ planContent += '## Brief from .memory/BRIEF.md\n' + briefContent + '\n\n';
16
+ }
17
+
18
+ const specResult = await tryBuildSpecTasks(request);
19
+ const { kind, plannerKind, tasks: generatedTasks } = specResult.usedSpec ? specResult : buildSmartTasks(request);
20
+
21
+ planContent += '## Generated Tasks\n';
22
+ planContent += '(plannerKind: ' + plannerKind + ', requestKind: ' + kind + ')\n\n';
23
+
24
+ const archive = await archiveActiveTasksBeforePlan();
25
+ if (archive.archived.length > 0) {
26
+ planContent += 'Archived active tasks before replacing plan: ' + archive.archivePath + '\n\n';
27
+ }
28
+ for (const task of generatedTasks) {
29
+ const savedTask = await addTask(task.description, task);
30
+ planContent += '- [ ] #' + savedTask.id + ' ' + savedTask.title + ' [' + savedTask.engine + ', ' + savedTask.priority + ']\n';
31
+ for (const item of savedTask.acceptance) {
32
+ planContent += ' - Acceptance: ' + item + '\n';
33
+ }
34
+ }
35
+
36
+ await writeTextWithBackup(planFilePath, planContent);
37
+ return { planContent, archive, plannerKind, kind };
38
+ }
@@ -0,0 +1 @@
1
+ console.log('Project module');