create-pmos 0.52.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 +4 -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 +56 -5
  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 +19 -3
  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
@@ -17,13 +17,14 @@
17
17
  *
18
18
  * The screen model:
19
19
  * { dest, kind, mode, id, dk, title, column }
20
- * dest one of DESTS — which destination this screen belongs to
21
- * kind 'list' | 'detail' | 'terminal' | 'doc' | 'more'
20
+ * dest one of DESTS — which tab this screen belongs to
21
+ * kind 'dest' | 'agent' (the frame) · 'list' | 'more' (the old shell) · 'detail' | 'terminal' | 'doc'
22
22
  * mode a MODES key, for kind 'list'
23
- * id the detail id / session id / viewer id
23
+ * id the frame destination (kind 'dest') / detail id / session id / viewer id
24
24
  * dk the DETAIL kind, for kind 'detail'
25
25
  * title only for 'terminal' and 'doc', where the name is not derivable
26
- * column 'side' | 'term' | 'rail' — determined by `kind` and by nothing else
26
+ * column 'side' | 'term' | 'rail' — determined by `kind` and by nothing else (panelOf is the same for the four
27
+ * panel grid)
27
28
  *
28
29
  * `column` is the whole of the exclusivity guarantee: the stylesheet shows exactly one of the three
29
30
  * column nodes per value, so P1 is a property of the CSS rather than something this reducer has to
@@ -65,87 +66,90 @@ const DETAIL_TITLE = {
65
66
  addprod: 'add repository', new: 'file new',
66
67
  };
67
68
 
68
- const MOBILE_DESTS = ['board', 'scheduled', 'agents', 'memory', 'more'];
69
- // Every MODES key has an entry. The suite ITERATES `MODES` against this map rather than a literal
70
- // list, so adding a mode without giving it a phone path fails the suite instead of going silently
71
- // unreachable — which is the failure P2 exists to catch.
72
- const MOBILE_DEST = { product: 'board', scheduled: 'scheduled', agent: 'agents', memory: 'memory',
73
- workflow: 'more', skills: 'more', knowledge: 'more' };
69
+ /* ── the frame's five phone tabs (pm-os-absorb Run 3, slice 3A; PRD R15) ─────────────────────────────────────────
70
+ * Today · Initiatives · Board · Reports · Agent. The first four are the frame's destinations (a `dest` screen, drawn
71
+ * in the frame's place, panel index 0); Agent is the dock (an `agent` screen, panel 4, the live terminals). Settings
72
+ * is not a tab: it is pushed on top of the current tab as a `dest` screen with id `settings`, so Back returns.
73
+ * The old cockpit's list modes still map to a tab (MOBILE_DEST), for the old shell (fixture mode, ?layout=legacy). */
74
+ const MOBILE_DESTS = ['today', 'initiatives', 'board', 'reports', 'agent'];
75
+ // What each tab and Settings is called. The same words as the desktop bar (public/pm/frame-copy.js COPY.nav), which
76
+ // MOBNAV-P7 holds equal.
77
+ const DEST_TITLE = { today: 'Today', initiatives: 'Initiatives', board: 'Board', reports: 'Reports', agent: 'Agent', settings: 'Settings' };
78
+ // Every old MODES key has a tab. The suite ITERATES this map, so a mode without a phone path fails.
79
+ const MOBILE_DEST = { product: 'initiatives', scheduled: 'reports', agent: 'agent', memory: 'initiatives',
80
+ workflow: 'initiatives', skills: 'initiatives', knowledge: 'initiatives' };
74
81
  const MOBILE_ROOT = {
75
- board: { kind: 'list', mode: 'product' },
76
- scheduled: { kind: 'list', mode: 'scheduled' },
77
- agents: { kind: 'list', mode: 'agent' },
78
- memory: { kind: 'list', mode: 'memory' },
79
- more: { kind: 'more', mode: null },
82
+ today: { kind: 'dest', id: 'today' },
83
+ initiatives: { kind: 'dest', id: 'initiatives' },
84
+ board: { kind: 'dest', id: 'board' },
85
+ reports: { kind: 'dest', id: 'reports' },
86
+ agent: { kind: 'agent', id: null },
80
87
  };
88
+ // The tab every stored value of the retired five-tab nav (board · scheduled · agents · memory · more) and of the
89
+ // three-way switcher before it lands on. Read once from the old key; a new value is never passed through it.
90
+ const LEGACY_DEST = { board: 'initiatives', scheduled: 'reports', agents: 'agent', memory: 'today', more: 'today',
91
+ sessions: 'agent', terminal: 'agent', library: 'today' };
81
92
  // kind → column. There is no second input, and no screen may carry a column its kind does not give.
82
- const MOBILE_COLUMN = { list: 'side', more: 'side', detail: 'rail', terminal: 'term', doc: 'term' };
93
+ const MOBILE_COLUMN = { list: 'side', more: 'side', dest: 'side', detail: 'rail', terminal: 'term', doc: 'term', agent: 'term' };
83
94
  function mobColumnOf(kind) { return MOBILE_COLUMN[kind] || 'side'; }
84
95
 
85
- /* kind → PANEL INDEX, for the four-panel surface (public/pm/).
86
- *
87
- * WHY THIS EXISTS ALONGSIDE `column`, RATHER THAN REPLACING IT. `column` names one of the three
88
- * columns of the retired `public/app.js` layout, and MOBNAV-P1 pins it to exactly those three
89
- * values. The surface that ships now has FOUR panels — repositories · mode list · detail · tabs —
90
- * and two of the old `side` screens belong to DIFFERENT panels there: a `list` screen is panel 2
91
- * (the mode list) while a `more` screen is panel 1, which is the structure/settings panel and is
92
- * therefore the phone's "more" destination in the four-panel design.
93
- *
94
- * So this is an ADDITIVE widening, not an edit: `columnOf` is untouched and still describes the
95
- * three-column layout truthfully for the surface that owns it, and `panelOf` describes the
96
- * four-panel one. Both are pure functions of `kind` alone, so the exclusivity guarantee — one
97
- * visible surface per screen — is the same property stated against a different layout. Putting it
98
- * HERE rather than in pm/mobile.js keeps a single node-walkable source of truth for "which surface
99
- * does this screen show", which is the whole reason the reducer was pulled out of app.js.
100
- */
101
- const MOBILE_PANEL = { more: 0, list: 1, detail: 2, terminal: 3, doc: 3 };
96
+ /* kind → PANEL INDEX: which child of the body grid a phone shows (pm/mobile.css `data-mpanel`). In the frame the body
97
+ * grid holds the place, the dock's rail (never shown on a phone), the detail and the dock, in that order; in the old
98
+ * shell it holds its four panels. `dest` is the frame's place, `agent` the dock. A pure function of `kind` alone, so
99
+ * one visible surface per screen is a property of the CSS. */
100
+ const MOBILE_PANEL = { dest: 0, more: 0, list: 1, detail: 2, terminal: 3, doc: 3, agent: 3 };
102
101
  function mobPanelOf(kind) {
103
- return Object.prototype.hasOwnProperty.call(MOBILE_PANEL, kind) ? MOBILE_PANEL[kind] : 1;
102
+ return Object.prototype.hasOwnProperty.call(MOBILE_PANEL, kind) ? MOBILE_PANEL[kind] : 0;
104
103
  }
105
104
  function mobDestForMode(k) { return MOBILE_DEST[k] || null; }
106
105
  // Complete a partial screen. The column is derived, never passed in, so a caller cannot put a
107
106
  // detail on the terminal column by writing it down.
108
107
  function mobScreen(s) {
109
- const kind = (s && s.kind) || 'list';
110
- return { dest: (s && s.dest) || 'board', kind, mode: (s && s.mode) || null,
108
+ const kind = (s && s.kind) || 'dest';
109
+ return { dest: (s && s.dest) || 'today', kind, mode: (s && s.mode) || null,
111
110
  id: (s && s.id) || null, dk: (s && s.dk) || null, title: (s && s.title) || null,
112
111
  column: mobColumnOf(kind) };
113
112
  }
114
113
  function mobRootOf(dest) {
115
- const r = MOBILE_ROOT[dest] || MOBILE_ROOT.board;
116
- return mobScreen({ dest: MOBILE_ROOT[dest] ? dest : 'board', kind: r.kind, mode: r.mode });
114
+ const d = MOBILE_ROOT[dest] ? dest : 'today';
115
+ const r = MOBILE_ROOT[d];
116
+ return mobScreen({ dest: d, kind: r.kind, id: r.id });
117
117
  }
118
- /* The title map. `list` and `more` derive; `detail` reads DETAIL_TITLE (declared far below — this
119
- * is only ever called at runtime, long after that const has evaluated); `terminal` and `doc` carry
120
- * a title because a session's name and a document's name are not derivable from a screen. */
118
+ /* The title map. `dest` and `agent` read DEST_TITLE; `list` and `more` derive (the old shell); `detail` reads
119
+ * DETAIL_TITLE; `terminal` and `doc` carry a title because a session's name and a document's name are not derivable. */
121
120
  function mobTitleOf(s) {
122
121
  if (!s) return '';
122
+ if (s.kind === 'dest') return DEST_TITLE[s.id] || DEST_TITLE[s.dest] || '';
123
+ if (s.kind === 'agent') return DEST_TITLE.agent;
123
124
  if (s.kind === 'list') return MODE_TITLE[s.mode] || String(s.mode || '');
124
125
  if (s.kind === 'more') return 'more';
125
126
  if (s.kind === 'detail') return DETAIL_TITLE[s.dk] || 'detail';
126
127
  return s.title || String(s.id || '');
127
128
  }
128
- function mobNavInit(dest) { return { dest: MOBILE_ROOT[dest] ? dest : 'board', stack: [] }; }
129
+ function mobNavInit(dest) { return { dest: MOBILE_ROOT[dest] ? dest : 'today', stack: [] }; }
129
130
  function mobPush(nav, screen) {
130
- const n = nav && MOBILE_ROOT[nav.dest] ? nav : mobNavInit('board');
131
+ const n = nav && MOBILE_ROOT[nav.dest] ? nav : mobNavInit('today');
131
132
  return { dest: n.dest, stack: n.stack.concat([mobScreen(Object.assign({ dest: n.dest }, screen))]) };
132
133
  }
133
- // Removes EXACTLY one. Back from a destination root is a no-op — it never exits the app and never
134
- // unwinds into another destination, which is the second half of P3.
134
+ // Removes EXACTLY one. Back from a tab's root is a no-op — it never exits the app and never
135
+ // unwinds into another tab.
135
136
  function mobPop(nav) {
136
- const n = nav && MOBILE_ROOT[nav.dest] ? nav : mobNavInit('board');
137
+ const n = nav && MOBILE_ROOT[nav.dest] ? nav : mobNavInit('today');
137
138
  return n.stack.length ? { dest: n.dest, stack: n.stack.slice(0, -1) } : { dest: n.dest, stack: [] };
138
139
  }
139
- // Choosing a destination RESETS it to its root with an empty stack — the prototype's {tab, stack:[]}.
140
+ // Choosing a tab RESETS it to its root with an empty stack.
140
141
  function mobSwitchDest(nav, dest) { return mobNavInit(dest); }
141
142
  function mobCurrentOf(nav) {
142
- const n = nav && MOBILE_ROOT[nav.dest] ? nav : mobNavInit('board');
143
+ const n = nav && MOBILE_ROOT[nav.dest] ? nav : mobNavInit('today');
143
144
  return n.stack.length ? n.stack[n.stack.length - 1] : mobRootOf(n.dest);
144
145
  }
145
- /* R11 — the legacy key is read ONCE, for migration, and never written again. Every value the old
146
- * three-way switcher could hold, plus anything unrecognised and nothing at all, lands on `board`:
147
- * the destination root is the only screen that is safe to draw without re-reading anything. */
148
- function mobMigrateView(v) { return MOBILE_ROOT[v] ? v : 'board'; }
146
+ /* R11 — a value stored by an older nav is read ONCE, for migration. Every value the retired navs could hold, plus
147
+ * anything unrecognised and nothing at all, lands on a tab's root: the only screen that is safe to draw without
148
+ * re-reading anything. */
149
+ function mobMigrateView(v) {
150
+ if (typeof v === 'string' && Object.prototype.hasOwnProperty.call(LEGACY_DEST, v)) return LEGACY_DEST[v];
151
+ return MOBILE_ROOT[v] ? v : 'today';
152
+ }
149
153
 
150
154
  return { push: mobPush, pop: mobPop, switchDest: mobSwitchDest, init: mobNavInit,
151
155
  current: mobCurrentOf, titleOf: mobTitleOf, columnOf: mobColumnOf, panelOf: mobPanelOf,
@@ -153,5 +157,5 @@ function mobMigrateView(v) { return MOBILE_ROOT[v] ? v : 'board'; }
153
157
  MODE_TITLE,
154
158
  migrateView: mobMigrateView, screen: mobScreen, root: mobRootOf,
155
159
  DESTS: MOBILE_DESTS, DEST_FOR_MODE: MOBILE_DEST, ROOTS: MOBILE_ROOT,
156
- PANEL_OF_KIND: MOBILE_PANEL, DETAIL_TITLE };
160
+ PANEL_OF_KIND: MOBILE_PANEL, DETAIL_TITLE, DEST_TITLE, LEGACY_DEST };
157
161
  });
@@ -0,0 +1,120 @@
1
+ 'use strict';
2
+ /* needs-providers.js — the planning layer's needs-you rows, one file per run, folded into server.js deriveNeeds
3
+ * (pm-os-absorb Runs 2 to 5, PRD R12).
4
+ *
5
+ * rowsFor(w, { lead }) → rows every provider's rows for workspace `w`, each tagged { derived: true, ws, provider }
6
+ * sigFor(w) → string one signature over every provider (server.js needsSig), so a count is never stale
7
+ * isProviderRow(n) → boolean server.js keeps these rows out of derived state, next steps and the portfolio,
8
+ * as it keeps Run 1's plan rows out (notPlanNeed)
9
+ * PROVIDERS · RESERVED_KINDS · status()
10
+ *
11
+ * Four runs add needs rows (kind 8 "needs repair", kind 7 cycle rows, kind 4 work to check, kind 1 report to issue).
12
+ * Each adds ONE file, named in PROVIDERS before it exists, instead of a block in deriveNeeds. A PROVIDER exports:
13
+ * module.exports = {
14
+ * kinds: ['pm-line'], // the row kinds it makes; unique across providers
15
+ * rowsFor(w, { lead }) → [{ run_id, kind, initiative_id, question, doc?, today? }],
16
+ * sig(w) → string, // optional: changes whenever its rows may change
17
+ * };
18
+ * `w` is a registered workspace ({ id, root, … }). `lead` is true for the one registered tree that speaks for a
19
+ * repository's ledgers (server.js isPlanNeedsTree): a provider of repository-wide rows returns [] when it is false, or
20
+ * a repository with a linked worktree registered counts every row twice. `run_id` is unique and stable per row and
21
+ * starts with the provider's name: the needs queue de-duplicates by it.
22
+ * `today` (optional) is what a Today card needs, passed through /api/needs unchanged:
23
+ * { kind: 1..8, source: '<provider>', cause, evidence, act: { label, cli? } }
24
+ * A row the page receives never carries a session token: name an agent by its page name (`agent:sess-<n>`).
25
+ *
26
+ * A provider file that is not on disk yet is skipped. One that fails to load, claims a kind another uses, or throws
27
+ * gives ONE row of kind 'provider-error' that names it: a broken source is never an empty list (R10).
28
+ */
29
+ const path = require('path');
30
+
31
+ const PROVIDERS = Object.freeze([
32
+ 'pm-lines-needs', // Run 2 (2B): a PM-only line that is not attested (kind 8)
33
+ 'initiative-needs', // Run 2 (2C): day one, agent proposals, passed check-by (kind 7); a record with no anchor (kind 8)
34
+ 'work-check-needs', // Run 4 (4D): a person's item in Review, a batched Release (kind 4)
35
+ 'report-needs', // Run 5 (5C): a report to issue (kind 1), cycle rows (kind 7), a schedule left no output (kind 8)
36
+ ]);
37
+ /** Kinds that already mean something in the needs queue. A provider may not reuse one. */
38
+ const RESERVED_KINDS = Object.freeze(['question', 'accept', 'outcome', 'keep', 'evidence', 'plan-flag', 'provider-error']);
39
+
40
+ let table = null;
41
+ function load() {
42
+ if (table) return table;
43
+ const mods = [];
44
+ const broken = [];
45
+ const owner = new Map(RESERVED_KINDS.map((k) => [k, '(reserved)']));
46
+ for (const name of PROVIDERS) {
47
+ const full = path.join(__dirname, name + '.js');
48
+ let mod;
49
+ try { mod = require(full); }
50
+ catch (e) {
51
+ if (e && e.code === 'MODULE_NOT_FOUND' && String(e.message).includes(full)) continue; // not built yet
52
+ broken.push({ name, error: `failed to load: ${String((e && e.message) || e).slice(0, 160)}` });
53
+ continue;
54
+ }
55
+ const kinds = mod && Array.isArray(mod.kinds) ? mod.kinds : null;
56
+ if (!kinds || !kinds.length || typeof mod.rowsFor !== 'function') { broken.push({ name, error: 'must export kinds and rowsFor' }); continue; }
57
+ const clash = kinds.find((k) => typeof k !== 'string' || owner.has(k));
58
+ if (clash !== undefined) { broken.push({ name, error: `kind ${JSON.stringify(clash)} is already used by ${owner.get(clash) || 'nobody (not a string)'}` }); continue; }
59
+ for (const k of kinds) owner.set(k, name);
60
+ mods.push({ name, mod, kinds });
61
+ }
62
+ table = { mods, broken };
63
+ return table;
64
+ }
65
+
66
+ const errorRow = (w, name, error) => ({
67
+ run_id: `${name}:error`, kind: 'provider-error', initiative_id: null, derived: true, doc: null, ws: w.id, provider: name,
68
+ question: `The needs source ${name} could not be read: ${error}`,
69
+ });
70
+
71
+ function rowsFor(w, { lead = true } = {}) {
72
+ if (!w) return [];
73
+ const { mods, broken } = load();
74
+ const out = broken.map((b) => errorRow(w, b.name, b.error));
75
+ for (const { name, mod, kinds } of mods) {
76
+ let rows;
77
+ try { rows = mod.rowsFor(w, { lead: !!lead }); }
78
+ catch (e) { out.push(errorRow(w, name, String((e && e.message) || e).slice(0, 160))); continue; }
79
+ for (const r of Array.isArray(rows) ? rows : []) {
80
+ if (!r || typeof r.run_id !== 'string' || !kinds.includes(r.kind)) { out.push(errorRow(w, name, 'a row has no run_id or a kind it did not declare')); break; }
81
+ out.push({ ...r, derived: true, ws: w.id, provider: name });
82
+ }
83
+ }
84
+ return out;
85
+ }
86
+
87
+ /** Each provider's state-health lines (optional `banner(w, { lead })` → [{ source, error }]): a source that is still being
88
+ * read, not a row. Today shows them in its banner. */
89
+ function bannersFor(w, { lead = true } = {}) {
90
+ if (!w) return [];
91
+ const out = [];
92
+ for (const { name, mod } of load().mods) {
93
+ if (typeof mod.banner !== 'function') continue;
94
+ try { for (const b of mod.banner(w, { lead: !!lead }) || []) if (b && b.error) out.push({ source: String(b.source || name), error: String(b.error) }); }
95
+ catch { /* a banner that fails says nothing; its rows still report errors */ }
96
+ }
97
+ return out;
98
+ }
99
+
100
+ function sigFor(w) {
101
+ if (!w) return '';
102
+ const { mods, broken } = load();
103
+ const parts = [`broken:${broken.length}`];
104
+ for (const { name, mod } of mods) {
105
+ if (typeof mod.sig !== 'function') { parts.push(`${name}:-`); continue; }
106
+ let s;
107
+ try { s = String(mod.sig(w)); } catch { s = 'unreadable'; }
108
+ parts.push(`${name}:${s}`);
109
+ }
110
+ return parts.join(',');
111
+ }
112
+
113
+ const isProviderRow = (n) => !!n && n.derived === true && typeof n.provider === 'string';
114
+
115
+ function status() {
116
+ const t = load();
117
+ return { loaded: t.mods.map((m) => ({ name: m.name, kinds: m.kinds.slice() })), broken: t.broken.map((b) => ({ ...b })) };
118
+ }
119
+
120
+ module.exports = { PROVIDERS, RESERVED_KINDS, rowsFor, bannersFor, sigFor, isProviderRow, status };