Context
Friction captured from PR #16319 (2026-08-02, this session): a test-only PR (new e2e spec witness, zero runtime delta) had no valid --change-class declaration — agent-preflight rejected test(e2e): … under both capability ("requires feat") and zero-delta ("requires chore"). The sanctioned escape (run preflight without semantic inputs) works but silently skips the class-consistency gate the tool exists to enforce.
The Problem
CHANGE_CLASS_TO_TYPE (buildScripts/util/agent-preflight.mjs:38-42) maps each declared class to exactly ONE conventional-commit type: capability→feat, restoration→fix, zero-delta→chore. But the repo's live commit convention includes test(...) subjects for test-only deltas — 8 merged test(...) subjects in the last 14 days alone (git log origin/dev --since="14 days ago": test(wake), test(harness), test(ci), test(workstation) ×2, test(ai), test(tooling), test:). Every one of those PRs either bypassed the semantic gate or mislabeled. The gate's model of the convention and the convention itself have drifted apart — and the friction lands precisely on the disciplined author who declares semantics.
The Architectural Reality
- The class gate was introduced by
#16111 (closed: "Make agent commit-type selection ordered and locally enforced") to stop capability-mislabeling (chore for capability work, per #10061). Its enforcement value is real — the gap is the type vocabulary, not the gate.
CONVENTIONAL_TYPE_PATTERN (agent-preflight.mjs:34) already parses any lowercase type; the restriction lives only in the one-type-per-class map.
The Fix
Widen the map to type sets per class and let history decide which set members are live:
zero-delta → ['chore', 'test', 'docs', 'ci', 'build'] (deltas that change neither runtime behavior nor capability — the repo's actual zero-delta vocabulary)
capability → ['feat'], restoration → ['fix'] (unchanged — these two classes are the mislabeling risk #16111/#10061 targeted; keep them strict)
Add a spec case: a declared test(...) subject under zero-delta validates; under capability/restoration still rejects.
Acceptance Criteria
Out of Scope
- Re-litigating the class system itself (
#16111 settled it — this is the vocabulary completion it left open)
- Allowing arbitrary types (the set is the boundary;
style/refactor/perf can join later by the same evidence rule)
Related
#16111 (the gate's origin, closed) · #10061 (the original mislabeling correction)
- Friction origin: PR #16319 (test-only spec PR forced onto the no-semantic-inputs path)
Live latest-open sweep: checked latest 20 open issues at 2026-08-02T02:1xZ; no equivalent found. KB sweep surfaced #16111 (closed; this is its vocabulary-completion successor, not a duplicate). A2A in-flight sweep: no claim on this scope in the last 60 min.
Origin Session ID: 14f1d6fa-235a-4101-88d4-c9490e3f7fd5
Retrieval Hint: "agent-preflight change-class test-prefix convention drift"
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
CHANGE_CLASS_TO_TYPES (renamed export, agent-preflight.mjs) |
#16111's ordered-class contract |
Frozen map of class → frozen type array; zero-delta → {chore, test, docs, ci, build}; capability/restoration strict single-member sets |
old scalar CHANGE_CLASS_TO_TYPE removed (zero external consumers — grep receipt, 2026-08-02) |
JSDoc comment on the map |
unit spec |
validateChangeClass().expectedTypes |
same |
Array-valued observation; isolated copy per call — mutating a result never changes later validations (regression-witnessed) |
n/a |
@returns JSDoc |
regression spec |
| Error rendering |
same |
Single-member set: requires `feat` ; multi-member: requires one of chore, test, docs, ci, build. |
n/a |
— |
unit spec |
| CLI success line |
same |
Prints the joined set (maps to chore, test, docs, ci, build) |
n/a |
— |
CLI receipt in PR body |
pull-request-workflow.md §3.1 |
the PR-lifecycle atlas |
Names the type set + the "labels, not proof — the author's truthful declaration stays the class authority" framing |
n/a |
the atlas itself |
manifest lint (21879→21976 B, ≤ 22000 budget) |
Per-member evidence (14-day origin/dev history, 2026-08-02): test 20 · docs 22 · chore 65 · build 4 · ci 0. ci is admitted by convention (CI-config deltas are definitionally runtime-zero-delta), explicitly NOT by count — the gate maps the author-declared class to the allowed set; a prefix never proves the class.
Context
Friction captured from PR #16319 (2026-08-02, this session): a test-only PR (new e2e spec witness, zero runtime delta) had no valid
--change-classdeclaration —agent-preflightrejectedtest(e2e): …under bothcapability("requiresfeat") andzero-delta("requireschore"). The sanctioned escape (run preflight without semantic inputs) works but silently skips the class-consistency gate the tool exists to enforce.The Problem
CHANGE_CLASS_TO_TYPE(buildScripts/util/agent-preflight.mjs:38-42) maps each declared class to exactly ONE conventional-commit type:capability→feat,restoration→fix,zero-delta→chore. But the repo's live commit convention includestest(...)subjects for test-only deltas — 8 mergedtest(...)subjects in the last 14 days alone (git log origin/dev --since="14 days ago":test(wake),test(harness),test(ci),test(workstation)×2,test(ai),test(tooling),test:). Every one of those PRs either bypassed the semantic gate or mislabeled. The gate's model of the convention and the convention itself have drifted apart — and the friction lands precisely on the disciplined author who declares semantics.The Architectural Reality
#16111(closed: "Make agent commit-type selection ordered and locally enforced") to stop capability-mislabeling (chorefor capability work, per#10061). Its enforcement value is real — the gap is the type vocabulary, not the gate.CONVENTIONAL_TYPE_PATTERN(agent-preflight.mjs:34) already parses any lowercase type; the restriction lives only in the one-type-per-class map.The Fix
Widen the map to type sets per class and let history decide which set members are live:
zero-delta→['chore', 'test', 'docs', 'ci', 'build'](deltas that change neither runtime behavior nor capability — the repo's actual zero-delta vocabulary)capability→['feat'],restoration→['fix'](unchanged — these two classes are the mislabeling risk#16111/#10061targeted; keep them strict)Add a spec case: a declared
test(...)subject underzero-deltavalidates; undercapability/restorationstill rejects.Acceptance Criteria
agent-preflight --change-class zero-delta --commit-subject "test(e2e): add witness (#N)"passes validation--change-class capabilitywith atest(...)subject still fails (the mislabeling guard is preserved)pull-request-workflow.md §3.1updated to state the zero-delta type set explicitly (the workflow and the tool must tell the same story)Out of Scope
#16111settled it — this is the vocabulary completion it left open)style/refactor/perfcan join later by the same evidence rule)Related
#16111(the gate's origin, closed) ·#10061(the original mislabeling correction)Live latest-open sweep: checked latest 20 open issues at 2026-08-02T02:1xZ; no equivalent found. KB sweep surfaced
#16111(closed; this is its vocabulary-completion successor, not a duplicate). A2A in-flight sweep: no claim on this scope in the last 60 min.Origin Session ID: 14f1d6fa-235a-4101-88d4-c9490e3f7fd5
Retrieval Hint: "agent-preflight change-class test-prefix convention drift"
Contract Ledger Matrix
CHANGE_CLASS_TO_TYPES(renamed export,agent-preflight.mjs)#16111's ordered-class contractzero-delta → {chore, test, docs, ci, build};capability/restorationstrict single-member setsCHANGE_CLASS_TO_TYPEremoved (zero external consumers — grep receipt, 2026-08-02)validateChangeClass().expectedTypes@returnsJSDocrequires `feat`; multi-member:requires one ofchore,test,docs,ci,build.maps to chore, test, docs, ci, build)pull-request-workflow.md§3.1Per-member evidence (14-day
origin/devhistory, 2026-08-02):test20 ·docs22 ·chore65 ·build4 ·ci0.ciis admitted by convention (CI-config deltas are definitionally runtime-zero-delta), explicitly NOT by count — the gate maps the author-declared class to the allowed set; a prefix never proves the class.