create-pmos 0.138.0 → 0.139.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/data-freshness.md +1 -1
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/components.css +3 -0
- package/workspace/public/pm/freshness-line.js +32 -11
- package/workspace/public/pm/freshness-words.js +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.139.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-12T01:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-12T01:17:12Z"}
|
|
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-12T01:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-12T01:17:12Z"}
|
|
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-12T01:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-12T01:17:12Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -43,6 +43,6 @@ Each page shows a short line under its title that says when each source was last
|
|
|
43
43
|
- A source that was never read says **not read yet**, in amber.
|
|
44
44
|
- A source whose last read failed is red and says when it was last read.
|
|
45
45
|
- A watched repository is not named, because the workspace checks it every few seconds.
|
|
46
|
-
-
|
|
46
|
+
- The refresh icon at the end of the line reads every source again: the initiatives on this machine, and an agent read for Calendar, Jira status, My work and tracker items. A source that is switched off or is being read already is left alone. A short note says how many reads started, or which could not start.
|
|
47
47
|
|
|
48
48
|
Settings › Data freshness shows every source in one table: when it was read, its state, its schedule, and **Refresh**.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.139.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.139.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -3507,6 +3507,8 @@
|
|
|
3507
3507
|
gap: 8px;
|
|
3508
3508
|
font-size: 12px;
|
|
3509
3509
|
color: var(--muted-foreground);
|
|
3510
|
+
/* the header row can be wider than a phone (its tools row); the line wraps inside the screen, not past it */
|
|
3511
|
+
max-width: calc(100vw - 24px);
|
|
3510
3512
|
}
|
|
3511
3513
|
.fr-fresh__state--fresh { color: var(--foreground); }
|
|
3512
3514
|
.fr-fresh__state--stale { color: var(--warning); }
|
|
@@ -3515,6 +3517,7 @@
|
|
|
3515
3517
|
.fr-fresh__state--building,
|
|
3516
3518
|
.fr-fresh__state--off { color: var(--muted-foreground); }
|
|
3517
3519
|
.fr-fresh__sep { color: var(--muted-foreground); }
|
|
3520
|
+
.fr-fresh__note { color: var(--muted-foreground); }
|
|
3518
3521
|
.fr-fresh-table__grid {
|
|
3519
3522
|
width: 100%;
|
|
3520
3523
|
border-collapse: collapse;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
*
|
|
3
3
|
* freshnessLine(ws) → element the console header's short line: when each source was last read ("Calendar read
|
|
4
4
|
* 07:15 · Jira status read 08:02"), the old, failed and never-read ones flagged, and
|
|
5
|
-
* a
|
|
5
|
+
* a refresh icon, always there, that reads every source again. It never says "up to date": the sources
|
|
6
6
|
* are read on a schedule, not live (pm/freshness-words.js). Updates itself every 15 s.
|
|
7
7
|
* freshnessTable(ws) → element Settings › Data freshness: every source, when it was read, its state, its schedule,
|
|
8
8
|
* Refresh, and the repository's Watch for changes switch.
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { el, setChildren } from './dom.js';
|
|
16
16
|
import { pmGet, pmPost } from './http.js';
|
|
17
|
-
import { button, SEL } from './primitives.js';
|
|
18
|
-
import { lineItems,
|
|
17
|
+
import { button, iconButton, SEL } from './primitives.js';
|
|
18
|
+
import { lineItems, refreshTargets } from './freshness-words.js';
|
|
19
19
|
|
|
20
20
|
const POLL_MS = 15000;
|
|
21
21
|
const live = new Map(); // kind|ws -> { node, timer, version }
|
|
@@ -67,25 +67,46 @@ function poll(key, ws, draw) {
|
|
|
67
67
|
return cur.node;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/* The line's refresh icon: one press reads every source again (the PM's choice, 2026-10-12). The initiatives rebuild on
|
|
71
|
+
* this machine; Calendar, Jira, My work and tracker items each start their agent read. A source already being read, or
|
|
72
|
+
* switched off, is left alone. The icon and its result note are kept on the line's element, so a redraw does not lose them. */
|
|
73
|
+
function refreshAll(n, d) {
|
|
74
|
+
n._frData = d;
|
|
75
|
+
if (n._frBtn) return n._frBtn;
|
|
76
|
+
const btn = iconButton({ icon: 'refresh-cw', ariaLabel: 'Read every source again', size: 'sm', attrs: { 'data-c3pg': 'freshness-refresh' },
|
|
77
|
+
onClick: async () => {
|
|
78
|
+
const cur = n._frData;
|
|
79
|
+
const ids = refreshTargets(cur.sources);
|
|
80
|
+
btn.disabled = true;
|
|
81
|
+
note(n, 'Starting ' + ids.length + ' reads…');
|
|
82
|
+
const res = await Promise.all(ids.map((id) => pmPost('/api/sources/refresh', { ws: cur.ws, source: id }).catch(() => null)));
|
|
83
|
+
const bad = ids.filter((id, i) => !(res[i] && res[i].ok)).map((id) => (cur.sources.find((s) => s.id === id) || {}).label || id);
|
|
84
|
+
note(n, bad.length ? 'Could not start: ' + bad.join(', ') : 'Reading ' + ids.length + ' sources again');
|
|
85
|
+
const c = live.get('line|' + cur.ws); if (c) c.tick();
|
|
86
|
+
setTimeout(() => { btn.disabled = false; note(n, ''); }, 30000);
|
|
87
|
+
} });
|
|
88
|
+
n._frBtn = btn;
|
|
89
|
+
return btn;
|
|
90
|
+
}
|
|
91
|
+
function note(n, text) {
|
|
92
|
+
if (!text) { if (n._frMsg) n._frMsg.remove(); n._frMsg = null; return; }
|
|
93
|
+
if (!n._frMsg) { n._frMsg = el('span', { class: 'fr-fresh__note', role: 'status' }); n.append(n._frMsg); }
|
|
94
|
+
n._frMsg.textContent = text;
|
|
95
|
+
}
|
|
96
|
+
|
|
70
97
|
/** The header line: when each source was last read, old, failed and never-read ones flagged. It never claims "up to date". */
|
|
71
98
|
export function freshnessLine(ws) {
|
|
72
99
|
return poll('line|' + ws, ws, (node, d) => {
|
|
73
100
|
const n = node || el('div', { class: 'fr-fresh', 'data-c3pg': 'freshness' });
|
|
74
101
|
if (!d) { if (node) setChildren(n, [el('span', { class: tone('never') }, 'Could not read when the sources were read')]); else n.textContent = ''; return n; }
|
|
75
102
|
const items = lineItems(d.sources);
|
|
76
|
-
const behind = behindOf(items);
|
|
77
103
|
const kids = [];
|
|
78
104
|
items.forEach((it, i) => {
|
|
79
105
|
if (i) kids.push(el('span', { class: 'fr-fresh__sep', 'aria-hidden': 'true' }, '·'));
|
|
80
106
|
kids.push(el('span', { class: tone(it.state), title: it.title, 'data-fr-line': it.id }, it.text));
|
|
81
107
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const src = d.sources.find((s) => s.id === first.id);
|
|
85
|
-
const btn = button({ label: 'Refresh ' + (src ? src.label : first.id), size: 'sm', attrs: { 'data-c3pg': 'freshness-refresh' },
|
|
86
|
-
onClick: () => refresh(d.ws, first.id, btn) });
|
|
87
|
-
kids.push(btn);
|
|
88
|
-
}
|
|
108
|
+
kids.push(refreshAll(n, d));
|
|
109
|
+
if (n._frMsg) kids.push(n._frMsg);
|
|
89
110
|
setChildren(n, kids);
|
|
90
111
|
return n;
|
|
91
112
|
});
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* lineItems(sources, now?) → [{ id, state, text, title }] one honest phrase per source: when it was last read, or
|
|
4
4
|
* that it was never read, failed or is reading now
|
|
5
5
|
* behindOf(items) → [item] the sources to flag: old, failed or never read
|
|
6
|
+
* refreshTargets(sources) → [id] what the refresh icon reads again: every source but the
|
|
7
|
+
* repository (its rebuild is the initiatives'), one switched
|
|
8
|
+
* off, and one being read now
|
|
6
9
|
* whenRead(iso, now?) → '07:15' | 'yesterday 18:02' | '3 d ago'
|
|
7
10
|
*
|
|
8
11
|
* The line never says "up to date". Calendar, Jira, My work and tracker items are read by an agent on a schedule, not
|
|
@@ -44,3 +47,5 @@ export function lineItems(sources, now = Date.now()) {
|
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
export const behindOf = (items) => items.filter((i) => i.state === 'stale' || i.state === 'failed' || i.state === 'never');
|
|
50
|
+
|
|
51
|
+
export const refreshTargets = (sources) => (sources || []).filter((s) => s && s.id !== 'repository' && s.state !== 'off' && s.state !== 'building').map((s) => s.id);
|