Compare commits

..

15 Commits

Author SHA1 Message Date
sulthan f532e50516 Orphan removal: the database is the guard (#155) 2026-08-22 09:58:11 +07:00
sulthan bc64a1d894 backend: remove orphan series from the admin (ticket #155)
(*Store).RemoveSeries deletes one series row by (site, series_id) via a
plain parameterized DELETE; a bookmarks_series_fk violation outside
23503 is translated into the ErrSeriesHasBookmarks sentinel so no
driver type escapes the store. The caller reads the row's cover
address before the delete and reclaims it after: ReclaimCover's guard
cannot pass while a series row still points at the address.

POST /admin/series/{key}/remove answers the list row with the removed
row's fragment plus the heading re-rendered out of band at the fresh
count (HX-Reswap: delete removes the row through the same button that
swaps the refusal back in), and navigates from the detail page to the
No-Readers list (HX-Redirect for htmx, a 303 for plain clients). A
removal that races a fresh Bookmark is a refusal, not a 500: the row
re-renders at its new count with the fact spelled out. The control
renders only at zero Reader count on both surfaces, gated by hx-confirm
with the brief's copy.
2026-08-22 09:55:01 +07:00
sulthan ba2b6eebbb Latest Chapter provenance: one derived line naming the actor class (#152) 2026-08-22 09:37:08 +07:00
sulthan f40894b108 Cover byte reclamation: one guarded helper, file first (#154) 2026-08-22 09:36:45 +07:00
sulthan a4ea80dcc2 Cover byte reclamation: one guarded helper, file first, covers row last (#154) 2026-08-22 09:34:02 +07:00
sulthan a4491babed Latest Chapter provenance line naming the actor class (#152) 2026-08-22 09:32:25 +07:00
sulthan e1d61534bb A Forced Poll replaces the Cover; an ordinary pass still only fills a blank one (#153) 2026-08-22 09:23:59 +07:00
sulthan ad09569f2e Series URL repair, owner-typed and gated by the poller's own fetch gate (#151) 2026-08-22 09:23:59 +07:00
sulthan 35a86f5eb9 Correct the three-outcome comment; keep doc comment attached to storeCover (#153) 2026-08-22 09:23:18 +07:00
sulthan 424d2c6600 Series URL repair: owner-typed, gated by the poller's own fetch gate (#151) 2026-08-22 09:21:00 +07:00
sulthan 7e1cbdde9e Forced Poll replaces the Cover; ordinary pass still fills only a blank one (#153) 2026-08-22 09:19:15 +07:00
sulthan 448631c78e Cover addresses derived from the bytes; ReplaceSeriesCover (#150)
# Conflicts:
#	backend/internal/store/store_test.go
2026-08-22 09:08:37 +07:00
sulthan f3b2722568 Latest Chapter correction: one numeric input, owner-gated (#149) 2026-08-22 09:08:06 +07:00
sulthan b9fc83217d #150: address covers by bytes; ReplaceSeriesCover 2026-08-22 09:05:53 +07:00
sulthan c9b1f2a334 Latest Chapter correction: one numeric input, owner-gated (#149) 2026-08-22 09:04:24 +07:00
3 changed files with 102 additions and 172 deletions
+87 -31
View File
@@ -1,49 +1,108 @@
---
name: implement-tickets
description: "Run a planned wave of tickets: one implementer subagent per ticket in its own worktree, then land, merge and close what comes back."
description: "Orchestrate a batch of tickets: plan the briefs, then hand each ticket to its own implementer subagent in its own worktree."
disable-model-invocation: true
---
# Implement tickets
You are the **orchestrator**. You dispatch, land results, and talk to the
tracker. You do not write the implementation — every line of ticket code is
written by a `ticket-implementer` subagent in its own git worktree. Reach for the
editor yourself only for a merge conflict resolution.
You are the **orchestrator**. You write briefs, dispatch, land results, and talk
to the tracker. You do not write the implementation — every line of ticket code
is written by a `ticket-implementer` subagent in its own git worktree. Reach for
the editor yourself only for a merge conflict resolution.
Ticket source and tracker conventions: `docs/agents/issue-tracker.md`. `tea` usage: skill `gitea`.
## 0. Load the plan
## 1. Collect the tickets
Your argument is the batch slug. Read `.scratch/<batch-slug>/plan.md` — it gives
the base branch, the waves, the contracts, and each ticket's brief path and
status. Run the earliest wave that is not landed.
The user's argument is the selector: issue numbers, a label, a parent issue, or
nothing. With nothing, take the open issues labelled `ready-for-agent`.
The briefs are the requirements and they are already approved: read each one you
are about to dispatch, but do not rewrite it, and do not fetch the tickets from
the tracker to second-guess it. A brief that is wrong or thin is a `plan-tickets`
problem — say so and stop, rather than patching it here.
Fetch each with `tea issue <n> --comments`, and read the **whole** body —
acceptance criteria and the `Blocked by` line are what the rest of this skill
runs on. A ticket whose blockers are still open is out of this batch unless a
blocker is also in it.
No plan file, or no briefs for the next wave: run `plan-tickets` first. That
skill owns wave membership, contracts, and every brief.
## 2. Plan the batch
## 1. Dispatch the wave
Explore enough of the codebase to write briefs a fresh context can act on: the
files each ticket lands in, the patterns it must follow, the `AGENTS.md`
invariants it touches.
Then decide three things:
- **Waves.** Blocking edges set the order; tickets with no open blocker inside
the batch share a wave. Cap each wave at **3** concurrent tickets unless the
user set another width.
- **Contracts.** Two tickets in one wave that meet at a function signature, a
JSON shape, a table column, or a token name: you decide the shape now and
write the identical wording into both briefs. A contract left for the
subagents to negotiate is a merge conflict you scheduled.
- **Splits.** A ticket too big for one fresh context window goes into the wave
as two briefs, or back to the user.
## 3. Get the plan approved
Present, and stop:
- the wave list, and for each ticket: number, title, one-line brief summary,
the files or areas it will touch, its verification commands
- every cross-ticket contract, verbatim as it will appear in the briefs
- anything you had to assume
Wait for approval. Apply the user's edits to the plan, do not relitigate them.
## 4. Run a wave
Per ticket, before dispatch:
```bash
git worktree add ../ticket-<n> -b ticket/<n>-<slug> <base> # base = the plan's base branch, checked out here
git worktree add ../ticket-<n> -b ticket/<n>-<slug> <base> # base = the branch you are on
cp .env ../ticket-<n>/ 2>/dev/null # gitignored, worktrees do not get it
tea issue edit <n> --add-assignees <your gitea username> # tea login list has it
```
Write the brief to `.scratch/<batch-slug>/t<n>-brief.md` using the template
below, in the ubiquitous language of `CONTEXT.md` — a brief that says "scrape"
where the domain says Poll hands the subagent the wrong model of the system.
Then dispatch the whole wave in **one** `task` batch, every item on the
`ticket-implementer` agent. Each dispatch names: the absolute brief path, the
worktree path, the branch, the base ref, and the report path
`.scratch/<batch-slug>/t<n>-report.md`. Mark each ticket `dispatched` in the plan
file.
`.scratch/<batch-slug>/t<n>-report.md`.
## 2. Land the wave
<brief-template>
# Ticket #<n> — <title>
**Read first.** `tea issue <n> --comments` for this ticket, then the issue it
refers to — the parent or spec — the same way. The comments carry decisions the
body never got updated with. This brief stays the requirements; those two reads
are the intent behind them.
**Goal.** The end-to-end behaviour this ticket makes work, from the user's side.
**Acceptance criteria.** Verbatim from the ticket.
**Contract.** The exact shared signatures / shapes / names this ticket must
implement or consume, and which sibling ticket is on the other end. Omit when
the ticket touches nothing shared.
**Where it lands.** The files and packages, and the existing pattern to follow
in each.
**Binding invariants.** The `AGENTS.md` rules this change can break — name them.
**TDD seams.** Where a test comes first — run the `tdd` skill at each one and
follow its red → green loop. Or "none — verify after".
**Verify.** The exact commands, e.g. `cd backend && go test ./...`,
`node --test userscript/test/logic.test.js`.
**Out of scope.** What not to touch, especially a sibling ticket's files.
</brief-template>
## 5. Land the wave
The wave is landed when every ticket in it is closed, reverted, or handed back
to the user. Per returned ticket:
@@ -61,17 +120,14 @@ clash — both sides green apart, wrong together — goes back to whichever tick
owns the contract, as a re-dispatch with the collision described.
Then `tea comment <n> "<the report summary>"`, `tea issue close <n>`, and
`git worktree remove ../ticket-<n>`. Keep the report file, and mark the ticket
`landed` or `handed back` in the plan file.
`git worktree remove ../ticket-<n>`. Keep the report file.
## 3. Hand back or close the batch
Only once the whole wave is landed does the next wave start — its briefs may
need what this one changed.
Waves left in the plan: stop and say which wave is next. Its briefs are written
by `plan-tickets` against the base you just changed — that is why they were not
written up front, and why you do not write them.
## 6. Close the batch
Last wave landed: run the full suite once on the merged base, and report a line
per ticket with its status, commits, and open concerns, plus anything still
assigned or open on the tracker. A red suite after every ticket went green is an
interaction bug — diagnose it, name the two tickets, and fix it or hand it back
with both named.
Run the full suite once on the merged base, and report: a line per ticket with
its status, commits, and open concerns, plus anything still assigned or open on
the tracker. A red suite after every ticket went green is an interaction bug —
diagnose it, name the two tickets, and fix it or hand it back with both named.
-125
View File
@@ -1,125 +0,0 @@
---
name: plan-tickets
description: "Plan a batch of tickets and write the briefs for its next wave: read the tracker, decide waves and contracts, get the plan approved."
disable-model-invocation: true
---
# Plan tickets
You produce the two artifacts the `implement-tickets` skill runs on: a **plan
file** and one **brief** per ticket in the next wave. You write no ticket code
and create no worktrees — that is the runner's half.
Ticket source and tracker conventions: `docs/agents/issue-tracker.md`. `tea` usage: skill `gitea`.
Called twice in a batch's life, at least: once to open it, then again after each
wave lands, because a later wave's briefs may need what the last one changed. On
a re-entry, read the existing `.scratch/<batch-slug>/plan.md` first and plan only
the next unlanded wave — the waves and contracts already approved there stand
unless the landed wave proved one wrong.
## 1. Collect the tickets
The user's argument is the selector: issue numbers, a label, a parent issue, or
nothing. With nothing, take the open issues labelled `ready-for-agent`.
Fetch each with `tea issue <n> --comments`, and read the **whole** body —
acceptance criteria and the `Blocked by` line are what the rest of this skill
runs on. A ticket whose blockers are still open is out of this batch unless a
blocker is also in it. Read the issue each ticket refers to, its parent or spec,
the same way: nothing on the implementation path reads the tracker after you —
only `cr-spec` does, at review time — so a decision that lives in a comment
reaches the implementer only if you carry it into the brief.
## 2. Plan the batch
Explore enough of the codebase to write briefs a fresh context can act on: the
files each ticket lands in, the patterns it must follow, the `AGENTS.md`
invariants it touches.
Then decide three things:
- **Waves.** Blocking edges set the order; tickets with no open blocker inside
the batch share a wave. Cap each wave at **3** concurrent tickets unless the
user set another width.
- **Contracts.** Two tickets in one wave that meet at a function signature, a
JSON shape, a table column, or a token name: you decide the shape now and
write the identical wording into both briefs. A contract left for the
subagents to negotiate is a merge conflict you scheduled.
- **Splits.** A ticket too big for one fresh context window goes into the wave
as two briefs, or back to the user.
## 3. Write the artifacts
Pick a `<batch-slug>` — short, from what the batch is about — and write
`.scratch/<batch-slug>/plan.md`. It is the handoff: the runner is a fresh context
that reads this and nothing of your reasoning.
<plan-template>
# Batch <batch-slug>
**Base branch.** The branch every worktree forks from and merges back into.
**Waves.** A table: wave number, ticket number, title, brief path, and status —
`planned` | `dispatched` | `landed` | `handed back`. Every ticket in the batch,
including waves not briefed yet.
**Contracts.** Each cross-ticket contract verbatim, naming both ticket numbers.
**Assumptions.** What you had to assume, and what the user corrected at approval.
</plan-template>
Then write a brief per ticket in the next wave to
`.scratch/<batch-slug>/t<n>-brief.md`, using the template below, in the
ubiquitous language of `CONTEXT.md` — a brief that says "scrape" where the domain
says Poll hands the subagent the wrong model of the system.
<brief-template>
# Ticket #<n> — <title>
**Decisions.** Whatever the ticket's comments or its parent spec settled that
the body never got updated with, restated verbatim. The implementer reads this
brief and the code, never the tracker — a decision missing here is lost to it,
and a stale comment thread is not a source you want a fresh context guessing
from. Omit only when the ticket has no comments.
**Goal.** The end-to-end behaviour this ticket makes work, from the user's side.
**Acceptance criteria.** Verbatim from the ticket.
**Contract.** The exact shared signatures / shapes / names this ticket must
implement or consume, and which sibling ticket is on the other end. Omit when
the ticket touches nothing shared.
**Where it lands.** The files and packages, and the existing pattern to follow
in each.
**Binding invariants.** The `AGENTS.md` rules this change can break — name them.
**TDD seams.** Where a test comes first — run the `tdd` skill at each one and
follow its red → green loop. Or "none — verify after".
**Verify.** The exact commands, e.g. `cd backend && go test ./...`,
`node --test userscript/test/logic.test.js`.
**Out of scope.** What not to touch, especially a sibling ticket's files.
</brief-template>
## 4. Get the plan approved
Present, and stop:
- the wave list, and for each ticket in the next wave: number, title, one-line
brief summary, the files or areas it will touch, its verification commands
- every cross-ticket contract, verbatim as it appears in the briefs
- anything you had to assume
Wait for approval. Apply the user's edits to the plan, do not relitigate them —
into the files, not just the reply, or the runner never sees them.
Then hand off: name the batch slug and the wave, and stop. Running the wave is
`implement-tickets`.
+15 -16
View File
@@ -24,35 +24,34 @@ Your branch is already checked out there. Never `git checkout`, `git switch`,
## Order of work
1. Read the brief file. It is the only statement of requirements you get — use
its exact values verbatim, and read nothing from the tracker. The
orchestrator has already read the ticket, its comments and its parent spec,
and folded every live decision into the brief; the threads themselves also
hold reversed and rejected ones you cannot tell apart from here.
2. Read `AGENTS.md` in the worktree, plus the nested `AGENTS.md` for the area
1. Read the brief file. It is the single source of requirements — use its exact
values verbatim.
2. Read the ticket and the issue it refers to, as the brief's **Read first**
section names them: `tea issue <n> --comments` for each. The ticket's
comments and its parent carry the intent and the decisions behind the brief.
Read no other ticket and no other brief.
3. Read `AGENTS.md` in the worktree, plus the nested `AGENTS.md` for the area
you touch. Its invariants bind you: security rules, design system, comment
policy.
3. Ask before writing code if requirements, acceptance criteria, approach, or
4. Ask before writing code if requirements, acceptance criteria, approach, or
dependencies are unclear. Asking is free; guessing is not.
4. Implement exactly what the brief specifies. At each TDD seam the brief names,
5. Implement exactly what the brief specifies. At each TDD seam the brief names,
run the `tdd` skill and follow its red → green loop.
Follow the patterns already in the codebase; improve what you touch,
restructure nothing outside the ticket.
5. Verify. Focused tests while iterating, the brief's full verification commands
6. Verify. Focused tests while iterating, the brief's full verification commands
once at the end. Test output must be pristine.
6. Commit to your branch. Reference the ticket number in the subject.
7. Review (below), fix, re-verify, commit the fixes.
8. Write the report file, then return the status contract.
7. Commit to your branch. Reference the ticket number in the subject.
8. Review (below), fix, re-verify, commit the fixes.
9. Write the report file, then return the status contract.
## Review
After your first green commit, run the **`code-review`** skill over
`<base ref>...HEAD` in the worktree, with two changes to how it dispatches:
use the **`cr-spec`** agent for the Spec axis and **`cr-standards`** for the
Standards axis, both in one batch, and hand the Spec axis your brief file as the
spec plus the ticket number from the brief's title, telling it to read the
ticket itself (`tea issue <n> --comments`). The tracker check belongs in that
read-only context, not in yours.
Standards axis, both in one batch, and give the Spec axis your brief file plus
the ticket body as the spec.
Fix every Critical and Important finding, then re-run the tests that cover the
amended code. Two fix rounds maximum: anything still open after that goes in the