create-pmos 0.52.0 → 0.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/README.md +2 -0
- package/workspace/REMOTE.md +24 -63
- package/workspace/bin/pmos.js +19 -3
- 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 +28 -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/launcher.md +51 -0
- package/workspace/guide/layout.md +102 -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/proactive-engine.md +21 -5
- 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 +75 -0
- package/workspace/lib/brief.js +20 -2
- 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/goals.js +165 -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 +210 -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/launcher-cli.js +207 -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 +380 -0
- package/workspace/lib/pm-guard.js +174 -0
- package/workspace/lib/pm-lines-needs.js +331 -0
- package/workspace/lib/pmos-verbs.js +69 -0
- package/workspace/lib/proactive-cli.js +58 -2
- package/workspace/lib/proactive.js +9 -6
- package/workspace/lib/qr.js +293 -0
- package/workspace/lib/remote-access.js +93 -1
- package/workspace/lib/report-cli.js +152 -0
- package/workspace/lib/report-draft.js +578 -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/icons/apple-touch-icon.png +0 -0
- package/workspace/public/icons/icon-192.png +0 -0
- package/workspace/public/icons/icon-512.png +0 -0
- package/workspace/public/index.html +40 -19
- package/workspace/public/pm/app.js +330 -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 +3135 -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 +182 -0
- package/workspace/public/pm/dialogs.js +88 -0
- package/workspace/public/pm/display-word.js +56 -0
- package/workspace/public/pm/dock.js +175 -0
- package/workspace/public/pm/fixture.js +8 -227
- package/workspace/public/pm/frame-copy.js +99 -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 +200 -111
- 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 +126 -0
- package/workspace/public/pm/today-goals.js +172 -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 +2877 -0
- package/workspace/run-tests.js +56 -5
- package/workspace/server.js +446 -40
- package/workspace/sheet-answer.js +61 -0
- package/workspace/skills/measure.skill +41 -0
- package/workspace/skills/morning-brief.skill +15 -9
- package/workspace/skills/report-biweekly.skill +23 -25
- package/workspace/skills/tracker-refresh.skill +50 -0
- package/workspace/test-files.json +21 -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-lists/workspace-launcher.json +5 -0
- package/workspace/test-lists/workspace-phone-app.json +5 -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
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/* pm/cycle-review.js — the Cycle review card of Today, kind 7 (pm-os-absorb Run 2, slice 2D; PRD R12, R26, R27).
|
|
2
|
+
*
|
|
3
|
+
* ONE renderer for two row kinds. The server's initiative provider makes `cycle-review` (day one, or a new cycle: what
|
|
4
|
+
* reads Now and is unconfirmed, the agents' proposals, the check-by dates that passed). The reports provider makes
|
|
5
|
+
* `cycle-kill-check` (the monthly kill-condition check). Both rows open the same sheet, so the PM reviews the cycle in one
|
|
6
|
+
* place. The kill-check row adds its own line: its cause and the file the check reads.
|
|
7
|
+
*
|
|
8
|
+
* The sheet has three lists:
|
|
9
|
+
* - Now, unconfirmed: Confirm (a placed line into Now, written by the PM's press) or Move back (to Later). One initiative
|
|
10
|
+
* for each press: Move to Now is never bulk (R17), so there is no "Confirm all";
|
|
11
|
+
* - Agent proposals: Keep (adopt: written as the PM's line) or Drop;
|
|
12
|
+
* - Check-by dates passed: Set check-by, or Move to Next.
|
|
13
|
+
* Each press is one write through initiatives-api.js; the sheet reads the Roadmap again and redraws.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { el } from './dom.js';
|
|
17
|
+
import { button, card, row, sheet, toast, ensureComponentsCss } from './primitives.js';
|
|
18
|
+
import { registerTodayCard } from './registry.js';
|
|
19
|
+
import { COPY, shortDate } from './initiatives-copy.js';
|
|
20
|
+
import { getRoadmap, place, placed, leaveNow, adopt, drop } from './initiatives-api.js';
|
|
21
|
+
import { say, checkBySheet } from './initiatives-ui.js';
|
|
22
|
+
import { changed } from './initiative-sheet.js';
|
|
23
|
+
|
|
24
|
+
const refreshToday = (ws) => { try { window.dispatchEvent(new CustomEvent('pmos:ledgers', { detail: { changed: [ws] } })); } catch { /* no window */ } };
|
|
25
|
+
|
|
26
|
+
/** What the sheet lists, from one Roadmap answer. */
|
|
27
|
+
export function cycleItems(data) {
|
|
28
|
+
const today = String((data && data.now) || '').slice(0, 10) || new Date().toISOString().slice(0, 10);
|
|
29
|
+
const now = (data && data.horizons && data.horizons.now) || [];
|
|
30
|
+
return {
|
|
31
|
+
unconfirmed: now.filter((r) => !r.confirmed),
|
|
32
|
+
proposals: (data && data.proposals) || [],
|
|
33
|
+
passed: now.filter((r) => r.check_by && r.check_by < today),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function openCycleReview({ row: needsRow, ws, repo }) {
|
|
38
|
+
ensureComponentsCss();
|
|
39
|
+
const list = el('div', { class: 'in-cycle', 'data-in-cycle': '' }, el('p', { class: 'in-note' }, COPY.cycle.reading));
|
|
40
|
+
const head = [];
|
|
41
|
+
if (needsRow && needsRow.kind === 'cycle-kill-check') {
|
|
42
|
+
const t = needsRow.today || {};
|
|
43
|
+
head.push(el('p', { class: 'in-note', 'data-in-kill': '' }, [needsRow.question, t.evidence ? ' · ' + t.evidence : ''].join('')));
|
|
44
|
+
} else if (needsRow && needsRow.today && needsRow.today.cause) head.push(el('p', { class: 'in-note' }, needsRow.today.cause));
|
|
45
|
+
const s = sheet({ mode: 'auto', title: COPY.cycle.sheetTitle, body: [...head, list], attrs: { 'data-in-sheet': 'cycle-review' }, actions: [button({ label: COPY.sheet.close, variant: 'outline', onClick: () => s.close(), attrs: { 'data-in-close': '' } })] });
|
|
46
|
+
|
|
47
|
+
let data = null;
|
|
48
|
+
const after = async (pending, text) => { const out = await pending; if (say(out, text)) { changed(); refreshToday(ws); } await load(); };
|
|
49
|
+
|
|
50
|
+
const sectionTitle = (text, hint) => [el('h3', { class: 'in-cycle__title' }, text), el('p', { class: 'in-note' }, hint)];
|
|
51
|
+
|
|
52
|
+
function draw() {
|
|
53
|
+
const { unconfirmed, proposals, passed } = cycleItems(data);
|
|
54
|
+
const kids = [];
|
|
55
|
+
if (!unconfirmed.length && !proposals.length && !passed.length) kids.push(el('p', { class: 'in-note', 'data-in-nothing': '' }, COPY.cycle.nothing));
|
|
56
|
+
if (unconfirmed.length) {
|
|
57
|
+
kids.push(...sectionTitle(COPY.cycle.confirmNow, COPY.cycle.confirmNowHint));
|
|
58
|
+
for (const r of unconfirmed) {
|
|
59
|
+
const idx = ((data.horizons.now || []).findIndex((x) => x.id === r.id));
|
|
60
|
+
const prev = idx > 0 ? data.horizons.now[idx - 1].id : null;
|
|
61
|
+
kids.push(row({
|
|
62
|
+
title: r.title, meta: [r.display && r.display.word, r.anchor], attrs: { 'data-in-cycle-row': r.id, 'data-kind': 'unconfirmed' },
|
|
63
|
+
trail: [
|
|
64
|
+
button({ label: COPY.cycle.confirm, variant: 'primary', size: 'sm', attrs: { 'data-in-confirm-one': r.id }, onClick: () => after(place(ws, [placed(r.id, 'now', prev)]), COPY.cycle.confirmed(r.title)) }),
|
|
65
|
+
button({ label: COPY.cycle.moveBack, variant: 'outline', size: 'sm', attrs: { 'data-in-moveback': r.id }, onClick: () => after(place(ws, [placed(r.id, 'later', null, leaveNow(r, 'later'))]), COPY.cycle.movedBack(r.title)) }),
|
|
66
|
+
],
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (proposals.length) {
|
|
71
|
+
kids.push(...sectionTitle(COPY.cycle.proposals, COPY.cycle.proposalsHint));
|
|
72
|
+
for (const p of proposals) {
|
|
73
|
+
kids.push(row({
|
|
74
|
+
title: p.title || p.initiative, meta: [COPY.cycle.proposalLine(COPY.horizons[p.horizon] || p.horizon, p.by)], proposed: true,
|
|
75
|
+
attrs: { 'data-in-cycle-row': p.initiative, 'data-kind': 'proposal', 'data-eid': p.eid },
|
|
76
|
+
trail: [
|
|
77
|
+
button({ label: COPY.cycle.keep, variant: 'primary', size: 'sm', attrs: { 'data-in-keep': p.eid }, onClick: () => after(adopt(ws, p.eid), COPY.cycle.kept) }),
|
|
78
|
+
button({ label: COPY.cycle.drop, variant: 'outline', size: 'sm', attrs: { 'data-in-drop': p.eid }, onClick: () => after(drop(ws, p.eid), COPY.cycle.dropped) }),
|
|
79
|
+
],
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (passed.length) {
|
|
84
|
+
kids.push(...sectionTitle(COPY.cycle.checkBys, COPY.cycle.checkBysHint));
|
|
85
|
+
for (const r of passed) {
|
|
86
|
+
const idx = (data.horizons.now || []).findIndex((x) => x.id === r.id);
|
|
87
|
+
const prev = idx > 0 ? data.horizons.now[idx - 1].id : null;
|
|
88
|
+
kids.push(row({
|
|
89
|
+
title: r.title, meta: [COPY.checkBy(shortDate(r.check_by))], attrs: { 'data-in-cycle-row': r.id, 'data-kind': 'check-by' },
|
|
90
|
+
trail: [
|
|
91
|
+
button({
|
|
92
|
+
label: COPY.cycle.setCheckBy, variant: 'outline', size: 'sm', attrs: { 'data-in-set-check-by': r.id },
|
|
93
|
+
onClick: () => checkBySheet({ title: COPY.sheet.checkByTitle, value: r.check_by, onSave: async (d) => { const out = await place(ws, [placed(r.id, 'now', prev, { check_by: d })]); if (out && out.ok) { toast({ text: COPY.moved.checkBy(r.title, d ? shortDate(d) : ''), timeout: 4000 }); changed(); refreshToday(ws); load(); } return out; } }),
|
|
94
|
+
}),
|
|
95
|
+
button({ label: COPY.cycle.moveNext, variant: 'outline', size: 'sm', attrs: { 'data-in-move-next': r.id }, onClick: () => after(place(ws, [placed(r.id, 'next', null, leaveNow(r, 'next'))]), COPY.moved.next(r.title)) }),
|
|
96
|
+
],
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
list.replaceChildren(...kids);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function load() {
|
|
104
|
+
const a = await getRoadmap(ws);
|
|
105
|
+
if (a && a.ok) { data = a; draw(); } else list.replaceChildren(el('p', { class: 'in-error', role: 'alert' }, COPY.readFailed((a && a.error) || 'the server did not answer')));
|
|
106
|
+
}
|
|
107
|
+
load();
|
|
108
|
+
return s;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The one renderer, for both row kinds. */
|
|
112
|
+
export function renderCycleCard(row_, ctx) {
|
|
113
|
+
ensureComponentsCss();
|
|
114
|
+
const t = row_.today || {};
|
|
115
|
+
const open = () => openCycleReview({ row: row_, ws: ctx.ws, repo: ctx.repo });
|
|
116
|
+
return card({
|
|
117
|
+
title: row_.question || COPY.cycle.cardKind, meta: [COPY.cycle.cardKind, t.cause, t.evidence],
|
|
118
|
+
trail: [button({ label: (t.act && t.act.label) || COPY.cycle.review, variant: 'primary', attrs: { 'data-in-cycle-open': row_.kind }, onClick: open })],
|
|
119
|
+
onOpen: open, openLabel: COPY.cycle.review + ': ' + (row_.question || ''),
|
|
120
|
+
attrs: { class: 'in-today-card', 'data-today-row': row_.kind, 'data-provider': row_.provider || null },
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
registerTodayCard('cycle-review', renderCycleCard);
|
|
125
|
+
registerTodayCard('cycle-kill-check', renderCycleCard);
|
|
@@ -25,16 +25,16 @@
|
|
|
25
25
|
* never a plausible-looking guess. `— not tracked` is the surface's job, not this file's.
|
|
26
26
|
*
|
|
27
27
|
* ── What is derived here, and by what rule ──────────────────────────────────────────────────
|
|
28
|
-
* PMOS has no task entity
|
|
28
|
+
* PMOS has no task entity. The phases below are folded from the workflow ladder; the tasks they once carried (derived
|
|
29
|
+
* from the rungs) are cut with the Task tab (R29), and the work an initiative holds is the Breakdown's:
|
|
29
30
|
* · the phase SET comes from the initiative's lane;
|
|
30
31
|
* · each phase folds a fixed set of the workflow engine's twelve rungs, so every rung's evidence
|
|
31
32
|
* lands in exactly one phase and none is dropped;
|
|
32
|
-
* · a
|
|
33
|
-
*
|
|
34
|
-
* evidence at all reads `
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* engine's own text for the rung it belongs to.
|
|
33
|
+
* · a phase's status comes from its rungs — `done` where they passed, `blocked` where one is
|
|
34
|
+
* blocking, `current` where one is current, else `pending`. A phase whose rungs carry no
|
|
35
|
+
* evidence at all reads `pending`, NEVER `done`.
|
|
36
|
+
* Nothing is invented per initiative: every phase's text is the engine's own text for the rung it
|
|
37
|
+
* belongs to.
|
|
38
38
|
*
|
|
39
39
|
* ── The one thing this file refuses to do ───────────────────────────────────────────────────
|
|
40
40
|
* Linking an artifact records a DECLARATION (`{artifact_path, kind, target}` appended to
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
|
|
54
54
|
import { state, setState, subscribe, FIXTURE } from './state.js';
|
|
55
55
|
import { C } from './fixture.js';
|
|
56
|
+
import { pressFetch } from './http.js';
|
|
56
57
|
|
|
57
58
|
/* ── transport ──────────────────────────────────────────────────────────────────────────────
|
|
58
59
|
* Relative URLs only. The server's origin/host allowlist 403s a page served from anywhere else,
|
|
@@ -81,8 +82,8 @@ async function getJson(path, params) {
|
|
|
81
82
|
async function postJson(path, body) {
|
|
82
83
|
let res;
|
|
83
84
|
try {
|
|
84
|
-
res = await
|
|
85
|
-
method: 'POST',
|
|
85
|
+
res = await pressFetch(path, {
|
|
86
|
+
method: 'POST', body: JSON.stringify(body), // pm/http.js: carries the per-launch nonce, and renews a stale one
|
|
86
87
|
});
|
|
87
88
|
} catch (e) { return { ok: false, error: 'could not reach the workspace server — ' + msg(e) }; }
|
|
88
89
|
try { return await res.json(); }
|
|
@@ -192,62 +193,7 @@ function decidingRung(rungs) {
|
|
|
192
193
|
|| rungs[rungs.length - 1] || null;
|
|
193
194
|
}
|
|
194
195
|
|
|
195
|
-
/**
|
|
196
|
-
const TASK_STATUS = {
|
|
197
|
-
done: ['done', 'done', 'done'],
|
|
198
|
-
current: ['done', 'doing', 'todo'],
|
|
199
|
-
blocked: ['blocked', 'todo', 'todo'],
|
|
200
|
-
pending: ['todo', 'todo', 'todo'],
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* The three derived tasks for one phase. Each field is the engine's own text for the rungs this
|
|
205
|
-
* phase folds — the instruction is what the engine says to DO, the expectation is what it says it
|
|
206
|
-
* LOOKED FOR, the evaluation names the rule that decides. A field with no source is null and the
|
|
207
|
-
* task pane renders it empty; it is never filled with a sentence that sounds like evidence.
|
|
208
|
-
*/
|
|
209
|
-
function derivedTasks(phase, rungs, ctx) {
|
|
210
|
-
const s = phaseRungStatus(rungs);
|
|
211
|
-
const lead = decidingRung(rungs) || {};
|
|
212
|
-
const titles = s === 'blocked'
|
|
213
|
-
? ['resolve the ' + phase + ' blocker', 'produce the ' + phase + ' output', 'record the result']
|
|
214
|
-
: ['read the inputs for ' + phase, 'produce the ' + phase + ' output', 'record the result'];
|
|
215
|
-
const evidence = rungs.filter((r) => r.evidence).map((r) => r.key + ': ' + r.evidence).join(' · ') || null;
|
|
216
|
-
const inputs = [ctx.record, ctx.brief, ctx.doc, ctx.prd, ctx.rubric].filter(Boolean)
|
|
217
|
-
.filter((v, i, a) => a.indexOf(v) === i).join(' · ') || null;
|
|
218
|
-
// D77 — what each rung read and the sentence that decides it, from the engine's own basis.
|
|
219
|
-
const basis = rungs.map((r) => ({ key: r.key, b: r.basis || null })).filter((x) => x.b);
|
|
220
|
-
const sentences = basis.map((x) => x.b.rule).filter((v, i, a) => v && a.indexOf(v) === i);
|
|
221
|
-
const readLine = basis.filter((x) => x.b.file)
|
|
222
|
-
.map((x) => x.b.file + (x.b.first_seen ? ' (first seen ' + x.b.first_seen + ')' : '')).filter((v, i, a) => a.indexOf(v) === i);
|
|
223
|
-
const rules = rungs.map((r) => r.rule).filter(Boolean);
|
|
224
|
-
// The produced artifact: a file a rung of this phase read, never guessed. A document or record is
|
|
225
|
-
// preferred over a ledger for the second task; the third task shows the ledger the result lands in.
|
|
226
|
-
const files = basis.filter((x) => x.b.file && x.b.source === 'file').map((x) => x.b.file);
|
|
227
|
-
const ledgers = basis.filter((x) => x.b.file && x.b.source === 'ledger').map((x) => x.b.file);
|
|
228
|
-
const produced = files[files.length - 1] || pathIn(lead.evidence) || pathIn(lead.next) || null;
|
|
229
|
-
const recorded = ledgers[ledgers.length - 1] || null;
|
|
230
|
-
const def = ctx.laneDef || null;
|
|
231
|
-
return titles.map((title, i) => ({
|
|
232
|
-
title,
|
|
233
|
-
status: (TASK_STATUS[s] || TASK_STATUS.pending)[i],
|
|
234
|
-
// The engine's own action for the phase — except on the first task, whose job IS the reading,
|
|
235
|
-
// so it names the files rather than repeating the action. Neither is authored prose: one is the
|
|
236
|
-
// engine's sentence, the other is a list of paths the record declares.
|
|
237
|
-
instruction: (i === 0 && inputs ? 'Read ' + inputs : null) || lead.next || lead.evidence || null,
|
|
238
|
-
input: inputs,
|
|
239
|
-
// What the lane says finishing means, then what the engine found. Both are data, not prose written here.
|
|
240
|
-
expect: [def ? def.done : null, evidence ? 'Found: ' + evidence : null].filter(Boolean).join(' — ') || null,
|
|
241
|
-
evalNote: sentences.length
|
|
242
|
-
? sentences.join(' ') + (readLine.length ? ' Decided from: ' + readLine.join(', ') + '.' : '')
|
|
243
|
-
: rules.length
|
|
244
|
-
? 'workflow rule ' + rules.join(', ') + ' decides this phase'
|
|
245
|
-
: 'the workflow engine derives this phase from ' + rungs.map((r) => r.key).join(', '),
|
|
246
|
-
output: i === 1 ? produced : (i === 2 ? recorded : null),
|
|
247
|
-
}));
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/** The first repo-relative path inside an engine message, or null. Used only for a task's `output`. */
|
|
196
|
+
/** The first repo-relative path inside an engine message, or null. Used for a step's `cmd`. */
|
|
251
197
|
function pathIn(text) {
|
|
252
198
|
const m = String(text || '').match(/\b(?:planning|prototype|workspace|okf|skills|build-skills)\/[\w./-]+\.\w+/);
|
|
253
199
|
return m ? m[0] : null;
|
|
@@ -492,21 +438,6 @@ function recordFrom(payload) {
|
|
|
492
438
|
const lane = fm.lane || (row && row.lane) || null;
|
|
493
439
|
const steps = row ? phaseSteps(lane, row.steps, row.profile) : [];
|
|
494
440
|
const laneDef = (payload.lanes && payload.lanes.defs && lane && payload.lanes.defs[lane]) || null;
|
|
495
|
-
const ctx = {
|
|
496
|
-
record: payload.record, prd: fm.prd_path || null, rubric: fm.rubric_path || null,
|
|
497
|
-
brief: fm.brief_path || null, doc: fm.doc_path || null, laneDef,
|
|
498
|
-
};
|
|
499
|
-
// Authored tasks win over derivation, per phase (PRD, "Tasks"). Their absence is not a defect.
|
|
500
|
-
const authored = payload.tasks || {};
|
|
501
|
-
const tasks = {};
|
|
502
|
-
const byKey = {};
|
|
503
|
-
for (const s of (row && row.steps) || []) byKey[s.key] = s;
|
|
504
|
-
for (const st of steps) {
|
|
505
|
-
const rungs = (RUNGS[laneOf(lane, row && row.profile)][st.k] || []).map((k) => byKey[k]).filter(Boolean);
|
|
506
|
-
tasks[st.k] = Array.isArray(authored[st.k]) && authored[st.k].length
|
|
507
|
-
? authored[st.k]
|
|
508
|
-
: derivedTasks(st.k, rungs, ctx);
|
|
509
|
-
}
|
|
510
441
|
const rec = {
|
|
511
442
|
tag: fm.title || (row && row.title) || '',
|
|
512
443
|
// the lane this record declares, and the lanes this repository allows — what the header's lane picker offers
|
|
@@ -522,7 +453,6 @@ function recordFrom(payload) {
|
|
|
522
453
|
branch: scope ? scope.branch : null,
|
|
523
454
|
answer: answerFor(row),
|
|
524
455
|
steps,
|
|
525
|
-
tasks,
|
|
526
456
|
sessions: (payload.sessions || []).map(sessionRow),
|
|
527
457
|
links: linksFor(payload),
|
|
528
458
|
arts: artsFor(payload),
|
|
@@ -905,101 +835,6 @@ function currentProductName() {
|
|
|
905
835
|
return productOrder[0] || null;
|
|
906
836
|
}
|
|
907
837
|
|
|
908
|
-
/* ── the Focus queue ─────────────────────────────────────────────────────────────────────────
|
|
909
|
-
* `/api/console?name=focus` is cross-product (the server accepts and ignores `ws`) and it runs the
|
|
910
|
-
* board and ladder derivations for every registered workspace — a cold derive the boot path
|
|
911
|
-
* deliberately avoids (see `lightRecord`). So it is fetched LAZILY, on entry to focus mode, never
|
|
912
|
-
* at load.
|
|
913
|
-
*
|
|
914
|
-
* The server's view (lib/focus.js buildFocus) is `{queue, handoffs, delegated, upkeep, badge, …}`;
|
|
915
|
-
* modes.js renders the fixture rows' shape `{first, waiting, syn, saw}`. The reshape happens here
|
|
916
|
-
* — one adapter, not a second renderer — and a failed read is published as `{readError}`, which
|
|
917
|
-
* the mode renders as a failed read, never as "a successful read of nothing".
|
|
918
|
-
*/
|
|
919
|
-
|
|
920
|
-
/** One focus row in the shape the mode's queue renderer reads. The `read:` label is the caller's
|
|
921
|
-
* to add (lib/focus.js R4) — the server's string is the content after it. */
|
|
922
|
-
function focusRow(r) {
|
|
923
|
-
return {
|
|
924
|
-
n: r.rank == null ? '' : String(r.rank),
|
|
925
|
-
slug: r.subject || '',
|
|
926
|
-
// `productName` is the board's own key (the repo name data.js registered), which is what
|
|
927
|
-
// `jump` needs; `product` is the server's repo-key path and matches no board.
|
|
928
|
-
prod2: r.productName || '',
|
|
929
|
-
why: r.why || '',
|
|
930
|
-
read: r.read ? 'read: ' + r.read : '',
|
|
931
|
-
};
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
/** The server's focus view, reshaped to what focusList renders. Every row is the payload's — no
|
|
935
|
-
* fixture constant is reachable from here. */
|
|
936
|
-
function focusFrom(view) {
|
|
937
|
-
const h = view.handoffs || {};
|
|
938
|
-
const d = view.delegated || {};
|
|
939
|
-
const u = view.upkeep || {};
|
|
940
|
-
|
|
941
|
-
const syn = [];
|
|
942
|
-
for (const s of d.running || []) {
|
|
943
|
-
syn.push({
|
|
944
|
-
t: (s.name || s.id) + ' · ' + (s.word || s.shown) + (s.productName ? ' — ' + s.productName : ''),
|
|
945
|
-
hasBtn: false,
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
/* `starts` are the engine's own delegate OFFERS. They render as rows, not buttons: this surface
|
|
949
|
-
* has no runner that can feed `initialInput` into a spawn, and a ▶ button PMOS cannot honour is
|
|
950
|
-
* worse than no button (the server's own starts_note rule). */
|
|
951
|
-
for (const s of d.starts || []) {
|
|
952
|
-
syn.push({ t: (s.initiative || '') + ' — ' + (s.action || s.skill || ''), hasBtn: false });
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
const saw = [];
|
|
956
|
-
if (u.memory && u.memory.heading) {
|
|
957
|
-
saw.push({ t: u.memory.heading + ' — ' + (u.memory.body || u.memory.state || '') });
|
|
958
|
-
}
|
|
959
|
-
const bud = u.budgets || {};
|
|
960
|
-
if (bud.state === 'error') {
|
|
961
|
-
saw.push({ t: 'health budgets — read failed: ' + (bud.error || 'no reason given') });
|
|
962
|
-
}
|
|
963
|
-
for (const r of bud.rows || []) {
|
|
964
|
-
saw.push({
|
|
965
|
-
t: r.id + ' — cap ' + (r.cap || 'not declared') + ' · ' + (r.total || 0)
|
|
966
|
-
+ ' anchored initiative' + (r.total === 1 ? '' : 's'),
|
|
967
|
-
});
|
|
968
|
-
}
|
|
969
|
-
// The server's caveat travels with the counts it qualifies: without it the totals above read as
|
|
970
|
-
// the cap test, which is exactly the claim the view refuses to make.
|
|
971
|
-
if ((bud.rows || []).length && bud.caveat) saw.push({ t: bud.caveat });
|
|
972
|
-
|
|
973
|
-
return {
|
|
974
|
-
first: (view.queue || []).map(focusRow),
|
|
975
|
-
waiting: (h.items || []).map(focusRow),
|
|
976
|
-
// The handoffs body for a tracker that is NOT CONNECTED — a different sentence from an empty
|
|
977
|
-
// list, rendered by the mode in place of the calm empty line.
|
|
978
|
-
waitingNote: (h.items || []).length ? null : (h.body || null),
|
|
979
|
-
syn,
|
|
980
|
-
saw,
|
|
981
|
-
};
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
let focusInFlight = false;
|
|
985
|
-
|
|
986
|
-
async function loadFocus() {
|
|
987
|
-
if (FIXTURE || focusInFlight) return;
|
|
988
|
-
focusInFlight = true;
|
|
989
|
-
// The in-flight read is STATED. With nothing published yet, the mode would otherwise render its
|
|
990
|
-
// calm empty line — a claim of a successful read of nothing while the read is still out. A
|
|
991
|
-
// usable earlier payload stays on screen for the refresh instead.
|
|
992
|
-
if (!state.focus || state.focus.readError) setState({ focus: { reading: true } });
|
|
993
|
-
try {
|
|
994
|
-
const view = await getJson('/api/console', { name: 'focus' });
|
|
995
|
-
setState({
|
|
996
|
-
focus: view && view.ok !== false
|
|
997
|
-
? focusFrom(view)
|
|
998
|
-
: { readError: (view && view.error) || 'the focus queue could not be read' },
|
|
999
|
-
});
|
|
1000
|
-
} finally { focusInFlight = false; }
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
838
|
/* ── sessions ───────────────────────────────────────────────────────────────────────────────
|
|
1004
839
|
* Polled rather than pushed. The WebSocket that carries `sessions` frames belongs to panel 4's
|
|
1005
840
|
* terminal module, which owns the attach/input protocol; opening a SECOND socket here to read a
|
|
@@ -1010,6 +845,22 @@ async function loadFocus() {
|
|
|
1010
845
|
let pollTimer = null;
|
|
1011
846
|
let sessionDigest = null;
|
|
1012
847
|
|
|
848
|
+
/* pm-os-absorb Run 1 — the plan-items signatures. An open Breakdown watches its workspace; the poll then asks for that
|
|
849
|
+
* workspace's `evidence` signature too, and `pmos:ledgers` tells every listener which workspaces' signatures moved. */
|
|
850
|
+
const ledgerWatch = new Map(); // ws id -> how many open Breakdowns watch it
|
|
851
|
+
let lastLedgers = null; // null until the first poll: its signatures are the baseline, not a change
|
|
852
|
+
export function watchLedgers(wsId) {
|
|
853
|
+
if (!wsId) return () => {};
|
|
854
|
+
ledgerWatch.set(wsId, (ledgerWatch.get(wsId) || 0) + 1);
|
|
855
|
+
let done = false;
|
|
856
|
+
return () => {
|
|
857
|
+
if (done) return;
|
|
858
|
+
done = true;
|
|
859
|
+
const n = (ledgerWatch.get(wsId) || 1) - 1;
|
|
860
|
+
if (n > 0) ledgerWatch.set(wsId, n); else ledgerWatch.delete(wsId);
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
|
|
1013
864
|
/**
|
|
1014
865
|
* WHAT THE SURFACE ACTUALLY RENDERS from a session row, as one string.
|
|
1015
866
|
*
|
|
@@ -1040,7 +891,8 @@ function sessionProjection(rows) {
|
|
|
1040
891
|
|
|
1041
892
|
async function pollSessions() {
|
|
1042
893
|
if (FIXTURE) return;
|
|
1043
|
-
const
|
|
894
|
+
const watched = [...ledgerWatch.keys()].slice(0, 4);
|
|
895
|
+
const out = await getJson('/api/sessions' + (watched.length ? '?ledgers=' + watched.map(encodeURIComponent).join(',') : ''));
|
|
1044
896
|
if (out && Array.isArray(out.sessions)) {
|
|
1045
897
|
const digest = sessionProjection(out.sessions);
|
|
1046
898
|
const count = liveCount(out.sessions);
|
|
@@ -1051,6 +903,12 @@ async function pollSessions() {
|
|
|
1051
903
|
setState({ sessions: out.sessions, runningCount: count });
|
|
1052
904
|
}
|
|
1053
905
|
}
|
|
906
|
+
if (out && out.ledgers && typeof out.ledgers === 'object' && typeof window !== 'undefined') {
|
|
907
|
+
const prev = lastLedgers;
|
|
908
|
+
const changed = Object.keys(out.ledgers).filter((ws) => prev && JSON.stringify(out.ledgers[ws]) !== JSON.stringify(prev[ws]));
|
|
909
|
+
lastLedgers = out.ledgers;
|
|
910
|
+
if (changed.length) window.dispatchEvent(new CustomEvent('pmos:ledgers', { detail: { changed, ledgers: out.ledgers } }));
|
|
911
|
+
}
|
|
1054
912
|
schedulePoll();
|
|
1055
913
|
}
|
|
1056
914
|
|
|
@@ -1102,6 +960,9 @@ export async function loadRepos() {
|
|
|
1102
960
|
const rows = foldWorkspaces(out && out.workspaces);
|
|
1103
961
|
productOrder = rows.map((r) => r.name);
|
|
1104
962
|
for (const r of rows) products.set(r.name, Object.assign(products.get(r.name) || {}, r));
|
|
963
|
+
// The session poll does not read a board, so it does not wait for one. Behind the board loads it first answered only
|
|
964
|
+
// after the (now larger) initiatives surface had been fetched, and a live agent's marker was missing until then.
|
|
965
|
+
pollSessions();
|
|
1105
966
|
await Promise.all(rows.map((r) => loadBoard(products.get(r.name))));
|
|
1106
967
|
publishBoards();
|
|
1107
968
|
const first = currentProductName();
|
|
@@ -1121,8 +982,6 @@ export async function loadRepos() {
|
|
|
1121
982
|
sel,
|
|
1122
983
|
});
|
|
1123
984
|
if (sel) hydrate(sel);
|
|
1124
|
-
schedulePoll();
|
|
1125
|
-
pollSessions();
|
|
1126
985
|
return productOrder.map((name) => {
|
|
1127
986
|
const prod = products.get(name);
|
|
1128
987
|
return {
|
|
@@ -1545,7 +1404,7 @@ export async function searchFiles(query, limit, all) {
|
|
|
1545
1404
|
export async function saveDoc({ ws, path, base, text }) {
|
|
1546
1405
|
if (FIXTURE) return { ok: true };
|
|
1547
1406
|
const p = products.get(currentProductName());
|
|
1548
|
-
return postJson('/api/doc/save', { ws: ws || state.wsId || (p ? p.ws : ''), path, base, text
|
|
1407
|
+
return postJson('/api/doc/save', { ws: ws || state.wsId || (p ? p.ws : ''), path, base, text });
|
|
1549
1408
|
}
|
|
1550
1409
|
|
|
1551
1410
|
/**
|
|
@@ -1636,7 +1495,7 @@ export function runningCount(s) {
|
|
|
1636
1495
|
* newly selected initiative, and a repaint when it lands.
|
|
1637
1496
|
*/
|
|
1638
1497
|
if (!FIXTURE) {
|
|
1639
|
-
let lastSel = null, lastProduct = null
|
|
1498
|
+
let lastSel = null, lastProduct = null;
|
|
1640
1499
|
subscribe(() => {
|
|
1641
1500
|
if (state.product !== lastProduct) {
|
|
1642
1501
|
lastProduct = state.product;
|
|
@@ -1644,12 +1503,6 @@ if (!FIXTURE) {
|
|
|
1644
1503
|
if (p && p.ws && state.wsId !== p.ws) setState({ wsId: p.ws });
|
|
1645
1504
|
if (p && !p.board) loadInitiatives(state.product);
|
|
1646
1505
|
}
|
|
1647
|
-
// The focus view is fetched on ENTRY to focus mode, and re-fetched on every re-entry — the
|
|
1648
|
-
// queue is a statement about now, and the mode switch is the moment the PM asks for it.
|
|
1649
|
-
if (state.mode !== lastMode) {
|
|
1650
|
-
lastMode = state.mode;
|
|
1651
|
-
if (state.mode === 'focus') loadFocus();
|
|
1652
|
-
}
|
|
1653
1506
|
const sel = state.sel;
|
|
1654
1507
|
if (sel && sel !== lastSel) {
|
|
1655
1508
|
lastSel = sel;
|