sign-off-axi 0.3.6 → 0.4.0
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 +351 -151
- package/dist/cli.js +352 -48
- package/dist/contract.js +204 -46
- package/dist/store.js +97 -12
- package/fixtures/contract/README.md +2 -2
- package/fixtures/contract/ask.v1/invalid-default_if_silent-missing.json +1 -0
- package/fixtures/contract/ask.v1/invalid-link-c1-control.json +1 -1
- package/fixtures/contract/ask.v1/invalid-merges-own-key.json +1 -0
- package/fixtures/contract/ask.v1/invalid-options-missing-letter.json +1 -0
- package/fixtures/contract/ask.v1/invalid-options-not-an-answer.json +1 -0
- package/fixtures/contract/ask.v1/invalid-options-outcome-missing.json +1 -0
- package/fixtures/contract/ask.v1/invalid-options-yes-label.json +1 -0
- package/fixtures/contract/ask.v1/invalid-options-yes-no-for-letters.json +1 -0
- package/fixtures/contract/ask.v1/invalid-recommend-unknown.json +1 -1
- package/fixtures/contract/ask.v1/invalid-recommend_reason-missing.json +1 -0
- package/fixtures/contract/ask.v1/invalid-recommend_value-missing.json +1 -1
- package/fixtures/contract/ask.v1/invalid-recommend_value-repeated.json +1 -1
- package/fixtures/contract/ask.v1/invalid-silent_by-not-utc.json +1 -0
- package/fixtures/contract/ask.v1/invalid-title-id-word.json +1 -0
- package/fixtures/contract/ask.v1/invalid-title-not-a-question.json +1 -0
- package/fixtures/contract/ask.v1/invalid-title-robot-prefix.json +1 -0
- package/fixtures/contract/ask.v1/invalid-title-too-long.json +1 -1
- package/fixtures/contract/ask.v1/invalid-title-too-many-words.json +1 -0
- package/fixtures/contract/ask.v1/invalid-title-unpaired-surrogate.json +1 -1
- package/fixtures/contract/ask.v1/invalid-why-missing.json +1 -0
- package/fixtures/contract/ask.v1/valid-pick-one.json +25 -6
- package/fixtures/contract/ask.v1/valid-yes-no.json +1 -1
- package/fixtures/contract/plan.v1/invalid-id-repeated.json +1 -0
- package/fixtures/contract/plan.v1/invalid-options-unknown.json +1 -0
- package/fixtures/contract/plan.v1/invalid-question-over-12-words.json +1 -0
- package/fixtures/contract/plan.v1/invalid-question-two-questions.json +1 -0
- package/fixtures/contract/plan.v1/invalid-recommend-missing.json +1 -0
- package/fixtures/contract/plan.v1/invalid-sections-missing.json +1 -0
- package/fixtures/contract/plan.v1/invalid-view-missing.json +1 -0
- package/fixtures/contract/plan.v1/page/index.html +42 -0
- package/fixtures/contract/plan.v1/valid-full.json +45 -0
- package/fixtures/contract/plan.v1/valid-minimal.json +13 -0
- package/package.json +2 -1
- package/web/demo/media/explainer.mp4 +0 -0
- package/web/demo/media/proof-dark.svg +12 -0
- package/web/demo/media/proof-light.svg +12 -0
- package/web/demo/mocks/a.html +26 -0
- package/web/demo/mocks/b.html +33 -0
- package/web/demo/mocks/c.html +29 -0
- package/web/demo/seed.json +116 -0
- package/web/demo/shim.js +180 -0
- package/web/index.html +173 -40
package/README.md
CHANGED
|
@@ -1,222 +1,422 @@
|
|
|
1
|
+
<p align="center"><b>▶ <a href="https://umeranjum17.github.io/sign-off/">Try it live</a></b> — the real board on demo data: tap an answer and watch it save. No install, nothing leaves your browser.</p>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="docs/media/demo-tap.gif" width="300" alt="Tap Yes on a demo question, see it saved, and read the saved answer back">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
1
7
|
<p align="center">
|
|
2
8
|
<img src="docs/logo.svg" width="72" height="72" alt="sign-off logo">
|
|
3
9
|
</p>
|
|
4
10
|
|
|
5
11
|
<h1 align="center">sign-off</h1>
|
|
6
12
|
|
|
7
|
-
<p align="center"><b>
|
|
13
|
+
<p align="center"><b>One calm page for every question the programs working for you need answered.</b><br>Yes or no, pick-one, mock-ups, proof photos, short videos, sign-in links: each lands on a board on your own machine, you answer with one tap, and the answer is saved for the asker to read.</p>
|
|
8
14
|
|
|
9
15
|
<p align="center">
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
<picture>
|
|
17
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/media/hero-dark.gif">
|
|
18
|
+
<img src="docs/media/hero.gif" width="420" alt="Ask, tap, answer: an agent raises a yes/no question, you tap Yes on your phone, the agent reads the saved answer">
|
|
19
|
+
</picture>
|
|
13
20
|
</p>
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
<img src="docs/media/desktop-light-list.png" width="860" alt="Light desktop: a pick-one question with Today, Friday and Next week, two Yes/No questions, and the weekly digest answered Yes with a note">
|
|
17
|
-
</p>
|
|
22
|
+
## One-minute quick start
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
```sh
|
|
25
|
+
npm install -g sign-off-axi
|
|
26
|
+
sign-off-axi raise \
|
|
27
|
+
--title "Keep the weekly \
|
|
28
|
+
digest?" \
|
|
29
|
+
--why "The paid plan \
|
|
30
|
+
renews Monday." \
|
|
31
|
+
--outcome yes="It keeps \
|
|
32
|
+
going out." \
|
|
33
|
+
--outcome no="It stops \
|
|
34
|
+
this week." \
|
|
35
|
+
--recommend yes \
|
|
36
|
+
--recommend-reason "Readers \
|
|
37
|
+
still open it." \
|
|
38
|
+
--default yes
|
|
39
|
+
sign-off-axi open
|
|
40
|
+
```
|
|
20
41
|
|
|
21
|
-
|
|
42
|
+
Open the printed URL, click **Yes**, then read the answer back the way an
|
|
43
|
+
agent would:
|
|
22
44
|
|
|
23
|
-
|
|
45
|
+
```sh
|
|
46
|
+
sign-off-axi answers \
|
|
47
|
+
--key keep-the-weekly-digest
|
|
48
|
+
```
|
|
24
49
|
|
|
25
|
-
|
|
50
|
+
That is the whole loop: the agent raises, you tap, the agent reads.
|
|
51
|
+
Every question says why it is asked, what each answer does, which one the
|
|
52
|
+
agent recommends and why, and what happens if you stay quiet; a question
|
|
53
|
+
that does not is refused before it reaches you (see
|
|
54
|
+
[the question standard](docs/contract.md#ask-schema-1)). The
|
|
55
|
+
board is the default one (`~/.sign-off/decisions.json`); name a file after
|
|
56
|
+
any command to use another board.
|
|
26
57
|
|
|
27
|
-
##
|
|
58
|
+
## What you can ask
|
|
28
59
|
|
|
29
|
-
|
|
60
|
+
One line on what the agent runs, one screenshot of what you see. The full
|
|
61
|
+
reference for each kind lives in [docs/question-kinds.md](docs/question-kinds.md);
|
|
62
|
+
the message rules are in [docs/contract.md](docs/contract.md).
|
|
30
63
|
|
|
31
|
-
|
|
64
|
+
### Yes or no
|
|
32
65
|
|
|
33
66
|
```sh
|
|
34
|
-
|
|
67
|
+
sign-off-axi raise \
|
|
68
|
+
--title "Keep the weekly \
|
|
69
|
+
digest?" \
|
|
70
|
+
--about "Open rate held \
|
|
71
|
+
steady at about a third \
|
|
72
|
+
for two months." \
|
|
73
|
+
--why "The paid plan \
|
|
74
|
+
renews Monday." \
|
|
75
|
+
--outcome yes="It keeps \
|
|
76
|
+
going out each Monday." \
|
|
77
|
+
--outcome no="It stops \
|
|
78
|
+
after this week." \
|
|
79
|
+
--recommend yes \
|
|
80
|
+
--recommend-reason "Readers \
|
|
81
|
+
still open it." \
|
|
82
|
+
--default yes \
|
|
83
|
+
--silent-by 2026-10-19T09:00Z
|
|
35
84
|
```
|
|
36
85
|
|
|
37
|
-
|
|
86
|
+
<picture>
|
|
87
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-yes-no-dark.webp">
|
|
88
|
+
<img src="docs/media/kind-yes-no.webp" alt="A yes/no question with the recommended Yes marked">
|
|
89
|
+
</picture>
|
|
38
90
|
|
|
39
|
-
|
|
40
|
-
|---|---|---|---|
|
|
41
|
-
| Claude Code | `claude-code` | `claude` | `.claude/skills/` |
|
|
42
|
-
| Codex | `codex` | `codex` | `.agents/skills/` |
|
|
43
|
-
| OpenCode | `opencode` | `opencode` | `.agents/skills/` |
|
|
44
|
-
| Pi | `pi` | `pi` | `.agents/skills/` |
|
|
45
|
-
| Gemini CLI | `gemini-cli` | `gemini` | `.agents/skills/` |
|
|
91
|
+
*A yes/no ask. The recommended answer is marked.*
|
|
46
92
|
|
|
47
|
-
|
|
93
|
+
### Pick one, with a recommendation
|
|
48
94
|
|
|
49
95
|
```sh
|
|
50
|
-
|
|
96
|
+
sign-off-axi raise \
|
|
97
|
+
--title "When should \
|
|
98
|
+
the new settings \
|
|
99
|
+
page go out?" \
|
|
100
|
+
--about "No new \
|
|
101
|
+
bug reports for three \
|
|
102
|
+
weeks." \
|
|
103
|
+
--why "Support is fully \
|
|
104
|
+
staffed on Friday." \
|
|
105
|
+
--option today=Today \
|
|
106
|
+
--option friday=Friday \
|
|
107
|
+
--option next-week="Next \
|
|
108
|
+
week" \
|
|
109
|
+
--outcome today="It goes \
|
|
110
|
+
out now." \
|
|
111
|
+
--outcome friday="It goes \
|
|
112
|
+
out with support on hand." \
|
|
113
|
+
--outcome next-week="It \
|
|
114
|
+
waits a week." \
|
|
115
|
+
--recommend friday \
|
|
116
|
+
--recommend-reason "Support \
|
|
117
|
+
can answer questions." \
|
|
118
|
+
--default next-week
|
|
51
119
|
```
|
|
52
120
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
121
|
+
<picture>
|
|
122
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-pick-one-dark.webp">
|
|
123
|
+
<img src="docs/media/kind-pick-one.webp" alt="A pick-one question with three options and Friday recommended">
|
|
124
|
+
</picture>
|
|
125
|
+
|
|
126
|
+
*Pick one of three. Friday is recommended; silence means next week.*
|
|
127
|
+
|
|
128
|
+
### Design pick, with mocks A/B/C
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
sign-off-axi post \
|
|
132
|
+
--type design \
|
|
133
|
+
--title "Pick the settings \
|
|
134
|
+
layout" \
|
|
135
|
+
--option a="Layout A" \
|
|
136
|
+
--option b="Layout B" \
|
|
137
|
+
--option c="Layout C" \
|
|
138
|
+
--mock a=mocks/a.html \
|
|
139
|
+
--mock b=mocks/b.html \
|
|
140
|
+
--mock c=mocks/c.html \
|
|
141
|
+
--tradeoff "A is fastest \
|
|
142
|
+
to tap; B shows both \
|
|
143
|
+
choices; C fits small \
|
|
144
|
+
screens." \
|
|
145
|
+
--recommend b
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
<picture>
|
|
149
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-design-dark.webp">
|
|
150
|
+
<img src="docs/media/kind-design.webp" alt="Three mock-ups side by side in phone frames with Pick buttons, Layout B recommended">
|
|
151
|
+
</picture>
|
|
152
|
+
|
|
153
|
+
*Three layouts side by side. Layout B is recommended.*
|
|
154
|
+
|
|
155
|
+
### Proof update, with Pass / Fail / Needs work
|
|
57
156
|
|
|
58
|
-
|
|
157
|
+
```sh
|
|
158
|
+
sign-off-axi post \
|
|
159
|
+
--type evidence \
|
|
160
|
+
--title "Settings page \
|
|
161
|
+
loads in a second" \
|
|
162
|
+
--about "Umer opens \
|
|
163
|
+
the new page on a phone." \
|
|
164
|
+
--media shot-light.png \
|
|
165
|
+
--media shot-dark.png
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
<picture>
|
|
169
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-evidence-dark.webp">
|
|
170
|
+
<img src="docs/media/kind-evidence.webp" alt="Evidence update 1 with two captures and Pass, Fail, Needs work buttons">
|
|
171
|
+
</picture>
|
|
59
172
|
|
|
60
|
-
|
|
173
|
+
*Proof of a fix, with two captures. Pass, Fail, or Needs work.*
|
|
61
174
|
|
|
62
|
-
|
|
175
|
+
### Explainer video, with chapters
|
|
63
176
|
|
|
64
177
|
```sh
|
|
65
|
-
|
|
66
|
-
|
|
178
|
+
sign-off-axi post \
|
|
179
|
+
--type explainer \
|
|
180
|
+
--title "Is the new \
|
|
181
|
+
sign-in clear?" \
|
|
182
|
+
--about "Two steps: \
|
|
183
|
+
password, then a code." \
|
|
184
|
+
--media explainer.mp4 \
|
|
185
|
+
--chapter 0:00=Start \
|
|
186
|
+
--chapter 0:02="The tap"
|
|
67
187
|
```
|
|
68
188
|
|
|
69
|
-
|
|
189
|
+
<picture>
|
|
190
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-explainer-dark.webp">
|
|
191
|
+
<img src="docs/media/kind-explainer.webp" alt="An explainer card with a video player, chapter buttons and Yes/No">
|
|
192
|
+
</picture>
|
|
70
193
|
|
|
71
|
-
|
|
194
|
+
*A short video with chapters, then Yes or No.*
|
|
72
195
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
on and read back.
|
|
196
|
+
### Explainer page, to read
|
|
197
|
+
|
|
198
|
+
```sh
|
|
199
|
+
sign-off-axi report init \
|
|
200
|
+
.sign-off/mocks/sign-in
|
|
201
|
+
d=mocks/sign-in
|
|
202
|
+
sign-off-axi post \
|
|
203
|
+
--type report \
|
|
204
|
+
--title "Sign-in now \
|
|
205
|
+
takes 2 steps" \
|
|
206
|
+
--about "Why the code \
|
|
207
|
+
step moved first." \
|
|
208
|
+
--view $d/index.html \
|
|
209
|
+
--preview $d/preview.svg
|
|
210
|
+
```
|
|
89
211
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
212
|
+
<picture>
|
|
213
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-report-dark.webp">
|
|
214
|
+
<img src="docs/media/kind-report.webp" alt="An explainer page: the answer in one sentence, a diagram, steps to tap through, folded details">
|
|
215
|
+
</picture>
|
|
93
216
|
|
|
94
|
-
|
|
217
|
+
*An explainer page to tap through and read.*
|
|
95
218
|
|
|
96
|
-
|
|
219
|
+
### Diagram
|
|
97
220
|
|
|
98
221
|
```sh
|
|
99
|
-
|
|
100
|
-
|
|
222
|
+
sign-off-axi post \
|
|
223
|
+
--type diagram \
|
|
224
|
+
--title "Is this the \
|
|
225
|
+
sign-in flow?" \
|
|
226
|
+
--about "Password first, \
|
|
227
|
+
then a code, then in." \
|
|
228
|
+
--view flow.svg \
|
|
229
|
+
--recommend yes
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
<picture>
|
|
233
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-diagram-dark.webp">
|
|
234
|
+
<img src="docs/media/kind-diagram.webp" alt="A question shown with a three-box flow diagram at card width">
|
|
235
|
+
</picture>
|
|
101
236
|
|
|
102
|
-
|
|
103
|
-
cp fixtures/demo-decisions.json /tmp/decisions.json
|
|
104
|
-
node dist/cli.js serve /tmp/decisions.json # prints http://127.0.0.1:4410/
|
|
105
|
-
# Optional: add --evidence-root /path/to/project-store to read proof records/media (the root containing .done-done/)
|
|
237
|
+
*A question drawn as a three-box flow.*
|
|
106
238
|
|
|
107
|
-
|
|
108
|
-
|
|
239
|
+
### Interactive page
|
|
240
|
+
|
|
241
|
+
```sh
|
|
242
|
+
sign-off-axi post \
|
|
243
|
+
--type interactive \
|
|
244
|
+
--title "How many \
|
|
245
|
+
testers approved?" \
|
|
246
|
+
--about "Tap the counter \
|
|
247
|
+
to add yourself." \
|
|
248
|
+
--view counter.html \
|
|
249
|
+
--option yes=Yes \
|
|
250
|
+
--option no="Needs \
|
|
251
|
+
work" \
|
|
252
|
+
--recommend yes
|
|
109
253
|
```
|
|
110
254
|
|
|
111
|
-
|
|
255
|
+
<picture>
|
|
256
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-interactive-dark.webp">
|
|
257
|
+
<img src="docs/media/kind-interactive.webp" alt="A question with a sandboxed counter page to tap">
|
|
258
|
+
</picture>
|
|
112
259
|
|
|
113
|
-
|
|
114
|
-
|
|
260
|
+
*A live counter to tap, in a sandboxed page.*
|
|
261
|
+
|
|
262
|
+
### Sign-in link
|
|
115
263
|
|
|
264
|
+
```sh
|
|
265
|
+
sign-off-axi post \
|
|
266
|
+
--type signin \
|
|
267
|
+
--service "npm" \
|
|
268
|
+
--link \
|
|
269
|
+
https://example.com/abc123 \
|
|
270
|
+
--unlocks "Publish \
|
|
271
|
+
the release" \
|
|
272
|
+
--waits "Umer" \
|
|
273
|
+
--expires 15m \
|
|
274
|
+
--key npm-login
|
|
116
275
|
```
|
|
117
|
-
sign-off-axi list the decisions waiting on the user (same as list)
|
|
118
|
-
sign-off-axi raise --title "<question>" [--key <key>] [--about <text>] [--detail <text>]
|
|
119
|
-
[--option <value>=<label>]... [--recommend <value>] [--default <value>] [--close done|release]
|
|
120
|
-
sign-off-axi raise --from <decisions.json> add every decision in a decisions file (a new item of a contract kind carries `contract`, `kind` and `schema`: see docs/contract.md)
|
|
121
|
-
sign-off-axi post --kind <kind> --from <message.json|-> post one contract message, from a file or stdin
|
|
122
|
-
sign-off-axi post --type design --title "<question>" [--option <value>=<label>]... [--mock <value>=<path>]...
|
|
123
|
-
[--tradeoff <text>] [--recommend <value>] ask for a pick between mocks, shown side by side
|
|
124
|
-
(--type decision is the same as raise; raise stays its alias)
|
|
125
|
-
sign-off-axi post --type evidence --title "<what this shows>" --about "<short note>" --media <file>... [--link <commit or URL>]
|
|
126
|
-
post the next numbered evidence update for a pass/fail/needs-work verdict
|
|
127
|
-
sign-off-axi post --type diagram|interactive --title "<question>" --view <path> [--option <value>=<label>]...
|
|
128
|
-
ask with a diagram image, or a sandboxed interactive .html page
|
|
129
|
-
sign-off-axi post --type explainer --title "<question>" --media <video> [--about <summary>] [--chapter <m:ss>=<title>]...
|
|
130
|
-
ask with a narrated video the agent made with free local tools
|
|
131
|
-
sign-off-axi report init <folder> write a report page to fill: the answer and a picture, a diagram, steps to tap, the detail folded
|
|
132
|
-
sign-off-axi post --type report --title "<the answer>" --about "<why it matters>" --view <page.html> --preview <picture>
|
|
133
|
-
post that page as a report: a card with its picture that opens full screen; text alone is refused
|
|
134
|
-
sign-off-axi init [dir] [--agent auto|<name>] write the repo setup under <dir>/.sign-off/ (config, board, mocks/ and evidence/ folders)
|
|
135
|
-
and install the skill for each chosen coding agent (auto: each one on PATH)
|
|
136
|
-
sign-off-axi doctor [dir] check that setup and each agent's skill; every failure names its fix
|
|
137
|
-
sign-off-axi validate <message.json>... [--kind <kind>] [--expect-invalid] check ask, report, evidence, gate and finding messages against the contract (docs/contract.md) without posting
|
|
138
|
-
sign-off-axi open [--port 4410] [--bind <address>] [--evidence-root <dir>] start the board in the background; prints its URL and pid
|
|
139
|
-
sign-off-axi status is the board open started for this file running? its URL and pid
|
|
140
|
-
sign-off-axi stop stop that board
|
|
141
|
-
sign-off-axi serve [--port 4410] [--bind <address>] [--evidence-root <dir>] run the board in the foreground (--port 0 picks a free port)
|
|
142
|
-
sign-off-axi answers [--key <key>] the latest answer per decision, and how many still wait
|
|
143
|
-
sign-off-axi wait [--filer <id>] [--key <key>] [--timeout 600] [--events (needs --filer)] block until the user answers, then print it like answers; --events prints one JSON event line per answer and round (docs/contract.md section 7)
|
|
144
|
-
(--filer: only that filer's items, each marked taken; without --key each answer once)
|
|
145
|
-
sign-off-axi poll [--timeout 600] block until the user comments or queues a prompt, then print each new one once
|
|
146
|
-
sign-off-axi export [--out <file.html>] write a frozen read-only copy of the board as one HTML file
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
- The board is the one you name right after the command (`sign-off-axi answers ./decisions.json`). With none, it is the nearest `.sign-off/` board that `init` set up, found by the walk described in [Test runs never touch your board](#test-runs-never-touch-your-board); otherwise the default board there (`~/.sign-off/decisions.json`, or under `SIGN_OFF_HOME`). Answers are saved next to it.
|
|
150
|
-
- Next-step commands in the output name paths under the working folder relative to it, and say `npx -y sign-off-axi` when sign-off-axi runs through `npx`.
|
|
151
|
-
- `post --type design` asks for a pick between up to 6 mock options, each with an optional `--mock <value>=<path>` file (`.html`, `.svg`, `.png`, `.jpg`, `.jpeg`, `.webp`, 5 MB or less) shown as a tall phone frame above its button (the recommended one first; on a phone the frames swipe sideways, the next one peeking in, and a tap opens one full size), plus a `--tradeoff` line and a `--recommend` badge. Mock paths are relative to the board file's folder and never escape it. One `.html` page can hold every variant: `--mock a=mocks/<key>/index.html?variant=a` loads the page with `?variant=a`. An `.html` mock or view loads the css, scripts, images and fonts beside it by relative name (`style.css`, `img/dot.png`), from its own folder only, never anything above it. The board adds a small pointing script to each `.html` mock: once the board turns it on for that frame, a tap on a part of the mock is kept from the mock and named back to the board as a path to that element, its tag and its words; it passes only text and gives the mock no reach into the board. With **Comment on the mocks** on, a tap opens a comment on that element: one per element, listed under the mocks with Edit and Delete (words typed but not yet saved stay with their element while the page is open, so tapping another element and back keeps them; a reload drops them), kept on the board and shown, until it is sent, only on the exact mock file it was made on. The comments go to the agent with the next answer on that card (a pick, Other answer or **Another round**, which may then have no note), each with its element, in `wait` and `answers` (`comments`); once sent they are listed as sent and can no longer change. An option with no mock, or whose file is gone, says "No mock for this option". `post --type decision` (the default) is exactly `raise`, and `raise` stays as its alias.
|
|
152
|
-
- `post --type evidence` posts the next numbered update (`update-1`, `update-2`, ...) with 1 to 12 screenshots or recordings (`.png`, `.jpg`, `.jpeg`, `.webp`, `.mp4`, `.webm`, up to 50 MB each), a short note and an optional commit or PR link. The files are copied into `evidence/<number>/` next to the board, so the board is the store: no hosted service. The page shows the updates newest first; each file is served only while its size and SHA-256 still match what was posted, and recordings play inline on a phone. The person gives a verdict, **Pass**, **Fail** or **Needs work**, with an optional note, read back with `answers` or `wait --key update-<number>`.
|
|
153
|
-
- `post --type diagram` and `post --type interactive` show a question with a picture: `--view <path>` names one board-relative file, an image (`.svg`, `.png`, `.jpg`, `.jpeg`, `.webp`) drawn at card width for a diagram, or one self-contained `.html` page that runs in a sandboxed frame for interactive. Every mock and view is served under a Content-Security-Policy with no `fetch`, no remote files and no access to the dashboard or its tab. They answer like a decision.
|
|
154
|
-
- `post --type report` posts an explainer page the user reads, with no answer: something they asked to have explained, or a concept, design or trade-off they must understand. `sign-off-axi report init <folder>` writes the page to fill, one self-contained `.html` file, phone first, light and dark: the answer in one plain sentence with a picture and an as-of line naming the date the facts were true, then a diagram, then steps the reader taps through, then the detail folded. Reports and explainers lists each one as a card with its picture; a tap opens the page over the whole board, on a phone and a desk alike, with **‹ Reports** (or Escape) to go back. A report of text only is refused with the command that makes the page; one stored before reports had pages still loads, folded under **Notes**. Proof that something works is evidence, and a merge or a status note is neither.
|
|
155
|
-
- `post --type explainer` shows a question with a narrated video the agent made itself (stills plus speech from a free local engine such as piper or espeak-ng, joined with ffmpeg; the skill has the recipe). `--media` names one `.mp4` or `.webm`, copied into `evidence/<number>/` and served hash-checked like evidence media; it plays on the card with its own controls, at phone width too. `--about` is a short summary and each `--chapter m:ss=<title>` is a button that plays the video from there. sign-off itself never makes the video: it calls no speech engine, no ffmpeg and no network service.
|
|
156
|
-
- The person can also tap **Another round** inside **Other answer** (with the typed note), which wakes a waiting agent the same way an answer does; the agent reads it back in `answers --json` under `rounds`, with any comments on mock elements it carried.
|
|
157
|
-
- **Dismiss** (the × on an item in a list, **Dismiss** under Needs you first and beside **Skip for now** in Triage, or **Clear all done** on Answered) hides items from the board, Today included; **Undo** in the toast brings them back, and so does **Restore** under **Dismissed (N)** at the end of Needs you first and Triage. `list` counts a dismissed item as neither waiting nor answered, as the board does (`list --full` says `dismissed` for an unanswered one and still its answer for an answered one), and `answers` does not count it as waiting; the agent is not told, so an ask it waits on keeps waiting, and `answers` still returns a dismissed answer. The storage is in [docs/file-formats.md](docs/file-formats.md).
|
|
158
|
-
- The person can comment on any item (**Send as a comment**, also inside **Other answer**: each card has that one place to type) and queue a free-text prompt for the agent (**Ask the agent**). `poll` blocks until one arrives and prints every new one once (`--json` prints `{"messages":[{"id","kind","key"?,"text","at"}]}`); the page then shows it as read by the agent. Typed notes that are not sent yet stay in the browser across reloads; everything sent is saved next to the board and survives a restart.
|
|
159
|
-
- The board runs on your own machine only: no hosted service, no recurring cost. It listens on `127.0.0.1` unless `--bind <address>` (or `bind:` in the `config.yaml` that `init` writes) names one private address of this machine, such as its VPN address, so a phone or tablet on your own network can open it. A `hosts:` line in that config names private DNS names that lead to the bind address, such as its mesh VPN name (commas between them), so the board also opens by name; `doctor` checks each one resolves to the bind address. All interfaces and public addresses are refused. Beyond `127.0.0.1` the board makes an access token once (`decisions.token`, mode 600, next to the board), `open` prints it in the link, and every request without it is refused; the link sets a cookie, so the page and its media work after the first load. Delete the token file to shut old links out at once, then `stop` and `open` for a new one.
|
|
160
|
-
- `export` writes one self-contained HTML file (`decisions.export.html` by default): every item, answer, note and another-round request with its local mocks and media inlined; comments and queued prompts are left out. `--out` must end in `.html`, so a wrong path never overwrites the board. It opens from disk with no server, is read-only, and makes no network request. A mock page is inlined on its own, so the css and images beside it show only on the running board.
|
|
161
|
-
- With no `--option` a question is Yes/No; give 2 to 6 `--option` flags for a pick-one question (`--option friday=Friday --option next-week="Next week"`). `--recommend` and `--default` name one of its values. Listing options needs `--recommend` (on `raise`, `post` and `--from` files): the board shows that option first and marks it; a plain Yes/No may leave it out. Each answer is one tap, with an Undo for a few seconds after it (an Undo is saved as an answer change). Typing is last: every question with options, a design's Picks included, has a text box folded behind **Other answer** (none when an option is valued `text`); the words are saved as the answer `text`, with the same Undo.
|
|
162
|
-
- `list` and `answers` take `--full` for every field and `--json` for JSON (`answers --json` prints `{"<key>":{"answer","label","close","at"}}`, plus `"note"` when the user wrote one). `answers` adds a note column when any shown answer has a note.
|
|
163
|
-
- Raising a question that is already on the board is a no-op that reports its status, so an agent can re-run it safely. `--key` defaults to a slug of the title.
|
|
164
|
-
- Each raised or posted item records when it was filed (`filed`), so Triage shows `Waiting since 09:15` and the Waiting on you tile `Oldest 09:15`; `post --replace` keeps an item's first time.
|
|
165
|
-
- `open` is a no-op when the same board is already open on the same address, and fails with `CONFLICT` when it runs on another one. `--evidence-root` is the folder holding `.done-done/`, for decisions that link run evidence.
|
|
166
|
-
- `--filer <id>` on `post` and `raise` saves who filed the item (an agent session id or any 1-128 of `A-Z a-z 0-9 . _ -`; `SIGN_OFF_FILER` sets it for every command, and a post with neither saves `CLAUDE_CODE_SESSION_ID` when it is set). `wait --filer <id>` then returns only answers to that filer's items it has not taken yet (with `--key`, that key's answer even if taken), marks each taken (`decisions.seen.jsonl`) and the card shows **Seen by agent**; it fails with `NOT_FOUND` when none of that filer's items still needs an answer. The page never shows the filer id.
|
|
167
|
-
- `on_answer: <command> [args]` in the `config.yaml` beside the board runs that command for every saved answer, read when the board starts. It runs with no shell, in the board's folder, and gets the answer as one JSON line on stdin (`{"event":"answer","contract":1,"kind","schema","key","answer","label","close","at","note"?,"title"?,"filer"?,"comments"?}`, where `kind` and `schema` are the item's contract kind, or `ask` and 1 for a board-format item, and `comments`, present when the answer carried comments on mock elements, lists each as `id`, `mock`, `hash`, `ref`, `tag`, `words` and `text`); the answer is never put into its arguments, so a typed note cannot inject a command. A failure is logged and never blocks the answer. With no shell, `|`, `>` and `&&` are passed as plain arguments, so put a pipeline in a script and name the script; a relative path such as `./hook.sh` is found from the board's folder. `doctor` checks that the command can run.
|
|
168
|
-
- `post --from <file> --replace --filer <id>` lets several programs feed one board: it replaces only that filer's items (and items with no filer that the file names by key), keeps every other item, the board's other top-level fields and all answers, and an empty items list removes that filer's items. A contract message that changed is checked against its kind's rules again, so one that no longer matches is refused with the board left as it was; an answered contract key the feed dropped cannot come back, so post it under a new key (a design, diagram, interactive or explainer item that comes back keeps its answer).
|
|
169
|
-
- `wait` replaces a polling loop. With `--key` it returns at once if that decision is already answered, and waits for a key that is not raised yet; without it, it waits for the next answer to any decision that still needs an answer and fails with `NOT_FOUND` when nothing needs one. It wakes on each save, gives up after `--timeout` seconds (600, ten minutes, by default) with code `TIMEOUT` and exit 1, and takes `--full` and `--json` like `answers`. Ctrl-C stops it without changing anything.
|
|
170
|
-
- `open` records the board's pid, port and address next to the board file (`decisions.board.pid`). `status` and `stop` read it and only act on a pid that really serves that board, so no one has to guess a pid to kill. `open` on a board already served by `serve` records that server, so `stop` can close it too.
|
|
171
|
-
|
|
172
|
-
```
|
|
173
|
-
$ sign-off-axi raise --title "Keep the weekly digest?" --recommend yes
|
|
174
|
-
board: ~/.sign-off/decisions.json
|
|
175
|
-
raised[1]{key,status,title}:
|
|
176
|
-
keep-the-weekly-digest,raised,Keep the weekly digest?
|
|
177
|
-
help[2]:
|
|
178
|
-
Run `sign-off-axi open` so the user can answer
|
|
179
|
-
Run `sign-off-axi answers --key keep-the-weekly-digest` to read the answer
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
Exit codes: `0` ok, including no-ops; `1` the request cannot be done (a key that already holds a different question, a busy port); `2` usage or input error (the message names the item, key and field). Errors print on stdout as `error:`, `code:` and `help:` lines.
|
|
183
276
|
|
|
184
|
-
|
|
277
|
+
Later, when it worked:
|
|
278
|
+
|
|
279
|
+
```sh
|
|
280
|
+
sign-off-axi signin done \
|
|
281
|
+
--key npm-login
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
<picture>
|
|
285
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-signin-dark.webp">
|
|
286
|
+
<img src="docs/media/kind-signin.webp" alt="A sign-in card on top with an Open button, what it unlocks, who waits and the time left">
|
|
287
|
+
</picture>
|
|
288
|
+
|
|
289
|
+
*A sign-in card: what it unlocks, who waits, the time left.*
|
|
290
|
+
|
|
291
|
+
### Release gate
|
|
292
|
+
|
|
293
|
+
```sh
|
|
294
|
+
sign-off-axi post \
|
|
295
|
+
--kind gate \
|
|
296
|
+
--from gate.json
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
A `gate.json` holds a run for the user: Approve or Fix first.
|
|
300
|
+
|
|
301
|
+
<picture>
|
|
302
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-gate-dark.webp">
|
|
303
|
+
<img src="docs/media/kind-gate.webp" alt="A gate question with Approve recommended and Fix first">
|
|
304
|
+
</picture>
|
|
305
|
+
|
|
306
|
+
*A held run: Approve it or Fix first.*
|
|
307
|
+
|
|
308
|
+
### Retro finding
|
|
309
|
+
|
|
310
|
+
```sh
|
|
311
|
+
sign-off-axi post \
|
|
312
|
+
--kind finding \
|
|
313
|
+
--from finding.json
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
A `finding.json` asks the user to Apply or Skip a proposed fix.
|
|
317
|
+
|
|
318
|
+
<picture>
|
|
319
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-finding-dark.webp">
|
|
320
|
+
<img src="docs/media/kind-finding.webp" alt="A finding question with Apply recommended and Skip">
|
|
321
|
+
</picture>
|
|
322
|
+
|
|
323
|
+
*A retro fix to Apply or Skip.*
|
|
324
|
+
|
|
325
|
+
## On your phone
|
|
326
|
+
|
|
327
|
+
The same board on a phone, light and dark.
|
|
328
|
+
|
|
329
|
+
<picture>
|
|
330
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-dark.webp">
|
|
331
|
+
<img src="docs/media/phone-light.webp" width="320" alt="Board summary on a phone in light mode">
|
|
332
|
+
</picture>
|
|
333
|
+
|
|
334
|
+
*The summary, the waiting list, and the answers, in a phone frame.*
|
|
185
335
|
|
|
186
336
|
## On your screen
|
|
187
337
|
|
|
188
|
-
|
|
338
|
+
The waiting list and the summary at desk width, light and dark.
|
|
189
339
|
|
|
190
|
-
|
|
340
|
+
<picture>
|
|
341
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/media/desktop-dark.webp">
|
|
342
|
+
<img src="docs/media/desktop-light.webp" alt="Board summary at desk width in light mode">
|
|
343
|
+
</picture>
|
|
191
344
|
|
|
192
|
-
|
|
345
|
+
*Everything in one glance: waiting, evidence, explainers, answered.*
|
|
193
346
|
|
|
194
|
-
|
|
347
|
+
## How agents use it
|
|
195
348
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
349
|
+
Every item records who filed it, and that agent gets the answer as an
|
|
350
|
+
event, by itself: the agent raises with `--filer my-agent` and blocks on
|
|
351
|
+
`sign-off-axi wait --filer my-agent`, which returns only that filer's
|
|
352
|
+
answers, each once, and the card shows **Seen by agent**. An agent that
|
|
353
|
+
cannot block can be woken instead: an `on_answer:` line in the
|
|
354
|
+
`config.yaml` beside the board names a command the board runs for every
|
|
355
|
+
answer. The full reference, with the worked example, is in
|
|
356
|
+
[docs/commands.md](docs/commands.md).
|
|
357
|
+
|
|
358
|
+
Every command takes `--help` and prints compact output with next-step hints.
|
|
359
|
+
sign-off itself calls no model and inherits the calling agent's model routing.
|
|
360
|
+
|
|
361
|
+
## Install
|
|
362
|
+
|
|
363
|
+
```sh
|
|
364
|
+
npm install -g sign-off-axi
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
In a project folder, one command sets up `.sign-off/` and installs the
|
|
368
|
+
skill for each coding agent on your `PATH`:
|
|
369
|
+
|
|
370
|
+
```sh
|
|
371
|
+
npx -y sign-off-axi init
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Docs and reference
|
|
375
|
+
|
|
376
|
+
- [Commands](docs/commands.md): every command, the agent setup, exit codes.
|
|
377
|
+
- [Question kinds](docs/question-kinds.md): the reference for each kind.
|
|
378
|
+
- [Contract](docs/contract.md): the message rules agents post by.
|
|
379
|
+
- [File formats](docs/file-formats.md): board and message files.
|
|
200
380
|
|
|
201
381
|
## Development
|
|
202
382
|
|
|
383
|
+
Builds first, then runs the integration journeys against the real CLI:
|
|
384
|
+
|
|
203
385
|
```sh
|
|
204
|
-
npm test
|
|
205
|
-
npm run build:skill
|
|
206
|
-
npm
|
|
386
|
+
npm test
|
|
387
|
+
npm run build:skill
|
|
388
|
+
npm run build:demo
|
|
389
|
+
npm pack
|
|
207
390
|
```
|
|
208
391
|
|
|
392
|
+
- `npm test`: full journeys
|
|
393
|
+
- `npm run build:skill`: skill stub
|
|
394
|
+
- `npm run build:demo`: demo site (Try it live)
|
|
395
|
+
- `npm pack`: tarball
|
|
396
|
+
|
|
209
397
|
A release is a merged version-bump PR: CI publishes the new version to npm.
|
|
210
398
|
|
|
211
399
|
### Test runs never touch your board
|
|
212
400
|
|
|
213
|
-
With no board named, a bare command uses the nearest `.sign-off/` that
|
|
214
|
-
|
|
215
|
-
|
|
401
|
+
With no board named, a bare command uses the nearest `.sign-off/` that
|
|
402
|
+
`init` set up, looking from the working folder up to its repo root (the
|
|
403
|
+
folder holding `.git`) or up to HOME, but never HOME's own `.sign-off/`.
|
|
404
|
+
Otherwise the default board is `~/.sign-off/decisions.json`, under
|
|
405
|
+
`SIGN_OFF_HOME` when it is set. A test or agent run that must not reach your
|
|
406
|
+
own board sets HOME and every `XDG_*` folder to one fresh temporary folder,
|
|
407
|
+
or sets `SIGN_OFF_HOME` to one. A board file named on the command line
|
|
408
|
+
always wins.
|
|
216
409
|
|
|
217
410
|
```sh
|
|
218
411
|
tmp=$(mktemp -d)
|
|
219
|
-
|
|
412
|
+
export SIGN_OFF_HOME="$tmp"
|
|
413
|
+
(cd "$tmp" &&
|
|
414
|
+
sign-off-axi raise \
|
|
415
|
+
--title "Probe?")
|
|
220
416
|
```
|
|
221
417
|
|
|
222
|
-
Proof lives in [`.done-done.yaml`](.done-done.yaml): the `install`, `test`
|
|
418
|
+
Proof lives in [`.done-done.yaml`](.done-done.yaml): the `install`, `test`
|
|
419
|
+
and `skill` journeys re-run every check the CI gate enforces, and CI calls
|
|
420
|
+
them through `done-done run`.
|
|
421
|
+
|
|
422
|
+
Write your own questions using the [file-format reference](docs/file-formats.md).
|