[US-402] docs: PM-tool adapters document membership + assignee — an item can no longer be written and stay invisible - #404
Conversation
Verdict
PR: [#404] · Author: rucka · Reviewer: independent reviewer (agent) · Date: 2026-07-31 · Story: [US-402] · Type: docs Classification matrix — per dimension
Tier = max(assessed) = yellow, same as refinement — no drift note. Cost = green (docs + 1 test file; no infra, provider or paid-service signal). Review value is a floor (D17): confirmed, not lowered. AssessmentsSecurity — Input validationVerdict: green — no input surface: the change is markdown plus one test that reads two fixed, Details
Security — Output handlingVerdict: green — no rendering/serialization surface touched. Details
Security — AuthenticationVerdict: green — no auth path changed. Details
Security — AuthorizationVerdict: green — no access-control code or policy changed. Details
Security — Introduced vulnerabilitiesVerdict: green — 0 introduced, 0 pre-existing. Details
No secret, credential, network call or executable production path in the diff. CostVerdict: Details
Architecture (Coupling)Verdict: yellow — the guard binds CI to ~20 exact prose strings across every Details
DetailsFindings by severityCritical (must fix before merge)
Major (should fix before merge)
Minor (consider)
Questions
Positive feedback
Functionality & requirements (AC coverage)
Testing & quality gates
Adoption compliance
Tech debt
Documentation
Performance & deployment
Fix list (shortest path to APPROVED): M1 (membership in the create recipe + reconcile the |
…p, not only the assignee Addresses PR #404's independent review: 1 Major (M1) + 3 Minor. M2 and M3 are handled outside the diff (PR description and #403's scope). M1 — the create recipe wrote the assignee and NOT the membership, so `/write-issue` called without `$status` (the follow-up path, which is exactly how #384 and #372 were filed) produced the defect this story exists to remove: open, assigned, green, off the board. AC1 only closed the status-write path. - the section is now "Create an Issue with its Assignee and its Membership" and opens with "Creating does not imply membership", naming the reachable path - `gh issue create` carries `--project "[project title]"` in the one-shot form - the MCP block states it has NO project field, so Step 2b is REQUIRED after it - the unqualified "gh issue create produces only the issue" is now qualified with `--project`, resolving a contradiction with this file's own :206 snippet Minor — that snippet passed `[PROJECT_ID]` where the flag takes a title or number; corrected in both `issue create` and `pr create`, with a pointer to the visibility section. `### Issue Management` now points at the create recipe, which lives further down next to the board mechanics an agent would not think to look under. The filesystem adapter says WHERE the `Assignee` line goes, since the item templates carry no such field and copying one leaves no slot. Minor — `membership is (explicit|implicit)` accepted either word for every adapter, so a flip stayed green. The loose form is kept for adapters this suite has never seen; the three known ones are now pinned by name via their own `board membership is <word>` declaration. Still count-free. 59 → 71 assertions. All five new claims injection-tested. Two of my own assertions were wrong and the tests caught them: - a bare `not.toContain('membership is implicit')` failed against github's legitimate cross-reference to the other adapters - the `[PROJECT_ID]` regression guard was VACUOUS: `normalize()` strips `_`, so a check written with the underscore could never match. Reinstating the regression left the suite green. Now asserted on raw content, and red on injection in both corpora. Mirror ported by hand, not regenerated: `github-implementation.md` carries pre-existing skill-reference transforms (`/review` -> `/pair-process-review`) that a blind copy would have reverted (#352 precedent). `pnpm quality-gate` green — 589 tests, 23 files. Refs #402 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
…T-4, T-6) Adapters said how to write an item's state but never how the item becomes visible. Two gaps, one reproduced: `Find the Item ID` had no branch for "not a project item yet", so it yielded an empty id and the status write either died or was silently skipped and reported as done. - github: Step 2b adds the issue as a project item before Step 3 writes the status; the empty-id lookup outcome is an explicit branch; the add call is documented idempotent, so it is safe as an unconditional precondition - github: assignee in the create/update recipes, plus "a PR author is not a PR assignee" — the case that made items invisible in the filtered view - azure-devops, filesystem: membership stated as implicit, so an agent does not invent GitHub's add-item step where it does not exist - README: documenting membership semantics + the assignee mechanic is now a stated requirement of the adapter contract Membership is explicit only on GitHub; a generic rule in the skill would be false for the other adapters, and unnecessary — write-issue Step 7.2 already delegates field configuration to the guide. Dataset edited first, mirrored to root with the transform applied. Refs #402 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
59 assertions over both corpora, data-driven: adapters are discovered from disk and each is asserted individually, so no count is hardcoded — #389 adds linear-implementation.md as a sixth and the suite absorbs it. Injection-tested: removing the idempotency claim, the mutation name, or the assignee prose each turns the suite red; restoring turns it green. The strongest assertion is structural — Step 2b must appear before Step 3, so the ordering cannot regress into prose that merely mentions both. Refs #402 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…p, not only the assignee Addresses PR #404's independent review: 1 Major (M1) + 3 Minor. M2 and M3 are handled outside the diff (PR description and #403's scope). M1 — the create recipe wrote the assignee and NOT the membership, so `/write-issue` called without `$status` (the follow-up path, which is exactly how #384 and #372 were filed) produced the defect this story exists to remove: open, assigned, green, off the board. AC1 only closed the status-write path. - the section is now "Create an Issue with its Assignee and its Membership" and opens with "Creating does not imply membership", naming the reachable path - `gh issue create` carries `--project "[project title]"` in the one-shot form - the MCP block states it has NO project field, so Step 2b is REQUIRED after it - the unqualified "gh issue create produces only the issue" is now qualified with `--project`, resolving a contradiction with this file's own :206 snippet Minor — that snippet passed `[PROJECT_ID]` where the flag takes a title or number; corrected in both `issue create` and `pr create`, with a pointer to the visibility section. `### Issue Management` now points at the create recipe, which lives further down next to the board mechanics an agent would not think to look under. The filesystem adapter says WHERE the `Assignee` line goes, since the item templates carry no such field and copying one leaves no slot. Minor — `membership is (explicit|implicit)` accepted either word for every adapter, so a flip stayed green. The loose form is kept for adapters this suite has never seen; the three known ones are now pinned by name via their own `board membership is <word>` declaration. Still count-free. 59 → 71 assertions. All five new claims injection-tested. Two of my own assertions were wrong and the tests caught them: - a bare `not.toContain('membership is implicit')` failed against github's legitimate cross-reference to the other adapters - the `[PROJECT_ID]` regression guard was VACUOUS: `normalize()` strips `_`, so a check written with the underscore could never match. Reinstating the regression left the suite green. Now asserted on raw content, and red on injection in both corpora. Mirror ported by hand, not regenerated: `github-implementation.md` carries pre-existing skill-reference transforms (`/review` -> `/pair-process-review`) that a blind copy would have reverted (#352 precedent). `pnpm quality-gate` green — 589 tests, 23 files. Refs #402 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ge constraint The constraint this PR declared has come due. #389 merged first, bringing `linear-implementation.md` (312 lines, zero occurrences of "assignee", no `Item Visibility` section) onto main. The guard added here requires that section from EVERY adapter present, so main would have gone red the moment this PR merged — 8 assertions (4 × 2 corpora), reproduced before the fix. Linear's membership is **implicit**, and for a sharper reason than the other implicit adapter: `issueCreate` *requires* `teamId`, so an issue cannot exist without belonging to a team. There is no add-to-board step to invent, and a state write can never fail for "not a member yet". Optional `projectId` narrows which project view shows it — a grouping inside the team, never what makes the issue visible. Stated explicitly, because silence is what makes an agent invent GitHub's `addProjectV2ItemById` for a tool that has no such concept. The assignee is NOT implicit and gets the same treatment as the other adapters: set as part of the create (`assigneeId` on `issueCreate`, with the user-id lookup shown), idempotent on update so it is safe to run unconditionally, and reported rather than dropped when it cannot be resolved. One Linear-specific note: the code host is separate from the PM tool here (#236's way-of-working override, merged as #389), so the PR-assignee write belongs to the host's adapter, not this one. Conformance 71 → 79. `pnpm quality-gate` green. Refs #402
b5a8cc8 to
bdd175c
Compare
…wide create recipe, ADL 14 findings, all resolved, none escalated. Major: - no-project no-op no longer cites canonical-states rule 5 (which HALTs) or D4 (missing-Ready-column): stated as its own degradation, contrasted with both. Outcome moved to Step 1 where discovery happens, with a table that separates "discovery returned no project" (no-op) from "discovery failed" (report). - every KB issue-create recipe now writes assignee + membership (github x2, azure x3, linear x1, secure-development x1), each pointing at the adapter's Item Visibility section. New KB-wide guard: no `gh issue create` without `--assignee`, whole-command regex, count-free. Minor: - KNOWN_SEMANTICS pins linear as implicit; README contract clause 4 and the heading-level/pinned-sentence form now asserted; docblock de-staled. - root mirror `_which_` — mirrors re-derived from dataset + transform only. - implicit membership != cannot be invisible: azure area path, linear projectId when the adoption names a project view. - Step 2b heading reframed as an unconditional precondition; its mutation parameterised (-F) with an empty-node-id abort. - ADL 2026-07-31-pm-adapter-visibility-contract records the contract invariant, the adapter-not-skill rationale (constrains #403) and the assignee-not-in- templates choice; cited from README as a path (not a link: adoption is per-project add-behaviour content). Conformance 79 -> 94, every new assertion injection-tested red-then-green. Gates: ts:check, lint, build, test (876), prettier, markdownlint — PASS. Refs: #402
…te recipes carry the visibility field - github: the `project` OAuth scope (gh auth refresh -s project) + the rule that a missing scope is reported, never worked around by dropping --project - github: "(or number)" dropped — gh 2.97 documents `-p, --project title` only - github: Step 1's no-project row now states its terminal state (skip Steps 2-3) - azure: --area on the create recipe; linear: projectId on the create, table row no longer contradicts its own body; linear ### Create carries assigneeId - secure-development: SECURITY_ONCALL declared beside the positional inputs - guard: KB-wide create families for az + linear (not gh only), discovery limited to fenced command-shaped lines, and the prose-only no-op/caveat rules pinned Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
(In reply to the first code-review comment above.) Escalation — PR #404 · story #402Three review rounds have run since the first review; round 3 converged with "nothing Per finding — attempted / current state
Convention for further work on this storyAny further rework or re-review on this story — including manual out-of-band rounds — Note: Status: escalated to human — not converging cleanly after 3 rounds; findings #3 and #11 |
…e recipes, test precision - README contract gains clause 5: implicit membership must still name the field that decides the read view. Guarded table-driven, keyed on the PINNED sentence form so github's cross-reference to implicit semantics is not read as a self-declaration. - Create recipes carry the membership field: --area on the three azure snippets; linear states the projectId condition next to the snippet (JSON has no comments); the security incident create says why it is deliberately board-less. - github-implementation: the no-project case is a legitimate outcome (omit --project, skip Step 2b), and [PROJECT_ID] is sourced from Step 1. - filesystem-implementation: the Assignee placement rule now covers epics and initiatives, and the initiative checklist carries the bullet the story one had. - occurrence numbers per file, not across the corpus. - The '(or number)' negative is scoped to the claim instead of the whole file. - FENCE_BREAK was a LITERAL NUL byte in the source, invisible in every editor and read as a space by the reviewer; now escaped and documented. - ADL records the azure/linear inlining asymmetry as deliberate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Remediation — round 4
Head: Docs — the create recipes now carry membership, not just prose about it
Tests
Description — finding 10: the Clause 5's guard was itself caught mid-flight: keyed on the bare phrase |
…ring Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PR Information
Story: #402 · Type: Documentation + Test · Priority: Medium (P1) · Assignee: @rucka
Reviewers: @rucka · Labels:
tech-debt,risk:yellowFollow-up: #403 (the deferred skill-contract half — now unblocked, #389 merged as
2da33a88)Summary
What Changed
The PM-tool adapters documented how to write an item's state but never how an item becomes visible. This PR closes that at the adapter layer and pins it with conformance tests.
github-implementation.md— new#### Step 2b: Add the Issue as a Project Item, placed before the status write and framed as an idempotent, unconditional precondition. The lookup's "not a project item yet" outcome is an explicit branch instead of an empty value falling through; the mutation is parameterised (-F) with an empty-node-id abort. Step 1 gained its own discovery-outcome table, separating "no project configured" (no-op) from "discovery failed" (report). Assignee added to the create/update recipes, with the case that actually bit us spelled out: a PR author is not a PR assignee.azure-devops-implementation.md,linear-implementation.md,filesystem-implementation.md— membership stated as implicit. Silence here is what makes an agent invent GitHub's add-item step for a tool that has no such concept. Each also states that implicit membership is not the same as "cannot be invisible" (Azure's area path; Linear'sprojectIdwhen the adoption names a project view).README.md— documenting membership semantics and the assignee mechanic is now a stated requirement of the adapter contract, with the heading level and the pinned sentence form spelled out so prose and gate say the same thing.issue-management/{github,azure-devops,linear}-issues.md— the KB's other create recipes no longer contradict the adapter: each carries the assignee (and membership on GitHub) and points at the adapter's Item Visibility section as authoritative.Why This Change
way-of-working.md› Assignment requires that skills set the assignee "as part of the write, never as a follow-up step". Nothing implemented it:assigneeappeared zero times inwrite-issue/SKILL.mdand zero times across every adapter. It was an adoption rule no capability knew how to execute.The second gap had a reproduced failure.
write-issueStep 7.3 writes the board status field "per the implementation guide", which presumed the item was already in the tracked view. On GitHub Projects it may not be — an issue and a project item are distinct objects.Find the Item IDhad no branch for that, so it yielded an empty id and Step 3 died with:Worse than the hard failure: a caller that does not check the empty id skips the board write and reports success.
Observed 2026-07-30 — #384 and #372 were open, assigned, green and absent from the board entirely, invisible in the assignee-filtered view the team reads. Found by hand while launching a batch; no gate caught it.
Story Context
As a maintainer whose board is read filtered by assignee I want every PM-tool implementation guide to document how an item becomes visible So that a skill following the guide cannot produce an item that exists, is open, is green, and is invisible.
AC1 (add-item precedes status) · AC2 (explicit empty-id branch) · AC3 (assignee in the create recipe) · AC4 (implicit membership stated) · AC5 (adapter contract) · AC6 (data-driven conformance, no count).
Changes Made
Implementation Details
issueCreaterequiresteamId, and in the filesystem adapter the file's location is its membership. A generic "add the item first" rule in the skill would be false for three adapters out of four, and it is unnecessary: Step 7.2 already delegates field configuration to the guide, so a corrected guide is picked up through the existing delegation with no skill edit. Recorded as an ADL so tech-debt: /write-issue contract — $assignee parameter + membership-precedes-state invariant (deferred half of #402) #403 inherits the constraint.projectOAuth scope is documented where the create recipe lives (gh auth refresh -s project), with the rule that a missing scope is reported, never worked around by dropping--project— that retry reproduces the off-board item.--projectis documented as a title only (verified against the installedgh 2.97.0:-p, --project title; numbers belong to thegh projectcommands), so the earlier "(or number)" affordance is gone. The two implicit-membership adapters now carry the visibility field on the recipe itself (azure--area, LinearprojectId), andSECURITY_ONCALLis declared and defaulted in the incident snippet so its create cannot fail on an unset variable.cp dataset → root+ the transform's skill-name rewrites only — so the two corpora differ by the transform and nothing else (this is how the round-2_which_slip was found and killed).Files Changed
21 files, +1161/−14. Every guideline file is ×2 (dataset authoring source + generated root mirror).
project-management-tool/(×2):README.md(+17),github-implementation.md(+127/−2, the largest change),azure-devops-implementation.md(+18/−1),filesystem-implementation.md(+17),linear-implementation.md(+37)issue-management/(×2):github-issues.md(+6/−2),azure-devops-issues.md(+5),linear-issues.md(+3) — the contradictory create recipesquality-assurance/security/secure-development.md(×2) (+9/−1) — the thirdgh issue createin the KB, brought under the new KB-wide guard.pair/adoption/decision-log/2026-07-31-pm-adapter-visibility-contract.md(+46)packages/knowledge-hub/src/conformance/pm-tool-adapter-contract.test.ts(+640)Testing
Test Coverage
127 assertions, data-driven: adapters are discovered from disk in both corpora and asserted individually, so no count is hardcoded — a new adapter enrols itself, and an omission reddens the gate.
Three guard families:
githubexplicit;azure-devops,linear,filesystemimplicit) so a regression cannot flip one and stay green; assignee-on-create; unresolvable-assignee reported.Board membership is …sentence form, so the prose cannot drift looser than the gate again.gh issue create→--assignee,az boards work-item create→--assigned-to,linear_gql … issueCreate→assigneeId. Whole-command regex, so a neighbouringgh pr create --assigneecannot satisfy it; recipes are discovered from fenced, command-shaped lines only (a prose mention is not a recipe), joined across trailing backslashes and single-quoted arguments spanning lines. Each family carries its own non-empty guard, so a discovery that stops matching one tool reddens instead of hiding behind the other two. 12 recipes per corpus today.--area, Linear's carriesprojectId. Prose naming a field the recipe below omits is how copying the recipe still produced the story's symptom.Injection-tested — every assertion proven non-vacuous. Round 3's new ones:
project-scope paragraph deleted (both corpora)--project" rule deleted--assigned-todeleted fromazure-devops-issues.md(×2)assigneeIddeleted fromlinear-issues.md(×2)--projectclaim--area/projectIddeleted from the adapter create recipesRound 2's, retained: linear flipped to
Board membership is explicit→ 2 failed / 92; README clause-4 fragment deleted → 2 failed / 92;--assignee/--projectstripped fromgithub-issues.md→ 4 failed / 90; README heading level demoted → 2 failed / 92.Earlier rounds, retained for the record: idempotency claim removed → 1 failed;
addProjectV2ItemByIdrenamed → 1 failed;assigneerenamed in the root README → 2 failed. Dropping #389'slinear-implementation.mdin without its section yielded 8 failures — only the count is ever absorbed, never the content.The strongest assertion is structural rather than textual:
Step 2bmust appear beforeStep 3, so the ordering cannot regress into prose that merely mentions both.Test Results
Gates for
risk:yellow(lint + type + build + unit), plus the ones this PR's own findings touch:pnpm ts:check(repo-wide, 8 tasks) — PASSpnpm lint,pnpm build,pnpm prettier:check(knowledge-hub) — PASSpnpm test(knowledge-hub) — PASS, 25 files / 909 tests, incl.check:linksvalid over the dataset corpus and root.pair/knowledgepnpm hygiene:check·pnpm skills:conformance(41 skills) ·pnpm docs:staleness— PASSmarkdownlintover every touched directory in both corpora — PASS. Run by hand on purpose: CI has no mdlint step andmdlint:fixnever reaches root.pair/**, which is exactly how theMD049slip in the root mirror escaped round 1.Quality Assurance
Code Quality Checklist
normalizefor emphasis-induced vacuity,sectionfor fence-awareness,fencedLines/joinContinuationsfor fenced command-shaped discovery and the two continuation shapes), and each non-obvious assertion cites the vacuity or regression it was written against.pair/knowledgeper corpus at collection time (suite runs in ~17 ms)Review Areas
issue-management/andquality-assurance/security/, so those three files are new surface for the same check.the membership step no-ops/never a halt.adoption/decision-log/is per-projectadd-behaviour content, so a markdown link would resolve here and break in every freshly seeded install — and would failcheck:linksover the dataset corpus. The reason is stated inline in the README so the next author does not "fix" it into a link.Notes for reviewers
On the create recipe (round-1 Question 2): it was an oversight, not a deliberate choice. The reasoning that might have justified it — Step 2b is idempotent and the status write always follows in the skill flow — does not hold: the follow-up-issue path has no status write at all, which is precisely how #384 and #372 ended up off the board. Fixed in
b5a8cc84, and round 2 extended the same reasoning to the threeissue-management/guides that carried the KB's only other create recipes.On the filesystem
Assigneeline (round-2 Question): the answer is now in the file, not only in a PR body. The shared item templates deliberately keep noAssigneeslot, because the assignee is a native tracker field on every other adapter; the filesystem adapter therefore owns the convention and states its own placement rule. Templates unchanged.This PR does not close the assignee gap on its own, by design. It makes the mechanic available and documented, reached through Step 7.2's existing delegation — it does not put
$assigneein/write-issue's contract, so a caller still cannot pass one explicitly. That is #403.Dependencies & Related Work
git merge-treeagainst all six pushed heads: clean./pair-capability-publish-prsetting the PR assignee.way-of-working.md:12names two skills, and only/write-issuewas tracked. Confirmed live the same day: PRs [US-402] docs: PM-tool adapters document membership + assignee — an item can no longer be written and stay invisible #404, [US-372] feat: coverage baseline auto commit-back — monotonic ratchet as a bot PR, opt-in and off by default #405, [US-384] chore: mirror-equality guard widened to every dataset skill artifact #406 and [US-278] feat: bootstrap quick mode — a second resolution depth, guided stays the default #408 were all born without an assignee.$assigneein the write contract, the tool-agnostic membership-precedes-state invariant, and a HALT on unresolvable item. Linear adapter coverage is no longer in its scope: it landed here (see below).Expected merge constraint — #404 ⟷ #389 — ✅ RESOLVED in
bdd175c8Not a rebase conflict: no line overlaps. The guard added here requires an
Item Visibilitysection, with an assignee mechanic, from every*-implementation.mdpresent. #389 introducedlinear-implementation.mdwithout one, so whichever merged second would have reddenedmain, and ordering could not close the window.Closes #402
🤖 Generated with Claude Code