substrate-ai 0.6.1 → 0.6.3
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 +31 -14
- package/dist/index.d.ts +25 -1
- package/dist/{routing-BUE9pIxW.js → routing-CobBiKeV.js} +2 -2
- package/dist/{routing-DbR9FPmj.js → routing-CpsRPjLE.js} +1 -1
- package/dist/{run-DTOsG7PJ.js → run-B5-a932y.js} +2 -2
- package/dist/{run-IDOmPys1.js → run-GwwiCYXX.js} +439 -49
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { AdapterTelemetryPersistence, AppError, DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DoltClient, DoltNotInstalled, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, FileStateStore, GitClient, GrammarLoader, IngestionServer, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SUBSTRATE_OWNED_SETTINGS_KEYS, SymbolParser, VALID_PHASES, WorkGraphRepository, buildPipelineStatusOutput, checkDoltInstalled, createConfigSystem, createContextCompiler, createDatabaseAdapter, createDispatcher, createDoltClient, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStateStore, createStopAfterGate, createTelemetryAdvisor, detectCycles, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, initSchema, initializeDolt, isSyncAdapter, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-
|
|
2
|
+
import { AdapterTelemetryPersistence, AppError, DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DoltClient, DoltNotInstalled, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, FileStateStore, GitClient, GrammarLoader, IngestionServer, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SUBSTRATE_OWNED_SETTINGS_KEYS, SymbolParser, VALID_PHASES, WorkGraphRepository, buildPipelineStatusOutput, checkDoltInstalled, createConfigSystem, createContextCompiler, createDatabaseAdapter, createDispatcher, createDoltClient, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStateStore, createStopAfterGate, createTelemetryAdvisor, detectCycles, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, initSchema, initializeDolt, isSyncAdapter, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-GwwiCYXX.js";
|
|
3
3
|
import { createLogger } from "../logger-D2fS2ccL.js";
|
|
4
4
|
import { AdapterRegistry } from "../adapter-registry-D2zdMwVu.js";
|
|
5
5
|
import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema } from "../config-migrator-DtZW1maj.js";
|
|
6
6
|
import { ConfigError, createEventBus } from "../helpers-BihqWgVe.js";
|
|
7
|
-
import { RoutingRecommender } from "../routing-
|
|
7
|
+
import { RoutingRecommender } from "../routing-CobBiKeV.js";
|
|
8
8
|
import { addTokenUsage, createDecision, createPipelineRun, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getTokenUsageSummary, listRequirements, updatePipelineRun } from "../decisions-C6MF2Cax.js";
|
|
9
9
|
import { ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, OPERATIONAL_FINDING, STORY_METRICS, aggregateTokenUsageForRun, compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline } from "../operational-BRpT8MYF.js";
|
|
10
10
|
import { abortMerge, createWorktree, getConflictingFiles, getMergedFiles, getOrphanedWorktrees, performMerge, removeBranch, removeWorktree, simulateMerge, verifyGitVersion } from "../git-utils-C-fdrHF_.js";
|
|
@@ -432,8 +432,8 @@ async function detectMonorepoProfile(rootDir) {
|
|
|
432
432
|
return { project: {
|
|
433
433
|
type: "monorepo",
|
|
434
434
|
tool: "turborepo",
|
|
435
|
-
buildCommand: "turbo build",
|
|
436
|
-
testCommand: "turbo test",
|
|
435
|
+
buildCommand: "npx turbo build",
|
|
436
|
+
testCommand: "npx turbo test",
|
|
437
437
|
packages
|
|
438
438
|
} };
|
|
439
439
|
}
|
|
@@ -912,6 +912,22 @@ async function scaffoldClaudeCommands(projectRoot, outputFormat) {
|
|
|
912
912
|
const TaskToolCommandGenerator = resolveExport(taskToolMod, "TaskToolCommandGenerator");
|
|
913
913
|
const manifestMod = _require(join(installerLibPath, "core", "manifest-generator.js"));
|
|
914
914
|
const ManifestGenerator = resolveExport(manifestMod, "ManifestGenerator");
|
|
915
|
+
const pathUtilsMod = _require(join(installerLibPath, "ide", "shared", "path-utils.js"));
|
|
916
|
+
const pathUtils = { toDashPath: pathUtilsMod.toDashPath ?? pathUtilsMod.default?.toDashPath };
|
|
917
|
+
const writeDashFallback = async (baseDir, artifacts, acceptTypes) => {
|
|
918
|
+
let written = 0;
|
|
919
|
+
for (const artifact of artifacts) {
|
|
920
|
+
if (!acceptTypes.includes(artifact.type)) continue;
|
|
921
|
+
const content = artifact.content;
|
|
922
|
+
if (!content || !artifact.relativePath) continue;
|
|
923
|
+
const flatName = pathUtils.toDashPath(artifact.relativePath);
|
|
924
|
+
const dest = join(baseDir, flatName);
|
|
925
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
926
|
+
writeFileSync(dest, content, "utf-8");
|
|
927
|
+
written++;
|
|
928
|
+
}
|
|
929
|
+
return written;
|
|
930
|
+
};
|
|
915
931
|
const nonCoreModules = scanBmadModules(bmadDir);
|
|
916
932
|
const allModules = ["core", ...nonCoreModules];
|
|
917
933
|
try {
|
|
@@ -925,13 +941,13 @@ async function scaffoldClaudeCommands(projectRoot, outputFormat) {
|
|
|
925
941
|
clearBmadCommandFiles(commandsDir);
|
|
926
942
|
const agentGen = new AgentCommandGenerator("_bmad");
|
|
927
943
|
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, nonCoreModules);
|
|
928
|
-
const agentCount = await agentGen.writeDashArtifacts(commandsDir, agentArtifacts);
|
|
944
|
+
const agentCount = typeof agentGen.writeDashArtifacts === "function" ? await agentGen.writeDashArtifacts(commandsDir, agentArtifacts) : await writeDashFallback(commandsDir, agentArtifacts, ["agent-launcher"]);
|
|
929
945
|
const workflowGen = new WorkflowCommandGenerator("_bmad");
|
|
930
946
|
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
|
931
|
-
const workflowCount = await workflowGen.writeDashArtifacts(commandsDir, workflowArtifacts);
|
|
947
|
+
const workflowCount = typeof workflowGen.writeDashArtifacts === "function" ? await workflowGen.writeDashArtifacts(commandsDir, workflowArtifacts) : await writeDashFallback(commandsDir, workflowArtifacts, ["workflow-command", "workflow-launcher"]);
|
|
932
948
|
const taskToolGen = new TaskToolCommandGenerator("_bmad");
|
|
933
949
|
const { artifacts: taskToolArtifacts } = await taskToolGen.collectTaskToolArtifacts(bmadDir);
|
|
934
|
-
const taskToolCount = await taskToolGen.writeDashArtifacts(commandsDir, taskToolArtifacts);
|
|
950
|
+
const taskToolCount = typeof taskToolGen.writeDashArtifacts === "function" ? await taskToolGen.writeDashArtifacts(commandsDir, taskToolArtifacts) : await writeDashFallback(commandsDir, taskToolArtifacts, ["task", "tool"]);
|
|
935
951
|
const total = agentCount + workflowCount + taskToolCount;
|
|
936
952
|
if (outputFormat !== "json") process.stdout.write(`Generated ${String(total)} Claude Code commands (${String(agentCount)} agents, ${String(workflowCount)} workflows, ${String(taskToolCount)} tasks/tools)\n`);
|
|
937
953
|
logger$18.info({
|
|
@@ -1541,7 +1557,7 @@ function mapInternalPhaseToEventPhase(internalPhase) {
|
|
|
1541
1557
|
}
|
|
1542
1558
|
}
|
|
1543
1559
|
async function runResumeAction(options) {
|
|
1544
|
-
const { runId: specifiedRunId, stopAfter, outputFormat, projectRoot, concurrency, pack: packName, events: eventsFlag, registry } = options;
|
|
1560
|
+
const { runId: specifiedRunId, stopAfter, outputFormat, projectRoot, concurrency, pack: packName, events: eventsFlag, registry, maxReviewCycles = 2 } = options;
|
|
1545
1561
|
if (stopAfter !== void 0 && !VALID_PHASES.includes(stopAfter)) {
|
|
1546
1562
|
const errorMsg = `Invalid phase: "${stopAfter}". Valid phases: ${VALID_PHASES.join(", ")}`;
|
|
1547
1563
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
|
|
@@ -1638,7 +1654,7 @@ async function runResumeAction(options) {
|
|
|
1638
1654
|
}
|
|
1639
1655
|
}
|
|
1640
1656
|
async function runFullPipelineFromPhase(options) {
|
|
1641
|
-
const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, events: eventsFlag, existingRunId, projectRoot, registry: injectedRegistry, stories: explicitStories } = options;
|
|
1657
|
+
const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, events: eventsFlag, existingRunId, projectRoot, registry: injectedRegistry, stories: explicitStories, maxReviewCycles = 2 } = options;
|
|
1642
1658
|
if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
|
|
1643
1659
|
const adapter = createDatabaseAdapter({
|
|
1644
1660
|
backend: "auto",
|
|
@@ -1775,7 +1791,7 @@ async function runFullPipelineFromPhase(options) {
|
|
|
1775
1791
|
eventBus,
|
|
1776
1792
|
config: {
|
|
1777
1793
|
maxConcurrency: concurrency,
|
|
1778
|
-
maxReviewCycles
|
|
1794
|
+
maxReviewCycles,
|
|
1779
1795
|
pipelineRunId: runId,
|
|
1780
1796
|
enableHeartbeat: eventsFlag === true
|
|
1781
1797
|
},
|
|
@@ -1955,7 +1971,7 @@ async function runFullPipelineFromPhase(options) {
|
|
|
1955
1971
|
}
|
|
1956
1972
|
}
|
|
1957
1973
|
function registerResumeCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
|
|
1958
|
-
program.command("resume").description("Resume a previously interrupted pipeline run").option("--run-id <id>", "Pipeline run ID to resume (defaults to latest)").option("--pack <name>", "Methodology pack name", "bmad").option("--stop-after <phase>", "Stop pipeline after this phase completes (overrides saved state)").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").action(async (opts) => {
|
|
1974
|
+
program.command("resume").description("Resume a previously interrupted pipeline run").option("--run-id <id>", "Pipeline run ID to resume (defaults to latest)").option("--pack <name>", "Methodology pack name", "bmad").option("--stop-after <phase>", "Stop pipeline after this phase completes (overrides saved state)").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("--max-review-cycles <n>", "Maximum review cycles per story (default: 2)", (v) => parseInt(v, 10), 2).action(async (opts) => {
|
|
1959
1975
|
const outputFormat = opts.outputFormat === "json" ? "json" : "human";
|
|
1960
1976
|
const exitCode = await runResumeAction({
|
|
1961
1977
|
runId: opts.runId,
|
|
@@ -1965,6 +1981,7 @@ function registerResumeCommand(program, _version = "0.0.0", projectRoot = proces
|
|
|
1965
1981
|
concurrency: opts.concurrency,
|
|
1966
1982
|
pack: opts.pack,
|
|
1967
1983
|
events: opts.events,
|
|
1984
|
+
maxReviewCycles: opts.maxReviewCycles,
|
|
1968
1985
|
registry
|
|
1969
1986
|
});
|
|
1970
1987
|
process.exitCode = exitCode;
|
|
@@ -2058,7 +2075,7 @@ async function runStatusAction(options) {
|
|
|
2058
2075
|
if (runId !== void 0 && runId !== "") run = await getPipelineRunById(adapter, runId);
|
|
2059
2076
|
else run = await getLatestRun(adapter);
|
|
2060
2077
|
if (run === void 0) {
|
|
2061
|
-
const errorMsg = runId !== void 0 ? `Pipeline run '${runId}' not found.` : "No pipeline runs found. Run `substrate run` first.";
|
|
2078
|
+
const errorMsg = runId !== void 0 ? `Pipeline run '${runId}' not found.` : "No pipeline runs found. Run `substrate run --events` to start a pipeline first.";
|
|
2062
2079
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
|
|
2063
2080
|
else process.stderr.write(`Error: ${errorMsg}\n`);
|
|
2064
2081
|
return 1;
|
|
@@ -3469,7 +3486,7 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
3469
3486
|
await initSchema(expAdapter);
|
|
3470
3487
|
const { runRunAction: runPipeline } = await import(
|
|
3471
3488
|
/* @vite-ignore */
|
|
3472
|
-
"../run-
|
|
3489
|
+
"../run-B5-a932y.js"
|
|
3473
3490
|
);
|
|
3474
3491
|
const runStoryFn = async (opts) => {
|
|
3475
3492
|
const exitCode = await runPipeline({
|
|
@@ -3985,7 +4002,7 @@ async function runMetricsAction(options) {
|
|
|
3985
4002
|
const routingConfigPath = join(dbDir, "routing.yml");
|
|
3986
4003
|
let routingConfig = null;
|
|
3987
4004
|
if (existsSync(routingConfigPath)) try {
|
|
3988
|
-
const { loadModelRoutingConfig } = await import("../routing-
|
|
4005
|
+
const { loadModelRoutingConfig } = await import("../routing-CpsRPjLE.js");
|
|
3989
4006
|
routingConfig = loadModelRoutingConfig(routingConfigPath);
|
|
3990
4007
|
} catch {}
|
|
3991
4008
|
if (routingConfig === null) routingConfig = {
|
package/dist/index.d.ts
CHANGED
|
@@ -514,6 +514,23 @@ interface SupervisorExperimentErrorEvent {
|
|
|
514
514
|
/** Error message describing why the experiment failed */
|
|
515
515
|
error: string;
|
|
516
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* Emitted after all stories complete when the `.substrate/project-profile.yaml`
|
|
519
|
+
* may be outdated relative to the actual project structure (e.g., profile says
|
|
520
|
+
* `type: single` but a `turbo.json` now exists, or new language markers appeared).
|
|
521
|
+
*
|
|
522
|
+
* Non-blocking warning — the pipeline has already finished. The user should
|
|
523
|
+
* re-run `substrate init --force` to regenerate the profile.
|
|
524
|
+
*/
|
|
525
|
+
interface PipelineProfileStaleEvent {
|
|
526
|
+
type: 'pipeline:profile-stale';
|
|
527
|
+
/** ISO-8601 timestamp generated at emit time */
|
|
528
|
+
ts: string;
|
|
529
|
+
/** Human-readable message describing the staleness indicators found */
|
|
530
|
+
message: string;
|
|
531
|
+
/** List of staleness indicators detected (e.g., "turbo.json exists but profile says type: single") */
|
|
532
|
+
indicators: string[];
|
|
533
|
+
}
|
|
517
534
|
/**
|
|
518
535
|
* Emitted when post-sprint contract verification finds a mismatch between
|
|
519
536
|
* declared export/import contracts (Story 25-6).
|
|
@@ -581,7 +598,7 @@ interface RoutingModelSelectedEvent {
|
|
|
581
598
|
* }
|
|
582
599
|
* ```
|
|
583
600
|
*/
|
|
584
|
-
type PipelineEvent = PipelineStartEvent | PipelineCompleteEvent | PipelinePreFlightFailureEvent | PipelineContractMismatchEvent | PipelineContractVerificationSummaryEvent | StoryPhaseEvent | StoryDoneEvent | StoryEscalationEvent | StoryWarnEvent | StoryLogEvent | PipelineHeartbeatEvent | StoryStallEvent | StoryZeroDiffEscalationEvent | StoryBuildVerificationFailedEvent | StoryBuildVerificationPassedEvent | StoryInterfaceChangeWarningEvent | StoryMetricsEvent | SupervisorPollEvent | SupervisorKillEvent | SupervisorRestartEvent | SupervisorAbortEvent | SupervisorSummaryEvent | SupervisorAnalysisCompleteEvent | SupervisorAnalysisErrorEvent | SupervisorExperimentStartEvent | SupervisorExperimentSkipEvent | SupervisorExperimentRecommendationsEvent | SupervisorExperimentCompleteEvent | SupervisorExperimentErrorEvent | RoutingModelSelectedEvent; //#endregion
|
|
601
|
+
type PipelineEvent = PipelineStartEvent | PipelineCompleteEvent | PipelinePreFlightFailureEvent | PipelineProfileStaleEvent | PipelineContractMismatchEvent | PipelineContractVerificationSummaryEvent | StoryPhaseEvent | StoryDoneEvent | StoryEscalationEvent | StoryWarnEvent | StoryLogEvent | PipelineHeartbeatEvent | StoryStallEvent | StoryZeroDiffEscalationEvent | StoryBuildVerificationFailedEvent | StoryBuildVerificationPassedEvent | StoryInterfaceChangeWarningEvent | StoryMetricsEvent | SupervisorPollEvent | SupervisorKillEvent | SupervisorRestartEvent | SupervisorAbortEvent | SupervisorSummaryEvent | SupervisorAnalysisCompleteEvent | SupervisorAnalysisErrorEvent | SupervisorExperimentStartEvent | SupervisorExperimentSkipEvent | SupervisorExperimentRecommendationsEvent | SupervisorExperimentCompleteEvent | SupervisorExperimentErrorEvent | RoutingModelSelectedEvent; //#endregion
|
|
585
602
|
//#region src/core/errors.d.ts
|
|
586
603
|
|
|
587
604
|
/**
|
|
@@ -1264,6 +1281,13 @@ interface OrchestratorEvents {
|
|
|
1264
1281
|
/** Build output (stdout+stderr), truncated to 2000 chars */
|
|
1265
1282
|
output: string;
|
|
1266
1283
|
};
|
|
1284
|
+
/** Project profile may be outdated relative to the actual project structure */
|
|
1285
|
+
'pipeline:profile-stale': {
|
|
1286
|
+
/** Human-readable message describing the staleness indicators found */
|
|
1287
|
+
message: string;
|
|
1288
|
+
/** List of staleness indicators detected */
|
|
1289
|
+
indicators: string[];
|
|
1290
|
+
};
|
|
1267
1291
|
/** Contract verification found a mismatch between declared export/import contracts */
|
|
1268
1292
|
'pipeline:contract-mismatch': {
|
|
1269
1293
|
/** Story key that declared the export for this contract */
|
|
@@ -263,7 +263,7 @@ var RoutingResolver = class RoutingResolver {
|
|
|
263
263
|
return new RoutingResolver(config, logger$1);
|
|
264
264
|
} catch (err) {
|
|
265
265
|
if (err instanceof RoutingConfigError && err.code === "CONFIG_NOT_FOUND") {
|
|
266
|
-
logger$1.
|
|
266
|
+
logger$1.debug({
|
|
267
267
|
configPath: filePath,
|
|
268
268
|
component: "routing",
|
|
269
269
|
reason: "config not found"
|
|
@@ -829,4 +829,4 @@ var RoutingTuner = class {
|
|
|
829
829
|
|
|
830
830
|
//#endregion
|
|
831
831
|
export { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig };
|
|
832
|
-
//# sourceMappingURL=routing-
|
|
832
|
+
//# sourceMappingURL=routing-CobBiKeV.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import "./logger-D2fS2ccL.js";
|
|
2
|
-
import { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig } from "./routing-
|
|
2
|
+
import { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig } from "./routing-CobBiKeV.js";
|
|
3
3
|
|
|
4
4
|
export { loadModelRoutingConfig };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { registerRunCommand, runRunAction } from "./run-
|
|
1
|
+
import { registerRunCommand, runRunAction } from "./run-GwwiCYXX.js";
|
|
2
2
|
import "./logger-D2fS2ccL.js";
|
|
3
3
|
import "./config-migrator-DtZW1maj.js";
|
|
4
4
|
import "./helpers-BihqWgVe.js";
|
|
5
|
-
import "./routing-
|
|
5
|
+
import "./routing-CobBiKeV.js";
|
|
6
6
|
import "./decisions-C6MF2Cax.js";
|
|
7
7
|
import "./operational-BRpT8MYF.js";
|
|
8
8
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createLogger, deepMask } from "./logger-D2fS2ccL.js";
|
|
2
2
|
import { CURRENT_CONFIG_FORMAT_VERSION, PartialSubstrateConfigSchema, SUPPORTED_CONFIG_FORMAT_VERSIONS, SubstrateConfigSchema, defaultConfigMigrator } from "./config-migrator-DtZW1maj.js";
|
|
3
3
|
import { ConfigError, ConfigIncompatibleFormatError, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-BihqWgVe.js";
|
|
4
|
-
import { RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, loadModelRoutingConfig } from "./routing-
|
|
4
|
+
import { RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, loadModelRoutingConfig } from "./routing-CobBiKeV.js";
|
|
5
5
|
import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./decisions-C6MF2Cax.js";
|
|
6
6
|
import { ADVISORY_NOTES, ESCALATION_DIAGNOSIS, OPERATIONAL_FINDING, STORY_METRICS, STORY_OUTCOME, TEST_EXPANSION_FINDING, TEST_PLAN, aggregateTokenUsageForRun, aggregateTokenUsageForStory, getStoryMetricsForRun, writeRunMetrics, writeStoryMetrics } from "./operational-BRpT8MYF.js";
|
|
7
7
|
import { createRequire } from "module";
|
|
@@ -325,7 +325,8 @@ var InMemoryDatabaseAdapter = class {
|
|
|
325
325
|
if (!m$1) return [];
|
|
326
326
|
return [this._evalSelectExprs(m$1[1].trim())];
|
|
327
327
|
}
|
|
328
|
-
const
|
|
328
|
+
const stripped = sql.replace(/\s+ORDER\s+BY\s+.+?(?=\s+LIMIT\s|\s*$)/is, "").replace(/\s+LIMIT\s+\d+\s*$/is, "");
|
|
329
|
+
const m = /SELECT\s+(.+?)\s+FROM\s+(\w+)(?:\s+WHERE\s+(.+))?$/is.exec(stripped);
|
|
329
330
|
if (!m) return [];
|
|
330
331
|
const colsStr = m[1].trim();
|
|
331
332
|
const tableName = m[2];
|
|
@@ -334,6 +335,7 @@ var InMemoryDatabaseAdapter = class {
|
|
|
334
335
|
let rows = table.map((r) => ({ ...r }));
|
|
335
336
|
if (whereStr) rows = rows.filter((row) => this._matchWhere(whereStr.trim(), row));
|
|
336
337
|
if (colsStr === "*") return rows;
|
|
338
|
+
if (/\b(?:SUM|COALESCE|COUNT|AVG|MIN|MAX)\s*\(/i.test(colsStr)) return [this._evalAggregate(colsStr, rows)];
|
|
337
339
|
return rows.map((row) => this._projectCols(colsStr, row));
|
|
338
340
|
}
|
|
339
341
|
_update(sql) {
|
|
@@ -393,12 +395,22 @@ var InMemoryDatabaseAdapter = class {
|
|
|
393
395
|
if (row[notNullM[1]] === null || row[notNullM[1]] === void 0) return false;
|
|
394
396
|
continue;
|
|
395
397
|
}
|
|
398
|
+
const likeM = /^(\w+)\s+LIKE\s+'(.*)'$/is.exec(trimmed);
|
|
399
|
+
if (likeM) {
|
|
400
|
+
const colVal = row[likeM[1]];
|
|
401
|
+
if (colVal === null || colVal === void 0) return false;
|
|
402
|
+
const pattern = likeM[2].replace(/''/g, "'");
|
|
403
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, (ch) => ch === "%" || ch === "_" ? ch : "\\" + ch);
|
|
404
|
+
const regex = new RegExp("^" + escaped.replace(/%/g, ".*").replace(/_/g, ".") + "$", "s");
|
|
405
|
+
if (!regex.test(String(colVal))) return false;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
396
408
|
}
|
|
397
409
|
return true;
|
|
398
410
|
}
|
|
399
411
|
_projectCols(colsStr, row) {
|
|
400
412
|
const result = {};
|
|
401
|
-
const cols =
|
|
413
|
+
const cols = this._splitTopLevelCommas(colsStr);
|
|
402
414
|
for (const col of cols) {
|
|
403
415
|
const aliasM = /^(.+?)\s+AS\s+(\w+)$/i.exec(col);
|
|
404
416
|
if (aliasM) result[aliasM[2]] = this._evalExprAgainstRow(aliasM[1].trim(), row);
|
|
@@ -408,7 +420,7 @@ var InMemoryDatabaseAdapter = class {
|
|
|
408
420
|
}
|
|
409
421
|
_evalSelectExprs(exprs) {
|
|
410
422
|
const result = {};
|
|
411
|
-
const parts =
|
|
423
|
+
const parts = this._splitTopLevelCommas(exprs);
|
|
412
424
|
for (const part of parts) {
|
|
413
425
|
const aliasM = /^(.+?)\s+AS\s+(\w+)$/i.exec(part);
|
|
414
426
|
if (aliasM) result[aliasM[2]] = this._evalLiteral(aliasM[1].trim());
|
|
@@ -431,6 +443,87 @@ var InMemoryDatabaseAdapter = class {
|
|
|
431
443
|
return literal;
|
|
432
444
|
}
|
|
433
445
|
/**
|
|
446
|
+
* Split a string by commas that are NOT inside parentheses.
|
|
447
|
+
* E.g. "COALESCE(SUM(x), 0) as a, y" → ["COALESCE(SUM(x), 0) as a", "y"]
|
|
448
|
+
*/
|
|
449
|
+
_splitTopLevelCommas(str) {
|
|
450
|
+
const parts = [];
|
|
451
|
+
let current = "";
|
|
452
|
+
let depth = 0;
|
|
453
|
+
let inStr = false;
|
|
454
|
+
for (let i = 0; i < str.length; i++) {
|
|
455
|
+
const ch = str[i];
|
|
456
|
+
if (ch === "'" && !inStr) {
|
|
457
|
+
inStr = true;
|
|
458
|
+
current += ch;
|
|
459
|
+
} else if (ch === "'" && inStr) if (str[i + 1] === "'") {
|
|
460
|
+
current += "''";
|
|
461
|
+
i++;
|
|
462
|
+
} else {
|
|
463
|
+
inStr = false;
|
|
464
|
+
current += ch;
|
|
465
|
+
}
|
|
466
|
+
else if (!inStr && ch === "(") {
|
|
467
|
+
depth++;
|
|
468
|
+
current += ch;
|
|
469
|
+
} else if (!inStr && ch === ")") {
|
|
470
|
+
depth--;
|
|
471
|
+
current += ch;
|
|
472
|
+
} else if (!inStr && ch === "," && depth === 0) {
|
|
473
|
+
parts.push(current.trim());
|
|
474
|
+
current = "";
|
|
475
|
+
} else current += ch;
|
|
476
|
+
}
|
|
477
|
+
if (current.trim() !== "") parts.push(current.trim());
|
|
478
|
+
return parts;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Evaluate aggregate SELECT expressions (SUM, COALESCE, COUNT) across
|
|
482
|
+
* a set of filtered rows, returning a single result row.
|
|
483
|
+
*/
|
|
484
|
+
_evalAggregate(colsStr, rows) {
|
|
485
|
+
const result = {};
|
|
486
|
+
const cols = this._splitTopLevelCommas(colsStr);
|
|
487
|
+
for (const col of cols) {
|
|
488
|
+
const aliasM = /^(.+?)\s+AS\s+(\w+)$/i.exec(col);
|
|
489
|
+
const expr = aliasM ? aliasM[1].trim() : col.trim();
|
|
490
|
+
const alias = aliasM ? aliasM[2] : col.trim();
|
|
491
|
+
result[alias] = this._evalAggregateExpr(expr, rows);
|
|
492
|
+
}
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Evaluate a single aggregate expression against a set of rows.
|
|
497
|
+
* Supports: SUM(col), COALESCE(expr, default), COUNT(*).
|
|
498
|
+
*/
|
|
499
|
+
_evalAggregateExpr(expr, rows) {
|
|
500
|
+
const trimmed = expr.trim();
|
|
501
|
+
const coalesceM = /^COALESCE\((.+)\)$/i.exec(trimmed);
|
|
502
|
+
if (coalesceM) {
|
|
503
|
+
const args = this._splitTopLevelCommas(coalesceM[1]);
|
|
504
|
+
for (const arg of args) {
|
|
505
|
+
const val = this._evalAggregateExpr(arg.trim(), rows);
|
|
506
|
+
if (val !== null && val !== void 0) return val;
|
|
507
|
+
}
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
const sumM = /^SUM\((\w+)\)$/i.exec(trimmed);
|
|
511
|
+
if (sumM) {
|
|
512
|
+
const col = sumM[1];
|
|
513
|
+
if (rows.length === 0) return null;
|
|
514
|
+
let total = 0;
|
|
515
|
+
for (const row of rows) total += Number(row[col] ?? 0);
|
|
516
|
+
return total;
|
|
517
|
+
}
|
|
518
|
+
if (/^COUNT\(\*\)$/i.test(trimmed)) return rows.length;
|
|
519
|
+
const countM = /^COUNT\((\w+)\)$/i.exec(trimmed);
|
|
520
|
+
if (countM) {
|
|
521
|
+
const col = countM[1];
|
|
522
|
+
return rows.filter((r) => r[col] !== null && r[col] !== void 0).length;
|
|
523
|
+
}
|
|
524
|
+
return this._evalLiteral(trimmed);
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
434
527
|
* Parse a comma-separated list of SQL literal values.
|
|
435
528
|
* Handles: NULL, numbers, single-quoted strings.
|
|
436
529
|
* Simple split by comma (assumes no commas inside string values).
|
|
@@ -1829,8 +1922,8 @@ function resolveBmadMethodVersion(fromDir = __dirname) {
|
|
|
1829
1922
|
const BMAD_BASELINE_TOKENS_FULL = 56800;
|
|
1830
1923
|
/** BMAD baseline token total for create+dev+review comparison */
|
|
1831
1924
|
const BMAD_BASELINE_TOKENS = 23800;
|
|
1832
|
-
/** Story key pattern: <epic>-<story> e.g. "10-1" */
|
|
1833
|
-
const STORY_KEY_PATTERN$1 =
|
|
1925
|
+
/** Story key pattern: <epic>-<story> e.g. "10-1", "1-1a", "NEW-26" */
|
|
1926
|
+
const STORY_KEY_PATTERN$1 = /^[A-Za-z0-9]+-[A-Za-z0-9]+$/;
|
|
1834
1927
|
/**
|
|
1835
1928
|
* Top-level keys in .claude/settings.json that substrate owns.
|
|
1836
1929
|
* On init, these are set/updated unconditionally.
|
|
@@ -3441,6 +3534,28 @@ const PIPELINE_EVENT_METADATA = [
|
|
|
3441
3534
|
description: "Overall verification result."
|
|
3442
3535
|
}
|
|
3443
3536
|
]
|
|
3537
|
+
},
|
|
3538
|
+
{
|
|
3539
|
+
type: "pipeline:profile-stale",
|
|
3540
|
+
description: "Project profile may be outdated. Non-blocking warning — run `substrate init --force` to re-detect.",
|
|
3541
|
+
when: "After all stories complete, before pipeline:complete. Emitted when staleness indicators are found.",
|
|
3542
|
+
fields: [
|
|
3543
|
+
{
|
|
3544
|
+
name: "ts",
|
|
3545
|
+
type: "string",
|
|
3546
|
+
description: "Timestamp."
|
|
3547
|
+
},
|
|
3548
|
+
{
|
|
3549
|
+
name: "message",
|
|
3550
|
+
type: "string",
|
|
3551
|
+
description: "Human-readable staleness warning message."
|
|
3552
|
+
},
|
|
3553
|
+
{
|
|
3554
|
+
name: "indicators",
|
|
3555
|
+
type: "string[]",
|
|
3556
|
+
description: "List of staleness indicators (e.g., \"turbo.json exists but profile says type: single\")."
|
|
3557
|
+
}
|
|
3558
|
+
]
|
|
3444
3559
|
}
|
|
3445
3560
|
];
|
|
3446
3561
|
/**
|
|
@@ -6338,7 +6453,8 @@ var DispatcherShuttingDownError = class extends Error {
|
|
|
6338
6453
|
const YAML_ANCHOR_KEYS = [
|
|
6339
6454
|
"result:",
|
|
6340
6455
|
"verdict:",
|
|
6341
|
-
"story_file:"
|
|
6456
|
+
"story_file:",
|
|
6457
|
+
"expansion_priority:"
|
|
6342
6458
|
];
|
|
6343
6459
|
/**
|
|
6344
6460
|
* Extract the YAML result block from sub-agent output.
|
|
@@ -7086,7 +7202,7 @@ function detectPackageManager(projectRoot) {
|
|
|
7086
7202
|
if (existsSync$1(join$1(projectRoot, "turbo.json"))) return {
|
|
7087
7203
|
packageManager: "none",
|
|
7088
7204
|
lockfile: "turbo.json",
|
|
7089
|
-
command: "turbo build"
|
|
7205
|
+
command: "npx turbo build"
|
|
7090
7206
|
};
|
|
7091
7207
|
const nodeCandidates = [
|
|
7092
7208
|
{
|
|
@@ -7188,6 +7304,16 @@ function runBuildVerification(options) {
|
|
|
7188
7304
|
output: combinedOutput,
|
|
7189
7305
|
reason: "build-verification-timeout"
|
|
7190
7306
|
};
|
|
7307
|
+
const missingScriptPattern = /Missing script[:\s]|No script found|Command "build" not found/i;
|
|
7308
|
+
if (missingScriptPattern.test(combinedOutput)) {
|
|
7309
|
+
logger$23.warn("Build script not found — skipping pre-flight (greenfield repo)");
|
|
7310
|
+
return {
|
|
7311
|
+
status: "skipped",
|
|
7312
|
+
exitCode,
|
|
7313
|
+
output: combinedOutput,
|
|
7314
|
+
reason: "build-script-not-found"
|
|
7315
|
+
};
|
|
7316
|
+
}
|
|
7191
7317
|
return {
|
|
7192
7318
|
status: "failed",
|
|
7193
7319
|
exitCode,
|
|
@@ -7665,12 +7791,12 @@ var FileStateStore = class {
|
|
|
7665
7791
|
//#region src/modules/state/dolt-store.ts
|
|
7666
7792
|
const log = createLogger("modules:state:dolt");
|
|
7667
7793
|
/**
|
|
7668
|
-
* Validate that a story key matches the expected pattern (e.g. "26-7").
|
|
7794
|
+
* Validate that a story key matches the expected pattern (e.g. "26-7", "1-1a", "NEW-26").
|
|
7669
7795
|
* Prevents SQL injection via string-interpolated identifiers.
|
|
7670
7796
|
*/
|
|
7671
|
-
const STORY_KEY_PATTERN = /^[
|
|
7797
|
+
const STORY_KEY_PATTERN = /^[A-Za-z0-9]+-[A-Za-z0-9]+$/;
|
|
7672
7798
|
function assertValidStoryKey(storyKey) {
|
|
7673
|
-
if (!STORY_KEY_PATTERN.test(storyKey)) throw new DoltQueryError("assertValidStoryKey", `Invalid story key: '${storyKey}'. Must match pattern <
|
|
7799
|
+
if (!STORY_KEY_PATTERN.test(storyKey)) throw new DoltQueryError("assertValidStoryKey", `Invalid story key: '${storyKey}'. Must match pattern <segment>-<segment> (e.g. "10-1", "1-1a", "NEW-26").`);
|
|
7674
7800
|
}
|
|
7675
7801
|
/**
|
|
7676
7802
|
* Dolt-backed implementation of the StateStore interface.
|
|
@@ -9095,9 +9221,17 @@ function readEpicShardFromFile(projectRoot, epicId) {
|
|
|
9095
9221
|
if (!epicsPath) return "";
|
|
9096
9222
|
const content = readFileSync$1(epicsPath, "utf-8");
|
|
9097
9223
|
const epicNum = epicId.replace(/^epic-/i, "");
|
|
9098
|
-
const
|
|
9099
|
-
const
|
|
9100
|
-
|
|
9224
|
+
const headingPattern = new RegExp(`^(#{2,4})\\s+(?:Epic\\s+)?${epicNum}[.:\\s]`, "m");
|
|
9225
|
+
const headingMatch = headingPattern.exec(content);
|
|
9226
|
+
if (!headingMatch) return "";
|
|
9227
|
+
const startIdx = headingMatch.index;
|
|
9228
|
+
const headingLevel = headingMatch[1].length;
|
|
9229
|
+
const hashes = "#".repeat(headingLevel);
|
|
9230
|
+
const endPattern = new RegExp(`\\n${hashes}\\s`, "g");
|
|
9231
|
+
endPattern.lastIndex = startIdx + headingMatch[0].length;
|
|
9232
|
+
const endMatch = endPattern.exec(content);
|
|
9233
|
+
const endIdx = endMatch ? endMatch.index : content.length;
|
|
9234
|
+
return content.slice(startIdx, endIdx).trim();
|
|
9101
9235
|
} catch (err) {
|
|
9102
9236
|
logger$20.warn({
|
|
9103
9237
|
epicId,
|
|
@@ -9356,9 +9490,9 @@ async function getProjectFindings(db) {
|
|
|
9356
9490
|
sections.push("**Prior escalations:**");
|
|
9357
9491
|
for (const d of diagnoses.slice(-3)) try {
|
|
9358
9492
|
const val = JSON.parse(d.value);
|
|
9359
|
-
sections.push(`- ${d.key.split(":")[0]}: ${val.recommendedAction} — ${val.rationale}`);
|
|
9493
|
+
sections.push(`- ${(d.key ?? "").split(":")[0]}: ${val.recommendedAction} — ${val.rationale}`);
|
|
9360
9494
|
} catch {
|
|
9361
|
-
sections.push(`- ${d.key}: escalated`);
|
|
9495
|
+
sections.push(`- ${d.key ?? "unknown"}: escalated`);
|
|
9362
9496
|
}
|
|
9363
9497
|
}
|
|
9364
9498
|
const highCycleStories = metrics.filter((m) => {
|
|
@@ -9373,16 +9507,16 @@ async function getProjectFindings(db) {
|
|
|
9373
9507
|
sections.push("**Stories with high review cycles:**");
|
|
9374
9508
|
for (const m of highCycleStories) try {
|
|
9375
9509
|
const val = JSON.parse(m.value);
|
|
9376
|
-
sections.push(`- ${m.key.split(":")[0]}: ${val.review_cycles} cycles`);
|
|
9510
|
+
sections.push(`- ${(m.key ?? "").split(":")[0]}: ${val.review_cycles} cycles`);
|
|
9377
9511
|
} catch {}
|
|
9378
9512
|
}
|
|
9379
|
-
const stalls = operational.filter((o) => o.key
|
|
9513
|
+
const stalls = operational.filter((o) => o.key?.startsWith("stall:"));
|
|
9380
9514
|
if (stalls.length > 0) sections.push(`**Prior stalls:** ${stalls.length} stall event(s) recorded`);
|
|
9381
9515
|
if (advisoryNotes.length > 0) {
|
|
9382
9516
|
sections.push("**Advisory notes from prior reviews (LGTM_WITH_NOTES):**");
|
|
9383
9517
|
for (const n of advisoryNotes.slice(-3)) try {
|
|
9384
9518
|
const val = JSON.parse(n.value);
|
|
9385
|
-
const storyId = n.key.split(":")[0];
|
|
9519
|
+
const storyId = (n.key ?? "").split(":")[0];
|
|
9386
9520
|
if (typeof val.notes === "string" && val.notes.length > 0) sections.push(`- ${storyId}: ${val.notes}`);
|
|
9387
9521
|
} catch {
|
|
9388
9522
|
sections.push(`- ${n.key}: advisory notes available`);
|
|
@@ -12377,7 +12511,7 @@ function detectInterfaceChanges(options) {
|
|
|
12377
12511
|
for (const name of allNames) {
|
|
12378
12512
|
let grepOutput = "";
|
|
12379
12513
|
try {
|
|
12380
|
-
grepOutput = execSync(`grep -r --include="*.test.ts" --include="*.spec.ts" -l "${name}" .`, {
|
|
12514
|
+
grepOutput = execSync(`grep -r --include="*.test.ts" --include="*.spec.ts" --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=dist --exclude-dir=.next --exclude-dir=coverage -l "${name}" .`, {
|
|
12381
12515
|
cwd: projectRoot,
|
|
12382
12516
|
encoding: "utf-8",
|
|
12383
12517
|
timeout: 1e4,
|
|
@@ -15870,6 +16004,9 @@ function createTelemetryAdvisor(deps) {
|
|
|
15870
16004
|
|
|
15871
16005
|
//#endregion
|
|
15872
16006
|
//#region src/modules/implementation-orchestrator/orchestrator-impl.ts
|
|
16007
|
+
function estimateDispatchCost(input, output) {
|
|
16008
|
+
return (input * 3 + output * 15) / 1e6;
|
|
16009
|
+
}
|
|
15873
16010
|
function createPauseGate() {
|
|
15874
16011
|
let resolve$2;
|
|
15875
16012
|
const promise = new Promise((res) => {
|
|
@@ -15902,6 +16039,69 @@ function buildTargetedFilesContent(issueList) {
|
|
|
15902
16039
|
return lines.join("\n");
|
|
15903
16040
|
}
|
|
15904
16041
|
/**
|
|
16042
|
+
* Normalize a title string into a set of meaningful words for comparison.
|
|
16043
|
+
* Strips punctuation, lowercases, and filters out very short words (<=2 chars)
|
|
16044
|
+
* and common stop words to focus on content-bearing terms.
|
|
16045
|
+
*/
|
|
16046
|
+
function titleToWordSet(title) {
|
|
16047
|
+
const stopWords = new Set([
|
|
16048
|
+
"the",
|
|
16049
|
+
"and",
|
|
16050
|
+
"for",
|
|
16051
|
+
"with",
|
|
16052
|
+
"from",
|
|
16053
|
+
"into",
|
|
16054
|
+
"that",
|
|
16055
|
+
"this",
|
|
16056
|
+
"via"
|
|
16057
|
+
]);
|
|
16058
|
+
return new Set(title.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/[\s-]+/).filter((w) => w.length > 2 && !stopWords.has(w)));
|
|
16059
|
+
}
|
|
16060
|
+
/**
|
|
16061
|
+
* Compute the word overlap ratio between two titles.
|
|
16062
|
+
* Returns a value between 0 and 1, where 1 means all words in the smaller set
|
|
16063
|
+
* are present in the larger set.
|
|
16064
|
+
*
|
|
16065
|
+
* Uses the smaller set as the denominator so that a generated title that is a
|
|
16066
|
+
* reasonable subset or superset of the expected title still scores well.
|
|
16067
|
+
*/
|
|
16068
|
+
function computeTitleOverlap(titleA, titleB) {
|
|
16069
|
+
const wordsA = titleToWordSet(titleA);
|
|
16070
|
+
const wordsB = titleToWordSet(titleB);
|
|
16071
|
+
if (wordsA.size === 0 || wordsB.size === 0) return 0;
|
|
16072
|
+
let shared = 0;
|
|
16073
|
+
for (const w of wordsA) if (wordsB.has(w)) shared++;
|
|
16074
|
+
const denominator = Math.min(wordsA.size, wordsB.size);
|
|
16075
|
+
return shared / denominator;
|
|
16076
|
+
}
|
|
16077
|
+
/**
|
|
16078
|
+
* Extract the expected story title from the epic shard content.
|
|
16079
|
+
*
|
|
16080
|
+
* Looks for patterns like:
|
|
16081
|
+
* - "### Story 37-1: Turborepo monorepo scaffold"
|
|
16082
|
+
* - "Story 37-1: Turborepo monorepo scaffold"
|
|
16083
|
+
* - "**37-1**: Turborepo monorepo scaffold"
|
|
16084
|
+
* - "37-1: Turborepo monorepo scaffold"
|
|
16085
|
+
*
|
|
16086
|
+
* Returns the title portion after the story key, or null if no match.
|
|
16087
|
+
*/
|
|
16088
|
+
function extractExpectedStoryTitle(shardContent, storyKey) {
|
|
16089
|
+
if (!shardContent || !storyKey) return null;
|
|
16090
|
+
const escaped = storyKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16091
|
+
const patterns = [
|
|
16092
|
+
new RegExp(`^#{2,4}\\s+Story\\s+${escaped}[:\\s]+\\s*(.+)$`, "mi"),
|
|
16093
|
+
new RegExp(`^Story\\s+${escaped}[:\\s]+\\s*(.+)$`, "mi"),
|
|
16094
|
+
new RegExp(`^\\*\\*${escaped}\\*\\*[:\\s]+\\s*(.+)$`, "mi"),
|
|
16095
|
+
new RegExp(`^${escaped}[:\\s]+\\s*(.+)$`, "mi")
|
|
16096
|
+
];
|
|
16097
|
+
for (const pattern of patterns) {
|
|
16098
|
+
const match$1 = pattern.exec(shardContent);
|
|
16099
|
+
if (match$1?.[1]) return match$1[1].replace(/\*+$/, "").trim();
|
|
16100
|
+
}
|
|
16101
|
+
return null;
|
|
16102
|
+
}
|
|
16103
|
+
const TITLE_OVERLAP_WARNING_THRESHOLD = .3;
|
|
16104
|
+
/**
|
|
15905
16105
|
* Map a StoryPhase to the corresponding WgStoryStatus for wg_stories writes.
|
|
15906
16106
|
* Returns null for PENDING (no write needed).
|
|
15907
16107
|
*/
|
|
@@ -15918,6 +16118,55 @@ function wgStatusForPhase(phase) {
|
|
|
15918
16118
|
}
|
|
15919
16119
|
}
|
|
15920
16120
|
/**
|
|
16121
|
+
* Check whether `.substrate/project-profile.yaml` is stale relative to
|
|
16122
|
+
* the actual project structure.
|
|
16123
|
+
*
|
|
16124
|
+
* Returns an array of human-readable indicator strings. An empty array
|
|
16125
|
+
* means the profile appears current (or doesn't exist).
|
|
16126
|
+
*
|
|
16127
|
+
* Staleness indicators checked:
|
|
16128
|
+
* - Profile says `type: single` but `turbo.json` exists (should be monorepo)
|
|
16129
|
+
* - Profile has no Go language but `go.mod` exists
|
|
16130
|
+
* - Profile has no Python language but `pyproject.toml` exists
|
|
16131
|
+
* - Profile has no Rust language but `Cargo.toml` exists
|
|
16132
|
+
*/
|
|
16133
|
+
function checkProfileStaleness(projectRoot) {
|
|
16134
|
+
const profilePath = join$1(projectRoot, ".substrate", "project-profile.yaml");
|
|
16135
|
+
if (!existsSync$1(profilePath)) return [];
|
|
16136
|
+
let profile;
|
|
16137
|
+
try {
|
|
16138
|
+
const raw = readFileSync$1(profilePath, "utf-8");
|
|
16139
|
+
profile = yaml.load(raw) ?? {};
|
|
16140
|
+
} catch {
|
|
16141
|
+
return [];
|
|
16142
|
+
}
|
|
16143
|
+
const project = profile.project;
|
|
16144
|
+
if (project === void 0) return [];
|
|
16145
|
+
const indicators = [];
|
|
16146
|
+
const declaredLanguages = new Set();
|
|
16147
|
+
if (typeof project.language === "string") declaredLanguages.add(project.language);
|
|
16148
|
+
if (Array.isArray(project.packages)) {
|
|
16149
|
+
for (const pkg of project.packages) if (typeof pkg.language === "string") declaredLanguages.add(pkg.language);
|
|
16150
|
+
}
|
|
16151
|
+
if (project.type === "single" && existsSync$1(join$1(projectRoot, "turbo.json"))) indicators.push("turbo.json exists but profile says type: single (should be monorepo)");
|
|
16152
|
+
const languageMarkers = [
|
|
16153
|
+
{
|
|
16154
|
+
file: "go.mod",
|
|
16155
|
+
language: "go"
|
|
16156
|
+
},
|
|
16157
|
+
{
|
|
16158
|
+
file: "pyproject.toml",
|
|
16159
|
+
language: "python"
|
|
16160
|
+
},
|
|
16161
|
+
{
|
|
16162
|
+
file: "Cargo.toml",
|
|
16163
|
+
language: "rust"
|
|
16164
|
+
}
|
|
16165
|
+
];
|
|
16166
|
+
for (const marker of languageMarkers) if (existsSync$1(join$1(projectRoot, marker.file)) && !declaredLanguages.has(marker.language)) indicators.push(`${marker.file} exists but profile does not declare ${marker.language}`);
|
|
16167
|
+
return indicators;
|
|
16168
|
+
}
|
|
16169
|
+
/**
|
|
15921
16170
|
* Factory function that creates an ImplementationOrchestrator instance.
|
|
15922
16171
|
*
|
|
15923
16172
|
* @param deps - Injected dependencies (db, pack, contextCompiler, dispatcher,
|
|
@@ -16274,8 +16523,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
16274
16523
|
for (const s of _stories.values()) if (s.phase === "COMPLETE" || s.phase === "ESCALATED") completed++;
|
|
16275
16524
|
else if (s.phase === "PENDING") queued++;
|
|
16276
16525
|
else active++;
|
|
16277
|
-
|
|
16278
|
-
if (timeSinceProgress >= HEARTBEAT_INTERVAL_MS) eventBus.emit("orchestrator:heartbeat", {
|
|
16526
|
+
eventBus.emit("orchestrator:heartbeat", {
|
|
16279
16527
|
runId: config.pipelineRunId ?? "",
|
|
16280
16528
|
activeDispatches: active,
|
|
16281
16529
|
completedDispatches: completed,
|
|
@@ -16474,7 +16722,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
16474
16722
|
agent: "create-story",
|
|
16475
16723
|
input_tokens: createResult.tokenUsage.input,
|
|
16476
16724
|
output_tokens: createResult.tokenUsage.output,
|
|
16477
|
-
cost_usd:
|
|
16725
|
+
cost_usd: estimateDispatchCost(createResult.tokenUsage.input, createResult.tokenUsage.output),
|
|
16478
16726
|
metadata: JSON.stringify({ storyKey })
|
|
16479
16727
|
});
|
|
16480
16728
|
} catch (tokenErr) {
|
|
@@ -16519,6 +16767,46 @@ function createImplementationOrchestrator(deps) {
|
|
|
16519
16767
|
return;
|
|
16520
16768
|
}
|
|
16521
16769
|
storyFilePath = createResult.story_file;
|
|
16770
|
+
if (createResult.story_title) try {
|
|
16771
|
+
const epicId = storyKey.split("-")[0] ?? storyKey;
|
|
16772
|
+
const implDecisions = await getDecisionsByPhase(db, "implementation");
|
|
16773
|
+
let shardContent;
|
|
16774
|
+
const perStoryShard = implDecisions.find((d) => d.category === "epic-shard" && d.key === storyKey);
|
|
16775
|
+
if (perStoryShard?.value) shardContent = perStoryShard.value;
|
|
16776
|
+
else {
|
|
16777
|
+
const epicShard = implDecisions.find((d) => d.category === "epic-shard" && d.key === epicId);
|
|
16778
|
+
if (epicShard?.value) shardContent = extractStorySection(epicShard.value, storyKey) ?? epicShard.value;
|
|
16779
|
+
}
|
|
16780
|
+
if (shardContent) {
|
|
16781
|
+
const expectedTitle = extractExpectedStoryTitle(shardContent, storyKey);
|
|
16782
|
+
if (expectedTitle) {
|
|
16783
|
+
const overlap = computeTitleOverlap(expectedTitle, createResult.story_title);
|
|
16784
|
+
if (overlap < TITLE_OVERLAP_WARNING_THRESHOLD) {
|
|
16785
|
+
const msg = `Story title mismatch: expected "${expectedTitle}" but got "${createResult.story_title}" (word overlap: ${Math.round(overlap * 100)}%). This may indicate the create-story agent received truncated context.`;
|
|
16786
|
+
logger$27.warn({
|
|
16787
|
+
storyKey,
|
|
16788
|
+
expectedTitle,
|
|
16789
|
+
generatedTitle: createResult.story_title,
|
|
16790
|
+
overlap
|
|
16791
|
+
}, msg);
|
|
16792
|
+
eventBus.emit("orchestrator:story-warn", {
|
|
16793
|
+
storyKey,
|
|
16794
|
+
msg
|
|
16795
|
+
});
|
|
16796
|
+
} else logger$27.debug({
|
|
16797
|
+
storyKey,
|
|
16798
|
+
expectedTitle,
|
|
16799
|
+
generatedTitle: createResult.story_title,
|
|
16800
|
+
overlap
|
|
16801
|
+
}, "Story title validation passed");
|
|
16802
|
+
}
|
|
16803
|
+
}
|
|
16804
|
+
} catch (titleValidationErr) {
|
|
16805
|
+
logger$27.debug({
|
|
16806
|
+
storyKey,
|
|
16807
|
+
err: titleValidationErr
|
|
16808
|
+
}, "Story title validation skipped due to error");
|
|
16809
|
+
}
|
|
16522
16810
|
} catch (err) {
|
|
16523
16811
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
16524
16812
|
endPhase(storyKey, "create-story");
|
|
@@ -16612,7 +16900,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
16612
16900
|
agent: "test-plan",
|
|
16613
16901
|
input_tokens: testPlanTokenUsage.input,
|
|
16614
16902
|
output_tokens: testPlanTokenUsage.output,
|
|
16615
|
-
cost_usd:
|
|
16903
|
+
cost_usd: estimateDispatchCost(testPlanTokenUsage.input, testPlanTokenUsage.output),
|
|
16616
16904
|
metadata: JSON.stringify({ storyKey })
|
|
16617
16905
|
});
|
|
16618
16906
|
} catch (tokenErr) {
|
|
@@ -16727,7 +17015,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
16727
17015
|
agent: `batch-${batch.batchIndex}`,
|
|
16728
17016
|
input_tokens: batchResult.tokenUsage.input,
|
|
16729
17017
|
output_tokens: batchResult.tokenUsage.output,
|
|
16730
|
-
cost_usd:
|
|
17018
|
+
cost_usd: estimateDispatchCost(batchResult.tokenUsage.input, batchResult.tokenUsage.output),
|
|
16731
17019
|
metadata: JSON.stringify({
|
|
16732
17020
|
storyKey,
|
|
16733
17021
|
batchIndex: batch.batchIndex,
|
|
@@ -16783,7 +17071,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
16783
17071
|
agent: "dev-story",
|
|
16784
17072
|
input_tokens: devResult.tokenUsage.input,
|
|
16785
17073
|
output_tokens: devResult.tokenUsage.output,
|
|
16786
|
-
cost_usd:
|
|
17074
|
+
cost_usd: estimateDispatchCost(devResult.tokenUsage.input, devResult.tokenUsage.output),
|
|
16787
17075
|
metadata: JSON.stringify({ storyKey })
|
|
16788
17076
|
});
|
|
16789
17077
|
} catch (tokenErr) {
|
|
@@ -17019,7 +17307,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
17019
17307
|
agent: useBatchedReview ? "code-review-batched" : "code-review",
|
|
17020
17308
|
input_tokens: reviewResult.tokenUsage.input,
|
|
17021
17309
|
output_tokens: reviewResult.tokenUsage.output,
|
|
17022
|
-
cost_usd:
|
|
17310
|
+
cost_usd: estimateDispatchCost(reviewResult.tokenUsage.input, reviewResult.tokenUsage.output),
|
|
17023
17311
|
metadata: JSON.stringify({
|
|
17024
17312
|
storyKey,
|
|
17025
17313
|
reviewCycle: reviewCycles
|
|
@@ -17041,6 +17329,28 @@ function createImplementationOrchestrator(deps) {
|
|
|
17041
17329
|
}, "Phantom review detected (0 issues + error) — retrying review once");
|
|
17042
17330
|
continue;
|
|
17043
17331
|
}
|
|
17332
|
+
if (isPhantomReview && timeoutRetried) {
|
|
17333
|
+
logger$27.warn({
|
|
17334
|
+
storyKey,
|
|
17335
|
+
reviewCycles,
|
|
17336
|
+
error: reviewResult.error
|
|
17337
|
+
}, "Consecutive review timeouts detected (original + retry both failed) — escalating immediately");
|
|
17338
|
+
endPhase(storyKey, "code-review");
|
|
17339
|
+
updateStory(storyKey, {
|
|
17340
|
+
phase: "ESCALATED",
|
|
17341
|
+
error: "consecutive-review-timeouts",
|
|
17342
|
+
completedAt: new Date().toISOString()
|
|
17343
|
+
});
|
|
17344
|
+
await writeStoryMetricsBestEffort(storyKey, "escalated", reviewCycles + 1);
|
|
17345
|
+
await emitEscalation({
|
|
17346
|
+
storyKey,
|
|
17347
|
+
lastVerdict: "consecutive-review-timeouts",
|
|
17348
|
+
reviewCycles: reviewCycles + 1,
|
|
17349
|
+
issues: ["Review dispatch failed twice consecutively (original + phantom-retry). Likely resource-constrained or diff too large for reviewer."]
|
|
17350
|
+
});
|
|
17351
|
+
await persistState();
|
|
17352
|
+
return;
|
|
17353
|
+
}
|
|
17044
17354
|
verdict = reviewResult.verdict;
|
|
17045
17355
|
issueList = reviewResult.issue_list ?? [];
|
|
17046
17356
|
if (verdict === "NEEDS_MAJOR_REWORK" && reviewCycles > 0 && previousIssueList.length > 0 && issueList.length < previousIssueList.length) {
|
|
@@ -17387,6 +17697,20 @@ function createImplementationOrchestrator(deps) {
|
|
|
17387
17697
|
const constraints = decisions.filter((d) => d.category === "architecture");
|
|
17388
17698
|
archConstraints = constraints.map((d) => `${d.key}: ${d.value}`).join("\n");
|
|
17389
17699
|
} catch {}
|
|
17700
|
+
let gitDiffContent = "";
|
|
17701
|
+
try {
|
|
17702
|
+
const diffFiles = checkGitDiffFiles(projectRoot ?? process.cwd());
|
|
17703
|
+
if (diffFiles.length > 0) gitDiffContent = execSync(`git diff HEAD -- ${diffFiles.map((f) => `"${f}"`).join(" ")}`, {
|
|
17704
|
+
cwd: projectRoot ?? process.cwd(),
|
|
17705
|
+
encoding: "utf-8",
|
|
17706
|
+
timeout: 1e4,
|
|
17707
|
+
stdio: [
|
|
17708
|
+
"ignore",
|
|
17709
|
+
"pipe",
|
|
17710
|
+
"pipe"
|
|
17711
|
+
]
|
|
17712
|
+
}).trim();
|
|
17713
|
+
} catch {}
|
|
17390
17714
|
const sections = isMajorRework ? [
|
|
17391
17715
|
{
|
|
17392
17716
|
name: "story_content",
|
|
@@ -17405,7 +17729,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
17405
17729
|
},
|
|
17406
17730
|
{
|
|
17407
17731
|
name: "git_diff",
|
|
17408
|
-
content:
|
|
17732
|
+
content: gitDiffContent,
|
|
17409
17733
|
priority: "optional"
|
|
17410
17734
|
}
|
|
17411
17735
|
] : (() => {
|
|
@@ -17836,6 +18160,19 @@ function createImplementationOrchestrator(deps) {
|
|
|
17836
18160
|
} catch (err) {
|
|
17837
18161
|
logger$27.error({ err }, "Post-sprint contract verification threw an error — skipping");
|
|
17838
18162
|
}
|
|
18163
|
+
if (projectRoot !== void 0) try {
|
|
18164
|
+
const indicators = checkProfileStaleness(projectRoot);
|
|
18165
|
+
if (indicators.length > 0) {
|
|
18166
|
+
const message = "Project profile may be outdated — consider running `substrate init --force` to re-detect";
|
|
18167
|
+
eventBus.emit("pipeline:profile-stale", {
|
|
18168
|
+
message,
|
|
18169
|
+
indicators
|
|
18170
|
+
});
|
|
18171
|
+
logger$27.warn({ indicators }, message);
|
|
18172
|
+
}
|
|
18173
|
+
} catch (err) {
|
|
18174
|
+
logger$27.debug({ err }, "Profile staleness check failed (best-effort)");
|
|
18175
|
+
}
|
|
17839
18176
|
let completed = 0;
|
|
17840
18177
|
let escalated = 0;
|
|
17841
18178
|
let failed = 0;
|
|
@@ -17966,14 +18303,14 @@ async function resolveStoryKeys(db, projectRoot, opts) {
|
|
|
17966
18303
|
function parseStoryKeysFromEpics(content) {
|
|
17967
18304
|
if (content.length === 0) return [];
|
|
17968
18305
|
const keys = new Set();
|
|
17969
|
-
const explicitKeyPattern = /\*\*Story key:\*\*\s*`?(
|
|
18306
|
+
const explicitKeyPattern = /\*\*Story key:\*\*\s*`?([A-Za-z0-9]+-[A-Za-z0-9]+)(?:-[^`\s]*)?`?/g;
|
|
17970
18307
|
let match$1;
|
|
17971
18308
|
while ((match$1 = explicitKeyPattern.exec(content)) !== null) if (match$1[1] !== void 0) keys.add(match$1[1]);
|
|
17972
|
-
const headingPattern = /^###\s+Story\s+(
|
|
18309
|
+
const headingPattern = /^###\s+Story\s+([A-Za-z0-9]+)[.\-]([A-Za-z0-9]+)/gm;
|
|
17973
18310
|
while ((match$1 = headingPattern.exec(content)) !== null) if (match$1[1] !== void 0 && match$1[2] !== void 0) keys.add(`${match$1[1]}-${match$1[2]}`);
|
|
17974
|
-
const inlineStoryPattern = /Story\s+(
|
|
18311
|
+
const inlineStoryPattern = /Story\s+([A-Za-z0-9]+)-([A-Za-z0-9]+)[:\s]/g;
|
|
17975
18312
|
while ((match$1 = inlineStoryPattern.exec(content)) !== null) if (match$1[1] !== void 0 && match$1[2] !== void 0) keys.add(`${match$1[1]}-${match$1[2]}`);
|
|
17976
|
-
const filePathPattern = /_bmad-output\/implementation-artifacts\/(
|
|
18313
|
+
const filePathPattern = /_bmad-output\/implementation-artifacts\/([A-Za-z0-9]+-[A-Za-z0-9]+)-/g;
|
|
17977
18314
|
while ((match$1 = filePathPattern.exec(content)) !== null) if (match$1[1] !== void 0) keys.add(match$1[1]);
|
|
17978
18315
|
return sortStoryKeys(Array.from(keys));
|
|
17979
18316
|
}
|
|
@@ -18019,6 +18356,12 @@ function discoverPendingStoryKeys(projectRoot, epicNumber) {
|
|
|
18019
18356
|
allKeys = sortStoryKeys([...new Set(allKeys)]);
|
|
18020
18357
|
}
|
|
18021
18358
|
}
|
|
18359
|
+
const sprintKeys = parseStoryKeysFromSprintStatus(projectRoot);
|
|
18360
|
+
if (sprintKeys.length > 0) {
|
|
18361
|
+
const merged = new Set(allKeys);
|
|
18362
|
+
for (const k of sprintKeys) merged.add(k);
|
|
18363
|
+
allKeys = sortStoryKeys([...merged]);
|
|
18364
|
+
}
|
|
18022
18365
|
if (allKeys.length === 0) return [];
|
|
18023
18366
|
const existingKeys = collectExistingStoryKeys(projectRoot);
|
|
18024
18367
|
return allKeys.filter((k) => !existingKeys.has(k));
|
|
@@ -18069,7 +18412,7 @@ function collectExistingStoryKeys(projectRoot) {
|
|
|
18069
18412
|
} catch {
|
|
18070
18413
|
return existing;
|
|
18071
18414
|
}
|
|
18072
|
-
const filePattern = /^(
|
|
18415
|
+
const filePattern = /^([A-Za-z0-9]+-[A-Za-z0-9]+)-/;
|
|
18073
18416
|
for (const entry of entries) {
|
|
18074
18417
|
if (!entry.endsWith(".md")) continue;
|
|
18075
18418
|
const m = filePattern.exec(entry);
|
|
@@ -18078,6 +18421,33 @@ function collectExistingStoryKeys(projectRoot) {
|
|
|
18078
18421
|
return existing;
|
|
18079
18422
|
}
|
|
18080
18423
|
/**
|
|
18424
|
+
* Parse story keys from sprint-status.yaml.
|
|
18425
|
+
* Reads the development_status map and extracts keys that match the
|
|
18426
|
+
* alphanumeric story key pattern (e.g., 1-1a, NEW-26, E5-accessibility).
|
|
18427
|
+
* Filters out epic status entries (epic-N) and retrospective entries.
|
|
18428
|
+
*/
|
|
18429
|
+
function parseStoryKeysFromSprintStatus(projectRoot) {
|
|
18430
|
+
const candidates = [join$1(projectRoot, "_bmad-output", "implementation-artifacts", "sprint-status.yaml"), join$1(projectRoot, "_bmad-output", "sprint-status.yaml")];
|
|
18431
|
+
const statusPath = candidates.find((p) => existsSync$1(p));
|
|
18432
|
+
if (!statusPath) return [];
|
|
18433
|
+
try {
|
|
18434
|
+
const content = readFileSync$1(statusPath, "utf-8");
|
|
18435
|
+
const keys = [];
|
|
18436
|
+
const linePattern = /^\s{2}([A-Za-z0-9]+-[A-Za-z0-9]+(?:-[A-Za-z0-9-]*)?)\s*:/gm;
|
|
18437
|
+
let match$1;
|
|
18438
|
+
while ((match$1 = linePattern.exec(content)) !== null) {
|
|
18439
|
+
const fullKey = match$1[1];
|
|
18440
|
+
if (/^epic-\d+$/.test(fullKey)) continue;
|
|
18441
|
+
if (fullKey.includes("retrospective")) continue;
|
|
18442
|
+
const segments = fullKey.split("-");
|
|
18443
|
+
if (segments.length >= 2) keys.push(`${segments[0]}-${segments[1]}`);
|
|
18444
|
+
}
|
|
18445
|
+
return [...new Set(keys)];
|
|
18446
|
+
} catch {
|
|
18447
|
+
return [];
|
|
18448
|
+
}
|
|
18449
|
+
}
|
|
18450
|
+
/**
|
|
18081
18451
|
* Collect story keys already completed in previous pipeline runs.
|
|
18082
18452
|
* Scans pipeline_runs with status='completed' and extracts story keys
|
|
18083
18453
|
* with phase='COMPLETE' from their token_usage_json state.
|
|
@@ -18096,16 +18466,26 @@ async function getCompletedStoryKeys(db) {
|
|
|
18096
18466
|
return completed;
|
|
18097
18467
|
}
|
|
18098
18468
|
/**
|
|
18099
|
-
* Sort story keys
|
|
18100
|
-
*
|
|
18469
|
+
* Sort story keys: numeric keys first (by epic then story number),
|
|
18470
|
+
* then alphabetic-prefix keys (NEW-*, E-*) sorted lexicographically.
|
|
18471
|
+
* E.g. ["10-1", "1-2a", "1-2", "NEW-26", "E5-acc"] → ["1-2", "1-2a", "10-1", "E5-acc", "NEW-26"]
|
|
18101
18472
|
*/
|
|
18102
18473
|
function sortStoryKeys(keys) {
|
|
18103
18474
|
return keys.slice().sort((a, b) => {
|
|
18104
|
-
const
|
|
18105
|
-
const
|
|
18106
|
-
const
|
|
18107
|
-
|
|
18108
|
-
|
|
18475
|
+
const aParts = a.split("-");
|
|
18476
|
+
const bParts = b.split("-");
|
|
18477
|
+
const aNum = Number(aParts[0]);
|
|
18478
|
+
const bNum = Number(bParts[0]);
|
|
18479
|
+
if (!isNaN(aNum) && !isNaN(bNum)) {
|
|
18480
|
+
if (aNum !== bNum) return aNum - bNum;
|
|
18481
|
+
const aStory = Number(aParts[1]);
|
|
18482
|
+
const bStory = Number(bParts[1]);
|
|
18483
|
+
if (!isNaN(aStory) && !isNaN(bStory) && aStory !== bStory) return aStory - bStory;
|
|
18484
|
+
return (aParts[1] ?? "").localeCompare(bParts[1] ?? "");
|
|
18485
|
+
}
|
|
18486
|
+
if (!isNaN(aNum)) return -1;
|
|
18487
|
+
if (!isNaN(bNum)) return 1;
|
|
18488
|
+
return a.localeCompare(b);
|
|
18109
18489
|
});
|
|
18110
18490
|
}
|
|
18111
18491
|
|
|
@@ -22114,7 +22494,7 @@ function mapInternalPhaseToEventPhase(internalPhase) {
|
|
|
22114
22494
|
}
|
|
22115
22495
|
}
|
|
22116
22496
|
async function runRunAction(options) {
|
|
22117
|
-
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, epic: epicNumber, dryRun, registry: injectedRegistry } = options;
|
|
22497
|
+
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, epic: epicNumber, dryRun, maxReviewCycles = 2, registry: injectedRegistry } = options;
|
|
22118
22498
|
if (startPhase !== void 0 && !VALID_PHASES.includes(startPhase)) {
|
|
22119
22499
|
const errorMsg = `Invalid phase '${startPhase}'. Valid phases: ${VALID_PHASES.join(", ")}`;
|
|
22120
22500
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
|
|
@@ -22196,7 +22576,7 @@ async function runRunAction(options) {
|
|
|
22196
22576
|
if (storiesArg !== void 0 && storiesArg !== "") {
|
|
22197
22577
|
parsedStoryKeys = storiesArg.split(",").map((k) => k.trim()).filter((k) => k.length > 0);
|
|
22198
22578
|
for (const key of parsedStoryKeys) if (!validateStoryKey(key)) {
|
|
22199
|
-
const errorMsg = `Story key '${key}' is not a valid format. Expected: <epic>-<story> (e.g., 10-1)`;
|
|
22579
|
+
const errorMsg = `Story key '${key}' is not a valid format. Expected: <epic>-<story> (e.g., 10-1, 1-1a, NEW-26)`;
|
|
22200
22580
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
|
|
22201
22581
|
else process.stderr.write(`Error: ${errorMsg}\n`);
|
|
22202
22582
|
return 1;
|
|
@@ -22252,7 +22632,8 @@ async function runRunAction(options) {
|
|
|
22252
22632
|
...telemetryEnabled ? {
|
|
22253
22633
|
telemetryEnabled: true,
|
|
22254
22634
|
telemetryPort
|
|
22255
|
-
} : {}
|
|
22635
|
+
} : {},
|
|
22636
|
+
maxReviewCycles
|
|
22256
22637
|
});
|
|
22257
22638
|
let storyKeys = [...parsedStoryKeys];
|
|
22258
22639
|
if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
|
|
@@ -22754,7 +23135,7 @@ async function runRunAction(options) {
|
|
|
22754
23135
|
type: "pipeline:pre-flight-failure",
|
|
22755
23136
|
ts: new Date().toISOString(),
|
|
22756
23137
|
exitCode: payload.exitCode,
|
|
22757
|
-
output: payload.output
|
|
23138
|
+
output: payload.output + "\nTip: Use --skip-preflight to bypass, or check your build command in .substrate/project-profile.yaml"
|
|
22758
23139
|
});
|
|
22759
23140
|
});
|
|
22760
23141
|
eventBus.on("pipeline:contract-mismatch", (payload) => {
|
|
@@ -22777,6 +23158,14 @@ async function runRunAction(options) {
|
|
|
22777
23158
|
verdict: payload.verdict
|
|
22778
23159
|
});
|
|
22779
23160
|
});
|
|
23161
|
+
eventBus.on("pipeline:profile-stale", (payload) => {
|
|
23162
|
+
ndjsonEmitter.emit({
|
|
23163
|
+
type: "pipeline:profile-stale",
|
|
23164
|
+
ts: new Date().toISOString(),
|
|
23165
|
+
message: payload.message,
|
|
23166
|
+
indicators: payload.indicators
|
|
23167
|
+
});
|
|
23168
|
+
});
|
|
22780
23169
|
}
|
|
22781
23170
|
const ingestionServer = telemetryEnabled ? new IngestionServer({ port: telemetryPort }) : void 0;
|
|
22782
23171
|
if (telemetryPersistence !== void 0) {
|
|
@@ -22809,7 +23198,7 @@ async function runRunAction(options) {
|
|
|
22809
23198
|
eventBus,
|
|
22810
23199
|
config: {
|
|
22811
23200
|
maxConcurrency: concurrency,
|
|
22812
|
-
maxReviewCycles
|
|
23201
|
+
maxReviewCycles,
|
|
22813
23202
|
pipelineRunId: pipelineRun.id,
|
|
22814
23203
|
enableHeartbeat: eventsFlag === true,
|
|
22815
23204
|
skipPreflight: skipPreflight === true
|
|
@@ -22928,7 +23317,7 @@ async function runRunAction(options) {
|
|
|
22928
23317
|
}
|
|
22929
23318
|
}
|
|
22930
23319
|
async function runFullPipeline(options) {
|
|
22931
|
-
const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, registry: injectedRegistry, tokenCeilings, stories: explicitStories, telemetryEnabled: fullTelemetryEnabled, telemetryPort: fullTelemetryPort } = options;
|
|
23320
|
+
const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, maxReviewCycles = 2, registry: injectedRegistry, tokenCeilings, stories: explicitStories, telemetryEnabled: fullTelemetryEnabled, telemetryPort: fullTelemetryPort } = options;
|
|
22932
23321
|
if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
|
|
22933
23322
|
const adapter = createDatabaseAdapter({
|
|
22934
23323
|
backend: "auto",
|
|
@@ -23148,7 +23537,7 @@ async function runFullPipeline(options) {
|
|
|
23148
23537
|
eventBus,
|
|
23149
23538
|
config: {
|
|
23150
23539
|
maxConcurrency: concurrency,
|
|
23151
|
-
maxReviewCycles
|
|
23540
|
+
maxReviewCycles,
|
|
23152
23541
|
pipelineRunId: runId,
|
|
23153
23542
|
skipPreflight: skipPreflight === true
|
|
23154
23543
|
},
|
|
@@ -23257,7 +23646,7 @@ async function runFullPipeline(options) {
|
|
|
23257
23646
|
}
|
|
23258
23647
|
}
|
|
23259
23648
|
function registerRunCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
|
|
23260
|
-
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("--epic <n>", "Scope story discovery to a single epic number (e.g., 27)", (v) => parseInt(v, 10)).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)").option("--dry-run", "Preview routing and repo-map injection without dispatching (Story 28-9)").action(async (opts) => {
|
|
23649
|
+
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("--epic <n>", "Scope story discovery to a single epic number (e.g., 27)", (v) => parseInt(v, 10)).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)").option("--max-review-cycles <n>", "Maximum review cycles per story (default: 2)", (v) => parseInt(v, 10), 2).option("--dry-run", "Preview routing and repo-map injection without dispatching (Story 28-9)").action(async (opts) => {
|
|
23261
23650
|
if (opts.helpAgent) {
|
|
23262
23651
|
process.exitCode = await runHelpAgent();
|
|
23263
23652
|
return;
|
|
@@ -23292,6 +23681,7 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
23292
23681
|
research: opts.research,
|
|
23293
23682
|
skipResearch: opts.skipResearch,
|
|
23294
23683
|
skipPreflight: opts.skipPreflight,
|
|
23684
|
+
maxReviewCycles: opts.maxReviewCycles,
|
|
23295
23685
|
dryRun: opts.dryRun,
|
|
23296
23686
|
registry
|
|
23297
23687
|
});
|
|
@@ -23301,4 +23691,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
23301
23691
|
|
|
23302
23692
|
//#endregion
|
|
23303
23693
|
export { AdapterTelemetryPersistence, AppError, DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DoltClient, DoltNotInstalled, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, FileStateStore, GitClient, GrammarLoader, IngestionServer, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SUBSTRATE_OWNED_SETTINGS_KEYS, SymbolParser, VALID_PHASES, WorkGraphRepository, buildPipelineStatusOutput, checkDoltInstalled, createConfigSystem, createContextCompiler, createDatabaseAdapter, createDispatcher, createDoltClient, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStateStore, createStopAfterGate, createTelemetryAdvisor, detectCycles, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, initSchema, initializeDolt, isSyncAdapter, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
|
|
23304
|
-
//# sourceMappingURL=run-
|
|
23694
|
+
//# sourceMappingURL=run-GwwiCYXX.js.map
|