create-pmos 0.193.0 → 0.194.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 +1 -1
- package/payload/okf/core/concepts/frontmatter-format.md +3 -2
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/c3-core-docs-test.js +152 -0
- package/workspace/guide/core-documents.md +49 -0
- package/workspace/guide/index.md +2 -1
- package/workspace/guide/initiative-boards.md +14 -0
- package/workspace/lib/core-docs-needs.js +86 -0
- package/workspace/lib/core-docs.js +675 -0
- package/workspace/lib/coredoc-cli.js +90 -0
- package/workspace/lib/doc-category.js +1 -1
- package/workspace/lib/driver-cli.js +16 -2
- package/workspace/lib/driver-decide.js +8 -1
- package/workspace/lib/driver-facts.js +8 -2
- package/workspace/lib/driver-run.js +6 -0
- package/workspace/lib/driver.js +32 -4
- package/workspace/lib/headless.js +13 -3
- package/workspace/lib/needs-providers.js +1 -0
- package/workspace/lib/pmos-verbs.js +3 -0
- package/workspace/lib/route-modules.js +1 -0
- package/workspace/lib/routes-core-docs.js +171 -0
- package/workspace/lib/routes-driver.js +1 -0
- package/workspace/lib/vendor/scripts/migrate-layout.js +1 -1
- package/workspace/lib/vendor/workspace/lib/doc-category.js +1 -1
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-brief-needs.js +3 -0
- package/workspace/public/pm/c3-core-docs.css +18 -0
- package/workspace/public/pm/c3-core-docs.js +234 -0
- package/workspace/public/pm/c3-demo-coredocs.js +211 -0
- package/workspace/public/pm/c3-demo.js +2 -1
- package/workspace/public/pm/c3-driver.js +6 -4
- package/workspace/public/pm/c3-ib-docs.js +24 -14
- package/workspace/public/pm/c3-ii-tabs.js +15 -8
- package/workspace/public/pm/c3-initiatives.js +29 -2
- package/workspace/server.js +15 -2
- package/workspace/skills/initiative-drive.skill +6 -2
- package/workspace/test-lists/c3-core-docs.json +9 -0
- package/workspace/test-lists/core-documents-backend.json +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.194.0",
|
|
4
4
|
"description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
package/payload/KIT-VERSION
CHANGED
|
@@ -3,7 +3,7 @@ type: Concept
|
|
|
3
3
|
title: Repo-wide frontmatter format
|
|
4
4
|
description: The PMOS house convention — every tracked .md carries OKF-format YAML frontmatter with a required type + title (timestamp optional since D74), so agents and the harness classify and route a file by reading it, not by guessing from its path.
|
|
5
5
|
tags: [frontmatter, okf, format, harness, aci, convention]
|
|
6
|
-
timestamp: 2026-
|
|
6
|
+
timestamp: 2026-10-13
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Repo-wide frontmatter format
|
|
@@ -46,10 +46,11 @@ OKF's reserved filenames carry **no** frontmatter and are skipped by both the mi
|
|
|
46
46
|
The enforced list lives in [`scripts/frontmatter_registry.txt`](/scripts/frontmatter_registry.txt) — the single source `frontmatter_lint.py` reads. A `type:` value not in it fails CI (this catches typos).
|
|
47
47
|
|
|
48
48
|
- **OKF core:** `Concept`, `ADR`, `Template`, `Playbook`, `Reference`
|
|
49
|
-
- **Planning:** `Initiative`, `PRD`, `Story`, `Rubric`, `OKR`, `Radar`, `Checklist`, `Calibration`, `Research`, `Design`, `Prototype`, `Handover`
|
|
49
|
+
- **Planning:** `Initiative`, `PRD`, `Story`, `Rubric`, `OKR`, `Radar`, `Checklist`, `Calibration`, `Research`, `Design`, `Prototype`, `Handover`, `Review`
|
|
50
50
|
- `Initiative` is the repo-native initiative record in [`planning/initiatives/`](/planning/initiatives/), read by the [Initiative Gate](/.github/scripts/initiative-gate.sh). Its D56 **work-type lane** rides a separate `lane:` key — not `type:` — so the lane vocabulary and this registry never collide.
|
|
51
51
|
- `Story` is a **user-story document** — the decomposition of a spec into the units of work handed to whoever builds them. It is the one type in this registry that is also **checked positionally** (see below), and it may carry an optional `jira:` / `confluence:` / `google_doc:` key naming the tracker record it corresponds to. That key is a *declaration* the workspace displays, never a lookup: PMOS holds no credential and makes no outbound request.
|
|
52
52
|
- `Handover` is the note the cockpit mod's `/pmos-handover` writes (`planning/handovers/<id>.md`, or `pmos/state/handovers/<id>.md` in a kit): one per initiative, replaced on each handover, with checked fields (`id`, `head_sha`, `branch`, `stage`, `next`, `commits`, `files`, `handover_at`) read back by the resume summary ([D81](/okf/products/pmos/adr/d81-cockpit-mod-and-grants.md)).
|
|
53
|
+
- `Review` is an initiative's **outcome review**, a core document the workspace drafts and the PM issues (`<state>/core-docs/initiative-<id>/outcome/<slot>.md`, initiative core-documents): did the work move its key result, and what was learned. The other core documents are `Reference`.
|
|
53
54
|
- **Root governance:** `Decision`, `Principle`, `Debt`, `Discovery`, `Guide`
|
|
54
55
|
|
|
55
56
|
### When the path decides the type
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Eval calibration log"
|
|
4
4
|
description: "Append-only record of every PM override of an evaluator verdict."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-13T08:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-13T08:20:29Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Health budgets"
|
|
4
4
|
description: "Closed registry of health-budget anchors (D59), edited only by the PM."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-13T08:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-13T08:20:29Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Discoveries and friction log"
|
|
4
4
|
description: "Append-only record of what runs discovered and where the process rubbed."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-13T08:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-13T08:20:29Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* PMOS workspace — the core documents of each level on the console3 Initiatives page (initiative core-documents; the approved mockup
|
|
3
|
+
* 43-doc-levels.js), in demo mode (browser). c3-demo-coredocs.js answers GET /api/core-docs and the POSTs. It drives:
|
|
4
|
+
* Portfolio > Documents (Overview | Documents), a board's Docs tab (core documents, then "Other documents") and an initiative's Docs tab
|
|
5
|
+
* (categories that fold), the rows (state chips, Draft it, Review the draft), the draft panel of the document sheet (Issue it, Ask PMOS to
|
|
6
|
+
* change it), the Briefing row core-doc-drafts, and no horizontal scroll at 400 px.
|
|
7
|
+
*
|
|
8
|
+
* The files it drives (named so `run-tests.js --changed` traces a change in any of them to this suite): public/pm/c3-core-docs.js,
|
|
9
|
+
* c3-core-docs.css, c3-demo-coredocs.js, c3-demo.js, c3-ib-docs.js, c3-ii-tabs.js, c3-initiatives.js, c3-ini-ui.js, c3-doc-sheet.js,
|
|
10
|
+
* c3-driver.js, c3-brief-needs.js and c3-ini-harness.js. At 400 px it keeps the desktop layout the way lib/c3-full-console.js does. Its list fragment is test-lists/c3-core-docs.json.
|
|
11
|
+
*/
|
|
12
|
+
'use strict';
|
|
13
|
+
const { harness } = require('./c3-ini-harness');
|
|
14
|
+
|
|
15
|
+
(async () => {
|
|
16
|
+
const H = await harness('c3-core-docs');
|
|
17
|
+
const { page, ok, until, st, open, toast, sleep } = H;
|
|
18
|
+
const rows = (sel) => st((s) => [...document.querySelectorAll(s)].map((n) => n.dataset.cdRow).join(','), sel || '[data-cd-row]');
|
|
19
|
+
const rowState = (k) => st((key) => { const n = document.querySelector('[data-cd-row="' + key + '"]'); return n ? n.dataset.cdSt : ''; }, k);
|
|
20
|
+
const waitToast = (re) => until(async () => { const t = await toast(); return re.test(t) && t; });
|
|
21
|
+
const noScroll = () => st(() => document.documentElement.scrollWidth <= window.innerWidth + 1 && [...document.querySelectorAll('.c3 .calwrap, .c3 .brief')].every((n) => n.scrollWidth <= n.clientWidth + 1));
|
|
22
|
+
const sheetOpen = () => st(() => !!document.querySelector('[data-shell="sheet"] [role="dialog"]'));
|
|
23
|
+
const settle = async (sel) => { await until(() => st((s) => !!document.querySelector(s), sel)); await sleep(120); };
|
|
24
|
+
|
|
25
|
+
/* ── Portfolio > Documents ── */
|
|
26
|
+
await open('docs');
|
|
27
|
+
await settle('[data-cd="core"]');
|
|
28
|
+
ok('P1 the Portfolio has an Overview | Documents control, Documents pressed', await st(() => { const b = [...document.querySelectorAll('[data-ini="pf-tabs"] [data-ini-tab]')]; return b.map((x) => x.dataset.iniTab + ':' + x.getAttribute('aria-pressed')).join(',') === 'overview:false,docs:true'; }));
|
|
29
|
+
ok('P2 the Documents badge counts the drafts and the due documents', (await st(() => (document.querySelector('[data-cd="seg-badge"]') || {}).textContent)) === '3');
|
|
30
|
+
const pf = await rows();
|
|
31
|
+
ok('P3 the core set lists 11 documents, strategy first', pf.split(',').length === 11 && pf.startsWith('strategy,okrs,roadmap,invest'), pf);
|
|
32
|
+
ok('P4 states: up to date, draft ready, due, comes later where it applies', (await rowState('strategy')) === 'ok' && (await rowState('roadmap')) === 'draft' && (await rowState('status')) === 'due');
|
|
33
|
+
ok('P5 a row shows its question, cadence, reader and who writes it', await st(() => { const t = document.querySelector('[data-cd-row="strategy"]').textContent; return /Yearly, reviewed each quarter/.test(t) && /Leadership, every PM/.test(t) && /You write it/.test(t) && /new/.test(t) && /2 key results do not trace to it/.test(t); }));
|
|
34
|
+
ok('P6 the right column says what the level is for, lists the drafts and how the levels connect', await st(() => { const l = document.querySelector('[data-cd="level"]'); return !!l && /Are we betting on the right things\?/.test(l.textContent) && document.querySelectorAll('[data-cd="drafts"] [data-cd-wait]').length === 2 && !!document.querySelector('[data-cd="connect"]'); }));
|
|
35
|
+
ok('P7 the address holds the Documents view', await st(() => /init=docs/.test(location.search)));
|
|
36
|
+
ok('P8 the summary line counts the drafts and what is due', await st(() => /2 drafts to review, 1 due/.test(document.querySelector('[data-cd="core"]').textContent)));
|
|
37
|
+
await H.shot('portfolio-docs');
|
|
38
|
+
await page.click('[data-ini="pf-tabs"] [data-ini-tab="overview"]');
|
|
39
|
+
ok('P9 Overview brings the boards back', await until(() => st(() => !!document.querySelector('[data-ini="boards"]') && !document.querySelector('[data-cd="core"]'))));
|
|
40
|
+
await page.click('[data-ini="pf-tabs"] [data-ini-tab="docs"]');
|
|
41
|
+
ok('P10 Documents opens again', await until(() => st(() => !!document.querySelector('[data-cd="core"]'))));
|
|
42
|
+
|
|
43
|
+
/* ── Review the draft, Issue it (Portfolio) ── */
|
|
44
|
+
await page.click('[data-cd-review="invest"]');
|
|
45
|
+
ok('R1 Review the draft opens the draft in the document sheet', await until(() => st(() => !!document.querySelector('[data-shell="sheet"] [data-cd="draft-panel"]'))));
|
|
46
|
+
ok('R2 the sheet shows the draft, with Issue it and Ask PMOS to change it', await st(() => { const d = document.querySelector('[data-shell="sheet"]'); return /Investment mix/.test(d.textContent) && !!d.querySelector('[data-cd-issue="invest"]') && !!d.querySelector('[data-cd-redraft="invest"]'); }));
|
|
47
|
+
await H.shot('portfolio-sheet');
|
|
48
|
+
await page.click('[data-cd-issue="invest"]');
|
|
49
|
+
ok('R3 Issue it says nothing was sent and closes the sheet', await until(async () => /Issued\. Nothing was sent/.test(await toast()) && !(await sheetOpen())));
|
|
50
|
+
ok('R4 the row is up to date and the draft is gone from the waiting list', await until(async () => (await rowState('invest')) === 'ok' && (await st(() => document.querySelectorAll('[data-cd="drafts"] [data-cd-wait]').length)) === 1));
|
|
51
|
+
|
|
52
|
+
/* ── a board: IDPF ── */
|
|
53
|
+
await open('board:idpf:docs');
|
|
54
|
+
await settle('[data-cd="core"]');
|
|
55
|
+
const bd = await rows();
|
|
56
|
+
ok('B1 the board set lists 10 documents, the charter first', bd.split(',').length === 10 && bd.startsWith('charter,backlog,intake,bstatus,capacity'), bd);
|
|
57
|
+
ok('B2 Core documents comes before "Other documents"', await st(() => { const c = document.querySelector('[data-cd="core"]'); const o = document.querySelector('[data-inb="docs-title"]'); return !!c && !!o && /Other documents/.test(o.textContent) && !!(c.compareDocumentPosition(o) & Node.DOCUMENT_POSITION_FOLLOWING); }));
|
|
58
|
+
ok('B3 the existing documents list is still there, grouped by initiative', await until(() => st(() => document.querySelectorAll('[data-inb-docgrp]').length > 0)));
|
|
59
|
+
ok('B4 the right column: what the level is for, and the initiative documents', await st(() => /Is this area moving, and what comes next\?/.test(document.querySelector('[data-cd="level"]').textContent) && /Build and deliver is on, so the set adds the sprint review and release notes from Jira IDPF/.test(document.querySelector('[data-cd="level"]').textContent) && /documents? across \d+ initiatives?/.test(document.querySelector('[data-cd="ini-docs"]').textContent)));
|
|
60
|
+
ok('B5 a live row opens the tab it names', await (async () => { await page.click('[data-cd-open="intake"]'); return until(() => st(() => /board:idpf:triage/.test(decodeURIComponent(location.search)))); })());
|
|
61
|
+
await open('board:idpf:docs');
|
|
62
|
+
await settle('[data-cd="core"]');
|
|
63
|
+
await H.shot('board-docs');
|
|
64
|
+
|
|
65
|
+
/* Redraft from the sheet */
|
|
66
|
+
await page.click('[data-cd-review="sprint"]');
|
|
67
|
+
await until(() => st(() => !!document.querySelector('[data-shell="sheet"] [data-cd-redraft="sprint"]')));
|
|
68
|
+
await page.click('[data-cd-redraft="sprint"]');
|
|
69
|
+
ok('RD1 Ask PMOS to change it asks for a new draft and closes the sheet', await until(async () => /drafting it again/.test(await toast()) && !(await sheetOpen())));
|
|
70
|
+
ok('RD2 the row shows the agent drafting', await until(async () => (await rowState('sprint')) === 'running' && (await st(() => /Agent drafting/.test(document.querySelector('[data-cd-row="sprint"]').textContent)))));
|
|
71
|
+
|
|
72
|
+
/* ── a board with Build and deliver off ── */
|
|
73
|
+
await open('board:office:docs');
|
|
74
|
+
await settle('[data-cd="core"]');
|
|
75
|
+
const off = await rows();
|
|
76
|
+
ok('O1 Build and deliver off drops the sprint review and release notes (8 documents)', off.split(',').length === 8 && !/sprint|release/.test(off), off);
|
|
77
|
+
ok('O2 the charter is not started and offers Draft it', (await rowState('charter')) === 'missing' && await st(() => !!document.querySelector('[data-cd-row="charter"] [data-cd-draft="charter"]')));
|
|
78
|
+
ok('O3 the level card says there is no Jira', await st(() => /Build and deliver is off, so there is no sprint review or release notes/.test(document.querySelector('[data-cd="level"]').textContent)));
|
|
79
|
+
await H.shot('board-off-docs');
|
|
80
|
+
await page.click('[data-cd-draft="charter"]');
|
|
81
|
+
ok('D1 Draft it says PMOS is drafting', await waitToast(/PMOS is drafting the board charter/));
|
|
82
|
+
ok('D2 the row shows "Agent drafting" and no button', await until(async () => (await rowState('charter')) === 'running' && await st(() => !document.querySelector('[data-cd-row="charter"] [data-cd-draft]') && /Agent drafting/.test(document.querySelector('[data-cd-row="charter"]').textContent))));
|
|
83
|
+
ok('D3 the draft arrives and offers Review the draft', await until(() => st(() => !!document.querySelector('[data-cd-row="charter"] [data-cd-review="charter"]')), 15000));
|
|
84
|
+
await page.click('[data-cd-review="charter"]');
|
|
85
|
+
await until(() => st(() => !!document.querySelector('[data-shell="sheet"] [data-cd="draft-panel"]')));
|
|
86
|
+
ok('D4 the draft opens in the sheet with its own text', await st(() => /Jakarta office/.test(document.querySelector('[data-shell="sheet"]').textContent)));
|
|
87
|
+
await page.click('[data-cd-issue="charter"]');
|
|
88
|
+
ok('D5 Issue it turns the row up to date', await until(async () => (await rowState('charter')) === 'ok'));
|
|
89
|
+
|
|
90
|
+
/* ── an initiative in Build: IDPF-12 ── */
|
|
91
|
+
await open('ini:idpf:IDPF-12:docs');
|
|
92
|
+
await settle('[data-cd-fold]');
|
|
93
|
+
const folds = await st(() => [...document.querySelectorAll('[data-cd-cat]')].map((n) => n.dataset.cdCat + ':' + n.dataset.cdFold).join(','));
|
|
94
|
+
ok('I1 Discover and Plan fold to one line, Do is the current one, Learn comes later', folds === 'Discover:done,Plan:done,Do:open,Learn:later', folds);
|
|
95
|
+
ok('I2 the folded category says "All 3 up to date" and keeps its documents as chips', await st(() => { const s = document.querySelector('[data-cd-cat="Discover"]'); return /All 3 up to date/.test(s.textContent) && s.querySelectorAll('[data-cd-chip]').length === 3 && !s.querySelector('.nrow'); }));
|
|
96
|
+
ok('I3 the current category is marked "now" and lists its rows', await st(() => { const s = document.querySelector('[data-cd-cat="Do"]'); return /Do · now/.test(s.textContent) && s.querySelectorAll('[data-cd-row]').length === 4 && /1 of 4 done/.test(s.textContent); }));
|
|
97
|
+
ok('I4 the right column: level, order rules, other documents', await st(() => !!document.querySelector('[data-cd="level"]') && !!document.querySelector('[data-cd="order"]') && /Other documents/.test(document.body.textContent)));
|
|
98
|
+
await H.shot('ini-docs');
|
|
99
|
+
await page.click('[data-cd-chip="prd"]');
|
|
100
|
+
ok('I5 a chip of a folded category opens the document in the sheet', await until(() => sheetOpen()));
|
|
101
|
+
await page.keyboard.press('Escape');
|
|
102
|
+
await until(async () => !(await sheetOpen()));
|
|
103
|
+
await page.click('[data-cd-row="risks"] [data-cd-open="risks"]');
|
|
104
|
+
ok('I6 a row with a stage tool opens the initiative Overview on that tool', await until(() => st(() => /ini:idpf:IDPF-12:overview:risks/.test(decodeURIComponent(location.search)))));
|
|
105
|
+
|
|
106
|
+
/* ── an initiative on research only, early: IDPF-21 ── */
|
|
107
|
+
await open('ini:idpf:IDPF-21:docs');
|
|
108
|
+
await settle('[data-cd-fold]');
|
|
109
|
+
const early = await st(() => [...document.querySelectorAll('[data-cd-cat]')].map((n) => n.dataset.cdCat + ':' + n.dataset.cdFold).join(','));
|
|
110
|
+
ok('E1 Discover is open, the later categories wait', early === 'Discover:open,Plan:later,Do:later,Learn:later', early);
|
|
111
|
+
ok('E2 the set has no PRD, rubric, design or launch checklist', await st(() => !document.querySelector('[data-cd-open="prd"], [data-cd-open="rubric"], [data-cd-open="design"], [data-cd-open="launch"]') && /Research only: nothing gets built/.test(document.querySelector('[data-cd="level"]').textContent)));
|
|
112
|
+
ok('E3 a draft problem statement offers Review the draft; research notes are due', await st(() => !!document.querySelector('[data-cd-row="problem"] [data-cd-review]')) && (await rowState('research')) === 'due');
|
|
113
|
+
await H.shot('ini-early-docs');
|
|
114
|
+
|
|
115
|
+
/* ── an initiative in Evaluate with an outcome review draft ── */
|
|
116
|
+
await open('ini:idpf:IDPF-62:docs');
|
|
117
|
+
await settle('[data-cd-fold]');
|
|
118
|
+
ok('V1 the outcome review is a draft to review', await st(() => !!document.querySelector('[data-cd-row="outcome"] [data-cd-review="outcome"]')));
|
|
119
|
+
await page.click('[data-cd-review="outcome"]');
|
|
120
|
+
await until(() => st(() => !!document.querySelector('[data-shell="sheet"] [data-cd-issue="outcome"]')));
|
|
121
|
+
ok('V2 the sheet shows the review and Issue it', await st(() => /Did it move the key result/.test(document.querySelector('[data-shell="sheet"]').textContent)));
|
|
122
|
+
await H.shot('outcome-sheet');
|
|
123
|
+
await page.click('[data-shell="sheet"] button[aria-label="Close"]');
|
|
124
|
+
await until(async () => !(await sheetOpen()));
|
|
125
|
+
|
|
126
|
+
/* ── the Briefing row opens the Docs view ── */
|
|
127
|
+
await page.route('**/api/needs*', (r) => r.fulfill({ contentType: 'application/json', body: JSON.stringify({ ok: true, needs: [{ run_id: 'core-docs:drafts:board:idpf', kind: 'core-doc-drafts', provider: 'core-docs', derived: true, scope: 'board:idpf', question: '1 IDPF draft to review', initiative_id: null }] }) }));
|
|
128
|
+
await page.goto(H.BASE + '/c3.html?demo=1&tab=brief', { waitUntil: 'load' });
|
|
129
|
+
await until(() => st(() => !!document.querySelector('[data-drv-need="core-doc-drafts"]')));
|
|
130
|
+
ok('N1 the Briefing shows the core-doc-drafts row', await st(() => /1 IDPF draft to review/.test(document.querySelector('[data-drv-need="core-doc-drafts"]').textContent)));
|
|
131
|
+
await page.click('[data-drv-need="core-doc-drafts"] .tt');
|
|
132
|
+
ok('N2 a press opens that board\'s Docs view', await until(() => st(() => !!document.querySelector('[data-ini-board-page="idpf"] [data-cd="core"]'))));
|
|
133
|
+
await page.unroute('**/api/needs*');
|
|
134
|
+
|
|
135
|
+
/* ── 400 px: no horizontal scroll ── */
|
|
136
|
+
await page.evaluate(() => { try { localStorage.setItem('pmos.c3.testDesk', 'desk'); } catch { /* no storage */ } }); // at 400 px the phone companion shows; the suites that test the desktop layout keep it (lib/c3-full-console.js)
|
|
137
|
+
await page.setViewportSize({ width: 400, height: 900 });
|
|
138
|
+
for (const [loc, label] of [['docs', 'Portfolio'], ['board:idpf:docs', 'board'], ['board:office:docs', 'board with Build and deliver off'], ['ini:idpf:IDPF-12:docs', 'initiative'], ['ini:idpf:IDPF-21:docs', 'early initiative']]) {
|
|
139
|
+
await open(loc);
|
|
140
|
+
await settle('[data-cd-row], [data-cd-fold]');
|
|
141
|
+
ok('W ' + label + ' Docs has no horizontal scroll at 400 px', await noScroll(), await st(() => document.documentElement.scrollWidth + ' > ' + window.innerWidth));
|
|
142
|
+
await H.shot('w400-' + label.replace(/\W+/g, '-'));
|
|
143
|
+
}
|
|
144
|
+
await open('board:idpf:docs');
|
|
145
|
+
await settle('[data-cd="core"]');
|
|
146
|
+
await page.click('[data-cd-review="bstatus"]');
|
|
147
|
+
await until(() => st(() => !!document.querySelector('[data-shell="sheet"] [data-cd="draft-panel"]')));
|
|
148
|
+
ok('W the draft sheet has no horizontal scroll at 400 px', await noScroll());
|
|
149
|
+
await H.shot('w400-sheet');
|
|
150
|
+
|
|
151
|
+
await H.finish();
|
|
152
|
+
})();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Guide
|
|
3
|
+
title: "Core documents: what each level keeps"
|
|
4
|
+
description: "The core documents of the portfolio, a board and an initiative, what each state means, how PMOS drafts them on a schedule and when you ask, how you issue a draft, and pmos coredoc write."
|
|
5
|
+
timestamp: 2026-10-13
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Core documents: what each level keeps
|
|
9
|
+
|
|
10
|
+
Each level answers one question for one reader:
|
|
11
|
+
|
|
12
|
+
- **Portfolio**: "Are we betting on the right things?" Leadership reads it. It changes each quarter, with a monthly pulse.
|
|
13
|
+
- **Board**: "Is this area moving, and what comes next?" The board owner, the team and the engineering lead read it. It changes every sprint or two weeks.
|
|
14
|
+
- **Initiative**: "What are we building, why, and did it work?" You, the builders and the reviewers read it. It changes at each stage.
|
|
15
|
+
|
|
16
|
+
The Docs view of each level starts with its core set. Each row tells you what the document is for, who reads it, how often it changes, who keeps it, and its state now.
|
|
17
|
+
|
|
18
|
+
## Where each document lives
|
|
19
|
+
|
|
20
|
+
Some core documents are documents of their own. PMOS keeps them in the state folder, under `core-docs/<scope>/<key>/<date>.md`. The others are read from a source that PMOS already keeps: the initiative record and the files it names, the stage tools (problem statement, RICE, launch checklist, kill conditions), the Initiatives and Triage tabs, the releases, and the recurring reports on a dashboard. PMOS never copies these sources.
|
|
21
|
+
|
|
22
|
+
Risks are written once, on the initiative. Board risks and cross-board risks collect them. Each outcome review feeds the board retro and the quarterly review.
|
|
23
|
+
|
|
24
|
+
## The states
|
|
25
|
+
|
|
26
|
+
| State | What it means |
|
|
27
|
+
|---|---|
|
|
28
|
+
| Up to date | The document is issued and its cadence has not passed. |
|
|
29
|
+
| Due | The cadence passed, or the current stage needs the document and it is not there. |
|
|
30
|
+
| Draft ready | PMOS drafted it. It waits for you. |
|
|
31
|
+
| Not started | Nothing is there, and the level needs it now. |
|
|
32
|
+
| Comes later | The initiative has not reached the stage that needs it. |
|
|
33
|
+
| Stale | It was issued before a change it depends on: a stage move (initiative risks, launch checklist, status update), a newer risk list or outcome review (rolled-up documents), or a change to the board settings (board charter). |
|
|
34
|
+
|
|
35
|
+
A row with no source and no document is "Not started" and has no date. PMOS does not guess a date or a warning.
|
|
36
|
+
|
|
37
|
+
## How PMOS drafts a document
|
|
38
|
+
|
|
39
|
+
- **When you ask.** Press **Draft it** on a row, or **Ask PMOS to change it** on a draft. PMOS starts one background run. The run reads the sources of that document and writes one draft. It has no tool that sends, posts or edits.
|
|
40
|
+
- **On a schedule.** Every 30 minutes inside your working hours, PMOS drafts each portfolio and board document whose cadence has passed and that has no draft. It drafts a document at most once a day, and it runs at most 3 drafts at once. It never drafts a document that you write (the product strategy).
|
|
41
|
+
- **The Initiative Driver.** When the current stage of an initiative needs a document that is not there or is stale, the driver drafts it and files a proposal. **Keep** issues the draft. The driver does not propose a stage move while a document of the current stage is due.
|
|
42
|
+
|
|
43
|
+
## Issue a draft
|
|
44
|
+
|
|
45
|
+
Open the draft from its row (**Review the draft**). Read it and change what you like. Then press **Issue it**. Only you can issue or drop a draft. Nothing is sent to anyone: to share an issued document, use the copy action of the document sheet.
|
|
46
|
+
|
|
47
|
+
## The command
|
|
48
|
+
|
|
49
|
+
`pmos coredoc write --scope <scope> --key <key> --file <path>` writes one draft of a core document. The scope is `portfolio`, `board:<id>` or `initiative:<id>`. With `--file -` the command reads the document from standard input. The command refuses a scope it does not know, a key that is not a document of that level, and a document that PMOS reads from another source. A background run can write only the document it was started for. The command writes a draft and nothing else: you issue it on the page.
|
package/workspace/guide/index.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Guide: start here"
|
|
4
|
-
timestamp: 2026-10-
|
|
4
|
+
timestamp: 2026-10-13
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Guide: start here
|
|
@@ -63,6 +63,7 @@ On a desktop the workspace is one frame. [Layout](layout.md) explains each part.
|
|
|
63
63
|
- [Initiatives: the Roadmap, the List and the initiative sheet](initiatives-screen.md): how to read and order the initiatives of a repository, open the sheet, make a new initiative, and review the cycle.
|
|
64
64
|
- [Initiatives and the Roadmap: Now, Next and Later](initiatives-roadmap.md): what day one shows, how an agent proposes a move, and `pmos initiative` and `pmos measure` from a terminal.
|
|
65
65
|
- [The Initiative Driver: drafts the agent files for you](initiative-driver.md): when the driver runs, the unblock sweep, the limits, Keep, Send back and Drop, the board modes, and `pmos driver`.
|
|
66
|
+
- [Core documents: what each level keeps](core-documents.md): the core documents of the portfolio, a board and an initiative, their states, how PMOS drafts them, Issue it, and `pmos coredoc write`.
|
|
66
67
|
- [The Horizon scan: the agent looks outside your initiatives](horizon-scan.md): how often it runs, the watch list you keep, what a scan does, where findings land, what it cannot do, and `pmos scan`.
|
|
67
68
|
- [PM-only acts: what only you can do](pm-only-acts.md): which acts are yours, how the workspace checks that a press comes from your page or your terminal, and what to do when one is refused.
|
|
68
69
|
- [PM-only lines: how the workspace checks your acts](pm-only-lines.md): how each act is recorded, what the Today card "A PM-only line is not attested" means, and the grants for a bulk lifecycle change and a Release.
|
|
@@ -97,6 +97,20 @@ The **Overview** shows the stage records of the current stage:
|
|
|
97
97
|
|
|
98
98
|
A project workflow has the launch checklist and the learnings only. If the workspace server cannot store stage records yet, the cards say "Nothing recorded yet." and nothing is saved.
|
|
99
99
|
|
|
100
|
+
## Core documents
|
|
101
|
+
|
|
102
|
+
Each level has a set of core documents. A level answers one question for one reader: the Portfolio asks "Are we betting on the right things?", a board asks "Is this area moving, and what comes next?", and an initiative asks "What are we building, why, and did it work?".
|
|
103
|
+
|
|
104
|
+
- **Portfolio:** press **Documents** next to **Overview**. The number on the button counts the drafts and the documents that are due.
|
|
105
|
+
- **Board:** the **Docs** tab starts with the core documents. The sprint review and the release notes show only when Build and deliver is on. The documents linked to the board follow under **Other documents**.
|
|
106
|
+
- **Initiative:** the **Docs** tab has one section for each of Discover, Plan, Do and Learn. The current section lists its documents. A finished section, or one not reached yet, folds to one line.
|
|
107
|
+
|
|
108
|
+
Each row shows what the document is for, its state, how often it changes, who reads it and who writes it. The states are Up to date, Due, Draft ready, Not started, Out of date and Comes later. Press a row to open the document, the tab, the stage tool or Reports that holds it.
|
|
109
|
+
|
|
110
|
+
- **Draft it:** on a document that is not started, PMOS drafts it. The row shows "Agent drafting" until the draft is ready.
|
|
111
|
+
- **Review the draft:** opens the draft in the document sheet. Above the draft, **Issue it** makes the draft the current document. **Ask PMOS to change it** asks for a new draft. PMOS sends nothing to anyone.
|
|
112
|
+
- The Briefing shows one row for each level that has drafts waiting. Press the row to open that Docs view.
|
|
113
|
+
|
|
100
114
|
## Demo mode
|
|
101
115
|
|
|
102
116
|
Open `/?demo=1` to see five sample boards and about 310 initiatives. In demo mode nothing is saved, and a reload starts again.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/* core-docs-needs.js — the needs provider of the core documents (initiative core-documents, PRD section 5).
|
|
3
|
+
* lib/needs-providers.js folds these rows into server.js deriveNeeds, and from there into /api/needs and the Briefing.
|
|
4
|
+
*
|
|
5
|
+
* kinds: core-doc-drafts
|
|
6
|
+
* rowsFor(w, { lead }) → rows · sig(w) → string · rowsOf(store, { boardName, titleOf, boardOf, ws, ledgerRel })
|
|
7
|
+
*
|
|
8
|
+
* core-doc-drafts one row per scope with drafts waiting for the PM: "2 portfolio drafts to review", "1 IDPF draft to review",
|
|
9
|
+
* "1 draft to review on <initiative title>". `go` is the Docs view of that scope:
|
|
10
|
+
* { view: 'portfolio', tab: 'docs' } · { view: 'board', board, tab: 'docs' } · { view: 'initiative', id, tab: 'docs' }.
|
|
11
|
+
* Each row also carries scope ('portfolio' | 'board:<id>' | 'initiative:<id>'), board (the board id; for an initiative its
|
|
12
|
+
* board, null when it cannot be read; null for the portfolio) and ws (the workspace id).
|
|
13
|
+
*
|
|
14
|
+
* The rows are repository-wide (the ledger is one per repository), so a tree that is not the lead returns []. Nothing here writes.
|
|
15
|
+
*/
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const crypto = require('crypto');
|
|
19
|
+
const CD = require('./core-docs.js');
|
|
20
|
+
|
|
21
|
+
const NAME = 'core-docs-needs';
|
|
22
|
+
const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
23
|
+
|
|
24
|
+
/** The rows of one folded store. Pure: names come from the two lookups. */
|
|
25
|
+
function rowsOf(store, { boardName = (id) => id, titleOf = (id) => id, boardOf = () => null, ws = null, ledgerRel = 'core-docs.jsonl' } = {}) {
|
|
26
|
+
const by = new Map();
|
|
27
|
+
for (const x of store.values()) {
|
|
28
|
+
if (!x.draft) continue;
|
|
29
|
+
if (!by.has(x.scope)) by.set(x.scope, []);
|
|
30
|
+
by.get(x.scope).push(x);
|
|
31
|
+
}
|
|
32
|
+
const rows = [];
|
|
33
|
+
for (const [scope, list] of by) {
|
|
34
|
+
const p = CD.parseScope(scope);
|
|
35
|
+
if (!p) continue;
|
|
36
|
+
const n = list.length;
|
|
37
|
+
const titles = list.map((x) => (CD.entryOf(p.level, x.key) || { title: x.key }).title).join(', ');
|
|
38
|
+
let question; let go; let ini = null; let board = null;
|
|
39
|
+
if (p.level === 'portfolio') { question = `${plural(n, 'portfolio draft', 'portfolio drafts')} to review`; go = { view: 'portfolio', tab: 'docs' }; }
|
|
40
|
+
else if (p.level === 'board') { question = `${n} ${boardName(p.id) || p.id} ${n === 1 ? 'draft' : 'drafts'} to review`; go = { view: 'board', board: p.id, tab: 'docs' }; board = p.id; }
|
|
41
|
+
else { question = `${plural(n, 'draft', 'drafts')} to review on ${titleOf(p.id) || p.id}`; go = { view: 'initiative', id: p.id, tab: 'docs' }; ini = p.id; board = boardOf(p.id) || null; }
|
|
42
|
+
rows.push({
|
|
43
|
+
run_id: `${NAME}:${scope}`, kind: 'core-doc-drafts', initiative_id: ini, question, doc: list[list.length - 1].draft.path, go, scope, board, ws,
|
|
44
|
+
today: { kind: 1, source: NAME, cause: `PMOS drafted ${titles}. Nothing is sent until you issue it`, evidence: ledgerRel, act: { label: 'Review', cli: null } },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return rows;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function rowsFor(w, { lead } = {}) {
|
|
51
|
+
if (!lead || !w || !w.root) return [];
|
|
52
|
+
const file = CD.ledgerFile(w.root);
|
|
53
|
+
if (!fs.existsSync(file)) return [];
|
|
54
|
+
const store = CD.fold(CD.readLines(file));
|
|
55
|
+
if (![...store.values()].some((x) => x.draft)) return [];
|
|
56
|
+
const names = new Map();
|
|
57
|
+
let fr = null;
|
|
58
|
+
try {
|
|
59
|
+
const D = require('./driver.js');
|
|
60
|
+
const dir = D.stateDirOf(w.root);
|
|
61
|
+
fr = require('./routes-boards.js').frameFor({ stateDirFor: () => dir }, w);
|
|
62
|
+
for (const b of fr.boards) names.set(b.id, b.name);
|
|
63
|
+
} catch { /* the id stands in */ }
|
|
64
|
+
const titles = new Map();
|
|
65
|
+
const boardOfIni = new Map();
|
|
66
|
+
try { const r = require('./initiative-rows.js').peek(w.root); if (r && r.ok) for (const x of r.rows) titles.set(x.id, x.title || x.id); } catch { /* the id stands in */ }
|
|
67
|
+
if (fr) {
|
|
68
|
+
try {
|
|
69
|
+
const RB = require('./routes-boards.js');
|
|
70
|
+
const b = RB.baseRows(w);
|
|
71
|
+
if (b && b.ok) for (const x of b.rows) { try { boardOfIni.set(x.id, RB.boardIdOf(x, fr.boards, fr.folded, w)); } catch { /* no board */ } }
|
|
72
|
+
} catch { /* board stays null */ }
|
|
73
|
+
}
|
|
74
|
+
let rel = 'core-docs.jsonl';
|
|
75
|
+
try { rel = path.relative(w.root, file).split(path.sep).join('/'); } catch { /* the plain name stands */ }
|
|
76
|
+
return rowsOf(store, { boardName: (id) => names.get(id) || id, titleOf: (id) => titles.get(id) || id, boardOf: (id) => boardOfIni.get(id) || null, ws: w.id || null, ledgerRel: rel });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function sig(w) {
|
|
80
|
+
if (!w || !w.root) return 'none';
|
|
81
|
+
let st = 'none';
|
|
82
|
+
try { const s = fs.statSync(CD.ledgerFile(w.root)); st = `${s.size}:${s.mtimeMs}:${s.ino}`; } catch { /* no ledger yet */ }
|
|
83
|
+
return crypto.createHash('sha1').update(st).digest('hex');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { kinds: ['core-doc-drafts'], rowsFor, sig, rowsOf };
|