substrate-ai 0.5.7 → 0.5.9

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 CHANGED
@@ -1,5 +1,5 @@
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-DwoihdAI.js";
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-DqMnPTZa.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";
@@ -2969,7 +2969,7 @@ async function runSupervisorAction(options, deps = {}) {
2969
2969
  await initSchema(expAdapter);
2970
2970
  const { runRunAction: runPipeline } = await import(
2971
2971
  /* @vite-ignore */
2972
- "../run-H1Xn6_C4.js"
2972
+ "../run-hr7YMtJF.js"
2973
2973
  );
2974
2974
  const runStoryFn = async (opts) => {
2975
2975
  const exitCode = await runPipeline({
@@ -6276,7 +6276,7 @@ const DEFAULT_TIMEOUTS = {
6276
6276
  "planning": 3e5,
6277
6277
  "architecture": 3e5,
6278
6278
  "story-generation": 3e5,
6279
- "create-story": 42e4,
6279
+ "create-story": 6e5,
6280
6280
  "dev-story": 18e5,
6281
6281
  "code-review": 9e5,
6282
6282
  "minor-fixes": 6e5,
@@ -6310,7 +6310,7 @@ const DEFAULT_MAX_TURNS = {
6310
6310
  "dev-story": 75,
6311
6311
  "major-rework": 50,
6312
6312
  "code-review": 25,
6313
- "create-story": 20,
6313
+ "create-story": 30,
6314
6314
  "minor-fixes": 25,
6315
6315
  "analysis-vision": 8,
6316
6316
  "analysis-scope": 10,
@@ -12680,6 +12680,20 @@ var AdapterTelemetryPersistence = class {
12680
12680
  });
12681
12681
  }
12682
12682
  /**
12683
+ * Delete all telemetry data for a story across all 5 telemetry tables.
12684
+ * Used to purge stale data from prior runs before persisting new data.
12685
+ */
12686
+ async purgeStoryTelemetry(storyKey) {
12687
+ await this._adapter.transaction(async (adapter) => {
12688
+ await adapter.query("DELETE FROM turn_analysis WHERE story_key = ?", [storyKey]);
12689
+ await adapter.query("DELETE FROM efficiency_scores WHERE story_key = ?", [storyKey]);
12690
+ await adapter.query("DELETE FROM recommendations WHERE story_key = ?", [storyKey]);
12691
+ await adapter.query("DELETE FROM category_stats WHERE story_key = ?", [storyKey]);
12692
+ await adapter.query("DELETE FROM consumer_stats WHERE story_key = ?", [storyKey]);
12693
+ });
12694
+ logger$9.debug({ storyKey }, "Purged stale telemetry data for story");
12695
+ }
12696
+ /**
12683
12697
  * Record a named span with arbitrary attributes.
12684
12698
  * Currently logs the span at debug level; no DB persistence.
12685
12699
  */
@@ -12749,6 +12763,9 @@ var TelemetryPersistence = class {
12749
12763
  async getConsumerStats(storyKey) {
12750
12764
  return this._impl.getConsumerStats(storyKey);
12751
12765
  }
12766
+ async purgeStoryTelemetry(storyKey) {
12767
+ return this._impl.purgeStoryTelemetry(storyKey);
12768
+ }
12752
12769
  /**
12753
12770
  * Record a named span with arbitrary attributes.
12754
12771
  * Currently logs the span at debug level; no DB persistence.
@@ -13038,32 +13055,42 @@ var IngestionServer = class {
13038
13055
  };
13039
13056
  }
13040
13057
  /**
13041
- * Extract the substrate.story_key attribute from a raw OTLP payload body.
13042
- * Looks in resourceSpans[].resource.attributes and resourceLogs[].resource.attributes.
13058
+ * Substrate resource attributes extracted from an OTLP payload.
13059
+ * These are set by ClaudeCodeAdapter via OTEL_RESOURCE_ATTRIBUTES env var.
13043
13060
  */
13044
- _extractStoryKeyFromPayload(body) {
13045
- if (!body || typeof body !== "object") return void 0;
13061
+ _extractSubstrateAttributes(body) {
13062
+ if (!body || typeof body !== "object") return {};
13046
13063
  const payload = body;
13047
13064
  const extractFromResources = (resources) => {
13048
- if (!Array.isArray(resources)) return void 0;
13065
+ const result = {};
13066
+ if (!Array.isArray(resources)) return result;
13049
13067
  for (const entry of resources) {
13050
13068
  if (!entry || typeof entry !== "object") continue;
13051
13069
  const resource = entry.resource;
13052
13070
  if (!resource || typeof resource !== "object") continue;
13053
- const attrs = resource.attributes;
13054
- if (!Array.isArray(attrs)) continue;
13055
- for (const attr of attrs) {
13071
+ const attrs$1 = resource.attributes;
13072
+ if (!Array.isArray(attrs$1)) continue;
13073
+ for (const attr of attrs$1) {
13056
13074
  if (!attr || typeof attr !== "object") continue;
13057
13075
  const a = attr;
13058
- if (a.key === "substrate.story_key") {
13076
+ const key = a.key;
13077
+ if (key !== void 0 && key.startsWith("substrate.")) {
13059
13078
  const val = a.value;
13060
- if (val && typeof val.stringValue === "string") return val.stringValue;
13079
+ if (val && typeof val.stringValue === "string") result[key] = val.stringValue;
13061
13080
  }
13062
13081
  }
13063
13082
  }
13064
- return void 0;
13083
+ return result;
13084
+ };
13085
+ const attrs = {
13086
+ ...extractFromResources(payload.resourceSpans),
13087
+ ...extractFromResources(payload.resourceLogs)
13088
+ };
13089
+ return {
13090
+ storyKey: attrs["substrate.story_key"],
13091
+ taskType: attrs["substrate.task_type"],
13092
+ dispatchId: attrs["substrate.dispatch_id"]
13065
13093
  };
13066
- return extractFromResources(payload.resourceSpans) ?? extractFromResources(payload.resourceLogs);
13067
13094
  }
13068
13095
  _handleRequest(req, res) {
13069
13096
  if (req.url === "/health" && req.method === "GET") {
@@ -13087,12 +13114,19 @@ var IngestionServer = class {
13087
13114
  if (this._buffer !== void 0) try {
13088
13115
  const body = JSON.parse(bodyStr);
13089
13116
  const source = detectSource(body);
13090
- const storyKey = this._extractStoryKeyFromPayload(body);
13091
- const dispatchContext = storyKey !== void 0 ? this._activeDispatches.get(storyKey) : void 0;
13117
+ const { storyKey, taskType, dispatchId } = this._extractSubstrateAttributes(body);
13118
+ let dispatchContext;
13119
+ if (taskType !== void 0 && dispatchId !== void 0) dispatchContext = {
13120
+ taskType,
13121
+ phase: taskType,
13122
+ dispatchId
13123
+ };
13124
+ else if (storyKey !== void 0) dispatchContext = this._activeDispatches.get(storyKey);
13092
13125
  const payload = {
13093
13126
  body,
13094
13127
  source,
13095
13128
  receivedAt: Date.now(),
13129
+ storyKey,
13096
13130
  ...dispatchContext !== void 0 && { dispatchContext }
13097
13131
  };
13098
13132
  this._buffer.push(payload);
@@ -13723,7 +13757,7 @@ var ConsumerAnalyzer = class {
13723
13757
 
13724
13758
  //#endregion
13725
13759
  //#region src/modules/telemetry/recommender.ts
13726
- var Recommender = class {
13760
+ var Recommender = class Recommender {
13727
13761
  _logger;
13728
13762
  constructor(logger$27) {
13729
13763
  this._logger = logger$27;
@@ -13781,8 +13815,21 @@ var Recommender = class {
13781
13815
  return spans.reduce((sum, s) => sum + s.inputTokens + s.outputTokens, 0);
13782
13816
  }
13783
13817
  /**
13784
- * Identify top 3 token consumers (by inputTokens + outputTokens) where pct >5%.
13785
- * Severity based on the consumer's percentage of total tokens.
13818
+ * Minimum absolute token count for a consumer to be flagged.
13819
+ * Below this, the consumer is too small to be actionable regardless of percentage.
13820
+ */
13821
+ static MIN_SIGNIFICANT_TOKENS = 1e4;
13822
+ /**
13823
+ * Identify top 3 token consumers (by inputTokens + outputTokens) where pct >5%
13824
+ * AND absolute tokens exceed MIN_SIGNIFICANT_TOKENS.
13825
+ *
13826
+ * Filters out model-only consumers (empty toolName, format "model|") since those
13827
+ * just indicate which model ran — not an actionable optimization target.
13828
+ *
13829
+ * Severity factors both percentage share and absolute magnitude:
13830
+ * - percentage-based tier via _assignSeverity()
13831
+ * - capped at 'warning' when absolute tokens < 50,000
13832
+ * - capped at 'info' when absolute tokens < 20,000
13786
13833
  */
13787
13834
  _runBiggestConsumers(ctx) {
13788
13835
  const { consumers, storyKey, sprintId, generatedAt } = ctx;
@@ -13791,11 +13838,18 @@ var Recommender = class {
13791
13838
  if (grandTotal === 0) return [];
13792
13839
  const sorted = [...consumers].sort((a, b) => b.totalTokens - a.totalTokens);
13793
13840
  const top3 = sorted.slice(0, 3).filter((c) => {
13794
- return c.percentage > 5;
13841
+ if (c.percentage <= 5) return false;
13842
+ if (c.totalTokens < Recommender.MIN_SIGNIFICANT_TOKENS) return false;
13843
+ if (c.consumerKey.endsWith("|") && !c.consumerKey.includes("|", 0)) return false;
13844
+ const parts = c.consumerKey.split("|");
13845
+ if (parts.length === 2 && parts[1] === "") return false;
13846
+ return true;
13795
13847
  });
13796
13848
  return top3.map((consumer, index) => {
13797
13849
  const pct = consumer.percentage;
13798
- const severity = this._assignSeverity(pct);
13850
+ let severity = this._assignSeverity(pct);
13851
+ if (consumer.totalTokens < 2e4 && severity !== "info") severity = "info";
13852
+ else if (consumer.totalTokens < 5e4 && severity === "critical") severity = "warning";
13799
13853
  const actionTarget = consumer.consumerKey;
13800
13854
  const id = this._makeId("biggest_consumers", storyKey, actionTarget, index);
13801
13855
  return {
@@ -14815,6 +14869,8 @@ var TelemetryPipeline = class {
14815
14869
  _efficiencyScorer;
14816
14870
  _recommender;
14817
14871
  _persistence;
14872
+ /** Stories that have had stale telemetry purged this pipeline lifetime. */
14873
+ _purgedStories = new Set();
14818
14874
  constructor(deps) {
14819
14875
  this._normalizer = deps.normalizer;
14820
14876
  this._turnAnalyzer = deps.turnAnalyzer;
@@ -15054,6 +15110,13 @@ var TelemetryPipeline = class {
15054
15110
  */
15055
15111
  async _persistStoryData(storyKey, data) {
15056
15112
  const { turns, efficiencyScore, categoryStats, consumerStats, recommendations, dispatchScores } = data;
15113
+ if (!this._purgedStories.has(storyKey)) {
15114
+ this._purgedStories.add(storyKey);
15115
+ await this._persistence.purgeStoryTelemetry(storyKey).catch((err) => logger$7.warn({
15116
+ err,
15117
+ storyKey
15118
+ }, "Failed to purge stale telemetry — continuing with persist"));
15119
+ }
15057
15120
  await Promise.all([
15058
15121
  turns.length > 0 ? this._persistence.storeTurnAnalysis(storyKey, turns).catch((err) => logger$7.warn({
15059
15122
  err,
@@ -22642,4 +22705,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
22642
22705
 
22643
22706
  //#endregion
22644
22707
  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 };
22645
- //# sourceMappingURL=run-DwoihdAI.js.map
22708
+ //# sourceMappingURL=run-DqMnPTZa.js.map
@@ -1,4 +1,4 @@
1
- import { registerRunCommand, runRunAction } from "./run-DwoihdAI.js";
1
+ import { registerRunCommand, runRunAction } from "./run-DqMnPTZa.js";
2
2
  import "./logger-D2fS2ccL.js";
3
3
  import "./config-migrator-DtZW1maj.js";
4
4
  import "./helpers-BihqWgVe.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",