substrate-ai 0.20.12 → 0.20.13
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 +193 -103
- package/dist/{health-0jqPFBEL.js → health-CxBbduMn.js} +58 -1
- package/dist/{health-C401hYzi.js → health-RIYFKE9U.js} +1 -1
- package/dist/index.d.ts +14 -0
- package/dist/{run-DEyd1p7U.js → run-BDQLMeWK.js} +2 -2
- package/dist/{run-B6qbsy-F.js → run-D5VAkItq.js} +142 -21
- package/package.json +1 -1
- package/packs/bmad/prompts/create-story.md +1 -0
|
@@ -4210,6 +4210,12 @@ const RunManifestSchema = z.object({
|
|
|
4210
4210
|
run_id: z.string(),
|
|
4211
4211
|
cli_flags: CliFlagsSchema.transform((v) => v),
|
|
4212
4212
|
story_scope: z.array(z.string()),
|
|
4213
|
+
run_status: z.enum([
|
|
4214
|
+
"running",
|
|
4215
|
+
"completed",
|
|
4216
|
+
"failed",
|
|
4217
|
+
"stopped"
|
|
4218
|
+
]).optional(),
|
|
4213
4219
|
supervisor_pid: z.number().nullable(),
|
|
4214
4220
|
supervisor_session_id: z.string().nullable(),
|
|
4215
4221
|
per_story_state: z.record(z.string(), PerStoryStateSchema),
|
|
@@ -4219,6 +4225,8 @@ const RunManifestSchema = z.object({
|
|
|
4219
4225
|
run_total: 0
|
|
4220
4226
|
}),
|
|
4221
4227
|
pending_proposals: z.array(ProposalSchema),
|
|
4228
|
+
stopped_reason: z.string().optional(),
|
|
4229
|
+
stopped_at: z.string().optional(),
|
|
4222
4230
|
generation: z.number().int().nonnegative(),
|
|
4223
4231
|
created_at: z.string(),
|
|
4224
4232
|
updated_at: z.string()
|
|
@@ -4560,6 +4568,55 @@ var RunManifest = class RunManifest {
|
|
|
4560
4568
|
/**
|
|
4561
4569
|
* Raw implementation — must only be called from within `_enqueue`.
|
|
4562
4570
|
*/
|
|
4571
|
+
async _patchRunStatusImpl(updates) {
|
|
4572
|
+
let existingData;
|
|
4573
|
+
try {
|
|
4574
|
+
const read = await RunManifest.read(this.runId, this.baseDir, this.doltAdapter);
|
|
4575
|
+
const { generation: _gen, updated_at: _ts,...rest } = read;
|
|
4576
|
+
existingData = rest;
|
|
4577
|
+
} catch {
|
|
4578
|
+
const now = new Date().toISOString();
|
|
4579
|
+
existingData = {
|
|
4580
|
+
run_id: this.runId,
|
|
4581
|
+
cli_flags: {},
|
|
4582
|
+
story_scope: [],
|
|
4583
|
+
supervisor_pid: null,
|
|
4584
|
+
supervisor_session_id: null,
|
|
4585
|
+
per_story_state: {},
|
|
4586
|
+
recovery_history: [],
|
|
4587
|
+
cost_accumulation: {
|
|
4588
|
+
per_story: {},
|
|
4589
|
+
run_total: 0
|
|
4590
|
+
},
|
|
4591
|
+
pending_proposals: [],
|
|
4592
|
+
created_at: now
|
|
4593
|
+
};
|
|
4594
|
+
}
|
|
4595
|
+
const merged = { ...existingData };
|
|
4596
|
+
if (updates.run_status !== void 0) merged.run_status = updates.run_status;
|
|
4597
|
+
if (updates.stopped_reason !== void 0) merged.stopped_reason = updates.stopped_reason;
|
|
4598
|
+
if (updates.stopped_at !== void 0) merged.stopped_at = updates.stopped_at;
|
|
4599
|
+
await this._writeImpl(merged);
|
|
4600
|
+
}
|
|
4601
|
+
/**
|
|
4602
|
+
* Atomically update the run-level status fields in the manifest.
|
|
4603
|
+
*
|
|
4604
|
+
* Reads the current manifest (or creates a minimal default if absent),
|
|
4605
|
+
* merges the provided status updates at the top level, and writes the
|
|
4606
|
+
* result atomically via a single `write()` call.
|
|
4607
|
+
*
|
|
4608
|
+
* Enqueues the operation via `_enqueue` so concurrent calls are serialized
|
|
4609
|
+
* (preserves the single-writer guarantee from Epic 57-1).
|
|
4610
|
+
* Non-fatal: callers MUST wrap in `.catch((err) => logger.warn(...))`.
|
|
4611
|
+
*
|
|
4612
|
+
* @param updates - Top-level status fields to merge (run_status, stopped_reason, stopped_at)
|
|
4613
|
+
*/
|
|
4614
|
+
async patchRunStatus(updates) {
|
|
4615
|
+
return this._enqueue(() => this._patchRunStatusImpl(updates));
|
|
4616
|
+
}
|
|
4617
|
+
/**
|
|
4618
|
+
* Raw implementation — must only be called from within `_enqueue`.
|
|
4619
|
+
*/
|
|
4563
4620
|
async _appendRecoveryEntryImpl(entry) {
|
|
4564
4621
|
let existingData;
|
|
4565
4622
|
try {
|
|
@@ -5477,4 +5534,4 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
|
|
|
5477
5534
|
|
|
5478
5535
|
//#endregion
|
|
5479
5536
|
export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDING_COUNTS, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, createStateStore, detectCycles, extractTargetFilesFromStoryContent, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, renderFindings, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, runHealthAction, validateStoryKey };
|
|
5480
|
-
//# sourceMappingURL=health-
|
|
5537
|
+
//# sourceMappingURL=health-CxBbduMn.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-
|
|
1
|
+
import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-CxBbduMn.js";
|
|
2
2
|
import "./logger-KeHncl-f.js";
|
|
3
3
|
import "./dist-CqtWS9wF.js";
|
|
4
4
|
import "./decisions-C0pz9Clx.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -2286,6 +2286,20 @@ interface OrchestratorEvents {
|
|
|
2286
2286
|
/** Retry attempt number (always 2 — first retry after initial timeout) */
|
|
2287
2287
|
attempt: number;
|
|
2288
2288
|
};
|
|
2289
|
+
/**
|
|
2290
|
+
* Emitted when an existing story artifact's stored source-AC hash differs
|
|
2291
|
+
* from the current source epic's AC hash, or when the artifact carries no
|
|
2292
|
+
* hash at all (legacy artifact). Causes the orchestrator to re-run
|
|
2293
|
+
* create-story instead of reusing the stale artifact.
|
|
2294
|
+
*/
|
|
2295
|
+
'story:ac-source-drift': {
|
|
2296
|
+
/** Story key whose artifact is stale */
|
|
2297
|
+
storyKey: string;
|
|
2298
|
+
/** Hash stored in the artifact's HTML comment (null if absent — legacy artifact) */
|
|
2299
|
+
storedHash: string | null;
|
|
2300
|
+
/** Hash computed from the current source epic's AC section */
|
|
2301
|
+
currentHash: string;
|
|
2302
|
+
};
|
|
2289
2303
|
}
|
|
2290
2304
|
|
|
2291
2305
|
//#endregion
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import "./health-
|
|
1
|
+
import "./health-CxBbduMn.js";
|
|
2
2
|
import "./logger-KeHncl-f.js";
|
|
3
3
|
import "./helpers-CElYrONe.js";
|
|
4
4
|
import "./dist-CqtWS9wF.js";
|
|
5
|
-
import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-
|
|
5
|
+
import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-D5VAkItq.js";
|
|
6
6
|
import "./routing-CcBOCuC9.js";
|
|
7
7
|
import "./decisions-C0pz9Clx.js";
|
|
8
8
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-
|
|
1
|
+
import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-CxBbduMn.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-CqtWS9wF.js";
|
|
@@ -5676,6 +5676,21 @@ function getTokenCeiling(workflowType, tokenCeilings) {
|
|
|
5676
5676
|
//#region src/modules/compiled-workflows/create-story.ts
|
|
5677
5677
|
const logger$18 = createLogger("compiled-workflows:create-story");
|
|
5678
5678
|
/**
|
|
5679
|
+
* Compute a hex SHA-256 of the normalized source AC section text.
|
|
5680
|
+
*
|
|
5681
|
+
* Normalization (minimal — avoids spurious regen from editor whitespace noise):
|
|
5682
|
+
* 1. Split on `\n`
|
|
5683
|
+
* 2. Strip trailing whitespace from each line (`.trimEnd()`)
|
|
5684
|
+
* 3. Rejoin with `\n`
|
|
5685
|
+
* 4. Trim the whole result (`.trim()`)
|
|
5686
|
+
*
|
|
5687
|
+
* Pure function: no I/O, no side effects. Safe to call from tests with zero setup.
|
|
5688
|
+
*/
|
|
5689
|
+
function hashSourceAcSection(section) {
|
|
5690
|
+
const normalized = section.split("\n").map((line) => line.trimEnd()).join("\n").trim();
|
|
5691
|
+
return createHash("sha256").update(normalized, "utf8").digest("hex");
|
|
5692
|
+
}
|
|
5693
|
+
/**
|
|
5679
5694
|
* Execute the compiled create-story workflow.
|
|
5680
5695
|
*
|
|
5681
5696
|
* Steps:
|
|
@@ -5693,7 +5708,7 @@ const logger$18 = createLogger("compiled-workflows:create-story");
|
|
|
5693
5708
|
* @returns Promise resolving to CreateStoryResult
|
|
5694
5709
|
*/
|
|
5695
5710
|
async function runCreateStory(deps, params) {
|
|
5696
|
-
const { epicId, storyKey, pipelineRunId } = params;
|
|
5711
|
+
const { epicId, storyKey, pipelineRunId, source_ac_hash } = params;
|
|
5697
5712
|
logger$18.debug({
|
|
5698
5713
|
epicId,
|
|
5699
5714
|
storyKey,
|
|
@@ -5764,7 +5779,12 @@ async function runCreateStory(deps, params) {
|
|
|
5764
5779
|
name: "story_template",
|
|
5765
5780
|
content: storyTemplateContent,
|
|
5766
5781
|
priority: "important"
|
|
5767
|
-
}
|
|
5782
|
+
},
|
|
5783
|
+
...source_ac_hash !== void 0 ? [{
|
|
5784
|
+
name: "source_ac_hash",
|
|
5785
|
+
content: source_ac_hash,
|
|
5786
|
+
priority: "required"
|
|
5787
|
+
}] : []
|
|
5768
5788
|
], TOKEN_CEILING);
|
|
5769
5789
|
logger$18.debug({
|
|
5770
5790
|
tokenCount,
|
|
@@ -11367,6 +11387,10 @@ function createImplementationOrchestrator(deps) {
|
|
|
11367
11387
|
let _costWarningEmitted = false;
|
|
11368
11388
|
let _budgetExhausted = false;
|
|
11369
11389
|
let _otlpEndpoint;
|
|
11390
|
+
let _shutdownRequested = false;
|
|
11391
|
+
let _inFlightCount = 0;
|
|
11392
|
+
let _drainResolve = null;
|
|
11393
|
+
let _drainPromise = Promise.resolve();
|
|
11370
11394
|
const verificationStore = new VerificationStore();
|
|
11371
11395
|
const verificationPipeline = createDefaultVerificationPipeline(toSdlcEventBus(eventBus));
|
|
11372
11396
|
const _stateStoreCache = new Map();
|
|
@@ -12004,6 +12028,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
12004
12028
|
startedAt: new Date().toISOString()
|
|
12005
12029
|
});
|
|
12006
12030
|
let storyFilePath;
|
|
12031
|
+
let sourceAcHash;
|
|
12007
12032
|
const artifactsDir = projectRoot ? join$1(projectRoot, "_bmad-output", "implementation-artifacts") : void 0;
|
|
12008
12033
|
if (artifactsDir && existsSync(artifactsDir)) try {
|
|
12009
12034
|
const files = readdirSync(artifactsDir);
|
|
@@ -12017,22 +12042,52 @@ function createImplementationOrchestrator(deps) {
|
|
|
12017
12042
|
reason: validation.reason
|
|
12018
12043
|
}, `Existing story file for ${storyKey} is invalid (${validation.reason}) — re-creating`);
|
|
12019
12044
|
else {
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12045
|
+
let isDrift = false;
|
|
12046
|
+
try {
|
|
12047
|
+
const epicsPath = projectRoot ? findEpicsFile(projectRoot) : void 0;
|
|
12048
|
+
if (epicsPath !== void 0) {
|
|
12049
|
+
const epicContent = readFileSync(epicsPath, "utf-8");
|
|
12050
|
+
const sourceSection = extractStorySection(epicContent, storyKey);
|
|
12051
|
+
if (sourceSection != null) {
|
|
12052
|
+
const currentHash = hashSourceAcSection(sourceSection);
|
|
12053
|
+
sourceAcHash = currentHash;
|
|
12054
|
+
const artifactContent = await readFile$1(candidatePath, "utf-8");
|
|
12055
|
+
const hashMatch = /<!--\s*source-ac-hash:\s*([0-9a-f]{64})\s*-->/.exec(artifactContent);
|
|
12056
|
+
const storedHash = hashMatch?.[1] ?? null;
|
|
12057
|
+
if (storedHash !== currentHash) {
|
|
12058
|
+
isDrift = true;
|
|
12059
|
+
eventBus.emit("story:ac-source-drift", {
|
|
12060
|
+
storyKey,
|
|
12061
|
+
storedHash,
|
|
12062
|
+
currentHash
|
|
12063
|
+
});
|
|
12064
|
+
logger$24.info({
|
|
12065
|
+
storyKey,
|
|
12066
|
+
storedHash,
|
|
12067
|
+
currentHash
|
|
12068
|
+
}, `[orchestrator] story ${storyKey}: source AC hash mismatch, regenerating story artifact`);
|
|
12069
|
+
}
|
|
12070
|
+
}
|
|
12033
12071
|
}
|
|
12034
|
-
}
|
|
12035
|
-
|
|
12072
|
+
} catch {}
|
|
12073
|
+
if (!isDrift) {
|
|
12074
|
+
storyFilePath = candidatePath;
|
|
12075
|
+
logger$24.info({
|
|
12076
|
+
storyKey,
|
|
12077
|
+
storyFilePath
|
|
12078
|
+
}, "Found existing story file — skipping create-story");
|
|
12079
|
+
endPhase(storyKey, "create-story");
|
|
12080
|
+
eventBus.emit("orchestrator:story-phase-complete", {
|
|
12081
|
+
storyKey,
|
|
12082
|
+
phase: "IN_STORY_CREATION",
|
|
12083
|
+
result: {
|
|
12084
|
+
result: "success",
|
|
12085
|
+
story_file: storyFilePath,
|
|
12086
|
+
story_key: storyKey
|
|
12087
|
+
}
|
|
12088
|
+
});
|
|
12089
|
+
await persistState();
|
|
12090
|
+
}
|
|
12036
12091
|
}
|
|
12037
12092
|
}
|
|
12038
12093
|
} catch {}
|
|
@@ -12069,7 +12124,8 @@ function createImplementationOrchestrator(deps) {
|
|
|
12069
12124
|
}, {
|
|
12070
12125
|
epicId: storyKey.split("-")[0] ?? storyKey,
|
|
12071
12126
|
storyKey,
|
|
12072
|
-
pipelineRunId: config.pipelineRunId
|
|
12127
|
+
pipelineRunId: config.pipelineRunId,
|
|
12128
|
+
source_ac_hash: sourceAcHash
|
|
12073
12129
|
});
|
|
12074
12130
|
endPhase(storyKey, "create-story");
|
|
12075
12131
|
eventBus.emit("orchestrator:story-phase-complete", {
|
|
@@ -13898,6 +13954,10 @@ function createImplementationOrchestrator(deps) {
|
|
|
13898
13954
|
async function processConflictGroup(group) {
|
|
13899
13955
|
const completedStoryKeys = [];
|
|
13900
13956
|
for (const storyKey of group) {
|
|
13957
|
+
if (_shutdownRequested) {
|
|
13958
|
+
logger$24.info({ storyKey }, "shutdown requested — skipping dispatch");
|
|
13959
|
+
return;
|
|
13960
|
+
}
|
|
13901
13961
|
if (runManifest !== null && runManifest !== void 0) try {
|
|
13902
13962
|
const manifestData = await runManifest.read();
|
|
13903
13963
|
const ceiling = manifestData.cli_flags.cost_ceiling;
|
|
@@ -13956,11 +14016,15 @@ function createImplementationOrchestrator(deps) {
|
|
|
13956
14016
|
const running = new Set();
|
|
13957
14017
|
function enqueue() {
|
|
13958
14018
|
if (_budgetExhausted) return;
|
|
14019
|
+
if (_shutdownRequested) return;
|
|
13959
14020
|
const group = queue.shift();
|
|
13960
14021
|
if (group === void 0) return;
|
|
14022
|
+
_inFlightCount++;
|
|
13961
14023
|
const p = processConflictGroup(group).finally(() => {
|
|
13962
14024
|
running.delete(p);
|
|
13963
|
-
|
|
14025
|
+
_inFlightCount--;
|
|
14026
|
+
if (_inFlightCount === 0 && _shutdownRequested) _drainResolve?.();
|
|
14027
|
+
while (running.size < maxConcurrency && queue.length > 0 && !_shutdownRequested && !_budgetExhausted) enqueue();
|
|
13964
14028
|
});
|
|
13965
14029
|
running.add(p);
|
|
13966
14030
|
if (running.size > _maxConcurrentActual) _maxConcurrentActual = running.size;
|
|
@@ -13969,6 +14033,47 @@ function createImplementationOrchestrator(deps) {
|
|
|
13969
14033
|
for (let i = 0; i < initial; i++) enqueue();
|
|
13970
14034
|
while (running.size > 0) await Promise.race(running);
|
|
13971
14035
|
}
|
|
14036
|
+
/**
|
|
14037
|
+
* Gracefully shut down the pipeline in response to a POSIX signal.
|
|
14038
|
+
*
|
|
14039
|
+
* 1. Sets the in-memory `_shutdownRequested` flag so the dispatch loop stops
|
|
14040
|
+
* scheduling new stories.
|
|
14041
|
+
* 2. Awaits any in-flight dispatches for up to `config.shutdownGracePeriodMs` ms
|
|
14042
|
+
* (default 5000).
|
|
14043
|
+
* 3. Writes `run_status: 'stopped'` and `stopped_reason` to the run manifest.
|
|
14044
|
+
* 4. Best-effort: updates `pipeline_runs.status = 'stopped'` in Dolt and
|
|
14045
|
+
* transitions active wg_stories to 'cancelled'.
|
|
14046
|
+
* 5. Calls `process.exit(130)` for SIGINT or `process.exit(143)` for SIGTERM.
|
|
14047
|
+
*/
|
|
14048
|
+
async function shutdownGracefully(reason, signal) {
|
|
14049
|
+
if (_shutdownRequested) return;
|
|
14050
|
+
_shutdownRequested = true;
|
|
14051
|
+
logger$24.info({
|
|
14052
|
+
reason,
|
|
14053
|
+
signal
|
|
14054
|
+
}, "Graceful shutdown initiated");
|
|
14055
|
+
const gracePeriod = config.shutdownGracePeriodMs ?? 5e3;
|
|
14056
|
+
if (_inFlightCount === 0) _drainResolve?.();
|
|
14057
|
+
await Promise.race([_drainPromise, new Promise((resolve$6) => setTimeout(resolve$6, gracePeriod))]);
|
|
14058
|
+
if (runManifest !== null && runManifest !== void 0) await runManifest.patchRunStatus({
|
|
14059
|
+
run_status: "stopped",
|
|
14060
|
+
stopped_reason: reason,
|
|
14061
|
+
stopped_at: new Date().toISOString()
|
|
14062
|
+
}).catch((err) => logger$24.warn({ err }, "patchRunStatus failed during shutdown (best-effort)"));
|
|
14063
|
+
if (config.pipelineRunId !== void 0) try {
|
|
14064
|
+
await updatePipelineRun(db, config.pipelineRunId, { status: "stopped" });
|
|
14065
|
+
} catch (err) {
|
|
14066
|
+
logger$24.warn({ err }, "updatePipelineRun(stopped) failed during shutdown (best-effort)");
|
|
14067
|
+
}
|
|
14068
|
+
for (const [storyKey, storyState] of _stories) {
|
|
14069
|
+
const isActive = storyState.phase === "PENDING" || storyState.phase === "IN_STORY_CREATION" || storyState.phase === "IN_TEST_PLANNING" || storyState.phase === "IN_DEV" || storyState.phase === "IN_REVIEW" || storyState.phase === "NEEDS_FIXES" || storyState.phase === "CHECKPOINT";
|
|
14070
|
+
if (isActive) wgRepo.updateStoryStatus(storyKey, "cancelled").catch((err) => logger$24.warn({
|
|
14071
|
+
err,
|
|
14072
|
+
storyKey
|
|
14073
|
+
}, "updateStoryStatus(cancelled) failed during shutdown (best-effort)"));
|
|
14074
|
+
}
|
|
14075
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
14076
|
+
}
|
|
13972
14077
|
async function run(storyKeys) {
|
|
13973
14078
|
if (_state === "RUNNING" || _state === "PAUSED") {
|
|
13974
14079
|
logger$24.warn({ state: _state }, "run() called while orchestrator is already running or paused — ignoring");
|
|
@@ -13980,6 +14085,20 @@ function createImplementationOrchestrator(deps) {
|
|
|
13980
14085
|
}
|
|
13981
14086
|
_state = "RUNNING";
|
|
13982
14087
|
_startedAt = new Date().toISOString();
|
|
14088
|
+
_shutdownRequested = false;
|
|
14089
|
+
_inFlightCount = 0;
|
|
14090
|
+
_drainResolve = null;
|
|
14091
|
+
_drainPromise = new Promise((resolve$6) => {
|
|
14092
|
+
_drainResolve = resolve$6;
|
|
14093
|
+
});
|
|
14094
|
+
const sigtermHandler = () => {
|
|
14095
|
+
shutdownGracefully("killed_by_user", "SIGTERM");
|
|
14096
|
+
};
|
|
14097
|
+
const sigintHandler = () => {
|
|
14098
|
+
shutdownGracefully("killed_by_user", "SIGINT");
|
|
14099
|
+
};
|
|
14100
|
+
process.on("SIGTERM", sigtermHandler);
|
|
14101
|
+
process.on("SIGINT", sigintHandler);
|
|
13983
14102
|
for (const key of storyKeys) {
|
|
13984
14103
|
const pendingState = {
|
|
13985
14104
|
phase: "PENDING",
|
|
@@ -14264,6 +14383,8 @@ function createImplementationOrchestrator(deps) {
|
|
|
14264
14383
|
await persistState();
|
|
14265
14384
|
return getStatus();
|
|
14266
14385
|
} finally {
|
|
14386
|
+
process.off("SIGTERM", sigtermHandler);
|
|
14387
|
+
process.off("SIGINT", sigintHandler);
|
|
14267
14388
|
if (stateStore !== void 0) await stateStore.close().catch((err) => logger$24.warn({ err }, "StateStore.close() failed (best-effort)"));
|
|
14268
14389
|
if (ingestionServer !== void 0) await ingestionServer.stop().catch((err) => logger$24.warn({ err }, "IngestionServer.stop() failed (best-effort)"));
|
|
14269
14390
|
}
|
|
@@ -43858,4 +43979,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
43858
43979
|
|
|
43859
43980
|
//#endregion
|
|
43860
43981
|
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 };
|
|
43861
|
-
//# sourceMappingURL=run-
|
|
43982
|
+
//# sourceMappingURL=run-D5VAkItq.js.map
|
package/package.json
CHANGED
|
@@ -38,6 +38,7 @@ a different story from the epic scope. The story key, title, and core scope are
|
|
|
38
38
|
5. **Fill out the story template** with:
|
|
39
39
|
- A clear user story (As a / I want / So that)
|
|
40
40
|
- Acceptance criteria: preserve the hard-clause text from the Story Definition verbatim. BDD Given/When/Then phrasing is **optional** — permitted for behavior-oriented criteria where it adds clarity, not mandatory. Never let BDD reshape a MUST / MUST NOT / SHALL clause; copy those clauses literally and, if BDD adds clarity, append the Given/When/Then alongside the original clause rather than replacing it. Aim for the same number of ACs as the source — do not condense clauses into fewer items to hit a target count.
|
|
41
|
+
- Immediately after the `## Acceptance Criteria` heading in the rendered story file, emit the line `<!-- source-ac-hash: {{source_ac_hash}} -->` on its own line. When the hash value is empty or blank (the `{{source_ac_hash}}` placeholder resolved to nothing), omit the comment entirely — do not write `<!-- source-ac-hash: -->` or any comment with an empty or missing hash value.
|
|
41
42
|
- Concrete tasks broken into 2–4 hour subtasks, each tied to specific ACs
|
|
42
43
|
- Dev Notes with file paths, import patterns, testing requirements
|
|
43
44
|
6. **Apply the scope cap** — see Scope Cap Guidance below
|