create-pmos 0.6.2 → 0.6.3

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.6.2",
3
+ "version": "0.6.3",
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. `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@2e880f8
1
+ kernel-source: pmos@f4589bf
2
2
  assembled: 2026-07-27
3
3
  mode: vendor
@@ -74,20 +74,23 @@ function openBrowser(url) {
74
74
  try { spawn(cmd[0], cmd[1], { detached: true, stdio: 'ignore' }).unref(); } catch {}
75
75
  }
76
76
 
77
- // node-pty compiles pty.node (require loads it) and, on macOS only, a separate spawn-helper
78
- // binary (exec'd at spawn — Linux uses forkpty directly and never builds it). A partial macOS
79
- // build with pty.node but no spawn-helper loads fine yet dies with 'posix_spawnp failed' on every
80
- // session — the #1 tenant failure, common on a brand-new/non-LTS Node. So "installed" is not
81
- // "works": verify the artifacts the running platform actually needs, not just the package folder.
82
- function nodePtyBuildOk() {
83
- const rel = path.join(WS, 'node_modules', 'node-pty', 'build', 'Release');
84
- if (!fs.existsSync(path.join(rel, 'pty.node'))) return false;
85
- return process.platform !== 'darwin' || fs.existsSync(path.join(rel, 'spawn-helper'));
86
- }
87
77
  function npm(args) {
88
78
  return spawnSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', args,
89
79
  { cwd: WS, stdio: 'inherit', shell: process.platform === 'win32' }).status === 0;
90
80
  }
81
+ // node-pty exec()s a prebuilt macOS `spawn-helper` to open each PTY; the prebuild-extraction path
82
+ // can drop its executable bit, and then EVERY session dies with 'posix_spawnp failed' — the #1
83
+ // field failure. Restore +x wherever node-pty keeps the helper (prebuilds/<plat>-<arch>/ or a
84
+ // from-source build/Release/) before the server starts. Idempotent; no-op off macOS.
85
+ function fixSpawnHelperPerms() {
86
+ if (process.platform === 'win32') return;
87
+ const root = path.join(WS, 'node_modules', 'node-pty');
88
+ const targets = [path.join(root, 'build', 'Release', 'spawn-helper')];
89
+ try { const pb = path.join(root, 'prebuilds'); for (const d of fs.readdirSync(pb)) targets.push(path.join(pb, d, 'spawn-helper')); } catch {}
90
+ for (const f of targets) {
91
+ try { const st = fs.statSync(f); if (!(st.mode & 0o111)) fs.chmodSync(f, st.mode | 0o755); } catch {}
92
+ }
93
+ }
91
94
  function ensureInstalled() {
92
95
  if (!fs.existsSync(path.join(WS, 'node_modules', 'node-pty'))) {
93
96
  say('First run — installing the workspace (one-time, ~20s)…');
@@ -96,21 +99,7 @@ function ensureInstalled() {
96
99
  return false;
97
100
  }
98
101
  }
99
- // Self-heal a partial/incomplete native build (node-pty ships no prebuilds, so `npm rebuild`
100
- // compiles from source) BEFORE the server tries to spawn a session and dies with posix_spawnp.
101
- if (!nodePtyBuildOk()) {
102
- say('node-pty is present but its native build is incomplete — rebuilding from source…');
103
- npm(['rebuild', 'node-pty']);
104
- }
105
- if (!nodePtyBuildOk()) {
106
- const toolchain = process.platform === 'darwin' ? 'xcode-select --install'
107
- : process.platform === 'win32' ? 'Visual Studio Build Tools (C++)'
108
- : 'apt install build-essential python3';
109
- say('✗ node-pty could not be built on this machine. Run `pmos doctor` for the full diagnosis.');
110
- say(` Most often: install a C/C++ toolchain (${toolchain}),`);
111
- say(` or switch to Node 22 LTS (you are on ${process.version}; a very new/non-LTS Node may not build node-pty).`);
112
- return false;
113
- }
102
+ fixSpawnHelperPerms(); // the #1 field failure: a non-executable prebuilt spawn-helper → posix_spawnp
114
103
  return true;
115
104
  }
116
105
 
@@ -31,54 +31,58 @@ function binaryArch(file) {
31
31
  console.log(`pmos workspace doctor — node ${process.version} (${process.platform}/${process.arch})`);
32
32
  console.log(` install dir: ${WSDIR}\n`);
33
33
 
34
- // node-pty ships NO prebuilds — it compiles a native addon on install — so `npm rebuild
35
- // node-pty` (or a clean reinstall) already builds from source; the old `--build-from-source`
36
- // npm flag is unnecessary and newer npm rejects it as unknown config. These are the canonical
37
- // fixes, dir-qualified so they are copy-pasteable from anywhere.
38
- const TOOLCHAIN = process.platform === 'darwin' ? 'xcode-select --install'
39
- : process.platform === 'win32' ? 'Visual Studio Build Tools (C++)'
40
- : 'apt install build-essential python3';
41
- const REINSTALL = `cd "${WSDIR}" && rm -rf node_modules && npm install (recompiles node-pty from source; needs a C/C++ toolchain: ${TOOLCHAIN})`;
42
- const REINSTALL_LTS = `${REINSTALL}\n If it still fails on Node ${process.version}, that Node may be too new/non-LTS for node-pty to build — switch to Node 22 LTS and retry.`;
34
+ const REINSTALL = `cd "${WSDIR}" && rm -rf node_modules && npm install (reinstalls node-pty for this platform)`;
43
35
 
44
36
  // 1. Node version
45
37
  const major = Number(process.versions.node.split('.')[0]);
46
- if (major < 18) bad(`Node ${process.version} is too old`, 'install Node 18+ (arm64-native on Apple Silicon)');
47
- else if (major % 2 === 1) { checks.push(true); console.log(`✓ Node ${process.version} (>= 18)`); console.log(` • note: Node ${major} is an ODD (non-LTS) line — node-pty may lack a matching build. If the checks below fail, try Node 22 LTS.`); }
48
- else ok(`Node ${process.version} (>= 18)`);
38
+ if (major >= 18) ok(`Node ${process.version} (>= 18)`);
39
+ else bad(`Node ${process.version} is too old`, 'install Node 18+ (arm64-native on Apple Silicon)');
49
40
 
50
41
  // 2. node-pty loads
51
42
  let pty = null;
52
43
  try { pty = require('node-pty'); ok('node-pty native module loads'); }
53
- catch (e) {
54
- bad(`node-pty failed to load: ${e.message.split('\n')[0]}`, REINSTALL_LTS);
55
- }
44
+ catch (e) { bad(`node-pty failed to load: ${e.message.split('\n')[0]}`, REINSTALL); }
56
45
 
57
- // 3. the compiled binaries are present + arch-matched (the posix_spawnp culprit). node-pty
58
- // compiles pty.node (loaded by require) and, on macOS ONLY, a separate spawn-helper binary
59
- // (exec'd at spawn time — Linux uses forkpty directly and never builds it). A PARTIAL macOS
60
- // build — pty.node present but spawn-helper missing — loads fine (check 2 passes) yet every
61
- // session dies with 'posix_spawnp failed'. That is exactly the field failure on a too-new
62
- // Node, so on macOS check both, not just one.
63
- const rel = path.join(__dirname, 'node_modules', 'node-pty', 'build', 'Release');
64
- const ptyNode = path.join(rel, 'pty.node');
65
- const helper = path.join(rel, 'spawn-helper');
66
- if (!fs.existsSync(ptyNode)) {
67
- bad('node-pty binary pty.node not found (compile did not complete)', REINSTALL_LTS);
68
- } else if (process.platform === 'darwin' && !fs.existsSync(helper)) {
69
- bad("node-pty's spawn-helper did not compile — pty.node loads but every session dies with 'posix_spawnp failed'", REINSTALL_LTS);
70
- } else {
71
- const info = binaryArch(ptyNode);
72
- const want = process.arch; // arm64 | x64 | …
73
- const got = info && info.arch;
74
- const match = !info || info.arch === 'universal' || info.x === 'ELF' || info.x === 'PE' ||
46
+ // 3. native binaries usable — and the #1 field failure, FIXED here. node-pty loads pty.node
47
+ // (from prebuilds/<plat>-<arch>/ or a from-source build/Release/) and, on macOS, exec()s a
48
+ // prebuilt spawn-helper to open each PTY. The prebuild-extraction path can drop that helper's
49
+ // executable bit, so it is present yet every session dies with 'posix_spawnp failed'. This
50
+ // check verifies pty.node's arch and, on macOS, that spawn-helper exists and is +x —
51
+ // RESTORING the bit rather than only reporting it. (Linux uses forkpty directly, no helper.)
52
+ let nproot; try { nproot = path.dirname(require.resolve('node-pty/package.json')); }
53
+ catch { nproot = path.join(__dirname, 'node_modules', 'node-pty'); }
54
+ const findBin = (name) => [
55
+ path.join(nproot, 'build', 'Release', name),
56
+ path.join(nproot, 'prebuilds', `${process.platform}-${process.arch}`, name),
57
+ ].find((f) => fs.existsSync(f));
58
+ const ptyNode = findBin('pty.node');
59
+ let bin3 = true; const note3 = [];
60
+ if (!ptyNode) { bin3 = false; note3.push('pty.node missing (install incomplete)'); }
61
+ else {
62
+ const info = binaryArch(ptyNode); const want = process.arch; const got = info && info.arch;
63
+ const archOk = !info || info.arch === 'universal' || info.x === 'ELF' || info.x === 'PE' ||
75
64
  (want === 'arm64' && got === 'arm64') || (want === 'x64' && got === 'x86_64');
76
- if (match) ok(`native binaries present${got && got !== 'universal' && info.x === 'Mach-O' ? ` (${got})` : ''}`);
77
- else bad(`ARCH MISMATCH: Node is ${want} but node-pty's binary is ${got} — this causes 'posix_spawnp failed' on every session`,
78
- `install a ${want}-native Node (e.g. \`brew install node\` on an arm64 shell), then: ${REINSTALL}`);
65
+ if (!archOk) { bin3 = false; note3.push(`ARCH MISMATCH (Node ${want} vs binary ${got})`); }
66
+ else if (got && got !== 'universal' && info.x === 'Mach-O') note3.push(got);
67
+ }
68
+ if (bin3 && process.platform === 'darwin') {
69
+ const helper = findBin('spawn-helper');
70
+ if (!helper) { bin3 = false; note3.push('spawn-helper missing'); }
71
+ else {
72
+ const st = fs.statSync(helper);
73
+ if (!(st.mode & 0o111)) {
74
+ try { fs.chmodSync(helper, st.mode | 0o755); note3.push('spawn-helper +x RESTORED — this was the posix_spawnp cause'); }
75
+ catch { bin3 = false; note3.push('spawn-helper not executable'); }
76
+ }
77
+ }
79
78
  }
79
+ if (bin3) ok('node-pty native binaries usable' + (note3.length ? ` (${note3.join('; ')})` : ''));
80
+ else bad('node-pty native binaries unusable' + (note3.length ? ` — ${note3.join('; ')}` : ''),
81
+ note3.some((n) => /MISMATCH/.test(n)) ? `install a ${process.arch}-native Node, then: ${REINSTALL}`
82
+ : note3.some((n) => /not executable/.test(n)) ? `chmod +x "${findBin('spawn-helper')}"`
83
+ : REINSTALL);
80
84
 
81
- // 4. a real PTY spawns and echoes
85
+ // 4. a real PTY spawns and echoes (after the perms fix above this should pass)
82
86
  if (pty) {
83
87
  const sh = process.platform === 'win32' ? (process.env.COMSPEC || 'powershell.exe') : '/bin/sh';
84
88
  try {
@@ -94,7 +98,7 @@ function binaryArch(file) {
94
98
  } catch (e) {
95
99
  bad(`PTY spawn failed: ${e.message}`,
96
100
  /posix_spawnp/.test(e.message)
97
- ? `broken-native-build signature (wrong-arch or spawn-helper missing). ${REINSTALL_LTS}`
101
+ ? `on macOS this is node-pty's spawn-helper lacking +x (check 3 restores it — re-run \`npm run doctor\`); otherwise a wrong-arch binary — ${REINSTALL}`
98
102
  : process.platform === 'win32' ? 'native Windows needs WSL for a real PTY — run the workspace inside WSL'
99
103
  : 'check that /bin/sh exists and is executable');
100
104
  }
@@ -36,12 +36,30 @@ try {
36
36
  } catch (e) {
37
37
  console.error('✗ node-pty failed to load — the native module is missing or built for the wrong platform.');
38
38
  console.error(` ${e.message.split('\n')[0]}`);
39
- console.error(' Fix: npm run doctor (diagnoses arch/toolchain/Node version)');
40
- console.error(' rm -rf node_modules && npm install (recompiles node-pty from source)');
41
- console.error(` node-pty compiles a native addon — needs a C/C++ toolchain (macOS: xcode-select --install; Debian/Ubuntu: apt install build-essential python3) and a supported Node LTS. You are on ${process.version}; if it is a very new or non-LTS release the build may fail — try Node 22 LTS.`);
39
+ console.error(' Fix: npm run doctor (diagnoses the exact cause)');
40
+ console.error(' rm -rf node_modules && npm install (reinstalls node-pty for this platform)');
42
41
  process.exit(1);
43
42
  }
44
43
 
44
+ // node-pty ships a prebuilt macOS `spawn-helper` binary that it exec()s to open each PTY. The
45
+ // prebuild-extraction path can drop its executable bit, and then EVERY session dies with
46
+ // `posix_spawnp failed` — the #1 field failure (it looks like a broken build but the binary is
47
+ // fine, just not +x). Restore the bit wherever node-pty keeps the helper (prebuilds/<plat>-<arch>/
48
+ // or a from-source build/Release/) BEFORE we spawn anything. Cheap, idempotent, no-op off macOS.
49
+ function ensureSpawnHelperExecutable() {
50
+ if (process.platform === 'win32') return;
51
+ try {
52
+ const root = path.dirname(require.resolve('node-pty/package.json'));
53
+ const targets = [path.join(root, 'build', 'Release', 'spawn-helper')];
54
+ const pb = path.join(root, 'prebuilds');
55
+ try { for (const d of fs.readdirSync(pb)) targets.push(path.join(pb, d, 'spawn-helper')); } catch {}
56
+ for (const f of targets) {
57
+ try { const st = fs.statSync(f); if (!(st.mode & 0o111)) fs.chmodSync(f, st.mode | 0o755); } catch {}
58
+ }
59
+ } catch {}
60
+ }
61
+ ensureSpawnHelperExecutable();
62
+
45
63
  const { checkSurface } = require('./lib/surface-contract');
46
64
  const scheduleLogic = require('./lib/schedule-logic');
47
65
  const harness = require('./lib/harness-parse');
@@ -819,20 +837,18 @@ function appendBuf(s, data) {
819
837
  s.parsed = null; // buffer moved; the cached parse is stale
820
838
  }
821
839
 
822
- // Platform-correct guidance when a spawn fails. `posix_spawnp failed` means node-pty's native
823
- // build is broken: either a wrong-arch binary (e.g. x64 Node on Apple Silicon) or an incomplete
824
- // compile (the spawn-helper never built — common on a brand-new/non-LTS Node). A shell change
825
- // won't fix it; a clean recompile will. `npm rebuild node-pty` compiles from source on its own —
826
- // node-pty ships no prebuilds — so the old `--build-from-source` npm flag is unnecessary (and
827
- // newer npm rejects it as an unknown config).
840
+ // Platform-correct guidance when a spawn fails. On macOS `posix_spawnp failed` is almost always
841
+ // node-pty's prebuilt `spawn-helper` missing its executable bit (prebuild extraction drops it) —
842
+ // the binary is fine, just not +x. The server auto-restores it at startup and `npm run doctor`
843
+ // re-applies + verifies the fix; a wrong-arch binary (x64 Node on Apple Silicon) is the rarer
844
+ // alternative, cured by a clean reinstall.
828
845
  function spawnFailureAdvice(err, cmd, isShell) {
829
846
  const msg = String(err && err.message || err);
830
847
  if (/posix_spawnp|EFAULT|ENOEXEC|wrong architecture|not valid/i.test(msg)) {
831
- return `could not spawn '${cmd}' (${msg}). node-pty's native build is broken (wrong-arch, or ` +
832
- `the spawn-helper never compiled). Fix: stop the server, run \`npm run doctor\` (it names the ` +
833
- `install dir + the exact cause), then in that dir \`rm -rf node_modules && npm install\`. ` +
834
- `Needs a C/C++ toolchain; on a very new or non-LTS Node (${process.version}) the compile can ` +
835
- `fail — try Node 22 LTS.`;
848
+ return `could not spawn '${cmd}' (${msg}). On macOS this is almost always node-pty's prebuilt ` +
849
+ `spawn-helper lacking its executable bit. Fix: run \`pmos doctor\` (it restores +x and verifies), ` +
850
+ `then ↻ respawn. If it persists, the binary is wrong-arch — \`rm -rf node_modules && npm install\` ` +
851
+ `in the workspace install dir.`;
836
852
  }
837
853
  if (process.platform === 'win32') {
838
854
  return `could not start '${cmd}': ${msg}. Native Windows needs WSL for a real PTY shell — ` +
@@ -905,8 +921,21 @@ function ensureTmuxSession(name, cmd, workdir, cols, rows) {
905
921
  function attachPty(s) {
906
922
  const name = tmuxName(s.id);
907
923
  const startedMs = Date.now();
908
- const p = pty.spawn(TMUX, ['-L', TMUX_SOCK, 'attach-session', '-t', name],
909
- { name: 'xterm-256color', cols: s.cols || 80, rows: s.rows || 24, cwd: s.cwd, env: sessionEnv() });
924
+ let p;
925
+ try {
926
+ p = pty.spawn(TMUX, ['-L', TMUX_SOCK, 'attach-session', '-t', name],
927
+ { name: 'xterm-256color', cols: s.cols || 80, rows: s.rows || 24, cwd: s.cwd, env: sessionEnv() });
928
+ } catch (e) {
929
+ // A broken node-pty (e.g. a non-executable spawn-helper) throws synchronously here. This must
930
+ // NOT propagate: on the startup first-shell it would crash the whole server and blank the
931
+ // entire UI ("no terminal painted"). Degrade to an honest exited pane carrying the fix, exactly
932
+ // like the direct-PTY path does — the server stays up and the session shows why + a respawn.
933
+ s.pty = null; s.state = 'exited';
934
+ s.note = spawnFailureAdvice(e, s.agent || 'session', false);
935
+ appendBuf(s, `\x1b[38;5;203m✗ ${s.note}\x1b[0m\r\n\x1b[38;5;244mFix the above, then ↻ respawn.\x1b[0m\r\n`);
936
+ persist(s); pushSessions();
937
+ return null;
938
+ }
910
939
  s.pty = p; s.tmux = name; s.state = 'working';
911
940
  // Start the quiet clock at ATTACH, not at first byte: a session that spawns and immediately
912
941
  // hangs should go quiet on schedule rather than read 'working' forever. On a restore this