[#251] feat: Process Profile section in way-of-working (per-step whitelist) - #467
[#251] feat: Process Profile section in way-of-working (per-step whitelist)#467rucka wants to merge 28 commits into
Conversation
Verdict
PR: #467 · Author: rucka · Reviewer: independent review agent · Date: 2026-08-28 · Story: US-251 · Type: feature Classification matrix — per dimension
Tier = max(assessed) = yellow — confirms the story's refinement-time tier, no drift. AssessmentsSecurity — Input validationVerdict: green (reviewer-assessed; Details
Security — Output handlingVerdict: green — CLI strings to stdout only; no rendering surface. Security — AuthenticationVerdict: not applicable — no auth surface touched. Security — AuthorizationVerdict: not applicable — the profile is explicitly documented as a project convention, not a permission system ( Security — Introduced vulnerabilitiesVerdict: green — 0 introduced, 0 pre-existing on the touched paths. CostVerdict: Architecture (Coupling)Verdict: green (reviewer-assessed) — one new KB→adoption read direction, already the corpus's established pattern ( Bug fix — Red test before fixVerdict: not applicable — not a bug fix. (RED evidence is nonetheless recorded in the PR: commit DetailsFindings by severityCritical (must fix before merge)
Major (must fix before merge)
Minor (must fix before merge — same bar as Major, just lower impact)
Questions (informational, never blocking)
Positive feedback
Functionality & requirements (AC coverage)
Business rules (unit is the step, profile only in adoption, convention written once, disabled≡not-installed in composition, convention-over-configuration) all hold in the diff. Testing & quality gates
Adoption compliance
Tech debt
Documentation
Performance & deployment
Review method: detached worktree pinned to |
Escalation — rounds 1–3, non-convergenceRound 3 closed clean per the working log, but re-review against the shipped resolver surfaces a new class of findings one level further in than round 3's fix — enough of them, and close enough to the story's own contract, that this cycle is escalating to a human rather than attempting a round 4 unilaterally. Rounds so far (full detail in the working log, summarized here):
Still-open actionable findings (found on this same head, one level further in than round 3's fix):
Convention for continuing this cycle: further rework or re-review on this story — including any manual, out-of-band round — should be appended to Preservation note: the working log is an untracked file that exists only in the persistent authoring worktree Not merged. No PR artifact beyond this comment. |
rucka
left a comment
There was a problem hiding this comment.
Verdict
risk:yellow · cost:green — APPROVED — profile parsing, adoption text and every cascade mapping now agree; no Major/Minor remains.
Open findings: 0. Every finding is resolved; nothing on this PR is waiting on anyone.
PR: #467 · Author: rucka · Reviewer: rucka (self-review) · Date: 2026-08-30 · Story: US-251 · Type: bug fix
Classification matrix — per dimension
| Dimension | Tier | Note |
|---|---|---|
| Service/domain criticality | yellow | Process-selection guard used by every /pair-next run. |
| Change/diff risk | yellow | Parser, conformance guard and distributed adoption/docs copies change together. |
| Business impact | yellow | A silent default can enable the full process. |
| Security relevance | green | No external boundary, credentials or privilege path changes. |
| Coupling balance | green | No new integration; source/dataset/mirrors stay covered by conformance. |
Tier is the max assessed dimension; cost is green because this is static parsing/documentation only.
Assessments
Security
Verdict: green — key-shaped text is parsed locally; fenced, commented and table examples remain non-input.
Cost
Verdict: cost:green — no runtime service, storage, network or token-cost surface.
Architecture (Coupling)
Verdict: green — the existing resolver remains the single behavior source; corpus/mirror assertions prevent drift.
Bug fix — Red test before fix
Verdict: green — orphan-key cases reproduced silent default/all steps before orphanKeyProblems; they now HALT and yield no step set.
Details
Finding closure and evidence
| Severity | Finding | Closure |
|---|---|---|
| Minor | A valid profile key below plural, unspaced, unrelated or absent heading was ignored and widened to default. |
profileSectionCoverage plus orphanKeyProblems fails closed, naming the line; examples in fences/comments/tables stay ignored. |
| Minor | Adoption said top-level-only while the resolver accepts a two-space nested key. | Template, guideline and docs describe the actual indentation rule. |
| Minor | Cascade filtering relied on a hand-maintained row-to-step table without per-row proof. | Corpus-derived tests require every executable cascade row to map to its catalogued step and forbid catalogued steps marked non-step. |
- Targeted TDD/regression suite:
pnpm --filter @pair/knowledge-hub exec vitest run src/tools/skills-conformance-check.test.ts src/conformance/process-profile.test.ts— 448 passed. - Full gate:
pnpm quality-gate— PASS. - Pair adoption verification:
pnpm --filter @pair/knowledge-hub skills:conformance— PASS, 44 skills, including dataset, installed mirrors, adoption template and worked examples. - Diff review: no unresolved Critical/Major/Minor; no new card created.
Required merge action
risk:yellow requires green checks and an approved review. This self-authored review is published as a comment; it does not claim a separate human approval. Once remote checks are green, this PR can move to pr-state:ready-to-merge; merge remains a human action.
Bound re-review to immutable deltas and require a complete finite-state inventory before review or remediation.
Require real boundary evidence for externally-defined state and repair claims.\n\nRefs: #416
39b1062 to
f6eec49
Compare
Questions are, by the review template's own definition, questions for the human: items the reviewer marks "No change requested". The floor defaulted to none, so they entered the set the fixer must resolve, and each answer enlarged the diff and created fresh surface for the next review. Measured on PR #477: the PR reached a zero-actionable APPROVED twice; both times the following round implemented the Questions and the re-review found new Minor findings inside the code that round had just added (three, then two). Work now completes when Critical, Major and Minor are closed. An explicit severityFloor still wins. The default is applied softly - a template without Minor, or without a usable ranking, falls back to no floor instead of throwing, because a default must not break a run that never asked for it. - ADL 2026-09-03-the-review-severity-floor-defaults-to-minor.md - three tests: Questions-only converges and is carried, a Minor still blocks, a vocabulary without Minor falls back Refs: #219 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… yaml-parsed allow-list (#477) The check-only pre-push gate was the sole enforcement point for formatting, and `--no-verify` skips it. CI now runs `format:check` in a dedicated workflow, and the workflow's own shape is guarded by a composition check. The guard parses `.github/workflows/format.yml` with yaml@2.8.2 and expresses every rule as an allow-list over the parsed document, per ADL 2026-09-01; the hand-rolled line reader it replaces is retired. Nothing is ever rewritten in CI — write-mode formatters are banned by the same guard. Also carries a path-scoped `.gitleaks.toml` allowlist for a synthetic 40-hex SHA fixture in scripts/smoke-tests, which gitleaks matched as a generic-api-key at historical commit 5e99d53. Closes #413 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- skills-conformance-check gains the catalogue<->corpus binding (both directions), the process-step marker check, built-in profile validation and resolveProcessProfile — the executable statement of the schema; fail-closed on a missing catalogue - 21 white-box unit tests incl. the six way-of-working states (AC1/3/4/5/9/10) - process-profile.test.ts: the prose contract a CLI cannot assert (schema HALTs, gate convention, WoW section, /next's resolution step) - RED: skills:conformance FAIL (1 violation), process-profile 42/44 failing - Tasks: T-7, T-8 (test-first half) Refs: #251
- step-catalogue.md: 12 steps, each with a stable id, its how-to and its executable (both nullable) and any-of prerequisites. The three asymmetries (04/05 retired guides -> capabilities, brainstorm with no guide) are ROWS, not conditionals; a "not a step" table draws the boundary the profile stops at - process-profiles.md: schema, default/poc built-ins, the five HALT cases and the one reported-not-fatal inconsistency, plus who reads the profile - corpus binding now green: 9 how-to guides and 10 process skills all catalogued - Tasks: T-1, T-2 Refs: #251
…omposition - new skill-conventions doc: the gate warns+confirms ONLY on direct invocation; a composed disabled step degrades through graceful-degradation scenario 3 (optional composed skill not installed), never a prompt - the composing skill applies the check BEFORE composing, so the composed skill's own gate cannot fire on a question the user never triggered - HALT cases stay HALTs: a typo must not resolve to "disabled" and be waved through by a confirmation - conventions index gains the row - Task: T-5 Refs: #251
… side) - shipped adoption template: optional section, field table, the omitted-means- default rule, the manual-path note, the three HALTs and two worked examples (poc, custom whitelist); points at the KB schema rather than restating it - this repo's own adoption: section present, nothing declared — pair runs the full process, same shape as `## Git Workflow` - the gate now resolves the SHIPPED TEMPLATE through resolveProcessProfile, so a template that ships an unreadable section fails CI - Task: T-3 Refs: #251
…e gate - `## Process Profile` section at each skill's invocation entry point, carrying the declared `<!-- process-step: id=... -->` marker and the one-line pointer; no gate logic is restated per skill - the 5 skills that COMPOSE a step (bootstrap, brainstorm, plan-epics, plan-tasks, refine-story) say so explicitly: the check runs before composing and a disabled step degrades as not-installed — this is AC7, the case most likely to be built as a prompt - map-subdomains/map-contexts are in the $approval family, so their round carries `kind=gate; auto=halt`; the convention states that resolution once - minor version bump on all 12 (new gate step, per skill-conventions/README) - skills:conformance PASS — 44 skills - Task: T-6 Refs: #251
…disabled step - new Step 0.5, before the cascade: read way-of-working's `## Process Profile`, resolve against the catalogue, carry the enabled step set into Steps 2-4 - absent section => `default` => the cascade runs unchanged (AC1) - the four HALTs (unknown name, unknown id, empty whitelist, whitelist under a built-in), with the two messages deliberately distinct (AC5/AC10) - a disabled row is SKIPPED, not errored, so enabled steps chain across the gaps (AC4); the row -> step-id mapping is a table, not row-wording inference - prerequisite consistency reported with the minimal fix, never repaired (AC9) - rows 7 and 12-16 propose capabilities that are not steps: never filtered - output gains a Profile line; degradation bullet states no-section is the zero-config default, not a degradation; version 0.5.1 -> 0.6.0 - Task: T-4 Refs: #251
- concepts/adoption-files: `## Process Profile` subsection under way-of-working.md — the three profiles, both worked examples, the "unit is the step" point that makes the no-skills manual path work, the three places a disabled step is met, and the validation rules - reference/pair-next: the profile is a SECOND narrowing, orthogonal to --root/--filter (items vs steps); no section => default; skipped not errored - no new page, so CP5's asserted page list is unchanged - Task: T-9 Refs: #251
- `pair update` run: `.claude/skills/**`, `.pair/knowledge/**` and `.pair/llms.txt` regenerated from the dataset sources - the 12 per-skill deltas compressed to the ONE line the convention asks for (~460 B, down from ~900); brainstorm hits its progressive-disclosure byte budget, so its half of the convention is disclosed to the sibling that already owns "a composition is missing" (degradation.md) — the budget's own instructed remedy, budget NOT raised (mirror 28,643 B of 28,672) - checkStepMarkers therefore resolves the convention POINTER over the skill's whole directory (the marker stays in SKILL.md) — the mirror of the rule checkApprovalSignalInSubDocs already applies; unit test added - gate module split for lint limits (Claims ledger, checkCatalogueRow / checkCatalogueOrphans / checkUniqueStepIds, resolveCustomWhitelist, checkProcessStepCorpus) — no behaviour change, 124 unit tests green - "epic-level AC4" removed from next's SKILL.md: story-local marker ban - Task: T-10 Refs: #251
…er split - ADR-024: why the profile whitelists a STEP id and never a representation — the measured asymmetry (9 guides vs 10 process skills, DDD mapping being capabilities per ADR-012, brainstorm having no guide) makes the epic's own poc case inexpressible on either representation alone; any-of prerequisites and the catalogue-as-scope-boundary recorded as part of the decision - ADL: a convention's MARKER belongs to the entrypoint, its POINTER may be disclosed to a sibling — how brainstorm honours the corpus-wide obligation inside its byte budget, and why the budget was not raised - llms.txt regenerated (indexes both records) Refs: #251
…ate binds the mirror + manual path Round 1 findings, all 8 resolved in place. - parseWowProfileSection: detect the KEY loosely (bold/unbackticked), accept the VALUE strictly; a detected key with no readable value HALTs with the schema shape instead of resolving to `default` — the silent WIDENING the review found (`- \`profile\`: poc` -> 12 steps, no halt). - sectionOf: line-start heading outside fences, `null` when absent — a prose cross-reference or a fenced sample no longer becomes the section. - resolveCustomWhitelist: no `whitelist` key != empty whitelist, two messages. - checkStepMarkersInMirror + checkManualPathEntrypoint, both wired into `skills:conformance`: the installed `.claude/skills/**` copy and AGENTS.md's manual flow are now governed; extractProfileExamples resolves every shipped worked example through the real reader. - dataset/AGENTS.md: manual flow gets a profile step before "identify your task" (mirrors regenerated). brainstorm degradation.md drops the `$approval` clause. step-catalogue heading + /next row->step table order. `custom` example is now prerequisite-clean. ADL: a config reader detects loosely and accepts strictly; a shipped mirror is a governed copy. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…d as loosely as the keys, fallback filtered - llms.txt regenerated: the round-1 ADL was missing from the index (RED gate) - parseWowProfileSection: readability decided on the RESIDUE, not token count — a partially backticked whitelist HALTs instead of dropping the bare ids; a `profile` line with >1 value HALTs instead of taking values[0] - isWowProfileHeading: equality on normalized heading text, so a decorated `## Process profile` no longer widens the profile to the full 12 steps - /next: enabled set carried into Steps 2–5; Step 5 fallback names only enabled steps and proposes /brainstorm when the profile leaves no reachable input 13 tests RED first, then 203 green. ADL extended in place (same decision, wider surface). Refs: #251 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ate section, heading level) - profileSectionProblems(): a second `## Process Profile` section HALTs (only the first was read, the later declaration took effect nowhere), and a heading at any level other than `##` HALTs instead of being neither a section nor a report - separate scan, NOT a widening of sectionOfWhere's `^##` predicate: that predicate also decides where a section ENDS, and an `###` sub-heading is legitimately inside `## The Catalogue` / `## Built-in Profiles` / `## Quick Start Process` - ProfileDeclaration.sectionHalts carries them; resolveProcessProfile checks WHERE the declaration sits before WHAT it says 6 tests RED first, then 216 green. KB error table, /next Step 0.5 and both website pages carry the two new normative cases; ADL extended in place. Refs: #251 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… the TEMPLATE's examples are - extractProfileExamples: a fence with `profile`/`whitelist` key lines IS an example; the shipped WoW template writes its heading outside the fence, so none of its examples were gate-checked while the PR claimed they were. Corrupting one now exits 1. - parseWowProfileSection: a key declared on two lines HALTs (was last-wins, silent, and order-dependent). Marker class widened to `-`/`*`/`+`; a bullet-less or ordered-list backticked key is DETECTED and HALTs instead of being invisible text. - process-profile-gate.md: the normative snippet is the `/refine-story` delta verbatim, pinned by a conformance test. - CLI PASS line enumerates this story's checks; `/next` 0.6.2 -> 0.6.0 (one minor bump). - KB schema + `/next` Step 0.5 + both website pages carry the two new normative rules; ADL extended in place. Refs: #251
…f text CRLF file resolved to `default` with zero halts (every guard silent at once); titled/`~~~` fences hid worked examples from the gate while the section parser skipped them; `~~~`/indented examples read as the real declaration; closed-ATX, ≤3-space-indented and setext headings neither matched nor reported; sweep covered 2 of 5 shipped surfaces; example errors named the fallback profile; ADR-024's H1 carried no number. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
…a truncated document is not read Five reader holes, all silent, all in the widening or narrowing direction: - a whitelist WRAPPED onto a second line was truncated to the first line's ids (residue check erased the trailing `,`, continuation line discarded): 8 ids in, 4 enabled, zero halts. Dangling separator + lazy continuation now HALT. - a key indented by 4 spaces or a tab was skipped as an indented code block, though it is equally a sublist item — 2 spaces read, 4 spaces `default`/12. Ambiguous now HALTs. - scanFences tracked the fence CHARACTER but not its LENGTH, so an inner ``` closed an outer ```` and its example escaped into declaration space. - a `## Process Profile` inside `<!-- -->` counted as a real section. - an UNTERMINATED fence above the section made the whole section invisible. scanProfileDocument replaces the raw line scan: fences by char AND length, HTML comment spans masked, an unclosed delimiter reported and HALTed. Prose + mirrors + docs site + ADL extended in place. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
…, and a HALT hands out no step set - gate: both way-of-working files resolved as DECLARATIONS, not only swept for fenced examples. This repo's own carries no example, so zero checks applied to it while the PASS line named it: `- `profile`: `pocc`` shipped PASS/exit 0. - resolveProcessProfile returns a discriminated union: a HALT arm has no `enabled`, so the widening a negligent caller could read is unwritable. - the 4 composers name the profile AT the composition beat + in degradation, as brainstorm already did; /next repeats the profile filter under Step 2. - off-marker comment corrected: a doc TABLE row is not a declaration and must not be (it would HALT the shipped template) — pinned by a unit case. - STEP_MARKER pinned on behaviour; docs error prose → scannable table. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
…step is named once - A key inside a blockquote HALTs: `> - `profile`: `poc`` matched neither key pattern (`>` is not whitespace), so it resolved to `default` / 12 steps with zero halts — the silent widening. A documentation TABLE row stays the deliberate opposite (no declaration, no HALT): the schema and the shipped template document the keys in one, so matching it would redden the gate on its own governing files. Both halves now stated in schema, template, /next 0.5 and both docs pages, pinned in both directions. - The other arm: a blockquote OPENS a block, so blockquoted prose under a key line is no longer read as that value's lazy continuation (it HALTed a readable declaration as "spilled"). - A step id repeated in a `custom` whitelist HALTs naming it, never deduped: the same mistake HALTs one rung up when it is the KEY that repeats, and unread it emitted the id's prerequisite warning twice byte for byte with `enabled` carrying the duplicate. - ADL 2026-08-28-a-config-reader-… extended in place (8th), with both rejected alternatives (dedupe; treat the blockquote as documentation). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
…e's is not - All three key patterns were case-sensitive while the HEADING one level up is not: `- `Profile`: `poc`` resolved to `default` / 12 steps, zero halts, zero warnings — byte-identical to writing nothing (the silent widening). Worse, `custom` + `- `Whitelist`: `implement`` HALTed with "declares no `whitelist`" about a line visibly in the file — the anti-pattern the schema's own table writes down. - Detection is now case-insensitive on all three patterns and the capture is canonicalized once (`canonicalKey`), so two spellings of one key are one key declared twice and every message prints the canonical name. Accepted, not HALTed: case is a decoration like bold/missing backticks, all honoured. The rejected MARKER shapes (off-marker, blockquote) keep their HALT in any case; the VALUE stays strict — `POC` is still an unknown profile name. - Schema error table, adoption template, /next 0.5 and both docs pages state the rule; ADL 2026-08-28-a-config-reader-… extended in place (9th) with the rejected alternative (route the case variant to unreadableShapeHalt). - The template's corruption test now anchors on the FENCE: its first-occurrence replace hit the new prose bullet, leaving the worked example intact and the test green whatever the gate did. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
…ile the gate cannot find is not "clean" - rejected-marker patterns: backticks optional behind a list marker, required only marker-less. `1. profile: poc` / `> - profile: poc` resolved default/12 with zero halts while each error axis HALTed alone. - checkShippedProfileProse: a MISSING/RENAMED governed file is an error, not a skip — `[]` was indistinguishable from "all five validated clean". - collectProcessSkillDirs → collectAllSkillDirs: it returns capability dirs too, and two catalogued steps (define-subdomains, define-bounded-contexts) need it. - ai-development README lists process-profiles.md + step-catalogue.md (both copies, pinned); process-profile-gate.md ships the `$approval`-family delta variant too, pinned to /map-subdomains. - brainstorm SKILL.md: one more disclosure split, budget unchanged — mirror 28,643 → 27,979 B (29 → 693 B headroom). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
…-skill rule is checked per skill - `/bootstrap` Phase 0 Step 0.1 asks the profile before composing `/specify-prd` (disabled ⇒ exactly "not installed": HALT at Phase 0, how-to-01 pointer, never a prompt). Guard: the composers test now asserts the clause once per COMPOSED STEP ID declared in the delta, inside that step's own beat — one clause per file let bootstrap's two map-* beats cover a third composed step - `checkInstalledProfileCorpus`: the installed `.pair/knowledge/**` catalogue + profiles are bound to the dataset (ids, how-to, requires, built-in whitelists; executables through the real `installedSkillDir` transform) and the manual-path check runs over root AGENTS.md/CLAUDE.md too. Fail-closed on a missing file - `checkStepDelta`: each step skill's `## Process Profile` section (plus disclosed siblings, never a link out of the dir) must state the DIRECT and COMPOSED clauses — the sentence was pinned for 2 of 12, deletable in the other 10 - `sectionOfWhere` ends a section at a level-1 heading too (`###`+ stays inside): a key under a later `#` was a phantom "declared more than once" - ADL extended in place (11th), mirror regenerated by `pair update` Test-first: 16 cases RED (14 + 2) → 269 / 114 green; hub suite 5,177 → 5,200. quality-gate exit 0; smoke-tests 20/20 PASS. Refs: #251
…report that names a step filters it - installed `process-profile-gate.md` bound (DIRECT/COMPOSED clauses + `auto=halt`, fail-closed): gutted to `TODO` it was `PASS`, exit 0, and an unattended run had no `auto=halt` instruction left anywhere - worked-example sweep extracted (`profileExampleErrors`) and run over both installed KB copies: `spcify-prd` in the installed `custom` example was reported by nothing - composers guard cross-checks its subject list against the `## Composed Skills` table and derives the composer list from the corpus — shrinking a delta reddens instead of de-scoping the guard - every step report that names a catalogued step filters its `Next:` line by the profile; rule stated once in the convention Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
…nd a guard's subjects come from the algorithm - normalizeHeading strips leading decoration (non-alnum run, `N.`/`N)`) and collapses internal separators (`-`/`_` → space, whitespace runs). `## 🎯 Process Profile`, `## 1. Process Profile`, `## Process Profile`, `## Process-Profile` over a valid `poc` declaration each resolved to `default`/12 steps, zero halts — the corpus's own house style. - composers guard: subject ids = delta ∪ `## Composed Skills` table ∪ the object of each `compos*` verb before `## Graceful Degradation`. Two declarations in one file de-scoped the guard to 147 green tests (from 149); a third edit gutting the beat then landed green. - checkInstalledGateConvention binds its fourth clause (the `Next:` filter), mirroring the dataset-side assertion. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
Regenerated with pair update (offline, local dataset) to resolve the .pair/llms.txt rebase conflict. Refs: #251 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FfhvsS5rippi6aUbbGrf6F
f6eec49 to
3fa9c30
Compare
7b55900 to
adb9627
Compare
PR Information
PR Title: [#251] feat: Process Profile section in way-of-working (per-step whitelist)
Story/Epic: #251 — epic #204
Type: Feature
Assignee: rucka
Labels:
risk:yellowSummary
What Changed
A team that runs a subset of the pair process declares it once, in
way-of-working.md:and no disabled step is ever proposed — through
/pair-next, through a direct skill invocation, through a composition, or by hand from the how-to guides.Five pieces:
dataset/.pair/knowledge/.../step-catalogue.md.../process-profiles.mddefault/poc/custom, the error table (every case normative), the one non-fatal case.../skill-conventions/process-profile-gate.mdway-of-working.md## Process Profile, optional; absent ⇒default⇒ today's behaviour byte for byte/nextStep 0.5 (+ the 12 step skills' delta),AGENTS.mdmanual flowWhy This Change
pocteams get/plan-initiatives,/plan-epicsand both DDD-mapping steps proposed at them today, with no way to say "we don't do that". The unit of the choice is the STEP, not the skill:define-subdomainshas no how-to,brainstormhas no how-to, and the manual path must obey the same declaration (R2.7, D19, AC8).Story Context
User story: as a team adopting a subset of the pair process, I declare it once so no disabled step is ever proposed.
AC coverage: AC1 absent section ⇒
defaultunchanged · AC3pocbuilt-in · AC4customwhitelist, a disabled row skipped not errored · AC5 unknown id / unknown name HALT with distinct messages · AC7 the composing skill applies the check before composing · AC8 the manual path is governed at its own entrypoint · AC9 prerequisite inconsistency reported with the minimal fix, never repaired · AC10 an empty whitelist is a misconfiguration.Changes Made
Implementation Details
pocpermanently self-inconsistent); the catalogue is the scope boundary, so "is/estimategoverned?" is answerable.resolveProcessProfilelives inskills-conformance-check.ts(the root-gatedskills:conformanceCLI, per the repo's "gate logic lives in a tested module" rule). It reads the shipped adoption template, this repo's own way-of-working, and every shipped worked example — so a template or a docs page carrying a shape no reader accepts is a red gate, not a shape every adopting project copies.default. The rule binds every level: the section (count, level, ATX/setext, and its heading's decoration on every side — a leading emoji orN.numbering,-/_separators and doubled spaces normalize away exactly as trailing emphasis and a trailing parenthetical do, since## 🎯 Quick Start Processis this corpus's own house style; the comparison stays an equality, so## Process Profile Gateis a different section), the key (once, on a-/*/+bullet, at the top level, one line however long), the value (residue, not token count, and no step id named twice), and the lexical layer beneath them (CRLF, the three code-block forms, fence run length, masked HTML comments, unterminated delimiters)./nextStep 0.5 and both docs pages, and pinned in both directions.- `Profile`: `poc`declares the profile (the heading one line above is Title Case and is itself matched case-insensitively, so mirroring it is the natural author error), the capture is canonicalized at one point so two spellings are one key for the duplicate-key rule, and- `profile`: `POC`still HALTs as an unknown profile name. The rejected MARKER shapes — off-marker, blockquoted — keep their HALT in any case.ProfileResolutionisResolvedProfile {ok:true, profile, enabled, warnings} | HaltedProfile {ok:false, halts}— a caller cannot read anenabledset from a resolution that could not be read.1. profile: pocand> - profile: pocHALT exactly as each error does alone. Backticks stay required only on the genuinely marker-less line, where they are the sole signal separating a declaration from a sentence —profile: pocis prose and stays prose (asserted).checkStepMarkersInMirror(names mapped through the realpair updatetransform) andcheckManualPathEntrypoint(asserted onAGENTS.md's Quick Start section).checkShippedProfileProseover a tree with none of the five present returns five errors, where it used to return[]— indistinguishable from "all five validated clean" while the PASS banner named them.collectAllSkillDirsis named for what it returns. It yields every skill dir, capabilities included, and both callers need that:define-subdomainsanddefine-bounded-contexts— the flagshippoccase — have capability executables, so a collector narrowed toprocess/**would drop the mirror marker guard for exactly those two, silently.checkInstalledProfileCorpusbinds.pair/knowledge/**/step-catalogue.mdand.../process-profiles.mdto the dataset (step ids, how-to,requires, built-in whitelists — executables compared through the realpair updatename transform, never a copy of it) and runs the manual-path check over the rootAGENTS.mdandCLAUDE.md. It binds their PROSE too — every worked example in either copy resolved through the real resolver — and the third governed file of the feature,.pair/knowledge/**/skill-conventions/process-profile-gate.md, read fail-closed and required to still state all four of its rules: the DIRECT and COMPOSED clauses, theauto=haltunattended resolution, and the completion-reportNext:filter — the last one bound on the installed side exactly as the dataset side already bound it. These are the copies/pair-next, every step skill and every human resolve at runtime; a half-runpair updateused to leave them ungoverned while the gate's PASS banner named them.## Process Profilesection (or in a sibling it discloses — never through the link OUT to the convention, which states both clauses itself), the DIRECT rule (warn, then confirm/HALT) and the COMPOSED rule (never prompts, degrades as not installed). Clauses rather than the convention snippet byte-for-byte: the corpus ships three legitimate shapes (plain,$approval-family,brainstorm's disclosed half) and a byte-check would force two of them to lie./bootstrapdeclares three; its Phase 0 beat now asks the profile before composing/specify-prd(disabled ⇒ exactly "not installed": HALT at Phase 0 with the how-to-01 pointer, never a prompt), and the conformance guard asserts the clause once per composed step id declared in the composer's own delta, matched inside that step's own beat.###+ still sits inside it): a key under a later#used to be reported as a duplicateprofileline inside a section that visibly carries one.Next:line a step prints names skills in prose, exactly as/pair-next's Step 5 fallback does — and only the fallback had the "never name a disabled step" rule. The rule is stated once in the gate convention and applied by the eight step reports that name a catalogued step, so an enabled skill can no longer end its run by telling acustomproject to run a step it declared it does not run.## Composed Skillstable (Skill-headed only, cells resolved through the real install transform) and the ALGORITHM — every catalogued executable that is the object of acompos*verb in the text before## Graceful Degradation. The first two are declarations by the same author in one file and fell together in one edit; the third is what the skill actually does, so deleting a claim no longer deletes the case that policed it. The list of composers itself is derived from the corpus rather than typed into the test.Files Changed
step-catalogue.md,process-profiles.md,process-profile-gate.md,process-profile.test.ts, ADR-024, one ADL — plus their.pair/knowledgemirrors.skills-conformance-check.ts(+ its unit file),next/SKILL.md(Step 0.5, the row→step table, Step 5's three ordered rules), 12 step skills (marker + one-line delta), both way-of-working files,AGENTS.md/CLAUDE.md, 2 docs-site pages,.claude/skills/**mirrors,.pair/llms.txt.Testing
Test Coverage
parseWowProfileSection,profileSectionProblems,extractProfileExamples,checkShippedProfileProse,checkInstalledProfileCorpusandresolveProcessProfile— every HALT has its pre-fix input and its post-fix outcome./next's Step 0.5 (dataset and installed mirror), the composers' composition-site statement, the adoption template, the two guideline indexes and the manual-path entrypoint say what the story requires. The gate convention's copy-me delta is pinned byte for byte to the corpus in both of its shapes — the plain one to/refine-story, the$approval-family one (auto=haltmarker +$approvalrow) to/map-subdomains— so a thirteenth author copying it into anassess-/map-skill cannot land three conformance errors off a snippet they followed literally.466e3f61landed the guard and the resolver alone —skills:conformanceFAILed (missing catalogue, fail-closed) andprocess-profile.test.tswas 42/44 red. Every subsequent fix round was RED-first too.Test Results
Tiering is
disabledon this repo, so the full suite was run rather than the 🟡 subset.Quality Assurance
Review Areas
brainstorm's delta is shaped unlike the other eleven (marker inSKILL.md, the composition half indegradation.md). Reason and rejected alternatives: ADL2026-08-28-a-conventions-marker-is-the-entrypoints-its-pointer-may-be-disclosed.md. Its installed mirror is 28,224 B of a 28,672 B budget — 448 B of headroom: the budget was never raised, and one further disclosure split was taken on this branch (the Parametrization paragraph restatingparametrization.md, the Notes bullet restating three pointers their own sections already carry) so the next unrelated PR does not pay for a split it did not come for.checkStepMarkersresolves the convention POINTER over the skill's whole directory (the marker itself is still required inSKILL.md). Deliberate, unit-tested.pair updatealso rewrites/setup-gates→/pair-capability-setup-gatesinadr-021,adr-022,adr-023and one context file. That drift predates this branch (theadoptionregistry has no mirror guard); those files were reverted after each regeneration so the diff stays this story's.Documentation
a config reader detects the KEY loosely and accepts the VALUE strictly(extended in place across the review rounds — one rule, from the key line down to the lexical layer and out to the files the gate reads).apps/website/content/docs/concepts/adoption-files.mdx(the section, the three profiles, the error table) anddocs/reference/pair-next.mdx(Step 0.5).Risk Assessment
Technical Risks
enabledset at all (type-level)skills:conformanceresolves both way-of-working files as declarations and every shipped worked example — dataset copies, docs site, and the installed.pair/knowledge/**copies — through the real resolver.claude/skills/pair-*through the real install transform; the installed KB catalogue/profiles (cells and worked examples), the installed gate convention (clauses +auto=halt) and the rootAGENTS.md/CLAUDE.mdbound to the dataset tooReviewer Guide
Testing the Changes
Key Test Scenarios
- `profile`: `pocc`under## Process Profilein.pair/adoption/tech/way-of-working.md→pnpm skills:conformanceFAILs, exit 1, naming the file and the unknown profile. Restore → PASS.customworked example → FAIL namingworked example #N (custom). Same for the two docs-site pages.> - `profile`: `poc`) → FAIL, exit 1, naming the shape; write it as a table row → stilldefault, no halt (documentation, by design).3b. Write it with both errors at once —
1. profile: poc, or> - profile: poc→ FAIL, exit 1, same shape HALT (before, each error alone HALTed and their combination resolved silently todefault/ 12 steps).3d. Retitle the section
## 🎯 Process Profile,## 1. Process Profile,## Process Profileor## Process-Profilewith a`pocc`value under it → FAIL, exit 1, each naming the unknown profile (before, all four printed PASS: the section evaporated and the project silently got all 12 steps back).## Process-Profile-Gatestill resolves todefault, no halt.3c. Rename
apps/website/content/docs/reference/pair-next.mdx→pnpm skills:conformanceFAILs naming the path it can no longer find, instead of reporting one fewer file checked.- `Profile`: `poc`→ resolvespoc/ 8 steps, no halt (case is detection);- `profile`: `POC`→ HALT, unknown profile name (case is not acceptance).- `profile`: `poc`and run/pair-nexton a repo with no epics →/brainstorm, never/plan-epics, never a DDD-mapping step.|brainstorm| … |row from.pair/knowledge/**/step-catalogue.md, or typopoc's first whitelist id in.pair/knowledge/**/process-profiles.md→ FAIL, exit 1, naming the installed copy and the drifted cell. Same for replacing the3. **Check the process profile**step in rootAGENTS.md/CLAUDE.md(3 violations per file).dataset/.skills/process/implement/SKILL.mdand its mirror, leaving marker + pointer → FAIL, exit 1, DIRECT and COMPOSED, dataset and mirror.## Process Profile+- `profile`: `poc`+# Other+- `profile`: `custom`→ resolvespoc, no halt (the level-1 heading ends the section); the same document with### Keysinstead of# Otherstill resolvespoc(a sub-heading stays inside)..pair/knowledge/**/skill-conventions/process-profile-gate.mdto# Process Profile Gate+TODO→ FAIL, exit 1, three violations (DIRECT, COMPOSED,auto=halt); delete only the**A completion report that names a next skill applies the same filter.**paragraph → FAIL, exit 1, one violation naming theNext:rule. Typo`specify-prd`→`spcify-prd`in the installedprocess-profiles.mdcustomworked example → FAIL namingworked example #2 (custom).plan-tasks' delta toa composer of the context-mapping capability(dataset + mirror) →process-profile.test.ts4 failed; drop one id of two fromrefine-story's → 3 failed. Before, both were green and the test COUNT silently dropped. Then the two-declaration version: deletedefine-subdomainsfrom/bootstrap's delta AND its| `/map-subdomains` | Capability |table row (dataset + mirror) → still 149 tests, because the Phase-3.5.1 beat still saysCompose /map-subdomains; gut that beat to decide on installation alone → 2 failed. Before, the two edits alone dropped the file to 147 green and the third edit landed green on top.Next:filter sentence from any of the eight step reports →process-profile.test.tsreddens on that file (dataset and mirror), derived from the corpus rather than a list.Dependencies & Related Work
way-of-working.mdshared with tech-debt: /write-issue contract — $assignee parameter + membership-precedes-state invariant (deferred half of #402) #403 (not landed here); the 12 skill files with /pair-process-review forwards its resolved tier to verify-quality (exact review-path parity) #382 / TDD bug-fix check in the review checklist (red-test-before-fix) #283 / Guided authoring of the Criticality Table / Overrides delta in bootstrap/quickstart #351. Nothing from those cards is on this branch.