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.
@@ -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-prdb:focus-visible, .bd-lho:focus-visible, .tg-pgo:focus-visible { outline: 2px solid var(--cz-blue); outline-offset: 2px; border-radius: 10px; }
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; cursor: pointer; flex: 1; display: flex; align-items: center; gap: 10px; font-size: var(--fs-3); font-weight: 800; color: var(--cz-ink); min-height: 28px; }
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
- return t && t !== s ? { b: t, s } : { b: '', s };
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');
@@ -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
- const re = new RegExp(`[/'"\`]${reEsc(stemOf(f))}\\.m?js['"\`]|/${reEsc(stemOf(f))}['"\`]`);
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) {
@@ -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
+ }