Skip to content

feat(#5989): Jira comment write support + tracker.Client for Jira - #5996

Merged
ralphbean merged 19 commits into
mainfrom
agent/5989-jira-tracker-client
Aug 11, 2026
Merged

feat(#5989): Jira comment write support + tracker.Client for Jira#5996
ralphbean merged 19 commits into
mainfrom
agent/5989-jira-tracker-client

Conversation

@ralphbean

Copy link
Copy Markdown
Member

Summary

  • Adds markdown<->ADF conversion (internal/forge/jira/adf.go) using
    goldmark, since Jira Cloud's comment/description fields require Atlassian
    Document Format rather than markdown.
  • Adds CreateComment/UpdateComment to the Jira REST client
    (internal/forge/jira/client.go), using Jira Cloud REST v3.
  • Adds tracker.JiraClient (internal/tracker/jira_client.go), a
    tracker.Client implementation backed by the Jira client, mapping
    (project, number) to the Jira issue key PROJECT-NUMBER.

Stacked on #5993 (adds number to tracker.Client.UpdateComment, needed
because Jira's update-comment endpoint requires the issue key alongside the
comment ID). Base branch is agent/5988-tracker-client so this shows as
stacked; will need rebasing onto main once #5993 merges.

Out of scope (per #5989): forge.Client for Jira (Jira isn't a forge), and
CLI wiring (tracked separately as a follow-up to #5991).

Closes #5989.

Test plan

  • go test ./internal/forge/jira/... ./internal/tracker/...
  • go build ./...
  • go vet ./...
  • pre-commit run (gofmt, go vet) on changed files
  • go test ./... — passes except two pre-existing, unrelated failures
    also present on origin/main (internal/scaffold
    TestFileModeMatchesFilesystem, internal/runtime
    TestDummyRuntime_Bootstrap/TestDummyRuntime_ClearIterationArtifacts)

@ralphbean
ralphbean requested a review from a team as a code owner August 6, 2026 19:58
@ralphbean ralphbean added the fullsend-fix Enables automatic bot-triggered fix runs on human-authored PRs label Aug 6, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add Jira comment write support via ADF conversion and tracker JiraClient

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add Markdown↔ADF conversion utilities to support Jira Cloud comment/description bodies.
• Add Jira REST v3 create/update comment support, converting outbound markdown to ADF.
• Introduce tracker.JiraClient adapter mapping (project, number) to Jira issue keys.
Diagram

graph TD
  A["Tracker consumers"] --> B["tracker.JiraClient"] --> C["jira.LiveClient"] --> D{{"Jira Cloud REST v3"}}
  B --> E["Markdown/ADF utils"] --> F["goldmark parser"]
  C --> E
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Reuse/Export jirapoll ADF walker
  • ➕ Avoids duplicate ADF-to-text logic and keeps parsing behavior consistent across packages
  • ➕ Potentially reduces long-term maintenance cost
  • ➖ Would require refactoring internal/jirapoll (private helpers/tests) outside the PR’s stated scope
  • ➖ Tightens coupling between tracker/forge and jirapoll internals
2. Use a dedicated ADF Go library (if available)
  • ➕ Could provide broader ADF coverage (tables/media/panels) and schema validation
  • ➕ Reduces custom conversion code
  • ➖ Adds a larger dependency surface; may not align with the limited ADF subset needed
  • ➖ May still require custom markdown mapping logic and behavior tuning

Recommendation: The PR’s approach is appropriate for the stated scope: use a well-known CommonMark parser (goldmark) and generate the minimal ADF subset Jira accepts for comments, while keeping read-side extraction bounded against attacker-controlled nesting. Reusing jirapoll’s walker is a reasonable future cleanup once the Jira tracker integration stabilizes, but deferring it avoids broad refactors in a feature PR.

Files changed (8) +1105 / -0

Enhancement (3) +396 / -0
adf.goImplement Markdown→ADF and ADF→plain-text conversion +252/-0

Implement Markdown→ADF and ADF→plain-text conversion

• Introduces MarkdownToADF to convert a CommonMark AST into an ADF "doc" structure compatible with Jira Cloud. Adds ADFToPlainText with a recursion depth cap and newline semantics for block nodes and hard breaks.

internal/forge/jira/adf.go

client.goAdd Jira REST v3 create/update comment methods +37/-0

Add Jira REST v3 create/update comment methods

• Adds CreateComment and UpdateComment to LiveClient, posting/putting Jira Cloud comment bodies as ADF after converting from markdown.

internal/forge/jira/client.go

jira_client.goAdd tracker.Client adapter backed by Jira client +107/-0

Add tracker.Client adapter backed by Jira client

• Implements tracker.Client for Jira by mapping (project, number) to issue keys (PROJECT-N). Converts Jira ADF issue/comment bodies to plain text and preserves original markdown on comment creation.

internal/tracker/jira_client.go

Tests (3) +706 / -0
adf_test.goAdd unit tests for ADF conversion utilities +426/-0

Add unit tests for ADF conversion utilities

• Adds coverage for block/inline markdown conversions (headings, lists, links, code, breaks) and for ADFToPlainText behavior including deep-nesting safety.

internal/forge/jira/adf_test.go

comment_test.goTest create/update Jira comment API calls and error mapping +95/-0

Test create/update Jira comment API calls and error mapping

• Adds REST handler-based tests validating request shape (ADF doc body), HTTP methods/paths, and NotFound error mapping for create/update comment operations.

internal/forge/jira/comment_test.go

jira_client_test.goAdd tests for tracker JiraClient adapter behavior +185/-0

Add tests for tracker JiraClient adapter behavior

• Uses a hand-written fake Jira client to test issue key mapping, ADF-to-text conversion, create comment body preservation, and update comment argument wiring.

internal/tracker/jira_client_test.go

Other (2) +3 / -0
go.modAdd goldmark CommonMark parser dependency +1/-0

Add goldmark CommonMark parser dependency

• Adds github.com/yuin/goldmark to support parsing markdown into an AST for ADF conversion.

go.mod

go.sumRecord goldmark dependency checksums +2/-0

Record goldmark dependency checksums

• Updates module checksums for the newly added goldmark dependency.

go.sum

@qodo-code-review

qodo-code-review Bot commented Aug 6, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Credential-bearing browse URLs ✓ Resolved 🐞 Bug ⛨ Security
Description
JiraClient concatenates an unvalidated baseURL into Issue.URL, so a baseURL containing userinfo
(e.g. https://user:token@host) will propagate credentials into returned browse links. Those URLs are
commonly logged, displayed, or persisted downstream, which can expose secrets.
Code

internal/tracker/jira_client.go[R53-55]

+		Body:   jira.ADFToPlainText(issue.Fields.Description),
+		URL:    c.baseURL + "/browse/" + key,
+		Labels: issue.Fields.Labels,
Relevance

●●● Strong

Repo often accepts URL/secret-hardening; preventing userinfo propagation into logged URLs is a clear
security fix.

PR-#5953
PR-#1982
PR-#736

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The tracker Jira adapter stores baseURL via TrimRight and then concatenates it into Issue.URL
without checking for embedded credentials; the existing Jira REST client explicitly rejects
credential-bearing base URLs, showing this repo considers that a security requirement.

internal/tracker/jira_client.go[30-56]
internal/forge/jira/client.go[75-99]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`tracker.NewJiraClient` stores `baseURL` without parsing/validation and `GetIssue` uses it directly to build `Issue.URL`. If an operator passes a credential-bearing URL (userinfo), those credentials become part of the returned browse URL.

### Issue Context
The Jira REST client already treats base URLs as security-sensitive and rejects embedded credentials.

### Fix Focus Areas
- internal/tracker/jira_client.go[30-56]

### Suggested fix
- Parse `baseURL` with `net/url` in `NewJiraClient` (or a small helper).
- Reject `u.User != nil` (embedded credentials).
- (Optional, align with jira client policy) Require `https` unless host is loopback.
- When building the browse URL, use `url.PathEscape(key)` to avoid path-breaking characters if inputs are ever malformed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Comment body format mismatch ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
JiraClient.CreateComment overwrites the returned Comment.Body with the input markdown, while
ListComments/fromJiraComment returns plain text derived via ADFToPlainText. This makes
tracker.Comment.Body inconsistent across methods for Jira and can lead callers to mis-handle
comparisons, caching, or follow-up updates.
Code

internal/tracker/jira_client.go[R83-85]

+	result := fromJiraComment(*comment)
+	result.Body = body
+	return &result, nil
Relevance

●● Moderate

Inconsistent Body semantics may be intentional (avoid lossy conversion); behavior change could
ripple to callers.

PR-#3820
PR-#5778

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
CreateComment explicitly overwrites Body with the markdown input, but fromJiraComment always
converts Jira comment bodies to plain text using ADFToPlainText; meanwhile, the forge adapter
returns comment bodies directly, implying callers may expect Body to have a stable meaning within an
implementation.

internal/tracker/jira_client.go[73-107]
internal/forge/jira/adf.go[184-199]
internal/tracker/forge_client.go[57-89]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`JiraClient.CreateComment` sets `result.Body = body` (markdown input), but `ListComments` returns `Body` as `jira.ADFToPlainText(c.Body)` (plain text). The same `tracker.Comment.Body` field therefore changes meaning depending on the call path.

### Issue Context
`tracker.Client` does not document a per-backend body format, and the forge-backed adapter returns the backend body directly.

### Fix Focus Areas
- internal/tracker/jira_client.go[73-107]
- internal/tracker/forge_client.go[57-89]

### Suggested fix options (pick one)
1) **Consistency-first (recommended):** Remove `result.Body = body` so CreateComment returns the same representation as ListComments (plain text via `ADFToPlainText`).
2) **Contract-first:** Keep the override, but update package/interface docs in `internal/tracker/tracker.go` and `JiraClient.CreateComment` docstring to explicitly state the Jira behavior (CreateComment returns submitted markdown; ListComments returns plain text).

Also update/extend tests to enforce the chosen contract.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context
✅ Compliance rules (platform): 54 rules

Grey Divider

Tip of the day
💡 Did you know, you can group findings by type and pick your Finding display, from Minimal to Full

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread internal/tracker/jira_client.go Outdated
Comment thread internal/tracker/jira_client.go

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Automated review sweep — 1 HIGH and 2 MEDIUM findings, focused on the new ADF conversion/write path and its overlap with existing jirapoll code.

Comment thread internal/forge/jira/adf.go Outdated
Comment thread internal/forge/jira/adf.go
Comment thread internal/forge/jira/adf.go
@ralphbean

Copy link
Copy Markdown
Member Author

Ran this against a real Jira instance (stage-redhat) to sanity check the whole path: GetIssue, ListComments, CreateComment, UpdateComment against KONFLUX-13045. All four worked end to end, including the markdown->ADF conversion on create and the round trip back through ADFToPlainText on a follow-up ListComments.

Posted a comment with bold/italic/link/list/fenced-code, then edited it, to check both directions: https://stage-redhat.atlassian.net/browse/KONFLUX-13045?focusedCommentId=17718459

Oh, and one thing I noticed along the way: that issue has an existing comment with an ADF table in it, and ListComments flattens it to separator-less text (cells run together). Table markup isn't in the block/inline vocabulary adf.go documents as supported, so that tracks with the design, but wanted to flag it here in case tables show up often enough in practice to be worth a follow-up issue.

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Automated review sweep — 2 HIGH and 2 MEDIUM findings on the ADF conversion/comment-write path and the tracker.Client Jira adapter.

Comment thread internal/forge/jira/adf.go Outdated
Comment thread internal/tracker/jira_client.go Outdated
Comment thread internal/forge/jira/adf.go
Comment thread internal/forge/jira/adf.go Outdated

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Automated review sweep — 1 HIGH and 4 MEDIUM new findings, all on the markdown→ADF write path (models: Claude, Gemini). The four still-open items from the previous sweep (parse-time DoS, dropped updateAuthor, silent block-level drop, unvalidated hrefs) remain valid at this head and are not re-posted.

Comment thread internal/forge/jira/adf.go Outdated
Comment thread internal/forge/jira/adf.go Outdated
Comment thread internal/forge/jira/adf.go
Comment thread internal/forge/jira/adf.go Outdated
Comment thread internal/forge/jira/adf.go Outdated
Base automatically changed from agent/5988-tracker-client to main August 10, 2026 17:39
ralphbean added a commit that referenced this pull request Aug 10, 2026
… links

Addresses review feedback from waynesun09 on PR #5996:

- Bound goldmark's Parse() input size: the existing maxADFWriteDepth cap
  only bounded the post-parse AST walk, but Parse() itself is ~O(N^2) on
  deeply nested blockquotes and dominates the cost long before that cap is
  ever reached (benchmarked at ~3.2s to parse 80,000 nesting levels).
- Fall back to a plain-text paragraph for block-level markdown outside the
  supported vocabulary (e.g. raw HTML blocks), instead of silently
  dropping it, mirroring walkInline's existing plain-text fallback for
  unknown inline nodes.
- Reject unsafe link schemes (javascript:, data:, vbscript:, ...) on both
  markdown links and autolinks, allowing http/https/mailto and schemeless
  relative/anchor links through.
- Make blockquote/listItem content schema-conformant: headings degrade to
  bold paragraphs, nested blockquotes flatten into their parent, and
  thematic breaks are dropped, since ADF's schema for those containers
  only allows paragraph/list/codeBlock/media children and Jira Cloud
  rejects anything else with a 400.
- Drop a container (blockquote/bulletList/orderedList/listItem) entirely
  rather than emit it with empty content, which ADF's minItems: 1 also
  forbids — reachable via the depth cutoff above or an unsupported-only
  child.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
ralphbean added a commit that referenced this pull request Aug 10, 2026
Addresses review feedback from waynesun09 on PR #5996: fromJiraComment set
tracker.Comment.Author to the original author even when a comment was
edited, ignoring Jira's UpdateAuthor field. jirapoll/discover.go already
attributes edit-detected events to the editor for this reason (ADR
0054) — someone with Edit-All-Comments can rewrite another user's
comment, and running that rewritten content under the original author's
identity would be a way to launder untrusted input through a trusted
name. No consumer of tracker.Client exists yet, but nothing about
Comment.Author signals this caveat, so this closes the gap before a
future authorization-sensitive caller trips over it.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 10, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:51 PM UTC · Completed 7:05 PM UTC

Commit: 8936e99 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review

Findings

Low

  • [scope-authorization-mismatch] go.mod:16 — The goldmark dependency was not explicitly named in issue Add Jira comment write support and implement tracker.Client for Jira #5989's scope, though the issue says "needs a markdown→ADF conversion step," making a CommonMark parser a predictable implementation choice. goldmark is the standard Go CommonMark parser and adds zero transitive dependencies (only 2 lines in go.sum). Noting for visibility — no action required before merge.

  • [injection] internal/forge/jira/adf.go:952 — In applyADFMarks, when rendering a link mark in the ADF→Markdown direction, the href value from ADF is interpolated into Markdown link syntax [text](href) without escaping ) in the href. A crafted ADF body with ) in the href could break out of the link syntax and inject Markdown structure. Practical impact is limited: the output is consumed by MarkdownToADF (which applies isSafeHref), and the result goes back to Jira as ADF, not to a browser.
    Remediation: Escape ) as %29 in the href portion of the Markdown link.

Previous run

Looks good to me

Previous run (2)

Review

Findings

Low

  • [intent-alignment] PR metadata — PR is labeled fullsend-fix but uses feat(#5989): prefix in title. The PR adds a new feature (Jira comment write support + tracker.Client for Jira), not a bugfix. The label should match the conventional commit type.

Labels: PR adds Jira tracker client integration (new feature)

Previous run (3)

Review

Findings

Low

  • [edge-case] internal/forge/jira/adf.go:564ADFToMarkdown's codeBlock rendering always emits a newline between the opening and closing fences ("```" + lang + "\n" + adfCodeBlockText(node) + "\n```"), even when adfCodeBlockText returns an empty string. This produces a code block containing a single blank line rather than an empty code block. An ADF-side empty codeBlock round-tripped through ADFToMarkdown and back would gain a blank line it did not have before. Practical impact is minimal since empty code blocks in real Jira comments are rare.

  • [edge-case] internal/forge/jira/adf.go:661applyADFMarks does not escape Markdown-significant characters in text before wrapping it in formatting delimiters. If an ADF text node contains literal *, _, `, [, or ] characters from user-authored Jira content (not from a MarkdownToADF round-trip), wrapping in **...** or [...]() can produce ambiguous Markdown. This is a design limitation for the Jira-native ADF→Markdown direction, not the round-trip use case.

  • [intent-alignment] PR metadata — PR is labeled fullsend-fix but uses feat(#5989): prefix in title. The PR adds a new feature (Jira comment write support + tracker.Client), not a bugfix. The label should match the conventional commit type.

Previous run (4)

Review

Findings

Low

  • [intent-alignment] PR metadata — PR is labeled fullsend-fix but uses feat(#5989): prefix in title. The PR adds a new feature (Jira comment write support + tracker.Client), not a bugfix. The label should match the conventional commit type.

  • [test-consistency] internal/forge/jira/adf_test.go — Uses stdlib testing conventions (custom helpers asMap/asSlice/mustADF, no t.Parallel()) while comment_test.go and client_test.go in the same package use testify assert/require with t.Parallel(). Mixed testing styles within a single package.

Previous run (5)

Review

Findings

High

  • [API contract violation] internal/tracker/jira_client.go:97JiraClient.GetIssue (and ListComments, CreateComment, UpdateComment) passes errors from the underlying jira client through without wrapping forge.ErrNotFound into tracker.ErrNotFound. The tracker.Client interface contract requires implementations to return errors satisfying tracker.IsNotFound. ForgeClient does this via wrapNotFound(); JiraClient does not. A caller using tracker.IsNotFound(err) to detect missing issues will get false negatives when the backend is Jira, because the error only satisfies forge.IsNotFound, not tracker.IsNotFound. The test TestJiraClient_GetIssue_NotFound checks errors.Is(err, forge.ErrNotFound) rather than tracker.IsNotFound(err), masking the contract violation.
    Remediation: Add a wrapNotFound call (or equivalent) around errors returned from c.jira.GetIssue/ListComments/CreateComment/UpdateComment in all four JiraClient methods, the same way ForgeClient does. Update the test to assert tracker.IsNotFound(err).

Medium

  • [type-assertion mismatch] internal/forge/jira/adf.go:559ADFToMarkdown's heading and ordered-list branches assert attrs["level"].(int) and attrs["order"].(int), but when ADF arrives from Jira's REST API via json.Decode into an any-typed field (Issue.Fields.Description, Comment.Body), JSON numbers are decoded as float64, not int. The assertions silently fail, causing every heading to render as level 1 (#) and every ordered list to start at 1 regardless of the actual values. The tests pass only because they construct ADF maps with Go int literals, never through JSON deserialization.
    Remediation: Add float64 fallback assertions after the int assertion fails (e.g., if l, ok := attrs["level"].(float64); ok { level = int(l) }). Apply the same fix to attrs["order"].

Low

  • [edge-case] internal/forge/jira/adf.go:134Paragraph/TextBlock cases in convertBlockNode unconditionally emit content: adfInlineContent(...). If inline content is empty (e.g., a paragraph containing only an unsupported inline element like an image node with no alt text), the resulting content: [] violates ADF's paragraph minItems: 1 and Jira Cloud would reject the write with a 400.

  • [intent-alignment] PR metadata — PR is labeled fullsend-fix but uses feat(#5989): prefix in title. The PR adds a new feature (Jira comment write support + tracker.Client), not a bugfix. The label should match the conventional commit type.

  • [test-consistency] internal/forge/jira/adf_test.go — Uses stdlib testing conventions (custom helpers asMap/asSlice/mustADF, no t.Parallel()) while comment_test.go in the same package uses testify assert/require with t.Parallel(). Mixed testing styles within a single package.


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
Previous run (6)

Review

Findings

Low

  • [edge-case] internal/forge/jira/adf.go:100Paragraph/TextBlock cases in convertBlockNode unconditionally emit content: adfInlineContent(...). If inline content is empty (e.g. a paragraph containing only an unsupported inline element like an image node), the resulting content: [] violates ADF's paragraph minItems: 1 and Jira Cloud would reject the write with a 400. Rare in practice but possible from edge-case markdown.

  • [test-consistency] internal/forge/jira/adf_test.go — Uses stdlib testing conventions (custom helpers asMap/asSlice/mustADF, no t.Parallel()) while comment_test.go in the same package uses testify assert/require with t.Parallel(). Mixed testing styles within a single package.

  • [intent-alignment] PR metadata — PR is labeled fullsend-fix but uses feat(#5989): prefix in title. The PR adds a new feature (Jira comment write support + tracker.Client), not a bugfix. The label should match the conventional commit type.

Previous run (7)

Review

Findings

High

  • [logic-error] internal/forge/jira/adf.go:308 — In the *ast.Link case of walkInline, marks = withMark(marks, ...) reassigns the loop-scoped marks parameter variable rather than shadowing it with a local. Because the reassignment persists across loop iterations, subsequent siblings of the Link node in the same for c := parent.FirstChild() loop inherit the link mark. For markdown like "before [link](url) after", the text node for " after" would incorrectly receive a link mark pointing at url. The *ast.AutoLink case correctly uses a local linkMarks variable, and *ast.Emphasis passes withMark(marks, ...) inline without reassigning.
    Remediation: Shadow marks with a local variable inside the *ast.Link case, matching the *ast.AutoLink pattern:
    case *ast.Link:
        dest := string(v.Destination)
        linkMarks := marks
        if isSafeHref(dest) {
            attrs := map[string]any{"href": dest}
            if len(v.Title) > 0 {
                attrs["title"] = string(v.Title)
            }
            linkMarks = withMark(marks, map[string]any{"type": "link", "attrs": attrs})
        }
        walkInline(v, source, linkMarks, out, depth+1)

Medium

  • [test-gap] internal/forge/jira/adf_test.go:798TestMarkdownToADF_Link uses "see [the docs](https://example.com/docs)" but only asserts that a link mark exists on the "the docs" text node. It does not verify that the preceding "see " text node does NOT carry a link mark, nor does it include text after the link. Because of the marks-leak bug above, a test with text after the link (e.g. "see [link](url) and more") that asserts " and more" has no link mark would catch the issue.

Low

  • [edge-case] internal/forge/jira/adf.go:137*ast.Paragraph and *ast.TextBlock unconditionally emit {"type":"paragraph","content":<inline>}. If inline content is empty (e.g. a paragraph containing only an unsupported inline element), the resulting content: [] violates ADF's paragraph minItems: 1 and Jira Cloud would reject the write with a 400. Rare in practice but possible from edge-case markdown.
  • [URL-scheme-bypass] internal/forge/jira/adf.go:335isSafeHref uses an allowlist approach (empty/http/https/mailto) with url.Parse and strings.ToLower for scheme normalization. No bypass found; the design is sound.
  • [UTF-8-boundary-walk] internal/forge/jira/adf.go:95truncateForParse walks back to a valid UTF-8 rune boundary before truncation. Worst-case walkback is 3 bytes. The function is sound.

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.

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review-squad sweep (3 agents: Claude, Grok) — 3 HIGH and 4 MEDIUM new/unresolved findings on the ADF write path and the tracker.Client Jira adapter, all independently verified against this head. One (the link-mark leak) was already raised by an earlier automated review but never landed as an inline comment (GitHub 422'd it against a stale line number); reposting here at the correct current line. The twelve findings from prior review rounds that are already fixed are not repeated.

Comment thread internal/forge/jira/adf.go
Comment thread internal/forge/jira/adf.go
Comment thread internal/forge/jira/adf.go Outdated
Comment thread internal/tracker/jira_client.go Outdated
Comment thread internal/tracker/jira_client.go
Comment thread internal/forge/jira/adf.go
@ralphbean

Copy link
Copy Markdown
Member Author

Re: #5996 (comment)

The high finding here is the same marks-leak bug flagged inline (and already fixed in a1f367a, with a regression test asserting text after the link carries no mark, which covers the test-gap note too). The truncateForParse note is stale now — that function's gone as of d26126e, which fails closed on oversized input instead of truncating it.

The one still-open item is the low one: a paragraph whose only inline child gets dropped (e.g. an image with no alt text and no other content) could emit content: [], which ADF's schema disallows. Leaving that one for now rather than fixing it here — flagging it as a known gap.

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 10, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:08 PM UTC · Completed 9:24 PM UTC

Commit: ba66455 · View workflow run →

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review August 10, 2026 21:24

Superseded by updated review

fullsend-ai-review[bot]

This comment was marked as outdated.

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

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Automated review sweep (review-only). 1 CRITICAL, 1 HIGH, 1 MEDIUM finding below, none overlapping prior comments on this PR.

Comment thread internal/tracker/jira_client.go Outdated
Comment thread internal/tracker/jira_client.go
Comment thread internal/forge/jira/adf.go
Jira Cloud's comment/description fields require Atlassian Document
Format (ADF), not markdown, so writing a comment needs a
markdown-to-ADF converter, and reading tracker.Client's plain-text
Issue.Body/Comment.Body back out needs an ADF-to-plain-text one.

MarkdownToADF parses source with goldmark and walks its AST into ADF
doc/paragraph/heading/list/blockquote/codeBlock nodes plus
strong/em/code/link/hardBreak inline marks.

ADFToPlainText is a fresh implementation, not a refactor of
jirapoll's existing extractPlainText/walkADFNode: #5989 scopes out
read-side changes, and moving the private helpers would touch
jirapoll and its tests. Same maxADFDepth=50 recursion cap, for the
same reason (attacker-controlled nesting).

Adds github.com/yuin/goldmark, a small pure-Go CommonMark parser
already widely used in the Go ecosystem (e.g. Hugo), rather than
hand-rolling markdown parsing.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Jira Cloud REST v3 comment endpoints: POST /issue/{key}/comment to
create, PUT /issue/{key}/comment/{id} to update. Both take the body
as ADF, converted from the markdown callers pass in via
MarkdownToADF (added in a prior commit). 404/403 already unwrap to
forge.ErrNotFound/ErrForbidden via APIError.Unwrap, so no new error
handling is needed beyond the existing wrapping convention.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
JiraClient adapts a Jira client to tracker.Client, mapping
(project, number) to the Jira issue key "PROJ-123" and converting
between tracker.Issue/Comment's plain-text Body and Jira's ADF via
jira.ADFToPlainText / jira.LiveClient's ADF-based comment writes.

CreateComment sets the returned Comment's Body to the caller's
original markdown rather than round-tripping through the ADF Jira
echoes back, since that round trip is lossy and the caller already
has the exact text verbatim. Comment.HTMLURL is left unset — Jira's
comment permalink format isn't confirmed against real Cloud
behavior, so guessing at a URL risks a broken link.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…lient

tracker.JiraClient stored baseURL with only strings.TrimRight, unlike
jira.LiveClient's ValidateBaseURL, so a base URL containing embedded
credentials (https://user:token@host) would propagate them into every
Issue.URL this client returns. Export jira's existing validation and
reuse it in NewJiraClient.

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
CreateComment overwrote the returned Comment.Body with the input
markdown, while ListComments/GetIssue always derive Body via
ADFToPlainText. That made tracker.Comment.Body mean different things
depending on which method produced it. Drop the override so
CreateComment returns the same plain-text representation everywhere.

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
adfBlockContent/convertBlockNode/walkInline recursed once per markdown
nesting level with no depth cap, unlike the read-side ADFToPlainText/
walkADFNode pair added in the same file, which caps recursion at
maxADFDepth for exactly this reason. Deeply nested input (e.g.
thousands of blockquote markers) showed clear superlinear blowup.
Thread a depth counter through the write-path converters and drop
content past maxADFWriteDepth, mirroring the existing "drop what we
don't support" behavior for unrecognized node types.

Also fix a comment on maxADFDepth that pointed at a package doc
comment explaining the read/write duplication with jirapoll — that
doc comment doesn't exist. State the reasoning directly instead.

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
… links

Addresses review feedback from waynesun09 on PR #5996:

- Bound goldmark's Parse() input size: the existing maxADFWriteDepth cap
  only bounded the post-parse AST walk, but Parse() itself is ~O(N^2) on
  deeply nested blockquotes and dominates the cost long before that cap is
  ever reached (benchmarked at ~3.2s to parse 80,000 nesting levels).
- Fall back to a plain-text paragraph for block-level markdown outside the
  supported vocabulary (e.g. raw HTML blocks), instead of silently
  dropping it, mirroring walkInline's existing plain-text fallback for
  unknown inline nodes.
- Reject unsafe link schemes (javascript:, data:, vbscript:, ...) on both
  markdown links and autolinks, allowing http/https/mailto and schemeless
  relative/anchor links through.
- Make blockquote/listItem content schema-conformant: headings degrade to
  bold paragraphs, nested blockquotes flatten into their parent, and
  thematic breaks are dropped, since ADF's schema for those containers
  only allows paragraph/list/codeBlock/media children and Jira Cloud
  rejects anything else with a 400.
- Drop a container (blockquote/bulletList/orderedList/listItem) entirely
  rather than emit it with empty content, which ADF's minItems: 1 also
  forbids — reachable via the depth cutoff above or an unsupported-only
  child.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Addresses review feedback from waynesun09 on PR #5996: fromJiraComment set
tracker.Comment.Author to the original author even when a comment was
edited, ignoring Jira's UpdateAuthor field. jirapoll/discover.go already
attributes edit-detected events to the editor for this reason (ADR
0054) — someone with Edit-All-Comments can rewrite another user's
comment, and running that rewritten content under the original author's
identity would be a way to launder untrusted input through a trusted
name. No consumer of tracker.Client exists yet, but nothing about
Comment.Author signals this caveat, so this closes the gap before a
future authorization-sensitive caller trips over it.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 11, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:36 PM UTC · Completed 3:49 PM UTC

Commit: 7e932b2 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review-squad sweep, round 4 (3 agents; models: Claude, Grok) — 4 HIGH and 3 MEDIUM new findings, all independently verified by execution against this head (7e932b2). All prior-round fixes were re-verified and hold; every finding below is on the new ADFToMarkdown surface and its round trip with MarkdownToADF, not a regression. The common theme: the pair is only tested against ADF shapes goldmark itself produces, never the shapes Jira Cloud's editor actually emits (split mark runs, panels, mentions, embedded fences) — fixtures sourced from real Jira Cloud responses would catch most of these classes at once.

One note on an existing unreplied thread rather than a new comment: the applyADFMarks no-markdown-escaping finding (raised earlier as low) has new escalation evidence — an ADF paragraph whose literal text is # not a heading round-trips through MarkdownToADF into a real heading node, and - /> /1. prefixes similarly change node types. That's block-structure corruption, not just mark leakage; worth re-grading to medium. Two LOW findings (trailing-hardBreak literal backslash; &nbsp;-only input bypassing the fail-closed empty-doc guarantee) and one LOW consistency note (Jira-backed tracker.Comment.CreatedAt is not RFC3339, unlike the forge backends) were held back per the medium-and-above posting scope.

Comment thread internal/forge/jira/adf.go
Comment thread internal/forge/jira/adf.go
Comment thread internal/forge/jira/adf.go Outdated
Comment thread internal/forge/jira/adf.go
Comment thread internal/forge/jira/adf.go
Comment thread internal/forge/jira/adf.go
Comment thread internal/forge/jira/adf.go
@ralphbean

Copy link
Copy Markdown
Member Author

/fs-fix address all findings

@fullsend-ai-coder

fullsend-ai-coder Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

🤖 Finished Fix · ✅ Success · Started 4:55 PM UTC · Completed 5:06 PM UTC

Commit: 7e932b2 · View workflow run →

…Markdown

- ADFToMarkdown's codeBlock rendering no longer emits a spurious blank
  line between fences when the code block has no text children.
- applyADFMarks now escapes markdown-significant characters (*, _, `,
  [, ], \) in text nodes before wrapping in formatting delimiters, so
  Jira-native ADF with literal special characters renders unambiguously.
  Text inside a code mark is left unescaped since code spans are verbatim.

Addresses review feedback on #5996
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

🔧 Fix agent — iteration 2 (human-triggered)

Addressed 2 of 3 review findings: fixed empty codeBlock blank-line edge case and added markdown-significant character escaping in applyADFMarks. Disagreed with 1 finding (fullsend-fix label is operational, not commit-type).

Fixed (2):

  1. ADFToMarkdown codeBlock emits blank line for empty code blocks (internal/forge/jira/adf.go): Added empty-text guard in codeBlock rendering: when adfCodeBlockText returns empty, the fences are joined without an intervening newline, producing a valid empty code block instead of one with a spurious blank line.
  2. applyADFMarks does not escape Markdown-significant characters (internal/forge/jira/adf.go): Added escapeMDText to escape *, _, `, [, ], and \ before wrapping in formatting delimiters. Text inside a code mark is left unescaped since code spans are verbatim. Added hasCodeMark helper to detect the code mark.

Disagreed (1):

  1. PR labeled fullsend-fix but uses feat(Add Jira comment write support and implement tracker.Client for Jira #5989): prefix in title: The fullsend-fix label is an operational label described as 'Enables automatic bot-triggered fix runs on human-authored PRs' — it controls CI behavior, not commit type. The feat prefix correctly describes the PR's content (new feature). Additionally, editing labels is outside the fix agent's scope.

Tests: passed

Decision points
  • Whether to escape markdown chars unconditionally or only when no code mark is present (alternatives: Escape always, Escape unless code mark present, Escape only when marks are present; rationale: Code spans are verbatim in Markdown — escaping inside backticks would insert visible backslashes. Skipping escape only for code marks matches Markdown semantics.)

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 Aug 11, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:07 PM UTC · Completed 5:19 PM UTC

Commit: 63bd8eb · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the type/feature New capability request label Aug 11, 2026
@waynesun09

Copy link
Copy Markdown
Member

/fs-fix

Follow-up on 63bd8eb ("handle empty codeBlock and escape markdown chars in ADFToMarkdown"): the empty-codeBlock fix is complete, but the escaping fix is partial. Two gaps remain in internal/forge/jira/adf.go, both verified by execution at head 63bd8eb, plus the 7 inline findings from review #5996 (review) are still open. Please fix the following:

1. Escape line-leading block syntax in ADFToMarkdown text output (the block-structure corruption case is still live)

escapeMDText covers inline characters (\ * _ [ ]) only. Text that begins a line with block syntax still changes node type on a round trip through MarkdownToADF`:

  • ADF paragraph text # not a heading → round-trips into a real level-1 heading node
  • - not a list item → becomes a bulletList
  • > not a quote → becomes a blockquote (1. , + , * prefixes are the same family)

Fix: in unmarked text, backslash-escape block-syntax characters when they appear at the start of the rendered line (\#, \-, \>, \+, digit followed by \.), i.e. at the start of the text node's output and after each emitted newline. Add round-trip regression tests asserting these stay paragraphs.

2. Escape & so HTML entities survive the round trip

Literal ADF text &copy; and &amp; stay literal is emitted unescaped; MarkdownToADF's textValue then resolves entities, so it reads back as © and & stay literal — text mutation. Fix: add &\& (CommonMark backslash-escapes &) or &&amp; to mdEscaper, keeping code-marked text verbatim as today. Add a regression test.

3. The 7 open findings from the round-4 inline review (4 HIGH, 3 MEDIUM), each with repro details and suggested fixes in its thread:

  • adf.go:584 HIGH — unknown container nodes (panel, table, expand, taskList) drop all their content in ADFToMarkdown; recurse into block children first, keep the inline fallback for leaf levels, and fix the false doc-comment claim
  • adf.go:635 HIGH — per-text-node mark wrapping corrupts round trips (adjacent same-marked nodes → literal ** in text; reachable from this PR's own soft-break output); coalesce adjacent nodes with identical mark sets and hoist leading/trailing whitespace outside delimiters
  • adf.go:562 HIGH — code-fence breakout: content containing a ``` line escapes the fixed 3-backtick fence; use max(3, longest backtick run + 1) for both fences and sanitize `attrs.language` to a conservative token
  • adf.go:554 HIGH — ADFToMarkdown panics on heading.attrs.level = -1 (strings.Repeat negative count); clamp level to [1,6], bound orderedList order similarly
  • adf.go:651 MEDIUM — inline atoms (mention, emoji, status, date, inlineCard) dropped; emit attrs.text (emoji fallback attrs.shortName, inlineCard attrs.url)
  • adf.go:356 MEDIUM — withMark type-only dedup rewrites a nested autolink's destination to the outer link's href; make the link mark replace the existing one (inner-most wins) or document/test outer-wins
  • adf.go:300 MEDIUM — image destination URLs dropped by MarkdownToADF; handle *ast.Image as alt text with a link mark when isSafeHref allows

All fixes should come with local regression tests in adf_test.go (no workflow changes needed).

@fullsend-ai-coder

fullsend-ai-coder Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

🤖 Finished Fix · ❌ Failure · Started 6:12 PM UTC · Completed 6:26 PM UTC

Commit: 63bd8eb · View workflow run →

The cfmint behaviour-test driver (#6037) writes FULLSEND_MINT_URL as a
repo-level variable on test-repo via `fullsend github setup`. Repo
variables take precedence over org-level variables of the same name,
but nothing deleted it afterward. Since e2e admin tests and behaviour
tests share the same org pool and both use a repo named test-repo, a
leaked repo-level FULLSEND_MINT_URL from a prior behaviour-test run
shadows the org-level value that admin install sets, and points to an
already-torn-down CF Worker preview mint — breaking dispatch with a
DNS resolution failure for any run that later reuses the org.

Delete the repo variable in TeardownPerRepoInstall (immediate cleanup
after a per-repo driver run) and in CleanupStaleResources (defensive
cleanup so already-poisoned orgs in the pool self-heal).

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 11, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 6:24 PM UTC · Ended 6:36 PM UTC

Commit: 1230d66 · View workflow run →

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

⚠️ Post-fix script failed — Push rejected (exit code 1)

The fix agent completed, but the post-fix script failed before finishing.

Workflow run: https://github.com/fullsend-ai/.fullsend/actions/runs/31521387295

Details:
To https://github.com/fullsend-ai/fullsend.git
! [rejected] agent/5989-jira-tracker-client -> agent/5989-jira-tracker-client (fetch first)
error: failed to push some refs to 'https://github.com/fullsend-ai/fullsend.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
To https://github.com/fullsend-ai/fullsend.git
! [rejected] agent/5989-jira-tracker-client -> agent/5989-jira-tracker-client (stale info)
error: failed to push some refs to 'https://github.com/fullsend-ai/fullsend.git'
Please check the workflow logs for full details and retry with /fs-fix if appropriate.

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:24 PM UTC · Completed 6:36 PM UTC

Commit: 1230d66 · View workflow run →

Fixes 9 issues from the second round of review (waynesun09 plus two
automated review agents):

- Clamp heading level and ordered-list start to valid ranges instead of
  panicking or emitting out-of-spec Markdown.
- Compute the code-fence length from the longest backtick run in the
  block's content, and sanitize the language attr, so both can no
  longer be used to break out of the fence.
- Fall back to recursing into block children (then inline text) for
  unknown ADF container nodes (panel, table, expand, taskList, ...)
  instead of dropping their content.
- Render mention/emoji/status/date/inlineCard atom nodes as their
  visible text instead of vanishing them.
- Preserve an ADF Image's destination as a link on its alt text in
  MarkdownToADF, instead of dropping it.
- Make withMark replace an existing link mark in place (inner-most
  wins) rather than treating it as a duplicate, matching CommonMark's
  resolution for a Markdown link wrapping an autolink.
- Escape ampersand and line-leading block syntax (#, >, -, +, ordered
  list markers) in ADFToMarkdown's unmarked text, and resolve
  backslash escapes and HTML entity/numeric references in a single
  pass on the MarkdownToADF side — doing it in two passes let an
  escaped "&" get re-interpreted as the start of an entity reference.
- Coalesce adjacent inline nodes with identical mark sets before
  wrapping them, and hoist leading/trailing whitespace outside
  emphasis delimiters, so real Jira-Cloud ADF (which commonly splits
  one bold run across several same-marked text nodes) round-trips
  instead of corrupting into literal asterisks.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 11, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 6:59 PM UTC · Ended 7:15 PM UTC

Commit: 22e3027 · View workflow run →

@ralphbean

Copy link
Copy Markdown
Member Author

The automated fix run on this one failed partway through, so I went through all 9 findings by hand in 22e3027 — the two escaping gaps here plus the 7 from the inline review. Each one has a regression test in adf_test.go. Replied on each inline thread with specifics. Let me know if anything's still off.

Comment thread go.mod
Comment thread internal/forge/jira/adf.go
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:59 PM UTC · Completed 7:14 PM UTC

Commit: 22e3027 · View workflow run →

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approving after five review rounds at head 22e3027.

Code: All 29 findings raised across the review history (including 4 HIGH and 3 MEDIUM in the final round on the ADFToMarkdown surface) are fixed and regression-tested — each fix independently re-verified by execution at this head: block-prefix/entity escaping, panel/taskList content preservation, mark-run coalescing, fence-length computation with language-attr sanitization, heading/order clamping (no panic on malformed ADF), inline-atom rendering, inner-link-wins mark replacement, and image-as-link degradation. Round trips of normal documents are byte-stable; internal/forge/jira and internal/tracker suites pass.

API: Purely additive — new exported jira.MarkdownToADF/ADFToMarkdown/ADFToPlainText/ValidateBaseURL and tracker.NewJiraClient; tracker.Client contract satisfied end-to-end including the IsNotFound semantics and Body type. One new dependency (goldmark v1.8.5 — MIT, pure Go, the de-facto standard CommonMark parser).

Workflow impact: No .github/workflows/ changes; all checks green (behaviour, e2e, unit, codecov/patch, DCO, commit-lint). The one out-of-scope commit (1230d66, e2e test-repo variable cleanup) is small, well-justified test-infra hygiene.

Remaining open items are two LOW bot findings (unescaped ) in link hrefs — confirmed real but minor, same escaping family; and the goldmark scope-attribution note) — neither blocks merge and both are reasonable follow-ups.

@ralphbean
ralphbean added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 7975d22 Aug 11, 2026
30 of 35 checks passed
@ralphbean
ralphbean deleted the agent/5989-jira-tracker-client branch August 11, 2026 21:12
@fullsend-ai-retro

fullsend-ai-retro Bot commented Aug 11, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 9:15 PM UTC · Completed 9:43 PM UTC

Commit: 22e3027 · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro Analysis: PR #5996

What happened

This human-authored PR by ralphbean added markdown↔ADF conversion (internal/forge/jira/adf.go), Jira comment create/update support, and a tracker.JiraClient implementation. Over 5 days and 19 commits, it went through 6 review rounds from waynesun09 (a human orchestrating multi-agent review sweeps using Claude and Grok) plus 8 fullsend-ai-review[bot] reviews, surfacing 29+ findings across security, correctness, and ADF schema conformance.

The fix agent contributed 2 clean commits (e81ff80, 63bd8eb) addressing 6 findings with no regressions. A third fix agent run (31521387295) completed its work but lost a push race when ralphbean pushed an unrelated commit (e2e test cleanup) concurrently — --force-with-lease correctly prevented overwriting.

What went well

  • Review quality was high: Agent reviews found genuine DoS vulnerabilities, credential leaks, link-scheme bypasses, ADF schema violations, and mark-propagation bugs. Round 2 independently verified that Round 1’s DoS fix was insufficient by benchmarking goldmark’s Parse() separately from the post-parse walk — a nuanced finding.
  • Fix agent code was clean: Both successful fix commits addressed their assigned findings without introducing regressions. All 9 subsequent review findings after each fix commit were on pre-existing code, not on the fix agent’s additions.
  • Push safety worked: The failed fix run’s --force-with-lease correctly refused to overwrite the human’s concurrent commit. No data was lost.
  • Iterative deepening was effective: Each review round surfaced progressively deeper issues. When ADFToMarkdown was added mid-review (rebase onto main after refactor(#5988): introduce tracker.Client interface with forge adapter #5993 merged), Review round 6 immediately caught 7 new issues on that surface including code-fence breakout, heading-level panics, and round-trip corruption from un-coalesced marks.

Evidence for existing issues

  • agents#409 (Post-fix/post-code push scripts should fetch before force-with-lease retry): Fix run 31521387295 lost ~16 minutes of completed work because it couldn’t push after a concurrent human commit. A fetch-rebase-retry loop would have recovered the work.
  • fullsend#3025 (Review agent posts diminishing-value findings after approval): fullsend-ai-review[bot] posted 2 LOW findings alongside its approval at 15:49, which triggered fix agent run 31514865676 to address them. The fix was clean but consumed tokens for low-severity improvements during an active review cycle.

Proposals

1 new proposal filed (review agent 422 stale-diff handling).

Proposals filed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fullsend-fix Enables automatic bot-triggered fix runs on human-authored PRs ready-for-merge All reviewers approved — ready to merge type/feature New capability request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Jira comment write support and implement tracker.Client for Jira

2 participants