Skip to content

[#251] feat: Process Profile section in way-of-working (per-step whitelist) - #467

Open
rucka wants to merge 28 commits into
mainfrom
feature/US-251-process-profile-section
Open

[#251] feat: Process Profile section in way-of-working (per-step whitelist)#467
rucka wants to merge 28 commits into
mainfrom
feature/US-251-process-profile-section

Conversation

@rucka

@rucka rucka commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

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:yellow

Summary

What Changed

A team that runs a subset of the pair process declares it once, in way-of-working.md:

## Process Profile

- `profile`: `poc`

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:

Piece Where What it is
Step catalogue dataset/.pair/knowledge/.../step-catalogue.md 12 steps; each carries a stable id, a nullable how-to, a nullable executable, any-of prerequisites
Profile schema .../process-profiles.md default / poc / custom, the error table (every case normative), the one non-fatal case
Gate convention .../skill-conventions/process-profile-gate.md direct invocation warns+confirms; a composed disabled step degrades as not-installed, never prompts
Adoption section shipped template + this repo's own way-of-working.md ## Process Profile, optional; absent ⇒ default ⇒ today's behaviour byte for byte
Readers /next Step 0.5 (+ the 12 step skills' delta), AGENTS.md manual flow resolve, filter, HALT — re-read every run, never cached

Why This Change

poc teams get /plan-initiatives, /plan-epics and 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-subdomains has no how-to, brainstorm has 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 ⇒ default unchanged · AC3 poc built-in · AC4 custom whitelist, 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

  • The profile's unit is the step — ADR-024. Prerequisites are an any-of (an all-of makes the shipped poc permanently self-inconsistent); the catalogue is the scope boundary, so "is /estimate governed?" is answerable.
  • A reference resolver with a real caller. resolveProcessProfile lives in skills-conformance-check.ts (the root-gated skills:conformance CLI, 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.
  • Detection is loose, acceptance is strict, and an unreadable declaration HALTs — never a quiet fallback to default. The rule binds every level: the section (count, level, ATX/setext, and its heading's decoration on every side — a leading emoji or N. numbering, -/_ separators and doubled spaces normalize away exactly as trailing emphasis and a trailing parenthetical do, since ## 🎯 Quick Start Process is this corpus's own house style; the comparison stays an equality, so ## Process Profile Gate is 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).
  • A decoration is a declaration; a quotation is documentation. A key inside a blockquote HALTs (nothing in this corpus writes a blockquoted list item, and reading it as prose re-enabled the whole process in silence); a key inside a documentation table row is deliberately neither read nor reported, because that is the shape the schema and the shipped template use to document the two keys. Both halves are stated in the schema, the adoption template, /next Step 0.5 and both docs pages, and pinned in both directions.
  • The key's CASE is part of its spelling, so it is on the detection side; the value's is not. - `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.
  • A HALT yields no step set. ProfileResolution is ResolvedProfile {ok:true, profile, enabled, warnings} | HaltedProfile {ok:false, halts} — a caller cannot read an enabled set from a resolution that could not be read.
  • Two author errors on one line compose; they do not cancel. The rejected-marker shapes are detected whether or not the key is backticked, so 1. profile: poc and > - profile: poc HALT 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: poc is prose and stays prose (asserted).
  • The mirror and the manual path are inside the gate: checkStepMarkersInMirror (names mapped through the real pair update transform) and checkManualPathEntrypoint (asserted on AGENTS.md's Quick Start section).
  • The gate fails closed on its own inputs. A governed file that is missing or renamed is an error naming the path, not a skip: checkShippedProfileProse over 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.
  • collectAllSkillDirs is named for what it returns. It yields every skill dir, capabilities included, and both callers need that: define-subdomains and define-bounded-contexts — the flagship poc case — have capability executables, so a collector narrowed to process/** would drop the mirror marker guard for exactly those two, silently.
  • A GENERATED copy is a governed copy, not a build artifact — and that now covers the KB and the root entrypoints, not only the skills mirror. checkInstalledProfileCorpus binds .pair/knowledge/**/step-catalogue.md and .../process-profiles.md to the dataset (step ids, how-to, requires, built-in whitelists — executables compared through the real pair update name transform, never a copy of it) and runs the manual-path check over the root AGENTS.md and CLAUDE.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, the auto=halt unattended resolution, and the completion-report Next: 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-run pair update used to leave them ungoverned while the gate's PASS banner named them.
  • A per-skill obligation is checked per skill. Each of the 12 step skills must state, in its ## Process Profile section (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.
  • A composition beat states the profile per COMPOSED STEP. /bootstrap declares 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.
  • A section ends at a level-1 heading as well as at level 2 (###+ still sits inside it): a key under a later # used to be reported as a duplicate profile line inside a section that visibly carries one.
  • A completion report is a governed surface. The 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 a custom project to run a step it declared it does not run.
  • A conformance guard never takes its subject list from the file under test. The composers guard read the ids it policed off the composer's own delta line, so shrinking that line shrank the guard with it. The subject list is now the union of three sources: the delta, the ## Composed Skills table (Skill-headed only, cells resolved through the real install transform) and the ALGORITHM — every catalogued executable that is the object of a compos* 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

  • Added: step-catalogue.md, process-profiles.md, process-profile-gate.md, process-profile.test.ts, ADR-024, one ADL — plus their .pair/knowledge mirrors.
  • Modified: 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.
  • 53 files, +6,063 / −131.

Testing

Test Coverage

  • Unit (white-box, the gate module): 279 cases over the catalogue parser, the marker checks, parseWowProfileSection, profileSectionProblems, extractProfileExamples, checkShippedProfileProse, checkInstalledProfileCorpus and resolveProcessProfile — every HALT has its pre-fix input and its post-fix outcome.
  • Conformance (the prose contract): 149 cases asserting the schema, the gate convention, /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=halt marker + $approval row) to /map-subdomains — so a thirteenth author copying it into an assess-/map- skill cannot land three conformance errors off a snippet they followed literally.
  • Written test-first: commit 466e3f61 landed the guard and the resolver aloneskills:conformance FAILed (missing catalogue, fail-closed) and process-profile.test.ts was 42/44 red. Every subsequent fix round was RED-first too.

Test Results

pnpm quality-gate                     exit 0  (ts:check test lint · workflows:test · format:check ·
                                              gate:composition · hygiene:check · smoke-modes:check ·
                                              docs:staleness · skills:conformance · dup:check)
./scripts/smoke-tests/run-all.sh --ci all 20 PASS
knowledge-hub full suite              5,245 passed

Tiering is disabled on 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 in SKILL.md, the composition half in degradation.md). Reason and rejected alternatives: ADL 2026-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 restating parametrization.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.
  • checkStepMarkers resolves the convention POINTER over the skill's whole directory (the marker itself is still required in SKILL.md). Deliberate, unit-tested.
  • Pre-existing drift, deliberately not included: running pair update also rewrites /setup-gates/pair-capability-setup-gates in adr-021, adr-022, adr-023 and one context file. That drift predates this branch (the adoption registry has no mirror guard); those files were reverted after each regeneration so the diff stays this story's.

Documentation

  • Technical decisions: ADR-024 (the unit is the step) · ADL on the marker/pointer split · ADL 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).
  • User-facing: apps/website/content/docs/concepts/adoption-files.mdx (the section, the three profiles, the error table) and docs/reference/pair-next.mdx (Step 0.5).

Risk Assessment

Technical Risks

Risk Impact Probability Mitigation
A misread profile silently WIDENS to the full process Med Low Every unreadable shape HALTs; a halted resolution has no enabled set at all (type-level)
A misread profile silently NARROWS (a step vanishes from every suggestion, indistinguishable from "not due yet") High Low Readability decided on the residue; partial/spilled/duplicated declarations HALT; an empty whitelist is a misconfiguration
A shipped template, docs page or installed KB copy teaches a shape no reader accepts Med Low skills:conformance resolves 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
The installed mirror drifts from the dataset Med Low Marker + gate pointer + the delta's two clauses asserted on .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 root AGENTS.md/CLAUDE.md bound to the dataset too

Reviewer Guide

Testing the Changes

git checkout feature/US-251-process-profile-section && pnpm install
pnpm skills:conformance                 # the gate, incl. both way-of-working files as declarations
pnpm --filter @pair/knowledge-hub test  # 5,245
pnpm quality-gate

Key Test Scenarios

  1. Add - `profile`: `pocc` under ## Process Profile in .pair/adoption/tech/way-of-working.mdpnpm skills:conformance FAILs, exit 1, naming the file and the unknown profile. Restore → PASS.
  2. Corrupt one step id in the shipped template's custom worked example → FAIL naming worked example #N (custom). Same for the two docs-site pages.
  3. Write the declaration inside a blockquote (> - `profile`: `poc`) → FAIL, exit 1, naming the shape; write it as a table row → still default, 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 to default / 12 steps).
    3d. Retitle the section ## 🎯 Process Profile, ## 1. Process Profile, ## Process Profile or ## Process-Profile with 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-Gate still resolves to default, no halt.
    3c. Rename apps/website/content/docs/reference/pair-next.mdxpnpm skills:conformance FAILs naming the path it can no longer find, instead of reporting one fewer file checked.
  4. Spell the key - `Profile`: `poc` → resolves poc / 8 steps, no halt (case is detection); - `profile`: `POC` → HALT, unknown profile name (case is not acceptance).
  5. Declare - `profile`: `poc` and run /pair-next on a repo with no epics → /brainstorm, never /plan-epics, never a DDD-mapping step.
  6. Delete the | brainstorm | … | row from .pair/knowledge/**/step-catalogue.md, or typo poc's first whitelist id in .pair/knowledge/**/process-profiles.md → FAIL, exit 1, naming the installed copy and the drifted cell. Same for replacing the 3. **Check the process profile** step in root AGENTS.md/CLAUDE.md (3 violations per file).
  7. Delete the delta sentence from dataset/.skills/process/implement/SKILL.md and its mirror, leaving marker + pointer → FAIL, exit 1, DIRECT and COMPOSED, dataset and mirror.
  8. ## Process Profile + - `profile`: `poc` + # Other + - `profile`: `custom` → resolves poc, no halt (the level-1 heading ends the section); the same document with ### Keys instead of # Other still resolves poc (a sub-heading stays inside).
  9. Reduce .pair/knowledge/**/skill-conventions/process-profile-gate.md to # 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 the Next: rule. Typo `specify-prd``spcify-prd` in the installed process-profiles.md custom worked example → FAIL naming worked example #2 (custom).
  10. Rewrite plan-tasks' delta to a composer of the context-mapping capability (dataset + mirror) → process-profile.test.ts 4 failed; drop one id of two from refine-story's → 3 failed. Before, both were green and the test COUNT silently dropped. Then the two-declaration version: delete define-subdomains from /bootstrap's delta AND its | `/map-subdomains` | Capability | table row (dataset + mirror) → still 149 tests, because the Phase-3.5.1 beat still says Compose /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.
  11. Remove the Next: filter sentence from any of the eight step reports → process-profile.test.ts reddens on that file (dataset and mirror), derived from the corpus rather than a list.

Dependencies & Related Work

@rucka rucka added the risk:yellow Classification: medium risk tier label Aug 28, 2026
@rucka rucka self-assigned this Aug 28, 2026
@rucka rucka added the pr-state:to-be-reviewed PR state: awaiting review / gate label Aug 28, 2026
@rucka

rucka commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict

risk:yellow · cost:n/aCHANGES-REQUESTED — the mechanism is right and well-tested, but the reference resolver silently widens to default on two plausible way-of-working shapes (the exact failure the schema declares must always HALT), and the manual (no-skills) path has no entrypoint that mentions the profile.

Open findings: 8. (2 Major, 6 Minor; 1 Question is informational and non-blocking.)

PR: #467 · Author: rucka · Reviewer: independent review agent · Date: 2026-08-28 · Story: US-251 · Type: feature

Classification matrix — per dimension
Dimension Tier Source Note
Service/domain criticality green tech/risk-matrix.md table KB dataset + guarded skill mirrors + one adoption file, all Low
Change/diff risk yellow diff footprint 46 files, additive; new KB catalogue + convention + 12 skill deltas + a second location (WoW adoption)
Business impact yellow subdomain class Collaborative Workflow (Core); skill-corpus precedent (#411/#404/#412)
Security relevance green reviewer-assessed No new runtime surface; the only code is a repo-local CI reader
Coupling balance green reviewer-assessed No new cross-context integration

Tier = max(assessed) = yellow — confirms the story's refinement-time tier, no drift.

Assessments

Security — Input validation

Verdict: green (reviewer-assessed; /pair-capability-assess-security not invoked) — the only inputs are repo-local markdown files read by a CI script.

Details

skills-conformance-check.ts reads dataset/** markdown with readFileSync/readdirSync under a fixed ROOT. No path segment comes from user input, no network, no shell, no deserialization. Regexes (STEP_MARKER, the key/backtick matchers) are linear and bounded — no catastrophic-backtracking shape.

Security — Output handling

Verdict: green — CLI strings to stdout only; no rendering surface.

Security — Authentication

Verdict: not applicable — no auth surface touched.

Security — Authorization

Verdict: not applicable — the profile is explicitly documented as a project convention, not a permission system (process-profile-gate.md:19), so it carries no authorization semantics to weaken.

Security — Introduced vulnerabilities

Verdict: green — 0 introduced, 0 pre-existing on the touched paths.

Cost

Verdict: cost:n/a/pair-capability-assess-cost not invoked in this review; no paid-provider signal is visible in the diff (no infra, no model calls, no new dependency).

Architecture (Coupling)

Verdict: green (reviewer-assessed) — one new KB→adoption read direction, already the corpus's established pattern (## State Mapping, ## Git Workflow).

Bug fix — Red test before fix

Verdict: not applicable — not a bug fix. (RED evidence is nonetheless recorded in the PR: commit 466e3f61 landed the guard alone, skills:conformance failing fail-closed and 42/44 red.)

Details

Findings by severity

Critical (must fix before merge)

  • none.

Major (must fix before merge)

  • packages/knowledge-hub/src/tools/skills-conformance-check.ts:466 (parseWowProfileSection, key regex ^\s*[-*]\s* + backticked key + backticked value) — a profile key whose value is not backticked, or whose key is bolded, is not matched at all, and the section silently resolves to default with zero halts and zero warnings. Verified by running the shipped resolver over the shipped catalogue:
    - \profile`: poc{profile:"default", enabled:12 steps, halts:[], warnings:[]}<br>- profile: `poc`{profile:"default", enabled:12 steps, halts:[], warnings:[]}<br>Failure case: a PoC team copies the shape from the **schema table** (| `profile` | `default` | `poc` | `custom` |) rather than from the fenced example, writes - `profile`: poc, and gets the full 12-step process — plan-initiatives, define-subdomains, define-bounded-contexts all enabled and proposable — with nothing reported. This inverts the file's own normative rule (process-profiles.md:44: *"Every case below therefore HALTs rather than narrowing quietly"*): the resolver narrows quietly in the widening direction, which is the one direction the story never anticipated. **Fix:** loosen key DETECTION (^\s*[-]\s*`?(profile|whitelist)`?*\s*:) while keeping value acceptance strict, and HALT with the schema shape when a detected profile` line yields no readable value.
  • packages/knowledge-hub/dataset/AGENTS.md:58-68 (Quick Start Process, manual flow) — AC8 has no entrypoint — the profile is reachable from /pair-next and from the 12 step skills, but nothing a human on the no-skills path reads mentions it: grep -rl "Process Profile" packages/knowledge-hub/dataset/ returns only the new KB files, way-of-working.md, and the 13 skill files — no how-to guide and not AGENTS.md. Failure case: a team declares profile: poc, has no skills installed, follows AGENTS.md step 3 "Identify your task: match your request to a task category using .pair/knowledge/how-to/", picks 03-how-to-create-and-prioritize-initiatives.mdplan-initiatives, disabled under poc — and runs a step the project declared it does not run, with no warning anywhere. The catalogue's how-to column makes the mapping expressible; it does not make the manual path governed, which is what AC8's "Then" and the story's success metric ("a project with no skills installed gets the same profile behaviour through the how-to path") ask for. Fix: one step in the shipped AGENTS.md manual flow (between "Understand the project" and "Identify your task") pointing at way-of-working.md## Process Profile and at the catalogue's how-to column, plus a conformance assertion binding it — the mirror regenerates via pair update.

Minor (must fix before merge — same bar as Major, just lower impact)

  • packages/knowledge-hub/src/conformance/process-profile.test.ts:118checkStepMarkers(entries, SKILLS_DIR) runs over dataset/.skills only, so the installed mirrors the assistant actually loads are unguarded for the marker. Empirically confirmed on this head: deleting <!-- process-step: id=review --> from .claude/skills/pair-process-review/SKILL.md leaves 168/168 tests green and skills:conformance PASS. Failure case: a mirror regeneration or hand edit drops a marker; the skill can no longer tell which step it is, its gate never fires under poc, and every gate stays green — the silently-ungoverned step the design exists to prevent. This file already treats the mirror as binding for /next (NEXT_MIRROR, line 48), and brainstorm-phases.test.ts:820 records the same class of finding from Adoption-informed story generation (decision log + ADR + context map) #280 ("it measured the DATASET copy only… the binding one is the installed MIRROR"). Fix: iterate the catalogue's executables and assert the corresponding .claude/skills/pair-*/SKILL.md carries the same marker + a gate pointer.
  • packages/knowledge-hub/src/tools/skills-conformance-check.ts:862 (sectionOf) — the section is located with content.indexOf('## ' + heading), i.e. the first textual occurrence anywhere, including inside prose, inside a fence, and inside a ### heading. Verified: a file whose earlier section says See `## Process Profile` below. and then declares - \profile`: `poc`in the real section resolves to{profile:"default", enabled:12, halts:[], warnings:[]} — the prose mention becomes the section start and the real heading becomes its terminator. This cross-reference style is already in use in the very file being parsed (.pair/adoption/tech/way-of-working.md:22: *"exactly like ## Git Workflow above"*). **Fix:** anchor on a line-start heading match (/^##[ \t]+[ \t]*$/m`) and skip fenced regions when locating it.
  • .pair/knowledge/guidelines/technical-standards/ai-development/process-profiles.md:21 (and the dataset copy) — the file's own worked custom example is prerequisite-inconsistent. Running the shipped resolver on it verbatim returns warnings: ["\refine-story` is enabled but none of its prerequisites are — minimal fix: enable `plan-stories`, or drop `refine-story`"]. A reader who copies the schema's example into way-of-working.mdis greeted by the inconsistency report the same file documents two sections later. (The WoW template's example, by contrast, is clean.) **Fix:** addplan-stories` to the example whitelist in both copies.
  • .claude/skills/pair-process-brainstorm/degradation.md:15 (and dataset copy) — the direct half states "under $approval: auto it HALTs", but /pair-process-brainstorm neither declares $approval nor carries an approval-round marker (0 occurrences of each in its SKILL.md), and APPROVAL_SIGNAL_FAMILIES = ['assess-', 'map-'] (skills-conformance-check.ts:354) excludes it from checkApprovalSignalInSubDocs. Failure case: an unattended run passes $approval: auto; brainstorm's entrypoint has no notion of the signal, so the promised HALT is asserted only in an ungated sub-doc and the behaviour under automation is whatever the executor improvises. The gate convention correctly hedges "for the skills that expose the signal" — this sub-doc drops the hedge. Fix: drop the $approval clause here (the marker-carrying map-* skills are the ones that expose it), or thread the signal into brainstorm properly.
  • packages/knowledge-hub/src/tools/skills-conformance-check.ts:562 (resolveCustomWhitelist)profile: custom with no whitelist key at all reports "declares an empty whitelist". Verified: - \profile`: `custom`alone →halts: ["profile `custom` declares an empty whitelist — …"]. The user is sent looking for a whitelist line that does not exist. HALTing is right; the message is not. **Fix:** distinguish whitelist === null("declares nowhitelistcustomrequires one") fromwhitelist.length === 0`.
  • .pair/knowledge/guidelines/technical-standards/ai-development/step-catalogue.md:40 — heading reads "The three asymmetries, as declared data" over two bullets (the first covers two steps). Related nit: the /next row→step table (.claude/skills/pair-next/SKILL.md:141-151) lists rows 6, 8-9, 10, 11, 7 & 12-16 out of numeric order, so a reader checking coverage has to sort it mentally. Fix: reword to "the three asymmetric steps" (or split the bullet), and order the table by row number.

Questions (informational, never blocking)

  • .claude/skills/pair-process-brainstorm/SKILL.md — 28,643 B against a 28,672 B budget (brainstorm-phases.test.ts:831) — 29 bytes of headroom means the next edit to that entrypoint, however unrelated, turns the gate red. Confirmed green here and deliberately not raised, per the guard's own instruction and ADL 2026-08-28. Flagged for the merge gate as an accepted cost, not a requested change.
Positive feedback
  • The modelling decision (step, not representation) is argued from measured corpus asymmetry and recorded as ADR-024 with the two rejected options — the reasoning survives without the PR.
  • The bidirectional catalogue↔corpus binding, and specifically its reverse half, is the right guard: an uncatalogued step reads as "enabled", and the check names exactly that in its own error string.
  • any-of prerequisites are justified by a real constraint (an all-of would make the shipped poc permanently self-inconsistent) rather than by generality.
  • Gate logic lives in a tested production module with white-box unit tests (124) rather than in the vitest conformance file — and resolveProcessProfile has a real caller, so the shipped WoW template is read by the real reader.
  • KB↔mirror parity is exact: every difference between .pair/** and dataset/.pair/** is the /next/pair-next registry rewrite, nothing else.
Functionality & requirements (AC coverage)
AC Verdict Evidence
1 — no section ⇒ default met resolveProcessProfile returns all 12 steps, no halts; unit test line 1162
2 — catalogue with declared asymmetries met 12 rows, nullable howTo/executable; test line 85
3 — poc never proposes DDD/strategic met built-in resolves to 8 steps, /next rows 3-4 dropped
4 — custom whitelist skips, chains across gaps met unit test line 1179; /next Step 0.5 item 4
5 — unknown id / unknown name HALT, distinct messages met both messages verified live
6 — direct invocation warns + confirms, written once met convention + 12 markers/pointers in both trees
7 — composed step degrades, never prompts met convention §Composition; composer deltas on refine-story/plan-tasks/brainstorm
8 — manual (no-skills) path governed partial catalogue maps the guides, but no manual-path entrypoint mentions the profile — Major #2
9 — disabled prerequisite flagged with minimal fix met prerequisiteWarnings; verified output
10 — empty whitelist is a misconfiguration met HALTs; unit test line 1197

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
  • Coverage: 44 conformance + 124 unit tests for the new surface. Ran on this head: 168/168 pass, skills:conformance PASS — 44 skills conformant.
  • Edge cases tested: all six WoW states, any-of satisfied by the alternative member, fenced example not read as a declaration, uncatalogued skill carrying a marker, both directions of the corpus binding.
  • Gaps: the installed mirrors' markers (Minor Setup & Project Management Integration #1) and the two silent-default parse paths (Major Setup & Project Management Integration #1, Minor Collaborative Knowledge Base #2) are untested in either direction — the holes are unasserted, not asserted-as-intended.
  • Gates: PASS (PR reports pnpm quality-gate exit 0 and 20/20 smoke; the two suites and the CLI reproduce green locally).
Adoption compliance
  • Degradation level: 1 (no degradation).
  • No new dependency; no tech-stack.md/architecture.md delta required.
  • ADR-024 records the architectural decision; ADL 2026-08-28 records the marker/pointer split. Both are indexed in .pair/llms.txt. No undocumented decision found.
  • Dataset edited as source with mirrors regenerated; the four pre-existing pair update drift files are correctly excluded and disclosed in the PR body.
Tech debt
Documentation
  • Docs site: concepts/adoption-files.mdx (schema, both examples, the three places a disabled step is met) and reference/pair-next.mdx (profile vs. arguments).
  • KB: catalogue, schema, convention, conventions README index, llms.txt.
  • Manual-path documentation is the gap — see Major Collaborative Knowledge Base #2.
Performance & deployment
  • No hot path. The added CI work is one extra directory walk of an already-walked corpus.
  • Reversible: absence of the section is the default, so the change is inert on every existing project.

Review method: detached worktree pinned to origin/feature/US-251-process-profile-section @ dc54f91f; findings reproduced by executing the shipped resolver against the shipped catalogue and by mutating a mirror to observe gate silence.

@rucka

rucka commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

In reply to the first review comment: #467 (comment)

Escalation — rounds 1–3, non-convergence

Round 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):

  • Round 1 (dd9450a2) — key/value detection loosened with a HALT on unreadable shape; manual-flow Check the process profile step added to dataset/AGENTS.md; dataset-only marker check wired into skills:conformance; sectionOf stopped treating prose/fenced/sub-headings as section boundaries; custom example whitelist fixed to a resolvable chain; stray $approval: auto dropped from brainstorm/degradation.md; resolveCustomWhitelist distinguishes null-whitelist from empty-whitelist; step-catalogue//next step tables reordered. Gates green, 16 new tests, escalated: none.
  • Round 2 (64b896f5) — .pair/llms.txt index regenerated; readability for both profile and whitelist decided on the line's RESIDUE (not token count), so a partially-backticked line HALTs; /next Step 5 gained three ordered fallback rules + a per-profile proposal table; heading match normalized (equality, not prefix) so decorated/parenthetical headings resolve. Gates green, 13 new tests, escalated: none.
  • Round 3 (66ded193) — duplicate ## Process Profile SECTIONS now HALT ("declared more than once — keep one section"); a mis-levelled heading (###, or any level ≠ 2) now HALTS naming the level. resolveProcessProfile extracted declarationShapeHalts to stay under lint limits. Gates green, 6 new tests, escalated: none.

Still-open actionable findings (found on this same head, one level further in than round 3's fix):

  1. [Minor] parseWowProfileSection (skills-conformance-check.ts:1460-1505) — a profile or whitelist key declared twice inside one section (not two sections — round 3 covers that) resolves last-write-wins, silently, in both the widening and the narrowing direction. Contradicts the two-lines-away rule that a multi-value line HALTs; a duplicate line doesn't. Same class as round 3's duplicate-section finding, one level further in.
  2. [Minor] fence/indent handling (skills-conformance-check.ts:1469-1475, :506-514 scanFences) — only backtick fences hide a worked example from the reader; a ~~~ fence or a 4-space-indented block is read as a real declaration. Carrying worked examples inside the section is the documented design (process-profiles.md:29 tells teams to copy a fenced example verbatim), so this is a live authoring hazard, not a hypothetical.
  3. [Minor] heading recognition (skills-conformance-check.ts:1385 ATX_HEADING, :881 sectionOfWhere) — three CommonMark-valid level-2 heading forms (setext, ≤3-leading-spaces ATX, closed ATX ## Process Profile ##) are read as neither a section nor a reported problem — the widening direction round 3 was written to close, reopened one shape further.
  4. [Minor] example sweep coverage (skills-conformance-check.ts:1773 checkShippedProfileProse / apps/website/content/docs/concepts/adoption-files.mdx) — the two worked examples added to the public docs page never go through the resolver; both are clean today but unguarded against the exact defect the PR argues for (a reader copies the example first).
  5. [Questions] .claude/skills/pair-process-brainstorm/SKILL.md disclosure budget — 29 B of headroom left in a 28 KiB budget (down from ~194 B on main), disclosed and justified in the ADL, but asks whether a further split was considered/deferred rather than left to the next contributor to hit.

Convention for continuing this cycle: further rework or re-review on this story — including any manual, out-of-band round — should be appended to .pair/working/reviews/251.md, not posted as a new standalone PR comment. The next orchestrated run on this story resumes from that same log; its eventual convergence will synthesize ONE final remediation comment and minimize this flush + any intermediate ones.

Preservation note: the working log is an untracked file that exists only in the persistent authoring worktree ../pair-worktrees/251. That worktree must stay in place until merge — if it is pruned or recreated, the round-by-round audit trail is lost (this comment and the first-review comment remain on the PR either way, and the first-review comment continues to prevent a duplicate first review on the next run).

Not merged. No PR artifact beyond this comment.

@rucka rucka left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Verdict

risk:yellow · cost:greenAPPROVED — 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.ts448 passed.
  • Full gate: pnpm quality-gatePASS.
  • Pair adoption verification: pnpm --filter @pair/knowledge-hub skills:conformancePASS, 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.

@rucka rucka added pr-state:ready-to-merge PR state: gates green + review approved and removed pr-state:to-be-reviewed PR state: awaiting review / gate labels Aug 30, 2026
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
rucka and others added 3 commits September 3, 2026 20:51
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
rucka and others added 23 commits September 3, 2026 21:42
- 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
@rucka
rucka force-pushed the feature/US-251-process-profile-section branch from f6eec49 to 3fa9c30 Compare September 3, 2026 19:51
@rucka
rucka force-pushed the main branch 2 times, most recently from 7b55900 to adb9627 Compare September 8, 2026 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-state:ready-to-merge PR state: gates green + review approved risk:yellow Classification: medium risk tier

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant