substrate-ai 0.3.1 → 0.3.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.
@@ -76,6 +76,13 @@ var ClaudeCodeAdapter = class {
76
76
  const unsetKeys = ["CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT"];
77
77
  if (options.billingMode === "api" && options.apiKey) envEntries.ANTHROPIC_API_KEY = options.apiKey;
78
78
  else unsetKeys.push("ANTHROPIC_API_KEY");
79
+ if (options.otlpEndpoint !== void 0) {
80
+ envEntries.CLAUDE_CODE_ENABLE_TELEMETRY = "1";
81
+ envEntries.OTEL_LOGS_EXPORTER = "otlp";
82
+ envEntries.OTEL_METRICS_EXPORTER = "otlp";
83
+ envEntries.OTEL_EXPORTER_OTLP_PROTOCOL = "http/json";
84
+ envEntries.OTEL_EXPORTER_OTLP_ENDPOINT = options.otlpEndpoint;
85
+ }
79
86
  return {
80
87
  binary: "claude",
81
88
  args,
@@ -815,4 +822,4 @@ var AdapterRegistry = class {
815
822
 
816
823
  //#endregion
817
824
  export { AdapterRegistry, ClaudeCodeAdapter, CodexCLIAdapter, GeminiCLIAdapter };
818
- //# sourceMappingURL=adapter-registry-PsWhP_1Q.js.map
825
+ //# sourceMappingURL=adapter-registry-DHl0W-YB.js.map
package/dist/cli/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, DoltNotInstalled, FileStateStore, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, checkDoltInstalled, createConfigSystem, createContextCompiler, createDispatcher, createDoltClient, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStateStore, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, initializeDolt, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-DNURadtJ.js";
2
+ import { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, DoltNotInstalled, FileStateStore, SUBSTRATE_OWNED_SETTINGS_KEYS, TelemetryPersistence, VALID_PHASES, buildPipelineStatusOutput, checkDoltInstalled, createConfigSystem, createContextCompiler, createDispatcher, createDoltClient, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStateStore, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, initializeDolt, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-DI9s014E.js";
3
3
  import { createLogger } from "../logger-D2fS2ccL.js";
4
- import { AdapterRegistry } from "../adapter-registry-PsWhP_1Q.js";
5
- import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema } from "../config-migrator-DSi8KhQC.js";
4
+ import { AdapterRegistry } from "../adapter-registry-DHl0W-YB.js";
5
+ import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema } from "../config-migrator-CQmBdKeG.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
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
- import "../version-manager-impl-CizNmmLT.js";
11
- import { registerUpgradeCommand } from "../upgrade-Cvwtnwl4.js";
10
+ import "../version-manager-impl-33JYXsqa.js";
11
+ import { registerUpgradeCommand } from "../upgrade-DO307rFf.js";
12
12
  import { Command } from "commander";
13
13
  import { fileURLToPath } from "url";
14
14
  import { dirname, join, resolve } from "path";
@@ -2554,6 +2554,7 @@ async function runSupervisorAction(options, deps = {}) {
2554
2554
  runId,
2555
2555
  restartCount: 0
2556
2556
  };
2557
+ let maxRestartsExhausted = false;
2557
2558
  const startTime = Date.now();
2558
2559
  function emitEvent(event) {
2559
2560
  if (outputFormat === "json") {
@@ -2664,7 +2665,7 @@ async function runSupervisorAction(options, deps = {}) {
2664
2665
  const expDb = expDbWrapper.db;
2665
2666
  const { runRunAction: runPipeline } = await import(
2666
2667
  /* @vite-ignore */
2667
- "../run-D6WEx9l2.js"
2668
+ "../run-33J0SBp1.js"
2668
2669
  );
2669
2670
  const runStoryFn = async (opts) => {
2670
2671
  const exitCode = await runPipeline({
@@ -2729,6 +2730,7 @@ async function runSupervisorAction(options, deps = {}) {
2729
2730
  }
2730
2731
  return summary.failed.length > 0 || summary.escalated.length > 0 ? 1 : 0;
2731
2732
  }
2733
+ if (maxRestartsExhausted) return 2;
2732
2734
  const stallResult = await handleStallRecovery(health, state, {
2733
2735
  stallThreshold,
2734
2736
  maxRestarts,
@@ -2745,10 +2747,8 @@ async function runSupervisorAction(options, deps = {}) {
2745
2747
  emitEvent,
2746
2748
  log
2747
2749
  });
2748
- if (stallResult !== null) {
2749
- if (stallResult.maxRestartsExceeded) return 2;
2750
- state = stallResult.state;
2751
- }
2750
+ if (stallResult !== null) if (stallResult.maxRestartsExceeded) maxRestartsExhausted = true;
2751
+ else state = stallResult.state;
2752
2752
  await sleep(pollInterval * 1e3);
2753
2753
  }
2754
2754
  }
@@ -2778,6 +2778,7 @@ async function runMultiProjectSupervisor(options, deps = {}) {
2778
2778
  }]));
2779
2779
  const doneProjects = new Set();
2780
2780
  const projectExitCodes = new Map();
2781
+ const maxRestartsExhaustedProjects = new Set();
2781
2782
  const startTime = Date.now();
2782
2783
  function emitEvent(event) {
2783
2784
  if (outputFormat === "json") {
@@ -2834,6 +2835,11 @@ async function runMultiProjectSupervisor(options, deps = {}) {
2834
2835
  projectExitCodes.set(projectRoot, summary.failed.length > 0 || summary.escalated.length > 0 ? 1 : 0);
2835
2836
  continue;
2836
2837
  }
2838
+ if (maxRestartsExhaustedProjects.has(projectRoot)) {
2839
+ doneProjects.add(projectRoot);
2840
+ projectExitCodes.set(projectRoot, 2);
2841
+ continue;
2842
+ }
2837
2843
  const stallResult = await handleStallRecovery(health, state, {
2838
2844
  stallThreshold,
2839
2845
  maxRestarts,
@@ -2846,10 +2852,8 @@ async function runMultiProjectSupervisor(options, deps = {}) {
2846
2852
  }),
2847
2853
  log: (msg) => log(`[${projectRoot}] ${msg}`)
2848
2854
  });
2849
- if (stallResult !== null) if (stallResult.maxRestartsExceeded) {
2850
- doneProjects.add(projectRoot);
2851
- projectExitCodes.set(projectRoot, 2);
2852
- } else states.set(projectRoot, stallResult.state);
2855
+ if (stallResult !== null) if (stallResult.maxRestartsExceeded) maxRestartsExhaustedProjects.add(projectRoot);
2856
+ else states.set(projectRoot, stallResult.state);
2853
2857
  }
2854
2858
  if (doneProjects.size >= projects.length) {
2855
2859
  const elapsedSeconds = Math.round((Date.now() - startTime) / 1e3);
@@ -2908,8 +2912,217 @@ function registerSupervisorCommand(program, _version = "0.0.0", projectRoot = pr
2908
2912
  //#endregion
2909
2913
  //#region src/cli/commands/metrics.ts
2910
2914
  const logger$11 = createLogger("metrics-cmd");
2915
+ async function openTelemetryDb(dbPath) {
2916
+ if (!existsSync(dbPath)) return null;
2917
+ try {
2918
+ const db = new Database(dbPath, { readonly: true });
2919
+ return db;
2920
+ } catch {
2921
+ return null;
2922
+ }
2923
+ }
2924
+ function rowsToEfficiencyScore(rows) {
2925
+ return rows;
2926
+ }
2927
+ function printEfficiencyTable(scores) {
2928
+ process.stdout.write(`\nEfficiency Scores (${scores.length} records)\n`);
2929
+ process.stdout.write("─".repeat(80) + "\n");
2930
+ process.stdout.write(` ${"Story Key".padEnd(14)} ${"Score".padStart(6)} ${"Cache Hit%".padStart(11)} ${"I/O Ratio".padStart(10)} ${"Ctx Mgmt".padStart(9)} Model\n`);
2931
+ process.stdout.write(" " + "─".repeat(76) + "\n");
2932
+ for (const s of scores) {
2933
+ const cacheHitPct = s.totalTurns > 0 ? `${(s.avgCacheHitRate * 100).toFixed(1)}%` : "0.0%";
2934
+ const ioRatio = s.avgIoRatio.toFixed(2);
2935
+ const ctxMgmt = String(Math.round(s.contextManagementSubScore));
2936
+ const model = s.perModelBreakdown.length > 0 ? s.perModelBreakdown[0]?.model ?? "unknown" : "unknown";
2937
+ process.stdout.write(` ${s.storyKey.padEnd(14)} ${String(s.compositeScore).padStart(6)} ${cacheHitPct.padStart(11)} ${ioRatio.padStart(10)} ${ctxMgmt.padStart(9)} ${model}\n`);
2938
+ }
2939
+ }
2940
+ function printRecommendationTable(recs) {
2941
+ process.stdout.write(`\nRecommendations (${recs.length} records)\n`);
2942
+ process.stdout.write("─".repeat(80) + "\n");
2943
+ process.stdout.write(` ${"Story".padEnd(12)} ${"Severity".padEnd(10)} ${"Rule".padEnd(24)} ${"Savings Tokens".padStart(15)}\n`);
2944
+ process.stdout.write(" " + "─".repeat(64) + "\n");
2945
+ for (const r of recs) {
2946
+ const savings = r.potentialSavingsTokens !== void 0 ? String(r.potentialSavingsTokens) : "-";
2947
+ process.stdout.write(` ${r.storyKey.padEnd(12)} ${r.severity.padEnd(10)} ${r.ruleId.padEnd(24)} ${savings.padStart(15)}\n`);
2948
+ process.stdout.write(` ${r.title}\n`);
2949
+ }
2950
+ }
2951
+ function printTurnTable(turns, storyKey) {
2952
+ process.stdout.write(`\nTurn Analysis: ${storyKey} (${turns.length} turns)\n`);
2953
+ process.stdout.write("─".repeat(80) + "\n");
2954
+ process.stdout.write(` ${"#".padStart(4)} ${"Tokens In".padStart(10)} ${"Tok Out".padStart(8)} ${"Cache Hit%".padStart(11)} ${"Ctx Size".padStart(9)} Spike\n`);
2955
+ process.stdout.write(" " + "─".repeat(60) + "\n");
2956
+ for (const t of turns) {
2957
+ const cacheHitPct = t.inputTokens > 0 ? `${(t.cacheHitRate * 100).toFixed(1)}%` : "0.0%";
2958
+ const spike = t.isContextSpike ? " ⚠" : "";
2959
+ process.stdout.write(` ${String(t.turnNumber).padStart(4)} ${t.inputTokens.toLocaleString().padStart(10)} ${t.outputTokens.toLocaleString().padStart(8)} ${cacheHitPct.padStart(11)} ${t.contextSize.toLocaleString().padStart(9)}${spike}\n`);
2960
+ }
2961
+ }
2962
+ function printConsumerTable(consumers, storyKey) {
2963
+ process.stdout.write(`\nConsumer Stats: ${storyKey} (${consumers.length} consumers)\n`);
2964
+ process.stdout.write("─".repeat(80) + "\n");
2965
+ process.stdout.write(` ${"Consumer Key".padEnd(36)} ${"Category".padEnd(20)} ${"Tokens".padStart(10)} ${"%".padStart(7)}\n`);
2966
+ process.stdout.write(" " + "─".repeat(76) + "\n");
2967
+ for (const c of consumers) {
2968
+ const key = c.consumerKey.slice(0, 34);
2969
+ const pct = `${c.percentage.toFixed(1)}%`;
2970
+ process.stdout.write(` ${key.padEnd(36)} ${c.category.padEnd(20)} ${c.totalTokens.toLocaleString().padStart(10)} ${pct.padStart(7)}\n`);
2971
+ }
2972
+ }
2973
+ function printCategoryTable(stats, label) {
2974
+ process.stdout.write(`\nCategory Stats${label} (${stats.length} categories)\n`);
2975
+ process.stdout.write("─".repeat(80) + "\n");
2976
+ process.stdout.write(` ${"Category".padEnd(22)} ${"Tokens".padStart(12)} ${"%".padStart(8)} ${"Events".padStart(8)} ${"Avg/Event".padStart(10)} Trend\n`);
2977
+ process.stdout.write(" " + "─".repeat(70) + "\n");
2978
+ const sorted = [...stats].sort((a, b) => b.totalTokens - a.totalTokens);
2979
+ for (const c of sorted) {
2980
+ const pct = `${c.percentage.toFixed(1)}%`;
2981
+ const avg = c.avgTokensPerEvent.toFixed(0);
2982
+ process.stdout.write(` ${c.category.padEnd(22)} ${c.totalTokens.toLocaleString().padStart(12)} ${pct.padStart(8)} ${String(c.eventCount).padStart(8)} ${avg.padStart(10)} ${c.trend}\n`);
2983
+ }
2984
+ }
2911
2985
  async function runMetricsAction(options) {
2912
- const { outputFormat, projectRoot, limit = 10, compare, tagBaseline, analysis, sprint, story, taskType, since, aggregate } = options;
2986
+ const { outputFormat, projectRoot, limit = 10, compare, tagBaseline, analysis, sprint, story, taskType, since, aggregate, efficiency, recommendations, turns, consumers, categories, compareStories } = options;
2987
+ const telemetryModes = [
2988
+ efficiency,
2989
+ recommendations,
2990
+ turns,
2991
+ consumers,
2992
+ categories,
2993
+ compareStories
2994
+ ].filter(Boolean);
2995
+ if (telemetryModes.length > 1) {
2996
+ process.stderr.write("Error: --efficiency, --recommendations, --turns, --consumers, --categories, and --compare-stories are mutually exclusive\n");
2997
+ return 1;
2998
+ }
2999
+ const hasTelemetryMode = telemetryModes.length > 0;
3000
+ if (hasTelemetryMode && (compare !== void 0 || tagBaseline !== void 0 || analysis !== void 0)) {
3001
+ process.stderr.write("Error: telemetry modes (--efficiency, --recommendations, --turns, --consumers, --categories, --compare-stories) cannot be combined with --compare, --tag-baseline, or --analysis\n");
3002
+ return 1;
3003
+ }
3004
+ if (hasTelemetryMode) {
3005
+ const dbRoot$1 = await resolveMainRepoRoot(projectRoot);
3006
+ const dbPath$1 = join(dbRoot$1, ".substrate", "substrate.db");
3007
+ const doltStatePath = join(dbRoot$1, ".substrate", "state", ".dolt");
3008
+ const doltExists = existsSync(doltStatePath);
3009
+ if (!doltExists && !existsSync(dbPath$1)) {
3010
+ const msg = "No telemetry data yet — run a pipeline with `telemetry.enabled: true`";
3011
+ if (turns !== void 0 || consumers !== void 0) {
3012
+ process.stderr.write(`Error: ${msg}\n`);
3013
+ return 1;
3014
+ }
3015
+ if (outputFormat === "json") process.stdout.write(formatOutput({ message: msg }, "json", true) + "\n");
3016
+ else process.stdout.write(msg + "\n");
3017
+ return 0;
3018
+ }
3019
+ const sqliteDb = await openTelemetryDb(dbPath$1);
3020
+ if (sqliteDb === null) {
3021
+ const msg = "No telemetry data yet — run a pipeline with `telemetry.enabled: true`";
3022
+ if (turns !== void 0 || consumers !== void 0) {
3023
+ process.stderr.write(`Error: ${msg}\n`);
3024
+ return 1;
3025
+ }
3026
+ if (outputFormat === "json") process.stdout.write(formatOutput({ message: msg }, "json", true) + "\n");
3027
+ else process.stdout.write(msg + "\n");
3028
+ return 0;
3029
+ }
3030
+ try {
3031
+ const telemetryPersistence = new TelemetryPersistence(sqliteDb);
3032
+ telemetryPersistence.initSchema();
3033
+ if (efficiency === true) {
3034
+ const scores = await telemetryPersistence.getEfficiencyScores(20);
3035
+ if (outputFormat === "json") process.stdout.write(formatOutput({ efficiency: rowsToEfficiencyScore(scores) }, "json", true) + "\n");
3036
+ else printEfficiencyTable(scores);
3037
+ return 0;
3038
+ }
3039
+ if (recommendations === true) {
3040
+ const recs = story !== void 0 ? await telemetryPersistence.getRecommendations(story) : await telemetryPersistence.getAllRecommendations(50);
3041
+ if (outputFormat === "json") process.stdout.write(formatOutput({
3042
+ recommendations: recs,
3043
+ ...story !== void 0 && { storyKey: story }
3044
+ }, "json", true) + "\n");
3045
+ else if (recs.length === 0) {
3046
+ const msg = story !== void 0 ? `No recommendations found for story '${story}'` : "No recommendations yet — run a pipeline with `telemetry.enabled: true`";
3047
+ process.stdout.write(msg + "\n");
3048
+ } else printRecommendationTable(recs);
3049
+ return 0;
3050
+ }
3051
+ if (turns !== void 0) {
3052
+ const turnData = await telemetryPersistence.getTurnAnalysis(turns);
3053
+ if (turnData.length === 0) {
3054
+ const msg = `No turn analysis data found for story '${turns}'`;
3055
+ if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
3056
+ else process.stderr.write(`Error: ${msg}\n`);
3057
+ return 1;
3058
+ }
3059
+ if (outputFormat === "json") process.stdout.write(formatOutput({ turns: turnData }, "json", true) + "\n");
3060
+ else printTurnTable(turnData, turns);
3061
+ return 0;
3062
+ }
3063
+ if (consumers !== void 0) {
3064
+ const consumerData = await telemetryPersistence.getConsumerStats(consumers);
3065
+ if (consumerData.length === 0) {
3066
+ const msg = `No consumer stats found for story '${consumers}'`;
3067
+ if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
3068
+ else process.stderr.write(`Error: ${msg}\n`);
3069
+ return 1;
3070
+ }
3071
+ if (outputFormat === "json") process.stdout.write(formatOutput({ consumers: consumerData }, "json", true) + "\n");
3072
+ else printConsumerTable(consumerData, consumers);
3073
+ return 0;
3074
+ }
3075
+ if (categories === true) {
3076
+ const storyKey = story;
3077
+ const categoryData = await telemetryPersistence.getCategoryStats(storyKey ?? "");
3078
+ const label = storyKey !== void 0 ? `: ${storyKey}` : "";
3079
+ if (outputFormat === "json") process.stdout.write(formatOutput({
3080
+ categories: categoryData,
3081
+ storyKey
3082
+ }, "json", true) + "\n");
3083
+ else printCategoryTable(categoryData, label);
3084
+ return 0;
3085
+ }
3086
+ if (compareStories !== void 0) {
3087
+ const [keyA, keyB] = compareStories;
3088
+ const [scoreA, scoreB] = await Promise.all([telemetryPersistence.getEfficiencyScore(keyA), telemetryPersistence.getEfficiencyScore(keyB)]);
3089
+ if (scoreA === null || scoreB === null) {
3090
+ const missing = [scoreA === null ? keyA : null, scoreB === null ? keyB : null].filter(Boolean).join(", ");
3091
+ const msg = `No efficiency score found for story: ${missing}`;
3092
+ if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
3093
+ else process.stderr.write(`Error: ${msg}\n`);
3094
+ return 1;
3095
+ }
3096
+ const delta = {
3097
+ compositeScore: scoreB.compositeScore - scoreA.compositeScore,
3098
+ cacheHitSubScore: scoreB.cacheHitSubScore - scoreA.cacheHitSubScore,
3099
+ ioRatioSubScore: scoreB.ioRatioSubScore - scoreA.ioRatioSubScore,
3100
+ contextManagementSubScore: scoreB.contextManagementSubScore - scoreA.contextManagementSubScore
3101
+ };
3102
+ if (outputFormat === "json") process.stdout.write(formatOutput({
3103
+ storyA: scoreA,
3104
+ storyB: scoreB,
3105
+ delta
3106
+ }, "json", true) + "\n");
3107
+ else {
3108
+ const sign = (n) => n > 0 ? "+" : "";
3109
+ process.stdout.write(`\nEfficiency Comparison: ${keyA} vs ${keyB}\n`);
3110
+ process.stdout.write("─".repeat(80) + "\n");
3111
+ process.stdout.write(` ${"Metric".padEnd(30)} ${keyA.padStart(12)} ${keyB.padStart(12)} ${"Delta".padStart(10)}\n`);
3112
+ process.stdout.write(" " + "─".repeat(66) + "\n");
3113
+ process.stdout.write(` ${"Composite Score".padEnd(30)} ${String(scoreA.compositeScore).padStart(12)} ${String(scoreB.compositeScore).padStart(12)} ${`${sign(delta.compositeScore)}${delta.compositeScore}`.padStart(10)}\n`);
3114
+ process.stdout.write(` ${"Cache Hit Sub-Score".padEnd(30)} ${scoreA.cacheHitSubScore.toFixed(1).padStart(12)} ${scoreB.cacheHitSubScore.toFixed(1).padStart(12)} ${`${sign(delta.cacheHitSubScore)}${delta.cacheHitSubScore.toFixed(1)}`.padStart(10)}\n`);
3115
+ process.stdout.write(` ${"I/O Ratio Sub-Score".padEnd(30)} ${scoreA.ioRatioSubScore.toFixed(1).padStart(12)} ${scoreB.ioRatioSubScore.toFixed(1).padStart(12)} ${`${sign(delta.ioRatioSubScore)}${delta.ioRatioSubScore.toFixed(1)}`.padStart(10)}\n`);
3116
+ process.stdout.write(` ${"Context Mgmt Sub-Score".padEnd(30)} ${scoreA.contextManagementSubScore.toFixed(1).padStart(12)} ${scoreB.contextManagementSubScore.toFixed(1).padStart(12)} ${`${sign(delta.contextManagementSubScore)}${delta.contextManagementSubScore.toFixed(1)}`.padStart(10)}\n`);
3117
+ }
3118
+ return 0;
3119
+ }
3120
+ } finally {
3121
+ try {
3122
+ sqliteDb.close();
3123
+ } catch {}
3124
+ }
3125
+ }
2913
3126
  if (analysis !== void 0) {
2914
3127
  const dbRoot$1 = await resolveMainRepoRoot(projectRoot);
2915
3128
  const reportBase = join(dbRoot$1, "_bmad-output", "supervisor-reports", `${analysis}-analysis`);
@@ -3142,26 +3355,43 @@ async function runMetricsAction(options) {
3142
3355
  }
3143
3356
  }
3144
3357
  function registerMetricsCommand(program, _version = "0.0.0", projectRoot = process.cwd()) {
3145
- program.command("metrics").description("Show historical pipeline run metrics and cross-run comparison").option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--limit <n>", "Number of runs to show (default: 10)", (v) => parseInt(v, 10), 10).option("--compare <run-id-a,run-id-b>", "Compare two runs side-by-side (comma-separated IDs, e.g. abc123,def456)").option("--tag-baseline <run-id>", "Mark a run as the performance baseline").option("--analysis <run-id>", "Read and output the analysis report for the specified run (AC5 of Story 17-3)").option("--sprint <sprint>", "Filter StateStore metrics by sprint (e.g. sprint-1)").option("--story <story-key>", "Filter StateStore metrics by story key (e.g. 26-1)").option("--task-type <type>", "Filter StateStore metrics by task type (e.g. dev-story)").option("--since <iso-date>", "Filter StateStore metrics at or after this ISO timestamp").option("--aggregate", "Aggregate StateStore metrics grouped by task_type").action(async (opts) => {
3358
+ program.command("metrics").description("Show historical pipeline run metrics and cross-run comparison").option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--limit <n>", "Number of runs to show (default: 10)", (v) => parseInt(v, 10), 10).option("--compare <run-id-a,run-id-b>", "Compare two runs side-by-side (comma-separated IDs, e.g. abc123,def456)").option("--tag-baseline <run-id>", "Mark a run as the performance baseline").option("--analysis <run-id>", "Read and output the analysis report for the specified run (AC5 of Story 17-3)").option("--sprint <sprint>", "Filter StateStore metrics by sprint (e.g. sprint-1)").option("--story <story-key>", "Filter StateStore metrics by story key (e.g. 26-1)").option("--task-type <type>", "Filter StateStore metrics by task type (e.g. dev-story)").option("--since <iso-date>", "Filter StateStore metrics at or after this ISO timestamp").option("--aggregate", "Aggregate StateStore metrics grouped by task_type").option("--efficiency", "Show telemetry efficiency scores for recent stories").option("--recommendations", "Show all telemetry recommendations across stories").option("--turns <storyKey>", "Show per-turn analysis for a specific story").option("--consumers <storyKey>", "Show consumer stats for a specific story").option("--categories", "Show category stats (optionally scoped by --story <storyKey>)").option("--compare-stories <storyA,storyB>", "Compare efficiency scores of two stories side-by-side (comma-separated keys)").action(async (opts) => {
3146
3359
  const outputFormat = opts.outputFormat === "json" ? "json" : "human";
3147
3360
  let compareIds;
3148
3361
  if (opts.compare !== void 0) {
3149
3362
  const parts = opts.compare.split(",").map((s) => s.trim());
3150
3363
  if (parts.length === 2 && parts[0] && parts[1]) compareIds = [parts[0], parts[1]];
3151
3364
  }
3152
- const exitCode = await runMetricsAction({
3365
+ let compareStoriesIds;
3366
+ if (opts.compareStories !== void 0) {
3367
+ const parts = opts.compareStories.split(",").map((s) => s.trim());
3368
+ if (parts.length === 2 && parts[0] && parts[1]) compareStoriesIds = [parts[0], parts[1]];
3369
+ else {
3370
+ process.stderr.write("Error: --compare-stories requires exactly two comma-separated story keys\n");
3371
+ process.exitCode = 1;
3372
+ return;
3373
+ }
3374
+ }
3375
+ const metricsOpts = {
3153
3376
  outputFormat,
3154
3377
  projectRoot: opts.projectRoot,
3155
3378
  limit: opts.limit,
3156
- compare: compareIds,
3157
- tagBaseline: opts.tagBaseline,
3158
- analysis: opts.analysis,
3159
- sprint: opts.sprint,
3160
- story: opts.story,
3161
- taskType: opts.taskType,
3162
- since: opts.since,
3163
- aggregate: opts.aggregate
3164
- });
3379
+ ...compareIds !== void 0 && { compare: compareIds },
3380
+ ...opts.tagBaseline !== void 0 && { tagBaseline: opts.tagBaseline },
3381
+ ...opts.analysis !== void 0 && { analysis: opts.analysis },
3382
+ ...opts.sprint !== void 0 && { sprint: opts.sprint },
3383
+ ...opts.story !== void 0 && { story: opts.story },
3384
+ ...opts.taskType !== void 0 && { taskType: opts.taskType },
3385
+ ...opts.since !== void 0 && { since: opts.since },
3386
+ ...opts.aggregate !== void 0 && { aggregate: opts.aggregate },
3387
+ ...opts.efficiency !== void 0 && { efficiency: opts.efficiency },
3388
+ ...opts.recommendations !== void 0 && { recommendations: opts.recommendations },
3389
+ ...opts.turns !== void 0 && { turns: opts.turns },
3390
+ ...opts.consumers !== void 0 && { consumers: opts.consumers },
3391
+ ...opts.categories !== void 0 && { categories: opts.categories },
3392
+ ...compareStoriesIds !== void 0 && { compareStories: compareStoriesIds }
3393
+ };
3394
+ const exitCode = await runMetricsAction(metricsOpts);
3165
3395
  process.exitCode = exitCode;
3166
3396
  });
3167
3397
  }
@@ -7226,8 +7456,8 @@ async function createProgram() {
7226
7456
  /** Fire-and-forget startup version check (story 8.3, AC3/AC5) */
7227
7457
  function checkForUpdatesInBackground(currentVersion) {
7228
7458
  if (process.env.SUBSTRATE_NO_UPDATE_CHECK === "1") return;
7229
- import("../upgrade-CImByfkk.js").then(async () => {
7230
- const { createVersionManager } = await import("../version-manager-impl-aL5IemIm.js");
7459
+ import("../upgrade-Ex1ukwsm.js").then(async () => {
7460
+ const { createVersionManager } = await import("../version-manager-impl-Dk3S31y6.js");
7231
7461
  const vm = createVersionManager();
7232
7462
  const result = await vm.checkForUpdates();
7233
7463
  if (result.updateAvailable) {
@@ -79,6 +79,10 @@ const TokenCeilingsSchema = z.object({
79
79
  "test-plan": z.number().int().positive("test-plan token ceiling must be a positive integer").optional(),
80
80
  "test-expansion": z.number().int().positive("test-expansion token ceiling must be a positive integer").optional()
81
81
  });
82
+ const TelemetryConfigSchema = z.object({
83
+ enabled: z.boolean().default(false),
84
+ port: z.number().int().min(1).max(65535).default(4318)
85
+ }).strict();
82
86
  /** Current supported config format version */
83
87
  const CURRENT_CONFIG_FORMAT_VERSION = "1";
84
88
  /** Current supported task graph version */
@@ -94,7 +98,8 @@ const SubstrateConfigSchema = z.object({
94
98
  providers: ProvidersSchema,
95
99
  cost_tracker: CostTrackerConfigSchema.optional(),
96
100
  budget: BudgetConfigSchema.optional(),
97
- token_ceilings: TokenCeilingsSchema.optional()
101
+ token_ceilings: TokenCeilingsSchema.optional(),
102
+ telemetry: TelemetryConfigSchema.optional()
98
103
  }).strict();
99
104
  const PartialProviderConfigSchema = ProviderConfigSchema.partial();
100
105
  const PartialGlobalSettingsSchema = GlobalSettingsSchema.partial();
@@ -109,7 +114,8 @@ const PartialSubstrateConfigSchema = z.object({
109
114
  }).partial().optional(),
110
115
  cost_tracker: CostTrackerConfigSchema.partial().optional(),
111
116
  budget: BudgetConfigSchema.partial().optional(),
112
- token_ceilings: TokenCeilingsSchema.optional()
117
+ token_ceilings: TokenCeilingsSchema.optional(),
118
+ telemetry: TelemetryConfigSchema.partial().optional()
113
119
  }).strict();
114
120
 
115
121
  //#endregion
@@ -241,4 +247,4 @@ const defaultConfigMigrator = new ConfigMigrator();
241
247
 
242
248
  //#endregion
243
249
  export { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema, SUPPORTED_CONFIG_FORMAT_VERSIONS, SUPPORTED_TASK_GRAPH_VERSIONS, SubstrateConfigSchema, defaultConfigMigrator };
244
- //# sourceMappingURL=config-migrator-DSi8KhQC.js.map
250
+ //# sourceMappingURL=config-migrator-CQmBdKeG.js.map
package/dist/index.d.ts CHANGED
@@ -780,6 +780,10 @@ declare const SubstrateConfigSchema: z.ZodObject<{
780
780
  'test-plan': z.ZodOptional<z.ZodNumber>;
781
781
  'test-expansion': z.ZodOptional<z.ZodNumber>;
782
782
  }, z.core.$strip>>;
783
+ telemetry: z.ZodOptional<z.ZodObject<{
784
+ enabled: z.ZodDefault<z.ZodBoolean>;
785
+ port: z.ZodDefault<z.ZodNumber>;
786
+ }, z.core.$strict>>;
783
787
  }, z.core.$strict>;
784
788
  type SubstrateConfig = z.infer<typeof SubstrateConfigSchema>;
785
789
 
@@ -1412,6 +1416,13 @@ interface AdapterOptions {
1412
1416
  apiKey?: string;
1413
1417
  /** Optional maximum agentic turns (passed as --max-turns to Claude CLI) */
1414
1418
  maxTurns?: number;
1419
+ /**
1420
+ * Optional OTLP endpoint URL for telemetry export (Story 27-9).
1421
+ * When set, injects the 5 OTLP env vars into the spawned process so that
1422
+ * Claude Code exports telemetry to the local IngestionServer.
1423
+ * Example: "http://localhost:4318"
1424
+ */
1425
+ otlpEndpoint?: string;
1415
1426
  }
1416
1427
  /**
1417
1428
  * Capabilities reported by an adapter for this CLI agent.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { childLogger, createLogger, logger } from "./logger-D2fS2ccL.js";
2
- import { AdapterRegistry, ClaudeCodeAdapter, CodexCLIAdapter, GeminiCLIAdapter } from "./adapter-registry-PsWhP_1Q.js";
2
+ import { AdapterRegistry, ClaudeCodeAdapter, CodexCLIAdapter, GeminiCLIAdapter } from "./adapter-registry-DHl0W-YB.js";
3
3
  import { AdtError, BudgetExceededError, ConfigError, ConfigIncompatibleFormatError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError, assertDefined, createEventBus, createTuiApp, deepClone, formatDuration, generateId, isPlainObject, isTuiCapable, printNonTtyWarning, sleep, withRetry } from "./helpers-RL22dYtn.js";
4
4
 
5
5
  //#region src/core/di.ts
@@ -1,6 +1,6 @@
1
- import { registerRunCommand, runRunAction } from "./run-DNURadtJ.js";
1
+ import { registerRunCommand, runRunAction } from "./run-DI9s014E.js";
2
2
  import "./logger-D2fS2ccL.js";
3
- import "./config-migrator-DSi8KhQC.js";
3
+ import "./config-migrator-CQmBdKeG.js";
4
4
  import "./helpers-RL22dYtn.js";
5
5
  import "./decisions-Dq4cAA2L.js";
6
6
  import "./operational-Bovj4fS-.js";