create-pmos 0.52.0 → 0.53.1

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 (229) hide show
  1. package/package.json +1 -1
  2. package/payload/KIT-VERSION +2 -2
  3. package/payload/mods/pmos-cockpit/README.md +13 -0
  4. package/payload/mods/pmos-cockpit/hooks/lib/commands.mjs +2 -1
  5. package/payload/mods/pmos-cockpit/hooks/lib/grants.mjs +41 -11
  6. package/payload/mods/pmos-cockpit/hooks/lib/guard.mjs +228 -8
  7. package/payload/mods/pmos-cockpit/hooks/register.tsx +11 -3
  8. package/payload/planning/evals/eval-calibration.md +1 -1
  9. package/payload/planning/okrs/health-budgets.md +1 -1
  10. package/payload/scripts/plan-items-stage.js +271 -0
  11. package/payload/scripts/pmos-log.sh +18 -7
  12. package/payload/scripts/workflow-state.js +28 -2
  13. package/payload/state/discoveries.md +1 -1
  14. package/payload/state/log.md +1 -1
  15. package/workspace/README.md +2 -0
  16. package/workspace/REMOTE.md +24 -63
  17. package/workspace/bin/pmos.js +19 -3
  18. package/workspace/cut-stub.js +40 -0
  19. package/workspace/first-paint-audit.js +145 -0
  20. package/workspace/fixtures/capture/requirements/capture-2026-10-04-1.md +10 -0
  21. package/workspace/fixtures/initiatives-api/list.json +176 -0
  22. package/workspace/fixtures/initiatives-api/post-403.json +5 -0
  23. package/workspace/fixtures/initiatives-api/post-ok.json +10 -0
  24. package/workspace/fixtures/initiatives-api/roadmap.json +208 -0
  25. package/workspace/fixtures/initiatives-api/sheet.json +112 -0
  26. package/workspace/fixtures/report-api/draft.json +177 -0
  27. package/workspace/fixtures/reports-page/draft-biweekly-2026-10-16.md +97 -0
  28. package/workspace/fixtures/today-api/provider-rows.json +204 -0
  29. package/workspace/fixtures/today-api/seam-probe-kinds.json +5 -0
  30. package/workspace/guide/board.md +81 -0
  31. package/workspace/guide/breakdown.md +154 -0
  32. package/workspace/guide/command-palette.md +75 -0
  33. package/workspace/guide/index.md +28 -17
  34. package/workspace/guide/initiatives-roadmap.md +130 -0
  35. package/workspace/guide/initiatives-screen.md +89 -0
  36. package/workspace/guide/initiatives-timeline.md +57 -0
  37. package/workspace/guide/integrated-work.md +94 -0
  38. package/workspace/guide/knowledge-graph.md +3 -68
  39. package/workspace/guide/launcher.md +51 -0
  40. package/workspace/guide/layout.md +102 -32
  41. package/workspace/guide/live-notes.md +3 -3
  42. package/workspace/guide/pm-only-acts.md +89 -0
  43. package/workspace/guide/pm-only-lines.md +61 -0
  44. package/workspace/guide/proactive-engine.md +21 -5
  45. package/workspace/guide/reports-page.md +80 -0
  46. package/workspace/guide/reports.md +73 -0
  47. package/workspace/guide/timeline.md +70 -0
  48. package/workspace/guide/today.md +85 -0
  49. package/workspace/guide/validate-with-typesafe.md +1 -1
  50. package/workspace/lib/actor.js +91 -0
  51. package/workspace/lib/board-cache.js +32 -2
  52. package/workspace/lib/board-cli.js +108 -0
  53. package/workspace/lib/board-fold.js +232 -0
  54. package/workspace/lib/breakdown-export.js +75 -0
  55. package/workspace/lib/brief.js +20 -2
  56. package/workspace/lib/capture.js +98 -0
  57. package/workspace/lib/engine/plan-items-stage.js +271 -0
  58. package/workspace/lib/engine/workflow-state.js +28 -2
  59. package/workspace/lib/factory-join.js +158 -0
  60. package/workspace/lib/goals.js +165 -0
  61. package/workspace/lib/grant-mint.js +113 -0
  62. package/workspace/lib/health-rules-factory.js +92 -0
  63. package/workspace/lib/health-rules-plan.js +164 -0
  64. package/workspace/lib/health.js +79 -0
  65. package/workspace/lib/initiative-cli.js +222 -0
  66. package/workspace/lib/initiative-display.js +183 -0
  67. package/workspace/lib/initiative-needs.js +127 -0
  68. package/workspace/lib/initiative-rows.js +210 -0
  69. package/workspace/lib/initiative-writer.js +141 -0
  70. package/workspace/lib/issues.js +206 -0
  71. package/workspace/lib/item-cli.js +195 -0
  72. package/workspace/lib/item-spans.js +137 -0
  73. package/workspace/lib/launcher-cli.js +207 -0
  74. package/workspace/lib/measure-cli.js +100 -0
  75. package/workspace/lib/measurements.js +189 -0
  76. package/workspace/lib/mobile-nav.js +57 -53
  77. package/workspace/lib/needs-providers.js +120 -0
  78. package/workspace/lib/placements.js +440 -0
  79. package/workspace/lib/plan-items.js +922 -0
  80. package/workspace/lib/plan-ledgers.js +85 -0
  81. package/workspace/lib/plan-rules.js +72 -0
  82. package/workspace/lib/plan-tree.js +380 -0
  83. package/workspace/lib/pm-guard.js +174 -0
  84. package/workspace/lib/pm-lines-needs.js +331 -0
  85. package/workspace/lib/pmos-verbs.js +69 -0
  86. package/workspace/lib/proactive-cli.js +58 -2
  87. package/workspace/lib/proactive.js +9 -6
  88. package/workspace/lib/qr.js +293 -0
  89. package/workspace/lib/remote-access.js +93 -1
  90. package/workspace/lib/report-cli.js +152 -0
  91. package/workspace/lib/report-draft.js +578 -0
  92. package/workspace/lib/report-needs.js +213 -0
  93. package/workspace/lib/route-modules.js +128 -0
  94. package/workspace/lib/routes-board.js +91 -0
  95. package/workspace/lib/routes-capture.js +52 -0
  96. package/workspace/lib/routes-initiatives.js +371 -0
  97. package/workspace/lib/routes-reports.js +163 -0
  98. package/workspace/lib/routes-timeline.js +197 -0
  99. package/workspace/lib/routes-today.js +32 -0
  100. package/workspace/lib/today-cli.js +99 -0
  101. package/workspace/lib/today.js +333 -0
  102. package/workspace/lib/triage.js +163 -0
  103. package/workspace/lib/verify-work-cli.js +81 -0
  104. package/workspace/lib/work-check-needs.js +99 -0
  105. package/workspace/package-lock.json +1 -1
  106. package/workspace/package.json +4 -5
  107. package/workspace/public/icons/apple-touch-icon.png +0 -0
  108. package/workspace/public/icons/icon-192.png +0 -0
  109. package/workspace/public/icons/icon-512.png +0 -0
  110. package/workspace/public/index.html +40 -19
  111. package/workspace/public/pm/app.js +330 -30
  112. package/workspace/public/pm/bd-board.js +113 -0
  113. package/workspace/public/pm/bd-drag.js +59 -0
  114. package/workspace/public/pm/bd-model.js +152 -0
  115. package/workspace/public/pm/bd-timeline.js +233 -0
  116. package/workspace/public/pm/bd-tree.js +244 -0
  117. package/workspace/public/pm/board-copy.js +57 -0
  118. package/workspace/public/pm/breakdown-api.js +58 -0
  119. package/workspace/public/pm/breakdown-copy.js +138 -0
  120. package/workspace/public/pm/components.css +3135 -0
  121. package/workspace/public/pm/cycle-review.js +125 -0
  122. package/workspace/public/pm/data.js +40 -187
  123. package/workspace/public/pm/dest-board.js +438 -0
  124. package/workspace/public/pm/dest-initiatives.js +134 -0
  125. package/workspace/public/pm/dest-reports.js +270 -0
  126. package/workspace/public/pm/dest-settings.js +217 -0
  127. package/workspace/public/pm/dest-today.js +182 -0
  128. package/workspace/public/pm/dialogs.js +88 -0
  129. package/workspace/public/pm/display-word.js +56 -0
  130. package/workspace/public/pm/dock.js +175 -0
  131. package/workspace/public/pm/fixture.js +8 -227
  132. package/workspace/public/pm/frame-copy.js +99 -0
  133. package/workspace/public/pm/handoff-box.js +82 -0
  134. package/workspace/public/pm/http.js +88 -0
  135. package/workspace/public/pm/icons.js +53 -0
  136. package/workspace/public/pm/initiative-list.js +218 -0
  137. package/workspace/public/pm/initiative-sheet.js +261 -0
  138. package/workspace/public/pm/initiatives-api.js +59 -0
  139. package/workspace/public/pm/initiatives-copy.js +215 -0
  140. package/workspace/public/pm/initiatives-timeline-copy.js +59 -0
  141. package/workspace/public/pm/initiatives-timeline.js +410 -0
  142. package/workspace/public/pm/initiatives-ui.js +123 -0
  143. package/workspace/public/pm/item-sheet.js +251 -0
  144. package/workspace/public/pm/keep-card.js +204 -0
  145. package/workspace/public/pm/lifecycle.js +7 -1
  146. package/workspace/public/pm/mobile.css +49 -20
  147. package/workspace/public/pm/mobile.js +157 -29
  148. package/workspace/public/pm/modes-live.js +7 -883
  149. package/workspace/public/pm/modes.js +32 -1024
  150. package/workspace/public/pm/new-initiative-sheet.js +74 -0
  151. package/workspace/public/pm/next-step-ui.js +3 -2
  152. package/workspace/public/pm/palette-copy.js +51 -0
  153. package/workspace/public/pm/palette.js +312 -0
  154. package/workspace/public/pm/panel-breakdown.js +702 -0
  155. package/workspace/public/pm/panel-cockpit.js +63 -295
  156. package/workspace/public/pm/panel-detail.js +25 -172
  157. package/workspace/public/pm/panel-initiatives.js +1 -19
  158. package/workspace/public/pm/panel-repos.js +31 -81
  159. package/workspace/public/pm/panel-tabs.js +200 -111
  160. package/workspace/public/pm/panel-trees.js +5 -2
  161. package/workspace/public/pm/portfolio-report.js +61 -0
  162. package/workspace/public/pm/primitives.js +656 -0
  163. package/workspace/public/pm/print.css +33 -0
  164. package/workspace/public/pm/provider-cards.js +136 -0
  165. package/workspace/public/pm/registry.js +194 -0
  166. package/workspace/public/pm/release-card.js +110 -0
  167. package/workspace/public/pm/report-view.js +309 -0
  168. package/workspace/public/pm/reports-copy.js +114 -0
  169. package/workspace/public/pm/roadmap.js +271 -0
  170. package/workspace/public/pm/sig-watch.js +41 -0
  171. package/workspace/public/pm/state.js +8 -4
  172. package/workspace/public/pm/status-banner.js +13 -5
  173. package/workspace/public/pm/tab-doc.js +2 -4
  174. package/workspace/public/pm/tab-term.js +12 -2
  175. package/workspace/public/pm/tab-web.js +3 -2
  176. package/workspace/public/pm/timeline-copy.js +48 -0
  177. package/workspace/public/pm/today-cards.js +244 -0
  178. package/workspace/public/pm/today-copy.js +126 -0
  179. package/workspace/public/pm/today-goals.js +172 -0
  180. package/workspace/public/pm/tokens.css +16 -18
  181. package/workspace/public/pm/topbar.js +102 -0
  182. package/workspace/public/pm/triage-card.js +60 -0
  183. package/workspace/public/pm/undo-hold.js +103 -0
  184. package/workspace/public/pm/view-addrepo.js +2 -1
  185. package/workspace/public/pm/view-livenotes.js +3 -1
  186. package/workspace/public/pm/view-today.js +7 -162
  187. package/workspace/public/vendor/Sortable.min.js +2 -0
  188. package/workspace/public/vendor/lucide-sprite.js +79 -0
  189. package/workspace/replaced-tests.json +2877 -0
  190. package/workspace/run-tests.js +56 -5
  191. package/workspace/server.js +446 -40
  192. package/workspace/sheet-answer.js +61 -0
  193. package/workspace/skills/measure.skill +41 -0
  194. package/workspace/skills/morning-brief.skill +15 -9
  195. package/workspace/skills/report-biweekly.skill +23 -25
  196. package/workspace/skills/tracker-refresh.skill +50 -0
  197. package/workspace/test-files.json +21 -3
  198. package/workspace/test-lists/base.json +6 -0
  199. package/workspace/test-lists/run2-a.json +6 -0
  200. package/workspace/test-lists/run2-b.json +13 -0
  201. package/workspace/test-lists/run2-c.json +19 -0
  202. package/workspace/test-lists/run2-d.json +22 -0
  203. package/workspace/test-lists/run3-0.json +5 -0
  204. package/workspace/test-lists/run3-a.json +5 -0
  205. package/workspace/test-lists/run3-b.json +6 -0
  206. package/workspace/test-lists/run3-c.json +15 -0
  207. package/workspace/test-lists/run3-d.json +6 -0
  208. package/workspace/test-lists/run4-a.json +8 -0
  209. package/workspace/test-lists/run4-b.json +6 -0
  210. package/workspace/test-lists/run4-c.json +13 -0
  211. package/workspace/test-lists/run4-d.json +9 -0
  212. package/workspace/test-lists/run5-a.json +6 -0
  213. package/workspace/test-lists/run5-b.json +6 -0
  214. package/workspace/test-lists/run5-c.json +15 -0
  215. package/workspace/test-lists/workspace-launcher.json +5 -0
  216. package/workspace/test-lists/workspace-phone-app.json +5 -0
  217. package/workspace/test-run.js +27 -1
  218. package/workspace/third-party/README.md +44 -0
  219. package/workspace/third-party/lucide-static-1.52.0.LICENSE +43 -0
  220. package/workspace/third-party/sortablejs-1.15.7.LICENSE +21 -0
  221. package/workspace/design-parity.js +0 -673
  222. package/workspace/public/pm/tab-task.js +0 -86
  223. package/workspace/public/pm/view-guide.js +0 -141
  224. package/workspace/public/pm/view-knowledge.js +0 -225
  225. package/workspace/public/pm/view-memory.js +0 -122
  226. package/workspace/public/pm/view-portfolio.js +0 -153
  227. package/workspace/public/pm/view-reports.js +0 -190
  228. package/workspace/public/pm/view-scheduled.js +0 -161
  229. package/workspace/public/pm/view-skills.js +0 -216
@@ -1,20 +1,35 @@
1
1
  ---
2
2
  type: Guide
3
3
  title: "Guide: Today and the proactive engine"
4
- description: "What the Today item shows, how the morning brief is made, and how the workspace works out what is waiting for you."
5
- timestamp: 2026-10-04
4
+ description: "What the Today item shows, how your goals for the day are prepared, how the morning brief is made, and how the workspace works out what is waiting for you."
5
+ timestamp: 2026-10-06
6
6
  ---
7
7
 
8
8
  # Guide: Today and the proactive engine
9
9
 
10
10
  The today item shows what is waiting for you now. The workspace works it out by itself from the records of your repositories. You do not ask for it.
11
11
 
12
+ ## Goals for today
13
+
14
+ At the top of Today you can list what you want done today. A goal is one line of text, and it can name an initiative.
15
+
16
+ - Open **Goals for today** and type a goal. Press Enter to add it. A day has at most 7 goals, each at most 140 characters.
17
+ - Press **Done**, **Drop**, **Reopen** or **Remove** on a goal. A goal is open, done or dropped.
18
+ - Press **Prepare my day**. The workspace makes the daily 06:45 morning-brief schedule if this repository has none, and runs it now. The session reads your goals, your calendar, and the mentions of you in Slack, Jira and Confluence. It ties each meeting, mention and step that moves a goal to that goal.
19
+ - Each goal shows its steps: yours first, then the agent's. A step comes from the brief, or from a Today card on the goal's initiative. A goal with no step says so. The session does not invent a step.
20
+ - A step is under a goal only when the brief names the goal, or when the step and the goal name the same initiative. The workspace never matches words.
21
+ - An agent step that names an initiative has **Prepare**. It shows the step, the branch and the prompt, the same as background preparation. **Queue it** approves that exact prompt. Background preparation must be on in the settings. Otherwise the queue says so and nothing is queued.
22
+ - What fits no goal is listed under **Also on your plate**, and items the session is less than 0.5 sure about under **Worth asking**.
23
+ - Today also says how much free time you have until 18:00, the event coming up, and which sources were not read.
24
+ - If the brief is for another day, it is not used. Only the Today cards are shown under the goals.
25
+ - The goals are stored in the workspace state folder on this computer, one file for each day (`goals/<date>.json`). They are not committed.
26
+
12
27
  ## The morning brief
13
28
 
14
29
  The top of Today is the morning brief: a greeting, the day's calendar, the priorities and a list of things worth asking about. The brief is not computed by the workspace. A skill called morning-brief runs in an agent session. The session reads the sources, and then submits one brief with `pmos proactive brief submit`. The workspace server only checks the brief and stores it. It makes no model call and reaches no calendar or mailbox.
15
30
 
16
31
  - Press **schedule the morning brief** in Today to make a daily schedule at 06:45. The schedule is an ordinary scheduled task, so it runs only while the workspace server runs. A sleeping computer gets no brief. A schedule that is made after its time of day for the first time runs within a minute, so pressing the button at 10:00 starts one brief session straight away.
17
- - Each source is a setting: calendar, wispr, mail, jira and confluence. Mail is off by default and the skill reads only subjects and senders, never bodies. A source that is off cannot be reported as read.
32
+ - Each source is a setting: calendar, wispr, mail, slack, jira and confluence. Mail is off by default and the skill reads only subjects and senders, never bodies. Slack is on by default. In Slack, Jira and Confluence the skill reads the mentions of you: where, who, and a one-line summary. It never copies a message. A source that is off cannot be reported as read.
18
33
  - The brief names the day it is for. If that is not today, Today says it is out of date.
19
34
  - An item the session is less than 0.5 sure about is shown under worth asking, not as a priority. The workspace sets that bucket from the confidence. The session cannot choose it.
20
35
  - The order is fixed: higher confidence first, then your items before the agent's, then by title.
@@ -85,7 +100,7 @@ A verdict never changes a status, a lifecycle record or an acceptance. It is rec
85
100
  |---|---|
86
101
  | engine on | Off stops automatic scans. The scan now button still works. |
87
102
  | watch: needs, returned, review, schedule, stalled | Turn one trigger off. |
88
- | brief sources | One switch for each of calendar, wispr, mail, jira and confluence. |
103
+ | brief sources | One switch for each of calendar, wispr, mail, slack, jira and confluence. |
89
104
  | background prepare | Off by default. On lets the workspace start a session for a plan you approved. |
90
105
  | typed scoring | Off by default. On lets the skill send titles and metadata to the Typesafe service. |
91
106
  | name | Used in the greeting. |
@@ -102,6 +117,7 @@ Text that you type in a setting (the name, the stalled days, the quiet hours) or
102
117
  - `pmos proactive today` lists the items.
103
118
  - `pmos proactive scan` scans now.
104
119
  - `pmos proactive settings` shows the settings. Add `--enabled`, `--trigger NAME=on|off`, `--stalled-days`, `--quiet HH:MM-HH:MM|none`, `--pause-learning`, `--background-prepare on|off`, `--brief-source NAME=on|off`, `--brief-scoring on|off` or `--name` to change one.
120
+ - `pmos proactive goals` lists today's goals with their steps. `pmos proactive goals add TEXT [--initiative ID]` adds one. `done N`, `drop N`, `reopen N` and `remove N` change the Nth goal. `pmos proactive goals plan` does the same as **Prepare my day**.
105
121
  - `pmos proactive brief` shows today's brief. `pmos proactive brief submit --file PATH` submits one. `pmos proactive brief schedule` makes the 06:45 schedule for the current repository.
106
122
  - `pmos proactive prepare` shows the queue. `pmos proactive prepare plan --initiative ID` shows a plan and its hash. `pmos proactive prepare queue --initiative ID --item ITEM --sha HASH` approves it. `pmos proactive prepare cancel ID` cancels an entry. `pmos proactive prepare run` looks at the queue now.
107
123
  - `pmos proactive learn` shows the rollup, the weights, the narrowed triggers and the lessons. `pmos proactive learn run` takes the weekly review now. `pmos proactive learn lessons` lists the approved lessons. `pmos proactive learn approve ID`, `reject ID` and `retire ID` decide a lesson. `pmos proactive learn restore TRIGGER` brings back a narrowed trigger. `pmos proactive learn reset` makes it ignore earlier feedback.
@@ -110,7 +126,7 @@ Text that you type in a setting (the name, the stalled days, the quiet hours) or
110
126
  ## Limits
111
127
 
112
128
  - It runs only while the workspace server runs. It does not catch up for time the server was off. A sleeping computer gets no scan.
113
- - The items from the records come from this computer. The workspace itself does not read your mail or your calendar. Only the morning-brief session does, and only the sources you switched on.
129
+ - The items from the records come from this computer. The workspace itself does not read your mail, your calendar, Slack, Jira or Confluence. Only the morning-brief session does, and only the sources you switched on.
114
130
  - It does not start a session, push, send, post, accept or merge. It only shows items and records your feedback, and learns from it as described above. The one exception is background preparation, which starts a session only for a plan you approved, as described above.
115
131
  - An unreadable repository is listed as not read. An empty list is not proof that nothing waits there.
116
132
  - The records stay in the workspace state folder on this computer. They are not committed.
@@ -0,0 +1,80 @@
1
+ ---
2
+ type: Guide
3
+ title: "Reports: draft, read and issue"
4
+ description: "How to draft a report, add your notes, issue it, and print or copy it. How the report schedules and the Today cards for reports work."
5
+ timestamp: 2026-10-05
6
+ ---
7
+
8
+ # Reports: draft, read and issue
9
+
10
+ A report tells one audience what moved in the repository. The workspace drafts it from the repository's own files. You read it, add your notes and issue it. A report that you issued is frozen.
11
+
12
+ The workspace keeps each report in a file of the repository, for example `planning/reports/biweekly/2026-10-16.md`. The issues ledger, `planning/runs/issues.jsonl`, records each Issue. The page and the `pmos report` command use the same writers, so they always show the same reports.
13
+
14
+ ## Open Reports
15
+
16
+ Open the Reports tab with the shortcut `g r`, from the command palette, or from the Today card of a report. Reports shows the repository that is open.
17
+
18
+ The page has three parts:
19
+
20
+ - **Report schedules.** The schedules that draft a report.
21
+ - **Reports.** The reports of the repository, newest first.
22
+ - **The selected report.** Its text and the actions you can do on it.
23
+
24
+ ## Draft a report
25
+
26
+ 1. Press **Draft now**. The workspace writes the draft of the cadence you have selected, for example biweekly. A cadence is one kind of report: daily, weekly, biweekly or monthly.
27
+ 2. The new draft is selected. Every number in it comes from the repository's ledgers. The page never changes a number.
28
+
29
+ A second **Draft now** for the same slot is refused. Press **Redraft** to write the draft again. A Redraft keeps every note. It moves a note with its initiative, even when the order of the initiatives changed. A note of an initiative that left the window stays at the end, under "Notes kept from an earlier draft".
30
+
31
+ To draft another cadence, open the menu with the three dots and choose **Draft now** with the name of the cadence.
32
+
33
+ ## Edit notes
34
+
35
+ Press **Edit notes**. One sheet shows every note of the draft: the headline, each initiative, and the needs-your-call part when the definition names an audience. Write your text and press **Save notes**. Saving a note draws the report again from the files as they are now, and keeps every note. Use it for the text, not to freeze a number: a number can change when the repository changed since the draft was written. A draft with no note markers has no note to edit. An issued report cannot be edited.
36
+
37
+ ## Issue a report
38
+
39
+ Issue takes two presses.
40
+
41
+ 1. Press **Issue**. This button is on the page and on the Today card "A draft is ready to issue". A sheet reads the draft and shows its headline.
42
+ 2. Press **Issue** in the sheet. The workspace records the Issue in the issues ledger with the sha of the file you read.
43
+
44
+ If the file changed after you read it, the workspace refuses and nothing is issued. Close the sheet, read the draft again and press Issue again.
45
+
46
+ After Issue the file is frozen. **Redraft** and **Edit notes** are gone for that report. If someone edits the file anyway, the row shows "Edited after issue".
47
+
48
+ Only you can issue. An agent cannot press Issue, and the page does not take a name from you: the workspace writes who pressed.
49
+
50
+ ## Print or copy an issued report
51
+
52
+ An issued report offers two actions, in the sheet after Issue and on the page:
53
+
54
+ - **Print / PDF** opens the print dialog with the report alone, in the light theme. Choose "Save as PDF" in that dialog to make a PDF.
55
+ - **Copy as markdown** puts the report on the clipboard without its frontmatter and without the note markers.
56
+
57
+ These are the only two ways a report leaves the page. The page has no control that moves a report out of the workspace. You decide where a printed or copied report goes.
58
+
59
+ ## Report schedules
60
+
61
+ A schedule drafts a report at its time. It never issues one. A schedule belongs to a report by its skill: the skill `report-biweekly` feeds the biweekly report, whatever the cadence of the schedule says.
62
+
63
+ - **Pause** stops a schedule. **Resume** starts it again.
64
+ - **Set up report schedules** appears when the repository has none. One press creates the recommended schedules. It never creates one twice.
65
+ - **Other schedules (n)** lists the schedules that feed no report. Manage them with the `pmos schedule` commands.
66
+
67
+ A schedule fires only while the workspace server runs.
68
+
69
+ ## The Today cards
70
+
71
+ | Card | When it shows | What the press does |
72
+ |---|---|---|
73
+ | A draft is ready to issue | A draft exists, it is newer than the last issued report of its cadence, and it is not issued. | Opens the Issue sheet. |
74
+ | A schedule ran and left no draft | A schedule ran and no report file of its cadence is newer than the run, after two hours of grace. | Writes the draft now. |
75
+
76
+ The card for the monthly kill-condition check is drawn with the initiative cards.
77
+
78
+ ## In a terminal
79
+
80
+ The same acts exist as commands: `pmos report draft`, `pmos report issue` and `pmos report list`. The ledger records who ran each command.
@@ -0,0 +1,73 @@
1
+ ---
2
+ type: Guide
3
+ title: "Reports: draft and issue a cadence report"
4
+ description: "How pmos report drafts a cadence report with every number, how the notes are kept when you draft again, and how you issue and freeze a report."
5
+ timestamp: 2026-10-05
6
+ ---
7
+
8
+ # Reports: draft and issue a cadence report
9
+
10
+ A cadence report tells its readers where each initiative stands. The biweekly report is the first one. The `pmos report` command drafts a report, issues it and lists the reports. It reads files in the repository only. It sends nothing and it fetches nothing.
11
+
12
+ ## What a draft contains
13
+
14
+ The script `pmos report draft` writes every number. No model runs. It reads the ledgers since the last issued report of the same cadence. If no report is issued yet, it reads the last 14 days (`report_cycle_days` in `planning/plan-rules.json`).
15
+
16
+ The draft has these sections, in this order:
17
+
18
+ 1. **Headline.** A count of the initiatives that moved, are off track or are at risk.
19
+ 2. **Timeline.** The dated items of each initiative, as they were when the numbers were read.
20
+ 3. **One block for each initiative.** Only work initiatives that were active in the window get a block. Off track and at risk come first. A block shows the stage, the health and its reason, and the progress, for example "7 of 12 (+4)": 7 of 12 items are done, and 4 of them were done in this window. **Last cycle** lists the items done, the gates passed, the moves between Now, Next and Later, and the overdue items. **Coming cycle** lists the items that start or are due in the next window. If there are none, it lists the next 3 items in order.
21
+ 4. **New since last report.** The initiatives that were added in the window.
22
+ 5. **Decided this cycle.** The decisions you made in the window. A decision that waits for you is not in the report. It comes to you on the Cycle review card, and it does not stop you from issuing.
23
+ 6. **Needs your call.** The asks to the audience that the definition names, and to no one else.
24
+ 7. **Sources reached and not reached.** The files the script read, and each source it did not read, with the reason.
25
+
26
+ A block shows a measured value only when a measurement line gives it, with its source as `<path>@<sha>`. The script does not calculate a value.
27
+
28
+ If a block is off track or at risk and has no reason, the script stops and writes no draft.
29
+
30
+ ## The definition
31
+
32
+ The definition file tells the script and the agent what the report is for. For PMOS itself it is `planning/reports/definitions/biweekly.md`. In a kit repository it is `pmos/state/reports/definitions/biweekly.md`. Its frontmatter gives the `cadence`, the `audience` and the `sections`. If the definition names no audience, Needs your call stays empty.
33
+
34
+ ## Notes
35
+
36
+ Each block has a note between two markers:
37
+
38
+ ```
39
+ <!-- pmos:note <initiative-id> -->
40
+ Your note. Start it with "Warning:" when the block needs attention.
41
+ <!-- /pmos:note -->
42
+ ```
43
+
44
+ The headline note is `report.headline`. The asks are in the `report.asks` note, one ask on each line, each line starting with "- ". Write prose only between the markers. Do not type numbers: the script writes them.
45
+
46
+ When you draft a slot again, every note stays as you wrote it, next to its initiative. The order of the blocks can change. A note for an initiative that is no longer in the window moves to "Notes kept from an earlier draft".
47
+
48
+ ## The commands
49
+
50
+ `pmos report draft <cadence> [<slot>]` writes the draft to `planning/reports/<cadence>/<slot>.md`. The slot is a date, YYYY-MM-DD. If you do not give a slot, the slot is today. If a draft for the slot exists, the command stops and writes nothing. To draft the slot again and keep every note, add `--redraft`. To fix the clock, add `--now <time>`: two drafts with the same inputs and the same `--now` are the same, byte for byte.
51
+
52
+ ```
53
+ pmos report draft biweekly 2026-10-16
54
+ pmos report draft biweekly 2026-10-16 --redraft
55
+ ```
56
+
57
+ `pmos report issue <cadence> <slot>` issues the report. Only you can do this, at a terminal. The command asks you to type the cadence and the slot, for example `biweekly-2026-10-16`. An agent session, a command with no terminal, or a different text is refused, and nothing is written. The command records the path and the git blob sha of the file in `planning/runs/issues.jsonl`. The report is then frozen: a draft of that slot is refused, and the Initiative Gate fails if the file changes. Nothing is sent. To share the report, print it or copy it as markdown.
58
+
59
+ ```
60
+ pmos report issue biweekly 2026-10-16
61
+ ```
62
+
63
+ `pmos report list [<cadence>]` shows each draft and each issued report, newest first, with who issued it.
64
+
65
+ Every command accepts `--root <dir>` to name the repository. `draft` and `list` also accept `--json`.
66
+
67
+ ## The window
68
+
69
+ The window starts at the time of the last issued report of the same cadence, so no time falls between two reports. The next draft starts where the last issued report stopped.
70
+
71
+ ## The report lane
72
+
73
+ An initiative in the report lane completes its last step when you issue its report. The workflow engine reads the issued line for the initiative's `doc_path` in `issues.jsonl`.
@@ -0,0 +1,70 @@
1
+ ---
2
+ type: Guide
3
+ title: "Breakdown › Timeline: the items on a time axis"
4
+ description: "How to read the Timeline view of a Breakdown, move the dates of an item by dragging its bar, change a due date by dragging the end of a bar, and read the health flags."
5
+ timestamp: 2026-10-05
6
+ ---
7
+
8
+ # Breakdown › Timeline: the items on a time axis
9
+
10
+ The Timeline view shows the items of one initiative on a time axis. Each item that has a start date or a due date has a bar. The bar runs from the start date to the due date.
11
+
12
+ ## Open the Timeline
13
+
14
+ 1. Open the Breakdown of an initiative.
15
+ 2. At the top of the Breakdown, press **Timeline**. The Timeline is next to **Tree** and **Board**.
16
+
17
+ ## Read the Timeline
18
+
19
+ - **Months.** The months are across the top. The axis starts at the first month that has a date and stops at the last month that has a date. It always shows today and at least three months.
20
+ - **Today.** A vertical line shows today. The label **Today** is on its own line above the month labels, next to the line.
21
+ - **Scrolling.** The chart scrolls to the side inside its own area, on every screen width. When the Timeline opens, the chart scrolls so that today shows. The column with the titles stays in its position while the chart scrolls.
22
+ - **All items.** The first row shows the full initiative, from the first start date to the last due date. It has one segment for each stage. A small gap shows where each stage starts, also when two stages overlap.
23
+ - **Stage rows.** Under the first row, the items are in groups by stage. The stages are in the same order as in the Tree. Each stage row shows the dates of its items and the number of dated items, for example "Build, 3 dated items".
24
+ - **Item rows.** Each item row shows the title, the dates and the bar. A Done item has a darker bar.
25
+ - **Milestones.** An item that is a milestone shows a diamond on its due date. The diamond also shows on the stage row and on the first row.
26
+ - **Proposed items.** An item that an agent proposed and that you did not keep yet has a lighter bar with a dashed edge and the signal **Proposed**. A proposed item does not make a stage bar or the first row longer.
27
+ - **Items with no dates.** An item with no start date and no due date has no bar. A line under the chart gives the number of such items. To give an item dates, open it and set **Start** and **Due** in the item sheet.
28
+
29
+ An item with only a start date or only a due date has a bar of one day.
30
+
31
+ ## Move the dates of an item
32
+
33
+ - **Move both dates.** Drag the bar to the left or to the right. The start date and the due date move by the same number of days.
34
+ - **Change the due date.** Drag the end of the bar (the line at its right edge). Only the due date changes. The due date cannot go before the start date.
35
+ - **Short bars.** A bar that is narrower than about 24 pixels always moves whole. To change only its due date, use the keyboard or the item sheet.
36
+ - **Open the item.** Press the bar without moving it. The item sheet opens.
37
+
38
+ The bar shows the new dates while you drag it. When you let go, the workspace saves the change.
39
+
40
+ With the keyboard:
41
+
42
+ 1. Press Tab until the bar has the focus.
43
+ 2. Press the Left or Right arrow key to move both dates by one day.
44
+ 3. Press Shift with the Left or Right arrow key to change only the due date.
45
+ 4. Press Enter to save. Press Escape to go back to the saved dates.
46
+
47
+ If you move the focus away from a bar that has a change, the workspace saves the change. If you press Tab, the focus then goes to the next bar.
48
+
49
+ What a move writes:
50
+
51
+ - Each drag adds one line to `planning/runs/plan-items.jsonl`. The line is a `set` line with the dates of the item. When you change only the due date, the line has only the due date.
52
+ - An item that has only a due date keeps only a due date when you move it.
53
+ - A move changes one item only. The dates of other items do not change, also when they come after the item. The Timeline does not count effort in days.
54
+ - If the workspace cannot save the change, a message gives the reason. The bar goes back to the saved dates.
55
+
56
+ ## Health
57
+
58
+ The Timeline shows a flag only when an item is not on track. The flag is text and a small dot, on its own line under the dates of the item. The bar of the item has a coloured outline.
59
+
60
+ - **At risk: starts before an item ends.** The item starts on or before the day that one of its predecessors ends. The flag names that predecessor and its due date. A Done predecessor does not cause this flag. A Done item never shows it.
61
+ - **At risk: due on a date, not done.** The due date is before today and the item is not Done.
62
+ - **Off track: milestone due on a date, not done.** A milestone has a due date before today and it is not Done. Its diamond has the same colour as the text of the flag.
63
+
64
+ Limit: this view checks only the predecessors in the same initiative. A predecessor in a different initiative does not cause a flag here. To see such a clash, compare the dates of the two items.
65
+
66
+ To remove a flag, change the dates of the item or of its predecessor, or move the item to Done.
67
+
68
+ ## On a phone
69
+
70
+ On a narrow screen, the page does not scroll to the side: only the chart does. To move a bar, touch it and drag it to the side. A short bar takes a touch a little to its left or right. To scroll the chart, drag the empty part of a row.
@@ -0,0 +1,85 @@
1
+ ---
2
+ type: Guide
3
+ title: "Today: what needs you"
4
+ description: "The eight kinds of card on Today, the one button on each card, the count, the empty state, and the pmos today command."
5
+ timestamp: 2026-10-05
6
+ ---
7
+
8
+ # Today: what needs you
9
+
10
+ Today lists only what waits for you. Each card is one thing that only you can close. When nothing waits, Today says so and names the next scheduled draft, for example "Nothing needs you. Next: biweekly draft Fri 16 Oct."
11
+
12
+ Press **Today** in the top bar, or press `g` and then `t`, to open it. The number on the Today button is the number of cards in the scope you chose: the repository, or All repositories. It is the same number that Today shows at the top of the list.
13
+
14
+ ## A card
15
+
16
+ Every card has four parts.
17
+
18
+ - **Cause.** One sentence: why the card is here.
19
+ - **Evidence.** The record that shows it, for example a run id, a file or a date.
20
+ - **One button.** The act that closes the card. The verb comes first, for example **Review**, **Keep** or **Open**.
21
+ - **The three dots.** A menu with **Open the initiative**, **Copy the command** and **Why is this here?**. The menu has the same items on every card. A touch screen and a keyboard reach them as a mouse does.
22
+
23
+ When you finish the act, the card leaves and the count goes down by one. You do not dismiss a card. A card leaves only when its cause is gone.
24
+
25
+ ## The eight kinds
26
+
27
+ Today puts the cards in this order. Within a kind, the cards keep a fixed order, so a card does not move while you read.
28
+
29
+ | Kind | What it is | The button |
30
+ |---|---|---|
31
+ | 1. A gate is owed | A run waits for your acceptance, an outcome check is due, or a report is ready to issue. | Review, Check or Issue |
32
+ | 2. A session is waiting | A session in a terminal asked you a question and waits for the answer. | Open |
33
+ | 3. A step you returned | You returned an acceptance and the initiative has not been accepted since. | Open |
34
+ | 4. Work to check | An item is in Review, or an item is Done with no evidence. | Open |
35
+ | 5. Keep an agent breakdown | An agent proposed items. They count in no progress until you keep them. | Keep |
36
+ | 6. A triaged capture | A capture was triaged and waits for you to add it or dismiss it. | Open |
37
+ | 7. Cycle review | The cycle review is due. You confirm what is in Now, or move it. | Open |
38
+ | 8. Needs repair | A schedule left no output, a line from the PM has no attestation, or a record has no anchor. Today shows the card until it is fixed. | Open |
39
+
40
+ A stalled initiative is not a card. It shows as a health reason on the initiative, in the list and in its detail.
41
+
42
+ A source that cannot be read is not a card either. It shows in the notice at the top of Today, so a source that fails never looks like an empty list.
43
+
44
+ ## Close a gate on a run
45
+
46
+ A card of kind 1 for a run has the button **Review**. The steps are:
47
+
48
+ 1. Press **Review** on the card. The card opens in place and shows what the evaluator said.
49
+ 2. Press **Right** or **Wrong** to say whether the evaluator was right.
50
+ 3. Optional: press **Add a note** and write one line on why.
51
+ 4. Press **Accept** to accept the run, or **Return** to send it back.
52
+
53
+ That is four taps, or three taps without the note. The workspace records your act in the acceptance ledger. The note and your label are part of that record. If the workspace cannot record the act, the card stays and a message says that nothing was recorded.
54
+
55
+ ## The scope
56
+
57
+ The switch at the top of Today has two choices.
58
+
59
+ - **Repository.** The cards of the repository you chose. The number on the Today button counts these cards.
60
+ - **All repositories.** The cards of every repository. The count at the top says how many repositories it counts across.
61
+
62
+ The number on the Today button and the number at the top of the list are the same in both choices.
63
+
64
+ ## Short lists
65
+
66
+ Today shows the first three cards. Press **Show more** to see the rest, and **Show fewer** to fold them again. The count always includes every card.
67
+
68
+ ## A source that cannot be read
69
+
70
+ When Today cannot read a source, for example a broken needs file, it says so in a notice at the top of the list. It does not show an empty list and it does not say that nothing needs you. The notice names the source and the reason. Today still shows the cards of the sources it could read.
71
+
72
+ ## From a terminal
73
+
74
+ `pmos today` prints the same list as Today, in the same order, with the kind first.
75
+
76
+ ```
77
+ pmos today # the repository you are in
78
+ pmos today --ws <id> # one repository by its id
79
+ pmos today --json # the list as JSON
80
+ pmos today --root <dir> # read a repository folder without a server
81
+ ```
82
+
83
+ With the workspace server running, the command asks the server, so it also lists the sessions that wait for you. Without the server it reads the repository files and prints a line that says it did not read the sessions. The command prints "Nothing needs you" and the next draft when the list is empty. It exits with 0 when it read the list, 1 when it could not read it, and 2 when you gave it an option it does not have.
84
+
85
+ The page and the command read the route `GET /api/today?ws=<id>`. The route changes nothing.
@@ -102,7 +102,7 @@ The workspace reads the TypeSafe key only on this machine. After you save the ke
102
102
 
103
103
  ## Set it up
104
104
 
105
- 1. Open **validate** in the settings and tools drawer.
105
+ 1. Open Settings. Under Validate consent, press **Open Validate**. Validate opens as a tab.
106
106
  2. Under **TypeSafe key**, paste the key and press **save**. Press **remove** to delete a saved key.
107
107
  3. Under **this repository**, press **turn on**. This choice applies to the selected repository only.
108
108
  4. Enter the paths that the workspace must never send, one for each line. A path ends with `/` for a folder. A `*` matches any characters. Press **save excluded paths**.
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+ /* actor.js — who a write is, read from the CALL PATH, never from a field (pm-os-absorb, PRD R23 and R25).
3
+ *
4
+ * fromUi() → { by: 'pm:wawan', via: 'ui' } the page, after the route's own checks
5
+ * fromCli({isTTY, env}) → { by, via: 'cli' } a bound agent session, else a terminal (the PM), else unknown
6
+ * isPm(actor) · isAgent(actor) · sessionOf(actor)
7
+ * pageName(by) → 'agent:sess-<n>' for 'agent:sess-<n>.<8hex>', else `by` unchanged
8
+ *
9
+ * F5: a ledger line's `by` carries the session token; the page must never see it. Every GET route and every needs
10
+ * provider maps each `by` it answers through pageName (proposals, moves, item owners, report attributions).
11
+ *
12
+ * The placements, measurements and issues writers take their actor from here, so the four ledgers attribute one way.
13
+ * A caller never passes `by` or `via`: a writer that accepted them would make attribution something a caller chooses.
14
+ *
15
+ * THE SESSION-TOKEN MAP (Run 2, slice 2A). server.js gives each agent session a token, `sess-<n>.<8 hex>`, in its PTY
16
+ * environment as PMOS_SESSION. The token itself is never written to disk or sent to the page (D08, F5). server.js
17
+ * persists its SHA-256 (`tokenHash`) in the session's own meta file, so after a restart the map still answers which
18
+ * session a token belongs to and which initiative that session is bound to. The map is read from the files each time
19
+ * (no cache, D33), so a CLI with no server reads the same answer as the server.
20
+ *
21
+ * tokenHash(token) → 64 hex characters, or null for a value that is not a token
22
+ * sessionForToken(token, { sessDir }) → { session, initiative } | null
23
+ *
24
+ * HONEST LIMIT. A token is an attribution, not a credential: a process that unsets PMOS_SESSION is no longer an agent
25
+ * to fromCli. pm-guard.js requirePmCli asks such a caller to type the id of a PM-only act (X12); CI detection is the
26
+ * backstop for the rest.
27
+ */
28
+ const fs = require('fs');
29
+ const path = require('path');
30
+ const crypto = require('crypto');
31
+
32
+ const PM_SLUG = 'wawan';
33
+ const SESSION_RE = /^sess-\d+\.[0-9a-f]{8}$/;
34
+ const BY_RE = /^(?:pm:[a-z0-9_-]{1,40}|agent:unknown|agent:sess-\d+\.[0-9a-f]{8})$/;
35
+ const VIA = ['ui', 'cli'];
36
+
37
+ const PM = Object.freeze({ by: `pm:${PM_SLUG}`, via: 'ui' });
38
+
39
+ /** The page. Call it only after the route has run its checks (pm-guard.js requirePmPress for a PM-only act). */
40
+ function fromUi() { return { by: PM.by, via: 'ui' }; }
41
+
42
+ /**
43
+ * A `pmos <verb>` call. A bound agent session FIRST, by its token: an agent that runs its commands in a PTY (or wraps
44
+ * them with `script`) still has a terminal, and a terminal must not turn its writes into the PM's. Only agent sessions
45
+ * get PMOS_SESSION (server.js spawnSession), so the PM's own shell is a terminal with no token: the PM. Else unknown.
46
+ */
47
+ function fromCli({ isTTY = !!(process.stdin.isTTY && process.stdout.isTTY), env = process.env } = {}) {
48
+ const tok = env && env.PMOS_SESSION;
49
+ if (typeof tok === 'string' && SESSION_RE.test(tok)) return { by: `agent:${tok}`, via: 'cli' };
50
+ if (isTTY) return { by: `pm:${PM_SLUG}`, via: 'cli' };
51
+ return { by: 'agent:unknown', via: 'cli' };
52
+ }
53
+
54
+ const valid = (a) => !!a && typeof a.by === 'string' && BY_RE.test(a.by) && VIA.includes(a.via);
55
+ const isPm = (a) => valid(a) && a.by.startsWith('pm:');
56
+ const isAgent = (a) => valid(a) && a.by.startsWith('agent:');
57
+ /** The session token of an agent actor, or null. */
58
+ const sessionOf = (a) => (isAgent(a) && a.by !== 'agent:unknown' ? a.by.slice('agent:'.length) : null);
59
+
60
+ /** The name the page may show. Strips the session token's secret suffix; anything else passes through. */
61
+ function pageName(by) {
62
+ if (typeof by !== 'string') return by;
63
+ const m = /^(agent:sess-\d+)\.[0-9a-f]{8}$/.exec(by);
64
+ return m ? m[1] : by;
65
+ }
66
+
67
+ /** The SHA-256 of a session token. The map stores this, never the token. */
68
+ function tokenHash(token) {
69
+ if (typeof token !== 'string' || !SESSION_RE.test(token)) return null;
70
+ return crypto.createHash('sha256').update(token).digest('hex');
71
+ }
72
+
73
+ /**
74
+ * The session a token belongs to, read from the session meta files server.js persists (`<sessDir>/<id>.json`). The id
75
+ * part of the token names the file, and the stored hash must match: a token with the right id and a wrong suffix is not
76
+ * that session. Null when there is no such session or no hash on record. `plan_item` is the item Hand to agent gave the
77
+ * session (null for any other session).
78
+ */
79
+ function sessionForToken(token, { sessDir } = {}) {
80
+ const h = tokenHash(token);
81
+ if (!h || typeof sessDir !== 'string' || !sessDir) return null;
82
+ const id = token.slice(0, token.indexOf('.'));
83
+ let meta;
84
+ try { meta = JSON.parse(fs.readFileSync(path.join(sessDir, id + '.json'), 'utf8')); } catch { return null; }
85
+ if (!meta || meta.id !== id || typeof meta.tokenHash !== 'string' || meta.tokenHash.length !== h.length) return null;
86
+ if (!crypto.timingSafeEqual(Buffer.from(meta.tokenHash), Buffer.from(h))) return null;
87
+ const str = (v) => (typeof v === 'string' && v ? v : null);
88
+ return { session: id, initiative: str(meta.initiative), plan_item: str(meta.plan_item) };
89
+ }
90
+
91
+ module.exports = { tokenHash, sessionForToken, PM_SLUG, SESSION_RE, BY_RE, VIA, fromUi, fromCli, valid, isPm, isAgent, sessionOf, pageName };
@@ -32,9 +32,38 @@ const REFRESH_TIMEOUT_MS = 120000; // a derivation that cannot finish in 2 min
32
32
  const cache = new Map(); // root -> { sig, at, value, error }
33
33
  const inflight = new Map(); // root -> true
34
34
 
35
+ // headSha runs on every signature, and the needs poll asks for a signature per registered tree several times per
36
+ // request, so a `git rev-parse` each time held the server for seconds with twenty trees registered. The answer is
37
+ // kept per root while the files a commit, checkout or reset touches (HEAD, its ref, packed-refs, the HEAD reflog)
38
+ // are unchanged; anything unreadable falls back to asking git.
39
+ const headMemo = new Map(); // root -> { key, sha }
40
+ function gitDirOf(root) {
41
+ const dotgit = path.join(root, '.git');
42
+ const st = fs.statSync(dotgit);
43
+ if (st.isDirectory()) return { dir: dotgit, common: dotgit };
44
+ const m = /^gitdir:\s*(.+)\s*$/m.exec(fs.readFileSync(dotgit, 'utf8'));
45
+ if (!m) throw new Error('no gitdir');
46
+ const dir = path.resolve(root, m[1].trim());
47
+ let common = dir;
48
+ try { common = path.resolve(dir, fs.readFileSync(path.join(dir, 'commondir'), 'utf8').trim()); } catch { /* not a linked worktree */ }
49
+ return { dir, common };
50
+ }
51
+ function headKey(root) {
52
+ const { dir, common } = gitDirOf(root);
53
+ const head = fs.readFileSync(path.join(dir, 'HEAD'), 'utf8').trim();
54
+ const stat = (f) => { try { const st = fs.statSync(f); return `${st.size}:${st.mtimeMs}:${st.ino}`; } catch { return '-'; } };
55
+ const ref = /^ref:\s*(.+)$/.exec(head);
56
+ return [head, ref ? stat(path.join(common, ref[1])) : '', stat(path.join(common, 'packed-refs')), stat(path.join(dir, 'logs', 'HEAD'))].join('|');
57
+ }
35
58
  function headSha(root) {
59
+ let key = null;
60
+ try { key = headKey(root); } catch { key = null; }
61
+ const hit = headMemo.get(root);
62
+ if (key && hit && hit.key === key) return hit.sha;
36
63
  const r = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8', timeout: 5000 });
37
- return r.status === 0 && r.stdout ? r.stdout.trim() : 'no-head';
64
+ const sha = r.status === 0 && r.stdout ? r.stdout.trim() : 'no-head';
65
+ if (key) headMemo.set(root, { key, sha });
66
+ return sha;
38
67
  }
39
68
 
40
69
  // The records' own state, so an UNCOMMITTED edit still invalidates. Cheap: one readdir + one stat
@@ -42,8 +71,9 @@ function headSha(root) {
42
71
  function recordsSig(root) {
43
72
  // Both layouts (pmos-self `planning/`, a PM repository's `pmos/state/`), and the ledgers the
44
73
  // board's rows depend on: an uncommitted lifecycle or acceptance line must not wait out the TTL (D70).
74
+ // pm-os-absorb: plan-items.jsonl (Run 4's Build rule reads items) and issues.jsonl (Run 5's report lane reads issues).
45
75
  const dirs = [path.join(root, 'planning', 'initiatives'), path.join(root, 'pmos', 'state', 'initiatives')];
46
- const ledgers = ['lifecycle.jsonl', 'acceptances.jsonl'].flatMap((f) => [
76
+ const ledgers = ['lifecycle.jsonl', 'acceptances.jsonl', 'plan-items.jsonl', 'issues.jsonl'].flatMap((f) => [
47
77
  path.join(root, 'planning', 'runs', f), path.join(root, 'pmos', 'state', f)]);
48
78
  let total = 0; let newest = 0; let any = false;
49
79
  for (const dir of dirs) {