create-pmos 0.53.2 → 0.54.1

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.53.2",
3
+ "version": "0.54.1",
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@439620f
1
+ kernel-source: pmos@4291761
2
2
  assembled: 2026-10-06
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-06T02:40:00Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-06T03:42:06Z"}
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-06T02:40:00Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-06T03:42:06Z"}
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-06T02:40:00Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-06T03:42:06Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -23,6 +23,7 @@ On a desktop the workspace is one frame. [Layout](layout.md) explains each part.
23
23
  |---|---|
24
24
  | What is waiting for you now | Today |
25
25
  | The initiatives of a repository, or of every repository | Initiatives. Choose **All repositories** in Repository ▾ for every repository. |
26
+ | The branches of a repository and the sessions running in each | Repository ▾ → **Branches and sessions**. |
26
27
  | The items of an initiative | Board, or Breakdown in the initiative detail |
27
28
  | Run reports | Reports |
28
29
  | Repositories, Profiles, New sessions (the default CLI and model), Validate consent, Theme | Settings |
@@ -14,6 +14,7 @@ On a desktop screen the workspace is one frame. It has one top bar, the place on
14
14
  The top bar has these parts, from left to right:
15
15
 
16
16
  - **Repository ▾** names the current repository. Press it to choose another repository, or **All repositories** to see the work of every repository at once. All repositories replaces the old Portfolio mode.
17
+ - **Branches and sessions**, at the end of Repository ▾ (or in ⌘K), opens a sheet with every branch of the repository (main first) and how many sessions run in each. Press a branch to see its sessions, open one, end one, or start a session there. **+ branch** makes a branch, and **+ session** starts one with a branch, an agent and a profile. With All repositories, the sheet lists every repository.
17
18
  - **Today, Initiatives, Board and Reports** are the four places. Press one to show it. The Today count shows how many items need you.
18
19
  - **Search or capture ⌘K** opens the command palette. Type to find an initiative, a repository, an open tab or a command.
19
20
  - **Sync** shows only when the current repository is not in sync with its remote. Press it to fetch, bring in the work of the other machine, record your changes and push. If the repository has not fetched in the last 10 minutes, Sync also shows, because a push from another machine is not visible until a fetch. When the repository fetched recently and is in sync, Sync is not shown. If Sync stops, the message names the step that stopped.
@@ -16,6 +16,7 @@ At the top of Today you can list what you want done today. A goal is one line of
16
16
  - Open **Goals for today** and type a goal. Press Enter to add it. A day has at most 7 goals, each at most 140 characters.
17
17
  - Press **Done**, **Drop**, **Reopen** or **Remove** on a goal. A goal is open, done or dropped.
18
18
  - Press **Prepare my day**. The workspace makes the daily 06:45 morning-brief schedule if this repository has none, and runs it now. The session reads your goals, your calendar, and the mentions of you in Slack, Jira and Confluence. It ties each meeting, mention and step that moves a goal to that goal.
19
+ - While the press runs, the button says **Starting…**. When the session starts, its terminal tab opens and the goals section says when it started; **Open the session** opens the tab again. If the session cannot start, the section says why in red.
19
20
  - Each goal shows its steps: yours first, then the agent's. A step comes from the brief, or from a Today card on the goal's initiative. A goal with no step says so. The session does not invent a step.
20
21
  - A step is under a goal only when the brief names the goal, or when the step and the goal name the same initiative. The workspace never matches words.
21
22
  - An agent step that names an initiative has **Prepare**. It shows the step, the branch and the prompt, the same as background preparation. **Queue it** approves that exact prompt. Background preparation must be on in the settings. Otherwise the queue says so and nothing is queued.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.53.2",
3
+ "version": "0.54.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.53.2",
3
+ "version": "0.54.1",
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",
@@ -42,6 +42,7 @@ import { createDockRail } from './dock.js';
42
42
  import { SEL, ensureComponentsCss, iconButton, button, menu, toast } from './primitives.js';
43
43
  import { ensureSprite } from './icons.js';
44
44
  import { COPY as FC } from './frame-copy.js';
45
+ import { registerCommand } from './registry.js';
45
46
 
46
47
  /* The LEGACY layout is the three-column shell of column-layout: repositories with the list under them, the detail, the tabs, and
47
48
  * a list that a mode (skills, memory …) replaces. A phone still draws it until Run 4 of workspace-cockpit-nav, and `?layout=legacy`
@@ -482,9 +483,20 @@ function repoMenu(anchor) {
482
483
  const items = tuples.map((t) => ({ label: t[0], icon: t[0] === cur ? 'check' : null, onSelect: () => chooseRepo(t[0]) }));
483
484
  if (items.length) items.push({ sep: true });
484
485
  items.push({ label: FC.repo.all, icon: fr.scope === 'all' ? 'check' : null, onSelect: chooseAll });
486
+ if (tuples.length) items.push({ sep: true }, { label: FC.repo.branches, onSelect: openBranchesSheet });
485
487
  menu({ anchor, items, ariaLabel: FC.repo.menu });
486
488
  }
487
489
 
490
+ /* Branches and sessions (0.54.1): the legacy panel 1's working trees, which the frame never drew. The current repository,
491
+ * or every repository when the scope is All. */
492
+ function openBranchesSheet() {
493
+ const tuples = mods.repos && typeof mods.repos.repoTuples === 'function' ? mods.repos.repoTuples() : [];
494
+ const cur = fr.scope === 'all' ? null : activeRepo();
495
+ const repos = cur ? [cur] : tuples.map((t) => t[0]);
496
+ import('./branches-sheet.js').then((m) => m.openBranches(repos)).catch((e) => console.error('[pm] could not open the branches:', e));
497
+ }
498
+ if (FRAME) registerCommand({ id: 'branches-open', label: FC.repo.branches, hint: FC.branches.hint, cli: 'git worktree list', run: () => openBranchesSheet() });
499
+
488
500
  /* Sync: the bar shows it only while the current repository is not in sync. The reading is local git (GET
489
501
  * /api/sync/status), re-read a minute after the last one and at once after a Sync. */
490
502
  const syncSeen = new Map(); // repo -> { at, st, pending }
@@ -681,7 +693,7 @@ function frameKey(e) {
681
693
 
682
694
  /* What the suites and the phone chrome reach the frame by. */
683
695
  const frameApi = {
684
- on: true, go, toggleDock, foldDock, openDock, toggleFocus, dockWidth, chooseRepo, chooseAll, openPalette, repoMenu, closeDetail, sync: runSync,
696
+ on: true, go, toggleDock, foldDock, openDock, toggleFocus, dockWidth, chooseRepo, chooseAll, openPalette, repoMenu, openBranchesSheet, closeDetail, sync: runSync,
685
697
  get unsynced() { return !!fr.unsynced; },
686
698
  get dest() { return fr.dest; }, get scope() { return fr.scope; }, get dockOpen() { return dockOpen(); }, get dockMax() { return !!fr.dockMax; },
687
699
  get detailOpen() { return !!state.col2; }, get syncing() { return fr.syncing; }, get needs() { return fr.needs || 0; },
@@ -0,0 +1,56 @@
1
+ /* pm/branches-sheet.js — the working trees of a repository and the sessions in each, in the frame (0.54.1).
2
+ *
3
+ * The legacy panel 1 listed every tree (main first) with its live sessions, `+ branch` and `+ session` (panel-trees.js).
4
+ * The frame never drew panel 1, so from 0.53.0 the branches and their sessions had no place. This sheet draws the same
5
+ * blocks, unchanged: one repository, or every repository when the scope is All. It repaints on every state change while
6
+ * it is open, so a session that starts or ends elsewhere (a terminal, the CLI) shows here within one poll.
7
+ *
8
+ * openBranches(repos) → the sheet. `repos` is the list of repository names to show, in order.
9
+ */
10
+
11
+ import { el, setChildren } from './dom.js';
12
+ import { sheet } from './primitives.js';
13
+ import { state, subscribe } from './state.js';
14
+ import { treesOf } from './panel-repos.js';
15
+ import { treesBlock, branchForm, treeNoteFor, sessionsIn } from './panel-trees.js';
16
+ import { refreshTrees } from './data.js';
17
+ import { COPY } from './frame-copy.js';
18
+
19
+ const live = (s) => s && s.state !== 'exited' && s.shown !== 'exited';
20
+
21
+ function sub(repos) {
22
+ let trees = 0; let sessions = 0;
23
+ for (const r of repos) for (const t of treesOf(r)) { trees++; sessions += sessionsIn(t.ws).filter(live).length; }
24
+ return COPY.branches.sub(trees, sessions);
25
+ }
26
+
27
+ function body(repos) {
28
+ if (!repos.length) return [el('p', { class: 'fr-branches__empty' }, COPY.branches.none)];
29
+ return repos.map((r) => {
30
+ const trees = treesOf(r);
31
+ return el('section', { class: 'fr-branches__repo', 'data-fr-branches': r },
32
+ repos.length > 1 ? el('h3', { class: 'fr-branches__name' }, r) : null,
33
+ branchForm(r),
34
+ trees.length ? treesBlock(r, trees, true) : el('p', { class: 'fr-branches__empty' }, COPY.branches.noTrees),
35
+ treeNoteFor(r));
36
+ });
37
+ }
38
+
39
+ export function openBranches(repos) {
40
+ const list = (repos || []).filter(Boolean);
41
+ const s = sheet({ title: COPY.branches.title, sub: sub(list), body: body(list), attrs: { 'data-fr': 'branches' }, onClose: () => stop() });
42
+ // The trees can change behind the page (a branch made in a terminal): read them once more on open.
43
+ try { Promise.resolve(refreshTrees()).catch(() => {}); } catch { /* no data layer */ }
44
+ let last = null;
45
+ const stop = subscribe(() => {
46
+ if (!s.open) return;
47
+ // Repaint only when something the sheet shows changed, so an input the PM types in keeps its node between polls.
48
+ const key = JSON.stringify([state.branchForm && { ...state.branchForm, name: null, from: null }, state.treeNote, state.treeSel, state.treeConfirm,
49
+ list.map((r) => treesOf(r).map((t) => [t.ws, t.branch, sessionsIn(t.ws).map((x) => [x.id, x.state, x.shown, x.name, x.initiative])]))]);
50
+ if (key === last) return;
51
+ last = key;
52
+ s.setHead(COPY.branches.title, sub(list));
53
+ setChildren(s.body, body(list));
54
+ });
55
+ return s;
56
+ }
@@ -2184,6 +2184,8 @@
2184
2184
  }
2185
2185
  .td-goal__prep { display: inline-flex; align-items: center; gap: 8px; }
2186
2186
  .td-goals__bar { display: flex; gap: 8px; }
2187
+ .td-goals__run { margin: 0; font-size: 12px; line-height: 16px; color: var(--foreground); }
2188
+ .td-goals__run--failed { color: var(--destructive); }
2187
2189
  .td-goals__sub { margin: 4px 0 0; font-size: 12px; font-weight: 600; color: var(--muted-foreground); }
2188
2190
  .td-card {
2189
2191
  display: flex;
@@ -11,6 +11,14 @@ export const COPY = {
11
11
  none: 'No repository',
12
12
  menu: 'Choose a repository',
13
13
  open: (name) => 'Repository: ' + name + '. Choose a repository.',
14
+ branches: 'Branches and sessions',
15
+ },
16
+ branches: {
17
+ title: 'Branches and sessions',
18
+ sub: (trees, live) => trees + (trees === 1 ? ' branch' : ' branches') + ' · ' + live + ' live ' + (live === 1 ? 'session' : 'sessions'),
19
+ none: 'Add a repository to see its branches.',
20
+ noTrees: 'This repository has no working tree the workspace can read.',
21
+ hint: 'Every branch and the sessions running in it',
14
22
  },
15
23
  nav: {
16
24
  label: 'Destinations',
@@ -67,6 +67,10 @@ export const COPY = {
67
67
  prepareDay: 'Prepare my day',
68
68
  prepareDayHint: 'Starts a session that reads your calendar and your Slack, Jira and Confluence mentions, and ties what it finds to your goals.',
69
69
  started: 'The session started. The steps show here when it is done.',
70
+ starting: 'Starting…',
71
+ runStarted: (at) => `Started at ${at}. The session is reading your calendar and mentions; the steps show here when it is done.`,
72
+ runFailed: (why) => `Not started. ${why}`,
73
+ openSession: 'Open the session',
70
74
  coverage: { planned: 'has steps', 'needs-plan': 'no step yet', done: 'done', dropped: 'dropped' },
71
75
  done: 'Done', drop: 'Drop', reopen: 'Reopen', remove: 'Remove',
72
76
  you: 'You', agent: 'Agent', mention: 'mention',
@@ -49,12 +49,35 @@ async function press(ws, body) {
49
49
  return true;
50
50
  }
51
51
 
52
+ // "Prepare my day", per repository: { state: 'busy' | 'started' | 'failed', at, session, error }. The session runs in the
53
+ // background, so the section says what happened and offers to open it; a toast alone is gone before the PM looks.
54
+ const runs = new Map();
55
+
52
56
  async function prepareDay(ws) {
57
+ const cur = runs.get(ws);
58
+ if (cur && cur.state === 'busy') return;
59
+ runs.set(ws, { state: 'busy' });
60
+ rerender();
53
61
  const r = await pmPost('/api/proactive/goals/plan', { ws });
54
- toast({ text: r && r.ok ? G.started : G.refused((r && r.error) || G.unreached), tone: r && r.ok ? undefined : 'destructive' });
62
+ const sid = r && r.result && r.result.sessionId;
63
+ if (r && r.ok) {
64
+ const session = sid ? { id: sid, name: 'morning-brief' } : null;
65
+ runs.set(ws, { state: 'started', at: Date.now(), session });
66
+ toast({ text: G.started });
67
+ if (session) openSession(session);
68
+ } else {
69
+ const error = (r && r.error) || G.unreached;
70
+ runs.set(ws, { state: 'failed', at: Date.now(), error });
71
+ toast({ text: G.runFailed(error), tone: 'destructive' });
72
+ }
73
+ rerender();
55
74
  load(ws);
56
75
  }
57
76
 
77
+ async function openSession(session) {
78
+ try { const m = await import('./handoff-box.js'); await m.openSessionTab(null, session); } catch (e) { console.error('[pm] could not open the session:', e); }
79
+ }
80
+
58
81
  async function prepareStep(ws, st) {
59
82
  const r = await pmPost('/api/proactive/prepare/plan', { ws, initiative_id: st.initiative_id });
60
83
  if (!r || r.ok === false) { toast({ text: G.refused((r && r.error) || G.unreached), tone: 'destructive' }); return; }
@@ -140,15 +163,26 @@ export function goalsSection({ ws, setState } = {}) {
140
163
  if (goals.length < max) {
141
164
  const f = field({ label: G.inputLabel, value: draft, placeholder: G.inputHint, hideLabel: true, attrs: { 'data-goals': 'input', maxlength: '140' },
142
165
  onCommit: async (v) => {
143
- draft = v;
144
- if (!String(v).trim()) return;
145
- if (await press(ws, { action: 'add', text: v })) { draft = ''; if (f.control.resetCommitted) f.control.resetCommitted(''); rerender(); }
166
+ if (!String(v).trim()) { draft = v; return; }
167
+ // clear before the press: its reload re-renders the section, and a rebuild must not bring the text back
168
+ draft = '';
169
+ if (await press(ws, { action: 'add', text: v })) { if (f.control.resetCommitted) f.control.resetCommitted(''); f.control.value = ''; rerender(); }
170
+ else { draft = v; rerender(); }
146
171
  } });
147
172
  f.control.addEventListener('input', () => { draft = f.control.value; }); // a rebuild keeps what is typed
148
173
  kids.push(f);
149
174
  } else kids.push(el('p', { class: 'td-goals__note' }, G.full(max)));
175
+ const run = runs.get(ws);
176
+ const busy = !!run && run.state === 'busy';
150
177
  kids.push(el('div', { class: 'td-goals__bar' },
151
- button({ label: G.prepareDay, attrs: { 'data-goals': 'prepare-day', title: G.prepareDayHint }, disabled: !openN, onClick: () => prepareDay(ws) })));
178
+ button({ label: busy ? G.starting : G.prepareDay, attrs: { 'data-goals': 'prepare-day', title: G.prepareDayHint, 'aria-busy': busy ? 'true' : 'false' },
179
+ disabled: !openN || busy, onClick: () => prepareDay(ws) }),
180
+ run && run.state === 'started' && run.session
181
+ ? button({ label: G.openSession, variant: 'ghost', attrs: { 'data-goals': 'open-session' }, onClick: () => openSession(run.session) }) : null));
182
+ if (run && run.state !== 'busy') {
183
+ kids.push(el('p', { class: 'td-goals__run' + (run.state === 'failed' ? ' td-goals__run--failed' : ''), 'data-goals': 'run', 'data-run': run.state,
184
+ role: run.state === 'failed' ? 'alert' : 'status' }, run.state === 'failed' ? G.runFailed(run.error) : G.runStarted(hhmm(run.at))));
185
+ }
152
186
  if (plan) {
153
187
  const lines = [];
154
188
  if (!plan.brief) lines.push(G.noBrief);
@@ -168,5 +202,5 @@ export function goalsSection({ ws, setState } = {}) {
168
202
 
169
203
  export function goalsSig(ws) {
170
204
  const c = cache.get(ws) || {};
171
- return JSON.stringify([open, c.data && c.data.plan, c.error, [...prepared.keys()]]);
205
+ return JSON.stringify([open, draft === '', c.data && c.data.plan, c.error, [...prepared.keys()], runs.get(ws) || null]);
172
206
  }
@@ -78,6 +78,7 @@
78
78
  "pm-r20-test.js",
79
79
  "pm-r21-test.js",
80
80
  "pm-r22-test.js",
81
+ "pm-today-goals-test.js",
81
82
  "pm-r23-test.js",
82
83
  "pm-r24-test.js",
83
84
  "pm-r25-test.js",
@@ -168,6 +169,7 @@
168
169
  "pm-r20-test.js": 4,
169
170
  "pm-r21-test.js": 4,
170
171
  "pm-r22-test.js": 32,
172
+ "pm-today-goals-test.js": 8,
171
173
  "pm-r23-test.js": 13,
172
174
  "pm-r24-test.js": 4,
173
175
  "pm-r25-test.js": 9,