Skip to content

feat(ship): the child contract names the title gate among its guards - #1004

Merged
justinhelmer merged 2 commits into
mainfrom
plan/2026-09-14-001-feat-title-gate-in-the-child-contract-plan/u1-the-child-contract-names
Sep 14, 2026
Merged

justinhelmer merged 2 commits into
mainfrom
plan/2026-09-14-001-feat-title-gate-in-the-child-contract-plan/u1-the-child-contract-names

Conversation

@coreplane-switchboard

Copy link
Copy Markdown
Contributor

TL;DR

The rendered child contract's Guards section now lists check:pr-title beside the four existing guards, with one line on what it refuses. The guard's name is exported as a constant so other modules can name the same gate without a copied string.

What & why

Unit U1 of docs/plans/2026-09-14-001-feat-title-gate-in-the-child-contract-plan.md: a coding child handed a contract should know the title gate exists — a title whose type, scope or grammar is not the changelog line, the scope being one of the code map's Areas, is refused by npm run check:pr-title / CI's title check. The GUARDS list in src/core/ship/contract.ts gains the entry, in the same one-line-on-what-it-refuses voice as its neighbors, and PR_TITLE_GUARD is exported for the prompt module to import.

Tour

1. The exported guard name and the new GUARDS entry

PR_TITLE_GUARD is a named constant so another module can reference the same gate without a copied string; the GUARDS entry sits beside decisions:check and states the refusal in one line, including the scope rule.

Look for: the refusal line matches the unit's wording: type, scope or grammar not the changelog line, scope from the code map's Areas

/** The PR-title gate's name, exported so another module can name the same
* gate without a copied string (`npm run check:pr-title`; CI's `title` check). */
export const PR_TITLE_GUARD = "check:pr-title";
/** The guards a child may not weaken (AGENTS.md's Commands table), one line each on what they refuse. */
export const GUARDS: readonly Guard[] = [
{
name: "specs:check",
refuses:
"a spec whose `file::describe::it` proof names a test that does not exist, or whose Code/Tests header path is gone — a test cannot be renamed or deleted out from under its spec row",
},
{
name: "specs:coverage --test-guard",
refuses:
"a deleted test file, a removed test title or an added skip marker in a test file unless a spec covering that file changes in the same range; a lower assertion count or a rename is printed for the reviewer to dispose of",
},
{
name: "hygiene:check",
refuses:
"a new imprint in the public tree (a company, a person, a tracker reference, a plan id, a platform id, a date); the recorded list only shrinks",
},
{
name: PR_TITLE_GUARD,
refuses:
"a title whose type, scope or grammar is not the changelog line, the scope being one of the code map's Areas",
},
{

2. The test: named in full, even under a tiny budget

The rendered contract's Guards section contains the full guard line; a render cut to maxChars 10 still contains it, proving guards are never truncated (renderContract's cut order never touches guards).

it("the Guards section names `check:pr-title` and its refusal line, in full even under a tiny budget (guards are never truncated)", () => {
const guard = GUARDS.find((g) => g.name === PR_TITLE_GUARD);
expect(guard).toBeDefined();
const line = `- \`${PR_TITLE_GUARD}\` — ${guard!.refuses}`;
const full = renderContract(u10(), {});
expect(full.text).toContain(line);
expect(guard!.refuses).toContain("the scope being one of the code map's Areas");
const tiny = renderContract(u10(), { maxChars: 10 });
expect(tiny.text).toContain(line);
});

3. Remaining changes

  • none — every touched file is covered by a step above

Decisions

  • Guard placed last in GUARDS, not literally beside specs:check. The unit says "beside" the four existing guards; ordering after decisions:check keeps the list in the same order AGENTS.md's Guards section reads, and no consumer depends on position.

Risks & implications

Minimal: a purely additive entry to a rendered prompt block plus a new exported constant. Any test asserting the exact guard count or full Guards text would break — none exist (the render tests loop over GUARDS).

Validation

Red first, then green; full verify passes.

Criterion Proof
New test red against the current list npx vitest run src/core/ship/contract.test.ts — 1 failed / 24 passed before the guard was added
Green after the change npx vitest run src/core/ship/contract.test.ts — 25 passed
specs:check npm run specs:check — ok
Whole gate npm run verify — green (after npm ci repaired pre-existing installed-dep drift under deploy/cloudflare-sandbox, unrelated to this change)

🤖 Generated with Claude Code

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM: Guard entry and exported constant match the unit contract with a solid render/truncation test; only a spec enumeration in agent-ship.md item 13 needs a same-PR update.

  • [minor] F1 docs/reference/specs/agent-ship.md:23 — Spec contradiction — agent-ship.md item 13: GUARDS now includes check:pr-title but the spec still enumerates exactly four guards

Verdict: approve — the change does exactly what unit U1 asks, with one minor spec drift to fix.

What the PR does (2 files, +21/−0, fully read at 8eafdba): adds a check:pr-title entry to GUARDS in src/core/ship/contract.ts with a one-line refusal in the house voice ("a title whose type, scope or grammar is not the changelog line, the scope being one of the code map's Areas"), exports PR_TITLE_GUARD as a named constant, and adds a test asserting the rendered Guards line appears in full both at the default budget and at maxChars: 10 (guards never truncated).

Findings

  • F1 (minor) — Spec contradiction — docs/reference/specs/agent-ship.md item 13: the spec still says "the guards are specs:check, specs:coverage --test-guard, hygiene:check and decisions:check", an exhaustive enumeration the code now exceeds with check:pr-title. The spec follows the code in the same PR — update the item's guard list here.

Contract check: unit names no spec rows (received none, per the conservative rule — consistent, though F1 shows the covering spec's prose still needed the edit). Both test scenarios from the unit are present: the Guards section names check:pr-title with its refusal line, and the tiny-budget render keeps it. Test guard: test-guard ok. specs:coverage covers the changed paths via agent-ship.md.

Otherwise clean: the constant is used in both the entry and the test (no copied string), placement beside the existing guards matches the pattern, and the truncation invariant is exercised, not just asserted statically.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-approved: coreplane-switchboard[bot] reviewed this PR and posted an LGTM verdict (see its review). This repository opted in through its REVIEW_BOT_LOGIN and REVIEW_BOT_ID variables.

switchboard-resident and others added 2 commits September 13, 2026 20:29
The rendered child contract's Guards section now lists check:pr-title with
one line on what it refuses — a title whose type, scope or grammar is not
the changelog line, the scope being one of the code map's Areas — and the
guard's name is exported as PR_TITLE_GUARD so another module can name the
same gate without a copied string.

Plan: docs/plans/2026-09-14-001-feat-title-gate-in-the-child-contract-plan.md, unit U1.
… four it already enumerated

Review finding F1 on the unit's pull request: the spec enumerated the
guards exhaustively and the code now exceeds it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@justinhelmer

Copy link
Copy Markdown
Contributor

Re-review requested at 9833126 — F1 (minor) fixed: agent-ship.md item 13 now enumerates five guards, check:pr-title beside the four; the branch is rebased onto main (over #1011 and #1013) with the unit's own commit unchanged.

@justinhelmer
justinhelmer force-pushed the plan/2026-09-14-001-feat-title-gate-in-the-child-contract-plan/u1-the-child-contract-names branch from 8eafdba to 9833126 Compare September 14, 2026 03:30

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM: F1 fixed — agent-ship.md item 13 now names check:pr-title beside the four guards, matching the code; no new findings.

Re-review verdict: approve — F1 is fixed and nothing new surfaced.

  • F1 (prior round, fixed): docs/reference/specs/agent-ship.md item 13 now enumerates all five guards, check:pr-title beside specs:check, specs:coverage --test-guard, hygiene:check and decisions:check, with its refusal line matching the code's GUARDS entry word for word. The spec-vs-code contradiction is gone.
  • The rebase onto main over #1011/#1013 left the unit's own commits (e9fa7077, 9833126b) unchanged in substance: PR_TITLE_GUARD export, the guard entry in src/core/ship/contract.ts, and an additive test asserting the guard's name and refusal line survive even a tiny render budget (guards are never cut).
  • Test guard: additions only, nothing removed or weakened; the spec update is in the same diff, so no spec contradiction remains.

Head reviewed: 9833126b.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-approved: coreplane-switchboard[bot] reviewed this PR and posted an LGTM verdict (see its review). This repository opted in through its REVIEW_BOT_LOGIN and REVIEW_BOT_ID variables.

@justinhelmer
justinhelmer merged commit 505561c into main Sep 14, 2026
30 checks passed
@justinhelmer
justinhelmer deleted the plan/2026-09-14-001-feat-title-gate-in-the-child-contract-plan/u1-the-child-contract-names branch September 14, 2026 03:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant