create-pmos 0.212.0 → 0.213.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/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-ini-life-test.js +1 -1
- package/workspace/c3-initiatives-test.js +17 -9
- package/workspace/guide/initiative-boards.md +3 -3
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-ini-life.js +3 -3
- package/workspace/public/pm/c3-initiatives.js +23 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.213.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
|
@@ -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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-14T03:05:22Z"}
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-14T03:05:22Z"}
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-14T03:05:22Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -155,7 +155,7 @@ const { harness } = require('./c3-ini-harness');
|
|
|
155
155
|
ok('P1 an on-hold initiative: the banner with the reason and the check-back day, Resume and Cancel', /On hold since/.test(ban) && /Q1 budget/.test(ban) && /Check back/.test(ban) && /Resume/.test(ban), ban);
|
|
156
156
|
await page.click('[data-il-menu="IDPF-152"]');
|
|
157
157
|
const pm = await st(() => [...document.querySelectorAll('[data-ini="menu"] .ini-mlbl')].map((n) => n.textContent).join(','));
|
|
158
|
-
ok('P2 the page "…" menu holds Status and Tidy up', pm === 'Status,Tidy up' && await st(() => !!document.querySelector('[data-ini="menu"] [data-il-resume]')), pm);
|
|
158
|
+
ok('P2 the page "…" menu holds This initiative, Status and Tidy up', pm === 'This initiative,Status,Tidy up' && await st(() => !!document.querySelector('[data-ini="menu"] [data-il-resume]')), pm);
|
|
159
159
|
await page.keyboard.press('Escape');
|
|
160
160
|
await page.click('[data-il="banner"] [data-il-resume]');
|
|
161
161
|
await until(async () => ((await row('IDPF-152')) || {}).status === 'on track');
|
|
@@ -121,11 +121,15 @@ const { harness } = require('./c3-ini-harness');
|
|
|
121
121
|
|
|
122
122
|
/* ── an initiative: tools, stepper, workflow editor ── */
|
|
123
123
|
await open('ini:idpf:IDPF-07:overview');
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
const head = await st(() => { const p = document.querySelector('[data-ini-page]'); const c = document.querySelector('[data-ini="crumbs"]').parentElement;
|
|
125
|
+
return { title: !!p.querySelector(':scope > .lh'), dash: [...c.querySelectorAll('button')].some((b) => b.textContent.trim() === 'Dashboard'), tag: /Jira not set|Jira · |PMOS tasks only/.test(p.textContent.split('Overview')[0]),
|
|
126
|
+
status: !!c.querySelector('[data-ini="ini-chips"]'), gear: !!document.querySelector('[data-ini="stepper"]').parentElement.querySelector('[data-ini="edit-workflow"]') }; });
|
|
127
|
+
ok('H1 the header is the breadcrumb row with the status, and the stepper row with the gear; no title row, Dashboard button or Jira tag', !head.title && !head.dash && !head.tag && head.status && head.gear, JSON.stringify(head));
|
|
128
|
+
await page.click('[data-ini="tools"]');
|
|
129
|
+
const strip = await until(() => st(() => { const m = document.querySelector('[data-ini="menu"]'); return m && [...m.querySelectorAll('[data-ini-tool]')].map((b) => b.dataset.iniTool).join(','); }));
|
|
130
|
+
ok('I1 Tools lists the tools of the current stage first (Plan: RICE, PRD, Done-criteria, ...)', /^rice,prd,rubric/.test(strip) && (await st(() => document.querySelector('[data-ini="menu"] .ini-mlbl').textContent)) === 'For Plan', strip);
|
|
131
|
+
const more = await st(() => document.querySelectorAll('[data-ini="menu"] [data-ini-moretool]').length);
|
|
132
|
+
ok('I2 then the tools of the other stages, grouped by stage', more > 3 && /Discover/.test(await st(() => document.querySelector('[data-ini="menu"]').textContent)), String(more));
|
|
129
133
|
await page.keyboard.press('Escape');
|
|
130
134
|
ok('I3 Escape closes the menu', await until(() => st(() => !document.querySelector('[data-ini="menu"]'))));
|
|
131
135
|
await page.click('[data-ini-step="Build & Deliver"]');
|
|
@@ -150,8 +154,9 @@ const { harness } = require('./c3-ini-harness');
|
|
|
150
154
|
ok('W4 the initiative now runs Research only and moved to Plan; other initiatives keep theirs', r07.stage === 'Plan' && r07.wf === 'Discover,Plan,Evaluate'
|
|
151
155
|
&& await store(() => !window.__c3boards.rows.find((r) => r.id === 'IDPF-12').template), JSON.stringify(r07));
|
|
152
156
|
|
|
153
|
-
/* ── Jira epics of an initiative (jira-epic-items) ── */
|
|
154
|
-
await page.click('[data-
|
|
157
|
+
/* ── Jira epics of an initiative (jira-epic-items): in the page "…" menu ── */
|
|
158
|
+
await page.click('[data-il-menu="IDPF-07"]');
|
|
159
|
+
await page.click('[data-ini="menu"] [data-ini="jira-epics"]');
|
|
155
160
|
await until(() => st(() => !!document.querySelector('[data-ini="epics-input"]')));
|
|
156
161
|
await page.fill('[data-ini="epics-input"]', 'IDPF-900, XX-1');
|
|
157
162
|
await page.click('[data-ini="epics-save"]');
|
|
@@ -160,8 +165,11 @@ const { harness } = require('./c3-ini-harness');
|
|
|
160
165
|
await page.fill('[data-ini="epics-input"]', 'idpf-900, idpf-901');
|
|
161
166
|
await page.click('[data-ini="epics-save"]');
|
|
162
167
|
await until(() => store(() => (window.__c3boards.rows.find((r) => r.id === 'IDPF-07').epics || []).length === 2));
|
|
163
|
-
|
|
164
|
-
|
|
168
|
+
await until(() => st(() => !document.querySelector('[data-ini="epics-input"]')));
|
|
169
|
+
await page.click('[data-il-menu="IDPF-07"]');
|
|
170
|
+
const epChip = await until(() => st(() => { const c = document.querySelector('[data-ini="menu"] [data-ini="jira-epics"]'); return c && /Jira epics IDPF-900, IDPF-901/.test(c.textContent) && c.textContent; }));
|
|
171
|
+
ok('E2 the epics are saved upper-cased, and the page menu names them', !!epChip, epChip);
|
|
172
|
+
await page.keyboard.press('Escape');
|
|
165
173
|
|
|
166
174
|
/* ── phone width ── */
|
|
167
175
|
await page.setViewportSize({ width: 400, height: 860 });
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Initiatives on the console: Portfolio, boards and workflows"
|
|
4
4
|
description: "How to use the Initiatives tab of the console page (/): the Portfolio of boards, a board and its nine tabs, an initiative with its own workflow and stage records, and the Build and deliver setting."
|
|
5
|
-
timestamp: 2026-10-
|
|
5
|
+
timestamp: 2026-10-14
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Initiatives on the console: Portfolio, boards and workflows
|
|
@@ -86,9 +86,9 @@ Press the gear in the board header to open the board settings.
|
|
|
86
86
|
|
|
87
87
|
## An initiative
|
|
88
88
|
|
|
89
|
-
Each initiative has its own workflow, picked from a template: for example Product, Quick fix, Research only, or Content and launch. The stepper shows the stages of that workflow. Press a stage to move the initiative there. The move is your decision, so nothing blocks it. An order rule shows as a warning chip, for example "Skipped Plan" or "No spec yet". Press
|
|
89
|
+
Each initiative has its own workflow, picked from a template: for example Product, Quick fix, Research only, or Content and launch. The stepper shows the stages of that workflow. Press a stage to move the initiative there. The move is your decision, so nothing blocks it. An order rule shows as a warning chip next to the status, at the right of the breadcrumb, for example "Skipped Plan" or "No spec yet". Press the gear next to the stepper to change the template or edit the stages. When you remove a stage that holds the initiative, the editor asks where it goes.
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
Press **Tools**, at the right of the stepper, to open a tool. The menu lists the tools of the current stage first, then the tools of the other stages. The **…** menu at the right of the breadcrumb opens the Dashboard and the Jira epics of the initiative, and holds Status and Tidy up. The tabs are **Overview**, **Breakdown**, **Docs**, **Tickets**, **Timeline** and **History**.
|
|
92
92
|
|
|
93
93
|
The **Overview** shows the stage records of the current stage:
|
|
94
94
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.213.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pmos-workspace",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.213.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* after moving its Jira epic links and PMOS tasks.
|
|
8
8
|
*
|
|
9
9
|
* rowMenu(anchor, L, r) the row's "…" menu: Move to a stage, Status, Tidy up
|
|
10
|
-
* pageMenu(anchor, L, r)
|
|
10
|
+
* pageMenu(anchor, L, r, own) the initiative page's "…" menu: the page's own items, Status and Tidy up
|
|
11
11
|
* lifeBanner(L, r) the page banner of an on-hold, cancelled or archived initiative, or null
|
|
12
12
|
* lifeChips(r) · archivedChips(r) · dupChip(L, r) the chips a row shows
|
|
13
13
|
* restoreBtn(L, r) Restore, or Undo merge
|
|
@@ -329,9 +329,9 @@ export function rowMenu(anchor, L, r, onMove) {
|
|
|
329
329
|
...(status.length ? [{ group: 'Status' }, ...status] : []),
|
|
330
330
|
{ group: 'Tidy up' }, ...tidyItems(L, r)] });
|
|
331
331
|
}
|
|
332
|
-
export function pageMenu(anchor, L, r) {
|
|
332
|
+
export function pageMenu(anchor, L, r, own) {
|
|
333
333
|
const status = statusItems(L, r);
|
|
334
|
-
openMenu(anchor, { label: 'Actions for ' + r.id, items: [...(status.length ? [{ group: 'Status' }, ...status] : []), { group: 'Tidy up' }, ...tidyItems(L, r)] });
|
|
334
|
+
openMenu(anchor, { label: 'Actions for ' + r.id, items: [...(own && own.length ? [{ group: 'This initiative' }, ...own] : []), ...(status.length ? [{ group: 'Status' }, ...status] : []), { group: 'Tidy up' }, ...tidyItems(L, r)] });
|
|
335
335
|
}
|
|
336
336
|
|
|
337
337
|
/* ── chips and the banner ── */
|
|
@@ -738,20 +738,21 @@ export function mountInitiatives(pane, opts) {
|
|
|
738
738
|
const ctxI = makeCtx(bb, { row, history, tools, toolsByCat, tab, tool: P.loc.tool || '' });
|
|
739
739
|
const items = await loadItems(ctxI);
|
|
740
740
|
if (token !== P.seq) return;
|
|
741
|
-
const work = items.items.filter((w) => w.ini === row.id);
|
|
741
|
+
const work = items.items.filter((w) => w.ini === row.id);
|
|
742
742
|
const wf = wfOf(row, bb); const idx = wf.findIndex((s) => s.name === row.stage); const cat = row.cat || catOf(wf, row.stage);
|
|
743
743
|
const warnings = Array.isArray(row.warnings) ? row.warnings : [];
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
...wf.map((s, k) => btn({ 'aria-pressed': String(k === idx), 'aria-current': k === idx ? 'step' : 'false', 'data-ini-step': s.name, title: bb.self ? SELF_WHY : k === idx ? 'Current stage' : 'Move to ' + s.name, onclick: () => stepTo(s.name) },
|
|
747
|
-
k < idx ? icon('check', 'sm') : sw(s.cat), s.name))),
|
|
748
|
-
el('span', { class: 'chips2' }, mono(tplName(row, bb) + ' workflow · ' + cat + ' · you decide when it moves'),
|
|
749
|
-
pill({ 'data-ini': 'edit-workflow', 'aria-haspopup': 'dialog', onclick: () => openWorkflow(bb, row) }, icon('settings', 'sm'), 'Edit workflow')));
|
|
744
|
+
// c3-initiative-declutter: three rows above the content. The breadcrumb carries the title, status and warnings;
|
|
745
|
+
// the stepper carries a gear for the workflow and one Tools menu; the tabs carry the counts.
|
|
750
746
|
const kind = kindOf(row, bb);
|
|
751
747
|
const toolList = [...recordToolsFor(kind, cat, bb.build).map((id) => ({ id, label: RECORD_META[id][0], rec: true })), ...(Array.isArray(tools) ? tools : []).map((t) => (typeof t === 'string' ? { id: t } : t)).filter((t) => t && t.id)];
|
|
752
|
-
const
|
|
753
|
-
const
|
|
754
|
-
|
|
748
|
+
const toolsBtn = pill({ 'data-ini': 'tools', 'aria-haspopup': 'menu', 'aria-expanded': 'false', onclick: (e) => openTools(e.currentTarget, row, bb, cat, kind, toolList) }, icon('bolt', 'sm'), 'Tools');
|
|
749
|
+
const stepper = el('div', { class: 'calbar' },
|
|
750
|
+
el('span', { class: 'chips2' },
|
|
751
|
+
el('div', { class: 'seg ini-seg', role: 'group', 'aria-label': 'Workflow position', 'data-ini': 'stepper' },
|
|
752
|
+
...wf.map((s, k) => btn({ 'aria-pressed': String(k === idx), 'aria-current': k === idx ? 'step' : 'false', 'data-ini-step': s.name, title: bb.self ? SELF_WHY : k === idx ? 'Current stage' : 'Move to ' + s.name, onclick: () => stepTo(s.name) },
|
|
753
|
+
k < idx ? icon('check', 'sm') : sw(s.cat), s.name))),
|
|
754
|
+
ib('Edit workflow', 'settings', { 'data-ini': 'edit-workflow', 'aria-haspopup': 'dialog', title: tplName(row, bb) + ' workflow, now in ' + cat + '. You decide when it moves. Edit workflow', onclick: () => openWorkflow(bb, row) })),
|
|
755
|
+
toolsBtn);
|
|
755
756
|
const tabs = INI_TAB_LIST.filter(([k]) => k !== 'tickets' || bb.build).map(([k, l, ic]) => [k, l, ic,
|
|
756
757
|
k === 'breakdown' && work.length ? el('span', { class: 'bdg g' }, String(work.length)) : k === 'tickets' ? el('span', { class: 'bdg g' }, String(work.filter((w) => w.src === 'jira').length)) : null]);
|
|
757
758
|
const host = el('div', { class: 'calbody', 'data-ini-panel': tab });
|
|
@@ -761,16 +762,15 @@ export function mountInitiatives(pane, opts) {
|
|
|
761
762
|
setTimeout(() => { if (token === P.seq && P.expect === ex) { P.ini = null; draw(); } }, 800);
|
|
762
763
|
} else if (ex && ex.id === row.id) P.expect = null;
|
|
763
764
|
const L = { ctx: ctxI, done: (change) => { P.expect = change || null; P.ini = null; P.boards = null; dropItems(); draw(); } };
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
765
|
+
const own = [{ kids: [icon('report', 'sm'), 'Dashboard'], attrs: { 'data-ini-act': 'dashboard' }, onPick: () => ctxI.showTab('rep') },
|
|
766
|
+
bb.build ? { kids: [icon('jira', 'sm'), (row.epics || []).length ? 'Jira epics ' + row.epics.join(', ') : 'Link Jira epics'], attrs: { 'data-ini': 'jira-epics' }, onPick: () => openEpics(bb, row) } : null].filter(Boolean);
|
|
767
|
+
put(root,
|
|
768
|
+
pathCrumbs(bb, row, el('div', { class: 'chips2' },
|
|
769
|
+
el('span', { class: 'chips2', 'data-ini': 'ini-chips' }, statusChip(row.status), el('span', { class: 'chips2', 'data-ini': 'warnings', 'aria-live': 'polite' }, ...warnings.map(warnChip))),
|
|
770
|
+
ib('Actions for ' + row.id, 'more', { 'data-il-menu': row.id, 'aria-haspopup': 'menu', 'aria-expanded': 'false', title: 'Dashboard, Jira epics, pause, cancel, merge or archive', onclick: (e) => pageMenu(e.currentTarget, L, row, own) }))),
|
|
767
771
|
el('div', { class: 'calbody', 'data-ini-page': row.id },
|
|
768
772
|
lifeBanner(L, row),
|
|
769
|
-
|
|
770
|
-
el('div', { class: 'chips2', 'data-ini': 'ini-chips' }, statusChip(row.status), chip(icon('bolt', 'sm'), plural(ep, 'epic') + ', ' + plural(work.length - ep, 'item')), buildTag(bb),
|
|
771
|
-
bb.build ? pill({ 'data-ini': 'jira-epics', 'aria-haspopup': 'dialog', title: 'The Jira epics of this initiative', onclick: () => openEpics(bb, row) }, icon('jira', 'sm'), (row.epics || []).length ? 'Jira epics ' + row.epics.join(', ') : 'Link Jira epics') : null,
|
|
772
|
-
row.lane ? tag(row.lane) : null, el('span', { class: 'chips2', 'data-ini': 'warnings', 'aria-live': 'polite' }, ...warnings.map(warnChip))),
|
|
773
|
-
stepper, bb.self ? note(SELF_WHY) : null, strip,
|
|
773
|
+
stepper, bb.self ? note(SELF_WHY) : null,
|
|
774
774
|
segTabs(tabs, tab, (k) => go({ view: 'ini', board: bb.id, id: row.id, tab: k }), 'Initiative views', { 'data-ini': 'ini-tabs' }),
|
|
775
775
|
host));
|
|
776
776
|
if (P.focusStep) { const f = stepper.querySelector('[data-ini-step="' + CSS.escape(P.focusStep) + '"]'); P.focusStep = null; if (f) f.focus(); }
|
|
@@ -809,16 +809,17 @@ export function mountInitiatives(pane, opts) {
|
|
|
809
809
|
const n = root.querySelector('[data-ini-tool-card="' + CSS.escape(id) + '"]');
|
|
810
810
|
if (n) { n.scrollIntoView({ behavior: 'smooth', block: 'start' }); n.classList.add('ini-flash'); setTimeout(() => n.classList.remove('ini-flash'), 1400); }
|
|
811
811
|
}
|
|
812
|
-
|
|
812
|
+
/* One Tools menu: this stage's tools first, then the other stages' tools under their stage names. */
|
|
813
|
+
function openTools(anchor, row, b, cat, kind, here) {
|
|
813
814
|
const byCat = (P.ini && P.ini.toolsByCat) || {};
|
|
814
815
|
const recOf = (c) => recordToolsFor(kind, c, b.build).map((id) => ({ id, label: RECORD_META[id][0], rec: true }));
|
|
815
|
-
const seen = new Set([...(byCat[cat] || []), ...recOf(cat)].map((t) => t.id));
|
|
816
|
-
const items = [];
|
|
816
|
+
const seen = new Set([...(byCat[cat] || []), ...recOf(cat), ...here].map((t) => t.id));
|
|
817
|
+
const items = here.length ? [{ group: 'For ' + row.stage }, ...here.map((t) => ({ kids: [icon(t.rec ? RECORD_META[t.id][1] : toolIcon(t.id), 'sm'), toolLabel(t)], current: P.loc.tool === t.id, attrs: { 'data-ini-tool': t.id }, onPick: () => pickTool(t.id) }))] : [];
|
|
817
818
|
for (const s of wfOf(row, b)) {
|
|
818
819
|
const ks = [...recOf(s.cat), ...(byCat[s.cat] || [])].filter((t) => !seen.has(t.id)); ks.forEach((t) => seen.add(t.id));
|
|
819
820
|
if (ks.length) items.push({ group: s.name }, ...ks.map((t) => ({ kids: [icon(t.rec ? RECORD_META[t.id][1] : toolIcon(t.id), 'sm'), toolLabel(t)], attrs: { 'data-ini-moretool': t.id }, onPick: () => go({ ...P.loc, tab: 'overview', tool: t.id }) })));
|
|
820
821
|
}
|
|
821
|
-
openMenu(anchor, { label: '
|
|
822
|
+
openMenu(anchor, { label: 'Tools', items: items.length ? items : [{ label: 'No tools for this workflow.', disabled: true, onPick: () => {} }] });
|
|
822
823
|
}
|
|
823
824
|
async function stepTo(stage) {
|
|
824
825
|
const ini = P.ini; if (!ini) return;
|