substrate-ai 0.20.57 → 0.20.58

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,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, aggregateProbeAuthorMetrics, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, parseRuntimeProbes, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics } from "../health-PdI4-96I.js";
2
+ import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, aggregateProbeAuthorMetrics, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, parseRuntimeProbes, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics } from "../health-CNqQFdaT.js";
3
3
  import { createLogger } from "../logger-KeHncl-f.js";
4
4
  import { createEventBus } from "../helpers-CElYrONe.js";
5
5
  import { AdapterRegistry, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, ConfigError, CostTrackerConfigSchema, DEFAULT_CONFIG, DoltClient, DoltNotInstalled, GlobalSettingsSchema, InMemoryDatabaseAdapter, IngestionServer, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProvidersSchema, RoutingRecommender, STORY_METRICS, TelemetryConfigSchema, addTokenUsage, aggregateTokenUsageForRun, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDecision, createDoltClient, createPipelineRun, getActiveDecisions, getAllCostEntriesFiltered, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRunMetrics, loadParentRunDecisions, supersedeDecision, tagRunAsBaseline, updatePipelineRun } from "../dist-W2emvN3F.js";
6
6
  import "../adapter-registry-DXLMTmfD.js";
7
- import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR, GitClient, GrammarLoader, Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runSolutioningPhase, unescape, validateStopAfterFromConflict } from "../run-DcDoaG12.js";
7
+ import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR, GitClient, GrammarLoader, Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runSolutioningPhase, unescape, validateStopAfterFromConflict } from "../run-DE5xoB9U.js";
8
8
  import "../errors-CKFu8YI9.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
@@ -7484,7 +7484,7 @@ async function runStatusAction(options) {
7484
7484
  logger$13.debug({ err }, "Work graph query failed, continuing without work graph data");
7485
7485
  }
7486
7486
  if (run === void 0) {
7487
- const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-HmyFdWEf.js");
7487
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-BX84L5Qe.js");
7488
7488
  const substrateDirPath = join(projectRoot, ".substrate");
7489
7489
  const processInfo = inspectProcessTree$1({
7490
7490
  projectRoot,
@@ -9030,7 +9030,7 @@ async function runSupervisorAction(options, deps = {}) {
9030
9030
  await initSchema(expAdapter);
9031
9031
  const { runRunAction: runPipeline } = await import(
9032
9032
  /* @vite-ignore */
9033
- "../run-BF8oeJhG.js"
9033
+ "../run-ChqBlPYZ.js"
9034
9034
  );
9035
9035
  const runStoryFn = async (opts) => {
9036
9036
  const exitCode = await runPipeline({
@@ -1,4 +1,4 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-PdI4-96I.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-CNqQFdaT.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./dist-W2emvN3F.js";
4
4
  import "./decisions-C0pz9Clx.js";
@@ -1,19 +1,22 @@
1
1
  import { createLogger } from "./logger-KeHncl-f.js";
2
2
  import { DoltClient, DoltQueryError, LEARNING_FINDING, createDatabaseAdapter$1 as createDatabaseAdapter, createDecision, getDecisionsByCategory, getLatestRun, getPipelineRunById, initSchema } from "./dist-W2emvN3F.js";
3
3
  import { createRequire } from "module";
4
+ import * as path$1 from "path";
4
5
  import { dirname, join } from "path";
5
6
  import { readFile } from "fs/promises";
6
7
  import { EventEmitter } from "node:events";
7
8
  import { YAMLException, load } from "js-yaml";
8
9
  import { existsSync, promises, readFileSync, readdirSync, statSync } from "node:fs";
9
10
  import { spawn, spawnSync } from "node:child_process";
10
- import * as path$1 from "node:path";
11
+ import * as path$2 from "node:path";
11
12
  import { basename as basename$1, dirname as dirname$1, join as join$1, resolve as resolve$1 } from "node:path";
12
13
  import { z } from "zod";
13
14
  import { mkdir as mkdir$1, open, readFile as readFile$1, unlink, writeFile as writeFile$1 } from "node:fs/promises";
15
+ import * as fs from "fs";
14
16
  import { existsSync as existsSync$1 } from "fs";
15
17
  import { createRequire as createRequire$1 } from "node:module";
16
18
  import { fileURLToPath } from "node:url";
19
+ import { execSync as execSync$1 } from "child_process";
17
20
 
18
21
  //#region rolldown:runtime
19
22
  var __create = Object.create;
@@ -481,20 +484,20 @@ function detectCycles(edges) {
481
484
  }
482
485
  const visited = new Set();
483
486
  const visiting = new Set();
484
- const path$2 = [];
487
+ const path$3 = [];
485
488
  function dfs(node) {
486
489
  if (visiting.has(node)) {
487
- const cycleStart = path$2.indexOf(node);
488
- return [...path$2.slice(cycleStart), node];
490
+ const cycleStart = path$3.indexOf(node);
491
+ return [...path$3.slice(cycleStart), node];
489
492
  }
490
493
  if (visited.has(node)) return null;
491
494
  visiting.add(node);
492
- path$2.push(node);
495
+ path$3.push(node);
493
496
  for (const neighbor of adj.get(node) ?? []) {
494
497
  const cycle = dfs(neighbor);
495
498
  if (cycle !== null) return cycle;
496
499
  }
497
- path$2.pop();
500
+ path$3.pop();
498
501
  visiting.delete(node);
499
502
  visited.add(node);
500
503
  return null;
@@ -2928,6 +2931,16 @@ const SEVERITY_PREFIX = {
2928
2931
  info: "INFO"
2929
2932
  };
2930
2933
  /**
2934
+ * source-ac-shellout-npx-fallback — Story 67-3, obs_2026-05-03_023 fix #3.
2935
+ *
2936
+ * Severity: warn. Emitted by SourceAcShelloutCheck when a bare `npx <package>`
2937
+ * invocation (without `--no-install`) is detected in a story-modified source file.
2938
+ * A bare `npx <package>` without `--no-install` falls through to the public npm
2939
+ * registry on first use if the package binary is not locally installed —
2940
+ * a dependency-confusion attack vector.
2941
+ */
2942
+ const CATEGORY_SHELLOUT_NPX_FALLBACK = "source-ac-shellout-npx-fallback";
2943
+ /**
2931
2944
  * Render a list of findings into the multi-line human-readable string that
2932
2945
  * populates VerificationResult.details. One line per finding:
2933
2946
  *
@@ -3444,15 +3457,15 @@ function findCodeEvidence(opts) {
3444
3457
  reason: `AC text references ${token}, which is in files_modified`
3445
3458
  };
3446
3459
  for (const token of tokens) {
3447
- const base = path$1.basename(token);
3448
- const match = filesModified.find((f) => path$1.basename(f) === base);
3460
+ const base = path$2.basename(token);
3461
+ const match = filesModified.find((f) => path$2.basename(f) === base);
3449
3462
  if (match !== void 0) return {
3450
3463
  found: true,
3451
3464
  reason: `AC text references ${token}; matching basename ${match} is in files_modified`
3452
3465
  };
3453
3466
  }
3454
3467
  for (const token of tokens) try {
3455
- if (existsSync(path$1.join(workingDir, token))) return {
3468
+ if (existsSync(path$2.join(workingDir, token))) return {
3456
3469
  found: true,
3457
3470
  reason: `AC text references ${token}, which exists in working tree`
3458
3471
  };
@@ -3462,7 +3475,7 @@ function findCodeEvidence(opts) {
3462
3475
  const acMentionRe = new RegExp(`\\bAC\\s*:?\\s*#?\\s*${num}\\b`, "i");
3463
3476
  const testFiles = filesModified.filter(isTestFilePath);
3464
3477
  for (const testFile of testFiles) try {
3465
- const content = readFileSync(path$1.join(workingDir, testFile), "utf-8");
3478
+ const content = readFileSync(path$2.join(workingDir, testFile), "utf-8");
3466
3479
  if (acMentionRe.test(content)) return {
3467
3480
  found: true,
3468
3481
  reason: `${testFile} mentions ${acId}`
@@ -3790,11 +3803,11 @@ function parseRuntimeProbes(storyContent) {
3790
3803
  const validation = RuntimeProbeListSchema.safeParse(parsed);
3791
3804
  if (!validation.success) {
3792
3805
  const first = validation.error.issues[0];
3793
- const path$2 = first?.path.join(".") ?? "";
3806
+ const path$3 = first?.path.join(".") ?? "";
3794
3807
  const message = first?.message ?? "schema validation failed";
3795
3808
  return {
3796
3809
  kind: "invalid",
3797
- error: `probe list is malformed at ${path$2 || "<root>"}: ${message}`
3810
+ error: `probe list is malformed at ${path$3 || "<root>"}: ${message}`
3798
3811
  };
3799
3812
  }
3800
3813
  const probes = validation.data;
@@ -4552,18 +4565,18 @@ function existsAnywhereUnderRoot(root, base) {
4552
4565
  depth: 0
4553
4566
  }];
4554
4567
  while (stack.length > 0) {
4555
- const { path: path$2, depth } = stack.pop();
4568
+ const { path: path$3, depth } = stack.pop();
4556
4569
  if (depth > MAX_WALK_DEPTH) continue;
4557
4570
  let entries;
4558
4571
  try {
4559
- entries = readdirSync(path$2);
4572
+ entries = readdirSync(path$3);
4560
4573
  } catch {
4561
4574
  continue;
4562
4575
  }
4563
4576
  for (const entry of entries) {
4564
4577
  if (SKIP_DIRS.has(entry)) continue;
4565
4578
  if (entry === base) return true;
4566
- const full = join$1(path$2, entry);
4579
+ const full = join$1(path$3, entry);
4567
4580
  try {
4568
4581
  const s = statSync(full);
4569
4582
  if (s.isDirectory()) stack.push({
@@ -5106,6 +5119,140 @@ var SourceAcFidelityCheck = class {
5106
5119
  }
5107
5120
  };
5108
5121
 
5122
+ //#endregion
5123
+ //#region packages/sdlc/dist/verification/checks/source-ac-shellout-check.js
5124
+ /** Matches `npx <name>` but NOT `npx --no-install <name>`. */
5125
+ const NPX_PATTERN = /npx\s+(?!--no-install)([a-zA-Z0-9_@\-/]+)/g;
5126
+ /**
5127
+ * Returns `true` when the line (after trimming leading whitespace) starts with
5128
+ * a single-line comment marker (`//` or `#`). Block comments (/* … *\/) are not
5129
+ * matched here — they are handled by the string-literal context check.
5130
+ */
5131
+ function isCommentLine(line) {
5132
+ const trimmed = line.trimStart();
5133
+ return trimmed.startsWith("//") || trimmed.startsWith("#");
5134
+ }
5135
+ /**
5136
+ * Returns `true` when `matchIndex` falls inside a single-quoted (`'...'`),
5137
+ * double-quoted (`"..."`), or template-literal (`` `...` ``) region of the line,
5138
+ * OR when the line is a shebang (`#!...`).
5139
+ *
5140
+ * Implementation: scan character-by-character from index 0, toggling
5141
+ * `inSingle`, `inDouble`, `inTemplate` flags at unescaped quote characters.
5142
+ * An escaped quote is one where the immediately preceding character is `\`.
5143
+ * (Note: this is a heuristic — it does not handle `\\` or complex escape
5144
+ * sequences correctly. For a static-analysis severity:warn heuristic, the
5145
+ * simplification is acceptable.)
5146
+ */
5147
+ function isInStringLiteralContext(line, matchIndex) {
5148
+ if (line.trimStart().startsWith("#!")) return true;
5149
+ let inSingle = false;
5150
+ let inDouble = false;
5151
+ let inTemplate = false;
5152
+ for (let i = 0; i < matchIndex; i++) {
5153
+ const char = line[i];
5154
+ const escaped = i > 0 && line[i - 1] === "\\";
5155
+ if (!escaped) {
5156
+ if (char === "'" && !inDouble && !inTemplate) inSingle = !inSingle;
5157
+ else if (char === "\"" && !inSingle && !inTemplate) inDouble = !inDouble;
5158
+ else if (char === "`" && !inSingle && !inDouble) inTemplate = !inTemplate;
5159
+ }
5160
+ }
5161
+ return inSingle || inDouble || inTemplate;
5162
+ }
5163
+ /**
5164
+ * Reads the file at `absolutePath` and returns every line/match pair where
5165
+ * a bare `npx <name>` (without `--no-install`) appears inside a string-literal
5166
+ * context on a non-comment line.
5167
+ *
5168
+ * Returns 1-indexed line numbers.
5169
+ */
5170
+ function scanFile(absolutePath) {
5171
+ const content = fs.readFileSync(absolutePath, "utf-8");
5172
+ const lines = content.split("\n");
5173
+ const results = [];
5174
+ for (let i = 0; i < lines.length; i++) {
5175
+ const line = lines[i];
5176
+ if (line === void 0) continue;
5177
+ if (isCommentLine(line)) continue;
5178
+ NPX_PATTERN.lastIndex = 0;
5179
+ let match;
5180
+ const linePattern = new RegExp(NPX_PATTERN.source, "g");
5181
+ while ((match = linePattern.exec(line)) !== null) {
5182
+ const name = match[1];
5183
+ if (name !== void 0 && isInStringLiteralContext(line, match.index)) results.push({
5184
+ lineNum: i + 1,
5185
+ name
5186
+ });
5187
+ }
5188
+ }
5189
+ return results;
5190
+ }
5191
+ /**
5192
+ * Standalone function implementing the shellout check logic.
5193
+ * Exported separately so tests can call it directly without instantiating the class.
5194
+ */
5195
+ async function runShelloutCheck(context) {
5196
+ const start = Date.now();
5197
+ const findings = [];
5198
+ let modifiedFiles = context.devStoryResult?.files_modified ?? [];
5199
+ if (modifiedFiles.length === 0) try {
5200
+ const output = execSync$1("git diff --name-only HEAD~1", {
5201
+ cwd: context.workingDir,
5202
+ encoding: "utf-8"
5203
+ });
5204
+ modifiedFiles = output.trim().split("\n").filter((f) => f.length > 0);
5205
+ } catch {
5206
+ return {
5207
+ status: "pass",
5208
+ details: "source-ac-shellout: no modified files available — skipping check",
5209
+ duration_ms: Date.now() - start,
5210
+ findings: []
5211
+ };
5212
+ }
5213
+ const filesToCheck = modifiedFiles.filter((f) => !f.endsWith(".md"));
5214
+ if (filesToCheck.length === 0) return {
5215
+ status: "pass",
5216
+ details: "source-ac-shellout: no non-.md modified files — skipping check",
5217
+ duration_ms: Date.now() - start,
5218
+ findings: []
5219
+ };
5220
+ for (const relPath of filesToCheck) {
5221
+ const absPath = path$1.join(context.workingDir, relPath);
5222
+ let matches;
5223
+ try {
5224
+ matches = scanFile(absPath);
5225
+ } catch {
5226
+ continue;
5227
+ }
5228
+ for (const { lineNum, name } of matches) findings.push({
5229
+ category: CATEGORY_SHELLOUT_NPX_FALLBACK,
5230
+ severity: "warn",
5231
+ message: `npx fallback detected in ${relPath}:${lineNum}: "npx ${name}" — bare \`npx <package>\` without \`--no-install\` falls through to the public npm registry on first use. If \`<package>\` isn't a registered binary in your dev dependencies, this is a dependency-confusion vector. Use absolute path or \`npx --no-install <package>\` instead.`
5232
+ });
5233
+ }
5234
+ const status = findings.some((f) => f.severity === "error") ? "fail" : findings.some((f) => f.severity === "warn") ? "warn" : "pass";
5235
+ return {
5236
+ status,
5237
+ details: findings.length > 0 ? renderFindings(findings) : "source-ac-shellout: no bare npx fallback patterns detected",
5238
+ duration_ms: Date.now() - start,
5239
+ findings
5240
+ };
5241
+ }
5242
+ /**
5243
+ * VerificationCheck class for the shellout static-analysis gate.
5244
+ *
5245
+ * name = 'source-ac-shellout'
5246
+ * tier = 'A' (fast — file I/O only, no LLM, no subprocess except optional git fallback)
5247
+ */
5248
+ var SourceAcShelloutCheck = class {
5249
+ name = "source-ac-shellout";
5250
+ tier = "A";
5251
+ async run(context) {
5252
+ return runShelloutCheck(context);
5253
+ }
5254
+ };
5255
+
5109
5256
  //#endregion
5110
5257
  //#region packages/sdlc/dist/verification/verification-pipeline.js
5111
5258
  /**
@@ -5232,7 +5379,8 @@ function createDefaultVerificationPipeline(bus, config) {
5232
5379
  new AcceptanceCriteriaEvidenceCheck(),
5233
5380
  new BuildCheck(),
5234
5381
  new RuntimeProbeCheck(),
5235
- new SourceAcFidelityCheck()
5382
+ new SourceAcFidelityCheck(),
5383
+ new SourceAcShelloutCheck()
5236
5384
  ];
5237
5385
  return new VerificationPipeline(bus, checks);
5238
5386
  }
@@ -6595,7 +6743,7 @@ function inspectProcessTree(opts) {
6595
6743
  }
6596
6744
  const lines = psOutput.split("\n");
6597
6745
  if (substrateDirPath !== void 0) try {
6598
- const readFileSyncFn = readFileSyncOverride ?? ((path$2, encoding) => readFileSync(path$2, encoding));
6746
+ const readFileSyncFn = readFileSyncOverride ?? ((path$3, encoding) => readFileSync(path$3, encoding));
6599
6747
  const pidContent = readFileSyncFn(join(substrateDirPath, "orchestrator.pid"), "utf-8");
6600
6748
  const pid = parseInt(pidContent.trim(), 10);
6601
6749
  if (!isNaN(pid) && pid > 0) {
@@ -7030,4 +7178,4 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
7030
7178
 
7031
7179
  //#endregion
7032
7180
  export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, RuntimeProbeListSchema, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, __commonJS, __require, __toESM, aggregateProbeAuthorMetrics, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, createStateStore, detectCycles, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, parseRuntimeProbes, registerHealthCommand, renderFindings, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics, runHealthAction, validateStoryKey };
7033
- //# sourceMappingURL=health-PdI4-96I.js.map
7181
+ //# sourceMappingURL=health-CNqQFdaT.js.map
@@ -1,8 +1,8 @@
1
- import "./health-PdI4-96I.js";
1
+ import "./health-CNqQFdaT.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-W2emvN3F.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-DcDoaG12.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-DE5xoB9U.js";
6
6
  import "./routing-CcBOCuC9.js";
7
7
  import "./decisions-C0pz9Clx.js";
8
8
 
@@ -1,4 +1,4 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, RuntimeProbeListSchema, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-PdI4-96I.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, RuntimeProbeListSchema, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-CNqQFdaT.js";
2
2
  import { createLogger } from "./logger-KeHncl-f.js";
3
3
  import { TypedEventBusImpl, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-CElYrONe.js";
4
4
  import { ADVISORY_NOTES, Categorizer, ConsumerAnalyzer, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, IngestionServer, LogTurnAnalyzer, OPERATIONAL_FINDING, Recommender, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, callLLM, createConfigSystem, createDatabaseAdapter$1, createDecision, createPipelineRun, createRequirement, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-W2emvN3F.js";
@@ -45472,4 +45472,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
45472
45472
 
45473
45473
  //#endregion
45474
45474
  export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR$1 as GLOBSTAR, GitClient, GrammarLoader, Minimatch$1 as Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape$1 as escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runRunAction, runSolutioningPhase, unescape$1 as unescape, validateStopAfterFromConflict, wireNdjsonEmitter };
45475
- //# sourceMappingURL=run-DcDoaG12.js.map
45475
+ //# sourceMappingURL=run-DE5xoB9U.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.20.57",
3
+ "version": "0.20.58",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -108,6 +108,20 @@ Use this exact format for each item:
108
108
 
109
109
  **Behavioral signals (runtime-dependent even when the artifact ships as TypeScript / JavaScript / Python source):** the AC describes the implementation invoking a **subprocess** (`execSync`, `spawn`, `child_process`), reading or writing the **filesystem outside test tmpdirs** (`fs.read*`, `fs.write*`, `path.join(homedir(), ...)`), running **git operations** (`git log`, `git push`, `git merge`), querying a **database** (Dolt, mysql, sqlite, postgres), making **network requests** (`fetch`, `axios`, `http.get`), or scanning a **registry / configuration source** ("queries the registry", "scans the fleet").
110
110
 
111
+ **Shell-script generation signals (runtime-dependent even when the artifact ships as source: the generated script runs on a live host, installs into `.git/hooks/`, or registers with the OS):** the AC describes the implementation generating or installing a script that a user or system event invokes — the correctness of that script can only be confirmed by running it in a real or ephemeral environment, not by inspecting source code alone. Signal types to recognize:
112
+
113
+ - **Hook generators**: code that writes or installs git hooks (`pre-push`, `post-merge`, `pre-commit`, `post-commit`, `post-rewrite`), configures `husky` or other hook managers, or writes files to `.git/hooks/*`.
114
+ - **Install scripts**: commands like `vg install`, `<binary> install`, or AC phrases like "installs the X hook", "writes the X script", "generates a wrapper for Y".
115
+ - **Lifecycle scripts**: generates npm `prepublish`, `postinstall`, or `prepare` scripts; writes entries into `package.json` `scripts` field programmatically.
116
+ - **Service generators**: generates systemd `.service` or `.timer` unit files, podman/docker image build scripts, or any file invoked by a scheduler or init system.
117
+ - **Wrapper scripts**: produces `#!/bin/sh`-style shell wrappers around binaries (e.g., `#!/bin/sh` / `exec node "$@"` shape), shim generators, or delegate-to-binary shell scripts.
118
+
119
+ Phrase patterns to flag as shell-script generation signals: "writes a hook", "generates a script", "installs a pre-push hook", "creates a wrapper", "emits a shell script", "writes to .git/hooks/", "creates a systemd unit".
120
+
121
+ Strata Stories 3-3+3-4 shipped LGTM_WITH_NOTES with a real dependency-confusion attack vector (`npx strata` fallback) because the verification gate accepted shell-script-generating code without a canonical-invocation probe. See `obs_2026-05-03_023` (create-story prompt enforce probe section for shell-out boundaries).
122
+
123
+ If any shell-script generation signal fires, the story MUST include a `## Runtime Probes` section that invokes the canonical user trigger in a fresh fixture project, not direct-call the script.
124
+
111
125
  **Architectural-level signals (the same external-state interactions described at higher abstraction levels — runtime-dependent identically):** the AC names a **named external dependency** (service, package, agent, skill, mesh, registry, queue, outbox, store, daemon, etc.) AND describes any **interaction verb** (queries, publishes, consumes, calls, writes to, reads from, subscribes to, registers with, delegates to, reaches for, persists to). Phrase patterns to recognize as the architectural-level equivalents of the code-API enumeration above:
112
126
 
113
127
  - `queries <service-or-skill>` (network / database) — e.g., "queries agent-mesh's `query-reports` skill", "queries the Dolt registry"
@@ -312,6 +312,59 @@ Read from an npm/package registry or fleet-config source. Precede the registry p
312
312
  description: npm registry resolves @substrate-ai/sdlc and returns a semver version string
313
313
  ```
314
314
 
315
+ ## Shell-script generation probe shapes
316
+
317
+ Shell-script generation ACs describe a generator that produces a lifecycle script — a pre-push hook, a postinstall wrapper, a systemd unit startup shim, or a cron-job body — that the **user** then invokes through a canonical mechanism (`git push`, `npm install`, etc.). This AC class was identified in obs_2026-05-03_023 (strata 3-3+3-4 incident: a pre-push hook generator shipped SHIP_IT with a dependency-confusion attack vector because the verification probe direct-called the generated script with synthetic inputs rather than invoking the canonical user trigger).
318
+
319
+ **Why the fresh-fixture requirement is critical:**
320
+
321
+ (a) The orchestrator's working tree may have global state (installed binaries, config files) that a typical user environment does not. A probe run against substrate's own working tree silently satisfies preconditions that would fail in a fresh project.
322
+
323
+ (b) The canonical user invocation runs in the user's project root — not substrate's. A probe that bypasses the install + wiring step (e.g., calls `bash .git/hooks/pre-push` directly) cannot detect that the hook was installed to the wrong path, was installed with the wrong mode, or was wired to the wrong trigger event.
324
+
325
+ (c) Defects like dependency-confusion (`npx <package>` fallback to global registry) only manifest when no local binary exists. On the orchestrator's machine, `node_modules/.bin/strata` satisfies the lookup before npm's fallback fires — masking the defect from any probe that runs inside the substrate working tree.
326
+
327
+ **Three rules for shell-script generation probes:**
328
+
329
+ 1. **Fresh fixture in `mktemp -d`** — never run against substrate's own project tree. The working tree silently satisfies probes that would fail in a user's fresh environment. Create a throwaway `mktemp -d` directory, `git init` it, and install the generator into it via the canonical install command.
330
+
331
+ 2. **Canonical user trigger** — `git push` for a pre-push hook, `npm install` for a postinstall hook, NOT direct script invocation (`bash .git/hooks/pre-push`). Direct invocation skips the wiring layer that determines whether the hook actually fires on the user's machine. See the trigger table in "Probes for event-driven mechanisms must invoke the production trigger" above.
332
+
333
+ 3. **Observable post-condition** — assert filesystem or process state the user would observe (e.g., `test -f .findings/history.jsonl`), not just exit code. A script that exits 0 without writing the expected artifact satisfies exit-code-only probes but silently fails the user. The assertion target must be the output the user can inspect after the event fires.
334
+
335
+ **Canonical worked example (strata 3-3 pre-push hook scenario — obs_2026-05-03_023 fix #1):**
336
+
337
+ ```yaml
338
+ - name: pre-push-hook-fires-on-real-push-and-archives-findings
339
+ sandbox: twin
340
+ command: |
341
+ set -e
342
+ FIXTURE=$(mktemp -d)
343
+ cd "$FIXTURE"
344
+ npm init -y >/dev/null
345
+ git init -q
346
+ git config user.email t@example.com && git config user.name test
347
+ # install via canonical user invocation (no global packages)
348
+ node <REPO_ROOT>/dist/cli.js vg install
349
+ # produce a finding-eligible change
350
+ mkdir -p src
351
+ echo "import x from 'lodash';" > src/bad.ts
352
+ git add . && git commit -qm "initial"
353
+ # trigger canonical user-facing event via git push (pre-push hook fires here)
354
+ REMOTE=$(mktemp -d)
355
+ git init --bare -q "$REMOTE"
356
+ git remote add origin "$REMOTE"
357
+ git push origin main 2>&1 || true
358
+ # assert observable post-condition
359
+ test -f .findings/history.jsonl && echo "ARCHIVE_PRESENT" || echo "ARCHIVE_MISSING"
360
+ expect_stdout_regex:
361
+ - ARCHIVE_PRESENT
362
+ description: >-
363
+ strata 3-3 canonical pre-push hook shape — fresh fixture (mktemp -d),
364
+ canonical user trigger (git push), observable post-condition assertion
365
+ (obs_2026-05-03_023 fix #1)
366
+ ```
367
+
315
368
  ## Mission
316
369
 
317
370
  Author runtime probes for the story described above. Use the AC sections provided: