Skip to content

[FORGE-94] feat(trackers): updateIssueBody on Linear + GitHub, Notion stub#159

Merged
firatcand merged 3 commits into
mainfrom
feat/FORGE-94-tracker-update-issue-body
May 17, 2026
Merged

[FORGE-94] feat(trackers): updateIssueBody on Linear + GitHub, Notion stub#159
firatcand merged 3 commits into
mainfrom
feat/FORGE-94-tracker-update-issue-body

Conversation

@firatcand
Copy link
Copy Markdown
Owner

What changed

  • Added Tracker.updateIssueBody(issueId, body) to the Tracker interface — replaces an issue's body wholesale while preserving every forge-managed <!-- forge:* --> footer (task, blockedBy, ownerType, future keys)
  • Implemented on LinearTracker (via client.updateIssue.description) and GitHubTracker (via gh issue edit --body), mirroring the existing setBlockedBy discipline
  • NotionTracker throws NOT_IMPLEMENTED referencing FORGE-117, which refactors the adapter onto the new ntn CLI transport and ships the real impl
  • Shared assertValidBodyInput helper: rejects non-string, embedded <!-- forge:* --> of any KEY, and bodies over the per-provider byte cap (65,536)
  • New parseExtraForgeFooters helper carries unknown forge:* comments (e.g. forge:ownerType) through a body replace
  • docs/adapters/notion.md prefaced with a planned-not-launched callout pointing at FORGE-117

Why

/reconcile --push and /apply-decision need a way to propagate spec/decision changes back into tracker issues without nuking the metadata footers the orchestrator depends on for the tracker→forgeTaskId round-trip. This adds the primitive.

How to test

  • npm test — 845 unit tests pass (24 new), 11 integration tests env-gated
  • npm run typecheck — clean
  • npm run build && node dist/bin/forge.cjs --version0.2.2
  • For real-tracker round-trip: set FORGE_E2E_GITHUB=1 FORGE_E2E_REPO=… (or Linear equivalent) and run npm test — new updateIssueBody round-trip preserves forgeTaskId integration test on both adapters

Reviewers

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 in 1178111. 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

firatcand and others added 3 commits May 17, 2026 18:25
…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>
@firatcand firatcand merged commit 9fe179e into main May 17, 2026
10 checks passed
@firatcand firatcand deleted the feat/FORGE-94-tracker-update-issue-body branch May 17, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant