substrate-ai 0.19.31 → 0.19.32
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 +9 -6
- package/dist/{health-C96NMCJX.js → health-BmmmZgp3.js} +1 -1
- package/dist/{health-DUgvybiN.js → health-D--wDx-U.js} +9 -1087
- package/dist/index.d.ts +57 -1
- package/dist/{run-CFmp4-qj.js → run-0MJeYpdb.js} +305 -821
- package/dist/{run-Bb51aPNw.js → run-BH03S2JH.js} +2 -2
- package/package.json +2 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore,
|
|
1
|
+
import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter, detectCycles, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, resolveMainRepoRoot, validateStoryKey } from "./health-D--wDx-U.js";
|
|
2
2
|
import { createLogger } from "./logger-KeHncl-f.js";
|
|
3
3
|
import { TypedEventBusImpl, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-CElYrONe.js";
|
|
4
4
|
import { ADVISORY_NOTES, Categorizer, ConsumerAnalyzer, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, IngestionServer, LogTurnAnalyzer, OPERATIONAL_FINDING, Recommender, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, callLLM, createConfigSystem, createDatabaseAdapter$1, createDecision, createPipelineRun, createRequirement, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-DYcDRyoS.js";
|
|
@@ -19,6 +19,7 @@ import { promisify } from "node:util";
|
|
|
19
19
|
import { fileURLToPath } from "node:url";
|
|
20
20
|
import { execFile as execFile$1, spawn as spawn$1 } from "child_process";
|
|
21
21
|
import { promisify as promisify$1 } from "util";
|
|
22
|
+
import { FindingsInjector, RunManifest, applyConfigToGraph, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, extractTargetFilesFromStoryContent, resolveGraphPath } from "@substrate-ai/sdlc";
|
|
22
23
|
import { createHash as createHash$1 } from "crypto";
|
|
23
24
|
import * as readline from "readline";
|
|
24
25
|
import Stream from "node:stream";
|
|
@@ -1706,6 +1707,76 @@ const PIPELINE_EVENT_METADATA = [
|
|
|
1706
1707
|
description: "Total duration of all checks in milliseconds."
|
|
1707
1708
|
}
|
|
1708
1709
|
]
|
|
1710
|
+
},
|
|
1711
|
+
{
|
|
1712
|
+
type: "cost:warning",
|
|
1713
|
+
description: "Cumulative pipeline cost has crossed 80% of the --cost-ceiling threshold.",
|
|
1714
|
+
when: "Emitted at most once per run, between story dispatches, when cumulative cost ≥ 80% of ceiling.",
|
|
1715
|
+
fields: [
|
|
1716
|
+
{
|
|
1717
|
+
name: "ts",
|
|
1718
|
+
type: "string",
|
|
1719
|
+
description: "Timestamp."
|
|
1720
|
+
},
|
|
1721
|
+
{
|
|
1722
|
+
name: "cumulative_cost",
|
|
1723
|
+
type: "number",
|
|
1724
|
+
description: "Cumulative pipeline cost in USD at time of check."
|
|
1725
|
+
},
|
|
1726
|
+
{
|
|
1727
|
+
name: "ceiling",
|
|
1728
|
+
type: "number",
|
|
1729
|
+
description: "Configured cost ceiling in USD."
|
|
1730
|
+
},
|
|
1731
|
+
{
|
|
1732
|
+
name: "percent_used",
|
|
1733
|
+
type: "number",
|
|
1734
|
+
description: "(cumulative / ceiling) * 100, rounded to two decimal places."
|
|
1735
|
+
}
|
|
1736
|
+
]
|
|
1737
|
+
},
|
|
1738
|
+
{
|
|
1739
|
+
type: "cost:ceiling-reached",
|
|
1740
|
+
description: "Cost ceiling reached — remaining undispatched stories are skipped.",
|
|
1741
|
+
when: "Emitted between story dispatches when cumulative cost ≥ 100% of ceiling.",
|
|
1742
|
+
fields: [
|
|
1743
|
+
{
|
|
1744
|
+
name: "ts",
|
|
1745
|
+
type: "string",
|
|
1746
|
+
description: "Timestamp."
|
|
1747
|
+
},
|
|
1748
|
+
{
|
|
1749
|
+
name: "cumulative_cost",
|
|
1750
|
+
type: "number",
|
|
1751
|
+
description: "Cumulative pipeline cost in USD at time of check."
|
|
1752
|
+
},
|
|
1753
|
+
{
|
|
1754
|
+
name: "ceiling",
|
|
1755
|
+
type: "number",
|
|
1756
|
+
description: "Configured cost ceiling in USD."
|
|
1757
|
+
},
|
|
1758
|
+
{
|
|
1759
|
+
name: "halt_on",
|
|
1760
|
+
type: "string",
|
|
1761
|
+
description: "--halt-on value in effect (none, all, critical)."
|
|
1762
|
+
},
|
|
1763
|
+
{
|
|
1764
|
+
name: "action",
|
|
1765
|
+
type: "string",
|
|
1766
|
+
description: "Action taken (always stopped in this story; interactive prompt is Epic 54 scope)."
|
|
1767
|
+
},
|
|
1768
|
+
{
|
|
1769
|
+
name: "skipped_stories",
|
|
1770
|
+
type: "string[]",
|
|
1771
|
+
description: "Story keys skipped because budget was exhausted."
|
|
1772
|
+
},
|
|
1773
|
+
{
|
|
1774
|
+
name: "severity",
|
|
1775
|
+
type: "string",
|
|
1776
|
+
description: "critical when halt_on is all or critical; absent when none.",
|
|
1777
|
+
optional: true
|
|
1778
|
+
}
|
|
1779
|
+
]
|
|
1709
1780
|
}
|
|
1710
1781
|
];
|
|
1711
1782
|
/**
|
|
@@ -4216,8 +4287,8 @@ var Minimatch = class {
|
|
|
4216
4287
|
});
|
|
4217
4288
|
return pp.filter((p) => p !== GLOBSTAR).join("/");
|
|
4218
4289
|
}).join("|");
|
|
4219
|
-
const [open
|
|
4220
|
-
re = "^" + open
|
|
4290
|
+
const [open, close] = set.length > 1 ? ["(?:", ")"] : ["", ""];
|
|
4291
|
+
re = "^" + open + re + close + "$";
|
|
4221
4292
|
if (this.negate) re = "^(?!" + re + ").+$";
|
|
4222
4293
|
try {
|
|
4223
4294
|
this.regexp = new RegExp(re, [...flags].join(""));
|
|
@@ -6507,7 +6578,7 @@ const DEFAULT_TIMEOUT_MS$1 = 18e5;
|
|
|
6507
6578
|
* @returns DevStoryResult with result, ac_met, ac_failures, files_modified, tests, tokenUsage
|
|
6508
6579
|
*/
|
|
6509
6580
|
async function runDevStory(deps, params) {
|
|
6510
|
-
const { storyKey, storyFilePath, taskScope, priorFiles } = params;
|
|
6581
|
+
const { storyKey, storyFilePath, taskScope, priorFiles, findingsPrompt: handlerFindingsPrompt } = params;
|
|
6511
6582
|
logger$15.info({
|
|
6512
6583
|
storyKey,
|
|
6513
6584
|
storyFilePath
|
|
@@ -6640,14 +6711,24 @@ async function runDevStory(deps, params) {
|
|
|
6640
6711
|
}, "Repo-map context assembled");
|
|
6641
6712
|
}
|
|
6642
6713
|
let priorFindingsContent = "";
|
|
6643
|
-
|
|
6644
|
-
|
|
6714
|
+
if (handlerFindingsPrompt !== void 0 && handlerFindingsPrompt.length > 0) {
|
|
6715
|
+
priorFindingsContent = handlerFindingsPrompt;
|
|
6716
|
+
logger$15.debug({
|
|
6717
|
+
storyKey,
|
|
6718
|
+
findingsLen: handlerFindingsPrompt.length
|
|
6719
|
+
}, "Using pre-computed findings from handler (Story 53-8 AC2)");
|
|
6720
|
+
} else try {
|
|
6721
|
+
const findings = await FindingsInjector.inject(deps.db, {
|
|
6722
|
+
storyKey,
|
|
6723
|
+
runId: params.pipelineRunId ?? "",
|
|
6724
|
+
targetFiles: extractTargetFilesFromStoryContent(storyContent)
|
|
6725
|
+
});
|
|
6645
6726
|
if (findings.length > 0) {
|
|
6646
|
-
priorFindingsContent =
|
|
6727
|
+
priorFindingsContent = findings;
|
|
6647
6728
|
logger$15.debug({
|
|
6648
6729
|
storyKey,
|
|
6649
6730
|
findingsLen: findings.length
|
|
6650
|
-
}, "Injecting
|
|
6731
|
+
}, "Injecting relevance-scored findings into dev-story prompt");
|
|
6651
6732
|
}
|
|
6652
6733
|
} catch {}
|
|
6653
6734
|
let testPlanContent = "";
|
|
@@ -8213,809 +8294,6 @@ function detectConflictGroupsWithContracts(storyKeys, config, declarations) {
|
|
|
8213
8294
|
};
|
|
8214
8295
|
}
|
|
8215
8296
|
|
|
8216
|
-
//#endregion
|
|
8217
|
-
//#region packages/sdlc/dist/handlers/sdlc-create-story-handler.js
|
|
8218
|
-
/**
|
|
8219
|
-
* SdlcCreateStoryHandler — wraps the runCreateStory compiled workflow
|
|
8220
|
-
* as a graph NodeHandler for sdlc.create-story nodes.
|
|
8221
|
-
*
|
|
8222
|
-
* Story 43-3.
|
|
8223
|
-
*
|
|
8224
|
-
* Architecture note (ADR-003): The SDLC package does not compile-time-depend on
|
|
8225
|
-
* @substrate-ai/factory to avoid circular references. Compatible types are
|
|
8226
|
-
* defined locally using TypeScript structural typing — they are assignable to
|
|
8227
|
-
* the factory types when the CLI composition root wires them together at runtime.
|
|
8228
|
-
*/
|
|
8229
|
-
/**
|
|
8230
|
-
* Create an sdlc.create-story node handler.
|
|
8231
|
-
*
|
|
8232
|
-
* The returned handler:
|
|
8233
|
-
* 1. Validates storyKey and epicId are present in GraphContext (AC5)
|
|
8234
|
-
* 2. Emits orchestrator:story-phase-start telemetry (AC4)
|
|
8235
|
-
* 3. Delegates to runCreateStory(deps, { epicId, storyKey, pipelineRunId }) (AC1)
|
|
8236
|
-
* 4. Emits orchestrator:story-phase-complete telemetry (AC4)
|
|
8237
|
-
* 5. Maps the CreateStoryResult to an Outcome (AC2, AC3)
|
|
8238
|
-
*
|
|
8239
|
-
* @param options - Handler configuration.
|
|
8240
|
-
* @returns A NodeHandler function ready for registration under the 'sdlc.create-story' key.
|
|
8241
|
-
*/
|
|
8242
|
-
function createSdlcCreateStoryHandler(options) {
|
|
8243
|
-
return async (_node, context, _graph) => {
|
|
8244
|
-
const storyKey = context.getString("storyKey", "");
|
|
8245
|
-
if (!storyKey) return {
|
|
8246
|
-
status: "FAILURE",
|
|
8247
|
-
failureReason: "storyKey is required in GraphContext"
|
|
8248
|
-
};
|
|
8249
|
-
const epicId = context.getString("epicId", "");
|
|
8250
|
-
if (!epicId) return {
|
|
8251
|
-
status: "FAILURE",
|
|
8252
|
-
failureReason: "epicId is required in GraphContext"
|
|
8253
|
-
};
|
|
8254
|
-
const pipelineRunIdRaw = context.getString("pipelineRunId", "");
|
|
8255
|
-
const createStoryParams = pipelineRunIdRaw !== "" ? {
|
|
8256
|
-
epicId,
|
|
8257
|
-
storyKey,
|
|
8258
|
-
pipelineRunId: pipelineRunIdRaw
|
|
8259
|
-
} : {
|
|
8260
|
-
epicId,
|
|
8261
|
-
storyKey
|
|
8262
|
-
};
|
|
8263
|
-
options.eventBus.emit("orchestrator:story-phase-start", {
|
|
8264
|
-
storyKey,
|
|
8265
|
-
phase: "create-story"
|
|
8266
|
-
});
|
|
8267
|
-
let workflowResult;
|
|
8268
|
-
try {
|
|
8269
|
-
workflowResult = await options.runCreateStory(options.deps, createStoryParams);
|
|
8270
|
-
} catch (err) {
|
|
8271
|
-
const failureReason = err instanceof Error ? err.message : String(err);
|
|
8272
|
-
const errorResult = {
|
|
8273
|
-
result: "failed",
|
|
8274
|
-
error: failureReason,
|
|
8275
|
-
tokenUsage: {
|
|
8276
|
-
input: 0,
|
|
8277
|
-
output: 0
|
|
8278
|
-
}
|
|
8279
|
-
};
|
|
8280
|
-
options.eventBus.emit("orchestrator:story-phase-complete", {
|
|
8281
|
-
storyKey,
|
|
8282
|
-
phase: "create-story",
|
|
8283
|
-
result: errorResult
|
|
8284
|
-
});
|
|
8285
|
-
return {
|
|
8286
|
-
status: "FAILURE",
|
|
8287
|
-
failureReason
|
|
8288
|
-
};
|
|
8289
|
-
}
|
|
8290
|
-
options.eventBus.emit("orchestrator:story-phase-complete", {
|
|
8291
|
-
storyKey,
|
|
8292
|
-
phase: "create-story",
|
|
8293
|
-
result: workflowResult
|
|
8294
|
-
});
|
|
8295
|
-
if (workflowResult.result === "success") return {
|
|
8296
|
-
status: "SUCCESS",
|
|
8297
|
-
contextUpdates: {
|
|
8298
|
-
storyFilePath: workflowResult.story_file,
|
|
8299
|
-
storyKey: workflowResult.story_key,
|
|
8300
|
-
storyTitle: workflowResult.story_title
|
|
8301
|
-
}
|
|
8302
|
-
};
|
|
8303
|
-
return {
|
|
8304
|
-
status: "FAILURE",
|
|
8305
|
-
failureReason: workflowResult.error ?? workflowResult.details ?? "create-story workflow failed"
|
|
8306
|
-
};
|
|
8307
|
-
};
|
|
8308
|
-
}
|
|
8309
|
-
|
|
8310
|
-
//#endregion
|
|
8311
|
-
//#region packages/sdlc/dist/handlers/sdlc-phase-handler.js
|
|
8312
|
-
/**
|
|
8313
|
-
* SdlcPhaseHandler — wraps SDLC pipeline phase execution as a graph NodeHandler.
|
|
8314
|
-
*
|
|
8315
|
-
* Story 43-2.
|
|
8316
|
-
*
|
|
8317
|
-
* Architecture note (ADR-003): This package does not import from @substrate-ai/factory
|
|
8318
|
-
* or from the monolith source tree at compile time. All external dependencies
|
|
8319
|
-
* (orchestrator, phaseDeps, phase runner functions) are injected via
|
|
8320
|
-
* SdlcPhaseHandlerDeps at construction time.
|
|
8321
|
-
*
|
|
8322
|
-
* TypeScript structural typing ensures the returned SdlcNodeHandler is
|
|
8323
|
-
* assignable to NodeHandler from @substrate-ai/factory at the CLI composition
|
|
8324
|
-
* root — no sdlc→factory import required.
|
|
8325
|
-
*/
|
|
8326
|
-
/**
|
|
8327
|
-
* Create an sdlc.phase node handler.
|
|
8328
|
-
*
|
|
8329
|
-
* The returned handler:
|
|
8330
|
-
* 1. Resolves the phase name from node.id (AC5)
|
|
8331
|
-
* 2. Looks up the corresponding runner in the PHASE_RUNNERS map (AC7)
|
|
8332
|
-
* 3. Calls the runner with phaseDeps and phase-specific params (AC1, AC2, AC5)
|
|
8333
|
-
* 4. On runner error, returns FAILURE without re-throwing (AC3)
|
|
8334
|
-
* 5. If advanceAfterRun !== false, calls orchestrator.advancePhase(runId) (AC4)
|
|
8335
|
-
* 6. If gate check fails (advanced === false), returns FAILURE with gate messages (AC4)
|
|
8336
|
-
* 7. On full success, returns SUCCESS with phase output in contextUpdates (AC1, AC2)
|
|
8337
|
-
*
|
|
8338
|
-
* @param deps - Injected dependencies: orchestrator, phaseDeps, phase runners.
|
|
8339
|
-
* @returns A SdlcNodeHandler ready for registration under the 'sdlc.phase' key.
|
|
8340
|
-
*/
|
|
8341
|
-
function createSdlcPhaseHandler(deps) {
|
|
8342
|
-
const PHASE_RUNNERS = new Map(Object.entries(deps.phases));
|
|
8343
|
-
return async (node, context, _graph) => {
|
|
8344
|
-
const phaseName = node.id;
|
|
8345
|
-
const runner = PHASE_RUNNERS.get(phaseName);
|
|
8346
|
-
if (runner === void 0) return {
|
|
8347
|
-
status: "FAILURE",
|
|
8348
|
-
failureReason: `No phase runner registered for phase: ${phaseName}`
|
|
8349
|
-
};
|
|
8350
|
-
const runId = context.getString("runId");
|
|
8351
|
-
const concept = phaseName === "analysis" ? context.getString("concept", "") : "";
|
|
8352
|
-
const PRE_IMPL_PHASES = [
|
|
8353
|
-
"analysis",
|
|
8354
|
-
"planning",
|
|
8355
|
-
"solutioning"
|
|
8356
|
-
];
|
|
8357
|
-
const storyKey = context.getString("storyKey", "");
|
|
8358
|
-
if (storyKey && PRE_IMPL_PHASES.includes(phaseName)) return {
|
|
8359
|
-
status: "SUCCESS",
|
|
8360
|
-
notes: `Phase ${phaseName} skipped — explicit story dispatch (storyKey=${storyKey})`
|
|
8361
|
-
};
|
|
8362
|
-
const PHASE_ARTIFACT_TYPES = {
|
|
8363
|
-
analysis: ["product-brief"],
|
|
8364
|
-
planning: ["prd"],
|
|
8365
|
-
solutioning: ["architecture", "stories"]
|
|
8366
|
-
};
|
|
8367
|
-
const artifactTypes = PHASE_ARTIFACT_TYPES[phaseName];
|
|
8368
|
-
if (artifactTypes !== void 0) try {
|
|
8369
|
-
const db = deps.phaseDeps.db;
|
|
8370
|
-
if (db) {
|
|
8371
|
-
let allExist = true;
|
|
8372
|
-
for (const at of artifactTypes) {
|
|
8373
|
-
const rows = await db.query("SELECT id, path, content_hash, summary FROM artifacts WHERE phase = ? AND type = ? ORDER BY created_at DESC LIMIT 1", [phaseName, at]);
|
|
8374
|
-
if (!Array.isArray(rows) || rows.length === 0) {
|
|
8375
|
-
allExist = false;
|
|
8376
|
-
break;
|
|
8377
|
-
}
|
|
8378
|
-
}
|
|
8379
|
-
if (allExist) {
|
|
8380
|
-
const pipelineRunId = context.getString("pipelineRunId", "");
|
|
8381
|
-
if (pipelineRunId) for (const at of artifactTypes) {
|
|
8382
|
-
const existing = await db.query("SELECT id, path, content_hash, summary FROM artifacts WHERE phase = ? AND type = ? ORDER BY created_at DESC LIMIT 1", [phaseName, at]);
|
|
8383
|
-
const src = existing[0];
|
|
8384
|
-
if (src) {
|
|
8385
|
-
const alreadyRegistered = await db.query("SELECT id FROM artifacts WHERE pipeline_run_id = ? AND phase = ? AND type = ? LIMIT 1", [
|
|
8386
|
-
pipelineRunId,
|
|
8387
|
-
phaseName,
|
|
8388
|
-
at
|
|
8389
|
-
]);
|
|
8390
|
-
if (!Array.isArray(alreadyRegistered) || alreadyRegistered.length === 0) {
|
|
8391
|
-
const newId = crypto.randomUUID();
|
|
8392
|
-
await db.query("INSERT INTO artifacts (id, pipeline_run_id, phase, type, path, content_hash, summary) VALUES (?, ?, ?, ?, ?, ?, ?)", [
|
|
8393
|
-
newId,
|
|
8394
|
-
pipelineRunId,
|
|
8395
|
-
phaseName,
|
|
8396
|
-
at,
|
|
8397
|
-
src.path,
|
|
8398
|
-
src.content_hash ?? null,
|
|
8399
|
-
src.summary ?? null
|
|
8400
|
-
]);
|
|
8401
|
-
}
|
|
8402
|
-
}
|
|
8403
|
-
}
|
|
8404
|
-
return {
|
|
8405
|
-
status: "SUCCESS",
|
|
8406
|
-
notes: `Phase ${phaseName} already complete — artifact(s) exist, skipping dispatch`
|
|
8407
|
-
};
|
|
8408
|
-
}
|
|
8409
|
-
}
|
|
8410
|
-
} catch {}
|
|
8411
|
-
const params = phaseName === "analysis" ? {
|
|
8412
|
-
runId,
|
|
8413
|
-
concept
|
|
8414
|
-
} : { runId };
|
|
8415
|
-
try {
|
|
8416
|
-
const entryGateResult = await deps.orchestrator.evaluateEntryGates(runId);
|
|
8417
|
-
if (!entryGateResult.passed) {
|
|
8418
|
-
const failures = entryGateResult.failures?.map((f$1) => `${f$1.gate}: ${f$1.error}`).join("; ") ?? "no details";
|
|
8419
|
-
return {
|
|
8420
|
-
status: "FAILURE",
|
|
8421
|
-
failureReason: `entry gate failed: ${failures}`
|
|
8422
|
-
};
|
|
8423
|
-
}
|
|
8424
|
-
const phaseOutput = await runner(deps.phaseDeps, params);
|
|
8425
|
-
if (deps.advanceAfterRun !== false) {
|
|
8426
|
-
const advanceResult = await deps.orchestrator.advancePhase(runId);
|
|
8427
|
-
if (!advanceResult.advanced) {
|
|
8428
|
-
const failures = advanceResult.gateFailures?.map((f$1) => `${f$1.gate}: ${f$1.error}`).join("; ") ?? "no details";
|
|
8429
|
-
return {
|
|
8430
|
-
status: "FAILURE",
|
|
8431
|
-
failureReason: `exit gate failed: ${failures}`
|
|
8432
|
-
};
|
|
8433
|
-
}
|
|
8434
|
-
return {
|
|
8435
|
-
status: "SUCCESS",
|
|
8436
|
-
contextUpdates: {
|
|
8437
|
-
...phaseOutput,
|
|
8438
|
-
advancedPhase: advanceResult.phase
|
|
8439
|
-
}
|
|
8440
|
-
};
|
|
8441
|
-
}
|
|
8442
|
-
return {
|
|
8443
|
-
status: "SUCCESS",
|
|
8444
|
-
contextUpdates: phaseOutput
|
|
8445
|
-
};
|
|
8446
|
-
} catch (err) {
|
|
8447
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
8448
|
-
return {
|
|
8449
|
-
status: "FAILURE",
|
|
8450
|
-
failureReason: message
|
|
8451
|
-
};
|
|
8452
|
-
}
|
|
8453
|
-
};
|
|
8454
|
-
}
|
|
8455
|
-
|
|
8456
|
-
//#endregion
|
|
8457
|
-
//#region packages/sdlc/dist/handlers/sdlc-dev-story-handler.js
|
|
8458
|
-
/**
|
|
8459
|
-
* SdlcDevStoryHandler — wraps the runDevStory compiled workflow
|
|
8460
|
-
* as a graph NodeHandler for sdlc.dev-story nodes.
|
|
8461
|
-
*
|
|
8462
|
-
* Story 43-4.
|
|
8463
|
-
*
|
|
8464
|
-
* Architecture note (ADR-003): The SDLC package does not compile-time-depend on
|
|
8465
|
-
* @substrate-ai/factory to avoid circular references. Compatible types are
|
|
8466
|
-
* defined locally using TypeScript structural typing — they are assignable to
|
|
8467
|
-
* the factory types when the CLI composition root wires them together at runtime.
|
|
8468
|
-
*/
|
|
8469
|
-
/**
|
|
8470
|
-
* Create an sdlc.dev-story node handler.
|
|
8471
|
-
*
|
|
8472
|
-
* The returned handler:
|
|
8473
|
-
* 1. Validates storyKey and storyFilePath are present in GraphContext (AC6)
|
|
8474
|
-
* 2. Reads optional retry remediation context from prior iteration (AC4)
|
|
8475
|
-
* 3. Emits orchestrator:story-phase-start telemetry (AC5)
|
|
8476
|
-
* 4. Delegates to runDevStory(deps, params) (AC1)
|
|
8477
|
-
* 5. Emits orchestrator:story-phase-complete telemetry in finally block (AC5)
|
|
8478
|
-
* 6. Maps the DevStoryResult to an Outcome (AC2, AC3)
|
|
8479
|
-
*
|
|
8480
|
-
* @param options - Handler configuration.
|
|
8481
|
-
* @returns A NodeHandler function ready for registration under the 'sdlc.dev-story' key.
|
|
8482
|
-
*/
|
|
8483
|
-
function createSdlcDevStoryHandler(options) {
|
|
8484
|
-
return async (_node, context, _graph) => {
|
|
8485
|
-
const storyKey = context.getString("storyKey", "");
|
|
8486
|
-
const storyFilePath = context.getString("storyFilePath", "");
|
|
8487
|
-
if (!storyKey || !storyFilePath) {
|
|
8488
|
-
const missingFields = [!storyKey && "storyKey", !storyFilePath && "storyFilePath"].filter(Boolean);
|
|
8489
|
-
return {
|
|
8490
|
-
status: "FAILURE",
|
|
8491
|
-
failureReason: `Missing required context: ${missingFields.join(", ")}`
|
|
8492
|
-
};
|
|
8493
|
-
}
|
|
8494
|
-
const pipelineRunIdRaw = context.getString("pipelineRunId", "");
|
|
8495
|
-
const priorFiles = context.getList?.("devStoryFilesModified") ?? [];
|
|
8496
|
-
const priorAcFailures = context.getList?.("devStoryAcFailures") ?? [];
|
|
8497
|
-
const devStoryParams = {
|
|
8498
|
-
storyKey,
|
|
8499
|
-
storyFilePath,
|
|
8500
|
-
...pipelineRunIdRaw !== "" ? { pipelineRunId: pipelineRunIdRaw } : {},
|
|
8501
|
-
...priorFiles.length > 0 ? { priorFiles } : {},
|
|
8502
|
-
...priorAcFailures.length > 0 ? { taskScope: `Prior attempt failed ACs: ${priorAcFailures.join(", ")}` } : {}
|
|
8503
|
-
};
|
|
8504
|
-
options.eventBus.emit("orchestrator:story-phase-start", {
|
|
8505
|
-
storyKey,
|
|
8506
|
-
phase: "dev-story",
|
|
8507
|
-
...pipelineRunIdRaw !== "" ? { pipelineRunId: pipelineRunIdRaw } : {}
|
|
8508
|
-
});
|
|
8509
|
-
let outcome = {
|
|
8510
|
-
status: "FAILURE",
|
|
8511
|
-
failureReason: "unexpected error in dev-story handler"
|
|
8512
|
-
};
|
|
8513
|
-
try {
|
|
8514
|
-
const workflowResult = await options.runDevStory(options.deps, devStoryParams);
|
|
8515
|
-
if (workflowResult.result === "success") {
|
|
8516
|
-
if (options.buildVerifier) {
|
|
8517
|
-
const projectRoot = context.getString("projectRoot", "");
|
|
8518
|
-
if (projectRoot) {
|
|
8519
|
-
const buildResult = options.buildVerifier(projectRoot);
|
|
8520
|
-
if (buildResult.status === "failed" || buildResult.status === "timeout") {
|
|
8521
|
-
outcome = {
|
|
8522
|
-
status: "FAILURE",
|
|
8523
|
-
failureReason: `build verification failed after dev-story: ${buildResult.output?.slice(0, 500) ?? "no output"}`,
|
|
8524
|
-
contextUpdates: {
|
|
8525
|
-
filesModified: workflowResult.files_modified,
|
|
8526
|
-
devStoryFilesModified: workflowResult.files_modified,
|
|
8527
|
-
devStoryAcFailures: ["build-verification"]
|
|
8528
|
-
}
|
|
8529
|
-
};
|
|
8530
|
-
return outcome;
|
|
8531
|
-
}
|
|
8532
|
-
}
|
|
8533
|
-
}
|
|
8534
|
-
outcome = {
|
|
8535
|
-
status: "SUCCESS",
|
|
8536
|
-
contextUpdates: {
|
|
8537
|
-
filesModified: workflowResult.files_modified,
|
|
8538
|
-
acMet: workflowResult.ac_met,
|
|
8539
|
-
devStoryFilesModified: workflowResult.files_modified
|
|
8540
|
-
}
|
|
8541
|
-
};
|
|
8542
|
-
} else {
|
|
8543
|
-
const failureReason = workflowResult.error ?? (workflowResult.ac_failures.length > 0 ? `dev-story failed ACs: ${workflowResult.ac_failures.join(", ")}` : "dev-story workflow failed");
|
|
8544
|
-
outcome = {
|
|
8545
|
-
status: "FAILURE",
|
|
8546
|
-
failureReason,
|
|
8547
|
-
contextUpdates: {
|
|
8548
|
-
acFailures: workflowResult.ac_failures,
|
|
8549
|
-
filesModified: workflowResult.files_modified,
|
|
8550
|
-
devStoryFilesModified: workflowResult.files_modified,
|
|
8551
|
-
devStoryAcFailures: workflowResult.ac_failures
|
|
8552
|
-
}
|
|
8553
|
-
};
|
|
8554
|
-
}
|
|
8555
|
-
} catch (err) {
|
|
8556
|
-
const failureReason = err instanceof Error ? err.message : String(err);
|
|
8557
|
-
outcome = {
|
|
8558
|
-
status: "FAILURE",
|
|
8559
|
-
failureReason
|
|
8560
|
-
};
|
|
8561
|
-
} finally {
|
|
8562
|
-
options.eventBus.emit("orchestrator:story-phase-complete", {
|
|
8563
|
-
storyKey,
|
|
8564
|
-
phase: "dev-story",
|
|
8565
|
-
result: { status: outcome.status },
|
|
8566
|
-
...pipelineRunIdRaw !== "" ? { pipelineRunId: pipelineRunIdRaw } : {}
|
|
8567
|
-
});
|
|
8568
|
-
}
|
|
8569
|
-
return outcome;
|
|
8570
|
-
};
|
|
8571
|
-
}
|
|
8572
|
-
|
|
8573
|
-
//#endregion
|
|
8574
|
-
//#region packages/sdlc/dist/handlers/sdlc-code-review-handler.js
|
|
8575
|
-
/**
|
|
8576
|
-
* SdlcCodeReviewHandler — wraps the runCodeReview compiled workflow
|
|
8577
|
-
* as a graph NodeHandler for sdlc.code-review nodes.
|
|
8578
|
-
*
|
|
8579
|
-
* Story 43-5.
|
|
8580
|
-
*
|
|
8581
|
-
* Architecture note (ADR-003): The SDLC package does not compile-time-depend on
|
|
8582
|
-
* @substrate-ai/factory to avoid circular references. Compatible types are
|
|
8583
|
-
* defined locally using TypeScript structural typing — they are assignable to
|
|
8584
|
-
* the factory types when the CLI composition root wires them together at runtime.
|
|
8585
|
-
*/
|
|
8586
|
-
/**
|
|
8587
|
-
* Create an sdlc.code-review node handler.
|
|
8588
|
-
*
|
|
8589
|
-
* The returned handler:
|
|
8590
|
-
* 1. Validates storyKey and storyFilePath are present in GraphContext (AC4)
|
|
8591
|
-
* 2. Reads optional context fields: pipelineRunId, filesModified, codeReviewIssueList (AC5)
|
|
8592
|
-
* 3. Emits orchestrator:story-phase-start telemetry (AC6)
|
|
8593
|
-
* 4. Delegates to runCodeReview(deps, params)
|
|
8594
|
-
* 5. Emits orchestrator:story-phase-complete telemetry in finally block (AC6)
|
|
8595
|
-
* 6. Maps the CodeReviewResult verdict to an Outcome (AC1, AC2, AC3)
|
|
8596
|
-
*
|
|
8597
|
-
* Verdict mapping:
|
|
8598
|
-
* SHIP_IT / LGTM_WITH_NOTES → SUCCESS with preferredLabel: 'SHIP_IT' (AC1)
|
|
8599
|
-
* NEEDS_MINOR_FIXES / NEEDS_MAJOR_REWORK → FAILURE with preferredLabel: 'NEEDS_FIXES' (AC2)
|
|
8600
|
-
* dispatchFailed: true → FAILURE with escalation failureReason, no contextUpdates (AC3)
|
|
8601
|
-
* throws → FAILURE with error message, no contextUpdates
|
|
8602
|
-
*
|
|
8603
|
-
* @param options - Handler configuration.
|
|
8604
|
-
* @returns A NodeHandler function ready for registration under the 'sdlc.code-review' key.
|
|
8605
|
-
*/
|
|
8606
|
-
function createSdlcCodeReviewHandler(options) {
|
|
8607
|
-
return async (_node, context, _graph) => {
|
|
8608
|
-
const storyKey = context.getString("storyKey", "");
|
|
8609
|
-
const storyFilePath = context.getString("storyFilePath", "");
|
|
8610
|
-
if (!storyKey || !storyFilePath) {
|
|
8611
|
-
const missingFields = [!storyKey && "storyKey", !storyFilePath && "storyFilePath"].filter(Boolean);
|
|
8612
|
-
return {
|
|
8613
|
-
status: "FAILURE",
|
|
8614
|
-
failureReason: `Missing required context: ${missingFields.join(", ")}`
|
|
8615
|
-
};
|
|
8616
|
-
}
|
|
8617
|
-
const pipelineRunIdRaw = context.getString("pipelineRunId", "");
|
|
8618
|
-
const pipelineRunId = pipelineRunIdRaw !== "" ? pipelineRunIdRaw : void 0;
|
|
8619
|
-
const filesModifiedRaw = context.getList?.("filesModified") ?? [];
|
|
8620
|
-
const filesModified = filesModifiedRaw.length > 0 ? filesModifiedRaw : void 0;
|
|
8621
|
-
const codeReviewIssueListRaw = context.get?.("codeReviewIssueList");
|
|
8622
|
-
const previousIssues = Array.isArray(codeReviewIssueListRaw) && codeReviewIssueListRaw.length > 0 ? codeReviewIssueListRaw : void 0;
|
|
8623
|
-
const params = {
|
|
8624
|
-
storyKey,
|
|
8625
|
-
storyFilePath,
|
|
8626
|
-
...pipelineRunId !== void 0 ? { pipelineRunId } : {},
|
|
8627
|
-
...filesModified !== void 0 ? { filesModified } : {},
|
|
8628
|
-
...previousIssues !== void 0 ? { previousIssues } : {}
|
|
8629
|
-
};
|
|
8630
|
-
options.eventBus.emit("orchestrator:story-phase-start", {
|
|
8631
|
-
storyKey,
|
|
8632
|
-
phase: "code-review"
|
|
8633
|
-
});
|
|
8634
|
-
let outcome = {
|
|
8635
|
-
status: "FAILURE",
|
|
8636
|
-
failureReason: "unexpected error in code-review handler"
|
|
8637
|
-
};
|
|
8638
|
-
let codeReviewVerdict;
|
|
8639
|
-
try {
|
|
8640
|
-
const result = await options.runCodeReview(options.deps, params);
|
|
8641
|
-
if (result.dispatchFailed === true) {
|
|
8642
|
-
outcome = {
|
|
8643
|
-
status: "FAILURE",
|
|
8644
|
-
failureReason: `escalation: code-review dispatch failed: ${result.error ?? "unknown error"}`
|
|
8645
|
-
};
|
|
8646
|
-
return outcome;
|
|
8647
|
-
}
|
|
8648
|
-
codeReviewVerdict = result.verdict;
|
|
8649
|
-
const contextUpdates = {
|
|
8650
|
-
codeReviewVerdict: result.verdict,
|
|
8651
|
-
codeReviewIssues: result.issues,
|
|
8652
|
-
codeReviewIssueList: result.issue_list
|
|
8653
|
-
};
|
|
8654
|
-
if (result.verdict === "SHIP_IT" || result.verdict === "LGTM_WITH_NOTES") outcome = {
|
|
8655
|
-
status: "SUCCESS",
|
|
8656
|
-
preferredLabel: "SHIP_IT",
|
|
8657
|
-
contextUpdates
|
|
8658
|
-
};
|
|
8659
|
-
else outcome = {
|
|
8660
|
-
status: "FAILURE",
|
|
8661
|
-
preferredLabel: "NEEDS_FIXES",
|
|
8662
|
-
failureReason: `${result.verdict}: ${result.issues} issue(s)`,
|
|
8663
|
-
contextUpdates
|
|
8664
|
-
};
|
|
8665
|
-
} catch (err) {
|
|
8666
|
-
const failureReason = err instanceof Error ? err.message : String(err);
|
|
8667
|
-
outcome = {
|
|
8668
|
-
status: "FAILURE",
|
|
8669
|
-
failureReason
|
|
8670
|
-
};
|
|
8671
|
-
} finally {
|
|
8672
|
-
options.eventBus.emit("orchestrator:story-phase-complete", {
|
|
8673
|
-
storyKey,
|
|
8674
|
-
phase: "code-review",
|
|
8675
|
-
result: {
|
|
8676
|
-
status: outcome.status,
|
|
8677
|
-
verdict: codeReviewVerdict
|
|
8678
|
-
}
|
|
8679
|
-
});
|
|
8680
|
-
}
|
|
8681
|
-
return outcome;
|
|
8682
|
-
};
|
|
8683
|
-
}
|
|
8684
|
-
|
|
8685
|
-
//#endregion
|
|
8686
|
-
//#region packages/sdlc/dist/verification/checks/phantom-review-check.js
|
|
8687
|
-
/**
|
|
8688
|
-
* PhantomReviewCheck — Story 51-2.
|
|
8689
|
-
*
|
|
8690
|
-
* Tier A verification check that detects when a code review dispatch failed
|
|
8691
|
-
* but was recorded as a passing verdict. Stories that were never actually
|
|
8692
|
-
* reviewed should not be counted as verified.
|
|
8693
|
-
*
|
|
8694
|
-
* Architecture constraints (FR-V9):
|
|
8695
|
-
* - No LLM calls.
|
|
8696
|
-
* - No shell invocations — pure static signal inspection over VerificationContext fields.
|
|
8697
|
-
* - Runs first in Tier A (before TrivialOutputCheck, before BuildCheck).
|
|
8698
|
-
*/
|
|
8699
|
-
/**
|
|
8700
|
-
* Detects phantom reviews — dispatches that failed or produced no output but
|
|
8701
|
-
* were recorded as passing verdicts.
|
|
8702
|
-
*
|
|
8703
|
-
* AC1: dispatch failed (non-zero exit, timeout, crash) → fail
|
|
8704
|
-
* AC2: empty or null rawOutput → fail
|
|
8705
|
-
* AC3: schema_validation_failed error → fail
|
|
8706
|
-
* AC5: valid review (non-empty rawOutput, no dispatchFailed) → pass
|
|
8707
|
-
* AC6: name='phantom-review', tier='A'
|
|
8708
|
-
*/
|
|
8709
|
-
var PhantomReviewCheck = class {
|
|
8710
|
-
name = "phantom-review";
|
|
8711
|
-
tier = "A";
|
|
8712
|
-
async run(context) {
|
|
8713
|
-
const start = Date.now();
|
|
8714
|
-
const review = context.reviewResult;
|
|
8715
|
-
if (!review) return {
|
|
8716
|
-
status: "pass",
|
|
8717
|
-
details: "phantom-review: no review result in context — skipping check",
|
|
8718
|
-
duration_ms: Date.now() - start
|
|
8719
|
-
};
|
|
8720
|
-
if (review.dispatchFailed === true) {
|
|
8721
|
-
const reason = review.error === "schema_validation_failed" ? "schema validation failed" : `dispatch failed${review.error ? ` — ${review.error}` : ""}`;
|
|
8722
|
-
return {
|
|
8723
|
-
status: "fail",
|
|
8724
|
-
details: `phantom-review: ${reason}`,
|
|
8725
|
-
duration_ms: Date.now() - start
|
|
8726
|
-
};
|
|
8727
|
-
}
|
|
8728
|
-
if (review.rawOutput !== void 0 && review.rawOutput.trim().length === 0) return {
|
|
8729
|
-
status: "fail",
|
|
8730
|
-
details: "phantom-review: empty review output",
|
|
8731
|
-
duration_ms: Date.now() - start
|
|
8732
|
-
};
|
|
8733
|
-
return {
|
|
8734
|
-
status: "pass",
|
|
8735
|
-
details: "phantom-review: review output is valid",
|
|
8736
|
-
duration_ms: Date.now() - start
|
|
8737
|
-
};
|
|
8738
|
-
}
|
|
8739
|
-
};
|
|
8740
|
-
|
|
8741
|
-
//#endregion
|
|
8742
|
-
//#region packages/sdlc/dist/verification/checks/trivial-output-check.js
|
|
8743
|
-
/**
|
|
8744
|
-
* TrivialOutputCheck — Story 51-3.
|
|
8745
|
-
*
|
|
8746
|
-
* Tier A verification check that flags story dispatches which produced
|
|
8747
|
-
* fewer output tokens than the configured threshold. A very low output
|
|
8748
|
-
* token count is a strong signal that the agent exited early (e.g. hit a
|
|
8749
|
-
* maxTurns limit, encountered a fatal error, or did no real work).
|
|
8750
|
-
*
|
|
8751
|
-
* Architecture constraints (DC-6, FR-V9):
|
|
8752
|
-
* - No LLM calls.
|
|
8753
|
-
* - No shell invocations — pure in-process computation.
|
|
8754
|
-
* - Runs in Tier A: before BuildCheck, after PhantomReviewCheck.
|
|
8755
|
-
*/
|
|
8756
|
-
/**
|
|
8757
|
-
* Default minimum output-token count a story must produce to be
|
|
8758
|
-
* considered non-trivial. Configurable via trivialOutputThreshold config field.
|
|
8759
|
-
*/
|
|
8760
|
-
const DEFAULT_TRIVIAL_OUTPUT_THRESHOLD = 100;
|
|
8761
|
-
/**
|
|
8762
|
-
* Checks that a completed story dispatch produced at least `threshold` output
|
|
8763
|
-
* tokens. Dispatches that produced fewer tokens are flagged as failures with
|
|
8764
|
-
* an actionable suggestion to re-run with increased maxTurns.
|
|
8765
|
-
*
|
|
8766
|
-
* AC1: fail when outputTokenCount < threshold.
|
|
8767
|
-
* AC2: details string includes "Re-run with increased maxTurns".
|
|
8768
|
-
* AC3: pass when outputTokenCount >= threshold.
|
|
8769
|
-
* AC4: threshold is configurable via trivialOutputThreshold config field.
|
|
8770
|
-
* AC5: warn (not fail) when outputTokenCount is undefined.
|
|
8771
|
-
* AC6: implements VerificationCheck with name='trivial-output', tier='A'.
|
|
8772
|
-
*/
|
|
8773
|
-
var TrivialOutputCheck = class {
|
|
8774
|
-
name = "trivial-output";
|
|
8775
|
-
tier = "A";
|
|
8776
|
-
threshold;
|
|
8777
|
-
constructor(config) {
|
|
8778
|
-
this.threshold = config?.trivialOutputThreshold ?? DEFAULT_TRIVIAL_OUTPUT_THRESHOLD;
|
|
8779
|
-
}
|
|
8780
|
-
async run(context) {
|
|
8781
|
-
const start = Date.now();
|
|
8782
|
-
if (context.outputTokenCount === void 0) return {
|
|
8783
|
-
status: "warn",
|
|
8784
|
-
details: "trivial-output: output token count unavailable — skipping check",
|
|
8785
|
-
duration_ms: Date.now() - start
|
|
8786
|
-
};
|
|
8787
|
-
const count = context.outputTokenCount;
|
|
8788
|
-
if (count < this.threshold) return {
|
|
8789
|
-
status: "fail",
|
|
8790
|
-
details: `trivial-output: output token count ${count} is below threshold ${this.threshold} — Re-run with increased maxTurns`,
|
|
8791
|
-
duration_ms: Date.now() - start
|
|
8792
|
-
};
|
|
8793
|
-
return {
|
|
8794
|
-
status: "pass",
|
|
8795
|
-
details: `output token count ${count} meets threshold ${this.threshold}`,
|
|
8796
|
-
duration_ms: Date.now() - start
|
|
8797
|
-
};
|
|
8798
|
-
}
|
|
8799
|
-
};
|
|
8800
|
-
|
|
8801
|
-
//#endregion
|
|
8802
|
-
//#region packages/sdlc/dist/verification/checks/build-check.js
|
|
8803
|
-
/** Hard timeout for the build command in milliseconds (FR-V11). */
|
|
8804
|
-
const BUILD_CHECK_TIMEOUT_MS = 6e4;
|
|
8805
|
-
/** Maximum characters to include in details string from build output. */
|
|
8806
|
-
const MAX_OUTPUT_CHARS = 2e3;
|
|
8807
|
-
/**
|
|
8808
|
-
* Detect the build command for a project based on files present in `workingDir`.
|
|
8809
|
-
*
|
|
8810
|
-
* Returns an empty string when no recognized build system is found, which
|
|
8811
|
-
* causes BuildCheck to return a 'warn' result without blocking the pipeline.
|
|
8812
|
-
*
|
|
8813
|
-
* NOTE: Do NOT import from src/modules/agent-dispatch/dispatcher-impl.ts —
|
|
8814
|
-
* that would create a circular dependency from packages/sdlc/ → monolith src/.
|
|
8815
|
-
* This function inlines the detection logic independently.
|
|
8816
|
-
*/
|
|
8817
|
-
function detectBuildCommand(workingDir) {
|
|
8818
|
-
if (existsSync(join$1(workingDir, "turbo.json"))) return "turbo build";
|
|
8819
|
-
if (existsSync(join$1(workingDir, "pnpm-lock.yaml"))) return "pnpm run build";
|
|
8820
|
-
if (existsSync(join$1(workingDir, "yarn.lock"))) return "yarn build";
|
|
8821
|
-
if (existsSync(join$1(workingDir, "bun.lockb"))) return "bun run build";
|
|
8822
|
-
if (existsSync(join$1(workingDir, "package.json"))) return "npm run build";
|
|
8823
|
-
const nonNodeMarkers = [
|
|
8824
|
-
"pyproject.toml",
|
|
8825
|
-
"poetry.lock",
|
|
8826
|
-
"setup.py",
|
|
8827
|
-
"Cargo.toml",
|
|
8828
|
-
"go.mod"
|
|
8829
|
-
];
|
|
8830
|
-
for (const marker of nonNodeMarkers) if (existsSync(join$1(workingDir, marker))) return "";
|
|
8831
|
-
return "";
|
|
8832
|
-
}
|
|
8833
|
-
/**
|
|
8834
|
-
* Runs the project's build command and returns pass/warn/fail based on exit code.
|
|
8835
|
-
*
|
|
8836
|
-
* AC1: exit code 0 → pass
|
|
8837
|
-
* AC2: non-zero exit code → fail with truncated output in details
|
|
8838
|
-
* AC3: timeout → kill process group, return fail with timeout message
|
|
8839
|
-
* AC4: no recognized build system → warn without blocking
|
|
8840
|
-
* AC5: explicit buildCommand override respected; empty string → warn (skip)
|
|
8841
|
-
* AC6: name === 'build', tier === 'A'
|
|
8842
|
-
*/
|
|
8843
|
-
var BuildCheck = class {
|
|
8844
|
-
name = "build";
|
|
8845
|
-
tier = "A";
|
|
8846
|
-
async run(context) {
|
|
8847
|
-
const start = Date.now();
|
|
8848
|
-
const cmd = context.buildCommand !== void 0 ? context.buildCommand : detectBuildCommand(context.workingDir);
|
|
8849
|
-
if (cmd === "") return {
|
|
8850
|
-
status: "warn",
|
|
8851
|
-
details: `build-skip: no build command detected for project at ${context.workingDir}`,
|
|
8852
|
-
duration_ms: Date.now() - start
|
|
8853
|
-
};
|
|
8854
|
-
return new Promise((resolve$6) => {
|
|
8855
|
-
const child = spawn(cmd, [], {
|
|
8856
|
-
cwd: context.workingDir,
|
|
8857
|
-
detached: true,
|
|
8858
|
-
shell: true,
|
|
8859
|
-
stdio: [
|
|
8860
|
-
"ignore",
|
|
8861
|
-
"pipe",
|
|
8862
|
-
"pipe"
|
|
8863
|
-
]
|
|
8864
|
-
});
|
|
8865
|
-
let output = "";
|
|
8866
|
-
child.stdout?.on("data", (chunk) => {
|
|
8867
|
-
output += chunk.toString();
|
|
8868
|
-
});
|
|
8869
|
-
child.stderr?.on("data", (chunk) => {
|
|
8870
|
-
output += chunk.toString();
|
|
8871
|
-
});
|
|
8872
|
-
const timeoutHandle = setTimeout(() => {
|
|
8873
|
-
try {
|
|
8874
|
-
process.kill(-child.pid, "SIGKILL");
|
|
8875
|
-
} catch {}
|
|
8876
|
-
resolve$6({
|
|
8877
|
-
status: "fail",
|
|
8878
|
-
details: `build-timeout: command exceeded ${BUILD_CHECK_TIMEOUT_MS}ms`,
|
|
8879
|
-
duration_ms: Date.now() - start
|
|
8880
|
-
});
|
|
8881
|
-
}, BUILD_CHECK_TIMEOUT_MS);
|
|
8882
|
-
child.on("close", (code) => {
|
|
8883
|
-
clearTimeout(timeoutHandle);
|
|
8884
|
-
if (code === 0) resolve$6({
|
|
8885
|
-
status: "pass",
|
|
8886
|
-
details: "build passed",
|
|
8887
|
-
duration_ms: Date.now() - start
|
|
8888
|
-
});
|
|
8889
|
-
else {
|
|
8890
|
-
const truncated = output.length > MAX_OUTPUT_CHARS ? output.slice(0, MAX_OUTPUT_CHARS) + "... (truncated)" : output;
|
|
8891
|
-
resolve$6({
|
|
8892
|
-
status: "fail",
|
|
8893
|
-
details: `build failed (exit ${code}): ${truncated}`,
|
|
8894
|
-
duration_ms: Date.now() - start
|
|
8895
|
-
});
|
|
8896
|
-
}
|
|
8897
|
-
});
|
|
8898
|
-
});
|
|
8899
|
-
}
|
|
8900
|
-
};
|
|
8901
|
-
|
|
8902
|
-
//#endregion
|
|
8903
|
-
//#region packages/sdlc/dist/verification/verification-pipeline.js
|
|
8904
|
-
/**
|
|
8905
|
-
* Compute the worst-case aggregate status across a list of check results.
|
|
8906
|
-
* Precedence: fail > warn > pass.
|
|
8907
|
-
*/
|
|
8908
|
-
function aggregateStatus(checks) {
|
|
8909
|
-
let result = "pass";
|
|
8910
|
-
for (const c of checks) {
|
|
8911
|
-
if (c.status === "fail") return "fail";
|
|
8912
|
-
if (c.status === "warn") result = "warn";
|
|
8913
|
-
}
|
|
8914
|
-
return result;
|
|
8915
|
-
}
|
|
8916
|
-
/**
|
|
8917
|
-
* Runs an ordered chain of VerificationCheck implementations after each story dispatch.
|
|
8918
|
-
*
|
|
8919
|
-
* Checks are stored in registration order. When `run()` is called with `tier: 'A'`
|
|
8920
|
-
* only Tier A checks execute; when called with `tier: 'B'` only Tier B checks execute.
|
|
8921
|
-
* (Story 51-5 will invoke both tiers at the appropriate orchestration points.)
|
|
8922
|
-
*/
|
|
8923
|
-
var VerificationPipeline = class {
|
|
8924
|
-
_bus;
|
|
8925
|
-
_checks = [];
|
|
8926
|
-
/**
|
|
8927
|
-
* @param bus Typed event bus for emitting verification events.
|
|
8928
|
-
* @param checks Optional initial list of checks to register at construction time.
|
|
8929
|
-
*/
|
|
8930
|
-
constructor(bus, checks = []) {
|
|
8931
|
-
this._bus = bus;
|
|
8932
|
-
for (const check of checks) this.register(check);
|
|
8933
|
-
}
|
|
8934
|
-
/**
|
|
8935
|
-
* Register a VerificationCheck.
|
|
8936
|
-
*
|
|
8937
|
-
* Checks are stored in insertion order within their tier.
|
|
8938
|
-
* Tier A checks always run before Tier B checks regardless of registration order.
|
|
8939
|
-
*/
|
|
8940
|
-
register(check) {
|
|
8941
|
-
this._checks.push(check);
|
|
8942
|
-
}
|
|
8943
|
-
/**
|
|
8944
|
-
* Execute all checks matching the specified tier sequentially.
|
|
8945
|
-
*
|
|
8946
|
-
* AC2: Tier A checks execute in registration order.
|
|
8947
|
-
* AC4: Results are aggregated into a VerificationSummary.
|
|
8948
|
-
* AC5: verification:check-complete and verification:story-complete events are emitted.
|
|
8949
|
-
* AC6: Unhandled exceptions are caught and recorded as warn.
|
|
8950
|
-
*
|
|
8951
|
-
* @param context Verification context for the story being verified.
|
|
8952
|
-
* @param tier Which tier of checks to execute ('A' | 'B'). Defaults to 'A'.
|
|
8953
|
-
*/
|
|
8954
|
-
async run(context, tier = "A") {
|
|
8955
|
-
const pipelineStart = Date.now();
|
|
8956
|
-
const checks = this._checks.filter((c) => c.tier === tier);
|
|
8957
|
-
const checkResults = [];
|
|
8958
|
-
for (const check of checks) {
|
|
8959
|
-
const checkStart = Date.now();
|
|
8960
|
-
let result;
|
|
8961
|
-
try {
|
|
8962
|
-
const runResult = await check.run(context);
|
|
8963
|
-
result = {
|
|
8964
|
-
checkName: check.name,
|
|
8965
|
-
status: runResult.status,
|
|
8966
|
-
details: runResult.details,
|
|
8967
|
-
duration_ms: runResult.duration_ms
|
|
8968
|
-
};
|
|
8969
|
-
} catch (err) {
|
|
8970
|
-
const elapsed = Date.now() - checkStart;
|
|
8971
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
8972
|
-
process.stderr.write(`[verification-pipeline] check "${check.name}" threw an unhandled exception: ${message}\n`);
|
|
8973
|
-
result = {
|
|
8974
|
-
checkName: check.name,
|
|
8975
|
-
status: "warn",
|
|
8976
|
-
details: message,
|
|
8977
|
-
duration_ms: elapsed
|
|
8978
|
-
};
|
|
8979
|
-
}
|
|
8980
|
-
checkResults.push(result);
|
|
8981
|
-
this._bus.emit("verification:check-complete", {
|
|
8982
|
-
storyKey: context.storyKey,
|
|
8983
|
-
checkName: result.checkName,
|
|
8984
|
-
status: result.status,
|
|
8985
|
-
details: result.details,
|
|
8986
|
-
duration_ms: result.duration_ms
|
|
8987
|
-
});
|
|
8988
|
-
}
|
|
8989
|
-
const summary = {
|
|
8990
|
-
storyKey: context.storyKey,
|
|
8991
|
-
checks: checkResults,
|
|
8992
|
-
status: aggregateStatus(checkResults),
|
|
8993
|
-
duration_ms: Date.now() - pipelineStart
|
|
8994
|
-
};
|
|
8995
|
-
this._bus.emit("verification:story-complete", summary);
|
|
8996
|
-
return summary;
|
|
8997
|
-
}
|
|
8998
|
-
};
|
|
8999
|
-
/**
|
|
9000
|
-
* Create a VerificationPipeline pre-loaded with the canonical check set.
|
|
9001
|
-
*
|
|
9002
|
-
* Canonical Tier A check order (architecture section 3.5):
|
|
9003
|
-
* 1. PhantomReviewCheck — story 51-2 (runs first: unreviewed stories skipped)
|
|
9004
|
-
* 2. TrivialOutputCheck — story 51-3
|
|
9005
|
-
* 3. BuildCheck — story 51-4
|
|
9006
|
-
*
|
|
9007
|
-
* @param bus Typed event bus for verification events.
|
|
9008
|
-
* @param config Optional config (used to forward threshold to TrivialOutputCheck).
|
|
9009
|
-
*/
|
|
9010
|
-
function createDefaultVerificationPipeline(bus, config) {
|
|
9011
|
-
const checks = [
|
|
9012
|
-
new PhantomReviewCheck(),
|
|
9013
|
-
new TrivialOutputCheck(config),
|
|
9014
|
-
new BuildCheck()
|
|
9015
|
-
];
|
|
9016
|
-
return new VerificationPipeline(bus, checks);
|
|
9017
|
-
}
|
|
9018
|
-
|
|
9019
8297
|
//#endregion
|
|
9020
8298
|
//#region src/modules/implementation-orchestrator/seed-methodology-context.ts
|
|
9021
8299
|
const logger$11 = createLogger("implementation-orchestrator:seed");
|
|
@@ -10867,6 +10145,58 @@ function persistVerificationResult(storyKey, summary, runManifest) {
|
|
|
10867
10145
|
}, "manifest verification_result write failed — pipeline continues"));
|
|
10868
10146
|
}
|
|
10869
10147
|
|
|
10148
|
+
//#endregion
|
|
10149
|
+
//#region src/modules/implementation-orchestrator/cost-governance.ts
|
|
10150
|
+
/**
|
|
10151
|
+
* Pure checker for run-level cost governance.
|
|
10152
|
+
*
|
|
10153
|
+
* Instantiate with `new CostGovernanceChecker()` — no constructor arguments.
|
|
10154
|
+
* All methods are stateless; results depend only on the manifest data passed in.
|
|
10155
|
+
*/
|
|
10156
|
+
var CostGovernanceChecker = class {
|
|
10157
|
+
/**
|
|
10158
|
+
* Compute cumulative run cost from the manifest.
|
|
10159
|
+
*
|
|
10160
|
+
* Sums `per_story_state[key].cost_usd ?? 0` for all story keys, then adds
|
|
10161
|
+
* `manifest.cost_accumulation.run_total` (retry cost).
|
|
10162
|
+
*/
|
|
10163
|
+
computeCumulativeCost(manifest) {
|
|
10164
|
+
const dispatchCost = Object.values(manifest.per_story_state).reduce((sum, s$1) => sum + (s$1.cost_usd ?? 0), 0);
|
|
10165
|
+
return dispatchCost + manifest.cost_accumulation.run_total;
|
|
10166
|
+
}
|
|
10167
|
+
/**
|
|
10168
|
+
* Estimate the cost of the next story.
|
|
10169
|
+
*
|
|
10170
|
+
* Returns the average `cost_usd` of stories that have a non-zero `cost_usd`.
|
|
10171
|
+
* Returns `0` if no completed stories with a cost exist.
|
|
10172
|
+
*/
|
|
10173
|
+
estimateNextStoryCost(manifest) {
|
|
10174
|
+
const completed = Object.values(manifest.per_story_state).map((s$1) => s$1.cost_usd).filter((c) => c !== void 0 && c > 0);
|
|
10175
|
+
if (completed.length === 0) return 0;
|
|
10176
|
+
return completed.reduce((s$1, c) => s$1 + c, 0) / completed.length;
|
|
10177
|
+
}
|
|
10178
|
+
/**
|
|
10179
|
+
* Check the cumulative run cost against the provided ceiling.
|
|
10180
|
+
*
|
|
10181
|
+
* @param manifest - Current run manifest data
|
|
10182
|
+
* @param ceiling - Cost ceiling in USD (must be > 0)
|
|
10183
|
+
* @returns CeilingCheckResult with status, cumulative, ceiling, percentUsed, estimatedNext
|
|
10184
|
+
*/
|
|
10185
|
+
checkCeiling(manifest, ceiling) {
|
|
10186
|
+
const cumulative = this.computeCumulativeCost(manifest);
|
|
10187
|
+
const estimatedNext = this.estimateNextStoryCost(manifest);
|
|
10188
|
+
const percentUsed = Math.round(cumulative / ceiling * 1e4) / 100;
|
|
10189
|
+
const status = percentUsed >= 100 ? "exceeded" : percentUsed >= 80 ? "warning" : "ok";
|
|
10190
|
+
return {
|
|
10191
|
+
status,
|
|
10192
|
+
cumulative,
|
|
10193
|
+
ceiling,
|
|
10194
|
+
percentUsed,
|
|
10195
|
+
estimatedNext
|
|
10196
|
+
};
|
|
10197
|
+
}
|
|
10198
|
+
};
|
|
10199
|
+
|
|
10870
10200
|
//#endregion
|
|
10871
10201
|
//#region src/modules/work-graph/epic-ingester.ts
|
|
10872
10202
|
var EpicIngester = class {
|
|
@@ -11663,10 +10993,14 @@ function createImplementationOrchestrator(deps) {
|
|
|
11663
10993
|
const _phaseStartMs = new Map();
|
|
11664
10994
|
const _phaseEndMs = new Map();
|
|
11665
10995
|
const _storyDispatches = new Map();
|
|
10996
|
+
const _storyRetryCount = new Map();
|
|
11666
10997
|
let _completedDispatches = 0;
|
|
11667
10998
|
let _maxConcurrentActual = 0;
|
|
11668
10999
|
let _packageSnapshot;
|
|
11669
11000
|
let _contractMismatches;
|
|
11001
|
+
const _costChecker = new CostGovernanceChecker();
|
|
11002
|
+
let _costWarningEmitted = false;
|
|
11003
|
+
let _budgetExhausted = false;
|
|
11670
11004
|
let _otlpEndpoint;
|
|
11671
11005
|
const verificationStore = new VerificationStore();
|
|
11672
11006
|
const verificationPipeline = createDefaultVerificationPipeline(toSdlcEventBus(eventBus));
|
|
@@ -11689,6 +11023,41 @@ function createImplementationOrchestrator(deps) {
|
|
|
11689
11023
|
function incrementDispatches(storyKey) {
|
|
11690
11024
|
_storyDispatches.set(storyKey, (_storyDispatches.get(storyKey) ?? 0) + 1);
|
|
11691
11025
|
}
|
|
11026
|
+
/**
|
|
11027
|
+
* Initialize `_storyRetryCount` from the run manifest for crash-recovery durability (AC6, Story 53-4).
|
|
11028
|
+
* Reads persisted retry_count so that budget gate correctly accounts for prior-session retries.
|
|
11029
|
+
* Best-effort: failures result in a starting count of 0 (safe — may allow one extra retry).
|
|
11030
|
+
*/
|
|
11031
|
+
async function initRetryCount(storyKey) {
|
|
11032
|
+
if (runManifest === null || runManifest === void 0) {
|
|
11033
|
+
_storyRetryCount.set(storyKey, 0);
|
|
11034
|
+
return;
|
|
11035
|
+
}
|
|
11036
|
+
try {
|
|
11037
|
+
const data = await runManifest.read();
|
|
11038
|
+
const storyState = data.per_story_state[storyKey];
|
|
11039
|
+
const existingCount = storyState?.retry_count ?? 0;
|
|
11040
|
+
_storyRetryCount.set(storyKey, existingCount);
|
|
11041
|
+
} catch (err) {
|
|
11042
|
+
logger$23.warn({
|
|
11043
|
+
err,
|
|
11044
|
+
storyKey
|
|
11045
|
+
}, "initRetryCount: failed to read manifest — starting at 0");
|
|
11046
|
+
_storyRetryCount.set(storyKey, 0);
|
|
11047
|
+
}
|
|
11048
|
+
}
|
|
11049
|
+
/**
|
|
11050
|
+
* Increment the in-memory retry count and persist best-effort to the run manifest (AC4, Story 53-4).
|
|
11051
|
+
*/
|
|
11052
|
+
function incrementRetryCount(storyKey) {
|
|
11053
|
+
const current = _storyRetryCount.get(storyKey) ?? 0;
|
|
11054
|
+
const next = current + 1;
|
|
11055
|
+
_storyRetryCount.set(storyKey, next);
|
|
11056
|
+
if (runManifest !== null && runManifest !== void 0) runManifest.patchStoryState(storyKey, { retry_count: next }).catch((err) => logger$23.warn({
|
|
11057
|
+
err,
|
|
11058
|
+
storyKey
|
|
11059
|
+
}, "patchStoryState(retry_count) failed — pipeline continues"));
|
|
11060
|
+
}
|
|
11692
11061
|
function buildPhaseDurationsJson(storyKey) {
|
|
11693
11062
|
const starts = _phaseStartMs.get(storyKey);
|
|
11694
11063
|
const ends = _phaseEndMs.get(storyKey);
|
|
@@ -12219,6 +11588,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
12219
11588
|
*/
|
|
12220
11589
|
async function processStory(storyKey, storyOptions) {
|
|
12221
11590
|
logger$23.info({ storyKey }, "Processing story");
|
|
11591
|
+
await initRetryCount(storyKey);
|
|
12222
11592
|
{
|
|
12223
11593
|
const memoryOk = await checkMemoryPressure(storyKey);
|
|
12224
11594
|
if (!memoryOk) {
|
|
@@ -13222,6 +12592,31 @@ function createImplementationOrchestrator(deps) {
|
|
|
13222
12592
|
await waitIfPaused();
|
|
13223
12593
|
if (_state !== "RUNNING") return;
|
|
13224
12594
|
if (reviewCycles === 0) startPhase(storyKey, "code-review");
|
|
12595
|
+
if (reviewCycles > 0) {
|
|
12596
|
+
const currentRetries = _storyRetryCount.get(storyKey) ?? 0;
|
|
12597
|
+
const budget = config.retryBudget ?? 2;
|
|
12598
|
+
if (currentRetries >= budget) {
|
|
12599
|
+
endPhase(storyKey, "code-review");
|
|
12600
|
+
updateStory(storyKey, {
|
|
12601
|
+
phase: "ESCALATED",
|
|
12602
|
+
reviewCycles,
|
|
12603
|
+
completedAt: new Date().toISOString(),
|
|
12604
|
+
error: "retry_budget_exhausted"
|
|
12605
|
+
});
|
|
12606
|
+
await writeStoryMetricsBestEffort(storyKey, "escalated", reviewCycles);
|
|
12607
|
+
await emitEscalation({
|
|
12608
|
+
storyKey,
|
|
12609
|
+
lastVerdict: "retry_budget_exhausted",
|
|
12610
|
+
reviewCycles,
|
|
12611
|
+
issues: [`retry budget exhausted: ${currentRetries}/${budget} retries used`],
|
|
12612
|
+
retryBudget: budget,
|
|
12613
|
+
retryCount: currentRetries
|
|
12614
|
+
});
|
|
12615
|
+
await persistState();
|
|
12616
|
+
return;
|
|
12617
|
+
}
|
|
12618
|
+
incrementRetryCount(storyKey);
|
|
12619
|
+
}
|
|
13225
12620
|
updateStory(storyKey, {
|
|
13226
12621
|
phase: "IN_REVIEW",
|
|
13227
12622
|
reviewCycles
|
|
@@ -13952,6 +13347,45 @@ function createImplementationOrchestrator(deps) {
|
|
|
13952
13347
|
}
|
|
13953
13348
|
}
|
|
13954
13349
|
/**
|
|
13350
|
+
* Handle the cost ceiling being exceeded before dispatching a story (Story 53-3).
|
|
13351
|
+
*
|
|
13352
|
+
* Transitions all skipped stories to ESCALATED phase, emits the
|
|
13353
|
+
* cost:ceiling-reached NDJSON event, and sets _budgetExhausted so that
|
|
13354
|
+
* runWithConcurrency stops enqueuing new groups.
|
|
13355
|
+
*
|
|
13356
|
+
* @param triggeredStoryKey - The story that would have been dispatched next
|
|
13357
|
+
* @param remainingInGroup - Other stories in the same conflict group after triggeredStoryKey
|
|
13358
|
+
* @param result - The ceiling check result
|
|
13359
|
+
* @param manifest - The current run manifest data
|
|
13360
|
+
*/
|
|
13361
|
+
async function handleCeilingExceeded(triggeredStoryKey, remainingInGroup, result, manifest) {
|
|
13362
|
+
const haltOn = manifest.cli_flags.halt_on ?? "none";
|
|
13363
|
+
const allSkipped = [triggeredStoryKey, ...remainingInGroup];
|
|
13364
|
+
for (const [key, state] of _stories) if (state.phase === "PENDING" && !allSkipped.includes(key)) allSkipped.push(key);
|
|
13365
|
+
for (const key of allSkipped) {
|
|
13366
|
+
updateStory(key, {
|
|
13367
|
+
phase: "ESCALATED",
|
|
13368
|
+
error: "cost-ceiling-reached",
|
|
13369
|
+
completedAt: new Date().toISOString()
|
|
13370
|
+
});
|
|
13371
|
+
if (runManifest !== null && runManifest !== void 0) runManifest.patchStoryState(key, { status: "escalated" }).catch(() => {});
|
|
13372
|
+
}
|
|
13373
|
+
eventBus.emit("cost:ceiling-reached", {
|
|
13374
|
+
cumulative_cost: result.cumulative,
|
|
13375
|
+
ceiling: result.ceiling,
|
|
13376
|
+
halt_on: haltOn,
|
|
13377
|
+
action: "stopped",
|
|
13378
|
+
skipped_stories: allSkipped,
|
|
13379
|
+
...haltOn !== "none" ? { severity: "critical" } : {}
|
|
13380
|
+
});
|
|
13381
|
+
_budgetExhausted = true;
|
|
13382
|
+
logger$23.warn({
|
|
13383
|
+
skipped: allSkipped.length,
|
|
13384
|
+
cumulative: result.cumulative,
|
|
13385
|
+
ceiling: result.ceiling
|
|
13386
|
+
}, "Cost ceiling reached — stopping dispatch");
|
|
13387
|
+
}
|
|
13388
|
+
/**
|
|
13955
13389
|
* Process a conflict group: run stories sequentially within the group.
|
|
13956
13390
|
*
|
|
13957
13391
|
* After each story completes (any outcome), a GC hint is issued and a short
|
|
@@ -13961,6 +13395,28 @@ function createImplementationOrchestrator(deps) {
|
|
|
13961
13395
|
async function processConflictGroup(group) {
|
|
13962
13396
|
const completedStoryKeys = [];
|
|
13963
13397
|
for (const storyKey of group) {
|
|
13398
|
+
if (runManifest !== null && runManifest !== void 0) try {
|
|
13399
|
+
const manifestData = await runManifest.read();
|
|
13400
|
+
const ceiling = manifestData.cli_flags.cost_ceiling;
|
|
13401
|
+
if (ceiling !== void 0 && ceiling > 0) {
|
|
13402
|
+
const checkResult = _costChecker.checkCeiling(manifestData, ceiling);
|
|
13403
|
+
if (checkResult.status === "warning" && !_costWarningEmitted) {
|
|
13404
|
+
_costWarningEmitted = true;
|
|
13405
|
+
eventBus.emit("cost:warning", {
|
|
13406
|
+
cumulative_cost: checkResult.cumulative,
|
|
13407
|
+
ceiling: checkResult.ceiling,
|
|
13408
|
+
percent_used: checkResult.percentUsed
|
|
13409
|
+
});
|
|
13410
|
+
}
|
|
13411
|
+
if (checkResult.status === "exceeded") {
|
|
13412
|
+
const remainingInGroup = group.slice(group.indexOf(storyKey) + 1);
|
|
13413
|
+
await handleCeilingExceeded(storyKey, remainingInGroup, checkResult, manifestData);
|
|
13414
|
+
return;
|
|
13415
|
+
}
|
|
13416
|
+
}
|
|
13417
|
+
} catch (err) {
|
|
13418
|
+
logger$23.debug({ err }, "Cost ceiling check failed — proceeding without enforcement");
|
|
13419
|
+
}
|
|
13964
13420
|
let optimizationDirectives;
|
|
13965
13421
|
if (telemetryAdvisor !== void 0 && completedStoryKeys.length > 0) try {
|
|
13966
13422
|
const recs = await telemetryAdvisor.getRecommendationsForRun(completedStoryKeys);
|
|
@@ -13996,6 +13452,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
13996
13452
|
const queue = [...groups];
|
|
13997
13453
|
const running = new Set();
|
|
13998
13454
|
function enqueue() {
|
|
13455
|
+
if (_budgetExhausted) return;
|
|
13999
13456
|
const group = queue.shift();
|
|
14000
13457
|
if (group === void 0) return;
|
|
14001
13458
|
const p = processConflictGroup(group).finally(() => {
|
|
@@ -37842,10 +37299,10 @@ var PathScurryBase = class {
|
|
|
37842
37299
|
if (er) return results.emit("error", er);
|
|
37843
37300
|
/* c8 ignore stop */
|
|
37844
37301
|
if (follow && !didRealpaths) {
|
|
37845
|
-
const promises
|
|
37846
|
-
for (const e of entries) if (e.isSymbolicLink()) promises
|
|
37847
|
-
if (promises
|
|
37848
|
-
Promise.all(promises
|
|
37302
|
+
const promises = [];
|
|
37303
|
+
for (const e of entries) if (e.isSymbolicLink()) promises.push(e.realpath().then((r) => r?.isUnknown() ? r.lstat() : r));
|
|
37304
|
+
if (promises.length) {
|
|
37305
|
+
Promise.all(promises).then(() => onReaddir(null, entries, true));
|
|
37849
37306
|
return;
|
|
37850
37307
|
}
|
|
37851
37308
|
}
|
|
@@ -41209,6 +40666,27 @@ function wireNdjsonEmitter(eventBus, ndjsonEmitter) {
|
|
|
41209
40666
|
duration_ms: payload.duration_ms
|
|
41210
40667
|
});
|
|
41211
40668
|
});
|
|
40669
|
+
eventBus.on("cost:warning", (payload) => {
|
|
40670
|
+
ndjsonEmitter.emit({
|
|
40671
|
+
type: "cost:warning",
|
|
40672
|
+
ts: new Date().toISOString(),
|
|
40673
|
+
cumulative_cost: payload.cumulative_cost,
|
|
40674
|
+
ceiling: payload.ceiling,
|
|
40675
|
+
percent_used: payload.percent_used
|
|
40676
|
+
});
|
|
40677
|
+
});
|
|
40678
|
+
eventBus.on("cost:ceiling-reached", (payload) => {
|
|
40679
|
+
ndjsonEmitter.emit({
|
|
40680
|
+
type: "cost:ceiling-reached",
|
|
40681
|
+
ts: new Date().toISOString(),
|
|
40682
|
+
cumulative_cost: payload.cumulative_cost,
|
|
40683
|
+
ceiling: payload.ceiling,
|
|
40684
|
+
halt_on: payload.halt_on,
|
|
40685
|
+
action: payload.action,
|
|
40686
|
+
skipped_stories: payload.skipped_stories,
|
|
40687
|
+
...payload.severity !== void 0 ? { severity: payload.severity } : {}
|
|
40688
|
+
});
|
|
40689
|
+
});
|
|
41212
40690
|
}
|
|
41213
40691
|
async function runRunAction(options) {
|
|
41214
40692
|
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, skipPreflight, skipVerification, epic: epicNumber, dryRun, maxReviewCycles = 2, engine, agent: agentId, registry: injectedRegistry, haltOn, costCeiling } = options;
|
|
@@ -41305,6 +40783,7 @@ async function runRunAction(options) {
|
|
|
41305
40783
|
let telemetryPort = 4318;
|
|
41306
40784
|
let meshUrl;
|
|
41307
40785
|
let meshProjectId;
|
|
40786
|
+
let configRetryBudget;
|
|
41308
40787
|
try {
|
|
41309
40788
|
const configSystem = createConfigSystem({ projectConfigDir: dbDir });
|
|
41310
40789
|
await configSystem.load();
|
|
@@ -41322,6 +40801,7 @@ async function runRunAction(options) {
|
|
|
41322
40801
|
meshUrl = cfg.telemetry.meshUrl;
|
|
41323
40802
|
meshProjectId = cfg.telemetry.projectId;
|
|
41324
40803
|
}
|
|
40804
|
+
if (typeof cfg.retry_budget === "number") configRetryBudget = cfg.retry_budget;
|
|
41325
40805
|
} catch {
|
|
41326
40806
|
logger.debug("Config loading skipped — using default token ceilings and telemetry settings");
|
|
41327
40807
|
}
|
|
@@ -41406,6 +40886,7 @@ async function runRunAction(options) {
|
|
|
41406
40886
|
} : {},
|
|
41407
40887
|
engineType: resolvedEngine,
|
|
41408
40888
|
maxReviewCycles: effectiveMaxReviewCycles,
|
|
40889
|
+
retryBudget: configRetryBudget ?? 2,
|
|
41409
40890
|
agentId
|
|
41410
40891
|
});
|
|
41411
40892
|
let storyKeys = [...parsedStoryKeys];
|
|
@@ -41911,6 +41392,7 @@ async function runRunAction(options) {
|
|
|
41911
41392
|
eventBus,
|
|
41912
41393
|
pipelineRunId: pipelineRun.id,
|
|
41913
41394
|
maxReviewCycles: effectiveMaxReviewCycles,
|
|
41395
|
+
retryBudget: configRetryBudget ?? 2,
|
|
41914
41396
|
gcPauseMs: 0
|
|
41915
41397
|
});
|
|
41916
41398
|
if (outputFormat === "human" && progressRenderer === void 0 && ndjsonEmitter === void 0) {
|
|
@@ -41930,6 +41412,7 @@ async function runRunAction(options) {
|
|
|
41930
41412
|
config: {
|
|
41931
41413
|
maxConcurrency: concurrency,
|
|
41932
41414
|
maxReviewCycles: effectiveMaxReviewCycles,
|
|
41415
|
+
retryBudget: configRetryBudget ?? 2,
|
|
41933
41416
|
pipelineRunId: pipelineRun.id,
|
|
41934
41417
|
enableHeartbeat: eventsFlag === true,
|
|
41935
41418
|
skipPreflight: skipPreflight === true,
|
|
@@ -42043,13 +41526,13 @@ async function runRunAction(options) {
|
|
|
42043
41526
|
process.stdout.write("\n");
|
|
42044
41527
|
process.stdout.write(formatTokenTelemetry(tokenSummary) + "\n");
|
|
42045
41528
|
}
|
|
42046
|
-
if (meshUrl !== void 0) reportToMesh(adapter, pipelineRun.id, meshUrl, {
|
|
41529
|
+
if (meshUrl !== void 0) await reportToMesh(adapter, pipelineRun.id, meshUrl, {
|
|
42047
41530
|
projectId: meshProjectId,
|
|
42048
41531
|
projectRoot,
|
|
42049
41532
|
agentBackend: agentId ?? "claude-code",
|
|
42050
41533
|
engineType: resolvedEngine,
|
|
42051
41534
|
concurrency
|
|
42052
|
-
})
|
|
41535
|
+
});
|
|
42053
41536
|
return 0;
|
|
42054
41537
|
} catch (err) {
|
|
42055
41538
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -42064,7 +41547,7 @@ async function runRunAction(options) {
|
|
|
42064
41547
|
}
|
|
42065
41548
|
}
|
|
42066
41549
|
async function runFullPipeline(options) {
|
|
42067
|
-
const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, skipVerification, maxReviewCycles = 2, registry: injectedRegistry, tokenCeilings, stories: explicitStories, telemetryEnabled: fullTelemetryEnabled, telemetryPort: fullTelemetryPort, agentId, meshUrl: fpMeshUrl, meshProjectId: fpMeshProjectId, engineType: fpEngineType } = options;
|
|
41550
|
+
const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, skipVerification, maxReviewCycles = 2, retryBudget, registry: injectedRegistry, tokenCeilings, stories: explicitStories, telemetryEnabled: fullTelemetryEnabled, telemetryPort: fullTelemetryPort, agentId, meshUrl: fpMeshUrl, meshProjectId: fpMeshProjectId, engineType: fpEngineType } = options;
|
|
42068
41551
|
if (!existsSync$1(dbDir)) mkdirSync$1(dbDir, { recursive: true });
|
|
42069
41552
|
const adapter = createDatabaseAdapter({
|
|
42070
41553
|
backend: "auto",
|
|
@@ -42357,6 +41840,7 @@ async function runFullPipeline(options) {
|
|
|
42357
41840
|
config: {
|
|
42358
41841
|
maxConcurrency: concurrency,
|
|
42359
41842
|
maxReviewCycles,
|
|
41843
|
+
retryBudget: retryBudget ?? 2,
|
|
42360
41844
|
pipelineRunId: runId,
|
|
42361
41845
|
skipPreflight: skipPreflight === true,
|
|
42362
41846
|
...skipVerification === true ? { skipVerification: true } : {}
|
|
@@ -42487,13 +41971,13 @@ async function runFullPipeline(options) {
|
|
|
42487
41971
|
failed: fpFailedKeys,
|
|
42488
41972
|
escalated: fpEscalatedKeys
|
|
42489
41973
|
});
|
|
42490
|
-
if (fpMeshUrl !== void 0) reportToMesh(adapter, runId, fpMeshUrl, {
|
|
41974
|
+
if (fpMeshUrl !== void 0) await reportToMesh(adapter, runId, fpMeshUrl, {
|
|
42491
41975
|
projectId: fpMeshProjectId,
|
|
42492
41976
|
projectRoot,
|
|
42493
41977
|
agentBackend: agentId ?? "claude-code",
|
|
42494
41978
|
engineType: fpEngineType ?? "linear",
|
|
42495
41979
|
concurrency
|
|
42496
|
-
})
|
|
41980
|
+
});
|
|
42497
41981
|
return 0;
|
|
42498
41982
|
} catch (err) {
|
|
42499
41983
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -42558,4 +42042,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
42558
42042
|
|
|
42559
42043
|
//#endregion
|
|
42560
42044
|
export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveMaxReviewCycles, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict, wireNdjsonEmitter };
|
|
42561
|
-
//# sourceMappingURL=run-
|
|
42045
|
+
//# sourceMappingURL=run-0MJeYpdb.js.map
|