@nanocollective/roster 0.1.0-alpha.2 → 0.1.0-alpha.4

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.
Files changed (68) hide show
  1. package/dist/cli.js +848 -164
  2. package/docs/README.md +10 -5
  3. package/docs/agents.md +320 -9
  4. package/docs/commands.md +5 -6
  5. package/docs/concepts.md +27 -9
  6. package/docs/cost.md +3 -2
  7. package/docs/doctor-codes.md +13 -4
  8. package/docs/export.md +2 -1
  9. package/docs/extending.md +11 -2
  10. package/docs/getting-started.md +89 -84
  11. package/docs/images/brain.jpg +0 -0
  12. package/docs/images/org.jpg +0 -0
  13. package/docs/images/prompt.jpg +0 -0
  14. package/docs/images/setup-org.jpg +0 -0
  15. package/docs/images/setup-plan.jpg +0 -0
  16. package/docs/images/staff.jpg +0 -0
  17. package/docs/manual-steps.md +36 -13
  18. package/docs/memory.md +9 -6
  19. package/docs/org-yaml.md +37 -9
  20. package/docs/portal.md +197 -31
  21. package/docs/prompts.md +50 -11
  22. package/docs/security.md +19 -7
  23. package/docs/session-workflow.md +8 -10
  24. package/docs/staff-yaml.md +3 -5
  25. package/docs/troubleshooting.md +17 -14
  26. package/docs/writing-a-charter.md +18 -17
  27. package/package.json +1 -1
  28. package/templates/brain/.github/workflows/%%STAFF%%-daily.yaml +1 -0
  29. package/templates/brain/.github/workflows/%%STAFF%%-mention.yaml +10 -4
  30. package/templates/brain/staff.yaml +0 -1
  31. package/templates/ops/.github/workflows/session.yaml +9 -26
  32. package/templates/ops/agents.mjs +121 -6
  33. package/templates/ops/compose.mjs +61 -4
  34. package/templates/ops/org/operating.md +0 -6
  35. package/templates/ops/prompts/_identity.md +8 -1
  36. package/templates/ops/prompts/mention.md +16 -2
  37. package/templates/portal/css/base.css +122 -8
  38. package/templates/portal/css/brain.css +8 -1
  39. package/templates/portal/css/diff.css +6 -2
  40. package/templates/portal/css/health.css +21 -2
  41. package/templates/portal/css/inbox.css +93 -5
  42. package/templates/portal/css/layout.css +26 -4
  43. package/templates/portal/css/markdown.css +23 -3
  44. package/templates/portal/css/setup.css +11 -6
  45. package/templates/portal/index.html +7 -1
  46. package/templates/portal/js/api.js +33 -0
  47. package/templates/portal/js/app.js +33 -7
  48. package/templates/portal/js/dialog.js +47 -4
  49. package/templates/portal/js/dom.js +25 -0
  50. package/templates/portal/js/icons.js +8 -1
  51. package/templates/portal/js/lightbox.js +273 -0
  52. package/templates/portal/js/md.js +23 -6
  53. package/templates/portal/js/mention.js +264 -0
  54. package/templates/portal/js/refresh.js +136 -6
  55. package/templates/portal/js/state.js +47 -5
  56. package/templates/portal/js/views/checklist.js +20 -7
  57. package/templates/portal/js/views/docs.js +94 -4
  58. package/templates/portal/js/views/files.js +58 -14
  59. package/templates/portal/js/views/health.js +163 -35
  60. package/templates/portal/js/views/inbox.js +882 -96
  61. package/templates/portal/js/views/memory.js +16 -1
  62. package/templates/portal/js/views/org.js +142 -62
  63. package/templates/portal/js/views/prompt.js +50 -63
  64. package/templates/portal/js/views/setup.js +37 -13
  65. package/templates/portal/js/views/staff.js +62 -2
  66. package/templates/portal/js/yaml.js +134 -0
  67. package/templates/brain/.github/workflows/%%STAFF%%-pr-mention.yaml +0 -50
  68. package/templates/ops/prompts/pr-mention.md +0 -57
package/docs/portal.md CHANGED
@@ -16,6 +16,19 @@ authentication and no API quota, and works offline. It can write to GitHub throu
16
16
 
17
17
  Keep the repos checked out beside each other, in the same shape the runner uses.
18
18
 
19
+ ## The sidebar
20
+
21
+ **The counts are right on load.** The badges beside Inbox and Pending work used to fill in only
22
+ once something caused a render with an inbox already loaded, which in practice meant "after you
23
+ visit the Inbox". A sidebar that says nothing until you look at it is not a sidebar. The page
24
+ now asks once at boot, and the Inbox screen shares that request rather than making a second one.
25
+
26
+ While the first answer is outstanding the badge is a placeholder rather than blank, because an
27
+ empty badge reads as zero and zero is a different claim from "still counting". The screens
28
+ themselves wait the same way: the inbox draws rows and the thread pane draws a page, in
29
+ placeholder shapes, for the seconds GitHub takes. Anything you start writing in that pane
30
+ survives the answer landing under it.
31
+
19
32
  ## Setup
20
33
 
21
34
  **With no tenant where you started it, the portal is the setup screen instead.** That is not an
@@ -62,7 +75,38 @@ down with the list, so opening a thread is a render rather than a request.
62
75
  a shorter timeline than open work because it is there to be read rather than triaged. A
63
76
  closed row is dimmed and marked; the sidebar badge keeps counting only what is open.
64
77
  - **Reply, close, reopen, open an issue.** All as you, through your own `gh`, so they are
65
- indistinguishable from doing it on the site. Closing asks for confirmation.
78
+ indistinguishable from doing it on the site. Closing asks for confirmation. A half-written
79
+ reply survives a repaint, and pressing Comment with an empty box says so rather than
80
+ silently doing nothing.
81
+ - **A new issue asks one question: who is it for.** One dropdown, over the staff. It goes to
82
+ that person's brain repo and their `@handle` is written into the body for you, because those
83
+ are the two things that make an issue reach an agent rather than sit there. It used to ask
84
+ for a staff member *and* a repo, which let you set the pair to a combination that woke
85
+ nobody. To file in a product repo instead, use GitHub: this form is for asking the staff for
86
+ something.
87
+ - **Labels are the repo's own, as toggles.** They are the labels that exist on the recipient's
88
+ repository, fetched from GitHub and cached. A text box was a spelling test: `from-cmo` and
89
+ `from-CMO` are different labels and only one of them exists.
90
+ - **Typing `@` offers the org.** A mention is the mechanism, not decoration: a staff member's
91
+ workflow gates on their handle, so a misspelled one is a message nobody is woken by. The list
92
+ is the staff and the humans, and nothing else. The Apps are deliberately absent: `@acme-cto`
93
+ is a login rather than an inbox, GitHub delivers nothing for mentioning one, and an agent
94
+ wakes on its own handle and not on the identity it posts as, so offering them is offering
95
+ entries that do nothing. Each row says which it is, the list appears under the caret, arrow
96
+ keys and Enter pick, Escape dismisses the list rather than the dialog around it, and an email
97
+ address is not a mention.
98
+ - **Attachments.** Drop a file on the box, pick one, or paste one. Paste is the one that
99
+ matters, because a screenshot is on the clipboard and never on disk. GitHub's own drag-and-drop
100
+ attachments are minted by its web app and cannot be made with `gh`, so the file is committed
101
+ to `attachments/<date>-<name>` in the repo the issue lives in and pushed. The body gets the
102
+ link for you and the repo-relative path for the agent, which reads it off its own checkout
103
+ rather than needing a token for a private repo. 25MB a file; a failed push is reported rather
104
+ than hidden.
105
+ - **Reactions.** The 👀 an agent leaves when it picks something up, under the comment it is on,
106
+ with who left it. Without it you post a comment, see nothing change, and have to open GitHub
107
+ to find out it landed.
108
+ - **How much conversation** is on each row, which is most of what tells a live thread from
109
+ something filed and never answered.
66
110
  - Issue and PR references in a body become chips you can click through, and `@handle`
67
111
  mentions become chips too. A mention of somebody on this roster goes to their repository
68
112
  rather than to a GitHub profile of that name, which for `@cto` is a stranger.
@@ -79,17 +123,105 @@ down with the list, so opening a thread is a render rather than a request.
79
123
  disclosure. An item a cross-reference points at opens in the portal when the inbox already
80
124
  holds it, and on GitHub when it does not.
81
125
 
126
+ ## Pending work
127
+
128
+ The same screen, scoped to pull requests, in its own place in the sidebar. An inbox is what is
129
+ waiting on you; a pull request is work that is finished and waiting on a merge, and the count
130
+ that matters is not how many are open but how many are green and still sitting there.
131
+
132
+ It is called **Pending work** rather than "Pull requests" because that is what is behind it: a
133
+ piece of work a staff member finished and cannot land alone. A pull request is how it arrives,
134
+ not what it is.
135
+
136
+ A pull request's thread has three tabs.
137
+
138
+ - **Conversation** is the thread: body, timeline, reactions, reply box, same as an issue.
139
+ - **Commits**, with subject, author and sha, each linking to GitHub.
140
+ - **Files**: the full patch, rendered as a diff with line numbers, the same renderer *What
141
+ changed* uses. A file with no patch is binary or too large for the API to send one, and says
142
+ so rather than showing nothing.
143
+
144
+ Both are fetched when you open the tab, not carried by the inbox. A diff is the biggest thing
145
+ on this screen by an order of magnitude, and paying for every open PR's diff on every refresh
146
+ of every repo to show one of them is the wrong trade.
147
+
148
+ **Merge** sits beside Close and asks before it goes. It never deletes the branch: that is a
149
+ second decision and not this button's to make. It runs `gh pr merge` as you, so a protected
150
+ branch, a failing required check or a merge queue behaves exactly as it would on the site.
151
+
152
+ **It does not ask how.** There used to be a squash / merge commit / rebase dropdown beside it.
153
+ That is a question about git rather than about the pull request in front of you, the repository
154
+ has already answered it in its own settings, and on any given repository most of the answers are
155
+ wrong. So the repository is asked instead: squash where it is allowed, then a merge commit, then
156
+ rebase.
157
+
158
+ ### Asking for a change
159
+
160
+ **`@cto` on a pull request wakes nobody.** A staff member's caller workflow lives in their own
161
+ brain repo and gates on their handle appearing *there*; on a product repo the same mention
162
+ posts, renders as a chip, and does nothing. That is deliberate, for the reasons in
163
+ [security](security.md#trust-in-a-prompt), and it used to be silent, which is worse than the
164
+ restriction itself.
165
+
166
+ **Reply is the one box, and it handles this.** Name somebody in a reply where a comment will
167
+ not reach them and the offer appears under the box, ticked: *open it on their tracker too*. One
168
+ press of Comment posts your words on the thread and opens the request on their tracker, which
169
+ carries
170
+
171
+ - the pull request, its link and its branch
172
+ - their `@handle`, which is what actually wakes them
173
+ - an instruction to **answer on the pull request**, because `prompts/mention.md` otherwise tells
174
+ them to answer where the request came from, which here is the tracker, leaving the diff silent
175
+ and you watching the wrong page
176
+ - the diff for the file you asked from, if you started from one, clipped: it is there to say
177
+ *which part*, not to be a copy of the diff that goes stale on the next push
178
+
179
+ Untick it and the reply is just a comment. Who gets asked comes off the text you actually sent,
180
+ so a handle you typed and then deleted is not asked.
181
+
182
+ On the **Files** tab each file's heading has its own Reply, which opens the same box about that
183
+ one file. "This bit is wrong" is what you want to say while looking at a diff, and the
184
+ alternative is describing in prose which of thirty files you meant.
185
+
186
+ There was briefly a second button up here called *Ask a staff member*. It did almost the same
187
+ thing as Reply, differing mainly in making you pick a name from a dropdown rather than typing
188
+ it, and nothing on the page said which one you wanted. Two ways to do one thing is worse than
189
+ either of them.
190
+
191
+ Both writes go through your own `gh`, as you. Nothing is dispatched between repositories and no
192
+ credential is put on a public repo. The tracker issue goes first, because it is the half that
193
+ reaches anybody; if the copy on the pull request then fails you are told, rather than being
194
+ shown an error that invites you to ask the same person the same thing twice.
195
+
82
196
  ## Org
83
197
 
84
- The layer every staff member inherits, in one place: `org.yaml` and the four org documents,
85
- each with a line saying what it is for, because five filenames tell you nothing about which to
86
- open. All five are editable, and saving commits and pushes.
198
+ The layer every staff member inherits, in one place: `org.yaml`, every `org/*.md`, and the
199
+ prompt files in `prompts/`, each with a line saying what it is for, because a filename tells
200
+ you nothing about which to open. Anything roster does not ship falls back to its own first
201
+ heading.
202
+
203
+ **The list comes off disk**, not out of the page. It used to be five paths written into the
204
+ portal, so a tenant that added `org/pricing.md` could not open it at all and one that had not
205
+ written `org/business.md` yet got "not found" with nothing to do about it. Only files the
206
+ portal may actually write are listed: an editor that offers a file it cannot save is a trap.
207
+
208
+ Every one of them is editable from the screen it is read on: an **Edit** button on the file,
209
+ ⌘S to save, and saving commits and pushes. Only `org.yaml` asks for confirmation first; a
210
+ prose edit is one commit to revert, and asking every time teaches people to click through the
211
+ question.
87
212
 
88
213
  `org.yaml` is the exception to the write allowlist. It belongs to the person rather than the
89
214
  agent, so it is writable. But it is the one file here that stops every prompt composing when
90
215
  it is wrong, so the server parses it with the tenant's own `compose.mjs` first and refuses
91
- anything that is not YAML it can read, or that has lost `org`, `name`, or a handle on a staff
92
- entry.
216
+ anything that is not YAML it can read, or that has lost `org`, `name`, a handle on a staff
217
+ entry, or a github login on an entry in [`humans`](org-yaml.md#human-and-humans).
218
+
219
+ YAML is coloured, here and anywhere else the portal shows it: comments, keys, strings, numbers
220
+ and the three keywords. `org.yaml` and `staff.yaml` are the two files anybody reads closely,
221
+ and finding a key in a flat grey wall means reading every line.
222
+
223
+ The card at the top names **everyone** the staff answer to, not the first of them. An org can
224
+ have more than one human, and a card that names one of two reads as the only one who counts.
93
225
 
94
226
  ## Staff
95
227
 
@@ -156,7 +288,7 @@ rather than going nowhere.
156
288
  `roster prompt <handle> --kind daily` in a terminal. Composed on the server by the tenant's own
157
289
  `compose.mjs`, so there is no second implementation to drift.
158
290
 
159
- Pick the kind: `daily`, `mention` or `pr-mention`. A mention prompt is written for the comment
291
+ Pick the kind: `daily` or `mention`. A mention prompt is written for the comment
160
292
  that woke it, so a preview fills in obviously-fake context.
161
293
 
162
294
  Beneath the composed text, the files it was made of, in two groups.
@@ -172,25 +304,9 @@ tells the agent to open these; it does not contain them. Editing a charter chang
172
304
  agent does without changing a byte of the composed prompt, and that distinction is easy to
173
305
  miss.
174
306
 
175
- ### Problems
176
-
177
- The audit, beside the layers. Nothing here judges prose: every check is something a machine
178
- can be sure about, because a linter you stop believing is worse than no linter.
179
-
180
- | Check | Why it matters |
181
- |---|---|
182
- | a file is still the scaffold | `org/business.md` is composed into every run. Leaving it as questions is invisible: the run works and the output is just generic |
183
- | a placeholder never resolved | `{{staff.product.repo}}` is null for a staff member who contributes to no other repo, and the agent reads the braces literally |
184
- | the same line in two layers | the layers are inherited, so a rule a charter repeats from `org/` is duplication nobody reading either file can see |
185
- | the prompt is long | it is read in full on every run, forever |
186
- | it names a file that is not there | an instruction to read something absent is a quiet no-op inside a run nobody watches |
187
- | an included layer is empty | it contributes nothing and costs a line of includes |
188
-
189
- **Every finding carries the fix.** "Copy a prompt to fix this" builds a brief containing the
190
- finding, the composed prompt, and every layer, and puts it on your clipboard. Paste it into
191
- whatever agent you use. Knowing there is a problem is the hard part; writing the paragraph is
192
- not. The box comes pre-filled with what the finding worked out, so you can add to it rather
193
- than retype it.
307
+ What is *wrong* with any of this is on [Health](#health). This screen answers "what is sent";
308
+ whether what is sent is any good is a different question, and a tree that was half prompt and
309
+ half complaints answered neither well.
194
310
 
195
311
  ### Getting help changing it
196
312
 
@@ -284,7 +400,11 @@ opening the diff that did it: one block per file, coloured, with line numbers.
284
400
 
285
401
  ## Health
286
402
 
287
- Three parts.
403
+ Four parts: the rig, the org, the prompts and the memory. The rig is about this staff member,
404
+ so it is first; the rest widens out from there.
405
+
406
+ **The rig**: schedule in words, workflows present, last commit, last commit touching `memory/`,
407
+ index and notes size, charter, status issue, missing surfaces.
288
408
 
289
409
  **The org, from `roster doctor`.** Every finding that is not `ok`, with what to do about it, and
290
410
  a button that turns the lot into one brief for a coding agent. That is `roster fix`: `doctor`,
@@ -300,20 +420,66 @@ an agent that has started editing has stopped reading.
300
420
 
301
421
  Paste it into whatever edits files here, then press *Check again*. The ids should be gone.
302
422
 
303
- **Memory problems**, and **the rig**.
423
+ **Prompt problems.** The audit, over all three kinds of run at once. Nothing here judges prose:
424
+ every check is something a machine can be sure about, because a linter you stop believing is
425
+ worse than no linter. A finding true of more than one prompt is one row, and it says which.
304
426
 
305
- Memory problems are the same checks `roster lint` runs. Each one has a button that opens an
427
+ | Check | Why it matters |
428
+ |---|---|
429
+ | a file is still the scaffold | `org/business.md` is composed into every run. Leaving it as questions is invisible: the run works and the output is just generic |
430
+ | a placeholder never resolved | `{{staff.product.repo}}` is null for a staff member who contributes to no other repo, and the agent reads the braces literally |
431
+ | the same line in two layers | the layers are inherited, so a rule a charter repeats from `org/` is duplication nobody reading either file can see |
432
+ | the prompt is long | it is read in full on every run, forever |
433
+ | it names a file that is not there | an instruction to read something absent is a quiet no-op inside a run nobody watches |
434
+ | an included layer is empty | it contributes nothing and costs a line of includes |
435
+
436
+ **Every finding carries the fix.** "Copy a prompt to fix this" builds a brief containing the
437
+ finding, the composed prompt, and every layer, and puts it on your clipboard. Paste it into
438
+ whatever agent you use. Knowing there is a problem is the hard part; writing the paragraph is
439
+ not. The box comes pre-filled with what the finding worked out, so you can add to it rather
440
+ than retype it. "Open the file" takes you to that layer on the Prompt screen.
441
+
442
+ **Memory problems** are the same checks `roster lint` runs. Each one has a button that opens an
306
443
  issue in that staff member's own repo asking them to fix it, which is usually right, because
307
444
  they wrote it.
308
445
 
309
- **The rig**: schedule in words, workflows present, last commit, last commit touching `memory/`,
310
- index and notes size, charter, status issue, missing surfaces.
446
+ ## Any image opens
447
+
448
+ **Click a picture and it opens over the page, fitted to the window.** Bottom right there is a
449
+ `-`, the current zoom, and a `+`. The percentage is a button too: it refits. Scrolling zooms,
450
+ dragging moves, clicking the picture goes between fitted and actual size, and `+`, `-`, `0` and
451
+ Escape do the same from the keyboard. Zooming is anchored on the pointer, so the thing you
452
+ aimed at stays where you aimed.
453
+
454
+ The percentage is of **actual size**, not of fitted, so 100% means one image pixel per screen
455
+ pixel. How far in it will go depends on the picture: a scaled image is a composited layer and
456
+ the browser allocates it at the rendered size, so the ceiling is whatever keeps that within
457
+ budget. Actual size is always reachable, however large the original is.
458
+
459
+ This is every image the portal renders as content: a screenshot on a doc page, a picture inside
460
+ a brain document, a file open in the Brain screen's viewer. A screenshot laid out for the column
461
+ it sits in is unreadable exactly when it matters, which is when it is a picture of an interface
462
+ and the part you need is the small print.
463
+
464
+ Gallery tiles are the exception, and deliberately: their click already means "open this file",
465
+ and the file's own view is one of the things that does open.
466
+
467
+ Fitted, the picture is inset from the window edges and framed. That is not decoration. Most of
468
+ these are screenshots *of this interface*, and one fitted edge to edge reads as the app having
469
+ navigated rather than as a picture of it.
311
470
 
312
471
  ## Docs
313
472
 
314
473
  The framework's own documentation, rendered where you already are. Links between pages navigate
315
474
  the portal.
316
475
 
476
+ **Search reads the pages, not their titles.** Twenty-odd pages is too many to scan by eye and
477
+ few enough for the server to read in full on every keystroke, and what you are looking for
478
+ ("which page explains the mention gate") is a sentence in a paragraph rather than a word in a
479
+ heading. A page has to contain every word you typed; results rank a title hit over a heading
480
+ hit over sheer frequency, carry the lines the words were found in, and the first one opens as
481
+ you type. The query is in the URL, so a search is a link.
482
+
317
483
  ## After upgrading roster
318
484
 
319
485
  **Restart the portal.** Its stylesheets and modules are read per request, so editing one and
package/docs/prompts.md CHANGED
@@ -8,7 +8,13 @@ sidebar_order: 15
8
8
 
9
9
  What a staff member is actually sent, and how to change it.
10
10
 
11
- See it for yourself before changing anything:
11
+ **Look at it before changing anything.** The portal's [Prompt screen](portal.md#prompt) is the
12
+ composed text and, beneath it, every file it was made of: which are inlined and in what order,
13
+ which are only named, and which repo each came from. That last column is the one that matters,
14
+ because a change to `roster-ops/org/voice.md` reaches every staff member and a change to a
15
+ brain's own `prompts/work.md` reaches one.
16
+
17
+ From a terminal, the same text:
12
18
 
13
19
  ```bash
14
20
  roster prompt cto --kind daily
@@ -35,16 +41,30 @@ does not control.
35
41
  |---|---|---|
36
42
  | `daily` | cron | no |
37
43
  | `mention` | `@handle` in a comment, or in a new issue body | yes |
38
- | `pr-mention` | a review comment on the product repo, forwarded in | yes |
39
44
 
40
- `mention` and `pr-mention` refuse to compose without context, because they are written for the
41
- comment that woke them. That is correct behaviour. To see one locally:
45
+ A `mention` refuses to compose without context, because it is written for the comment that woke
46
+ it. That is correct behaviour rather than a bug.
47
+
48
+ The Prompt screen's kind picker handles that for you: pick **Mention** and it fills in
49
+ obviously-fake trigger context so there is something to look at. From a terminal you supply it
50
+ yourself:
42
51
 
43
52
  ```bash
44
- ROSTER_CONTEXT='{"issue_number":"1","comment_id":"1","pr_number":"1","repo":"o/r"}' \
53
+ ROSTER_CONTEXT='{"issue_number":"1","comment_id":"1","repo":"o/r"}' \
45
54
  roster prompt cto --kind mention
46
55
  ```
47
56
 
57
+ **There are two routes in, and the prompt is not the same on both.** A comment carries a
58
+ `comment_id`; a mention typed into the body of a *new* issue does not, and there is no comment
59
+ for the agent to fetch. So `compose.mjs` derives `event.no_comment` from the absence, and
60
+ `prompts/mention.md` branches on it: one side points at the comment, the other at the issue
61
+ body. Without that, the first instruction in the prompt was a `gh api .../issues/comments/`
62
+ call with no id on the end, which 404s.
63
+
64
+ The second route is the busier one now. It is what the portal's [Ask a staff
65
+ member](portal.md#asking-for-a-change) produces, and those issues often carry a pull request
66
+ that lives somewhere else and say to answer there instead.
67
+
48
68
  ## Syntax
49
69
 
50
70
  Four forms, and nothing else.
@@ -71,8 +91,11 @@ than a hang.
71
91
  |---|---|
72
92
  | `org` | the whole of `org.yaml` |
73
93
  | `org.name`, `org.org` | the business name, the GitHub org |
74
- | `human` | the `human` block from `org.yaml` |
94
+ | `human` | the primary human: the first of `humans`, or the singular `human` block |
75
95
  | `human.name`, `human.github`, `human.marker` | |
96
+ | `humans` | everyone the staff answer to, in order. See [org.yaml](org-yaml.md#human-and-humans) |
97
+ | `human_list` | all of them as a sentence: "Will (@will-lamerton) and Sam (@sam-x)" |
98
+ | `humans_extra` | the same, minus the primary. **Empty when there is only one**, which is what makes `{{#if humans_extra}}` the way to mention the others |
76
99
  | `ops.dir` | ops repo directory in the checkout |
77
100
  | `staff` | the whole of this staff member's `staff.yaml` |
78
101
  | `staff.dir` | where their brain lands in the checkout |
@@ -81,9 +104,10 @@ than a hang.
81
104
  | `peers` | list of the other staff members |
82
105
  | `peer` | the first peer, or null |
83
106
  | `peer_list` | peers pre-rendered as a markdown list |
84
- | `kind` | `daily`, `mention` or `pr-mention` |
107
+ | `kind` | `daily` or `mention` |
85
108
  | `event` | trigger context, from `ROSTER_CONTEXT` |
86
- | `event.issue_number`, `event.comment_id`, `event.pr_number`, `event.repo`, `event.actor` | |
109
+ | `event.issue_number`, `event.comment_id`, `event.repo`, `event.actor` | |
110
+ | `event.no_comment` | true when the ask is the issue body rather than a comment. There is no `{{#unless}}`, so the absence is a value |
87
111
 
88
112
  Anything else in a manifest is reachable under `staff.`, so `staff.status_issue` and
89
113
  `staff.public_token_env` work without being listed here.
@@ -105,6 +129,9 @@ This is not hypothetical. The shipped prompts referred to `{{staff.product.repo}
105
129
  which nobody noticed because every existing staff member had one. The first staff member of any
106
130
  new org could not compose a prompt at all.
107
131
 
132
+ [Health](portal.md#health) checks for exactly this ("a placeholder never resolved"), per staff
133
+ member, which is the only way to catch it before 07:00 rather than in a run nobody watched.
134
+
108
135
  ## Overriding a fragment for one staff member
109
136
 
110
137
  ```
@@ -120,7 +147,17 @@ else's. `daily.md` already carries this hook.
120
147
  `prompts/` is `seeded` class: yours to edit, and `roster upgrade` gives you a real three-way
121
148
  merge. Changes reach every staff member on their next run.
122
149
 
123
- Before pushing a prompt change to a live org, diff the composed result:
150
+ **Edit them where you read them.** Every layer on the Prompt screen has an Edit button; saving
151
+ writes that one file, commits it and pushes, and the confirmation names the repository and who
152
+ picks it up. The org's own layers (`org/*.md`, `<ops>/prompts/*.md`) are the same edit on the
153
+ [Org screen](portal.md#org). Not everything is writable: `compose.mjs`, `staff.yaml`, the
154
+ workflows and `memory/INDEX.md` are readable and not editable, because a wrong one of those
155
+ stops every prompt composing or overwrites what the next run is about to write.
156
+
157
+ **The thing to check is what the edit did to the composed prompt, not to the file.** Those are
158
+ different questions: a line added to one fragment can land three times or not at all. Saving
159
+ from the Prompt screen shows you the first. From a terminal, the same check is two composes and
160
+ a diff:
124
161
 
125
162
  ```bash
126
163
  roster prompt cto --kind daily > before.txt
@@ -129,5 +166,7 @@ roster prompt cto --kind daily > after.txt
129
166
  diff before.txt after.txt
130
167
  ```
131
168
 
132
- That is the only test there is for a prompt change. A change that composes fine and reads
133
- differently is not caught by anything else.
169
+ Either way, that is the only test there is for a prompt change. One that composes fine and
170
+ reads differently is not caught by anything else. [Health](portal.md#health) runs a prompt audit
171
+ over all the kinds at once, but everything it checks is something a machine can be sure about,
172
+ which never includes whether the prose is any good.
package/docs/security.md CHANGED
@@ -42,7 +42,8 @@ workflows that constrain them.
42
42
  misconfiguration and it fails at run time. Over-granting is the risk: an App installed on the
43
43
  whole organisation can reach anything in it.
44
44
 
45
- Install narrowly. `roster app` prints the list it actually needs.
45
+ Install narrowly. The Staff card's **GitHub App** panel prints the list it actually needs, and
46
+ so does `roster app`.
46
47
 
47
48
  ## Permissions a new App asks for
48
49
 
@@ -86,6 +87,8 @@ constrained rather than sanitised:
86
87
  - `/api/file` resolves the path and refuses anything outside the workspace root.
87
88
  - `/api/diff` requires the directory to be a known staff repo and the sha to look like a sha.
88
89
  - `/api/doc` requires the page to be one the listing offered.
90
+ - `/api/docasset` requires the screenshot to be one sitting in `docs/images/`, matched by name
91
+ against that listing. `..` is not a case to get wrong; it is simply a name not on it.
89
92
 
90
93
  `--host` overrides the bind address and prints a warning. It exists for people who know what
91
94
  they are doing on a network they control. See [hosting the portal](hosting.md).
@@ -96,12 +99,21 @@ Everything composed into a prompt is content you or your agents wrote: `org/`, t
96
99
  memory index. A `mention` run additionally carries the text of a comment.
97
100
 
98
101
  **On a private tracker that is you.** On the public product repo it is not, which is exactly
99
- why the product lane is split: a mention on a public PR is caught by a forwarder in that repo
100
- which does no work of its own, and dispatches to the private lane. The run that prints a
101
- charter and a chain of reasoning happens where the logs are not world-readable.
102
-
103
- The forwarder has an author gate, and it is load-bearing: anyone can comment on a public PR,
104
- and a run started from a comment executes with repository secrets. Do not relax it.
102
+ why **nothing in a product repo wakes an agent at all**. There is no caller workflow there, by
103
+ design: anyone can comment on a public pull request, a run started from a comment executes with
104
+ repository secrets, and a run that prints a charter and a chain of reasoning would print it
105
+ into a world-readable log.
106
+
107
+ So a mention on a public pull request is decoration. It posts, it renders as a chip, and
108
+ nothing happens, which is safe and also invisible. The portal is what makes it visible and what
109
+ gets you out of it: replying with an `@handle` where nothing listens says so, and **Ask a staff
110
+ member** opens the request on that person's own private tracker instead, carrying the pull
111
+ request and the hunk. It writes through your own `gh`, as you, so no credential lives on the
112
+ public repo and nothing is dispatched across a boundary.
113
+
114
+ There was once a forwarder in the product repo that bridged this automatically. It was removed;
115
+ see [concepts](concepts.md#kinds-of-run). If you reinstate one, its author gate is
116
+ load-bearing. Do not relax it.
105
117
 
106
118
  ## The loop guard
107
119
 
@@ -26,14 +26,13 @@ repositories in this organisation**. Without it, callers fail with "workflow not
26
26
  | Input | Type | Default | Means |
27
27
  |---|---|---|---|
28
28
  | `staff` | string | required | Handle, as in `org.yaml`. |
29
- | `kind` | string | `daily` | `daily`, `mention` or `pr-mention`. |
29
+ | `kind` | string | `daily` | `daily` or `mention`. |
30
30
  | `ops_repo` | string | required | `owner/name` of the ops repo. |
31
31
  | `model` | string | `claude-opus-5` | Passed to the agent, unless the agent resolves its own. |
32
- | `timeout_minutes` | number | `60` | Job ceiling. |
32
+ | `timeout_minutes` | number | `90` | Job ceiling. |
33
33
  | `allowed_tools` | string | `Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch` | Tool permissions, for agents that take them. |
34
34
  | `issue_number` | string | `""` | Trigger context. |
35
35
  | `comment_id` | string | `""` | Trigger context. |
36
- | `pr_number` | string | `""` | Trigger context. |
37
36
 
38
37
  ## Secrets
39
38
 
@@ -62,14 +61,13 @@ authentication error forty lines into a log.
62
61
  6. **Check out the brain**, full history. The agent reads its own past.
63
62
  7. **Check out peers and product repos**, per the plan.
64
63
  8. **Set git identity** to the App.
65
- 9. **Check out the PR branch**, on a `pr-mention`.
66
- 10. **Set up Node and pnpm**, if the plan found a `package.json`.
67
- 11. **Compose the prompt**, to a step output and to `.roster-prompt.txt`.
68
- 12. **Check the agent has a credential.**
69
- 13. **Work out which agent runs this**, by running `agents.mjs`.
70
- 14. **Run the session**, by one of two steps: the Action-based reference runner, or the generic
64
+ 9. **Set up Node and pnpm**, if the plan found a `package.json`.
65
+ 10. **Compose the prompt**, to a step output and to `.roster-prompt.txt`.
66
+ 11. **Check the agent has a credential.**
67
+ 12. **Work out which agent runs this**, by running `agents.mjs`.
68
+ 13. **Run the session**, by one of two steps: the Action-based reference runner, or the generic
71
69
  CLI one. See [choosing a coding agent](agents.md).
72
- 15. **Say so if the run did not finish.**
70
+ 14. **Say so if the run did not finish.**
73
71
 
74
72
  ## What `runner-plan.mjs` emits
75
73
 
@@ -24,8 +24,7 @@ status_issue: 15
24
24
  schedule: "0 7 * * 1-5"
25
25
  model: claude-opus-5
26
26
  timeout_minutes: 90
27
- mention_timeout_minutes: 30
28
- pr_mention_timeout_minutes: 60
27
+ mention_timeout_minutes: 90
29
28
 
30
29
  bot: acme-cto[bot]
31
30
  public_bot: acme-robot[bot]
@@ -69,9 +68,8 @@ labels:
69
68
  |---|---|---|
70
69
  | `schedule` | none | Cron for the daily run. Rendered into the caller. |
71
70
  | `model` | org default | Model id. |
72
- | `timeout_minutes` | 60 | Ceiling on the daily session. |
73
- | `mention_timeout_minutes` | 30 | Ceiling on a mention run. |
74
- | `pr_mention_timeout_minutes` | 60 | Ceiling on a PR-amendment run. |
71
+ | `timeout_minutes` | 90 | Ceiling on the daily session. |
72
+ | `mention_timeout_minutes` | 90 | Ceiling on a mention run. |
75
73
 
76
74
  The three ceilings are separate on purpose. Raising the daily one because sessions have grown
77
75
  should not double the budget for a PR amendment. A job killed by a ceiling is reported by
@@ -9,8 +9,10 @@ sidebar_order: 10
9
9
  Every trap on this page has actually been hit. Most of them fail in a way that points somewhere
10
10
  else, which is why they are worth writing down.
11
11
 
12
- Start with `roster doctor`. It groups by staff member and every finding that is not `ok` says
13
- what to do about it.
12
+ Start with the portal's [Health](portal.md#health) screen. It groups by staff member, every
13
+ finding that is not `ok` says what to do about it, and a button turns the lot into one brief for
14
+ a coding agent, split into what an agent can fix and what only a person can. `roster doctor` and
15
+ `roster fix` are the same two things from a terminal.
14
16
 
15
17
  ---
16
18
 
@@ -20,8 +22,8 @@ what to do about it.
20
22
 
21
23
  **Is:** the ops repo's Actions access is not set to organisation-wide.
22
24
 
23
- Settings -> Actions -> General on `roster-ops`. `roster doctor` checks this explicitly, and the
24
- portal's setup screen links straight to the page.
25
+ Settings -> Actions -> General on `roster-ops`. The setup screen deep-links that exact page,
26
+ which is the fastest way to fix it; Health and `roster doctor` both check it explicitly.
25
27
 
26
28
  ---
27
29
 
@@ -31,7 +33,7 @@ portal's setup screen links straight to the page.
31
33
  `timeout-minutes` as `cancelled`, which reads as though somebody pressed a button.
32
34
 
33
35
  Tell them apart by duration. Several cancelled runs all stopping at the same minute is a
34
- ceiling, not a coincidence. `roster doctor` does this for you and names the number:
36
+ ceiling, not a coincidence. Health does this for you and names the number:
35
37
 
36
38
  ```
37
39
  ✗ cto-daily.yaml: 5 of the last 10 ran to a 60m ceiling and were killed
@@ -49,8 +51,8 @@ so old timeouts keep being reported as timeouts.
49
51
  gated run still appears in the list with conclusion `skipped`. Most of a mention workflow's
50
52
  history is skipped runs.
51
53
 
52
- `roster doctor` ignores them. A workflow whose runs are *all* skipped is reported differently,
53
- because nothing has exercised the credentials.
54
+ Health ignores them. A workflow whose runs are *all* skipped is reported differently, because
55
+ nothing has exercised the credentials.
54
56
 
55
57
  ---
56
58
 
@@ -63,11 +65,12 @@ been **installed** on the repository in question, or which repositories the inst
63
65
  granted. The two are reported separately and they disagree exactly when you care.
64
66
 
65
67
  Do not verify an installation by reading the API. The only proof of the whole chain is a run
66
- that finished. `roster doctor` reads recent runs for this reason and calls a workflow that has
67
- never run **unproven** rather than fine.
68
+ that finished. Health reads recent runs for this reason and calls a workflow that has never run
69
+ **unproven** rather than fine.
68
70
 
69
71
  Fix: open the App's installation settings and check the repository list includes every tracker
70
- the staff member writes to, not just its own. `roster app` prints that list.
72
+ the staff member writes to, not just its own. The **GitHub App** panel on that staff member's
73
+ card says so loudly, and prints the list.
71
74
 
72
75
  ---
73
76
 
@@ -107,8 +110,9 @@ first entry in `works_in`, and a staff member who contributes to no other reposi
107
110
  The shipped prompts guard these. A prompt fragment you have written yourself needs
108
111
  `{{#if staff.product}}` around anything that assumes one. Conditionals do not nest.
109
112
 
110
- Similarly `{{staff.status_issue}}` is empty until `roster hire --apply` has opened the pinned
111
- issue.
113
+ Similarly `{{staff.status_issue}}` is empty until hiring has actually been applied and opened
114
+ the pinned issue. Health's prompt audit has this check ("a placeholder never resolved"), and the
115
+ [Prompt screen](portal.md#prompt) shows you the composed text with the braces still in it.
112
116
 
113
117
  ---
114
118
 
@@ -119,8 +123,7 @@ seconds. If it does not:
119
123
 
120
124
  - The reaction is `continue-on-error`. A missing reaction never costs the answer, so check
121
125
  whether the run itself started at all.
122
- - It is scoped to `kind == 'mention'`. A pr-mention is acknowledged by the forwarder in the
123
- public repo instead, so that it gets one reaction rather than two.
126
+ - It is scoped to `kind == 'mention'`. A daily run has nothing to react to.
124
127
  - On the `issues` route (a mention typed into a new issue body) the eyes go on the issue, not
125
128
  on a comment, because that payload has no comment.
126
129