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,228 @@
1
+ import fs from 'fs/promises';
2
+ import { createHash } from 'crypto';
3
+ import path from 'path';
4
+ import { execFileRaw } from './shell.mjs';
5
+
6
+ const IGNORED_DIRS = new Set(['.memory', '.maestro', 'node_modules', '.venv', 'dist', 'build', '.git']);
7
+ const IGNORED_LOG_PATTERN = /\.log$/i;
8
+
9
+ function isIgnoredPath(relPath) {
10
+ const normalized = relPath.replace(/\\/g, '/');
11
+ if (IGNORED_LOG_PATTERN.test(normalized)) {
12
+ return true;
13
+ }
14
+ return normalized.split('/').some(segment => IGNORED_DIRS.has(segment));
15
+ }
16
+
17
+ async function isGitRepo(root) {
18
+ const result = await execFileRaw('git', ['rev-parse', '--is-inside-work-tree'], { cwd: root, quiet: true });
19
+ return result.code === 0 && result.stdout.trim() === 'true';
20
+ }
21
+
22
+ async function gitStatusPorcelain(root) {
23
+ const result = await execFileRaw('git', ['status', '--porcelain'], { cwd: root, quiet: true });
24
+ return result.code === 0 ? result.stdout.split(/\r?\n/).filter(Boolean) : [];
25
+ }
26
+
27
+ function parsePorcelainLine(line) {
28
+ const code = line.slice(0, 2);
29
+ let rawPath = line.slice(3).trim();
30
+ if (rawPath.startsWith('"') && rawPath.endsWith('"')) {
31
+ rawPath = rawPath.slice(1, -1);
32
+ }
33
+ const arrowIndex = rawPath.indexOf(' -> ');
34
+ const filePath = arrowIndex >= 0 ? rawPath.slice(arrowIndex + 4) : rawPath;
35
+ return { code, path: filePath };
36
+ }
37
+
38
+ function normalizePath(relPath) {
39
+ return relPath.replace(/\\/g, '/');
40
+ }
41
+
42
+ function snapshotLines(snapshot) {
43
+ if (Array.isArray(snapshot?.lines)) {
44
+ return snapshot.lines;
45
+ }
46
+ if (!Array.isArray(snapshot?.entries)) {
47
+ return [];
48
+ }
49
+ return snapshot.entries
50
+ .map(entry => {
51
+ if (typeof entry === 'string') {
52
+ return entry;
53
+ }
54
+ return typeof entry?.line === 'string' ? entry.line : null;
55
+ })
56
+ .filter(Boolean);
57
+ }
58
+
59
+ function snapshotEntries(snapshot) {
60
+ const entries = new Map();
61
+ for (const line of snapshotLines(snapshot)) {
62
+ const parsed = parsePorcelainLine(line);
63
+ entries.set(normalizePath(parsed.path), { ...parsed, path: normalizePath(parsed.path), line });
64
+ }
65
+ if (entries.size > 0 || !Array.isArray(snapshot?.entries)) {
66
+ return entries;
67
+ }
68
+ for (const entry of snapshot.entries) {
69
+ if (!entry || typeof entry !== 'object' || typeof entry.path !== 'string') {
70
+ continue;
71
+ }
72
+ const filePath = normalizePath(entry.path);
73
+ entries.set(filePath, {
74
+ code: typeof entry.code === 'string' ? entry.code : '',
75
+ path: filePath,
76
+ line: typeof entry.line === 'string' ? entry.line : null
77
+ });
78
+ }
79
+ return entries;
80
+ }
81
+
82
+ function snapshotSignature(snapshot, relPath) {
83
+ const signatures = snapshot?.signatures;
84
+ if (!signatures) {
85
+ return null;
86
+ }
87
+ if (signatures instanceof Map) {
88
+ return signatures.get(relPath) ?? null;
89
+ }
90
+ return typeof signatures[relPath] === 'string' ? signatures[relPath] : null;
91
+ }
92
+
93
+ function sameSnapshotEntry(beforeEntry, afterEntry) {
94
+ if (beforeEntry.line && afterEntry.line) {
95
+ return beforeEntry.line === afterEntry.line;
96
+ }
97
+ return beforeEntry.code === afterEntry.code;
98
+ }
99
+
100
+ async function fileContentSignature(root, relPath) {
101
+ const content = await fs.readFile(path.join(root, relPath));
102
+ return createHash('sha256').update(content).digest('hex');
103
+ }
104
+
105
+ async function gitDirtySignatures(root, lines) {
106
+ const signatures = {};
107
+ for (const line of lines) {
108
+ const { code, path: parsedPath } = parsePorcelainLine(line);
109
+ const filePath = normalizePath(parsedPath);
110
+ if (isIgnoredPath(filePath) || code.includes('D')) {
111
+ continue;
112
+ }
113
+ try {
114
+ signatures[filePath] = await fileContentSignature(root, filePath);
115
+ } catch (error) {
116
+ // The path may be a directory or may have disappeared between status and read.
117
+ }
118
+ }
119
+ return signatures;
120
+ }
121
+
122
+ async function walkFiles(root, base, acc) {
123
+ let entries;
124
+ try {
125
+ entries = await fs.readdir(base, { withFileTypes: true });
126
+ } catch (error) {
127
+ return;
128
+ }
129
+ for (const entry of entries) {
130
+ if (entry.isDirectory()) {
131
+ if (IGNORED_DIRS.has(entry.name)) {
132
+ continue;
133
+ }
134
+ await walkFiles(root, path.join(base, entry.name), acc);
135
+ } else if (entry.isFile()) {
136
+ const fullPath = path.join(base, entry.name);
137
+ const rel = path.relative(root, fullPath).replace(/\\/g, '/');
138
+ if (isIgnoredPath(rel)) {
139
+ continue;
140
+ }
141
+ try {
142
+ const stat = await fs.stat(fullPath);
143
+ acc.set(rel, { size: stat.size, mtimeMs: stat.mtimeMs });
144
+ } catch (error) {
145
+ // unreadable between readdir and stat; skip
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ export async function snapshotWorkspace(root = process.cwd()) {
152
+ if (await isGitRepo(root)) {
153
+ const lines = await gitStatusPorcelain(root);
154
+ return { kind: 'git', root, lines, signatures: await gitDirtySignatures(root, lines) };
155
+ }
156
+ const files = new Map();
157
+ await walkFiles(root, root, files);
158
+ return { kind: 'fs', root, files };
159
+ }
160
+
161
+ function classifyOutOfScope(changedFiles, createdFiles, deletedFiles, scopeFiles) {
162
+ if (!scopeFiles || scopeFiles.length === 0) {
163
+ return [];
164
+ }
165
+ const scopeSet = new Set(scopeFiles.map(file => file.replace(/\\/g, '/')));
166
+ return [...changedFiles, ...createdFiles, ...deletedFiles].filter(file => !scopeSet.has(file));
167
+ }
168
+
169
+ export function diffWorkspace(before, after, { scopeFiles = [] } = {}) {
170
+ const changedFiles = [];
171
+ const createdFiles = [];
172
+ const deletedFiles = [];
173
+ const pushUnique = (list, filePath) => {
174
+ if (!list.includes(filePath)) {
175
+ list.push(filePath);
176
+ }
177
+ };
178
+
179
+ if (before.kind === 'git' && after.kind === 'git') {
180
+ const beforeEntries = snapshotEntries(before);
181
+ for (const afterEntry of snapshotEntries(after).values()) {
182
+ const { code, path: filePath } = afterEntry;
183
+ if (isIgnoredPath(filePath)) {
184
+ continue;
185
+ }
186
+ const beforeEntry = beforeEntries.get(filePath);
187
+ if (beforeEntry && sameSnapshotEntry(beforeEntry, afterEntry)) {
188
+ const beforeSignature = snapshotSignature(before, filePath);
189
+ const afterSignature = snapshotSignature(after, filePath);
190
+ if (beforeSignature && afterSignature && beforeSignature !== afterSignature) {
191
+ pushUnique(changedFiles, filePath);
192
+ }
193
+ continue;
194
+ }
195
+ if (code.includes('D')) {
196
+ pushUnique(deletedFiles, filePath);
197
+ } else if (code === '??') {
198
+ pushUnique(createdFiles, filePath);
199
+ } else {
200
+ pushUnique(changedFiles, filePath);
201
+ }
202
+ }
203
+ } else {
204
+ const beforeFiles = before.files || new Map();
205
+ const afterFiles = after.files || new Map();
206
+ for (const [relPath, afterMeta] of afterFiles) {
207
+ const beforeMeta = beforeFiles.get(relPath);
208
+ if (!beforeMeta) {
209
+ createdFiles.push(relPath);
210
+ } else if (beforeMeta.size !== afterMeta.size || beforeMeta.mtimeMs !== afterMeta.mtimeMs) {
211
+ changedFiles.push(relPath);
212
+ }
213
+ }
214
+ for (const relPath of beforeFiles.keys()) {
215
+ if (!afterFiles.has(relPath)) {
216
+ deletedFiles.push(relPath);
217
+ }
218
+ }
219
+ }
220
+
221
+ return {
222
+ workspaceChanged: changedFiles.length > 0 || createdFiles.length > 0 || deletedFiles.length > 0,
223
+ changedFiles,
224
+ createdFiles,
225
+ deletedFiles,
226
+ outOfScopeChanges: classifyOutOfScope(changedFiles, createdFiles, deletedFiles, scopeFiles)
227
+ };
228
+ }