create-pmos 0.508.0 → 0.510.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 +33 -27
- package/payload/AGENTS.md +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/board-programs-test.js +3 -0
- package/workspace/changelog.json +32 -0
- package/workspace/guide/index.md +1 -0
- package/workspace/guide/outcomes.md +39 -0
- package/workspace/guide/programs.md +2 -1
- package/workspace/guide/settings.md +2 -2
- package/workspace/lib/initiative-cli.js +3 -1
- package/workspace/lib/initiative-writer.js +52 -1
- package/workspace/lib/kit-update.js +16 -4
- package/workspace/lib/outcome-props.js +120 -0
- package/workspace/lib/outcomes-cli.js +138 -0
- package/workspace/lib/outcomes.js +312 -0
- package/workspace/lib/pmos-verbs.js +3 -0
- package/workspace/lib/proposals.js +14 -4
- package/workspace/lib/routes-proposals.js +6 -0
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/bd-prog.js +10 -5
- package/workspace/public/pm/cozy-board.css +8 -2
- package/workspace/public/pm/ini-props.js +5 -2
- package/workspace/public/pm/premium-board.js +4 -0
- package/workspace/public/pm/readonly.js +3 -1
- package/workspace/public/pm/upd-weeks.js +6 -2
- package/workspace/run-tests.js +4 -2
- package/workspace/server.js +14 -0
- package/workspace/test-lists/outcome-for-every-initiative.json +5 -0
|
@@ -1085,7 +1085,7 @@ html[data-theme="dark"] body.bd-page { --bd-prog: rgb(180,170,240); --bd-progbg:
|
|
|
1085
1085
|
.bd-rdy i { display: block; width: 6px; height: 6px; border-radius: 1px; transform: rotate(45deg); background: var(--bd-plan); }
|
|
1086
1086
|
.bd-rdy i.on { background: var(--bd-prog); }
|
|
1087
1087
|
.bd-fold { color: var(--cz-ink3); }
|
|
1088
|
-
.bd-fold:focus-visible, .bt-pgo:focus-visible, .bd-
|
|
1088
|
+
.bd-fold:focus-visible, .bt-pgo:focus-visible, .bd-prdo:focus-visible, .bd-prdf:focus-visible, .bd-lho:focus-visible, .tg-pgo:focus-visible { outline: 2px solid var(--cz-blue); outline-offset: 2px; border-radius: 10px; }
|
|
1089
1089
|
/* the grouping control in the toolbar (PgBoardProduct: "By product" on in violet) */
|
|
1090
1090
|
.bd-grpw2 { position: relative; display: inline-flex; align-items: center; }
|
|
1091
1091
|
.bd-grpw2[hidden] { display: none; }
|
|
@@ -1119,7 +1119,13 @@ html[data-theme="dark"] body.bd-page { --bd-prog: rgb(180,170,240); --bd-progbg:
|
|
|
1119
1119
|
.bt-psb > span, .tg-psb > span { display: inline-flex; align-items: center; gap: 8px; }
|
|
1120
1120
|
.bt-psb::before, .tg-psb::before { content: ""; position: absolute; left: 26px; top: 0; bottom: 0; width: 2px; background: var(--bd-progln); }
|
|
1121
1121
|
.bt-prd, .tg-prd { display: flex; align-items: center; padding: 8px 14px; background: var(--bd-row); border-top: 1px solid var(--cz-hair); }
|
|
1122
|
-
.bd-prdb { all: unset; box-sizing: border-box;
|
|
1122
|
+
.bd-prdb { all: unset; box-sizing: border-box; flex: 1; display: flex; align-items: center; gap: 10px; font-size: var(--fs-3); font-weight: 800; color: var(--cz-ink); min-height: 28px; }
|
|
1123
|
+
/* the tile and name open the product's page (batch 26 item 3, as Home's header); the rest of the head, its dots, count and chevron, folds it */
|
|
1124
|
+
.bd-prdo, .bd-prdf { all: unset; box-sizing: border-box; cursor: pointer; display: flex; align-items: center; gap: 10px; min-height: 28px; }
|
|
1125
|
+
.bd-prdo { flex: 0 1 auto; min-width: 0; }
|
|
1126
|
+
.bd-prdo > span:last-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1127
|
+
.bd-prdf { flex: 1; align-self: stretch; justify-content: flex-end; }
|
|
1128
|
+
@media (hover: hover) { .bd-prdo:hover > span:last-child, .bd-lh.pd .bd-lho:hover > span:last-child { color: var(--cz-blueink); } }
|
|
1123
1129
|
.bd-prdb svg { width: 15px; height: 15px; stroke-width: 2; color: var(--cz-ink3); margin-left: auto; }
|
|
1124
1130
|
/* a product head's goal dots (PgBoard .gd): Home's source and look (cozy-today.css .hv-gd) */
|
|
1125
1131
|
.bd-prdb .n { font-size: var(--fs-2); font-weight: 700; color: var(--cz-ink3); margin-left: auto; }
|
|
@@ -27,6 +27,7 @@ const I = {
|
|
|
27
27
|
doc: 'M7 3h7l5 5v13H7zM14 3v5h5', tasks: 'M8 6h12M8 12h12M8 18h12M4 6h.01M4 12h.01M4 18h.01', stage: 'M5 21V4M5 4h12l-2 4 2 4H5',
|
|
28
28
|
link: 'M10 14a4 4 0 0 0 5.66 0l3-3a4 4 0 0 0-5.66-5.66l-1 1M14 10a4 4 0 0 0-5.66 0l-3 3a4 4 0 0 0 5.66 5.66l1-1',
|
|
29
29
|
cal: 'M4 5h16v15H4zM8 3v4M16 3v4M4 10h16', done: 'M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18M8 12l3 3 5-6', name: 'M4 20h4L19 9l-4-4L4 16zM13 7l4 4',
|
|
30
|
+
goal: 'M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18M12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10M12 11a1 1 0 1 0 0 2 1 1 0 0 0 0-2',
|
|
30
31
|
ask: 'M4 5h16v11H9l-5 4z', maybe: 'M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18M9.5 9.5a2.5 2.5 0 1 1 3.5 2.3c-.6.3-1 .9-1 1.6M12 17h.01', keep: 'M5 12l5 5 9-10', ignore: 'M5 12h14',
|
|
31
32
|
};
|
|
32
33
|
const MON = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
@@ -35,7 +36,7 @@ const md = (iso) => { const m = DAY.exec(String(iso || '')); return m ? MON[+m[2
|
|
|
35
36
|
const span = (a, b) => (a && b && a !== b ? md(a) + ' – ' + md(b) : md(a || b));
|
|
36
37
|
const plural = (n, a, b) => n + ' ' + (n === 1 ? a : b);
|
|
37
38
|
const clip = (s, n) => { s = String(s || ''); return s.length > n ? s.slice(0, n - 1) + '…' : s; };
|
|
38
|
-
const GROUP = { found: 'Found elsewhere', dates: 'Dates', status: 'Looks done', names: 'Names', links: 'Links', grill: 'Claude grilled it' };
|
|
39
|
+
const GROUP = { outcome: 'Expected outcome', found: 'Found elsewhere', dates: 'Dates', status: 'Looks done', names: 'Names', links: 'Links', grill: 'Claude grilled it' };
|
|
39
40
|
|
|
40
41
|
/* ── the chip and the live look ── */
|
|
41
42
|
export function chipOf(p) {
|
|
@@ -93,9 +94,10 @@ const titleOf = (p) => {
|
|
|
93
94
|
if (p.group === 'found') return p.type === 'stage' ? 'Stage: ' + ((p.data && p.data.stage) || '') : p.type === 'docs' ? plural(n, 'document', 'documents') : p.type === 'tasks' ? plural(n, 'task', 'tasks') : p.type === 'link' ? 'Already in another initiative' : p.type === 'maybe' ? 'Maybe this initiative' : plural(n, 'thing', 'things') + ' not placed';
|
|
94
95
|
if (p.group === 'status') return plural(n, 'task looks', 'tasks look') + ' done';
|
|
95
96
|
if (p.group === 'names') return 'Tidy ' + plural(n, 'name', 'names');
|
|
97
|
+
if (p.group === 'outcome') return p.type === 'date' ? 'A date to check the outcome' : 'What should change once it ships';
|
|
96
98
|
return '';
|
|
97
99
|
};
|
|
98
|
-
const iconOf = (p) => (p.group === 'found' ? (p.type === 'stage' ? I.stage : p.type === 'docs' ? I.doc : p.type === 'tasks' ? I.tasks : p.type === 'link' ? I.link : I.maybe)
|
|
100
|
+
const iconOf = (p) => (p.group === 'outcome' ? I.goal : p.group === 'found' ? (p.type === 'stage' ? I.stage : p.type === 'docs' ? I.doc : p.type === 'tasks' ? I.tasks : p.type === 'link' ? I.link : I.maybe)
|
|
99
101
|
: p.group === 'dates' ? I.cal : p.group === 'status' ? I.done : p.group === 'names' ? I.name : I.link);
|
|
100
102
|
const ib = (attr, label, icon, cls = '') => `<button type="button" class="pp-ib${cls ? ' ' + cls : ''}" ${attr} aria-label="${esc(label)}" title="${esc(label)}">${SVG(icon, 16, 2.2)}</button>`;
|
|
101
103
|
function itemHtml(p, x) {
|
|
@@ -107,6 +109,7 @@ function itemHtml(p, x) {
|
|
|
107
109
|
else { t = x.title; m = x.other ? 'In ' + x.other.title : x.why || ''; }
|
|
108
110
|
} else if (p.group === 'dates') { t = x.title || x.task; m = (x.from && x.from.due ? md(x.from.due) + ' → ' : '') + span(x.to && x.to.start, x.to && x.to.due) + (x.jira ? ' · Jira: a drafted comment' : '') + (x.ms ? ' · ◇ a promise' : ''); }
|
|
109
111
|
else if (p.group === 'status') { t = x.title || taskTitle(p.ini, x.task); m = x.jira ? 'Jira: a drafted comment' : ''; }
|
|
112
|
+
else if (p.group === 'outcome') { t = x.title; m = x.due ? 'Check on ' + md(x.due) : ''; }
|
|
110
113
|
else if (p.group === 'names') { t = x.to; m = 'Was: ' + (x.from || '') + (x.jira ? ' · Jira: a drafted comment' : ''); }
|
|
111
114
|
return `<div class="pp-it"><span class="w"><b>${esc(clip(t, 90))}</b>${m ? `<small>${esc(m)}</small>` : ''}</span>${x.sig ? ib(`data-pp-dropit="${esc(x.sig)}"`, 'Leave this one out', I.x) : ''}</div>`;
|
|
112
115
|
}
|
|
@@ -639,6 +639,8 @@ $('#ibBody').addEventListener('click', (e) => {
|
|
|
639
639
|
const tk = e.target.closest('[data-tk]'); if (tk) { openTicket(tk.dataset.ini, tk.dataset.tk, tk); return; }
|
|
640
640
|
if (e.target.closest('[data-act], [data-hold], [data-ms-pick], [data-fc-pick]')) return; // pm/bd-acts.js handles its own presses (a ⏸ on the timeline opens its hold there; a ◇ its milestones, pm/bd-pin-card.js)
|
|
641
641
|
const pg = e.target.closest('[data-prog]'); if (pg) { PG.openProgram(pg.dataset.prog, pg); return; }
|
|
642
|
+
// a product head's tile and name (batch 26 item 3): that product's page in Home's Viewer, the one place the product page is drawn
|
|
643
|
+
const pd = e.target.closest('[data-prodopen]'); if (pd) { location.assign(productHref(pd.dataset.prodws)); return; }
|
|
642
644
|
// a counted milestone ◇ lists its milestones right under it; on a phone the row's bar lane opens them in a sheet (batch 11 item 3)
|
|
643
645
|
const mc = e.target.closest('[data-ms-row]'); if (mc) { msPop(mc, rowOf(mc.dataset.msRow), mc.dataset.msIds.split(' ')); return; }
|
|
644
646
|
// a product lane's counted ◇ lists its milestones with their initiatives; on a phone a tap on the lane lists them all in a sheet (batch 11 item 17)
|
|
@@ -693,6 +695,8 @@ function askHref(r, q) {
|
|
|
693
695
|
const ws = (r && r.ws) || M.board.ws; if (ws) p.set('ws', ws);
|
|
694
696
|
return '/premium.html?' + p.toString();
|
|
695
697
|
}
|
|
698
|
+
/** A product's page (Home's Viewer, home-app.js openProductPage): Home on that product's workspace with ?ini=product. ws '': this board's own. */
|
|
699
|
+
const productHref = (ws) => { const w = ws || (M && !M.board.all ? M.board.ws : ''); return '/premium.html?' + (w ? 'ws=' + encodeURIComponent(w) + '&' : '') + 'ini=product'; };
|
|
696
700
|
const pageHref = (r) => { const ws = r.ws || M.board.ws, b = r.boardId || M.board.id; return '/premium-initiative.html?id=' + encodeURIComponent(r.id) + (ws ? '&ws=' + encodeURIComponent(ws) + '&board=' + encodeURIComponent(b) : ''); };
|
|
697
701
|
|
|
698
702
|
/* ── the initiative and its Jira ticket open in the Viewer's shell A, floating over the page (pm/bd-ini-panel.js; canvas XvABoard, XvOptAJira).
|
|
@@ -47,7 +47,9 @@ const REMOVE = [
|
|
|
47
47
|
* milestone opens the program's charter in the workspace; a share does not hold the charter. */
|
|
48
48
|
const PLAIN = ['button.dl-e[aria-label*="Program milestone"]:not([aria-label*="press to open"])', 'button.cz-vw-rn', 'button.iv-tt', 'button.iv-dc', 'button.iv-stc', 'button.iv-dtc', 'button.iv-wf', 'button.iv-cb', 'button.tg-end',
|
|
49
49
|
// the program page (batch 21 item 2): a task's check box, a goal (its Viewer is in Intelligence), a report (its file is the publisher's), a program check
|
|
50
|
-
'button.pp-ck', 'button.pv-gr', 'button.db-rep', 'button.pp-dm'
|
|
50
|
+
'button.pp-ck', 'button.pv-gr', 'button.db-rep', 'button.pp-dm',
|
|
51
|
+
// a product head's tile and name (batch 26 item 3): its page is in the publisher's Home, which a share does not hold
|
|
52
|
+
'button[data-prodopen]'];
|
|
51
53
|
/** A fold whose body goes with it: Documents and Linked are not listed. */
|
|
52
54
|
const WITH_BODY = ['[data-iv-fold="docs"]', '[data-iv-docs]', '[data-iv-fold="linked"]'];
|
|
53
55
|
|
|
@@ -15,9 +15,13 @@ export function kindOf(p) {
|
|
|
15
15
|
if (p && (p.k === 'new' || p.k === 'imp' || p.k === 'fix' || p.k === 'x')) return p.k;
|
|
16
16
|
return FIX.test((p && (p.t || p.s)) || '') ? 'fix' : 'imp';
|
|
17
17
|
}
|
|
18
|
+
/** A title and a summary are the same line when they differ only in code ticks, case, spacing or the closing stop
|
|
19
|
+
* ("`pmos x` names y" and "Pmos x names y."): the line is then the title once, without its ticks, so a command keeps its own case. */
|
|
20
|
+
const same = (a, b) => { const f = (x) => x.replace(/`/g, '').replace(/[.!?]+$/, '').replace(/\s+/g, ' ').trim().toLowerCase(); return f(a) === f(b); };
|
|
18
21
|
export function lineOf(p) {
|
|
19
|
-
const s = String((p && p.s) || ''), t = String((p && p.t) || '');
|
|
20
|
-
|
|
22
|
+
const s = String((p && p.s) || ''), t = String((p && p.t) || '').replace(/`([^`]*)`/g, '$1');
|
|
23
|
+
if (!t) return { b: '', s };
|
|
24
|
+
return same(t, s) ? { b: '', s: t } : { b: t, s };
|
|
21
25
|
}
|
|
22
26
|
const mondayOf = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate() - ((d.getDay() + 6) % 7));
|
|
23
27
|
const keyOf = (d) => d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
|
package/workspace/run-tests.js
CHANGED
|
@@ -445,8 +445,10 @@ function traceChanges(changes, ctx) {
|
|
|
445
445
|
return w;
|
|
446
446
|
};
|
|
447
447
|
const users1 = (f) => {
|
|
448
|
-
// a path or a file name: './x', '/x.js' or 'x.js'. A bare quoted word ('grill') is a string, not a use of lib/grill.js
|
|
449
|
-
|
|
448
|
+
// a path or a file name: './x', '../lib/x', 'lib/x', '/x.js' or 'x.js'. A bare quoted word ('grill') is a string, not a use of lib/grill.js,
|
|
449
|
+
// and neither is a folder path that ends in the same word ('memory/proposals' in lib/headless.js is not lib/proposals.js: it sent every
|
|
450
|
+
// change of proposals.js to every suite).
|
|
451
|
+
const re = new RegExp(`[/'"\`]${reEsc(stemOf(f))}\\.m?js['"\`]|(?:\\.\\.?|\\blib)/(?:[\\w.-]+/)*${reEsc(stemOf(f))}['"\`]`);
|
|
450
452
|
const pathRe = new RegExp(`/${reEsc(stemOf(f))}(?:\\.m?js)?['"\`]`);
|
|
451
453
|
let n = 0;
|
|
452
454
|
for (const g of code) {
|
package/workspace/server.js
CHANGED
|
@@ -3092,6 +3092,20 @@ const driftRuns = require('./lib/drift-runs.js').create({
|
|
|
3092
3092
|
every: TICK_MS, // its own tick, as often as the server's; only the server that owns the schedules runs it
|
|
3093
3093
|
mayRun: () => { try { return scheduleOwner.mayRun(); } catch { return true; } },
|
|
3094
3094
|
warn: (what, e) => warnWriteFail(what, e) });
|
|
3095
|
+
// outcome-for-every-initiative (D57): once a day Claude drafts each missing expected outcome and check date under the ✦ of the initiative,
|
|
3096
|
+
// from the success criteria of the PRD or the KR (lib/outcome-props.js, lib/outcomes.js). No session and no model; through the shared guard,
|
|
3097
|
+
// its audit in the runtime folder. A ✓ of the PM writes the record. Off with PMOS_OUTCOMES=off or pmos ai off outcomes.
|
|
3098
|
+
const outcomeProps = require('./lib/outcome-props.js').create({
|
|
3099
|
+
workspaces: () => [...workspaces.values()],
|
|
3100
|
+
isLead: (w) => isPlanNeedsTree(w),
|
|
3101
|
+
stateDirFor: (w) => stateDirFor(w),
|
|
3102
|
+
boardOf: (w, id) => require('./lib/routes-boards.js').boardIndex(proposalCtx(), w).of(id),
|
|
3103
|
+
runtime: () => STATE,
|
|
3104
|
+
open: (now) => !proactiveLib.inQuietHours(now, proSettings().quiet),
|
|
3105
|
+
env: process.env,
|
|
3106
|
+
every: TICK_MS,
|
|
3107
|
+
mayRun: () => { try { return scheduleOwner.mayRun(); } catch { return true; } },
|
|
3108
|
+
warn: (what, e) => warnWriteFail(what, e) });
|
|
3095
3109
|
// batch 16 item 6 ("Claude runs the OKRs"): Claude reads each goal with its recipe (lib/metric-read-runs.js) when its cadence comes round, files the
|
|
3096
3110
|
// value with its source and time, says honestly what it could not read and drafts the fix. A broken limit opens one alert: a bell line only when
|
|
3097
3111
|
// the goal's notify is push. At most 2 runs at once, one per product. PMOS_METRIC_READ=off.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "outcome-for-every-initiative: every initiative has an expected outcome and a check date. The scan, the cited drafts, the guard and its replay, the daily filer under the ✦, the record writers, pmos outcomes, check-outcomes --root and ✓ / Undo through the routes (lib/outcomes.js, lib/outcome-props.js, lib/outcomes-cli.js, lib/initiative-writer.js setOutcome, lib/proposals.js group outcome, lib/routes-proposals.js).",
|
|
3
|
+
"modules": ["outcomes.test.js"],
|
|
4
|
+
"seconds": { "outcomes.test.js": 8 }
|
|
5
|
+
}
|