* test(#3172): failing-first suite for the stated failing-direction probe Pins the <fails_when> pairing walk, placeholder denylist, MISSING sentinel exemption, degraded-read contract, CLI arm and the plan-authoring contract text. RED by construction: the module exports it requires do not exist yet. Executed on the remote runner. * feat(#3172): require a stated failing direction for every automated acceptance command Every runnable <automated> command now carries a <fails_when> sibling naming what output constitutes failure. A command with no expressible failure mode is not an acceptance test: it reads as rigour and is not falsifiable. - verify-command-grounding gains a failing-direction probe sharing the existing <automated> grammar, MISSING sentinel and walk guard rather than copying them - gsd-tools check verify-failure-directions <N> backs it; plan-phase dispatches it and hands the JSON to gsd-plan-checker check 8f - Dimension 8 detail extracted to references to stay under the agent size cap Verified on the remote runner. * fix(#3172): close four review findings in the failing-direction probe - MISSING_SENTINEL_RE matched an env-var assignment prefix (MISSING=1 cmd), so a real command was exempted from the new blocking gate. Tightened the SHARED constant rather than adding a second copy. - Both token regexes scanned to EOF on unclosed openers (O(n^2), 1562ms at 40k). Bodies are now non-crossing; 1ms, byte-identical on well-formed input. The pre-existing AUTOMATED_BLOCK_RE carried the same defect and is fixed here too. - probePhaseFailingDirections reported status 'ok' when one plan was unreadable, conflating 'could not look' with 'nothing to report'. - Extracted the phase-resolution block both check arms had copied verbatim. Also corrects a docs/AGENTS.md dimension list stale since #2401. Verified on the remote runner. * fix(#3172): project the planner rule onto the spawn contract, settle emitted bookkeeping The remote runner refuted the planner-side edit. agents/gsd-planner.md is frozen under a 49152-LF-char cap asserted by four suites and sat at 49,146 — six chars of headroom — so the +537 of authoring rule blew it. #3297/#3645 already settled where such a rule goes: the planner spawn contract in plan-phase.md, beside <tracked_source_paths>. The agent file is reverted to origin/next verbatim. - plan-phase.md gains <failing_direction_contract>; tests row 30 now asserts the contract there and row 30b guards the freeze in both directions - plan-phase.md growth acknowledged by APPENDING to the 3409 fragment, per the precedent that two ack sources may never name the same path - install-tree fixtures regenerated for the three new reference files Verified on the remote runner. * chore(#3172): backfill PR number into the changeset fragment pr:0 -> pr:3825 now that the PR exists. --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -52,7 +52,7 @@ import planningScopeMod = require('./planning-scope.cjs');
|
||||
const { SCOPE } = planningScopeMod;
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import verifyCommandGroundingMod = require('./verify-command-grounding.cjs');
|
||||
const { probePhaseVerifyCommands } = verifyCommandGroundingMod;
|
||||
const { probePhaseVerifyCommands, probePhaseFailingDirections } = verifyCommandGroundingMod;
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -935,6 +935,30 @@ function cmdTddReviewCheckpoint(projectDir: string, args: string[], raw: boolean
|
||||
output(result, raw, undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a phase argument to an absolute phase directory, or '' when it
|
||||
* cannot be resolved. Shared by every `check` arm that probes a phase's
|
||||
* PLAN.md files, so the two never drift (DEFECT.GENERATIVE-FIX-DIVERGENCE).
|
||||
* Never throws — the callers emit a degraded JSON payload instead, because
|
||||
* a consumer must be able to tell "nothing to report" from "could not look".
|
||||
*/
|
||||
function resolvePhaseDirOrEmpty(projectDir: string, phase: string): string {
|
||||
try {
|
||||
const result = findPhaseInternal(projectDir, phase);
|
||||
if (result && typeof result === 'object') {
|
||||
// findPhaseInternal returns { directory: '<relative-posix-path>', ... }
|
||||
// directory is relative to cwd — resolve it to absolute.
|
||||
const relDir = typeof result['directory'] === 'string' ? result['directory'] : '';
|
||||
if (relDir) {
|
||||
return path.resolve(projectDir, relDir);
|
||||
}
|
||||
} else if (typeof result === 'string') {
|
||||
return result;
|
||||
}
|
||||
} catch { /* phase dir lookup failure → caller emits degraded payload */ }
|
||||
return '';
|
||||
}
|
||||
|
||||
// ─── verify-command-paths (#2401) ──────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -968,20 +992,7 @@ function cmdVerifyCommandPaths(projectDir: string, args: string[], raw: boolean)
|
||||
return;
|
||||
}
|
||||
|
||||
let phaseDir = '';
|
||||
try {
|
||||
const result = findPhaseInternal(projectDir, phase);
|
||||
if (result && typeof result === 'object') {
|
||||
// findPhaseInternal returns { directory: '<relative-posix-path>', ... }
|
||||
// directory is relative to cwd — resolve it to absolute.
|
||||
const relDir = typeof result['directory'] === 'string' ? result['directory'] : '';
|
||||
if (relDir) {
|
||||
phaseDir = path.resolve(projectDir, relDir);
|
||||
}
|
||||
} else if (typeof result === 'string') {
|
||||
phaseDir = result;
|
||||
}
|
||||
} catch { /* phase dir lookup failure → degraded payload below */ }
|
||||
const phaseDir = resolvePhaseDirOrEmpty(projectDir, phase);
|
||||
|
||||
if (!phaseDir) {
|
||||
output(
|
||||
@@ -1001,6 +1012,59 @@ function cmdVerifyCommandPaths(projectDir: string, args: string[], raw: boolean)
|
||||
output(probed, raw, undefined);
|
||||
}
|
||||
|
||||
// ─── verify-failure-directions (#3172) ─────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* verify-failure-directions: probes every `<automated>` verify command
|
||||
* declared in a phase's `-PLAN.md` files for a stated `<fails_when>` failing
|
||||
* direction — see verify-command-grounding.cjs for the recognizer contract.
|
||||
*
|
||||
* Args: check verify-failure-directions <phase>
|
||||
* Invocable as: gsd_run check verify-failure-directions <phase>
|
||||
*
|
||||
* When the phase cannot be resolved to a directory, this emits a non-throwing
|
||||
* degraded JSON payload (status/commands/counts all zeroed, `readError`
|
||||
* populated) rather than calling `error()` — the plan-checker parses this
|
||||
* result and must be able to distinguish "nothing to report" from "could not
|
||||
* look", which a non-zero exit / thrown error would collapse.
|
||||
*/
|
||||
function cmdVerifyFailureDirections(projectDir: string, args: string[], raw: boolean): void {
|
||||
// args[0] = 'check', args[1] = 'verify-failure-directions', args[2] = phase
|
||||
const phase = args[2] || '';
|
||||
if (!phase) {
|
||||
output(
|
||||
{
|
||||
status: 'unresolvable',
|
||||
commands: [],
|
||||
counts: { blocker: 0, warning: 0, total: 0 },
|
||||
readError: 'verify-failure-directions requires a phase argument: check verify-failure-directions <phase>',
|
||||
},
|
||||
raw,
|
||||
undefined,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const phaseDir = resolvePhaseDirOrEmpty(projectDir, phase);
|
||||
|
||||
if (!phaseDir) {
|
||||
output(
|
||||
{
|
||||
status: 'unresolvable',
|
||||
commands: [],
|
||||
counts: { blocker: 0, warning: 0, total: 0 },
|
||||
readError: `could not resolve phase directory for phase ${phase}`,
|
||||
},
|
||||
raw,
|
||||
undefined,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = probePhaseFailingDirections({ phaseDir });
|
||||
output(result, raw, undefined);
|
||||
}
|
||||
|
||||
// ─── gap-analysis-plan-post ───────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -1594,6 +1658,12 @@ function routeCheckCommand({ args, cwd, raw }: RouteCheckCommandOptions): void {
|
||||
cmdVerifyCommandPaths(cwd, args, raw);
|
||||
return;
|
||||
}
|
||||
if (subcommand === 'verify-failure-directions') {
|
||||
// Presence probe for a stated <fails_when> per <automated> command
|
||||
// (#3172) — never executes anything; see verify-command-grounding.cjs.
|
||||
cmdVerifyFailureDirections(cwd, args, raw);
|
||||
return;
|
||||
}
|
||||
if (subcommand === 'api-coverage-verify-pre') {
|
||||
// ai-integration capability blocking gate at verify:pre (#1562). Dot-to-
|
||||
// hyphen normalization means query "api-coverage.verify-pre" routes here.
|
||||
@@ -1640,7 +1710,7 @@ function routeCheckCommand({ args, cwd, raw }: RouteCheckCommandOptions): void {
|
||||
routeProhibitionEnforcement(args, raw);
|
||||
return;
|
||||
}
|
||||
error('Unknown check subcommand. Available: api-coverage-verify-pre, auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, predicate, prohibition-enforcement, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-command-paths, verify-schema-drift, verify-codebase-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
||||
error('Unknown check subcommand. Available: api-coverage-verify-pre, auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, predicate, prohibition-enforcement, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-command-paths, verify-failure-directions, verify-schema-drift, verify-codebase-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
||||
}
|
||||
|
||||
export = {
|
||||
@@ -1651,6 +1721,7 @@ export = {
|
||||
computeUiSafetyGate,
|
||||
cmdGapAnalysisPlanPost,
|
||||
cmdVerifyCommandPaths,
|
||||
cmdVerifyFailureDirections,
|
||||
cmdTddReviewCheckpoint,
|
||||
cmdCheckPredicate,
|
||||
buildPredicateDeps,
|
||||
|
||||
Reference in New Issue
Block a user