substrate-ai 0.20.52 → 0.20.54
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
|
@@ -4,7 +4,7 @@ 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-VcMmfo2w.js";
|
|
6
6
|
import "../adapter-registry-DXLMTmfD.js";
|
|
7
|
-
import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runSolutioningPhase, validateStopAfterFromConflict } from "../run-
|
|
7
|
+
import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runSolutioningPhase, validateStopAfterFromConflict } from "../run-DgmoC6HT.js";
|
|
8
8
|
import "../errors-CogpxBUg.js";
|
|
9
9
|
import "../routing-CcBOCuC9.js";
|
|
10
10
|
import "../decisions-C0pz9Clx.js";
|
|
@@ -5204,7 +5204,7 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
5204
5204
|
await initSchema(expAdapter);
|
|
5205
5205
|
const { runRunAction: runPipeline } = await import(
|
|
5206
5206
|
/* @vite-ignore */
|
|
5207
|
-
"../run-
|
|
5207
|
+
"../run-B-SRlzdi.js"
|
|
5208
5208
|
);
|
|
5209
5209
|
const runStoryFn = async (opts) => {
|
|
5210
5210
|
const exitCode = await runPipeline({
|
|
@@ -2,7 +2,7 @@ import "./health-sQ1X_5_6.js";
|
|
|
2
2
|
import "./logger-KeHncl-f.js";
|
|
3
3
|
import "./helpers-CElYrONe.js";
|
|
4
4
|
import "./dist-VcMmfo2w.js";
|
|
5
|
-
import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-
|
|
5
|
+
import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-DgmoC6HT.js";
|
|
6
6
|
import "./routing-CcBOCuC9.js";
|
|
7
7
|
import "./decisions-C0pz9Clx.js";
|
|
8
8
|
|
|
@@ -8448,12 +8448,13 @@ const TIMEOUT_RETRY_MULTIPLIER = 1.5;
|
|
|
8448
8448
|
*/
|
|
8449
8449
|
async function runProbeAuthor(deps, params) {
|
|
8450
8450
|
const start = Date.now();
|
|
8451
|
-
const { storyKey, storyFilePath, pipelineRunId, sourceAcContent, epicContent, emitEvent, bypassGates } = params;
|
|
8451
|
+
const { storyKey, storyFilePath, pipelineRunId, sourceAcContent, epicContent, emitEvent, bypassGates, stateIntegratingEnabled } = params;
|
|
8452
8452
|
const tokenUsage = {
|
|
8453
8453
|
input: 0,
|
|
8454
8454
|
output: 0
|
|
8455
8455
|
};
|
|
8456
|
-
|
|
8456
|
+
const stateIntegratingActive = stateIntegratingEnabled !== false;
|
|
8457
|
+
if (bypassGates !== true && !detectsEventDrivenAC(epicContent) && !(stateIntegratingActive && detectsStateIntegratingAC(epicContent))) {
|
|
8457
8458
|
logger$14.debug({ storyKey }, "probe-author: source AC neither event-driven nor state-integrating — skipping");
|
|
8458
8459
|
emitEvent?.("probe-author:skipped", {
|
|
8459
8460
|
storyKey,
|
|
@@ -13467,7 +13468,8 @@ function createImplementationOrchestrator(deps) {
|
|
|
13467
13468
|
const section = extractStorySection(epicFull, storyKey);
|
|
13468
13469
|
probeAuthorEpicContent = section ?? epicFull;
|
|
13469
13470
|
} catch {}
|
|
13470
|
-
|
|
13471
|
+
const stateIntegratingEnabled = config.probeAuthorStateIntegrating !== false;
|
|
13472
|
+
if (detectsEventDrivenAC(probeAuthorEpicContent) || stateIntegratingEnabled && detectsStateIntegratingAC(probeAuthorEpicContent)) {
|
|
13471
13473
|
let artifactHasProbes = false;
|
|
13472
13474
|
try {
|
|
13473
13475
|
const artifactContent = readFileSync(storyFilePath, "utf-8");
|
|
@@ -13489,6 +13491,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
13489
13491
|
pipelineRunId: config.pipelineRunId ?? "",
|
|
13490
13492
|
sourceAcContent: probeAuthorEpicContent,
|
|
13491
13493
|
epicContent: probeAuthorEpicContent,
|
|
13494
|
+
stateIntegratingEnabled,
|
|
13492
13495
|
emitEvent: (name, payload) => {
|
|
13493
13496
|
eventBus.emit("orchestrator:story-warn", {
|
|
13494
13497
|
storyKey,
|
|
@@ -44004,8 +44007,23 @@ function wireNdjsonEmitter(eventBus, ndjsonEmitter) {
|
|
|
44004
44007
|
});
|
|
44005
44008
|
});
|
|
44006
44009
|
}
|
|
44010
|
+
/**
|
|
44011
|
+
* Resolve the `probeAuthorStateIntegrating` boolean from CLI flag and env var.
|
|
44012
|
+
*
|
|
44013
|
+
* Precedence: CLI flag (`on`/`off`) > env var `SUBSTRATE_PROBE_AUTHOR_STATE_INTEGRATING`
|
|
44014
|
+
* (`on`/`off`) > default `true`.
|
|
44015
|
+
*
|
|
44016
|
+
* @param cliFlag - The value of `--probe-author-state-integrating` CLI flag, or undefined
|
|
44017
|
+
* @returns `true` when state-integrating dispatch is enabled, `false` when disabled
|
|
44018
|
+
*/
|
|
44019
|
+
function resolveProbeAuthorStateIntegrating(cliFlag) {
|
|
44020
|
+
if (cliFlag !== void 0) return cliFlag === "on";
|
|
44021
|
+
const envVal = process.env["SUBSTRATE_PROBE_AUTHOR_STATE_INTEGRATING"];
|
|
44022
|
+
if (envVal !== void 0) return envVal === "on";
|
|
44023
|
+
return true;
|
|
44024
|
+
}
|
|
44007
44025
|
async function runRunAction(options) {
|
|
44008
|
-
const { pack: packName, from: startPhase, stopAfter, concept: conceptArg, conceptFile, stories: storiesArg, concurrency, outputFormat, projectRoot, events: eventsFlag, verbose: verboseFlag, tui: tuiFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, skipVerification, epic: epicNumber, dryRun, maxReviewCycles = 2, engine, agent: agentId, registry: injectedRegistry, haltOn, costCeiling, probeAuthor } = options;
|
|
44026
|
+
const { pack: packName, from: startPhase, stopAfter, concept: conceptArg, conceptFile, stories: storiesArg, concurrency, outputFormat, projectRoot, events: eventsFlag, verbose: verboseFlag, tui: tuiFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, skipVerification, epic: epicNumber, dryRun, maxReviewCycles = 2, engine, agent: agentId, registry: injectedRegistry, haltOn, costCeiling, probeAuthor, probeAuthorStateIntegrating: probeAuthorStateIntegratingFlag } = options;
|
|
44009
44027
|
const VALID_PROBE_AUTHOR_MODES = [
|
|
44010
44028
|
"enabled",
|
|
44011
44029
|
"disabled",
|
|
@@ -44017,6 +44035,14 @@ async function runRunAction(options) {
|
|
|
44017
44035
|
else process.stderr.write(`Error: ${errorMsg}\n`);
|
|
44018
44036
|
return 1;
|
|
44019
44037
|
}
|
|
44038
|
+
const VALID_PROBE_AUTHOR_STATE_INTEGRATING = ["on", "off"];
|
|
44039
|
+
if (probeAuthorStateIntegratingFlag !== void 0 && !VALID_PROBE_AUTHOR_STATE_INTEGRATING.includes(probeAuthorStateIntegratingFlag)) {
|
|
44040
|
+
const errorMsg = `Invalid --probe-author-state-integrating value '${probeAuthorStateIntegratingFlag}'. Valid values: on | off`;
|
|
44041
|
+
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
|
|
44042
|
+
else process.stderr.write(`Error: ${errorMsg}\n`);
|
|
44043
|
+
return 1;
|
|
44044
|
+
}
|
|
44045
|
+
const resolvedProbeAuthorStateIntegrating = resolveProbeAuthorStateIntegrating(probeAuthorStateIntegratingFlag);
|
|
44020
44046
|
const VALID_HALT_ON = [
|
|
44021
44047
|
"all",
|
|
44022
44048
|
"critical",
|
|
@@ -44212,6 +44238,7 @@ async function runRunAction(options) {
|
|
|
44212
44238
|
meshProjectId
|
|
44213
44239
|
} : {},
|
|
44214
44240
|
...probeAuthor !== void 0 ? { probeAuthor } : {},
|
|
44241
|
+
probeAuthorStateIntegrating: resolvedProbeAuthorStateIntegrating,
|
|
44215
44242
|
engineType: resolvedEngine,
|
|
44216
44243
|
maxReviewCycles: effectiveMaxReviewCycles,
|
|
44217
44244
|
retryBudget: configRetryBudget ?? 2,
|
|
@@ -44750,7 +44777,8 @@ async function runRunAction(options) {
|
|
|
44750
44777
|
enableHeartbeat: eventsFlag === true,
|
|
44751
44778
|
skipPreflight: skipPreflight === true,
|
|
44752
44779
|
...skipVerification === true ? { skipVerification: true } : {},
|
|
44753
|
-
...probeAuthor !== void 0 ? { probeAuthorMode: probeAuthor } : {}
|
|
44780
|
+
...probeAuthor !== void 0 ? { probeAuthorMode: probeAuthor } : {},
|
|
44781
|
+
probeAuthorStateIntegrating: resolvedProbeAuthorStateIntegrating
|
|
44754
44782
|
},
|
|
44755
44783
|
projectRoot,
|
|
44756
44784
|
tokenCeilings,
|
|
@@ -44881,7 +44909,7 @@ async function runRunAction(options) {
|
|
|
44881
44909
|
}
|
|
44882
44910
|
}
|
|
44883
44911
|
async function runFullPipeline(options) {
|
|
44884
|
-
const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, skipVerification, maxReviewCycles = 2, retryBudget, registry: injectedRegistry, tokenCeilings, stories: explicitStories, telemetryEnabled: fullTelemetryEnabled, telemetryPort: fullTelemetryPort, agentId, meshUrl: fpMeshUrl, meshProjectId: fpMeshProjectId, engineType: fpEngineType, probeAuthor } = options;
|
|
44912
|
+
const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, skipVerification, maxReviewCycles = 2, retryBudget, registry: injectedRegistry, tokenCeilings, stories: explicitStories, telemetryEnabled: fullTelemetryEnabled, telemetryPort: fullTelemetryPort, agentId, meshUrl: fpMeshUrl, meshProjectId: fpMeshProjectId, engineType: fpEngineType, probeAuthor, probeAuthorStateIntegrating: fpProbeAuthorStateIntegrating } = options;
|
|
44885
44913
|
if (!existsSync$1(dbDir)) mkdirSync$1(dbDir, { recursive: true });
|
|
44886
44914
|
let doltServerFull = null;
|
|
44887
44915
|
try {
|
|
@@ -45183,7 +45211,8 @@ async function runFullPipeline(options) {
|
|
|
45183
45211
|
pipelineRunId: runId,
|
|
45184
45212
|
skipPreflight: skipPreflight === true,
|
|
45185
45213
|
...skipVerification === true ? { skipVerification: true } : {},
|
|
45186
|
-
...probeAuthor !== void 0 ? { probeAuthorMode: probeAuthor } : {}
|
|
45214
|
+
...probeAuthor !== void 0 ? { probeAuthorMode: probeAuthor } : {},
|
|
45215
|
+
...fpProbeAuthorStateIntegrating !== void 0 ? { probeAuthorStateIntegrating: fpProbeAuthorStateIntegrating } : {}
|
|
45187
45216
|
},
|
|
45188
45217
|
projectRoot,
|
|
45189
45218
|
tokenCeilings,
|
|
@@ -45339,7 +45368,7 @@ async function runFullPipeline(options) {
|
|
|
45339
45368
|
}
|
|
45340
45369
|
}
|
|
45341
45370
|
function registerRunCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
|
|
45342
|
-
program.command("run").description("Run the autonomous pipeline (use --from to start from a specific phase)").option("--pack <name>", "Methodology pack name", "bmad").option("--from <phase>", "Start from this phase: analysis, planning, solutioning, implementation").option("--stop-after <phase>", "Stop pipeline after this phase completes").option("--concept <text>", "Inline concept text (required when --from analysis)").option("--concept-file <path>", "Path to a file containing the concept text").option("--stories <keys>", "Comma-separated story keys (e.g., 10-1,10-2)").option("--epic <n>", "Scope story discovery to a single epic number (e.g., 27)", (v) => parseInt(v, 10)).option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").option("--verbose", "Show detailed pino log output").option("--help-agent", "Print a machine-optimized prompt fragment for AI agents and exit").option("--tui", "Show TUI dashboard").option("--skip-ux", "Skip the UX design phase even if enabled in the pack manifest").option("--research", "Enable the research phase even if not set in the pack manifest").option("--skip-research", "Skip the research phase even if enabled in the pack manifest").option("--skip-preflight", "Skip the pre-flight build check (escape hatch for known-broken projects)").option("--skip-verification", "Skip the post-dispatch verification pipeline (Story 51-5)").option("--max-review-cycles <n>", "Maximum review cycles per story (default: 2)", (v) => parseInt(v, 10), 2).option("--dry-run", "Preview routing and repo-map injection without dispatching (Story 28-9)").option("--engine <type>", "Execution engine: linear (default) or graph").option("--agent <id>", "Agent backend: claude-code (default), codex, or gemini").option("--halt-on <severity>", "Halt pipeline on escalation severity: all | critical | none (default: none)", "none").option("--cost-ceiling <amount>", "Maximum cost ceiling in USD (positive number); halts pipeline when exceeded", parseFloat).option("--probe-author <mode>", "probe-author phase mode: enabled | disabled | auto (default: auto = SUBSTRATE_PROBE_AUTHOR_ENABLED env, default true) (Story 60-14)", "auto").action(async (opts) => {
|
|
45371
|
+
program.command("run").description("Run the autonomous pipeline (use --from to start from a specific phase)").option("--pack <name>", "Methodology pack name", "bmad").option("--from <phase>", "Start from this phase: analysis, planning, solutioning, implementation").option("--stop-after <phase>", "Stop pipeline after this phase completes").option("--concept <text>", "Inline concept text (required when --from analysis)").option("--concept-file <path>", "Path to a file containing the concept text").option("--stories <keys>", "Comma-separated story keys (e.g., 10-1,10-2)").option("--epic <n>", "Scope story discovery to a single epic number (e.g., 27)", (v) => parseInt(v, 10)).option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").option("--verbose", "Show detailed pino log output").option("--help-agent", "Print a machine-optimized prompt fragment for AI agents and exit").option("--tui", "Show TUI dashboard").option("--skip-ux", "Skip the UX design phase even if enabled in the pack manifest").option("--research", "Enable the research phase even if not set in the pack manifest").option("--skip-research", "Skip the research phase even if enabled in the pack manifest").option("--skip-preflight", "Skip the pre-flight build check (escape hatch for known-broken projects)").option("--skip-verification", "Skip the post-dispatch verification pipeline (Story 51-5)").option("--max-review-cycles <n>", "Maximum review cycles per story (default: 2)", (v) => parseInt(v, 10), 2).option("--dry-run", "Preview routing and repo-map injection without dispatching (Story 28-9)").option("--engine <type>", "Execution engine: linear (default) or graph").option("--agent <id>", "Agent backend: claude-code (default), codex, or gemini").option("--halt-on <severity>", "Halt pipeline on escalation severity: all | critical | none (default: none)", "none").option("--cost-ceiling <amount>", "Maximum cost ceiling in USD (positive number); halts pipeline when exceeded", parseFloat).option("--probe-author <mode>", "probe-author phase mode: enabled | disabled | auto (default: auto = SUBSTRATE_PROBE_AUTHOR_ENABLED env, default true) (Story 60-14)", "auto").option("--probe-author-state-integrating <value>", "Disable probe-author dispatch for state-integrating ACs (Phase 3). Use to ramp DOWN if catch rate drops below the GREEN threshold. Values: on | off (default: on)").action(async (opts) => {
|
|
45343
45372
|
if (opts.helpAgent) {
|
|
45344
45373
|
process.exitCode = await runHelpAgent();
|
|
45345
45374
|
return;
|
|
@@ -45382,12 +45411,13 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
45382
45411
|
registry,
|
|
45383
45412
|
haltOn: opts.haltOn,
|
|
45384
45413
|
costCeiling: opts.costCeiling,
|
|
45385
|
-
probeAuthor: opts.probeAuthor
|
|
45414
|
+
probeAuthor: opts.probeAuthor,
|
|
45415
|
+
probeAuthorStateIntegrating: opts.probeAuthorStateIntegrating
|
|
45386
45416
|
});
|
|
45387
45417
|
process.exitCode = exitCode;
|
|
45388
45418
|
});
|
|
45389
45419
|
}
|
|
45390
45420
|
|
|
45391
45421
|
//#endregion
|
|
45392
|
-
export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveMaxReviewCycles, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runRunAction, runSolutioningPhase, validateStopAfterFromConflict, wireNdjsonEmitter };
|
|
45393
|
-
//# sourceMappingURL=run-
|
|
45422
|
+
export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runRunAction, runSolutioningPhase, validateStopAfterFromConflict, wireNdjsonEmitter };
|
|
45423
|
+
//# sourceMappingURL=run-DgmoC6HT.js.map
|
package/package.json
CHANGED
|
@@ -162,6 +162,156 @@ Strata Story 2-4 ("morning briefing generator", v0.20.41) shipped two architectu
|
|
|
162
162
|
|
|
163
163
|
A one-repo variant of this probe would pass against the (broken) v0.20.41 implementation; the two-repo variant catches the wrong-cwd defect because the parent-cwd `git log --all` returns BOTH commits but substring-match attribution mis-routes them.
|
|
164
164
|
|
|
165
|
+
## State-integration probe shapes
|
|
166
|
+
|
|
167
|
+
State-integration probes exercise code that reads from or writes to external state: the filesystem, subprocesses, git repositories, databases, network endpoints, and registries. Four principles govern probe design in this category:
|
|
168
|
+
|
|
169
|
+
1. **Real-state context, not synthesized**: populate a tmpdir with a structure matching the production layout (e.g., for fleet-scanning logic, N subdirs each containing a `.git` directory). Do not pass artificial in-memory structures or bypass the I/O layer.
|
|
170
|
+
2. **Sandbox choice leans `twin` more often**: any probe that touches the user's home directory, writes to the filesystem outside the project, or exercises a running service MUST use `sandbox: twin`. Reserve `sandbox: host` exclusively for read-only registry / config-shape probes that cannot mutate host state.
|
|
171
|
+
3. **Multi-resource fixtures MUST contain ≥2 distinct, non-overlapping resources**: a single-resource fixture silently passes when the defect only surfaces under multiplicity. Create two or more distinct instances and assert each one independently.
|
|
172
|
+
4. **External-binary availability assertions**: if the probe invokes `git`, `dolt`, `podman`, or any other binary that may not be installed, either add a sibling availability probe or put an inline `command -v <binary> || { echo "NOT_FOUND"; exit 1; }` check at the top of the `command:` block.
|
|
173
|
+
|
|
174
|
+
### Filesystem shape
|
|
175
|
+
|
|
176
|
+
Populate a tmpdir with a production-layout directory structure and assert file contents or directory invariants. Use `sandbox: twin` — the probe writes to the filesystem.
|
|
177
|
+
|
|
178
|
+
```yaml
|
|
179
|
+
- name: fleet-config-files-written-per-project
|
|
180
|
+
sandbox: twin
|
|
181
|
+
command: |
|
|
182
|
+
set -e
|
|
183
|
+
FLEET=$(mktemp -d)
|
|
184
|
+
mkdir -p "$FLEET/alpha" "$FLEET/beta"
|
|
185
|
+
node <REPO_ROOT>/dist/cli.mjs scan-fleet --root "$FLEET"
|
|
186
|
+
test -f "$FLEET/alpha/config.json" && echo "ALPHA_CONFIG_FOUND"
|
|
187
|
+
test -f "$FLEET/beta/config.json" && echo "BETA_CONFIG_FOUND"
|
|
188
|
+
expect_stdout_regex:
|
|
189
|
+
- ALPHA_CONFIG_FOUND
|
|
190
|
+
- BETA_CONFIG_FOUND
|
|
191
|
+
description: fleet scanner writes per-project config files to production-layout tmpdir with ≥2 dirs
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Subprocess shape
|
|
195
|
+
|
|
196
|
+
Assert binary availability first via `command -v`, then exercise the subprocess via its production invocation path. Use `sandbox: twin` — the probe may mutate host state.
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
- name: git-binary-available
|
|
200
|
+
sandbox: host
|
|
201
|
+
command: |
|
|
202
|
+
command -v git && echo "GIT_FOUND" || { echo "GIT_NOT_FOUND"; exit 1; }
|
|
203
|
+
expect_stdout_regex:
|
|
204
|
+
- GIT_FOUND
|
|
205
|
+
description: git binary must be on PATH before subprocess probes run
|
|
206
|
+
- name: subprocess-production-path-invoked
|
|
207
|
+
sandbox: twin
|
|
208
|
+
command: |
|
|
209
|
+
set -e
|
|
210
|
+
TMPOUT=$(mktemp)
|
|
211
|
+
node <REPO_ROOT>/dist/cli.mjs run-task --output "$TMPOUT"
|
|
212
|
+
grep -q '"status":"ok"' "$TMPOUT" && echo "TASK_COMPLETED"
|
|
213
|
+
expect_stdout_regex:
|
|
214
|
+
- TASK_COMPLETED
|
|
215
|
+
description: subprocess invocation via production CLI path produces expected output shape
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Git shape (canonical obs_017 pattern)
|
|
219
|
+
|
|
220
|
+
Create a ≥2-repo fleet in a tmpdir with non-overlapping commit messages, set `cwd` per-repo (NOT fleet root), and assert each repo's commits are attributed correctly. This is the canonical obs_017 pattern: the cwd-as-parent defect produces plausible output against a one-repo fleet but fails with two repos because `git log` at fleet root aggregates all repos' commits into one undifferentiated blob, making per-project attribution impossible.
|
|
221
|
+
|
|
222
|
+
```yaml
|
|
223
|
+
- name: git-per-repo-commit-attribution
|
|
224
|
+
sandbox: twin
|
|
225
|
+
command: |
|
|
226
|
+
set -e
|
|
227
|
+
FLEET=$(mktemp -d)
|
|
228
|
+
for proj in alpha beta; do
|
|
229
|
+
mkdir -p "$FLEET/$proj"
|
|
230
|
+
git -C "$FLEET/$proj" init -q
|
|
231
|
+
git -C "$FLEET/$proj" config user.email t@example.com
|
|
232
|
+
git -C "$FLEET/$proj" config user.name test
|
|
233
|
+
echo "$proj content" > "$FLEET/$proj/a.md"
|
|
234
|
+
git -C "$FLEET/$proj" add .
|
|
235
|
+
git -C "$FLEET/$proj" commit -qm "$proj-only commit"
|
|
236
|
+
done
|
|
237
|
+
ALPHA_LOG=$(git -C "$FLEET/alpha" log --oneline)
|
|
238
|
+
BETA_LOG=$(git -C "$FLEET/beta" log --oneline)
|
|
239
|
+
echo "alpha: $ALPHA_LOG"
|
|
240
|
+
echo "beta: $BETA_LOG"
|
|
241
|
+
expect_stdout_regex:
|
|
242
|
+
- 'alpha:.*alpha-only commit'
|
|
243
|
+
- 'beta:.*beta-only commit'
|
|
244
|
+
expect_stdout_no_regex:
|
|
245
|
+
- 'alpha:.*beta-only commit'
|
|
246
|
+
- 'beta:.*alpha-only commit'
|
|
247
|
+
description: >-
|
|
248
|
+
per-repo cwd correctly isolates each project's commits — catches the cwd-as-parent
|
|
249
|
+
defect (obs_017) where git log at fleet root aggregates all repos into one blob
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Database shape
|
|
253
|
+
|
|
254
|
+
Exercise a Dolt or SQLite database in a twin sandbox, seeding ≥2 rows and asserting per-row behavior. Use `sandbox: twin`.
|
|
255
|
+
|
|
256
|
+
```yaml
|
|
257
|
+
- name: database-per-row-attribution
|
|
258
|
+
sandbox: twin
|
|
259
|
+
command: |
|
|
260
|
+
set -e
|
|
261
|
+
DBDIR=$(mktemp -d)
|
|
262
|
+
sqlite3 "$DBDIR/test.db" "CREATE TABLE items (id INTEGER, name TEXT);"
|
|
263
|
+
sqlite3 "$DBDIR/test.db" "INSERT INTO items VALUES (1, 'alpha-item');"
|
|
264
|
+
sqlite3 "$DBDIR/test.db" "INSERT INTO items VALUES (2, 'beta-item');"
|
|
265
|
+
node <REPO_ROOT>/dist/cli.mjs query-items --db "$DBDIR/test.db"
|
|
266
|
+
expect_stdout_regex:
|
|
267
|
+
- alpha-item
|
|
268
|
+
- beta-item
|
|
269
|
+
description: database probe seeds ≥2 rows and asserts per-row output is correctly attributed
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Network shape
|
|
273
|
+
|
|
274
|
+
Exercise an HTTP endpoint with `expect_stdout_no_regex` error-envelope guards. Use `sandbox: twin` for endpoints that mutate state; `sandbox: host` for strictly read-only checks that cannot affect the host.
|
|
275
|
+
|
|
276
|
+
```yaml
|
|
277
|
+
- name: api-endpoint-returns-success-shape
|
|
278
|
+
sandbox: twin
|
|
279
|
+
command: |
|
|
280
|
+
set -e
|
|
281
|
+
curl -sf http://localhost:3000/health
|
|
282
|
+
expect_stdout_no_regex:
|
|
283
|
+
- '"isError"\s*:\s*true'
|
|
284
|
+
- '"status"\s*:\s*"error"'
|
|
285
|
+
- '"error"\s*:'
|
|
286
|
+
expect_stdout_regex:
|
|
287
|
+
- '"status"\s*:\s*"ok"'
|
|
288
|
+
description: health endpoint returns success-shaped JSON without error envelope
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Registry shape
|
|
292
|
+
|
|
293
|
+
Read from an npm/package registry or fleet-config source. Precede the registry probe with a binary-availability sibling probe. Use `sandbox: host` — registry reads are strictly read-only and cannot mutate host state.
|
|
294
|
+
|
|
295
|
+
```yaml
|
|
296
|
+
- name: npm-binary-available
|
|
297
|
+
sandbox: host
|
|
298
|
+
command: |
|
|
299
|
+
command -v npm && echo "NPM_FOUND" || { echo "NPM_NOT_FOUND"; exit 1; }
|
|
300
|
+
expect_stdout_regex:
|
|
301
|
+
- NPM_FOUND
|
|
302
|
+
description: npm binary must be on PATH before registry probe runs
|
|
303
|
+
- name: package-registry-version-resolves
|
|
304
|
+
sandbox: host
|
|
305
|
+
command: |
|
|
306
|
+
npm view @substrate-ai/sdlc version 2>&1
|
|
307
|
+
expect_stdout_no_regex:
|
|
308
|
+
- 'Not found'
|
|
309
|
+
- 'npm ERR!'
|
|
310
|
+
expect_stdout_regex:
|
|
311
|
+
- '\d+\.\d+\.\d+'
|
|
312
|
+
description: npm registry resolves @substrate-ai/sdlc and returns a semver version string
|
|
313
|
+
```
|
|
314
|
+
|
|
165
315
|
## Mission
|
|
166
316
|
|
|
167
317
|
Author runtime probes for the story described above. Use the AC sections provided:
|