Skip to content

[US-402] docs: PM-tool adapters document membership + assignee — an item can no longer be written and stay invisible - #404

Merged
rucka merged 8 commits into
mainfrom
chore/US-402-adapter-membership-and-assignee
Jul 31, 2026
Merged

[US-402] docs: PM-tool adapters document membership + assignee — an item can no longer be written and stay invisible#404
rucka merged 8 commits into
mainfrom
chore/US-402-adapter-membership-and-assignee

Conversation

@rucka

@rucka rucka commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

PR Information

Story: #402 · Type: Documentation + Test · Priority: Medium (P1) · Assignee: @rucka
Reviewers: @rucka · Labels: tech-debt, risk:yellow
Follow-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's projectId when 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.
  • ADL — the contract invariant, the "why the adapter, not the skill" rationale (which constrains tech-debt: /write-issue contract — $assignee parameter + membership-precedes-state invariant (deferred half of #402) #403) and the assignee-not-in-templates choice are recorded, not left in a story body that stops being ambient context once closed.
  • Conformance — 127 assertions over both corpora, count-free.

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: assignee appeared zero times in write-issue/SKILL.md and zero times across every adapter. It was an adoption rule no capability knew how to execute.

The second gap had a reproduced failure. write-issue Step 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 ID had no branch for that, so it yielded an empty id and Step 3 died with:

gh: Could not resolve to a node with the global id of ''

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

  • Why the adapter and not the skill: membership-as-a-separate-step is GitHub-specific — on Azure the work item belongs to its team project on create, on Linear issueCreate requires teamId, 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.
  • Round 3 additions: the project OAuth 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. --project is documented as a title only (verified against the installed gh 2.97.0: -p, --project title; numbers belong to the gh project commands), so the earlier "(or number)" affordance is gone. The two implicit-membership adapters now carry the visibility field on the recipe itself (azure --area, Linear projectId), and SECURITY_ONCALL is declared and defaulted in the incident snippet so its create cannot fail on an unset variable.
  • Dataset edited first, then every root file re-derived as 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 recipes
  • quality-assurance/security/secure-development.md (×2) (+9/−1) — the third gh issue create in the KB, brought under the new KB-wide guard
  • Added: .pair/adoption/decision-log/2026-07-31-pm-adapter-visibility-contract.md (+46)
  • Added: 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:

  1. Per-adapter — the visibility section exists; membership semantics stated as explicit-or-implicit (loose, must hold for an adapter never seen), plus the known values pinned by name (github explicit; azure-devops, linear, filesystem implicit) so a regression cannot flip one and stay green; assignee-on-create; unresolvable-assignee reported.
  2. README contract — all four clauses, including the heading level and the pinned Board membership is … sentence form, so the prose cannot drift looser than the gate again.
  3. KB-wide — no create recipe anywhere in either corpus omits the assignee, for all three tracker CLIs: gh issue create--assignee, az boards work-item create--assigned-to, linear_gql … issueCreateassigneeId. Whole-command regex, so a neighbouring gh pr create --assignee cannot 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.
  4. Visibility field on the create — the two implicit-membership adapters must ship it: azure's create recipe carries --area, Linear's carries projectId. 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:

Injection Result
the project-scope paragraph deleted (both corpora) 2 failed / 125 passed
the "never worked around by dropping --project" rule deleted 2 failed / 125 passed
the failed-discovery rule / Step 1 branch / skip-Steps-2-3 each deleted 2 failed / 125 passed
the implicit-membership caveat deleted from azure + linear (×2) 4 failed / 123 passed
every --assigned-to deleted from azure-devops-issues.md (×2) 6 failed / 121 passed
assigneeId deleted from linear-issues.md (×2) 2 failed / 125 passed
"(or number)" restored on the --project claim 2 failed / 125 passed
--area / projectId deleted from the adapter create recipes 2 failed / 125 passed
the prose mention rewritten without code spans (must stay green) 127 passed
restored 127 passed

Round 2's, retained: linear flipped to Board membership is explicit → 2 failed / 92; README clause-4 fragment deleted → 2 failed / 92; --assignee/--project stripped from github-issues.md → 4 failed / 90; README heading level demoted → 2 failed / 92.

Earlier rounds, retained for the record: idempotency claim removed → 1 failed; addProjectV2ItemById renamed → 1 failed; assignee renamed in the root README → 2 failed. Dropping #389's linear-implementation.md in without its section yielded 8 failures — only the count is ever absorbed, never the content.

The strongest assertion is structural rather than textual: Step 2b must appear before Step 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) — PASS
  • pnpm lint, pnpm build, pnpm prettier:check (knowledge-hub) — PASS
  • pnpm test (knowledge-hub) — PASS, 25 files / 909 tests, incl. check:links valid over the dataset corpus and root .pair/knowledge
  • pnpm hygiene:check · pnpm skills:conformance (41 skills) · pnpm docs:stalenessPASS
  • markdownlint over every touched directory in both corpora — PASS. Run by hand on purpose: CI has no mdlint step and mdlint:fix never reaches root .pair/**, which is exactly how the MD049 slip in the root mirror escaped round 1.

Quality Assurance

Code Quality Checklist

  • Code follows established style guides and conventions — prettier + eslint + markdownlint green; the new guard lives in the existing cross-cutting conformance file per the per-artifact ADL's stated exception, not in a story-named file
  • Functions and classes have appropriate documentation — each helper carries the reason it exists (normalize for emphasis-induced vacuity, section for fence-awareness, fencedLines/joinContinuations for fenced command-shaped discovery and the two continuation shapes), and each non-obvious assertion cites the vacuity or regression it was written against
  • Error handling implemented for edge cases — the two degenerate cases fail loudly rather than passing empty (zero adapter files fails at collection; a broken discovery filter reddens the non-empty guards)
  • Security best practices followed — the Step 2b mutation is parameterised rather than string-concatenated, with an empty-id abort; no exploitable path existed, this is hygiene the KB now teaches by example
  • Performance considerations addressed — n/a beyond suite cost: the KB-wide walk is a single recursive read of .pair/knowledge per corpus at collection time (suite runs in ~17 ms)
  • No debugging code or console logs left behind

Review Areas

  • Placement. This is the most contested area in the repo right now. Disjunction was verified at hunk level, not file level, and no prose was reflowed or reordered. No rebase conflicts expected — worth a second pair of eyes, because an incidental whitespace change would silently invalidate that claim. Round 2 widened the blast radius into issue-management/ and quality-assurance/security/, so those three files are new surface for the same check.
  • Is the no-project no-op right, and is it now attributed correctly? Round 1 justified it with two citations that did not support it (one said the opposite). It is now stated as its own degradation, explicitly contrasted with canonical-states write rule 5 (which HALTs when a board exists but no state maps) and with the D4 readiness fallback (board exists, lacks a Ready column). The behaviour is unchanged and still pinned by the membership step no-ops / never a halt.
  • The ADL is cited by path, not linked. Deliberate: adoption/decision-log/ is per-project add-behaviour content, so a markdown link would resolve here and break in every freshly seeded install — and would fail check:links over 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 three issue-management/ guides that carried the KB's only other create recipes.

On the filesystem Assignee line (round-2 Question): the answer is now in the file, not only in a PR body. The shared item templates deliberately keep no Assignee slot, 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 $assignee in /write-issue's contract, so a caller still cannot pass one explicitly. That is #403.

Dependencies & Related Work

Expected merge constraint — #404#389 — ✅ RESOLVED in bdd175c8

Not a rebase conflict: no line overlaps. The guard added here requires an Item Visibility section, with an assignee mechanic, from every *-implementation.md present. #389 introduced linear-implementation.md without one, so whichever merged second would have reddened main, and ordering could not close the window.

Closed 2026-07-31. #389 merged first, so the obligation landed on this PR and has been discharged: linear-implementation.md carries its Item Visibility section in both corpora — membership implicit (issueCreate requires teamId, so an issue cannot exist without belonging to a team; projectId groups within the team, and is part of visibility only when the adoption names a project view), plus the assignee mechanic (assigneeId on create, idempotent on update, reported rather than dropped when unresolvable). Its semantics are also pinned by name in KNOWN_SEMANTICS, so a later flip to "explicit" cannot pass. Nothing is left for the merger to do here.

Closes #402

🤖 Generated with Claude Code

@rucka

rucka commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict

risk:yellow · cost:greenCHANGES-REQUESTED — ACs 1–6 are all met and every gate is green, but the guide's own create recipe still produces the invisible item the story exists to remove, and the PR description understates a cross-PR CI break this guard arms.

PR: [#404] · Author: rucka · Reviewer: independent reviewer (agent) · Date: 2026-07-31 · Story: [US-402] · Type: docs

Classification matrix — per dimension
Dimension Tier Source Note
Service/domain criticality yellow KB default no Criticality Table declared ⇒ Medium (D21)
Change/diff risk yellow diff footprint 8 doc hunks + 1 new test; disjointness from the 6 parked PRs verified (see positive feedback)
Business impact green subdomain class Integration & Process Standardization (Supporting); no product surface
Security relevance green manual read docs + a file-reading test; no credential, no runtime code, no network
Coupling balance yellow manual (diff) new textual gate couples CI to exact prose of every present and future adapter — see M1

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.

Assessments

Security — Input validation

Verdict: green — no input surface: the change is markdown plus one test that reads two fixed, __dirname-relative directories.

Details
  • pm-tool-adapter-contract.test.ts:31-34,88-93readdirSync/readFileSync on constant paths; no argv, env or network input.
  • Documented shell recipe interpolates $ISSUE_NODE_ID (a value produced by gh api, not user input) into a double-quoted GraphQL string — correct quoting, no injection path introduced into the repo.

Security — Output handling

Verdict: green — no rendering/serialization surface touched.

Details
  • Markdown guidance only; no template rendering, no HTML, no logs.

Security — Authentication

Verdict: green — no auth path changed.

Details
  • Recipes rely on the operator's already-authenticated gh/az CLI; no token handling is documented or added.

Security — Authorization

Verdict: green — no access-control code or policy changed.

Details
  • The unresolvable-assignee branch ("not a collaborator, org/SSO restriction ⇒ report it") is the only permission-adjacent statement and it fails loud, which is the safe direction.

Security — Introduced vulnerabilities

Verdict: green — 0 introduced, 0 pre-existing.

Details
Severity Category File:location Introduced / pre-existing Recommendation
none

No secret, credential, network call or executable production path in the diff.

Cost

Verdict: cost:green — documentation plus one fast local test (7 ms, 59 assertions); no provider, no runtime, no storage signal.

Details
Signal Class Provider Note
CI test time green GitHub Actions +59 assertions, 7 ms — negligible

Architecture (Coupling)

Verdict: yellow — the guard binds CI to ~20 exact prose strings across every *-implementation.md present and future: high integration strength, zero socio-technical distance, high volatility (six open PRs edit these very files). The imbalance is not hypothetical — it fires on #389 (M1).

Details
  • Integration strength: high (exact-substring contract on prose).
  • Socio-technical distance: low (same repo, same maintainer) — this is what makes high strength normally tolerable.
  • Volatility: high (the directory is the most contested in the repo right now).
  • The normalize() helper is the right mitigation for the emphasis-marker class of brittleness and is well-reasoned; it does not mitigate the new-adapter class, which is M1.

Details

Findings by severity

Critical (must fix before merge)

  • none

Major (should fix before merge)

Minor (consider)

  • packages/knowledge-hub/src/conformance/pm-tool-adapter-contract.test.ts:109-115 — the per-adapter assertion accepts membership is (explicit|implicit), so a regression that flips azure/filesystem to "explicit" (or github to "implicit") stays green; only github's correct value is pinned (:187-192). AC4's substance ("silence is what makes an agent invent an add-item step") is only half-guarded. A named expectation for the two known implicit adapters is still count-free and matches the existing githubCases pattern in the same file.
  • .pair/knowledge/guidelines/collaboration/project-management-tool/github-implementation.md:375### Item Visibility: Membership and Assignee is nested under ## Code Review & PR Management (:217), directly after ### Hierarchy Queries. Board-status and issue-close do belong there; an issue-create recipe does not — an agent looking under ### Issue Management (:37) will not find it. Consider a one-line pointer from ### Issue Management (cheaper than moving the section, which is what keeps the diff disjoint from the parked hunks).
  • .pair/knowledge/guidelines/collaboration/project-management-tool/filesystem-implementation.md:210-216 and :348 — the adapter says the assignee "lives in the item file's header as an Assignee line" and lists it among the "required sections", but the create recipe at :329-337 copies user-story-template.md (:335 — via a collaboration/assets/ path that, pre-existing, does not exist in the repo), and that template has no Assignee field (only pr-template.md:9 has one). Following the recipe literally yields a file with nowhere to put the line. Either say where it goes (e.g. in the Epic Context block, after **Priority**) or add the field to the item templates.
  • PR [US-402] docs: PM-tool adapters document membership + assignee — an item can no longer be written and stay invisible #404 labels (none) vs story tech-debt: PM-tool adapters implement neither assignee nor board membership — an item can be written and stay invisible #402 (risk:yellow, tech-debt)/pair-capability-publish-pr Step 3 (SKILL.md:88) requires copying the story's classification tags to the PR verbatim; [US-234] feat: PR state flow (gate≠review) + pair review as required check #390/[US-279] feat: pair-cli scaffold-kb — external KB repo + release script (reuses pair package) #391 do carry them. Missing tags also make the tier-gate resolution fail-safe-red for anything reading the PR. Add risk:yellow (+ tech-debt).

Questions

  • PR description › Dependencies — is the intended merge order #404 → #389 → #403, knowingly accepting a red main between the last two? If so, say it out loud in the PR body: it is a human merge-gate decision, not a reviewer's.
  • github-implementation.md:388 — was the create recipe deliberately kept membership-free (because Step 2b is idempotent and the status write always follows in the skill flow), or is M1 an oversight? If deliberate, the reasoning belongs in the guide, since a human reading only that section gets no such guarantee.
Positive feedback
Functionality & requirements (AC coverage)
  • AC1 — add-item precedes the status write: Step 2b present, ordering asserted, Step 3 states it requires an id from 2 or 2b. Caveat: the create path is not covered — see M1.
  • AC2 — explicit empty-id branch: 3-row outcome table + "never carry an empty id" + "never report success" + pagination rule; all three pinned.
  • AC3 — assignee in the create recipe: github (MCP assignees: + gh issue create --assignee), azure (--assigned-to in the create block), filesystem (Assignee header line). See minor on the filesystem template.
  • AC4 — implicit membership stated verbatim in azure + filesystem, with "do not invent an add-item step".
  • AC5 — README ### Adapter Contract — Required Coverage states all four required claims as a requirement on future adapters.
  • AC6 — data-driven over *-implementation.md in both corpora; no count asserted (toBeGreaterThan(0) non-empty guard only, and it is documented as such).
  • Edge cases: idempotency of addProjectV2ItemById, multi-project targeting ("never take the first project found"), D4 no-op ("never a HALT"), unresolvable assignee ⇒ report — all present and pinned.
  • Business logic / So thatnot fully: an item created without a subsequent status write is still board-invisible (M1).
Testing & quality gates
  • Coverage adequate for a docs change: 59 assertions, data-driven over 2 corpora × 3 adapters, non-vacuity independently verified.
  • Edge/error scenarios: empty-id, no-project (D4), already-a-member, multi-project, unresolvable assignee.
  • Quality gates re-run in a detached worktree at head 3e5ddf82: vitest 577/577 pass (23 files), new suite 59/59; prettier-check exit 0; markdownlint-check exit 0; check-broken-links green (dataset + root). (tsc -p packages/knowledge-hub reported only TS6306 for an unbuilt sibling project — an artifact of my symlinked node_modules, not the diff.)
  • Forward-looking: the suite is red the moment [US-236] feat: code host separate from PM tool (WoW override) — GitHub + Linear reference case #389 lands (M2), reproduced locally.
Adoption compliance
  • Degradation level: 2 (/pair-capability-verify-adoption and /pair-capability-assess-stack not composed here; inline check performed instead).
  • No new dependency, no new pattern ⇒ nothing to reconcile with tech-stack.md / architecture.md; no ADR required and none missing.
  • way-of-working.md › Assignment: the rule is now executable at the adapter layer for issues; the PR half remains open and untracked (M3).
  • ADL 2026-07-18-conformance-test-per-file-not-per-story.md — a new file is justified: the ADL's "genuinely new cross-cutting invariant spanning many artifacts" exception (line 21) applies, and the file is named for the invariant, not the story. ✅
  • Conventions: __dirname + dataset/mirror corpora pattern matches all 18 sibling conformance tests. ✅
Tech debt
Documentation
  • The change is documentation; both corpora updated with the transform applied.
  • No website doc obligation: apps/website/content/docs/pm-tools/github-projects.mdx is a setup guide that defers to this KB README, not a mirror of adapter mechanics.
Performance & deployment
  • No hot path; +7 ms of CI.
  • Reversible: docs + one test file, revert is a single git revert. The only non-local effect is the guard's reach over future adapters (M2), which is a merge-order concern, not a rollback one.

Fix list (shortest path to APPROVED): M1 (membership in the create recipe + reconcile the --project contradiction) → M2 (correct the description sentence, record the merge-order constraint) → M3 (one scope line on #403). Minors are cheap and can ride along.

@rucka rucka added risk:yellow Classification: medium risk tier tech-debt Tracked technical debt (living backlog, R7.2 — never blocks a PR) labels Jul 31, 2026
rucka added a commit that referenced this pull request Jul 31, 2026
…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>
@rucka

This comment has been minimized.

rucka and others added 4 commits July 31, 2026 18:22
…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
@rucka
rucka force-pushed the chore/US-402-adapter-membership-and-assignee branch from b5a8cc8 to bdd175c Compare July 31, 2026 16:24
rucka and others added 2 commits July 31, 2026 19:04
…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>
@rucka

rucka commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

(In reply to the first code-review comment above.)

Escalation — PR #404 · story #402

Three review rounds have run since the first review; round 3 converged with "nothing
escalated" but a re-run of the cycle surfaced a fresh batch of findings (below), so this
run is escalating instead of converging. The prior round-1 remediation comment claiming
"ready for merge" has been minimized as outdated — it no longer reflects the PR's state
(rounds 2 and 3 landed real fixes after it, and this round found more).

Per finding — attempted / current state

  1. Abbreviation banners omit the visibility field they name (azure-devops-issues.md,
    linear-issues.md, secure-development.md) — round 3 (R3-m3) added the field to the
    adapters' own ### Create snippets and gated it there. This finding is the same
    defect class one layer up, in the issue-management/security snippets that defer to
    those adapters — not yet fixed. Open.
  2. gh issue create banner claims --project is carried; only --assignee is
    enforced
    (github-issues.md:9 + contract test:548) — the create-family gate (R3-m1)
    requires only the assignee for gh. The banner's membership half is asserted, not
    enforced. Open.
  3. Round-3's two new obligations (implicit-caveat sentence, visibility-field-on-create)
    are pinned by file allow-lists, not by a rule in the README contract
    — README still
    lists 4 clauses; IMPLICIT_CAVEAT_FILES / VISIBILITY_FIELD_CASES name 2 files each,
    so a 5th adapter with the same defect shape is invisible to both contract and guard.
    Open.
  4. github-implementation.md create recipe has no branch for "Step 1 succeeded, no
    project"
    — the no-project outcome is documented two sections later but not on the
    create recipe itself, where "never worked around by dropping --project" (R3-M1) reads
    as forbidding the one case where dropping it is correct. Open.
  5. Step 2b's [PROJECT_ID] provenance not pointed to from the create recipe — an
    agent arriving via the create path (added by round 1) hits an unbound placeholder in
    Step 2b; Step 2b's own preamble mentions Step 2, never Step 1. Open.
  6. filesystem-implementation.md's assignee placement rule covers story/task only
    epics and initiatives (named explicitly by way-of-working.md's Assignment rule) have no
    stated slot; the initiative checklist has no Assignee bullet. Open.
  7. Test-name accuracy nitoccurrence in pm-tool-adapter-contract.test.ts:581 is a
    corpus-global index, not per-file; failure messages point at a number not locatable in
    the named file. Open, nit.
  8. Over-broad negative-assertion nit.not.toContain('(or number)') at test.ts:401
    is whole-file, not sentence-scoped; any future legitimate use elsewhere in
    github-implementation.md reddens the gate with an unrelated message. Open, nit.
  9. Undocumented collision-safety invariant nitFENCE_BREAK = ' fence' at
    test.ts:492 is collision-safe only because fencedLines trims content lines; the
    docblock doesn't say why it can't collide. Open, nit.
  10. PR description still cites code-host-routing.test.ts as a stale-gate repair
    verified false against current origin/main (768e8a2): main already carries the
    identical form (landed in eff5248 / chore: post-merge fixups — assess-coupling no longer 'pending #263' + prettier format #385); the file contributes nothing on merge.
    Open (description-only, no code impact).
  11. linear vs azure round-3 fix applied asymmetrically — azure's ### Create got
    --area inline; linear's ### Create got only a comment, with the inline projectId
    landing in a second, visibility-section recipe. Question: deliberate (conditional
    field) or oversight? Open — needs an answer, not just a fix.

Convention for further work on this story

Any further rework or re-review on this story — including manual out-of-band rounds —
should be appended to .pair/working/reviews/402.md (the working log), not posted as
new standalone PR comments. The next orchestrated run on this story continues the same
cycle; when it converges, it will synthesize ONE final remediation comment and minimize
this flush plus the earlier remediation comment.

Note: .pair/working/reviews/402.md is an untracked file that lives only in the
persistent authoring worktree ../pair-worktrees/402. That worktree must be preserved
until merge
— if it is pruned or recreated, the audit log is lost. (This flush and the
first-review comment stay on the PR regardless, so the first-review signal still prevents
a duplicate first review on the next run.)

Status: escalated to human — not converging cleanly after 3 rounds; findings #3 and #11
above are design questions, not mechanical fixes.

…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>
@rucka

rucka commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

Remediation — round 4

Open findings: 0. All eleven findings are resolved; nothing on this PR is waiting on anyone.

Head: 7df44f36. Gate: 22 + 12 tasks green. Conformance 127 → 135.

Docs — the create recipes now carry membership, not just prose about it

# Finding What changed
1 Membership missing from create snippets in three files --area "[team area path]" added to all three az boards work-item create snippets (both corpora). For Linear the field is conditional, and JSON carries no comments, so the condition is stated immediately after the snippet: add projectId only when way-of-working.md names a project view.
2 The guard's ban vs. legitimately board-less creates The security-incident create now says why it has no --project: an incident is not a backlog item, and if a team does track incidents on the board, add --project plus Step 2b. The recipe and the adapter invariant no longer read as contradicting each other.
3 The contract had no clause for "implicit but still invisible" Clause 5 added: an adapter declaring implicit membership must name the field that decides the read view. Guarded table-driven (IMPLICIT_VIEW_DECIDERS), and it fails loudly on an implicit adapter it does not know — enrolling one is a deliberate edit.
4 The no-project case read as a workaround One clause: no project named ⇒ omit --project and skip Step 2b; that is the Step 1 outcome, and there is no board to be absent from.
5 [PROJECT_ID] had no provenance in Step 2b One sentence: it comes from Step 1 — run that first when arriving straight from the create recipe.
6 Placement rule covered stories and tasks only Extended to epics and initiatives (their own header block), and the initiative checklist gained the Assignee bullet the story checklist already had.
11 Azure inlines --area, Linear does not inline projectId Deliberate, and now recorded in the ADL: the fields differ in conditionality, so inlining projectId would teach an agent to send a project id on every create — including the common team-scoped board where there is none.

Tests

# Finding What changed
7 occurrence numbered across the corpus Now per file, so adding a snippet to one adapter no longer renames every later adapter's cases.
8 not.toContain('(or number)') banned the phrase file-wide Scoped to the claim itself. Injection-tested: reinstating (or number) inside the claim reddens exactly one case.
9 The fence sentinel's collision-safety was undocumented It turned out not to be a space at all — FENCE_BREAK was a literal NUL byte in the source, invisible in every editor and diff, which is exactly why it read as a space. It is genuinely collision-proof (markdown cannot contain a NUL), so the value stays and is now written escaped as a \u0000 sequence, with a comment saying the escape is load-bearing: as a raw byte, a well-meant whitespace tidy-up would have silently turned it into a value markdown can produce.

Description — finding 10: the code-host-routing.test.ts paragraph is removed. The file is byte-identical to origin/main (a three-dot merge-base artifact), so it contributes nothing to this merge and the paragraph described work that no longer exists.

Clause 5's guard was itself caught mid-flight: keyed on the bare phrase membership is implicit, it reddened on github-implementation.md, whose section legitimately explains implicit semantics while declaring itself explicit. It is now keyed on the pinned sentence form (Board membership is implicit) — the same false-positive shape that made main red earlier today.

…ring

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rucka
rucka merged commit 044c3a6 into main Jul 31, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:yellow Classification: medium risk tier tech-debt Tracked technical debt (living backlog, R7.2 — never blocks a PR)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tech-debt: PM-tool adapters implement neither assignee nor board membership — an item can be written and stay invisible

1 participant