substrate-ai 0.2.16 → 0.2.17
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/dist/cli/index.js +52 -319
- package/dist/cli/templates/claude-md-substrate-section.md +6 -0
- package/dist/{decisions-SyswIRKz.js → decisions-Dq4cAA2L.js} +9 -2
- package/dist/decisions-DxgMpQpz.js +3 -0
- package/dist/{event-bus-J-bw-pkp.js → event-bus-BMxhfxfT.js} +1 -1
- package/dist/{experimenter-Cd04gAWQ.js → experimenter-CHRVkV3d.js} +4 -4
- package/dist/{git-utils-BtI5eNoN.js → git-utils-CtmrZrHS.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/{logger-C6n1g8uP.js → logger-D2fS2ccL.js} +1 -1
- package/dist/{run-BrI2xzk7.js → run-CLpXJNQ8.js} +465 -188
- package/dist/run-DPrhfU2T.js +7 -0
- package/dist/{upgrade-BjYVeC6G.js → upgrade-CF8EjNuO.js} +2 -2
- package/dist/{upgrade-rV26kdh3.js → upgrade-CjjAx5kD.js} +2 -2
- package/dist/{version-manager-impl-9N_519Ey.js → version-manager-impl-C6jmvble.js} +1 -1
- package/dist/{version-manager-impl-BpVx2DkY.js → version-manager-impl-CZ6KF1Ds.js} +1 -1
- package/package.json +1 -1
- package/dist/decisions-BmqXQ3Se.js +0 -3
- package/dist/run-fjuwOUib.js +0 -7
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { createLogger } from "./logger-
|
|
2
|
-
import { AdapterRegistry, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning } from "./event-bus-
|
|
3
|
-
import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByPhase, getDecisionsByPhaseForRun, getPipelineRunById, getTokenUsageSummary, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./decisions-
|
|
1
|
+
import { createLogger } from "./logger-D2fS2ccL.js";
|
|
2
|
+
import { AdapterRegistry, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning } from "./event-bus-BMxhfxfT.js";
|
|
3
|
+
import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./decisions-Dq4cAA2L.js";
|
|
4
4
|
import { STORY_METRICS, aggregateTokenUsageForRun, aggregateTokenUsageForStory, getStoryMetricsForRun, writeRunMetrics, writeStoryMetrics } from "./operational-CobuCGbM.js";
|
|
5
|
+
import { createRequire } from "module";
|
|
5
6
|
import { dirname, join } from "path";
|
|
6
7
|
import { access, readFile, readdir, stat } from "fs/promises";
|
|
7
8
|
import { existsSync, mkdirSync, readFileSync } from "fs";
|
|
8
9
|
import yaml from "js-yaml";
|
|
9
|
-
import { createRequire } from "node:module";
|
|
10
|
+
import { createRequire as createRequire$1 } from "node:module";
|
|
10
11
|
import { z } from "zod";
|
|
11
12
|
import { execSync, spawn } from "node:child_process";
|
|
12
13
|
import { dirname as dirname$1, join as join$1, resolve as resolve$1 } from "node:path";
|
|
@@ -17,6 +18,10 @@ import { freemem, platform } from "node:os";
|
|
|
17
18
|
import { randomUUID } from "node:crypto";
|
|
18
19
|
import { readFile as readFile$1, stat as stat$1 } from "node:fs/promises";
|
|
19
20
|
|
|
21
|
+
//#region rolldown:runtime
|
|
22
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
20
25
|
//#region src/utils/git-root.ts
|
|
21
26
|
/**
|
|
22
27
|
* Resolve the main git repository root, even from a linked worktree.
|
|
@@ -534,7 +539,7 @@ const migration010RunMetrics = {
|
|
|
534
539
|
|
|
535
540
|
//#endregion
|
|
536
541
|
//#region src/persistence/migrations/index.ts
|
|
537
|
-
const logger$
|
|
542
|
+
const logger$16 = createLogger("persistence:migrations");
|
|
538
543
|
const MIGRATIONS = [
|
|
539
544
|
initialSchemaMigration,
|
|
540
545
|
costTrackerSchemaMigration,
|
|
@@ -552,7 +557,7 @@ const MIGRATIONS = [
|
|
|
552
557
|
* Safe to call multiple times — already-applied migrations are skipped.
|
|
553
558
|
*/
|
|
554
559
|
function runMigrations(db) {
|
|
555
|
-
logger$
|
|
560
|
+
logger$16.info("Starting migration runner");
|
|
556
561
|
db.exec(`
|
|
557
562
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
558
563
|
version INTEGER PRIMARY KEY,
|
|
@@ -563,12 +568,12 @@ function runMigrations(db) {
|
|
|
563
568
|
const appliedVersions = new Set(db.prepare("SELECT version FROM schema_migrations").all().map((row) => row.version));
|
|
564
569
|
const pending = MIGRATIONS.filter((m) => !appliedVersions.has(m.version)).sort((a, b) => a.version - b.version);
|
|
565
570
|
if (pending.length === 0) {
|
|
566
|
-
logger$
|
|
571
|
+
logger$16.info("No pending migrations");
|
|
567
572
|
return;
|
|
568
573
|
}
|
|
569
574
|
const insertMigration = db.prepare("INSERT INTO schema_migrations (version, name) VALUES (?, ?)");
|
|
570
575
|
for (const migration of pending) {
|
|
571
|
-
logger$
|
|
576
|
+
logger$16.info({
|
|
572
577
|
version: migration.version,
|
|
573
578
|
name: migration.name
|
|
574
579
|
}, "Applying migration");
|
|
@@ -582,14 +587,14 @@ function runMigrations(db) {
|
|
|
582
587
|
});
|
|
583
588
|
applyMigration();
|
|
584
589
|
}
|
|
585
|
-
logger$
|
|
590
|
+
logger$16.info({ version: migration.version }, "Migration applied successfully");
|
|
586
591
|
}
|
|
587
|
-
logger$
|
|
592
|
+
logger$16.info({ count: pending.length }, "All pending migrations applied");
|
|
588
593
|
}
|
|
589
594
|
|
|
590
595
|
//#endregion
|
|
591
596
|
//#region src/persistence/database.ts
|
|
592
|
-
const logger$
|
|
597
|
+
const logger$15 = createLogger("persistence:database");
|
|
593
598
|
/**
|
|
594
599
|
* Thin wrapper that opens a SQLite database, applies required PRAGMAs,
|
|
595
600
|
* and exposes the raw BetterSqlite3 instance.
|
|
@@ -606,14 +611,14 @@ var DatabaseWrapper = class {
|
|
|
606
611
|
*/
|
|
607
612
|
open() {
|
|
608
613
|
if (this._db !== null) return;
|
|
609
|
-
logger$
|
|
614
|
+
logger$15.info({ path: this._path }, "Opening SQLite database");
|
|
610
615
|
this._db = new BetterSqlite3(this._path);
|
|
611
616
|
const walResult = this._db.pragma("journal_mode = WAL");
|
|
612
|
-
if (walResult?.[0]?.journal_mode !== "wal") logger$
|
|
617
|
+
if (walResult?.[0]?.journal_mode !== "wal") logger$15.warn({ result: walResult?.[0]?.journal_mode }, "WAL pragma did not return expected \"wal\" — journal_mode may be \"memory\" or unsupported");
|
|
613
618
|
this._db.pragma("busy_timeout = 5000");
|
|
614
619
|
this._db.pragma("synchronous = NORMAL");
|
|
615
620
|
this._db.pragma("foreign_keys = ON");
|
|
616
|
-
logger$
|
|
621
|
+
logger$15.info({ path: this._path }, "SQLite database opened with WAL mode");
|
|
617
622
|
}
|
|
618
623
|
/**
|
|
619
624
|
* Close the database. Idempotent — calling close() when already closed is a no-op.
|
|
@@ -622,7 +627,7 @@ var DatabaseWrapper = class {
|
|
|
622
627
|
if (this._db === null) return;
|
|
623
628
|
this._db.close();
|
|
624
629
|
this._db = null;
|
|
625
|
-
logger$
|
|
630
|
+
logger$15.info({ path: this._path }, "SQLite database closed");
|
|
626
631
|
}
|
|
627
632
|
/**
|
|
628
633
|
* Return the raw BetterSqlite3 instance.
|
|
@@ -1064,8 +1069,8 @@ const PACKAGE_ROOT = join(__dirname, "..", "..", "..");
|
|
|
1064
1069
|
*/
|
|
1065
1070
|
function resolveBmadMethodSrcPath(fromDir = __dirname) {
|
|
1066
1071
|
try {
|
|
1067
|
-
const require = createRequire(join(fromDir, "synthetic.js"));
|
|
1068
|
-
const pkgJsonPath = require.resolve("bmad-method/package.json");
|
|
1072
|
+
const require$1 = createRequire$1(join(fromDir, "synthetic.js"));
|
|
1073
|
+
const pkgJsonPath = require$1.resolve("bmad-method/package.json");
|
|
1069
1074
|
return join(dirname(pkgJsonPath), "src");
|
|
1070
1075
|
} catch {
|
|
1071
1076
|
return null;
|
|
@@ -1077,9 +1082,9 @@ function resolveBmadMethodSrcPath(fromDir = __dirname) {
|
|
|
1077
1082
|
*/
|
|
1078
1083
|
function resolveBmadMethodVersion(fromDir = __dirname) {
|
|
1079
1084
|
try {
|
|
1080
|
-
const require = createRequire(join(fromDir, "synthetic.js"));
|
|
1081
|
-
const pkgJsonPath = require.resolve("bmad-method/package.json");
|
|
1082
|
-
const pkg = require(pkgJsonPath);
|
|
1085
|
+
const require$1 = createRequire$1(join(fromDir, "synthetic.js"));
|
|
1086
|
+
const pkgJsonPath = require$1.resolve("bmad-method/package.json");
|
|
1087
|
+
const pkg = require$1(pkgJsonPath);
|
|
1083
1088
|
return pkg.version ?? "unknown";
|
|
1084
1089
|
} catch {
|
|
1085
1090
|
return "unknown";
|
|
@@ -2491,7 +2496,7 @@ function truncateToTokens(text, maxTokens) {
|
|
|
2491
2496
|
|
|
2492
2497
|
//#endregion
|
|
2493
2498
|
//#region src/modules/context-compiler/context-compiler-impl.ts
|
|
2494
|
-
const logger$
|
|
2499
|
+
const logger$14 = createLogger("context-compiler");
|
|
2495
2500
|
/**
|
|
2496
2501
|
* Fraction of the original token budget that must remain (after required +
|
|
2497
2502
|
* important sections) before an optional section is included.
|
|
@@ -2583,7 +2588,7 @@ var ContextCompilerImpl = class {
|
|
|
2583
2588
|
includedParts.push(truncated);
|
|
2584
2589
|
remainingBudget -= truncatedTokens;
|
|
2585
2590
|
anyTruncated = true;
|
|
2586
|
-
logger$
|
|
2591
|
+
logger$14.warn({
|
|
2587
2592
|
section: section.name,
|
|
2588
2593
|
originalTokens: tokens,
|
|
2589
2594
|
budgetTokens: truncatedTokens
|
|
@@ -2597,7 +2602,7 @@ var ContextCompilerImpl = class {
|
|
|
2597
2602
|
});
|
|
2598
2603
|
} else {
|
|
2599
2604
|
anyTruncated = true;
|
|
2600
|
-
logger$
|
|
2605
|
+
logger$14.warn({
|
|
2601
2606
|
section: section.name,
|
|
2602
2607
|
tokens
|
|
2603
2608
|
}, "Context compiler: omitted \"important\" section — no budget remaining");
|
|
@@ -2624,7 +2629,7 @@ var ContextCompilerImpl = class {
|
|
|
2624
2629
|
} else {
|
|
2625
2630
|
if (tokens > 0) {
|
|
2626
2631
|
anyTruncated = true;
|
|
2627
|
-
logger$
|
|
2632
|
+
logger$14.warn({
|
|
2628
2633
|
section: section.name,
|
|
2629
2634
|
tokens,
|
|
2630
2635
|
budgetFractionRemaining: budgetFractionRemaining.toFixed(2)
|
|
@@ -2892,7 +2897,7 @@ function parseYamlResult(yamlText, schema) {
|
|
|
2892
2897
|
|
|
2893
2898
|
//#endregion
|
|
2894
2899
|
//#region src/modules/agent-dispatch/dispatcher-impl.ts
|
|
2895
|
-
const logger$
|
|
2900
|
+
const logger$13 = createLogger("agent-dispatch");
|
|
2896
2901
|
const SHUTDOWN_GRACE_MS = 1e4;
|
|
2897
2902
|
const SHUTDOWN_MAX_WAIT_MS = 3e4;
|
|
2898
2903
|
const CHARS_PER_TOKEN = 4;
|
|
@@ -2929,7 +2934,7 @@ function getAvailableMemory() {
|
|
|
2929
2934
|
encoding: "utf-8"
|
|
2930
2935
|
}).trim(), 10);
|
|
2931
2936
|
if (pressureLevel >= 2) {
|
|
2932
|
-
logger$
|
|
2937
|
+
logger$13.warn({ pressureLevel }, "macOS kernel reports memory pressure");
|
|
2933
2938
|
return 0;
|
|
2934
2939
|
}
|
|
2935
2940
|
} catch {}
|
|
@@ -3015,7 +3020,7 @@ var DispatcherImpl = class {
|
|
|
3015
3020
|
resolve: typedResolve,
|
|
3016
3021
|
reject
|
|
3017
3022
|
});
|
|
3018
|
-
logger$
|
|
3023
|
+
logger$13.debug({
|
|
3019
3024
|
id,
|
|
3020
3025
|
queueLength: this._queue.length
|
|
3021
3026
|
}, "Dispatch queued");
|
|
@@ -3046,7 +3051,7 @@ var DispatcherImpl = class {
|
|
|
3046
3051
|
async shutdown() {
|
|
3047
3052
|
this._shuttingDown = true;
|
|
3048
3053
|
this._stopMemoryPressureTimer();
|
|
3049
|
-
logger$
|
|
3054
|
+
logger$13.info({
|
|
3050
3055
|
running: this._running.size,
|
|
3051
3056
|
queued: this._queue.length
|
|
3052
3057
|
}, "Dispatcher shutting down");
|
|
@@ -3079,13 +3084,13 @@ var DispatcherImpl = class {
|
|
|
3079
3084
|
}
|
|
3080
3085
|
}, 50);
|
|
3081
3086
|
});
|
|
3082
|
-
logger$
|
|
3087
|
+
logger$13.info("Dispatcher shutdown complete");
|
|
3083
3088
|
}
|
|
3084
3089
|
async _startDispatch(id, request, resolve$2) {
|
|
3085
3090
|
const { prompt, agent, taskType, timeout, outputSchema, workingDirectory, model, maxTurns } = request;
|
|
3086
3091
|
const adapter = this._adapterRegistry.get(agent);
|
|
3087
3092
|
if (adapter === void 0) {
|
|
3088
|
-
logger$
|
|
3093
|
+
logger$13.warn({
|
|
3089
3094
|
id,
|
|
3090
3095
|
agent
|
|
3091
3096
|
}, "No adapter found for agent");
|
|
@@ -3131,7 +3136,7 @@ var DispatcherImpl = class {
|
|
|
3131
3136
|
});
|
|
3132
3137
|
const startedAt = Date.now();
|
|
3133
3138
|
proc.on("error", (err) => {
|
|
3134
|
-
logger$
|
|
3139
|
+
logger$13.error({
|
|
3135
3140
|
id,
|
|
3136
3141
|
binary: cmd.binary,
|
|
3137
3142
|
error: err.message
|
|
@@ -3139,7 +3144,7 @@ var DispatcherImpl = class {
|
|
|
3139
3144
|
});
|
|
3140
3145
|
if (proc.stdin !== null) {
|
|
3141
3146
|
proc.stdin.on("error", (err) => {
|
|
3142
|
-
if (err.code !== "EPIPE") logger$
|
|
3147
|
+
if (err.code !== "EPIPE") logger$13.warn({
|
|
3143
3148
|
id,
|
|
3144
3149
|
error: err.message
|
|
3145
3150
|
}, "stdin write error");
|
|
@@ -3181,7 +3186,7 @@ var DispatcherImpl = class {
|
|
|
3181
3186
|
agent,
|
|
3182
3187
|
taskType
|
|
3183
3188
|
});
|
|
3184
|
-
logger$
|
|
3189
|
+
logger$13.debug({
|
|
3185
3190
|
id,
|
|
3186
3191
|
agent,
|
|
3187
3192
|
taskType,
|
|
@@ -3198,7 +3203,7 @@ var DispatcherImpl = class {
|
|
|
3198
3203
|
dispatchId: id,
|
|
3199
3204
|
timeoutMs
|
|
3200
3205
|
});
|
|
3201
|
-
logger$
|
|
3206
|
+
logger$13.warn({
|
|
3202
3207
|
id,
|
|
3203
3208
|
agent,
|
|
3204
3209
|
taskType,
|
|
@@ -3252,7 +3257,7 @@ var DispatcherImpl = class {
|
|
|
3252
3257
|
exitCode: code,
|
|
3253
3258
|
output: stdout
|
|
3254
3259
|
});
|
|
3255
|
-
logger$
|
|
3260
|
+
logger$13.debug({
|
|
3256
3261
|
id,
|
|
3257
3262
|
agent,
|
|
3258
3263
|
taskType,
|
|
@@ -3278,7 +3283,7 @@ var DispatcherImpl = class {
|
|
|
3278
3283
|
error: stderr || `Process exited with code ${String(code)}`,
|
|
3279
3284
|
exitCode: code
|
|
3280
3285
|
});
|
|
3281
|
-
logger$
|
|
3286
|
+
logger$13.debug({
|
|
3282
3287
|
id,
|
|
3283
3288
|
agent,
|
|
3284
3289
|
taskType,
|
|
@@ -3337,7 +3342,7 @@ var DispatcherImpl = class {
|
|
|
3337
3342
|
const next = this._queue.shift();
|
|
3338
3343
|
if (next === void 0) return;
|
|
3339
3344
|
next.handle.status = "running";
|
|
3340
|
-
logger$
|
|
3345
|
+
logger$13.debug({
|
|
3341
3346
|
id: next.id,
|
|
3342
3347
|
queueLength: this._queue.length
|
|
3343
3348
|
}, "Dequeued dispatch");
|
|
@@ -3350,7 +3355,7 @@ var DispatcherImpl = class {
|
|
|
3350
3355
|
_isMemoryPressured() {
|
|
3351
3356
|
const free = getAvailableMemory();
|
|
3352
3357
|
if (free < MIN_FREE_MEMORY_BYTES) {
|
|
3353
|
-
logger$
|
|
3358
|
+
logger$13.warn({
|
|
3354
3359
|
freeMB: Math.round(free / 1024 / 1024),
|
|
3355
3360
|
thresholdMB: Math.round(MIN_FREE_MEMORY_BYTES / 1024 / 1024)
|
|
3356
3361
|
}, "Memory pressure detected — holding dispatch queue");
|
|
@@ -3390,7 +3395,7 @@ function createDispatcher(options) {
|
|
|
3390
3395
|
|
|
3391
3396
|
//#endregion
|
|
3392
3397
|
//#region src/modules/compiled-workflows/prompt-assembler.ts
|
|
3393
|
-
const logger$
|
|
3398
|
+
const logger$12 = createLogger("compiled-workflows:prompt-assembler");
|
|
3394
3399
|
/**
|
|
3395
3400
|
* Assemble a final prompt from a template and sections map.
|
|
3396
3401
|
*
|
|
@@ -3415,7 +3420,7 @@ function assemblePrompt(template, sections, tokenCeiling = 2200) {
|
|
|
3415
3420
|
tokenCount,
|
|
3416
3421
|
truncated: false
|
|
3417
3422
|
};
|
|
3418
|
-
logger$
|
|
3423
|
+
logger$12.warn({
|
|
3419
3424
|
tokenCount,
|
|
3420
3425
|
ceiling: tokenCeiling
|
|
3421
3426
|
}, "Prompt exceeds token ceiling — truncating optional sections");
|
|
@@ -3431,10 +3436,10 @@ function assemblePrompt(template, sections, tokenCeiling = 2200) {
|
|
|
3431
3436
|
const targetSectionTokens = Math.max(0, currentSectionTokens - overBy);
|
|
3432
3437
|
if (targetSectionTokens === 0) {
|
|
3433
3438
|
contentMap[section.name] = "";
|
|
3434
|
-
logger$
|
|
3439
|
+
logger$12.warn({ sectionName: section.name }, "Section eliminated to fit token budget");
|
|
3435
3440
|
} else {
|
|
3436
3441
|
contentMap[section.name] = truncateToTokens(section.content, targetSectionTokens);
|
|
3437
|
-
logger$
|
|
3442
|
+
logger$12.warn({
|
|
3438
3443
|
sectionName: section.name,
|
|
3439
3444
|
targetSectionTokens
|
|
3440
3445
|
}, "Section truncated to fit token budget");
|
|
@@ -3445,7 +3450,7 @@ function assemblePrompt(template, sections, tokenCeiling = 2200) {
|
|
|
3445
3450
|
}
|
|
3446
3451
|
if (tokenCount <= tokenCeiling) break;
|
|
3447
3452
|
}
|
|
3448
|
-
if (tokenCount > tokenCeiling) logger$
|
|
3453
|
+
if (tokenCount > tokenCeiling) logger$12.warn({
|
|
3449
3454
|
tokenCount,
|
|
3450
3455
|
ceiling: tokenCeiling
|
|
3451
3456
|
}, "Required sections alone exceed token ceiling — returning over-budget prompt");
|
|
@@ -3586,7 +3591,7 @@ const CodeReviewResultSchema = z.object({
|
|
|
3586
3591
|
|
|
3587
3592
|
//#endregion
|
|
3588
3593
|
//#region src/modules/compiled-workflows/create-story.ts
|
|
3589
|
-
const logger$
|
|
3594
|
+
const logger$11 = createLogger("compiled-workflows:create-story");
|
|
3590
3595
|
/**
|
|
3591
3596
|
* Hard ceiling for the assembled create-story prompt.
|
|
3592
3597
|
*/
|
|
@@ -3610,7 +3615,7 @@ const TOKEN_CEILING$2 = 3e3;
|
|
|
3610
3615
|
*/
|
|
3611
3616
|
async function runCreateStory(deps, params) {
|
|
3612
3617
|
const { epicId, storyKey, pipelineRunId } = params;
|
|
3613
|
-
logger$
|
|
3618
|
+
logger$11.debug({
|
|
3614
3619
|
epicId,
|
|
3615
3620
|
storyKey,
|
|
3616
3621
|
pipelineRunId
|
|
@@ -3620,7 +3625,7 @@ async function runCreateStory(deps, params) {
|
|
|
3620
3625
|
template = await deps.pack.getPrompt("create-story");
|
|
3621
3626
|
} catch (err) {
|
|
3622
3627
|
const error = err instanceof Error ? err.message : String(err);
|
|
3623
|
-
logger$
|
|
3628
|
+
logger$11.error({ error }, "Failed to retrieve create-story prompt template");
|
|
3624
3629
|
return {
|
|
3625
3630
|
result: "failed",
|
|
3626
3631
|
error: `Failed to retrieve prompt template: ${error}`,
|
|
@@ -3662,7 +3667,7 @@ async function runCreateStory(deps, params) {
|
|
|
3662
3667
|
priority: "important"
|
|
3663
3668
|
}
|
|
3664
3669
|
], TOKEN_CEILING$2);
|
|
3665
|
-
logger$
|
|
3670
|
+
logger$11.debug({
|
|
3666
3671
|
tokenCount,
|
|
3667
3672
|
truncated,
|
|
3668
3673
|
tokenCeiling: TOKEN_CEILING$2
|
|
@@ -3679,7 +3684,7 @@ async function runCreateStory(deps, params) {
|
|
|
3679
3684
|
dispatchResult = await handle.result;
|
|
3680
3685
|
} catch (err) {
|
|
3681
3686
|
const error = err instanceof Error ? err.message : String(err);
|
|
3682
|
-
logger$
|
|
3687
|
+
logger$11.error({
|
|
3683
3688
|
epicId,
|
|
3684
3689
|
storyKey,
|
|
3685
3690
|
error
|
|
@@ -3700,7 +3705,7 @@ async function runCreateStory(deps, params) {
|
|
|
3700
3705
|
if (dispatchResult.status === "failed") {
|
|
3701
3706
|
const errorMsg = dispatchResult.parseError ?? `Dispatch failed with exit code ${dispatchResult.exitCode}`;
|
|
3702
3707
|
const stderrDetail = dispatchResult.output ? ` Output: ${dispatchResult.output}` : "";
|
|
3703
|
-
logger$
|
|
3708
|
+
logger$11.warn({
|
|
3704
3709
|
epicId,
|
|
3705
3710
|
storyKey,
|
|
3706
3711
|
exitCode: dispatchResult.exitCode
|
|
@@ -3712,7 +3717,7 @@ async function runCreateStory(deps, params) {
|
|
|
3712
3717
|
};
|
|
3713
3718
|
}
|
|
3714
3719
|
if (dispatchResult.status === "timeout") {
|
|
3715
|
-
logger$
|
|
3720
|
+
logger$11.warn({
|
|
3716
3721
|
epicId,
|
|
3717
3722
|
storyKey
|
|
3718
3723
|
}, "Create-story dispatch timed out");
|
|
@@ -3725,7 +3730,7 @@ async function runCreateStory(deps, params) {
|
|
|
3725
3730
|
if (dispatchResult.parsed === null) {
|
|
3726
3731
|
const details = dispatchResult.parseError ?? "No YAML block found in output";
|
|
3727
3732
|
const rawSnippet = dispatchResult.output ? dispatchResult.output.slice(0, 1e3) : "(empty)";
|
|
3728
|
-
logger$
|
|
3733
|
+
logger$11.warn({
|
|
3729
3734
|
epicId,
|
|
3730
3735
|
storyKey,
|
|
3731
3736
|
details,
|
|
@@ -3741,7 +3746,7 @@ async function runCreateStory(deps, params) {
|
|
|
3741
3746
|
const parseResult = CreateStoryResultSchema.safeParse(dispatchResult.parsed);
|
|
3742
3747
|
if (!parseResult.success) {
|
|
3743
3748
|
const details = parseResult.error.message;
|
|
3744
|
-
logger$
|
|
3749
|
+
logger$11.warn({
|
|
3745
3750
|
epicId,
|
|
3746
3751
|
storyKey,
|
|
3747
3752
|
details
|
|
@@ -3754,7 +3759,7 @@ async function runCreateStory(deps, params) {
|
|
|
3754
3759
|
};
|
|
3755
3760
|
}
|
|
3756
3761
|
const parsed = parseResult.data;
|
|
3757
|
-
logger$
|
|
3762
|
+
logger$11.info({
|
|
3758
3763
|
epicId,
|
|
3759
3764
|
storyKey,
|
|
3760
3765
|
storyFile: parsed.story_file,
|
|
@@ -3776,7 +3781,7 @@ function getImplementationDecisions(deps) {
|
|
|
3776
3781
|
try {
|
|
3777
3782
|
return getDecisionsByPhase(deps.db, "implementation");
|
|
3778
3783
|
} catch (err) {
|
|
3779
|
-
logger$
|
|
3784
|
+
logger$11.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve implementation decisions");
|
|
3780
3785
|
return [];
|
|
3781
3786
|
}
|
|
3782
3787
|
}
|
|
@@ -3792,13 +3797,13 @@ function getEpicShard(decisions, epicId, projectRoot) {
|
|
|
3792
3797
|
if (projectRoot) {
|
|
3793
3798
|
const fallback = readEpicShardFromFile(projectRoot, epicId);
|
|
3794
3799
|
if (fallback) {
|
|
3795
|
-
logger$
|
|
3800
|
+
logger$11.info({ epicId }, "Using file-based fallback for epic shard (decisions table empty)");
|
|
3796
3801
|
return fallback;
|
|
3797
3802
|
}
|
|
3798
3803
|
}
|
|
3799
3804
|
return "";
|
|
3800
3805
|
} catch (err) {
|
|
3801
|
-
logger$
|
|
3806
|
+
logger$11.warn({
|
|
3802
3807
|
epicId,
|
|
3803
3808
|
error: err instanceof Error ? err.message : String(err)
|
|
3804
3809
|
}, "Failed to retrieve epic shard");
|
|
@@ -3815,7 +3820,7 @@ function getPrevDevNotes(decisions, epicId) {
|
|
|
3815
3820
|
if (devNotes.length === 0) return "";
|
|
3816
3821
|
return devNotes[devNotes.length - 1].value;
|
|
3817
3822
|
} catch (err) {
|
|
3818
|
-
logger$
|
|
3823
|
+
logger$11.warn({
|
|
3819
3824
|
epicId,
|
|
3820
3825
|
error: err instanceof Error ? err.message : String(err)
|
|
3821
3826
|
}, "Failed to retrieve prev dev notes");
|
|
@@ -3835,13 +3840,13 @@ function getArchConstraints$1(deps) {
|
|
|
3835
3840
|
if (deps.projectRoot) {
|
|
3836
3841
|
const fallback = readArchConstraintsFromFile(deps.projectRoot);
|
|
3837
3842
|
if (fallback) {
|
|
3838
|
-
logger$
|
|
3843
|
+
logger$11.info("Using file-based fallback for architecture constraints (decisions table empty)");
|
|
3839
3844
|
return fallback;
|
|
3840
3845
|
}
|
|
3841
3846
|
}
|
|
3842
3847
|
return "";
|
|
3843
3848
|
} catch (err) {
|
|
3844
|
-
logger$
|
|
3849
|
+
logger$11.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve architecture constraints");
|
|
3845
3850
|
return "";
|
|
3846
3851
|
}
|
|
3847
3852
|
}
|
|
@@ -3861,7 +3866,7 @@ function readEpicShardFromFile(projectRoot, epicId) {
|
|
|
3861
3866
|
const match = pattern.exec(content);
|
|
3862
3867
|
return match ? match[0].trim() : "";
|
|
3863
3868
|
} catch (err) {
|
|
3864
|
-
logger$
|
|
3869
|
+
logger$11.warn({
|
|
3865
3870
|
epicId,
|
|
3866
3871
|
error: err instanceof Error ? err.message : String(err)
|
|
3867
3872
|
}, "File-based epic shard fallback failed");
|
|
@@ -3884,7 +3889,7 @@ function readArchConstraintsFromFile(projectRoot) {
|
|
|
3884
3889
|
const content = readFileSync$1(archPath, "utf-8");
|
|
3885
3890
|
return content.slice(0, 1500);
|
|
3886
3891
|
} catch (err) {
|
|
3887
|
-
logger$
|
|
3892
|
+
logger$11.warn({ error: err instanceof Error ? err.message : String(err) }, "File-based architecture fallback failed");
|
|
3888
3893
|
return "";
|
|
3889
3894
|
}
|
|
3890
3895
|
}
|
|
@@ -3897,14 +3902,14 @@ async function getStoryTemplate(deps) {
|
|
|
3897
3902
|
try {
|
|
3898
3903
|
return await deps.pack.getTemplate("story");
|
|
3899
3904
|
} catch (err) {
|
|
3900
|
-
logger$
|
|
3905
|
+
logger$11.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve story template from pack");
|
|
3901
3906
|
return "";
|
|
3902
3907
|
}
|
|
3903
3908
|
}
|
|
3904
3909
|
|
|
3905
3910
|
//#endregion
|
|
3906
3911
|
//#region src/modules/compiled-workflows/git-helpers.ts
|
|
3907
|
-
const logger$
|
|
3912
|
+
const logger$10 = createLogger("compiled-workflows:git-helpers");
|
|
3908
3913
|
/**
|
|
3909
3914
|
* Capture the full git diff for HEAD (working tree vs current commit).
|
|
3910
3915
|
*
|
|
@@ -4028,7 +4033,7 @@ async function runGitCommand(args, cwd, logLabel) {
|
|
|
4028
4033
|
stderr += chunk.toString("utf-8");
|
|
4029
4034
|
});
|
|
4030
4035
|
proc.on("error", (err) => {
|
|
4031
|
-
logger$
|
|
4036
|
+
logger$10.warn({
|
|
4032
4037
|
label: logLabel,
|
|
4033
4038
|
cwd,
|
|
4034
4039
|
error: err.message
|
|
@@ -4037,7 +4042,7 @@ async function runGitCommand(args, cwd, logLabel) {
|
|
|
4037
4042
|
});
|
|
4038
4043
|
proc.on("close", (code) => {
|
|
4039
4044
|
if (code !== 0) {
|
|
4040
|
-
logger$
|
|
4045
|
+
logger$10.warn({
|
|
4041
4046
|
label: logLabel,
|
|
4042
4047
|
cwd,
|
|
4043
4048
|
code,
|
|
@@ -4053,7 +4058,7 @@ async function runGitCommand(args, cwd, logLabel) {
|
|
|
4053
4058
|
|
|
4054
4059
|
//#endregion
|
|
4055
4060
|
//#region src/modules/compiled-workflows/dev-story.ts
|
|
4056
|
-
const logger$
|
|
4061
|
+
const logger$9 = createLogger("compiled-workflows:dev-story");
|
|
4057
4062
|
/** Hard token ceiling for the assembled dev-story prompt */
|
|
4058
4063
|
const TOKEN_CEILING$1 = 24e3;
|
|
4059
4064
|
/** Default timeout for dev-story dispatches in milliseconds (30 min) */
|
|
@@ -4078,7 +4083,7 @@ const DEFAULT_VITEST_PATTERNS = `## Test Patterns (defaults)
|
|
|
4078
4083
|
*/
|
|
4079
4084
|
async function runDevStory(deps, params) {
|
|
4080
4085
|
const { storyKey, storyFilePath, taskScope, priorFiles } = params;
|
|
4081
|
-
logger$
|
|
4086
|
+
logger$9.info({
|
|
4082
4087
|
storyKey,
|
|
4083
4088
|
storyFilePath
|
|
4084
4089
|
}, "Starting compiled dev-story workflow");
|
|
@@ -4120,10 +4125,10 @@ async function runDevStory(deps, params) {
|
|
|
4120
4125
|
let template;
|
|
4121
4126
|
try {
|
|
4122
4127
|
template = await deps.pack.getPrompt("dev-story");
|
|
4123
|
-
logger$
|
|
4128
|
+
logger$9.debug({ storyKey }, "Retrieved dev-story prompt template from pack");
|
|
4124
4129
|
} catch (err) {
|
|
4125
4130
|
const error = err instanceof Error ? err.message : String(err);
|
|
4126
|
-
logger$
|
|
4131
|
+
logger$9.error({
|
|
4127
4132
|
storyKey,
|
|
4128
4133
|
error
|
|
4129
4134
|
}, "Failed to retrieve dev-story prompt template");
|
|
@@ -4134,14 +4139,14 @@ async function runDevStory(deps, params) {
|
|
|
4134
4139
|
storyContent = await readFile$1(storyFilePath, "utf-8");
|
|
4135
4140
|
} catch (err) {
|
|
4136
4141
|
if (err.code === "ENOENT") {
|
|
4137
|
-
logger$
|
|
4142
|
+
logger$9.error({
|
|
4138
4143
|
storyKey,
|
|
4139
4144
|
storyFilePath
|
|
4140
4145
|
}, "Story file not found");
|
|
4141
4146
|
return makeFailureResult("story_file_not_found");
|
|
4142
4147
|
}
|
|
4143
4148
|
const error = err instanceof Error ? err.message : String(err);
|
|
4144
|
-
logger$
|
|
4149
|
+
logger$9.error({
|
|
4145
4150
|
storyKey,
|
|
4146
4151
|
storyFilePath,
|
|
4147
4152
|
error
|
|
@@ -4149,7 +4154,7 @@ async function runDevStory(deps, params) {
|
|
|
4149
4154
|
return makeFailureResult(`story_file_read_error: ${error}`);
|
|
4150
4155
|
}
|
|
4151
4156
|
if (storyContent.trim().length === 0) {
|
|
4152
|
-
logger$
|
|
4157
|
+
logger$9.error({
|
|
4153
4158
|
storyKey,
|
|
4154
4159
|
storyFilePath
|
|
4155
4160
|
}, "Story file is empty");
|
|
@@ -4161,17 +4166,17 @@ async function runDevStory(deps, params) {
|
|
|
4161
4166
|
const testPatternDecisions = solutioningDecisions.filter((d) => d.category === "test-patterns");
|
|
4162
4167
|
if (testPatternDecisions.length > 0) {
|
|
4163
4168
|
testPatternsContent = "## Test Patterns\n" + testPatternDecisions.map((d) => `- ${d.key}: ${d.value}`).join("\n");
|
|
4164
|
-
logger$
|
|
4169
|
+
logger$9.debug({
|
|
4165
4170
|
storyKey,
|
|
4166
4171
|
count: testPatternDecisions.length
|
|
4167
4172
|
}, "Loaded test patterns from decision store");
|
|
4168
4173
|
} else {
|
|
4169
4174
|
testPatternsContent = DEFAULT_VITEST_PATTERNS;
|
|
4170
|
-
logger$
|
|
4175
|
+
logger$9.debug({ storyKey }, "No test-pattern decisions found — using default Vitest patterns");
|
|
4171
4176
|
}
|
|
4172
4177
|
} catch (err) {
|
|
4173
4178
|
const error = err instanceof Error ? err.message : String(err);
|
|
4174
|
-
logger$
|
|
4179
|
+
logger$9.warn({
|
|
4175
4180
|
storyKey,
|
|
4176
4181
|
error
|
|
4177
4182
|
}, "Failed to load test patterns — using defaults");
|
|
@@ -4214,7 +4219,7 @@ async function runDevStory(deps, params) {
|
|
|
4214
4219
|
}
|
|
4215
4220
|
];
|
|
4216
4221
|
const { prompt, tokenCount, truncated } = assemblePrompt(template, sections, TOKEN_CEILING$1);
|
|
4217
|
-
logger$
|
|
4222
|
+
logger$9.info({
|
|
4218
4223
|
storyKey,
|
|
4219
4224
|
tokenCount,
|
|
4220
4225
|
ceiling: TOKEN_CEILING$1,
|
|
@@ -4233,7 +4238,7 @@ async function runDevStory(deps, params) {
|
|
|
4233
4238
|
dispatchResult = await handle.result;
|
|
4234
4239
|
} catch (err) {
|
|
4235
4240
|
const error = err instanceof Error ? err.message : String(err);
|
|
4236
|
-
logger$
|
|
4241
|
+
logger$9.error({
|
|
4237
4242
|
storyKey,
|
|
4238
4243
|
error
|
|
4239
4244
|
}, "Dispatch threw an unexpected error");
|
|
@@ -4244,11 +4249,11 @@ async function runDevStory(deps, params) {
|
|
|
4244
4249
|
output: dispatchResult.tokenEstimate.output
|
|
4245
4250
|
};
|
|
4246
4251
|
if (dispatchResult.status === "timeout") {
|
|
4247
|
-
logger$
|
|
4252
|
+
logger$9.error({
|
|
4248
4253
|
storyKey,
|
|
4249
4254
|
durationMs: dispatchResult.durationMs
|
|
4250
4255
|
}, "Dev-story dispatch timed out");
|
|
4251
|
-
if (dispatchResult.output.length > 0) logger$
|
|
4256
|
+
if (dispatchResult.output.length > 0) logger$9.info({
|
|
4252
4257
|
storyKey,
|
|
4253
4258
|
partialOutput: dispatchResult.output.slice(0, 500)
|
|
4254
4259
|
}, "Partial output before timeout");
|
|
@@ -4258,12 +4263,12 @@ async function runDevStory(deps, params) {
|
|
|
4258
4263
|
};
|
|
4259
4264
|
}
|
|
4260
4265
|
if (dispatchResult.status === "failed" || dispatchResult.exitCode !== 0) {
|
|
4261
|
-
logger$
|
|
4266
|
+
logger$9.error({
|
|
4262
4267
|
storyKey,
|
|
4263
4268
|
exitCode: dispatchResult.exitCode,
|
|
4264
4269
|
status: dispatchResult.status
|
|
4265
4270
|
}, "Dev-story dispatch failed");
|
|
4266
|
-
if (dispatchResult.output.length > 0) logger$
|
|
4271
|
+
if (dispatchResult.output.length > 0) logger$9.info({
|
|
4267
4272
|
storyKey,
|
|
4268
4273
|
partialOutput: dispatchResult.output.slice(0, 500)
|
|
4269
4274
|
}, "Partial output from failed dispatch");
|
|
@@ -4275,7 +4280,7 @@ async function runDevStory(deps, params) {
|
|
|
4275
4280
|
if (dispatchResult.parseError !== null || dispatchResult.parsed === null) {
|
|
4276
4281
|
const details = dispatchResult.parseError ?? "parsed result was null";
|
|
4277
4282
|
const rawSnippet = dispatchResult.output ? dispatchResult.output.slice(0, 1e3) : "(empty)";
|
|
4278
|
-
logger$
|
|
4283
|
+
logger$9.error({
|
|
4279
4284
|
storyKey,
|
|
4280
4285
|
parseError: details,
|
|
4281
4286
|
rawOutputSnippet: rawSnippet
|
|
@@ -4283,12 +4288,12 @@ async function runDevStory(deps, params) {
|
|
|
4283
4288
|
let filesModified = [];
|
|
4284
4289
|
try {
|
|
4285
4290
|
filesModified = await getGitChangedFiles(deps.projectRoot ?? process.cwd());
|
|
4286
|
-
if (filesModified.length > 0) logger$
|
|
4291
|
+
if (filesModified.length > 0) logger$9.info({
|
|
4287
4292
|
storyKey,
|
|
4288
4293
|
fileCount: filesModified.length
|
|
4289
4294
|
}, "Recovered files_modified from git status (YAML fallback)");
|
|
4290
4295
|
} catch (err) {
|
|
4291
|
-
logger$
|
|
4296
|
+
logger$9.warn({
|
|
4292
4297
|
storyKey,
|
|
4293
4298
|
error: err instanceof Error ? err.message : String(err)
|
|
4294
4299
|
}, "Failed to recover files_modified from git");
|
|
@@ -4305,7 +4310,7 @@ async function runDevStory(deps, params) {
|
|
|
4305
4310
|
};
|
|
4306
4311
|
}
|
|
4307
4312
|
const parsed = dispatchResult.parsed;
|
|
4308
|
-
logger$
|
|
4313
|
+
logger$9.info({
|
|
4309
4314
|
storyKey,
|
|
4310
4315
|
result: parsed.result,
|
|
4311
4316
|
acMet: parsed.ac_met.length
|
|
@@ -4444,7 +4449,7 @@ function extractFilesInScope(storyContent) {
|
|
|
4444
4449
|
|
|
4445
4450
|
//#endregion
|
|
4446
4451
|
//#region src/modules/compiled-workflows/code-review.ts
|
|
4447
|
-
const logger$
|
|
4452
|
+
const logger$8 = createLogger("compiled-workflows:code-review");
|
|
4448
4453
|
/**
|
|
4449
4454
|
* Hard token ceiling for the assembled code-review prompt (50,000 tokens).
|
|
4450
4455
|
* Quality reviews require seeing actual code diffs, not just file names.
|
|
@@ -4484,7 +4489,7 @@ function defaultFailResult(error, tokenUsage) {
|
|
|
4484
4489
|
async function runCodeReview(deps, params) {
|
|
4485
4490
|
const { storyKey, storyFilePath, workingDirectory, pipelineRunId, filesModified, previousIssues } = params;
|
|
4486
4491
|
const cwd = workingDirectory ?? process.cwd();
|
|
4487
|
-
logger$
|
|
4492
|
+
logger$8.debug({
|
|
4488
4493
|
storyKey,
|
|
4489
4494
|
storyFilePath,
|
|
4490
4495
|
cwd,
|
|
@@ -4495,7 +4500,7 @@ async function runCodeReview(deps, params) {
|
|
|
4495
4500
|
template = await deps.pack.getPrompt("code-review");
|
|
4496
4501
|
} catch (err) {
|
|
4497
4502
|
const error = err instanceof Error ? err.message : String(err);
|
|
4498
|
-
logger$
|
|
4503
|
+
logger$8.error({ error }, "Failed to retrieve code-review prompt template");
|
|
4499
4504
|
return defaultFailResult(`Failed to retrieve prompt template: ${error}`, {
|
|
4500
4505
|
input: 0,
|
|
4501
4506
|
output: 0
|
|
@@ -4506,7 +4511,7 @@ async function runCodeReview(deps, params) {
|
|
|
4506
4511
|
storyContent = await readFile$1(storyFilePath, "utf-8");
|
|
4507
4512
|
} catch (err) {
|
|
4508
4513
|
const error = err instanceof Error ? err.message : String(err);
|
|
4509
|
-
logger$
|
|
4514
|
+
logger$8.error({
|
|
4510
4515
|
storyFilePath,
|
|
4511
4516
|
error
|
|
4512
4517
|
}, "Failed to read story file");
|
|
@@ -4526,12 +4531,12 @@ async function runCodeReview(deps, params) {
|
|
|
4526
4531
|
const scopedTotal = nonDiffTokens + countTokens(scopedDiff);
|
|
4527
4532
|
if (scopedTotal <= TOKEN_CEILING) {
|
|
4528
4533
|
gitDiffContent = scopedDiff;
|
|
4529
|
-
logger$
|
|
4534
|
+
logger$8.debug({
|
|
4530
4535
|
fileCount: filesModified.length,
|
|
4531
4536
|
tokenCount: scopedTotal
|
|
4532
4537
|
}, "Using scoped file diff");
|
|
4533
4538
|
} else {
|
|
4534
|
-
logger$
|
|
4539
|
+
logger$8.warn({
|
|
4535
4540
|
estimatedTotal: scopedTotal,
|
|
4536
4541
|
ceiling: TOKEN_CEILING,
|
|
4537
4542
|
fileCount: filesModified.length
|
|
@@ -4545,7 +4550,7 @@ async function runCodeReview(deps, params) {
|
|
|
4545
4550
|
const fullTotal = nonDiffTokens + countTokens(fullDiff);
|
|
4546
4551
|
if (fullTotal <= TOKEN_CEILING) gitDiffContent = fullDiff;
|
|
4547
4552
|
else {
|
|
4548
|
-
logger$
|
|
4553
|
+
logger$8.warn({
|
|
4549
4554
|
estimatedTotal: fullTotal,
|
|
4550
4555
|
ceiling: TOKEN_CEILING
|
|
4551
4556
|
}, "Full git diff would exceed token ceiling — using stat-only summary");
|
|
@@ -4583,11 +4588,11 @@ async function runCodeReview(deps, params) {
|
|
|
4583
4588
|
}
|
|
4584
4589
|
];
|
|
4585
4590
|
const assembleResult = assemblePrompt(template, sections, TOKEN_CEILING);
|
|
4586
|
-
if (assembleResult.truncated) logger$
|
|
4591
|
+
if (assembleResult.truncated) logger$8.warn({
|
|
4587
4592
|
storyKey,
|
|
4588
4593
|
tokenCount: assembleResult.tokenCount
|
|
4589
4594
|
}, "Code-review prompt truncated to fit token ceiling");
|
|
4590
|
-
logger$
|
|
4595
|
+
logger$8.debug({
|
|
4591
4596
|
storyKey,
|
|
4592
4597
|
tokenCount: assembleResult.tokenCount,
|
|
4593
4598
|
truncated: assembleResult.truncated
|
|
@@ -4605,7 +4610,7 @@ async function runCodeReview(deps, params) {
|
|
|
4605
4610
|
dispatchResult = await handle.result;
|
|
4606
4611
|
} catch (err) {
|
|
4607
4612
|
const error = err instanceof Error ? err.message : String(err);
|
|
4608
|
-
logger$
|
|
4613
|
+
logger$8.error({
|
|
4609
4614
|
storyKey,
|
|
4610
4615
|
error
|
|
4611
4616
|
}, "Code-review dispatch threw unexpected error");
|
|
@@ -4621,7 +4626,7 @@ async function runCodeReview(deps, params) {
|
|
|
4621
4626
|
const rawOutput = dispatchResult.output ?? void 0;
|
|
4622
4627
|
if (dispatchResult.status === "failed") {
|
|
4623
4628
|
const errorMsg = `Dispatch status: failed. Exit code: ${dispatchResult.exitCode}. ${dispatchResult.parseError ?? ""} ${dispatchResult.output ? `Stderr: ${dispatchResult.output}` : ""}`.trim();
|
|
4624
|
-
logger$
|
|
4629
|
+
logger$8.warn({
|
|
4625
4630
|
storyKey,
|
|
4626
4631
|
exitCode: dispatchResult.exitCode
|
|
4627
4632
|
}, "Code-review dispatch failed");
|
|
@@ -4631,7 +4636,7 @@ async function runCodeReview(deps, params) {
|
|
|
4631
4636
|
};
|
|
4632
4637
|
}
|
|
4633
4638
|
if (dispatchResult.status === "timeout") {
|
|
4634
|
-
logger$
|
|
4639
|
+
logger$8.warn({ storyKey }, "Code-review dispatch timed out");
|
|
4635
4640
|
return {
|
|
4636
4641
|
...defaultFailResult("Dispatch status: timeout. The agent did not complete within the allowed time.", tokenUsage),
|
|
4637
4642
|
rawOutput
|
|
@@ -4639,7 +4644,7 @@ async function runCodeReview(deps, params) {
|
|
|
4639
4644
|
}
|
|
4640
4645
|
if (dispatchResult.parsed === null) {
|
|
4641
4646
|
const details = dispatchResult.parseError ?? "No YAML block found in output";
|
|
4642
|
-
logger$
|
|
4647
|
+
logger$8.warn({
|
|
4643
4648
|
storyKey,
|
|
4644
4649
|
details
|
|
4645
4650
|
}, "Code-review output schema validation failed");
|
|
@@ -4656,7 +4661,7 @@ async function runCodeReview(deps, params) {
|
|
|
4656
4661
|
const parseResult = CodeReviewResultSchema.safeParse(dispatchResult.parsed);
|
|
4657
4662
|
if (!parseResult.success) {
|
|
4658
4663
|
const details = parseResult.error.message;
|
|
4659
|
-
logger$
|
|
4664
|
+
logger$8.warn({
|
|
4660
4665
|
storyKey,
|
|
4661
4666
|
details
|
|
4662
4667
|
}, "Code-review output failed schema validation");
|
|
@@ -4671,13 +4676,13 @@ async function runCodeReview(deps, params) {
|
|
|
4671
4676
|
};
|
|
4672
4677
|
}
|
|
4673
4678
|
const parsed = parseResult.data;
|
|
4674
|
-
if (parsed.agentVerdict !== parsed.verdict) logger$
|
|
4679
|
+
if (parsed.agentVerdict !== parsed.verdict) logger$8.info({
|
|
4675
4680
|
storyKey,
|
|
4676
4681
|
agentVerdict: parsed.agentVerdict,
|
|
4677
4682
|
pipelineVerdict: parsed.verdict,
|
|
4678
4683
|
issues: parsed.issues
|
|
4679
4684
|
}, "Pipeline overrode agent verdict based on issue severities");
|
|
4680
|
-
logger$
|
|
4685
|
+
logger$8.info({
|
|
4681
4686
|
storyKey,
|
|
4682
4687
|
verdict: parsed.verdict,
|
|
4683
4688
|
issues: parsed.issues
|
|
@@ -4702,7 +4707,7 @@ function getArchConstraints(deps) {
|
|
|
4702
4707
|
if (constraints.length === 0) return "";
|
|
4703
4708
|
return constraints.map((d) => `${d.key}: ${d.value}`).join("\n");
|
|
4704
4709
|
} catch (err) {
|
|
4705
|
-
logger$
|
|
4710
|
+
logger$8.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve architecture constraints");
|
|
4706
4711
|
return "";
|
|
4707
4712
|
}
|
|
4708
4713
|
}
|
|
@@ -5034,7 +5039,7 @@ function detectConflictGroups(storyKeys, config) {
|
|
|
5034
5039
|
|
|
5035
5040
|
//#endregion
|
|
5036
5041
|
//#region src/modules/implementation-orchestrator/seed-methodology-context.ts
|
|
5037
|
-
const logger$
|
|
5042
|
+
const logger$7 = createLogger("implementation-orchestrator:seed");
|
|
5038
5043
|
/** Max chars for the architecture summary seeded into decisions */
|
|
5039
5044
|
const MAX_ARCH_CHARS = 6e3;
|
|
5040
5045
|
/** Max chars per epic shard */
|
|
@@ -5068,12 +5073,12 @@ function seedMethodologyContext(db, projectRoot) {
|
|
|
5068
5073
|
const testCount = seedTestPatterns(db, projectRoot);
|
|
5069
5074
|
if (testCount === -1) result.skippedCategories.push("test-patterns");
|
|
5070
5075
|
else result.decisionsCreated += testCount;
|
|
5071
|
-
logger$
|
|
5076
|
+
logger$7.info({
|
|
5072
5077
|
decisionsCreated: result.decisionsCreated,
|
|
5073
5078
|
skippedCategories: result.skippedCategories
|
|
5074
5079
|
}, "Methodology context seeding complete");
|
|
5075
5080
|
} catch (err) {
|
|
5076
|
-
logger$
|
|
5081
|
+
logger$7.warn({ error: err instanceof Error ? err.message : String(err) }, "Methodology context seeding failed (non-fatal)");
|
|
5077
5082
|
}
|
|
5078
5083
|
return result;
|
|
5079
5084
|
}
|
|
@@ -5117,7 +5122,7 @@ function seedArchitecture(db, projectRoot) {
|
|
|
5117
5122
|
});
|
|
5118
5123
|
count = 1;
|
|
5119
5124
|
}
|
|
5120
|
-
logger$
|
|
5125
|
+
logger$7.debug({ count }, "Seeded architecture decisions");
|
|
5121
5126
|
return count;
|
|
5122
5127
|
}
|
|
5123
5128
|
/**
|
|
@@ -5145,7 +5150,7 @@ function seedEpicShards(db, projectRoot) {
|
|
|
5145
5150
|
});
|
|
5146
5151
|
count++;
|
|
5147
5152
|
}
|
|
5148
|
-
logger$
|
|
5153
|
+
logger$7.debug({ count }, "Seeded epic shard decisions");
|
|
5149
5154
|
return count;
|
|
5150
5155
|
}
|
|
5151
5156
|
/**
|
|
@@ -5166,7 +5171,7 @@ function seedTestPatterns(db, projectRoot) {
|
|
|
5166
5171
|
value: patterns.slice(0, MAX_TEST_PATTERNS_CHARS),
|
|
5167
5172
|
rationale: "Detected from project configuration at orchestrator startup"
|
|
5168
5173
|
});
|
|
5169
|
-
logger$
|
|
5174
|
+
logger$7.debug("Seeded test patterns decision");
|
|
5170
5175
|
return 1;
|
|
5171
5176
|
}
|
|
5172
5177
|
/**
|
|
@@ -5358,7 +5363,7 @@ function createPauseGate() {
|
|
|
5358
5363
|
*/
|
|
5359
5364
|
function createImplementationOrchestrator(deps) {
|
|
5360
5365
|
const { db, pack, contextCompiler, dispatcher, eventBus, config, projectRoot } = deps;
|
|
5361
|
-
const logger$
|
|
5366
|
+
const logger$17 = createLogger("implementation-orchestrator");
|
|
5362
5367
|
let _state = "IDLE";
|
|
5363
5368
|
let _startedAt;
|
|
5364
5369
|
let _completedAt;
|
|
@@ -5395,7 +5400,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5395
5400
|
const nowMs = Date.now();
|
|
5396
5401
|
for (const [phase, startMs] of starts) {
|
|
5397
5402
|
const endMs = ends?.get(phase);
|
|
5398
|
-
if (endMs === void 0) logger$
|
|
5403
|
+
if (endMs === void 0) logger$17.warn({
|
|
5399
5404
|
storyKey,
|
|
5400
5405
|
phase
|
|
5401
5406
|
}, "Phase has no end time — story may have errored mid-phase. Duration capped to now() and may be inflated.");
|
|
@@ -5442,13 +5447,13 @@ function createImplementationOrchestrator(deps) {
|
|
|
5442
5447
|
rationale: `Story ${storyKey} completed with result=${result} in ${wallClockSeconds}s. Tokens: ${tokenAgg.input}+${tokenAgg.output}. Review cycles: ${reviewCycles}.`
|
|
5443
5448
|
});
|
|
5444
5449
|
} catch (decisionErr) {
|
|
5445
|
-
logger$
|
|
5450
|
+
logger$17.warn({
|
|
5446
5451
|
err: decisionErr,
|
|
5447
5452
|
storyKey
|
|
5448
5453
|
}, "Failed to write story-metrics decision (best-effort)");
|
|
5449
5454
|
}
|
|
5450
5455
|
} catch (err) {
|
|
5451
|
-
logger$
|
|
5456
|
+
logger$17.warn({
|
|
5452
5457
|
err,
|
|
5453
5458
|
storyKey
|
|
5454
5459
|
}, "Failed to write story metrics (best-effort)");
|
|
@@ -5484,7 +5489,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5484
5489
|
token_usage_json: serialized
|
|
5485
5490
|
});
|
|
5486
5491
|
} catch (err) {
|
|
5487
|
-
logger$
|
|
5492
|
+
logger$17.warn("Failed to persist orchestrator state", { err });
|
|
5488
5493
|
}
|
|
5489
5494
|
}
|
|
5490
5495
|
function recordProgress() {
|
|
@@ -5514,7 +5519,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5514
5519
|
if (_stalledStories.has(key)) continue;
|
|
5515
5520
|
_stalledStories.add(key);
|
|
5516
5521
|
_storiesWithStall.add(key);
|
|
5517
|
-
logger$
|
|
5522
|
+
logger$17.warn({
|
|
5518
5523
|
storyKey: key,
|
|
5519
5524
|
phase: s.phase,
|
|
5520
5525
|
elapsedMs: elapsed
|
|
@@ -5551,7 +5556,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5551
5556
|
* exhausted retries the story is ESCALATED.
|
|
5552
5557
|
*/
|
|
5553
5558
|
async function processStory(storyKey) {
|
|
5554
|
-
logger$
|
|
5559
|
+
logger$17.info("Processing story", { storyKey });
|
|
5555
5560
|
await waitIfPaused();
|
|
5556
5561
|
if (_state !== "RUNNING") return;
|
|
5557
5562
|
startPhase(storyKey, "create-story");
|
|
@@ -5566,7 +5571,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5566
5571
|
const match = files.find((f) => f.startsWith(`${storyKey}-`) && f.endsWith(".md"));
|
|
5567
5572
|
if (match) {
|
|
5568
5573
|
storyFilePath = join$1(artifactsDir, match);
|
|
5569
|
-
logger$
|
|
5574
|
+
logger$17.info({
|
|
5570
5575
|
storyKey,
|
|
5571
5576
|
storyFilePath
|
|
5572
5577
|
}, "Found existing story file — skipping create-story");
|
|
@@ -5667,7 +5672,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5667
5672
|
try {
|
|
5668
5673
|
storyContentForAnalysis = await readFile$1(storyFilePath ?? "", "utf-8");
|
|
5669
5674
|
} catch (err) {
|
|
5670
|
-
logger$
|
|
5675
|
+
logger$17.error({
|
|
5671
5676
|
storyKey,
|
|
5672
5677
|
storyFilePath,
|
|
5673
5678
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -5675,7 +5680,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5675
5680
|
}
|
|
5676
5681
|
const analysis = analyzeStoryComplexity(storyContentForAnalysis);
|
|
5677
5682
|
const batches = planTaskBatches(analysis);
|
|
5678
|
-
logger$
|
|
5683
|
+
logger$17.info({
|
|
5679
5684
|
storyKey,
|
|
5680
5685
|
estimatedScope: analysis.estimatedScope,
|
|
5681
5686
|
batchCount: batches.length,
|
|
@@ -5693,7 +5698,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5693
5698
|
if (_state !== "RUNNING") break;
|
|
5694
5699
|
const taskScope = batch.taskIds.map((id, i) => `T${id}: ${batch.taskTitles[i] ?? ""}`).join("\n");
|
|
5695
5700
|
const priorFiles = allFilesModified.size > 0 ? Array.from(allFilesModified) : void 0;
|
|
5696
|
-
logger$
|
|
5701
|
+
logger$17.info({
|
|
5697
5702
|
storyKey,
|
|
5698
5703
|
batchIndex: batch.batchIndex,
|
|
5699
5704
|
taskCount: batch.taskIds.length
|
|
@@ -5717,7 +5722,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5717
5722
|
});
|
|
5718
5723
|
} catch (batchErr) {
|
|
5719
5724
|
const errMsg = batchErr instanceof Error ? batchErr.message : String(batchErr);
|
|
5720
|
-
logger$
|
|
5725
|
+
logger$17.warn({
|
|
5721
5726
|
storyKey,
|
|
5722
5727
|
batchIndex: batch.batchIndex,
|
|
5723
5728
|
error: errMsg
|
|
@@ -5737,7 +5742,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5737
5742
|
filesModified: batchFilesModified,
|
|
5738
5743
|
result: batchResult.result === "success" ? "success" : "failed"
|
|
5739
5744
|
};
|
|
5740
|
-
logger$
|
|
5745
|
+
logger$17.info(batchMetrics, "Batch dev-story metrics");
|
|
5741
5746
|
for (const f of batchFilesModified) allFilesModified.add(f);
|
|
5742
5747
|
if (batchFilesModified.length > 0) batchFileGroups.push({
|
|
5743
5748
|
batchIndex: batch.batchIndex,
|
|
@@ -5759,13 +5764,13 @@ function createImplementationOrchestrator(deps) {
|
|
|
5759
5764
|
})
|
|
5760
5765
|
});
|
|
5761
5766
|
} catch (tokenErr) {
|
|
5762
|
-
logger$
|
|
5767
|
+
logger$17.warn({
|
|
5763
5768
|
storyKey,
|
|
5764
5769
|
batchIndex: batch.batchIndex,
|
|
5765
5770
|
err: tokenErr
|
|
5766
5771
|
}, "Failed to record batch token usage");
|
|
5767
5772
|
}
|
|
5768
|
-
if (batchResult.result === "failed") logger$
|
|
5773
|
+
if (batchResult.result === "failed") logger$17.warn({
|
|
5769
5774
|
storyKey,
|
|
5770
5775
|
batchIndex: batch.batchIndex,
|
|
5771
5776
|
error: batchResult.error
|
|
@@ -5798,7 +5803,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5798
5803
|
result: devResult
|
|
5799
5804
|
});
|
|
5800
5805
|
persistState();
|
|
5801
|
-
if (devResult.result === "failed") logger$
|
|
5806
|
+
if (devResult.result === "failed") logger$17.warn("Dev-story reported failure, proceeding to code review", {
|
|
5802
5807
|
storyKey,
|
|
5803
5808
|
error: devResult.error,
|
|
5804
5809
|
filesModified: devFilesModified.length
|
|
@@ -5855,7 +5860,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5855
5860
|
"NEEDS_MAJOR_REWORK": 2
|
|
5856
5861
|
};
|
|
5857
5862
|
for (const group of batchFileGroups) {
|
|
5858
|
-
logger$
|
|
5863
|
+
logger$17.info({
|
|
5859
5864
|
storyKey,
|
|
5860
5865
|
batchIndex: group.batchIndex,
|
|
5861
5866
|
fileCount: group.files.length
|
|
@@ -5892,7 +5897,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5892
5897
|
rawOutput: lastRawOutput,
|
|
5893
5898
|
tokenUsage: aggregateTokens
|
|
5894
5899
|
};
|
|
5895
|
-
logger$
|
|
5900
|
+
logger$17.info({
|
|
5896
5901
|
storyKey,
|
|
5897
5902
|
batchCount: batchFileGroups.length,
|
|
5898
5903
|
verdict: worstVerdict,
|
|
@@ -5918,7 +5923,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5918
5923
|
const isPhantomReview = reviewResult.verdict !== "SHIP_IT" && (reviewResult.issue_list === void 0 || reviewResult.issue_list.length === 0) && reviewResult.error !== void 0;
|
|
5919
5924
|
if (isPhantomReview && !timeoutRetried) {
|
|
5920
5925
|
timeoutRetried = true;
|
|
5921
|
-
logger$
|
|
5926
|
+
logger$17.warn({
|
|
5922
5927
|
storyKey,
|
|
5923
5928
|
reviewCycles,
|
|
5924
5929
|
error: reviewResult.error
|
|
@@ -5928,7 +5933,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5928
5933
|
verdict = reviewResult.verdict;
|
|
5929
5934
|
issueList = reviewResult.issue_list ?? [];
|
|
5930
5935
|
if (verdict === "NEEDS_MAJOR_REWORK" && reviewCycles > 0 && previousIssueList.length > 0 && issueList.length < previousIssueList.length) {
|
|
5931
|
-
logger$
|
|
5936
|
+
logger$17.info({
|
|
5932
5937
|
storyKey,
|
|
5933
5938
|
originalVerdict: verdict,
|
|
5934
5939
|
issuesBefore: previousIssueList.length,
|
|
@@ -5964,7 +5969,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5964
5969
|
if (_decomposition !== void 0) parts.push(`decomposed: ${_decomposition.batchCount} batches`);
|
|
5965
5970
|
parts.push(`${fileCount} files`);
|
|
5966
5971
|
parts.push(`${totalTokensK} tokens`);
|
|
5967
|
-
logger$
|
|
5972
|
+
logger$17.info({
|
|
5968
5973
|
storyKey,
|
|
5969
5974
|
verdict,
|
|
5970
5975
|
agentVerdict: reviewResult.agentVerdict
|
|
@@ -6022,7 +6027,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6022
6027
|
persistState();
|
|
6023
6028
|
return;
|
|
6024
6029
|
}
|
|
6025
|
-
logger$
|
|
6030
|
+
logger$17.info({
|
|
6026
6031
|
storyKey,
|
|
6027
6032
|
reviewCycles: finalReviewCycles,
|
|
6028
6033
|
issueCount: issueList.length
|
|
@@ -6072,7 +6077,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6072
6077
|
fixPrompt = assembled.prompt;
|
|
6073
6078
|
} catch {
|
|
6074
6079
|
fixPrompt = `Fix story ${storyKey}: verdict=${verdict}, minor fixes needed`;
|
|
6075
|
-
logger$
|
|
6080
|
+
logger$17.warn("Failed to assemble auto-approve fix prompt, using fallback", { storyKey });
|
|
6076
6081
|
}
|
|
6077
6082
|
const handle = dispatcher.dispatch({
|
|
6078
6083
|
prompt: fixPrompt,
|
|
@@ -6089,9 +6094,9 @@ function createImplementationOrchestrator(deps) {
|
|
|
6089
6094
|
output: fixResult.tokenEstimate.output
|
|
6090
6095
|
} : void 0 }
|
|
6091
6096
|
});
|
|
6092
|
-
if (fixResult.status === "timeout") logger$
|
|
6097
|
+
if (fixResult.status === "timeout") logger$17.warn("Auto-approve fix timed out — approving anyway (issues were minor)", { storyKey });
|
|
6093
6098
|
} catch (err) {
|
|
6094
|
-
logger$
|
|
6099
|
+
logger$17.warn("Auto-approve fix dispatch failed — approving anyway (issues were minor)", {
|
|
6095
6100
|
storyKey,
|
|
6096
6101
|
err
|
|
6097
6102
|
});
|
|
@@ -6163,7 +6168,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6163
6168
|
fixPrompt = assembled.prompt;
|
|
6164
6169
|
} catch {
|
|
6165
6170
|
fixPrompt = `Fix story ${storyKey}: verdict=${verdict}, taskType=${taskType}`;
|
|
6166
|
-
logger$
|
|
6171
|
+
logger$17.warn("Failed to assemble fix prompt, using fallback", {
|
|
6167
6172
|
storyKey,
|
|
6168
6173
|
taskType
|
|
6169
6174
|
});
|
|
@@ -6186,7 +6191,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6186
6191
|
} : void 0 }
|
|
6187
6192
|
});
|
|
6188
6193
|
if (fixResult.status === "timeout") {
|
|
6189
|
-
logger$
|
|
6194
|
+
logger$17.warn("Fix dispatch timed out — escalating story", {
|
|
6190
6195
|
storyKey,
|
|
6191
6196
|
taskType
|
|
6192
6197
|
});
|
|
@@ -6206,13 +6211,13 @@ function createImplementationOrchestrator(deps) {
|
|
|
6206
6211
|
persistState();
|
|
6207
6212
|
return;
|
|
6208
6213
|
}
|
|
6209
|
-
if (fixResult.status === "failed") logger$
|
|
6214
|
+
if (fixResult.status === "failed") logger$17.warn("Fix dispatch failed", {
|
|
6210
6215
|
storyKey,
|
|
6211
6216
|
taskType,
|
|
6212
6217
|
exitCode: fixResult.exitCode
|
|
6213
6218
|
});
|
|
6214
6219
|
} catch (err) {
|
|
6215
|
-
logger$
|
|
6220
|
+
logger$17.warn("Fix dispatch failed, continuing to next review", {
|
|
6216
6221
|
storyKey,
|
|
6217
6222
|
taskType,
|
|
6218
6223
|
err
|
|
@@ -6266,11 +6271,11 @@ function createImplementationOrchestrator(deps) {
|
|
|
6266
6271
|
}
|
|
6267
6272
|
async function run(storyKeys) {
|
|
6268
6273
|
if (_state === "RUNNING" || _state === "PAUSED") {
|
|
6269
|
-
logger$
|
|
6274
|
+
logger$17.warn("run() called while orchestrator is already running or paused — ignoring", { state: _state });
|
|
6270
6275
|
return getStatus();
|
|
6271
6276
|
}
|
|
6272
6277
|
if (_state === "COMPLETE") {
|
|
6273
|
-
logger$
|
|
6278
|
+
logger$17.warn("run() called on a COMPLETE orchestrator — ignoring", { state: _state });
|
|
6274
6279
|
return getStatus();
|
|
6275
6280
|
}
|
|
6276
6281
|
_state = "RUNNING";
|
|
@@ -6288,13 +6293,13 @@ function createImplementationOrchestrator(deps) {
|
|
|
6288
6293
|
if (config.enableHeartbeat) startHeartbeat();
|
|
6289
6294
|
if (projectRoot !== void 0) {
|
|
6290
6295
|
const seedResult = seedMethodologyContext(db, projectRoot);
|
|
6291
|
-
if (seedResult.decisionsCreated > 0) logger$
|
|
6296
|
+
if (seedResult.decisionsCreated > 0) logger$17.info({
|
|
6292
6297
|
decisionsCreated: seedResult.decisionsCreated,
|
|
6293
6298
|
skippedCategories: seedResult.skippedCategories
|
|
6294
6299
|
}, "Methodology context seeded from planning artifacts");
|
|
6295
6300
|
}
|
|
6296
6301
|
const groups = detectConflictGroups(storyKeys);
|
|
6297
|
-
logger$
|
|
6302
|
+
logger$17.info("Orchestrator starting", {
|
|
6298
6303
|
storyCount: storyKeys.length,
|
|
6299
6304
|
groupCount: groups.length,
|
|
6300
6305
|
maxConcurrency: config.maxConcurrency
|
|
@@ -6306,7 +6311,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6306
6311
|
_state = "FAILED";
|
|
6307
6312
|
_completedAt = new Date().toISOString();
|
|
6308
6313
|
persistState();
|
|
6309
|
-
logger$
|
|
6314
|
+
logger$17.error("Orchestrator failed with unhandled error", { err });
|
|
6310
6315
|
return getStatus();
|
|
6311
6316
|
}
|
|
6312
6317
|
stopHeartbeat();
|
|
@@ -6333,7 +6338,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6333
6338
|
_pauseGate = createPauseGate();
|
|
6334
6339
|
_state = "PAUSED";
|
|
6335
6340
|
eventBus.emit("orchestrator:paused", {});
|
|
6336
|
-
logger$
|
|
6341
|
+
logger$17.info("Orchestrator paused");
|
|
6337
6342
|
}
|
|
6338
6343
|
function resume() {
|
|
6339
6344
|
if (_state !== "PAUSED") return;
|
|
@@ -6344,7 +6349,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6344
6349
|
}
|
|
6345
6350
|
_state = "RUNNING";
|
|
6346
6351
|
eventBus.emit("orchestrator:resumed", {});
|
|
6347
|
-
logger$
|
|
6352
|
+
logger$17.info("Orchestrator resumed");
|
|
6348
6353
|
}
|
|
6349
6354
|
return {
|
|
6350
6355
|
run,
|
|
@@ -7019,7 +7024,7 @@ const CritiqueOutputSchema = z.object({
|
|
|
7019
7024
|
|
|
7020
7025
|
//#endregion
|
|
7021
7026
|
//#region src/modules/phase-orchestrator/critique-loop.ts
|
|
7022
|
-
const logger$
|
|
7027
|
+
const logger$6 = createLogger("critique-loop");
|
|
7023
7028
|
/**
|
|
7024
7029
|
* Maps a phase name to the critique prompt template name.
|
|
7025
7030
|
* Falls back to `critique-${phase}` for unknown phases.
|
|
@@ -7073,7 +7078,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7073
7078
|
critiquePrompt = critiqueTemplate.replace("{{artifact_content}}", currentArtifact).replace("{{project_context}}", projectContext);
|
|
7074
7079
|
} catch (err) {
|
|
7075
7080
|
const message = err instanceof Error ? err.message : String(err);
|
|
7076
|
-
logger$
|
|
7081
|
+
logger$6.warn({
|
|
7077
7082
|
phaseId,
|
|
7078
7083
|
promptName: critiquePromptName,
|
|
7079
7084
|
err: message
|
|
@@ -7101,7 +7106,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7101
7106
|
critiqueTokens.output += result.tokenEstimate.output;
|
|
7102
7107
|
if (result.status !== "completed" || result.parsed === null) {
|
|
7103
7108
|
const errMsg = result.parseError ?? `Critique dispatch ended with status '${result.status}'`;
|
|
7104
|
-
logger$
|
|
7109
|
+
logger$6.warn({
|
|
7105
7110
|
phaseId,
|
|
7106
7111
|
iteration: i + 1,
|
|
7107
7112
|
err: errMsg
|
|
@@ -7120,7 +7125,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7120
7125
|
lastCritiqueOutput = critiqueOutput;
|
|
7121
7126
|
} catch (err) {
|
|
7122
7127
|
const message = err instanceof Error ? err.message : String(err);
|
|
7123
|
-
logger$
|
|
7128
|
+
logger$6.warn({
|
|
7124
7129
|
phaseId,
|
|
7125
7130
|
iteration: i + 1,
|
|
7126
7131
|
err: message
|
|
@@ -7160,14 +7165,14 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7160
7165
|
});
|
|
7161
7166
|
} catch (err) {
|
|
7162
7167
|
const message = err instanceof Error ? err.message : String(err);
|
|
7163
|
-
logger$
|
|
7168
|
+
logger$6.warn({
|
|
7164
7169
|
phaseId,
|
|
7165
7170
|
iteration: i + 1,
|
|
7166
7171
|
err: message
|
|
7167
7172
|
}, "Critique loop: failed to store critique decision — continuing");
|
|
7168
7173
|
}
|
|
7169
7174
|
if (critiqueOutput.verdict === "pass") {
|
|
7170
|
-
logger$
|
|
7175
|
+
logger$6.info({
|
|
7171
7176
|
phaseId,
|
|
7172
7177
|
iteration: i + 1
|
|
7173
7178
|
}, "Critique loop: artifact passed critique — loop complete");
|
|
@@ -7180,7 +7185,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7180
7185
|
totalMs: Date.now() - startMs
|
|
7181
7186
|
};
|
|
7182
7187
|
}
|
|
7183
|
-
logger$
|
|
7188
|
+
logger$6.info({
|
|
7184
7189
|
phaseId,
|
|
7185
7190
|
iteration: i + 1,
|
|
7186
7191
|
issueCount: critiqueOutput.issue_count
|
|
@@ -7193,7 +7198,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7193
7198
|
refinePrompt = refineTemplate.replace("{{original_artifact}}", currentArtifact).replace("{{critique_issues}}", issuesText).replace("{{phase_context}}", phaseContext);
|
|
7194
7199
|
} catch (err) {
|
|
7195
7200
|
const message = err instanceof Error ? err.message : String(err);
|
|
7196
|
-
logger$
|
|
7201
|
+
logger$6.warn({
|
|
7197
7202
|
phaseId,
|
|
7198
7203
|
iteration: i + 1,
|
|
7199
7204
|
err: message
|
|
@@ -7214,7 +7219,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7214
7219
|
const originalLength = currentArtifact.length;
|
|
7215
7220
|
const refinedLength = refineResult.output.length;
|
|
7216
7221
|
const delta = refinedLength - originalLength;
|
|
7217
|
-
logger$
|
|
7222
|
+
logger$6.info({
|
|
7218
7223
|
phaseId,
|
|
7219
7224
|
iteration: i + 1,
|
|
7220
7225
|
originalLength,
|
|
@@ -7223,7 +7228,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7223
7228
|
}, "Critique loop: refinement complete");
|
|
7224
7229
|
currentArtifact = refineResult.output;
|
|
7225
7230
|
} else {
|
|
7226
|
-
logger$
|
|
7231
|
+
logger$6.warn({
|
|
7227
7232
|
phaseId,
|
|
7228
7233
|
iteration: i + 1,
|
|
7229
7234
|
status: refineResult.status
|
|
@@ -7232,7 +7237,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7232
7237
|
}
|
|
7233
7238
|
} catch (err) {
|
|
7234
7239
|
const message = err instanceof Error ? err.message : String(err);
|
|
7235
|
-
logger$
|
|
7240
|
+
logger$6.warn({
|
|
7236
7241
|
phaseId,
|
|
7237
7242
|
iteration: i + 1,
|
|
7238
7243
|
err: message
|
|
@@ -7243,12 +7248,12 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7243
7248
|
}
|
|
7244
7249
|
const remainingIssues = lastCritiqueOutput?.issues ?? [];
|
|
7245
7250
|
if (remainingIssues.length > 0) {
|
|
7246
|
-
logger$
|
|
7251
|
+
logger$6.warn({
|
|
7247
7252
|
phaseId,
|
|
7248
7253
|
maxIterations,
|
|
7249
7254
|
issueCount: remainingIssues.length
|
|
7250
7255
|
}, "Critique loop: max iterations reached with unresolved issues");
|
|
7251
|
-
for (const issue of remainingIssues) logger$
|
|
7256
|
+
for (const issue of remainingIssues) logger$6.warn({
|
|
7252
7257
|
phaseId,
|
|
7253
7258
|
severity: issue.severity,
|
|
7254
7259
|
category: issue.category,
|
|
@@ -7267,7 +7272,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7267
7272
|
|
|
7268
7273
|
//#endregion
|
|
7269
7274
|
//#region src/modules/phase-orchestrator/elicitation-selector.ts
|
|
7270
|
-
const logger$
|
|
7275
|
+
const logger$5 = createLogger("elicitation-selector");
|
|
7271
7276
|
/**
|
|
7272
7277
|
* Affinity scores (0.0–1.0) for each category per content type.
|
|
7273
7278
|
*
|
|
@@ -7389,10 +7394,10 @@ function loadElicitationMethods() {
|
|
|
7389
7394
|
try {
|
|
7390
7395
|
const content = readFileSync(csvPath, "utf-8");
|
|
7391
7396
|
const methods = parseMethodsCsv(content);
|
|
7392
|
-
logger$
|
|
7397
|
+
logger$5.debug({ count: methods.length }, "Loaded elicitation methods");
|
|
7393
7398
|
return methods;
|
|
7394
7399
|
} catch (err) {
|
|
7395
|
-
logger$
|
|
7400
|
+
logger$5.warn({
|
|
7396
7401
|
csvPath,
|
|
7397
7402
|
err
|
|
7398
7403
|
}, "Failed to load elicitation methods CSV");
|
|
@@ -7712,7 +7717,7 @@ const ElicitationOutputSchema = z.object({
|
|
|
7712
7717
|
|
|
7713
7718
|
//#endregion
|
|
7714
7719
|
//#region src/modules/phase-orchestrator/step-runner.ts
|
|
7715
|
-
const logger$
|
|
7720
|
+
const logger$4 = createLogger("step-runner");
|
|
7716
7721
|
/**
|
|
7717
7722
|
* Format an array of decision records into a markdown section for injection.
|
|
7718
7723
|
*
|
|
@@ -7819,7 +7824,7 @@ async function runSteps(steps, deps, runId, phase, params) {
|
|
|
7819
7824
|
if (estimatedTokens > budgetTokens) {
|
|
7820
7825
|
const decisionRefs = step.context.filter((ref) => ref.source.startsWith("decision:"));
|
|
7821
7826
|
if (decisionRefs.length > 0) {
|
|
7822
|
-
logger$
|
|
7827
|
+
logger$4.warn({
|
|
7823
7828
|
step: step.name,
|
|
7824
7829
|
estimatedTokens,
|
|
7825
7830
|
budgetTokens
|
|
@@ -7846,7 +7851,7 @@ async function runSteps(steps, deps, runId, phase, params) {
|
|
|
7846
7851
|
}
|
|
7847
7852
|
prompt = summarizedPrompt;
|
|
7848
7853
|
estimatedTokens = Math.ceil(prompt.length / 4);
|
|
7849
|
-
if (estimatedTokens <= budgetTokens) logger$
|
|
7854
|
+
if (estimatedTokens <= budgetTokens) logger$4.info({
|
|
7850
7855
|
step: step.name,
|
|
7851
7856
|
estimatedTokens,
|
|
7852
7857
|
budgetTokens
|
|
@@ -8027,7 +8032,7 @@ async function runSteps(steps, deps, runId, phase, params) {
|
|
|
8027
8032
|
const critiqueResult = await runCritiqueLoop(artifactContent, phase, runId, phase, deps);
|
|
8028
8033
|
totalInput += critiqueResult.critiqueTokens.input + critiqueResult.refinementTokens.input;
|
|
8029
8034
|
totalOutput += critiqueResult.critiqueTokens.output + critiqueResult.refinementTokens.output;
|
|
8030
|
-
logger$
|
|
8035
|
+
logger$4.info({
|
|
8031
8036
|
step: step.name,
|
|
8032
8037
|
verdict: critiqueResult.verdict,
|
|
8033
8038
|
iterations: critiqueResult.iterations,
|
|
@@ -8035,7 +8040,7 @@ async function runSteps(steps, deps, runId, phase, params) {
|
|
|
8035
8040
|
}, "Step critique loop complete");
|
|
8036
8041
|
} catch (critiqueErr) {
|
|
8037
8042
|
const critiqueMsg = critiqueErr instanceof Error ? critiqueErr.message : String(critiqueErr);
|
|
8038
|
-
logger$
|
|
8043
|
+
logger$4.warn({
|
|
8039
8044
|
step: step.name,
|
|
8040
8045
|
err: critiqueMsg
|
|
8041
8046
|
}, "Step critique loop threw an error — continuing without critique");
|
|
@@ -8045,7 +8050,7 @@ async function runSteps(steps, deps, runId, phase, params) {
|
|
|
8045
8050
|
const contentType = deriveContentType(phase, step.name);
|
|
8046
8051
|
const selectedMethods = selectMethods({ content_type: contentType }, usedElicitationMethods);
|
|
8047
8052
|
if (selectedMethods.length > 0) {
|
|
8048
|
-
logger$
|
|
8053
|
+
logger$4.info({
|
|
8049
8054
|
step: step.name,
|
|
8050
8055
|
methods: selectedMethods.map((m) => m.name),
|
|
8051
8056
|
contentType
|
|
@@ -8084,13 +8089,13 @@ async function runSteps(steps, deps, runId, phase, params) {
|
|
|
8084
8089
|
key: `${phase}-round-${roundIndex}-insights`,
|
|
8085
8090
|
value: elicitParsed.insights
|
|
8086
8091
|
});
|
|
8087
|
-
logger$
|
|
8092
|
+
logger$4.info({
|
|
8088
8093
|
step: step.name,
|
|
8089
8094
|
method: method.name,
|
|
8090
8095
|
roundIndex
|
|
8091
8096
|
}, "Elicitation insights stored in decision store");
|
|
8092
8097
|
}
|
|
8093
|
-
} else logger$
|
|
8098
|
+
} else logger$4.warn({
|
|
8094
8099
|
step: step.name,
|
|
8095
8100
|
method: method.name,
|
|
8096
8101
|
status: elicitResult.status
|
|
@@ -8106,7 +8111,7 @@ async function runSteps(steps, deps, runId, phase, params) {
|
|
|
8106
8111
|
}
|
|
8107
8112
|
} catch (elicitErr) {
|
|
8108
8113
|
const elicitMsg = elicitErr instanceof Error ? elicitErr.message : String(elicitErr);
|
|
8109
|
-
logger$
|
|
8114
|
+
logger$4.warn({
|
|
8110
8115
|
step: step.name,
|
|
8111
8116
|
err: elicitMsg
|
|
8112
8117
|
}, "Step elicitation threw an error — continuing without elicitation");
|
|
@@ -8448,7 +8453,7 @@ async function runAnalysisPhase(deps, params) {
|
|
|
8448
8453
|
|
|
8449
8454
|
//#endregion
|
|
8450
8455
|
//#region src/modules/phase-orchestrator/phases/planning.ts
|
|
8451
|
-
const logger$
|
|
8456
|
+
const logger$3 = createLogger("planning-phase");
|
|
8452
8457
|
/** Maximum total prompt length in tokens (3,500 tokens × 4 chars/token = 14,000 chars) */
|
|
8453
8458
|
const MAX_PROMPT_TOKENS = 3500;
|
|
8454
8459
|
const MAX_PROMPT_CHARS = MAX_PROMPT_TOKENS * 4;
|
|
@@ -8675,7 +8680,7 @@ async function runPlanningMultiStep(deps, params) {
|
|
|
8675
8680
|
const techConstraintDecisions = allAnalysisDecisions.filter((d) => d.category === "technology-constraints");
|
|
8676
8681
|
const violation = detectTechStackViolation(techStack, techConstraintDecisions);
|
|
8677
8682
|
if (violation) {
|
|
8678
|
-
logger$
|
|
8683
|
+
logger$3.warn({ violation }, "Tech stack constraint violation detected — retrying step 3 with correction");
|
|
8679
8684
|
const correctionPrefix = `CRITICAL CORRECTION: Your previous output was rejected because it violates the stated technology constraints.\n\nViolation: ${violation}\n\nYou MUST NOT use TypeScript, JavaScript, or Node.js for ANY backend service. Choose from Go, Kotlin/JVM, or Rust as stated in the technology constraints.\n\nRe-generate your output with a compliant tech stack. Everything else (NFRs, domain model, out-of-scope) can remain the same.\n\n---\n\n`;
|
|
8680
8685
|
const step3Template = await deps.pack.getPrompt("planning-step-3-nfrs");
|
|
8681
8686
|
const stepOutputs = new Map();
|
|
@@ -8702,10 +8707,10 @@ async function runPlanningMultiStep(deps, params) {
|
|
|
8702
8707
|
const retryTechStack = retryParsed.tech_stack;
|
|
8703
8708
|
const retryViolation = retryTechStack ? detectTechStackViolation(retryTechStack, techConstraintDecisions) : null;
|
|
8704
8709
|
if (!retryViolation) {
|
|
8705
|
-
logger$
|
|
8710
|
+
logger$3.info("Retry produced compliant tech stack — using corrected output");
|
|
8706
8711
|
nfrsOutput = retryParsed;
|
|
8707
|
-
} else logger$
|
|
8708
|
-
} else logger$
|
|
8712
|
+
} else logger$3.warn({ retryViolation }, "Retry still violates constraints — using original output");
|
|
8713
|
+
} else logger$3.warn("Retry dispatch failed — using original output");
|
|
8709
8714
|
}
|
|
8710
8715
|
}
|
|
8711
8716
|
const frs = frsOutput.functional_requirements;
|
|
@@ -8992,7 +8997,7 @@ const ReadinessOutputSchema = z.object({
|
|
|
8992
8997
|
|
|
8993
8998
|
//#endregion
|
|
8994
8999
|
//#region src/modules/phase-orchestrator/phases/solutioning.ts
|
|
8995
|
-
const logger$
|
|
9000
|
+
const logger$2 = createLogger("solutioning");
|
|
8996
9001
|
/** Base token budget for architecture generation (covers template + requirements) */
|
|
8997
9002
|
const BASE_ARCH_PROMPT_TOKENS = 3e3;
|
|
8998
9003
|
/** Base token budget for story generation (covers template + requirements + architecture) */
|
|
@@ -9401,7 +9406,7 @@ async function runReadinessCheck(deps, runId) {
|
|
|
9401
9406
|
input: tokenEstimate.input,
|
|
9402
9407
|
output: tokenEstimate.output
|
|
9403
9408
|
};
|
|
9404
|
-
logger$
|
|
9409
|
+
logger$2.info({
|
|
9405
9410
|
runId,
|
|
9406
9411
|
durationMs: dispatchResult.durationMs,
|
|
9407
9412
|
tokens: tokenEstimate
|
|
@@ -9662,7 +9667,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9662
9667
|
let archResult;
|
|
9663
9668
|
if (existingArchArtifact) {
|
|
9664
9669
|
const existingDecisions = getDecisionsByPhaseForRun(deps.db, params.runId, "solutioning").filter((d) => d.category === "architecture");
|
|
9665
|
-
logger$
|
|
9670
|
+
logger$2.info({
|
|
9666
9671
|
runId: params.runId,
|
|
9667
9672
|
artifactId: existingArchArtifact.id,
|
|
9668
9673
|
decisionCount: existingDecisions.length
|
|
@@ -9693,7 +9698,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9693
9698
|
output: totalOutput
|
|
9694
9699
|
}
|
|
9695
9700
|
};
|
|
9696
|
-
logger$
|
|
9701
|
+
logger$2.info({
|
|
9697
9702
|
runId: params.runId,
|
|
9698
9703
|
decisionCount: archResult.decisions.length,
|
|
9699
9704
|
mode: hasSteps ? "multi-step" : "single-dispatch"
|
|
@@ -9715,7 +9720,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9715
9720
|
totalInput += readinessResult.tokenUsage.input;
|
|
9716
9721
|
totalOutput += readinessResult.tokenUsage.output;
|
|
9717
9722
|
if (readinessResult.verdict === "error") {
|
|
9718
|
-
logger$
|
|
9723
|
+
logger$2.error({
|
|
9719
9724
|
runId: params.runId,
|
|
9720
9725
|
error: readinessResult.error
|
|
9721
9726
|
}, "Readiness check agent failed");
|
|
@@ -9731,7 +9736,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9731
9736
|
}
|
|
9732
9737
|
};
|
|
9733
9738
|
}
|
|
9734
|
-
logger$
|
|
9739
|
+
logger$2.info({
|
|
9735
9740
|
runId: params.runId,
|
|
9736
9741
|
verdict: readinessResult.verdict,
|
|
9737
9742
|
coverageScore: readinessResult.coverageScore,
|
|
@@ -9747,7 +9752,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9747
9752
|
key: `finding-${i + 1}`,
|
|
9748
9753
|
value: JSON.stringify(finding)
|
|
9749
9754
|
});
|
|
9750
|
-
logger$
|
|
9755
|
+
logger$2.error({
|
|
9751
9756
|
runId: params.runId,
|
|
9752
9757
|
verdict: "NOT_READY",
|
|
9753
9758
|
coverageScore: readinessResult.coverageScore,
|
|
@@ -9813,7 +9818,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9813
9818
|
"",
|
|
9814
9819
|
"Please generate additional or revised stories to specifically address each blocker above."
|
|
9815
9820
|
].join("\n");
|
|
9816
|
-
logger$
|
|
9821
|
+
logger$2.info({
|
|
9817
9822
|
runId: params.runId,
|
|
9818
9823
|
blockerCount: blockers.length
|
|
9819
9824
|
}, "Readiness NEEDS_WORK with blockers — retrying story generation with gap analysis");
|
|
@@ -9852,7 +9857,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9852
9857
|
};
|
|
9853
9858
|
if (retryReadiness.verdict === "NOT_READY" || retryReadiness.verdict === "NEEDS_WORK") {
|
|
9854
9859
|
const retryBlockers = retryReadiness.findings.filter((f) => f.severity === "blocker");
|
|
9855
|
-
logger$
|
|
9860
|
+
logger$2.error({
|
|
9856
9861
|
runId: params.runId,
|
|
9857
9862
|
verdict: retryReadiness.verdict,
|
|
9858
9863
|
retryBlockers: retryBlockers.length
|
|
@@ -9876,7 +9881,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9876
9881
|
}
|
|
9877
9882
|
const retryStories = retryResult.epics.reduce((sum, epic) => sum + epic.stories.length, 0);
|
|
9878
9883
|
const minorFindings$1 = retryReadiness.findings.filter((f) => f.severity === "minor");
|
|
9879
|
-
if (minorFindings$1.length > 0) logger$
|
|
9884
|
+
if (minorFindings$1.length > 0) logger$2.warn({
|
|
9880
9885
|
runId: params.runId,
|
|
9881
9886
|
minorFindings: minorFindings$1
|
|
9882
9887
|
}, "Readiness READY with minor findings after retry");
|
|
@@ -9905,7 +9910,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9905
9910
|
};
|
|
9906
9911
|
}
|
|
9907
9912
|
const majorFindings = readinessResult.findings.filter((f) => f.severity === "major");
|
|
9908
|
-
logger$
|
|
9913
|
+
logger$2.warn({
|
|
9909
9914
|
runId: params.runId,
|
|
9910
9915
|
majorCount: majorFindings.length,
|
|
9911
9916
|
findings: readinessResult.findings
|
|
@@ -9921,7 +9926,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9921
9926
|
const minorFindings = readinessResult.findings.filter((f) => f.severity === "minor");
|
|
9922
9927
|
if (minorFindings.length > 0) {
|
|
9923
9928
|
const verdictLabel = readinessResult.verdict === "READY" ? "READY" : "NEEDS_WORK (no blockers)";
|
|
9924
|
-
logger$
|
|
9929
|
+
logger$2.warn({
|
|
9925
9930
|
runId: params.runId,
|
|
9926
9931
|
verdict: readinessResult.verdict,
|
|
9927
9932
|
minorFindings
|
|
@@ -10332,6 +10337,268 @@ async function runResearchPhase(deps, params) {
|
|
|
10332
10337
|
}
|
|
10333
10338
|
}
|
|
10334
10339
|
|
|
10340
|
+
//#endregion
|
|
10341
|
+
//#region src/cli/commands/health.ts
|
|
10342
|
+
const logger$1 = createLogger("health-cmd");
|
|
10343
|
+
/** Default stall threshold in seconds — also used by supervisor default */
|
|
10344
|
+
const DEFAULT_STALL_THRESHOLD_SECONDS = 600;
|
|
10345
|
+
/**
|
|
10346
|
+
* Determine whether a ps output line represents the substrate pipeline orchestrator.
|
|
10347
|
+
* Handles invocation via:
|
|
10348
|
+
* - `substrate run` (globally installed)
|
|
10349
|
+
* - `substrate-ai run`
|
|
10350
|
+
* - `node dist/cli/index.js run` (npm run substrate:dev)
|
|
10351
|
+
* - `npx substrate run`
|
|
10352
|
+
* - any node process whose command contains `run` with `--events` or `--stories`
|
|
10353
|
+
*
|
|
10354
|
+
* When `projectRoot` is provided, additionally checks that the command line
|
|
10355
|
+
* contains that path (via `--project-root` flag or as part of the binary/CWD path).
|
|
10356
|
+
* This ensures multi-project environments match the correct orchestrator.
|
|
10357
|
+
*/
|
|
10358
|
+
function isOrchestratorProcessLine(line, projectRoot) {
|
|
10359
|
+
if (line.includes("grep")) return false;
|
|
10360
|
+
let isOrchestrator = false;
|
|
10361
|
+
if (line.includes("substrate run")) isOrchestrator = true;
|
|
10362
|
+
else if (line.includes("substrate-ai run")) isOrchestrator = true;
|
|
10363
|
+
else if (line.includes("index.js run")) isOrchestrator = true;
|
|
10364
|
+
else if (line.includes("node") && /\srun(\s|$)/.test(line) && (line.includes("--events") || line.includes("--stories"))) isOrchestrator = true;
|
|
10365
|
+
if (!isOrchestrator) return false;
|
|
10366
|
+
if (projectRoot !== void 0) return line.includes(projectRoot);
|
|
10367
|
+
return true;
|
|
10368
|
+
}
|
|
10369
|
+
function inspectProcessTree(opts) {
|
|
10370
|
+
const { projectRoot, execFileSync: execFileSyncOverride } = opts ?? {};
|
|
10371
|
+
const result = {
|
|
10372
|
+
orchestrator_pid: null,
|
|
10373
|
+
child_pids: [],
|
|
10374
|
+
zombies: []
|
|
10375
|
+
};
|
|
10376
|
+
try {
|
|
10377
|
+
let psOutput;
|
|
10378
|
+
if (execFileSyncOverride !== void 0) psOutput = execFileSyncOverride("ps", ["-eo", "pid,ppid,stat,command"], {
|
|
10379
|
+
encoding: "utf-8",
|
|
10380
|
+
timeout: 5e3
|
|
10381
|
+
});
|
|
10382
|
+
else {
|
|
10383
|
+
const { execFileSync } = __require("node:child_process");
|
|
10384
|
+
psOutput = execFileSync("ps", ["-eo", "pid,ppid,stat,command"], {
|
|
10385
|
+
encoding: "utf-8",
|
|
10386
|
+
timeout: 5e3
|
|
10387
|
+
});
|
|
10388
|
+
}
|
|
10389
|
+
const lines = psOutput.split("\n");
|
|
10390
|
+
for (const line of lines) if (isOrchestratorProcessLine(line, projectRoot)) {
|
|
10391
|
+
const match = line.trim().match(/^(\d+)/);
|
|
10392
|
+
if (match) {
|
|
10393
|
+
result.orchestrator_pid = parseInt(match[1], 10);
|
|
10394
|
+
break;
|
|
10395
|
+
}
|
|
10396
|
+
}
|
|
10397
|
+
if (result.orchestrator_pid !== null) for (const line of lines) {
|
|
10398
|
+
const parts = line.trim().split(/\s+/);
|
|
10399
|
+
if (parts.length >= 3) {
|
|
10400
|
+
const pid = parseInt(parts[0], 10);
|
|
10401
|
+
const ppid = parseInt(parts[1], 10);
|
|
10402
|
+
const stat$2 = parts[2];
|
|
10403
|
+
if (ppid === result.orchestrator_pid && pid !== result.orchestrator_pid) {
|
|
10404
|
+
result.child_pids.push(pid);
|
|
10405
|
+
if (stat$2.includes("Z")) result.zombies.push(pid);
|
|
10406
|
+
}
|
|
10407
|
+
}
|
|
10408
|
+
}
|
|
10409
|
+
} catch {}
|
|
10410
|
+
return result;
|
|
10411
|
+
}
|
|
10412
|
+
/**
|
|
10413
|
+
* Collect all descendant PIDs of the given root PIDs by walking the process
|
|
10414
|
+
* tree recursively. This ensures that grandchildren of the orchestrator
|
|
10415
|
+
* (e.g. node subprocesses spawned by `claude -p`) are also killed during
|
|
10416
|
+
* stall recovery, leaving no orphan processes.
|
|
10417
|
+
*
|
|
10418
|
+
* Returns only the descendants — the root PIDs themselves are NOT included.
|
|
10419
|
+
*/
|
|
10420
|
+
function getAllDescendantPids(rootPids, execFileSyncOverride) {
|
|
10421
|
+
if (rootPids.length === 0) return [];
|
|
10422
|
+
try {
|
|
10423
|
+
let psOutput;
|
|
10424
|
+
if (execFileSyncOverride !== void 0) psOutput = execFileSyncOverride("ps", ["-eo", "pid,ppid"], {
|
|
10425
|
+
encoding: "utf-8",
|
|
10426
|
+
timeout: 5e3
|
|
10427
|
+
});
|
|
10428
|
+
else {
|
|
10429
|
+
const { execFileSync } = __require("node:child_process");
|
|
10430
|
+
psOutput = execFileSync("ps", ["-eo", "pid,ppid"], {
|
|
10431
|
+
encoding: "utf-8",
|
|
10432
|
+
timeout: 5e3
|
|
10433
|
+
});
|
|
10434
|
+
}
|
|
10435
|
+
const childrenOf = new Map();
|
|
10436
|
+
for (const line of psOutput.split("\n")) {
|
|
10437
|
+
const parts = line.trim().split(/\s+/);
|
|
10438
|
+
if (parts.length >= 2) {
|
|
10439
|
+
const pid = parseInt(parts[0], 10);
|
|
10440
|
+
const ppid = parseInt(parts[1], 10);
|
|
10441
|
+
if (!isNaN(pid) && !isNaN(ppid) && pid > 0) {
|
|
10442
|
+
if (!childrenOf.has(ppid)) childrenOf.set(ppid, []);
|
|
10443
|
+
childrenOf.get(ppid).push(pid);
|
|
10444
|
+
}
|
|
10445
|
+
}
|
|
10446
|
+
}
|
|
10447
|
+
const descendants = [];
|
|
10448
|
+
const seen = new Set(rootPids);
|
|
10449
|
+
const queue = [...rootPids];
|
|
10450
|
+
while (queue.length > 0) {
|
|
10451
|
+
const current = queue.shift();
|
|
10452
|
+
const children = childrenOf.get(current) ?? [];
|
|
10453
|
+
for (const child of children) if (!seen.has(child)) {
|
|
10454
|
+
seen.add(child);
|
|
10455
|
+
descendants.push(child);
|
|
10456
|
+
queue.push(child);
|
|
10457
|
+
}
|
|
10458
|
+
}
|
|
10459
|
+
return descendants;
|
|
10460
|
+
} catch {
|
|
10461
|
+
return [];
|
|
10462
|
+
}
|
|
10463
|
+
}
|
|
10464
|
+
/**
|
|
10465
|
+
* Fetch pipeline health data as a structured object without any stdout side-effects.
|
|
10466
|
+
* Used by runSupervisorAction to poll health without formatting overhead.
|
|
10467
|
+
*
|
|
10468
|
+
* Returns a NO_PIPELINE_RUNNING health object for all graceful "no data" cases
|
|
10469
|
+
* (missing DB, missing run, terminal run status). Throws only on unexpected errors.
|
|
10470
|
+
*/
|
|
10471
|
+
async function getAutoHealthData(options) {
|
|
10472
|
+
const { runId, projectRoot } = options;
|
|
10473
|
+
const dbRoot = await resolveMainRepoRoot(projectRoot);
|
|
10474
|
+
const dbPath = join(dbRoot, ".substrate", "substrate.db");
|
|
10475
|
+
const NO_PIPELINE = {
|
|
10476
|
+
verdict: "NO_PIPELINE_RUNNING",
|
|
10477
|
+
run_id: null,
|
|
10478
|
+
status: null,
|
|
10479
|
+
current_phase: null,
|
|
10480
|
+
staleness_seconds: 0,
|
|
10481
|
+
last_activity: "",
|
|
10482
|
+
process: {
|
|
10483
|
+
orchestrator_pid: null,
|
|
10484
|
+
child_pids: [],
|
|
10485
|
+
zombies: []
|
|
10486
|
+
},
|
|
10487
|
+
stories: {
|
|
10488
|
+
active: 0,
|
|
10489
|
+
completed: 0,
|
|
10490
|
+
escalated: 0,
|
|
10491
|
+
details: {}
|
|
10492
|
+
}
|
|
10493
|
+
};
|
|
10494
|
+
if (!existsSync(dbPath)) return NO_PIPELINE;
|
|
10495
|
+
const dbWrapper = new DatabaseWrapper(dbPath);
|
|
10496
|
+
try {
|
|
10497
|
+
dbWrapper.open();
|
|
10498
|
+
const db = dbWrapper.db;
|
|
10499
|
+
let run;
|
|
10500
|
+
if (runId !== void 0) run = getPipelineRunById(db, runId);
|
|
10501
|
+
else run = getLatestRun(db);
|
|
10502
|
+
if (run === void 0) return NO_PIPELINE;
|
|
10503
|
+
const updatedAt = parseDbTimestampAsUtc(run.updated_at);
|
|
10504
|
+
const stalenessSeconds = Math.round((Date.now() - updatedAt.getTime()) / 1e3);
|
|
10505
|
+
let storyDetails = {};
|
|
10506
|
+
let active = 0;
|
|
10507
|
+
let completed = 0;
|
|
10508
|
+
let escalated = 0;
|
|
10509
|
+
try {
|
|
10510
|
+
if (run.token_usage_json) {
|
|
10511
|
+
const state = JSON.parse(run.token_usage_json);
|
|
10512
|
+
if (state.stories) for (const [key, s] of Object.entries(state.stories)) {
|
|
10513
|
+
storyDetails[key] = {
|
|
10514
|
+
phase: s.phase,
|
|
10515
|
+
review_cycles: s.reviewCycles
|
|
10516
|
+
};
|
|
10517
|
+
if (s.phase === "COMPLETE") completed++;
|
|
10518
|
+
else if (s.phase === "ESCALATED") escalated++;
|
|
10519
|
+
else if (s.phase !== "PENDING") active++;
|
|
10520
|
+
}
|
|
10521
|
+
}
|
|
10522
|
+
} catch {}
|
|
10523
|
+
const processInfo = inspectProcessTree({ projectRoot });
|
|
10524
|
+
let verdict = "NO_PIPELINE_RUNNING";
|
|
10525
|
+
if (run.status === "running") if (processInfo.orchestrator_pid === null && active === 0 && completed > 0) verdict = "NO_PIPELINE_RUNNING";
|
|
10526
|
+
else if (processInfo.zombies.length > 0) verdict = "STALLED";
|
|
10527
|
+
else if (processInfo.orchestrator_pid !== null && processInfo.child_pids.length > 0 && stalenessSeconds > DEFAULT_STALL_THRESHOLD_SECONDS) verdict = "HEALTHY";
|
|
10528
|
+
else if (stalenessSeconds > DEFAULT_STALL_THRESHOLD_SECONDS) verdict = "STALLED";
|
|
10529
|
+
else if (processInfo.orchestrator_pid !== null && processInfo.child_pids.length === 0 && active > 0) verdict = "STALLED";
|
|
10530
|
+
else verdict = "HEALTHY";
|
|
10531
|
+
else if (run.status === "completed" || run.status === "failed" || run.status === "stopped") verdict = "NO_PIPELINE_RUNNING";
|
|
10532
|
+
return {
|
|
10533
|
+
verdict,
|
|
10534
|
+
run_id: run.id,
|
|
10535
|
+
status: run.status,
|
|
10536
|
+
current_phase: run.current_phase,
|
|
10537
|
+
staleness_seconds: stalenessSeconds,
|
|
10538
|
+
last_activity: run.updated_at,
|
|
10539
|
+
process: processInfo,
|
|
10540
|
+
stories: {
|
|
10541
|
+
active,
|
|
10542
|
+
completed,
|
|
10543
|
+
escalated,
|
|
10544
|
+
details: storyDetails
|
|
10545
|
+
}
|
|
10546
|
+
};
|
|
10547
|
+
} finally {
|
|
10548
|
+
try {
|
|
10549
|
+
dbWrapper.close();
|
|
10550
|
+
} catch {}
|
|
10551
|
+
}
|
|
10552
|
+
}
|
|
10553
|
+
async function runHealthAction(options) {
|
|
10554
|
+
const { outputFormat } = options;
|
|
10555
|
+
try {
|
|
10556
|
+
const health = await getAutoHealthData(options);
|
|
10557
|
+
if (outputFormat === "json") process.stdout.write(formatOutput(health, "json", true) + "\n");
|
|
10558
|
+
else {
|
|
10559
|
+
const verdictLabel = health.verdict === "HEALTHY" ? "HEALTHY" : health.verdict === "STALLED" ? "STALLED" : "NO PIPELINE RUNNING";
|
|
10560
|
+
process.stdout.write(`\nPipeline Health: ${verdictLabel}\n`);
|
|
10561
|
+
if (health.run_id !== null) {
|
|
10562
|
+
process.stdout.write(` Run: ${health.run_id}\n`);
|
|
10563
|
+
process.stdout.write(` Status: ${health.status}\n`);
|
|
10564
|
+
process.stdout.write(` Phase: ${health.current_phase ?? "N/A"}\n`);
|
|
10565
|
+
process.stdout.write(` Last Active: ${health.last_activity} (${health.staleness_seconds}s ago)\n`);
|
|
10566
|
+
const processInfo = health.process;
|
|
10567
|
+
if (processInfo.orchestrator_pid !== null) {
|
|
10568
|
+
process.stdout.write(` Orchestrator: PID ${processInfo.orchestrator_pid}\n`);
|
|
10569
|
+
process.stdout.write(` Children: ${processInfo.child_pids.length} active`);
|
|
10570
|
+
if (processInfo.zombies.length > 0) process.stdout.write(` (${processInfo.zombies.length} ZOMBIE)`);
|
|
10571
|
+
process.stdout.write("\n");
|
|
10572
|
+
} else process.stdout.write(" Orchestrator: not running\n");
|
|
10573
|
+
const storyDetails = health.stories.details;
|
|
10574
|
+
if (Object.keys(storyDetails).length > 0) {
|
|
10575
|
+
process.stdout.write("\n Stories:\n");
|
|
10576
|
+
for (const [key, s] of Object.entries(storyDetails)) process.stdout.write(` ${key}: ${s.phase} (${s.review_cycles} review cycles)\n`);
|
|
10577
|
+
process.stdout.write(`\n Summary: ${health.stories.active} active, ${health.stories.completed} completed, ${health.stories.escalated} escalated\n`);
|
|
10578
|
+
}
|
|
10579
|
+
}
|
|
10580
|
+
}
|
|
10581
|
+
return 0;
|
|
10582
|
+
} catch (err) {
|
|
10583
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10584
|
+
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
|
|
10585
|
+
else process.stderr.write(`Error: ${msg}\n`);
|
|
10586
|
+
logger$1.error({ err }, "health action failed");
|
|
10587
|
+
return 1;
|
|
10588
|
+
}
|
|
10589
|
+
}
|
|
10590
|
+
function registerHealthCommand(program, _version = "0.0.0", projectRoot = process.cwd()) {
|
|
10591
|
+
program.command("health").description("Check pipeline health: process status, stall detection, and verdict").option("--run-id <id>", "Pipeline run ID to query (defaults to latest)").option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").action(async (opts) => {
|
|
10592
|
+
const outputFormat = opts.outputFormat === "json" ? "json" : "human";
|
|
10593
|
+
const exitCode = await runHealthAction({
|
|
10594
|
+
outputFormat,
|
|
10595
|
+
runId: opts.runId,
|
|
10596
|
+
projectRoot: opts.projectRoot
|
|
10597
|
+
});
|
|
10598
|
+
process.exitCode = exitCode;
|
|
10599
|
+
});
|
|
10600
|
+
}
|
|
10601
|
+
|
|
10335
10602
|
//#endregion
|
|
10336
10603
|
//#region src/cli/commands/run.ts
|
|
10337
10604
|
const logger = createLogger("run-cmd");
|
|
@@ -10478,6 +10745,16 @@ async function runRunAction(options) {
|
|
|
10478
10745
|
return 0;
|
|
10479
10746
|
}
|
|
10480
10747
|
}
|
|
10748
|
+
const staleRuns = getRunningPipelineRuns(db) ?? [];
|
|
10749
|
+
if (staleRuns.length > 0) {
|
|
10750
|
+
const processInfo = inspectProcessTree({ projectRoot });
|
|
10751
|
+
let swept = 0;
|
|
10752
|
+
for (const stale of staleRuns) if (processInfo.orchestrator_pid === null) {
|
|
10753
|
+
updatePipelineRun(db, stale.id, { status: "failed" });
|
|
10754
|
+
swept++;
|
|
10755
|
+
}
|
|
10756
|
+
if (swept > 0) process.stderr.write(`Swept ${swept} stale pipeline run(s) (dead orchestrator)\n`);
|
|
10757
|
+
}
|
|
10481
10758
|
const pipelineRun = createPipelineRun(db, {
|
|
10482
10759
|
methodology: pack.manifest.name,
|
|
10483
10760
|
start_phase: "implementation",
|
|
@@ -11195,5 +11472,5 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
11195
11472
|
}
|
|
11196
11473
|
|
|
11197
11474
|
//#endregion
|
|
11198
|
-
export { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getSubstrateDefaultSettings,
|
|
11199
|
-
//# sourceMappingURL=run-
|
|
11475
|
+
export { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
|
|
11476
|
+
//# sourceMappingURL=run-CLpXJNQ8.js.map
|