substrate-ai 0.2.20 → 0.2.21
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 +343 -123
- package/dist/{experimenter-prkFLFPw.js → experimenter-bc40oi8p.js} +2 -2
- package/dist/{operational-Dq4IfJzE.js → operational-CnMlvWqc.js} +47 -2
- package/dist/{run-CcWb6Kb-.js → run-BLIgARum.js} +776 -167
- package/dist/run-gmS6DsGT.js +7 -0
- package/package.json +1 -1
- package/packs/bmad/prompts/analysis-step-1-vision.md +4 -1
- package/packs/bmad/prompts/test-expansion.md +65 -0
- package/packs/bmad/prompts/test-plan.md +41 -0
- package/dist/run-C5zfaWYN.js +0 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createLogger } from "./logger-D2fS2ccL.js";
|
|
2
2
|
import { AdapterRegistry, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning } from "./event-bus-BMxhfxfT.js";
|
|
3
3
|
import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./decisions-Dq4cAA2L.js";
|
|
4
|
-
import { ESCALATION_DIAGNOSIS, OPERATIONAL_FINDING, STORY_METRICS, STORY_OUTCOME, aggregateTokenUsageForRun, aggregateTokenUsageForStory, getStoryMetricsForRun, writeRunMetrics, writeStoryMetrics } from "./operational-
|
|
4
|
+
import { ESCALATION_DIAGNOSIS, OPERATIONAL_FINDING, STORY_METRICS, STORY_OUTCOME, TEST_EXPANSION_FINDING, TEST_PLAN, aggregateTokenUsageForRun, aggregateTokenUsageForStory, getStoryMetricsForRun, writeRunMetrics, writeStoryMetrics } from "./operational-CnMlvWqc.js";
|
|
5
5
|
import { createRequire } from "module";
|
|
6
6
|
import { dirname, join } from "path";
|
|
7
7
|
import { access, readFile, readdir, stat } from "fs/promises";
|
|
@@ -539,7 +539,7 @@ const migration010RunMetrics = {
|
|
|
539
539
|
|
|
540
540
|
//#endregion
|
|
541
541
|
//#region src/persistence/migrations/index.ts
|
|
542
|
-
const logger$
|
|
542
|
+
const logger$19 = createLogger("persistence:migrations");
|
|
543
543
|
const MIGRATIONS = [
|
|
544
544
|
initialSchemaMigration,
|
|
545
545
|
costTrackerSchemaMigration,
|
|
@@ -557,7 +557,7 @@ const MIGRATIONS = [
|
|
|
557
557
|
* Safe to call multiple times — already-applied migrations are skipped.
|
|
558
558
|
*/
|
|
559
559
|
function runMigrations(db) {
|
|
560
|
-
logger$
|
|
560
|
+
logger$19.info("Starting migration runner");
|
|
561
561
|
db.exec(`
|
|
562
562
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
563
563
|
version INTEGER PRIMARY KEY,
|
|
@@ -568,12 +568,12 @@ function runMigrations(db) {
|
|
|
568
568
|
const appliedVersions = new Set(db.prepare("SELECT version FROM schema_migrations").all().map((row) => row.version));
|
|
569
569
|
const pending = MIGRATIONS.filter((m) => !appliedVersions.has(m.version)).sort((a, b) => a.version - b.version);
|
|
570
570
|
if (pending.length === 0) {
|
|
571
|
-
logger$
|
|
571
|
+
logger$19.info("No pending migrations");
|
|
572
572
|
return;
|
|
573
573
|
}
|
|
574
574
|
const insertMigration = db.prepare("INSERT INTO schema_migrations (version, name) VALUES (?, ?)");
|
|
575
575
|
for (const migration of pending) {
|
|
576
|
-
logger$
|
|
576
|
+
logger$19.info({
|
|
577
577
|
version: migration.version,
|
|
578
578
|
name: migration.name
|
|
579
579
|
}, "Applying migration");
|
|
@@ -587,14 +587,14 @@ function runMigrations(db) {
|
|
|
587
587
|
});
|
|
588
588
|
applyMigration();
|
|
589
589
|
}
|
|
590
|
-
logger$
|
|
590
|
+
logger$19.info({ version: migration.version }, "Migration applied successfully");
|
|
591
591
|
}
|
|
592
|
-
logger$
|
|
592
|
+
logger$19.info({ count: pending.length }, "All pending migrations applied");
|
|
593
593
|
}
|
|
594
594
|
|
|
595
595
|
//#endregion
|
|
596
596
|
//#region src/persistence/database.ts
|
|
597
|
-
const logger$
|
|
597
|
+
const logger$18 = createLogger("persistence:database");
|
|
598
598
|
/**
|
|
599
599
|
* Thin wrapper that opens a SQLite database, applies required PRAGMAs,
|
|
600
600
|
* and exposes the raw BetterSqlite3 instance.
|
|
@@ -611,14 +611,14 @@ var DatabaseWrapper = class {
|
|
|
611
611
|
*/
|
|
612
612
|
open() {
|
|
613
613
|
if (this._db !== null) return;
|
|
614
|
-
logger$
|
|
614
|
+
logger$18.info({ path: this._path }, "Opening SQLite database");
|
|
615
615
|
this._db = new BetterSqlite3(this._path);
|
|
616
616
|
const walResult = this._db.pragma("journal_mode = WAL");
|
|
617
|
-
if (walResult?.[0]?.journal_mode !== "wal") logger$
|
|
617
|
+
if (walResult?.[0]?.journal_mode !== "wal") logger$18.warn({ result: walResult?.[0]?.journal_mode }, "WAL pragma did not return expected \"wal\" — journal_mode may be \"memory\" or unsupported");
|
|
618
618
|
this._db.pragma("busy_timeout = 5000");
|
|
619
619
|
this._db.pragma("synchronous = NORMAL");
|
|
620
620
|
this._db.pragma("foreign_keys = ON");
|
|
621
|
-
logger$
|
|
621
|
+
logger$18.info({ path: this._path }, "SQLite database opened with WAL mode");
|
|
622
622
|
}
|
|
623
623
|
/**
|
|
624
624
|
* Close the database. Idempotent — calling close() when already closed is a no-op.
|
|
@@ -627,7 +627,7 @@ var DatabaseWrapper = class {
|
|
|
627
627
|
if (this._db === null) return;
|
|
628
628
|
this._db.close();
|
|
629
629
|
this._db = null;
|
|
630
|
-
logger$
|
|
630
|
+
logger$18.info({ path: this._path }, "SQLite database closed");
|
|
631
631
|
}
|
|
632
632
|
/**
|
|
633
633
|
* Return the raw BetterSqlite3 instance.
|
|
@@ -1208,11 +1208,38 @@ function buildPipelineStatusOutput(run, tokenSummary, decisionsCount, storiesCou
|
|
|
1208
1208
|
totalCost += row.total_cost_usd;
|
|
1209
1209
|
}
|
|
1210
1210
|
let activeDispatches = 0;
|
|
1211
|
+
let storiesSummary;
|
|
1211
1212
|
try {
|
|
1212
1213
|
if (run.token_usage_json) {
|
|
1213
1214
|
const state = JSON.parse(run.token_usage_json);
|
|
1214
|
-
if (state.stories) {
|
|
1215
|
-
|
|
1215
|
+
if (state.stories && Object.keys(state.stories).length > 0) {
|
|
1216
|
+
const now = Date.now();
|
|
1217
|
+
let completed = 0;
|
|
1218
|
+
let inProgress = 0;
|
|
1219
|
+
let escalated = 0;
|
|
1220
|
+
let pending = 0;
|
|
1221
|
+
const details = {};
|
|
1222
|
+
for (const [key, s] of Object.entries(state.stories)) {
|
|
1223
|
+
const phase = s.phase ?? "PENDING";
|
|
1224
|
+
if (phase !== "PENDING" && phase !== "COMPLETE" && phase !== "ESCALATED") activeDispatches++;
|
|
1225
|
+
if (phase === "COMPLETE") completed++;
|
|
1226
|
+
else if (phase === "ESCALATED") escalated++;
|
|
1227
|
+
else if (phase === "PENDING") pending++;
|
|
1228
|
+
else inProgress++;
|
|
1229
|
+
const elapsed = s.startedAt != null ? Math.max(0, Math.round((now - new Date(s.startedAt).getTime()) / 1e3)) : 0;
|
|
1230
|
+
details[key] = {
|
|
1231
|
+
phase,
|
|
1232
|
+
review_cycles: s.reviewCycles ?? 0,
|
|
1233
|
+
elapsed_seconds: elapsed
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
storiesSummary = {
|
|
1237
|
+
completed,
|
|
1238
|
+
in_progress: inProgress,
|
|
1239
|
+
escalated,
|
|
1240
|
+
pending,
|
|
1241
|
+
details
|
|
1242
|
+
};
|
|
1216
1243
|
}
|
|
1217
1244
|
}
|
|
1218
1245
|
} catch {}
|
|
@@ -1230,7 +1257,8 @@ function buildPipelineStatusOutput(run, tokenSummary, decisionsCount, storiesCou
|
|
|
1230
1257
|
last_activity: run.updated_at,
|
|
1231
1258
|
staleness_seconds: Math.round((Date.now() - parseDbTimestampAsUtc(run.updated_at).getTime()) / 1e3),
|
|
1232
1259
|
last_event_ts: run.updated_at,
|
|
1233
|
-
active_dispatches: activeDispatches
|
|
1260
|
+
active_dispatches: activeDispatches,
|
|
1261
|
+
...storiesSummary !== void 0 ? { stories: storiesSummary } : {}
|
|
1234
1262
|
};
|
|
1235
1263
|
}
|
|
1236
1264
|
/**
|
|
@@ -1259,6 +1287,19 @@ function formatPipelineStatusHuman(status) {
|
|
|
1259
1287
|
lines.push(` Total Cost: $${status.total_tokens.cost_usd.toFixed(4)}`);
|
|
1260
1288
|
lines.push(` Decisions: ${status.decisions_count}`);
|
|
1261
1289
|
lines.push(` Stories: ${status.stories_count}`);
|
|
1290
|
+
if (status.stories !== void 0 && Object.keys(status.stories.details).length > 0) {
|
|
1291
|
+
lines.push("");
|
|
1292
|
+
lines.push(" Sprint Progress:");
|
|
1293
|
+
lines.push(" " + "─".repeat(68));
|
|
1294
|
+
lines.push(` ${"STORY".padEnd(10)} ${"PHASE".padEnd(24)} ${"CYCLES".padEnd(8)} ELAPSED`);
|
|
1295
|
+
lines.push(" " + "─".repeat(68));
|
|
1296
|
+
for (const [key, detail] of Object.entries(status.stories.details)) {
|
|
1297
|
+
const elapsed = detail.elapsed_seconds > 0 ? `${detail.elapsed_seconds}s` : "-";
|
|
1298
|
+
lines.push(` ${key.padEnd(10)} ${detail.phase.padEnd(24)} ${String(detail.review_cycles).padEnd(8)} ${elapsed}`);
|
|
1299
|
+
}
|
|
1300
|
+
lines.push(" " + "─".repeat(68));
|
|
1301
|
+
lines.push(` Completed: ${status.stories.completed} In Progress: ${status.stories.in_progress} Escalated: ${status.stories.escalated} Pending: ${status.stories.pending}`);
|
|
1302
|
+
}
|
|
1262
1303
|
return lines.join("\n");
|
|
1263
1304
|
}
|
|
1264
1305
|
/**
|
|
@@ -2496,7 +2537,7 @@ function truncateToTokens(text, maxTokens) {
|
|
|
2496
2537
|
|
|
2497
2538
|
//#endregion
|
|
2498
2539
|
//#region src/modules/context-compiler/context-compiler-impl.ts
|
|
2499
|
-
const logger$
|
|
2540
|
+
const logger$17 = createLogger("context-compiler");
|
|
2500
2541
|
/**
|
|
2501
2542
|
* Fraction of the original token budget that must remain (after required +
|
|
2502
2543
|
* important sections) before an optional section is included.
|
|
@@ -2588,7 +2629,7 @@ var ContextCompilerImpl = class {
|
|
|
2588
2629
|
includedParts.push(truncated);
|
|
2589
2630
|
remainingBudget -= truncatedTokens;
|
|
2590
2631
|
anyTruncated = true;
|
|
2591
|
-
logger$
|
|
2632
|
+
logger$17.warn({
|
|
2592
2633
|
section: section.name,
|
|
2593
2634
|
originalTokens: tokens,
|
|
2594
2635
|
budgetTokens: truncatedTokens
|
|
@@ -2602,7 +2643,7 @@ var ContextCompilerImpl = class {
|
|
|
2602
2643
|
});
|
|
2603
2644
|
} else {
|
|
2604
2645
|
anyTruncated = true;
|
|
2605
|
-
logger$
|
|
2646
|
+
logger$17.warn({
|
|
2606
2647
|
section: section.name,
|
|
2607
2648
|
tokens
|
|
2608
2649
|
}, "Context compiler: omitted \"important\" section — no budget remaining");
|
|
@@ -2629,7 +2670,7 @@ var ContextCompilerImpl = class {
|
|
|
2629
2670
|
} else {
|
|
2630
2671
|
if (tokens > 0) {
|
|
2631
2672
|
anyTruncated = true;
|
|
2632
|
-
logger$
|
|
2673
|
+
logger$17.warn({
|
|
2633
2674
|
section: section.name,
|
|
2634
2675
|
tokens,
|
|
2635
2676
|
budgetFractionRemaining: budgetFractionRemaining.toFixed(2)
|
|
@@ -2914,7 +2955,7 @@ function parseYamlResult(yamlText, schema) {
|
|
|
2914
2955
|
|
|
2915
2956
|
//#endregion
|
|
2916
2957
|
//#region src/modules/agent-dispatch/dispatcher-impl.ts
|
|
2917
|
-
const logger$
|
|
2958
|
+
const logger$16 = createLogger("agent-dispatch");
|
|
2918
2959
|
const SHUTDOWN_GRACE_MS = 1e4;
|
|
2919
2960
|
const SHUTDOWN_MAX_WAIT_MS = 3e4;
|
|
2920
2961
|
const CHARS_PER_TOKEN = 4;
|
|
@@ -2957,7 +2998,7 @@ function getAvailableMemory() {
|
|
|
2957
2998
|
encoding: "utf-8"
|
|
2958
2999
|
}).trim(), 10);
|
|
2959
3000
|
if (pressureLevel >= 4) {
|
|
2960
|
-
logger$
|
|
3001
|
+
logger$16.warn({ pressureLevel }, "macOS kernel reports critical memory pressure");
|
|
2961
3002
|
return 0;
|
|
2962
3003
|
}
|
|
2963
3004
|
} catch {}
|
|
@@ -2972,7 +3013,7 @@ function getAvailableMemory() {
|
|
|
2972
3013
|
const speculative = parseInt(vmstat.match(/Pages speculative:\s+(\d+)/)?.[1] ?? "0", 10);
|
|
2973
3014
|
const available = (free + purgeable + speculative) * pageSize;
|
|
2974
3015
|
if (pressureLevel >= 2) {
|
|
2975
|
-
logger$
|
|
3016
|
+
logger$16.warn({
|
|
2976
3017
|
pressureLevel,
|
|
2977
3018
|
availableBeforeDiscount: available
|
|
2978
3019
|
}, "macOS kernel reports memory pressure — discounting estimate");
|
|
@@ -3051,7 +3092,7 @@ var DispatcherImpl = class {
|
|
|
3051
3092
|
resolve: typedResolve,
|
|
3052
3093
|
reject
|
|
3053
3094
|
});
|
|
3054
|
-
logger$
|
|
3095
|
+
logger$16.debug({
|
|
3055
3096
|
id,
|
|
3056
3097
|
queueLength: this._queue.length
|
|
3057
3098
|
}, "Dispatch queued");
|
|
@@ -3082,7 +3123,7 @@ var DispatcherImpl = class {
|
|
|
3082
3123
|
async shutdown() {
|
|
3083
3124
|
this._shuttingDown = true;
|
|
3084
3125
|
this._stopMemoryPressureTimer();
|
|
3085
|
-
logger$
|
|
3126
|
+
logger$16.info({
|
|
3086
3127
|
running: this._running.size,
|
|
3087
3128
|
queued: this._queue.length
|
|
3088
3129
|
}, "Dispatcher shutting down");
|
|
@@ -3115,13 +3156,13 @@ var DispatcherImpl = class {
|
|
|
3115
3156
|
}
|
|
3116
3157
|
}, 50);
|
|
3117
3158
|
});
|
|
3118
|
-
logger$
|
|
3159
|
+
logger$16.info("Dispatcher shutdown complete");
|
|
3119
3160
|
}
|
|
3120
3161
|
async _startDispatch(id, request, resolve$2) {
|
|
3121
3162
|
const { prompt, agent, taskType, timeout, outputSchema, workingDirectory, model, maxTurns } = request;
|
|
3122
3163
|
const adapter = this._adapterRegistry.get(agent);
|
|
3123
3164
|
if (adapter === void 0) {
|
|
3124
|
-
logger$
|
|
3165
|
+
logger$16.warn({
|
|
3125
3166
|
id,
|
|
3126
3167
|
agent
|
|
3127
3168
|
}, "No adapter found for agent");
|
|
@@ -3167,7 +3208,7 @@ var DispatcherImpl = class {
|
|
|
3167
3208
|
});
|
|
3168
3209
|
const startedAt = Date.now();
|
|
3169
3210
|
proc.on("error", (err) => {
|
|
3170
|
-
logger$
|
|
3211
|
+
logger$16.error({
|
|
3171
3212
|
id,
|
|
3172
3213
|
binary: cmd.binary,
|
|
3173
3214
|
error: err.message
|
|
@@ -3175,7 +3216,7 @@ var DispatcherImpl = class {
|
|
|
3175
3216
|
});
|
|
3176
3217
|
if (proc.stdin !== null) {
|
|
3177
3218
|
proc.stdin.on("error", (err) => {
|
|
3178
|
-
if (err.code !== "EPIPE") logger$
|
|
3219
|
+
if (err.code !== "EPIPE") logger$16.warn({
|
|
3179
3220
|
id,
|
|
3180
3221
|
error: err.message
|
|
3181
3222
|
}, "stdin write error");
|
|
@@ -3217,7 +3258,7 @@ var DispatcherImpl = class {
|
|
|
3217
3258
|
agent,
|
|
3218
3259
|
taskType
|
|
3219
3260
|
});
|
|
3220
|
-
logger$
|
|
3261
|
+
logger$16.debug({
|
|
3221
3262
|
id,
|
|
3222
3263
|
agent,
|
|
3223
3264
|
taskType,
|
|
@@ -3234,7 +3275,7 @@ var DispatcherImpl = class {
|
|
|
3234
3275
|
dispatchId: id,
|
|
3235
3276
|
timeoutMs
|
|
3236
3277
|
});
|
|
3237
|
-
logger$
|
|
3278
|
+
logger$16.warn({
|
|
3238
3279
|
id,
|
|
3239
3280
|
agent,
|
|
3240
3281
|
taskType,
|
|
@@ -3288,7 +3329,7 @@ var DispatcherImpl = class {
|
|
|
3288
3329
|
exitCode: code,
|
|
3289
3330
|
output: stdout
|
|
3290
3331
|
});
|
|
3291
|
-
logger$
|
|
3332
|
+
logger$16.debug({
|
|
3292
3333
|
id,
|
|
3293
3334
|
agent,
|
|
3294
3335
|
taskType,
|
|
@@ -3314,7 +3355,7 @@ var DispatcherImpl = class {
|
|
|
3314
3355
|
error: stderr || `Process exited with code ${String(code)}`,
|
|
3315
3356
|
exitCode: code
|
|
3316
3357
|
});
|
|
3317
|
-
logger$
|
|
3358
|
+
logger$16.debug({
|
|
3318
3359
|
id,
|
|
3319
3360
|
agent,
|
|
3320
3361
|
taskType,
|
|
@@ -3373,7 +3414,7 @@ var DispatcherImpl = class {
|
|
|
3373
3414
|
const next = this._queue.shift();
|
|
3374
3415
|
if (next === void 0) return;
|
|
3375
3416
|
next.handle.status = "running";
|
|
3376
|
-
logger$
|
|
3417
|
+
logger$16.debug({
|
|
3377
3418
|
id: next.id,
|
|
3378
3419
|
queueLength: this._queue.length
|
|
3379
3420
|
}, "Dequeued dispatch");
|
|
@@ -3386,7 +3427,7 @@ var DispatcherImpl = class {
|
|
|
3386
3427
|
_isMemoryPressured() {
|
|
3387
3428
|
const free = getAvailableMemory();
|
|
3388
3429
|
if (free < MIN_FREE_MEMORY_BYTES) {
|
|
3389
|
-
logger$
|
|
3430
|
+
logger$16.warn({
|
|
3390
3431
|
freeMB: Math.round(free / 1024 / 1024),
|
|
3391
3432
|
thresholdMB: Math.round(MIN_FREE_MEMORY_BYTES / 1024 / 1024)
|
|
3392
3433
|
}, "Memory pressure detected — holding dispatch queue");
|
|
@@ -3511,7 +3552,7 @@ function pickRecommendation(distribution, profile, totalIssues, reviewCycles, la
|
|
|
3511
3552
|
|
|
3512
3553
|
//#endregion
|
|
3513
3554
|
//#region src/modules/compiled-workflows/prompt-assembler.ts
|
|
3514
|
-
const logger$
|
|
3555
|
+
const logger$15 = createLogger("compiled-workflows:prompt-assembler");
|
|
3515
3556
|
/**
|
|
3516
3557
|
* Assemble a final prompt from a template and sections map.
|
|
3517
3558
|
*
|
|
@@ -3536,7 +3577,7 @@ function assemblePrompt(template, sections, tokenCeiling = 2200) {
|
|
|
3536
3577
|
tokenCount,
|
|
3537
3578
|
truncated: false
|
|
3538
3579
|
};
|
|
3539
|
-
logger$
|
|
3580
|
+
logger$15.warn({
|
|
3540
3581
|
tokenCount,
|
|
3541
3582
|
ceiling: tokenCeiling
|
|
3542
3583
|
}, "Prompt exceeds token ceiling — truncating optional sections");
|
|
@@ -3552,10 +3593,10 @@ function assemblePrompt(template, sections, tokenCeiling = 2200) {
|
|
|
3552
3593
|
const targetSectionTokens = Math.max(0, currentSectionTokens - overBy);
|
|
3553
3594
|
if (targetSectionTokens === 0) {
|
|
3554
3595
|
contentMap[section.name] = "";
|
|
3555
|
-
logger$
|
|
3596
|
+
logger$15.warn({ sectionName: section.name }, "Section eliminated to fit token budget");
|
|
3556
3597
|
} else {
|
|
3557
3598
|
contentMap[section.name] = truncateToTokens(section.content, targetSectionTokens);
|
|
3558
|
-
logger$
|
|
3599
|
+
logger$15.warn({
|
|
3559
3600
|
sectionName: section.name,
|
|
3560
3601
|
targetSectionTokens
|
|
3561
3602
|
}, "Section truncated to fit token budget");
|
|
@@ -3566,7 +3607,7 @@ function assemblePrompt(template, sections, tokenCeiling = 2200) {
|
|
|
3566
3607
|
}
|
|
3567
3608
|
if (tokenCount <= tokenCeiling) break;
|
|
3568
3609
|
}
|
|
3569
|
-
if (tokenCount > tokenCeiling) logger$
|
|
3610
|
+
if (tokenCount > tokenCeiling) logger$15.warn({
|
|
3570
3611
|
tokenCount,
|
|
3571
3612
|
ceiling: tokenCeiling
|
|
3572
3613
|
}, "Required sections alone exceed token ceiling — returning over-budget prompt");
|
|
@@ -3704,14 +3745,89 @@ const CodeReviewResultSchema = z.object({
|
|
|
3704
3745
|
agentVerdict: data.verdict,
|
|
3705
3746
|
verdict: computeVerdict(data.issue_list)
|
|
3706
3747
|
}));
|
|
3748
|
+
/**
|
|
3749
|
+
* Schema for the YAML output contract of the test-plan sub-agent.
|
|
3750
|
+
*
|
|
3751
|
+
* The agent must emit YAML with result, test_files, test_categories, and coverage_notes.
|
|
3752
|
+
* Example:
|
|
3753
|
+
* result: success
|
|
3754
|
+
* test_files:
|
|
3755
|
+
* - src/modules/foo/__tests__/foo.test.ts
|
|
3756
|
+
* test_categories:
|
|
3757
|
+
* - unit
|
|
3758
|
+
* - integration
|
|
3759
|
+
* coverage_notes: "AC1 covered by foo.test.ts"
|
|
3760
|
+
*/
|
|
3761
|
+
const TestPlanResultSchema = z.object({
|
|
3762
|
+
result: z.preprocess((val) => val === "failure" ? "failed" : val, z.enum(["success", "failed"])),
|
|
3763
|
+
test_files: z.array(z.string()).default([]),
|
|
3764
|
+
test_categories: z.array(z.string()).default([]),
|
|
3765
|
+
coverage_notes: z.string().default("")
|
|
3766
|
+
});
|
|
3767
|
+
/**
|
|
3768
|
+
* Schema for a single coverage gap identified during test expansion analysis.
|
|
3769
|
+
*/
|
|
3770
|
+
const CoverageGapSchema = z.object({
|
|
3771
|
+
ac_ref: z.string(),
|
|
3772
|
+
description: z.string(),
|
|
3773
|
+
gap_type: z.enum([
|
|
3774
|
+
"missing-e2e",
|
|
3775
|
+
"missing-integration",
|
|
3776
|
+
"unit-only"
|
|
3777
|
+
])
|
|
3778
|
+
});
|
|
3779
|
+
/**
|
|
3780
|
+
* Schema for a single suggested test generated during test expansion analysis.
|
|
3781
|
+
*/
|
|
3782
|
+
const SuggestedTestSchema = z.object({
|
|
3783
|
+
test_name: z.string(),
|
|
3784
|
+
test_type: z.enum([
|
|
3785
|
+
"e2e",
|
|
3786
|
+
"integration",
|
|
3787
|
+
"unit"
|
|
3788
|
+
]),
|
|
3789
|
+
description: z.string(),
|
|
3790
|
+
target_ac: z.string().optional()
|
|
3791
|
+
});
|
|
3792
|
+
/**
|
|
3793
|
+
* Schema for the YAML output contract of the test-expansion sub-agent.
|
|
3794
|
+
*
|
|
3795
|
+
* The agent must emit YAML with expansion_priority, coverage_gaps, and suggested_tests.
|
|
3796
|
+
* Example:
|
|
3797
|
+
* expansion_priority: medium
|
|
3798
|
+
* coverage_gaps:
|
|
3799
|
+
* - ac_ref: AC1
|
|
3800
|
+
* description: "Happy path not exercised at module boundary"
|
|
3801
|
+
* gap_type: missing-integration
|
|
3802
|
+
* suggested_tests:
|
|
3803
|
+
* - test_name: "runFoo integration happy path"
|
|
3804
|
+
* test_type: integration
|
|
3805
|
+
* description: "Test runFoo with real DB to verify AC1 end-to-end"
|
|
3806
|
+
* target_ac: AC1
|
|
3807
|
+
* notes: "Unit coverage is solid but integration layer is untested."
|
|
3808
|
+
*/
|
|
3809
|
+
const TestExpansionResultSchema = z.object({
|
|
3810
|
+
expansion_priority: z.preprocess((val) => [
|
|
3811
|
+
"low",
|
|
3812
|
+
"medium",
|
|
3813
|
+
"high"
|
|
3814
|
+
].includes(val) ? val : "low", z.enum([
|
|
3815
|
+
"low",
|
|
3816
|
+
"medium",
|
|
3817
|
+
"high"
|
|
3818
|
+
])),
|
|
3819
|
+
coverage_gaps: z.array(CoverageGapSchema).default([]),
|
|
3820
|
+
suggested_tests: z.array(SuggestedTestSchema).default([]),
|
|
3821
|
+
notes: z.string().optional()
|
|
3822
|
+
});
|
|
3707
3823
|
|
|
3708
3824
|
//#endregion
|
|
3709
3825
|
//#region src/modules/compiled-workflows/create-story.ts
|
|
3710
|
-
const logger$
|
|
3826
|
+
const logger$14 = createLogger("compiled-workflows:create-story");
|
|
3711
3827
|
/**
|
|
3712
3828
|
* Hard ceiling for the assembled create-story prompt.
|
|
3713
3829
|
*/
|
|
3714
|
-
const TOKEN_CEILING$
|
|
3830
|
+
const TOKEN_CEILING$4 = 3e3;
|
|
3715
3831
|
/**
|
|
3716
3832
|
* Execute the compiled create-story workflow.
|
|
3717
3833
|
*
|
|
@@ -3731,7 +3847,7 @@ const TOKEN_CEILING$2 = 3e3;
|
|
|
3731
3847
|
*/
|
|
3732
3848
|
async function runCreateStory(deps, params) {
|
|
3733
3849
|
const { epicId, storyKey, pipelineRunId } = params;
|
|
3734
|
-
logger$
|
|
3850
|
+
logger$14.debug({
|
|
3735
3851
|
epicId,
|
|
3736
3852
|
storyKey,
|
|
3737
3853
|
pipelineRunId
|
|
@@ -3741,7 +3857,7 @@ async function runCreateStory(deps, params) {
|
|
|
3741
3857
|
template = await deps.pack.getPrompt("create-story");
|
|
3742
3858
|
} catch (err) {
|
|
3743
3859
|
const error = err instanceof Error ? err.message : String(err);
|
|
3744
|
-
logger$
|
|
3860
|
+
logger$14.error({ error }, "Failed to retrieve create-story prompt template");
|
|
3745
3861
|
return {
|
|
3746
3862
|
result: "failed",
|
|
3747
3863
|
error: `Failed to retrieve prompt template: ${error}`,
|
|
@@ -3754,7 +3870,7 @@ async function runCreateStory(deps, params) {
|
|
|
3754
3870
|
const implementationDecisions = getImplementationDecisions(deps);
|
|
3755
3871
|
const epicShardContent = getEpicShard(implementationDecisions, epicId, deps.projectRoot);
|
|
3756
3872
|
const prevDevNotesContent = getPrevDevNotes(implementationDecisions, epicId);
|
|
3757
|
-
const archConstraintsContent = getArchConstraints$
|
|
3873
|
+
const archConstraintsContent = getArchConstraints$2(deps);
|
|
3758
3874
|
const storyTemplateContent = await getStoryTemplate(deps);
|
|
3759
3875
|
const { prompt, tokenCount, truncated } = assemblePrompt(template, [
|
|
3760
3876
|
{
|
|
@@ -3782,11 +3898,11 @@ async function runCreateStory(deps, params) {
|
|
|
3782
3898
|
content: storyTemplateContent,
|
|
3783
3899
|
priority: "important"
|
|
3784
3900
|
}
|
|
3785
|
-
], TOKEN_CEILING$
|
|
3786
|
-
logger$
|
|
3901
|
+
], TOKEN_CEILING$4);
|
|
3902
|
+
logger$14.debug({
|
|
3787
3903
|
tokenCount,
|
|
3788
3904
|
truncated,
|
|
3789
|
-
tokenCeiling: TOKEN_CEILING$
|
|
3905
|
+
tokenCeiling: TOKEN_CEILING$4
|
|
3790
3906
|
}, "Prompt assembled for create-story");
|
|
3791
3907
|
const handle = deps.dispatcher.dispatch({
|
|
3792
3908
|
prompt,
|
|
@@ -3800,7 +3916,7 @@ async function runCreateStory(deps, params) {
|
|
|
3800
3916
|
dispatchResult = await handle.result;
|
|
3801
3917
|
} catch (err) {
|
|
3802
3918
|
const error = err instanceof Error ? err.message : String(err);
|
|
3803
|
-
logger$
|
|
3919
|
+
logger$14.error({
|
|
3804
3920
|
epicId,
|
|
3805
3921
|
storyKey,
|
|
3806
3922
|
error
|
|
@@ -3821,7 +3937,7 @@ async function runCreateStory(deps, params) {
|
|
|
3821
3937
|
if (dispatchResult.status === "failed") {
|
|
3822
3938
|
const errorMsg = dispatchResult.parseError ?? `Dispatch failed with exit code ${dispatchResult.exitCode}`;
|
|
3823
3939
|
const stderrDetail = dispatchResult.output ? ` Output: ${dispatchResult.output}` : "";
|
|
3824
|
-
logger$
|
|
3940
|
+
logger$14.warn({
|
|
3825
3941
|
epicId,
|
|
3826
3942
|
storyKey,
|
|
3827
3943
|
exitCode: dispatchResult.exitCode
|
|
@@ -3833,7 +3949,7 @@ async function runCreateStory(deps, params) {
|
|
|
3833
3949
|
};
|
|
3834
3950
|
}
|
|
3835
3951
|
if (dispatchResult.status === "timeout") {
|
|
3836
|
-
logger$
|
|
3952
|
+
logger$14.warn({
|
|
3837
3953
|
epicId,
|
|
3838
3954
|
storyKey
|
|
3839
3955
|
}, "Create-story dispatch timed out");
|
|
@@ -3846,7 +3962,7 @@ async function runCreateStory(deps, params) {
|
|
|
3846
3962
|
if (dispatchResult.parsed === null) {
|
|
3847
3963
|
const details = dispatchResult.parseError ?? "No YAML block found in output";
|
|
3848
3964
|
const rawSnippet = dispatchResult.output ? dispatchResult.output.slice(0, 1e3) : "(empty)";
|
|
3849
|
-
logger$
|
|
3965
|
+
logger$14.warn({
|
|
3850
3966
|
epicId,
|
|
3851
3967
|
storyKey,
|
|
3852
3968
|
details,
|
|
@@ -3862,7 +3978,7 @@ async function runCreateStory(deps, params) {
|
|
|
3862
3978
|
const parseResult = CreateStoryResultSchema.safeParse(dispatchResult.parsed);
|
|
3863
3979
|
if (!parseResult.success) {
|
|
3864
3980
|
const details = parseResult.error.message;
|
|
3865
|
-
logger$
|
|
3981
|
+
logger$14.warn({
|
|
3866
3982
|
epicId,
|
|
3867
3983
|
storyKey,
|
|
3868
3984
|
details
|
|
@@ -3875,7 +3991,7 @@ async function runCreateStory(deps, params) {
|
|
|
3875
3991
|
};
|
|
3876
3992
|
}
|
|
3877
3993
|
const parsed = parseResult.data;
|
|
3878
|
-
logger$
|
|
3994
|
+
logger$14.info({
|
|
3879
3995
|
epicId,
|
|
3880
3996
|
storyKey,
|
|
3881
3997
|
storyFile: parsed.story_file,
|
|
@@ -3897,7 +4013,7 @@ function getImplementationDecisions(deps) {
|
|
|
3897
4013
|
try {
|
|
3898
4014
|
return getDecisionsByPhase(deps.db, "implementation");
|
|
3899
4015
|
} catch (err) {
|
|
3900
|
-
logger$
|
|
4016
|
+
logger$14.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve implementation decisions");
|
|
3901
4017
|
return [];
|
|
3902
4018
|
}
|
|
3903
4019
|
}
|
|
@@ -3913,13 +4029,13 @@ function getEpicShard(decisions, epicId, projectRoot) {
|
|
|
3913
4029
|
if (projectRoot) {
|
|
3914
4030
|
const fallback = readEpicShardFromFile(projectRoot, epicId);
|
|
3915
4031
|
if (fallback) {
|
|
3916
|
-
logger$
|
|
4032
|
+
logger$14.info({ epicId }, "Using file-based fallback for epic shard (decisions table empty)");
|
|
3917
4033
|
return fallback;
|
|
3918
4034
|
}
|
|
3919
4035
|
}
|
|
3920
4036
|
return "";
|
|
3921
4037
|
} catch (err) {
|
|
3922
|
-
logger$
|
|
4038
|
+
logger$14.warn({
|
|
3923
4039
|
epicId,
|
|
3924
4040
|
error: err instanceof Error ? err.message : String(err)
|
|
3925
4041
|
}, "Failed to retrieve epic shard");
|
|
@@ -3936,7 +4052,7 @@ function getPrevDevNotes(decisions, epicId) {
|
|
|
3936
4052
|
if (devNotes.length === 0) return "";
|
|
3937
4053
|
return devNotes[devNotes.length - 1].value;
|
|
3938
4054
|
} catch (err) {
|
|
3939
|
-
logger$
|
|
4055
|
+
logger$14.warn({
|
|
3940
4056
|
epicId,
|
|
3941
4057
|
error: err instanceof Error ? err.message : String(err)
|
|
3942
4058
|
}, "Failed to retrieve prev dev notes");
|
|
@@ -3948,7 +4064,7 @@ function getPrevDevNotes(decisions, epicId) {
|
|
|
3948
4064
|
* Looks for decisions with phase='solutioning', category='architecture'.
|
|
3949
4065
|
* Falls back to reading _bmad-output/architecture/architecture.md on disk if decisions are empty.
|
|
3950
4066
|
*/
|
|
3951
|
-
function getArchConstraints$
|
|
4067
|
+
function getArchConstraints$2(deps) {
|
|
3952
4068
|
try {
|
|
3953
4069
|
const decisions = getDecisionsByPhase(deps.db, "solutioning");
|
|
3954
4070
|
const constraints = decisions.filter((d) => d.category === "architecture");
|
|
@@ -3956,13 +4072,13 @@ function getArchConstraints$1(deps) {
|
|
|
3956
4072
|
if (deps.projectRoot) {
|
|
3957
4073
|
const fallback = readArchConstraintsFromFile(deps.projectRoot);
|
|
3958
4074
|
if (fallback) {
|
|
3959
|
-
logger$
|
|
4075
|
+
logger$14.info("Using file-based fallback for architecture constraints (decisions table empty)");
|
|
3960
4076
|
return fallback;
|
|
3961
4077
|
}
|
|
3962
4078
|
}
|
|
3963
4079
|
return "";
|
|
3964
4080
|
} catch (err) {
|
|
3965
|
-
logger$
|
|
4081
|
+
logger$14.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve architecture constraints");
|
|
3966
4082
|
return "";
|
|
3967
4083
|
}
|
|
3968
4084
|
}
|
|
@@ -3982,7 +4098,7 @@ function readEpicShardFromFile(projectRoot, epicId) {
|
|
|
3982
4098
|
const match = pattern.exec(content);
|
|
3983
4099
|
return match ? match[0].trim() : "";
|
|
3984
4100
|
} catch (err) {
|
|
3985
|
-
logger$
|
|
4101
|
+
logger$14.warn({
|
|
3986
4102
|
epicId,
|
|
3987
4103
|
error: err instanceof Error ? err.message : String(err)
|
|
3988
4104
|
}, "File-based epic shard fallback failed");
|
|
@@ -4005,7 +4121,7 @@ function readArchConstraintsFromFile(projectRoot) {
|
|
|
4005
4121
|
const content = readFileSync$1(archPath, "utf-8");
|
|
4006
4122
|
return content.slice(0, 1500);
|
|
4007
4123
|
} catch (err) {
|
|
4008
|
-
logger$
|
|
4124
|
+
logger$14.warn({ error: err instanceof Error ? err.message : String(err) }, "File-based architecture fallback failed");
|
|
4009
4125
|
return "";
|
|
4010
4126
|
}
|
|
4011
4127
|
}
|
|
@@ -4018,14 +4134,14 @@ async function getStoryTemplate(deps) {
|
|
|
4018
4134
|
try {
|
|
4019
4135
|
return await deps.pack.getTemplate("story");
|
|
4020
4136
|
} catch (err) {
|
|
4021
|
-
logger$
|
|
4137
|
+
logger$14.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve story template from pack");
|
|
4022
4138
|
return "";
|
|
4023
4139
|
}
|
|
4024
4140
|
}
|
|
4025
4141
|
|
|
4026
4142
|
//#endregion
|
|
4027
4143
|
//#region src/modules/compiled-workflows/git-helpers.ts
|
|
4028
|
-
const logger$
|
|
4144
|
+
const logger$13 = createLogger("compiled-workflows:git-helpers");
|
|
4029
4145
|
/**
|
|
4030
4146
|
* Capture the full git diff for HEAD (working tree vs current commit).
|
|
4031
4147
|
*
|
|
@@ -4149,7 +4265,7 @@ async function runGitCommand(args, cwd, logLabel) {
|
|
|
4149
4265
|
stderr += chunk.toString("utf-8");
|
|
4150
4266
|
});
|
|
4151
4267
|
proc.on("error", (err) => {
|
|
4152
|
-
logger$
|
|
4268
|
+
logger$13.warn({
|
|
4153
4269
|
label: logLabel,
|
|
4154
4270
|
cwd,
|
|
4155
4271
|
error: err.message
|
|
@@ -4158,7 +4274,7 @@ async function runGitCommand(args, cwd, logLabel) {
|
|
|
4158
4274
|
});
|
|
4159
4275
|
proc.on("close", (code) => {
|
|
4160
4276
|
if (code !== 0) {
|
|
4161
|
-
logger$
|
|
4277
|
+
logger$13.warn({
|
|
4162
4278
|
label: logLabel,
|
|
4163
4279
|
cwd,
|
|
4164
4280
|
code,
|
|
@@ -4174,7 +4290,7 @@ async function runGitCommand(args, cwd, logLabel) {
|
|
|
4174
4290
|
|
|
4175
4291
|
//#endregion
|
|
4176
4292
|
//#region src/modules/implementation-orchestrator/project-findings.ts
|
|
4177
|
-
const logger$
|
|
4293
|
+
const logger$12 = createLogger("project-findings");
|
|
4178
4294
|
/** Maximum character length for the findings summary */
|
|
4179
4295
|
const MAX_CHARS = 2e3;
|
|
4180
4296
|
/**
|
|
@@ -4229,7 +4345,7 @@ function getProjectFindings(db) {
|
|
|
4229
4345
|
if (summary.length > MAX_CHARS) summary = summary.slice(0, MAX_CHARS - 3) + "...";
|
|
4230
4346
|
return summary;
|
|
4231
4347
|
} catch (err) {
|
|
4232
|
-
logger$
|
|
4348
|
+
logger$12.warn({ err }, "Failed to query project findings (graceful fallback)");
|
|
4233
4349
|
return "";
|
|
4234
4350
|
}
|
|
4235
4351
|
}
|
|
@@ -4252,11 +4368,11 @@ function extractRecurringPatterns(outcomes) {
|
|
|
4252
4368
|
|
|
4253
4369
|
//#endregion
|
|
4254
4370
|
//#region src/modules/compiled-workflows/dev-story.ts
|
|
4255
|
-
const logger$
|
|
4371
|
+
const logger$11 = createLogger("compiled-workflows:dev-story");
|
|
4256
4372
|
/** Hard token ceiling for the assembled dev-story prompt */
|
|
4257
|
-
const TOKEN_CEILING$
|
|
4373
|
+
const TOKEN_CEILING$3 = 24e3;
|
|
4258
4374
|
/** Default timeout for dev-story dispatches in milliseconds (30 min) */
|
|
4259
|
-
const DEFAULT_TIMEOUT_MS = 18e5;
|
|
4375
|
+
const DEFAULT_TIMEOUT_MS$1 = 18e5;
|
|
4260
4376
|
/** Default Vitest test patterns injected when no test-pattern decisions exist */
|
|
4261
4377
|
const DEFAULT_VITEST_PATTERNS = `## Test Patterns (defaults)
|
|
4262
4378
|
- Framework: Vitest (NOT jest — --testPathPattern flag does not work, use -- "pattern")
|
|
@@ -4277,7 +4393,7 @@ const DEFAULT_VITEST_PATTERNS = `## Test Patterns (defaults)
|
|
|
4277
4393
|
*/
|
|
4278
4394
|
async function runDevStory(deps, params) {
|
|
4279
4395
|
const { storyKey, storyFilePath, taskScope, priorFiles } = params;
|
|
4280
|
-
logger$
|
|
4396
|
+
logger$11.info({
|
|
4281
4397
|
storyKey,
|
|
4282
4398
|
storyFilePath
|
|
4283
4399
|
}, "Starting compiled dev-story workflow");
|
|
@@ -4319,10 +4435,10 @@ async function runDevStory(deps, params) {
|
|
|
4319
4435
|
let template;
|
|
4320
4436
|
try {
|
|
4321
4437
|
template = await deps.pack.getPrompt("dev-story");
|
|
4322
|
-
logger$
|
|
4438
|
+
logger$11.debug({ storyKey }, "Retrieved dev-story prompt template from pack");
|
|
4323
4439
|
} catch (err) {
|
|
4324
4440
|
const error = err instanceof Error ? err.message : String(err);
|
|
4325
|
-
logger$
|
|
4441
|
+
logger$11.error({
|
|
4326
4442
|
storyKey,
|
|
4327
4443
|
error
|
|
4328
4444
|
}, "Failed to retrieve dev-story prompt template");
|
|
@@ -4333,14 +4449,14 @@ async function runDevStory(deps, params) {
|
|
|
4333
4449
|
storyContent = await readFile$1(storyFilePath, "utf-8");
|
|
4334
4450
|
} catch (err) {
|
|
4335
4451
|
if (err.code === "ENOENT") {
|
|
4336
|
-
logger$
|
|
4452
|
+
logger$11.error({
|
|
4337
4453
|
storyKey,
|
|
4338
4454
|
storyFilePath
|
|
4339
4455
|
}, "Story file not found");
|
|
4340
4456
|
return makeFailureResult("story_file_not_found");
|
|
4341
4457
|
}
|
|
4342
4458
|
const error = err instanceof Error ? err.message : String(err);
|
|
4343
|
-
logger$
|
|
4459
|
+
logger$11.error({
|
|
4344
4460
|
storyKey,
|
|
4345
4461
|
storyFilePath,
|
|
4346
4462
|
error
|
|
@@ -4348,7 +4464,7 @@ async function runDevStory(deps, params) {
|
|
|
4348
4464
|
return makeFailureResult(`story_file_read_error: ${error}`);
|
|
4349
4465
|
}
|
|
4350
4466
|
if (storyContent.trim().length === 0) {
|
|
4351
|
-
logger$
|
|
4467
|
+
logger$11.error({
|
|
4352
4468
|
storyKey,
|
|
4353
4469
|
storyFilePath
|
|
4354
4470
|
}, "Story file is empty");
|
|
@@ -4360,17 +4476,17 @@ async function runDevStory(deps, params) {
|
|
|
4360
4476
|
const testPatternDecisions = solutioningDecisions.filter((d) => d.category === "test-patterns");
|
|
4361
4477
|
if (testPatternDecisions.length > 0) {
|
|
4362
4478
|
testPatternsContent = "## Test Patterns\n" + testPatternDecisions.map((d) => `- ${d.key}: ${d.value}`).join("\n");
|
|
4363
|
-
logger$
|
|
4479
|
+
logger$11.debug({
|
|
4364
4480
|
storyKey,
|
|
4365
4481
|
count: testPatternDecisions.length
|
|
4366
4482
|
}, "Loaded test patterns from decision store");
|
|
4367
4483
|
} else {
|
|
4368
4484
|
testPatternsContent = DEFAULT_VITEST_PATTERNS;
|
|
4369
|
-
logger$
|
|
4485
|
+
logger$11.debug({ storyKey }, "No test-pattern decisions found — using default Vitest patterns");
|
|
4370
4486
|
}
|
|
4371
4487
|
} catch (err) {
|
|
4372
4488
|
const error = err instanceof Error ? err.message : String(err);
|
|
4373
|
-
logger$
|
|
4489
|
+
logger$11.warn({
|
|
4374
4490
|
storyKey,
|
|
4375
4491
|
error
|
|
4376
4492
|
}, "Failed to load test patterns — using defaults");
|
|
@@ -4385,12 +4501,29 @@ async function runDevStory(deps, params) {
|
|
|
4385
4501
|
const findings = getProjectFindings(deps.db);
|
|
4386
4502
|
if (findings.length > 0) {
|
|
4387
4503
|
priorFindingsContent = "Previous pipeline runs encountered these issues — avoid repeating them:\n\n" + findings;
|
|
4388
|
-
logger$
|
|
4504
|
+
logger$11.debug({
|
|
4389
4505
|
storyKey,
|
|
4390
4506
|
findingsLen: findings.length
|
|
4391
4507
|
}, "Injecting prior findings into dev-story prompt");
|
|
4392
4508
|
}
|
|
4393
4509
|
} catch {}
|
|
4510
|
+
let testPlanContent = "";
|
|
4511
|
+
try {
|
|
4512
|
+
const testPlanDecisions = getDecisionsByCategory(deps.db, "test-plan");
|
|
4513
|
+
const matchingPlan = testPlanDecisions.find((d) => d.key === storyKey);
|
|
4514
|
+
if (matchingPlan) {
|
|
4515
|
+
const plan = JSON.parse(matchingPlan.value);
|
|
4516
|
+
const parts = ["## Test Plan"];
|
|
4517
|
+
if (plan.test_files && plan.test_files.length > 0) {
|
|
4518
|
+
parts.push("\n### Test Files");
|
|
4519
|
+
for (const f of plan.test_files) parts.push(`- ${f}`);
|
|
4520
|
+
}
|
|
4521
|
+
if (plan.test_categories && plan.test_categories.length > 0) parts.push(`\n### Categories: ${plan.test_categories.join(", ")}`);
|
|
4522
|
+
if (plan.coverage_notes) parts.push(`\n### Coverage Notes\n${plan.coverage_notes}`);
|
|
4523
|
+
testPlanContent = parts.join("\n");
|
|
4524
|
+
logger$11.debug({ storyKey }, "Injecting test plan into dev-story prompt");
|
|
4525
|
+
}
|
|
4526
|
+
} catch {}
|
|
4394
4527
|
const sections = [
|
|
4395
4528
|
{
|
|
4396
4529
|
name: "story_content",
|
|
@@ -4422,17 +4555,22 @@ async function runDevStory(deps, params) {
|
|
|
4422
4555
|
content: testPatternsContent,
|
|
4423
4556
|
priority: "optional"
|
|
4424
4557
|
},
|
|
4558
|
+
{
|
|
4559
|
+
name: "test_plan",
|
|
4560
|
+
content: testPlanContent,
|
|
4561
|
+
priority: "optional"
|
|
4562
|
+
},
|
|
4425
4563
|
{
|
|
4426
4564
|
name: "prior_findings",
|
|
4427
4565
|
content: priorFindingsContent,
|
|
4428
4566
|
priority: "optional"
|
|
4429
4567
|
}
|
|
4430
4568
|
];
|
|
4431
|
-
const { prompt, tokenCount, truncated } = assemblePrompt(template, sections, TOKEN_CEILING$
|
|
4432
|
-
logger$
|
|
4569
|
+
const { prompt, tokenCount, truncated } = assemblePrompt(template, sections, TOKEN_CEILING$3);
|
|
4570
|
+
logger$11.info({
|
|
4433
4571
|
storyKey,
|
|
4434
4572
|
tokenCount,
|
|
4435
|
-
ceiling: TOKEN_CEILING$
|
|
4573
|
+
ceiling: TOKEN_CEILING$3,
|
|
4436
4574
|
truncated
|
|
4437
4575
|
}, "Assembled dev-story prompt");
|
|
4438
4576
|
let dispatchResult;
|
|
@@ -4441,14 +4579,14 @@ async function runDevStory(deps, params) {
|
|
|
4441
4579
|
prompt,
|
|
4442
4580
|
agent: "claude-code",
|
|
4443
4581
|
taskType: "dev-story",
|
|
4444
|
-
timeout: DEFAULT_TIMEOUT_MS,
|
|
4582
|
+
timeout: DEFAULT_TIMEOUT_MS$1,
|
|
4445
4583
|
outputSchema: DevStoryResultSchema,
|
|
4446
4584
|
...deps.projectRoot !== void 0 ? { workingDirectory: deps.projectRoot } : {}
|
|
4447
4585
|
});
|
|
4448
4586
|
dispatchResult = await handle.result;
|
|
4449
4587
|
} catch (err) {
|
|
4450
4588
|
const error = err instanceof Error ? err.message : String(err);
|
|
4451
|
-
logger$
|
|
4589
|
+
logger$11.error({
|
|
4452
4590
|
storyKey,
|
|
4453
4591
|
error
|
|
4454
4592
|
}, "Dispatch threw an unexpected error");
|
|
@@ -4459,11 +4597,11 @@ async function runDevStory(deps, params) {
|
|
|
4459
4597
|
output: dispatchResult.tokenEstimate.output
|
|
4460
4598
|
};
|
|
4461
4599
|
if (dispatchResult.status === "timeout") {
|
|
4462
|
-
logger$
|
|
4600
|
+
logger$11.error({
|
|
4463
4601
|
storyKey,
|
|
4464
4602
|
durationMs: dispatchResult.durationMs
|
|
4465
4603
|
}, "Dev-story dispatch timed out");
|
|
4466
|
-
if (dispatchResult.output.length > 0) logger$
|
|
4604
|
+
if (dispatchResult.output.length > 0) logger$11.info({
|
|
4467
4605
|
storyKey,
|
|
4468
4606
|
partialOutput: dispatchResult.output.slice(0, 500)
|
|
4469
4607
|
}, "Partial output before timeout");
|
|
@@ -4473,12 +4611,12 @@ async function runDevStory(deps, params) {
|
|
|
4473
4611
|
};
|
|
4474
4612
|
}
|
|
4475
4613
|
if (dispatchResult.status === "failed" || dispatchResult.exitCode !== 0) {
|
|
4476
|
-
logger$
|
|
4614
|
+
logger$11.error({
|
|
4477
4615
|
storyKey,
|
|
4478
4616
|
exitCode: dispatchResult.exitCode,
|
|
4479
4617
|
status: dispatchResult.status
|
|
4480
4618
|
}, "Dev-story dispatch failed");
|
|
4481
|
-
if (dispatchResult.output.length > 0) logger$
|
|
4619
|
+
if (dispatchResult.output.length > 0) logger$11.info({
|
|
4482
4620
|
storyKey,
|
|
4483
4621
|
partialOutput: dispatchResult.output.slice(0, 500)
|
|
4484
4622
|
}, "Partial output from failed dispatch");
|
|
@@ -4490,7 +4628,7 @@ async function runDevStory(deps, params) {
|
|
|
4490
4628
|
if (dispatchResult.parseError !== null || dispatchResult.parsed === null) {
|
|
4491
4629
|
const details = dispatchResult.parseError ?? "parsed result was null";
|
|
4492
4630
|
const rawSnippet = dispatchResult.output ? dispatchResult.output.slice(0, 1e3) : "(empty)";
|
|
4493
|
-
logger$
|
|
4631
|
+
logger$11.error({
|
|
4494
4632
|
storyKey,
|
|
4495
4633
|
parseError: details,
|
|
4496
4634
|
rawOutputSnippet: rawSnippet
|
|
@@ -4498,12 +4636,12 @@ async function runDevStory(deps, params) {
|
|
|
4498
4636
|
let filesModified = [];
|
|
4499
4637
|
try {
|
|
4500
4638
|
filesModified = await getGitChangedFiles(deps.projectRoot ?? process.cwd());
|
|
4501
|
-
if (filesModified.length > 0) logger$
|
|
4639
|
+
if (filesModified.length > 0) logger$11.info({
|
|
4502
4640
|
storyKey,
|
|
4503
4641
|
fileCount: filesModified.length
|
|
4504
4642
|
}, "Recovered files_modified from git status (YAML fallback)");
|
|
4505
4643
|
} catch (err) {
|
|
4506
|
-
logger$
|
|
4644
|
+
logger$11.warn({
|
|
4507
4645
|
storyKey,
|
|
4508
4646
|
error: err instanceof Error ? err.message : String(err)
|
|
4509
4647
|
}, "Failed to recover files_modified from git");
|
|
@@ -4520,7 +4658,7 @@ async function runDevStory(deps, params) {
|
|
|
4520
4658
|
};
|
|
4521
4659
|
}
|
|
4522
4660
|
const parsed = dispatchResult.parsed;
|
|
4523
|
-
logger$
|
|
4661
|
+
logger$11.info({
|
|
4524
4662
|
storyKey,
|
|
4525
4663
|
result: parsed.result,
|
|
4526
4664
|
acMet: parsed.ac_met.length
|
|
@@ -4659,13 +4797,13 @@ function extractFilesInScope(storyContent) {
|
|
|
4659
4797
|
|
|
4660
4798
|
//#endregion
|
|
4661
4799
|
//#region src/modules/compiled-workflows/code-review.ts
|
|
4662
|
-
const logger$
|
|
4800
|
+
const logger$10 = createLogger("compiled-workflows:code-review");
|
|
4663
4801
|
/**
|
|
4664
4802
|
* Hard token ceiling for the assembled code-review prompt (50,000 tokens).
|
|
4665
4803
|
* Quality reviews require seeing actual code diffs, not just file names.
|
|
4666
4804
|
* // TODO: consider externalizing to pack config when multiple packs exist
|
|
4667
4805
|
*/
|
|
4668
|
-
const TOKEN_CEILING = 1e5;
|
|
4806
|
+
const TOKEN_CEILING$2 = 1e5;
|
|
4669
4807
|
/**
|
|
4670
4808
|
* Default fallback result when dispatch fails or times out.
|
|
4671
4809
|
* Uses NEEDS_MINOR_FIXES (not NEEDS_MAJOR_REWORK) so a parse/schema failure
|
|
@@ -4702,7 +4840,7 @@ function defaultFailResult(error, tokenUsage) {
|
|
|
4702
4840
|
async function runCodeReview(deps, params) {
|
|
4703
4841
|
const { storyKey, storyFilePath, workingDirectory, pipelineRunId, filesModified, previousIssues } = params;
|
|
4704
4842
|
const cwd = workingDirectory ?? process.cwd();
|
|
4705
|
-
logger$
|
|
4843
|
+
logger$10.debug({
|
|
4706
4844
|
storyKey,
|
|
4707
4845
|
storyFilePath,
|
|
4708
4846
|
cwd,
|
|
@@ -4713,7 +4851,7 @@ async function runCodeReview(deps, params) {
|
|
|
4713
4851
|
template = await deps.pack.getPrompt("code-review");
|
|
4714
4852
|
} catch (err) {
|
|
4715
4853
|
const error = err instanceof Error ? err.message : String(err);
|
|
4716
|
-
logger$
|
|
4854
|
+
logger$10.error({ error }, "Failed to retrieve code-review prompt template");
|
|
4717
4855
|
return defaultFailResult(`Failed to retrieve prompt template: ${error}`, {
|
|
4718
4856
|
input: 0,
|
|
4719
4857
|
output: 0
|
|
@@ -4724,7 +4862,7 @@ async function runCodeReview(deps, params) {
|
|
|
4724
4862
|
storyContent = await readFile$1(storyFilePath, "utf-8");
|
|
4725
4863
|
} catch (err) {
|
|
4726
4864
|
const error = err instanceof Error ? err.message : String(err);
|
|
4727
|
-
logger$
|
|
4865
|
+
logger$10.error({
|
|
4728
4866
|
storyFilePath,
|
|
4729
4867
|
error
|
|
4730
4868
|
}, "Failed to read story file");
|
|
@@ -4733,7 +4871,7 @@ async function runCodeReview(deps, params) {
|
|
|
4733
4871
|
output: 0
|
|
4734
4872
|
});
|
|
4735
4873
|
}
|
|
4736
|
-
const archConstraintsContent = getArchConstraints(deps);
|
|
4874
|
+
const archConstraintsContent = getArchConstraints$1(deps);
|
|
4737
4875
|
const templateTokens = countTokens(template);
|
|
4738
4876
|
const storyTokens = countTokens(storyContent);
|
|
4739
4877
|
const constraintTokens = countTokens(archConstraintsContent);
|
|
@@ -4742,16 +4880,16 @@ async function runCodeReview(deps, params) {
|
|
|
4742
4880
|
if (filesModified && filesModified.length > 0) {
|
|
4743
4881
|
const scopedDiff = await getGitDiffForFiles(filesModified, cwd);
|
|
4744
4882
|
const scopedTotal = nonDiffTokens + countTokens(scopedDiff);
|
|
4745
|
-
if (scopedTotal <= TOKEN_CEILING) {
|
|
4883
|
+
if (scopedTotal <= TOKEN_CEILING$2) {
|
|
4746
4884
|
gitDiffContent = scopedDiff;
|
|
4747
|
-
logger$
|
|
4885
|
+
logger$10.debug({
|
|
4748
4886
|
fileCount: filesModified.length,
|
|
4749
4887
|
tokenCount: scopedTotal
|
|
4750
4888
|
}, "Using scoped file diff");
|
|
4751
4889
|
} else {
|
|
4752
|
-
logger$
|
|
4890
|
+
logger$10.warn({
|
|
4753
4891
|
estimatedTotal: scopedTotal,
|
|
4754
|
-
ceiling: TOKEN_CEILING,
|
|
4892
|
+
ceiling: TOKEN_CEILING$2,
|
|
4755
4893
|
fileCount: filesModified.length
|
|
4756
4894
|
}, "Scoped diff exceeds token ceiling — falling back to stat-only summary");
|
|
4757
4895
|
gitDiffContent = await getGitDiffStatSummary(cwd);
|
|
@@ -4761,11 +4899,11 @@ async function runCodeReview(deps, params) {
|
|
|
4761
4899
|
await stageIntentToAdd(changedFiles, cwd);
|
|
4762
4900
|
const fullDiff = await getGitDiffSummary(cwd);
|
|
4763
4901
|
const fullTotal = nonDiffTokens + countTokens(fullDiff);
|
|
4764
|
-
if (fullTotal <= TOKEN_CEILING) gitDiffContent = fullDiff;
|
|
4902
|
+
if (fullTotal <= TOKEN_CEILING$2) gitDiffContent = fullDiff;
|
|
4765
4903
|
else {
|
|
4766
|
-
logger$
|
|
4904
|
+
logger$10.warn({
|
|
4767
4905
|
estimatedTotal: fullTotal,
|
|
4768
|
-
ceiling: TOKEN_CEILING
|
|
4906
|
+
ceiling: TOKEN_CEILING$2
|
|
4769
4907
|
}, "Full git diff would exceed token ceiling — using stat-only summary");
|
|
4770
4908
|
gitDiffContent = await getGitDiffStatSummary(cwd);
|
|
4771
4909
|
}
|
|
@@ -4783,7 +4921,7 @@ async function runCodeReview(deps, params) {
|
|
|
4783
4921
|
const findings = getProjectFindings(deps.db);
|
|
4784
4922
|
if (findings.length > 0) {
|
|
4785
4923
|
priorFindingsContent = "Previous reviews found these recurring patterns — pay special attention:\n\n" + findings;
|
|
4786
|
-
logger$
|
|
4924
|
+
logger$10.debug({
|
|
4787
4925
|
storyKey,
|
|
4788
4926
|
findingsLen: findings.length
|
|
4789
4927
|
}, "Injecting prior findings into code-review prompt");
|
|
@@ -4816,12 +4954,12 @@ async function runCodeReview(deps, params) {
|
|
|
4816
4954
|
priority: "optional"
|
|
4817
4955
|
}
|
|
4818
4956
|
];
|
|
4819
|
-
const assembleResult = assemblePrompt(template, sections, TOKEN_CEILING);
|
|
4820
|
-
if (assembleResult.truncated) logger$
|
|
4957
|
+
const assembleResult = assemblePrompt(template, sections, TOKEN_CEILING$2);
|
|
4958
|
+
if (assembleResult.truncated) logger$10.warn({
|
|
4821
4959
|
storyKey,
|
|
4822
4960
|
tokenCount: assembleResult.tokenCount
|
|
4823
4961
|
}, "Code-review prompt truncated to fit token ceiling");
|
|
4824
|
-
logger$
|
|
4962
|
+
logger$10.debug({
|
|
4825
4963
|
storyKey,
|
|
4826
4964
|
tokenCount: assembleResult.tokenCount,
|
|
4827
4965
|
truncated: assembleResult.truncated
|
|
@@ -4839,7 +4977,7 @@ async function runCodeReview(deps, params) {
|
|
|
4839
4977
|
dispatchResult = await handle.result;
|
|
4840
4978
|
} catch (err) {
|
|
4841
4979
|
const error = err instanceof Error ? err.message : String(err);
|
|
4842
|
-
logger$
|
|
4980
|
+
logger$10.error({
|
|
4843
4981
|
storyKey,
|
|
4844
4982
|
error
|
|
4845
4983
|
}, "Code-review dispatch threw unexpected error");
|
|
@@ -4855,7 +4993,7 @@ async function runCodeReview(deps, params) {
|
|
|
4855
4993
|
const rawOutput = dispatchResult.output ?? void 0;
|
|
4856
4994
|
if (dispatchResult.status === "failed") {
|
|
4857
4995
|
const errorMsg = `Dispatch status: failed. Exit code: ${dispatchResult.exitCode}. ${dispatchResult.parseError ?? ""} ${dispatchResult.output ? `Stderr: ${dispatchResult.output}` : ""}`.trim();
|
|
4858
|
-
logger$
|
|
4996
|
+
logger$10.warn({
|
|
4859
4997
|
storyKey,
|
|
4860
4998
|
exitCode: dispatchResult.exitCode
|
|
4861
4999
|
}, "Code-review dispatch failed");
|
|
@@ -4865,7 +5003,7 @@ async function runCodeReview(deps, params) {
|
|
|
4865
5003
|
};
|
|
4866
5004
|
}
|
|
4867
5005
|
if (dispatchResult.status === "timeout") {
|
|
4868
|
-
logger$
|
|
5006
|
+
logger$10.warn({ storyKey }, "Code-review dispatch timed out");
|
|
4869
5007
|
return {
|
|
4870
5008
|
...defaultFailResult("Dispatch status: timeout. The agent did not complete within the allowed time.", tokenUsage),
|
|
4871
5009
|
rawOutput
|
|
@@ -4873,7 +5011,7 @@ async function runCodeReview(deps, params) {
|
|
|
4873
5011
|
}
|
|
4874
5012
|
if (dispatchResult.parsed === null) {
|
|
4875
5013
|
const details = dispatchResult.parseError ?? "No YAML block found in output";
|
|
4876
|
-
logger$
|
|
5014
|
+
logger$10.warn({
|
|
4877
5015
|
storyKey,
|
|
4878
5016
|
details
|
|
4879
5017
|
}, "Code-review output schema validation failed");
|
|
@@ -4890,7 +5028,7 @@ async function runCodeReview(deps, params) {
|
|
|
4890
5028
|
const parseResult = CodeReviewResultSchema.safeParse(dispatchResult.parsed);
|
|
4891
5029
|
if (!parseResult.success) {
|
|
4892
5030
|
const details = parseResult.error.message;
|
|
4893
|
-
logger$
|
|
5031
|
+
logger$10.warn({
|
|
4894
5032
|
storyKey,
|
|
4895
5033
|
details
|
|
4896
5034
|
}, "Code-review output failed schema validation");
|
|
@@ -4905,13 +5043,13 @@ async function runCodeReview(deps, params) {
|
|
|
4905
5043
|
};
|
|
4906
5044
|
}
|
|
4907
5045
|
const parsed = parseResult.data;
|
|
4908
|
-
if (parsed.agentVerdict !== parsed.verdict) logger$
|
|
5046
|
+
if (parsed.agentVerdict !== parsed.verdict) logger$10.info({
|
|
4909
5047
|
storyKey,
|
|
4910
5048
|
agentVerdict: parsed.agentVerdict,
|
|
4911
5049
|
pipelineVerdict: parsed.verdict,
|
|
4912
5050
|
issues: parsed.issues
|
|
4913
5051
|
}, "Pipeline overrode agent verdict based on issue severities");
|
|
4914
|
-
logger$
|
|
5052
|
+
logger$10.info({
|
|
4915
5053
|
storyKey,
|
|
4916
5054
|
verdict: parsed.verdict,
|
|
4917
5055
|
issues: parsed.issues
|
|
@@ -4929,6 +5067,384 @@ async function runCodeReview(deps, params) {
|
|
|
4929
5067
|
* Retrieve architecture constraints from the decision store.
|
|
4930
5068
|
* Looks for decisions with phase='solutioning', category='architecture'.
|
|
4931
5069
|
*/
|
|
5070
|
+
function getArchConstraints$1(deps) {
|
|
5071
|
+
try {
|
|
5072
|
+
const decisions = getDecisionsByPhase(deps.db, "solutioning");
|
|
5073
|
+
const constraints = decisions.filter((d) => d.category === "architecture");
|
|
5074
|
+
if (constraints.length === 0) return "";
|
|
5075
|
+
return constraints.map((d) => `${d.key}: ${d.value}`).join("\n");
|
|
5076
|
+
} catch (err) {
|
|
5077
|
+
logger$10.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve architecture constraints");
|
|
5078
|
+
return "";
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
|
|
5082
|
+
//#endregion
|
|
5083
|
+
//#region src/modules/compiled-workflows/test-plan.ts
|
|
5084
|
+
const logger$9 = createLogger("compiled-workflows:test-plan");
|
|
5085
|
+
/** Hard token ceiling for the assembled test-plan prompt */
|
|
5086
|
+
const TOKEN_CEILING$1 = 8e3;
|
|
5087
|
+
/** Default timeout for test-plan dispatches in milliseconds (5 min — lightweight call) */
|
|
5088
|
+
const DEFAULT_TIMEOUT_MS = 3e5;
|
|
5089
|
+
/**
|
|
5090
|
+
* Execute the compiled test-plan workflow.
|
|
5091
|
+
*
|
|
5092
|
+
* @param deps - Injected dependencies (db, pack, contextCompiler, dispatcher)
|
|
5093
|
+
* @param params - Parameters (storyKey, storyFilePath, pipelineRunId)
|
|
5094
|
+
* @returns TestPlanResult with result, test_files, test_categories, coverage_notes, tokenUsage
|
|
5095
|
+
*/
|
|
5096
|
+
async function runTestPlan(deps, params) {
|
|
5097
|
+
const { storyKey, storyFilePath, pipelineRunId } = params;
|
|
5098
|
+
logger$9.info({
|
|
5099
|
+
storyKey,
|
|
5100
|
+
storyFilePath
|
|
5101
|
+
}, "Starting compiled test-plan workflow");
|
|
5102
|
+
let template;
|
|
5103
|
+
try {
|
|
5104
|
+
template = await deps.pack.getPrompt("test-plan");
|
|
5105
|
+
logger$9.debug({ storyKey }, "Retrieved test-plan prompt template from pack");
|
|
5106
|
+
} catch (err) {
|
|
5107
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
5108
|
+
logger$9.warn({
|
|
5109
|
+
storyKey,
|
|
5110
|
+
error
|
|
5111
|
+
}, "Failed to retrieve test-plan prompt template");
|
|
5112
|
+
return makeTestPlanFailureResult(`template_load_failed: ${error}`);
|
|
5113
|
+
}
|
|
5114
|
+
let storyContent;
|
|
5115
|
+
try {
|
|
5116
|
+
storyContent = await readFile$1(storyFilePath, "utf-8");
|
|
5117
|
+
} catch (err) {
|
|
5118
|
+
if (err.code === "ENOENT") {
|
|
5119
|
+
logger$9.warn({
|
|
5120
|
+
storyKey,
|
|
5121
|
+
storyFilePath
|
|
5122
|
+
}, "Story file not found for test planning");
|
|
5123
|
+
return makeTestPlanFailureResult("story_file_not_found");
|
|
5124
|
+
}
|
|
5125
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
5126
|
+
logger$9.warn({
|
|
5127
|
+
storyKey,
|
|
5128
|
+
storyFilePath,
|
|
5129
|
+
error
|
|
5130
|
+
}, "Failed to read story file for test planning");
|
|
5131
|
+
return makeTestPlanFailureResult(`story_file_read_error: ${error}`);
|
|
5132
|
+
}
|
|
5133
|
+
const { prompt, tokenCount, truncated } = assemblePrompt(template, [{
|
|
5134
|
+
name: "story_content",
|
|
5135
|
+
content: storyContent,
|
|
5136
|
+
priority: "required"
|
|
5137
|
+
}], TOKEN_CEILING$1);
|
|
5138
|
+
logger$9.info({
|
|
5139
|
+
storyKey,
|
|
5140
|
+
tokenCount,
|
|
5141
|
+
ceiling: TOKEN_CEILING$1,
|
|
5142
|
+
truncated
|
|
5143
|
+
}, "Assembled test-plan prompt");
|
|
5144
|
+
let dispatchResult;
|
|
5145
|
+
try {
|
|
5146
|
+
const handle = deps.dispatcher.dispatch({
|
|
5147
|
+
prompt,
|
|
5148
|
+
agent: "claude-code",
|
|
5149
|
+
taskType: "test-plan",
|
|
5150
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
5151
|
+
outputSchema: TestPlanResultSchema,
|
|
5152
|
+
...deps.projectRoot !== void 0 ? { workingDirectory: deps.projectRoot } : {}
|
|
5153
|
+
});
|
|
5154
|
+
dispatchResult = await handle.result;
|
|
5155
|
+
} catch (err) {
|
|
5156
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
5157
|
+
logger$9.warn({
|
|
5158
|
+
storyKey,
|
|
5159
|
+
error
|
|
5160
|
+
}, "Test-plan dispatch threw an unexpected error");
|
|
5161
|
+
return makeTestPlanFailureResult(`dispatch_error: ${error}`);
|
|
5162
|
+
}
|
|
5163
|
+
const tokenUsage = {
|
|
5164
|
+
input: dispatchResult.tokenEstimate.input,
|
|
5165
|
+
output: dispatchResult.tokenEstimate.output
|
|
5166
|
+
};
|
|
5167
|
+
if (dispatchResult.status === "timeout") {
|
|
5168
|
+
logger$9.warn({
|
|
5169
|
+
storyKey,
|
|
5170
|
+
durationMs: dispatchResult.durationMs
|
|
5171
|
+
}, "Test-plan dispatch timed out");
|
|
5172
|
+
return {
|
|
5173
|
+
...makeTestPlanFailureResult(`dispatch_timeout after ${dispatchResult.durationMs}ms`),
|
|
5174
|
+
tokenUsage
|
|
5175
|
+
};
|
|
5176
|
+
}
|
|
5177
|
+
if (dispatchResult.status === "failed" || dispatchResult.exitCode !== 0) {
|
|
5178
|
+
logger$9.warn({
|
|
5179
|
+
storyKey,
|
|
5180
|
+
exitCode: dispatchResult.exitCode,
|
|
5181
|
+
status: dispatchResult.status
|
|
5182
|
+
}, "Test-plan dispatch failed");
|
|
5183
|
+
return {
|
|
5184
|
+
...makeTestPlanFailureResult(`dispatch_failed with exit_code=${dispatchResult.exitCode}`),
|
|
5185
|
+
tokenUsage
|
|
5186
|
+
};
|
|
5187
|
+
}
|
|
5188
|
+
if (dispatchResult.parseError !== null || dispatchResult.parsed === null) {
|
|
5189
|
+
const details = dispatchResult.parseError ?? "parsed result was null";
|
|
5190
|
+
logger$9.warn({
|
|
5191
|
+
storyKey,
|
|
5192
|
+
parseError: details
|
|
5193
|
+
}, "Test-plan YAML schema validation failed");
|
|
5194
|
+
return {
|
|
5195
|
+
...makeTestPlanFailureResult(`schema_validation_failed: ${details}`),
|
|
5196
|
+
tokenUsage
|
|
5197
|
+
};
|
|
5198
|
+
}
|
|
5199
|
+
const parsed = dispatchResult.parsed;
|
|
5200
|
+
try {
|
|
5201
|
+
createDecision(deps.db, {
|
|
5202
|
+
pipeline_run_id: pipelineRunId,
|
|
5203
|
+
phase: "implementation",
|
|
5204
|
+
category: TEST_PLAN,
|
|
5205
|
+
key: storyKey,
|
|
5206
|
+
value: JSON.stringify({
|
|
5207
|
+
test_files: parsed.test_files,
|
|
5208
|
+
test_categories: parsed.test_categories,
|
|
5209
|
+
coverage_notes: parsed.coverage_notes
|
|
5210
|
+
}),
|
|
5211
|
+
rationale: `Test plan for ${storyKey}: ${parsed.test_files.length} test files, categories: ${parsed.test_categories.join(", ")}`
|
|
5212
|
+
});
|
|
5213
|
+
logger$9.info({
|
|
5214
|
+
storyKey,
|
|
5215
|
+
fileCount: parsed.test_files.length,
|
|
5216
|
+
categories: parsed.test_categories
|
|
5217
|
+
}, "Test plan stored in decision store");
|
|
5218
|
+
} catch (err) {
|
|
5219
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
5220
|
+
logger$9.warn({
|
|
5221
|
+
storyKey,
|
|
5222
|
+
error
|
|
5223
|
+
}, "Failed to store test plan in decision store — proceeding anyway");
|
|
5224
|
+
}
|
|
5225
|
+
logger$9.info({
|
|
5226
|
+
storyKey,
|
|
5227
|
+
result: parsed.result
|
|
5228
|
+
}, "Test-plan workflow completed");
|
|
5229
|
+
return {
|
|
5230
|
+
result: parsed.result,
|
|
5231
|
+
test_files: parsed.test_files,
|
|
5232
|
+
test_categories: parsed.test_categories,
|
|
5233
|
+
coverage_notes: parsed.coverage_notes,
|
|
5234
|
+
tokenUsage
|
|
5235
|
+
};
|
|
5236
|
+
}
|
|
5237
|
+
/**
|
|
5238
|
+
* Build a failure result with sensible defaults.
|
|
5239
|
+
*/
|
|
5240
|
+
function makeTestPlanFailureResult(error) {
|
|
5241
|
+
return {
|
|
5242
|
+
result: "failed",
|
|
5243
|
+
test_files: [],
|
|
5244
|
+
test_categories: [],
|
|
5245
|
+
coverage_notes: "",
|
|
5246
|
+
error,
|
|
5247
|
+
tokenUsage: {
|
|
5248
|
+
input: 0,
|
|
5249
|
+
output: 0
|
|
5250
|
+
}
|
|
5251
|
+
};
|
|
5252
|
+
}
|
|
5253
|
+
|
|
5254
|
+
//#endregion
|
|
5255
|
+
//#region src/modules/compiled-workflows/test-expansion.ts
|
|
5256
|
+
const logger$8 = createLogger("compiled-workflows:test-expansion");
|
|
5257
|
+
/**
|
|
5258
|
+
* Hard token ceiling for the assembled test-expansion prompt (20,000 tokens).
|
|
5259
|
+
*/
|
|
5260
|
+
const TOKEN_CEILING = 2e4;
|
|
5261
|
+
function defaultFallbackResult(error, tokenUsage) {
|
|
5262
|
+
return {
|
|
5263
|
+
expansion_priority: "low",
|
|
5264
|
+
coverage_gaps: [],
|
|
5265
|
+
suggested_tests: [],
|
|
5266
|
+
error,
|
|
5267
|
+
tokenUsage
|
|
5268
|
+
};
|
|
5269
|
+
}
|
|
5270
|
+
/**
|
|
5271
|
+
* Execute the compiled test-expansion workflow.
|
|
5272
|
+
*
|
|
5273
|
+
* Steps:
|
|
5274
|
+
* 1. Retrieve compiled prompt template via pack.getPrompt('test-expansion')
|
|
5275
|
+
* 2. Read story file contents from storyFilePath
|
|
5276
|
+
* 3. Query decision store for architecture constraints (solutioning, architecture)
|
|
5277
|
+
* 4. Capture scoped git diff for filesModified, with stat-only fallback if oversized
|
|
5278
|
+
* 5. Assemble prompt with 20,000-token ceiling
|
|
5279
|
+
* 6. Dispatch via dispatcher with taskType='test-expansion'
|
|
5280
|
+
* 7. Validate YAML output against TestExpansionResultSchema
|
|
5281
|
+
* 8. Return typed TestExpansionResult (never throws — all errors return graceful fallback)
|
|
5282
|
+
*
|
|
5283
|
+
* @param deps - Injected dependencies (db, pack, contextCompiler, dispatcher)
|
|
5284
|
+
* @param params - Story key, story file path, files modified, working directory, pipeline run ID
|
|
5285
|
+
* @returns Promise resolving to TestExpansionResult (never rejects)
|
|
5286
|
+
*/
|
|
5287
|
+
async function runTestExpansion(deps, params) {
|
|
5288
|
+
const { storyKey, storyFilePath, pipelineRunId, filesModified, workingDirectory } = params;
|
|
5289
|
+
const cwd = workingDirectory ?? process.cwd();
|
|
5290
|
+
logger$8.debug({
|
|
5291
|
+
storyKey,
|
|
5292
|
+
storyFilePath,
|
|
5293
|
+
cwd,
|
|
5294
|
+
pipelineRunId
|
|
5295
|
+
}, "Starting test-expansion workflow");
|
|
5296
|
+
let template;
|
|
5297
|
+
try {
|
|
5298
|
+
template = await deps.pack.getPrompt("test-expansion");
|
|
5299
|
+
} catch (err) {
|
|
5300
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
5301
|
+
logger$8.warn({ error }, "Failed to retrieve test-expansion prompt template");
|
|
5302
|
+
return defaultFallbackResult(`Failed to retrieve prompt template: ${error}`, {
|
|
5303
|
+
input: 0,
|
|
5304
|
+
output: 0
|
|
5305
|
+
});
|
|
5306
|
+
}
|
|
5307
|
+
let storyContent;
|
|
5308
|
+
try {
|
|
5309
|
+
storyContent = await readFile$1(storyFilePath, "utf-8");
|
|
5310
|
+
} catch (err) {
|
|
5311
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
5312
|
+
logger$8.warn({
|
|
5313
|
+
storyFilePath,
|
|
5314
|
+
error
|
|
5315
|
+
}, "Failed to read story file");
|
|
5316
|
+
return defaultFallbackResult(`Failed to read story file: ${error}`, {
|
|
5317
|
+
input: 0,
|
|
5318
|
+
output: 0
|
|
5319
|
+
});
|
|
5320
|
+
}
|
|
5321
|
+
const archConstraintsContent = getArchConstraints(deps);
|
|
5322
|
+
let gitDiffContent = "";
|
|
5323
|
+
if (filesModified && filesModified.length > 0) try {
|
|
5324
|
+
const templateTokens = countTokens(template);
|
|
5325
|
+
const storyTokens = countTokens(storyContent);
|
|
5326
|
+
const constraintTokens = countTokens(archConstraintsContent);
|
|
5327
|
+
const nonDiffTokens = templateTokens + storyTokens + constraintTokens;
|
|
5328
|
+
const scopedDiff = await getGitDiffForFiles(filesModified, cwd);
|
|
5329
|
+
const scopedTotal = nonDiffTokens + countTokens(scopedDiff);
|
|
5330
|
+
if (scopedTotal <= TOKEN_CEILING) {
|
|
5331
|
+
gitDiffContent = scopedDiff;
|
|
5332
|
+
logger$8.debug({
|
|
5333
|
+
fileCount: filesModified.length,
|
|
5334
|
+
tokenCount: scopedTotal
|
|
5335
|
+
}, "Using scoped file diff");
|
|
5336
|
+
} else {
|
|
5337
|
+
logger$8.warn({
|
|
5338
|
+
estimatedTotal: scopedTotal,
|
|
5339
|
+
ceiling: TOKEN_CEILING,
|
|
5340
|
+
fileCount: filesModified.length
|
|
5341
|
+
}, "Scoped diff exceeds token ceiling — falling back to stat-only summary");
|
|
5342
|
+
gitDiffContent = await getGitDiffStatSummary(cwd);
|
|
5343
|
+
}
|
|
5344
|
+
} catch (err) {
|
|
5345
|
+
logger$8.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to get git diff — proceeding with empty diff");
|
|
5346
|
+
}
|
|
5347
|
+
const sections = [
|
|
5348
|
+
{
|
|
5349
|
+
name: "story_content",
|
|
5350
|
+
content: storyContent,
|
|
5351
|
+
priority: "required"
|
|
5352
|
+
},
|
|
5353
|
+
{
|
|
5354
|
+
name: "git_diff",
|
|
5355
|
+
content: gitDiffContent,
|
|
5356
|
+
priority: "important"
|
|
5357
|
+
},
|
|
5358
|
+
{
|
|
5359
|
+
name: "arch_constraints",
|
|
5360
|
+
content: archConstraintsContent,
|
|
5361
|
+
priority: "optional"
|
|
5362
|
+
}
|
|
5363
|
+
];
|
|
5364
|
+
const assembleResult = assemblePrompt(template, sections, TOKEN_CEILING);
|
|
5365
|
+
if (assembleResult.truncated) logger$8.warn({
|
|
5366
|
+
storyKey,
|
|
5367
|
+
tokenCount: assembleResult.tokenCount
|
|
5368
|
+
}, "Test-expansion prompt truncated to fit token ceiling");
|
|
5369
|
+
logger$8.debug({
|
|
5370
|
+
storyKey,
|
|
5371
|
+
tokenCount: assembleResult.tokenCount,
|
|
5372
|
+
truncated: assembleResult.truncated
|
|
5373
|
+
}, "Prompt assembled for test-expansion");
|
|
5374
|
+
const { prompt } = assembleResult;
|
|
5375
|
+
const handle = deps.dispatcher.dispatch({
|
|
5376
|
+
prompt,
|
|
5377
|
+
agent: "claude-code",
|
|
5378
|
+
taskType: "test-expansion",
|
|
5379
|
+
outputSchema: TestExpansionResultSchema,
|
|
5380
|
+
workingDirectory: deps.projectRoot
|
|
5381
|
+
});
|
|
5382
|
+
let dispatchResult;
|
|
5383
|
+
try {
|
|
5384
|
+
dispatchResult = await handle.result;
|
|
5385
|
+
} catch (err) {
|
|
5386
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
5387
|
+
logger$8.warn({
|
|
5388
|
+
storyKey,
|
|
5389
|
+
error
|
|
5390
|
+
}, "Test-expansion dispatch threw unexpected error");
|
|
5391
|
+
return defaultFallbackResult(`Dispatch error: ${error}`, {
|
|
5392
|
+
input: Math.ceil(prompt.length / 4),
|
|
5393
|
+
output: 0
|
|
5394
|
+
});
|
|
5395
|
+
}
|
|
5396
|
+
const tokenUsage = {
|
|
5397
|
+
input: dispatchResult.tokenEstimate.input,
|
|
5398
|
+
output: dispatchResult.tokenEstimate.output
|
|
5399
|
+
};
|
|
5400
|
+
if (dispatchResult.status === "failed") {
|
|
5401
|
+
const errorMsg = `Dispatch status: failed. Exit code: ${dispatchResult.exitCode}. ${dispatchResult.parseError ?? ""}`.trim();
|
|
5402
|
+
logger$8.warn({
|
|
5403
|
+
storyKey,
|
|
5404
|
+
exitCode: dispatchResult.exitCode
|
|
5405
|
+
}, "Test-expansion dispatch failed");
|
|
5406
|
+
return defaultFallbackResult(errorMsg, tokenUsage);
|
|
5407
|
+
}
|
|
5408
|
+
if (dispatchResult.status === "timeout") {
|
|
5409
|
+
logger$8.warn({ storyKey }, "Test-expansion dispatch timed out");
|
|
5410
|
+
return defaultFallbackResult("Dispatch status: timeout. The agent did not complete within the allowed time.", tokenUsage);
|
|
5411
|
+
}
|
|
5412
|
+
if (dispatchResult.parsed === null) {
|
|
5413
|
+
const details = dispatchResult.parseError ?? "No YAML block found in output";
|
|
5414
|
+
logger$8.warn({
|
|
5415
|
+
storyKey,
|
|
5416
|
+
details
|
|
5417
|
+
}, "Test-expansion output has no parseable YAML");
|
|
5418
|
+
return defaultFallbackResult(`schema_validation_failed: ${details}`, tokenUsage);
|
|
5419
|
+
}
|
|
5420
|
+
const parseResult = TestExpansionResultSchema.safeParse(dispatchResult.parsed);
|
|
5421
|
+
if (!parseResult.success) {
|
|
5422
|
+
const details = parseResult.error.message;
|
|
5423
|
+
logger$8.warn({
|
|
5424
|
+
storyKey,
|
|
5425
|
+
details
|
|
5426
|
+
}, "Test-expansion output failed schema validation");
|
|
5427
|
+
return defaultFallbackResult(`schema_validation_failed: ${details}`, tokenUsage);
|
|
5428
|
+
}
|
|
5429
|
+
const parsed = parseResult.data;
|
|
5430
|
+
logger$8.info({
|
|
5431
|
+
storyKey,
|
|
5432
|
+
expansion_priority: parsed.expansion_priority,
|
|
5433
|
+
coverage_gaps: parsed.coverage_gaps.length,
|
|
5434
|
+
suggested_tests: parsed.suggested_tests.length
|
|
5435
|
+
}, "Test-expansion workflow completed successfully");
|
|
5436
|
+
return {
|
|
5437
|
+
expansion_priority: parsed.expansion_priority,
|
|
5438
|
+
coverage_gaps: parsed.coverage_gaps,
|
|
5439
|
+
suggested_tests: parsed.suggested_tests,
|
|
5440
|
+
notes: parsed.notes,
|
|
5441
|
+
tokenUsage
|
|
5442
|
+
};
|
|
5443
|
+
}
|
|
5444
|
+
/**
|
|
5445
|
+
* Retrieve architecture constraints from the decision store.
|
|
5446
|
+
* Looks for decisions with phase='solutioning', category='architecture'.
|
|
5447
|
+
*/
|
|
4932
5448
|
function getArchConstraints(deps) {
|
|
4933
5449
|
try {
|
|
4934
5450
|
const decisions = getDecisionsByPhase(deps.db, "solutioning");
|
|
@@ -5592,7 +6108,7 @@ function createPauseGate() {
|
|
|
5592
6108
|
*/
|
|
5593
6109
|
function createImplementationOrchestrator(deps) {
|
|
5594
6110
|
const { db, pack, contextCompiler, dispatcher, eventBus, config, projectRoot } = deps;
|
|
5595
|
-
const logger$
|
|
6111
|
+
const logger$20 = createLogger("implementation-orchestrator");
|
|
5596
6112
|
let _state = "IDLE";
|
|
5597
6113
|
let _startedAt;
|
|
5598
6114
|
let _completedAt;
|
|
@@ -5629,7 +6145,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5629
6145
|
const nowMs = Date.now();
|
|
5630
6146
|
for (const [phase, startMs] of starts) {
|
|
5631
6147
|
const endMs = ends?.get(phase);
|
|
5632
|
-
if (endMs === void 0) logger$
|
|
6148
|
+
if (endMs === void 0) logger$20.warn({
|
|
5633
6149
|
storyKey,
|
|
5634
6150
|
phase
|
|
5635
6151
|
}, "Phase has no end time — story may have errored mid-phase. Duration capped to now() and may be inflated.");
|
|
@@ -5676,13 +6192,13 @@ function createImplementationOrchestrator(deps) {
|
|
|
5676
6192
|
rationale: `Story ${storyKey} completed with result=${result} in ${wallClockSeconds}s. Tokens: ${tokenAgg.input}+${tokenAgg.output}. Review cycles: ${reviewCycles}.`
|
|
5677
6193
|
});
|
|
5678
6194
|
} catch (decisionErr) {
|
|
5679
|
-
logger$
|
|
6195
|
+
logger$20.warn({
|
|
5680
6196
|
err: decisionErr,
|
|
5681
6197
|
storyKey
|
|
5682
6198
|
}, "Failed to write story-metrics decision (best-effort)");
|
|
5683
6199
|
}
|
|
5684
6200
|
} catch (err) {
|
|
5685
|
-
logger$
|
|
6201
|
+
logger$20.warn({
|
|
5686
6202
|
err,
|
|
5687
6203
|
storyKey
|
|
5688
6204
|
}, "Failed to write story metrics (best-effort)");
|
|
@@ -5711,7 +6227,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5711
6227
|
rationale: `Story ${storyKey} ${outcome} after ${reviewCycles} review cycle(s).`
|
|
5712
6228
|
});
|
|
5713
6229
|
} catch (err) {
|
|
5714
|
-
logger$
|
|
6230
|
+
logger$20.warn({
|
|
5715
6231
|
err,
|
|
5716
6232
|
storyKey
|
|
5717
6233
|
}, "Failed to write story-outcome decision (best-effort)");
|
|
@@ -5737,7 +6253,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5737
6253
|
rationale: `Escalation diagnosis for ${payload.storyKey}: ${diagnosis.recommendedAction} — ${diagnosis.rationale}`
|
|
5738
6254
|
});
|
|
5739
6255
|
} catch (err) {
|
|
5740
|
-
logger$
|
|
6256
|
+
logger$20.warn({
|
|
5741
6257
|
err,
|
|
5742
6258
|
storyKey: payload.storyKey
|
|
5743
6259
|
}, "Failed to persist escalation diagnosis (best-effort)");
|
|
@@ -5787,7 +6303,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5787
6303
|
token_usage_json: serialized
|
|
5788
6304
|
});
|
|
5789
6305
|
} catch (err) {
|
|
5790
|
-
logger$
|
|
6306
|
+
logger$20.warn("Failed to persist orchestrator state", { err });
|
|
5791
6307
|
}
|
|
5792
6308
|
}
|
|
5793
6309
|
function recordProgress() {
|
|
@@ -5817,7 +6333,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5817
6333
|
if (_stalledStories.has(key)) continue;
|
|
5818
6334
|
_stalledStories.add(key);
|
|
5819
6335
|
_storiesWithStall.add(key);
|
|
5820
|
-
logger$
|
|
6336
|
+
logger$20.warn({
|
|
5821
6337
|
storyKey: key,
|
|
5822
6338
|
phase: s.phase,
|
|
5823
6339
|
elapsedMs: elapsed
|
|
@@ -5854,7 +6370,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5854
6370
|
* exhausted retries the story is ESCALATED.
|
|
5855
6371
|
*/
|
|
5856
6372
|
async function processStory(storyKey) {
|
|
5857
|
-
logger$
|
|
6373
|
+
logger$20.info("Processing story", { storyKey });
|
|
5858
6374
|
await waitIfPaused();
|
|
5859
6375
|
if (_state !== "RUNNING") return;
|
|
5860
6376
|
startPhase(storyKey, "create-story");
|
|
@@ -5869,7 +6385,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5869
6385
|
const match = files.find((f) => f.startsWith(`${storyKey}-`) && f.endsWith(".md"));
|
|
5870
6386
|
if (match) {
|
|
5871
6387
|
storyFilePath = join$1(artifactsDir, match);
|
|
5872
|
-
logger$
|
|
6388
|
+
logger$20.info({
|
|
5873
6389
|
storyKey,
|
|
5874
6390
|
storyFilePath
|
|
5875
6391
|
}, "Found existing story file — skipping create-story");
|
|
@@ -5961,6 +6477,39 @@ function createImplementationOrchestrator(deps) {
|
|
|
5961
6477
|
}
|
|
5962
6478
|
await waitIfPaused();
|
|
5963
6479
|
if (_state !== "RUNNING") return;
|
|
6480
|
+
startPhase(storyKey, "test-plan");
|
|
6481
|
+
updateStory(storyKey, { phase: "IN_TEST_PLANNING" });
|
|
6482
|
+
persistState();
|
|
6483
|
+
let testPlanPhaseResult = "failed";
|
|
6484
|
+
try {
|
|
6485
|
+
const testPlanResult = await runTestPlan({
|
|
6486
|
+
db,
|
|
6487
|
+
pack,
|
|
6488
|
+
contextCompiler,
|
|
6489
|
+
dispatcher,
|
|
6490
|
+
projectRoot
|
|
6491
|
+
}, {
|
|
6492
|
+
storyKey,
|
|
6493
|
+
storyFilePath: storyFilePath ?? "",
|
|
6494
|
+
pipelineRunId: config.pipelineRunId
|
|
6495
|
+
});
|
|
6496
|
+
testPlanPhaseResult = testPlanResult.result;
|
|
6497
|
+
if (testPlanResult.result === "success") logger$20.info({ storyKey }, "Test plan generated successfully");
|
|
6498
|
+
else logger$20.warn({ storyKey }, "Test planning returned failed result — proceeding to dev-story without test plan");
|
|
6499
|
+
} catch (err) {
|
|
6500
|
+
logger$20.warn({
|
|
6501
|
+
storyKey,
|
|
6502
|
+
err
|
|
6503
|
+
}, "Test planning failed — proceeding to dev-story without test plan");
|
|
6504
|
+
}
|
|
6505
|
+
endPhase(storyKey, "test-plan");
|
|
6506
|
+
eventBus.emit("orchestrator:story-phase-complete", {
|
|
6507
|
+
storyKey,
|
|
6508
|
+
phase: "IN_TEST_PLANNING",
|
|
6509
|
+
result: { result: testPlanPhaseResult }
|
|
6510
|
+
});
|
|
6511
|
+
await waitIfPaused();
|
|
6512
|
+
if (_state !== "RUNNING") return;
|
|
5964
6513
|
startPhase(storyKey, "dev-story");
|
|
5965
6514
|
updateStory(storyKey, { phase: "IN_DEV" });
|
|
5966
6515
|
persistState();
|
|
@@ -5971,7 +6520,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5971
6520
|
try {
|
|
5972
6521
|
storyContentForAnalysis = await readFile$1(storyFilePath ?? "", "utf-8");
|
|
5973
6522
|
} catch (err) {
|
|
5974
|
-
logger$
|
|
6523
|
+
logger$20.error({
|
|
5975
6524
|
storyKey,
|
|
5976
6525
|
storyFilePath,
|
|
5977
6526
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -5979,7 +6528,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5979
6528
|
}
|
|
5980
6529
|
const analysis = analyzeStoryComplexity(storyContentForAnalysis);
|
|
5981
6530
|
const batches = planTaskBatches(analysis);
|
|
5982
|
-
logger$
|
|
6531
|
+
logger$20.info({
|
|
5983
6532
|
storyKey,
|
|
5984
6533
|
estimatedScope: analysis.estimatedScope,
|
|
5985
6534
|
batchCount: batches.length,
|
|
@@ -5997,7 +6546,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5997
6546
|
if (_state !== "RUNNING") break;
|
|
5998
6547
|
const taskScope = batch.taskIds.map((id, i) => `T${id}: ${batch.taskTitles[i] ?? ""}`).join("\n");
|
|
5999
6548
|
const priorFiles = allFilesModified.size > 0 ? Array.from(allFilesModified) : void 0;
|
|
6000
|
-
logger$
|
|
6549
|
+
logger$20.info({
|
|
6001
6550
|
storyKey,
|
|
6002
6551
|
batchIndex: batch.batchIndex,
|
|
6003
6552
|
taskCount: batch.taskIds.length
|
|
@@ -6021,7 +6570,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6021
6570
|
});
|
|
6022
6571
|
} catch (batchErr) {
|
|
6023
6572
|
const errMsg = batchErr instanceof Error ? batchErr.message : String(batchErr);
|
|
6024
|
-
logger$
|
|
6573
|
+
logger$20.warn({
|
|
6025
6574
|
storyKey,
|
|
6026
6575
|
batchIndex: batch.batchIndex,
|
|
6027
6576
|
error: errMsg
|
|
@@ -6041,7 +6590,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6041
6590
|
filesModified: batchFilesModified,
|
|
6042
6591
|
result: batchResult.result === "success" ? "success" : "failed"
|
|
6043
6592
|
};
|
|
6044
|
-
logger$
|
|
6593
|
+
logger$20.info(batchMetrics, "Batch dev-story metrics");
|
|
6045
6594
|
for (const f of batchFilesModified) allFilesModified.add(f);
|
|
6046
6595
|
if (batchFilesModified.length > 0) batchFileGroups.push({
|
|
6047
6596
|
batchIndex: batch.batchIndex,
|
|
@@ -6063,13 +6612,13 @@ function createImplementationOrchestrator(deps) {
|
|
|
6063
6612
|
})
|
|
6064
6613
|
});
|
|
6065
6614
|
} catch (tokenErr) {
|
|
6066
|
-
logger$
|
|
6615
|
+
logger$20.warn({
|
|
6067
6616
|
storyKey,
|
|
6068
6617
|
batchIndex: batch.batchIndex,
|
|
6069
6618
|
err: tokenErr
|
|
6070
6619
|
}, "Failed to record batch token usage");
|
|
6071
6620
|
}
|
|
6072
|
-
if (batchResult.result === "failed") logger$
|
|
6621
|
+
if (batchResult.result === "failed") logger$20.warn({
|
|
6073
6622
|
storyKey,
|
|
6074
6623
|
batchIndex: batch.batchIndex,
|
|
6075
6624
|
error: batchResult.error
|
|
@@ -6102,7 +6651,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6102
6651
|
result: devResult
|
|
6103
6652
|
});
|
|
6104
6653
|
persistState();
|
|
6105
|
-
if (devResult.result === "failed") logger$
|
|
6654
|
+
if (devResult.result === "failed") logger$20.warn("Dev-story reported failure, proceeding to code review", {
|
|
6106
6655
|
storyKey,
|
|
6107
6656
|
error: devResult.error,
|
|
6108
6657
|
filesModified: devFilesModified.length
|
|
@@ -6159,7 +6708,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6159
6708
|
"NEEDS_MAJOR_REWORK": 2
|
|
6160
6709
|
};
|
|
6161
6710
|
for (const group of batchFileGroups) {
|
|
6162
|
-
logger$
|
|
6711
|
+
logger$20.info({
|
|
6163
6712
|
storyKey,
|
|
6164
6713
|
batchIndex: group.batchIndex,
|
|
6165
6714
|
fileCount: group.files.length
|
|
@@ -6196,7 +6745,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6196
6745
|
rawOutput: lastRawOutput,
|
|
6197
6746
|
tokenUsage: aggregateTokens
|
|
6198
6747
|
};
|
|
6199
|
-
logger$
|
|
6748
|
+
logger$20.info({
|
|
6200
6749
|
storyKey,
|
|
6201
6750
|
batchCount: batchFileGroups.length,
|
|
6202
6751
|
verdict: worstVerdict,
|
|
@@ -6222,7 +6771,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6222
6771
|
const isPhantomReview = reviewResult.verdict !== "SHIP_IT" && (reviewResult.issue_list === void 0 || reviewResult.issue_list.length === 0) && reviewResult.error !== void 0;
|
|
6223
6772
|
if (isPhantomReview && !timeoutRetried) {
|
|
6224
6773
|
timeoutRetried = true;
|
|
6225
|
-
logger$
|
|
6774
|
+
logger$20.warn({
|
|
6226
6775
|
storyKey,
|
|
6227
6776
|
reviewCycles,
|
|
6228
6777
|
error: reviewResult.error
|
|
@@ -6232,7 +6781,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6232
6781
|
verdict = reviewResult.verdict;
|
|
6233
6782
|
issueList = reviewResult.issue_list ?? [];
|
|
6234
6783
|
if (verdict === "NEEDS_MAJOR_REWORK" && reviewCycles > 0 && previousIssueList.length > 0 && issueList.length < previousIssueList.length) {
|
|
6235
|
-
logger$
|
|
6784
|
+
logger$20.info({
|
|
6236
6785
|
storyKey,
|
|
6237
6786
|
originalVerdict: verdict,
|
|
6238
6787
|
issuesBefore: previousIssueList.length,
|
|
@@ -6268,7 +6817,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6268
6817
|
if (_decomposition !== void 0) parts.push(`decomposed: ${_decomposition.batchCount} batches`);
|
|
6269
6818
|
parts.push(`${fileCount} files`);
|
|
6270
6819
|
parts.push(`${totalTokensK} tokens`);
|
|
6271
|
-
logger$
|
|
6820
|
+
logger$20.info({
|
|
6272
6821
|
storyKey,
|
|
6273
6822
|
verdict,
|
|
6274
6823
|
agentVerdict: reviewResult.agentVerdict
|
|
@@ -6305,6 +6854,38 @@ function createImplementationOrchestrator(deps) {
|
|
|
6305
6854
|
reviewCycles
|
|
6306
6855
|
});
|
|
6307
6856
|
persistState();
|
|
6857
|
+
try {
|
|
6858
|
+
const expansionResult = await runTestExpansion({
|
|
6859
|
+
db,
|
|
6860
|
+
pack,
|
|
6861
|
+
contextCompiler,
|
|
6862
|
+
dispatcher,
|
|
6863
|
+
projectRoot
|
|
6864
|
+
}, {
|
|
6865
|
+
storyKey,
|
|
6866
|
+
storyFilePath: storyFilePath ?? "",
|
|
6867
|
+
pipelineRunId: config.pipelineRunId,
|
|
6868
|
+
filesModified: devFilesModified,
|
|
6869
|
+
workingDirectory: projectRoot
|
|
6870
|
+
});
|
|
6871
|
+
logger$20.debug({
|
|
6872
|
+
storyKey,
|
|
6873
|
+
expansion_priority: expansionResult.expansion_priority,
|
|
6874
|
+
coverage_gaps: expansionResult.coverage_gaps.length
|
|
6875
|
+
}, "Test expansion analysis complete");
|
|
6876
|
+
createDecision(db, {
|
|
6877
|
+
pipeline_run_id: config.pipelineRunId ?? "unknown",
|
|
6878
|
+
phase: "implementation",
|
|
6879
|
+
category: TEST_EXPANSION_FINDING,
|
|
6880
|
+
key: `${storyKey}:${config.pipelineRunId ?? "unknown"}`,
|
|
6881
|
+
value: JSON.stringify(expansionResult)
|
|
6882
|
+
});
|
|
6883
|
+
} catch (expansionErr) {
|
|
6884
|
+
logger$20.warn({
|
|
6885
|
+
storyKey,
|
|
6886
|
+
error: expansionErr instanceof Error ? expansionErr.message : String(expansionErr)
|
|
6887
|
+
}, "Test expansion failed — story verdict unchanged");
|
|
6888
|
+
}
|
|
6308
6889
|
keepReviewing = false;
|
|
6309
6890
|
return;
|
|
6310
6891
|
}
|
|
@@ -6327,7 +6908,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6327
6908
|
persistState();
|
|
6328
6909
|
return;
|
|
6329
6910
|
}
|
|
6330
|
-
logger$
|
|
6911
|
+
logger$20.info({
|
|
6331
6912
|
storyKey,
|
|
6332
6913
|
reviewCycles: finalReviewCycles,
|
|
6333
6914
|
issueCount: issueList.length
|
|
@@ -6377,7 +6958,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6377
6958
|
fixPrompt = assembled.prompt;
|
|
6378
6959
|
} catch {
|
|
6379
6960
|
fixPrompt = `Fix story ${storyKey}: verdict=${verdict}, minor fixes needed`;
|
|
6380
|
-
logger$
|
|
6961
|
+
logger$20.warn("Failed to assemble auto-approve fix prompt, using fallback", { storyKey });
|
|
6381
6962
|
}
|
|
6382
6963
|
const handle = dispatcher.dispatch({
|
|
6383
6964
|
prompt: fixPrompt,
|
|
@@ -6394,9 +6975,9 @@ function createImplementationOrchestrator(deps) {
|
|
|
6394
6975
|
output: fixResult.tokenEstimate.output
|
|
6395
6976
|
} : void 0 }
|
|
6396
6977
|
});
|
|
6397
|
-
if (fixResult.status === "timeout") logger$
|
|
6978
|
+
if (fixResult.status === "timeout") logger$20.warn("Auto-approve fix timed out — approving anyway (issues were minor)", { storyKey });
|
|
6398
6979
|
} catch (err) {
|
|
6399
|
-
logger$
|
|
6980
|
+
logger$20.warn("Auto-approve fix dispatch failed — approving anyway (issues were minor)", {
|
|
6400
6981
|
storyKey,
|
|
6401
6982
|
err
|
|
6402
6983
|
});
|
|
@@ -6469,7 +7050,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6469
7050
|
fixPrompt = assembled.prompt;
|
|
6470
7051
|
} catch {
|
|
6471
7052
|
fixPrompt = `Fix story ${storyKey}: verdict=${verdict}, taskType=${taskType}`;
|
|
6472
|
-
logger$
|
|
7053
|
+
logger$20.warn("Failed to assemble fix prompt, using fallback", {
|
|
6473
7054
|
storyKey,
|
|
6474
7055
|
taskType
|
|
6475
7056
|
});
|
|
@@ -6492,7 +7073,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6492
7073
|
} : void 0 }
|
|
6493
7074
|
});
|
|
6494
7075
|
if (fixResult.status === "timeout") {
|
|
6495
|
-
logger$
|
|
7076
|
+
logger$20.warn("Fix dispatch timed out — escalating story", {
|
|
6496
7077
|
storyKey,
|
|
6497
7078
|
taskType
|
|
6498
7079
|
});
|
|
@@ -6512,13 +7093,13 @@ function createImplementationOrchestrator(deps) {
|
|
|
6512
7093
|
persistState();
|
|
6513
7094
|
return;
|
|
6514
7095
|
}
|
|
6515
|
-
if (fixResult.status === "failed") logger$
|
|
7096
|
+
if (fixResult.status === "failed") logger$20.warn("Fix dispatch failed", {
|
|
6516
7097
|
storyKey,
|
|
6517
7098
|
taskType,
|
|
6518
7099
|
exitCode: fixResult.exitCode
|
|
6519
7100
|
});
|
|
6520
7101
|
} catch (err) {
|
|
6521
|
-
logger$
|
|
7102
|
+
logger$20.warn("Fix dispatch failed, continuing to next review", {
|
|
6522
7103
|
storyKey,
|
|
6523
7104
|
taskType,
|
|
6524
7105
|
err
|
|
@@ -6572,11 +7153,11 @@ function createImplementationOrchestrator(deps) {
|
|
|
6572
7153
|
}
|
|
6573
7154
|
async function run(storyKeys) {
|
|
6574
7155
|
if (_state === "RUNNING" || _state === "PAUSED") {
|
|
6575
|
-
logger$
|
|
7156
|
+
logger$20.warn("run() called while orchestrator is already running or paused — ignoring", { state: _state });
|
|
6576
7157
|
return getStatus();
|
|
6577
7158
|
}
|
|
6578
7159
|
if (_state === "COMPLETE") {
|
|
6579
|
-
logger$
|
|
7160
|
+
logger$20.warn("run() called on a COMPLETE orchestrator — ignoring", { state: _state });
|
|
6580
7161
|
return getStatus();
|
|
6581
7162
|
}
|
|
6582
7163
|
_state = "RUNNING";
|
|
@@ -6594,13 +7175,13 @@ function createImplementationOrchestrator(deps) {
|
|
|
6594
7175
|
if (config.enableHeartbeat) startHeartbeat();
|
|
6595
7176
|
if (projectRoot !== void 0) {
|
|
6596
7177
|
const seedResult = seedMethodologyContext(db, projectRoot);
|
|
6597
|
-
if (seedResult.decisionsCreated > 0) logger$
|
|
7178
|
+
if (seedResult.decisionsCreated > 0) logger$20.info({
|
|
6598
7179
|
decisionsCreated: seedResult.decisionsCreated,
|
|
6599
7180
|
skippedCategories: seedResult.skippedCategories
|
|
6600
7181
|
}, "Methodology context seeded from planning artifacts");
|
|
6601
7182
|
}
|
|
6602
7183
|
const groups = detectConflictGroups(storyKeys);
|
|
6603
|
-
logger$
|
|
7184
|
+
logger$20.info("Orchestrator starting", {
|
|
6604
7185
|
storyCount: storyKeys.length,
|
|
6605
7186
|
groupCount: groups.length,
|
|
6606
7187
|
maxConcurrency: config.maxConcurrency
|
|
@@ -6612,7 +7193,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6612
7193
|
_state = "FAILED";
|
|
6613
7194
|
_completedAt = new Date().toISOString();
|
|
6614
7195
|
persistState();
|
|
6615
|
-
logger$
|
|
7196
|
+
logger$20.error("Orchestrator failed with unhandled error", { err });
|
|
6616
7197
|
return getStatus();
|
|
6617
7198
|
}
|
|
6618
7199
|
stopHeartbeat();
|
|
@@ -6639,7 +7220,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6639
7220
|
_pauseGate = createPauseGate();
|
|
6640
7221
|
_state = "PAUSED";
|
|
6641
7222
|
eventBus.emit("orchestrator:paused", {});
|
|
6642
|
-
logger$
|
|
7223
|
+
logger$20.info("Orchestrator paused");
|
|
6643
7224
|
}
|
|
6644
7225
|
function resume() {
|
|
6645
7226
|
if (_state !== "PAUSED") return;
|
|
@@ -6650,7 +7231,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6650
7231
|
}
|
|
6651
7232
|
_state = "RUNNING";
|
|
6652
7233
|
eventBus.emit("orchestrator:resumed", {});
|
|
6653
|
-
logger$
|
|
7234
|
+
logger$20.info("Orchestrator resumed");
|
|
6654
7235
|
}
|
|
6655
7236
|
return {
|
|
6656
7237
|
run,
|
|
@@ -8481,6 +9062,10 @@ const AMENDMENT_CONTEXT_HEADER$2 = "\n\n--- AMENDMENT CONTEXT (Parent Run Decisi
|
|
|
8481
9062
|
const AMENDMENT_CONTEXT_FOOTER$2 = "\n--- END AMENDMENT CONTEXT ---\n";
|
|
8482
9063
|
/** Marker appended when amendment context is truncated to fit token budget */
|
|
8483
9064
|
const TRUNCATED_MARKER$2 = "\n[TRUNCATED]";
|
|
9065
|
+
/** Prior run findings framing block prefix */
|
|
9066
|
+
const PRIOR_FINDINGS_HEADER = "\n\n--- PRIOR RUN FINDINGS ---\n";
|
|
9067
|
+
/** Prior run findings framing block suffix */
|
|
9068
|
+
const PRIOR_FINDINGS_FOOTER = "\n--- END PRIOR RUN FINDINGS ---\n";
|
|
8484
9069
|
/** Concept placeholder in the prompt template */
|
|
8485
9070
|
const CONCEPT_PLACEHOLDER = "{{concept}}";
|
|
8486
9071
|
/** Product brief fields to persist as decisions */
|
|
@@ -8521,6 +9106,9 @@ function buildAnalysisSteps() {
|
|
|
8521
9106
|
context: [{
|
|
8522
9107
|
placeholder: "concept",
|
|
8523
9108
|
source: "param:concept"
|
|
9109
|
+
}, {
|
|
9110
|
+
placeholder: "prior_findings",
|
|
9111
|
+
source: "param:prior_findings"
|
|
8524
9112
|
}],
|
|
8525
9113
|
persist: [{
|
|
8526
9114
|
field: "problem_statement",
|
|
@@ -8584,7 +9172,14 @@ async function runAnalysisMultiStep(deps, params) {
|
|
|
8584
9172
|
};
|
|
8585
9173
|
try {
|
|
8586
9174
|
const steps = buildAnalysisSteps();
|
|
8587
|
-
|
|
9175
|
+
let priorFindings = "";
|
|
9176
|
+
try {
|
|
9177
|
+
priorFindings = getProjectFindings(deps.db);
|
|
9178
|
+
} catch {}
|
|
9179
|
+
const result = await runSteps(steps, deps, params.runId, "analysis", {
|
|
9180
|
+
concept: params.concept,
|
|
9181
|
+
prior_findings: priorFindings
|
|
9182
|
+
});
|
|
8588
9183
|
if (!result.success) return {
|
|
8589
9184
|
result: "failed",
|
|
8590
9185
|
error: result.error ?? "multi_step_failed",
|
|
@@ -8665,6 +9260,18 @@ async function runAnalysisPhase(deps, params) {
|
|
|
8665
9260
|
let effectiveConcept = concept;
|
|
8666
9261
|
if (concept.length > MAX_CONCEPT_CHARS) effectiveConcept = concept.slice(0, MAX_CONCEPT_CHARS) + "...";
|
|
8667
9262
|
let prompt = template.replace(CONCEPT_PLACEHOLDER, effectiveConcept);
|
|
9263
|
+
try {
|
|
9264
|
+
const priorFindings = getProjectFindings(db);
|
|
9265
|
+
if (priorFindings !== "") {
|
|
9266
|
+
const maxPromptChars = MAX_PROMPT_TOKENS$1 * 4;
|
|
9267
|
+
const framingLen = PRIOR_FINDINGS_HEADER.length + PRIOR_FINDINGS_FOOTER.length;
|
|
9268
|
+
const availableForFindings = maxPromptChars - prompt.length - framingLen - TRUNCATED_MARKER$2.length;
|
|
9269
|
+
if (availableForFindings > 0) {
|
|
9270
|
+
const findingsToInject = priorFindings.length > availableForFindings ? priorFindings.slice(0, availableForFindings) + TRUNCATED_MARKER$2 : priorFindings;
|
|
9271
|
+
prompt += PRIOR_FINDINGS_HEADER + findingsToInject + PRIOR_FINDINGS_FOOTER;
|
|
9272
|
+
}
|
|
9273
|
+
}
|
|
9274
|
+
} catch {}
|
|
8668
9275
|
if (amendmentContext !== void 0 && amendmentContext !== "") {
|
|
8669
9276
|
const maxPromptChars = MAX_PROMPT_TOKENS$1 * 4;
|
|
8670
9277
|
const basePromptLen = prompt.length;
|
|
@@ -10919,7 +11526,7 @@ function mapInternalPhaseToEventPhase(internalPhase) {
|
|
|
10919
11526
|
}
|
|
10920
11527
|
}
|
|
10921
11528
|
async function runRunAction(options) {
|
|
10922
|
-
const { pack: packName, from: startPhase, stopAfter, concept: conceptArg, conceptFile, stories: storiesArg, concurrency, outputFormat, projectRoot, events: eventsFlag, verbose: verboseFlag, tui: tuiFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag } = options;
|
|
11529
|
+
const { pack: packName, from: startPhase, stopAfter, concept: conceptArg, conceptFile, stories: storiesArg, concurrency, outputFormat, projectRoot, events: eventsFlag, verbose: verboseFlag, tui: tuiFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, registry: injectedRegistry } = options;
|
|
10923
11530
|
if (startPhase !== void 0 && !VALID_PHASES.includes(startPhase)) {
|
|
10924
11531
|
const errorMsg = `Invalid phase '${startPhase}'. Valid phases: ${VALID_PHASES.join(", ")}`;
|
|
10925
11532
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
|
|
@@ -10977,7 +11584,8 @@ async function runRunAction(options) {
|
|
|
10977
11584
|
...eventsFlag === true ? { events: true } : {},
|
|
10978
11585
|
...skipUx === true ? { skipUx: true } : {},
|
|
10979
11586
|
...researchFlag === true ? { research: true } : {},
|
|
10980
|
-
...skipResearchFlag === true ? { skipResearch: true } : {}
|
|
11587
|
+
...skipResearchFlag === true ? { skipResearch: true } : {},
|
|
11588
|
+
...injectedRegistry !== void 0 ? { registry: injectedRegistry } : {}
|
|
10981
11589
|
});
|
|
10982
11590
|
let storyKeys = [];
|
|
10983
11591
|
if (storiesArg !== void 0 && storiesArg !== "") {
|
|
@@ -11066,8 +11674,8 @@ async function runRunAction(options) {
|
|
|
11066
11674
|
});
|
|
11067
11675
|
const eventBus = createEventBus();
|
|
11068
11676
|
const contextCompiler = createContextCompiler({ db });
|
|
11069
|
-
const adapterRegistry = new AdapterRegistry();
|
|
11070
|
-
await adapterRegistry.discoverAndRegister();
|
|
11677
|
+
const adapterRegistry = injectedRegistry ?? new AdapterRegistry();
|
|
11678
|
+
if (injectedRegistry === void 0) await adapterRegistry.discoverAndRegister();
|
|
11071
11679
|
const dispatcher = createDispatcher({
|
|
11072
11680
|
eventBus,
|
|
11073
11681
|
adapterRegistry
|
|
@@ -11430,7 +12038,7 @@ async function runRunAction(options) {
|
|
|
11430
12038
|
}
|
|
11431
12039
|
}
|
|
11432
12040
|
async function runFullPipeline(options) {
|
|
11433
|
-
const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag } = options;
|
|
12041
|
+
const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, registry: injectedRegistry } = options;
|
|
11434
12042
|
if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
|
|
11435
12043
|
const dbWrapper = new DatabaseWrapper(dbPath);
|
|
11436
12044
|
try {
|
|
@@ -11458,8 +12066,8 @@ async function runFullPipeline(options) {
|
|
|
11458
12066
|
}
|
|
11459
12067
|
const eventBus = createEventBus();
|
|
11460
12068
|
const contextCompiler = createContextCompiler({ db });
|
|
11461
|
-
const adapterRegistry = new AdapterRegistry();
|
|
11462
|
-
await adapterRegistry.discoverAndRegister();
|
|
12069
|
+
const adapterRegistry = injectedRegistry ?? new AdapterRegistry();
|
|
12070
|
+
if (injectedRegistry === void 0) await adapterRegistry.discoverAndRegister();
|
|
11463
12071
|
const dispatcher = createDispatcher({
|
|
11464
12072
|
eventBus,
|
|
11465
12073
|
adapterRegistry
|
|
@@ -11734,7 +12342,7 @@ async function runFullPipeline(options) {
|
|
|
11734
12342
|
} catch {}
|
|
11735
12343
|
}
|
|
11736
12344
|
}
|
|
11737
|
-
function registerRunCommand(program, _version = "0.0.0", projectRoot = process.cwd()) {
|
|
12345
|
+
function registerRunCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
|
|
11738
12346
|
program.command("run").description("Run the autonomous pipeline (use --from to start from a specific phase)").option("--pack <name>", "Methodology pack name", "bmad").option("--from <phase>", "Start from this phase: analysis, planning, solutioning, implementation").option("--stop-after <phase>", "Stop pipeline after this phase completes").option("--concept <text>", "Inline concept text (required when --from analysis)").option("--concept-file <path>", "Path to a file containing the concept text").option("--stories <keys>", "Comma-separated story keys (e.g., 10-1,10-2)").option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").option("--verbose", "Show detailed pino log output").option("--help-agent", "Print a machine-optimized prompt fragment for AI agents and exit").option("--tui", "Show TUI dashboard").option("--skip-ux", "Skip the UX design phase even if enabled in the pack manifest").option("--research", "Enable the research phase even if not set in the pack manifest").option("--skip-research", "Skip the research phase even if enabled in the pack manifest").action(async (opts) => {
|
|
11739
12347
|
if (opts.helpAgent) {
|
|
11740
12348
|
process.exitCode = await runHelpAgent();
|
|
@@ -11767,7 +12375,8 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
11767
12375
|
tui: opts.tui,
|
|
11768
12376
|
skipUx: opts.skipUx,
|
|
11769
12377
|
research: opts.research,
|
|
11770
|
-
skipResearch: opts.skipResearch
|
|
12378
|
+
skipResearch: opts.skipResearch,
|
|
12379
|
+
registry
|
|
11771
12380
|
});
|
|
11772
12381
|
process.exitCode = exitCode;
|
|
11773
12382
|
});
|
|
@@ -11775,4 +12384,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
11775
12384
|
|
|
11776
12385
|
//#endregion
|
|
11777
12386
|
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 };
|
|
11778
|
-
//# sourceMappingURL=run-
|
|
12387
|
+
//# sourceMappingURL=run-BLIgARum.js.map
|