check-pr
無料Check an externally created PR for coding rule violations against memory/INDEX.md atoms and docs/architecture/code-review-rules.md.
日本語の概要は準備中です。原文の説明を表示しています。
Three-axis orchestrator: (1) clean section boundaries — naming, routes, views, ViewModels, DB ownership, cross-section access; (2) internal cohesion — no EF in service layer, caching in service only, proper interfaces, reusable ViewComponents, architecture-test coverage; (3) focused tests — grouped under the section, covering invariants/negatives/triggers, pruned of redundancy. Push-and-bot-review loop. Use when a sizable PR landed, a section shows arch drift, or /pr-review returned a non-trivial violation list. Camps is the gold-standard reference.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Drive one section of the Humans app to architectural alignment along three axes — boundary integrity, internal cohesion, test focus — through a phased PR that's pushed for bot review at each phase boundary. Surface follow-up /section-align targets for cross-section gaps the run discovers.
Every section in the app is a self-contained vertical slice: one canonical name, its own data, public API others consume cleanly, current internal standards, and a focused test suite grouped with the section. Drift gets corrected one section at a time; new violations stand out instead of disappearing into noise.
A section is "aligned" when all three axes hold:
Axis 1 — boundary integrity. The section has a single canonical name shared by folder, namespace, controller, views, ViewModels, route prefix, role suffix, invariant doc. Routes follow /<Section>/*, /<Section>/Admin/*, /api/<section>/*. No other section reads, writes, joins, or navigates EF into this section's tables. The section's controller, views folder, and ViewModels file exist under the section's name — not in Views/Shared/, Models/AdminViewModels.cs, or other sections' controllers.
Axis 2 — internal cohesion. Services live in the section's own Services/ — $SERVICES per the resolution block below — and never import Microsoft.EntityFrameworkCore or take DbContext. Repositories are sealed, factory-based, registered Singleton. Caching lives in the service layer only (decorator or service-internal IMemoryCache), never in repos or controllers. Interfaces obey the budget ratchet. ViewComponents exist where the section's data is rendered on other sections' pages. Architecture tests pin each invariant. Migrations are EF-auto-generated.
Axis 3 — test focus. Section-aligned tests live under one canonical location — $TESTS per the resolution block below: the section's own tests/Humans.<Section>.Tests/ project. Coverage maps to invariants, Negative Access Rules, and Triggers from the section doc — not to every method permutation. Redundant tests are pruned. Test maintenance cost matches test value. Tests assert observable behavior, not mock-graph internals.
Camps is the reference.
Every section lives at src/Sections/Humans.<Section>/ (an optional .Contracts sibling project holds its cross-section contracts). Resolve these paths once, before running any check on any axis, and substitute them into every command below.
SECTION=<Section>
SEC=src/Sections/Humans.$SECTION
SERVICES="$SEC/Services"
ENTITIES="$SEC/Domain"
REPOS="$SEC/Data"
CONTROLLERS="$SEC/Controllers"
VIEWS="$SEC/Views"
CONTRACTS=$(ls -d src/Sections/Humans.$SECTION.Contracts "$SEC/Contracts" 2>/dev/null)
TESTS=tests/Humans.$SECTION.Tests
DOC="$SEC/Docs/$SECTION.md"
ALL="$SEC" # whole section, for size/inventory only
# Every OTHER section's entities — for inbound-navigation scans. Never $ENTITIES,
# which is this section's own and would invert the check into a self-scan.
OTHER_ENTITIES=$(ls -d src/Sections/Humans.*/Domain 2>/dev/null | grep -v "^$SEC/Domain$")
# Not every section has every folder: Humans.Auth has no
# Controllers/ and no Views/. `git grep pat a/ b/` (no `--`) aborts the whole
# command with "fatal: no such path in the working tree" if ANY path is
# missing, so filter the list before passing it.
exist() { for p in "$@"; do [ -e "$p" ] && printf '%s ' "$p"; done; }
Every multi-path git grep below must wrap its paths in $(exist ...). A section legitimately missing a folder should scan the folders it does have, not abort the inventory. That is separate from the existence checks in A1.2 and A1.4, which report a missing controller or views folder as a finding — keep both: exist decides what to scan, A1.2/A1.4 decide what to report.
Each section owns far more than Services/ and Data/: Humans.Events also carries its own Controllers/, Views/, Models/, Domain/, Resources/ and Docs/, and there is no EventsController.cs under src/Humans.Web/Controllers/ at all. Any check that greps a fixed src/Humans.Web/... path misreports a section as missing or clean when it is neither.
Two things the section project does not absorb — resolve them separately:
HumansDbContext. dotnet ef commands need --context <Context> --project $SEC --output-dir Data/Migrations, with <Context> looked up rather than synthesized: <Section>DbContext is wrong for Humans.Consent (LegalDbContext) and Humans.Events (EventGuideDbContext). The SECTION_DB_CONTEXTS map in .github/workflows/build.yml is authoritative — see memory/process/ef-multi-context-commands.md.$SEC/Docs/<Section>.md, resolved once as $DOC — every later phase that reads or edits the invariant doc must use $DOC, never a literal docs/sections/ path.Zero-tolerance rule: the only allowed cross-section data link going forward is the User ↔ Profile relationship (identity row ↔ volunteer profile). Every other cross-section read or write is a violation, including patterns previously documented as "sanctioned exceptions" in section docs (narrow display lookups like ctx.Users.Select(u => u.DisplayName), ctx.Teams.Select(t => t.Name), etc.). "Sanctioned" is not a category in this skill.
When the inventory finds cross-section access (either direction), apply this protocol — never just "fix the offending file" silently, because that violates section ownership.
.Include across into another section's entity): if their public service API exists and delivers what we need, switch to it in THIS PR. If their API doesn't exist, flag their section as the next /section-align target, document the gap in our plan's follow-up list, and pause that specific fix until they're aligned. Then we come back and close out our section.Result-oriented framing: a section is provisionally aligned when all in-section work is done; fully aligned when every follow-up supplier has caught up and the last consumer-side fixes land. The Phase 4 doc polish acknowledges any provisional state by naming the pending suppliers.
Output every Phase 0 plan with an explicit Follow-up /section-align targets list naming each section that surfaced as needing its own pass.
PR <n> — inventory against diff; work on PR branch in a worktree.section <Name> — resolves to the section's invariant doc ($DOC, per the resolution block) + matching code; fresh branch off origin/main.<empty> — ask which target.--inventory-only — Phase 0 only.design-rules.md §8).SECTION-TEMPLATE.md shape.Do not treat section-doc-declared exceptions as closed questions. When the doc says "served from two controllers — required because…", that's a description of current drift, not a sanction. Evaluate the exception against the convention; if the convention can hold, fix; if a true exception is needed, surface it for owner sign-off.
Surface as: "I hit X. Decision needed: [A] / [B] / [C]."
Phase 0 Inventory (both axes) → docs/plans/<YYYY-MM-DD>-section-align-<target>.md; stop conditions; ask user
Phase 1 Surface alignment (axis 1) — create/move controllers, views, ViewModels, extensions, routes
Phase 2 Fix arch violations + prior review items (both axes; boundary-fix protocol)
Phase 3 /simplify pass — Opus; internal cohesion + interface trimming
Phase 4 Doc polish — Opus; final review loop until merge-ready
Sequential phases. Within a phase, fan out parallel subagents as wide as the files are disjoint (the old 3-agent cap was lifted 2026-09-10; see [[model-tiering]]). Escalate model: Sonnet for Phase 1 mechanical work → Opus for Phases 2–4.
Phase 0 inventory reads a lot of the section's code and grows the context fast. By the time Phase 0 lands as a committed plan file, the main-session context is typically 100k–250k tokens.
Split rule:
docs/plans/<YYYY-MM-DD>-section-align-<target>.md./cls or new conversation) before Phase 1 so impl begins on cached context.superpowers:subagent-driven-development pattern. The orchestrator:
/cls again, resuming via the plan file.This keeps the orchestrator focused on plan-execution decisions; the heavy reading/editing happens in disposable subagent contexts.
| Mode | Trigger | Branch | Phase 2 prior comments |
|---|---|---|---|
| PR | PR <n> | PR head branch in worktree | yes |
| Existing-section | section <Name>, main clean | new align/<section> | none |
| Mid-build | section <Name>, on feature branch | continue | none |
Locally, always use a worktree under .claude/worktrees/section-align-<target>/; in a cloud run work in the repo root on the branch instead (memory/process/always-use-worktree.md).
Output: docs/plans/<YYYY-MM-DD>-section-align-<target>.md. Both axes always audited.
A1.1 Section name consistency. Variants across folders, namespaces, controllers, views, ViewModels, roles, routes, docs. Propose canonical name. Flag collisions.
A1.2 Controller existence. Does $CONTROLLERS/<Section>Controller.cs (or <Section>AdminController.cs / <Section>ApiController.cs if split) exist? Use $CONTROLLERS — a section keeps its controllers in-project (Humans.Events/Controllers/EventsController.cs), so checking src/Humans.Web/Controllers/ would report "no controller" for every section. If routes serving this section's data live on other sections' controllers (e.g. BoardController hosting /Board/AuditLog), that is drift. List each foreign route and propose the move.
A1.3 URL surface. Every route serving this section's data. Catch:
/<Section>/* or /<Section>/Admin/* or /api/<section>/*/Admin/<Section>/* paths (architecture_no_admin_url_section)feedback_no_url_aliases)A1.4 Views folder. Does $VIEWS/ exist? A section owns its views in-project (Humans.Events/Views/), not under src/Humans.Web/Views/<Section>/. Section-owned page views left behind in src/Humans.Web/Views/Shared/ (e.g. Shared/<Section>.cshtml) are drift unless they are genuinely cross-section partials reused by widgets. ViewComponent partials at src/Humans.Web/Views/Shared/Components/<Section>/ are fine (and expected) — those stay platform-side.
A1.5 ViewModel placement. Section ViewModels in Models/<Section>ViewModels.cs or Models/<Section>/. Types parked in Models/AdminViewModels.cs or other grab-bag files = drift.
A1.6 Controller-base leak. Search HumansControllerBase (or equivalent) for section-specific helper methods or ViewModels. Section names appearing in protected method names, parameter types, or return types = drift. Move into <Section>Controller.
A1.7 DI wiring placement. A section registers its own DI from $SEC/Section.cs (ISection.Register) — not src/Humans.Web/Extensions/. Any <Section>*.cs under Extensions/ (root or Extensions/Sections/) doing this section's DI wiring = drift; that folder is reserved for Shell-level chrome (Admin, Auth).
A1.8 Role surface. Domain-scoped roles need *Admin suffix (feedback_admin_superset). Exceptions only when semantics don't fit (e.g., ConsentCoordinator).
A1.9 INBOUND cross-section DB access (reads + writes). Grep for any non-test file outside this section's repo touching this section's DbSets:
git grep -nE '_(db|context|ctx)\.(<TableA>|<TableB>)\.(Where|FirstOrDefault|Find|FindAsync|ToListAsync|Single|First|Count|Any|Add|AddRange|Update|Remove|Attach)\b' src/
git grep -nE '\b(_db|_context|ctx)\.<TableA>\b' src/ # bare DbSet refs catch e.g. AddRange that the verb regex misses
For each hit: apply the boundary-fix protocol (producer side). Do we expose the public API the caller actually needs? If yes, the caller's section becomes a /section-align follow-up target (they own the migration). If no, add the API in THIS PR's Phase 2 — then the caller becomes a follow-up target.
A1.10 INBOUND EF navigations. Search other sections' entities for navigation properties pointing AT this section's entities:
git grep -nE 'public\s+<SectionEntity>\??\s+\w+\s*\{' $OTHER_ENTITIES
A nav from another section's entity to ours = a cross-domain coupling that breaks §6 if anyone Includes it.
A1.11 OUTBOUND cross-section access (this section reaches OUT). Grep this section's repository for reads of other sections' DbSets or .Include across into other-section navs:
git grep -nE '_(db|context|ctx)\.(<OtherSectionTableA>|<OtherSectionTableB>)' $(exist $REPOS)
git grep -nE '\.Include\([^)]+\)' $(exist $REPOS)
For each hit: apply the boundary-fix protocol (consumer side). Does the other section expose a public service method that gives us what we need? If yes, switch (Phase 2). If no, document the API gap on their side, leave the current code in place, and flag their section as a follow-up /section-align target. Do not add a method to their repo or service in this PR.
A1.12 Controller → DbContext. Any controller injecting a DbContext — or an IDbContextFactory<> of one — violates §2a, whichever context it is. Don't grep for HumansDbContext; it was deleted in nobodies-collective/Humans#858, so that spelling matches nothing while the violation still happens with a section context. Hard violation; always Phase 2 fix.
A1.13 Migrations. Hand-edited body or snapshot violates architecture_no_hand_edited_migrations. Intentional migrationBuilder.Sql(...) for triggers/seeds is allowed when named in the section doc.
A1.14 Section invariant doc shape. Present? name matches? follows SECTION-TEMPLATE.md? structural gaps?
A1.15 Open prior-review items (PR mode).
gh pr view <n> --repo peterdrier/Humans --json comments,reviews
gh api repos/peterdrier/Humans/pulls/<n>/comments
Also check nobodies-collective/Humans (feedback_pr_review_both_repos). List each unresolved comment with inline-comment ID.
A2.1 EF leakage from service layer. Grep this section's services for EF types and query operators:
git grep -nE '(Microsoft\.EntityFrameworkCore|IQueryable|DbContext|DbSet|\.Include\(|\.AsNoTracking|\.ToListAsync|\.FirstOrDefaultAsync|\.SaveChangesAsync)' $(exist $SERVICES)
Hits in using statements, ctor params, or method bodies = violation. Doc comments (<see cref>) are OK. Add <Section>Service_HasNoDbContextConstructorParameter arch test if missing.
A2.2 Caching placement. Grep section's services + repos + controllers + view components:
git grep -nE '(IMemoryCache|MemoryCache|IDistributedCache|_cache\b)' $(exist $SERVICES $REPOS $CONTROLLERS src/Humans.Web/ViewComponents/<Section>*.cs)
Caching belongs in the service layer only per §15 — either via a Caching<Section>Service decorator (Singleton, dict-backed) or service-internal IMemoryCache for short-TTL request acceleration. Never in repositories. Never in controllers. ViewComponents per feedback_viewcomponent_no_cache.
A2.3 DI lifetimes. Read the section's DI registration — src/Sections/Humans.<Section>/Section.cs (ISection.Register). Verify:
IDbContextFactory<TContext> where TContext is the section's own context — never HumansDbContext, which doesn't existIUserDataContributor → existing service): bound to the registered concreteA2.4 Repository pattern. The section's repository:
internal sealedIDbContextFactory<TContext> for the section's own context — resolve TContext from the section's Data/ folder or the SECTION_DB_CONTEXTS map in .github/workflows/build.yml; do not assume <Section>DbContext, since e.g. Humans.Consent is LegalDbContext and Humans.Events is EventGuideDbContext. A repository taking its own section context is correct and must not be flagged.src/Sections/Humans.<Section>/Data/A handful of repositories inject the context directly rather than a factory (e.g. AgentRepository(AgentDbContext db, IClock clock)). Note it, but it is not a §2a violation on its own.
A2.5 Shared visual components — inventory by type, ViewComponent preferred. Cross-page UI for this section's data can live as one of three things; this skill is opinionated about which:
| Type | Preference | Use when |
|---|---|---|
| ViewComponent | ✓ preferred | Section data rendered on other sections' pages, needs DI to call section services, has parameter-driven invocation. |
| TagHelper | call out | Wrap small reusable markup primitives (badges, buttons, formatters). Often candidates for promotion to ViewComponent if they take section data. |
Partial view (_Partial.cshtml) | call out | Acceptable for fragmenting one section's own view; not for cross-section reuse. Often candidates for promotion to ViewComponent if invoked across sections. |
Inventory all three categories for this section:
git grep -ln 'ViewComponent' src/Humans.Web/ViewComponents/<Section>*.cs src/Humans.Web/ViewComponents/*<Section>*.cs
git grep -ln 'ITagHelper\|TagHelper\b' src/Humans.Web/TagHelpers/ 2>/dev/null | grep -i <section>
find src/Humans.Web/Views -name '_*.cshtml' | xargs grep -l '<section-related-pattern>'
For each shared component, classify and decide: keep, convert TagHelper→ViewComponent, convert Partial→ViewComponent, or leave (intra-section partials). Inverse check: section pages with inline rendering of "should-be-a-VC" content (10+ lines of Razor stitching this section's data into another section's view).
Grep for ViewComponent invocations to verify reuse. Consumers can be anywhere, so search the platform's views and every section project — section pages live in their own Views/, not under src/Humans.Web/Views/:
git grep -nE '<vc:<section-kebab>|Component\.InvokeAsync\("<Section>"' \
src/Humans.Web/Views/ src/Sections/*/Views/
A2.5a Redundancy check against system-level shared components. The biggest source of UI maintenance debt in this codebase is sections rolling their own renderer for things that already have a canonical shared component — especially anything that shows a user (avatar + name, profile cards, baseball-card popovers, role badges, email badges, search inputs). When a section invents its own version, every cross-cutting change (avatar shape, name format, link target, missing-user fallback, accessibility) has to be hunted down and applied N times.
First, build/refresh the catalog of system-level shared components the section might collide with. These live in src/Humans.Web/ViewComponents/*.cs and src/Humans.Web/Views/Shared/_*.cshtml and are owned by the platform, not any one section:
ls src/Humans.Web/ViewComponents/
ls src/Humans.Web/Views/Shared/Components/
find src/Humans.Web/Views/Shared -maxdepth 2 -name '_*.cshtml'
The user-display family — always check first, since it is where redundancy bites hardest:
| Concern | Canonical shared component |
|---|---|
| User name + avatar inline (anywhere a user appears in a list, table cell, audit row) | <vc:human> / HumanViewComponent (_HumanPopover partial for the hover card) |
| Full profile / baseball card | <vc:profile-card> / ProfileCardViewComponent / _ProfileCard |
| Role pill / authorization indicator | _RoleBadge.cshtml / _AuthorizationPill.cshtml |
| Nobodies email badge | <vc:nobodies-email-badge> |
| User search box + results | <vc:human-search> / <vc:user-search-result> |
| User dropdown / signed-in menu | _LoginPartial.cshtml / _AdminTopbarUserMenu.cshtml |
(Refresh this table from the actual ViewComponents/ and Views/Shared/ listing each run — components get added.)
Then scan the whole section's view + component surface (not just shared-folder candidates from A2.5 — inline rendering inside the section's own page views is the more common form of this drift):
# Inline user displays — avatar + name combos, name links to /Humans/<id>, etc.
git grep -nE '(avatar|profile-pic|@user\.DisplayName|@Model\.DisplayName.*<img|asp-action="Detail".*Humans|/Humans/Detail/)' $(exist $VIEWS src/Humans.Web/ViewComponents/<Section>*.cs)
# Hand-rolled role/auth pills
git grep -nE '(badge|pill).*role|role.*badge|class="[^"]*role[^"]*"' $(exist $VIEWS)
# Hand-rolled user lookups / searches
git grep -nE 'autocomplete.*user|user.*autocomplete|search.*human|human.*search' $(exist $VIEWS)
For each hit, decide:
| Finding | Disposition |
|---|---|
| Section renders user (avatar/name/link) inline with its own markup | Phase 3 fix: replace with <vc:human user-id="..." /> or @await Html.PartialAsync("_HumanPopover", ...). |
| Section has its own role/auth badge markup | Phase 3 fix: replace with _RoleBadge / _AuthorizationPill. |
| Section has its own user-search input + results panel | Phase 3 fix: replace with <vc:human-search> / <vc:user-search-result>. |
| Shared component is almost right but missing one parameter the section needs (e.g. compact mode, hide-link) | Phase 2 fix on the shared component (add the parameter), then Phase 3 callsite swap. Note: this means the shared component owner — typically the platform/admin shell — is the producer; flag as a follow-up if the parameter add is non-trivial. |
| Section's renderer is genuinely different in domain meaning (not a near-duplicate, just happens to also show a user) | Keep; record the distinction in the plan so future runs don't re-flag it. |
Inverse check (don't only look for duplicates of user-display). Anything that should be reusable across sections and currently isn't — section-local components rendering generic concerns (date formatters, money formatters, status pills, action menus, attachment lists) — is a Phase 3 candidate to promote into Views/Shared/ or ViewComponents/. The signal: another section's view contains near-identical markup. Surface as a candidate; the actual promotion is judgment-call (don't preemptively over-share).
Output for each section pass: a Redundancy candidates subsection in the plan listing each duplicate find with disposition (Phase 3 swap / Phase 2 shared-component extension / keep + justify).
A2.6 Interface budget + segregation + consolidation. Count methods on each public interface. For each interface ≥10 methods, ensure InterfaceMethodBudgetTests.Budgets entry exists with current count. Beyond the budget number, flag:
GetAll() + caller-side filter. The canonical anti-pattern: GetActiveUsers(), GetSuspendedUsers(), GetDeletedUsers() instead of GetUsers() with callers writing .Where(u => u.IsActive). At our small scale most data fits in RAM; in-memory filtering is cheaper and clearer than predicate-pushed splits. Consolidate to GetAll/GetByX and let callers project. Architecture: the service holds the data; callers filter.## Architecture.IAuditLogService.GetUserDisplayNamesAsync called only by AuditViewerService. Move private, or move to the consumer.A2.7 Architecture test coverage — prefer generic over per-section. Tests that apply to every section's same-shape class should be one reflection-based test over the matching interface, not a per-section copy. Prefer:
| Generic test (preferred) | Replaces per-section copies |
|---|---|
IRepository_Implementations_AreAllSealed (reflect over all IRepository impls) | FooRepository_IsSealed, BarRepository_IsSealed, … |
Application_Services_TakeNoDbContext (reflect over IApplicationService impls) | per-section FooService_HasNoDbContextConstructorParameter |
Application_Services_TakeNoIMemoryCache_UnlessRegistered | per-section copies |
Repository_Implementations_LiveInSectionDataFolder | per-section namespace tests |
Section-specific tests are reserved for section-specific invariants that don't generalize:
Only<Section>Repository_References_<DbSet> — Roslyn or reflection scan).Phase 0 inventory:
<Section>ArchitectureTests.cs.tests/Humans.Web.Tests/Architecture/Rules/ (e.g. RepositorySealing.cs), alongside existing generic rules like ApplicationServicesTakeNoDbContextRule.cs.Missing tests (in either category) are Phase 2 adds. Per-section duplicates of generic patterns are Phase 3 prunes.
A3.1 Test folder placement. Canonical layout is $TESTS as resolved above — the section's own tests/Humans.<Section>.Tests/ project, where the section's tests sit at the project root by category rather than under a <Section>/ folder:
$TESTS/ # tests/Humans.<Section>.Tests/
<Section>ArchitectureTests.cs
Services/ Data/ Controllers/ Domain/
A section's tests must live in its own test project. No other test project references the section assembly, and a section grants InternalsVisibleTo only to Humans.<Section>.Tests (plus Humans.Integration.Tests) — so a test placed anywhere else cannot see the section's internal services and will not compile.
Tests that belong elsewhere by category (not section drift):
$TESTS/Architecture/<Section>ArchitectureTests.cs — arch tests for this section (covered in Axis 2.7); typically sits at the project root, e.g. tests/Humans.Events.Tests/EventsArchitectureTests.cs$TESTS/Authorization/<Section>AuthorizationHandlerTests.cs — auth handler teststests/Humans.Integration.Tests/... — cross-section by definitiontests/Humans.Web.Tests/Controllers/<Section>*Tests.cs — controller tests live with the Web projectA3.1a One test file per production class (1-to-1 rule). For every production class in this section, there should be exactly one test file named <ClassName>Tests.cs:
| Production | Test file |
|---|---|
<Section>Service.cs | <Section>ServiceTests.cs |
<Section>Repository.cs | <Section>RepositoryTests.cs |
<HelperClass>.cs | <HelperClass>Tests.cs |
Two production classes sharing one test file (e.g. AuditEvent + AuditEventTextualizer → AuditEventRenderTests.cs) is drift — reviewers expect a 1-to-1 map. Exception: a single test file may exercise a tightly-coupled production pair if the pair is a documented helper-aggregator (rare; surface to user).
A production class with no test file at all (e.g. <Section>Repository.cs covered only by <Section>ArchitectureTests) is drift — the architecture test pins shape, not behavior. Add a unit test file or document why none is needed (e.g. "pure pass-through repository — coverage flows through <Section>ServiceTests").
A3.2 Coverage map against section doc. Read $DOC — the resolved invariant doc at $SEC/Docs/<Section>.md (Events' is at src/Sections/Humans.Events/Docs/Events.md, and there is no docs/sections/Events.md). Build a coverage map:
Missing coverage on invariants/negatives/triggers = Phase 2 add. Missing happy-path on a method = Phase 2 add if the method is non-trivial; skip if it's a one-line pass-through with the underlying call already tested.
A3.3 Redundancy / over-testing flags. Scan for:
Each flag is a Phase 3 prune candidate. The goal is to reduce test maintenance overhead where test value is already captured by a more direct test.
A3.4 Test-to-section ratio (sanity check). Eyeball: section LOC vs test file count and test LOC.
find $(exist $ALL $ENTITIES/<SectionEntity>*.cs) -name '*.cs' | xargs wc -l | tail -1
find tests -path '*<Section>*' -name '*.cs' | xargs wc -l | tail -1
Order-of-magnitude check only. Outliers worth investigating:
Not a budget. Judgment call surfaced to the user.
A3.5 Brittleness signals. Tests that:
IClock / FakeClock[Slow] markerEach is a Phase 3 candidate (prune or rewrite).
A3.6 Mutation testing signal (Stryker.NET, optional but preferred). The repo has Stryker.NET wired (docs/testing/mutation-testing.md). Use the Profile-section probe pattern to get section-targeted under/over-test signal without paying the cost of a full-suite run:
Look for an existing report at local/stryker-runs/<section>/reports/mutation-report.json. If recent (< 30 days) and matches current HEAD, use it. If stale or absent and the section is significant, surface to user: "Run Stryker against this section before Phase 3? (~N minutes)" with the section-narrowed config command:
Push-Location $TESTS
dotnet tool run dotnet-stryker --config-file stryker-<section>-config.json `
--output ..\..\local\stryker-runs\<section>
Pop-Location
If no stryker-<section>-config.json exists, propose creating one mirroring stryker-profiles-config.json with a narrowed mutate glob targeting this section's src/ paths.
Run scripts/analyze-test-utility.ps1 against the report to produce the High-Confidence Test-Debt Candidates queue:
powershell -ExecutionPolicy Bypass -File scripts/analyze-test-utility.ps1 `
-Top 50 `
-StrykerReport local/stryker-runs/<section>/reports/mutation-report.json
Output lands under local/test-utility/. The Markdown report has two queues — High-Confidence Test-Debt Candidates is the better starting point for Phase 3 pruning.
Interpretation:
Stryker is a signal, not an authority. Treat results as a triage queue. The skill never auto-deletes a test based on Stryker output alone.
# Section Align — <target>
**Run started:** <date> | **Mode:** <mode> | **Worktree:** <path>
**Branch:** `<branch>` (off `<base>` @ `<sha>`)
**Canonical section name proposal:** <name>
## Axis 1 — boundary integrity
1. Name consistency: <findings>
2. Controller existence: <findings>
3. URL surface: <routes table with violation flags>
4. Views folder: <findings>
5. ViewModel placement: <findings>
6. Controller-base leak: <findings>
7. Extensions placement: <findings>
8. Role surface: <violations>
9. Inbound cross-section DB access (reads + writes): <list with protocol disposition>
10. Inbound EF navs: <list>
11. Outbound cross-section access: <list with protocol disposition>
12. Controller→DbContext: <list>
13. Migrations: <findings>
14. Invariant doc: <gaps>
15. Prior review items: <list with comment IDs>
## Axis 2 — internal cohesion
1. EF leakage from service: <findings>
2. Caching placement: <findings>
3. DI lifetimes: <findings>
4. Repository pattern: <findings>
5. ViewComponent presence + reuse: <findings>
5a. Redundancy vs system-level shared components: <duplicates with disposition; user-display family checked first>
6. Interface budget + segregation: <conflicts and trims>
7. Architecture test coverage: <missing tests>
## Axis 3 — test focus
1. Test folder placement: <canonical home + scatter list>
1a. 1-to-1 map (production class → test file): <table; missing tests; bundled-class drift>
2. Coverage map (invariants / negatives / triggers / methods): <coverage holes>
3. Redundancy flags: <list of prune candidates>
4. Test-to-section ratio: <LOC ratio + verdict>
5. Brittleness signals: <list>
6. Mutation signal (Stryker.NET): <report path + summary, or "no recent report; propose run">
## Test-attribute gate (per `docs/testing/mutation-testing.md`)
- Baseline as of last gate update: <count from doc>
- This run's net delta: +<added> / -<deleted> = <net>
- Justification if net > 0: <explain or refuse>
## Stop conditions tripped
<none or list with proposed decisions>
## Follow-up /section-align targets
<list of other sections this run surfaced as needing their own pass, with reason>
## Phase plan
- Phase 1 (axis 1 + axis 3 mechanical — create/move surfaces and test folders): <list>
- Phase 2 (axes 1, 2, 3; boundary-fix protocol; add missing arch tests + invariant tests): <list, blocking items flagged>
- Phase 3 (simplify / internal cohesion / prune redundant tests): ~<N> fixes
- Phase 4 (docs): <list>
Surface to user. If --inventory-only, stop. Otherwise: "Phase 0 complete. Proceed to Phase 1?"
Sonnet subagents. Build must stay green after each step.
Tooling discipline (applies to all Phase 1+):
/reforge before any rename, move, or interface change. The reforge skill returns the exact callers, references, and impact list so the subsequent Edit/Write is one-shot rather than iterative. Use it for: rename impact, find-references for a method, trace-call-chain when moving a route handler. Always call /reforge before the Edit, never instead of testing after.dotnet build + dotnet test watchers in background via Bash run_in_background: true; tail with BashOutput between commits. At phase end, stop the background processes with KillShell. Pattern:
# Phase start (background)
dotnet watch --project Humans.slnx test -v quiet # tail across commits
Always inside the worktree. Don't leak background processes across phases or sessions./reforge finds C# symbol references but misses string-typed references (Razor tag helpers, allow-lists, route strings, doc links). Renames that ship without sweeping these break user-facing navigation silently — the build is green, the code is broken. This has bitten us twice (e.g. PR 505: ApplicationController → GovernanceApplicationsController left 6 asp-controller="Application" view links and a stale MembershipRequiredFilter.ExemptControllers entry).
After every rename or move of a controller / service / view / route / role / config key, run the full sweep below before committing. Both OLD and NEW name; OLD must return zero matches.
OLD=Application # e.g. controller name minus 'Controller' suffix
NEW=GovernanceApplications
# 1. Razor tag-helper attributes (controllers, views)
git grep -nE "asp-controller=\"${OLD}\"" src/
git grep -nE "asp-(action|route|page)=\"[^\"]*${OLD}[^\"]*\"" src/
# 2. Url.Action / Url.RouteUrl / Url.Page / redirects (controller name as string arg)
git grep -nE "Url\.(Action|RouteUrl|Page)\([^)]*\"${OLD}\"" src/
git grep -nE "RedirectToAction\([^)]*\"${OLD}\"" src/
git grep -nE "RedirectToRoute\([^)]*\"${OLD}\"" src/
# 3. Allow-lists / filter sets keyed by controller name string
git grep -nE "\"${OLD}\"" src/Humans.Web/Authorization/ src/Humans.Web/Middleware/ src/Humans.Web/Filters/
# 4. ViewComponent invocations (tag-helper + Html.InvokeAsync forms)
git grep -nE "<vc:${OLD,,}|Component\.InvokeAsync\(\"${OLD}\"" src/
# 5. Test fixtures referencing route paths or controller names
git grep -nE "\"/${OLD}/" tests/
git grep -nE "\"${OLD}\"" tests/Humans.Web.Tests/ tests/Humans.Integration.Tests/
# 6. Config keys, localization resources, navigation menus, sitemaps
# Whole-word match — bare ${OLD} would false-positive when NEW contains OLD as a substring
# (e.g. OLD=Application, NEW=GovernanceApplications).
git grep -nE "\b${OLD}\b" src/Humans.Web/appsettings*.json src/Humans.Web/Resources/ src/Humans.Web/ViewComponents/*Nav*.cs
# 7. Docs that name the symbol
git grep -nE "\b${OLD}(Controller|Service|Repository)?\b" docs/ memory/
For service / interface renames, swap to grep I${OLD}Service / ${OLD}Service and check DI registrations, decorators, and ctor params.
For entity / DbSet renames, additionally check [Table(...)] attributes, EF configuration files, and OnModelCreating — but per architecture_no_drops_until_prod_verified, prefer keeping the DB table name and renaming only the C# symbol.
Verification gate: before staging the rename commit, the OLD-name grep across all 7 buckets must return zero matches (or each remaining match has an explicit "intentional — historical reference" rationale captured in the commit message). String references don't fail CI; they fail in production navigation. "I'll catch the rest when CI fails" is not an acceptable plan.
If the sweep finds non-trivial inbound work (>10 fixes, or fixes that touch other sections' views), surface as a stop-condition decision: bundle into this PR vs. flag the consumer section as a follow-up /section-align target. Bundling is usually correct for view-link fixes (mechanical, reviewers expect renames to be complete); a follow-up is correct only when the consumer needs a deeper structural change.
Create what's missing (not only rename):
git mv $(dirname $DOC)/<Old>.md $DOC if renamed. That's $SEC/Docs/, not docs/sections/.<Section>Controller.cs doesn't exist and routes live on foreign controllers, create it. Migrate route handlers off BoardController / GoogleController / etc. Run the inbound-link sweep (above) before committing — controller renames must update asp-controller=, Url.Action, RedirectToAction, allow-lists in auth filters/middleware, and test fixtures. /reforge does not catch these.Views/<Section>/ doesn't exist and page views live in Views/Shared/<Section>.cshtml, create it and move the page views. Keep genuine cross-section partials in Shared.Models/AdminViewModels.cs or other grab-bag files into Models/<Section>ViewModels.cs.HumansControllerBase into <Section>Controller (or section-local helpers).git mv Services/, Data/, Domain/, Controllers/, Views/ contents if rename triggered.I<Section>Service, <Section>Controller, etc. via /reforge + bulk Edit./<Section>/*; admin under /<Section>/Admin/*.*Admin suffix unless Phase 0 justified exception.appsettings*.json.$SEC/Section.cs (ISection.Register) in lockstep with any renamed types.git mv them into the canonical home picked in A3.1. Rename test files to <ClassUnderTest>Tests.cs 1-to-1 if they aren't already (one production class → one test file).architecture_no_drops_until_prod_verified); use [Table("legacy_name")] if entity renamed.After each rename/move group:
dotnet build Humans.slnx -v quiet && dotnet test Humans.slnx -v quiet
Commit every 3–5 logical units. Update PR body if routes changed. Thread-reply prior comments addressed (feedback_codex_thread_replies):
gh api repos/peterdrier/Humans/pulls/<n>/comments/<id>/replies -X POST -f body="Addressed in <sha> — ..."
Push at end of phase. Bot-review sub-loop until clean (feedback_done_means_codex_clean).
Sonnet by default; Opus for nuanced items.
Boundary-fix protocol applies to every cross-section finding. State the protocol disposition in the commit message ("Producer-side fix: added IFooService.GetByIdsAsync for the X consumer; flagging X for follow-up /section-align").
Axis 1 items:
InterfaceMethodBudgetTests.Budgets entry). The caller's section becomes a follow-up /section-align target; do not touch their call site in this PR unless trivial (one or two lines).dotnet ef migrations remove --context <C> --project <P> + redo the add with the full flag set for that context's shape: --context <C> --project <P> --output-dir <Data/Migrations | Migrations/<Area>> (per memory/process/ef-multi-context-commands.md). Omitting --output-dir silently regenerates into the project's default Migrations/ folder, away from the snapshot — and the check-ef-output-dir.sh guard only inspects commands that already carry the flag. Verify snapshot is byte-for-byte EF output.gh api .../comments/<id>/replies; never top-level.Axis 2 items:
<Section>SectionExtensions.InterfaceMethodBudgetTests.Budgets entry with exact count; run the test.feedback_db_enforcement_minimal).Axis 3 items:
<Section>Service exists but <Section>ServiceTests.cs doesn't, create it (even if just stub + happy path). 1-to-1 helps reviewers find the test surface for a given class fast.AuditEventRenderTests.cs (covers 2 classes) into per-class files unless the pair is a documented helper-aggregator.Build + tests green. Push at end of phase. Bot-review sub-loop until clean.
Opus. Volume scales to section LOC (feedback_simplify_scope_to_section_size): ~6–10 fixes for 7k LOC, ~2–3 for 1k LOC.
Common targets:
feedback_no_linq_at_db_layer)..ToList().Cached* names (feedback_caching_transparent).feedback_no_extensions_for_owned_classes).feedback_viewcomponent_no_cache).<vc:human> / <vc:profile-card> / _RoleBadge / <vc:human-search> etc. State the swap in the commit message ("Replace inline user-row markup in Views/<Section>/Index.cshtml with <vc:human> — collapses N call sites onto the shared component"). If a shared component needs a small parameter add to fit the section's case, do the parameter add as a Phase 2 producer-side fix and the call-site swap here.docs/testing/mutation-testing.md): the net delta should trend down across Phase 3 commits.Push at end of phase. Bot-review sub-loop until clean.
Opus.
SECTION-TEMPLATE.md sections. Correct any false claims surfaced in Phase 0 (e.g., "only repo writes the table" claims that turned out false until Phase 2 fixed them — restore once truly true).docs/architecture/dependency-graph.md — update the section's inbound/outbound edges to reflect actual state after Phase 2. Add new dependencies introduced; remove ones eliminated by the cross-section fixes. The dependency graph is the at-a-glance map reviewers consult; if it's stale, alignment work is invisible.Docs/features/*.md, or docs/features/global/*.md when cross-section) — match implementation; rename if section name changed.maintenance-log.md — if a recurring task ran. Always update this section's Section Refactor History row: Last Lane (date + PR) and Post-Lane Score (the section's built reforge surface-score after the final commit)./freshness-sweep — if catalog covers touched docs.Push. Final bot-review loop until merge-ready. Report: PR/branch URL, commits per phase, follow-up /section-align targets, one-line status. Do not offer /schedule follow-ups (feedback_no_schedule_offers).
Re-run /section-align at the end (still in the same worktree) as the closing audit. If the second run finds anything beyond the documented follow-up targets, that's leftover Phase 1–3 work — go back and finish before merging. Ideal end-state: the closing run returns "clean except for <list of pending suppliers>."
"Done" = bot-review-clean, not pushed+green (feedback_done_means_codex_clean).
https://github.com/peterdrier/Humans/blob/<branch>/docs/plans/<file>.md) so they can review it inline.main; never --no-verify.Plan files are durable, committed artifacts under docs/plans/ — one per run, date-prefixed. On startup, glob docs/plans/*-section-align-<target>.md. If any exists for the current target, take the newest, read it, find last completed phase, ask: "Found plan from <date>. Last complete: <phase>. Resume or start fresh?" Update the plan file at end of each phase. Commit + push the plan file with Phase 0's commit so the user can review it via the PR/branch URL.
architecture_no_admin_url_section · feedback_no_url_aliases · feedback_admin_supersetarchitecture_no_hand_edited_migrations · architecture_no_drops_until_prod_verifiedarchitecture_interface_budget_ratchet_down_only · feedback_db_enforcement_minimalfeedback_no_linq_at_db_layer · feedback_caching_transparent · feedback_no_extensions_for_owned_classes · feedback_viewcomponent_no_cachefeedback_codex_thread_replies · feedback_pr_review_both_repos · feedback_done_means_codex_cleanfeedback_simplify_scope_to_section_size · feedback_dotnet_verbosity · feedback_always_use_worktreefeedback_no_direct_to_main · feedback_no_schedule_offers.Include) · §8 (table ownership) · §11 (auth) · §12 (append-only) · §15 (caching pattern) — docs/architecture/design-rules.mddocs/sections/SECTION-TEMPLATE.mddocs/testing/mutation-testing.md (Stryker.NET setup, test-attribute gate)scripts/analyze-test-utility.ps1 (Stryker → high-confidence test-debt queue)まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Check an externally created PR for coding rule violations against memory/INDEX.md atoms and docs/architecture/code-review-rules.md.
日本語の概要は準備中です。原文の説明を表示しています。
Two-phase memory hygiene. Phase 1: scan external Claude Code memory (~/.claude/projects/<slug>/memory/) for durable rules to migrate into the repo and entries already duplicated by repo atoms. Phase 2: audit in-repo memory/, CLAUDE.md, docs/architecture/, and docs/sections/ for dead links, duplication, drift, and bloat.
日本語の概要は準備中です。原文の説明を表示しています。
Review, repair and steward the nightly Codex debt-sweep PR (branch codex/daily-debt/<date>). Judges every commit GOOD / REPAIR / REVERT, triages the bot review findings through /fix, lands one round-1 commit, files follow-up issues, then stewards the PR itself with a 3-round ceiling. Run by the 08:00 UTC cloud routine; also '/debt-review', '/debt-review 1789'.
日本語の概要は準備中です。原文の説明を表示しています。
Autonomous themed tech-debt cleanup. Reads docs/architecture/debt-ledger.yml, rotates to the least-recently-served debt theme, works it for a time budget (default 2h) or until drained, opens one PR, and asks judgment questions inline at the end. Use for daily debt burndown without Peter pointing at a target: grandfathered analyzer rules, architecture-test baselines, obsolete-field reads, cross-section stitching.
日本語の概要は準備中です。原文の説明を表示しています。
Scrap and regenerate the in-flight EF migrations on the current branch as one consolidated migration. Use when migrations have accumulated mid-development cruft (added/removed columns, stacked add-column-to-existing-table fixes, hand-edited SQL that needs to come out, schema changes that should have been one migration but ended up as five), or when the branch has merged main and its migrations are now stuck mid-chain so `dotnet ef migrations remove` is unsafe. Triggers on phrases like 'regen the migrations', 'redo these migrations', 'scrap and regen migrations', 'consolidate the in-flight migrations', 'redo the migration stack', or any time an agent hits the mid-chain situation in `memory/architecture/migration-regen-after-rebase.md`.
日本語の概要は準備中です。原文の説明を表示しています。
Use when a sprint tracking issue (label `sprint`) on peterdrier/Humans needs working — routine-fired cloud runs, or any unattended session told to execute the sprint queue.
日本語の概要は準備中です。原文の説明を表示しています。