substrate-ai 0.2.15 → 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 +338 -336
- package/dist/cli/templates/claude-md-substrate-section.md +6 -0
- package/dist/{decisions-DNYByk0U.js → decisions-Dq4cAA2L.js} +16 -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-BSu2ie3J.js → experimenter-CHRVkV3d.js} +23 -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/{metrics-BSg8VIHd.js → operational-CobuCGbM.js} +79 -2
- package/dist/{run-CoP8UQU3.js → run-CLpXJNQ8.js} +489 -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-DKXc-jnv.js +0 -3
- package/dist/run-B9IglY4m.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-
|
|
4
|
-
import { aggregateTokenUsageForRun, aggregateTokenUsageForStory, getStoryMetricsForRun, writeRunMetrics, writeStoryMetrics } from "./
|
|
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
|
+
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;
|
|
@@ -5371,6 +5376,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5371
5376
|
const HEARTBEAT_INTERVAL_MS = 3e4;
|
|
5372
5377
|
const WATCHDOG_TIMEOUT_MS = 6e5;
|
|
5373
5378
|
const _stalledStories = new Set();
|
|
5379
|
+
const _storiesWithStall = new Set();
|
|
5374
5380
|
const _phaseStartMs = new Map();
|
|
5375
5381
|
const _phaseEndMs = new Map();
|
|
5376
5382
|
const _storyDispatches = new Map();
|
|
@@ -5394,7 +5400,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5394
5400
|
const nowMs = Date.now();
|
|
5395
5401
|
for (const [phase, startMs] of starts) {
|
|
5396
5402
|
const endMs = ends?.get(phase);
|
|
5397
|
-
if (endMs === void 0) logger$
|
|
5403
|
+
if (endMs === void 0) logger$17.warn({
|
|
5398
5404
|
storyKey,
|
|
5399
5405
|
phase
|
|
5400
5406
|
}, "Phase has no end time — story may have errored mid-phase. Duration capped to now() and may be inflated.");
|
|
@@ -5424,8 +5430,30 @@ function createImplementationOrchestrator(deps) {
|
|
|
5424
5430
|
review_cycles: reviewCycles,
|
|
5425
5431
|
dispatches: _storyDispatches.get(storyKey) ?? 0
|
|
5426
5432
|
});
|
|
5433
|
+
try {
|
|
5434
|
+
const runId = config.pipelineRunId ?? "unknown";
|
|
5435
|
+
createDecision(db, {
|
|
5436
|
+
pipeline_run_id: config.pipelineRunId,
|
|
5437
|
+
phase: "implementation",
|
|
5438
|
+
category: STORY_METRICS,
|
|
5439
|
+
key: `${storyKey}:${runId}`,
|
|
5440
|
+
value: JSON.stringify({
|
|
5441
|
+
wall_clock_seconds: wallClockSeconds,
|
|
5442
|
+
input_tokens: tokenAgg.input,
|
|
5443
|
+
output_tokens: tokenAgg.output,
|
|
5444
|
+
review_cycles: reviewCycles,
|
|
5445
|
+
stalled: _storiesWithStall.has(storyKey)
|
|
5446
|
+
}),
|
|
5447
|
+
rationale: `Story ${storyKey} completed with result=${result} in ${wallClockSeconds}s. Tokens: ${tokenAgg.input}+${tokenAgg.output}. Review cycles: ${reviewCycles}.`
|
|
5448
|
+
});
|
|
5449
|
+
} catch (decisionErr) {
|
|
5450
|
+
logger$17.warn({
|
|
5451
|
+
err: decisionErr,
|
|
5452
|
+
storyKey
|
|
5453
|
+
}, "Failed to write story-metrics decision (best-effort)");
|
|
5454
|
+
}
|
|
5427
5455
|
} catch (err) {
|
|
5428
|
-
logger$
|
|
5456
|
+
logger$17.warn({
|
|
5429
5457
|
err,
|
|
5430
5458
|
storyKey
|
|
5431
5459
|
}, "Failed to write story metrics (best-effort)");
|
|
@@ -5461,7 +5489,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5461
5489
|
token_usage_json: serialized
|
|
5462
5490
|
});
|
|
5463
5491
|
} catch (err) {
|
|
5464
|
-
logger$
|
|
5492
|
+
logger$17.warn("Failed to persist orchestrator state", { err });
|
|
5465
5493
|
}
|
|
5466
5494
|
}
|
|
5467
5495
|
function recordProgress() {
|
|
@@ -5490,7 +5518,8 @@ function createImplementationOrchestrator(deps) {
|
|
|
5490
5518
|
for (const [key, s] of _stories) if (s.phase !== "PENDING" && s.phase !== "COMPLETE" && s.phase !== "ESCALATED") {
|
|
5491
5519
|
if (_stalledStories.has(key)) continue;
|
|
5492
5520
|
_stalledStories.add(key);
|
|
5493
|
-
|
|
5521
|
+
_storiesWithStall.add(key);
|
|
5522
|
+
logger$17.warn({
|
|
5494
5523
|
storyKey: key,
|
|
5495
5524
|
phase: s.phase,
|
|
5496
5525
|
elapsedMs: elapsed
|
|
@@ -5527,7 +5556,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5527
5556
|
* exhausted retries the story is ESCALATED.
|
|
5528
5557
|
*/
|
|
5529
5558
|
async function processStory(storyKey) {
|
|
5530
|
-
logger$
|
|
5559
|
+
logger$17.info("Processing story", { storyKey });
|
|
5531
5560
|
await waitIfPaused();
|
|
5532
5561
|
if (_state !== "RUNNING") return;
|
|
5533
5562
|
startPhase(storyKey, "create-story");
|
|
@@ -5542,7 +5571,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5542
5571
|
const match = files.find((f) => f.startsWith(`${storyKey}-`) && f.endsWith(".md"));
|
|
5543
5572
|
if (match) {
|
|
5544
5573
|
storyFilePath = join$1(artifactsDir, match);
|
|
5545
|
-
logger$
|
|
5574
|
+
logger$17.info({
|
|
5546
5575
|
storyKey,
|
|
5547
5576
|
storyFilePath
|
|
5548
5577
|
}, "Found existing story file — skipping create-story");
|
|
@@ -5643,7 +5672,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5643
5672
|
try {
|
|
5644
5673
|
storyContentForAnalysis = await readFile$1(storyFilePath ?? "", "utf-8");
|
|
5645
5674
|
} catch (err) {
|
|
5646
|
-
logger$
|
|
5675
|
+
logger$17.error({
|
|
5647
5676
|
storyKey,
|
|
5648
5677
|
storyFilePath,
|
|
5649
5678
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -5651,7 +5680,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5651
5680
|
}
|
|
5652
5681
|
const analysis = analyzeStoryComplexity(storyContentForAnalysis);
|
|
5653
5682
|
const batches = planTaskBatches(analysis);
|
|
5654
|
-
logger$
|
|
5683
|
+
logger$17.info({
|
|
5655
5684
|
storyKey,
|
|
5656
5685
|
estimatedScope: analysis.estimatedScope,
|
|
5657
5686
|
batchCount: batches.length,
|
|
@@ -5669,7 +5698,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5669
5698
|
if (_state !== "RUNNING") break;
|
|
5670
5699
|
const taskScope = batch.taskIds.map((id, i) => `T${id}: ${batch.taskTitles[i] ?? ""}`).join("\n");
|
|
5671
5700
|
const priorFiles = allFilesModified.size > 0 ? Array.from(allFilesModified) : void 0;
|
|
5672
|
-
logger$
|
|
5701
|
+
logger$17.info({
|
|
5673
5702
|
storyKey,
|
|
5674
5703
|
batchIndex: batch.batchIndex,
|
|
5675
5704
|
taskCount: batch.taskIds.length
|
|
@@ -5693,7 +5722,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5693
5722
|
});
|
|
5694
5723
|
} catch (batchErr) {
|
|
5695
5724
|
const errMsg = batchErr instanceof Error ? batchErr.message : String(batchErr);
|
|
5696
|
-
logger$
|
|
5725
|
+
logger$17.warn({
|
|
5697
5726
|
storyKey,
|
|
5698
5727
|
batchIndex: batch.batchIndex,
|
|
5699
5728
|
error: errMsg
|
|
@@ -5713,7 +5742,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5713
5742
|
filesModified: batchFilesModified,
|
|
5714
5743
|
result: batchResult.result === "success" ? "success" : "failed"
|
|
5715
5744
|
};
|
|
5716
|
-
logger$
|
|
5745
|
+
logger$17.info(batchMetrics, "Batch dev-story metrics");
|
|
5717
5746
|
for (const f of batchFilesModified) allFilesModified.add(f);
|
|
5718
5747
|
if (batchFilesModified.length > 0) batchFileGroups.push({
|
|
5719
5748
|
batchIndex: batch.batchIndex,
|
|
@@ -5735,13 +5764,13 @@ function createImplementationOrchestrator(deps) {
|
|
|
5735
5764
|
})
|
|
5736
5765
|
});
|
|
5737
5766
|
} catch (tokenErr) {
|
|
5738
|
-
logger$
|
|
5767
|
+
logger$17.warn({
|
|
5739
5768
|
storyKey,
|
|
5740
5769
|
batchIndex: batch.batchIndex,
|
|
5741
5770
|
err: tokenErr
|
|
5742
5771
|
}, "Failed to record batch token usage");
|
|
5743
5772
|
}
|
|
5744
|
-
if (batchResult.result === "failed") logger$
|
|
5773
|
+
if (batchResult.result === "failed") logger$17.warn({
|
|
5745
5774
|
storyKey,
|
|
5746
5775
|
batchIndex: batch.batchIndex,
|
|
5747
5776
|
error: batchResult.error
|
|
@@ -5774,7 +5803,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5774
5803
|
result: devResult
|
|
5775
5804
|
});
|
|
5776
5805
|
persistState();
|
|
5777
|
-
if (devResult.result === "failed") logger$
|
|
5806
|
+
if (devResult.result === "failed") logger$17.warn("Dev-story reported failure, proceeding to code review", {
|
|
5778
5807
|
storyKey,
|
|
5779
5808
|
error: devResult.error,
|
|
5780
5809
|
filesModified: devFilesModified.length
|
|
@@ -5831,7 +5860,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5831
5860
|
"NEEDS_MAJOR_REWORK": 2
|
|
5832
5861
|
};
|
|
5833
5862
|
for (const group of batchFileGroups) {
|
|
5834
|
-
logger$
|
|
5863
|
+
logger$17.info({
|
|
5835
5864
|
storyKey,
|
|
5836
5865
|
batchIndex: group.batchIndex,
|
|
5837
5866
|
fileCount: group.files.length
|
|
@@ -5868,7 +5897,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5868
5897
|
rawOutput: lastRawOutput,
|
|
5869
5898
|
tokenUsage: aggregateTokens
|
|
5870
5899
|
};
|
|
5871
|
-
logger$
|
|
5900
|
+
logger$17.info({
|
|
5872
5901
|
storyKey,
|
|
5873
5902
|
batchCount: batchFileGroups.length,
|
|
5874
5903
|
verdict: worstVerdict,
|
|
@@ -5894,7 +5923,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5894
5923
|
const isPhantomReview = reviewResult.verdict !== "SHIP_IT" && (reviewResult.issue_list === void 0 || reviewResult.issue_list.length === 0) && reviewResult.error !== void 0;
|
|
5895
5924
|
if (isPhantomReview && !timeoutRetried) {
|
|
5896
5925
|
timeoutRetried = true;
|
|
5897
|
-
logger$
|
|
5926
|
+
logger$17.warn({
|
|
5898
5927
|
storyKey,
|
|
5899
5928
|
reviewCycles,
|
|
5900
5929
|
error: reviewResult.error
|
|
@@ -5904,7 +5933,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5904
5933
|
verdict = reviewResult.verdict;
|
|
5905
5934
|
issueList = reviewResult.issue_list ?? [];
|
|
5906
5935
|
if (verdict === "NEEDS_MAJOR_REWORK" && reviewCycles > 0 && previousIssueList.length > 0 && issueList.length < previousIssueList.length) {
|
|
5907
|
-
logger$
|
|
5936
|
+
logger$17.info({
|
|
5908
5937
|
storyKey,
|
|
5909
5938
|
originalVerdict: verdict,
|
|
5910
5939
|
issuesBefore: previousIssueList.length,
|
|
@@ -5940,7 +5969,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5940
5969
|
if (_decomposition !== void 0) parts.push(`decomposed: ${_decomposition.batchCount} batches`);
|
|
5941
5970
|
parts.push(`${fileCount} files`);
|
|
5942
5971
|
parts.push(`${totalTokensK} tokens`);
|
|
5943
|
-
logger$
|
|
5972
|
+
logger$17.info({
|
|
5944
5973
|
storyKey,
|
|
5945
5974
|
verdict,
|
|
5946
5975
|
agentVerdict: reviewResult.agentVerdict
|
|
@@ -5998,7 +6027,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5998
6027
|
persistState();
|
|
5999
6028
|
return;
|
|
6000
6029
|
}
|
|
6001
|
-
logger$
|
|
6030
|
+
logger$17.info({
|
|
6002
6031
|
storyKey,
|
|
6003
6032
|
reviewCycles: finalReviewCycles,
|
|
6004
6033
|
issueCount: issueList.length
|
|
@@ -6048,7 +6077,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6048
6077
|
fixPrompt = assembled.prompt;
|
|
6049
6078
|
} catch {
|
|
6050
6079
|
fixPrompt = `Fix story ${storyKey}: verdict=${verdict}, minor fixes needed`;
|
|
6051
|
-
logger$
|
|
6080
|
+
logger$17.warn("Failed to assemble auto-approve fix prompt, using fallback", { storyKey });
|
|
6052
6081
|
}
|
|
6053
6082
|
const handle = dispatcher.dispatch({
|
|
6054
6083
|
prompt: fixPrompt,
|
|
@@ -6065,9 +6094,9 @@ function createImplementationOrchestrator(deps) {
|
|
|
6065
6094
|
output: fixResult.tokenEstimate.output
|
|
6066
6095
|
} : void 0 }
|
|
6067
6096
|
});
|
|
6068
|
-
if (fixResult.status === "timeout") logger$
|
|
6097
|
+
if (fixResult.status === "timeout") logger$17.warn("Auto-approve fix timed out — approving anyway (issues were minor)", { storyKey });
|
|
6069
6098
|
} catch (err) {
|
|
6070
|
-
logger$
|
|
6099
|
+
logger$17.warn("Auto-approve fix dispatch failed — approving anyway (issues were minor)", {
|
|
6071
6100
|
storyKey,
|
|
6072
6101
|
err
|
|
6073
6102
|
});
|
|
@@ -6139,7 +6168,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6139
6168
|
fixPrompt = assembled.prompt;
|
|
6140
6169
|
} catch {
|
|
6141
6170
|
fixPrompt = `Fix story ${storyKey}: verdict=${verdict}, taskType=${taskType}`;
|
|
6142
|
-
logger$
|
|
6171
|
+
logger$17.warn("Failed to assemble fix prompt, using fallback", {
|
|
6143
6172
|
storyKey,
|
|
6144
6173
|
taskType
|
|
6145
6174
|
});
|
|
@@ -6162,7 +6191,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6162
6191
|
} : void 0 }
|
|
6163
6192
|
});
|
|
6164
6193
|
if (fixResult.status === "timeout") {
|
|
6165
|
-
logger$
|
|
6194
|
+
logger$17.warn("Fix dispatch timed out — escalating story", {
|
|
6166
6195
|
storyKey,
|
|
6167
6196
|
taskType
|
|
6168
6197
|
});
|
|
@@ -6182,13 +6211,13 @@ function createImplementationOrchestrator(deps) {
|
|
|
6182
6211
|
persistState();
|
|
6183
6212
|
return;
|
|
6184
6213
|
}
|
|
6185
|
-
if (fixResult.status === "failed") logger$
|
|
6214
|
+
if (fixResult.status === "failed") logger$17.warn("Fix dispatch failed", {
|
|
6186
6215
|
storyKey,
|
|
6187
6216
|
taskType,
|
|
6188
6217
|
exitCode: fixResult.exitCode
|
|
6189
6218
|
});
|
|
6190
6219
|
} catch (err) {
|
|
6191
|
-
logger$
|
|
6220
|
+
logger$17.warn("Fix dispatch failed, continuing to next review", {
|
|
6192
6221
|
storyKey,
|
|
6193
6222
|
taskType,
|
|
6194
6223
|
err
|
|
@@ -6242,11 +6271,11 @@ function createImplementationOrchestrator(deps) {
|
|
|
6242
6271
|
}
|
|
6243
6272
|
async function run(storyKeys) {
|
|
6244
6273
|
if (_state === "RUNNING" || _state === "PAUSED") {
|
|
6245
|
-
logger$
|
|
6274
|
+
logger$17.warn("run() called while orchestrator is already running or paused — ignoring", { state: _state });
|
|
6246
6275
|
return getStatus();
|
|
6247
6276
|
}
|
|
6248
6277
|
if (_state === "COMPLETE") {
|
|
6249
|
-
logger$
|
|
6278
|
+
logger$17.warn("run() called on a COMPLETE orchestrator — ignoring", { state: _state });
|
|
6250
6279
|
return getStatus();
|
|
6251
6280
|
}
|
|
6252
6281
|
_state = "RUNNING";
|
|
@@ -6264,13 +6293,13 @@ function createImplementationOrchestrator(deps) {
|
|
|
6264
6293
|
if (config.enableHeartbeat) startHeartbeat();
|
|
6265
6294
|
if (projectRoot !== void 0) {
|
|
6266
6295
|
const seedResult = seedMethodologyContext(db, projectRoot);
|
|
6267
|
-
if (seedResult.decisionsCreated > 0) logger$
|
|
6296
|
+
if (seedResult.decisionsCreated > 0) logger$17.info({
|
|
6268
6297
|
decisionsCreated: seedResult.decisionsCreated,
|
|
6269
6298
|
skippedCategories: seedResult.skippedCategories
|
|
6270
6299
|
}, "Methodology context seeded from planning artifacts");
|
|
6271
6300
|
}
|
|
6272
6301
|
const groups = detectConflictGroups(storyKeys);
|
|
6273
|
-
logger$
|
|
6302
|
+
logger$17.info("Orchestrator starting", {
|
|
6274
6303
|
storyCount: storyKeys.length,
|
|
6275
6304
|
groupCount: groups.length,
|
|
6276
6305
|
maxConcurrency: config.maxConcurrency
|
|
@@ -6282,7 +6311,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6282
6311
|
_state = "FAILED";
|
|
6283
6312
|
_completedAt = new Date().toISOString();
|
|
6284
6313
|
persistState();
|
|
6285
|
-
logger$
|
|
6314
|
+
logger$17.error("Orchestrator failed with unhandled error", { err });
|
|
6286
6315
|
return getStatus();
|
|
6287
6316
|
}
|
|
6288
6317
|
stopHeartbeat();
|
|
@@ -6309,7 +6338,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6309
6338
|
_pauseGate = createPauseGate();
|
|
6310
6339
|
_state = "PAUSED";
|
|
6311
6340
|
eventBus.emit("orchestrator:paused", {});
|
|
6312
|
-
logger$
|
|
6341
|
+
logger$17.info("Orchestrator paused");
|
|
6313
6342
|
}
|
|
6314
6343
|
function resume() {
|
|
6315
6344
|
if (_state !== "PAUSED") return;
|
|
@@ -6320,7 +6349,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6320
6349
|
}
|
|
6321
6350
|
_state = "RUNNING";
|
|
6322
6351
|
eventBus.emit("orchestrator:resumed", {});
|
|
6323
|
-
logger$
|
|
6352
|
+
logger$17.info("Orchestrator resumed");
|
|
6324
6353
|
}
|
|
6325
6354
|
return {
|
|
6326
6355
|
run,
|
|
@@ -6995,7 +7024,7 @@ const CritiqueOutputSchema = z.object({
|
|
|
6995
7024
|
|
|
6996
7025
|
//#endregion
|
|
6997
7026
|
//#region src/modules/phase-orchestrator/critique-loop.ts
|
|
6998
|
-
const logger$
|
|
7027
|
+
const logger$6 = createLogger("critique-loop");
|
|
6999
7028
|
/**
|
|
7000
7029
|
* Maps a phase name to the critique prompt template name.
|
|
7001
7030
|
* Falls back to `critique-${phase}` for unknown phases.
|
|
@@ -7049,7 +7078,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7049
7078
|
critiquePrompt = critiqueTemplate.replace("{{artifact_content}}", currentArtifact).replace("{{project_context}}", projectContext);
|
|
7050
7079
|
} catch (err) {
|
|
7051
7080
|
const message = err instanceof Error ? err.message : String(err);
|
|
7052
|
-
logger$
|
|
7081
|
+
logger$6.warn({
|
|
7053
7082
|
phaseId,
|
|
7054
7083
|
promptName: critiquePromptName,
|
|
7055
7084
|
err: message
|
|
@@ -7077,7 +7106,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7077
7106
|
critiqueTokens.output += result.tokenEstimate.output;
|
|
7078
7107
|
if (result.status !== "completed" || result.parsed === null) {
|
|
7079
7108
|
const errMsg = result.parseError ?? `Critique dispatch ended with status '${result.status}'`;
|
|
7080
|
-
logger$
|
|
7109
|
+
logger$6.warn({
|
|
7081
7110
|
phaseId,
|
|
7082
7111
|
iteration: i + 1,
|
|
7083
7112
|
err: errMsg
|
|
@@ -7096,7 +7125,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7096
7125
|
lastCritiqueOutput = critiqueOutput;
|
|
7097
7126
|
} catch (err) {
|
|
7098
7127
|
const message = err instanceof Error ? err.message : String(err);
|
|
7099
|
-
logger$
|
|
7128
|
+
logger$6.warn({
|
|
7100
7129
|
phaseId,
|
|
7101
7130
|
iteration: i + 1,
|
|
7102
7131
|
err: message
|
|
@@ -7136,14 +7165,14 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7136
7165
|
});
|
|
7137
7166
|
} catch (err) {
|
|
7138
7167
|
const message = err instanceof Error ? err.message : String(err);
|
|
7139
|
-
logger$
|
|
7168
|
+
logger$6.warn({
|
|
7140
7169
|
phaseId,
|
|
7141
7170
|
iteration: i + 1,
|
|
7142
7171
|
err: message
|
|
7143
7172
|
}, "Critique loop: failed to store critique decision — continuing");
|
|
7144
7173
|
}
|
|
7145
7174
|
if (critiqueOutput.verdict === "pass") {
|
|
7146
|
-
logger$
|
|
7175
|
+
logger$6.info({
|
|
7147
7176
|
phaseId,
|
|
7148
7177
|
iteration: i + 1
|
|
7149
7178
|
}, "Critique loop: artifact passed critique — loop complete");
|
|
@@ -7156,7 +7185,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7156
7185
|
totalMs: Date.now() - startMs
|
|
7157
7186
|
};
|
|
7158
7187
|
}
|
|
7159
|
-
logger$
|
|
7188
|
+
logger$6.info({
|
|
7160
7189
|
phaseId,
|
|
7161
7190
|
iteration: i + 1,
|
|
7162
7191
|
issueCount: critiqueOutput.issue_count
|
|
@@ -7169,7 +7198,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7169
7198
|
refinePrompt = refineTemplate.replace("{{original_artifact}}", currentArtifact).replace("{{critique_issues}}", issuesText).replace("{{phase_context}}", phaseContext);
|
|
7170
7199
|
} catch (err) {
|
|
7171
7200
|
const message = err instanceof Error ? err.message : String(err);
|
|
7172
|
-
logger$
|
|
7201
|
+
logger$6.warn({
|
|
7173
7202
|
phaseId,
|
|
7174
7203
|
iteration: i + 1,
|
|
7175
7204
|
err: message
|
|
@@ -7190,7 +7219,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7190
7219
|
const originalLength = currentArtifact.length;
|
|
7191
7220
|
const refinedLength = refineResult.output.length;
|
|
7192
7221
|
const delta = refinedLength - originalLength;
|
|
7193
|
-
logger$
|
|
7222
|
+
logger$6.info({
|
|
7194
7223
|
phaseId,
|
|
7195
7224
|
iteration: i + 1,
|
|
7196
7225
|
originalLength,
|
|
@@ -7199,7 +7228,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7199
7228
|
}, "Critique loop: refinement complete");
|
|
7200
7229
|
currentArtifact = refineResult.output;
|
|
7201
7230
|
} else {
|
|
7202
|
-
logger$
|
|
7231
|
+
logger$6.warn({
|
|
7203
7232
|
phaseId,
|
|
7204
7233
|
iteration: i + 1,
|
|
7205
7234
|
status: refineResult.status
|
|
@@ -7208,7 +7237,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7208
7237
|
}
|
|
7209
7238
|
} catch (err) {
|
|
7210
7239
|
const message = err instanceof Error ? err.message : String(err);
|
|
7211
|
-
logger$
|
|
7240
|
+
logger$6.warn({
|
|
7212
7241
|
phaseId,
|
|
7213
7242
|
iteration: i + 1,
|
|
7214
7243
|
err: message
|
|
@@ -7219,12 +7248,12 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7219
7248
|
}
|
|
7220
7249
|
const remainingIssues = lastCritiqueOutput?.issues ?? [];
|
|
7221
7250
|
if (remainingIssues.length > 0) {
|
|
7222
|
-
logger$
|
|
7251
|
+
logger$6.warn({
|
|
7223
7252
|
phaseId,
|
|
7224
7253
|
maxIterations,
|
|
7225
7254
|
issueCount: remainingIssues.length
|
|
7226
7255
|
}, "Critique loop: max iterations reached with unresolved issues");
|
|
7227
|
-
for (const issue of remainingIssues) logger$
|
|
7256
|
+
for (const issue of remainingIssues) logger$6.warn({
|
|
7228
7257
|
phaseId,
|
|
7229
7258
|
severity: issue.severity,
|
|
7230
7259
|
category: issue.category,
|
|
@@ -7243,7 +7272,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
|
|
|
7243
7272
|
|
|
7244
7273
|
//#endregion
|
|
7245
7274
|
//#region src/modules/phase-orchestrator/elicitation-selector.ts
|
|
7246
|
-
const logger$
|
|
7275
|
+
const logger$5 = createLogger("elicitation-selector");
|
|
7247
7276
|
/**
|
|
7248
7277
|
* Affinity scores (0.0–1.0) for each category per content type.
|
|
7249
7278
|
*
|
|
@@ -7365,10 +7394,10 @@ function loadElicitationMethods() {
|
|
|
7365
7394
|
try {
|
|
7366
7395
|
const content = readFileSync(csvPath, "utf-8");
|
|
7367
7396
|
const methods = parseMethodsCsv(content);
|
|
7368
|
-
logger$
|
|
7397
|
+
logger$5.debug({ count: methods.length }, "Loaded elicitation methods");
|
|
7369
7398
|
return methods;
|
|
7370
7399
|
} catch (err) {
|
|
7371
|
-
logger$
|
|
7400
|
+
logger$5.warn({
|
|
7372
7401
|
csvPath,
|
|
7373
7402
|
err
|
|
7374
7403
|
}, "Failed to load elicitation methods CSV");
|
|
@@ -7688,7 +7717,7 @@ const ElicitationOutputSchema = z.object({
|
|
|
7688
7717
|
|
|
7689
7718
|
//#endregion
|
|
7690
7719
|
//#region src/modules/phase-orchestrator/step-runner.ts
|
|
7691
|
-
const logger$
|
|
7720
|
+
const logger$4 = createLogger("step-runner");
|
|
7692
7721
|
/**
|
|
7693
7722
|
* Format an array of decision records into a markdown section for injection.
|
|
7694
7723
|
*
|
|
@@ -7795,7 +7824,7 @@ async function runSteps(steps, deps, runId, phase, params) {
|
|
|
7795
7824
|
if (estimatedTokens > budgetTokens) {
|
|
7796
7825
|
const decisionRefs = step.context.filter((ref) => ref.source.startsWith("decision:"));
|
|
7797
7826
|
if (decisionRefs.length > 0) {
|
|
7798
|
-
logger$
|
|
7827
|
+
logger$4.warn({
|
|
7799
7828
|
step: step.name,
|
|
7800
7829
|
estimatedTokens,
|
|
7801
7830
|
budgetTokens
|
|
@@ -7822,7 +7851,7 @@ async function runSteps(steps, deps, runId, phase, params) {
|
|
|
7822
7851
|
}
|
|
7823
7852
|
prompt = summarizedPrompt;
|
|
7824
7853
|
estimatedTokens = Math.ceil(prompt.length / 4);
|
|
7825
|
-
if (estimatedTokens <= budgetTokens) logger$
|
|
7854
|
+
if (estimatedTokens <= budgetTokens) logger$4.info({
|
|
7826
7855
|
step: step.name,
|
|
7827
7856
|
estimatedTokens,
|
|
7828
7857
|
budgetTokens
|
|
@@ -8003,7 +8032,7 @@ async function runSteps(steps, deps, runId, phase, params) {
|
|
|
8003
8032
|
const critiqueResult = await runCritiqueLoop(artifactContent, phase, runId, phase, deps);
|
|
8004
8033
|
totalInput += critiqueResult.critiqueTokens.input + critiqueResult.refinementTokens.input;
|
|
8005
8034
|
totalOutput += critiqueResult.critiqueTokens.output + critiqueResult.refinementTokens.output;
|
|
8006
|
-
logger$
|
|
8035
|
+
logger$4.info({
|
|
8007
8036
|
step: step.name,
|
|
8008
8037
|
verdict: critiqueResult.verdict,
|
|
8009
8038
|
iterations: critiqueResult.iterations,
|
|
@@ -8011,7 +8040,7 @@ async function runSteps(steps, deps, runId, phase, params) {
|
|
|
8011
8040
|
}, "Step critique loop complete");
|
|
8012
8041
|
} catch (critiqueErr) {
|
|
8013
8042
|
const critiqueMsg = critiqueErr instanceof Error ? critiqueErr.message : String(critiqueErr);
|
|
8014
|
-
logger$
|
|
8043
|
+
logger$4.warn({
|
|
8015
8044
|
step: step.name,
|
|
8016
8045
|
err: critiqueMsg
|
|
8017
8046
|
}, "Step critique loop threw an error — continuing without critique");
|
|
@@ -8021,7 +8050,7 @@ async function runSteps(steps, deps, runId, phase, params) {
|
|
|
8021
8050
|
const contentType = deriveContentType(phase, step.name);
|
|
8022
8051
|
const selectedMethods = selectMethods({ content_type: contentType }, usedElicitationMethods);
|
|
8023
8052
|
if (selectedMethods.length > 0) {
|
|
8024
|
-
logger$
|
|
8053
|
+
logger$4.info({
|
|
8025
8054
|
step: step.name,
|
|
8026
8055
|
methods: selectedMethods.map((m) => m.name),
|
|
8027
8056
|
contentType
|
|
@@ -8060,13 +8089,13 @@ async function runSteps(steps, deps, runId, phase, params) {
|
|
|
8060
8089
|
key: `${phase}-round-${roundIndex}-insights`,
|
|
8061
8090
|
value: elicitParsed.insights
|
|
8062
8091
|
});
|
|
8063
|
-
logger$
|
|
8092
|
+
logger$4.info({
|
|
8064
8093
|
step: step.name,
|
|
8065
8094
|
method: method.name,
|
|
8066
8095
|
roundIndex
|
|
8067
8096
|
}, "Elicitation insights stored in decision store");
|
|
8068
8097
|
}
|
|
8069
|
-
} else logger$
|
|
8098
|
+
} else logger$4.warn({
|
|
8070
8099
|
step: step.name,
|
|
8071
8100
|
method: method.name,
|
|
8072
8101
|
status: elicitResult.status
|
|
@@ -8082,7 +8111,7 @@ async function runSteps(steps, deps, runId, phase, params) {
|
|
|
8082
8111
|
}
|
|
8083
8112
|
} catch (elicitErr) {
|
|
8084
8113
|
const elicitMsg = elicitErr instanceof Error ? elicitErr.message : String(elicitErr);
|
|
8085
|
-
logger$
|
|
8114
|
+
logger$4.warn({
|
|
8086
8115
|
step: step.name,
|
|
8087
8116
|
err: elicitMsg
|
|
8088
8117
|
}, "Step elicitation threw an error — continuing without elicitation");
|
|
@@ -8424,7 +8453,7 @@ async function runAnalysisPhase(deps, params) {
|
|
|
8424
8453
|
|
|
8425
8454
|
//#endregion
|
|
8426
8455
|
//#region src/modules/phase-orchestrator/phases/planning.ts
|
|
8427
|
-
const logger$
|
|
8456
|
+
const logger$3 = createLogger("planning-phase");
|
|
8428
8457
|
/** Maximum total prompt length in tokens (3,500 tokens × 4 chars/token = 14,000 chars) */
|
|
8429
8458
|
const MAX_PROMPT_TOKENS = 3500;
|
|
8430
8459
|
const MAX_PROMPT_CHARS = MAX_PROMPT_TOKENS * 4;
|
|
@@ -8651,7 +8680,7 @@ async function runPlanningMultiStep(deps, params) {
|
|
|
8651
8680
|
const techConstraintDecisions = allAnalysisDecisions.filter((d) => d.category === "technology-constraints");
|
|
8652
8681
|
const violation = detectTechStackViolation(techStack, techConstraintDecisions);
|
|
8653
8682
|
if (violation) {
|
|
8654
|
-
logger$
|
|
8683
|
+
logger$3.warn({ violation }, "Tech stack constraint violation detected — retrying step 3 with correction");
|
|
8655
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`;
|
|
8656
8685
|
const step3Template = await deps.pack.getPrompt("planning-step-3-nfrs");
|
|
8657
8686
|
const stepOutputs = new Map();
|
|
@@ -8678,10 +8707,10 @@ async function runPlanningMultiStep(deps, params) {
|
|
|
8678
8707
|
const retryTechStack = retryParsed.tech_stack;
|
|
8679
8708
|
const retryViolation = retryTechStack ? detectTechStackViolation(retryTechStack, techConstraintDecisions) : null;
|
|
8680
8709
|
if (!retryViolation) {
|
|
8681
|
-
logger$
|
|
8710
|
+
logger$3.info("Retry produced compliant tech stack — using corrected output");
|
|
8682
8711
|
nfrsOutput = retryParsed;
|
|
8683
|
-
} else logger$
|
|
8684
|
-
} 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");
|
|
8685
8714
|
}
|
|
8686
8715
|
}
|
|
8687
8716
|
const frs = frsOutput.functional_requirements;
|
|
@@ -8968,7 +8997,7 @@ const ReadinessOutputSchema = z.object({
|
|
|
8968
8997
|
|
|
8969
8998
|
//#endregion
|
|
8970
8999
|
//#region src/modules/phase-orchestrator/phases/solutioning.ts
|
|
8971
|
-
const logger$
|
|
9000
|
+
const logger$2 = createLogger("solutioning");
|
|
8972
9001
|
/** Base token budget for architecture generation (covers template + requirements) */
|
|
8973
9002
|
const BASE_ARCH_PROMPT_TOKENS = 3e3;
|
|
8974
9003
|
/** Base token budget for story generation (covers template + requirements + architecture) */
|
|
@@ -9377,7 +9406,7 @@ async function runReadinessCheck(deps, runId) {
|
|
|
9377
9406
|
input: tokenEstimate.input,
|
|
9378
9407
|
output: tokenEstimate.output
|
|
9379
9408
|
};
|
|
9380
|
-
logger$
|
|
9409
|
+
logger$2.info({
|
|
9381
9410
|
runId,
|
|
9382
9411
|
durationMs: dispatchResult.durationMs,
|
|
9383
9412
|
tokens: tokenEstimate
|
|
@@ -9638,7 +9667,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9638
9667
|
let archResult;
|
|
9639
9668
|
if (existingArchArtifact) {
|
|
9640
9669
|
const existingDecisions = getDecisionsByPhaseForRun(deps.db, params.runId, "solutioning").filter((d) => d.category === "architecture");
|
|
9641
|
-
logger$
|
|
9670
|
+
logger$2.info({
|
|
9642
9671
|
runId: params.runId,
|
|
9643
9672
|
artifactId: existingArchArtifact.id,
|
|
9644
9673
|
decisionCount: existingDecisions.length
|
|
@@ -9669,7 +9698,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9669
9698
|
output: totalOutput
|
|
9670
9699
|
}
|
|
9671
9700
|
};
|
|
9672
|
-
logger$
|
|
9701
|
+
logger$2.info({
|
|
9673
9702
|
runId: params.runId,
|
|
9674
9703
|
decisionCount: archResult.decisions.length,
|
|
9675
9704
|
mode: hasSteps ? "multi-step" : "single-dispatch"
|
|
@@ -9691,7 +9720,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9691
9720
|
totalInput += readinessResult.tokenUsage.input;
|
|
9692
9721
|
totalOutput += readinessResult.tokenUsage.output;
|
|
9693
9722
|
if (readinessResult.verdict === "error") {
|
|
9694
|
-
logger$
|
|
9723
|
+
logger$2.error({
|
|
9695
9724
|
runId: params.runId,
|
|
9696
9725
|
error: readinessResult.error
|
|
9697
9726
|
}, "Readiness check agent failed");
|
|
@@ -9707,7 +9736,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9707
9736
|
}
|
|
9708
9737
|
};
|
|
9709
9738
|
}
|
|
9710
|
-
logger$
|
|
9739
|
+
logger$2.info({
|
|
9711
9740
|
runId: params.runId,
|
|
9712
9741
|
verdict: readinessResult.verdict,
|
|
9713
9742
|
coverageScore: readinessResult.coverageScore,
|
|
@@ -9723,7 +9752,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9723
9752
|
key: `finding-${i + 1}`,
|
|
9724
9753
|
value: JSON.stringify(finding)
|
|
9725
9754
|
});
|
|
9726
|
-
logger$
|
|
9755
|
+
logger$2.error({
|
|
9727
9756
|
runId: params.runId,
|
|
9728
9757
|
verdict: "NOT_READY",
|
|
9729
9758
|
coverageScore: readinessResult.coverageScore,
|
|
@@ -9789,7 +9818,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9789
9818
|
"",
|
|
9790
9819
|
"Please generate additional or revised stories to specifically address each blocker above."
|
|
9791
9820
|
].join("\n");
|
|
9792
|
-
logger$
|
|
9821
|
+
logger$2.info({
|
|
9793
9822
|
runId: params.runId,
|
|
9794
9823
|
blockerCount: blockers.length
|
|
9795
9824
|
}, "Readiness NEEDS_WORK with blockers — retrying story generation with gap analysis");
|
|
@@ -9828,7 +9857,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9828
9857
|
};
|
|
9829
9858
|
if (retryReadiness.verdict === "NOT_READY" || retryReadiness.verdict === "NEEDS_WORK") {
|
|
9830
9859
|
const retryBlockers = retryReadiness.findings.filter((f) => f.severity === "blocker");
|
|
9831
|
-
logger$
|
|
9860
|
+
logger$2.error({
|
|
9832
9861
|
runId: params.runId,
|
|
9833
9862
|
verdict: retryReadiness.verdict,
|
|
9834
9863
|
retryBlockers: retryBlockers.length
|
|
@@ -9852,7 +9881,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9852
9881
|
}
|
|
9853
9882
|
const retryStories = retryResult.epics.reduce((sum, epic) => sum + epic.stories.length, 0);
|
|
9854
9883
|
const minorFindings$1 = retryReadiness.findings.filter((f) => f.severity === "minor");
|
|
9855
|
-
if (minorFindings$1.length > 0) logger$
|
|
9884
|
+
if (minorFindings$1.length > 0) logger$2.warn({
|
|
9856
9885
|
runId: params.runId,
|
|
9857
9886
|
minorFindings: minorFindings$1
|
|
9858
9887
|
}, "Readiness READY with minor findings after retry");
|
|
@@ -9881,7 +9910,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9881
9910
|
};
|
|
9882
9911
|
}
|
|
9883
9912
|
const majorFindings = readinessResult.findings.filter((f) => f.severity === "major");
|
|
9884
|
-
logger$
|
|
9913
|
+
logger$2.warn({
|
|
9885
9914
|
runId: params.runId,
|
|
9886
9915
|
majorCount: majorFindings.length,
|
|
9887
9916
|
findings: readinessResult.findings
|
|
@@ -9897,7 +9926,7 @@ async function runSolutioningPhase(deps, params) {
|
|
|
9897
9926
|
const minorFindings = readinessResult.findings.filter((f) => f.severity === "minor");
|
|
9898
9927
|
if (minorFindings.length > 0) {
|
|
9899
9928
|
const verdictLabel = readinessResult.verdict === "READY" ? "READY" : "NEEDS_WORK (no blockers)";
|
|
9900
|
-
logger$
|
|
9929
|
+
logger$2.warn({
|
|
9901
9930
|
runId: params.runId,
|
|
9902
9931
|
verdict: readinessResult.verdict,
|
|
9903
9932
|
minorFindings
|
|
@@ -10308,6 +10337,268 @@ async function runResearchPhase(deps, params) {
|
|
|
10308
10337
|
}
|
|
10309
10338
|
}
|
|
10310
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
|
+
|
|
10311
10602
|
//#endregion
|
|
10312
10603
|
//#region src/cli/commands/run.ts
|
|
10313
10604
|
const logger = createLogger("run-cmd");
|
|
@@ -10454,6 +10745,16 @@ async function runRunAction(options) {
|
|
|
10454
10745
|
return 0;
|
|
10455
10746
|
}
|
|
10456
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
|
+
}
|
|
10457
10758
|
const pipelineRun = createPipelineRun(db, {
|
|
10458
10759
|
methodology: pack.manifest.name,
|
|
10459
10760
|
start_phase: "implementation",
|
|
@@ -11171,5 +11472,5 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
11171
11472
|
}
|
|
11172
11473
|
|
|
11173
11474
|
//#endregion
|
|
11174
|
-
export { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getSubstrateDefaultSettings,
|
|
11175
|
-
//# 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
|