substrate-ai 0.2.30 → 0.2.31
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 +4 -4
- package/dist/{experimenter-f_Y1rreV.js → experimenter-Br1-vzYv.js} +2 -2
- package/dist/index.d.ts +51 -1
- package/dist/{operational-CnMlvWqc.js → operational-Bovj4fS-.js} +16 -2
- package/dist/{run-Ajt187oE.js → run-BxXwD2xY.js} +531 -22
- package/dist/{run-KBcR3Jpi.js → run-CMagiY2Y.js} +2 -2
- package/package.json +1 -1
- package/packs/bmad/manifest.yaml +2 -0
- package/packs/bmad/prompts/code-review.md +7 -2
- package/packs/bmad/prompts/create-story.md +20 -0
- package/packs/bmad/prompts/test-plan.md +10 -4
package/dist/cli/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createConfigSystem, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-
|
|
2
|
+
import { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createConfigSystem, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-BxXwD2xY.js";
|
|
3
3
|
import { createLogger } from "../logger-D2fS2ccL.js";
|
|
4
4
|
import { AdapterRegistry } from "../adapter-registry-PsWhP_1Q.js";
|
|
5
5
|
import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema } from "../config-migrator-DSi8KhQC.js";
|
|
6
6
|
import { ConfigError, createEventBus } from "../helpers-RL22dYtn.js";
|
|
7
7
|
import { addTokenUsage, createDecision, createPipelineRun, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestRun, getTokenUsageSummary, listRequirements, updatePipelineRun } from "../decisions-Dq4cAA2L.js";
|
|
8
|
-
import { ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, OPERATIONAL_FINDING, STORY_METRICS, aggregateTokenUsageForRun, compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline } from "../operational-
|
|
8
|
+
import { ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, OPERATIONAL_FINDING, STORY_METRICS, aggregateTokenUsageForRun, compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline } from "../operational-Bovj4fS-.js";
|
|
9
9
|
import { abortMerge, createWorktree, getConflictingFiles, getMergedFiles, getOrphanedWorktrees, performMerge, removeBranch, removeWorktree, simulateMerge, verifyGitVersion } from "../git-utils-CtmrZrHS.js";
|
|
10
10
|
import "../version-manager-impl-CizNmmLT.js";
|
|
11
11
|
import { registerUpgradeCommand } from "../upgrade-Cvwtnwl4.js";
|
|
@@ -2571,7 +2571,7 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
2571
2571
|
try {
|
|
2572
2572
|
const { createExperimenter } = await import(
|
|
2573
2573
|
/* @vite-ignore */
|
|
2574
|
-
"../experimenter-
|
|
2574
|
+
"../experimenter-Br1-vzYv.js"
|
|
2575
2575
|
);
|
|
2576
2576
|
const { getLatestRun: getLatest } = await import(
|
|
2577
2577
|
/* @vite-ignore */
|
|
@@ -2585,7 +2585,7 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
2585
2585
|
const expDb = expDbWrapper.db;
|
|
2586
2586
|
const { runRunAction: runPipeline } = await import(
|
|
2587
2587
|
/* @vite-ignore */
|
|
2588
|
-
"../run-
|
|
2588
|
+
"../run-CMagiY2Y.js"
|
|
2589
2589
|
);
|
|
2590
2590
|
const runStoryFn = async (opts) => {
|
|
2591
2591
|
const exitCode = await runPipeline({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./logger-D2fS2ccL.js";
|
|
2
2
|
import { createDecision } from "./decisions-Dq4cAA2L.js";
|
|
3
|
-
import { EXPERIMENT_RESULT, getRunMetrics, getStoryMetricsForRun } from "./operational-
|
|
3
|
+
import { EXPERIMENT_RESULT, getRunMetrics, getStoryMetricsForRun } from "./operational-Bovj4fS-.js";
|
|
4
4
|
import { spawnGit } from "./git-utils-CtmrZrHS.js";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { join } from "node:path";
|
|
@@ -500,4 +500,4 @@ function createExperimenter(config, deps) {
|
|
|
500
500
|
|
|
501
501
|
//#endregion
|
|
502
502
|
export { createExperimenter };
|
|
503
|
-
//# sourceMappingURL=experimenter-
|
|
503
|
+
//# sourceMappingURL=experimenter-Br1-vzYv.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -185,6 +185,19 @@ interface StoryZeroDiffEscalationEvent {
|
|
|
185
185
|
/** Always "zero-diff-on-complete" */
|
|
186
186
|
reason: string;
|
|
187
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Emitted when the pre-flight build check fails before any story is dispatched (Story 25-2).
|
|
190
|
+
* Pipeline aborts immediately — no stories are processed.
|
|
191
|
+
*/
|
|
192
|
+
interface PipelinePreFlightFailureEvent {
|
|
193
|
+
type: 'pipeline:pre-flight-failure';
|
|
194
|
+
/** ISO-8601 timestamp generated at emit time */
|
|
195
|
+
ts: string;
|
|
196
|
+
/** Exit code from the build command (-1 for timeout) */
|
|
197
|
+
exitCode: number;
|
|
198
|
+
/** Combined stdout+stderr output, truncated to 2000 chars */
|
|
199
|
+
output: string;
|
|
200
|
+
}
|
|
188
201
|
/**
|
|
189
202
|
* Emitted when the build verification command exits with a non-zero code
|
|
190
203
|
* or times out, before code-review is dispatched (Story 24-2).
|
|
@@ -501,6 +514,26 @@ interface SupervisorExperimentErrorEvent {
|
|
|
501
514
|
/** Error message describing why the experiment failed */
|
|
502
515
|
error: string;
|
|
503
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* Emitted when post-sprint contract verification finds a mismatch between
|
|
519
|
+
* declared export/import contracts (Story 25-6).
|
|
520
|
+
*
|
|
521
|
+
* Failures are warnings only — stories already completed. The user should
|
|
522
|
+
* inspect the mismatch and fix manually.
|
|
523
|
+
*/
|
|
524
|
+
interface PipelineContractMismatchEvent {
|
|
525
|
+
type: 'pipeline:contract-mismatch';
|
|
526
|
+
/** ISO-8601 timestamp generated at emit time */
|
|
527
|
+
ts: string;
|
|
528
|
+
/** Story key that declared the export for this contract */
|
|
529
|
+
exporter: string;
|
|
530
|
+
/** Story key that declared the import for this contract (null if no importer found) */
|
|
531
|
+
importer: string | null;
|
|
532
|
+
/** TypeScript interface or Zod schema name (e.g., "JudgeResult") */
|
|
533
|
+
contractName: string;
|
|
534
|
+
/** Human-readable description of the mismatch (e.g., missing file, type error) */
|
|
535
|
+
mismatchDescription: string;
|
|
536
|
+
}
|
|
504
537
|
/**
|
|
505
538
|
* Discriminated union of all pipeline event types.
|
|
506
539
|
*
|
|
@@ -513,7 +546,7 @@ interface SupervisorExperimentErrorEvent {
|
|
|
513
546
|
* }
|
|
514
547
|
* ```
|
|
515
548
|
*/
|
|
516
|
-
type PipelineEvent = PipelineStartEvent | PipelineCompleteEvent | StoryPhaseEvent | StoryDoneEvent | StoryEscalationEvent | StoryWarnEvent | StoryLogEvent | PipelineHeartbeatEvent | StoryStallEvent | StoryZeroDiffEscalationEvent | StoryBuildVerificationFailedEvent | StoryBuildVerificationPassedEvent | StoryInterfaceChangeWarningEvent | StoryMetricsEvent | SupervisorPollEvent | SupervisorKillEvent | SupervisorRestartEvent | SupervisorAbortEvent | SupervisorSummaryEvent | SupervisorAnalysisCompleteEvent | SupervisorAnalysisErrorEvent | SupervisorExperimentStartEvent | SupervisorExperimentSkipEvent | SupervisorExperimentRecommendationsEvent | SupervisorExperimentCompleteEvent | SupervisorExperimentErrorEvent; //#endregion
|
|
549
|
+
type PipelineEvent = PipelineStartEvent | PipelineCompleteEvent | PipelinePreFlightFailureEvent | PipelineContractMismatchEvent | StoryPhaseEvent | StoryDoneEvent | StoryEscalationEvent | StoryWarnEvent | StoryLogEvent | PipelineHeartbeatEvent | StoryStallEvent | StoryZeroDiffEscalationEvent | StoryBuildVerificationFailedEvent | StoryBuildVerificationPassedEvent | StoryInterfaceChangeWarningEvent | StoryMetricsEvent | SupervisorPollEvent | SupervisorKillEvent | SupervisorRestartEvent | SupervisorAbortEvent | SupervisorSummaryEvent | SupervisorAnalysisCompleteEvent | SupervisorAnalysisErrorEvent | SupervisorExperimentStartEvent | SupervisorExperimentSkipEvent | SupervisorExperimentRecommendationsEvent | SupervisorExperimentCompleteEvent | SupervisorExperimentErrorEvent; //#endregion
|
|
517
550
|
//#region src/core/errors.d.ts
|
|
518
551
|
|
|
519
552
|
/**
|
|
@@ -1160,6 +1193,23 @@ interface OrchestratorEvents {
|
|
|
1160
1193
|
affected_items: string[];
|
|
1161
1194
|
}>;
|
|
1162
1195
|
};
|
|
1196
|
+
/** Pre-flight build check failed before any stories were dispatched */
|
|
1197
|
+
'pipeline:pre-flight-failure': {
|
|
1198
|
+
exitCode: number;
|
|
1199
|
+
/** Build output (stdout+stderr), truncated to 2000 chars */
|
|
1200
|
+
output: string;
|
|
1201
|
+
};
|
|
1202
|
+
/** Contract verification found a mismatch between declared export/import contracts */
|
|
1203
|
+
'pipeline:contract-mismatch': {
|
|
1204
|
+
/** Story key that declared the export for this contract */
|
|
1205
|
+
exporter: string;
|
|
1206
|
+
/** Story key that declared the import for this contract (null if no importer found) */
|
|
1207
|
+
importer: string | null;
|
|
1208
|
+
/** TypeScript interface or Zod schema name (e.g., "JudgeResult") */
|
|
1209
|
+
contractName: string;
|
|
1210
|
+
/** Human-readable description of the mismatch */
|
|
1211
|
+
mismatchDescription: string;
|
|
1212
|
+
};
|
|
1163
1213
|
/** Build verification command failed with non-zero exit or timeout */
|
|
1164
1214
|
'story:build-verification-failed': {
|
|
1165
1215
|
storyKey: string;
|
|
@@ -339,7 +339,21 @@ const TEST_EXPANSION_FINDING = "test-expansion-finding";
|
|
|
339
339
|
* ```
|
|
340
340
|
*/
|
|
341
341
|
const TEST_PLAN = "test-plan";
|
|
342
|
+
/**
|
|
343
|
+
* Category for advisory notes persisted when a code review returns LGTM_WITH_NOTES.
|
|
344
|
+
*
|
|
345
|
+
* Key schema: "{storyKey}:{runId}"
|
|
346
|
+
*
|
|
347
|
+
* Value shape:
|
|
348
|
+
* ```json
|
|
349
|
+
* {
|
|
350
|
+
* "storyKey": "25-3",
|
|
351
|
+
* "notes": "Consider extracting the helper into a shared module for reuse."
|
|
352
|
+
* }
|
|
353
|
+
* ```
|
|
354
|
+
*/
|
|
355
|
+
const ADVISORY_NOTES = "advisory-notes";
|
|
342
356
|
|
|
343
357
|
//#endregion
|
|
344
|
-
export { ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, OPERATIONAL_FINDING, STORY_METRICS, STORY_OUTCOME, TEST_EXPANSION_FINDING, TEST_PLAN, aggregateTokenUsageForRun, aggregateTokenUsageForStory, compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline, writeRunMetrics, writeStoryMetrics };
|
|
345
|
-
//# sourceMappingURL=operational-
|
|
358
|
+
export { ADVISORY_NOTES, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, OPERATIONAL_FINDING, STORY_METRICS, STORY_OUTCOME, TEST_EXPANSION_FINDING, TEST_PLAN, aggregateTokenUsageForRun, aggregateTokenUsageForStory, compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline, writeRunMetrics, writeStoryMetrics };
|
|
359
|
+
//# sourceMappingURL=operational-Bovj4fS-.js.map
|
|
@@ -2,7 +2,7 @@ import { createLogger, deepMask } from "./logger-D2fS2ccL.js";
|
|
|
2
2
|
import { CURRENT_CONFIG_FORMAT_VERSION, PartialSubstrateConfigSchema, SUPPORTED_CONFIG_FORMAT_VERSIONS, SubstrateConfigSchema, defaultConfigMigrator } from "./config-migrator-DSi8KhQC.js";
|
|
3
3
|
import { ConfigError, ConfigIncompatibleFormatError, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-RL22dYtn.js";
|
|
4
4
|
import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./decisions-Dq4cAA2L.js";
|
|
5
|
-
import { ESCALATION_DIAGNOSIS, OPERATIONAL_FINDING, STORY_METRICS, STORY_OUTCOME, TEST_EXPANSION_FINDING, TEST_PLAN, aggregateTokenUsageForRun, aggregateTokenUsageForStory, getStoryMetricsForRun, writeRunMetrics, writeStoryMetrics } from "./operational-
|
|
5
|
+
import { ADVISORY_NOTES, ESCALATION_DIAGNOSIS, OPERATIONAL_FINDING, STORY_METRICS, STORY_OUTCOME, TEST_EXPANSION_FINDING, TEST_PLAN, aggregateTokenUsageForRun, aggregateTokenUsageForStory, getStoryMetricsForRun, writeRunMetrics, writeStoryMetrics } from "./operational-Bovj4fS-.js";
|
|
6
6
|
import { createRequire } from "module";
|
|
7
7
|
import { dirname, join, resolve } from "path";
|
|
8
8
|
import { access, mkdir, readFile, readdir, stat, writeFile } from "fs/promises";
|
|
@@ -2521,6 +2521,28 @@ const PIPELINE_EVENT_METADATA = [
|
|
|
2521
2521
|
}
|
|
2522
2522
|
]
|
|
2523
2523
|
},
|
|
2524
|
+
{
|
|
2525
|
+
type: "pipeline:pre-flight-failure",
|
|
2526
|
+
description: "Pre-flight build check failed before any story was dispatched. Pipeline aborts immediately.",
|
|
2527
|
+
when: "When the build command exits with a non-zero code before the first story dispatch.",
|
|
2528
|
+
fields: [
|
|
2529
|
+
{
|
|
2530
|
+
name: "ts",
|
|
2531
|
+
type: "string",
|
|
2532
|
+
description: "Timestamp."
|
|
2533
|
+
},
|
|
2534
|
+
{
|
|
2535
|
+
name: "exitCode",
|
|
2536
|
+
type: "number",
|
|
2537
|
+
description: "Exit code from the build command (-1 for timeout)."
|
|
2538
|
+
},
|
|
2539
|
+
{
|
|
2540
|
+
name: "output",
|
|
2541
|
+
type: "string",
|
|
2542
|
+
description: "Combined stdout+stderr from the build command (truncated to 2000 chars)."
|
|
2543
|
+
}
|
|
2544
|
+
]
|
|
2545
|
+
},
|
|
2524
2546
|
{
|
|
2525
2547
|
type: "story:zero-diff-escalation",
|
|
2526
2548
|
description: "Dev-story reported COMPLETE but git diff shows no file changes (phantom completion).",
|
|
@@ -2652,6 +2674,38 @@ const PIPELINE_EVENT_METADATA = [
|
|
|
2652
2674
|
description: "Dispatch count."
|
|
2653
2675
|
}
|
|
2654
2676
|
]
|
|
2677
|
+
},
|
|
2678
|
+
{
|
|
2679
|
+
type: "pipeline:contract-mismatch",
|
|
2680
|
+
description: "Post-sprint contract mismatch found. Non-blocking — stories done. Manual fix required.",
|
|
2681
|
+
when: "After all stories complete, before pipeline:complete. When contract declarations exist and mismatch found.",
|
|
2682
|
+
fields: [
|
|
2683
|
+
{
|
|
2684
|
+
name: "ts",
|
|
2685
|
+
type: "string",
|
|
2686
|
+
description: "Timestamp."
|
|
2687
|
+
},
|
|
2688
|
+
{
|
|
2689
|
+
name: "exporter",
|
|
2690
|
+
type: "string",
|
|
2691
|
+
description: "Exporting story key."
|
|
2692
|
+
},
|
|
2693
|
+
{
|
|
2694
|
+
name: "importer",
|
|
2695
|
+
type: "string|null",
|
|
2696
|
+
description: "Importing story key (null if none)."
|
|
2697
|
+
},
|
|
2698
|
+
{
|
|
2699
|
+
name: "contractName",
|
|
2700
|
+
type: "string",
|
|
2701
|
+
description: "Contract name (e.g., \"JudgeResult\")."
|
|
2702
|
+
},
|
|
2703
|
+
{
|
|
2704
|
+
name: "mismatchDescription",
|
|
2705
|
+
type: "string",
|
|
2706
|
+
description: "Mismatch details (missing file, type error)."
|
|
2707
|
+
}
|
|
2708
|
+
]
|
|
2655
2709
|
}
|
|
2656
2710
|
];
|
|
2657
2711
|
/**
|
|
@@ -4347,6 +4401,9 @@ const AcChecklistEntrySchema = z.object({
|
|
|
4347
4401
|
* - Any blocker → NEEDS_MAJOR_REWORK (security, data loss, architectural breakage)
|
|
4348
4402
|
* - Any major or minor issues → NEEDS_MINOR_FIXES (fixable by sonnet with guidance)
|
|
4349
4403
|
* - No issues → SHIP_IT
|
|
4404
|
+
*
|
|
4405
|
+
* Note: LGTM_WITH_NOTES is handled in the transform (not here) because it
|
|
4406
|
+
* requires knowledge of the agent's original verdict.
|
|
4350
4407
|
*/
|
|
4351
4408
|
function computeVerdict(issueList) {
|
|
4352
4409
|
const hasBlocker = issueList.some((i) => i.severity === "blocker");
|
|
@@ -4386,7 +4443,8 @@ const CodeReviewResultSchema = z.object({
|
|
|
4386
4443
|
verdict: z.enum([
|
|
4387
4444
|
"SHIP_IT",
|
|
4388
4445
|
"NEEDS_MINOR_FIXES",
|
|
4389
|
-
"NEEDS_MAJOR_REWORK"
|
|
4446
|
+
"NEEDS_MAJOR_REWORK",
|
|
4447
|
+
"LGTM_WITH_NOTES"
|
|
4390
4448
|
]),
|
|
4391
4449
|
issues: coerceNumber,
|
|
4392
4450
|
issue_list: z.array(CodeReviewIssueSchema),
|
|
@@ -4403,12 +4461,14 @@ const CodeReviewResultSchema = z.object({
|
|
|
4403
4461
|
file: ""
|
|
4404
4462
|
});
|
|
4405
4463
|
}
|
|
4464
|
+
const computedVerdict = computeVerdict(augmentedIssueList);
|
|
4465
|
+
const finalVerdict = data.verdict === "LGTM_WITH_NOTES" && computedVerdict === "SHIP_IT" ? "LGTM_WITH_NOTES" : computedVerdict;
|
|
4406
4466
|
return {
|
|
4407
4467
|
...data,
|
|
4408
4468
|
issue_list: augmentedIssueList,
|
|
4409
4469
|
issues: augmentedIssueList.length,
|
|
4410
4470
|
agentVerdict: data.verdict,
|
|
4411
|
-
verdict:
|
|
4471
|
+
verdict: finalVerdict
|
|
4412
4472
|
};
|
|
4413
4473
|
});
|
|
4414
4474
|
/**
|
|
@@ -4576,7 +4636,7 @@ async function runCreateStory(deps, params) {
|
|
|
4576
4636
|
const implementationDecisions = getImplementationDecisions(deps);
|
|
4577
4637
|
const epicShardContent = getEpicShard(implementationDecisions, epicId, deps.projectRoot, storyKey);
|
|
4578
4638
|
const prevDevNotesContent = getPrevDevNotes(implementationDecisions, epicId);
|
|
4579
|
-
const archConstraintsContent = getArchConstraints$
|
|
4639
|
+
const archConstraintsContent = getArchConstraints$3(deps);
|
|
4580
4640
|
const storyTemplateContent = await getStoryTemplate(deps);
|
|
4581
4641
|
const { prompt, tokenCount, truncated } = assemblePrompt(template, [
|
|
4582
4642
|
{
|
|
@@ -4823,7 +4883,7 @@ function getPrevDevNotes(decisions, epicId) {
|
|
|
4823
4883
|
* Looks for decisions with phase='solutioning', category='architecture'.
|
|
4824
4884
|
* Falls back to reading _bmad-output/architecture/architecture.md on disk if decisions are empty.
|
|
4825
4885
|
*/
|
|
4826
|
-
function getArchConstraints$
|
|
4886
|
+
function getArchConstraints$3(deps) {
|
|
4827
4887
|
try {
|
|
4828
4888
|
const decisions = getDecisionsByPhase(deps.db, "solutioning");
|
|
4829
4889
|
const constraints = decisions.filter((d) => d.category === "architecture");
|
|
@@ -5100,7 +5160,8 @@ function getProjectFindings(db) {
|
|
|
5100
5160
|
const operational = getDecisionsByCategory(db, OPERATIONAL_FINDING);
|
|
5101
5161
|
const metrics = getDecisionsByCategory(db, STORY_METRICS);
|
|
5102
5162
|
const diagnoses = getDecisionsByCategory(db, ESCALATION_DIAGNOSIS);
|
|
5103
|
-
|
|
5163
|
+
const advisoryNotes = getDecisionsByCategory(db, ADVISORY_NOTES);
|
|
5164
|
+
if (outcomes.length === 0 && operational.length === 0 && metrics.length === 0 && diagnoses.length === 0 && advisoryNotes.length === 0) return "";
|
|
5104
5165
|
const sections = [];
|
|
5105
5166
|
if (outcomes.length > 0) {
|
|
5106
5167
|
const patterns = extractRecurringPatterns(outcomes);
|
|
@@ -5135,6 +5196,16 @@ function getProjectFindings(db) {
|
|
|
5135
5196
|
}
|
|
5136
5197
|
const stalls = operational.filter((o) => o.key.startsWith("stall:"));
|
|
5137
5198
|
if (stalls.length > 0) sections.push(`**Prior stalls:** ${stalls.length} stall event(s) recorded`);
|
|
5199
|
+
if (advisoryNotes.length > 0) {
|
|
5200
|
+
sections.push("**Advisory notes from prior reviews (LGTM_WITH_NOTES):**");
|
|
5201
|
+
for (const n of advisoryNotes.slice(-3)) try {
|
|
5202
|
+
const val = JSON.parse(n.value);
|
|
5203
|
+
const storyId = n.key.split(":")[0];
|
|
5204
|
+
if (typeof val.notes === "string" && val.notes.length > 0) sections.push(`- ${storyId}: ${val.notes}`);
|
|
5205
|
+
} catch {
|
|
5206
|
+
sections.push(`- ${n.key}: advisory notes available`);
|
|
5207
|
+
}
|
|
5208
|
+
}
|
|
5138
5209
|
if (sections.length === 0) return "";
|
|
5139
5210
|
let summary = sections.join("\n");
|
|
5140
5211
|
if (summary.length > MAX_CHARS) summary = summary.slice(0, MAX_CHARS - 3) + "...";
|
|
@@ -5785,7 +5856,7 @@ async function runCodeReview(deps, params) {
|
|
|
5785
5856
|
output: 0
|
|
5786
5857
|
});
|
|
5787
5858
|
}
|
|
5788
|
-
const archConstraintsContent = getArchConstraints$
|
|
5859
|
+
const archConstraintsContent = getArchConstraints$2(deps);
|
|
5789
5860
|
const templateTokens = countTokens(template);
|
|
5790
5861
|
const storyTokens = countTokens(storyContent);
|
|
5791
5862
|
const constraintTokens = countTokens(archConstraintsContent);
|
|
@@ -5994,7 +6065,7 @@ async function runCodeReview(deps, params) {
|
|
|
5994
6065
|
* Retrieve architecture constraints from the decision store.
|
|
5995
6066
|
* Looks for decisions with phase='solutioning', category='architecture'.
|
|
5996
6067
|
*/
|
|
5997
|
-
function getArchConstraints$
|
|
6068
|
+
function getArchConstraints$2(deps) {
|
|
5998
6069
|
try {
|
|
5999
6070
|
const decisions = getDecisionsByPhase(deps.db, "solutioning");
|
|
6000
6071
|
const constraints = decisions.filter((d) => d.category === "architecture");
|
|
@@ -6061,10 +6132,15 @@ async function runTestPlan(deps, params) {
|
|
|
6061
6132
|
}, "Failed to read story file for test planning");
|
|
6062
6133
|
return makeTestPlanFailureResult(`story_file_read_error: ${error}`);
|
|
6063
6134
|
}
|
|
6135
|
+
const archConstraintsContent = getArchConstraints$1(deps);
|
|
6064
6136
|
const { prompt, tokenCount, truncated } = assemblePrompt(template, [{
|
|
6065
6137
|
name: "story_content",
|
|
6066
6138
|
content: storyContent,
|
|
6067
6139
|
priority: "required"
|
|
6140
|
+
}, {
|
|
6141
|
+
name: "architecture_constraints",
|
|
6142
|
+
content: archConstraintsContent,
|
|
6143
|
+
priority: "optional"
|
|
6068
6144
|
}], TOKEN_CEILING);
|
|
6069
6145
|
logger$10.info({
|
|
6070
6146
|
storyKey,
|
|
@@ -6181,6 +6257,22 @@ function makeTestPlanFailureResult(error) {
|
|
|
6181
6257
|
}
|
|
6182
6258
|
};
|
|
6183
6259
|
}
|
|
6260
|
+
/**
|
|
6261
|
+
* Retrieve architecture constraints from the decision store.
|
|
6262
|
+
* Looks for decisions with phase='solutioning', category='architecture'.
|
|
6263
|
+
* Returns empty string if none found or on error (graceful degradation).
|
|
6264
|
+
*/
|
|
6265
|
+
function getArchConstraints$1(deps) {
|
|
6266
|
+
try {
|
|
6267
|
+
const decisions = getDecisionsByPhase(deps.db, "solutioning");
|
|
6268
|
+
const constraints = decisions.filter((d) => d.category === "architecture");
|
|
6269
|
+
if (constraints.length === 0) return "";
|
|
6270
|
+
return constraints.map((d) => `${d.key}: ${d.value}`).join("\n");
|
|
6271
|
+
} catch (err) {
|
|
6272
|
+
logger$10.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve architecture constraints for test-plan — proceeding without them");
|
|
6273
|
+
return "";
|
|
6274
|
+
}
|
|
6275
|
+
}
|
|
6184
6276
|
|
|
6185
6277
|
//#endregion
|
|
6186
6278
|
//#region src/modules/compiled-workflows/test-expansion.ts
|
|
@@ -6695,6 +6787,125 @@ function detectConflictGroups(storyKeys, config) {
|
|
|
6695
6787
|
}
|
|
6696
6788
|
return Array.from(moduleToStories.values());
|
|
6697
6789
|
}
|
|
6790
|
+
/**
|
|
6791
|
+
* Build a contract dependency graph from a list of contract declarations.
|
|
6792
|
+
*
|
|
6793
|
+
* Rules:
|
|
6794
|
+
* 1. If story A exports contract "FooSchema" and story B imports "FooSchema",
|
|
6795
|
+
* add a directed edge A→B (A must dispatch before B).
|
|
6796
|
+
* 2. If story A and story B both export the same contract (dual export),
|
|
6797
|
+
* add a one-way edge between them (alphabetically sorted, to avoid cycles)
|
|
6798
|
+
* so they are serialized and cannot produce conflicting definitions in parallel.
|
|
6799
|
+
*
|
|
6800
|
+
* @param declarations - Array of contract declarations from all stories
|
|
6801
|
+
* @returns List of directed dependency edges
|
|
6802
|
+
*/
|
|
6803
|
+
function buildContractDependencyGraph(declarations) {
|
|
6804
|
+
const edges = [];
|
|
6805
|
+
const exportsByName = new Map();
|
|
6806
|
+
const importsByName = new Map();
|
|
6807
|
+
for (const decl of declarations) if (decl.direction === "export") {
|
|
6808
|
+
const arr = exportsByName.get(decl.contractName) ?? [];
|
|
6809
|
+
arr.push(decl.storyKey);
|
|
6810
|
+
exportsByName.set(decl.contractName, arr);
|
|
6811
|
+
} else {
|
|
6812
|
+
const arr = importsByName.get(decl.contractName) ?? [];
|
|
6813
|
+
arr.push(decl.storyKey);
|
|
6814
|
+
importsByName.set(decl.contractName, arr);
|
|
6815
|
+
}
|
|
6816
|
+
for (const [contractName, importerKeys] of importsByName) {
|
|
6817
|
+
const exporterKeys = exportsByName.get(contractName) ?? [];
|
|
6818
|
+
for (const from of exporterKeys) for (const to of importerKeys) {
|
|
6819
|
+
if (from === to) continue;
|
|
6820
|
+
edges.push({
|
|
6821
|
+
from,
|
|
6822
|
+
to,
|
|
6823
|
+
contractName,
|
|
6824
|
+
reason: `${from} exports ${contractName}, ${to} imports it`
|
|
6825
|
+
});
|
|
6826
|
+
}
|
|
6827
|
+
}
|
|
6828
|
+
for (const [contractName, exporterKeys] of exportsByName) {
|
|
6829
|
+
if (exporterKeys.length < 2) continue;
|
|
6830
|
+
const sorted = [...exporterKeys].sort();
|
|
6831
|
+
for (let i = 0; i < sorted.length - 1; i++) edges.push({
|
|
6832
|
+
from: sorted[i],
|
|
6833
|
+
to: sorted[i + 1],
|
|
6834
|
+
contractName,
|
|
6835
|
+
reason: `dual export: ${sorted[i]} and ${sorted[i + 1]} both export ${contractName} — serialized to prevent conflicting definitions`
|
|
6836
|
+
});
|
|
6837
|
+
}
|
|
6838
|
+
return edges;
|
|
6839
|
+
}
|
|
6840
|
+
/**
|
|
6841
|
+
* Detect conflict groups with contract-aware dispatch ordering.
|
|
6842
|
+
*
|
|
6843
|
+
* Combines file-based conflict grouping (via moduleMap) with semantic
|
|
6844
|
+
* contract dependency ordering (via ContractDeclaration[]).
|
|
6845
|
+
*
|
|
6846
|
+
* Ordering rules:
|
|
6847
|
+
* - If story A exports a contract that story B imports, A's conflict group
|
|
6848
|
+
* is placed in an earlier batch than B's conflict group.
|
|
6849
|
+
* - If story A and story B both export the same contract (dual export),
|
|
6850
|
+
* they are serialized into different batches.
|
|
6851
|
+
* - Stories with no contract overlap keep their original grouping
|
|
6852
|
+
* (no regression — all placed in the same single batch).
|
|
6853
|
+
*
|
|
6854
|
+
* Cycle detection: if contract edges form a cycle at the group level
|
|
6855
|
+
* (e.g., A→B and B→A), the affected groups are placed in a single
|
|
6856
|
+
* batch together (graceful degradation — serialization within the group
|
|
6857
|
+
* still applies via the file-conflict mechanism).
|
|
6858
|
+
*
|
|
6859
|
+
* @param storyKeys - Array of story key strings
|
|
6860
|
+
* @param config - Optional conflict detector configuration (moduleMap)
|
|
6861
|
+
* @param declarations - Array of contract declarations from all stories
|
|
6862
|
+
* @returns Ordered batches of conflict groups and the edges found
|
|
6863
|
+
*/
|
|
6864
|
+
function detectConflictGroupsWithContracts(storyKeys, config, declarations) {
|
|
6865
|
+
const groups = detectConflictGroups(storyKeys, config);
|
|
6866
|
+
const edges = buildContractDependencyGraph(declarations);
|
|
6867
|
+
if (edges.length === 0) return {
|
|
6868
|
+
batches: [groups],
|
|
6869
|
+
edges: []
|
|
6870
|
+
};
|
|
6871
|
+
const storyToGroupIdx = new Map();
|
|
6872
|
+
for (let i = 0; i < groups.length; i++) for (const key of groups[i]) storyToGroupIdx.set(key, i);
|
|
6873
|
+
const successors = new Map();
|
|
6874
|
+
const inDegree = new Array(groups.length).fill(0);
|
|
6875
|
+
for (let i = 0; i < groups.length; i++) successors.set(i, new Set());
|
|
6876
|
+
for (const edge of edges) {
|
|
6877
|
+
const fromGroup = storyToGroupIdx.get(edge.from);
|
|
6878
|
+
const toGroup = storyToGroupIdx.get(edge.to);
|
|
6879
|
+
if (fromGroup === void 0 || toGroup === void 0) continue;
|
|
6880
|
+
if (fromGroup === toGroup) continue;
|
|
6881
|
+
if (!successors.get(fromGroup).has(toGroup)) {
|
|
6882
|
+
successors.get(fromGroup).add(toGroup);
|
|
6883
|
+
inDegree[toGroup]++;
|
|
6884
|
+
}
|
|
6885
|
+
}
|
|
6886
|
+
const waves = [];
|
|
6887
|
+
const processed = new Set();
|
|
6888
|
+
while (processed.size < groups.length) {
|
|
6889
|
+
const wave = [];
|
|
6890
|
+
for (let i = 0; i < groups.length; i++) if (!processed.has(i) && inDegree[i] === 0) wave.push(i);
|
|
6891
|
+
if (wave.length === 0) {
|
|
6892
|
+
const remaining = [];
|
|
6893
|
+
for (let i = 0; i < groups.length; i++) if (!processed.has(i)) remaining.push(i);
|
|
6894
|
+
waves.push(remaining);
|
|
6895
|
+
break;
|
|
6896
|
+
}
|
|
6897
|
+
waves.push(wave);
|
|
6898
|
+
for (const idx of wave) {
|
|
6899
|
+
processed.add(idx);
|
|
6900
|
+
for (const successor of successors.get(idx)) inDegree[successor]--;
|
|
6901
|
+
}
|
|
6902
|
+
}
|
|
6903
|
+
const batches = waves.map((wave) => wave.map((idx) => groups[idx]));
|
|
6904
|
+
return {
|
|
6905
|
+
batches,
|
|
6906
|
+
edges
|
|
6907
|
+
};
|
|
6908
|
+
}
|
|
6698
6909
|
|
|
6699
6910
|
//#endregion
|
|
6700
6911
|
//#region src/cli/commands/health.ts
|
|
@@ -7416,6 +7627,163 @@ function detectInterfaceChanges(options) {
|
|
|
7416
7627
|
}
|
|
7417
7628
|
}
|
|
7418
7629
|
|
|
7630
|
+
//#endregion
|
|
7631
|
+
//#region src/modules/compiled-workflows/interface-contracts.ts
|
|
7632
|
+
/**
|
|
7633
|
+
* Parse the `## Interface Contracts` section from a story file.
|
|
7634
|
+
*
|
|
7635
|
+
* Looks for lines matching the format:
|
|
7636
|
+
* - **Export**: SchemaName @ src/path/to/file.ts (optional transport)
|
|
7637
|
+
* - **Import**: SchemaName @ src/path/to/file.ts (optional transport)
|
|
7638
|
+
*
|
|
7639
|
+
* The section is optional — returns empty array when not found or malformed.
|
|
7640
|
+
* Parsing stops at the next `##` heading to avoid false positives in other sections.
|
|
7641
|
+
*
|
|
7642
|
+
* @param storyContent - Full text content of the story markdown file
|
|
7643
|
+
* @param storyKey - Story key to associate with each declaration (e.g., "25-4")
|
|
7644
|
+
* @returns Array of typed contract declarations (may be empty)
|
|
7645
|
+
*/
|
|
7646
|
+
function parseInterfaceContracts(storyContent, storyKey) {
|
|
7647
|
+
if (!storyContent || !storyKey) return [];
|
|
7648
|
+
const sectionMatch = /^##\s+Interface\s+Contracts\s*$/im.exec(storyContent);
|
|
7649
|
+
if (!sectionMatch) return [];
|
|
7650
|
+
const sectionStart = sectionMatch.index + sectionMatch[0].length;
|
|
7651
|
+
const afterSection = storyContent.slice(sectionStart);
|
|
7652
|
+
const nextHeading = /^##\s+/m.exec(afterSection);
|
|
7653
|
+
const sectionContent = nextHeading ? afterSection.slice(0, nextHeading.index) : afterSection;
|
|
7654
|
+
const linePattern = /^\s*-\s+\*\*(Export|Import)\*\*:\s+(\S+)\s+@\s+(\S+)(?:\s+\(([^)]+)\))?/gim;
|
|
7655
|
+
const declarations = [];
|
|
7656
|
+
let match;
|
|
7657
|
+
while ((match = linePattern.exec(sectionContent)) !== null) {
|
|
7658
|
+
const [, directionRaw, contractName, filePath, transport] = match;
|
|
7659
|
+
declarations.push({
|
|
7660
|
+
contractName,
|
|
7661
|
+
direction: directionRaw.toLowerCase(),
|
|
7662
|
+
filePath,
|
|
7663
|
+
storyKey,
|
|
7664
|
+
...transport !== void 0 ? { transport } : {}
|
|
7665
|
+
});
|
|
7666
|
+
}
|
|
7667
|
+
return declarations;
|
|
7668
|
+
}
|
|
7669
|
+
|
|
7670
|
+
//#endregion
|
|
7671
|
+
//#region src/modules/implementation-orchestrator/contract-verifier.ts
|
|
7672
|
+
/**
|
|
7673
|
+
* Verify all declared contract export/import pairs after sprint completion.
|
|
7674
|
+
*
|
|
7675
|
+
* @param declarations - All ContractDeclaration entries from the decision store
|
|
7676
|
+
* @param projectRoot - Absolute path to the project root for file resolution
|
|
7677
|
+
* @returns - Array of ContractMismatch entries (empty = all passed)
|
|
7678
|
+
*/
|
|
7679
|
+
function verifyContracts(declarations, projectRoot) {
|
|
7680
|
+
if (declarations.length === 0) return [];
|
|
7681
|
+
const exports = declarations.filter((d) => d.direction === "export");
|
|
7682
|
+
const imports = declarations.filter((d) => d.direction === "import");
|
|
7683
|
+
if (exports.length === 0) return [];
|
|
7684
|
+
const mismatches = [];
|
|
7685
|
+
for (const exp of exports) {
|
|
7686
|
+
if (!exp.filePath) continue;
|
|
7687
|
+
const absPath = join$1(projectRoot, exp.filePath);
|
|
7688
|
+
if (!existsSync$1(absPath)) {
|
|
7689
|
+
const importers = imports.filter((i) => i.contractName === exp.contractName);
|
|
7690
|
+
if (importers.length > 0) for (const imp of importers) mismatches.push({
|
|
7691
|
+
exporter: exp.storyKey,
|
|
7692
|
+
importer: imp.storyKey,
|
|
7693
|
+
contractName: exp.contractName,
|
|
7694
|
+
mismatchDescription: `Exported file not found: ${exp.filePath}`
|
|
7695
|
+
});
|
|
7696
|
+
else mismatches.push({
|
|
7697
|
+
exporter: exp.storyKey,
|
|
7698
|
+
importer: null,
|
|
7699
|
+
contractName: exp.contractName,
|
|
7700
|
+
mismatchDescription: `Exported file not found: ${exp.filePath}`
|
|
7701
|
+
});
|
|
7702
|
+
}
|
|
7703
|
+
}
|
|
7704
|
+
const tsconfigPath = join$1(projectRoot, "tsconfig.json");
|
|
7705
|
+
const tscBinPath = join$1(projectRoot, "node_modules", ".bin", "tsc");
|
|
7706
|
+
if (existsSync$1(tsconfigPath) && existsSync$1(tscBinPath)) {
|
|
7707
|
+
let tscOutput = "";
|
|
7708
|
+
let tscFailed = false;
|
|
7709
|
+
try {
|
|
7710
|
+
execSync(`"${tscBinPath}" --noEmit`, {
|
|
7711
|
+
cwd: projectRoot,
|
|
7712
|
+
timeout: 12e4,
|
|
7713
|
+
encoding: "utf-8",
|
|
7714
|
+
stdio: [
|
|
7715
|
+
"pipe",
|
|
7716
|
+
"pipe",
|
|
7717
|
+
"pipe"
|
|
7718
|
+
]
|
|
7719
|
+
});
|
|
7720
|
+
} catch (err) {
|
|
7721
|
+
tscFailed = true;
|
|
7722
|
+
if (err != null && typeof err === "object") {
|
|
7723
|
+
const e = err;
|
|
7724
|
+
const stdoutStr = typeof e.stdout === "string" ? e.stdout : Buffer.isBuffer(e.stdout) ? e.stdout.toString("utf-8") : "";
|
|
7725
|
+
const stderrStr = typeof e.stderr === "string" ? e.stderr : Buffer.isBuffer(e.stderr) ? e.stderr.toString("utf-8") : "";
|
|
7726
|
+
tscOutput = [stdoutStr, stderrStr].filter((s) => s.length > 0).join("\n");
|
|
7727
|
+
if (!tscOutput && e.message) tscOutput = e.message;
|
|
7728
|
+
}
|
|
7729
|
+
}
|
|
7730
|
+
if (tscFailed) {
|
|
7731
|
+
const truncatedOutput = tscOutput.slice(0, 1e3);
|
|
7732
|
+
const matchedExports = new Set();
|
|
7733
|
+
for (const exp of exports) {
|
|
7734
|
+
if (!exp.filePath) continue;
|
|
7735
|
+
if (tscOutput.includes(exp.filePath)) {
|
|
7736
|
+
matchedExports.add(exp.contractName);
|
|
7737
|
+
const importers = imports.filter((i) => i.contractName === exp.contractName);
|
|
7738
|
+
if (importers.length > 0) for (const imp of importers) mismatches.push({
|
|
7739
|
+
exporter: exp.storyKey,
|
|
7740
|
+
importer: imp.storyKey,
|
|
7741
|
+
contractName: exp.contractName,
|
|
7742
|
+
mismatchDescription: `TypeScript type-check failed for ${exp.filePath}: ${truncatedOutput}`
|
|
7743
|
+
});
|
|
7744
|
+
else mismatches.push({
|
|
7745
|
+
exporter: exp.storyKey,
|
|
7746
|
+
importer: null,
|
|
7747
|
+
contractName: exp.contractName,
|
|
7748
|
+
mismatchDescription: `TypeScript type-check failed for ${exp.filePath}: ${truncatedOutput}`
|
|
7749
|
+
});
|
|
7750
|
+
}
|
|
7751
|
+
}
|
|
7752
|
+
if (matchedExports.size === 0) {
|
|
7753
|
+
const reportedPairs = new Set();
|
|
7754
|
+
for (const exp of exports) {
|
|
7755
|
+
const importers = imports.filter((i) => i.contractName === exp.contractName);
|
|
7756
|
+
if (importers.length > 0) for (const imp of importers) {
|
|
7757
|
+
const pairKey = `${exp.storyKey}:${imp.storyKey}:${exp.contractName}`;
|
|
7758
|
+
if (!reportedPairs.has(pairKey)) {
|
|
7759
|
+
reportedPairs.add(pairKey);
|
|
7760
|
+
mismatches.push({
|
|
7761
|
+
exporter: exp.storyKey,
|
|
7762
|
+
importer: imp.storyKey,
|
|
7763
|
+
contractName: exp.contractName,
|
|
7764
|
+
mismatchDescription: `TypeScript type-check failed: ${truncatedOutput}`
|
|
7765
|
+
});
|
|
7766
|
+
}
|
|
7767
|
+
}
|
|
7768
|
+
else {
|
|
7769
|
+
const pairKey = `${exp.storyKey}:null:${exp.contractName}`;
|
|
7770
|
+
if (!reportedPairs.has(pairKey)) {
|
|
7771
|
+
reportedPairs.add(pairKey);
|
|
7772
|
+
mismatches.push({
|
|
7773
|
+
exporter: exp.storyKey,
|
|
7774
|
+
importer: null,
|
|
7775
|
+
contractName: exp.contractName,
|
|
7776
|
+
mismatchDescription: `TypeScript type-check failed: ${truncatedOutput}`
|
|
7777
|
+
});
|
|
7778
|
+
}
|
|
7779
|
+
}
|
|
7780
|
+
}
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
7783
|
+
}
|
|
7784
|
+
return mismatches;
|
|
7785
|
+
}
|
|
7786
|
+
|
|
7419
7787
|
//#endregion
|
|
7420
7788
|
//#region src/modules/implementation-orchestrator/orchestrator-impl.ts
|
|
7421
7789
|
function createPauseGate() {
|
|
@@ -7477,6 +7845,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
7477
7845
|
const _phaseEndMs = new Map();
|
|
7478
7846
|
const _storyDispatches = new Map();
|
|
7479
7847
|
let _maxConcurrentActual = 0;
|
|
7848
|
+
let _contractMismatches;
|
|
7480
7849
|
const MEMORY_PRESSURE_BACKOFF_MS = [
|
|
7481
7850
|
3e4,
|
|
7482
7851
|
6e4,
|
|
@@ -7673,6 +8042,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
7673
8042
|
}
|
|
7674
8043
|
if (_decomposition !== void 0) status.decomposition = { ..._decomposition };
|
|
7675
8044
|
if (_maxConcurrentActual > 0) status.maxConcurrentActual = _maxConcurrentActual;
|
|
8045
|
+
if (_contractMismatches !== void 0 && _contractMismatches.length > 0) status.contractMismatches = [..._contractMismatches];
|
|
7676
8046
|
return status;
|
|
7677
8047
|
}
|
|
7678
8048
|
function updateStory(storyKey, updates) {
|
|
@@ -7947,6 +8317,35 @@ function createImplementationOrchestrator(deps) {
|
|
|
7947
8317
|
persistState();
|
|
7948
8318
|
return;
|
|
7949
8319
|
}
|
|
8320
|
+
if (storyFilePath) try {
|
|
8321
|
+
const storyContent = await readFile$1(storyFilePath, "utf-8");
|
|
8322
|
+
const contracts = parseInterfaceContracts(storyContent, storyKey);
|
|
8323
|
+
if (contracts.length > 0) {
|
|
8324
|
+
for (const contract of contracts) createDecision(db, {
|
|
8325
|
+
pipeline_run_id: config.pipelineRunId ?? null,
|
|
8326
|
+
phase: "implementation",
|
|
8327
|
+
category: "interface-contract",
|
|
8328
|
+
key: `${storyKey}:${contract.contractName}`,
|
|
8329
|
+
value: JSON.stringify({
|
|
8330
|
+
direction: contract.direction,
|
|
8331
|
+
schemaName: contract.contractName,
|
|
8332
|
+
filePath: contract.filePath,
|
|
8333
|
+
storyKey: contract.storyKey,
|
|
8334
|
+
...contract.transport !== void 0 ? { transport: contract.transport } : {}
|
|
8335
|
+
})
|
|
8336
|
+
});
|
|
8337
|
+
logger$23.info({
|
|
8338
|
+
storyKey,
|
|
8339
|
+
contractCount: contracts.length,
|
|
8340
|
+
contracts
|
|
8341
|
+
}, "Stored interface contract declarations in decision store");
|
|
8342
|
+
}
|
|
8343
|
+
} catch (err) {
|
|
8344
|
+
logger$23.warn({
|
|
8345
|
+
storyKey,
|
|
8346
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8347
|
+
}, "Failed to parse interface contracts — continuing without contract declarations");
|
|
8348
|
+
}
|
|
7950
8349
|
await waitIfPaused();
|
|
7951
8350
|
if (_state !== "RUNNING") return;
|
|
7952
8351
|
startPhase(storyKey, "test-plan");
|
|
@@ -8254,9 +8653,9 @@ function createImplementationOrchestrator(deps) {
|
|
|
8254
8653
|
});
|
|
8255
8654
|
let verdict;
|
|
8256
8655
|
let issueList = [];
|
|
8656
|
+
let reviewResult;
|
|
8257
8657
|
try {
|
|
8258
8658
|
const useBatchedReview = batchFileGroups.length > 1 && previousIssueList.length === 0;
|
|
8259
|
-
let reviewResult;
|
|
8260
8659
|
if (useBatchedReview) {
|
|
8261
8660
|
const allIssues = [];
|
|
8262
8661
|
let worstVerdict = "SHIP_IT";
|
|
@@ -8268,6 +8667,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
8268
8667
|
let lastRawOutput;
|
|
8269
8668
|
const verdictRank = {
|
|
8270
8669
|
"SHIP_IT": 0,
|
|
8670
|
+
"LGTM_WITH_NOTES": .5,
|
|
8271
8671
|
"NEEDS_MINOR_FIXES": 1,
|
|
8272
8672
|
"NEEDS_MAJOR_REWORK": 2
|
|
8273
8673
|
};
|
|
@@ -8334,7 +8734,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
8334
8734
|
...previousIssueList.length > 0 ? { previousIssues: previousIssueList } : {}
|
|
8335
8735
|
});
|
|
8336
8736
|
}
|
|
8337
|
-
const isPhantomReview = reviewResult.dispatchFailed === true || reviewResult.verdict !== "SHIP_IT" && (reviewResult.issue_list === void 0 || reviewResult.issue_list.length === 0) && reviewResult.error !== void 0;
|
|
8737
|
+
const isPhantomReview = reviewResult.dispatchFailed === true || reviewResult.verdict !== "SHIP_IT" && reviewResult.verdict !== "LGTM_WITH_NOTES" && (reviewResult.issue_list === void 0 || reviewResult.issue_list.length === 0) && reviewResult.error !== void 0;
|
|
8338
8738
|
if (isPhantomReview && !timeoutRetried) {
|
|
8339
8739
|
timeoutRetried = true;
|
|
8340
8740
|
logger$23.warn({
|
|
@@ -8407,19 +8807,38 @@ function createImplementationOrchestrator(deps) {
|
|
|
8407
8807
|
persistState();
|
|
8408
8808
|
return;
|
|
8409
8809
|
}
|
|
8410
|
-
if (verdict === "SHIP_IT") {
|
|
8810
|
+
if (verdict === "SHIP_IT" || verdict === "LGTM_WITH_NOTES") {
|
|
8411
8811
|
endPhase(storyKey, "code-review");
|
|
8412
8812
|
updateStory(storyKey, {
|
|
8413
8813
|
phase: "COMPLETE",
|
|
8414
8814
|
completedAt: new Date().toISOString()
|
|
8415
8815
|
});
|
|
8416
|
-
writeStoryMetricsBestEffort(storyKey,
|
|
8816
|
+
writeStoryMetricsBestEffort(storyKey, verdict, reviewCycles + 1);
|
|
8417
8817
|
writeStoryOutcomeBestEffort(storyKey, "complete", reviewCycles + 1);
|
|
8418
8818
|
eventBus.emit("orchestrator:story-complete", {
|
|
8419
8819
|
storyKey,
|
|
8420
8820
|
reviewCycles
|
|
8421
8821
|
});
|
|
8422
8822
|
persistState();
|
|
8823
|
+
if (verdict === "LGTM_WITH_NOTES" && reviewResult.notes && config.pipelineRunId) try {
|
|
8824
|
+
createDecision(db, {
|
|
8825
|
+
pipeline_run_id: config.pipelineRunId,
|
|
8826
|
+
phase: "implementation",
|
|
8827
|
+
category: ADVISORY_NOTES,
|
|
8828
|
+
key: `${storyKey}:${config.pipelineRunId}`,
|
|
8829
|
+
value: JSON.stringify({
|
|
8830
|
+
storyKey,
|
|
8831
|
+
notes: reviewResult.notes
|
|
8832
|
+
}),
|
|
8833
|
+
rationale: `Advisory notes from LGTM_WITH_NOTES review of ${storyKey}`
|
|
8834
|
+
});
|
|
8835
|
+
logger$23.info({ storyKey }, "Advisory notes persisted to decision store");
|
|
8836
|
+
} catch (advisoryErr) {
|
|
8837
|
+
logger$23.warn({
|
|
8838
|
+
storyKey,
|
|
8839
|
+
error: advisoryErr instanceof Error ? advisoryErr.message : String(advisoryErr)
|
|
8840
|
+
}, "Failed to persist advisory notes (best-effort)");
|
|
8841
|
+
}
|
|
8423
8842
|
try {
|
|
8424
8843
|
const expansionResult = await runTestExpansion({
|
|
8425
8844
|
db,
|
|
@@ -8566,7 +8985,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
8566
8985
|
reviewCycles: finalReviewCycles,
|
|
8567
8986
|
completedAt: new Date().toISOString()
|
|
8568
8987
|
});
|
|
8569
|
-
writeStoryMetricsBestEffort(storyKey,
|
|
8988
|
+
writeStoryMetricsBestEffort(storyKey, verdict, finalReviewCycles);
|
|
8570
8989
|
writeStoryOutcomeBestEffort(storyKey, "complete", finalReviewCycles);
|
|
8571
8990
|
eventBus.emit("orchestrator:story-complete", {
|
|
8572
8991
|
storyKey,
|
|
@@ -8843,14 +9262,64 @@ function createImplementationOrchestrator(deps) {
|
|
|
8843
9262
|
skippedCategories: seedResult.skippedCategories
|
|
8844
9263
|
}, "Methodology context seeded from planning artifacts");
|
|
8845
9264
|
}
|
|
8846
|
-
const
|
|
9265
|
+
const interfaceContractDecisions = getDecisionsByCategory(db, "interface-contract");
|
|
9266
|
+
const contractDeclarations = interfaceContractDecisions.map((d) => {
|
|
9267
|
+
try {
|
|
9268
|
+
const parsed = JSON.parse(d.value);
|
|
9269
|
+
const storyKey = typeof parsed.storyKey === "string" ? parsed.storyKey : "";
|
|
9270
|
+
const contractName = typeof parsed.schemaName === "string" ? parsed.schemaName : "";
|
|
9271
|
+
const direction = parsed.direction === "export" ? "export" : "import";
|
|
9272
|
+
const filePath = typeof parsed.filePath === "string" ? parsed.filePath : "";
|
|
9273
|
+
if (!storyKey || !contractName) return null;
|
|
9274
|
+
return {
|
|
9275
|
+
storyKey,
|
|
9276
|
+
contractName,
|
|
9277
|
+
direction,
|
|
9278
|
+
filePath,
|
|
9279
|
+
...typeof parsed.transport === "string" ? { transport: parsed.transport } : {}
|
|
9280
|
+
};
|
|
9281
|
+
} catch {
|
|
9282
|
+
return null;
|
|
9283
|
+
}
|
|
9284
|
+
}).filter((d) => d !== null);
|
|
9285
|
+
const { batches, edges: contractEdges } = detectConflictGroupsWithContracts(storyKeys, { moduleMap: pack.manifest.conflictGroups }, contractDeclarations);
|
|
9286
|
+
if (contractEdges.length > 0) logger$23.info({
|
|
9287
|
+
contractEdges,
|
|
9288
|
+
edgeCount: contractEdges.length
|
|
9289
|
+
}, "Contract dependency edges detected — applying contract-aware dispatch ordering");
|
|
8847
9290
|
logger$23.info("Orchestrator starting", {
|
|
8848
9291
|
storyCount: storyKeys.length,
|
|
8849
|
-
groupCount:
|
|
9292
|
+
groupCount: batches.reduce((sum, b) => sum + b.length, 0),
|
|
9293
|
+
batchCount: batches.length,
|
|
8850
9294
|
maxConcurrency: config.maxConcurrency
|
|
8851
9295
|
});
|
|
9296
|
+
if (config.skipPreflight !== true) {
|
|
9297
|
+
const preFlightResult = runBuildVerification({
|
|
9298
|
+
verifyCommand: pack.manifest.verifyCommand,
|
|
9299
|
+
verifyTimeoutMs: pack.manifest.verifyTimeoutMs,
|
|
9300
|
+
projectRoot: projectRoot ?? process.cwd()
|
|
9301
|
+
});
|
|
9302
|
+
if (preFlightResult.status === "failed" || preFlightResult.status === "timeout") {
|
|
9303
|
+
stopHeartbeat();
|
|
9304
|
+
const truncatedOutput = (preFlightResult.output ?? "").slice(0, 2e3);
|
|
9305
|
+
const exitCode = preFlightResult.exitCode ?? 1;
|
|
9306
|
+
eventBus.emit("pipeline:pre-flight-failure", {
|
|
9307
|
+
exitCode,
|
|
9308
|
+
output: truncatedOutput
|
|
9309
|
+
});
|
|
9310
|
+
logger$23.error({
|
|
9311
|
+
exitCode,
|
|
9312
|
+
reason: preFlightResult.reason
|
|
9313
|
+
}, "Pre-flight build check failed — aborting pipeline before any story dispatch");
|
|
9314
|
+
_state = "FAILED";
|
|
9315
|
+
_completedAt = new Date().toISOString();
|
|
9316
|
+
persistState();
|
|
9317
|
+
return getStatus();
|
|
9318
|
+
}
|
|
9319
|
+
if (preFlightResult.status !== "skipped") logger$23.info("Pre-flight build check passed");
|
|
9320
|
+
}
|
|
8852
9321
|
try {
|
|
8853
|
-
await runWithConcurrency(
|
|
9322
|
+
for (const batchGroups of batches) await runWithConcurrency(batchGroups, config.maxConcurrency);
|
|
8854
9323
|
} catch (err) {
|
|
8855
9324
|
stopHeartbeat();
|
|
8856
9325
|
_state = "FAILED";
|
|
@@ -8862,6 +9331,24 @@ function createImplementationOrchestrator(deps) {
|
|
|
8862
9331
|
stopHeartbeat();
|
|
8863
9332
|
_state = "COMPLETE";
|
|
8864
9333
|
_completedAt = new Date().toISOString();
|
|
9334
|
+
if (projectRoot !== void 0 && contractDeclarations.length > 0) try {
|
|
9335
|
+
const mismatches = verifyContracts(contractDeclarations, projectRoot);
|
|
9336
|
+
if (mismatches.length > 0) {
|
|
9337
|
+
_contractMismatches = mismatches;
|
|
9338
|
+
for (const mismatch of mismatches) eventBus.emit("pipeline:contract-mismatch", {
|
|
9339
|
+
exporter: mismatch.exporter,
|
|
9340
|
+
importer: mismatch.importer,
|
|
9341
|
+
contractName: mismatch.contractName,
|
|
9342
|
+
mismatchDescription: mismatch.mismatchDescription
|
|
9343
|
+
});
|
|
9344
|
+
logger$23.warn({
|
|
9345
|
+
mismatchCount: mismatches.length,
|
|
9346
|
+
mismatches
|
|
9347
|
+
}, "Post-sprint contract verification found mismatches — manual review required");
|
|
9348
|
+
} else logger$23.info("Post-sprint contract verification passed — all declared contracts satisfied");
|
|
9349
|
+
} catch (err) {
|
|
9350
|
+
logger$23.error({ err }, "Post-sprint contract verification threw an error — skipping");
|
|
9351
|
+
}
|
|
8865
9352
|
let completed = 0;
|
|
8866
9353
|
let escalated = 0;
|
|
8867
9354
|
let failed = 0;
|
|
@@ -12927,7 +13414,7 @@ function mapInternalPhaseToEventPhase(internalPhase) {
|
|
|
12927
13414
|
}
|
|
12928
13415
|
}
|
|
12929
13416
|
async function runRunAction(options) {
|
|
12930
|
-
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;
|
|
13417
|
+
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, registry: injectedRegistry } = options;
|
|
12931
13418
|
if (startPhase !== void 0 && !VALID_PHASES.includes(startPhase)) {
|
|
12932
13419
|
const errorMsg = `Invalid phase '${startPhase}'. Valid phases: ${VALID_PHASES.join(", ")}`;
|
|
12933
13420
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
|
|
@@ -13014,6 +13501,7 @@ async function runRunAction(options) {
|
|
|
13014
13501
|
...skipUx === true ? { skipUx: true } : {},
|
|
13015
13502
|
...researchFlag === true ? { research: true } : {},
|
|
13016
13503
|
...skipResearchFlag === true ? { skipResearch: true } : {},
|
|
13504
|
+
...skipPreflight === true ? { skipPreflight: true } : {},
|
|
13017
13505
|
...injectedRegistry !== void 0 ? { registry: injectedRegistry } : {}
|
|
13018
13506
|
});
|
|
13019
13507
|
let storyKeys = [];
|
|
@@ -13403,6 +13891,24 @@ async function runRunAction(options) {
|
|
|
13403
13891
|
dispatches: payload.dispatches
|
|
13404
13892
|
});
|
|
13405
13893
|
});
|
|
13894
|
+
eventBus.on("pipeline:pre-flight-failure", (payload) => {
|
|
13895
|
+
ndjsonEmitter.emit({
|
|
13896
|
+
type: "pipeline:pre-flight-failure",
|
|
13897
|
+
ts: new Date().toISOString(),
|
|
13898
|
+
exitCode: payload.exitCode,
|
|
13899
|
+
output: payload.output
|
|
13900
|
+
});
|
|
13901
|
+
});
|
|
13902
|
+
eventBus.on("pipeline:contract-mismatch", (payload) => {
|
|
13903
|
+
ndjsonEmitter.emit({
|
|
13904
|
+
type: "pipeline:contract-mismatch",
|
|
13905
|
+
ts: new Date().toISOString(),
|
|
13906
|
+
exporter: payload.exporter,
|
|
13907
|
+
importer: payload.importer,
|
|
13908
|
+
contractName: payload.contractName,
|
|
13909
|
+
mismatchDescription: payload.mismatchDescription
|
|
13910
|
+
});
|
|
13911
|
+
});
|
|
13406
13912
|
}
|
|
13407
13913
|
const orchestrator = createImplementationOrchestrator({
|
|
13408
13914
|
db,
|
|
@@ -13414,7 +13920,8 @@ async function runRunAction(options) {
|
|
|
13414
13920
|
maxConcurrency: concurrency,
|
|
13415
13921
|
maxReviewCycles: 2,
|
|
13416
13922
|
pipelineRunId: pipelineRun.id,
|
|
13417
|
-
enableHeartbeat: eventsFlag === true
|
|
13923
|
+
enableHeartbeat: eventsFlag === true,
|
|
13924
|
+
skipPreflight: skipPreflight === true
|
|
13418
13925
|
},
|
|
13419
13926
|
projectRoot,
|
|
13420
13927
|
tokenCeilings
|
|
@@ -13513,7 +14020,7 @@ async function runRunAction(options) {
|
|
|
13513
14020
|
}
|
|
13514
14021
|
}
|
|
13515
14022
|
async function runFullPipeline(options) {
|
|
13516
|
-
const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, registry: injectedRegistry, tokenCeilings } = options;
|
|
14023
|
+
const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, registry: injectedRegistry, tokenCeilings } = options;
|
|
13517
14024
|
if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
|
|
13518
14025
|
const dbWrapper = new DatabaseWrapper(dbPath);
|
|
13519
14026
|
try {
|
|
@@ -13718,7 +14225,8 @@ async function runFullPipeline(options) {
|
|
|
13718
14225
|
config: {
|
|
13719
14226
|
maxConcurrency: concurrency,
|
|
13720
14227
|
maxReviewCycles: 2,
|
|
13721
|
-
pipelineRunId: runId
|
|
14228
|
+
pipelineRunId: runId,
|
|
14229
|
+
skipPreflight: skipPreflight === true
|
|
13722
14230
|
},
|
|
13723
14231
|
projectRoot,
|
|
13724
14232
|
tokenCeilings
|
|
@@ -13818,7 +14326,7 @@ async function runFullPipeline(options) {
|
|
|
13818
14326
|
}
|
|
13819
14327
|
}
|
|
13820
14328
|
function registerRunCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
|
|
13821
|
-
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) => {
|
|
14329
|
+
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").option("--skip-preflight", "Skip the pre-flight build check (escape hatch for known-broken projects)").action(async (opts) => {
|
|
13822
14330
|
if (opts.helpAgent) {
|
|
13823
14331
|
process.exitCode = await runHelpAgent();
|
|
13824
14332
|
return;
|
|
@@ -13851,6 +14359,7 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
13851
14359
|
skipUx: opts.skipUx,
|
|
13852
14360
|
research: opts.research,
|
|
13853
14361
|
skipResearch: opts.skipResearch,
|
|
14362
|
+
skipPreflight: opts.skipPreflight,
|
|
13854
14363
|
registry
|
|
13855
14364
|
});
|
|
13856
14365
|
process.exitCode = exitCode;
|
|
@@ -13859,4 +14368,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
13859
14368
|
|
|
13860
14369
|
//#endregion
|
|
13861
14370
|
export { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createConfigSystem, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
|
|
13862
|
-
//# sourceMappingURL=run-
|
|
14371
|
+
//# sourceMappingURL=run-BxXwD2xY.js.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { registerRunCommand, runRunAction } from "./run-
|
|
1
|
+
import { registerRunCommand, runRunAction } from "./run-BxXwD2xY.js";
|
|
2
2
|
import "./logger-D2fS2ccL.js";
|
|
3
3
|
import "./config-migrator-DSi8KhQC.js";
|
|
4
4
|
import "./helpers-RL22dYtn.js";
|
|
5
5
|
import "./decisions-Dq4cAA2L.js";
|
|
6
|
-
import "./operational-
|
|
6
|
+
import "./operational-Bovj4fS-.js";
|
|
7
7
|
|
|
8
8
|
export { runRunAction };
|
package/package.json
CHANGED
package/packs/bmad/manifest.yaml
CHANGED
|
@@ -221,6 +221,8 @@ prompts:
|
|
|
221
221
|
refine-artifact: prompts/refine-artifact.md
|
|
222
222
|
# Readiness check prompt (Story 16-6)
|
|
223
223
|
readiness-check: prompts/readiness-check.md
|
|
224
|
+
# Test plan prompt (Story 25-7)
|
|
225
|
+
test-plan: prompts/test-plan.md
|
|
224
226
|
|
|
225
227
|
constraints:
|
|
226
228
|
create-story: constraints/create-story.yaml
|
|
@@ -100,6 +100,11 @@ ac_checklist:
|
|
|
100
100
|
**IMPORTANT**: `ac_checklist` must contain one entry for every AC found in the story. If the story has no parseable ACs (e.g. a refactoring story), `ac_checklist` may be an empty array.
|
|
101
101
|
|
|
102
102
|
**Verdict rules:**
|
|
103
|
-
- `SHIP_IT` — zero
|
|
104
|
-
- `
|
|
103
|
+
- `SHIP_IT` — zero issues of any kind
|
|
104
|
+
- `LGTM_WITH_NOTES` — zero correctness/logic/security issues; only advisory or style observations that do not need to be fixed before shipping. Use this when you have optional suggestions but the code is production-ready as-is. Include your suggestions in the `notes` field.
|
|
105
|
+
- `NEEDS_MINOR_FIXES` — one or more minor issues that should be fixed, or 1-2 major issues with no blockers
|
|
105
106
|
- `NEEDS_MAJOR_REWORK` — any blocker issue, or 3+ major issues
|
|
107
|
+
|
|
108
|
+
**LGTM_WITH_NOTES vs NEEDS_MINOR_FIXES:**
|
|
109
|
+
- Use `LGTM_WITH_NOTES` when: all findings are purely advisory (naming preferences, optional refactors, docs suggestions) and the code ships safely without any changes
|
|
110
|
+
- Use `NEEDS_MINOR_FIXES` when: any finding represents a real gap that should be corrected before the story is considered done (missing error handling, incomplete AC coverage, confusing logic)
|
|
@@ -35,6 +35,26 @@ Using the context above, write a complete, implementation-ready story file for s
|
|
|
35
35
|
- Status must be: `ready-for-dev`
|
|
36
36
|
- Dev Agent Record section must be present but left blank (to be filled by dev agent)
|
|
37
37
|
|
|
38
|
+
## Interface Contracts Guidance
|
|
39
|
+
|
|
40
|
+
**Identify cross-story dependencies** when the story creates or consumes shared schemas, interfaces, or message contracts.
|
|
41
|
+
|
|
42
|
+
If the story exports (creates) or imports (consumes from another story) any TypeScript interfaces, Zod schemas, message queue contracts, or API types that are shared across module boundaries, add an `## Interface Contracts` section to the story file.
|
|
43
|
+
|
|
44
|
+
Use this exact format for each item:
|
|
45
|
+
|
|
46
|
+
```markdown
|
|
47
|
+
## Interface Contracts
|
|
48
|
+
|
|
49
|
+
- **Export**: SchemaName @ src/path/to/file.ts (queue: some-queue-name)
|
|
50
|
+
- **Import**: SchemaName @ src/path/to/file.ts (from story 25-X)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- `Export` = this story creates/defines the schema that other stories will consume
|
|
54
|
+
- `Import` = this story consumes a schema defined by another story
|
|
55
|
+
- The transport annotation `(queue: ...)` or `(api: ...)` or `(from story X-Y)` is optional but recommended when applicable
|
|
56
|
+
- **The `## Interface Contracts` section is optional** — omit it entirely if the story has no cross-story schema dependencies
|
|
57
|
+
|
|
38
58
|
## Scope Cap Guidance
|
|
39
59
|
|
|
40
60
|
**Aim for 6-7 acceptance criteria and 7-8 tasks per story.**
|
|
@@ -2,24 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
## Mission
|
|
4
4
|
|
|
5
|
-
Analyze the story's Acceptance Criteria and tasks. Produce a concrete test plan listing the test files to create, the test categories to cover (unit/integration/e2e), and
|
|
5
|
+
Analyze the story's Acceptance Criteria and tasks. Produce a concrete test plan listing the test files to create, the test categories to cover (unit/integration/e2e), and comprehensive coverage notes that include: which ACs each test covers, dependencies to mock, and error conditions to assert.
|
|
6
6
|
|
|
7
7
|
## Story Content
|
|
8
8
|
|
|
9
9
|
{{story_content}}
|
|
10
10
|
|
|
11
|
+
## Architecture Constraints
|
|
12
|
+
|
|
13
|
+
{{architecture_constraints}}
|
|
14
|
+
|
|
11
15
|
## Instructions
|
|
12
16
|
|
|
13
17
|
1. Read the Acceptance Criteria (AC1, AC2, etc.) and Tasks in the story above.
|
|
14
18
|
2. Identify the source files that will need tests (from Dev Notes, Key File Paths, and Tasks).
|
|
15
19
|
3. For each AC, determine which test file and test function will validate it.
|
|
16
|
-
4.
|
|
20
|
+
4. Identify all external dependencies (modules, services, fs, db) that must be mocked or stubbed.
|
|
21
|
+
5. Identify error conditions and edge cases that must be asserted (not just the happy path).
|
|
22
|
+
6. Produce a concise test plan — one or two test files is typical for small stories.
|
|
17
23
|
|
|
18
24
|
**Rules:**
|
|
19
25
|
- List only test files that will be NEW (not existing ones you'd extend unless necessary).
|
|
20
26
|
- Use the project's test path convention: `src/modules/<module>/__tests__/<file>.test.ts`
|
|
21
27
|
- Test categories: `unit` for isolated function tests, `integration` for multi-module tests, `e2e` for full pipeline tests.
|
|
22
|
-
-
|
|
28
|
+
- `coverage_notes` must include: (a) which test file covers each AC, (b) dependencies to mock (e.g., `vi.mock('node:fs/promises')`), and (c) error conditions to assert (e.g., missing file, schema validation failure, timeout).
|
|
23
29
|
|
|
24
30
|
## Output Contract
|
|
25
31
|
|
|
@@ -31,7 +37,7 @@ test_files:
|
|
|
31
37
|
test_categories:
|
|
32
38
|
- unit
|
|
33
39
|
- integration
|
|
34
|
-
coverage_notes: "AC1
|
|
40
|
+
coverage_notes: "AC1: foo.test.ts describe('runFoo') covers the happy path. AC2: same file covers error path (rejects on ENOENT). Mocks needed: vi.mock('node:fs/promises'), vi.mock('../db.js'). Error conditions: file not found returns failed result, schema parse error returns failed with details, timeout triggers fallback."
|
|
35
41
|
|
|
36
42
|
If you cannot produce a plan (e.g., story content is missing or unreadable), emit:
|
|
37
43
|
|