create-pmos 0.51.0 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) 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/bin/pmos.js +15 -1
  16. package/workspace/cut-stub.js +40 -0
  17. package/workspace/first-paint-audit.js +145 -0
  18. package/workspace/fixtures/capture/requirements/capture-2026-10-04-1.md +10 -0
  19. package/workspace/fixtures/initiatives-api/list.json +176 -0
  20. package/workspace/fixtures/initiatives-api/post-403.json +5 -0
  21. package/workspace/fixtures/initiatives-api/post-ok.json +10 -0
  22. package/workspace/fixtures/initiatives-api/roadmap.json +208 -0
  23. package/workspace/fixtures/initiatives-api/sheet.json +112 -0
  24. package/workspace/fixtures/report-api/draft.json +177 -0
  25. package/workspace/fixtures/reports-page/draft-biweekly-2026-10-16.md +97 -0
  26. package/workspace/fixtures/today-api/provider-rows.json +204 -0
  27. package/workspace/fixtures/today-api/seam-probe-kinds.json +5 -0
  28. package/workspace/guide/board.md +81 -0
  29. package/workspace/guide/breakdown.md +154 -0
  30. package/workspace/guide/command-palette.md +75 -0
  31. package/workspace/guide/index.md +27 -17
  32. package/workspace/guide/initiatives-roadmap.md +130 -0
  33. package/workspace/guide/initiatives-screen.md +89 -0
  34. package/workspace/guide/initiatives-timeline.md +57 -0
  35. package/workspace/guide/integrated-work.md +94 -0
  36. package/workspace/guide/knowledge-graph.md +3 -68
  37. package/workspace/guide/layout.md +98 -32
  38. package/workspace/guide/live-notes.md +3 -3
  39. package/workspace/guide/pm-only-acts.md +89 -0
  40. package/workspace/guide/pm-only-lines.md +61 -0
  41. package/workspace/guide/reports-page.md +80 -0
  42. package/workspace/guide/reports.md +73 -0
  43. package/workspace/guide/timeline.md +70 -0
  44. package/workspace/guide/today.md +85 -0
  45. package/workspace/guide/validate-with-typesafe.md +1 -1
  46. package/workspace/lib/actor.js +91 -0
  47. package/workspace/lib/board-cache.js +32 -2
  48. package/workspace/lib/board-cli.js +108 -0
  49. package/workspace/lib/board-fold.js +232 -0
  50. package/workspace/lib/breakdown-export.js +80 -0
  51. package/workspace/lib/capture.js +98 -0
  52. package/workspace/lib/engine/plan-items-stage.js +271 -0
  53. package/workspace/lib/engine/workflow-state.js +28 -2
  54. package/workspace/lib/factory-join.js +158 -0
  55. package/workspace/lib/grant-mint.js +113 -0
  56. package/workspace/lib/health-rules-factory.js +92 -0
  57. package/workspace/lib/health-rules-plan.js +164 -0
  58. package/workspace/lib/health.js +79 -0
  59. package/workspace/lib/initiative-cli.js +222 -0
  60. package/workspace/lib/initiative-display.js +183 -0
  61. package/workspace/lib/initiative-needs.js +127 -0
  62. package/workspace/lib/initiative-rows.js +206 -0
  63. package/workspace/lib/initiative-writer.js +141 -0
  64. package/workspace/lib/issues.js +206 -0
  65. package/workspace/lib/item-cli.js +195 -0
  66. package/workspace/lib/item-spans.js +137 -0
  67. package/workspace/lib/measure-cli.js +100 -0
  68. package/workspace/lib/measurements.js +189 -0
  69. package/workspace/lib/mobile-nav.js +57 -53
  70. package/workspace/lib/needs-providers.js +120 -0
  71. package/workspace/lib/placements.js +440 -0
  72. package/workspace/lib/plan-items.js +922 -0
  73. package/workspace/lib/plan-ledgers.js +85 -0
  74. package/workspace/lib/plan-rules.js +72 -0
  75. package/workspace/lib/plan-tree.js +367 -0
  76. package/workspace/lib/pm-guard.js +174 -0
  77. package/workspace/lib/pm-lines-needs.js +331 -0
  78. package/workspace/lib/pmos-verbs.js +66 -0
  79. package/workspace/lib/proactive.js +7 -4
  80. package/workspace/lib/report-cli.js +152 -0
  81. package/workspace/lib/report-draft.js +579 -0
  82. package/workspace/lib/report-needs.js +213 -0
  83. package/workspace/lib/route-modules.js +128 -0
  84. package/workspace/lib/routes-board.js +91 -0
  85. package/workspace/lib/routes-capture.js +52 -0
  86. package/workspace/lib/routes-initiatives.js +371 -0
  87. package/workspace/lib/routes-reports.js +163 -0
  88. package/workspace/lib/routes-timeline.js +197 -0
  89. package/workspace/lib/routes-today.js +32 -0
  90. package/workspace/lib/today-cli.js +99 -0
  91. package/workspace/lib/today.js +333 -0
  92. package/workspace/lib/triage.js +163 -0
  93. package/workspace/lib/verify-work-cli.js +81 -0
  94. package/workspace/lib/work-check-needs.js +99 -0
  95. package/workspace/package-lock.json +1 -1
  96. package/workspace/package.json +5 -5
  97. package/workspace/public/index.html +29 -19
  98. package/workspace/public/pm/app.js +299 -30
  99. package/workspace/public/pm/bd-board.js +113 -0
  100. package/workspace/public/pm/bd-drag.js +59 -0
  101. package/workspace/public/pm/bd-model.js +152 -0
  102. package/workspace/public/pm/bd-timeline.js +233 -0
  103. package/workspace/public/pm/bd-tree.js +244 -0
  104. package/workspace/public/pm/board-copy.js +57 -0
  105. package/workspace/public/pm/breakdown-api.js +58 -0
  106. package/workspace/public/pm/breakdown-copy.js +138 -0
  107. package/workspace/public/pm/components.css +3054 -0
  108. package/workspace/public/pm/cycle-review.js +125 -0
  109. package/workspace/public/pm/data.js +40 -187
  110. package/workspace/public/pm/dest-board.js +438 -0
  111. package/workspace/public/pm/dest-initiatives.js +134 -0
  112. package/workspace/public/pm/dest-reports.js +270 -0
  113. package/workspace/public/pm/dest-settings.js +217 -0
  114. package/workspace/public/pm/dest-today.js +176 -0
  115. package/workspace/public/pm/dialogs.js +88 -0
  116. package/workspace/public/pm/display-word.js +56 -0
  117. package/workspace/public/pm/dock.js +105 -0
  118. package/workspace/public/pm/fixture.js +8 -227
  119. package/workspace/public/pm/frame-copy.js +94 -0
  120. package/workspace/public/pm/handoff-box.js +82 -0
  121. package/workspace/public/pm/http.js +88 -0
  122. package/workspace/public/pm/icons.js +53 -0
  123. package/workspace/public/pm/initiative-list.js +218 -0
  124. package/workspace/public/pm/initiative-sheet.js +261 -0
  125. package/workspace/public/pm/initiatives-api.js +59 -0
  126. package/workspace/public/pm/initiatives-copy.js +215 -0
  127. package/workspace/public/pm/initiatives-timeline-copy.js +59 -0
  128. package/workspace/public/pm/initiatives-timeline.js +410 -0
  129. package/workspace/public/pm/initiatives-ui.js +123 -0
  130. package/workspace/public/pm/item-sheet.js +251 -0
  131. package/workspace/public/pm/keep-card.js +204 -0
  132. package/workspace/public/pm/lifecycle.js +7 -1
  133. package/workspace/public/pm/mobile.css +49 -20
  134. package/workspace/public/pm/mobile.js +157 -29
  135. package/workspace/public/pm/modes-live.js +7 -883
  136. package/workspace/public/pm/modes.js +32 -1024
  137. package/workspace/public/pm/new-initiative-sheet.js +74 -0
  138. package/workspace/public/pm/next-step-ui.js +3 -2
  139. package/workspace/public/pm/palette-copy.js +51 -0
  140. package/workspace/public/pm/palette.js +312 -0
  141. package/workspace/public/pm/panel-breakdown.js +702 -0
  142. package/workspace/public/pm/panel-cockpit.js +63 -295
  143. package/workspace/public/pm/panel-detail.js +25 -172
  144. package/workspace/public/pm/panel-initiatives.js +1 -19
  145. package/workspace/public/pm/panel-repos.js +31 -81
  146. package/workspace/public/pm/panel-tabs.js +197 -110
  147. package/workspace/public/pm/panel-trees.js +5 -2
  148. package/workspace/public/pm/portfolio-report.js +61 -0
  149. package/workspace/public/pm/primitives.js +656 -0
  150. package/workspace/public/pm/print.css +33 -0
  151. package/workspace/public/pm/provider-cards.js +136 -0
  152. package/workspace/public/pm/registry.js +194 -0
  153. package/workspace/public/pm/release-card.js +110 -0
  154. package/workspace/public/pm/report-view.js +309 -0
  155. package/workspace/public/pm/reports-copy.js +114 -0
  156. package/workspace/public/pm/roadmap.js +271 -0
  157. package/workspace/public/pm/sig-watch.js +41 -0
  158. package/workspace/public/pm/state.js +8 -4
  159. package/workspace/public/pm/status-banner.js +13 -5
  160. package/workspace/public/pm/tab-doc.js +2 -4
  161. package/workspace/public/pm/tab-term.js +12 -2
  162. package/workspace/public/pm/tab-web.js +3 -2
  163. package/workspace/public/pm/timeline-copy.js +48 -0
  164. package/workspace/public/pm/today-cards.js +244 -0
  165. package/workspace/public/pm/today-copy.js +97 -0
  166. package/workspace/public/pm/tokens.css +16 -18
  167. package/workspace/public/pm/topbar.js +102 -0
  168. package/workspace/public/pm/triage-card.js +60 -0
  169. package/workspace/public/pm/undo-hold.js +103 -0
  170. package/workspace/public/pm/view-addrepo.js +2 -1
  171. package/workspace/public/pm/view-livenotes.js +3 -1
  172. package/workspace/public/pm/view-today.js +7 -162
  173. package/workspace/public/vendor/Sortable.min.js +2 -0
  174. package/workspace/public/vendor/lucide-sprite.js +79 -0
  175. package/workspace/replaced-tests.json +2864 -0
  176. package/workspace/run-tests.js +117 -6
  177. package/workspace/server.js +312 -36
  178. package/workspace/sheet-answer.js +61 -0
  179. package/workspace/skills/measure.skill +41 -0
  180. package/workspace/skills/report-biweekly.skill +23 -25
  181. package/workspace/skills/tracker-refresh.skill +50 -0
  182. package/workspace/test-files.json +20 -4
  183. package/workspace/test-lists/base.json +6 -0
  184. package/workspace/test-lists/run2-a.json +6 -0
  185. package/workspace/test-lists/run2-b.json +13 -0
  186. package/workspace/test-lists/run2-c.json +19 -0
  187. package/workspace/test-lists/run2-d.json +22 -0
  188. package/workspace/test-lists/run3-0.json +5 -0
  189. package/workspace/test-lists/run3-a.json +5 -0
  190. package/workspace/test-lists/run3-b.json +6 -0
  191. package/workspace/test-lists/run3-c.json +15 -0
  192. package/workspace/test-lists/run3-d.json +6 -0
  193. package/workspace/test-lists/run4-a.json +8 -0
  194. package/workspace/test-lists/run4-b.json +6 -0
  195. package/workspace/test-lists/run4-c.json +13 -0
  196. package/workspace/test-lists/run4-d.json +9 -0
  197. package/workspace/test-lists/run5-a.json +6 -0
  198. package/workspace/test-lists/run5-b.json +6 -0
  199. package/workspace/test-lists/run5-c.json +15 -0
  200. package/workspace/test-run.js +27 -1
  201. package/workspace/third-party/README.md +44 -0
  202. package/workspace/third-party/lucide-static-1.52.0.LICENSE +43 -0
  203. package/workspace/third-party/sortablejs-1.15.7.LICENSE +21 -0
  204. package/workspace/design-parity.js +0 -673
  205. package/workspace/public/pm/tab-task.js +0 -86
  206. package/workspace/public/pm/view-guide.js +0 -141
  207. package/workspace/public/pm/view-knowledge.js +0 -225
  208. package/workspace/public/pm/view-memory.js +0 -122
  209. package/workspace/public/pm/view-portfolio.js +0 -153
  210. package/workspace/public/pm/view-reports.js +0 -190
  211. package/workspace/public/pm/view-scheduled.js +0 -161
  212. package/workspace/public/pm/view-skills.js +0 -216
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+ /* sheet-answer.js — a test helper (pm-os-absorb Run 3, slice 3D). The workspace asks its questions as sheets
3
+ * (public/pm/dialogs.js), so a suite that used to accept a native prompt or confirm answers the sheet instead.
4
+ *
5
+ * answerSheet(page, answer?, press?) → { kind, title } | null
6
+ * waits for the next sheet, fills each field named in `answer` ({ key: text }, or a string for the single field of an
7
+ * askText sheet), presses "confirm" (default) or "cancel", and reports the sheet's kind and title.
8
+ * watchSheets(page, handler) → stops answering with the returned function
9
+ * answers every sheet that opens with handler({ kind, title, fields }) → { answer, press } (or null to leave it open).
10
+ */
11
+
12
+ async function answerSheet(page, answer, press = 'confirm', timeout = 8000) {
13
+ try { await page.waitForSelector('[data-dialog]', { timeout }); } catch { return null; }
14
+ return page.evaluate(({ a, p }) => {
15
+ const d = document.querySelector('[data-dialog]');
16
+ const kind = d.getAttribute('data-dialog');
17
+ const title = ((d.querySelector('[class*="title"]') || {}).textContent || '').trim();
18
+ const set = (c, v) => { c.value = v; c.dispatchEvent(new Event('input', { bubbles: true })); c.dispatchEvent(new Event('change', { bubbles: true })); };
19
+ if (a != null) {
20
+ const fields = [...d.querySelectorAll('[data-dialog-field]')];
21
+ if (typeof a === 'string') { if (fields[0]) set(fields[0], a); }
22
+ else for (const [k, v] of Object.entries(a)) { const c = d.querySelector('[data-dialog-field="' + k + '"]'); if (c) set(c, v); }
23
+ }
24
+ d.querySelector(p === 'cancel' ? '[data-dialog-cancel]' : '[data-dialog-confirm]').click();
25
+ return { kind, title };
26
+ }, { a: answer == null ? null : answer, p: press });
27
+ }
28
+
29
+ function watchSheets(page, handler) {
30
+ let on = true;
31
+ (async () => {
32
+ while (on) {
33
+ let seen = null;
34
+ try {
35
+ seen = await page.evaluate(() => {
36
+ const d = document.querySelector('[data-dialog]:not([data-seen])');
37
+ if (!d) return null;
38
+ d.setAttribute('data-seen', '1');
39
+ return { kind: d.getAttribute('data-dialog'), title: ((d.querySelector('[class*="title"]') || {}).textContent || '').trim(), fields: [...d.querySelectorAll('[data-dialog-field]')].map((c) => c.getAttribute('data-dialog-field')) };
40
+ });
41
+ } catch { return; }
42
+ if (seen) {
43
+ const r = handler(seen);
44
+ if (r) await page.evaluate(({ a, p }) => {
45
+ const d = document.querySelector('[data-dialog][data-seen]');
46
+ if (!d) return;
47
+ const set = (c, v) => { c.value = v; c.dispatchEvent(new Event('input', { bubbles: true })); };
48
+ if (a != null) {
49
+ if (typeof a === 'string') { const f = d.querySelector('[data-dialog-field]'); if (f) set(f, a); }
50
+ else for (const [k, v] of Object.entries(a)) { const c = d.querySelector('[data-dialog-field="' + k + '"]'); if (c) set(c, v); }
51
+ }
52
+ d.querySelector(p === 'cancel' ? '[data-dialog-cancel]' : '[data-dialog-confirm]').click();
53
+ }, { a: r.answer == null ? null : r.answer, p: r.press || 'confirm' }).catch(() => {});
54
+ }
55
+ await new Promise((res) => setTimeout(res, 60));
56
+ }
57
+ })();
58
+ return () => { on = false; };
59
+ }
60
+
61
+ module.exports = { answerSheet, watchSheets };
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: measure
3
+ label: "Record a measurement"
4
+ description: "Read the current value of a KR or a health budget from a committed file, and record it with `pmos measure add` and its source."
5
+ run: agent
6
+ agent: claude
7
+ version: 1.0.0
8
+ owner: wawan
9
+ risk: low
10
+ scope: read:state, write:state/measurements
11
+ output:
12
+ - Recorded
13
+ - Not recorded
14
+ - Sources
15
+ ---
16
+
17
+ /measure — record the current value of one or more anchors (a KR or a health budget) for THIS repository. A measurement is evidence. It is not a decision and it does not move an initiative.
18
+
19
+ Rules that always hold:
20
+ 1. ONLY FROM A COMMITTED FILE. Read the value from a file in this repository. Use the commit that holds the file now. If the value is not in a committed file, do not record it. Write it under "Not recorded" with the reason.
21
+ 2. ALWAYS A SOURCE. Every measurement has a source in the form `<path>@<commit sha>`. The command refuses a measurement with no source.
22
+ 3. NOTHING INVENTED. Do not estimate, round up or carry a value forward. If two files disagree, record neither value. Write both under "Not recorded".
23
+ 4. ONE WRITER. Record a value only with `pmos measure add`. Do not edit `planning/runs/measurements.jsonl` or any ledger file.
24
+ 5. NO ATTRIBUTION FLAGS. Do not give `--by` or `--via`. The command sets who wrote the line from the session.
25
+
26
+ Procedure (each step ends with a check that shows it is done):
27
+ 1. Find the anchors. Read `planning/okrs/` for the KRs of the current period and `planning/okrs/health-budgets.md` for the health budgets. If the PM named anchors, use only those.
28
+ Done when: you have a list of anchors, each as a KR (for example `KR 2.1`) or `health:<id>`.
29
+ 2. For each anchor, find the file that states its current value and its target. Get the commit with `git log -1 --format=%h -- <path>`.
30
+ Done when: each anchor has a value, a target or none, a date and a source, or a reason it has none.
31
+ 3. Record each value:
32
+ `pmos measure add "<anchor>" <value> --source <path>@<sha> [--target <number>] [--unit <unit>] [--as-of YYYY-MM-DD]`
33
+ Use the date the file gives for the value as `--as-of`. With no date in the file, give no `--as-of`; the command uses today.
34
+ Done when: the command printed "recorded" for each value. If it refused a value, read its message, correct the call once, and otherwise write the value under "Not recorded".
35
+ 4. Check the result with `pmos measure list --json`.
36
+ Done when: each value you recorded is in the list.
37
+
38
+ Output sections:
39
+ - **Recorded** — one line per value: anchor, value, target, as_of, source.
40
+ - **Not recorded** — one line per anchor with no value, and why.
41
+ - **Sources** — each file you read, with its commit.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: report-biweekly
3
3
  label: "Bi-weekly report (from your definition)"
4
- description: "Prepare the biweekly report as a draft, following the PM's own definition of what it is for and how it looks. Proposes a definition if none exists yet."
4
+ description: "Prepare the biweekly report as a draft: a script with no model writes every number, then this skill writes the headline, the notes and the asks between the note markers. Proposes a definition if none exists yet."
5
5
  run: agent
6
6
  agent: claude
7
7
  output:
@@ -11,40 +11,38 @@ output:
11
11
  - Sources reached and not reached
12
12
  ---
13
13
 
14
- /report-biweekly — prepare a bi-weekly report for THIS repository as a draft. Work only from what you can actually read. Covers the last 14 days.
14
+ /report-biweekly — prepare a bi-weekly report for THIS repository as a draft. A script with no model writes the draft and every number in it; you write the words between the note markers. Work only from what you can actually read. Covers everything since the last issued bi-weekly report (the last 14 days when none was issued).
15
15
 
16
16
  Rules that always hold (D71):
17
17
  1. DRAFT ONLY. You write a file in this repository. Never send, post, share, email, publish or upload the report anywhere; the PM does that.
18
18
  2. READ-ONLY on every source. Never send, reply, forward, post, comment, create, edit, transition, label, archive or delete anything in mail, chat, Jira, Confluence, calendar or any other source.
19
- 3. SOURCED. Every claim carries a pointer: a requirement note, an initiative id, an acceptance, or a connector item (kind, id or link, date, author). A claim you cannot point to is left out or marked "unverified".
19
+ 3. SOURCED. Every claim carries a pointer the reader can open: an initiative id, a file path in this repository, or an acceptance. Write the pointer as a name. Do not type a date in it; the block above the note carries the dates. A claim you cannot point to is left out or marked "unverified".
20
20
  4. LIGHT ON CONTENT. Never copy message bodies, attachments, tokens or credentials. At most one short quotation per item. Skip threads or pages marked private, personal, confidential or HR, and count them.
21
- 5. NOTHING INVENTED. If a source is not reachable in this session, say so under "Sources reached and not reached". If nothing happened, say that. An honest short report is correct.
22
- 6. THE PM'S DEFINITION RULES. The shape of this report is whatever the definition says. Do not substitute your own format.
21
+ 5. NOTHING INVENTED. The script lists the sources it reached and did not reach under "Sources reached and not reached": that section is not yours to edit. If a source that you need is not reachable in this session, say so in your final message. If nothing happened, say that in the note. An honest short note is correct.
22
+ 6. THE PM'S DEFINITION RULES. The script shapes the file from the definition: its sections, their order and its blocks. The definition's purpose, audience, length and tone govern the words of your notes. Do not substitute your own format.
23
+ 7. THE SCRIPT WRITES EVERY NUMBER. In a note you type none: no count, no ratio such as "7 of 12", no percentage, no date, no day count. If a sentence needs a number, write it without the number; the block above the note already shows it. A name is not a number: an initiative id, a file path and a note id may be written as they are.
24
+ 8. PROSE ONLY BETWEEN NOTE MARKERS. In the draft file, write only between a `<!-- pmos:note <id> -->` line and its `<!-- /pmos:note -->` line. Never edit, move or delete a marker or any line outside one. Never add a section to the file.
23
25
 
24
26
  Procedure:
25
27
  1. Find the state directory: `pmos/state/` if it exists, otherwise `planning/`. Call it <state>.
26
28
  2. Read the definition: `<state>/reports/definitions/biweekly.md`.
27
- - If it exists, follow its purpose, audience, sources, sections, length and tone exactly.
28
- - If it does NOT exist, do not stop. Look for earlier biweekly reports in the repository and under <state>/reports/ to learn the shape the PM already uses. Write a PROPOSED definition (purpose, audience, sources, sections, length, tone) to `<state>/reports/definitions/biweekly.md` with this frontmatter, one key per line and nothing indented: `type: Reference`, `title`, `description`, `generated: {by: claude-code/report-biweekly, at: "…"}`, `status: draft` — and then use it for this draft. Never set `status: stable` or add `verified` yourself: only the PM confirms a definition.
29
- 3. Gather the material for the last 14 days: the requirement notes in `<state>/requirements/`, the initiatives and their acceptances (`node scripts/workflow-state.js` if the repository has it, otherwise read `<state>/initiatives/` and `acceptances.jsonl`), and every connected source the definition names that you can reach.
30
- 4. Write the draft to `<state>/reports/biweekly/YYYY-MM-DD.md` (today's date). If today's file exists, do NOT overwrite it: append a section `## Later run HH:MM`. Start with this frontmatter (OKF v0.2, D73). Every key is on ONE line — never indent a key under another:
31
- ```
32
- ---
33
- type: Reference
34
- title: "Bi-weekly report YYYY-MM-DD"
35
- description: "A draft biweekly report, prepared from the PM's own definition."
36
- generated: {by: claude-code/report-biweekly, at: "<today's date and time, ISO 8601>"}
37
- sources: [{resource: "<requirement note, initiative, or connector item>", title: "<what it is>"}]
38
- status: draft
39
- definition: stable | draft
40
- ---
41
- ```
42
- `definition` says whether the definition used is confirmed (`stable`) or only proposed (`draft`). Do not add a `timestamp` or `verified`.
43
- 5. Under the frontmatter: a Headline (one or two sentences: what the reader most needs to know), then the sections the definition names in its order, then "Needs your call" (decisions or answers only the PM can give), then "Sources reached and not reached" (each source kind: reached or not reached and why; private items skipped).
44
- 6. If the definition was a draft, say so in the first line of the Headline so the PM sees it.
45
- 7. Finish by saying in one line where the draft is. Do not commit; the PM's Sync carries the file.
29
+ - If it exists, write your notes to its purpose, audience, length and tone. The script applies its sections.
30
+ - If it does NOT exist, do not stop. Look for earlier biweekly reports in the repository and under <state>/reports/ to learn the shape the PM already uses. Write a PROPOSED definition (purpose, sources, sections, length, tone) to `<state>/reports/definitions/biweekly.md` with this frontmatter, one key per line and nothing indented: `type: Reference`, `title`, `description`, `cadence: biweekly`, `sections: [headline, timeline, initiatives, new, decided, needs, sources]`, `generated: {by: claude-code/report-biweekly, at: "…"}`, `status: draft` — and then use it for this draft. Never set `status: stable` or add `verified` yourself: only the PM confirms a definition. Never set `audience`: only the PM names who reads the report, and the script writes no asks until the PM does.
31
+ 3. Do not read a tracker. Do not run `tracker-refresh`. That skill is scheduled before this one and writes its evidence into the plan items from local files. If it did not run, the script lists the tracker under "Sources reached and not reached". Repeat that line in your final message.
32
+ 4. Run `pmos report draft biweekly`. The script has no model: it folds the ledgers since the last issued biweekly report and writes the whole draft to `<state>/reports/biweekly/YYYY-MM-DD.md` (the slot is today's date), with one empty note marker for each block. Read what it prints.
33
+ - It prints the path: go on.
34
+ - It refuses because a draft for the slot exists: do NOT overwrite it. Do not pass `--redraft` here, and do not pass `--now`. Write your notes into the file that exists.
35
+ - It refuses because the slot is issued: stop. An issued report is frozen. Say so in one line.
36
+ - It refuses for any other reason: stop and quote the refusal. Do not work around it, and do not write the file yourself. A block with no reason for its health is the PM's to fix, not yours to invent.
37
+ 5. Read the draft. Then read what you need to write well: the requirement notes in `<state>/requirements/`, the initiatives and their acceptances (`node scripts/workflow-state.js` if the repository has it). The blocks, the numbers and the sources are the script's. Do not correct them.
38
+ 6. Write the prose, and only between note markers:
39
+ - For each `<!-- pmos:note <initiative-id> -->`, one or two sentences: what moved and what it means for the reader. Start with "Warning:" when the block is off track or at risk. A note that already has text is the PM's or an earlier run's: leave it.
40
+ - For `<!-- pmos:note report.headline -->`, one or two sentences: what the reader most needs to know. If the definition is a draft, say so in the first line.
41
+ - For `<!-- pmos:note report.asks -->`, if the file has it: the decisions only the audience can give, one per line, each line starting with "- ". Never ask the audience for a decision that is the PM's own.
42
+ 7. Run `pmos report draft biweekly --redraft`. The script is the one writer of the draft file: it writes the file again and keeps every note, so a line you changed outside a note marker is put back. Read the file once more and check that each note you wrote is still in place. Do not commit; the PM's Sync carries the file. Finish by saying in one line where the draft is.
43
+ - It refuses (the slot was issued since step 4, or a block has no reason for its health): quote the refusal and stop. Say that your notes are in the file and that the file is not re-rendered, so a line outside a note marker may differ from the script's. Do not edit the file again.
46
44
 
47
- Default shape, used ONLY when proposing a definition: a two-week report: progress, decisions, risks, what is next. Use the report frame of D78: a Headline, then What moved, Risks and Metrics, then Needs your call and Sources reached and not reached.
45
+ Default shape, used ONLY when proposing a definition: the report frame of D78 as amended by D86 point 3. A Headline, a Timeline, one block for each initiative active in the window, New since last report, Decided this cycle, Needs your call, and Sources reached and not reached.
48
46
 
49
47
  Output contract — produce EXACTLY these sections, the same shape every run:
50
48
  - Headline
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: tracker-refresh
3
+ label: "Refresh tracker items (from local files)"
4
+ description: "Bring the plan items that a tracker owns up to date from files already in this repository, and append one evidence line for each change. It reads local files only: no tracker call, no network."
5
+ run: agent
6
+ agent: claude
7
+ output:
8
+ - Moved
9
+ - Left as they are
10
+ - Not found
11
+ - Sources reached and not reached
12
+ ---
13
+
14
+ /tracker-refresh — bring the plan items that a tracker owns up to date for THIS repository. A tracker-owned item has an owner that starts with `tracker:`. You read files that are already here and you append evidence lines. You call no tracker.
15
+
16
+ This skill runs before the bi-weekly report (`report-biweekly`). The report script reads only the ledgers. It cannot call a tracker (R30, D71), so the evidence lines you write are the only way a tracker item reaches the report.
17
+
18
+ Rules that always hold (D71):
19
+ 1. LOCAL FILES ONLY. Read files in this repository. Never call a tracker, a connector, a URL or any network tool. Never run `curl`, `wget`, `git fetch` or `git pull`. A tracker that no local file describes is "not reached".
20
+ 2. READ-ONLY on every source. Never send, reply, forward, post, comment, create, edit, transition, label, archive or delete anything in Jira, Confluence, mail, chat, calendar or any other source.
21
+ 3. THE ONLY WRITE is `pmos item move`. Never edit a repository file. Never run `pmos item add`, `set` or `withdraw`. Never write a ledger by hand. Never commit; the PM's Sync carries the ledger.
22
+ 4. EVIDENCE FOR EVERY MOVE. Each move carries `--evidence file:<repository path>`: the local file that shows the tracker state. If you have no file to point at, do not move the item.
23
+ 5. NOTHING INVENTED. Move an item only to the state a file says. If the files disagree, use the newest one and say so. If no file says, leave the item and list it under "Not found".
24
+ 6. LIGHT ON CONTENT. Read titles, ids and statuses. Never copy a message body, an attachment, a token or a credential. Skip a file marked private, personal, confidential or HR, and count it.
25
+ 7. NEVER REOPEN, NEVER WITHDRAW. Never move an item that is Done or Withdrawn. Never withdraw an item: if a file says the tracker closed it as "won't do", list it under "Left as they are" and say why.
26
+ 8. A FILE THAT PMOS WRITES IS NEVER EVIDENCE. Never use a plan-items ledger, any `*.jsonl` file, anything under `<state>/runs/` or anything under `<state>/reports/` as evidence. Those files hold the state you are about to check, so they cannot confirm it.
27
+ 9. AN OLD FILE DOES NOT UNDO A NEWER MOVE. Move an item only when the file is dated later than the item's last move.
28
+
29
+ Procedure:
30
+ 1. Find the state directory: `pmos/state/` if it exists, otherwise `planning/`. Call it <state>.
31
+ 2. List the initiatives: the file names in `<state>/initiatives/`, without `.md`. For each one, run `pmos item list <initiative> --json`. Keep the items whose `owner` starts with `tracker:` and whose `state` is `todo`, `doing` or `review`. The text after `tracker:` is the tracker reference, for example `PROJ-12`.
32
+ 3. For each kept item, search the local files for its tracker reference: first `<state>/requirements/`, then the rest of `<state>/` and `docs/`. Use `grep -rl --exclude='*.jsonl' "<reference>" <folder>`. Skip every file under `<state>/runs/` and `<state>/reports/` (rule 8). Accept a file that names the reference and states its status. Take the newest accepted file. Its date is its `timestamp` frontmatter, or its last git commit if it has none.
33
+ 4. Read the status in that file and map it to a state:
34
+ | The file says | State |
35
+ |---|---|
36
+ | To Do, Open, Backlog, Ready | `todo` |
37
+ | In Progress, Doing, Started | `doing` |
38
+ | In Review, Code Review, QA, Testing | `review` |
39
+ | Done, Closed, Resolved, Released | `done` |
40
+ A status that is in no row is not a state: leave the item and list it under "Not found".
41
+ 5. Find the item's last move: the `at` of the last entry with `"ev":"moved"` in its `history` (from `pmos item list --json`), or its `createdAt` if it has no such entry. If the file's date is not later than that, leave the item and list it under "Left as they are" as "file older than the last move" (rule 9).
42
+ 6. If the mapped state is the item's state, leave the item. Write nothing. List it under "Left as they are" with the file and its date.
43
+ 7. If the mapped state is different, run `pmos item move <item> <state> --evidence file:<path of the file>`. If the command is refused, read the reason and do not work around it. List the item under "Not found" with the reason.
44
+ 8. Finish with the four sections of the output contract, as short lines: one line for each item, with its id, the old and new state and the file. Under "Sources reached and not reached", give each tracker reference as reached (the file and its date) or not reached. If you moved nothing, say so in one line: a refresh that moves nothing writes no line, so the bi-weekly report can still list the tracker as not refreshed. Do not commit.
45
+
46
+ Output contract — produce EXACTLY these sections, the same shape every run:
47
+ - Moved
48
+ - Left as they are
49
+ - Not found
50
+ - Sources reached and not reached
@@ -39,7 +39,12 @@
39
39
  "proactive-prepare.test.js",
40
40
  "learn.test.js",
41
41
  "session-flag.test.js",
42
- "live-notes.test.js"
42
+ "live-notes.test.js",
43
+ "plan-items.test.js",
44
+ "plan-tree.test.js",
45
+ "item-cli.test.js",
46
+ "plan-routes.test.js",
47
+ "glossary.test.js"
43
48
  ],
44
49
  "panels": [
45
50
  "pm-shell-test.js",
@@ -88,7 +93,10 @@
88
93
  "pm-r29-filespad-test.js",
89
94
  "pm-r29-newsession-alignment-test.js",
90
95
  "pm-live-notes-test.js",
91
- "pm-r30-test.js"
96
+ "pm-r30-test.js",
97
+ "pm-breakdown-test.js",
98
+ "pm-breakdown-phone-test.js",
99
+ "first-paint-count.test.js"
92
100
  ],
93
101
  "ui": [
94
102
  "ui-test.js"
@@ -131,7 +139,7 @@
131
139
  "pm-repos-test.js": 3,
132
140
  "pm-initiatives-test.js": 15,
133
141
  "pm-detail-test.js": 16,
134
- "pm-tabs-test.js": 59,
142
+ "pm-tabs-test.js": 50,
135
143
  "pm-term-test.js": 28,
136
144
  "pm-modes-test.js": 18,
137
145
  "pm-r1-test.js": 6,
@@ -174,7 +182,15 @@
174
182
  "pm-r29-newsession-alignment-test.js": 8,
175
183
  "pm-live-notes-test.js": 24,
176
184
  "pm-r30-test.js": 4,
177
- "ui-test.js": 30
185
+ "ui-test.js": 30,
186
+ "plan-items.test.js": 1,
187
+ "plan-tree.test.js": 2,
188
+ "item-cli.test.js": 2,
189
+ "plan-routes.test.js": 3,
190
+ "glossary.test.js": 1,
191
+ "pm-breakdown-test.js": 62,
192
+ "pm-breakdown-phone-test.js": 19,
193
+ "first-paint-count.test.js": 15
178
194
  },
179
195
  "_comment_seconds": "Measured wall time of each suite run alone (4-CPU sandbox, 2026-10-05). Only the ORDER uses it: the slowest suites start first, and --shard deals suites to the least-loaded shard so the shards finish together. A wrong or missing number never skips a suite; a missing one counts as 10."
180
196
  }
@@ -0,0 +1,6 @@
1
+ {
2
+ "_comment": "The seam suites of pm-os-absorb Runs 2 to 5 (the base every slice is cut from). Each slice adds its own suites in test-lists/<slice-id>.json, for example run2-a.json, and never edits this file or test-files.json.",
3
+ "modules": ["d86-seams.test.js"],
4
+ "panels": ["pm-seams-test.js"],
5
+ "seconds": { "d86-seams.test.js": 8, "pm-seams-test.js": 30 }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "_comment": "run2-a (Runtime guards): the R25 guard suites. r25-guards.test.js boots real servers, PTYs and tmux on scratch repositories; pm-nonce-test.js is a short browser suite.",
3
+ "modules": ["r25-guards.test.js"],
4
+ "panels": ["pm-nonce-test.js"],
5
+ "seconds": { "r25-guards.test.js": 10, "pm-nonce-test.js": 12 }
6
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "_comment": "pm-os-absorb Run 2, slice 2B (detection, matcher, grants). The suites outside workspace/ run from the repository root: node --test \"kit/mods/pmos-cockpit/hooks/lib/*.test.mjs\" (kit CI), bash scripts/tests/pm-lines.sh and bash scripts/tests/plan-items.sh (through scripts/check-plan-ledgers.js in the Initiative Gate), node scripts/tests/record-grant.test.js.",
3
+ "modules": [
4
+ "grant-mint.test.js",
5
+ "pm-lines-needs.test.js"
6
+ ],
7
+ "panels": [],
8
+ "exclusive": [],
9
+ "seconds": {
10
+ "grant-mint.test.js": 4,
11
+ "pm-lines-needs.test.js": 4
12
+ }
13
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "_comment": "pm-os-absorb Run 2, slice run2-c (ledgers, writers, CLI, routes): the placements and measurements writers, the initiative writer, pmos initiative and pmos measure, the Initiatives API and initiative-needs. Unit and route suites only; this slice has no browser suite.",
3
+ "modules": [
4
+ "placements.test.js",
5
+ "measurements.test.js",
6
+ "initiative-writer.test.js",
7
+ "initiative-cli.test.js",
8
+ "routes-initiatives.test.js",
9
+ "initiative-needs.test.js"
10
+ ],
11
+ "seconds": {
12
+ "placements.test.js": 4,
13
+ "measurements.test.js": 3,
14
+ "initiative-writer.test.js": 5,
15
+ "initiative-cli.test.js": 15,
16
+ "routes-initiatives.test.js": 6,
17
+ "initiative-needs.test.js": 5
18
+ }
19
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "_comment": "Slice 2D of pm-os-absorb Run 2: display, health and the Initiatives destination. Its suites only; never test-files.json or base.json.",
3
+ "modules": [
4
+ "health.test.js",
5
+ "r3-display.test.js"
6
+ ],
7
+ "panels": [
8
+ "pm-initiatives-dest-test.js",
9
+ "pm-initiatives-dest-phone-test.js",
10
+ "first-paint-initiatives.test.js"
11
+ ],
12
+ "seconds": {
13
+ "health.test.js": 1,
14
+ "r3-display.test.js": 3,
15
+ "pm-initiatives-dest-test.js": 60,
16
+ "pm-initiatives-dest-phone-test.js": 40,
17
+ "first-paint-initiatives.test.js": 25
18
+ },
19
+ "helpers": {
20
+ "pm-initiatives-stub-test.js": "not a suite: serves the Initiatives routes to the page from the goldens, for pm-initiatives-dest-test.js, pm-initiatives-dest-phone-test.js and first-paint-initiatives.test.js"
21
+ }
22
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "_comment": "Slice run3-0 of pm-os-absorb: the replaced-tests manifest check. Node only; reads the base's test files through git show at the commit SHA pinned in replaced-tests.json (full history needed); it exits 2 when that commit is missing while an entry waits for the PM.",
3
+ "modules": ["replaced-tests.test.js"],
4
+ "seconds": { "replaced-tests.test.js": 5 }
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "_comment": "Slice run3-a of pm-os-absorb (the frame shell): the frame on a desktop (bar, places, dock, Settings, keys, first paint with the bar) and on a phone (390x844, touch: the five tabs, the pinned palette, one panel at a time, the 44px row menus, nothing on hover). Both boot a real server on a synthetic fixture repository through pm-breakdown-stub-test.js bootWithRepo.",
3
+ "panels": ["pm-frame-test.js", "pm-frame-phone-test.js"],
4
+ "seconds": { "pm-frame-test.js": 70, "pm-frame-phone-test.js": 40 }
5
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "_comment": "Slice run3-b of pm-os-absorb (Run 3, Today): the ranking and its route and CLI, and the Today destination in a browser. pm-r29-today-test.js is listed in test-files.json since the base; this slice replaced its content (manifest entry C02).",
3
+ "modules": ["today.test.js", "routes-today.test.js"],
4
+ "panels": ["pm-today-test.js", "pm-today-phone-test.js", "first-paint-today.test.js"],
5
+ "seconds": { "today.test.js": 5, "routes-today.test.js": 25, "pm-today-test.js": 120, "pm-today-phone-test.js": 90, "first-paint-today.test.js": 60 }
6
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "_comment": "Slice run3-c of pm-os-absorb (the ⌘K palette and capture): the capture writer and its route, and the browser suite pm-palette-test.js.",
3
+ "modules": [
4
+ "capture.test.js",
5
+ "routes-capture.test.js"
6
+ ],
7
+ "seconds": {
8
+ "capture.test.js": 1,
9
+ "routes-capture.test.js": 4,
10
+ "pm-palette-test.js": 60
11
+ },
12
+ "panels": [
13
+ "pm-palette-test.js"
14
+ ]
15
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "_comment": "Slice run3-d of pm-os-absorb (Run 3: tokens, the cuts, the parity lock lifted). cuts.test.js reads the shipped source and asserts each cut is absent, the tokens, the dialogs and the one /api/reports caller; pm-cuts-test.js loads the frame and asserts the bar, Settings (Open Validate, Rename and Remove in sheets), the three Portfolio report commands in the palette and the sheet dialogs. The browser suite runs only through runs/browser-run.sh.",
3
+ "modules": ["cuts.test.js"],
4
+ "panels": ["pm-cuts-test.js"],
5
+ "seconds": { "cuts.test.js": 2, "pm-cuts-test.js": 40 }
6
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "_comment": "pm-os-absorb Run 4, slice run4-a (Integrator): the factory join and its health rules, and the parity of the engine's plan-item fold with the workspace's (X29). The shell suites scripts/tests/verify-work.sh and scripts/tests/build-rule.sh run from scripts/check-plan-ledgers.js (the Initiative Gate), not from here.",
3
+ "modules": ["factory-join.test.js", "plan-items-stage-parity.test.js"],
4
+ "panels": [],
5
+ "exclusive": [],
6
+ "helpers": [],
7
+ "seconds": { "factory-join.test.js": 3, "plan-items-stage-parity.test.js": 2 }
8
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "_comment": "pm-os-absorb Run 4, slice 4B (Timeline engine and Breakdown › Timeline): the item-spans engine and the Breakdown › Timeline browser suite.",
3
+ "modules": ["item-spans.test.js"],
4
+ "panels": ["pm-breakdown-timeline-test.js"],
5
+ "seconds": { "item-spans.test.js": 1, "pm-breakdown-timeline-test.js": 40 }
6
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "_comment": "Slice 4C (Initiatives › Timeline) of pm-os-absorb Run 4. Its suites run in the lists named here, never in test-files.json.",
3
+ "modules": [
4
+ "routes-timeline.test.js"
5
+ ],
6
+ "seconds": {
7
+ "routes-timeline.test.js": 12,
8
+ "pm-initiatives-timeline-test.js": 60
9
+ },
10
+ "panels": [
11
+ "pm-initiatives-timeline-test.js"
12
+ ]
13
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "_comment": "Slice 4D (the Board destination) of pm-os-absorb Run 4: the route, the CLI and the Today rows, then the destination in a desktop, a phone and a first-paint browser suite.",
3
+ "modules": ["routes-board.test.js", "board-cli.test.js", "work-check-needs.test.js"],
4
+ "panels": ["pm-board-dest-test.js", "pm-board-dest-phone-test.js", "first-paint-board.test.js"],
5
+ "seconds": {
6
+ "routes-board.test.js": 12, "board-cli.test.js": 10, "work-check-needs.test.js": 4,
7
+ "pm-board-dest-test.js": 60, "pm-board-dest-phone-test.js": 25, "first-paint-board.test.js": 20
8
+ }
9
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "_comment": "pm-os-absorb Run 5, slice run5-a (drafter and issues ledger). The scripts/ suites (issues.sh, report-lane-issues.sh) run through scripts/check-plan-ledgers.js, not here.",
3
+ "modules": ["report-draft.test.js", "issues.test.js", "report-cli.test.js"],
4
+ "panels": [],
5
+ "seconds": { "report-draft.test.js": 15, "issues.test.js": 6, "report-cli.test.js": 6 }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "_comment": "pm-os-absorb Run 5, slice run5-b (skill, generator, tracker refresh, schedule). scripts/tests/comms-reporting.test.js and gen-report-skills.js --check run from the repository root, not here.",
3
+ "modules": ["tracker-refresh.test.js"],
4
+ "panels": [],
5
+ "seconds": { "tracker-refresh.test.js": 8 }
6
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "_comment": "pm-os-absorb Run 5, slice run5-c (Reports destination and Today providers). The browser suites run only through runs/browser-run.sh, two at a time machine-wide.",
3
+ "modules": ["report-needs.test.js", "routes-reports.test.js"],
4
+ "panels": ["pm-reports-test.js", "pm-reports-phone-test.js", "first-paint-reports.test.js"],
5
+ "seconds": {
6
+ "report-needs.test.js": 4,
7
+ "routes-reports.test.js": 8,
8
+ "pm-reports-test.js": 70,
9
+ "pm-reports-phone-test.js": 40,
10
+ "first-paint-reports.test.js": 25
11
+ },
12
+ "helpers": {
13
+ "pm-reports-stub-test.js": "not a suite — the helpers pm-reports-test.js, pm-reports-phone-test.js and first-paint-reports.test.js require (the synthetic reports, the schedules store, and the POST routes answered while slice 5A's writers are stubs)"
14
+ }
15
+ }
@@ -83,6 +83,11 @@ function configure({ name = 'run', port = null } = {}) {
83
83
  delete process.env.PMOS_STATE_DIR;
84
84
  delete process.env.PMOS_WS_PORT;
85
85
  delete process.env.PMOS_TMUX_SOCK;
86
+ // Likewise PMOS_SESSION and PMOS_BIN: a suite run from inside an agent session holds that session's token, and every
87
+ // `pmos item` the suite runs (and every server it boots) would otherwise be attributed to it. A suite that needs a
88
+ // token sets one explicitly.
89
+ delete process.env.PMOS_SESSION;
90
+ delete process.env.PMOS_BIN;
86
91
  const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), `pmos-state-${name}-`));
87
92
  // Remove the scratch tree when the suite exits, green or red — nothing did, and every run left
88
93
  // a pmos-state-* directory (scrollback, registry JSON) in tmpdir. Registered beside the mkdtemp
@@ -112,6 +117,9 @@ function configure({ name = 'run', port = null } = {}) {
112
117
  port: claimEphemeralPort,
113
118
  /** Poll a spawned server until it answers — see waitForServer above. */
114
119
  waitForServer,
120
+ /** The per-launch nonce and the press headers (below); `T.nonce(T.BASE)`, `T.pressHeaders(T.BASE)`. */
121
+ nonce: (b) => nonce(b),
122
+ pressHeaders: (b) => pressHeaders(b),
115
123
  /** Kill the tmux daemon this run minted, with every pane on it. Safe by construction: the
116
124
  * socket is private and per-pid, so this can never reach the PM's own ('pmos'). Every suite
117
125
  * that spawns the server must call this on its way out — SIGKILLing the server child leaves
@@ -123,4 +131,22 @@ function configure({ name = 'run', port = null } = {}) {
123
131
  return state;
124
132
  }
125
133
 
126
- module.exports = { configure, claimEphemeralPort, waitForServer, HOST };
134
+ /**
135
+ * The per-launch nonce of a running server (pm-os-absorb R25; lib/pm-guard.js has the wire contract). Read from the
136
+ * `<meta name="pmos-nonce" content="…">` of GET /. Answers '' when the server mints none (the base, before Run 2's 2A).
137
+ */
138
+ async function nonce(base) {
139
+ try {
140
+ const html = await (await fetch(base + '/')).text();
141
+ const m = /<meta name="pmos-nonce" content="([^"]*)"/.exec(html);
142
+ return m ? m[1] : '';
143
+ } catch { return ''; }
144
+ }
145
+
146
+ /** The headers a page press carries. Every D86 route test POSTs with these, never with a bare Origin. */
147
+ async function pressHeaders(base) {
148
+ const n = await nonce(base);
149
+ return { 'Content-Type': 'application/json', Origin: base, ...(n ? { 'x-pmos-nonce': n } : {}) };
150
+ }
151
+
152
+ module.exports = { configure, claimEphemeralPort, waitForServer, HOST, nonce, pressHeaders };
@@ -0,0 +1,44 @@
1
+ ---
2
+ type: Reference
3
+ title: "Third-party files in the workspace"
4
+ description: "The two browser files the workspace vendors under D86 (SortableJS and a lucide icon sprite): where each came from, its licence, the tarball hash and how to regenerate it."
5
+ timestamp: 2026-10-05
6
+ ---
7
+
8
+ # Third-party files in the workspace
9
+
10
+ The workspace runs with no network (D62). Two third-party browser files are committed into `public/vendor/` under D86, points 1 and 6 (the ADR is `okf/products/pmos/adr/d86-planning-layer.md`). They are not fetched from a CDN, the server makes no request for them at run time, and `package.json` has no dependency entry for them. The server serves each one from disk, and `no-network.test.js` names each one in its vendored inventory.
11
+
12
+ | Package | Version | Licence | Tarball | Tarball sha256 | Shipped file |
13
+ |---|---|---|---|---|---|
14
+ | sortablejs | 1.15.7 | MIT | https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.7.tgz | `66cbfc8fa18860783584fe0c346baf6cbf3399da72d23d21053425ba232abed4` | `public/vendor/Sortable.min.js`: an unmodified copy of `package/Sortable.min.js` (45 478 bytes, sha256 `bf4241bc73fef7f11c59a283a69fe8051cdd31c6d8ff5a2b9ba219e7831fcf76`; npm integrity `sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==`) |
15
+ | lucide-static | 1.52.0 | ISC, plus the Feather MIT notice | https://registry.npmjs.org/lucide-static/-/lucide-static-1.52.0.tgz | `4d2aa079171e4eba4c704844b8b0c9edb6e4cfb2fb8bd86f7b14353994f04a87` | `public/vendor/lucide-sprite.js`: generated by `build-lucide-sprite.js` (11 930 bytes, sha256 `692b332b79456b0fa8a47ef813a2ce54d6f5f47d00e41e85277d6ecd39e6a38b`). It holds 29 icons: grip-vertical, plus, check, square, square-check-big, x, undo-2, flag, bot, chevron-right, chevron-down, ellipsis, circle-alert, history, arrow-up, arrow-down, diamond, then the eight the frame's chrome draws (Run 3): settings, search, refresh-cw, chevron-left, sun, moon, arrow-left, arrow-right, then globe, columns-2, arrow-up-down and arrow-left-right for the dock strip's web, split and orientation chips (Run 3, C6) |
16
+
17
+ The licences are in this folder: [sortablejs-1.15.7.LICENSE](sortablejs-1.15.7.LICENSE) and [lucide-static-1.52.0.LICENSE](lucide-static-1.52.0.LICENSE). The sprite also carries the lucide licence text as its first comment.
18
+
19
+ ## Why these builds
20
+
21
+ - SortableJS: `Sortable.min.js` is the UMD build. It sets `window.Sortable` when the page loads it as a classic script, which `pm/bd-drag.js` does on first use. The ES module build (`modular/sortable.esm.js`) is not used: its comments name three `https://` addresses, and `no-network.test.js` fails any front-end file that names a remote origin. The minified build's only address is its `git://` header.
22
+ - lucide: the package holds about 1 900 icons. The sprite holds only the icons the workspace uses, as one `<symbol>` each, so the page loads one small module and no icon font.
23
+
24
+ ## Regenerate
25
+
26
+ Run the first block in a scratch folder outside the repository (`$SRC` below), then run the second block from the repository root.
27
+
28
+ ```sh
29
+ cd "$SRC"
30
+ npm pack sortablejs@1.15.7
31
+ npm pack lucide-static@1.52.0
32
+ sha256sum sortablejs-1.15.7.tgz lucide-static-1.52.0.tgz
33
+ mkdir -p sortable lucide
34
+ tar -xzf sortablejs-1.15.7.tgz -C sortable
35
+ tar -xzf lucide-static-1.52.0.tgz -C lucide
36
+ ```
37
+
38
+ ```sh
39
+ cp "$SRC/sortable/package/Sortable.min.js" workspace/public/vendor/Sortable.min.js
40
+ node workspace/third-party/build-lucide-sprite.js "$SRC/lucide/package/icons" > workspace/public/vendor/lucide-sprite.js
41
+ sha256sum workspace/public/vendor/Sortable.min.js workspace/public/vendor/lucide-sprite.js
42
+ ```
43
+
44
+ Compare each tarball hash with the table before you copy a file. When a hash changes, update the table in the same commit. To add an icon, add its name to `ICONS` in `build-lucide-sprite.js`, regenerate the sprite and record its new hash.