sign-off-axi 0.6.8 → 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,239 +1,99 @@
1
1
  <p align="center">
2
2
  <picture>
3
3
  <source media="(prefers-color-scheme: dark)" srcset="docs/logo-dark.svg">
4
- <img src="docs/logo.svg" width="96" height="96" alt="sign-off logo: a white check mark on a blue tile">
4
+ <img src="docs/logo.svg" width="56" height="56" alt="sign-off logo">
5
5
  </picture>
6
6
  </p>
7
-
8
7
  <h1 align="center">sign-off</h1>
9
8
 
10
- Guided plans and reports now offer **Simplify this** under each part (and for any words you select inside one paragraph): the same owner explains it in place, with pending and persistent **New** states. New defaults require a filer; older documents still load. [Owner setup and engagement contract](docs/contract.md#report-schema-4-and-plan-schema-3-engagement-v1).
11
-
12
- <p align="center"><b>One calm page for every question the programs working for you need answered.</b><br>Agents ask; you answer with one tap on your own machine; the answer is saved for the asker to read. Proof of finished work lands on the same page, so you check it instead of taking anyone's word.</p>
9
+ <p align="center"><b>Answer your coding agents’ questions and review their work from one board.</b></p>
13
10
 
14
- <p align="center"><b>▶ <a href="https://umeranjum17.github.io/sign-off/demo/">Try it live</a></b>: the real board on demo data. No install, nothing leaves your browser. · <a href="https://umeranjum17.github.io/sign-off/">Website</a></p>
11
+ <p align="center">For people running coding agents. Read plans, pick designs, and check proof on your phone or desktop.</p>
15
12
 
16
13
  <p align="center">
17
14
  <picture>
18
- <source media="(prefers-color-scheme: dark)" srcset="docs/media/desk-today-dark.webp">
19
- <img src="docs/media/desk-today.webp" alt="The board at desk width: Today with the next ask, what waits, designs to review and what was proven">
15
+ <source media="(max-width: 600px) and (prefers-color-scheme: dark)" srcset="docs/media/phone-today-dark.webp">
16
+ <source media="(max-width: 600px)" srcset="docs/media/phone-today.webp">
17
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/hero-dark.webp">
18
+ <img src="docs/media/hero-light.webp" width="880" alt="Today on the real board: a hosting question with answer buttons, waiting work, and proof">
20
19
  </picture>
21
20
  </p>
22
21
 
23
- ## Install in 3 steps
22
+ <p align="center"><img src="docs/media/answer-light.gif" width="300" alt="Real demo board: Umer answers the release question and Today updates"></p>
24
23
 
25
- ```sh
26
- npm install -g sign-off-axi # 1. install from npm
27
- npx -y sign-off-axi init # 2. in your project: set up .sign-off/ and the agent skill
28
- sign-off-axi open # 3. start the board and open the printed link
29
- ```
24
+ A real ask answered on the demo board. The capture journey checks that the CLI reads back **Friday**. The projects and work shown are demo data.
30
25
 
31
- Then ask your first question, the way an agent would, and answer it on the board:
26
+ ## Quick start
27
+
28
+ Requires Node.js 22.5 or newer. Run in your project folder. This example installs the Codex skill; [choose another agent](docs/commands.md#agent-setup) if needed.
32
29
 
33
30
  ```sh
34
- sign-off-axi raise --ask-ref message-example \
35
- --title "Keep the weekly digest?" \
36
- --why "The paid plan renews Monday." \
37
- --outcome yes="It keeps going out." --outcome no="It stops this week." \
38
- --recommend yes --recommend-reason "Readers still open it." --default yes
39
- sign-off-axi answers --key keep-the-weekly-digest # read the tap back
31
+ npm install -g sign-off-axi
32
+ sign-off-axi init --agent codex
33
+ sign-off-axi open
40
34
  ```
41
35
 
42
- You need Node 22.5 or newer. `init` installs the skill for each coding agent it finds on your `PATH`; `sign-off-axi --help` lists every command, and every command takes `--help`.
43
-
44
- ## The board
45
-
46
- Five places, in the dock at the bottom of a phone and the sidebar on a desk: **Today**, **Decide**, **Evidence**, **Reports** and **Tickets**.
47
-
48
- <table>
49
- <tr>
50
- <td align="center" width="25"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-today-dark.webp"><img src="docs/media/phone-today.webp" alt="Today: the next ask with its answer buttons, what waits, designs to review and what was cleared and proven"></picture><br><b>Today</b><br>What needs you, what was proven.</td>
51
- <td align="center" width="25"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-decide-dark.webp"><img src="docs/media/phone-decide.webp" alt="Decide: asks with their priority and ticket progress, and Taken without you with Change"></picture><br><b>Decide</b><br>Every ask that waits, one tap each.</td>
52
- <td align="center" width="25"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-reader-dark.webp"><img src="docs/media/phone-reader.webp" alt="A report in the reader: its answer first, and the one action bar with Agree, Disagree, Comment and Ask"></picture><br><b>Reader</b><br>One action bar on the part in view.</td>
53
- <td align="center" width="25"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-evidence-dark.webp"><img src="docs/media/phone-evidence.webp" alt="Evidence with Previews on: each proof with its first capture and the check's result"></picture><br><b>Evidence</b><br>Proof, with its first capture inline.</td>
54
- </tr>
55
- </table>
56
-
57
- <table>
58
- <tr>
59
- <td align="center" width="33"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-tickets-dark.webp"><img src="docs/media/phone-tickets.webp" alt="A ticket: its progress, the epic it is part of, its sub-tasks and a linked pull request"></picture><br><b>Tickets</b><br>Epics, tickets and tasks that roll up.</td>
60
- <td align="center" width="33"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-designs-dark.webp"><img src="docs/media/phone-designs.webp" alt="Designs: the design waiting for review, with a thumbnail of its first screen"></picture><br><b>Designs</b><br>Frames to review, one question each.</td>
61
- <td align="center" width="33"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-install-dark.webp"><img src="docs/media/phone-install.webp" alt="Today's More: the leash, Install app and the version the board runs"></picture><br><b>Install</b><br>An app on your home screen.</td>
62
- </tr>
63
- </table>
64
-
65
- Every card opens to its own screen with the same parts: one **action bar** (the answer, or Agree, Disagree, Comment and Ask on a report), one **Activity** (who posted it, each answer, note, comment and reply, and how far each got), and **Previous** and **Next** through the list you opened it from (swipe on a phone, `J` and `K` on a desk). The board names its own version (`--version`, the sidebar's foot, Today's More), and a reply links the new version of the work.
36
+ Open the printed link. Tell your agent to use the installed sign-off skill when it needs your answer.
66
37
 
67
- The board installs as an app (More, then Install app; on an iPad, Share, then Add to Home Screen) and, once opened, reads offline: with the board unreachable it shows the last update under "Offline, showing the last update", and sends fail rather than queue. A phone needs the board on HTTPS or localhost to install it.
38
+ [Try the demo](https://umeranjum17.github.io/sign-off/demo/) · [Website](https://umeranjum17.github.io/sign-off/) · [Commands](docs/commands.md) · [Agent skill](skills/sign-off-axi/SKILL.md)
68
39
 
69
- On a phone, larger Decide lists open quickly and **Show more** brings in the next asks, Later items or answers. The counts always include the whole board.
40
+ [![npm version](https://img.shields.io/npm/v/sign-off-axi)](https://www.npmjs.com/package/sign-off-axi) [![CI](https://github.com/umeranjum17/sign-off/actions/workflows/ci.yml/badge.svg)](https://github.com/umeranjum17/sign-off/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
70
41
 
71
- ### Evidence is not a decision
42
+ ## Why sign-off
72
43
 
73
- **Decide** holds only real asks: things an agent cannot do without your answer. **Evidence** holds proof (screenshots or a short recording of the running product) with the result of the check that made it: Passed, Not run, Failed, Did not finish or Could not check. Each row shows its first capture and the check's word; tap one and it opens in place with every capture in a strip, and **Previews** shows every row large. A proof update is never counted as something that needs you; you can give it a verdict (Pass, Fail or Needs work) or dismiss it. **Today** counts the two separately: what needs you, and what was proven today.
44
+ When several agents need you, questions get spread across terminals. sign-off collects them and saves each answer for its agent.
74
45
 
75
- ### You see how far each word got
76
-
77
- What you send on a card (an answer, a note, a new round, a comment) shows its trail: **Sent**, **Delivered** (the agent's wait or hook got it), **Read** and **Replied**, with the agent's words and a link to the pull request or new version. It shows on the card, in its Activity, as a badge in the lists, and a reply waits in Today until you open it. An agent closes the loop with `sign-off-axi feedback <key> --reply "..." --link <url>`; `--read`, `--delivered` and `--acted --link` mark the other steps, and `--at <epoch seconds>` replays a step that happened earlier without turning a later real one back.
78
-
79
- ### Plans and reports, on their own pages
80
-
81
- An agent writes a report as data and the board draws it: `sign-off-axi report init <folder> --purpose <kind>` writes `report.json`, and each section takes `blocks` to show a point rather than tell it.
82
-
83
- | Block | What it draws |
46
+ | Your current workflow | With sign-off |
84
47
  | --- | --- |
85
- | `callout` | a note, warning or question set apart |
86
- | `beats` | a stepper of pictures, each with a time, title and words |
87
- | `video` | a short film with its poster |
88
- | `table` | the same measures for each option |
89
- | `compare` | before and after |
90
- | `evidence` | captures, each marked Fixed, In progress or Waiting on you |
48
+ | Find a question in each terminal | Today shows what needs you next |
49
+ | Type the same answer again | The agent reads your saved answer |
50
+ | Read “done” in chat | Open screenshots and recordings in Evidence |
51
+ | Explain which part needs changing | Comment on the part of a plan or report in view |
52
+ | Ask whether feedback arrived | See Sent, Delivered, Read, and Replied |
91
53
 
92
- Design output (screens a model made) goes in a `gallery` on the section: `report init --gallery`. Every picture and film is a file beside `report.json`, and post pins it. A page an agent writes itself (`report init --page`) is refused when it brings its own colours, three or more, or a card inside a card; reports already on the board stay as filed.
54
+ ## What you get
93
55
 
94
56
  <table>
95
- <tr>
96
- <td align="center" width="33%"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-plan-dark.webp"><img src="docs/media/phone-plan.webp" alt="A plan page: the idea in one picture, then each section to approve or change"></picture><br><b>Plan</b><br>Approve or change each section, answer each decision, send it all back at once.</td>
97
- <td align="center" width="33%"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-report-dark.webp"><img src="docs/media/phone-report.webp" alt="A report page: the answer first, then a diagram to tap through"></picture><br><b>Report</b><br>An explainer page: the answer first, then a diagram and steps to tap through.</td>
98
- <td align="center" width="33%"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-signin-dark.webp"><img src="docs/media/phone-signin.webp" alt="A sign-in card: the service, what it unlocks, who waits and the time left"></picture><br><b>Sign-in</b><br>An agent needs you to log in: tap Open, then it carries on.</td>
99
- </tr>
57
+ <tr>
58
+ <td width="50%"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-decide-dark.webp"><img src="docs/media/phone-decide.webp" width="260" alt="Decide with waiting asks and the Leash setting"></picture><br><b>Answer what needs you.</b><br>Pick an answer, add a note, or ask for it later. The leash sets when agents may take a default; guarded asks always wait.</td>
59
+ <td width="50%"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-reader-dark.webp"><img src="docs/media/phone-reader.webp" width="260" alt="Report reader with Agree, Disagree, Comment, and Ask"></picture><br><b>Review the work in place.</b><br>Plans and report kinds have one action bar for the part in view. Ask the author to simplify a part. The report kit supports tables, video, comparisons, and a design gallery.</td>
60
+ </tr>
61
+ <tr>
62
+ <td><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-evidence-dark.webp"><img src="docs/media/phone-evidence.webp" width="260" alt="Evidence with inline captures and check results"></picture><br><b>Check the proof.</b><br>Open captures inline, or turn on Previews to see them all. Each proof carries its check result.</td>
63
+ <td><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-tickets-dark.webp"><img src="docs/media/phone-tickets.webp" width="260" alt="A ticket with its epic, tasks, progress, and linked work"></picture><br><b>Keep the task together.</b><br>Epics, tickets, and tasks roll up progress. Open their linked cards and Activity.</td>
64
+ </tr>
100
65
  </table>
101
66
 
102
- A plan is what an agent wants to do before it starts. A report is an explanation you asked for. Both open over the board, and the board, not the agent's page, owns the answer buttons. A **sign-in card** goes to the top of Decide with the service, what it unlocks, who is waiting and the time left on the link; the agent closes it with `sign-off-axi signin done`.
103
-
104
- ### It keeps only what matters
105
-
106
- A board that fills up with noise stops being read, so the rules sit at the door.
107
-
108
- - A question must say why it is asked, what each answer does, which one the agent recommends and what happens if you stay quiet; one that does not is refused with the fix ([the question standard](docs/contract.md#ask-schema-1)).
109
- - One decision is one card: raising the same question again changes nothing, and `--merge <key>` folds an older card into the better-asked one.
110
- - Proof posted as a Pass/Fail question is refused, and proof that is mostly text, a placeholder or a test card is refused too: proof goes to Evidence, as a capture of the real product; a report must be a page to tap, not a wall of words.
111
- - **Discard** takes a card off the board with one tap on a reason (Not relevant, Replaced by newer or Done elsewhere): it moves to **Archived**, **Undo** brings it back for 10 seconds, and its agent is told why once those 10 seconds are over. **Select** in Decide and Designs discards several at once, and **Restore** under Archived brings one back (`sign-off-axi dismiss <key> --reason replaced` and `restore <key>` do the same from a shell; without `--reason`, `dismiss` hides a card and tells no one). `sign-off-axi dismiss --filer <id>` closes a finished task: every card that id still waits on leaves the board. Reports leave the main view a day after they were filed and wait under **Archived**; nothing is deleted, and Restore brings one back. Evidence stays on its screen for good, newest first, until dismissed. Posting an evidence message again under its key, from the same filer, replaces the card. A new ask that repeats an open ask on the same decision is refused, and answering one closes its twins.
112
-
113
- ### One ticket ties a task's cards together
114
-
115
- A ticket is one piece of work, with a title and how far along it is, and optionally its priority, its project, its release, its state and its pull requests. Tickets nest as **epics > tickets > tasks**, and each is as far along as the average of what is under it (a roll-up from the tasks up). The **Tickets** tab shows your asks (and the system's own work) done, in progress, on hold and to do; a ticket's page shows its release, its state, each linked pull request and every card on it. Post any card with `--ticket <id>` (an ask, a design, proof, a report or a plan) and its screen shows a **Ticket** row (on an ask, **Its ask** in the panel under the question) with the ticket's title and progress; tap it for the ticket, which lists the tickets under it and every card on it, newest first, with their Activity. It is optional and only a link: a card without one, or naming a ticket the board has not got, works exactly as before.
116
-
117
- ```sh
118
- sign-off-axi ticket --id E-1 --title "Settings, start to finish" --release 0.6.5
119
- sign-off-axi ticket --id T-29 --title "Ship the new settings page" --parent E-1 --progress 40 --state "in progress" --pr https://github.com/owner/repo/pull/241
120
- sign-off-axi post --type evidence --ticket T-29 --title "Settings on a phone" --about "Umer changes the theme in one tap." --media shot.png
121
- ```
122
-
123
- Agents pass `--ticket` whenever they know the ticket their work belongs to, and move its `--progress` as the work goes; the shipped skill tells them so.
67
+ Every card has Previous and Next through its list. Feedback keeps its delivery trail. Discard asks for a reason and offers Undo; Archived keeps cards available to restore. The board installs as an app and reads its last update offline.
124
68
 
125
- ### How urgent, and whose project
69
+ ## Proof you can inspect
126
70
 
127
- A card may say how urgent it is with `--priority P0` (most urgent) to `P3` (least), on any `raise` or `post`; it shows as a chip, and a card without one shows none. A project may have its own icon: a picture in the board's folder (.png, .jpg, .webp or a script-free .svg, 500 KB or less), set once. The board serves only that exact file, and a project with no icon shows its initials.
71
+ - [Live demo](https://umeranjum17.github.io/sign-off/demo/): the real board with demo data. No install; demo answers stay in your browser.
72
+ - [Published package](https://www.npmjs.com/package/sign-off-axi): version **0.6.9**. The quick start above was run against the published package in an empty scratch folder.
73
+ - [Capture recipe](.agents/skills/done-done-verify-sign-off/SKILL.md): the README pictures come from the real board at 390 and 1280 pixels, light and dark. The answer loop also checks the saved answer through the CLI.
74
+ - [Integration journeys](test/) and [CI runs](https://github.com/umeranjum17/sign-off/actions/workflows/ci.yml): checks you can inspect and rerun.
128
75
 
129
- ```sh
130
- sign-off-axi raise --priority P1 --title "Ship the new settings page this week?" ...
131
- sign-off-axi project --name Beacon --icon icons/beacon.png
132
- ```
133
-
134
- Agents pass `--priority` when they know it, and set their project's icon when it ships one; the shipped skill tells them so.
135
-
136
- Every card's screen also keeps its **Activity**, oldest first, from what the board already saved: who posted it, your answer and note (and the one a change replaced), when the agent picked it up, a new round you asked for and your comments. A ticket's page shows its cards' activity together.
137
-
138
- ### The leash: what agents may do without you
139
-
140
- Every ask says what happens if you stay quiet (`--default`) and when (`--silent-by`). Your leash decides whether it happens. Set it in Today's More, or tap the **Leash** chip by the count on Decide. It is saved as `leash:` in the `config.yaml` beside the board.
141
-
142
- - **Ask me first** (`ask`): nothing happens; agents wait for you on every ask.
143
- - **When it matters** (`matters`, the default): when an ask's `--silent-by` time passes with no answer, the open board takes its default as the answer. Its card says **Taken by default at 13:00**, and the waiting agent gets it at once. A default that fell due while the board was closed is taken when it starts. An ask with no `--silent-by` waits.
144
- - **Act, then tell me** (`act`): the agent's recommended pick is taken the moment it raises the ask. Its card says **Agent picked B: Friday, tell me if wrong**.
145
-
146
- Both kinds wait under **Taken without you** in Decide until you open them, each with a one-tap **Change** on a phone. You can still answer or change them. The change reaches the agent as an answer that replaces the taken one, and the card's trail and Activity say what happened: Taken or Picked, then Delivered and Read.
147
-
148
- Some asks are never answered for you, whatever the leash: an ask raised with `--guard destructive|irreversible|money|credential|security`, and an ask whose words talk of spending money, deleting, what can't be undone, passwords and keys, or access. Designs, plans and sign-ins always wait too. Such an ask says **Always waits for you** and why.
149
-
150
- <p align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/desk-decide-dark.webp"><img src="docs/media/desk-decide.webp" alt="Decide on a desk: the Leash chip by the count, the asks that wait, Taken without you, and a money ask that always waits for you"></picture></p>
151
-
152
- ## What you can ask
153
-
154
- One line on what the agent runs per kind; the full reference is [docs/question-kinds.md](docs/question-kinds.md) and the message rules are in [docs/contract.md](docs/contract.md).
155
-
156
- | Kind | The agent runs | You see |
157
- |---|---|---|
158
- | Yes or no, pick one | `raise --ask-ref <captain-message-id-or-hold-id> --title ... --recommend ...` | buttons, the recommended one first |
159
- | Design pick (mocks A/B/C) | `post --type design --mock a=... --mock b=... --link a=<url>` | mocks side by side, an Open live link under each option that has a `--link`, a note on any part, and Open full screen: the prototype over the whole screen with its own navigation, Phone or Desktop width and another option as a reference |
160
- | Design review (one plain question) | `post --kind design --from design.json` | one question answered with **Yes** (a note optional) or **Change** (a note needed): each screen's frames side by side (Before and After, or Today, New and Target design), the changed area outlined and named on each and the frame Yes approves marked; post refuses an internal title and frames that look the same. Nothing in Decide: it waits under **Designs** for Main to review, and Main's `promote <key>` puts it in Decide |
161
- | Proof update | `post --type evidence --media shot.png` | the capture, its result, Pass / Fail / Needs work |
162
- | Proof with a file to install | `post --type evidence --media shot.png --attach app.apk` | the capture, and the file with its size and a Download button (any type, up to 1 GB, `SIGN_OFF_ATTACH_MAX_MB` to change) |
163
- | Explainer video | `post --type explainer --media clip.mp4 --chapter 0:00=Start` | a player with chapter buttons, then Yes or No |
164
- | Report | `report init <folder> --purpose <kind>` (always: research, plan, decision, audit, incident, status or other), fill `report.json`, then `post --kind report --from <folder>/report.json` | one scroll: the answer and a picture, what I understood, what I found, the next step; numbers under their part, one bar (Agree, Disagree, Comment, Ask) that acts on the part in view, and one Activity of every note, ask, reply and read |
165
- | Design output report | `report init <folder> --purpose <kind> --gallery`, put the screenshots under the board's folder, fill the gallery (label, model, run time, a caption and device per frame), then `post --kind report` | a swipeable strip of options on a phone, a row on a desk: each option's phone and desktop frame side by side, the caption with the model and run time under each, a tap to open full size with Previous and Next |
166
- | Report kit blocks | add `blocks` to a section of `report.json`: `callout`, `beats` (a stepper), `video`, `table`, `compare` (before and after), `evidence` rows, with files under the board's folder | the board draws each in its own type and colours, light and dark: full width, one gutter, no card inside a card; a gallery and a video may run wider than the reading column on a desk |
167
- | Diagram, interactive page | `post --type diagram --view flow.svg` | the picture on the card; interactive pages run sandboxed, and open full screen at phone or desktop width |
168
- | Plan | `report init <folder> --plan`, fill `plan.json`, then `post --kind plan --from plan.json` | the same scroll, plus each decision with my pick and a Go ahead |
169
- | Sign-in link | `post --type signin --service npm --link <url> --unlocks ...` | an Open button and the time left |
170
-
171
- ## How agents use it
76
+ ## Agent setup and limits
172
77
 
173
- Every item records who filed it. The agent raises with `--filer my-agent`, then blocks on `sign-off-axi wait --filer my-agent`, which returns that filer's answers, each once, and the card shows **Seen by agent**. An agent that cannot block can be woken instead: an `on_answer:` line in the `config.yaml` beside the board names a command the board runs for every answer, and a `notify:` block can send an HTTP POST when a card enters Decide. The worked examples are in [docs/commands.md](docs/commands.md).
78
+ The CLI and skill work without a supervisor. Agents post cards and wait for their own answers. Each ordinary command calls no model. The optional Claude Code owner dispatcher resumes the author for follow-up questions.
174
79
 
175
- For Go deeper on a guided plan or report, an idle **Claude Code** author can opt in to native session resumption: register its actual UUID with `owner --filer <id> --host claude --session <UUID> --cwd <absolute project folder>`, then run a separate `owner --dispatch --timeout 3600` process for the same board. `owner` shows queued delivery failures and distinguishes delivered from owner-read. No Firstmate is needed; other hosts are not supported for this wake path. See the [standalone owner recipe](docs/commands.md#standalone-go-deeper-owner).
80
+ The board runs on your machine. A phone needs a reachable address; [private network setup](docs/commands.md) explains binding and access tokens. Installing the app requires HTTPS or localhost. Offline mode shows the last update; sends fail and are not queued.
176
81
 
177
- Each ordinary sign-off command calls no model; the opt-in dispatcher invokes the installed Claude Code with its normal credentials and optional registered model. The shipped skill is deliberately thin: it tells the agent how to write a good card and defers to `sign-off-axi --help` for every flag, so an installed copy never goes stale.
82
+ ## More
178
83
 
179
- ## Works alone, or with its siblings
84
+ - [Board guide](docs/board-guide.md): screens, feedback trail, Discard, the leash, tickets, and report kit.
85
+ - [Commands and setup](docs/commands.md): install details, agent choices, network access, and all flags.
86
+ - [Question kinds](docs/question-kinds.md) · [Message contract](docs/contract.md) · [File formats](docs/file-formats.md).
87
+ - [Vision](VISION.md) · [Version history](https://github.com/umeranjum17/sign-off/tags).
180
88
 
181
- sign-off stands alone: the CLI and the skill need no other tool and no supervisor. They connect through one small written contract ([docs/contract.md](docs/contract.md)):
182
-
183
- - **done-done** (verification) posts its proof as Evidence and holds a run for your Approve or Fix first as a gate.
184
- - **learn-from-mistakes** (retros) posts each proposed change as a finding for you to Apply or Skip.
185
- - **first-draft** (design) writes its review links into a design card, shown as real links to open the mock-up.
186
- - **An agent supervisor**, such as Firstmate, or a lone agent in a terminal: both post the same asks, plans and reports and read the same answers.
187
-
188
- ## Docs and reference
189
-
190
- - [Commands](docs/commands.md): every command, the agent setup, exit codes.
191
- - [Question kinds](docs/question-kinds.md): the reference for each kind.
192
- - [Contract](docs/contract.md): the message rules agents post by.
193
- - [File formats](docs/file-formats.md): board and message files.
194
- - [Vision](VISION.md): who sign-off is for and where it is going.
195
-
196
- ## Development
197
-
198
- Builds first, then runs the integration journeys against the real CLI:
89
+ ## Contributing
199
90
 
200
91
  ```sh
92
+ npm install
201
93
  npm test
202
94
  npm run build:skill
203
- npm run build:demo
204
- npm pack
205
- ```
206
-
207
- - `npm test`: full journeys
208
- - `npm run build:skill`: writes the skill from `src/skill.ts`
209
- - `npm run build:demo`: the website (`site/`) and the demo board at `/demo/`, built into `demo-dist/`
210
- - `npm pack`: tarball
211
-
212
- A release is a merged version-bump PR: CI publishes the new version to npm.
213
-
214
- Change what a user sees or runs, and update this README, the skill (`src/skill.ts`), the website (`site/index.html`) and any screenshot they show in the same PR.
215
-
216
- ### Test runs never touch your board
217
-
218
- With no board named, a bare command uses the nearest `.sign-off/` that
219
- `init` set up, looking from the working folder up to its repo root (the
220
- folder holding `.git`) or up to HOME, but never HOME's own `.sign-off/`.
221
- Otherwise the default board is `~/.sign-off/decisions.json`, under
222
- `SIGN_OFF_HOME` when it is set. A test or agent run that must not reach your
223
- own board sets HOME and every `XDG_*` folder to one fresh temporary folder,
224
- or sets `SIGN_OFF_HOME` to one. A board file named on the command line
225
- always wins.
226
-
227
- ```sh
228
- tmp=$(mktemp -d)
229
- export SIGN_OFF_HOME="$tmp"
230
- (cd "$tmp" &&
231
- sign-off-axi raise --ask-ref message-example \
232
- --title "Probe?")
233
95
  ```
234
96
 
235
- Proof lives in [`.done-done.yaml`](.done-done.yaml): the `install`, `test`
236
- and `skill` journeys re-run every check the CI gate enforces, and CI calls
237
- them through `done-done run`.
97
+ Use an isolated board for tests. See the [development notes](docs/board-guide.md#development) and [verification skill](.agents/skills/done-done-verify-sign-off/SKILL.md).
238
98
 
239
- Write your own questions using the [file-format reference](docs/file-formats.md).
99
+ [MIT license](LICENSE).
package/dist/cli.js CHANGED
@@ -15,7 +15,7 @@ import { encode } from '@toon-format/toon';
15
15
  import { acknowledgeOwner, checkOwner, deliveries, dispatchOwners, registerOwner } from './owner.js';
16
16
  import { AxiError, runAxiCli } from 'axi-sdk-js';
17
17
  import { ATTACH_DEFAULT_MB, ATTACH_MAX, ATTACH_NAME, guardOf, plainAsk, CHAPTERS_MAX, SIGNED_IN, CLEARED, InputError, MEDIA_MAX, NOTE_MAX, REPLY, answersPath, appendAnnotation, appendPartComment, annotationsPath, markCommentsRead, readPartComments, replyComment, asks, appendAnswer, appendDismissed, DISCARD_REASONS, readDiscards, UNDO_MS, appendPromoted, appendKind, readKinds, appendSendBack, appendTicket, readTickets, rollUp, appendTicketComment, ticketCommentFault, ticketFault, appendProject, readProjects, projectFault, iconPathFault, iconContentFault, ICON_BYTES, ICON_TYPES, PRIORITIES, REPORT_PARTS, REPORT_PURPOSES, appendDesignAnswer, answerLines, appendMark, readTrail, trailPath, appendMessage, appendRound, awaitsVerdict, lapsed, askEngagement, checkEngagement, markEngagementSeen, appendRead, appendOpened, readOpened, answerDeeper, askDeeper, deeperPath, guidePart, markDeeperRead, readDeeper, readReads, screenViews, seenAt, isKey, loadDecisions, markDelivered, markSeen, messagesPath, readAnnotations, readAnswers, reading, readDismissed, readItems, readPromoted, sameDecision, readMessages, readRounds, roundsPath, sameRound, seenBy, seenPath, sentIds, unreadable as reasonOf, writeItems } from './store.js';
18
- import { ContractError, KINDS, MEDIA_BYTES, REPORT_INIT, checkMessage, diagramPages, diagramViews, galleryFrames, guided, kitWarnings, mediaFiles, parseMessage, reportPage } from './contract.js';
18
+ import { ContractError, KINDS, MEDIA_BYTES, REPORT_INIT, checkMessage, diagramPages, diagramViews, downloadFiles, galleryFrames, guided, kitWarnings, mediaFiles, parseMessage, reportPage } from './contract.js';
19
19
  import { faults, isStill } from './evidence-text.js';
20
20
  import { copiesOf, differs } from './images.js';
21
21
  import { deliverVault, errors, readVaultExpiry, sweepVault, takeVaultExpiries, VaultError } from './vault.js';
@@ -321,7 +321,7 @@ Brings back a card that dismiss hid. See \`sign-off-axi dismiss --help\`.
321
321
  `,
322
322
  ticket: `usage: sign-off-axi ticket [board.json] [--json]
323
323
  sign-off-axi ticket [board.json] --id <id> [--json]
324
- sign-off-axi ticket [board.json] --id <id> [--title <text>] [--progress <0-100>] [--parent <id>] [--priority P0-P3] [--project <name>] [--hold yes|no] [--asked yes|no] [--release <text>] [--state <state>] [--pr <url>]...
324
+ sign-off-axi ticket [board.json] --id <id> [--title <text>] [--progress <0-100>] [--parent <id>] [--priority P0-P3] [--project <name>] [--hold yes|no] [--asked yes|no] [--release <text>] [--state <state>] [--pr <url>]... [--stage queued|building|review|merging|done] [--last-event-at <epoch seconds or ISO UTC>]
325
325
  sign-off-axi ticket [board.json] --id <id> --comment '<json>'
326
326
  A ticket is one piece of work: its id, title, percent done and, optionally, the ticket it sits under, its priority, its project (whose icon its rows show),
327
327
  whether it is on hold and whether you asked for it (the board's Tickets tab then lists it under Your asks). It stands alone: nothing else needs it.
@@ -331,6 +331,7 @@ With any other flag, writes it: a new ticket needs --title (progress starts at 0
331
331
  A card names its ticket with --ticket <id> on raise and post (every --type and --kind), or "ticket" in a message or --from item:
332
332
  its screen then shows a Ticket row, the title and progress, that opens the ticket, which lists its asks, designs, proof and reports.
333
333
  A card whose ticket is not on the board shows no row; a ticket with no cards shows an empty page.
334
+ --stage is the sync-published stage, independent of progress. --last-event-at takes whole epoch seconds or an ISO UTC time ending in Z; stored as epoch seconds. Empty values clear either field.
334
335
  --release labels the release (up to 40 characters). --state is queued, "in progress", "on hold" or done; it takes precedence over progress/hold for grouping.
335
336
  --pr takes full HTTP(S) URLs, repeatable or comma separated; it replaces the linked PR list. Empty --release, --state or --pr clears that field.
336
337
  Without --state, grouping stays Done at 100%, else On hold with --hold yes, else In progress above 0%, else To do.
@@ -1443,12 +1444,12 @@ function feedback(args) {
1443
1444
  * What it prints carries the roll-up: a ticket with tickets under it is as far along as they are on average. */
1444
1445
  function ticket(args) {
1445
1446
  const { file, values, on } = parse('ticket', args, { id: { type: 'string' }, title: { type: 'string' }, progress: { type: 'string' }, parent: { type: 'string' }, priority: { type: 'string' },
1446
- project: { type: 'string' }, hold: { type: 'string' }, asked: { type: 'string' }, release: { type: 'string' }, state: { type: 'string' }, pr: { type: 'string', multiple: true }, comment: { type: 'string' }, json: { type: 'boolean' } });
1447
+ project: { type: 'string' }, hold: { type: 'string' }, asked: { type: 'string' }, release: { type: 'string' }, state: { type: 'string' }, stage: { type: 'string' }, 'last-event-at': { type: 'string' }, pr: { type: 'string', multiple: true }, comment: { type: 'string' }, json: { type: 'boolean' } });
1447
1448
  if (values.comment !== undefined)
1448
1449
  return ticketComment(file, values, on);
1449
- const { id, title, parent } = values, write = [title, values.progress, parent, values.priority, values.project, values.hold, values.asked, values.release, values.state, values.pr].some((v) => v !== undefined);
1450
+ const { id, title, parent } = values, write = [title, values.progress, parent, values.priority, values.project, values.hold, values.asked, values.release, values.state, values.pr, values.stage, values['last-event-at']].some((v) => v !== undefined);
1450
1451
  if (id === undefined && write)
1451
- throw fail('--title, --progress, --parent, --priority, --project, --hold, --asked, --release, --state and --pr need --id <ticket id>', 'VALIDATION_ERROR', 'sign-off-axi ticket --id T-29 --title "Ship the new settings page" --progress 40');
1452
+ throw fail('--title, --progress, --parent, --priority, --project, --hold, --asked, --release, --state, --pr, --stage and --last-event-at need --id <ticket id>', 'VALIDATION_ERROR', 'sign-off-axi ticket --id T-29 --title "Ship the new settings page" --progress 40');
1452
1453
  // The board is read once per command, and its warnings print once, however many tickets name cards.
1453
1454
  let byTicket;
1454
1455
  const cardsOf = (t) => {
@@ -1460,7 +1461,7 @@ function ticket(args) {
1460
1461
  }
1461
1462
  return [...(byTicket.get(t.id) ?? [])].reverse().sort((x, y) => (Date.parse(y.filed ?? '') || 0) - (Date.parse(x.filed ?? '') || 0));
1462
1463
  };
1463
- const row = (t) => ({ id: t.id, title: t.title, progress: t.progress, parent: t.parent ?? '', priority: t.priority ?? '', project: t.project ?? '', hold: t.hold ? 'yes' : 'no', asked: t.asked ? 'yes' : 'no', release: t.release ?? '', state: t.state ?? '', prs: t.prs ?? [] });
1464
+ const row = (t) => ({ id: t.id, title: t.title, progress: t.progress, parent: t.parent ?? '', priority: t.priority ?? '', project: t.project ?? '', hold: t.hold ? 'yes' : 'no', asked: t.asked ? 'yes' : 'no', release: t.release ?? '', state: t.state ?? '', prs: t.prs ?? [], stage: t.stage ?? '', last_event_at: t.last_event_at ?? '' });
1464
1465
  if (id === undefined) {
1465
1466
  const all = rollUp(readTickets(file, warn));
1466
1467
  if (values.json)
@@ -1481,6 +1482,15 @@ function ticket(args) {
1481
1482
  return toon({ board: tilde(file), ticket: row(t), cards: cards.length ? cards : '0 cards name this ticket' }, [`Pass --ticket ${id} to raise or post, so the card shows this ticket`]);
1482
1483
  }
1483
1484
  const progress = values.progress === undefined ? undefined : /^\d{1,3}$/.test(values.progress) ? Number(values.progress) : NaN;
1485
+ const event = values['last-event-at'];
1486
+ let lastEvent = event === '' ? '' : event === undefined ? undefined : NaN;
1487
+ if (event && /^\d+$/.test(event))
1488
+ lastEvent = Number(event);
1489
+ else if (event && /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.000)?Z$/.test(event)) {
1490
+ const ms = Date.parse(event);
1491
+ if (Number.isFinite(ms) && new Date(ms).toISOString() === event.replace(/(?:\.000)?Z$/, '.000Z'))
1492
+ lastEvent = ms / 1000;
1493
+ }
1484
1494
  // yes or no; anything else is kept as given, so the rule below refuses it by name.
1485
1495
  const yesNo = (v) => (v === undefined ? undefined : { yes: true, no: false }[v] ?? v);
1486
1496
  mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
@@ -1490,14 +1500,14 @@ function ticket(args) {
1490
1500
  throw fail(`no ticket ${id} on ${tilde(file)} yet: a new ticket needs --title`, 'VALIDATION_ERROR', `sign-off-axi ticket${on} --id ${id} --title "<what the work is>" --progress 0`);
1491
1501
  // A later write changes only what it gives; an empty --parent, --priority, --project, --hold or --asked takes that field off.
1492
1502
  const next = { ...had, id, title: title ?? had.title, progress: progress ?? had?.progress ?? 0 };
1493
- for (const [k, v] of Object.entries({ parent, priority: values.priority, project: values.project, hold: yesNo(values.hold), asked: yesNo(values.asked), release: values.release, state: values.state, prs: values.pr === undefined ? undefined : values.pr.every((v) => v === '') ? '' : [...new Set(values.pr.flatMap((v) => v.split(',').map((u) => u.trim())))] }))
1503
+ for (const [k, v] of Object.entries({ parent, priority: values.priority, project: values.project, hold: yesNo(values.hold), asked: yesNo(values.asked), release: values.release, state: values.state, stage: values.stage, last_event_at: lastEvent, prs: values.pr === undefined ? undefined : values.pr.every((v) => v === '') ? '' : [...new Set(values.pr.flatMap((v) => v.split(',').map((u) => u.trim())))] }))
1494
1504
  if (v === '')
1495
1505
  delete next[k];
1496
1506
  else if (v !== undefined)
1497
1507
  next[k] = v;
1498
1508
  const fault = ticketFault(next);
1499
1509
  if (fault)
1500
- throw fail(`ticket ${id}: ${fault.replace(/^(title|progress|parent|priority|project|hold|asked|release|state|prs)/, '--$1').replace(/^--prs\b/, '--pr').replace('must be true or false', 'must be yes or no')}`, 'VALIDATION_ERROR', 'Run `sign-off-axi ticket --help`');
1510
+ throw fail(`ticket ${id}: ${fault.replace(/^(title|progress|parent|priority|project|hold|asked|release|state|stage|last_event_at|prs)/, '--$1').replace(/^--prs\b/, '--pr').replace(/^--last_event_at\b/, '--last-event-at').replace('must be true or false', 'must be yes or no')}`, 'VALIDATION_ERROR', 'Run `sign-off-axi ticket --help`');
1501
1511
  const same = had && JSON.stringify(row(had)) === JSON.stringify(row(next));
1502
1512
  if (!same)
1503
1513
  appendTicket(file, next);
@@ -1510,7 +1520,7 @@ function ticketComment(file, values, on) {
1510
1520
  const { id } = values, EXAMPLE = `sign-off-axi ticket${on} --id T-29 --comment '{"id":"3f9a1c2e7b6d4a10","at":1792820000,"text":"Is the export in this?","error":null,"picked":1792820300,"replies":[]}'`;
1511
1521
  if (id === undefined || !isKey(id))
1512
1522
  throw fail('--comment needs --id <ticket id>, 1-128 of A-Z a-z 0-9 . _ -', 'VALIDATION_ERROR', EXAMPLE);
1513
- if (['title', 'progress', 'parent', 'priority', 'project', 'hold', 'asked', 'release', 'state', 'pr'].some((k) => values[k] !== undefined))
1523
+ if (['title', 'progress', 'parent', 'priority', 'project', 'hold', 'asked', 'release', 'state', 'pr', 'stage', 'last-event-at'].some((k) => values[k] !== undefined))
1514
1524
  throw fail('--comment is written on its own, with no other ticket field', 'VALIDATION_ERROR', EXAMPLE);
1515
1525
  let c;
1516
1526
  try {
@@ -3444,8 +3454,10 @@ async function route(req) {
3444
3454
  const mock = diagram || frame ? (diagram ?? frame).src : value ? d?.mocks?.find((m) => m.value === value)?.path : value === '' && d?.view;
3445
3455
  try {
3446
3456
  const path = rest.map(decodeURIComponent).join('/');
3447
- const download = frame && d?.sections?.some((s) => s.blocks?.some((b) => b.type === 'file' && b.src === frame.src));
3448
- const type = download ? 'application/octet-stream' : assetTypes[extname(path).toLowerCase()];
3457
+ const linked = frame && downloadFiles(d).includes(frame.src);
3458
+ const explicitFile = frame && d?.sections?.some((s) => s.blocks?.some((b) => b.type === 'file' && b.src === frame.src));
3459
+ const download = explicitFile || linked && new URL(req.url, 'http://localhost').searchParams.get('download') === '1';
3460
+ const type = download ? 'application/octet-stream' : linked && !/\.(png|jpe?g|webp|mp4|webm)$/i.test(path) ? undefined : assetTypes[extname(path).toLowerCase()];
3449
3461
  if (!mock || !type)
3450
3462
  return [404, { error: 'Mock is unavailable' }];
3451
3463
  const actual = under(dirname(under(dirname(file), mockFile(mock))), path);
@@ -3543,7 +3555,7 @@ async function route(req) {
3543
3555
  }
3544
3556
  if (req.method === 'GET' && req.url === '/api/board')
3545
3557
  return [200, { file, pid: process.pid }];
3546
- if (req.method === 'GET' && req.url === '/api/decisions') {
3558
+ if (req.method === 'GET' && ['/api/decisions', '/api/state'].includes(req.url ?? '')) {
3547
3559
  let timer;
3548
3560
  await Promise.race([guardReady, new Promise((ok) => { timer = setTimeout(ok, GUARD_WAIT); })]);
3549
3561
  clearTimeout(timer);