create-pmos 0.51.0 → 0.52.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.51.0",
3
+ "version": "0.52.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@4689fc3
1
+ kernel-source: pmos@a6425d1
2
2
  assembled: 2026-10-05
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-05T04:02:12Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-05T21:40:45Z"}
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-05T04:02:12Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-05T21:40:45Z"}
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-05T04:02:12Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-05T21:40:45Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.51.0",
3
+ "version": "0.52.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.51.0",
3
+ "version": "0.52.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",
@@ -19,6 +19,7 @@
19
19
  "test:panels": "node run-tests.js panels",
20
20
  "test:offline": "node offline-tarball.test.js",
21
21
  "test:browser": "node run-tests.js browser",
22
+ "test:changed": "node run-tests.js all --changed",
22
23
  "test:list-check": "node run-tests.js --check"
23
24
  },
24
25
  "engines": {
@@ -2,7 +2,7 @@
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] [--list] [--verbose]
5
+ * node run-tests.js <group> [--jobs=N] [--shard=I/N] [--changed[=REF]] [--list] [--verbose]
6
6
  * node run-tests.js --check
7
7
  *
8
8
  * Groups come from test-files.json: unit (test.js + modules), modules, panels, ui, browser
@@ -17,6 +17,11 @@
17
17
  * file in exactly one shard), and --check fails when a suite on disk is in no list, so a new suite
18
18
  * that nobody wired in is a red build rather than a suite that runs nowhere.
19
19
  *
20
+ * --changed is for the LOCAL inner loop only. It narrows the group to the suites a diff against REF
21
+ * (default origin/main) can reach, and runs the whole group whenever it cannot tell. CI never passes
22
+ * it: Workspace CI runs every suite on every PR, so a suite --changed leaves out still runs before
23
+ * the merge. See changedSuites() for the rule.
24
+ *
20
25
  * Output: a suite's own output is buffered and printed whole when it finishes, so lines from two
21
26
  * suites never interleave. A passing suite prints its last lines; --verbose prints all of it.
22
27
  */
@@ -65,6 +70,53 @@ function shardOf(list, spec) {
65
70
  return out[i - 1];
66
71
  }
67
72
 
73
+ // Files every page or every suite loads. A change to one can break any suite, so --changed runs
74
+ // the whole group. A changed file no suite can be traced to is treated the same way (see below).
75
+ const WIDE = new Set([
76
+ 'server.js', 'test-run.js', 'run-tests.js', 'test-files.json', 'package.json', 'package-lock.json',
77
+ 'public/pm/app.js', 'public/pm/state.js', 'public/pm/dom.js', 'public/pm/data.js',
78
+ 'public/pm/tokens.css', 'public/pm/fixture.js', 'public/pm/mobile.css', 'lib/find-browser.js',
79
+ ]);
80
+
81
+ /* The suites in `list` that the diff against `ref` can reach. A suite is selected when its own file
82
+ * changed, when its source names a changed file (`panel-tabs` in pm-tabs-test.js), or when its file
83
+ * name carries the changed file's stem with any panel-/view-/tab- prefix removed (view-memory.js ->
84
+ * pm-r29-memory-test.js). It errs toward running MORE: a WIDE file, or a changed workspace file that
85
+ * no suite can be traced to, selects the whole list. Returns { files, suites, why }. */
86
+ function changedSuites(list, ref) {
87
+ const git = (...a) => {
88
+ const r = require('child_process').spawnSync('git', a, { cwd: DIR, encoding: 'utf8' });
89
+ return r.status === 0 ? r.stdout : null;
90
+ };
91
+ const top = (git('rev-parse', '--show-toplevel') || '').trim();
92
+ if (!top) return { files: [], suites: list, why: 'not a git checkout, so every suite runs' };
93
+ const base = (git('merge-base', 'HEAD', ref) || '').trim();
94
+ if (!base) return { files: [], suites: list, why: `no merge base with ${ref} (fetch it first), so every suite runs` };
95
+ const prefix = path.relative(top, DIR).split(path.sep).join('/') + '/';
96
+ const names = [
97
+ git('diff', '--name-only', base), // committed on this branch and uncommitted changes
98
+ git('ls-files', '--others', '--exclude-standard', '--full-name', DIR),
99
+ ].join('\n').split('\n').map((f) => f.trim()).filter(Boolean);
100
+ const files = [...new Set(names)].filter((f) => f.startsWith(prefix)).map((f) => f.slice(prefix.length));
101
+ if (!files.length) return { files, suites: [], why: `nothing under workspace/ differs from ${ref}` };
102
+ const wide = files.filter((f) => WIDE.has(f));
103
+ if (wide.length) return { files, suites: list, why: `${wide.join(', ')} changed, and every suite depends on it` };
104
+ const source = new Map(list.map((f) => [f, fs.existsSync(path.join(DIR, f)) ? fs.readFileSync(path.join(DIR, f), 'utf8') : '']));
105
+ const picked = new Set();
106
+ const untraced = [];
107
+ for (const f of files) {
108
+ if (source.has(f)) { picked.add(f); continue; }
109
+ if (/^(guide|docs)\/|\.md$/.test(f)) continue; // prose: no suite asserts on it except guide.test.js, a unit suite
110
+ const stem = path.basename(f).replace(/\.[^.]+$/, '');
111
+ const key = stem.replace(/^(panel|view|tab)-/, '');
112
+ const hits = list.filter((s) => source.get(s).includes(stem) || s.includes(`-${key}-`) || s.includes(`-${key}.`));
113
+ if (!hits.length) untraced.push(f);
114
+ for (const h of hits) picked.add(h);
115
+ }
116
+ if (untraced.length) return { files, suites: list, why: `no suite names ${untraced.slice(0, 3).join(', ')}${untraced.length > 3 ? ' …' : ''}, so every suite runs` };
117
+ return { files, suites: list.filter((f) => picked.has(f)), why: `${files.length} changed file(s) reach these suites` };
118
+ }
119
+
68
120
  function check() {
69
121
  const listed = new Set(GROUPS.all);
70
122
  const problems = [];
@@ -114,6 +166,14 @@ async function main() {
114
166
  let list = GROUPS[group];
115
167
  const shard = flag('shard');
116
168
  list = shard ? shardOf(list, shard) : bySlowest(list);
169
+ const changed = flag('changed');
170
+ if (changed) {
171
+ const ref = changed === true ? 'origin/main' : changed;
172
+ const pick = changedSuites(list, ref);
173
+ console.log(`run-tests: --changed against ${ref}: ${pick.why}. ${pick.suites.length} of ${list.length} suites run here; CI runs all ${list.length}.`);
174
+ list = pick.suites;
175
+ if (!list.length) return;
176
+ }
117
177
  if (flag('list')) { console.log(list.join('\n')); return; }
118
178
  const cpus = typeof os.availableParallelism === 'function' ? os.availableParallelism() : os.cpus().length;
119
179
  // A browser suite runs a server, a tmux daemon and a Chromium of its own, and several of them assert
@@ -131,7 +131,7 @@
131
131
  "pm-repos-test.js": 3,
132
132
  "pm-initiatives-test.js": 15,
133
133
  "pm-detail-test.js": 16,
134
- "pm-tabs-test.js": 59,
134
+ "pm-tabs-test.js": 50,
135
135
  "pm-term-test.js": 28,
136
136
  "pm-modes-test.js": 18,
137
137
  "pm-r1-test.js": 6,