Repository navigation
feat(items): link and unlink blockers and parent on an existing item - #104
Merged
Merged
Conversation
5 tasks done
ryanlindsey
force-pushed
the
issue-97-item-update-tool
branch
from
October 11, 2026 00:57
340105e to
ac26ee2
Compare
linkItem adds and removes blockers and sets, replaces or clears a parent, verifies the links by read-back, then syncs every canonical Depends on line to the resulting blockers from the latest body, and verifies again. dist/server.js is unchanged: nothing imports linkItem until item_link (#99). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ling Resolve link targets through ISSUE_IDENTITY so dry run, prose and read-back carry one spelling; open PartialLinkError with only what was observed; never call a write refused that the read-back shows landed; word a missing current parent as a removal. Cover the no-op, read-failure and dry-run-parity cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ryanlindsey
force-pushed
the
issue-98-item-link-adapter
branch
from
October 11, 2026 00:58
d4cecfc to
e8e572c
Compare
ryanlindsey
added a commit
that referenced
this pull request
Oct 11, 2026
Closes #99 Stacked on #104 (#98), the `linkItem` adapter. Part of epic #94. ## What Wires `linkItem` through the provider and `dispatch` as the `item_link` tool. - `BoardProvider.link?(ref, change): Promise<LinkedItem>` is **optional**, documented like `check?`. Keeping `Depends on` prose in step is a Markdown-body shape, so an adapter that cannot do that declines the method. `LinkedItem` now requires `blockedBy`, and `BoardItem` gains an optional `blockedBy?`, because dispatch logs the blocker change against `getItem`. - `GitHubBoardProvider.link` calls `invalidate()` in a `finally`. - `LinkUnsupportedError` is shaped like `ChecklistUnsupportedError`: nothing was written; change the links in the tracker itself. - Tool `item_link` takes `{ ref, addBlockedBy?, removeBlockedBy?, parent?, clearParent? }`, with only `ref` required. Every argument is checked before the capability check. `clearParent: true` reaches the provider as `parent: null`, and `parent` sent together with it is refused. A call that asks for no change is refused. - `refList` is extracted and shared with `item_create`'s `blockedBy`. - Log lines: `Blocked by` and `Parent` are written only when they changed. `Depends on` is written as a diff (`before` = lines removed, `after` = lines added). No body text is logged. Every line is marked under a dry run. - Contract: `links?` fixture block with three tests. The GitHub contract fake now handles `removeBlockedBy` and `ISSUE_IDENTITY`. - README: ten tools, an `item_link` row, and a sentence on changing links later. - `dist/server.js` rebuilt. ## Acceptance receipt - [x] `item_link` is advertised and handled, with every argument checked before the capability check. Evidence: `tests/index.test.ts` › item_link › "is advertised with exactly the arguments the handler accepts" and "validates every argument before checking the capability" (seven malformed calls against a provider without `link`, none raising `LinkUnsupportedError`); suite green. - [x] Clearing a parent uses `clearParent: true`, and `parent` together with it is refused. Evidence: "hands clearParent to the provider as parent: null", "names the parent when it is sent with clearParent", and the "a parent together with clearParent" refusal case; suite green. - [x] Log lines record blockers, parent and Depends-on changes; dry-run results are marked. Evidence: "logs the blockers, the parent and the Depends on lines that changed", "logs a cleared parent", "logs a Depends on repair even when the links did not change", "marks a dry run, in the result and in every log line"; all captured via `logWrite`; suite green. - [x] The contract suite holds an adapter that offers `link` to links and lines together. Evidence: `tests/contract/github.contract.test.ts` › "changing links, if the adapter keeps prose in step" (add, remove, refuse-missing) passes against `GitHubBoardProvider`. - [x] README updated; `dist/server.js` rebuilt; `npm test` and `npm run typecheck` pass. Evidence: README read back (ten tools, `item_link` row and sentence); `npm run build` followed by `git diff --exit-code dist/server.js` clean; `npm test` 725 passed / 18 skipped; `npm run typecheck` clean. The plan-step checkboxes (Steps 1–6, under no heading) are left alone, as they are not acceptance criteria. ## Review One review round: no Critical or Important findings. Minor findings fixed in the second commit: the `link?` doc now says `getItem` must report `blockedBy`, a comment explains the `Depends on` log shape, and the fake's `removeBlockedBy` now matches on owner too. Open, minor, left for a follow-up: **writes that landed are not logged when `link` throws** `PartialLinkError` / `ProseOutOfStepError`. Logging runs only after the provider resolves. `item_create`'s partial errors behave the same way, so a fix belongs to both tools. ## Board repairs none 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_014YX1awC8SkssEro6XoXTaE --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
ryanlindsey
pushed a commit
that referenced
this pull request
Oct 11, 2026
🤖 I have created a release *beep* *boop* --- ## [0.9.0](armature-v0.8.0...armature-v0.9.0) (2026-10-11) ### Features * **item_link:** expose blocker and parent changes as a tool ([#105](#105)) ([6d28299](6d28299)) * **item_update:** expose title and body edits as a tool ([#103](#103)) ([a9626f0](a9626f0)) * **items:** link and unlink blockers and parent on an existing item ([#104](#104)) ([60c05b0](60c05b0)) * **items:** report an item's version and edit its title and body, guarded ([#102](#102)) ([eca5b1e](eca5b1e)) * **skill:** repair defects through item_update and item_link, and report them ([#106](#106)) ([fd8671a](fd8671a)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: ryanlindsey-bot[bot] <324957459+ryanlindsey-bot[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #98
The adapter half of
item_link. It addslinkIteminserver/providers/github/items.ts, which adds and removes blockers on an existing item and sets, replaces or clears its parent. It then brings every canonicalDepends online into step with the resulting blockers, including drift the call did not name. Each phase is verified by its own read-back. This PR only adds the adapter; wiring it into the provider,dispatch,TOOLS, the contract suite and the README is #99.Stacked on #103 (
issue-97-item-update-tool).What's in it
LinkChangeandLinkedIteminserver/providers/types.ts.REMOVE_BLOCKED_BY,SET_PARENT(addSubIssuewithreplaceParent: true) andREMOVE_SUB_ISSUE.ISSUE_IDENTITY. It resolves a target's node id together with GitHub's own spelling of its owner and repo. That keeps the dry run, the writtenDepends online and the read-back on one spelling, soAcme/Web#3is written asacme/web#3. Without it, the dry-run prediction and the real result disagreed on spelling.PARENT_IDis unchanged, socreateItemis unaffected.ConflictingLinkError: raised before any read.MissingLinkTargetError: raised before any write, dry run included.PartialLinkError: some link writes did not show on read-back. The prose is not touched. Its opening sentence states only what was observed: in part, none confirmed, or links moved while this call sent nothing.ProseOutOfStepError: every link is confirmed but the prose is in doubt.UnclosedFenceErrorplacement:syncDependsOn(before.body, desired)runs before any link mutation, dry run included. A body that can never take a new line is refused with nothing written, so it is never left half-linked. If the fence appears only after the links land, the error is restated asProseOutOfStepError, because the original error's "nothing was changed" is no longer true.Acceptance receipt
linkItemadds and removes blockers and sets, replaces and clears a parent, each verified by read-back. Evidence:tests/items-link.test.tscovers add, remove, set-over-existing (SET_PARENT, replaceParent) and clear (REMOVE_SUB_ISSUE, current parent's id). Each test scripts the read-back. The suite is green.Depends onset equals itsblockedBy, including blockers the call did not name. Evidence: the "repairs drift on blockers the call did not name" test. The step-9 check raisesProseOutOfStepErrorotherwise, and a test covers that too.PartialLinkError(missing add, refused write, failed read-back, concurrent blocker change, no-op with moved links), forProseOutOfStepError(prose mismatch, fence opened since, refused body write, links moved after confirmation), and for the person's edit surviving.tests/integration/queries.integration.test.ts, run against the live schema withARMATURE_INTEGRATION=1 ARMATURE_IT_OWNER=ryanlindsey ARMATURE_IT_BOARD=1 ARMATURE_IT_REPO=armature: 13/13 passed. That includes the three new mutations andISSUE_IDENTITY.dist/server.jsrebuilt;npm testandnpm run typecheckpass. Evidence:npm run buildran and the bundle is byte-identical, because nothing importslinkItemuntil feat(item_link): expose blocker and parent changes as a tool #99 wires it in.npm testpassed (688, 18 skipped) andnpm run typecheckis clean.Review
One review round and one re-review. No Critical findings. The Important findings were fixed before this PR:
PartialLinkErrorwording overclaimed what had landed.Minor findings left open:
version, and itsblockedByorder may differ from GitHub's.ISSUE_IDENTITYfollows the redirect for a transferred issue. Naming such an issue by its old location can send one extra add, which is idempotent and verified by the read-back. An old ref that redirects to the item itself gets past the self-reference check, and GitHub refuses the write, which raisesPartialLinkError.Board repairs
none
🤖 Generated with Claude Code
https://claude.ai/code/session_014YX1awC8SkssEro6XoXTaE