create-pmos 0.91.0 → 0.92.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/guide/layout.md +1 -1
- package/workspace/guide/today.md +2 -0
- package/workspace/lib/today.js +2 -1
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/panel-cockpit.js +1 -1
- package/workspace/public/pm/panel-repos.js +1 -1
- package/workspace/public/pm/tab-gone.js +21 -0
- package/workspace/public/pm/tab-term.js +15 -0
- package/workspace/test-files.json +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.92.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-07T18:14:04Z"}
|
|
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-07T18:14:04Z"}
|
|
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-07T18:14:04Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -50,7 +50,7 @@ The dock holds your terminals, documents, conversations, skills and web pages. I
|
|
|
50
50
|
|
|
51
51
|
- Press ⌘J (Ctrl-J), or the button on the rail, to open the dock. Press ⌘J again, or the fold button in the dock, to fold it to the rail.
|
|
52
52
|
- The dock never opens by itself. A tab that opens while the dock is a rail waits in the dock until you open it.
|
|
53
|
-
- The dock lists the sessions of the repository by initiative. A session is in the group of the initiative it is recorded on, or of its `initiative/<id>` branch. Then come **Not linked** (no initiative) and **Automatic** (anything you did not start yourself, for example a schedule, the day run, a note's summary or bullets, or a Jira push). **Automatic** is hidden until you press **Show automatic** at the foot of the list (or beside the group select in the strip). A session in front, or one held on a permission question, is always shown. An agent that has finished and reads "needs you" is hidden like the rest. Finished automatic sessions remove themselves a minute after a clean exit, and one with no output for 60 minutes is ended and removed. A failed one stays. The setting is in `state/session-policy.json`: `finish` (true or false) and `idleMinutes` (5 or more, or 0 for off). Then **Open** lists the tabs that are not sessions, for example documents and skills. Each one shows its title, what it is (for example PRD, Document or Web page) and its branch when it is not main. **✕** on an Open row closes the tab only. A terminal keeps running.
|
|
53
|
+
- The dock lists the sessions of the repository by initiative. A session is in the group of the initiative it is recorded on, or of its `initiative/<id>` branch. Then come **Not linked** (no initiative) and **Automatic** (anything you did not start yourself, for example a schedule, the day run, a note's summary or bullets, or a Jira push). **Automatic** is hidden until you press **Show automatic** at the foot of the list (or beside the group select in the strip). A session in front, or one held on a permission question, is always shown. An agent that has finished and reads "needs you" is hidden like the rest. Finished automatic sessions remove themselves a minute after a clean exit, and one with no output for 60 minutes is ended and removed. A failed one stays. When a session has been removed, its terminal tab closes too, also when the workspace was stopped for an update, so the tab does not come back as a row under **Open**. The setting is in `state/session-policy.json`: `finish` (true or false) and `idleMinutes` (5 or more, or 0 for off). Then **Open** lists the tabs that are not sessions, for example documents and skills. Each one shows its title, what it is (for example PRD, Document or Web page) and its branch when it is not main. **✕** on an Open row closes the tab only. A terminal keeps running.
|
|
54
54
|
- Each session row starts with a mark for its state: ● working, ◼ quiet, ◆ needs you, ◇ maybe blocked, ○ ended. Under the name, the row shows the state when the session is not working, the type of work when one is set, and the branch when it is not main and not the initiative's own branch. Running sessions come first and ended sessions last. Press the row to bring the session to the front.
|
|
55
55
|
- Press the name of a group to fold it or open it. The number beside it counts its sessions. **Automatic** is folded while none of its sessions is running, because each run leaves an ended session. The group of the session in front always stays open, and the workspace keeps your choice for each group.
|
|
56
56
|
- **⋯** and **✕** show on the row in front, on the row under the pointer and on the row with the keyboard focus. On a touch screen they always show.
|
package/workspace/guide/today.md
CHANGED
|
@@ -117,6 +117,8 @@ The page also shows which sources were read and how many items each gave, and th
|
|
|
117
117
|
|
|
118
118
|
Today lists only what waits for you. Each card is one thing that only you can close. When nothing waits, Today says so and names the next scheduled draft, for example "Nothing needs you. Next: biweekly draft Fri 16 Oct."
|
|
119
119
|
|
|
120
|
+
A session that you did not start yourself, for example a schedule, a day task, or a note's summary, never makes a card, even when its agent has stopped and is "waiting". Only a session you started is waiting for you.
|
|
121
|
+
|
|
120
122
|
Press **Today** in the top bar, or press `g` and then `t`, to open it. The number on the Today button is the number of cards in the scope you chose: the repository, or All repositories. It is the same number that Today shows at the top of the list.
|
|
121
123
|
|
|
122
124
|
## A card
|
package/workspace/lib/today.js
CHANGED
|
@@ -291,7 +291,8 @@ function gather(w, { stateDir = stateDirOf(w.root), sessions = [], registered }
|
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
293
|
for (const s of sessions || []) {
|
|
294
|
-
|
|
294
|
+
// a session the PM did not start (a schedule, the day run, a note's summary) is not waiting on the PM
|
|
295
|
+
if (!s || s.ws !== w.id || s.shown !== 'needs' || s.automatic) continue;
|
|
295
296
|
rows.push({ run_id: `session:${s.id}`, initiative_id: s.initiative || planItems.initiativeOf(s.plan_item) || null, kind: 'session', derived: true,
|
|
296
297
|
question: 'A session is waiting for you.', doc: [s.name, s.label].filter(Boolean).join(' · ') || null });
|
|
297
298
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.92.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.92.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -176,7 +176,7 @@ function repoNeeds(repo) {
|
|
|
176
176
|
const sess = Array.isArray(state.sessions) ? state.sessions : [];
|
|
177
177
|
if (!sess.length) return false;
|
|
178
178
|
const wss = new Set(treesOf(repo).map((x) => x.ws));
|
|
179
|
-
return sess.some((x) => x && wss.has(x.ws) && x.shown === 'needs');
|
|
179
|
+
return sess.some((x) => x && wss.has(x.ws) && x.shown === 'needs' && !x.automatic);
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
/* ── the rail ─────────────────────────────────────────────────────────────────────────────────── */
|
|
@@ -361,7 +361,7 @@ export function railGlyphs() {
|
|
|
361
361
|
const rp = repoRow(t);
|
|
362
362
|
const name = rp.name;
|
|
363
363
|
const wss = new Set(treesOf(name).map((x) => x.ws));
|
|
364
|
-
const needs = sess.some((x) => x && wss.has(x.ws) && x.shown === 'needs');
|
|
364
|
+
const needs = sess.some((x) => x && wss.has(x.ws) && x.shown === 'needs' && !x.automatic);
|
|
365
365
|
const active = product === name;
|
|
366
366
|
const ini = (dataMod && dataMod.repoInitials && dataMod.repoInitials(name)) || (String(name).replace(/[^A-Za-z0-9]+/g, ' ').trim().split(' ').filter(Boolean).slice(0, 2)
|
|
367
367
|
.map((w) => w[0]).join('') || String(name).slice(0, 2)).toUpperCase();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* pm/tab-gone.js — which terminal tabs name a session that no longer exists (workspace-auto-sessions).
|
|
2
|
+
*
|
|
3
|
+
* A terminal tab is stored in the browser (state.js `tabs`) with the server's session id, so a reload re-attaches to the same
|
|
4
|
+
* shell. The server now removes an automatic session when its job finishes or it sits idle, and a session can also be removed
|
|
5
|
+
* while the workspace is stopped for an update. The tab was never told: it came back on every reload as an "Open" row
|
|
6
|
+
* ("Morning brief", "Weekly report") over a pane that said nothing was attached.
|
|
7
|
+
*
|
|
8
|
+
* Pure over its two arguments. `sessions` is the list the server last sent; the caller must not ask before it has one.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** The ids of the terminal tabs whose session is not in `sessions`. A tab that has not yet been given a session id is not listed. */
|
|
12
|
+
export function goneTermTabIds(tabs, sessions, { starting = () => false } = {}) {
|
|
13
|
+
const live = new Set((sessions || []).filter(Boolean).map((s) => s.id));
|
|
14
|
+
const out = [];
|
|
15
|
+
for (const t of tabs || []) {
|
|
16
|
+
if (!t || t.kind !== 'term' || !t.sessionId) continue;
|
|
17
|
+
if (t.spawn || starting(t)) continue; // a tab that is about to start a session names none yet
|
|
18
|
+
if (!live.has(t.sessionId)) out.push(t.id);
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
import { el, setChildren } from './dom.js';
|
|
48
48
|
import { state, setState, subscribe, FIXTURE } from './state.js';
|
|
49
49
|
import * as FX from './fixture.js';
|
|
50
|
+
import { goneTermTabIds } from './tab-gone.js';
|
|
50
51
|
import { pageNonce } from './http.js'; // R25: a session started with an initiative binds only with the nonce
|
|
51
52
|
|
|
52
53
|
/* ── the design's terminal colours ───────────────────────────────────────────────────────────
|
|
@@ -358,6 +359,20 @@ function handle(m) {
|
|
|
358
359
|
SOCK.listed = true;
|
|
359
360
|
if (m.t === 'hello' && Array.isArray(m.workspaces)) SOCK.workspaces = m.workspaces;
|
|
360
361
|
if (m.t === 'hello' && m.home) SOCK.home = m.home;
|
|
362
|
+
/* A tab restored from the browser may name a session the server has since removed (an automatic one that finished or
|
|
363
|
+
* went idle). Close it: it would otherwise return on every reload as an "Open" row over an empty pane. A session the
|
|
364
|
+
* data layer still lists also keeps its tab, so a tab is never closed on the socket's word alone. */
|
|
365
|
+
const gone = goneTermTabIds(state.tabs, [...SOCK.sessions, ...(Array.isArray(state.sessions) ? state.sessions : [])], { starting: (t) => SOCK.spawning.get(t.id) });
|
|
366
|
+
if (gone.length) {
|
|
367
|
+
setState((s) => {
|
|
368
|
+
const rest = (s.tabs || []).filter((t) => !gone.includes(t.id));
|
|
369
|
+
return {
|
|
370
|
+
tabs: rest,
|
|
371
|
+
active: gone.includes(s.active) ? (rest.length ? rest[rest.length - 1].id : null) : s.active,
|
|
372
|
+
splitId: gone.includes(s.splitId) ? null : s.splitId,
|
|
373
|
+
};
|
|
374
|
+
});
|
|
375
|
+
}
|
|
361
376
|
for (const rec of panes.values()) if (rec.kind === 'real') bind(rec);
|
|
362
377
|
return;
|
|
363
378
|
}
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"learn.test.js",
|
|
47
47
|
"session-flag.test.js",
|
|
48
48
|
"session-origin.test.js",
|
|
49
|
+
"tab-gone.test.js",
|
|
49
50
|
"live-notes.test.js",
|
|
50
51
|
"plan-items.test.js",
|
|
51
52
|
"plan-tree.test.js",
|
|
@@ -150,6 +151,7 @@
|
|
|
150
151
|
"learn.test.js": 3,
|
|
151
152
|
"session-flag.test.js": 1,
|
|
152
153
|
"session-origin.test.js": 1,
|
|
154
|
+
"tab-gone.test.js": 1,
|
|
153
155
|
"live-notes.test.js": 3,
|
|
154
156
|
"pm-shell-test.js": 6,
|
|
155
157
|
"pm-repos-test.js": 3,
|