* test(#1949): add failing-first tests for <precondition> element
Red phase for issue #1949 (Design by Contract: <precondition> element
asserted before task execution). Tests assert:
- docs/reference/plan-md.md documents the new <precondition> element
- agents/gsd-planner.md @-references planner-preconditions.md and stays
under the 49152-char cap (progressive-disclosure requirement)
- gsd-core/references/planner-preconditions.md exists and documents the
three emission cases mandated by the issue (user_setup / prior-phase
artifact / env-var) and the contract triad mapping
- agents/gsd-executor.md asserts <precondition> before task execution
and routes unmet preconditions through existing checkpoint machinery
- cmdVerifyPlanStructure (behavioral via runGsdTools) accepts plans both
with and without <precondition> — the additive-validation guarantee
- Parity assertion: plan-md.md and planner-preconditions.md agree on the
canonical tag spelling (DEFECT.GENERATIVE-FIX-DIVERGENCE guard)
Most prose-contract assertions are Red until the implementation lands.
The behavioral validator assertions pass immediately (regression guards
proving the validator already accepts unknown optional tags).
* feat(#1949): <precondition> task element — Design by Contract
Add an optional <precondition> element to <task> in PLAN.md (issue #1949,
The Pragmatic Programmer Topic 23). The front-of-task side of the plan
contract — preconditions (before) ↔ postconditions (<verify>/<done>/
<acceptance_criteria>, after) ↔ invariants (must_haves.truths, across the
whole plan). Together with the tracer-bullet proposal (#1945), this closes
both ends of the 'outrunning your headlights' failure mode for an
autonomous AI executor.
Acceptance criteria met:
- <precondition> is an optional element on <task>; plans that omit it
validate unchanged (cmdVerifyPlanStructure checks for presence of
required tags, does not reject unknown optional tags).
- gsd-executor evaluates the precondition before any other task work.
Unmet halts execution with a checkpoint:human-verify and no partial
commit; met or absent produces no visible change to execution flow.
Unmet is never auto-approved under AUTO_CFG=true — a missing
prerequisite is a fact the executor cannot establish on its own.
- gsd-planner emits <precondition> in exactly the three cases the issue
mandates: user_setup consumption, prior-phase artifact dependency, and
env-var/runtime-config dependency.
- Tests cover met, unmet, and absent preconditions plus the additive-
validator guarantee.
Files:
- gsd-core/references/planner-preconditions.md (NEW): full emission
rules, the three cases with worked examples, format guidance,
anti-patterns, the contract triad mapping, and the executor assertion
contract. Progressive disclosure.
- agents/gsd-planner.md: slim <precondition> note in Task Anatomy with
@-reference to the new file. To stay under the 49152-char agent-file
cap (27-char headroom before this change), the inline
<comment_text_discipline> and <region_scoped_negative_gate> summaries
are compressed to one-line pointers — their full rules already live in
planner-antipatterns.md, so no content is lost.
- agents/gsd-executor.md: new step 0 'Precondition check' in the
execute_tasks loop, before the type dispatch, routing unmet through
checkpoint_return_format.
- docs/reference/plan-md.md: new Preconditions section in the schema
reference, with the canonical example and the three emission cases.
- CONTEXT.md: Precondition glossary entry as a sibling of Tracer Bullet.
- docs/INVENTORY.md + INVENTORY-MANIFEST.json: row for the new
references/planner-preconditions.md (regen via gen-inventory-manifest).
- tests/precondition-element.test.cjs: failing-first tests covering
schema docs, planner emission contract, executor assertion contract,
reference-file presence + the three cases, behavioral additive-
validator guarantee, and a parity assertion (DEFECT.GENERATIVE-FIX-
DIVERGENCE guard).
- .changeset/quick-hawks-bark.md: Added fragment.
Companion to #1945 (tracer bullets).
* chore(#1949): regen agent-size baseline + install-tree goldens
Documented baseline regenerations required by the feat(#1949) prose changes
(RULESET.AGENT_SIZE_BUDGET + golden-install-parity):
- npm run size:baseline — locks in the new gsd-executor.md size (+1050
bytes: the precondition-check step 0 block). gsd-planner.md is net
smaller (-142 bytes: compressed two inline summary blocks whose full
rules already lived in planner-antipatterns.md to make room for the
slim <precondition> pointer). No hard-cap breach.
- npm run gen:golden — pick up the new references/planner-preconditions.md
+ the two changed agent files across all 18 runtime install trees.
Both regens are CI-mandated after intentional agent/reference changes;
see CLAUDE.md 'RULESET.AGENT_SIZE_BUDGET' and the comments in
tests/golden-install-parity.test.cjs.
* fix(#1949): bound <precondition> checks to read-only (security review)
Apply the security-review finding (LOW, isolated /security-review subagent):
the executor's 'run the cheapest check' phrasing for a plan-author-controlled
prose line was broader than ideal — a hostile plan author could craft a
<precondition> whose 'cheapest check' is side-effecting (curl to an attacker
host under the guise of verification, rm -rf before checking, secret emission).
The risk is inherited from GSD's existing plan-trust model (<verify>, <action>,
<done> already direct the executor to run arbitrary shell), so <precondition>
does not materially expand it. But the new prose actively directs execution
('run the check') rather than passively consuming the element, so the bound
is worth making explicit.
Tightened across all four surfaces that describe the check shape:
- agents/gsd-executor.md step 0: 'Verify with read-only checks only — file
existence, env var presence (no value output), idempotent GET /health-style
pings. Do NOT run commands with side effects (writes, network POSTs, secret
emission) as the check; if a side-effecting check seems required, halt and
surface via checkpoint instead.'
- gsd-core/references/planner-preconditions.md Format section: same bound,
plus the halt-and-surface escape hatch.
- docs/reference/plan-md.md Preconditions section: mirrored.
- CONTEXT.md Precondition glossary entry: mirrored.
Regenerated agent-size baseline (executor grew 46186 -> 46440; still under
the 49152 cap) and install-tree goldens.
* chore(#1949): backfill changeset pr number 2422
Per CONTRIBUTING.md changeset workflow + feature-builder directive Step 8.7:
backfill the placeholder pr:0 with the real PR number immediately after
gh pr create returns. Avoids the fail_invalid_fragment gate.
* fix(#1949): cite [#1949] on allow-test-rule exemption (ADR-456)
CI's lint:ci runs lint-allow-test-rule-refs which per ADR-456 requires
every // allow-test-rule: exemption on a NEW test file to carry an issue
reference (#NNN or URL). My earlier push omitted it.
Local 'npm run lint' (eslint) does NOT run this check — only 'npm run
lint:ci' does. CLAUDE.md explicitly warns: 'lint:ci ≠ lint — CI runs
lint:ci; a local pass is not the gate.' I should have run lint:ci before
pushing; correcting now.
Pattern matches the companion feature's test file:
tests/tracer-bullet.test.cjs:1 // allow-test-rule: source-text-is-the-product [#1945]