substrate-ai 0.1.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.
- package/README.md +204 -0
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.js +14639 -0
- package/dist/cli/templates/parallel.yaml +72 -0
- package/dist/cli/templates/research-then-implement.yaml +103 -0
- package/dist/cli/templates/review-cycle.yaml +91 -0
- package/dist/cli/templates/sequential.yaml +68 -0
- package/dist/config-schema-C9tTMcm1.js +102 -0
- package/dist/config-watcher-P5CR4cZ0.js +5599 -0
- package/dist/index.d.ts +1389 -0
- package/dist/index.js +465 -0
- package/dist/upgrade-0Q2TKgQ4.js +5 -0
- package/dist/upgrade-BBPbOHol.js +126 -0
- package/dist/version-manager-impl-Bij9-k0W.js +4 -0
- package/dist/version-manager-impl-DbHmed-I.js +485 -0
- package/package.json +85 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { AdapterRegistry, AdtError, BudgetExceededError, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, GeminiCLIAdapter, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError, childLogger, computeChangedKeys, createConfigWatcher, createDatabaseService, createEventBus, createGitWorktreeManager, createLogger, createMonitorAgent, createMonitorDatabase, createRoutingEngine, createTaskGraphEngine, createWorkerPoolManager, logger } from "./config-watcher-P5CR4cZ0.js";
|
|
2
|
+
import "./config-schema-C9tTMcm1.js";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { randomUUID } from "crypto";
|
|
5
|
+
|
|
6
|
+
//#region src/utils/helpers.ts
|
|
7
|
+
/**
|
|
8
|
+
* Sleep for a given number of milliseconds
|
|
9
|
+
* @param ms - Milliseconds to sleep
|
|
10
|
+
*/
|
|
11
|
+
function sleep(ms) {
|
|
12
|
+
return new Promise((resolve$1) => setTimeout(resolve$1, ms));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Assert that a value is defined (not null or undefined)
|
|
16
|
+
* @param value - Value to check
|
|
17
|
+
* @param message - Error message if undefined
|
|
18
|
+
*/
|
|
19
|
+
function assertDefined(value, message) {
|
|
20
|
+
if (value === null || value === void 0) throw new Error(message);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Format a duration in milliseconds to a human-readable string
|
|
24
|
+
* @param ms - Duration in milliseconds
|
|
25
|
+
*/
|
|
26
|
+
function formatDuration(ms) {
|
|
27
|
+
if (ms < 1e3) return `${String(ms)}ms`;
|
|
28
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
29
|
+
if (ms < 36e5) {
|
|
30
|
+
const minutes$1 = Math.floor(ms / 6e4);
|
|
31
|
+
const seconds = Math.floor(ms % 6e4 / 1e3);
|
|
32
|
+
return `${String(minutes$1)}m ${String(seconds)}s`;
|
|
33
|
+
}
|
|
34
|
+
const hours = Math.floor(ms / 36e5);
|
|
35
|
+
const minutes = Math.floor(ms % 36e5 / 6e4);
|
|
36
|
+
return `${String(hours)}h ${String(minutes)}m`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Generate a unique identifier using crypto.randomUUID()
|
|
40
|
+
* @param prefix - Optional prefix for the ID
|
|
41
|
+
*/
|
|
42
|
+
function generateId(prefix = "") {
|
|
43
|
+
const uuid = randomUUID();
|
|
44
|
+
return prefix ? `${prefix}-${uuid}` : uuid;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Deep clone an object using structuredClone.
|
|
48
|
+
* Supports most built-in types (Date, Map, Set, ArrayBuffer, etc.)
|
|
49
|
+
* but does NOT support functions, DOM nodes, or symbols as keys.
|
|
50
|
+
* @param obj - Object to clone
|
|
51
|
+
*/
|
|
52
|
+
function deepClone(obj) {
|
|
53
|
+
return structuredClone(obj);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if a value is a plain object (not an array, Date, or other special object)
|
|
57
|
+
* @param value - Value to check
|
|
58
|
+
*/
|
|
59
|
+
function isPlainObject(value) {
|
|
60
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
61
|
+
const proto = Object.getPrototypeOf(value);
|
|
62
|
+
return proto === Object.prototype || proto === null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Retry an async operation with exponential backoff
|
|
66
|
+
* @param fn - Async function to retry
|
|
67
|
+
* @param maxRetries - Maximum number of retries
|
|
68
|
+
* @param baseDelayMs - Base delay in milliseconds (doubles each retry)
|
|
69
|
+
*/
|
|
70
|
+
async function withRetry(fn, maxRetries = 3, baseDelayMs = 100) {
|
|
71
|
+
let lastError;
|
|
72
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
73
|
+
return await fn();
|
|
74
|
+
} catch (error) {
|
|
75
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
76
|
+
if (attempt < maxRetries) await sleep(baseDelayMs * Math.pow(2, attempt));
|
|
77
|
+
}
|
|
78
|
+
throw lastError ?? new Error("Operation failed after retries");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/core/di.ts
|
|
83
|
+
/**
|
|
84
|
+
* Simple service registry — stores named service instances for DI resolution.
|
|
85
|
+
*
|
|
86
|
+
* Services are registered by name and can be retrieved by name or iterated
|
|
87
|
+
* in registration order for lifecycle management.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* const registry = new ServiceRegistry()
|
|
91
|
+
* registry.register('taskGraph', taskGraphEngine)
|
|
92
|
+
* registry.register('workerManager', workerManager)
|
|
93
|
+
*
|
|
94
|
+
* // Initialize all services
|
|
95
|
+
* await registry.initializeAll()
|
|
96
|
+
*
|
|
97
|
+
* // Shutdown all services in reverse order
|
|
98
|
+
* await registry.shutdownAll()
|
|
99
|
+
*/
|
|
100
|
+
var ServiceRegistry = class {
|
|
101
|
+
_services = new Map();
|
|
102
|
+
_order = [];
|
|
103
|
+
/**
|
|
104
|
+
* Register a named service. Registration order is preserved for lifecycle calls.
|
|
105
|
+
* @throws {Error} if a service with the same name is already registered.
|
|
106
|
+
*/
|
|
107
|
+
register(name, service) {
|
|
108
|
+
if (this._services.has(name)) throw new Error(`Service "${name}" is already registered`);
|
|
109
|
+
this._services.set(name, service);
|
|
110
|
+
this._order.push(name);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Retrieve a registered service by name.
|
|
114
|
+
* @throws {Error} if no service with the given name is registered.
|
|
115
|
+
*/
|
|
116
|
+
get(name) {
|
|
117
|
+
const service = this._services.get(name);
|
|
118
|
+
if (service === void 0) throw new Error(`Service "${name}" is not registered`);
|
|
119
|
+
return service;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Returns true if a service with the given name is registered.
|
|
123
|
+
*/
|
|
124
|
+
has(name) {
|
|
125
|
+
return this._services.has(name);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Initialize all registered services in registration order.
|
|
129
|
+
* Fails fast on the first error — later services may depend on
|
|
130
|
+
* already-initialized ones, so continuing after a failure is unsafe.
|
|
131
|
+
* @throws the first initialization error encountered.
|
|
132
|
+
*/
|
|
133
|
+
async initializeAll() {
|
|
134
|
+
for (const name of this._order) {
|
|
135
|
+
const service = this._services.get(name);
|
|
136
|
+
if (service !== void 0) await service.initialize();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Shut down all registered services in reverse registration order.
|
|
141
|
+
* Errors are collected and re-thrown as an AggregateError after all services
|
|
142
|
+
* have had a chance to shut down.
|
|
143
|
+
*/
|
|
144
|
+
async shutdownAll() {
|
|
145
|
+
const errors = [];
|
|
146
|
+
const reversed = [...this._order].reverse();
|
|
147
|
+
for (const name of reversed) {
|
|
148
|
+
const service = this._services.get(name);
|
|
149
|
+
if (service !== void 0) try {
|
|
150
|
+
await service.shutdown();
|
|
151
|
+
} catch (err) {
|
|
152
|
+
errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (errors.length > 0) throw new AggregateError(errors, `Shutdown errors in ${errors.length} service(s)`);
|
|
156
|
+
}
|
|
157
|
+
/** Return names of all registered services in registration order */
|
|
158
|
+
get serviceNames() {
|
|
159
|
+
return [...this._order];
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/modules/budget/budget-tracker.ts
|
|
165
|
+
const logger$3 = createLogger("budget");
|
|
166
|
+
var BudgetTrackerImpl = class {
|
|
167
|
+
_eventBus;
|
|
168
|
+
constructor(eventBus) {
|
|
169
|
+
this._eventBus = eventBus;
|
|
170
|
+
}
|
|
171
|
+
async initialize() {
|
|
172
|
+
logger$3.info("BudgetTracker.initialize() — stub");
|
|
173
|
+
this._eventBus.on("budget:warning", ({ taskId, currentSpend, limit }) => {
|
|
174
|
+
logger$3.warn({
|
|
175
|
+
taskId,
|
|
176
|
+
currentSpend,
|
|
177
|
+
limit
|
|
178
|
+
}, "budget:warning");
|
|
179
|
+
});
|
|
180
|
+
this._eventBus.on("budget:exceeded", ({ taskId, spend, limit }) => {
|
|
181
|
+
logger$3.error({
|
|
182
|
+
taskId,
|
|
183
|
+
spend,
|
|
184
|
+
limit
|
|
185
|
+
}, "budget:exceeded");
|
|
186
|
+
});
|
|
187
|
+
this._eventBus.on("task:complete", ({ taskId, result }) => {
|
|
188
|
+
logger$3.debug({
|
|
189
|
+
taskId,
|
|
190
|
+
costUsd: result.costUsd
|
|
191
|
+
}, "task:complete — accumulate cost");
|
|
192
|
+
});
|
|
193
|
+
this._eventBus.on("task:progress", ({ taskId, tokensUsed }) => {
|
|
194
|
+
logger$3.debug({
|
|
195
|
+
taskId,
|
|
196
|
+
tokensUsed
|
|
197
|
+
}, "task:progress — update token usage");
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
async shutdown() {
|
|
201
|
+
logger$3.info("BudgetTracker.shutdown() — stub");
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
function createBudgetTracker(options) {
|
|
205
|
+
return new BudgetTrackerImpl(options.eventBus);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/modules/git/git-manager.ts
|
|
210
|
+
const logger$2 = createLogger("git");
|
|
211
|
+
var GitManagerImpl = class {
|
|
212
|
+
_eventBus;
|
|
213
|
+
repoRoot;
|
|
214
|
+
constructor(eventBus, repoRoot) {
|
|
215
|
+
this._eventBus = eventBus;
|
|
216
|
+
this.repoRoot = repoRoot;
|
|
217
|
+
}
|
|
218
|
+
async initialize() {
|
|
219
|
+
logger$2.info({ repoRoot: this.repoRoot }, "GitManager.initialize() — stub");
|
|
220
|
+
this._eventBus.on("worktree:created", ({ taskId, worktreePath, branchName }) => {
|
|
221
|
+
logger$2.debug({
|
|
222
|
+
taskId,
|
|
223
|
+
worktreePath,
|
|
224
|
+
branchName
|
|
225
|
+
}, "worktree:created");
|
|
226
|
+
});
|
|
227
|
+
this._eventBus.on("worktree:merged", ({ taskId, branch }) => {
|
|
228
|
+
logger$2.debug({
|
|
229
|
+
taskId,
|
|
230
|
+
branch
|
|
231
|
+
}, "worktree:merged");
|
|
232
|
+
});
|
|
233
|
+
this._eventBus.on("worktree:conflict", ({ taskId, conflictingFiles }) => {
|
|
234
|
+
logger$2.warn({
|
|
235
|
+
taskId,
|
|
236
|
+
conflictingFiles
|
|
237
|
+
}, "worktree:conflict");
|
|
238
|
+
});
|
|
239
|
+
this._eventBus.on("worktree:removed", ({ taskId, branchName }) => {
|
|
240
|
+
logger$2.debug({
|
|
241
|
+
taskId,
|
|
242
|
+
branchName
|
|
243
|
+
}, "worktree:removed");
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
async shutdown() {
|
|
247
|
+
logger$2.info("GitManager.shutdown() — stub");
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
function createGitManager(options) {
|
|
251
|
+
return new GitManagerImpl(options.eventBus, options.repoRoot);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/core/orchestrator-impl.ts
|
|
256
|
+
const logger$1 = createLogger("orchestrator");
|
|
257
|
+
/** Internal symbol used to expose lifecycle hooks to the factory only */
|
|
258
|
+
const INTERNAL = Symbol("OrchestratorImpl.internal");
|
|
259
|
+
var OrchestratorImpl = class {
|
|
260
|
+
eventBus;
|
|
261
|
+
_registry;
|
|
262
|
+
_ready = false;
|
|
263
|
+
_shutdown = false;
|
|
264
|
+
_shutdownHandlersRegistered = false;
|
|
265
|
+
_configWatcher = null;
|
|
266
|
+
constructor(eventBus, registry) {
|
|
267
|
+
this.eventBus = eventBus;
|
|
268
|
+
this._registry = registry;
|
|
269
|
+
}
|
|
270
|
+
get isReady() {
|
|
271
|
+
return this._ready;
|
|
272
|
+
}
|
|
273
|
+
async shutdown() {
|
|
274
|
+
if (this._shutdown) return;
|
|
275
|
+
this._shutdown = true;
|
|
276
|
+
logger$1.info("Orchestrator shutdown initiated");
|
|
277
|
+
this.eventBus.emit("orchestrator:shutdown", { reason: "shutdown() called" });
|
|
278
|
+
if (this._configWatcher !== null) {
|
|
279
|
+
this._configWatcher.stop();
|
|
280
|
+
this._configWatcher = null;
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
await this._registry.shutdownAll();
|
|
284
|
+
} catch (err) {
|
|
285
|
+
logger$1.error({ err }, "Error during orchestrator shutdown");
|
|
286
|
+
}
|
|
287
|
+
logger$1.info("Orchestrator shutdown complete");
|
|
288
|
+
}
|
|
289
|
+
_markReady() {
|
|
290
|
+
this._ready = true;
|
|
291
|
+
}
|
|
292
|
+
_sigtermHandler = null;
|
|
293
|
+
_sigintHandler = null;
|
|
294
|
+
_registerShutdownHandlers() {
|
|
295
|
+
if (this._shutdownHandlersRegistered) return;
|
|
296
|
+
this._shutdownHandlersRegistered = true;
|
|
297
|
+
const makeHandler = (signal) => () => {
|
|
298
|
+
logger$1.info({ signal }, "Received signal — initiating graceful shutdown");
|
|
299
|
+
this.shutdown().then(() => {
|
|
300
|
+
process.exit(0);
|
|
301
|
+
}).catch((err) => {
|
|
302
|
+
logger$1.error({ err }, "Error during signal-triggered shutdown");
|
|
303
|
+
process.exit(1);
|
|
304
|
+
});
|
|
305
|
+
};
|
|
306
|
+
this._sigtermHandler = makeHandler("SIGTERM");
|
|
307
|
+
this._sigintHandler = makeHandler("SIGINT");
|
|
308
|
+
process.once("SIGTERM", this._sigtermHandler);
|
|
309
|
+
process.once("SIGINT", this._sigintHandler);
|
|
310
|
+
}
|
|
311
|
+
_removeShutdownHandlers() {
|
|
312
|
+
if (this._sigtermHandler !== null) {
|
|
313
|
+
process.removeListener("SIGTERM", this._sigtermHandler);
|
|
314
|
+
this._sigtermHandler = null;
|
|
315
|
+
}
|
|
316
|
+
if (this._sigintHandler !== null) {
|
|
317
|
+
process.removeListener("SIGINT", this._sigintHandler);
|
|
318
|
+
this._sigintHandler = null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Internal accessor used exclusively by the createOrchestrator factory.
|
|
323
|
+
* Do not call outside of this module.
|
|
324
|
+
* @internal
|
|
325
|
+
*/
|
|
326
|
+
[INTERNAL]() {
|
|
327
|
+
return {
|
|
328
|
+
markReady: () => this._markReady(),
|
|
329
|
+
registerShutdownHandlers: () => this._registerShutdownHandlers(),
|
|
330
|
+
removeShutdownHandlers: () => this._removeShutdownHandlers(),
|
|
331
|
+
setConfigWatcher: (watcher) => {
|
|
332
|
+
this._configWatcher = watcher;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
/**
|
|
338
|
+
* Initialize the orchestrator with all modules wired via dependency injection.
|
|
339
|
+
*
|
|
340
|
+
* Steps performed:
|
|
341
|
+
* 1. Create the TypedEventBus
|
|
342
|
+
* 2. Create the SQLite database service
|
|
343
|
+
* 3. Instantiate all modules (TaskGraphEngine, RoutingEngine, WorkerManager,
|
|
344
|
+
* BudgetTracker, GitManager) with constructor injection
|
|
345
|
+
* 4. Register all modules in the ServiceRegistry
|
|
346
|
+
* 5. Call initialize() on all services in registration order
|
|
347
|
+
* 6. Register SIGTERM/SIGINT graceful shutdown handlers
|
|
348
|
+
* 7. Emit orchestrator:ready
|
|
349
|
+
*
|
|
350
|
+
* @param config - Orchestrator configuration
|
|
351
|
+
* @returns Initialized Orchestrator instance
|
|
352
|
+
*/
|
|
353
|
+
async function createOrchestrator(config) {
|
|
354
|
+
logger$1.info({ databasePath: config.databasePath }, "Initializing orchestrator");
|
|
355
|
+
const eventBus = createEventBus();
|
|
356
|
+
const databaseService = createDatabaseService(config.databasePath);
|
|
357
|
+
const taskGraphEngine = createTaskGraphEngine({
|
|
358
|
+
eventBus,
|
|
359
|
+
databaseService
|
|
360
|
+
});
|
|
361
|
+
const adapterRegistry = new AdapterRegistry();
|
|
362
|
+
const routingEngine = createRoutingEngine({
|
|
363
|
+
eventBus,
|
|
364
|
+
adapterRegistry
|
|
365
|
+
});
|
|
366
|
+
const budgetTracker = createBudgetTracker({ eventBus });
|
|
367
|
+
const gitManager = createGitManager({
|
|
368
|
+
eventBus,
|
|
369
|
+
repoRoot: config.projectRoot
|
|
370
|
+
});
|
|
371
|
+
const gitWorktreeManager = createGitWorktreeManager({
|
|
372
|
+
eventBus,
|
|
373
|
+
projectRoot: config.projectRoot,
|
|
374
|
+
db: databaseService
|
|
375
|
+
});
|
|
376
|
+
const workerPoolManager = createWorkerPoolManager({
|
|
377
|
+
eventBus,
|
|
378
|
+
adapterRegistry,
|
|
379
|
+
engine: taskGraphEngine,
|
|
380
|
+
db: databaseService,
|
|
381
|
+
gitWorktreeManager
|
|
382
|
+
});
|
|
383
|
+
const monitorDbPath = config.monitor?.databasePath ?? ":memory:";
|
|
384
|
+
const monitorDatabase = createMonitorDatabase(monitorDbPath);
|
|
385
|
+
const monitorAgent = createMonitorAgent({
|
|
386
|
+
eventBus,
|
|
387
|
+
monitorDb: monitorDatabase,
|
|
388
|
+
config: {
|
|
389
|
+
retentionDays: config.monitor?.retentionDays ?? 90,
|
|
390
|
+
customTaxonomy: config.monitor?.customTaxonomy,
|
|
391
|
+
use_recommendations: config.monitor?.use_recommendations ?? false,
|
|
392
|
+
recommendation_threshold_percentage: config.monitor?.recommendation_threshold_percentage,
|
|
393
|
+
min_sample_size: config.monitor?.min_sample_size,
|
|
394
|
+
recommendation_history_days: config.monitor?.recommendation_history_days
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
const registry = new ServiceRegistry();
|
|
398
|
+
registry.register("database", databaseService);
|
|
399
|
+
registry.register("taskGraph", taskGraphEngine);
|
|
400
|
+
registry.register("routingEngine", routingEngine);
|
|
401
|
+
registry.register("gitWorktreeManager", gitWorktreeManager);
|
|
402
|
+
registry.register("workerPoolManager", workerPoolManager);
|
|
403
|
+
registry.register("budgetTracker", budgetTracker);
|
|
404
|
+
registry.register("gitManager", gitManager);
|
|
405
|
+
registry.register("monitorAgent", monitorAgent);
|
|
406
|
+
const orchestrator = new OrchestratorImpl(eventBus, registry);
|
|
407
|
+
const internal = orchestrator[INTERNAL]();
|
|
408
|
+
try {
|
|
409
|
+
await registry.initializeAll();
|
|
410
|
+
} catch (err) {
|
|
411
|
+
logger$1.error({ err }, "Service initialization failed — cleaning up");
|
|
412
|
+
internal.removeShutdownHandlers();
|
|
413
|
+
try {
|
|
414
|
+
await registry.shutdownAll();
|
|
415
|
+
} catch (shutdownErr) {
|
|
416
|
+
logger$1.error({ err: shutdownErr }, "Error during cleanup after failed initialization");
|
|
417
|
+
}
|
|
418
|
+
throw err;
|
|
419
|
+
}
|
|
420
|
+
internal.registerShutdownHandlers();
|
|
421
|
+
const enableConfigHotReload = config.enableConfigHotReload ?? true;
|
|
422
|
+
if (enableConfigHotReload) {
|
|
423
|
+
const configFilePath = config.configPath ?? join(config.projectRoot, "substrate.config.yaml");
|
|
424
|
+
let currentConfig = null;
|
|
425
|
+
const configWatcher = createConfigWatcher({
|
|
426
|
+
configPath: configFilePath,
|
|
427
|
+
onReload: (newConfig) => {
|
|
428
|
+
const previousConfig = currentConfig;
|
|
429
|
+
if (previousConfig === null) {
|
|
430
|
+
currentConfig = newConfig;
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const changedKeys = computeChangedKeys(previousConfig, newConfig);
|
|
434
|
+
currentConfig = newConfig;
|
|
435
|
+
const n = changedKeys.length;
|
|
436
|
+
logger$1.info({
|
|
437
|
+
changedKeys,
|
|
438
|
+
configPath: configFilePath
|
|
439
|
+
}, `Config reloaded: ${n} setting(s) changed`);
|
|
440
|
+
eventBus.emit("config:reloaded", {
|
|
441
|
+
path: configFilePath,
|
|
442
|
+
previousConfig,
|
|
443
|
+
newConfig,
|
|
444
|
+
changedKeys
|
|
445
|
+
});
|
|
446
|
+
},
|
|
447
|
+
onError: (err) => {
|
|
448
|
+
logger$1.error({
|
|
449
|
+
err,
|
|
450
|
+
configPath: configFilePath
|
|
451
|
+
}, `Config reload failed: ${err.message}. Continuing with previous config.`);
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
configWatcher.start();
|
|
455
|
+
internal.setConfigWatcher(configWatcher);
|
|
456
|
+
}
|
|
457
|
+
internal.markReady();
|
|
458
|
+
eventBus.emit("orchestrator:ready", {});
|
|
459
|
+
logger$1.info("Orchestrator ready");
|
|
460
|
+
return orchestrator;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
//#endregion
|
|
464
|
+
export { AdapterRegistry, AdtError, BudgetExceededError, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, GeminiCLIAdapter, GitError, RecoveryError, ServiceRegistry, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError, assertDefined, childLogger, createEventBus, createLogger, createOrchestrator, deepClone, formatDuration, generateId, isPlainObject, logger, sleep, withRetry };
|
|
465
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { createVersionManager } from "./version-manager-impl-DbHmed-I.js";
|
|
2
|
+
import { execSync, spawn } from "child_process";
|
|
3
|
+
import * as readline from "readline";
|
|
4
|
+
|
|
5
|
+
//#region src/cli/commands/upgrade.ts
|
|
6
|
+
/**
|
|
7
|
+
* Detect whether substrate is installed globally.
|
|
8
|
+
*/
|
|
9
|
+
function isGlobalInstall() {
|
|
10
|
+
try {
|
|
11
|
+
execSync("npm list -g substrate --depth=0", { stdio: "ignore" });
|
|
12
|
+
return true;
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function printVersionTable(currentVersion, latestVersion, isBreaking, changelog) {
|
|
18
|
+
console.log("\nSubstrate update available:");
|
|
19
|
+
console.log(` Current version : v${currentVersion}`);
|
|
20
|
+
console.log(` Latest version : v${latestVersion}`);
|
|
21
|
+
console.log(` Breaking changes: ${isBreaking ? "Yes (major version bump)" : "No"}`);
|
|
22
|
+
console.log(` Changelog : ${changelog}`);
|
|
23
|
+
console.log();
|
|
24
|
+
}
|
|
25
|
+
async function promptConfirm(question) {
|
|
26
|
+
const rl = readline.createInterface({
|
|
27
|
+
input: process.stdin,
|
|
28
|
+
output: process.stdout
|
|
29
|
+
});
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
rl.question(question, (answer) => {
|
|
32
|
+
rl.close();
|
|
33
|
+
resolve(answer.toLowerCase() === "y");
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async function runNpmInstall(version, global, spawnFn = spawn) {
|
|
38
|
+
const args = global ? [
|
|
39
|
+
"install",
|
|
40
|
+
"-g",
|
|
41
|
+
`substrate@${version}`
|
|
42
|
+
] : ["install", `substrate@${version}`];
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const child = spawnFn("npm", args, { stdio: "inherit" });
|
|
45
|
+
child.on("close", (code) => {
|
|
46
|
+
if (code === 0) resolve();
|
|
47
|
+
else reject(new Error(`npm install exited with code ${String(code)}`));
|
|
48
|
+
});
|
|
49
|
+
child.on("error", (err) => {
|
|
50
|
+
reject(new Error(`Failed to spawn npm: ${err.message}`));
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Execute the upgrade command logic.
|
|
56
|
+
* Exported for testability.
|
|
57
|
+
*/
|
|
58
|
+
async function runUpgradeCommand(options) {
|
|
59
|
+
const versionManager = options.versionManager ?? createVersionManager();
|
|
60
|
+
const promptFn = options.promptFn ?? promptConfirm;
|
|
61
|
+
const spawnFn = options.spawnFn ?? spawn;
|
|
62
|
+
if (options.check) {
|
|
63
|
+
let result$1;
|
|
64
|
+
try {
|
|
65
|
+
result$1 = await versionManager.checkForUpdates(true);
|
|
66
|
+
} catch {
|
|
67
|
+
process.stderr.write("Warning: Could not reach npm registry to check for updates. Continuing anyway.\n");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!result$1.updateAvailable) {
|
|
71
|
+
console.log(`substrate is up to date (v${result$1.currentVersion})`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
printVersionTable(result$1.currentVersion, result$1.latestVersion, result$1.isBreaking, result$1.changelog);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
let result;
|
|
78
|
+
try {
|
|
79
|
+
result = await versionManager.checkForUpdates();
|
|
80
|
+
} catch {
|
|
81
|
+
process.stderr.write("Warning: Could not reach npm registry to check for updates. Continuing anyway.\n");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (!result.updateAvailable) {
|
|
85
|
+
console.log(`substrate is up to date (v${result.currentVersion})`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const preview = versionManager.getUpgradePreview(result.latestVersion);
|
|
89
|
+
printVersionTable(result.currentVersion, result.latestVersion, result.isBreaking, result.changelog);
|
|
90
|
+
if (preview.breakingChanges.length > 0) {
|
|
91
|
+
console.log("Breaking changes:");
|
|
92
|
+
for (const change of preview.breakingChanges) console.log(` - ${change}`);
|
|
93
|
+
console.log();
|
|
94
|
+
}
|
|
95
|
+
if (preview.migrationSteps.length > 0) {
|
|
96
|
+
console.log("Migration steps:");
|
|
97
|
+
for (const step of preview.migrationSteps) console.log(` - ${step}`);
|
|
98
|
+
console.log();
|
|
99
|
+
}
|
|
100
|
+
if (!options.yes) {
|
|
101
|
+
const confirmed = await promptFn(`Upgrade substrate from v${result.currentVersion} to v${result.latestVersion}? (y/N) `);
|
|
102
|
+
if (!confirmed) {
|
|
103
|
+
console.log("Upgrade aborted.");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const global = isGlobalInstall();
|
|
108
|
+
console.log(`Running: npm install ${global ? "-g " : ""}substrate@${result.latestVersion}`);
|
|
109
|
+
try {
|
|
110
|
+
await runNpmInstall(result.latestVersion, global, spawnFn);
|
|
111
|
+
console.log(`\nSuccessfully upgraded to v${result.latestVersion}`);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
114
|
+
process.stderr.write(`Upgrade failed: ${message}\n`);
|
|
115
|
+
process.exitCode = 1;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function registerUpgradeCommand(program) {
|
|
119
|
+
program.command("upgrade").description("Check for updates and upgrade substrate to the latest version").option("--check", "Check for updates without upgrading").option("-y, --yes", "Skip confirmation prompt (non-interactive upgrade)").action(async (options) => {
|
|
120
|
+
await runUpgradeCommand(options);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
|
|
126
|
+
//# sourceMappingURL=upgrade-BBPbOHol.js.map
|