create-pmos 0.66.0 → 0.67.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.66.0",
3
+ "version": "0.67.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@c6198fe
1
+ kernel-source: pmos@5233c8c
2
2
  assembled: 2026-10-07
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-07T01:43:50Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T02:01:39Z"}
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-07T01:43:50Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T02:01:39Z"}
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-07T01:43:50Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T02:01:39Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.66.0",
3
+ "version": "0.67.0",
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.66.0",
3
+ "version": "0.67.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",
@@ -2,8 +2,9 @@
2
2
  'use strict';
3
3
  /* run-tests.js — run the workspace suites side by side.
4
4
  *
5
- * node run-tests.js <group> [--jobs=N] [--shard=I/N] [--changed[=REF]] [--list] [--verbose]
5
+ * node run-tests.js <group> [--jobs=N] [--shard=I/N] [--changed[=REF]] [--timings-out=FILE] [--list] [--verbose]
6
6
  * node run-tests.js --check
7
+ * node run-tests.js --merge-timings=DIR
7
8
  *
8
9
  * Groups come from test-files.json: unit (test.js + modules), modules, panels, ui, browser
9
10
  * (panels + ui), all. The npm scripts call this file, so `npm test` and CI run the same lists.
@@ -31,6 +32,14 @@
31
32
  * test-files.json, plus `helpers` (a -test.js file that is a helper, not a suite, with the reason, as
32
33
  * in ELSEWHERE). This file merges every fragment into the lists before it does anything else, so
33
34
  * --check, --shard and every group see one list. A fragment that is not valid fails every command.
35
+ *
36
+ * Timings. --shard balances the shards by each suite's time, and the shards only finish together when
37
+ * those times are the CI runner's. test-timings.json holds times measured on CI; the `seconds` keys in
38
+ * test-files.json and the fragments are the fallback for a suite it does not name yet, and a suite in
39
+ * neither counts as 10. The `seconds` numbers were measured on a 4-CPU sandbox and were off by up to
40
+ * 4x on the 2-CPU runners, which left one shard running 280s while two others finished in 130s.
41
+ * Every CI job writes its times with --timings-out and uploads them; --merge-timings=DIR folds a
42
+ * downloaded set into test-timings.json. A stale time never skips a suite: it only moves it.
34
43
  */
35
44
  const fs = require('fs');
36
45
  const os = require('os');
@@ -98,7 +107,10 @@ const args = process.argv.slice(2);
98
107
  const flag = (name) => { const a = args.find((x) => x === `--${name}` || x.startsWith(`--${name}=`)); return a === undefined ? undefined : (a.includes('=') ? a.slice(a.indexOf('=') + 1) : true); };
99
108
  const group = args.find((a) => !a.startsWith('--'));
100
109
 
101
- const secs = (f) => Number((FILES.seconds || {})[f]) || 10;
110
+ const TIMINGS_FILE = path.join(DIR, 'test-timings.json');
111
+ let TIMINGS = {};
112
+ try { TIMINGS = JSON.parse(fs.readFileSync(TIMINGS_FILE, 'utf8')).seconds || {}; } catch { /* no CI timings yet: the seconds keys decide */ }
113
+ const secs = (f) => Number(TIMINGS[f]) || Number((FILES.seconds || {})[f]) || 10;
102
114
  // Slowest first, so a long suite never starts last and holds the run open on its own.
103
115
  const bySlowest = (list) => list.map((f, k) => ({ f, k })).sort((a, b) => secs(b.f) - secs(a.f) || a.k - b.k).map((x) => x.f);
104
116
 
@@ -155,6 +167,7 @@ function changedSuites(list, ref) {
155
167
  for (const f of files) {
156
168
  if (source.has(f)) { picked.add(f); continue; }
157
169
  if (/^(guide|docs)\/|\.md$/.test(f)) continue; // prose: no suite asserts on it except guide.test.js, a unit suite
170
+ if (f === 'test-timings.json') continue; // run order only: no suite reads it
158
171
  const stem = path.basename(f).replace(/\.[^.]+$/, '');
159
172
  const key = stem.replace(/^(panel|view|tab)-/, '');
160
173
  const hits = list.filter((s) => source.get(s).includes(stem) || s.includes(`-${key}-`) || s.includes(`-${key}.`));
@@ -207,8 +220,30 @@ function runOne(file, verbose) {
207
220
  });
208
221
  }
209
222
 
223
+ // Fold the --timings-out files a CI run uploaded (one per job, in DIR) into test-timings.json. A suite
224
+ // the run did not reach keeps its old time; a time under one second is recorded as one.
225
+ function mergeTimings(dir) {
226
+ const old = fs.existsSync(TIMINGS_FILE) ? JSON.parse(fs.readFileSync(TIMINGS_FILE, 'utf8')) : {};
227
+ const seconds = { ...(old.seconds || {}) };
228
+ let n = 0;
229
+ for (const name of fs.readdirSync(dir, { recursive: true }).filter((x) => String(x).endsWith('.json'))) {
230
+ for (const [f, v] of Object.entries(JSON.parse(fs.readFileSync(path.join(dir, String(name)), 'utf8')))) {
231
+ if (typeof v === 'number' && GROUPS.all.includes(f)) { seconds[f] = Math.max(1, Math.round(v)); n++; }
232
+ }
233
+ }
234
+ const sorted = Object.fromEntries(Object.keys(seconds).filter((f) => GROUPS.all.includes(f)).sort().map((f) => [f, seconds[f]]));
235
+ const out = {
236
+ _comment: old._comment || 'Wall time of each suite on the Workspace CI runners, in seconds. run-tests.js --shard balances the shards with these numbers. Refresh: download the timings-* artifacts of a green Workspace CI run into one directory, then run `node run-tests.js --merge-timings=DIR`.',
237
+ measured: new Date().toISOString().slice(0, 10),
238
+ seconds: sorted,
239
+ };
240
+ fs.writeFileSync(TIMINGS_FILE, `${JSON.stringify(out, null, 2)}\n`);
241
+ console.log(`run-tests: ${n} times merged into test-timings.json (${Object.keys(sorted).length} suites).`);
242
+ }
243
+
210
244
  async function main() {
211
245
  if (flag('check')) return check();
246
+ if (flag('merge-timings')) return mergeTimings(flag('merge-timings'));
212
247
  if (fragProblems.length) { for (const p of fragProblems) console.error(`✗ ${p}`); process.exitCode = 2; return; }
213
248
  if (!group || !GROUPS[group]) {
214
249
  console.error(`usage: node run-tests.js <${Object.keys(GROUPS).join('|')}> [--jobs=N] [--shard=I/N] [--list] [--verbose]\n node run-tests.js --check`);
@@ -250,6 +285,8 @@ async function main() {
250
285
 
251
286
  const failed = results.filter((r) => !r.ok);
252
287
  const slowest = [...results].sort((a, b) => b.secs - a.secs).slice(0, 5).map((r) => `${r.file} ${r.secs}s`).join(', ');
288
+ const timingsOut = flag('timings-out');
289
+ if (timingsOut && timingsOut !== true) fs.writeFileSync(timingsOut, JSON.stringify(Object.fromEntries(results.map((r) => [r.file, r.secs])), null, 2));
253
290
  console.log(`\nrun-tests: ${results.length - failed.length}/${results.length} suites passed in ${((Date.now() - t0) / 1000).toFixed(1)}s wall. Slowest: ${slowest}.`);
254
291
  // exitCode, never process.exit(): stdout to a pipe is asynchronous, and exiting here cut the CI log
255
292
  // off mid-suite, before the summary that names what failed.
@@ -0,0 +1,172 @@
1
+ {
2
+ "_comment": "Wall time of each suite on the Workspace CI runners, in seconds. run-tests.js --shard balances the shards with these numbers. Refresh: download the timings-* artifacts of a green Workspace CI run into one directory, then run `node run-tests.js --merge-timings=DIR`.",
3
+ "measured": "2026-10-07",
4
+ "seconds": {
5
+ "agenda.test.js": 1,
6
+ "auto-sync.test.js": 2,
7
+ "board-cli.test.js": 3,
8
+ "capture.test.js": 1,
9
+ "claims.test.js": 2,
10
+ "cuts.test.js": 1,
11
+ "d86-seams.test.js": 1,
12
+ "day-run.test.js": 21,
13
+ "doc-category-corrections.test.js": 1,
14
+ "doc-category.test.js": 1,
15
+ "doc-library.test.js": 1,
16
+ "doc-sync.test.js": 1,
17
+ "docs-cli.test.js": 5,
18
+ "external-linking.test.js": 1,
19
+ "factory-join.test.js": 1,
20
+ "first-paint-board.test.js": 8,
21
+ "first-paint-count.test.js": 23,
22
+ "first-paint-initiatives.test.js": 12,
23
+ "first-paint-reports.test.js": 8,
24
+ "first-paint-today.test.js": 26,
25
+ "fixture-parity.test.mjs": 1,
26
+ "focus.test.js": 1,
27
+ "glossary.test.js": 1,
28
+ "goals.test.js": 1,
29
+ "grant-mint.test.js": 1,
30
+ "guide.test.js": 1,
31
+ "handoff.test.js": 1,
32
+ "health.test.js": 1,
33
+ "initiative-cli.test.js": 8,
34
+ "initiative-needs.test.js": 2,
35
+ "initiative-surface.test.js": 3,
36
+ "initiative-writer.test.js": 1,
37
+ "issues.test.js": 1,
38
+ "item-cli.test.js": 7,
39
+ "item-spans.test.js": 1,
40
+ "jev-categorize.test.js": 1,
41
+ "jev-claims.test.js": 1,
42
+ "jev-route.test.js": 1,
43
+ "jev-status.test.js": 1,
44
+ "jira-wbs.test.js": 1,
45
+ "kg.test.js": 2,
46
+ "kit-update.test.js": 1,
47
+ "launcher.test.js": 1,
48
+ "learn.test.js": 4,
49
+ "live-notes.test.js": 5,
50
+ "markdown.test.mjs": 1,
51
+ "measurements.test.js": 1,
52
+ "next-step-actions.test.js": 11,
53
+ "no-network.test.js": 1,
54
+ "notes-listen.test.js": 2,
55
+ "notes.test.js": 5,
56
+ "online-mode.test.js": 1,
57
+ "people.test.js": 2,
58
+ "phone-access.test.js": 1,
59
+ "phone-command.test.js": 4,
60
+ "placements.test.js": 1,
61
+ "plan-items-stage-parity.test.js": 1,
62
+ "plan-items.test.js": 1,
63
+ "plan-routes.test.js": 11,
64
+ "plan-tree.test.js": 6,
65
+ "pm-board-dest-phone-test.js": 14,
66
+ "pm-board-dest-test.js": 27,
67
+ "pm-breakdown-phone-test.js": 33,
68
+ "pm-breakdown-test.js": 68,
69
+ "pm-breakdown-timeline-test.js": 25,
70
+ "pm-cuts-test.js": 16,
71
+ "pm-detail-test.js": 18,
72
+ "pm-doc-category-test.js": 6,
73
+ "pm-docs-test.js": 37,
74
+ "pm-frame-phone-test.js": 14,
75
+ "pm-frame-test.js": 21,
76
+ "pm-initiatives-dest-phone-test.js": 9,
77
+ "pm-initiatives-dest-test.js": 28,
78
+ "pm-initiatives-test.js": 19,
79
+ "pm-initiatives-timeline-test.js": 39,
80
+ "pm-jira-test.js": 6,
81
+ "pm-lines-needs.test.js": 1,
82
+ "pm-live-notes-test.js": 25,
83
+ "pm-modes-test.js": 11,
84
+ "pm-nonce-test.js": 14,
85
+ "pm-notes-test.js": 40,
86
+ "pm-palette-test.js": 29,
87
+ "pm-people-test.js": 12,
88
+ "pm-r1-test.js": 10,
89
+ "pm-r10-test.js": 1,
90
+ "pm-r11-test.js": 2,
91
+ "pm-r12-test.js": 17,
92
+ "pm-r13-test.js": 12,
93
+ "pm-r14-test.js": 7,
94
+ "pm-r15-test.js": 7,
95
+ "pm-r16-test.js": 16,
96
+ "pm-r17-test.js": 7,
97
+ "pm-r18-test.js": 11,
98
+ "pm-r19-test.js": 20,
99
+ "pm-r2-test.js": 7,
100
+ "pm-r20-test.js": 19,
101
+ "pm-r21-test.js": 29,
102
+ "pm-r22-test.js": 2,
103
+ "pm-r23-test.js": 17,
104
+ "pm-r24-test.js": 15,
105
+ "pm-r25-test.js": 1,
106
+ "pm-r26-test.js": 1,
107
+ "pm-r27-test.js": 26,
108
+ "pm-r28-test.js": 1,
109
+ "pm-r29-filespad-test.js": 1,
110
+ "pm-r29-knowledge-test.js": 1,
111
+ "pm-r29-memory-test.js": 1,
112
+ "pm-r29-newsession-alignment-test.js": 1,
113
+ "pm-r29-portfolio-test.js": 1,
114
+ "pm-r29-reports-addrepo-test.js": 1,
115
+ "pm-r29-skills-scheduled-test.js": 1,
116
+ "pm-r29-today-test.js": 12,
117
+ "pm-r29-validate-guide-test.js": 1,
118
+ "pm-r3-test.js": 11,
119
+ "pm-r30-test.js": 13,
120
+ "pm-r4-test.js": 4,
121
+ "pm-r5-test.js": 6,
122
+ "pm-r6-test.js": 5,
123
+ "pm-r7-test.js": 1,
124
+ "pm-r8-test.js": 1,
125
+ "pm-r9-test.js": 10,
126
+ "pm-report-slides-test.js": 6,
127
+ "pm-reports-phone-test.js": 9,
128
+ "pm-reports-test.js": 20,
129
+ "pm-repos-test.js": 10,
130
+ "pm-seams-test.js": 16,
131
+ "pm-shell-test.js": 6,
132
+ "pm-tabs-test.js": 55,
133
+ "pm-term-test.js": 38,
134
+ "pm-today-goals-test.js": 30,
135
+ "pm-today-phone-test.js": 27,
136
+ "pm-today-test.js": 60,
137
+ "pm-updates-test.js": 16,
138
+ "proactive-prepare.test.js": 12,
139
+ "proactive.test.js": 17,
140
+ "profiles.test.js": 1,
141
+ "public-host.test.js": 1,
142
+ "r25-guards.test.js": 9,
143
+ "r3-display.test.js": 4,
144
+ "replaced-tests.test.js": 17,
145
+ "repo-scan.test.js": 1,
146
+ "repo-watch.test.js": 1,
147
+ "report-cli.test.js": 2,
148
+ "report-draft.test.js": 4,
149
+ "report-needs.test.js": 1,
150
+ "report-slides-data.test.js": 1,
151
+ "routes-board.test.js": 1,
152
+ "routes-capture.test.js": 1,
153
+ "routes-initiatives.test.js": 3,
154
+ "routes-reports.test.js": 1,
155
+ "routes-timeline.test.js": 1,
156
+ "routes-today.test.js": 6,
157
+ "scan-consistency.test.js": 1,
158
+ "schedule-cli.test.js": 3,
159
+ "session-flag.test.js": 1,
160
+ "start-policy.test.js": 1,
161
+ "state-isolation.test.js": 37,
162
+ "status-facts-panel.test.js": 1,
163
+ "story-artifacts.test.js": 1,
164
+ "sync.test.js": 4,
165
+ "test.js": 65,
166
+ "today.test.js": 5,
167
+ "tracker-refresh.test.js": 1,
168
+ "typesafe-validation.test.js": 1,
169
+ "ui-test.js": 43,
170
+ "work-check-needs.test.js": 1
171
+ }
172
+ }