[FORGE-94] feat(trackers): updateIssueBody on Linear + GitHub, Notion stub#159
Merged
Merged
Conversation
…tub (FORGE-94)
Add `Tracker.updateIssueBody(issueId, body)` that replaces an issue's body
wholesale while preserving forge-managed footers (`forge:task`,
`forge:blockedBy`, `forge:ownerType`, and any future `<!-- forge:* -->`
comments). Used by /reconcile --push and /apply-decision to propagate
spec/decision changes back into tracker issues.
Per-adapter status:
- LinearTracker: implemented via existing `description` field mutation
- GitHubTracker: implemented via `gh issue edit --body`
- NotionTracker: throws `NOT_IMPLEMENTED` referencing FORGE-117, which
refactors the adapter onto the new `ntn` CLI transport
(https://developers.notion.com/cli/get-started/overview) and ships
`updateIssueBody` as part of that refactor
Body validation (shared `assertValidBodyInput` helper in footers.ts):
- Reject non-string input
- Reject embedded `<!-- forge:task=... -->` or `<!-- forge:blockedBy=... -->`
(caller mistake — the adapter appends those)
- Reject bodies over the per-provider byte cap (65,536 for both Linear
and GitHub)
Footer-preservation contract: matches `setBlockedBy` — task + blockedBy are
re-serialized, all other `forge:*` comments are carried through via the
new `parseExtraForgeFooters` helper.
Conformance suite gains an `expectUpdateIssueBody: 'works' | 'not_implemented'`
flag so Notion's stub passes the structural contract today and the same call
site continues to work once FORGE-117 lands.
AC bullet 6 (Notion flagged as planned-not-launched): docs/adapters/notion.md
prefaces with a callout to FORGE-117.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tests (FORGE-94 review)
Codex + Claude code-reviewer 2nd-pass on FORGE-94. Both reviewers converged on
the same blocker: assertValidBodyInput only rejected forge:task and
forge:blockedBy, leaving any other `<!-- forge:KEY=... -->` (e.g.
`forge:ownerType=evil`) to silently collide with the adapter-managed extra-
footer preservation and produce duplicate contradictory comments on round-trip.
Changes
- footers.ts: assertValidBodyInput now rejects ANY `<!--\s*forge:` in caller
input, not just task/blockedBy. New FORGE_ANY_INPUT_RE constant.
- footers.ts: FORGE_ANY_RE value capture changed from `[^>]*?` to `[\s\S]*?`
so extra-footer values containing `>` (e.g. `forge:threshold=a>b`) are
captured fully instead of silently truncated.
- base.ts: Tracker.updateIssueBody JSDoc now spells out the caller contract
(no forge-managed footers in input; caller holds claim — no CAS at this
layer).
- linear.test.ts + github.test.ts: round-trip tests now call
parseForgeFooters on the captured output and assert
forgeTaskId/blockerIds round-trip — per AC-as-unit-test discipline
("structure exists ⇒ behavior correct" smell flagged by claude-reviewer).
- linear.test.ts: new VALIDATION-on-oversized-body test mirrors GitHub's
byte-cap coverage.
- github.test.ts: new test sweeps unknown forge:KEY rejection
(ownerType, threshold, made-up-key, X).
Deferred to FORGE-118 (filed)
- withRetry on body-mutating methods (pattern issue across class, predates
this PR)
- parseExtraForgeFooters dedup of duplicates
- Fenced-code-block false-positive parsing
- Single-writer enforcement (today: caller contract via claim)
- Linear byte-cap source verification (no npm reachable in codex sandbox)
- README adapter-matrix mention (currently in docs/adapters/notion.md)
All gates green: typecheck, 845 unit tests pass, build, smoke.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.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.
What changed
Tracker.updateIssueBody(issueId, body)to theTrackerinterface — replaces an issue's body wholesale while preserving every forge-managed<!-- forge:* -->footer (task, blockedBy, ownerType, future keys)client.updateIssue.description) and GitHubTracker (viagh issue edit --body), mirroring the existingsetBlockedBydisciplineNOT_IMPLEMENTEDreferencing FORGE-117, which refactors the adapter onto the newntnCLI transport and ships the real implassertValidBodyInputhelper: rejects non-string, embedded<!-- forge:* -->of any KEY, and bodies over the per-provider byte cap (65,536)parseExtraForgeFootershelper carries unknownforge:*comments (e.g.forge:ownerType) through a body replacedocs/adapters/notion.mdprefaced with a planned-not-launched callout pointing at FORGE-117Why
/reconcile --pushand/apply-decisionneed a way to propagate spec/decision changes back into tracker issues without nuking the metadata footers the orchestrator depends on for the tracker→forgeTaskIdround-trip. This adds the primitive.How to test
npm test— 845 unit tests pass (24 new), 11 integration tests env-gatednpm run typecheck— cleannpm run build && node dist/bin/forge.cjs --version—0.2.2FORGE_E2E_GITHUB=1 FORGE_E2E_REPO=…(or Linear equivalent) and runnpm test— newupdateIssueBody round-trip preserves forgeTaskIdintegration test on both adaptersReviewers
Codex (gpt-5.5) + Claude code-reviewer both ran 2nd-pass against the CRITICAL.md tracker paths. Both converged on the same blocker (caller-input could smuggle
<!-- forge:ownerType=evil -->past validation) — fixed in1178111. Remaining findings (withRetry, dedup, fenced-code-block parsing, CAS, Linear cap source) deferred to FORGE-118.Linked
Closes FORGE-94
Follow-ups filed: FORGE-117 (Notion refactor), FORGE-118 (body-mutation hardening)
🤖 Generated with Claude Code