substrate-ai 0.4.6 → 0.4.7
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 { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, DoltClient, DoltNotInstalled, DoltRepoMapMetaRepository, DoltSymbolRepository, FileStateStore, GitClient, GrammarLoader, IngestionServer, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SUBSTRATE_OWNED_SETTINGS_KEYS, SymbolParser, 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-
|
|
2
|
+
import { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, DoltClient, DoltNotInstalled, DoltRepoMapMetaRepository, DoltSymbolRepository, FileStateStore, GitClient, GrammarLoader, IngestionServer, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SUBSTRATE_OWNED_SETTINGS_KEYS, SymbolParser, 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-fWZd8vvq.js";
|
|
3
3
|
import { createLogger } from "../logger-D2fS2ccL.js";
|
|
4
4
|
import { AdapterRegistry } from "../adapter-registry-Cd-7lG5v.js";
|
|
5
5
|
import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema } from "../config-migrator-DtZW1maj.js";
|
|
@@ -2709,7 +2709,7 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
2709
2709
|
const expDb = expDbWrapper.db;
|
|
2710
2710
|
const { runRunAction: runPipeline } = await import(
|
|
2711
2711
|
/* @vite-ignore */
|
|
2712
|
-
"../run-
|
|
2712
|
+
"../run-RerrMpM1.js"
|
|
2713
2713
|
);
|
|
2714
2714
|
const runStoryFn = async (opts) => {
|
|
2715
2715
|
const exitCode = await runPipeline({
|
|
@@ -12632,6 +12632,7 @@ var Categorizer = class {
|
|
|
12632
12632
|
if (lower.includes("conversation") || lower.includes("history") || lower.includes("chat")) return "conversation_history";
|
|
12633
12633
|
if (lower.includes("user") || lower.includes("human")) return "user_prompts";
|
|
12634
12634
|
if (toolName !== void 0 && toolName.length > 0) return "tool_outputs";
|
|
12635
|
+
if (lower === "log_turn") return "conversation_history";
|
|
12635
12636
|
return "other";
|
|
12636
12637
|
}
|
|
12637
12638
|
/**
|
|
@@ -12665,6 +12666,75 @@ var Categorizer = class {
|
|
|
12665
12666
|
return "stable";
|
|
12666
12667
|
}
|
|
12667
12668
|
/**
|
|
12669
|
+
* Compute per-category token statistics from TurnAnalysis data (not raw spans).
|
|
12670
|
+
*
|
|
12671
|
+
* All six SemanticCategory values are always present in the result (zero-token
|
|
12672
|
+
* categories are included with totalTokens: 0). Results are sorted by
|
|
12673
|
+
* totalTokens descending.
|
|
12674
|
+
*
|
|
12675
|
+
* Trend is computed by comparing first-half vs second-half turn token attribution
|
|
12676
|
+
* for each category, using the same 1.2×/0.8× thresholds as computeTrend().
|
|
12677
|
+
*
|
|
12678
|
+
* @param turns - TurnAnalysis[] for the story
|
|
12679
|
+
*/
|
|
12680
|
+
computeCategoryStatsFromTurns(turns) {
|
|
12681
|
+
if (turns.length === 0) return ALL_CATEGORIES.map((category) => ({
|
|
12682
|
+
category,
|
|
12683
|
+
totalTokens: 0,
|
|
12684
|
+
percentage: 0,
|
|
12685
|
+
eventCount: 0,
|
|
12686
|
+
avgTokensPerEvent: 0,
|
|
12687
|
+
trend: "stable"
|
|
12688
|
+
}));
|
|
12689
|
+
const grandTotal = turns.reduce((sum, t) => sum + t.inputTokens + t.outputTokens, 0);
|
|
12690
|
+
const buckets = new Map();
|
|
12691
|
+
for (const cat of ALL_CATEGORIES) buckets.set(cat, {
|
|
12692
|
+
total: 0,
|
|
12693
|
+
count: 0,
|
|
12694
|
+
first: 0,
|
|
12695
|
+
second: 0
|
|
12696
|
+
});
|
|
12697
|
+
const half = Math.floor(turns.length / 2);
|
|
12698
|
+
for (let i = 0; i < turns.length; i++) {
|
|
12699
|
+
const turn = turns[i];
|
|
12700
|
+
const cat = this.classify(turn.name, turn.toolName);
|
|
12701
|
+
const bucket = buckets.get(cat);
|
|
12702
|
+
const tokens = turn.inputTokens + turn.outputTokens;
|
|
12703
|
+
bucket.total += tokens;
|
|
12704
|
+
bucket.count += 1;
|
|
12705
|
+
if (i < half) bucket.first += tokens;
|
|
12706
|
+
else bucket.second += tokens;
|
|
12707
|
+
}
|
|
12708
|
+
const results = ALL_CATEGORIES.map((category) => {
|
|
12709
|
+
const bucket = buckets.get(category);
|
|
12710
|
+
const totalTokens = bucket.total;
|
|
12711
|
+
const eventCount = bucket.count;
|
|
12712
|
+
const percentage = grandTotal > 0 ? Math.round(totalTokens / grandTotal * 100 * 1e3) / 1e3 : 0;
|
|
12713
|
+
const avgTokensPerEvent = eventCount > 0 ? totalTokens / eventCount : 0;
|
|
12714
|
+
let trend = "stable";
|
|
12715
|
+
if (turns.length >= 2) {
|
|
12716
|
+
const { first, second } = bucket;
|
|
12717
|
+
if (first === 0 && second === 0) trend = "stable";
|
|
12718
|
+
else if (first === 0) trend = "growing";
|
|
12719
|
+
else if (second > 1.2 * first) trend = "growing";
|
|
12720
|
+
else if (second < .8 * first) trend = "shrinking";
|
|
12721
|
+
}
|
|
12722
|
+
return {
|
|
12723
|
+
category,
|
|
12724
|
+
totalTokens,
|
|
12725
|
+
percentage,
|
|
12726
|
+
eventCount,
|
|
12727
|
+
avgTokensPerEvent,
|
|
12728
|
+
trend
|
|
12729
|
+
};
|
|
12730
|
+
});
|
|
12731
|
+
this._logger.debug({
|
|
12732
|
+
categories: results.length,
|
|
12733
|
+
grandTotal
|
|
12734
|
+
}, "Computed category stats from turns");
|
|
12735
|
+
return results.sort((a, b) => b.totalTokens - a.totalTokens);
|
|
12736
|
+
}
|
|
12737
|
+
/**
|
|
12668
12738
|
* Compute per-category token statistics for a complete set of spans.
|
|
12669
12739
|
*
|
|
12670
12740
|
* All six SemanticCategory values are always present in the result (zero-token
|
|
@@ -12794,6 +12864,54 @@ var ConsumerAnalyzer = class {
|
|
|
12794
12864
|
return results.sort((a, b) => b.totalTokens - a.totalTokens);
|
|
12795
12865
|
}
|
|
12796
12866
|
/**
|
|
12867
|
+
* Group turns by consumer key (model|toolName), rank by totalTokens descending,
|
|
12868
|
+
* and return ConsumerStats for each non-zero-token group.
|
|
12869
|
+
*
|
|
12870
|
+
* @param turns - All TurnAnalysis records for the story
|
|
12871
|
+
*/
|
|
12872
|
+
analyzeFromTurns(turns) {
|
|
12873
|
+
if (turns.length === 0) return [];
|
|
12874
|
+
const grandTotal = turns.reduce((sum, t) => sum + t.inputTokens + t.outputTokens, 0);
|
|
12875
|
+
const groups = new Map();
|
|
12876
|
+
for (const turn of turns) {
|
|
12877
|
+
const key = this._buildConsumerKeyFromTurn(turn);
|
|
12878
|
+
const existing = groups.get(key);
|
|
12879
|
+
if (existing !== void 0) existing.push(turn);
|
|
12880
|
+
else groups.set(key, [turn]);
|
|
12881
|
+
}
|
|
12882
|
+
const results = [];
|
|
12883
|
+
for (const [consumerKey, groupTurns] of groups) {
|
|
12884
|
+
const totalTokens = groupTurns.reduce((sum, t) => sum + t.inputTokens + t.outputTokens, 0);
|
|
12885
|
+
if (totalTokens === 0) continue;
|
|
12886
|
+
const percentage = grandTotal > 0 ? Math.round(totalTokens / grandTotal * 100 * 1e3) / 1e3 : 0;
|
|
12887
|
+
const eventCount = groupTurns.length;
|
|
12888
|
+
const firstTurn = groupTurns[0];
|
|
12889
|
+
const category = this._categorizer.classify(firstTurn.name, firstTurn.toolName);
|
|
12890
|
+
const sorted = groupTurns.slice().sort((a, b) => b.inputTokens + b.outputTokens - (a.inputTokens + a.outputTokens));
|
|
12891
|
+
const topInvocations = sorted.slice(0, 20).map((t) => ({
|
|
12892
|
+
spanId: t.spanId,
|
|
12893
|
+
name: t.name,
|
|
12894
|
+
toolName: t.toolName,
|
|
12895
|
+
totalTokens: t.inputTokens + t.outputTokens,
|
|
12896
|
+
inputTokens: t.inputTokens,
|
|
12897
|
+
outputTokens: t.outputTokens
|
|
12898
|
+
}));
|
|
12899
|
+
results.push({
|
|
12900
|
+
consumerKey,
|
|
12901
|
+
category,
|
|
12902
|
+
totalTokens,
|
|
12903
|
+
percentage,
|
|
12904
|
+
eventCount,
|
|
12905
|
+
topInvocations
|
|
12906
|
+
});
|
|
12907
|
+
}
|
|
12908
|
+
this._logger.debug({
|
|
12909
|
+
consumers: results.length,
|
|
12910
|
+
grandTotal
|
|
12911
|
+
}, "Computed consumer stats from turns");
|
|
12912
|
+
return results.sort((a, b) => b.totalTokens - a.totalTokens);
|
|
12913
|
+
}
|
|
12914
|
+
/**
|
|
12797
12915
|
* Build a stable, collision-resistant consumer key from a span.
|
|
12798
12916
|
* Format: `operationName|toolName` (tool part is empty string if absent).
|
|
12799
12917
|
*/
|
|
@@ -12803,6 +12921,15 @@ var ConsumerAnalyzer = class {
|
|
|
12803
12921
|
return `${operationPart}|${toolPart}`;
|
|
12804
12922
|
}
|
|
12805
12923
|
/**
|
|
12924
|
+
* Build a stable consumer key from a turn.
|
|
12925
|
+
* Format: `model|toolName` (tool part is empty string if absent).
|
|
12926
|
+
*/
|
|
12927
|
+
_buildConsumerKeyFromTurn(turn) {
|
|
12928
|
+
const modelPart = (turn.model ?? "unknown").slice(0, 200);
|
|
12929
|
+
const toolPart = (turn.toolName ?? "").slice(0, 100);
|
|
12930
|
+
return `${modelPart}|${toolPart}`;
|
|
12931
|
+
}
|
|
12932
|
+
/**
|
|
12806
12933
|
* Extract a tool name from span attributes, checking three known attribute keys
|
|
12807
12934
|
* in priority order.
|
|
12808
12935
|
*/
|
|
@@ -13200,6 +13327,99 @@ var TurnAnalyzer = class {
|
|
|
13200
13327
|
}
|
|
13201
13328
|
};
|
|
13202
13329
|
|
|
13330
|
+
//#endregion
|
|
13331
|
+
//#region src/modules/telemetry/log-turn-analyzer.ts
|
|
13332
|
+
var LogTurnAnalyzer = class {
|
|
13333
|
+
_logger;
|
|
13334
|
+
constructor(logger$27) {
|
|
13335
|
+
this._logger = logger$27;
|
|
13336
|
+
}
|
|
13337
|
+
/**
|
|
13338
|
+
* Analyze a list of NormalizedLog records and produce TurnAnalysis[].
|
|
13339
|
+
*
|
|
13340
|
+
* Returns an empty array immediately when logs is empty or on any error.
|
|
13341
|
+
*
|
|
13342
|
+
* @param logs - All log records for a story
|
|
13343
|
+
*/
|
|
13344
|
+
analyze(logs) {
|
|
13345
|
+
try {
|
|
13346
|
+
if (!Array.isArray(logs) || logs.length === 0) return [];
|
|
13347
|
+
const validLogs = logs.filter((log$2) => log$2 != null && typeof log$2 === "object" && ((log$2.inputTokens ?? 0) > 0 || (log$2.outputTokens ?? 0) > 0));
|
|
13348
|
+
if (validLogs.length === 0) {
|
|
13349
|
+
this._logger.debug("LogTurnAnalyzer: no LLM logs with tokens to analyze");
|
|
13350
|
+
return [];
|
|
13351
|
+
}
|
|
13352
|
+
const grouped = new Map();
|
|
13353
|
+
for (const log$2 of validLogs) {
|
|
13354
|
+
const key = log$2.traceId != null && log$2.spanId != null ? `${log$2.traceId}:${log$2.spanId}` : log$2.logId;
|
|
13355
|
+
const group = grouped.get(key) ?? [];
|
|
13356
|
+
group.push(log$2);
|
|
13357
|
+
grouped.set(key, group);
|
|
13358
|
+
}
|
|
13359
|
+
const merged = [];
|
|
13360
|
+
for (const group of grouped.values()) {
|
|
13361
|
+
const sorted = [...group].sort((a, b) => a.timestamp - b.timestamp);
|
|
13362
|
+
const representative = sorted[0];
|
|
13363
|
+
let inputTokens = 0;
|
|
13364
|
+
let outputTokens = 0;
|
|
13365
|
+
let cacheReadTokens = 0;
|
|
13366
|
+
let costUsd = 0;
|
|
13367
|
+
for (const log$2 of group) {
|
|
13368
|
+
inputTokens += log$2.inputTokens ?? 0;
|
|
13369
|
+
outputTokens += log$2.outputTokens ?? 0;
|
|
13370
|
+
cacheReadTokens += log$2.cacheReadTokens ?? 0;
|
|
13371
|
+
costUsd += log$2.costUsd ?? 0;
|
|
13372
|
+
}
|
|
13373
|
+
merged.push({
|
|
13374
|
+
representative,
|
|
13375
|
+
inputTokens,
|
|
13376
|
+
outputTokens,
|
|
13377
|
+
cacheReadTokens,
|
|
13378
|
+
costUsd
|
|
13379
|
+
});
|
|
13380
|
+
}
|
|
13381
|
+
merged.sort((a, b) => a.representative.timestamp - b.representative.timestamp);
|
|
13382
|
+
let runningContext = 0;
|
|
13383
|
+
const turns = merged.map(({ representative: log$2, inputTokens, outputTokens, cacheReadTokens, costUsd }, idx) => {
|
|
13384
|
+
const prevContext = runningContext;
|
|
13385
|
+
runningContext += inputTokens;
|
|
13386
|
+
const freshTokens = inputTokens - cacheReadTokens;
|
|
13387
|
+
const cacheHitRate = inputTokens > 0 ? cacheReadTokens / inputTokens : 0;
|
|
13388
|
+
return {
|
|
13389
|
+
spanId: log$2.spanId ?? log$2.logId,
|
|
13390
|
+
turnNumber: idx + 1,
|
|
13391
|
+
name: log$2.eventName ?? "log_turn",
|
|
13392
|
+
timestamp: log$2.timestamp,
|
|
13393
|
+
source: "claude-code",
|
|
13394
|
+
model: log$2.model,
|
|
13395
|
+
inputTokens,
|
|
13396
|
+
outputTokens,
|
|
13397
|
+
cacheReadTokens,
|
|
13398
|
+
freshTokens,
|
|
13399
|
+
cacheHitRate,
|
|
13400
|
+
costUsd,
|
|
13401
|
+
durationMs: 0,
|
|
13402
|
+
contextSize: runningContext,
|
|
13403
|
+
contextDelta: runningContext - prevContext,
|
|
13404
|
+
toolName: log$2.toolName,
|
|
13405
|
+
isContextSpike: false,
|
|
13406
|
+
childSpans: []
|
|
13407
|
+
};
|
|
13408
|
+
});
|
|
13409
|
+
const avg = turns.reduce((sum, t) => sum + t.inputTokens, 0) / turns.length;
|
|
13410
|
+
for (const turn of turns) turn.isContextSpike = avg > 0 && turn.inputTokens > 2 * avg;
|
|
13411
|
+
this._logger.debug({
|
|
13412
|
+
turnCount: turns.length,
|
|
13413
|
+
avg
|
|
13414
|
+
}, "LogTurnAnalyzer.analyze complete");
|
|
13415
|
+
return turns;
|
|
13416
|
+
} catch (err) {
|
|
13417
|
+
this._logger.warn({ err }, "LogTurnAnalyzer.analyze failed — returning empty array");
|
|
13418
|
+
return [];
|
|
13419
|
+
}
|
|
13420
|
+
}
|
|
13421
|
+
};
|
|
13422
|
+
|
|
13203
13423
|
//#endregion
|
|
13204
13424
|
//#region src/modules/telemetry/cost-table.ts
|
|
13205
13425
|
/**
|
|
@@ -13764,6 +13984,7 @@ const logger$6 = createLogger("telemetry:pipeline");
|
|
|
13764
13984
|
var TelemetryPipeline = class {
|
|
13765
13985
|
_normalizer;
|
|
13766
13986
|
_turnAnalyzer;
|
|
13987
|
+
_logTurnAnalyzer;
|
|
13767
13988
|
_categorizer;
|
|
13768
13989
|
_consumerAnalyzer;
|
|
13769
13990
|
_efficiencyScorer;
|
|
@@ -13772,6 +13993,7 @@ var TelemetryPipeline = class {
|
|
|
13772
13993
|
constructor(deps) {
|
|
13773
13994
|
this._normalizer = deps.normalizer;
|
|
13774
13995
|
this._turnAnalyzer = deps.turnAnalyzer;
|
|
13996
|
+
this._logTurnAnalyzer = deps.logTurnAnalyzer;
|
|
13775
13997
|
this._categorizer = deps.categorizer;
|
|
13776
13998
|
this._consumerAnalyzer = deps.consumerAnalyzer;
|
|
13777
13999
|
this._efficiencyScorer = deps.efficiencyScorer;
|
|
@@ -13781,8 +14003,14 @@ var TelemetryPipeline = class {
|
|
|
13781
14003
|
/**
|
|
13782
14004
|
* Process a batch of raw OTLP payloads through the full analysis pipeline.
|
|
13783
14005
|
*
|
|
13784
|
-
* Each payload is normalized independently. Spans are
|
|
13785
|
-
* for per-story analysis. Items that fail normalization are skipped
|
|
14006
|
+
* Each payload is normalized independently. Spans and logs are grouped by
|
|
14007
|
+
* storyKey for per-story analysis. Items that fail normalization are skipped
|
|
14008
|
+
* with a warning.
|
|
14009
|
+
*
|
|
14010
|
+
* Dual-track analysis (Story 27-15):
|
|
14011
|
+
* - Span-derived turns via TurnAnalyzer
|
|
14012
|
+
* - Log-derived turns via LogTurnAnalyzer
|
|
14013
|
+
* - Merged (deduplicated by spanId) before downstream analysis
|
|
13786
14014
|
*/
|
|
13787
14015
|
async processBatch(items) {
|
|
13788
14016
|
if (items.length === 0) return;
|
|
@@ -13807,25 +14035,46 @@ var TelemetryPipeline = class {
|
|
|
13807
14035
|
spans: allSpans.length,
|
|
13808
14036
|
logs: allLogs.length
|
|
13809
14037
|
}, "TelemetryPipeline: normalized batch");
|
|
13810
|
-
if (allSpans.length === 0) {
|
|
13811
|
-
logger$6.debug("TelemetryPipeline: no spans normalized from batch");
|
|
14038
|
+
if (allSpans.length === 0 && allLogs.length === 0) {
|
|
14039
|
+
logger$6.debug("TelemetryPipeline: no spans or logs normalized from batch");
|
|
13812
14040
|
return;
|
|
13813
14041
|
}
|
|
13814
|
-
const spansByStory = new Map();
|
|
13815
14042
|
const unknownStoryKey = "__unknown__";
|
|
14043
|
+
const spansByStory = new Map();
|
|
13816
14044
|
for (const span of allSpans) {
|
|
13817
14045
|
const key = span.storyKey ?? unknownStoryKey;
|
|
13818
14046
|
const existing = spansByStory.get(key);
|
|
13819
14047
|
if (existing !== void 0) existing.push(span);
|
|
13820
14048
|
else spansByStory.set(key, [span]);
|
|
13821
14049
|
}
|
|
13822
|
-
|
|
14050
|
+
const logsByStory = new Map();
|
|
14051
|
+
for (const log$2 of allLogs) {
|
|
14052
|
+
const key = log$2.storyKey ?? unknownStoryKey;
|
|
14053
|
+
const existing = logsByStory.get(key);
|
|
14054
|
+
if (existing !== void 0) existing.push(log$2);
|
|
14055
|
+
else logsByStory.set(key, [log$2]);
|
|
14056
|
+
}
|
|
14057
|
+
const allStoryKeys = new Set();
|
|
14058
|
+
for (const key of spansByStory.keys()) allStoryKeys.add(key);
|
|
14059
|
+
for (const key of logsByStory.keys()) allStoryKeys.add(key);
|
|
14060
|
+
for (const storyKey of allStoryKeys) {
|
|
13823
14061
|
if (storyKey === unknownStoryKey) {
|
|
13824
|
-
|
|
14062
|
+
const spanCount = spansByStory.get(unknownStoryKey)?.length ?? 0;
|
|
14063
|
+
const logCount = logsByStory.get(unknownStoryKey)?.length ?? 0;
|
|
14064
|
+
logger$6.debug({
|
|
14065
|
+
spanCount,
|
|
14066
|
+
logCount
|
|
14067
|
+
}, "TelemetryPipeline: data without storyKey — skipping analysis");
|
|
13825
14068
|
continue;
|
|
13826
14069
|
}
|
|
13827
14070
|
try {
|
|
13828
|
-
|
|
14071
|
+
const spans = spansByStory.get(storyKey) ?? [];
|
|
14072
|
+
const logs = logsByStory.get(storyKey) ?? [];
|
|
14073
|
+
const spanTurns = spans.length > 0 ? this._turnAnalyzer.analyze(spans) : [];
|
|
14074
|
+
const logTurns = logs.length > 0 ? this._logTurnAnalyzer.analyze(logs) : [];
|
|
14075
|
+
const mergedTurns = this._mergeTurns(spanTurns, logTurns);
|
|
14076
|
+
if (spans.length > 0) await this._processStory(storyKey, spans, mergedTurns);
|
|
14077
|
+
else await this._processStoryFromTurns(storyKey, mergedTurns);
|
|
13829
14078
|
} catch (err) {
|
|
13830
14079
|
logger$6.warn({
|
|
13831
14080
|
err,
|
|
@@ -13833,10 +14082,29 @@ var TelemetryPipeline = class {
|
|
|
13833
14082
|
}, "TelemetryPipeline: story processing failed — skipping");
|
|
13834
14083
|
}
|
|
13835
14084
|
}
|
|
13836
|
-
logger$6.debug({ storyCount:
|
|
14085
|
+
logger$6.debug({ storyCount: allStoryKeys.size }, "TelemetryPipeline.processBatch complete");
|
|
13837
14086
|
}
|
|
13838
|
-
|
|
13839
|
-
|
|
14087
|
+
/**
|
|
14088
|
+
* Merge span-derived and log-derived turns, deduplicating by spanId.
|
|
14089
|
+
* When a span and a log share the same spanId, the span-derived turn is preferred
|
|
14090
|
+
* (richer data). The merged result is sorted chronologically and renumbered.
|
|
14091
|
+
*/
|
|
14092
|
+
_mergeTurns(spanTurns, logTurns) {
|
|
14093
|
+
if (logTurns.length === 0) return spanTurns;
|
|
14094
|
+
if (spanTurns.length === 0) return logTurns;
|
|
14095
|
+
const spanTurnIds = new Set(spanTurns.map((t) => t.spanId));
|
|
14096
|
+
const uniqueLogTurns = logTurns.filter((t) => !spanTurnIds.has(t.spanId));
|
|
14097
|
+
return [...spanTurns, ...uniqueLogTurns].sort((a, b) => a.timestamp - b.timestamp).map((t, i) => ({
|
|
14098
|
+
...t,
|
|
14099
|
+
turnNumber: i + 1
|
|
14100
|
+
}));
|
|
14101
|
+
}
|
|
14102
|
+
/**
|
|
14103
|
+
* Full span-based analysis path (unchanged behavior when no logs present — AC4).
|
|
14104
|
+
* When mergedTurns is provided, uses those instead of computing from spans alone.
|
|
14105
|
+
*/
|
|
14106
|
+
async _processStory(storyKey, spans, mergedTurns) {
|
|
14107
|
+
const turns = mergedTurns;
|
|
13840
14108
|
const categories = this._categorizer.computeCategoryStats(spans, turns);
|
|
13841
14109
|
const consumers = this._consumerAnalyzer.analyze(spans);
|
|
13842
14110
|
const efficiencyScore = this._efficiencyScorer.score(storyKey, turns);
|
|
@@ -13880,6 +14148,28 @@ var TelemetryPipeline = class {
|
|
|
13880
14148
|
recommendations: recommendations.length
|
|
13881
14149
|
}, "TelemetryPipeline: story analysis complete");
|
|
13882
14150
|
}
|
|
14151
|
+
/**
|
|
14152
|
+
* Log-only analysis path (AC3, AC6): processes turns from LogTurnAnalyzer
|
|
14153
|
+
* through efficiency scoring and persistence.
|
|
14154
|
+
*
|
|
14155
|
+
* Categorizer and consumer analyzer remain span-only for now (story 27-16).
|
|
14156
|
+
*/
|
|
14157
|
+
async _processStoryFromTurns(storyKey, turns) {
|
|
14158
|
+
if (turns.length === 0) return;
|
|
14159
|
+
const efficiencyScore = this._efficiencyScorer.score(storyKey, turns);
|
|
14160
|
+
await Promise.all([this._persistence.storeTurnAnalysis(storyKey, turns).catch((err) => logger$6.warn({
|
|
14161
|
+
err,
|
|
14162
|
+
storyKey
|
|
14163
|
+
}, "Failed to store turn analysis")), this._persistence.storeEfficiencyScore(efficiencyScore).catch((err) => logger$6.warn({
|
|
14164
|
+
err,
|
|
14165
|
+
storyKey
|
|
14166
|
+
}, "Failed to store efficiency score"))]);
|
|
14167
|
+
logger$6.info({
|
|
14168
|
+
storyKey,
|
|
14169
|
+
turns: turns.length,
|
|
14170
|
+
compositeScore: efficiencyScore.compositeScore
|
|
14171
|
+
}, "TelemetryPipeline: story analysis from turns complete");
|
|
14172
|
+
}
|
|
13883
14173
|
};
|
|
13884
14174
|
|
|
13885
14175
|
//#endregion
|
|
@@ -15130,13 +15420,12 @@ function createImplementationOrchestrator(deps) {
|
|
|
15130
15420
|
}
|
|
15131
15421
|
if (telemetryPersistence !== void 0) try {
|
|
15132
15422
|
const turns = await telemetryPersistence.getTurnAnalysis(storyKey);
|
|
15133
|
-
|
|
15134
|
-
if (spans.length === 0) logger$27.debug({ storyKey }, "No spans for telemetry categorization — skipping");
|
|
15423
|
+
if (turns.length === 0) logger$27.debug({ storyKey }, "No turn analysis data for telemetry categorization — skipping");
|
|
15135
15424
|
else {
|
|
15136
15425
|
const categorizer = new Categorizer(logger$27);
|
|
15137
15426
|
const consumerAnalyzer = new ConsumerAnalyzer(categorizer, logger$27);
|
|
15138
|
-
const categoryStats = categorizer.
|
|
15139
|
-
const consumerStats = consumerAnalyzer.
|
|
15427
|
+
const categoryStats = categorizer.computeCategoryStatsFromTurns(turns);
|
|
15428
|
+
const consumerStats = consumerAnalyzer.analyzeFromTurns(turns);
|
|
15140
15429
|
await telemetryPersistence.storeCategoryStats(storyKey, categoryStats);
|
|
15141
15430
|
await telemetryPersistence.storeConsumerStats(storyKey, consumerStats);
|
|
15142
15431
|
const growingCount = categoryStats.filter((c) => c.trend === "growing").length;
|
|
@@ -15617,6 +15906,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
15617
15906
|
const telemetryPipeline = new TelemetryPipeline({
|
|
15618
15907
|
normalizer: new TelemetryNormalizer(pipelineLogger),
|
|
15619
15908
|
turnAnalyzer: new TurnAnalyzer(pipelineLogger),
|
|
15909
|
+
logTurnAnalyzer: new LogTurnAnalyzer(pipelineLogger),
|
|
15620
15910
|
categorizer: new Categorizer(pipelineLogger),
|
|
15621
15911
|
consumerAnalyzer: new ConsumerAnalyzer(new Categorizer(pipelineLogger), pipelineLogger),
|
|
15622
15912
|
efficiencyScorer: new EfficiencyScorer(pipelineLogger),
|
|
@@ -21136,4 +21426,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
21136
21426
|
|
|
21137
21427
|
//#endregion
|
|
21138
21428
|
export { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, DoltClient, DoltNotInstalled, DoltRepoMapMetaRepository, DoltSymbolRepository, FileStateStore, GitClient, GrammarLoader, IngestionServer, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SUBSTRATE_OWNED_SETTINGS_KEYS, SymbolParser, 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, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
|
|
21139
|
-
//# sourceMappingURL=run-
|
|
21429
|
+
//# sourceMappingURL=run-fWZd8vvq.js.map
|