Agents now start pre-mr-review on the work account themselves, through a Klaude delegate, and continue from its verdict. A council of models on my own accounts replaces the single reviewer before it, and adding a seat is one table row. Proof comes from T3's browser, with mp4 recordings, and capture-proof stays as the fallback. Ticket pages carry their own data, so index.html renders alone in T3's file viewer, and worktree threads share the main checkout's docs. A PreToolUse hook sends subagents in T3 threads to delegate_task. Blocker, proof, review and MR summary rules come from ideas in t3dotgg/tstack.
10 KiB
The ticket page
Every ticket this skill works has a ticket page, and every epic has an epic index. The
page is where Gib follows the work: the ticket as Jira has it, the plan, live progress, proof
beside the mocks, and the review. It updates while you work, so a write you skip is a gap he
sees. It is a personal working file, exempt from the prose bar like the rest of .claude/docs/.
.claude/docs/epics/
<EPIC-KEY>/
index.html, epic.data.js # the epic index, in build order
<TICKET-KEY>/
index.html, ticket.data.js # the ticket page
resources/ proof/ deliverables/ mr.md
tickets/<TICKET-KEY>/ # same shape, for a ticket with no epic, and no index
Each index.html carries the renderer from templates/site/ and its own data inline, rewritten
on every write, so it renders alone with no server and no other file. That is what T3 Code's
file viewer needs: it sandboxes the page, with no fetch or localStorage, and serves files
only from the page's own folder and below. The .data.js beside it is the store ticket-page
reads and writes. An open page polls it to stay live, and shows its last write if it cannot.
Screenshots and attachments stay as relative files under the ticket folder.
In T3 Code
Gib follows the work in a T3 Code thread, so the page and the plan come to him there.
- The live page.
ticket-page open <KEY>refreshes the page and prints its absolute path. Link that path in your reply, and it opens in T3's file viewer beside the thread. Outside T3 (noT3CODE_THREAD_ID) it also opens the browser. Never start an http server for the page. - Inline snapshots.
ticket-page view <KEY> <view>prints a self-contained page styled with T3's theme. Pass its output, verbatim, tohtml_preview, check the screenshot, then tohtml_renderwith thecontentHeightit reported (capped at 2000) as the height. Do not restate what the snapshot shows in your reply. Snapshots do not update, so render one at the moment it is for, not on every write.
| Moment | View |
|---|---|
| Plan ready for approval (Phase 1, last step) | plan, then mocks --mock <id> once per artboard |
| Starting pre-mr-review, final hand-off, and on resume | progress |
- One artboard per render, so each can be expanded full screen on its own and options sit
one after another for comparison.
viewrefuses a page over html_render's 512 KB and names the artboards to render one at a time. - Keep artboards light: CSS and real copy, not base64 screenshots. A screenshot pasted into an artboard as a data URI is most of its size, and every byte goes through the conversation.
Write through ticket-page, always
~/.agents/skills/ticket/scripts/ticket-page is the only writer of the data files. It validates
before every write and refuses a broken one, writes atomically so the open page never reads half
a file, appends each change to the page's live log, and keeps the ticket's row on the epic index
current. Editing a .data.js file by hand skips all four. ticket-page help lists every
command and its flags.
It finds the ticket by key under the current repo's .claude/docs/epics/, so run it from inside
the repo.
What to record, and when
| Moment | Command |
|---|---|
| Issue fetched to a scratch path | init <KEY> --issue <issue.raw.json>. Prints the ticket folder, then any populated Jira fields the page does not show. Then open <KEY> and link the path. |
| Attachments downloaded, a video transcribed, frames extracted | scan <KEY> |
| Ticket branch resolved, ticket classified | meta <KEY> --branch <name> --classified bounded |
| Research notes worth keeping: what a video says, what a spreadsheet holds, a check against prod | note <KEY> "<Title>" --file notes.html |
| Plan written | plan <KEY> --file plan.json, then one mock per artboard |
| Plan approved, Phase 2 begins | approve <KEY> |
| Starting a step | step <KEY> <n> running |
| A step's commit exists | step <KEY> <n> done --commit <sha>, one --commit per commit |
| A criterion is met | criterion <KEY> <ID> done --evidence "<what shows it>" |
Proof captured (image, mp4, or text), or dropped from proof/ |
proof <KEY> --file proof/<name> --proves AC1,AC3 --caption "<test case>" [--mock <id>], proof <KEY> --test "<name>" ..., unproof <KEY> <file> |
| Council finding, and its resolution | finding <KEY> add "[should] <text> (<seats>)" --severity CONFIRMED --where <file:line>, then finding <KEY> F1 fixed --commit <sha> or rejected --reason "<why>" |
| pre-mr-review audit read | verdict <KEY> <verdict text> --recommendation <R> --head <sha> |
| A Jira field PUT and verified | jira <KEY> "<field name>" |
mr.md written |
mr <KEY> |
| Anything else Gib would want to see happen: suite green, a surprise, a blocker | event <KEY> "<text>" |
A step is done when its commit exists and ticket-page show <KEY> lists that commit beside it.
step done refuses without --commit. A step with nothing to commit, such as a verification
pass, takes --no-commit.
plan.json
The plan is one JSON file, written with the Write tool and loaded with ticket-page plan. Prose
fields are HTML fragments: <p>, <b>, <code>, <ul><li>. plan replaces any earlier plan
and clears its progress and proof, because a rewritten plan is a restart. To change a plan
mid-build (a new step, a reworded criterion), print it with plan-json <KEY> > plan.json, edit
that, and load it with plan --amend: steps whose number and text are unchanged, and
criteria whose id remains, keep their progress, and proof keeps the criteria that still exist.
{
"criteria": [
{ "id": "AC1", "text": "Every company can carry a five character canonical ID.", "steps": [1, 2] },
{ "id": "AC2", "text": "A duplicate canonical ID is rejected with a readable message.", "steps": [3] }
],
"questions": [],
"approach": "<p><b>Company.canonicalId</b> is the only new state. The engagement ID is derived by <code>formatEngagementCanonicalId</code>, so the two cannot drift.</p>",
"decisions": [["Engagement canonical ID", "Derived at read time", "Matches the spreadsheet formula, with no second column to backfill."]],
"steps": [
{ "n": 1, "text": "Add the canonicalId module, tests first.", "tdd": true },
{ "n": 2, "text": "Schema, migration and backfill." },
{ "n": 3, "text": "Map the unique violation to CONFLICT.", "tdd": true },
{ "n": 4, "text": "Canonical ID field on the company form.", "kind": "mock" }
],
"risks": [
{ "risk": "Two companies given the same code", "handling": "The unique index blocks it and step 3 returns a readable CONFLICT.", "criteria": ["AC2"], "fromTicket": true }
],
"tests": [
{ "kind": "integration", "text": "companies.update with a taken code throws CONFLICT.", "criteria": ["AC2"] }
]
}
- Steps are numbered 1 to N in order.
kindiscode(the default),mock, ordeliverable. - Every criterion names the steps that satisfy it. Risks and tests name the criteria they cover.
- A risk or test that comes from the ticket's own table carries
"fromTicket": true. Your own additions leave it off, so the page shows which rows the ticket asked for. questionsholds only what genuinely needs Gib's answer, each as a direct question. Empty means none.
Mocks
When a step changes what a user sees (a new screen, a changed layout, a new control, copy a user reads), the plan carries mock artboards:
ticket-page mock <KEY> <id> --title "<screen>" --criteria AC3,AC8 --note "<what the build will not do>" --file artboard.html
- An artboard is a complete small HTML document with its own
<style>, rendered in a sandboxed frame beside the proof. Draw the screen state the plan introduces, with the app's real navigation and density around it rather than a wireframe of boxes. - One artboard per new screen state. When the direction is Gib's call, draw each candidate as
its own artboard (
option-a,option-b) and ask the choice inquestions, naming the artboards. This is how the house rule of several static mocks before touching real components is met inside the ticket flow. --criterianames what the artboard illustrates. The Progress tab shows it under those criteria, and replaces the empty half with the proof once aproof ... --mock <id>lands.--notestates what the artboard shows that the implementation will not do.- A spike deliverable's polished mocks (see Mocks and datamodel changes) can start from the approved artboard. The built screens are still screenshotted for real.
The epic index
The index lists the epic's stories in build order: the order they should be completed in, which is not key order and not always the Jira link graph. Each worked ticket keeps its own row current. The rest of the index is yours to seed and keep true.
- Seed it the first time a ticket under the epic is worked, and refresh it on later runs.
Search the epic's children (
parent = <EPIC> ORDER BY rank). For each, record the title, the estimate as--hours, the Story Risk Level as--risk, the stories it is blocked by as--after, and--status doneif Jira says Done:ticket-page epic <EPIC> story <KEY> .... Closed spikes go in withepic <EPIC> closed <KEY> --title T. - Set the order with
ticket-page epic <EPIC> order <KEY> <KEY> ..., listing every story. If the epic already records a build order (asummary.mdor plan in its folder), that order wins. Otherwise order by the blocked-by links, then Jira rank. Write the reasons the order is what it is withepic <EPIC> why --file reasons.txt, one reason per line. - Gib reorders by asking. When he does, rerun
orderand updatewhy. - A story is marked done only by
--status done, once it has merged or Jira says Done. The index never guesses that.
Reading it as an agent
ticket-page md <KEY> prints the ticket and the plan as markdown. Use it when you resume a
ticket, and write it to a scratch file for the council and the audit, which read it there.
Tickets from before the page
Tickets planned before the page existed have resources/ticket.md and plan.md and no
ticket.data.js. Leave those files alone. When Phase 0 resumes such a ticket, convert it
first: init from resources/issue.raw.json, plan from the plan's content, then approve and
step ... done for each step git log shows committed. From then on the page is the record.