substrate-ai 0.4.7 → 0.4.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 +39 -8
- package/dist/index.d.ts +19 -1
- package/dist/{routing-CZfJB3y9.js → routing-BUE9pIxW.js} +360 -5
- package/dist/routing-DbR9FPmj.js +4 -0
- package/dist/{run-fWZd8vvq.js → run-Dx7bxebF.js} +182 -20
- package/dist/{run-RerrMpM1.js → run-c8_Yj6xH.js} +2 -2
- package/dist/schema.sql +3 -1
- package/package.json +3 -3
- package/dist/routing-DWCBjrt7.js +0 -4
package/dist/cli/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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 { AppError, DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, DoltClient, DoltNotInstalled, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, 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-Dx7bxebF.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";
|
|
6
6
|
import { ConfigError, createEventBus } from "../helpers-BihqWgVe.js";
|
|
7
|
-
import { RoutingRecommender } from "../routing-
|
|
7
|
+
import { RoutingRecommender } from "../routing-BUE9pIxW.js";
|
|
8
8
|
import { addTokenUsage, createDecision, createPipelineRun, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestRun, getTokenUsageSummary, listRequirements, updatePipelineRun } from "../decisions-Db8GTbH2.js";
|
|
9
9
|
import { ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, OPERATIONAL_FINDING, STORY_METRICS, aggregateTokenUsageForRun, compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline } from "../operational-C0_y8DAs.js";
|
|
10
10
|
import { abortMerge, createWorktree, getConflictingFiles, getMergedFiles, getOrphanedWorktrees, performMerge, removeBranch, removeWorktree, simulateMerge, verifyGitVersion } from "../git-utils-C-fdrHF_.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-c8_Yj6xH.js"
|
|
2713
2713
|
);
|
|
2714
2714
|
const runStoryFn = async (opts) => {
|
|
2715
2715
|
const exitCode = await runPipeline({
|
|
@@ -3194,7 +3194,7 @@ async function runMetricsAction(options) {
|
|
|
3194
3194
|
const routingConfigPath = join(dbDir, "routing.yml");
|
|
3195
3195
|
let routingConfig = null;
|
|
3196
3196
|
if (existsSync(routingConfigPath)) try {
|
|
3197
|
-
const { loadModelRoutingConfig } = await import("../routing-
|
|
3197
|
+
const { loadModelRoutingConfig } = await import("../routing-DbR9FPmj.js");
|
|
3198
3198
|
routingConfig = loadModelRoutingConfig(routingConfigPath);
|
|
3199
3199
|
} catch {}
|
|
3200
3200
|
if (routingConfig === null) routingConfig = {
|
|
@@ -7540,6 +7540,15 @@ function registerRepoMapCommand(program) {
|
|
|
7540
7540
|
return;
|
|
7541
7541
|
}
|
|
7542
7542
|
const doltClient = new DoltClient({ repoPath: statePath });
|
|
7543
|
+
try {
|
|
7544
|
+
const colRows = await doltClient.query(`SHOW COLUMNS FROM repo_map_symbols LIKE 'dependencies'`);
|
|
7545
|
+
if (colRows.length === 0) {
|
|
7546
|
+
await doltClient.query(`ALTER TABLE repo_map_symbols ADD COLUMN dependencies JSON`);
|
|
7547
|
+
logger$2.info("Applied migration: added dependencies column to repo_map_symbols");
|
|
7548
|
+
}
|
|
7549
|
+
} catch {
|
|
7550
|
+
logger$2.debug("Skipping repo_map_symbols migration: table not yet created");
|
|
7551
|
+
}
|
|
7543
7552
|
const symbolRepo = new DoltSymbolRepository(doltClient, logger$2);
|
|
7544
7553
|
const metaRepo = new DoltRepoMapMetaRepository(doltClient);
|
|
7545
7554
|
const repoMapModule = new RepoMapModule(metaRepo, logger$2);
|
|
@@ -7572,19 +7581,41 @@ function registerRepoMapCommand(program) {
|
|
|
7572
7581
|
logger$2.info("repo-map --update: triggering incremental update");
|
|
7573
7582
|
const gitClient = new GitClient(logger$2);
|
|
7574
7583
|
const grammarLoader = new GrammarLoader(logger$2);
|
|
7584
|
+
if (grammarLoader.getGrammar(".ts") === null) {
|
|
7585
|
+
const msg = "tree-sitter grammars not installed. Run `npm install tree-sitter tree-sitter-typescript tree-sitter-javascript tree-sitter-python` in the substrate installation directory.";
|
|
7586
|
+
if (options.outputFormat === "json") console.log(JSON.stringify({
|
|
7587
|
+
result: "error",
|
|
7588
|
+
error: msg
|
|
7589
|
+
}));
|
|
7590
|
+
else process.stderr.write(`Error: ${msg}\n`);
|
|
7591
|
+
process.exitCode = 1;
|
|
7592
|
+
return;
|
|
7593
|
+
}
|
|
7575
7594
|
const parser = new SymbolParser(grammarLoader, logger$2);
|
|
7576
7595
|
const storage = new RepoMapStorage(symbolRepo, metaRepo, gitClient, logger$2);
|
|
7577
|
-
|
|
7596
|
+
let updateWarning;
|
|
7597
|
+
try {
|
|
7598
|
+
await storage.incrementalUpdate(dbRoot, parser);
|
|
7599
|
+
} catch (err) {
|
|
7600
|
+
if (err instanceof AppError && err.code === ERR_REPO_MAP_STORAGE_WRITE) {
|
|
7601
|
+
updateWarning = err.message;
|
|
7602
|
+
logger$2.warn({ err }, "repo-map --update: storage write error (partial update)");
|
|
7603
|
+
} else throw err;
|
|
7604
|
+
}
|
|
7578
7605
|
const meta = await metaRepo.getMeta();
|
|
7579
7606
|
const symbolCount = (await symbolRepo.getSymbols()).length;
|
|
7580
7607
|
if (options.outputFormat === "json") console.log(JSON.stringify({
|
|
7581
|
-
result: "updated",
|
|
7608
|
+
result: updateWarning ? "partial" : "updated",
|
|
7582
7609
|
symbolCount,
|
|
7583
7610
|
fileCount: meta?.fileCount ?? 0,
|
|
7584
7611
|
commitSha: meta?.commitSha ?? null,
|
|
7585
|
-
updatedAt: meta?.updatedAt?.toISOString() ?? null
|
|
7612
|
+
updatedAt: meta?.updatedAt?.toISOString() ?? null,
|
|
7613
|
+
...updateWarning ? { warning: updateWarning } : {}
|
|
7586
7614
|
}));
|
|
7587
|
-
else
|
|
7615
|
+
else if (updateWarning) {
|
|
7616
|
+
console.log(`Repo-map partially updated: ${symbolCount} symbols across ${meta?.fileCount ?? 0} files`);
|
|
7617
|
+
console.log(`Warning: ${updateWarning}`);
|
|
7618
|
+
} else console.log(`Repo-map updated: ${symbolCount} symbols across ${meta?.fileCount ?? 0} files`);
|
|
7588
7619
|
return;
|
|
7589
7620
|
}
|
|
7590
7621
|
if (options.query !== void 0) {
|
package/dist/index.d.ts
CHANGED
|
@@ -551,6 +551,24 @@ interface PipelineContractVerificationSummaryEvent {
|
|
|
551
551
|
/** 'pass' if zero mismatches, 'fail' otherwise */
|
|
552
552
|
verdict: 'pass' | 'fail';
|
|
553
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* Emitted when the RoutingResolver selects a model for a dispatch.
|
|
556
|
+
*/
|
|
557
|
+
interface RoutingModelSelectedEvent {
|
|
558
|
+
type: 'routing:model-selected';
|
|
559
|
+
/** ISO-8601 timestamp generated at emit time */
|
|
560
|
+
ts: string;
|
|
561
|
+
/** Unique dispatch ID */
|
|
562
|
+
dispatch_id: string;
|
|
563
|
+
/** Task type (e.g. 'dev-story', 'test-plan', 'code-review') */
|
|
564
|
+
task_type: string;
|
|
565
|
+
/** Routing phase that matched (e.g. 'generate', 'explore', 'review') */
|
|
566
|
+
phase: string;
|
|
567
|
+
/** Selected model ID */
|
|
568
|
+
model: string;
|
|
569
|
+
/** How the model was selected: 'phase', 'baseline', 'override' */
|
|
570
|
+
source: string;
|
|
571
|
+
}
|
|
554
572
|
/**
|
|
555
573
|
* Discriminated union of all pipeline event types.
|
|
556
574
|
*
|
|
@@ -563,7 +581,7 @@ interface PipelineContractVerificationSummaryEvent {
|
|
|
563
581
|
* }
|
|
564
582
|
* ```
|
|
565
583
|
*/
|
|
566
|
-
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; //#endregion
|
|
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
|
|
567
585
|
//#region src/core/errors.d.ts
|
|
568
586
|
|
|
569
587
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createLogger } from "./logger-D2fS2ccL.js";
|
|
2
|
-
import { load } from "js-yaml";
|
|
2
|
+
import { dump, load } from "js-yaml";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
|
|
@@ -280,13 +280,142 @@ var RoutingResolver = class RoutingResolver {
|
|
|
280
280
|
}
|
|
281
281
|
};
|
|
282
282
|
|
|
283
|
+
//#endregion
|
|
284
|
+
//#region src/modules/routing/routing-token-accumulator.ts
|
|
285
|
+
/**
|
|
286
|
+
* Accumulates per-dispatch routing decisions and agent token usage, then
|
|
287
|
+
* flushes an aggregated `PhaseTokenBreakdown` to the StateStore at run end.
|
|
288
|
+
*
|
|
289
|
+
* Thread-safety: all methods are synchronous accumulators; `flush` is async
|
|
290
|
+
* but should only be called once per run after all dispatches settle.
|
|
291
|
+
*/
|
|
292
|
+
var RoutingTokenAccumulator = class {
|
|
293
|
+
_config;
|
|
294
|
+
_stateStore;
|
|
295
|
+
_logger;
|
|
296
|
+
/** Maps dispatchId → { phase, model } registered from routing:model-selected events */
|
|
297
|
+
_dispatchMap = new Map();
|
|
298
|
+
/**
|
|
299
|
+
* Bucket key = `"${phase}::${model}"`.
|
|
300
|
+
* Separate entries per (phase, model) combination so mixed-model runs
|
|
301
|
+
* produce distinct rows in the breakdown.
|
|
302
|
+
*/
|
|
303
|
+
_buckets = new Map();
|
|
304
|
+
constructor(config, stateStore, logger$1) {
|
|
305
|
+
this._config = config;
|
|
306
|
+
this._stateStore = stateStore;
|
|
307
|
+
this._logger = logger$1;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Register the routing decision for a dispatch.
|
|
311
|
+
* A second event for the same `dispatchId` overwrites the prior entry (last-writer-wins).
|
|
312
|
+
*
|
|
313
|
+
* @param event - payload from `routing:model-selected`
|
|
314
|
+
*/
|
|
315
|
+
onRoutingSelected(event) {
|
|
316
|
+
this._dispatchMap.set(event.dispatchId, {
|
|
317
|
+
phase: event.phase,
|
|
318
|
+
model: event.model
|
|
319
|
+
});
|
|
320
|
+
this._logger.debug({
|
|
321
|
+
dispatchId: event.dispatchId,
|
|
322
|
+
phase: event.phase,
|
|
323
|
+
model: event.model
|
|
324
|
+
}, "routing:model-selected registered");
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Attribute token usage to the phase bucket for this dispatch.
|
|
328
|
+
* Unknown `dispatchId` values are attributed to `phase: 'default', model: 'unknown'`.
|
|
329
|
+
*
|
|
330
|
+
* @param event - payload from `agent:completed` (must include inputTokens / outputTokens)
|
|
331
|
+
*/
|
|
332
|
+
onAgentCompleted(event) {
|
|
333
|
+
const mapping = this._dispatchMap.get(event.dispatchId);
|
|
334
|
+
const phase = mapping?.phase ?? "default";
|
|
335
|
+
const model = mapping?.model ?? "unknown";
|
|
336
|
+
this._upsertBucket(phase, model, event.inputTokens, event.outputTokens);
|
|
337
|
+
this._logger.debug({
|
|
338
|
+
dispatchId: event.dispatchId,
|
|
339
|
+
phase,
|
|
340
|
+
model,
|
|
341
|
+
inputTokens: event.inputTokens
|
|
342
|
+
}, "agent:completed attributed");
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Construct the `PhaseTokenBreakdown` from the accumulated buckets and
|
|
346
|
+
* persist it to the StateStore via `setMetric`.
|
|
347
|
+
* Clears all in-memory state afterwards so a second call writes an empty entry.
|
|
348
|
+
*
|
|
349
|
+
* @param runId - the pipeline run ID used to scope the metric key
|
|
350
|
+
*/
|
|
351
|
+
async flush(runId) {
|
|
352
|
+
const entries = Array.from(this._buckets.values());
|
|
353
|
+
const breakdown = {
|
|
354
|
+
entries,
|
|
355
|
+
baselineModel: this._config.baseline_model,
|
|
356
|
+
runId
|
|
357
|
+
};
|
|
358
|
+
await this._stateStore.setMetric(runId, "phase_token_breakdown", breakdown);
|
|
359
|
+
this._logger.debug({
|
|
360
|
+
runId,
|
|
361
|
+
entryCount: entries.length
|
|
362
|
+
}, "Phase token breakdown flushed to StateStore");
|
|
363
|
+
this._dispatchMap.clear();
|
|
364
|
+
this._buckets.clear();
|
|
365
|
+
}
|
|
366
|
+
_upsertBucket(phase, model, inputTokens, outputTokens) {
|
|
367
|
+
const key = `${phase}::${model}`;
|
|
368
|
+
const existing = this._buckets.get(key);
|
|
369
|
+
if (existing) {
|
|
370
|
+
existing.inputTokens += inputTokens;
|
|
371
|
+
existing.outputTokens += outputTokens;
|
|
372
|
+
existing.dispatchCount += 1;
|
|
373
|
+
} else this._buckets.set(key, {
|
|
374
|
+
phase,
|
|
375
|
+
model,
|
|
376
|
+
inputTokens,
|
|
377
|
+
outputTokens,
|
|
378
|
+
dispatchCount: 1
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/modules/routing/routing-telemetry.ts
|
|
385
|
+
/**
|
|
386
|
+
* Emits `routing.model_resolved` OTEL spans via a TelemetryPersistence instance.
|
|
387
|
+
*
|
|
388
|
+
* Injected into the run command alongside RoutingResolver. When telemetry is
|
|
389
|
+
* not configured, pass null to the run command; no spans are emitted.
|
|
390
|
+
*/
|
|
391
|
+
var RoutingTelemetry = class {
|
|
392
|
+
_telemetry;
|
|
393
|
+
_logger;
|
|
394
|
+
constructor(telemetry, logger$1) {
|
|
395
|
+
this._telemetry = telemetry;
|
|
396
|
+
this._logger = logger$1;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Emit a `routing.model_resolved` span for a single routing decision.
|
|
400
|
+
*
|
|
401
|
+
* @param attrs - span attributes including dispatchId, taskType, phase, model, source, latencyMs
|
|
402
|
+
*/
|
|
403
|
+
recordModelResolved(attrs) {
|
|
404
|
+
this._telemetry.recordSpan({
|
|
405
|
+
name: "routing.model_resolved",
|
|
406
|
+
attributes: attrs
|
|
407
|
+
});
|
|
408
|
+
this._logger.debug(attrs, "routing.model_resolved span emitted");
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
283
412
|
//#endregion
|
|
284
413
|
//#region src/modules/routing/routing-recommender.ts
|
|
285
414
|
/**
|
|
286
415
|
* Ordered tier list: index 0 = cheapest / smallest, index N = most expensive / largest.
|
|
287
416
|
* Tiers are determined by substring matching — e.g. 'claude-haiku-4-5' → tier 1.
|
|
288
417
|
*/
|
|
289
|
-
const TIER_KEYWORDS = [
|
|
418
|
+
const TIER_KEYWORDS$1 = [
|
|
290
419
|
{
|
|
291
420
|
keyword: "haiku",
|
|
292
421
|
tier: 1
|
|
@@ -330,7 +459,7 @@ var RoutingRecommender = class {
|
|
|
330
459
|
*/
|
|
331
460
|
_getTier(model) {
|
|
332
461
|
const lower = model.toLowerCase();
|
|
333
|
-
for (const { keyword, tier } of TIER_KEYWORDS) if (lower.includes(keyword)) return tier;
|
|
462
|
+
for (const { keyword, tier } of TIER_KEYWORDS$1) if (lower.includes(keyword)) return tier;
|
|
334
463
|
return 2;
|
|
335
464
|
}
|
|
336
465
|
/**
|
|
@@ -473,5 +602,231 @@ var RoutingRecommender = class {
|
|
|
473
602
|
};
|
|
474
603
|
|
|
475
604
|
//#endregion
|
|
476
|
-
|
|
477
|
-
|
|
605
|
+
//#region src/modules/routing/model-tier.ts
|
|
606
|
+
/**
|
|
607
|
+
* Shared model tier resolution utility.
|
|
608
|
+
*
|
|
609
|
+
* Determines whether a model string belongs to the haiku (1), sonnet (2),
|
|
610
|
+
* or opus (3) tier based on substring matching against well-known keywords.
|
|
611
|
+
*
|
|
612
|
+
* Used by both RoutingRecommender and RoutingTuner to ensure consistent
|
|
613
|
+
* tier comparisons — in particular the one-step guard in RoutingTuner.
|
|
614
|
+
*/
|
|
615
|
+
/** Ordered tier keywords: index 0 = cheapest, index N = most expensive. */
|
|
616
|
+
const TIER_KEYWORDS = [
|
|
617
|
+
{
|
|
618
|
+
keyword: "haiku",
|
|
619
|
+
tier: 1
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
keyword: "sonnet",
|
|
623
|
+
tier: 2
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
keyword: "opus",
|
|
627
|
+
tier: 3
|
|
628
|
+
}
|
|
629
|
+
];
|
|
630
|
+
/**
|
|
631
|
+
* Get the model tier for a given model name string.
|
|
632
|
+
*
|
|
633
|
+
* Returns:
|
|
634
|
+
* - 1 for haiku-tier models
|
|
635
|
+
* - 2 for sonnet-tier models (also the default when unrecognized)
|
|
636
|
+
* - 3 for opus-tier models
|
|
637
|
+
*
|
|
638
|
+
* Matching is case-insensitive substring search.
|
|
639
|
+
*/
|
|
640
|
+
function getModelTier(model) {
|
|
641
|
+
const lower = model.toLowerCase();
|
|
642
|
+
for (const { keyword, tier } of TIER_KEYWORDS) if (lower.includes(keyword)) return tier;
|
|
643
|
+
return 2;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
//#endregion
|
|
647
|
+
//#region src/modules/routing/routing-tuner.ts
|
|
648
|
+
/** Minimum number of breakdowns required before auto-tuning is attempted. */
|
|
649
|
+
const MIN_BREAKDOWNS_FOR_TUNING = 5;
|
|
650
|
+
/** Key used to store the list of known run IDs in the StateStore. */
|
|
651
|
+
const RUN_INDEX_KEY = "phase_token_breakdown_runs";
|
|
652
|
+
/** Key used to store the tune log in the StateStore. */
|
|
653
|
+
const TUNE_LOG_KEY = "routing_tune_log";
|
|
654
|
+
/**
|
|
655
|
+
* Auto-applies a single conservative model downgrade per invocation when
|
|
656
|
+
* `config.auto_tune` is `true` and sufficient historical data is available.
|
|
657
|
+
*
|
|
658
|
+
* The tuner reads the current routing YAML config, applies the change in memory,
|
|
659
|
+
* and writes it back to disk synchronously. It also appends a `TuneLogEntry`
|
|
660
|
+
* to the StateStore for audit purposes, and emits a `routing:auto-tuned` event.
|
|
661
|
+
*/
|
|
662
|
+
var RoutingTuner = class {
|
|
663
|
+
_stateStore;
|
|
664
|
+
_recommender;
|
|
665
|
+
_eventEmitter;
|
|
666
|
+
_configPath;
|
|
667
|
+
_logger;
|
|
668
|
+
constructor(stateStore, recommender, eventEmitter, configPath, logger$1) {
|
|
669
|
+
this._stateStore = stateStore;
|
|
670
|
+
this._recommender = recommender;
|
|
671
|
+
this._eventEmitter = eventEmitter;
|
|
672
|
+
this._configPath = configPath;
|
|
673
|
+
this._logger = logger$1;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Called at the end of a pipeline run. When auto_tune is enabled and sufficient
|
|
677
|
+
* historical data exists, applies a single conservative model downgrade to the
|
|
678
|
+
* routing config YAML file.
|
|
679
|
+
*
|
|
680
|
+
* @param runId - ID of the just-completed pipeline run
|
|
681
|
+
* @param config - Current model routing config (already loaded from disk)
|
|
682
|
+
*/
|
|
683
|
+
async maybeAutoTune(runId, config) {
|
|
684
|
+
if (config.auto_tune !== true) {
|
|
685
|
+
this._logger.debug({ runId }, "auto_tune_disabled — skipping RoutingTuner");
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
await this._registerRunId(runId);
|
|
689
|
+
const breakdowns = await this._loadRecentBreakdowns(10);
|
|
690
|
+
if (breakdowns.length < MIN_BREAKDOWNS_FOR_TUNING) {
|
|
691
|
+
this._logger.debug({
|
|
692
|
+
runId,
|
|
693
|
+
available: breakdowns.length,
|
|
694
|
+
required: MIN_BREAKDOWNS_FOR_TUNING
|
|
695
|
+
}, "insufficient_data — not enough breakdowns for auto-tuning");
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const analysis = this._recommender.analyze(breakdowns, config);
|
|
699
|
+
if (analysis.insufficientData) {
|
|
700
|
+
this._logger.debug({ runId }, "Recommender returned insufficientData");
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
const downgradeCandidates = analysis.recommendations.filter((rec) => {
|
|
704
|
+
if (rec.direction !== "downgrade") return false;
|
|
705
|
+
const tierDiff = Math.abs(getModelTier(rec.currentModel) - getModelTier(rec.suggestedModel));
|
|
706
|
+
return tierDiff === 1;
|
|
707
|
+
});
|
|
708
|
+
if (downgradeCandidates.length === 0) {
|
|
709
|
+
this._logger.debug({ runId }, "no_safe_recommendation");
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
const topRec = downgradeCandidates.sort((a, b) => b.confidence - a.confidence)[0];
|
|
713
|
+
let rawContent;
|
|
714
|
+
try {
|
|
715
|
+
rawContent = readFileSync(this._configPath, "utf-8");
|
|
716
|
+
} catch (err) {
|
|
717
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
718
|
+
this._logger.warn({
|
|
719
|
+
err: msg,
|
|
720
|
+
configPath: this._configPath
|
|
721
|
+
}, "Failed to read routing config for auto-tune");
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
let rawObject;
|
|
725
|
+
try {
|
|
726
|
+
rawObject = load(rawContent);
|
|
727
|
+
} catch (err) {
|
|
728
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
729
|
+
this._logger.warn({ err: msg }, "Failed to parse routing config YAML for auto-tune");
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const configObj = rawObject;
|
|
733
|
+
if (configObj.phases === void 0) configObj.phases = {};
|
|
734
|
+
const existingPhase = configObj.phases[topRec.phase];
|
|
735
|
+
if (existingPhase !== void 0) existingPhase.model = topRec.suggestedModel;
|
|
736
|
+
else configObj.phases[topRec.phase] = { model: topRec.suggestedModel };
|
|
737
|
+
try {
|
|
738
|
+
writeFileSync(this._configPath, dump(rawObject, { lineWidth: 120 }), "utf-8");
|
|
739
|
+
} catch (err) {
|
|
740
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
741
|
+
this._logger.warn({
|
|
742
|
+
err: msg,
|
|
743
|
+
configPath: this._configPath
|
|
744
|
+
}, "Failed to write updated routing config");
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
const tuneEntry = {
|
|
748
|
+
id: crypto.randomUUID(),
|
|
749
|
+
runId,
|
|
750
|
+
phase: topRec.phase,
|
|
751
|
+
oldModel: topRec.currentModel,
|
|
752
|
+
newModel: topRec.suggestedModel,
|
|
753
|
+
estimatedSavingsPct: topRec.estimatedSavingsPct,
|
|
754
|
+
appliedAt: new Date().toISOString()
|
|
755
|
+
};
|
|
756
|
+
await this._appendTuneLog(tuneEntry);
|
|
757
|
+
this._eventEmitter.emit("routing:auto-tuned", {
|
|
758
|
+
runId,
|
|
759
|
+
phase: topRec.phase,
|
|
760
|
+
oldModel: topRec.currentModel,
|
|
761
|
+
newModel: topRec.suggestedModel,
|
|
762
|
+
estimatedSavingsPct: topRec.estimatedSavingsPct
|
|
763
|
+
});
|
|
764
|
+
this._logger.info({
|
|
765
|
+
runId,
|
|
766
|
+
phase: topRec.phase,
|
|
767
|
+
oldModel: topRec.currentModel,
|
|
768
|
+
newModel: topRec.suggestedModel
|
|
769
|
+
}, "Auto-tuned routing config — applied downgrade");
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Register a run ID in the stored run index so future calls can discover
|
|
773
|
+
* all historical breakdowns without a separate run listing endpoint.
|
|
774
|
+
*/
|
|
775
|
+
async _registerRunId(runId) {
|
|
776
|
+
const existing = await this._stateStore.getMetric("__global__", RUN_INDEX_KEY);
|
|
777
|
+
const runIds = Array.isArray(existing) ? existing : [];
|
|
778
|
+
if (!runIds.includes(runId)) {
|
|
779
|
+
runIds.push(runId);
|
|
780
|
+
await this._stateStore.setMetric("__global__", RUN_INDEX_KEY, runIds);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Load the most recent `lookback` PhaseTokenBreakdown records from the StateStore.
|
|
785
|
+
*
|
|
786
|
+
* Each breakdown is stored by RoutingTokenAccumulator under the key
|
|
787
|
+
* `'phase_token_breakdown'` scoped to the run ID. The run IDs themselves are
|
|
788
|
+
* tracked in a global index stored under `('__global__', RUN_INDEX_KEY)`.
|
|
789
|
+
*
|
|
790
|
+
* @param lookback - Maximum number of recent runs to inspect
|
|
791
|
+
*/
|
|
792
|
+
async _loadRecentBreakdowns(lookback) {
|
|
793
|
+
const existing = await this._stateStore.getMetric("__global__", RUN_INDEX_KEY);
|
|
794
|
+
const allRunIds = Array.isArray(existing) ? existing : [];
|
|
795
|
+
const recentRunIds = allRunIds.slice(-lookback);
|
|
796
|
+
const breakdowns = [];
|
|
797
|
+
for (const runId of recentRunIds) try {
|
|
798
|
+
const raw = await this._stateStore.getMetric(runId, "phase_token_breakdown");
|
|
799
|
+
if (raw !== void 0 && raw !== null) {
|
|
800
|
+
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
801
|
+
breakdowns.push(parsed);
|
|
802
|
+
}
|
|
803
|
+
} catch (err) {
|
|
804
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
805
|
+
this._logger.debug({
|
|
806
|
+
runId,
|
|
807
|
+
err: msg
|
|
808
|
+
}, "Failed to load breakdown for run — skipping");
|
|
809
|
+
}
|
|
810
|
+
return breakdowns;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Append a TuneLogEntry to the persisted tune log in the StateStore.
|
|
814
|
+
*
|
|
815
|
+
* NOTE: This uses `'__global__'` as the scope key (codebase convention) rather
|
|
816
|
+
* than the literal `'global'` mentioned in AC6. The tune log is stored as a raw
|
|
817
|
+
* array (not a JSON-stringified string) for internal consistency with how other
|
|
818
|
+
* array values are stored in this StateStore. Story 28-9's
|
|
819
|
+
* `substrate routing --history` command MUST use the same `'__global__'` scope
|
|
820
|
+
* key and `'routing_tune_log'` metric key when reading this log.
|
|
821
|
+
*/
|
|
822
|
+
async _appendTuneLog(entry) {
|
|
823
|
+
const existing = await this._stateStore.getMetric("__global__", TUNE_LOG_KEY);
|
|
824
|
+
const log = Array.isArray(existing) ? existing : [];
|
|
825
|
+
log.push(entry);
|
|
826
|
+
await this._stateStore.setMetric("__global__", TUNE_LOG_KEY, log);
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
//#endregion
|
|
831
|
+
export { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig };
|
|
832
|
+
//# sourceMappingURL=routing-BUE9pIxW.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import "./logger-D2fS2ccL.js";
|
|
2
|
+
import { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig } from "./routing-BUE9pIxW.js";
|
|
3
|
+
|
|
4
|
+
export { loadModelRoutingConfig };
|
|
@@ -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 { RoutingResolver } from "./routing-
|
|
4
|
+
import { RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, loadModelRoutingConfig } from "./routing-BUE9pIxW.js";
|
|
5
5
|
import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./decisions-Db8GTbH2.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-C0_y8DAs.js";
|
|
7
7
|
import { createRequire } from "module";
|
|
@@ -2810,6 +2810,43 @@ const PIPELINE_EVENT_METADATA = [
|
|
|
2810
2810
|
}
|
|
2811
2811
|
]
|
|
2812
2812
|
},
|
|
2813
|
+
{
|
|
2814
|
+
type: "routing:model-selected",
|
|
2815
|
+
description: "Model routing resolver selected a model for a dispatch.",
|
|
2816
|
+
when: "When a story dispatch uses model routing and the resolver returns a non-null model.",
|
|
2817
|
+
fields: [
|
|
2818
|
+
{
|
|
2819
|
+
name: "ts",
|
|
2820
|
+
type: "string",
|
|
2821
|
+
description: "Timestamp."
|
|
2822
|
+
},
|
|
2823
|
+
{
|
|
2824
|
+
name: "dispatch_id",
|
|
2825
|
+
type: "string",
|
|
2826
|
+
description: "Unique dispatch ID."
|
|
2827
|
+
},
|
|
2828
|
+
{
|
|
2829
|
+
name: "task_type",
|
|
2830
|
+
type: "string",
|
|
2831
|
+
description: "Task type (dev-story, test-plan, code-review)."
|
|
2832
|
+
},
|
|
2833
|
+
{
|
|
2834
|
+
name: "phase",
|
|
2835
|
+
type: "string",
|
|
2836
|
+
description: "Routing phase that matched (generate, explore, review)."
|
|
2837
|
+
},
|
|
2838
|
+
{
|
|
2839
|
+
name: "model",
|
|
2840
|
+
type: "string",
|
|
2841
|
+
description: "Selected model ID."
|
|
2842
|
+
},
|
|
2843
|
+
{
|
|
2844
|
+
name: "source",
|
|
2845
|
+
type: "string",
|
|
2846
|
+
description: "How selected: phase, baseline, or override."
|
|
2847
|
+
}
|
|
2848
|
+
]
|
|
2849
|
+
},
|
|
2813
2850
|
{
|
|
2814
2851
|
type: "pipeline:pre-flight-failure",
|
|
2815
2852
|
description: "Pre-flight build check failed before any story was dispatched. Pipeline aborts immediately.",
|
|
@@ -3273,6 +3310,9 @@ Patterns for \`substrate supervisor --output-format json\` events:
|
|
|
3273
3310
|
|
|
3274
3311
|
### On \`supervisor:experiment:error\`
|
|
3275
3312
|
- Report error. Suggest running without \`--experiment\` for a clean run.
|
|
3313
|
+
|
|
3314
|
+
### On \`routing:model-selected\`
|
|
3315
|
+
- Informational. Log which model was selected for the dispatch and why (phase config, baseline, or override).
|
|
3276
3316
|
`;
|
|
3277
3317
|
}
|
|
3278
3318
|
/**
|
|
@@ -3832,10 +3872,12 @@ var DoltSymbolRepository = class {
|
|
|
3832
3872
|
this._logger.debug({ filePath }, "upsertFileSymbols: cleared symbols for deleted/empty file");
|
|
3833
3873
|
return;
|
|
3834
3874
|
}
|
|
3835
|
-
const
|
|
3875
|
+
const deps = symbols.filter((s) => s.kind === "import").map((s) => s.name);
|
|
3876
|
+
const depsJson = JSON.stringify(deps);
|
|
3877
|
+
const placeholders = symbols.map(() => "(?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
3836
3878
|
const params = [];
|
|
3837
|
-
for (const sym of symbols) params.push(filePath, sym.name, sym.kind, sym.signature ?? "", sym.lineNumber, sym.exported ? 1 : 0, fileHash);
|
|
3838
|
-
await this._client.query(`INSERT INTO repo_map_symbols (file_path, symbol_name, symbol_kind, signature, line_number, exported, file_hash) VALUES ${placeholders}`, params);
|
|
3879
|
+
for (const sym of symbols) params.push(filePath, sym.name, sym.kind, sym.signature ?? "", sym.lineNumber, sym.exported ? 1 : 0, fileHash, depsJson);
|
|
3880
|
+
await this._client.query(`INSERT INTO repo_map_symbols (file_path, symbol_name, symbol_kind, signature, line_number, exported, file_hash, dependencies) VALUES ${placeholders}`, params);
|
|
3839
3881
|
this._logger.debug({
|
|
3840
3882
|
filePath,
|
|
3841
3883
|
count: symbols.length
|
|
@@ -3899,7 +3941,7 @@ var DoltSymbolRepository = class {
|
|
|
3899
3941
|
if (filePaths.length === 0) return [];
|
|
3900
3942
|
try {
|
|
3901
3943
|
const placeholders = filePaths.map(() => "?").join(", ");
|
|
3902
|
-
const rows = await this._client.query(`SELECT file_path, symbol_name, symbol_kind, signature, line_number, exported, file_hash FROM repo_map_symbols WHERE file_path IN (${placeholders})`, filePaths);
|
|
3944
|
+
const rows = await this._client.query(`SELECT file_path, symbol_name, symbol_kind, signature, line_number, exported, file_hash, dependencies FROM repo_map_symbols WHERE file_path IN (${placeholders})`, filePaths);
|
|
3903
3945
|
return rows.map((r) => this._rowToRepoMapSymbol(r));
|
|
3904
3946
|
} catch (err) {
|
|
3905
3947
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -3910,7 +3952,7 @@ var DoltSymbolRepository = class {
|
|
|
3910
3952
|
if (names.length === 0) return [];
|
|
3911
3953
|
try {
|
|
3912
3954
|
const placeholders = names.map(() => "?").join(", ");
|
|
3913
|
-
const rows = await this._client.query(`SELECT file_path, symbol_name, symbol_kind, signature, line_number, exported, file_hash FROM repo_map_symbols WHERE symbol_name IN (${placeholders})`, names);
|
|
3955
|
+
const rows = await this._client.query(`SELECT file_path, symbol_name, symbol_kind, signature, line_number, exported, file_hash, dependencies FROM repo_map_symbols WHERE symbol_name IN (${placeholders})`, names);
|
|
3914
3956
|
return rows.map((r) => this._rowToRepoMapSymbol(r));
|
|
3915
3957
|
} catch (err) {
|
|
3916
3958
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -3921,7 +3963,7 @@ var DoltSymbolRepository = class {
|
|
|
3921
3963
|
if (types$1.length === 0) return [];
|
|
3922
3964
|
try {
|
|
3923
3965
|
const placeholders = types$1.map(() => "?").join(", ");
|
|
3924
|
-
const rows = await this._client.query(`SELECT file_path, symbol_name, symbol_kind, signature, line_number, exported, file_hash FROM repo_map_symbols WHERE symbol_kind IN (${placeholders})`, types$1);
|
|
3966
|
+
const rows = await this._client.query(`SELECT file_path, symbol_name, symbol_kind, signature, line_number, exported, file_hash, dependencies FROM repo_map_symbols WHERE symbol_kind IN (${placeholders})`, types$1);
|
|
3925
3967
|
return rows.map((r) => this._rowToRepoMapSymbol(r));
|
|
3926
3968
|
} catch (err) {
|
|
3927
3969
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -3929,16 +3971,20 @@ var DoltSymbolRepository = class {
|
|
|
3929
3971
|
}
|
|
3930
3972
|
}
|
|
3931
3973
|
/**
|
|
3932
|
-
* Returns symbols from files whose dependencies
|
|
3933
|
-
* Currently returns [] — the repo_map_symbols schema does not yet include a
|
|
3934
|
-
* `dependencies` JSON column. A future migration will add it.
|
|
3974
|
+
* Returns symbols from files whose dependencies array contains symbolName.
|
|
3935
3975
|
*/
|
|
3936
|
-
async findByDependedBy(
|
|
3937
|
-
|
|
3976
|
+
async findByDependedBy(symbolName) {
|
|
3977
|
+
try {
|
|
3978
|
+
const rows = await this._client.query(`SELECT file_path, symbol_name, symbol_kind, signature, line_number, exported, file_hash, dependencies FROM repo_map_symbols WHERE JSON_CONTAINS(dependencies, JSON_QUOTE(?), '$')`, [symbolName]);
|
|
3979
|
+
return rows.map((r) => this._rowToRepoMapSymbol(r));
|
|
3980
|
+
} catch (err) {
|
|
3981
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
3982
|
+
throw new AppError(ERR_REPO_MAP_STORAGE_READ, 2, `findByDependedBy failed: ${detail}`);
|
|
3983
|
+
}
|
|
3938
3984
|
}
|
|
3939
3985
|
async findAll() {
|
|
3940
3986
|
try {
|
|
3941
|
-
const rows = await this._client.query("SELECT file_path, symbol_name, symbol_kind, signature, line_number, exported, file_hash FROM repo_map_symbols");
|
|
3987
|
+
const rows = await this._client.query("SELECT file_path, symbol_name, symbol_kind, signature, line_number, exported, file_hash, dependencies FROM repo_map_symbols");
|
|
3942
3988
|
return rows.map((r) => this._rowToRepoMapSymbol(r));
|
|
3943
3989
|
} catch (err) {
|
|
3944
3990
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -3946,13 +3992,18 @@ var DoltSymbolRepository = class {
|
|
|
3946
3992
|
}
|
|
3947
3993
|
}
|
|
3948
3994
|
_rowToRepoMapSymbol(row) {
|
|
3995
|
+
let deps = [];
|
|
3996
|
+
if (row.dependencies) try {
|
|
3997
|
+
const parsed = typeof row.dependencies === "string" ? JSON.parse(row.dependencies) : row.dependencies;
|
|
3998
|
+
if (Array.isArray(parsed)) deps = parsed;
|
|
3999
|
+
} catch {}
|
|
3949
4000
|
return {
|
|
3950
4001
|
filePath: row.file_path,
|
|
3951
4002
|
symbolName: row.symbol_name,
|
|
3952
4003
|
symbolType: row.symbol_kind,
|
|
3953
4004
|
signature: row.signature ?? void 0,
|
|
3954
4005
|
lineNumber: row.line_number,
|
|
3955
|
-
dependencies:
|
|
4006
|
+
dependencies: deps,
|
|
3956
4007
|
fileHash: row.file_hash
|
|
3957
4008
|
};
|
|
3958
4009
|
}
|
|
@@ -3978,7 +4029,7 @@ var DoltRepoMapMetaRepository = class {
|
|
|
3978
4029
|
updated_at = VALUES(updated_at),
|
|
3979
4030
|
file_count = VALUES(file_count)`, [
|
|
3980
4031
|
meta.commitSha,
|
|
3981
|
-
meta.updatedAt,
|
|
4032
|
+
meta.updatedAt.toISOString(),
|
|
3982
4033
|
meta.fileCount
|
|
3983
4034
|
]);
|
|
3984
4035
|
} catch (err) {
|
|
@@ -4050,15 +4101,18 @@ var RepoMapStorage = class {
|
|
|
4050
4101
|
const changedFiles = await this._gitClient.getChangedFiles(projectRoot, meta.commitSha);
|
|
4051
4102
|
this._logger.debug({ count: changedFiles.length }, "incrementalUpdate: changed files");
|
|
4052
4103
|
const supported = changedFiles.filter((f) => SUPPORTED_EXTENSIONS.has(extname(f)));
|
|
4104
|
+
let parsedCount = 0;
|
|
4053
4105
|
for (const filePath of supported) try {
|
|
4054
4106
|
const exists = await fileExists(filePath);
|
|
4055
4107
|
if (!exists) {
|
|
4056
4108
|
await this._symbolRepo.upsertFileSymbols(filePath, [], "");
|
|
4109
|
+
parsedCount++;
|
|
4057
4110
|
continue;
|
|
4058
4111
|
}
|
|
4059
4112
|
const symbols = await parser.parseFile(filePath);
|
|
4060
4113
|
const hash = await computeFileHash(filePath);
|
|
4061
4114
|
await this._symbolRepo.upsertFileSymbols(filePath, symbols, hash);
|
|
4115
|
+
parsedCount++;
|
|
4062
4116
|
} catch (err) {
|
|
4063
4117
|
this._logger.warn({
|
|
4064
4118
|
filePath,
|
|
@@ -4069,7 +4123,7 @@ var RepoMapStorage = class {
|
|
|
4069
4123
|
await this._metaRepo.updateMeta({
|
|
4070
4124
|
commitSha: currentSha,
|
|
4071
4125
|
updatedAt: new Date(),
|
|
4072
|
-
fileCount:
|
|
4126
|
+
fileCount: parsedCount
|
|
4073
4127
|
});
|
|
4074
4128
|
}
|
|
4075
4129
|
/**
|
|
@@ -5699,6 +5753,36 @@ var RepoMapFormatter = class {
|
|
|
5699
5753
|
}
|
|
5700
5754
|
};
|
|
5701
5755
|
|
|
5756
|
+
//#endregion
|
|
5757
|
+
//#region src/modules/repo-map/repo-map-telemetry.ts
|
|
5758
|
+
/**
|
|
5759
|
+
* Emits `repo_map.query` OTEL spans via a TelemetryPersistence instance.
|
|
5760
|
+
*
|
|
5761
|
+
* Constructed with an `ITelemetryPersistence` and a logger. Pass an instance
|
|
5762
|
+
* to `RepoMapQueryEngine` constructor to enable query telemetry; omit it to
|
|
5763
|
+
* skip telemetry without changing existing query behaviour.
|
|
5764
|
+
*/
|
|
5765
|
+
var RepoMapTelemetry = class {
|
|
5766
|
+
_telemetry;
|
|
5767
|
+
_logger;
|
|
5768
|
+
constructor(telemetry, logger$27) {
|
|
5769
|
+
this._telemetry = telemetry;
|
|
5770
|
+
this._logger = logger$27;
|
|
5771
|
+
}
|
|
5772
|
+
/**
|
|
5773
|
+
* Emit a `repo_map.query` span.
|
|
5774
|
+
*
|
|
5775
|
+
* @param attrs - query telemetry attributes
|
|
5776
|
+
*/
|
|
5777
|
+
recordQuery(attrs) {
|
|
5778
|
+
this._telemetry.recordSpan({
|
|
5779
|
+
name: "repo_map.query",
|
|
5780
|
+
attributes: attrs
|
|
5781
|
+
});
|
|
5782
|
+
this._logger.debug(attrs, "repo_map.query span emitted");
|
|
5783
|
+
}
|
|
5784
|
+
};
|
|
5785
|
+
|
|
5702
5786
|
//#endregion
|
|
5703
5787
|
//#region src/modules/repo-map/RepoMapModule.ts
|
|
5704
5788
|
/**
|
|
@@ -7061,6 +7145,21 @@ var DoltStateStore = class DoltStateStore {
|
|
|
7061
7145
|
)`
|
|
7062
7146
|
];
|
|
7063
7147
|
for (const sql of ddl) await this._client.query(sql);
|
|
7148
|
+
try {
|
|
7149
|
+
const colRows = await this._client.query(`SHOW COLUMNS FROM repo_map_symbols LIKE 'dependencies'`);
|
|
7150
|
+
if (colRows.length === 0) {
|
|
7151
|
+
await this._client.query(`ALTER TABLE repo_map_symbols ADD COLUMN dependencies JSON`);
|
|
7152
|
+
await this._client.query(`INSERT IGNORE INTO _schema_version (version, description) VALUES (6, 'Add dependencies JSON column to repo_map_symbols (Epic 28-3)')`);
|
|
7153
|
+
log$1.info({
|
|
7154
|
+
component: "dolt-state",
|
|
7155
|
+
migration: "v5-to-v6",
|
|
7156
|
+
column: "dependencies",
|
|
7157
|
+
table: "repo_map_symbols"
|
|
7158
|
+
}, "Applied migration v5-to-v6: added dependencies column to repo_map_symbols");
|
|
7159
|
+
}
|
|
7160
|
+
} catch {
|
|
7161
|
+
log$1.debug("Skipping repo_map_symbols migration: table not yet created");
|
|
7162
|
+
}
|
|
7064
7163
|
log$1.debug("Schema migrations applied");
|
|
7065
7164
|
}
|
|
7066
7165
|
/**
|
|
@@ -20478,6 +20577,7 @@ async function runRunAction(options) {
|
|
|
20478
20577
|
return 1;
|
|
20479
20578
|
}
|
|
20480
20579
|
const db = dbWrapper.db;
|
|
20580
|
+
const telemetryPersistence = telemetryEnabled ? new TelemetryPersistence(db) : void 0;
|
|
20481
20581
|
const packLoader = createPackLoader();
|
|
20482
20582
|
let pack;
|
|
20483
20583
|
try {
|
|
@@ -20547,6 +20647,33 @@ async function runRunAction(options) {
|
|
|
20547
20647
|
if (!injectedRegistry) throw new Error("AdapterRegistry is required — must be initialized at CLI startup");
|
|
20548
20648
|
const routingConfigPath = join(projectRoot, "substrate.routing.yml");
|
|
20549
20649
|
const routingResolver = RoutingResolver.createWithFallback(routingConfigPath, logger);
|
|
20650
|
+
let routingTokenAccumulator;
|
|
20651
|
+
let routingConfig;
|
|
20652
|
+
try {
|
|
20653
|
+
routingConfig = loadModelRoutingConfig(routingConfigPath);
|
|
20654
|
+
} catch {
|
|
20655
|
+
logger.debug("Routing config not loadable — RoutingTokenAccumulator skipped");
|
|
20656
|
+
}
|
|
20657
|
+
let routingTuner;
|
|
20658
|
+
if (routingConfig !== void 0) {
|
|
20659
|
+
const kvStateStore = new FileStateStore({ basePath: join(dbRoot, ".substrate") });
|
|
20660
|
+
routingTokenAccumulator = new RoutingTokenAccumulator(routingConfig, kvStateStore, logger);
|
|
20661
|
+
eventBus.on("routing:model-selected", (payload) => {
|
|
20662
|
+
routingTokenAccumulator.onRoutingSelected({
|
|
20663
|
+
dispatchId: payload.dispatchId,
|
|
20664
|
+
phase: payload.phase,
|
|
20665
|
+
model: payload.model
|
|
20666
|
+
});
|
|
20667
|
+
});
|
|
20668
|
+
eventBus.on("agent:completed", (payload) => {
|
|
20669
|
+
routingTokenAccumulator.onAgentCompleted({
|
|
20670
|
+
dispatchId: payload.dispatchId,
|
|
20671
|
+
inputTokens: payload.inputTokens ?? 0,
|
|
20672
|
+
outputTokens: payload.outputTokens ?? 0
|
|
20673
|
+
});
|
|
20674
|
+
});
|
|
20675
|
+
if (routingConfig.auto_tune === true) routingTuner = new RoutingTuner(kvStateStore, new RoutingRecommender(logger), eventBus, routingConfigPath, logger);
|
|
20676
|
+
}
|
|
20550
20677
|
const statePath = join(dbRoot, ".substrate", "state");
|
|
20551
20678
|
const isDoltAvailable = existsSync(join(statePath, ".dolt"));
|
|
20552
20679
|
let repoMapInjector;
|
|
@@ -20556,7 +20683,8 @@ async function runRunAction(options) {
|
|
|
20556
20683
|
const doltClient = new DoltClient({ repoPath: statePath });
|
|
20557
20684
|
const symbolRepo = new DoltSymbolRepository(doltClient, logger);
|
|
20558
20685
|
const metaRepo = new DoltRepoMapMetaRepository(doltClient);
|
|
20559
|
-
const
|
|
20686
|
+
const repoMapTelemetry = telemetryPersistence !== void 0 ? new RepoMapTelemetry(telemetryPersistence, logger) : void 0;
|
|
20687
|
+
const queryEngine = new RepoMapQueryEngine(symbolRepo, logger, repoMapTelemetry);
|
|
20560
20688
|
repoMapInjector = new RepoMapInjector(queryEngine, logger);
|
|
20561
20689
|
repoMapModule = new RepoMapModule(metaRepo, logger);
|
|
20562
20690
|
logger.debug("repo-map injector constructed (Dolt backend detected)");
|
|
@@ -20810,6 +20938,17 @@ async function runRunAction(options) {
|
|
|
20810
20938
|
});
|
|
20811
20939
|
}
|
|
20812
20940
|
});
|
|
20941
|
+
eventBus.on("routing:model-selected", (payload) => {
|
|
20942
|
+
ndjsonEmitter.emit({
|
|
20943
|
+
type: "routing:model-selected",
|
|
20944
|
+
ts: new Date().toISOString(),
|
|
20945
|
+
dispatch_id: payload.dispatchId,
|
|
20946
|
+
task_type: payload.taskType,
|
|
20947
|
+
phase: payload.phase,
|
|
20948
|
+
model: payload.model,
|
|
20949
|
+
source: payload.source
|
|
20950
|
+
});
|
|
20951
|
+
});
|
|
20813
20952
|
eventBus.on("orchestrator:story-complete", (payload) => {
|
|
20814
20953
|
ndjsonEmitter.emit({
|
|
20815
20954
|
type: "story:done",
|
|
@@ -20944,7 +21083,19 @@ async function runRunAction(options) {
|
|
|
20944
21083
|
});
|
|
20945
21084
|
}
|
|
20946
21085
|
const ingestionServer = telemetryEnabled ? new IngestionServer({ port: telemetryPort }) : void 0;
|
|
20947
|
-
|
|
21086
|
+
if (telemetryPersistence !== void 0) {
|
|
21087
|
+
const routingTelemetry = new RoutingTelemetry(telemetryPersistence, logger);
|
|
21088
|
+
eventBus.on("routing:model-selected", (payload) => {
|
|
21089
|
+
routingTelemetry.recordModelResolved({
|
|
21090
|
+
dispatchId: payload.dispatchId,
|
|
21091
|
+
taskType: payload.taskType,
|
|
21092
|
+
phase: payload.phase,
|
|
21093
|
+
model: payload.model,
|
|
21094
|
+
source: payload.source,
|
|
21095
|
+
latencyMs: 0
|
|
21096
|
+
});
|
|
21097
|
+
});
|
|
21098
|
+
}
|
|
20948
21099
|
if (repoMapModule !== void 0) try {
|
|
20949
21100
|
const stale = await repoMapModule.checkStaleness();
|
|
20950
21101
|
if (stale !== null) {
|
|
@@ -20982,6 +21133,17 @@ async function runRunAction(options) {
|
|
|
20982
21133
|
process.stdout.write(`Stories: ${storyKeys.join(", ")}\n`);
|
|
20983
21134
|
}
|
|
20984
21135
|
const status = await orchestrator.run(storyKeys);
|
|
21136
|
+
if (routingTokenAccumulator !== void 0) try {
|
|
21137
|
+
await routingTokenAccumulator.flush(pipelineRun.id);
|
|
21138
|
+
logger.debug({ runId: pipelineRun.id }, "Phase token breakdown flushed");
|
|
21139
|
+
} catch (flushErr) {
|
|
21140
|
+
logger.warn({ err: flushErr }, "Failed to flush phase token breakdown (best-effort)");
|
|
21141
|
+
}
|
|
21142
|
+
if (routingTuner !== void 0 && routingConfig !== void 0) try {
|
|
21143
|
+
await routingTuner.maybeAutoTune(pipelineRun.id, routingConfig);
|
|
21144
|
+
} catch (tuneErr) {
|
|
21145
|
+
logger.warn({ err: tuneErr }, "RoutingTuner.maybeAutoTune failed (best-effort)");
|
|
21146
|
+
}
|
|
20985
21147
|
const succeededKeys = [];
|
|
20986
21148
|
const failedKeys = [];
|
|
20987
21149
|
const escalatedKeys = [];
|
|
@@ -21425,5 +21587,5 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
21425
21587
|
}
|
|
21426
21588
|
|
|
21427
21589
|
//#endregion
|
|
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 };
|
|
21429
|
-
//# sourceMappingURL=run-
|
|
21590
|
+
export { AppError, DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, DoltClient, DoltNotInstalled, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, 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 };
|
|
21591
|
+
//# sourceMappingURL=run-Dx7bxebF.js.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { registerRunCommand, runRunAction } from "./run-
|
|
1
|
+
import { registerRunCommand, runRunAction } from "./run-Dx7bxebF.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-BUE9pIxW.js";
|
|
6
6
|
import "./decisions-Db8GTbH2.js";
|
|
7
7
|
import "./operational-C0_y8DAs.js";
|
|
8
8
|
|
package/dist/schema.sql
CHANGED
|
@@ -215,7 +215,8 @@ CREATE TABLE IF NOT EXISTS repo_map_symbols (
|
|
|
215
215
|
signature TEXT,
|
|
216
216
|
line_number INT NOT NULL DEFAULT 0,
|
|
217
217
|
exported TINYINT(1) NOT NULL DEFAULT 0,
|
|
218
|
-
file_hash
|
|
218
|
+
file_hash VARCHAR(64) NOT NULL,
|
|
219
|
+
dependencies JSON,
|
|
219
220
|
PRIMARY KEY (id)
|
|
220
221
|
);
|
|
221
222
|
|
|
@@ -234,3 +235,4 @@ CREATE TABLE IF NOT EXISTS repo_map_meta (
|
|
|
234
235
|
);
|
|
235
236
|
|
|
236
237
|
INSERT IGNORE INTO _schema_version (version, description) VALUES (5, 'Add repo_map_symbols and repo_map_meta tables (Epic 28-2)');
|
|
238
|
+
INSERT IGNORE INTO _schema_version (version, description) VALUES (6, 'Add dependencies JSON column to repo_map_symbols (Epic 28-3)');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "substrate-ai",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "Substrate — multi-agent orchestration daemon for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
},
|
|
76
76
|
"optionalDependencies": {
|
|
77
77
|
"tree-sitter": "^0.21.1",
|
|
78
|
-
"tree-sitter-typescript": "^0.21.2",
|
|
79
78
|
"tree-sitter-javascript": "^0.21.4",
|
|
80
|
-
"tree-sitter-python": "^0.21.0"
|
|
79
|
+
"tree-sitter-python": "^0.21.0",
|
|
80
|
+
"tree-sitter-typescript": "^0.21.2"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@eslint/js": "^9.17.0",
|
package/dist/routing-DWCBjrt7.js
DELETED