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 +4 -4
- package/dist/{health-HmyFdWEf.js → health-BX84L5Qe.js} +1 -1
- package/dist/{health-PdI4-96I.js → health-CNqQFdaT.js} +166 -18
- package/dist/{run-BF8oeJhG.js → run-ChqBlPYZ.js} +2 -2
- package/dist/{run-DcDoaG12.js → run-DE5xoB9U.js} +2 -2
- package/package.json +1 -1
- package/packs/bmad/prompts/create-story.md +14 -0
- package/packs/bmad/prompts/probe-author.md +53 -0
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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$
|
|
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$
|
|
487
|
+
const path$3 = [];
|
|
485
488
|
function dfs(node) {
|
|
486
489
|
if (visiting.has(node)) {
|
|
487
|
-
const cycleStart = path$
|
|
488
|
-
return [...path$
|
|
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$
|
|
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$
|
|
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$
|
|
3448
|
-
const match = filesModified.find((f) => path$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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-
|
|
7181
|
+
//# sourceMappingURL=health-CNqQFdaT.js.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import "./health-
|
|
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-
|
|
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-
|
|
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-
|
|
45475
|
+
//# sourceMappingURL=run-DE5xoB9U.js.map
|
package/package.json
CHANGED
|
@@ -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:
|