Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"name": "fathom",
"source": "./",
"description": "Two agent skills, scaffold and execute, that carry a tracker issue from requirements to an open code review, on GitHub or any other forge with an adapter. Works with Asana or Linear, runs on Claude Code and Kiro, tracks tasks in beads or a committed markdown checklist, and resumes from repository state.",
"version": "2.0.0",
"version": "2.1.0",
"author": {
"name": "crod951"
},
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fathom",
"version": "2.0.0",
"version": "2.1.0",
"description": "Fathom provides the execute and scaffold skills for driving Asana or Linear tracker issues from requirements to an open code review, on GitHub or any other forge with an adapter. Agent-portable skills for Claude Code and Kiro with resumable, task-tracked implementation runs.",
"author": {
"name": "crod951",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ When the installer asks which skills to take, take all of them; `fathom-shared`

## Available Plugins

### fathom (v2.0.0)
### fathom (v2.1.0)

Fathom provides two agent skills, execute and scaffold, that carry a tracker issue from requirements to an open code review, on GitHub or any other forge with an adapter.
It works with Asana or Linear as your issue tracker, and both skills run unchanged on Claude Code and Kiro.
Expand Down
18 changes: 12 additions & 6 deletions docs/fathom.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ Because that file is committed, **teammates who clone the repo are never asked a
| Which destination? | The Asana project or Linear team new issues go to. |
| How do your states map? | Your real board sections or workflow states get mapped to three phases: in progress, in review, done. |
| Which forge? | Where your reviews live. Seeded from your `origin` remote, then from CLIs on your `PATH`. See [Forges](#forges). |
| How should the tracker learn a review merged? | Asana and Linear differ; skipped entirely when your forge has no CI hooks. See [How the tracker learns a review merged](#how-the-tracker-learns-a-review-merged). |
| How should the tracker learn a review merged? | Asana and Linear differ; skipped entirely when your forge is not GitHub, or declares no CI hooks. See [How the tracker learns a review merged](#how-the-tracker-learns-a-review-merged). |
| Which base branch? | Feature branches start from it and reviews target it. Defaults to your current branch, unless that is itself a Fathom branch. |
| Stop for approval, or run straight through? | Sets the default approval mode for future runs. See [Approval modes](#approval-modes). |

Expand Down Expand Up @@ -319,7 +319,7 @@ In the manual tier the "review merged" line never fires, because nothing can obs
| --- | --- |
| `.fathom/config.md` | The committed profile: tracker, forge, destination, state mapping, base branch, closer choice. |
| `.fathom/forge.md` | Only if you wrote an adapter for a forge Fathom does not ship. See [Forges](#forges). |
| `.fathom/plans/<ref>.md` | The per-issue plan: issue link, codebase context, approach, tasks, testing strategy. Written for people, never carries status. |
| `.fathom/plans/<ref>.md` | The per-issue plan: issue link, branch, codebase context, approach, tasks, testing strategy. Written for people, never carries status. The branch sits on its own `- Branch:` line, which the merge-closer matches to find this issue. |
| `.fathom/tasks/<ref>.md` | Task statuses as checkboxes. Only when the checklist backend is active. |
| `.beads/` | Beads task database and its JSONL export, when beads is the backend. |
| `.github/workflows/fathom-close.yml` | Only if you accepted the optional merge-closer Action. GitHub only; never offered on a forge without CI hooks. |
Expand Down Expand Up @@ -357,17 +357,23 @@ Two rules the skills hold to, no matter the tier: **anything executed against a
## How the tracker learns a review merged

Linear can close issues natively, Asana cannot, so Fathom supports several arrangements.
It asks once which one you use and records the answer - unless your forge declares no CI hooks, in which case the question is skipped and the sweep is the only mechanism.
It asks once which one you use and records the answer - unless your forge is not GitHub, or declares no CI hooks, in which case the question is skipped and the sweep is the only mechanism.

1. **The tracker's own forge integration.** On GitHub, Linear closes an issue when a review body contains `Closes TES-5`.
Asana can do the equivalent with its free GitHub App plus a rule that completes a task when its linked pull request merges.
Nothing from this plugin runs.
Best option when your organization allows the app.
2. **The optional merge-closer Action.** If you cannot install an integration, the skills offer a small GitHub Actions workflow that closes the issue on merge using a repository secret.
Server-side, no agent needed at merge time.
Offered only on a forge with CI hooks; on any other, accepting it would write a file that never runs.
3. **The sweep.** Whatever you choose, every run looks up each recorded review by id and closes anything the first two missed.
This is the backstop, and it is unavailable in the manual tier.
Offered only on GitHub, and only when the forge declares CI hooks; anywhere else, accepting it would write a file that never runs. Both closure paths above are GitHub-specific, one a GitHub App and the other a GitHub Actions workflow.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
3. **The sweep.** When your forge adapter can look a review up by id, every run does exactly that for each recorded review and closes anything the first two missed.
This is the backstop, and it is the only one of the three that is not GitHub-specific.
An adapter that declares `reviewLookup: none`, which includes the bundled generic-git fallback, cannot perform that lookup, so the sweep does not run there.
On such a forge none of the three mechanisms applies and there is no automated closure at all: issues stay in review until you close them in the tracker yourself, and the run says so rather than reporting a sweep it never performed.

An installed merge-closer is a copy of the template taken when you accepted it, so a fix to the template does not reach a repository that already has one.
From 2.1.0 a run that finds an out-of-date copy offers once to rewrite it, and records your answer either way so the offer is not repeated.
Accepting replaces only `.github/workflows/fathom-close.yml`.

You can also just say so, and the skill confirms the real state through the forge before acting.

Expand Down
1 change: 1 addition & 0 deletions skills/execute/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ If any of these files cannot be found and read, stop immediately and report whic
Still apply `inReview`, and say plainly that no later run will move this issue to `done` on its own because the forge cannot be observed, so closing it is now a manual step.

Finally, post a completion comment on the issue, including the done-on-merge note from `asana.md` when the tracker is Asana, and commit and push the task-state files this run changed as a final closing commit so the branch carries the completed state, staging them by explicit path per the staging rules in `conventions.md`: the beads JSONL export and `metadata.json` when beads is the backend, and this issue's files under `.fathom/`; never sweep `.beads/` or `.fathom/` as directories, since the beads database and runtime files are intentionally ignored and must not ride into the review.
Push this closing commit with an ordinary `git push` of the branch even when the adapter declares `pushesForYou`, since that capability governs only the push that opens the review, as `../fathom-shared/forges.md` states; skipping it here would leave the completed task state in the local clone while the review reads as finished.
12. Report a final summary: the issue, the review URL when one was opened or the resolved tier when one was not, the tracker's current phase, and the task counts from `status()`.

## Display overlay
Expand Down
7 changes: 6 additions & 1 deletion skills/fathom-shared/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ This document is a reference artifact for people, never resume state; task statu

Include these sections:

- **Issue** - the title, the tracker URL, the ref, and the branch.
- **Issue** - the title, the tracker URL, the ref, and the branch, each on its own labeled line.
Use these exact labels: `- Issue: <tracker url>`, `- Ref: <issue ref>`, and `- Branch: <branch>`.
The labels are load-bearing rather than cosmetic.
The merge-closer Action finds this issue's record by matching the `Branch` line exactly, then reads the tracker id only from a line labeled `Tracker`, `Issue`, or `Ref`.
A label that reads more naturally, such as `Issue link` or `Tracker URL`, matches neither, and the Action then exits successfully having closed nothing.
This matters most on a beads-backed issue, where the plan document is the only record the Action has, since no checklist file exists.
- **Issue description** - what the issue asks for, in your own words.
- **Codebase context** - the files, modules, and existing patterns this work touches, with paths.
- **Implementation approach** - how the change will be made, including anything deliberately out of scope.
Expand Down
25 changes: 18 additions & 7 deletions skills/fathom-shared/forges.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,24 @@ Adapter files implement each one against a specific forge's tools; treat the ope
| --- | --- |
| `verifyForge()` | Cheap read-only preflight check that the forge is reachable and authenticated; returns verified or unverified and nothing else. |
| `resolveBase(branch)` | Confirm the named base branch exists on the remote and is a valid merge target. |
| `openReview(branch, base, title, body)` | Create the review for a branch against a base. Return a stable review id and the review's URL or, on a forge where no review can be created, the manual-handoff result defined below. |
| `openReview(branch, base, title, body)` | Create the review for a branch against a base. Return a stable review id and the review's URL - or, on a forge where no review can be created, the manual-handoff result defined below. |
| `publishReview(id)` | Move a draft review to the notified-review state, where the forge distinguishes the two. A no-op everywhere else. |
| `getReviewState(id)` | Return one of `open`, `merged`, `closed-unmerged`, or `unknown`, plus a merge timestamp when merged. |

`openReview` owns the push.
Some forges' review CLIs push the review ref themselves, and running `git push` alongside them produces a wrong branch state, so whether a push happens is the adapter's decision and not the calling skill's.
The `pushesForYou` capability tells the caller which arrangement it is in; the caller must never push when it is true.
Which side performs the push that opens the review is decided by `pushesForYou`, and by nothing else.
When it is true, `openReview` owns that push and the caller must never perform it: some forges' review CLIs push the review ref themselves, and running `git push` alongside them produces a wrong branch state.
When it is false, the caller pushes the branch before calling `openReview`, which is what both bundled adapters declare and what `execute` does.
Stating this unconditionally in either direction is what makes an agent either skip the push or perform it twice.

That ownership is scoped to `openReview` and does not extend to the whole branch.
Commits the caller makes after the review is open, including the final closing commit that carries completed task state, are the caller's to push, on every adapter and whatever `pushesForYou` declares.
The contract has no operation for publishing them, and it deliberately does not need one: they are ordinary commits on a branch the forge already knows about, so an ordinary `git push` of that branch is correct.
Reading `pushesForYou: true` as "never push this branch again" is the failure this paragraph exists to prevent, because it leaves the closing commit sitting in the local clone while the review looks complete.

The review id returned by `openReview` must be something `getReviewState` can look up directly on a later run, from a different clone, without a listing call.
A branch name is not a review id.

`openReview` has one other legitimate outcome, the **manual-handoff result**: no id at all, plus the four things a human needs to open the review by hand the pushed branch, the base, a suggested title, and a suggested body in full.
`openReview` has one other legitimate outcome, the **manual-handoff result**: no id at all, plus the four things a human needs to open the review by hand - the pushed branch, the base, a suggested title, and a suggested body in full.
An adapter that cannot create reviews (see `generic-git.md`) returns this instead of an id, and it is a typed outcome of the contract rather than a failure.
A caller receiving it must not call `publishReview`, must not record a review id, and must say plainly that opening the review is now the user's step.

Expand All @@ -51,7 +57,7 @@ Forges differ in kind, not only in command names, which is the same lesson `agen
A capability that is false is a fact to design around, not a gap to work around.

`stackedReviews` is declared and unused.
It is recorded now because the difference is real `retarget` describes a forge where stacking is branch retargeting with automatic retarget when the upstream merges, and `declared-dependency` describes one where stacking is an explicit dependency plus a manually scoped commit range with no auto-rebase and because adding a capability key later means revisiting every adapter file.
It is recorded now because the difference is real - `retarget` describes a forge where stacking is branch retargeting with automatic retarget when the upstream merges, and `declared-dependency` describes one where stacking is an explicit dependency plus a manually scoped commit range with no auto-rebase - and because adding a capability key later means revisiting every adapter file.

## Adapter resolution

Expand All @@ -62,8 +68,13 @@ An internal forge on an unrecognized host matches no bundled adapter, so remote-
Resolve in this order, first match winning.

Use `.fathom/forge.md` when the repository contains one; a repo-local adapter wins over everything.
Otherwise use the bundled adapter named by the profile's `forge` field, for example `forges/github.md` for `forge: github`.
Otherwise, when the profile records `forge: local`, stop.
`local` names the repo-local adapter and nothing else, so a profile that says `local` with no `.fathom/forge.md` present describes an adapter that is missing rather than one to go looking for.
Say which file is absent, that the profile points at it, and that the fix is to restore `.fathom/forge.md` from `forges/TEMPLATE.md` or to change the profile's `forge` field to a bundled adapter or to `none`.
Never resolve `local` to a bundled adapter file, since `forges/local.md` does not exist and treating the field as a filename would look up a path that never resolves.
Otherwise, when the profile records `forge: none`, run in the manual tier described below and make no offers.
Otherwise use the bundled adapter named by the profile's `forge` field, for example `forges/github.md` for `forge: github`.
Stop the same way when that named file does not exist: report the missing adapter and the profile field that named it rather than continuing with no adapter resolved.
Otherwise the repository has no forge answer yet, so run the first-run forge question.

Never infer a forge from the presence of a CLI on `PATH` alone, and never infer one from another repository's profile.
Expand Down
Loading
Loading