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.
- package/package.json +1 -1
- package/payload/KIT-VERSION +2 -2
- package/payload/mods/pmos-cockpit/README.md +13 -0
- package/payload/mods/pmos-cockpit/hooks/lib/commands.mjs +2 -1
- package/payload/mods/pmos-cockpit/hooks/lib/grants.mjs +41 -11
- package/payload/mods/pmos-cockpit/hooks/lib/guard.mjs +228 -8
- package/payload/mods/pmos-cockpit/hooks/register.tsx +11 -3
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/scripts/plan-items-stage.js +271 -0
- package/payload/scripts/pmos-log.sh +18 -7
- package/payload/scripts/workflow-state.js +28 -2
- package/payload/state/discoveries.md +1 -1
- package/payload/state/log.md +1 -1
- package/workspace/bin/pmos.js +15 -1
- package/workspace/cut-stub.js +40 -0
- package/workspace/first-paint-audit.js +145 -0
- package/workspace/fixtures/capture/requirements/capture-2026-10-04-1.md +10 -0
- package/workspace/fixtures/initiatives-api/list.json +176 -0
- package/workspace/fixtures/initiatives-api/post-403.json +5 -0
- package/workspace/fixtures/initiatives-api/post-ok.json +10 -0
- package/workspace/fixtures/initiatives-api/roadmap.json +208 -0
- package/workspace/fixtures/initiatives-api/sheet.json +112 -0
- package/workspace/fixtures/report-api/draft.json +177 -0
- package/workspace/fixtures/reports-page/draft-biweekly-2026-10-16.md +97 -0
- package/workspace/fixtures/today-api/provider-rows.json +204 -0
- package/workspace/fixtures/today-api/seam-probe-kinds.json +5 -0
- package/workspace/guide/board.md +81 -0
- package/workspace/guide/breakdown.md +154 -0
- package/workspace/guide/command-palette.md +75 -0
- package/workspace/guide/index.md +27 -17
- package/workspace/guide/initiatives-roadmap.md +130 -0
- package/workspace/guide/initiatives-screen.md +89 -0
- package/workspace/guide/initiatives-timeline.md +57 -0
- package/workspace/guide/integrated-work.md +94 -0
- package/workspace/guide/knowledge-graph.md +3 -68
- package/workspace/guide/layout.md +98 -32
- package/workspace/guide/live-notes.md +3 -3
- package/workspace/guide/pm-only-acts.md +89 -0
- package/workspace/guide/pm-only-lines.md +61 -0
- package/workspace/guide/reports-page.md +80 -0
- package/workspace/guide/reports.md +73 -0
- package/workspace/guide/timeline.md +70 -0
- package/workspace/guide/today.md +85 -0
- package/workspace/guide/validate-with-typesafe.md +1 -1
- package/workspace/lib/actor.js +91 -0
- package/workspace/lib/board-cache.js +32 -2
- package/workspace/lib/board-cli.js +108 -0
- package/workspace/lib/board-fold.js +232 -0
- package/workspace/lib/breakdown-export.js +80 -0
- package/workspace/lib/capture.js +98 -0
- package/workspace/lib/engine/plan-items-stage.js +271 -0
- package/workspace/lib/engine/workflow-state.js +28 -2
- package/workspace/lib/factory-join.js +158 -0
- package/workspace/lib/grant-mint.js +113 -0
- package/workspace/lib/health-rules-factory.js +92 -0
- package/workspace/lib/health-rules-plan.js +164 -0
- package/workspace/lib/health.js +79 -0
- package/workspace/lib/initiative-cli.js +222 -0
- package/workspace/lib/initiative-display.js +183 -0
- package/workspace/lib/initiative-needs.js +127 -0
- package/workspace/lib/initiative-rows.js +206 -0
- package/workspace/lib/initiative-writer.js +141 -0
- package/workspace/lib/issues.js +206 -0
- package/workspace/lib/item-cli.js +195 -0
- package/workspace/lib/item-spans.js +137 -0
- package/workspace/lib/measure-cli.js +100 -0
- package/workspace/lib/measurements.js +189 -0
- package/workspace/lib/mobile-nav.js +57 -53
- package/workspace/lib/needs-providers.js +120 -0
- package/workspace/lib/placements.js +440 -0
- package/workspace/lib/plan-items.js +922 -0
- package/workspace/lib/plan-ledgers.js +85 -0
- package/workspace/lib/plan-rules.js +72 -0
- package/workspace/lib/plan-tree.js +367 -0
- package/workspace/lib/pm-guard.js +174 -0
- package/workspace/lib/pm-lines-needs.js +331 -0
- package/workspace/lib/pmos-verbs.js +66 -0
- package/workspace/lib/proactive.js +7 -4
- package/workspace/lib/report-cli.js +152 -0
- package/workspace/lib/report-draft.js +579 -0
- package/workspace/lib/report-needs.js +213 -0
- package/workspace/lib/route-modules.js +128 -0
- package/workspace/lib/routes-board.js +91 -0
- package/workspace/lib/routes-capture.js +52 -0
- package/workspace/lib/routes-initiatives.js +371 -0
- package/workspace/lib/routes-reports.js +163 -0
- package/workspace/lib/routes-timeline.js +197 -0
- package/workspace/lib/routes-today.js +32 -0
- package/workspace/lib/today-cli.js +99 -0
- package/workspace/lib/today.js +333 -0
- package/workspace/lib/triage.js +163 -0
- package/workspace/lib/verify-work-cli.js +81 -0
- package/workspace/lib/work-check-needs.js +99 -0
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +4 -5
- package/workspace/public/index.html +29 -19
- package/workspace/public/pm/app.js +299 -30
- package/workspace/public/pm/bd-board.js +113 -0
- package/workspace/public/pm/bd-drag.js +59 -0
- package/workspace/public/pm/bd-model.js +152 -0
- package/workspace/public/pm/bd-timeline.js +233 -0
- package/workspace/public/pm/bd-tree.js +244 -0
- package/workspace/public/pm/board-copy.js +57 -0
- package/workspace/public/pm/breakdown-api.js +58 -0
- package/workspace/public/pm/breakdown-copy.js +138 -0
- package/workspace/public/pm/components.css +3054 -0
- package/workspace/public/pm/cycle-review.js +125 -0
- package/workspace/public/pm/data.js +40 -187
- package/workspace/public/pm/dest-board.js +438 -0
- package/workspace/public/pm/dest-initiatives.js +134 -0
- package/workspace/public/pm/dest-reports.js +270 -0
- package/workspace/public/pm/dest-settings.js +217 -0
- package/workspace/public/pm/dest-today.js +176 -0
- package/workspace/public/pm/dialogs.js +88 -0
- package/workspace/public/pm/display-word.js +56 -0
- package/workspace/public/pm/dock.js +105 -0
- package/workspace/public/pm/fixture.js +8 -227
- package/workspace/public/pm/frame-copy.js +94 -0
- package/workspace/public/pm/handoff-box.js +82 -0
- package/workspace/public/pm/http.js +88 -0
- package/workspace/public/pm/icons.js +53 -0
- package/workspace/public/pm/initiative-list.js +218 -0
- package/workspace/public/pm/initiative-sheet.js +261 -0
- package/workspace/public/pm/initiatives-api.js +59 -0
- package/workspace/public/pm/initiatives-copy.js +215 -0
- package/workspace/public/pm/initiatives-timeline-copy.js +59 -0
- package/workspace/public/pm/initiatives-timeline.js +410 -0
- package/workspace/public/pm/initiatives-ui.js +123 -0
- package/workspace/public/pm/item-sheet.js +251 -0
- package/workspace/public/pm/keep-card.js +204 -0
- package/workspace/public/pm/lifecycle.js +7 -1
- package/workspace/public/pm/mobile.css +49 -20
- package/workspace/public/pm/mobile.js +157 -29
- package/workspace/public/pm/modes-live.js +7 -883
- package/workspace/public/pm/modes.js +32 -1024
- package/workspace/public/pm/new-initiative-sheet.js +74 -0
- package/workspace/public/pm/next-step-ui.js +3 -2
- package/workspace/public/pm/palette-copy.js +51 -0
- package/workspace/public/pm/palette.js +312 -0
- package/workspace/public/pm/panel-breakdown.js +702 -0
- package/workspace/public/pm/panel-cockpit.js +63 -295
- package/workspace/public/pm/panel-detail.js +25 -172
- package/workspace/public/pm/panel-initiatives.js +1 -19
- package/workspace/public/pm/panel-repos.js +31 -81
- package/workspace/public/pm/panel-tabs.js +197 -110
- package/workspace/public/pm/panel-trees.js +5 -2
- package/workspace/public/pm/portfolio-report.js +61 -0
- package/workspace/public/pm/primitives.js +656 -0
- package/workspace/public/pm/print.css +33 -0
- package/workspace/public/pm/provider-cards.js +136 -0
- package/workspace/public/pm/registry.js +194 -0
- package/workspace/public/pm/release-card.js +110 -0
- package/workspace/public/pm/report-view.js +309 -0
- package/workspace/public/pm/reports-copy.js +114 -0
- package/workspace/public/pm/roadmap.js +271 -0
- package/workspace/public/pm/sig-watch.js +41 -0
- package/workspace/public/pm/state.js +8 -4
- package/workspace/public/pm/status-banner.js +13 -5
- package/workspace/public/pm/tab-doc.js +2 -4
- package/workspace/public/pm/tab-term.js +12 -2
- package/workspace/public/pm/tab-web.js +3 -2
- package/workspace/public/pm/timeline-copy.js +48 -0
- package/workspace/public/pm/today-cards.js +244 -0
- package/workspace/public/pm/today-copy.js +97 -0
- package/workspace/public/pm/tokens.css +16 -18
- package/workspace/public/pm/topbar.js +102 -0
- package/workspace/public/pm/triage-card.js +60 -0
- package/workspace/public/pm/undo-hold.js +103 -0
- package/workspace/public/pm/view-addrepo.js +2 -1
- package/workspace/public/pm/view-livenotes.js +3 -1
- package/workspace/public/pm/view-today.js +7 -162
- package/workspace/public/vendor/Sortable.min.js +2 -0
- package/workspace/public/vendor/lucide-sprite.js +79 -0
- package/workspace/replaced-tests.json +2864 -0
- package/workspace/run-tests.js +56 -5
- package/workspace/server.js +312 -36
- package/workspace/sheet-answer.js +61 -0
- package/workspace/skills/measure.skill +41 -0
- package/workspace/skills/report-biweekly.skill +23 -25
- package/workspace/skills/tracker-refresh.skill +50 -0
- package/workspace/test-files.json +19 -3
- package/workspace/test-lists/base.json +6 -0
- package/workspace/test-lists/run2-a.json +6 -0
- package/workspace/test-lists/run2-b.json +13 -0
- package/workspace/test-lists/run2-c.json +19 -0
- package/workspace/test-lists/run2-d.json +22 -0
- package/workspace/test-lists/run3-0.json +5 -0
- package/workspace/test-lists/run3-a.json +5 -0
- package/workspace/test-lists/run3-b.json +6 -0
- package/workspace/test-lists/run3-c.json +15 -0
- package/workspace/test-lists/run3-d.json +6 -0
- package/workspace/test-lists/run4-a.json +8 -0
- package/workspace/test-lists/run4-b.json +6 -0
- package/workspace/test-lists/run4-c.json +13 -0
- package/workspace/test-lists/run4-d.json +9 -0
- package/workspace/test-lists/run5-a.json +6 -0
- package/workspace/test-lists/run5-b.json +6 -0
- package/workspace/test-lists/run5-c.json +15 -0
- package/workspace/test-run.js +27 -1
- package/workspace/third-party/README.md +44 -0
- package/workspace/third-party/lucide-static-1.52.0.LICENSE +43 -0
- package/workspace/third-party/sortablejs-1.15.7.LICENSE +21 -0
- package/workspace/design-parity.js +0 -673
- package/workspace/public/pm/tab-task.js +0 -86
- package/workspace/public/pm/view-guide.js +0 -141
- package/workspace/public/pm/view-knowledge.js +0 -225
- package/workspace/public/pm/view-memory.js +0 -122
- package/workspace/public/pm/view-portfolio.js +0 -153
- package/workspace/public/pm/view-reports.js +0 -190
- package/workspace/public/pm/view-scheduled.js +0 -161
- 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
|
|
21
|
-
* kind '
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
|
|
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] :
|
|
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) || '
|
|
110
|
-
return { dest: (s && s.dest) || '
|
|
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
|
|
116
|
-
|
|
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. `
|
|
119
|
-
*
|
|
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 : '
|
|
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('
|
|
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
|
|
134
|
-
// unwinds into another
|
|
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('
|
|
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
|
|
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('
|
|
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 —
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
function mobMigrateView(v) {
|
|
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 };
|