create-pmos 0.216.0 → 0.218.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pmos",
3
- "version": "0.216.0",
3
+ "version": "0.218.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": {
@@ -1,3 +1,3 @@
1
- kernel-source: pmos@da1c3aa
1
+ kernel-source: pmos@e9a15ea
2
2
  assembled: 2026-10-14
3
3
  mode: vendor
@@ -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-14T04:16:13Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-14T04:40:51Z"}
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-14T04:16:13Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-14T04:40:51Z"}
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-14T04:16:13Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-14T04:40:51Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -261,6 +261,24 @@ function startShell(name) {
261
261
  }, cvSel, 10000));
262
262
  ok('S2 the tool line lists each call with its target', await page.evaluate((s) => [...document.querySelectorAll(s + ' details[data-chat="tool"] li')].map((li) => li.querySelector('b').textContent + ' ' + ((li.querySelector('span') || {}).textContent || '')).join() === 'Bash git log', cvSel));
263
263
  ok('S3 the status reads Your turn and the meta names the model', await page.evaluate(() => (document.querySelector('[data-cv="status"]') || {}).textContent === 'Your turn' && /claude-test/.test((document.querySelector('[data-cv="meta"]') || {}).textContent || '')));
264
+ // at Whole the list of sessions shows: each row's title sits above its subtitle, both at the row's left, and rows don't overlap
265
+ await page.click('[data-c3-size="whole"]');
266
+ await sleep(250);
267
+ const lay = await page.evaluate((s) => {
268
+ const rows = [...document.querySelectorAll('[data-c3-sess-list] [data-c3-sess-pick]')].map((b) => {
269
+ const r = b.getBoundingClientRect(), n = b.querySelector('.nm').getBoundingClientRect(), u = b.querySelector('.sub').getBoundingClientRect();
270
+ return { top: r.top, bottom: r.bottom, left: r.left, nl: n.left, nb: n.bottom, ul: u.left, ut: u.top, ub: u.bottom };
271
+ });
272
+ const f = document.querySelector(s + ' [data-cv="feed"]').getBoundingClientRect();
273
+ const m = document.querySelector(s + ' [data-chat="me"],' + s + ' [data-chat="bot"]');
274
+ const w = document.querySelector(s + ' [data-cv="feed"] > .wrap').getBoundingClientRect();
275
+ return { rows, gap: w.left - f.left, shown: !!m && m.getBoundingClientRect().height > 0 };
276
+ }, cvSel);
277
+ ok('S3b the list rows are laid out cleanly (title over subtitle, left aligned, no overlap)', lay.rows.length > 0 && lay.rows.every((r, i) =>
278
+ r.nb <= r.ut + 1 && r.ub <= r.bottom && Math.abs(r.nl - r.ul) < 1 && r.nl - r.left < 14 && (i === 0 || r.top >= lay.rows[i - 1].bottom - 1)), JSON.stringify(lay.rows));
279
+ ok('S3c at Whole the conversation starts at the left of its pane, next to the list', lay.shown && lay.gap < 16, String(lay.gap));
280
+ await page.click('[data-c3-size="half"]');
281
+ await sleep(150);
264
282
  await page.fill('[data-cv="msg"]', 'echo c3-conv-$((5*5))');
265
283
  await page.press('[data-cv="msg"]', 'Enter');
266
284
  ok('S4 a sent message shows at once', await waitFor(page, () => [...document.querySelectorAll('[data-c3-conv] [data-chat="me"]')].some((n) => /c3-conv-/.test(n.textContent)), null, 3000));
@@ -71,7 +71,7 @@ A shell where Claude has not started says "No Claude in this shell yet". Press *
71
71
 
72
72
  If PMOS cannot find the transcript of a Claude session, the view says so, and it does not show the transcript of a different session. Read the session in the terminal. PMOS looks for transcripts in `~/.claude`, in each `~/.claude-*` profile folder, and in the folder that `CLAUDE_CONFIG_DIR` names.
73
73
 
74
- At **2/3** and **Whole**, the sheet also shows a list of your running sessions at the left. Press a session to open it.
74
+ At **2/3** and **Whole**, the sheet also shows a list of your running sessions at the left. Each row shows the session name and, below it, the agent and its state. Press a session to open it. The conversation starts next to the list.
75
75
 
76
76
  ### The scratchpad
77
77
 
@@ -681,7 +681,7 @@ function sprintsOf(snapshots, onBoard = () => true, whole = null, notes = null,
681
681
  sp.summarisedTickets = Number.isInteger(n.tickets) ? n.tickets : null;
682
682
  sp.closed = sp.state === 'closed' && n.closed ? { ...n.closed } : null;
683
683
  sp.frozen = sp.state === 'closed' && n.state === 'closed';
684
- sp.brief = sp.state !== 'closed' && n.brief ? n.brief : null;
684
+ sp.brief = sp.state === 'active' && n.brief ? n.brief : null; // the sprint brief is the active sprint's only
685
685
  sp.hit = sp.state === 'closed' && n.hit ? n.hit : null;
686
686
  sp.got = sp.state === 'closed' && n.got ? n.got : null;
687
687
  } else {
@@ -691,7 +691,10 @@ function sprintsOf(snapshots, onBoard = () => true, whole = null, notes = null,
691
691
  }
692
692
  }
693
693
  const order = { active: 0, future: 1, closed: 2 };
694
- const sprints = [...byName.values()].sort((a, b) => ((order[a.state] ?? 3) - (order[b.state] ?? 3)) || a.name.localeCompare(b.name));
694
+ // Two active sprints: the one Jira dated and gave an id comes first, then the larger one; the name only breaks a tie.
695
+ const real = (sp) => (sp.start || sp.end || wt.some((t) => t && t.sprint && t.sprint.id && String(t.sprint.name) === sp.name) ? 1 : 0);
696
+ const sprints = [...byName.values()].sort((a, b) => ((order[a.state] ?? 3) - (order[b.state] ?? 3))
697
+ || (a.state === 'active' ? (real(b) - real(a)) || (b.issues.length - a.issues.length) : 0) || a.name.localeCompare(b.name));
695
698
  for (const sp of sprints) sp.issues.sort((a, b) => a.key.localeCompare(b.key, undefined, { numeric: true }));
696
699
  // Counted by PMOS, never written by the agent: each sprint's points and status flow, the velocity of the last four closed
697
700
  // sprints, and the active sprint's pace and forecast (the sprint brief).
@@ -89,20 +89,24 @@ function fixVersionsOf(v) {
89
89
  return [...new Set(list.map((x) => clip(nameOf(x), 80)).filter(Boolean))];
90
90
  }
91
91
  const SPRINT_STATES = ['active', 'future', 'closed'];
92
- /** One sprint, from an object { name, state } or a bare name. */
92
+ /** A name that says "no sprint" ("Future", "Backlog", "No sprint"): a ticket planned for later, which is in no sprint. */
93
+ const NOT_A_SPRINT = /^(future|next|later|planned|unplanned|unscheduled|backlog|icebox|none|null|no sprint|not in a sprint|unassigned)( sprints?)?$/i;
94
+ /** One sprint, from an object { name, state } or a bare name. Only Jira's own state makes a sprint active: a bare name or an
95
+ * unknown state is a planned sprint, and a "Future" or "Backlog" with no Jira id is no sprint (the backlog). */
93
96
  function sprintOne(v) {
94
97
  if (!v) return null;
95
- if (isStr(v)) { const name = clip(oneLine(v), 80); return name ? { name, state: 'active' } : null; }
98
+ if (isStr(v)) { const name = clip(oneLine(v), 80); return name && !NOT_A_SPRINT.test(name) ? { name, state: 'future' } : null; }
96
99
  if (typeof v !== 'object') return null;
97
100
  const name = clip(oneLine(v.name), 80);
98
101
  const state = String(v.state || '').toLowerCase();
99
102
  if (!name) return null;
100
- const out = { name, state: SPRINT_STATES.includes(state) ? state : 'active' };
103
+ const out = { name, state: SPRINT_STATES.includes(state) ? state : 'future' };
101
104
  if ((Number.isInteger(v.id) && v.id > 0) || (isStr(v.id) && /^\d{1,12}$/.test(v.id))) out.id = String(v.id);
102
105
  for (const [k, from] of [['start', ['startDate', 'start']], ['end', ['endDate', 'end']], ['completed', ['completeDate', 'completed']]]) {
103
106
  const d = dayOf(from.map((f) => v[f]).find((x) => x != null));
104
107
  if (d) out[k] = d;
105
108
  }
109
+ if (!out.id && NOT_A_SPRINT.test(name)) return null;
106
110
  const goal = clip(oneLine(v.goal), 300); // Jira's sprint goal, often empty or broad (sprint-goal-summary)
107
111
  if (goal) out.goal = goal;
108
112
  return out;
@@ -235,7 +239,8 @@ function readSnapshot(root, initiative) {
235
239
  let data;
236
240
  try { data = JSON.parse(m[1]); } catch { return null; }
237
241
  const tickets = new Map();
238
- for (const r of Array.isArray(data.tickets) ? data.tickets : []) if (r && KEY_RE.test(String(r.key || ''))) tickets.set(r.key, r);
242
+ // The sprint is read again, so a reading saved before the "Future" fix is no longer an active sprint.
243
+ for (const r of Array.isArray(data.tickets) ? data.tickets : []) if (r && KEY_RE.test(String(r.key || ''))) tickets.set(r.key, { ...r, sprint: r.sprint ? sprintOne(r.sprint) : null });
239
244
  return { fetchedAt: isStr(data.fetched_at) ? data.fetched_at : null, query: isStr(data.query) ? data.query : null, tickets };
240
245
  }
241
246
 
@@ -415,7 +420,7 @@ function noteOne(x) {
415
420
  const note = { name, state, goal, summary, themes };
416
421
  // The sprint brief (sprint-goal-summary, mockup-c3-sprint-brief): where the sprint stands, the issues to resolve with a proposed
417
422
  // plan each, what is done and the asks for the PM. Written by the agent from the tickets; optional, so a note without one still loads.
418
- if (state !== 'closed' && x.brief && typeof x.brief === 'object') {
423
+ if (state === 'active' && x.brief && typeof x.brief === 'object') {
419
424
  const r = briefOf(x.brief, name);
420
425
  if (r.error) return r;
421
426
  note.brief = r.brief;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.216.0",
3
+ "version": "0.218.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.216.0",
9
+ "version": "0.218.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@xterm/addon-fit": "^0.11.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.216.0",
3
+ "version": "0.218.0",
4
4
  "private": true,
5
5
  "description": "PMOS Terminal Workspace — local companion hosting real PTYs beside the library and gates (D62)",
6
6
  "license": "MIT",
@@ -1701,7 +1701,8 @@
1701
1701
  .c3 .c3t-cv-panes .c3t-tw{flex:1 1 0;height:auto;min-height:0}
1702
1702
  .c3 .c3t-cv-list{display:none;border-right:1px solid var(--border);overflow:auto;padding:0 var(--sp-2) 0 0;margin-right:var(--sp-3);gap:2px;align-content:start}
1703
1703
  .c3 .c3t-cv-list .eyebrow{padding:var(--sp-2) 10px 4px;font:500 var(--fs-1) var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--muted-foreground)}
1704
- .c3 .c3t-cv-list button{display:grid;gap:2px;text-align:left;border:0;background:none;padding:8px 10px;border-radius:8px;cursor:pointer;color:var(--foreground);font:inherit;font-size:var(--fs-3);width:100%;min-width:0}
1704
+ .c3 .c3t-cv-list button{display:grid;gap:2px;justify-content:stretch;justify-items:start;align-content:start;text-align:left;border:0;background:none;padding:8px 10px;border-radius:8px;cursor:pointer;color:var(--foreground);font:inherit;font-size:var(--fs-3);line-height:1.3;width:100%;min-width:0;height:auto;min-height:0}
1705
+ .c3 .c3t-cv-list button>span{max-width:100%}
1705
1706
  .c3 .c3t-cv-list button .nm,.c3 .c3t-cv-list button .sub{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1706
1707
  .c3 .c3t-cv-list button .sub{font:var(--fs-1) var(--mono);color:var(--muted-foreground)}
1707
1708
  .c3 .c3t-cv-list button[aria-current="true"]{background:color-mix(in oklab,var(--acc) 12%,transparent)}
@@ -1712,7 +1713,7 @@
1712
1713
  .c3 .c3t-cv-meta .tg[data-tone="muted"]{--tc:var(--muted-foreground)}
1713
1714
  .c3 .c3t-cv-meta .tg[data-tone="warning"]{--tc:var(--warning)}
1714
1715
  .c3 .c3t-cv-feed{flex:1 1 0;min-height:0;overflow:auto;padding:var(--sp-3) 2px}
1715
- .c3 .c3t-cv-feed>.wrap{max-width:760px;margin:0 auto;display:flex;flex-direction:column;gap:var(--sp-2)}
1716
+ .c3 .c3t-cv-feed>.wrap{max-width:820px;margin:0;display:flex;flex-direction:column;gap:var(--sp-2)}
1716
1717
  .c3 .c3t-cv .m{padding:8px 14px;border-radius:8px;max-width:85%;overflow-wrap:anywhere;font-size:var(--fs-3);line-height:1.5}
1717
1718
  .c3 .c3t-cv .m.me{align-self:flex-end;background:var(--acc);color:var(--on-accent);white-space:pre-wrap}
1718
1719
  .c3 .c3t-cv .m.bot{align-self:flex-start;background:var(--muted)}
@@ -27,7 +27,7 @@ Procedure:
27
27
  - `type`: the issue type's name, for example `Epic`, `Story`, `Task` or `Bug`.
28
28
  - `parent`: the parent issue's key (the epic of a story), or `null`.
29
29
  - `category`: the status category, `new`, `indeterminate` or `done`.
30
- - `sprint`: `{"id": "42", "name": "Sprint 42", "state": "active", "start": "2026-10-01", "end": "2026-10-14"}` for the active sprint, `"future"` for a planned one, or `null` when the issue is in the backlog. Copy `id`, `start`, `end`, `completed` (a closed sprint's completion date) and `goal` (the sprint goal, as Jira writes it) from Jira's sprint field; leave out any that Jira does not give.
30
+ - `sprint`: Jira's sprint, as `{"id": "42", "name": "Sprint 42", "state": "active", "start": "2026-10-01", "end": "2026-10-14"}`. Copy `state` as Jira gives it: `"active"`, `"future"` (a planned sprint) or `"closed"`. Use `null` when the issue is in no sprint: never write a made-up sprint such as `"Future"` or `"Backlog"` for it. Copy `id`, `start`, `end`, `completed` (a closed sprint's completion date) and `goal` (the sprint goal, as Jira writes it) from Jira's sprint field; leave out any that Jira does not give.
31
31
  - `points`: the story points as a number, or `null` when none are set.
32
32
  - `resolved`: the resolution date as `YYYY-MM-DD`, or `null` when the issue is not resolved.
33
33
  - `subtasks`: `{"done": 2, "total": 5}`.