substrate-ai 0.2.16 → 0.2.17
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 +52 -319
- package/dist/cli/templates/claude-md-substrate-section.md +6 -0
- package/dist/{decisions-SyswIRKz.js → decisions-Dq4cAA2L.js} +9 -2
- package/dist/decisions-DxgMpQpz.js +3 -0
- package/dist/{event-bus-J-bw-pkp.js → event-bus-BMxhfxfT.js} +1 -1
- package/dist/{experimenter-Cd04gAWQ.js → experimenter-CHRVkV3d.js} +4 -4
- package/dist/{git-utils-BtI5eNoN.js → git-utils-CtmrZrHS.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/{logger-C6n1g8uP.js → logger-D2fS2ccL.js} +1 -1
- package/dist/{run-BrI2xzk7.js → run-CLpXJNQ8.js} +465 -188
- package/dist/run-DPrhfU2T.js +7 -0
- package/dist/{upgrade-BjYVeC6G.js → upgrade-CF8EjNuO.js} +2 -2
- package/dist/{upgrade-rV26kdh3.js → upgrade-CjjAx5kD.js} +2 -2
- package/dist/{version-manager-impl-9N_519Ey.js → version-manager-impl-C6jmvble.js} +1 -1
- package/dist/{version-manager-impl-BpVx2DkY.js → version-manager-impl-CZ6KF1Ds.js} +1 -1
- package/package.json +1 -1
- package/dist/decisions-BmqXQ3Se.js +0 -3
- package/dist/run-fjuwOUib.js +0 -7
package/dist/cli/index.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-CLpXJNQ8.js";
|
|
3
|
+
import { createLogger, deepMask } from "../logger-D2fS2ccL.js";
|
|
4
|
+
import { AdapterRegistry, createEventBus } from "../event-bus-BMxhfxfT.js";
|
|
5
|
+
import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema, SUPPORTED_CONFIG_FORMAT_VERSIONS, SubstrateConfigSchema, defaultConfigMigrator } from "../version-manager-impl-CZ6KF1Ds.js";
|
|
6
6
|
import { ConfigError, ConfigIncompatibleFormatError } from "../errors-BPqtzQ4U.js";
|
|
7
|
-
import { addTokenUsage, createDecision, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestRun,
|
|
7
|
+
import { addTokenUsage, createDecision, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestRun, getTokenUsageSummary, listRequirements, updatePipelineRun } from "../decisions-Dq4cAA2L.js";
|
|
8
8
|
import { EXPERIMENT_RESULT, OPERATIONAL_FINDING, STORY_METRICS, aggregateTokenUsageForRun, compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline } from "../operational-CobuCGbM.js";
|
|
9
|
-
import { abortMerge, createWorktree, getConflictingFiles, getMergedFiles, getOrphanedWorktrees, performMerge, removeBranch, removeWorktree, simulateMerge, verifyGitVersion } from "../git-utils-
|
|
10
|
-
import { registerUpgradeCommand } from "../upgrade-
|
|
11
|
-
import { createRequire } from "module";
|
|
9
|
+
import { abortMerge, createWorktree, getConflictingFiles, getMergedFiles, getOrphanedWorktrees, performMerge, removeBranch, removeWorktree, simulateMerge, verifyGitVersion } from "../git-utils-CtmrZrHS.js";
|
|
10
|
+
import { registerUpgradeCommand } from "../upgrade-CjjAx5kD.js";
|
|
12
11
|
import { Command } from "commander";
|
|
13
12
|
import { fileURLToPath } from "url";
|
|
14
13
|
import { dirname, join, resolve } from "path";
|
|
15
14
|
import { access, mkdir, readFile, writeFile } from "fs/promises";
|
|
16
15
|
import { chmodSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
17
16
|
import yaml from "js-yaml";
|
|
18
|
-
import { createRequire
|
|
17
|
+
import { createRequire } from "node:module";
|
|
19
18
|
import * as path$1 from "node:path";
|
|
20
19
|
import { isAbsolute, join as join$1 } from "node:path";
|
|
21
20
|
import BetterSqlite3 from "better-sqlite3";
|
|
@@ -26,10 +25,6 @@ import { access as access$1 } from "node:fs/promises";
|
|
|
26
25
|
import { randomUUID } from "crypto";
|
|
27
26
|
import { createInterface as createInterface$1 } from "readline";
|
|
28
27
|
|
|
29
|
-
//#region rolldown:runtime
|
|
30
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
31
|
-
|
|
32
|
-
//#endregion
|
|
33
28
|
//#region src/cli/utils/formatting.ts
|
|
34
29
|
/**
|
|
35
30
|
* Build adapter list rows from discovery results.
|
|
@@ -329,7 +324,7 @@ const DEFAULT_CONFIG = {
|
|
|
329
324
|
|
|
330
325
|
//#endregion
|
|
331
326
|
//#region src/cli/commands/init.ts
|
|
332
|
-
const logger$
|
|
327
|
+
const logger$16 = createLogger("init");
|
|
333
328
|
const __dirname = dirname(new URL(import.meta.url).pathname);
|
|
334
329
|
const INIT_EXIT_SUCCESS = 0;
|
|
335
330
|
const INIT_EXIT_ERROR = 1;
|
|
@@ -350,7 +345,7 @@ async function scaffoldBmadFramework(projectRoot, force, outputFormat) {
|
|
|
350
345
|
const version = resolveBmadMethodVersion();
|
|
351
346
|
if (force && bmadExists) process.stderr.write(`Warning: Replacing existing _bmad/ framework with bmad-method@${version}\n`);
|
|
352
347
|
process.stdout.write(`Scaffolding BMAD framework from bmad-method@${version}\n`);
|
|
353
|
-
logger$
|
|
348
|
+
logger$16.info({
|
|
354
349
|
version,
|
|
355
350
|
dest: bmadDest
|
|
356
351
|
}, "Scaffolding BMAD framework");
|
|
@@ -360,7 +355,7 @@ async function scaffoldBmadFramework(projectRoot, force, outputFormat) {
|
|
|
360
355
|
const destDir = join(bmadDest, dir);
|
|
361
356
|
mkdirSync(destDir, { recursive: true });
|
|
362
357
|
cpSync(srcDir, destDir, { recursive: true });
|
|
363
|
-
logger$
|
|
358
|
+
logger$16.info({
|
|
364
359
|
dir,
|
|
365
360
|
dest: destDir
|
|
366
361
|
}, "Scaffolded BMAD framework directory");
|
|
@@ -379,7 +374,7 @@ async function scaffoldBmadFramework(projectRoot, force, outputFormat) {
|
|
|
379
374
|
"document_output_language: English"
|
|
380
375
|
].join("\n") + "\n";
|
|
381
376
|
await writeFile(configFile, configStub, "utf8");
|
|
382
|
-
logger$
|
|
377
|
+
logger$16.info({ configFile }, "Generated _bmad/_config/config.yaml stub");
|
|
383
378
|
}
|
|
384
379
|
}
|
|
385
380
|
const CLAUDE_MD_START_MARKER = "<!-- substrate:start -->";
|
|
@@ -394,7 +389,7 @@ async function scaffoldClaudeMd(projectRoot) {
|
|
|
394
389
|
try {
|
|
395
390
|
sectionContent = await readFile(templatePath, "utf8");
|
|
396
391
|
} catch {
|
|
397
|
-
logger$
|
|
392
|
+
logger$16.warn({ templatePath }, "CLAUDE.md substrate section template not found; skipping");
|
|
398
393
|
return;
|
|
399
394
|
}
|
|
400
395
|
if (!sectionContent.endsWith("\n")) sectionContent += "\n";
|
|
@@ -412,7 +407,7 @@ async function scaffoldClaudeMd(projectRoot) {
|
|
|
412
407
|
newContent = existingContent + separator + sectionContent;
|
|
413
408
|
}
|
|
414
409
|
await writeFile(claudeMdPath, newContent, "utf8");
|
|
415
|
-
logger$
|
|
410
|
+
logger$16.info({ claudeMdPath }, "Wrote substrate section to CLAUDE.md");
|
|
416
411
|
}
|
|
417
412
|
async function scaffoldStatuslineScript(projectRoot) {
|
|
418
413
|
const pkgRoot = findPackageRoot(__dirname);
|
|
@@ -423,7 +418,7 @@ async function scaffoldStatuslineScript(projectRoot) {
|
|
|
423
418
|
try {
|
|
424
419
|
content = await readFile(templatePath, "utf8");
|
|
425
420
|
} catch {
|
|
426
|
-
logger$
|
|
421
|
+
logger$16.warn({ templatePath }, "statusline.sh template not found; skipping");
|
|
427
422
|
return;
|
|
428
423
|
}
|
|
429
424
|
const claudeDir = join(projectRoot, ".claude");
|
|
@@ -431,7 +426,7 @@ async function scaffoldStatuslineScript(projectRoot) {
|
|
|
431
426
|
mkdirSync(claudeDir, { recursive: true });
|
|
432
427
|
await writeFile(statuslinePath, content, "utf8");
|
|
433
428
|
chmodSync(statuslinePath, 493);
|
|
434
|
-
logger$
|
|
429
|
+
logger$16.info({ statuslinePath }, "Wrote .claude/statusline.sh");
|
|
435
430
|
}
|
|
436
431
|
async function scaffoldClaudeSettings(projectRoot) {
|
|
437
432
|
const claudeDir = join(projectRoot, ".claude");
|
|
@@ -447,11 +442,11 @@ async function scaffoldClaudeSettings(projectRoot) {
|
|
|
447
442
|
if (!merged["$schema"]) merged["$schema"] = "https://json.schemastore.org/claude-code-settings.json";
|
|
448
443
|
mkdirSync(claudeDir, { recursive: true });
|
|
449
444
|
await writeFile(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
450
|
-
logger$
|
|
445
|
+
logger$16.info({ settingsPath }, "Wrote substrate settings to .claude/settings.json");
|
|
451
446
|
}
|
|
452
447
|
function resolveBmadMethodInstallerLibPath(fromDir = __dirname) {
|
|
453
448
|
try {
|
|
454
|
-
const _require = createRequire
|
|
449
|
+
const _require = createRequire(join(fromDir, "synthetic.js"));
|
|
455
450
|
const pkgJsonPath = _require.resolve("bmad-method/package.json");
|
|
456
451
|
return join(dirname(pkgJsonPath), "tools", "cli", "installers", "lib");
|
|
457
452
|
} catch {
|
|
@@ -482,7 +477,7 @@ function clearBmadCommandFiles(commandsDir) {
|
|
|
482
477
|
} catch {}
|
|
483
478
|
}
|
|
484
479
|
async function compileBmadAgents(bmadDir) {
|
|
485
|
-
const _require = createRequire
|
|
480
|
+
const _require = createRequire(join(__dirname, "synthetic.js"));
|
|
486
481
|
let compilerPath;
|
|
487
482
|
try {
|
|
488
483
|
const pkgJsonPath = _require.resolve("bmad-method/package.json");
|
|
@@ -517,7 +512,7 @@ async function compileBmadAgents(bmadDir) {
|
|
|
517
512
|
writeFileSync(mdPath, result.xml, "utf-8");
|
|
518
513
|
compiled++;
|
|
519
514
|
} catch (compileErr) {
|
|
520
|
-
logger$
|
|
515
|
+
logger$16.debug({
|
|
521
516
|
err: compileErr,
|
|
522
517
|
file
|
|
523
518
|
}, "Failed to compile agent YAML");
|
|
@@ -535,12 +530,12 @@ async function scaffoldClaudeCommands(projectRoot, outputFormat) {
|
|
|
535
530
|
return;
|
|
536
531
|
}
|
|
537
532
|
try {
|
|
538
|
-
const _require = createRequire
|
|
533
|
+
const _require = createRequire(join(__dirname, "synthetic.js"));
|
|
539
534
|
try {
|
|
540
535
|
const compiledCount = await compileBmadAgents(bmadDir);
|
|
541
|
-
if (compiledCount > 0) logger$
|
|
536
|
+
if (compiledCount > 0) logger$16.info({ compiledCount }, "Compiled agent YAML files to MD");
|
|
542
537
|
} catch (compileErr) {
|
|
543
|
-
logger$
|
|
538
|
+
logger$16.warn({ err: compileErr }, "Agent compilation failed; agent commands may be incomplete");
|
|
544
539
|
}
|
|
545
540
|
const { AgentCommandGenerator } = _require(join(installerLibPath, "ide", "shared", "agent-command-generator.js"));
|
|
546
541
|
const { WorkflowCommandGenerator } = _require(join(installerLibPath, "ide", "shared", "workflow-command-generator.js"));
|
|
@@ -552,7 +547,7 @@ async function scaffoldClaudeCommands(projectRoot, outputFormat) {
|
|
|
552
547
|
const manifestGen = new ManifestGenerator();
|
|
553
548
|
await manifestGen.generateManifests(bmadDir, allModules, [], { ides: ["claude-code"] });
|
|
554
549
|
} catch (manifestErr) {
|
|
555
|
-
logger$
|
|
550
|
+
logger$16.warn({ err: manifestErr }, "ManifestGenerator failed; workflow/task commands may be incomplete");
|
|
556
551
|
}
|
|
557
552
|
const commandsDir = join(projectRoot, ".claude", "commands");
|
|
558
553
|
mkdirSync(commandsDir, { recursive: true });
|
|
@@ -568,7 +563,7 @@ async function scaffoldClaudeCommands(projectRoot, outputFormat) {
|
|
|
568
563
|
const taskToolCount = await taskToolGen.writeDashArtifacts(commandsDir, taskToolArtifacts);
|
|
569
564
|
const total = agentCount + workflowCount + taskToolCount;
|
|
570
565
|
if (outputFormat !== "json") process.stdout.write(`Generated ${String(total)} Claude Code commands (${String(agentCount)} agents, ${String(workflowCount)} workflows, ${String(taskToolCount)} tasks/tools)\n`);
|
|
571
|
-
logger$
|
|
566
|
+
logger$16.info({
|
|
572
567
|
agentCount,
|
|
573
568
|
workflowCount,
|
|
574
569
|
taskToolCount,
|
|
@@ -578,7 +573,7 @@ async function scaffoldClaudeCommands(projectRoot, outputFormat) {
|
|
|
578
573
|
} catch (err) {
|
|
579
574
|
const msg = err instanceof Error ? err.message : String(err);
|
|
580
575
|
if (outputFormat !== "json") process.stderr.write(`Warning: .claude/commands/ generation failed: ${msg}\n`);
|
|
581
|
-
logger$
|
|
576
|
+
logger$16.warn({ err }, "scaffoldClaudeCommands failed; init continues");
|
|
582
577
|
}
|
|
583
578
|
}
|
|
584
579
|
const PROVIDER_DEFAULTS = DEFAULT_CONFIG.providers;
|
|
@@ -652,7 +647,7 @@ async function runInitAction(options) {
|
|
|
652
647
|
discoveryReport = await registry.discoverAndRegister();
|
|
653
648
|
} catch (err) {
|
|
654
649
|
const message = err instanceof Error ? err.message : String(err);
|
|
655
|
-
logger$
|
|
650
|
+
logger$16.error({ err }, "Adapter discovery failed");
|
|
656
651
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, `Adapter discovery failed: ${message}`) + "\n");
|
|
657
652
|
else process.stderr.write(` Error: adapter discovery failed — ${message}\n`);
|
|
658
653
|
return INIT_EXIT_ERROR;
|
|
@@ -701,12 +696,12 @@ async function runInitAction(options) {
|
|
|
701
696
|
return INIT_EXIT_ERROR;
|
|
702
697
|
}
|
|
703
698
|
if (force && existsSync(localManifest)) {
|
|
704
|
-
logger$
|
|
699
|
+
logger$16.info({ pack: packName }, "Replacing existing pack with bundled version");
|
|
705
700
|
process.stderr.write(`Warning: Replacing existing pack '${packName}' with bundled version\n`);
|
|
706
701
|
}
|
|
707
702
|
mkdirSync(dirname(packPath), { recursive: true });
|
|
708
703
|
cpSync(bundledPackPath, packPath, { recursive: true });
|
|
709
|
-
logger$
|
|
704
|
+
logger$16.info({
|
|
710
705
|
pack: packName,
|
|
711
706
|
dest: packPath
|
|
712
707
|
}, "Scaffolded methodology pack");
|
|
@@ -759,7 +754,7 @@ async function runInitAction(options) {
|
|
|
759
754
|
const msg = err instanceof Error ? err.message : String(err);
|
|
760
755
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
|
|
761
756
|
else process.stderr.write(`Error: ${msg}\n`);
|
|
762
|
-
logger$
|
|
757
|
+
logger$16.error({ err }, "init failed");
|
|
763
758
|
return INIT_EXIT_ERROR;
|
|
764
759
|
}
|
|
765
760
|
}
|
|
@@ -805,7 +800,7 @@ function formatUnsupportedVersionError(formatType, version, supported) {
|
|
|
805
800
|
|
|
806
801
|
//#endregion
|
|
807
802
|
//#region src/modules/config/config-system-impl.ts
|
|
808
|
-
const logger$
|
|
803
|
+
const logger$15 = createLogger("config");
|
|
809
804
|
function deepMerge(base, override) {
|
|
810
805
|
const result = { ...base };
|
|
811
806
|
for (const [key, val] of Object.entries(override)) if (val !== null && val !== void 0 && typeof val === "object" && !Array.isArray(val) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) result[key] = deepMerge(result[key], val);
|
|
@@ -850,7 +845,7 @@ function readEnvOverrides() {
|
|
|
850
845
|
}
|
|
851
846
|
const parsed = PartialSubstrateConfigSchema.safeParse(overrides);
|
|
852
847
|
if (!parsed.success) {
|
|
853
|
-
logger$
|
|
848
|
+
logger$15.warn({ errors: parsed.error.issues }, "Invalid environment variable overrides ignored");
|
|
854
849
|
return {};
|
|
855
850
|
}
|
|
856
851
|
return parsed.data;
|
|
@@ -914,7 +909,7 @@ var ConfigSystemImpl = class {
|
|
|
914
909
|
throw new ConfigError(`Configuration validation failed:\n${issues}`, { issues: result.error.issues });
|
|
915
910
|
}
|
|
916
911
|
this._config = result.data;
|
|
917
|
-
logger$
|
|
912
|
+
logger$15.debug("Configuration loaded successfully");
|
|
918
913
|
}
|
|
919
914
|
getConfig() {
|
|
920
915
|
if (this._config === null) throw new ConfigError("Configuration has not been loaded. Call load() before getConfig().", {});
|
|
@@ -977,7 +972,7 @@ var ConfigSystemImpl = class {
|
|
|
977
972
|
if (version !== void 0 && typeof version === "string" && !isVersionSupported(version, SUPPORTED_CONFIG_FORMAT_VERSIONS)) if (defaultConfigMigrator.canMigrate(version, CURRENT_CONFIG_FORMAT_VERSION)) {
|
|
978
973
|
const migrationOutput = defaultConfigMigrator.migrate(rawObj, version, CURRENT_CONFIG_FORMAT_VERSION, filePath);
|
|
979
974
|
if (migrationOutput.result.success) {
|
|
980
|
-
logger$
|
|
975
|
+
logger$15.info({
|
|
981
976
|
from: version,
|
|
982
977
|
to: CURRENT_CONFIG_FORMAT_VERSION,
|
|
983
978
|
backup: migrationOutput.result.backupPath
|
|
@@ -1020,7 +1015,7 @@ function createConfigSystem(options = {}) {
|
|
|
1020
1015
|
|
|
1021
1016
|
//#endregion
|
|
1022
1017
|
//#region src/cli/commands/config.ts
|
|
1023
|
-
const logger$
|
|
1018
|
+
const logger$14 = createLogger("config-cmd");
|
|
1024
1019
|
const CONFIG_EXIT_SUCCESS = 0;
|
|
1025
1020
|
const CONFIG_EXIT_ERROR = 1;
|
|
1026
1021
|
const CONFIG_EXIT_INVALID = 2;
|
|
@@ -1046,7 +1041,7 @@ async function runConfigShow(opts = {}) {
|
|
|
1046
1041
|
return CONFIG_EXIT_INVALID;
|
|
1047
1042
|
}
|
|
1048
1043
|
const message = err instanceof Error ? err.message : String(err);
|
|
1049
|
-
logger$
|
|
1044
|
+
logger$14.error({ err }, "Failed to load configuration");
|
|
1050
1045
|
process.stderr.write(` Error loading configuration: ${message}\n`);
|
|
1051
1046
|
return CONFIG_EXIT_ERROR;
|
|
1052
1047
|
}
|
|
@@ -1120,7 +1115,7 @@ async function runConfigExport(opts = {}) {
|
|
|
1120
1115
|
return CONFIG_EXIT_INVALID;
|
|
1121
1116
|
}
|
|
1122
1117
|
const message = err instanceof Error ? err.message : String(err);
|
|
1123
|
-
logger$
|
|
1118
|
+
logger$14.error({ err }, "Failed to load configuration");
|
|
1124
1119
|
process.stderr.write(`Error loading configuration: ${message}\n`);
|
|
1125
1120
|
return CONFIG_EXIT_ERROR;
|
|
1126
1121
|
}
|
|
@@ -1274,7 +1269,7 @@ function registerConfigCommand(program, _version) {
|
|
|
1274
1269
|
|
|
1275
1270
|
//#endregion
|
|
1276
1271
|
//#region src/cli/commands/resume.ts
|
|
1277
|
-
const logger$
|
|
1272
|
+
const logger$13 = createLogger("resume-cmd");
|
|
1278
1273
|
async function runResumeAction(options) {
|
|
1279
1274
|
const { runId: specifiedRunId, stopAfter, outputFormat, projectRoot, concurrency, pack: packName } = options;
|
|
1280
1275
|
if (stopAfter !== void 0 && !VALID_PHASES.includes(stopAfter)) {
|
|
@@ -1356,7 +1351,7 @@ async function runResumeAction(options) {
|
|
|
1356
1351
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1357
1352
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
|
|
1358
1353
|
else process.stderr.write(`Error: ${msg}\n`);
|
|
1359
|
-
logger$
|
|
1354
|
+
logger$13.error({ err }, "auto resume failed");
|
|
1360
1355
|
return 1;
|
|
1361
1356
|
} finally {
|
|
1362
1357
|
try {
|
|
@@ -1507,7 +1502,7 @@ async function runFullPipelineFromPhase(options) {
|
|
|
1507
1502
|
});
|
|
1508
1503
|
}
|
|
1509
1504
|
} catch (err) {
|
|
1510
|
-
logger$
|
|
1505
|
+
logger$13.warn({ err }, "Failed to record token usage");
|
|
1511
1506
|
}
|
|
1512
1507
|
});
|
|
1513
1508
|
const storyDecisions = db.prepare(`SELECT description FROM requirements WHERE pipeline_run_id = ? AND source = 'solutioning-phase'`).all(runId);
|
|
@@ -1566,7 +1561,7 @@ async function runFullPipelineFromPhase(options) {
|
|
|
1566
1561
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1567
1562
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
|
|
1568
1563
|
else process.stderr.write(`Error: ${msg}\n`);
|
|
1569
|
-
logger$
|
|
1564
|
+
logger$13.error({ err }, "pipeline from phase failed");
|
|
1570
1565
|
return 1;
|
|
1571
1566
|
} finally {
|
|
1572
1567
|
try {
|
|
@@ -1591,7 +1586,7 @@ function registerResumeCommand(program, _version = "0.0.0", projectRoot = proces
|
|
|
1591
1586
|
|
|
1592
1587
|
//#endregion
|
|
1593
1588
|
//#region src/cli/commands/status.ts
|
|
1594
|
-
const logger$
|
|
1589
|
+
const logger$12 = createLogger("status-cmd");
|
|
1595
1590
|
async function runStatusAction(options) {
|
|
1596
1591
|
const { outputFormat, runId, projectRoot } = options;
|
|
1597
1592
|
const dbRoot = await resolveMainRepoRoot(projectRoot);
|
|
@@ -1668,7 +1663,7 @@ async function runStatusAction(options) {
|
|
|
1668
1663
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1669
1664
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
|
|
1670
1665
|
else process.stderr.write(`Error: ${msg}\n`);
|
|
1671
|
-
logger$
|
|
1666
|
+
logger$12.error({ err }, "status action failed");
|
|
1672
1667
|
return 1;
|
|
1673
1668
|
} finally {
|
|
1674
1669
|
try {
|
|
@@ -2092,7 +2087,7 @@ Analyze thoroughly and return ONLY the JSON array with no additional text.`;
|
|
|
2092
2087
|
|
|
2093
2088
|
//#endregion
|
|
2094
2089
|
//#region src/cli/commands/amend.ts
|
|
2095
|
-
const logger$
|
|
2090
|
+
const logger$11 = createLogger("amend-cmd");
|
|
2096
2091
|
/**
|
|
2097
2092
|
* Detect and apply supersessions after a phase completes in an amendment run.
|
|
2098
2093
|
*
|
|
@@ -2123,7 +2118,7 @@ function runPostPhaseSupersessionDetection(db, amendmentRunId, currentPhase, han
|
|
|
2123
2118
|
});
|
|
2124
2119
|
} catch (err) {
|
|
2125
2120
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2126
|
-
logger$
|
|
2121
|
+
logger$11.warn({
|
|
2127
2122
|
err,
|
|
2128
2123
|
originalId: parentMatch.id,
|
|
2129
2124
|
supersedingId: newDec.id
|
|
@@ -2258,7 +2253,7 @@ async function runAmendAction(options) {
|
|
|
2258
2253
|
for (let i = startIdx; i < phaseOrder.length; i++) {
|
|
2259
2254
|
const currentPhase = phaseOrder[i];
|
|
2260
2255
|
const amendmentContext = handler.loadContextForPhase(currentPhase);
|
|
2261
|
-
logger$
|
|
2256
|
+
logger$11.info({
|
|
2262
2257
|
phase: currentPhase,
|
|
2263
2258
|
amendmentContextLen: amendmentContext.length
|
|
2264
2259
|
}, "Amendment context loaded for phase");
|
|
@@ -2378,7 +2373,7 @@ async function runAmendAction(options) {
|
|
|
2378
2373
|
} catch (err) {
|
|
2379
2374
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2380
2375
|
process.stderr.write(`Error: ${msg}\n`);
|
|
2381
|
-
logger$
|
|
2376
|
+
logger$11.error({ err }, "amend failed");
|
|
2382
2377
|
return 1;
|
|
2383
2378
|
} finally {
|
|
2384
2379
|
try {
|
|
@@ -2401,268 +2396,6 @@ function registerAmendCommand(program, _version = "0.0.0", projectRoot = process
|
|
|
2401
2396
|
});
|
|
2402
2397
|
}
|
|
2403
2398
|
|
|
2404
|
-
//#endregion
|
|
2405
|
-
//#region src/cli/commands/health.ts
|
|
2406
|
-
const logger$11 = createLogger("health-cmd");
|
|
2407
|
-
/** Default stall threshold in seconds — also used by supervisor default */
|
|
2408
|
-
const DEFAULT_STALL_THRESHOLD_SECONDS = 600;
|
|
2409
|
-
/**
|
|
2410
|
-
* Determine whether a ps output line represents the substrate pipeline orchestrator.
|
|
2411
|
-
* Handles invocation via:
|
|
2412
|
-
* - `substrate run` (globally installed)
|
|
2413
|
-
* - `substrate-ai run`
|
|
2414
|
-
* - `node dist/cli/index.js run` (npm run substrate:dev)
|
|
2415
|
-
* - `npx substrate run`
|
|
2416
|
-
* - any node process whose command contains `run` with `--events` or `--stories`
|
|
2417
|
-
*
|
|
2418
|
-
* When `projectRoot` is provided, additionally checks that the command line
|
|
2419
|
-
* contains that path (via `--project-root` flag or as part of the binary/CWD path).
|
|
2420
|
-
* This ensures multi-project environments match the correct orchestrator.
|
|
2421
|
-
*/
|
|
2422
|
-
function isOrchestratorProcessLine(line, projectRoot) {
|
|
2423
|
-
if (line.includes("grep")) return false;
|
|
2424
|
-
let isOrchestrator = false;
|
|
2425
|
-
if (line.includes("substrate run")) isOrchestrator = true;
|
|
2426
|
-
else if (line.includes("substrate-ai run")) isOrchestrator = true;
|
|
2427
|
-
else if (line.includes("index.js run")) isOrchestrator = true;
|
|
2428
|
-
else if (line.includes("node") && /\srun(\s|$)/.test(line) && (line.includes("--events") || line.includes("--stories"))) isOrchestrator = true;
|
|
2429
|
-
if (!isOrchestrator) return false;
|
|
2430
|
-
if (projectRoot !== void 0) return line.includes(projectRoot);
|
|
2431
|
-
return true;
|
|
2432
|
-
}
|
|
2433
|
-
function inspectProcessTree(opts) {
|
|
2434
|
-
const { projectRoot, execFileSync: execFileSyncOverride } = opts ?? {};
|
|
2435
|
-
const result = {
|
|
2436
|
-
orchestrator_pid: null,
|
|
2437
|
-
child_pids: [],
|
|
2438
|
-
zombies: []
|
|
2439
|
-
};
|
|
2440
|
-
try {
|
|
2441
|
-
let psOutput;
|
|
2442
|
-
if (execFileSyncOverride !== void 0) psOutput = execFileSyncOverride("ps", ["-eo", "pid,ppid,stat,command"], {
|
|
2443
|
-
encoding: "utf-8",
|
|
2444
|
-
timeout: 5e3
|
|
2445
|
-
});
|
|
2446
|
-
else {
|
|
2447
|
-
const { execFileSync } = __require("node:child_process");
|
|
2448
|
-
psOutput = execFileSync("ps", ["-eo", "pid,ppid,stat,command"], {
|
|
2449
|
-
encoding: "utf-8",
|
|
2450
|
-
timeout: 5e3
|
|
2451
|
-
});
|
|
2452
|
-
}
|
|
2453
|
-
const lines = psOutput.split("\n");
|
|
2454
|
-
for (const line of lines) if (isOrchestratorProcessLine(line, projectRoot)) {
|
|
2455
|
-
const match = line.trim().match(/^(\d+)/);
|
|
2456
|
-
if (match) {
|
|
2457
|
-
result.orchestrator_pid = parseInt(match[1], 10);
|
|
2458
|
-
break;
|
|
2459
|
-
}
|
|
2460
|
-
}
|
|
2461
|
-
if (result.orchestrator_pid !== null) for (const line of lines) {
|
|
2462
|
-
const parts = line.trim().split(/\s+/);
|
|
2463
|
-
if (parts.length >= 3) {
|
|
2464
|
-
const pid = parseInt(parts[0], 10);
|
|
2465
|
-
const ppid = parseInt(parts[1], 10);
|
|
2466
|
-
const stat$2 = parts[2];
|
|
2467
|
-
if (ppid === result.orchestrator_pid && pid !== result.orchestrator_pid) {
|
|
2468
|
-
result.child_pids.push(pid);
|
|
2469
|
-
if (stat$2.includes("Z")) result.zombies.push(pid);
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
} catch {}
|
|
2474
|
-
return result;
|
|
2475
|
-
}
|
|
2476
|
-
/**
|
|
2477
|
-
* Collect all descendant PIDs of the given root PIDs by walking the process
|
|
2478
|
-
* tree recursively. This ensures that grandchildren of the orchestrator
|
|
2479
|
-
* (e.g. node subprocesses spawned by `claude -p`) are also killed during
|
|
2480
|
-
* stall recovery, leaving no orphan processes.
|
|
2481
|
-
*
|
|
2482
|
-
* Returns only the descendants — the root PIDs themselves are NOT included.
|
|
2483
|
-
*/
|
|
2484
|
-
function getAllDescendantPids(rootPids, execFileSyncOverride) {
|
|
2485
|
-
if (rootPids.length === 0) return [];
|
|
2486
|
-
try {
|
|
2487
|
-
let psOutput;
|
|
2488
|
-
if (execFileSyncOverride !== void 0) psOutput = execFileSyncOverride("ps", ["-eo", "pid,ppid"], {
|
|
2489
|
-
encoding: "utf-8",
|
|
2490
|
-
timeout: 5e3
|
|
2491
|
-
});
|
|
2492
|
-
else {
|
|
2493
|
-
const { execFileSync } = __require("node:child_process");
|
|
2494
|
-
psOutput = execFileSync("ps", ["-eo", "pid,ppid"], {
|
|
2495
|
-
encoding: "utf-8",
|
|
2496
|
-
timeout: 5e3
|
|
2497
|
-
});
|
|
2498
|
-
}
|
|
2499
|
-
const childrenOf = new Map();
|
|
2500
|
-
for (const line of psOutput.split("\n")) {
|
|
2501
|
-
const parts = line.trim().split(/\s+/);
|
|
2502
|
-
if (parts.length >= 2) {
|
|
2503
|
-
const pid = parseInt(parts[0], 10);
|
|
2504
|
-
const ppid = parseInt(parts[1], 10);
|
|
2505
|
-
if (!isNaN(pid) && !isNaN(ppid) && pid > 0) {
|
|
2506
|
-
if (!childrenOf.has(ppid)) childrenOf.set(ppid, []);
|
|
2507
|
-
childrenOf.get(ppid).push(pid);
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
}
|
|
2511
|
-
const descendants = [];
|
|
2512
|
-
const seen = new Set(rootPids);
|
|
2513
|
-
const queue = [...rootPids];
|
|
2514
|
-
while (queue.length > 0) {
|
|
2515
|
-
const current = queue.shift();
|
|
2516
|
-
const children = childrenOf.get(current) ?? [];
|
|
2517
|
-
for (const child of children) if (!seen.has(child)) {
|
|
2518
|
-
seen.add(child);
|
|
2519
|
-
descendants.push(child);
|
|
2520
|
-
queue.push(child);
|
|
2521
|
-
}
|
|
2522
|
-
}
|
|
2523
|
-
return descendants;
|
|
2524
|
-
} catch {
|
|
2525
|
-
return [];
|
|
2526
|
-
}
|
|
2527
|
-
}
|
|
2528
|
-
/**
|
|
2529
|
-
* Fetch pipeline health data as a structured object without any stdout side-effects.
|
|
2530
|
-
* Used by runSupervisorAction to poll health without formatting overhead.
|
|
2531
|
-
*
|
|
2532
|
-
* Returns a NO_PIPELINE_RUNNING health object for all graceful "no data" cases
|
|
2533
|
-
* (missing DB, missing run, terminal run status). Throws only on unexpected errors.
|
|
2534
|
-
*/
|
|
2535
|
-
async function getAutoHealthData(options) {
|
|
2536
|
-
const { runId, projectRoot } = options;
|
|
2537
|
-
const dbRoot = await resolveMainRepoRoot(projectRoot);
|
|
2538
|
-
const dbPath = join(dbRoot, ".substrate", "substrate.db");
|
|
2539
|
-
const NO_PIPELINE = {
|
|
2540
|
-
verdict: "NO_PIPELINE_RUNNING",
|
|
2541
|
-
run_id: null,
|
|
2542
|
-
status: null,
|
|
2543
|
-
current_phase: null,
|
|
2544
|
-
staleness_seconds: 0,
|
|
2545
|
-
last_activity: "",
|
|
2546
|
-
process: {
|
|
2547
|
-
orchestrator_pid: null,
|
|
2548
|
-
child_pids: [],
|
|
2549
|
-
zombies: []
|
|
2550
|
-
},
|
|
2551
|
-
stories: {
|
|
2552
|
-
active: 0,
|
|
2553
|
-
completed: 0,
|
|
2554
|
-
escalated: 0,
|
|
2555
|
-
details: {}
|
|
2556
|
-
}
|
|
2557
|
-
};
|
|
2558
|
-
if (!existsSync(dbPath)) return NO_PIPELINE;
|
|
2559
|
-
const dbWrapper = new DatabaseWrapper(dbPath);
|
|
2560
|
-
try {
|
|
2561
|
-
dbWrapper.open();
|
|
2562
|
-
const db = dbWrapper.db;
|
|
2563
|
-
let run;
|
|
2564
|
-
if (runId !== void 0) run = getPipelineRunById(db, runId);
|
|
2565
|
-
else run = getLatestRun(db);
|
|
2566
|
-
if (run === void 0) return NO_PIPELINE;
|
|
2567
|
-
const updatedAt = parseDbTimestampAsUtc(run.updated_at);
|
|
2568
|
-
const stalenessSeconds = Math.round((Date.now() - updatedAt.getTime()) / 1e3);
|
|
2569
|
-
let storyDetails = {};
|
|
2570
|
-
let active = 0;
|
|
2571
|
-
let completed = 0;
|
|
2572
|
-
let escalated = 0;
|
|
2573
|
-
try {
|
|
2574
|
-
if (run.token_usage_json) {
|
|
2575
|
-
const state = JSON.parse(run.token_usage_json);
|
|
2576
|
-
if (state.stories) for (const [key, s] of Object.entries(state.stories)) {
|
|
2577
|
-
storyDetails[key] = {
|
|
2578
|
-
phase: s.phase,
|
|
2579
|
-
review_cycles: s.reviewCycles
|
|
2580
|
-
};
|
|
2581
|
-
if (s.phase === "COMPLETE") completed++;
|
|
2582
|
-
else if (s.phase === "ESCALATED") escalated++;
|
|
2583
|
-
else if (s.phase !== "PENDING") active++;
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
} catch {}
|
|
2587
|
-
const processInfo = inspectProcessTree({ projectRoot });
|
|
2588
|
-
let verdict = "NO_PIPELINE_RUNNING";
|
|
2589
|
-
if (run.status === "running") if (processInfo.orchestrator_pid === null && active === 0 && completed > 0) verdict = "NO_PIPELINE_RUNNING";
|
|
2590
|
-
else if (processInfo.zombies.length > 0) verdict = "STALLED";
|
|
2591
|
-
else if (processInfo.orchestrator_pid !== null && processInfo.child_pids.length > 0 && stalenessSeconds > DEFAULT_STALL_THRESHOLD_SECONDS) verdict = "HEALTHY";
|
|
2592
|
-
else if (stalenessSeconds > DEFAULT_STALL_THRESHOLD_SECONDS) verdict = "STALLED";
|
|
2593
|
-
else if (processInfo.orchestrator_pid !== null && processInfo.child_pids.length === 0 && active > 0) verdict = "STALLED";
|
|
2594
|
-
else verdict = "HEALTHY";
|
|
2595
|
-
else if (run.status === "completed" || run.status === "failed" || run.status === "stopped") verdict = "NO_PIPELINE_RUNNING";
|
|
2596
|
-
return {
|
|
2597
|
-
verdict,
|
|
2598
|
-
run_id: run.id,
|
|
2599
|
-
status: run.status,
|
|
2600
|
-
current_phase: run.current_phase,
|
|
2601
|
-
staleness_seconds: stalenessSeconds,
|
|
2602
|
-
last_activity: run.updated_at,
|
|
2603
|
-
process: processInfo,
|
|
2604
|
-
stories: {
|
|
2605
|
-
active,
|
|
2606
|
-
completed,
|
|
2607
|
-
escalated,
|
|
2608
|
-
details: storyDetails
|
|
2609
|
-
}
|
|
2610
|
-
};
|
|
2611
|
-
} finally {
|
|
2612
|
-
try {
|
|
2613
|
-
dbWrapper.close();
|
|
2614
|
-
} catch {}
|
|
2615
|
-
}
|
|
2616
|
-
}
|
|
2617
|
-
async function runHealthAction(options) {
|
|
2618
|
-
const { outputFormat } = options;
|
|
2619
|
-
try {
|
|
2620
|
-
const health = await getAutoHealthData(options);
|
|
2621
|
-
if (outputFormat === "json") process.stdout.write(formatOutput(health, "json", true) + "\n");
|
|
2622
|
-
else {
|
|
2623
|
-
const verdictLabel = health.verdict === "HEALTHY" ? "HEALTHY" : health.verdict === "STALLED" ? "STALLED" : "NO PIPELINE RUNNING";
|
|
2624
|
-
process.stdout.write(`\nPipeline Health: ${verdictLabel}\n`);
|
|
2625
|
-
if (health.run_id !== null) {
|
|
2626
|
-
process.stdout.write(` Run: ${health.run_id}\n`);
|
|
2627
|
-
process.stdout.write(` Status: ${health.status}\n`);
|
|
2628
|
-
process.stdout.write(` Phase: ${health.current_phase ?? "N/A"}\n`);
|
|
2629
|
-
process.stdout.write(` Last Active: ${health.last_activity} (${health.staleness_seconds}s ago)\n`);
|
|
2630
|
-
const processInfo = health.process;
|
|
2631
|
-
if (processInfo.orchestrator_pid !== null) {
|
|
2632
|
-
process.stdout.write(` Orchestrator: PID ${processInfo.orchestrator_pid}\n`);
|
|
2633
|
-
process.stdout.write(` Children: ${processInfo.child_pids.length} active`);
|
|
2634
|
-
if (processInfo.zombies.length > 0) process.stdout.write(` (${processInfo.zombies.length} ZOMBIE)`);
|
|
2635
|
-
process.stdout.write("\n");
|
|
2636
|
-
} else process.stdout.write(" Orchestrator: not running\n");
|
|
2637
|
-
const storyDetails = health.stories.details;
|
|
2638
|
-
if (Object.keys(storyDetails).length > 0) {
|
|
2639
|
-
process.stdout.write("\n Stories:\n");
|
|
2640
|
-
for (const [key, s] of Object.entries(storyDetails)) process.stdout.write(` ${key}: ${s.phase} (${s.review_cycles} review cycles)\n`);
|
|
2641
|
-
process.stdout.write(`\n Summary: ${health.stories.active} active, ${health.stories.completed} completed, ${health.stories.escalated} escalated\n`);
|
|
2642
|
-
}
|
|
2643
|
-
}
|
|
2644
|
-
}
|
|
2645
|
-
return 0;
|
|
2646
|
-
} catch (err) {
|
|
2647
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2648
|
-
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
|
|
2649
|
-
else process.stderr.write(`Error: ${msg}\n`);
|
|
2650
|
-
logger$11.error({ err }, "health action failed");
|
|
2651
|
-
return 1;
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
function registerHealthCommand(program, _version = "0.0.0", projectRoot = process.cwd()) {
|
|
2655
|
-
program.command("health").description("Check pipeline health: process status, stall detection, and verdict").option("--run-id <id>", "Pipeline run ID to query (defaults to latest)").option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").action(async (opts) => {
|
|
2656
|
-
const outputFormat = opts.outputFormat === "json" ? "json" : "human";
|
|
2657
|
-
const exitCode = await runHealthAction({
|
|
2658
|
-
outputFormat,
|
|
2659
|
-
runId: opts.runId,
|
|
2660
|
-
projectRoot: opts.projectRoot
|
|
2661
|
-
});
|
|
2662
|
-
process.exitCode = exitCode;
|
|
2663
|
-
});
|
|
2664
|
-
}
|
|
2665
|
-
|
|
2666
2399
|
//#endregion
|
|
2667
2400
|
//#region src/cli/commands/supervisor.ts
|
|
2668
2401
|
function defaultSupervisorDeps() {
|
|
@@ -3095,11 +2828,11 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
3095
2828
|
try {
|
|
3096
2829
|
const { createExperimenter } = await import(
|
|
3097
2830
|
/* @vite-ignore */
|
|
3098
|
-
"../experimenter-
|
|
2831
|
+
"../experimenter-CHRVkV3d.js"
|
|
3099
2832
|
);
|
|
3100
2833
|
const { getLatestRun: getLatest } = await import(
|
|
3101
2834
|
/* @vite-ignore */
|
|
3102
|
-
"../decisions-
|
|
2835
|
+
"../decisions-DxgMpQpz.js"
|
|
3103
2836
|
);
|
|
3104
2837
|
const dbPath = join(projectRoot, ".substrate", "substrate.db");
|
|
3105
2838
|
const expDbWrapper = new DatabaseWrapper(dbPath);
|
|
@@ -3109,7 +2842,7 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
3109
2842
|
const expDb = expDbWrapper.db;
|
|
3110
2843
|
const { runRunAction: runPipeline } = await import(
|
|
3111
2844
|
/* @vite-ignore */
|
|
3112
|
-
"../run-
|
|
2845
|
+
"../run-DPrhfU2T.js"
|
|
3113
2846
|
);
|
|
3114
2847
|
const runStoryFn = async (opts) => {
|
|
3115
2848
|
const exitCode = await runPipeline({
|
|
@@ -6969,8 +6702,8 @@ async function createProgram() {
|
|
|
6969
6702
|
/** Fire-and-forget startup version check (story 8.3, AC3/AC5) */
|
|
6970
6703
|
function checkForUpdatesInBackground(currentVersion) {
|
|
6971
6704
|
if (process.env.SUBSTRATE_NO_UPDATE_CHECK === "1") return;
|
|
6972
|
-
import("../upgrade-
|
|
6973
|
-
const { createVersionManager } = await import("../version-manager-impl-
|
|
6705
|
+
import("../upgrade-CF8EjNuO.js").then(async () => {
|
|
6706
|
+
const { createVersionManager } = await import("../version-manager-impl-C6jmvble.js");
|
|
6974
6707
|
const vm = createVersionManager();
|
|
6975
6708
|
const result = await vm.checkForUpdates();
|
|
6976
6709
|
if (result.updateAvailable) {
|