Skip to content

fix(#8174)!: normalize review-thread position fields across forges - #8693

Open
fullsend-ai-coder[bot] wants to merge 2 commits into
mainfrom
agent/8174-normalize-review-thread-positions
Open

fullsend-ai-coder[bot] wants to merge 2 commits into
mainfrom
agent/8174-normalize-review-thread-positions

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

Summary

This PR replaces the overloaded line / original_line fields on forge.ReviewThread with one position contract that means the same thing on GitHub and GitLab. Consumers of fetch-review-threads can now tell apart:

  • the old and new side of a diff
  • GitHub outdated threads
  • GitLab removed-line discussions
  • multi-line ranges

Breaking (GitLab output): the old-file line moves from original_line to old_line. A removed-line discussion now reports its old-file line in line, with side: "left". GitHub line and original_line keep their meaning.

Related Issue

Follow-up from #7907.

Changes

  • internal/forge/forge.go: adds a DiffSide type (left/right). ReviewThread now has side, line, start_side, start_line, original_line, original_start_line, old_line, new_line and is_outdated. Each field is documented in the struct, and every one is null when the forge does not report it. The names reuse the vocabulary of the entity-context-snapshot reviews.schema.json.
  • GitHub (internal/forge/github/github.go):
    • The GraphQL query now also fetches diffSide, startDiffSide, startLine, originalStartLine and isOutdated.
    • Sides are lowercased.
    • The current line is also copied to old_line or new_line, depending on the side.
  • GitLab (internal/forge/gitlab/mr.go):
    • old_line and new_line are kept exactly as GitLab reports them.
    • side and line come from them: right if a new line exists, otherwise left.
    • line_range.start is read for multi-line ranges. A range that covers only one line is not treated as multi-line.
    • is_outdated, original_line and original_start_line are always null, because GitLab does not expose them (as the maintainer confirmed on the issue).
  • Fake client: deep-copies all the new pointer fields.
  • Docs (docs/guides/dev/cli-internals.md): adds a table mapping each field to its GitHub and GitLab source, notes on matching (compare path + new_line), and a migration note for consumers of the old fields.

Testing

  • Tests added or updated for the new or changed logic:
    • GitHub: a LEFT-side thread, an outdated thread (line: null, is_outdated: true), a multi-line thread, and a check that the query asks for the new fields.
    • GitLab: a discussion with both old and new lines, a removed-line discussion (new_line: null, is_outdated: null), a multi-line range on one side and across sides, a single-line line_range, a general (no position) discussion, and table tests for gitlabSideLine.
    • CLI: the JSON output contains every documented position key.
  • go test ./internal/forge/... passes, and so do the fetch-review-threads CLI tests. Patch coverage on the changed functions is 98.9–100%.
  • make lint: pre-commit could not fetch its remote hook repos in the sandbox (TLS verification failure). I ran the relevant hooks directly instead: pre-commit-hooks v6.0.0 (EOF, whitespace, merge-conflict, mixed line endings, private key), gofmt, go vet ./..., lint-broken-symlinks, lint-docs-links and lychee --offline. All passed. scan-secrets stood in for gitleaks.
  • Six internal/cli tests (TestStartFetchService_*, TestPostScriptEnv_NoSchema*) fail in the sandbox both on clean main and with this change, so they are not related to it.

Migration for consumers

The agents-side forwarding (fullsend-ai/agents review-ops.lib.sh) matches dismissals by finding ID, so it does not break. If it forwards GitLab original_line, it should switch to old_line, and use new_line for head-file line matching.

Checklist

  • PR title follows Conventional Commits (correct type, ! for breaking changes)
  • Commits are signed off (DCO): not applicable, autonomous agent commit
  • Generated by an autonomous agent; every change is explained above

🤖 Generated with Claude Code


Closes #8174

Post-script verification

  • Branch is not main/master (agent/8174-normalize-review-thread-positions)
  • Secret scan passed (gitleaks — 35c8bf8dc98dd91abc582bfd22fab9e5ce905d47..HEAD)
  • PR body secret scan passed (gitleaks — no-git)

ReviewThread exposed only line and original_line, but the two forges
filled them with different meanings. GitHub used them for the current
and creation-time line on an unfetched diff side, while GitLab put
new_line and old_line into them. A GitLab removed-line discussion
therefore looked like an outdated GitHub thread, and consumers could
not match threads by path and line across forges.

Define one position contract on forge.ReviewThread: side, line,
start_side, start_line, original_line, original_start_line, old_line,
new_line and is_outdated, reusing the entity-context-snapshot reviews
vocabulary. The GitHub query now fetches diffSide, startDiffSide,
startLine, originalStartLine and isOutdated, and maps the current line
to old_line or new_line by side. The GitLab fetcher keeps old_line and
new_line as reported, derives side and line from them, reads
line_range for multi-line starts, and leaves is_outdated and the
original_* fields null because GitLab does not expose them. The fake
client deep-copies the new pointer fields, and the CLI internals guide
documents the contract and the migration.

Pre-commit could not fetch remote hook repositories in the sandbox
(TLS verification failure). The pre-commit-hooks checks (v6.0.0),
gofmt, go vet, lint-broken-symlinks, lint-docs-links and lychee were
run directly and passed. scan-secrets replaced gitleaks.

BREAKING CHANGE: fetch-review-threads output on GitLab changed. Read
the old-file line from old_line instead of original_line, which is now
always null, and the new-file line from new_line instead of line. For
removed-line discussions, line now holds the old-file line with side
"left" instead of null. GitHub line and original_line keep their
meaning.

Closes #8174

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:14 AM UTC · Completed 8:27 AM UTC

Commit: de0bb51 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $3.10

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Site preview

Preview: https://11bc7759-site.fullsend-ai.workers.dev

Commit: 3d721198a9227ef887f38dd29d48496baa90af1d

@codecov

codecov Bot commented Oct 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@fullsend-ai-review fullsend-ai-review Bot added the risk/moderate PR risk: moderate label Oct 22, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Breaking API change across forge review-thread position fields in frequently churned files, balanced by zero protected path changes, high test ratio, and exact coverage of the linked issue acceptance criteria.

Previous run

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Breaking API change across forge review-thread position fields in frequently churned files, balanced by zero protected path changes, high test ratio, and exact coverage of the linked issue acceptance criteria.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Review

Findings

Low

  • [architectural-conflict] internal/forge/forge.go:653 — The PR description says the new ReviewThread fields reuse the entity-context-snapshot reviews vocabulary, but docs/normative/entity-context-snapshot/v1/reviews.schema.json defines original_side and has no start_side, old_line or new_line. The code and the CLI internals guide make no such claim, so this concerns the PR description, not runtime behavior. The latest push did not change this.
    Remediation: Reword the PR description to say the fields adapt that vocabulary, or align the names with the schema.
  • [backward-incompatible] internal/forge/gitlab/mr.go:962 — On GitLab, line changes meaning without a key change. Removed-line discussions now report the old-file line in line with side: left, and original_line is now always null. The ! prefix, the BREAKING CHANGE footer and the migration note cover this, so the remaining risk is consumers outside this repo (for example fullsend-ai/agents) that still read the old fields. This code is unchanged since the prior review.
    Remediation: Coordinate the fullsend-ai/agents migration to old_line and new_line with this release.

Earlier findings

  • internal/forge/github/github.go:4002, logic-error — resolved. StartSide is now set whenever startDiffSide is reported, and a new outdated multi-line test covers it.
  • docs/guides/dev/cli-internals.md:244, api-contract — resolved. The guide and the ReviewThread comments now say original_start_line belongs to start_side.
  • internal/forge/gitlab/methods_test.go:262, pattern-inconsistency — resolved. The test now registers its handler with handleTracked.
Previous run

<!-- fullsend:review-findings-v2:eyJ2ZXJzaW9uIjoyLCJhY3Rpb24iOiJyZXF1ZXN0LWNoYW5nZXMiLCJmaW5kaW5ncyI6W3sic2V2ZXJpdHkiOiJtZWRpdW0iLCJjYXRlZ29yeSI6ImxvZ2ljLWVycm9yIiwiZmlsZSI6ImludGVybmFsL2ZvcmdlL2dpdGh1Yi9naXRodWIuZ28iLCJpZCI6ImZfMDk4NjZhNTU1NDNjZDQ3NiIsImxpbmUiOjQwMDIsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJtZWRpdW0iLCJjYXRlZ29yeSI6ImFwaS1jb250cmFjdCIsImZpbGUiOiJkb2NzL2d1aWRlcy9kZXYvY2xpLWludGVybmFscy5tZCIsImlkIjoiZl8zMmQzODFlNWQ3OGIxZWIzIiwibGluZSI6MjQ0LCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoibG93IiwiY2F0ZWdvcnkiOiJwYXR0ZXJuLWluY29uc2lzdGVuY3kiLCJmaWxlIjoiaW50ZXJuYWwvZm9yZ2UvZ2l0bGFiL21ldGhvZHNfdGVzdC5nbyIsImlkIjoiZl81ZDYwNzljZDM3ZmRjMjFhIiwibGluZSI6MjYyLCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoibG93IiwiY2F0ZWdvcnkiOiJhcmNoaXRlY3R1cmFsLWNvbmZsaWN0IiwiZmlsZSI6ImludGVybmFsL2ZvcmdlL2ZvcmdlLmdvIiwiaWQiOiJmXzZhYWM1YzgyZjE5YzFmOTQiLCJsaW5lIjo2NTMsImFjdGlvbmFibGUiOmZhbHNlfSx7InNldmVyaXR5IjoibG93IiwiY2F0ZWdvcnkiOiJiYWNrd2FyZC1pbmNvbXBhdGlibGUiLCJmaWxlIjoiaW50ZXJuYWwvZm9yZ2UvZ2l0bGFiL21yLmdvIiwiaWQiOiJmXzA1OTIyYTY5ZTE4ZjNlNTAiLCJsaW5lIjo5NjIsImFjdGlvbmFibGUiOmZhbHNlfV19 -->

Review

Findings

Medium

  • [logic-error] internal/forge/github/github.go:4002 — StartSide is dropped whenever startLine is null, even if GitHub reports startDiffSide, while OriginalStartLine is copied unconditionally (line 3998). For outdated multi-line threads (line/startLine null, originalStartLine set) the start side is lost, so consumers cannot tell which file version the original start line belongs to, especially for cross-side ranges. The new tests have no outdated multi-line case.
    Remediation: Set StartSide from startDiffSide whenever GitHub reports it, not only when startLine is non-null, and add a test with null startLine, non-null originalStartLine and a reported startDiffSide. If GitHub does not return startDiffSide for outdated threads, document that in the contract.
  • [api-contract] docs/guides/dev/cli-internals.md:244 — The position contract says original_start_line is the start line on side, and the ReviewThread comment in internal/forge/forge.go says OriginalLine and OriginalStartLine are both on Side. For cross-side ranges the start line lives on start_side: the PR's own GitHub fixture has diffSide RIGHT, startDiffSide LEFT and originalStartLine 9. A consumer following the documented contract would read original_start_line against the wrong file version.
    Remediation: Describe original_line as belonging to side and original_start_line as belonging to start_side in both the field table in docs/guides/dev/cli-internals.md and the ReviewThread comments in internal/forge/forge.go.

Low

  • [pattern-inconsistency] internal/forge/gitlab/methods_test.go:262 — TestListPullRequestReviewThreads_PositionContract registers its discussions handler directly on the mux without asserting that the route was hit. docs/contributing/go-code.md requires invocation tracking for every handler registration, and this file already has a handleTracked helper (line 28) for that. The test's value assertions make a silent pass unlikely, so the risk is low.
    Remediation: Register the handler with handleTracked(t, mux, pattern, handler), or track a handlerCalled flag and assert it after ListPullRequestReviewThreads returns.
  • [architectural-conflict] internal/forge/forge.go:653 — The PR claims ReviewThread reuses the entity-context-snapshot reviews vocabulary, but docs/normative/entity-context-snapshot/v1/reviews.schema.json defines original_side and not start_side, old_line or new_line, and thread-state.schema.json uses outdated where ReviewThread uses is_outdated. The core fields (side, line, start_line, original_line, original_start_line) do match. The difference is in the PR's claim, not in runtime behavior.
    Remediation: Either align the names with the snapshot schema, or describe the new fields as an adaptation of that vocabulary rather than a direct reuse (for example in the cli-internals.md contract section).
  • [backward-incompatible] internal/forge/gitlab/mr.go:962 — On GitLab, line changes meaning without a key or type change: it was new_line (null for removed-line discussions) and is now the old-file line when side is left, and original_line goes from the old-file line to always null. Consumers in fullsend-ai/agents that still read line as a new-file line could misattribute findings until they migrate; those consumers could not be inspected from this review. The PR has the ! suffix, a BREAKING CHANGE footer and a migration note, and the maintainer approved dropping the legacy names, so this is a coordination risk only.
    Remediation: Coordinate the fullsend-ai/agents migration to new_line/old_line with this release and link it from the PR.

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-coder

fullsend-ai-coder Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 8:29 AM UTC · Completed 8:34 AM UTC

Commit: de0bb51 · View workflow run →

Runtime: claude · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $0.33

Keep StartSide when GitHub reports startDiffSide even if startLine is
null (outdated multi-line threads) and cover it with a test. Document
original_start_line as belonging to start_side in the position contract
and the ReviewThread field comments. Track the GitLab position-contract
test route with handleTracked.

Not changed: the entity-context-snapshot vocabulary note (no claim in
the code or docs) and the GitLab line-meaning migration (cross-repo
coordination).

pre-commit could not fetch hook repos (certificate failure); ran gofmt,
go vet, interface-doc-sync and whitespace checks directly instead.

Addresses #8693

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 1 (bot-triggered)

Fixed the three actionable findings: StartSide handling for outdated multi-line GitHub threads (with a new test), original_start_line side documentation in the contract and field comments, and tracked handler registration in the GitLab test. Recorded disagreements for the two non-actionable findings.

Fixed (3):

  1. StartSide dropped when startLine is null (outdated multi-line GitHub threads) (internal/forge/github/github.go): StartSide is now set whenever GitHub reports startDiffSide, not only when startLine is non-null. Added an outdated multi-line test case (null startLine, set originalStartLine and startDiffSide).
  2. original_start_line documented against the wrong side for cross-side ranges (docs/guides/dev/cli-internals.md): The field table and the ReviewThread field comments in internal/forge/forge.go now say original_start_line belongs to start_side, and that start_line is null for outdated multi-line threads while start_side may remain set.
  3. GitLab position-contract test registers handler without invocation tracking (internal/forge/gitlab/methods_test.go): Switched to the existing handleTracked helper so the test fails if the discussions route is never hit.

Disagreed (2):

  1. ReviewThread claims to reuse entity-context-snapshot vocabulary but names differ (start_side, old_line, new_line, is_outdated): Neither forge.go nor cli-internals.md makes that claim; it appears only in the PR description, so there is no in-repo text to correct. The reviewer marked it non-actionable. The PR description can be edited to say the fields adapt the snapshot vocabulary.
  2. GitLab line changes meaning without a key or type change; fullsend-ai/agents consumers need coordination: Cross-repo coordination, not a change in this repo. The reviewer marked it non-actionable. The PR already carries the breaking-change marker, footer and migration note, and the maintainer approved dropping the legacy names. Linking the agents migration from the PR is left to a human.

Tests: passed

CI inspections (6):

  1. test (passing, success)
  2. build (passing, success)
  3. commit-lint (passing, success)
  4. codecov/patch (passing, success)
  5. playback (passing, success)
  6. behaviour (pending, in_progress): Still running when inspected; no failure to act on.

Conflict update: skipped; strategy none; forge MERGEABLE; target main

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:36 AM UTC · Completed 8:47 AM UTC

Commit: 3d72119 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $2.30

Comment thread internal/forge/forge.go
// diff. Nil when the thread no longer maps onto the current diff (for
// example an outdated GitHub thread) or has no line position.
Line *int `json:"line"`
// StartSide and StartLine give the first line of a multi-line range.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] architectural-conflict

The PR description says the new ReviewThread fields reuse the entity-context-snapshot reviews vocabulary, but reviews.schema.json defines original_side and has no start_side, old_line or new_line. The code and docs make no such claim; the mismatch is in the PR description. Not changed by the latest push.

Suggested fix: Reword the PR description to say the fields adapt that vocabulary, or align the names with the schema.

}
thread.Line = note.Position.NewLine
thread.OriginalLine = note.Position.OldLine
thread.OldLine = note.Position.OldLine

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] backward-incompatible

On GitLab, line changes meaning without a key change: removed-line discussions now report the old-file line in line with side left, and original_line is always null. Documented with a breaking-change marker and migration note; remaining risk is external consumers still reading the old fields.

Suggested fix: Coordinate the fullsend-ai/agents migration to old_line and new_line with this release.

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Oct 22, 2026

This branch was successfully deployed

2 active deployments
site-preview — 3d721198 Deployed Oct 22, 2026 by github-actions[bot]
dev — 3d721198 Deployed Oct 22, 2026 by fullsend-ai-coder[bot] via playback #17606
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-merge All reviewers approved — ready to merge ready-for-review Triggers review agent dispatch risk/moderate PR risk: moderate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Normalize review-thread position metadata across GitHub and GitLab

1 participant