create-pmos 0.522.0 → 0.523.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.
@@ -316,12 +316,37 @@ function installWorkspaceGlobal() {
316
316
  { encoding: 'utf8', shell, ...hide() });
317
317
  const tgz = pack.status === 0 ? (pack.stdout || '').trim().split('\n').pop() : '';
318
318
  if (!tgz) { console.error(`create-pmos: could not pack the workspace app${pack.stderr ? ' — ' + pack.stderr.trim() : ''}`); return 1; }
319
- return cp.spawnSync(npmCmd, ['install', '-g', path.join(tmp, tgz), '--no-audit', '--no-fund'],
319
+ const status = cp.spawnSync(npmCmd, ['install', '-g', path.join(tmp, tgz), '--no-audit', '--no-fund'],
320
320
  { stdio: 'inherit', shell, ...hide() }).status;
321
+ if (status === 0) shipKitWithApp(npmCmd, shell);
322
+ return status;
321
323
  } finally {
322
324
  try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {}
323
325
  }
324
326
  }
327
+ // The installed app sets up new projects ("Set up PMOS in <folder>"), so it needs the kit. Only this temporary npx bundle has
328
+ // payload/ beside workspace/; the global copy got workspace/ alone and every setup failed "This PMOS install has no kit to add"
329
+ // (onboarding grill, 2026-11-04). Copy the kit into the installed app as <app>/payload, where lib/project-onboard.js looks first.
330
+ // It is copied after `npm install -g` and not packed into the tarball: the workspace's .gitignore patterns (state/acceptances.jsonl)
331
+ // would drop kit files from an `npm pack`. Best-effort: without it setup falls back to `npx create-pmos`.
332
+ function shipKitWithApp(npmCmd, shell) {
333
+ if (!fs.existsSync(path.join(PAYLOAD, 'AGENTS.md'))) return false;
334
+ const root = cp.spawnSync(npmCmd, ['root', '-g'], { encoding: 'utf8', shell, ...hide() });
335
+ const app = path.join(String(root.stdout || '').trim(), 'pmos-workspace');
336
+ if (root.status !== 0 || !fs.existsSync(path.join(app, 'package.json'))) {
337
+ console.log(' (the kit was not copied into the installed app; project setup will download it when needed)');
338
+ return false;
339
+ }
340
+ try {
341
+ const dest = path.join(app, 'payload');
342
+ fs.rmSync(dest, { recursive: true, force: true });
343
+ fs.cpSync(PAYLOAD, dest, { recursive: true });
344
+ return true;
345
+ } catch (e) {
346
+ console.log(` (the kit was not copied into the installed app: ${e.message}; project setup will download it when needed)`);
347
+ return false;
348
+ }
349
+ }
325
350
  // A persistent server started by the OLD code keeps serving OLD assets after an upgrade — stop it so
326
351
  // the next launch brings the server up on the freshly installed code. Best-effort; ignore failures.
327
352
  function stopWorkspaceServer() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pmos",
3
- "version": "0.522.0",
3
+ "version": "0.523.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": {
@@ -32,6 +32,19 @@
32
32
  "audit"
33
33
  ],
34
34
  "pmosChanges": [
35
+ {
36
+ "v": "0.523.0",
37
+ "at": "2026-11-04T08:06:51.586Z",
38
+ "f": 4,
39
+ "prs": [
40
+ {
41
+ "n": 1221,
42
+ "t": "The installed app carries the kit, and ~/PMOS holds one product",
43
+ "s": "The installed app carries the kit, and ~/PMOS holds one product",
44
+ "k": "fix"
45
+ }
46
+ ]
47
+ },
35
48
  {
36
49
  "v": "0.522.0",
37
50
  "at": "2026-11-04T07:22:01.639Z",
@@ -373,37 +386,6 @@
373
386
  "d": "The Working card could keep counting after Claude had answered. Now it goes away when the answer ends."
374
387
  }
375
388
  ]
376
- },
377
- {
378
- "v": "0.503.0",
379
- "at": "2026-11-03T05:48:42.979Z",
380
- "f": 4,
381
- "prs": [
382
- {
383
- "n": 1186,
384
- "t": "A timeline end ◇ names the initiative's own workflow, never a fixed Deliver",
385
- "s": "A timeline end ◇ names the initiative's own workflow, never a fixed Deliver",
386
- "k": "fix",
387
- "h": "Timelines end at the right step",
388
- "d": "A timeline always ended at Deliver, whatever the workflow. Now it ends at the initiative's own last step."
389
- },
390
- {
391
- "n": 1187,
392
- "t": "Working card leads with the todo or turn goal, not the raw command",
393
- "s": "Working card leads with the todo or turn goal, not the raw command",
394
- "k": "new",
395
- "h": "A calmer Working card",
396
- "d": "While Claude worked, a card showed the raw command. Now it leads with the to-do or goal it is working on."
397
- },
398
- {
399
- "n": 1188,
400
- "t": "New meeting recording view, word fix in place",
401
- "s": "New meeting recording view, word fix in place",
402
- "k": "new",
403
- "h": "A new meeting recording view",
404
- "d": "A recording showed everything at once. Now it opens on a short summary, each point has one check, and you can fix a misheard word in place."
405
- }
406
- ]
407
389
  }
408
390
  ]
409
391
  }
@@ -1,3 +1,3 @@
1
- kernel-source: pmos@60230431
1
+ kernel-source: pmos@45be2110
2
2
  assembled: 2026-11-04
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-11-04T07:21:58Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-04T08:06:47Z"}
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-11-04T07:21:58Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-04T08:06:47Z"}
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-11-04T07:21:58Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-04T08:06:47Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -1,4 +1,17 @@
1
1
  [
2
+ {
3
+ "v": "0.523.0",
4
+ "at": "2026-11-04T08:06:51.586Z",
5
+ "f": 4,
6
+ "prs": [
7
+ {
8
+ "n": 1221,
9
+ "t": "The installed app carries the kit, and ~/PMOS holds one product",
10
+ "s": "The installed app carries the kit, and ~/PMOS holds one product",
11
+ "k": "fix"
12
+ }
13
+ ]
14
+ },
2
15
  {
3
16
  "v": "0.522.0",
4
17
  "at": "2026-11-04T07:22:01.639Z",
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+ /* connector-read.js — which connector tools are read-shaped. Shared by project setup (lib/project-onboard.js) and background runs
3
+ * (lib/headless.js connectorReads). Kept in its own file so a change to project setup does not reach every suite that uses headless runs. */
4
+ const READ_VERBS = new Set(['search', 'get', 'list', 'read', 'fetch', 'find', 'query', 'lookup', 'view', 'describe', 'retrieve', 'browse', 'resolve']);
5
+ const WRITE_VERBS = new Set(['create', 'update', 'delete', 'add', 'post', 'send', 'write', 'edit', 'move', 'transition', 'comment', 'assign', 'upload',
6
+ 'remove', 'set', 'merge', 'close', 'archive', 'share', 'copy', 'trash', 'label', 'mark', 'apply', 'schedule', 'reply', 'forward', 'draft', 'respond',
7
+ 'invite', 'publish', 'run', 'execute', 'trigger', 'link', 'unlink', 'approve', 'submit', 'save', 'log', 'record', 'register', 'insert', 'append',
8
+ 'clear', 'fork', 'enable', 'disable', 'untrash', 'unmark', 'unlabel', 'cancel', 'install', 'import', 'export', 'generate', 'use', 'push', 'request', 'rename']);
9
+ const words = (s) => String(s).replace(/([a-z0-9])([A-Z])/g, '$1 $2').split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w.toLowerCase());
10
+
11
+ /** A tool may be granted only when its own name (after the server prefix) leads with a read verb and holds no write verb. */
12
+ function readTool(toolName, prefix) {
13
+ const own = prefix && toolName.startsWith(prefix) ? toolName.slice(prefix.length) : toolName.replace(/^mcp__[^_]+(?:_[^_]+)*?__/, '');
14
+ const w = words(own);
15
+ if (!w.length || w.some((x) => WRITE_VERBS.has(x))) return false;
16
+ return w.slice(0, 3).some((x) => READ_VERBS.has(x));
17
+ }
18
+
19
+ module.exports = { readTool, READ_VERBS, WRITE_VERBS };
@@ -215,7 +215,7 @@ const TOOLS = Object.freeze({
215
215
  // without a prompt. The kinds below read the PM's sources through connectors (Jira, Confluence, Slack, the calendar, mail, Drive), and before
216
216
  // dontAsk they relied on the PM's default mode letting a read-only connector tool run. Now the server names those tools: the read-shaped tools
217
217
  // of the PM's connected servers (CONNECTOR_READ_SEED, plus what each run's init line reports, server.js), filtered by the same read-verb rule the
218
- // onboarding search uses (lib/project-onboard.js readTool). A connector WRITE is never named, so it is still denied. The other kinds read no
218
+ // onboarding search uses (lib/connector-read.js readTool). A connector WRITE is never named, so it is still denied. The other kinds read no
219
219
  // connector and get none.
220
220
  const CONNECTOR_READ_KINDS = Object.freeze(['skill', 'report-setup', 'report-find', 'ms-review', 'propose', 'metric-draft', 'impact', 'metric-read',
221
221
  'report-sample-fetch', 'report-structure', 'report-structure-dry', 'report-structure-pull']);
@@ -235,7 +235,7 @@ const CONNECTOR_READ_SEED = Object.freeze([
235
235
  const MCP_TOOL_RE = /^mcp__[\w-]+__[\w.-]+$/; // one exact tool: Claude Code's rule syntax, no server-wide or wildcard rule
236
236
  /** The read-shaped connector tools among `names` (a run's init line, or a cached list): exact names only, never a write verb. */
237
237
  function connectorReads(names) {
238
- const { readTool } = require('./project-onboard.js');
238
+ const { readTool } = require('./connector-read.js');
239
239
  const out = [];
240
240
  for (const n of Array.isArray(names) ? names : []) {
241
241
  const t = String(n || '');
@@ -24,11 +24,7 @@ const os = require('os');
24
24
  const crypto = require('crypto');
25
25
  const cp = require('child_process');
26
26
 
27
- const READ_VERBS = new Set(['search', 'get', 'list', 'read', 'fetch', 'find', 'query', 'lookup', 'view', 'describe', 'retrieve', 'browse', 'resolve']);
28
- const WRITE_VERBS = new Set(['create', 'update', 'delete', 'add', 'post', 'send', 'write', 'edit', 'move', 'transition', 'comment', 'assign', 'upload',
29
- 'remove', 'set', 'merge', 'close', 'archive', 'share', 'copy', 'trash', 'label', 'mark', 'apply', 'schedule', 'reply', 'forward', 'draft', 'respond',
30
- 'invite', 'publish', 'run', 'execute', 'trigger', 'link', 'unlink', 'approve', 'submit', 'save', 'log', 'record', 'register', 'insert', 'append',
31
- 'clear', 'fork', 'enable', 'disable', 'untrash', 'unmark', 'unlabel', 'cancel', 'install', 'import', 'export', 'generate', 'use', 'push', 'request', 'rename']);
27
+ const { readTool } = require('./connector-read.js');
32
28
  const HOOKS_OFF = JSON.stringify({ disableAllHooks: true });
33
29
  const MAX_FINDINGS = 40;
34
30
  const FIELDS = ['purpose', 'team', 'owner', 'timeline', 'goals'];
@@ -37,15 +33,6 @@ const clean = (v, n = 300) => String(v == null ? '' : v).replace(/[\u0000-\u001f
37
33
  const httpsOnly = (u) => { try { const x = new URL(String(u)); return x.protocol === 'https:' ? x.href.slice(0, 500) : null; } catch { return null; } };
38
34
  /** Claude Code's tool prefix for a server name: `mcp__<name with every other character as _>__`. */
39
35
  const prefixOf = (name) => 'mcp__' + String(name).replace(/[^A-Za-z0-9_-]/g, '_') + '__';
40
- const words = (s) => String(s).replace(/([a-z0-9])([A-Z])/g, '$1 $2').split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w.toLowerCase());
41
-
42
- /** A tool may be granted only when its own name (after the server prefix) leads with a read verb and holds no write verb. */
43
- function readTool(toolName, prefix) {
44
- const own = prefix && toolName.startsWith(prefix) ? toolName.slice(prefix.length) : toolName.replace(/^mcp__[^_]+(?:_[^_]+)*?__/, '');
45
- const w = words(own);
46
- if (!w.length || w.some((x) => WRITE_VERBS.has(x))) return false;
47
- return w.slice(0, 3).some((x) => READ_VERBS.has(x));
48
- }
49
36
 
50
37
  function parseMcpList(stdout) {
51
38
  const out = [];
@@ -282,7 +269,9 @@ function installKit(root, kit) {
282
269
  if (fs.existsSync(dest)) throw new Error('A pmos folder is already there. PMOS will not replace it.');
283
270
  if (kit.payload) { fs.cpSync(kit.payload, dest, { recursive: true }); }
284
271
  else if (kit.buildScript) { cp.execFileSync('bash', [kit.buildScript, root], { stdio: 'ignore', timeout: 300000 }); }
285
- else throw new Error('This PMOS install has no kit to add.');
272
+ // No npm fallback here: only lib/kit-update.js reaches the registry (no-network.test.js). An app installed before the kit shipped with
273
+ // it gets the kit with its next update.
274
+ else throw new Error('This PMOS install has no kit to add. Update PMOS in Settings › Updates, then try again.');
286
275
  if (!fs.existsSync(path.join(dest, 'AGENTS.md'))) throw new Error('The kit did not install completely.');
287
276
  const profile = path.join(root, '.pmos-profile');
288
277
  if (!fs.existsSync(profile)) fs.writeFileSync(profile, 'pm-artifacts\n# PMOS repository profile (D68): pm-artifacts | product-code\n');
@@ -290,8 +279,10 @@ function installKit(root, kit) {
290
279
  const cur = fs.existsSync(ga) ? fs.readFileSync(ga, 'utf8') : '';
291
280
  if (!cur.split('\n').some((l) => l.trim() === '*.jsonl merge=union')) fs.writeFileSync(ga, `${cur}${cur && !cur.endsWith('\n') ? '\n' : ''}*.jsonl merge=union\n`);
292
281
  }
282
+ /** Where the kit comes from: the installed app's own payload/ (create-pmos copies it there), the npx bundle's, a PMOS checkout's
283
+ * build script. */
293
284
  function kitSource(here = __dirname, env = process.env) {
294
- const candidates = [env.PMOS_KIT_PAYLOAD, path.join(here, '..', '..', 'payload'), path.join(here, '..', '..', 'npm-pkg', 'payload')].filter(Boolean);
285
+ const candidates = [env.PMOS_KIT_PAYLOAD, path.join(here, '..', 'payload'), path.join(here, '..', '..', 'payload'), path.join(here, '..', '..', 'npm-pkg', 'payload')].filter(Boolean);
295
286
  const payload = candidates.find((p) => fs.existsSync(path.join(p, 'AGENTS.md')));
296
287
  if (payload) return { payload };
297
288
  const buildScript = path.join(here, '..', '..', 'kit', 'build-kit.sh');
@@ -236,7 +236,8 @@ function create(host) {
236
236
  }
237
237
  start(src, ws, { trigger: d.trigger, slot: d.slot, note: d.note });
238
238
  }
239
- try { healTick(t, set); } catch (e) { warn('source repair', e); }
239
+ // PMOS_SOURCE_HEAL=off stops the repairs (a suite about pressed and scheduled runs sets it, so no repair run races them)
240
+ if (process.env.PMOS_SOURCE_HEAL !== 'off') { try { healTick(t, set); } catch (e) { warn('source repair', e); } }
240
241
  if (t - lastPrune > PRUNE_EVERY_MS) {
241
242
  lastPrune = t;
242
243
  try { const p = R.prune(dir, rules.keep, t); if (p.lines || p.copies) warn('refresh log', { message: `removed ${p.lines} lines and ${p.copies} copies older than ${rules.keep} days` }); } catch (e) { warn('refresh log prune', e); }
@@ -62,6 +62,14 @@ function hasWork(root) {
62
62
  return INITIATIVE_DIRS.some((d) => ls(d).some((n) => n.endsWith('.md') && !/^README|template/i.test(n)))
63
63
  || RECORD_DIRS.some((d) => ls(d).some((n) => n.endsWith('.jsonl') && filled(d, n)));
64
64
  }
65
+ /** The installer's home folder while it is still an empty kit (PMOS in it, no work yet), as folder facts; otherwise null. */
66
+ function installerKit(ctx) {
67
+ if (!ctx.homeRoot || !(ctx.workspaceList ? ctx.workspaceList() : []).some((w) => w && w.root && path.resolve(w.root) === path.resolve(ctx.homeRoot))) return null;
68
+ const pm = P.pmosIn(ctx.homeRoot);
69
+ if (!pm || !pm.kit || hasWork(ctx.homeRoot)) return null;
70
+ const facts = P.folderFacts(ctx.homeRoot);
71
+ return facts.ok && facts.exists && !facts.inside_other_git ? facts : null;
72
+ }
65
73
  function view(ctx) {
66
74
  const { st, bound } = fresh(ctx, read(ctx));
67
75
  const ws = st.done && st.done.ws && ctx.getWs(st.done.ws);
@@ -123,6 +131,15 @@ async function handle(req, res, url, ctx) {
123
131
  if (r.cancelled || !r.path) return send({ ok: false, cancelled: true });
124
132
  if (b.mode === 'new') parent = r.path; else root = r.path;
125
133
  }
134
+ // One folder per product: the one-line installer already made ~/PMOS a Git repository with an empty kit and opened it as the home
135
+ // product. The first proposal is that folder, so setup fills it (and renames it to the product) instead of nesting a second
136
+ // repository inside it and leaving an empty "PMOS" product behind (onboarding grill, 2026-11-04). Choosing "Make a new folder"
137
+ // afterwards still proposes a new one.
138
+ const reuse = b.mode === 'new' && !parent && !st.folder && installerKit(ctx);
139
+ if (reuse) {
140
+ write(ctx, { ...st, folder: { mode: 'existing', root: reuse.root, facts: reuse, chosen: false, reused: true } });
141
+ return send(view(ctx));
142
+ }
126
143
  if (b.mode === 'new') {
127
144
  const pr = P.proposeFolder(parent, st.project.name);
128
145
  if (!pr.ok) return send(pr, 400);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.522.0",
3
+ "version": "0.523.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.522.0",
9
+ "version": "0.523.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@anthropic-ai/sdk": "^1.3.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.522.0",
3
+ "version": "0.523.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",