refactor: drop GitHub integration from issue-lifecycle, use swamp-club directly#1139
refactor: drop GitHub integration from issue-lifecycle, use swamp-club directly#1139
Conversation
…b directly
The issue-lifecycle extension and skill now operate directly on swamp-club
lab issues. Issues must already exist in swamp-club — the model fetches them
by sequential lab number via GET /api/v1/lab/issues/{number} and PATCHes the
type during triage to reflect the classification back.
- Delete extensions/models/_lib/issue_tracker.ts and every gh CLI call.
- Swap GlobalArgs: drop repo, issueNumber now refers to a swamp-club lab
issue. Version bumped to 2026.04.08.1 with an upgrade that strips repo.
- Rework SwampClubClient: operate on the lab number directly, add
fetchIssue() and updateType(). Drop the /ensure round-trip.
- Reconcile classification types to swamp-club's bug/feature/security.
Regressions become type=bug with an isRegression flag on the
classification record. unclear is gone — use confidence=low +
clarifyingQuestions instead.
- Remove ci_status, record_pr, fix methods and ciResults/fixDirective
resources. The ci_review phase is gone; implementing transitions
straight to done.
- Rewrite skill SKILL.md, triage.md, implementation.md and the extension
README for the swamp-club-first workflow.
Depends on swamp-club PATCH-type support in systeminit/swamp-club#374.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…b directly Mirrors the changes in systeminit/swamp#1139 — the @swamp/issue-lifecycle extension and skill now operate directly on swamp-club lab issues. Issues must already exist in swamp-club; the model fetches them by sequential lab number via GET /api/v1/lab/issues/{number} and PATCHes the type during triage to reflect the classification back. - Delete extensions/models/_lib/issue_tracker.ts and every gh CLI call. - Swap GlobalArgs: drop repo, issueNumber now refers to a swamp-club lab issue. Version bumped to 2026.04.08.1 with an upgrade that strips repo. - Rework SwampClubClient: operate on the lab number directly, add fetchIssue() and updateType(). Drop the /ensure round-trip. - Reconcile classification types to swamp-club's bug/feature/security. Regressions become type=bug with an isRegression flag on the classification record. unclear is gone — use confidence=low + clarifyingQuestions instead. - Remove ci_status, record_pr, fix methods and ciResults/fixDirective resources. The ci_review phase is gone; implementing transitions straight to done. - Update schemas_test.ts state machine tests to the new topology: happy path ends implementing→done, new completion gate test, old fix loop and ci_review tests removed. - Rewrite skill SKILL.md, triage.md, implementation.md, extension README.md, and manifest.yaml description/method list for the swamp-club-first workflow. Depends on swamp-club PATCH-type support in systeminit/swamp-club#374 (merged). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The _swampClub module-level singleton was keyed on nothing — once created for issue #N, every subsequent getSwampClub() call returned the same client regardless of the new issueNumber in globalArgs. Since user models are loaded via dynamic import() in the same process, the module stays cached across method calls, so running start for issue #10 and then issue #20 in the same session silently sent #20's lifecycle entries, type updates, and status transitions to #10. Drop the cache entirely and call createSwampClubClient directly at each use. The reachability check is a single 5s-timeout HTTP GET and runs once per method invocation — negligible next to the lifecycle POST already happening on the same code path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The swamp-club refactor accidentally dropped the final two lines of the
required copyright header ("You should have received a copy..." and
"with Swamp. If not, see..."). Per CLAUDE.md, every .ts file must carry
the full AGPLv3 header from FILE-LICENSE-TEMPLATE.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Adversarial Review
Critical / High
None found.
Medium
-
extensions/models/issue_lifecycle.ts:475-497—triagesilently skips swamp-club type update if server goes down betweenstartandtriage.
Thestartmethod (line 351) enforces swamp-club availability and throws if it's unreachable. Buttriage(line 479) checksif (sc)and silently skips theupdateType()andtransitionStatus()calls if the client can't be created. SincecreateSwampClubClientdoes a health check every time, a temporary network blip betweenstartandtriagemeans the swamp-club issue type is never updated, yet the local classification is persisted and the phase advances toclassified. The README states "swamp-club is the single source of truth" — this silent degradation contradicts that contract.
Breaking scenario: User runsstart(succeeds), server has a 30-second outage, user runstriage type=security— local state saysclassifiedwithtype: security, but swamp-club still shows the original type and status. No error is surfaced.
Suggested fix: Either throw (likestartdoes) to make swamp-club mandatory on every mutation, or at minimum log atwarninglevel that the type update was skipped. The same pattern applies toplan,iterate,approve,implement, andcomplete— all silently degrade. This is a conscious design choice vs. the old optional integration, so flagging for the author to confirm the intent. -
extensions/models/issue_lifecycle.ts:1002-1010—approvewrites state to "approved" before reading the plan.
At line 1002 the state is written tophase: "approved", then at line 1010 the plan is read. IfreadResourcefails (corrupt data, disk error), the state is already committed as "approved" but the swamp-club lifecycle entry was never posted. Theplan-existscheck guards against a missing plan, but not against a read failure after the check passes. Reordering to read the plan first would be safer. -
extensions/models/issue_lifecycle.ts:876-974—resolve_findingssilently ignores unknown finding IDs.
If the user passesfindingId: "ADV-99"but onlyADV-1throughADV-3exist, the resolution is silently dropped. TheresolutionMap.get(f.id)at line 924 simply returnsundefinedfor unknown IDs. The user sees "Resolved 1 finding(s)" in the log but no finding was actually resolved.
Suggested fix: Track unmatched IDs fromresolutionMapafter the loop and warn or throw.
Low
-
extensions/models/_lib/swamp_club.ts:247-308—createSwampClubClientperforms a health check on every call.
Every method (triage,plan,approve, etc.) callscreateSwampClubClient, which fetches/api/healthbefore returning. Over a full lifecycle (start → triage → plan → adversarial_review → resolve_findings → approve → implement → complete = 8 methods), that's 8 extra HTTP round-trips purely for health checks. Consider caching the client or passing it through context. -
extensions/models/issue_lifecycle.ts:817-820—adversarial_reviewrecordsplanVersion: 0if plan resource is somehow absent.
The transition check restricts this toplan_generatedphase, so a plan should always exist. But the fallback to version0means a corrupted state would produce a review that can never match a real plan version, which theadversarial-review-clearcheck would correctly reject. Self-healing, but worth noting.
Verdict
PASS — This is a clean deletion-heavy refactor that removes ~1050 lines of GitHub/CI integration in favor of direct swamp-club operation. The state machine is simpler and correct. The medium findings are design-level concerns about silent degradation that the author should confirm are intentional, but none represent data loss or security issues.
There was a problem hiding this comment.
Code Review
Clean, well-executed refactoring that removes ~1050 lines of GitHub integration code and simplifies the issue-lifecycle model to operate directly on swamp-club lab issues. The state machine simplification (implementing → done instead of the ci_review/fix loop) is a welcome reduction in complexity.
Blocking Issues
None.
Suggestions
-
Inconsistent swamp-club error handling across methods:
startcorrectly throws when swamp-club is unavailable (if (!sc) throw ...), buttriage,plan,iterate,approve,implement, andcompleteall silently skip swamp-club updates when the client is null (if (sc) { ... }). Since swamp-club is now mandatory (not optional), a failedupdateType()intriagemeans swamp-club's type field silently drifts from the local classification. Consider either throwing on null in all methods (matchingstart's behavior) or at least logging a warning when swamp-club calls are skipped in methods that mutate swamp-club state (liketriage'supdateType). -
createSwampClubClientcalled fresh per method: Each method callscreateSwampClubClient()independently, which performs a health check (fetchto the base URL) every time. The old code cached this in a module-level_swampClubvariable. Since each method run is a separate process (as noted in the old code's comments), this is functionally correct, but worth being aware of — each method invocation now makes 2+ HTTP calls to swamp-club before doing its real work (health check + the actual operation). -
Type cast in
fetchIssue:(issue.type ?? "feature") as IssueTypeatswamp_club.ts:114— if the API returns an unexpected type string, this cast silently accepts it. Consider usingIssueType.safeParse()to validate and fall back explicitly, or at least log when the fallback is used.
…b directly (#57) * refactor: drop GitHub integration from issue-lifecycle, use swamp-club directly Mirrors the changes in systeminit/swamp#1139 — the @swamp/issue-lifecycle extension and skill now operate directly on swamp-club lab issues. Issues must already exist in swamp-club; the model fetches them by sequential lab number via GET /api/v1/lab/issues/{number} and PATCHes the type during triage to reflect the classification back. - Delete extensions/models/_lib/issue_tracker.ts and every gh CLI call. - Swap GlobalArgs: drop repo, issueNumber now refers to a swamp-club lab issue. Version bumped to 2026.04.08.1 with an upgrade that strips repo. - Rework SwampClubClient: operate on the lab number directly, add fetchIssue() and updateType(). Drop the /ensure round-trip. - Reconcile classification types to swamp-club's bug/feature/security. Regressions become type=bug with an isRegression flag on the classification record. unclear is gone — use confidence=low + clarifyingQuestions instead. - Remove ci_status, record_pr, fix methods and ciResults/fixDirective resources. The ci_review phase is gone; implementing transitions straight to done. - Update schemas_test.ts state machine tests to the new topology: happy path ends implementing→done, new completion gate test, old fix loop and ci_review tests removed. - Rewrite skill SKILL.md, triage.md, implementation.md, extension README.md, and manifest.yaml description/method list for the swamp-club-first workflow. Depends on swamp-club PATCH-type support in systeminit/swamp-club#374 (merged). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove module-level SwampClubClient cache The _swampClub module-level singleton was keyed on nothing — once created for issue #N, every subsequent getSwampClub() call returned the same client regardless of the new issueNumber in globalArgs. Since user models are loaded via dynamic import() in the same process, the module stays cached across method calls, so running start for issue #10 and then issue #20 in the same session silently sent #20's lifecycle entries, type updates, and status transitions to #10. Drop the cache entirely and call createSwampClubClient directly at each use. The reachability check is a single 5s-timeout HTTP GET and runs once per method invocation — negligible next to the lifecycle POST already happening on the same code path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: restore truncated AGPLv3 header in swamp_club.ts The swamp-club refactor accidentally dropped the final two lines of the required copyright header ("You should have received a copy..." and "with Swamp. If not, see..."). Per CLAUDE.md, every .ts file must carry the full AGPLv3 header from FILE-LICENSE-TEMPLATE.md. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
The
@swamp/issue-lifecycleextension and skill now operate directly on swamp-club lab issues. Issues must already exist in swamp-club — the model fetches them by sequential lab number viaGET /api/v1/lab/issues/{number}and PATCHes thetypefield during triage so the classification is reflected back in swamp-club.extensions/models/_lib/issue_tracker.ts(387 lines ofghCLI wrapper) and every tracker/GitHub call inissue_lifecycle.ts.repois dropped;issueNumbernow refers to a swamp-club lab issue number. Version bumped to2026.04.08.1with an upgrade entry that stripsrepofrom old instances.fetchIssue()(GET) andupdateType()(PATCH) methods. The GitHub-coupled/ensureround-trip is gone.bug | feature | security. Regressions are modelled astype: bugwith a newisRegression: booleanflag on the classification record.unclearis gone — useconfidence: low+clarifyingQuestionsinstead.ci_status,record_pr,fix(all GitHub-PR/CI coupled). Resources removed:ciResults,fixDirective. Phase removed:ci_review. The state machine is nowimplementing → donedirectly.SKILL.md,triage.md,implementation.md) and the extensionREADME.mdupdated for the swamp-club-first workflow.Depends on systeminit/swamp-club#374 (already merged), which adds PATCH-type support to swamp-club. Without that PR the
triagemethod's type update would no-op with a 400.New state machine
Diff stats
9 files changed, 375 insertions(+), 1422 deletions(-) — net −1047 lines.
Test plan
deno check— cleandeno lint— 1007 files, cleandeno fmt— 1113 files formatteddeno run test— 4193 passed, 0 faileddeno run compile— binary rebuiltswamp model create @swamp/issue-lifecycle issue-<N> --global-arg issueNumber=<N>, thenstart→triageand verify the swamp-club issue type flips and the lifecycle entries appear.🤖 Generated with Claude Code