create-pmos 0.243.0 → 0.244.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.243.0",
3
+ "version": "0.244.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@ad62a21
1
+ kernel-source: pmos@c924d0d
2
2
  assembled: 2026-10-15
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-15T01:32:11Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-15T02:53:25Z"}
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-15T01:32:11Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-15T02:53:25Z"}
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-15T01:32:11Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-15T02:53:25Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -217,6 +217,11 @@ function parseResult(stdout) {
217
217
  };
218
218
  }
219
219
 
220
+ /** Did the run fail because Claude is signed out on this computer? The CLI prints "Failed to authenticate: OAuth session expired…" and exits 1. */
221
+ const SIGNED_OUT_RE = /failed to authenticate|oauth (?:session|token) (?:has )?expired|could not be refreshed|please run \/login|not logged in|invalid (?:api key|bearer token)|authentication_error/i;
222
+ const signedOut = (text) => SIGNED_OUT_RE.test(String(text || ''));
223
+ const SIGNED_OUT_WHY = 'Claude is signed out on this computer. Open Terminal, run `claude`, type /login and sign in. PMOS carries on by itself afterwards.';
224
+
220
225
  const oneLine = (v, max) => { const t = String(v == null ? '' : v).replace(/\s+/g, ' ').trim(); return t.length > max ? t.slice(0, max - 1) + '…' : t; };
221
226
  // The one argument that says what a tool call does: the command, the file, the pattern, the address.
222
227
  function toolWhat(input) {
@@ -260,17 +265,19 @@ function outcomeOf(r, { code = null, signal = null, timedOut = false, stopped =
260
265
  const first = String(res.text || '').split('\n').map((x) => x.replace(/^[#>*\-\s]+/, '').trim()).find(Boolean) || '';
261
266
  const denied = Array.isArray(res.denied) ? res.denied : [];
262
267
  const base = { turns: res.turns == null ? null : res.turns, durationMs: res.durationMs == null ? null : res.durationMs, denied };
268
+ const auth = !timedOut && !stopped && signedOut(res.text);
263
269
  const ok = !timedOut && !stopped && !signal && code === 0 && res.ok && !res.isError;
264
270
  if (ok) return { ok: true, headline: oneLine(first || 'Finished. It wrote no summary.', 160), why: null, ...base };
265
271
  let why;
266
272
  if (stopped) why = 'Stopped by you.';
267
273
  else if (timedOut) why = `Stopped: still running after ${timeoutMin || Math.round(DEFAULT_TIMEOUT_MS / 60000)} minutes.`;
268
274
  else if (signal) why = `The process was ended (${signal}).`;
275
+ else if (signedOut(res.text)) why = SIGNED_OUT_WHY;
269
276
  else if (!res.ok) why = `It exited with code ${code} and printed no result${res.text ? ': ' + oneLine(res.text.split('\n').filter(Boolean).pop(), 200) : '.'}`;
270
277
  else if (res.isError) why = `The run reported an error${first ? ': ' + oneLine(first, 200) : '.'}`;
271
278
  else why = `It exited with code ${code}.`;
272
279
  if (denied.length) why += ` Not allowed in a background run: ${denied.join(', ')}.`;
273
- return { ok: false, headline: null, why, ...base };
280
+ return { ok: false, headline: null, why, ...(auth ? { auth: true } : {}), ...base };
274
281
  }
275
282
 
276
283
  function logLines(r, exitCode) {
@@ -284,4 +291,4 @@ function logLines(r, exitCode) {
284
291
  return out;
285
292
  }
286
293
 
287
- module.exports = { HEADLESS_SKILLS, HEADLESS_ORIGINS, ASK_ORIGINS, DRIVE_ORIGINS, SCAN_WEB_ORIGINS, SCAN_REPO_ORIGINS, SCAN_RESEARCH_WEB_ORIGINS, SCAN_RESEARCH_REPO_ORIGINS, WEB_KINDS, REPORT_SETUP_ORIGINS, REPORT_FIND_ORIGINS, CORE_DOC_ORIGINS, REPORT_DESIGN_ORIGINS, REPORT_SAMPLE_FETCH_ORIGINS, REPORT_STRUCTURE_ORIGINS, REPORT_STRUCTURE_DRY_ORIGINS, TOOLS, DEFAULT_TIMEOUT_MS, kindFor, argsFor, timeoutMs, parseResult, streamLines, outcomeOf, logLines, switchedOff };
294
+ module.exports = { HEADLESS_SKILLS, HEADLESS_ORIGINS, ASK_ORIGINS, DRIVE_ORIGINS, SCAN_WEB_ORIGINS, SCAN_REPO_ORIGINS, SCAN_RESEARCH_WEB_ORIGINS, SCAN_RESEARCH_REPO_ORIGINS, WEB_KINDS, REPORT_SETUP_ORIGINS, REPORT_FIND_ORIGINS, CORE_DOC_ORIGINS, REPORT_DESIGN_ORIGINS, REPORT_SAMPLE_FETCH_ORIGINS, REPORT_STRUCTURE_ORIGINS, REPORT_STRUCTURE_DRY_ORIGINS, TOOLS, DEFAULT_TIMEOUT_MS, kindFor, argsFor, timeoutMs, parseResult, streamLines, outcomeOf, signedOut, SIGNED_OUT_WHY, logLines, switchedOff };
@@ -24,6 +24,7 @@
24
24
  const CATS = Object.freeze(['Answer', 'Fact check', 'Decision', 'Time', 'Topics', 'Context', 'Prep', 'Follow-up']);
25
25
  const REF_TYPES = Object.freeze(['initiative', 'plan-item', 'release', 'item', 'jira', 'ask', 'report', 'note', 'decision', 'portfolio', 'event']);
26
26
  const MIN_GAP_MS = 40000; // the companion runs at most this often per note
27
+ const AUTH_WAIT_MS = 120000; // after a run found Claude signed out, wait this long before the next try
27
28
  const TAIL_MINUTES = 5; // how much of the talk the companion reads
28
29
  const TAIL_MAX = 40; // and at most this many lines
29
30
  const TOPICS_MAX = 8;
@@ -231,7 +232,7 @@ function foldState(lines, { topics = [], startMs = null, nowMs = Date.now() } =
231
232
  return byKey.get(k);
232
233
  };
233
234
  for (const t of topics) addTopic(t.text || t, t.src || 'steps');
234
- let current = null; const runs = []; let gist = ''; let chips = []; const notes = []; let seen = 0; let lastRunAt = 0; let pending = false; let error = null;
235
+ let current = null; const runs = []; let gist = ''; let chips = []; const notes = []; let seen = 0; let lastRunAt = 0; let pending = false; let error = null; let authAt = 0;
235
236
  const asks = new Map();
236
237
  const close = (at) => { const r = runs[runs.length - 1]; if (r && r.to == null) r.to = at; };
237
238
  const switchTo = (row, at) => { if (!row || (current && current.id === row.id && runs.length && runs[runs.length - 1].to == null)) return; close(at); row.parked = false; current = row; runs.push({ topic: row.id, from: at, to: null }); };
@@ -240,9 +241,9 @@ function foldState(lines, { topics = [], startMs = null, nowMs = Date.now() } =
240
241
  if (!l || typeof l !== 'object') continue;
241
242
  const at = Number.isFinite(l.at) ? l.at : 0;
242
243
  if (l.k === 'run') { lastRunAt = at; seen = Number.isFinite(l.lines) ? l.lines : seen; pending = true; }
243
- else if (l.k === 'fail') { if (!l.id) { pending = false; error = str(l.error, 200) || 'The companion did not answer.'; } else if (asks.has(l.id)) asks.get(l.id).state = 'failed', asks.get(l.id).error = str(l.error, 200); }
244
+ else if (l.k === 'fail') { if (!l.id) { pending = false; error = str(l.error, 200) || 'The companion did not answer.'; authAt = l.auth ? at : 0; } else if (asks.has(l.id)) asks.get(l.id).state = 'failed', asks.get(l.id).error = str(l.error, 200); }
244
245
  else if (l.k === 'out') {
245
- pending = false; error = null;
246
+ pending = false; error = null; authAt = 0;
246
247
  if (l.gist) gist = l.gist;
247
248
  if (Array.isArray(l.chips)) chips = l.chips;
248
249
  const tp = l.topic || {};
@@ -271,7 +272,7 @@ function foldState(lines, { topics = [], startMs = null, nowMs = Date.now() } =
271
272
  const state = current && current.id === r.id ? 'now' : r.parked ? 'parked' : spent(r.id) > 0 ? 'discussed' : 'notyet';
272
273
  return { id: r.id, text: r.text, src: r.src, state, minutes, ...(r.at ? { at: r.at } : {}), ...(r.by ? { by: r.by } : {}) };
273
274
  });
274
- return { gist, chips, current: current ? current.id : null, topics: outTopics, runs: outRuns, notes, seen, last_run_at: lastRunAt, pending, asks: [...asks.values()], error };
275
+ return { gist, chips, current: current ? current.id : null, topics: outTopics, runs: outRuns, notes, seen, last_run_at: lastRunAt, pending, asks: [...asks.values()], error, auth_at: authAt };
275
276
  }
276
277
 
277
278
  /* ── when the companion runs ────────────────────────────────────────────────────────────────────────────────────── */
@@ -283,6 +284,7 @@ const off = (env) => /^(off|0|false|no)$/i.test(String((env || process.env).PMOS
283
284
  function shouldRun(now, state, note, opts = {}) {
284
285
  const st = state || {};
285
286
  if (off(opts.env)) return { run: false, reason: 'off' };
287
+ if (st.auth_at && now - st.auth_at < AUTH_WAIT_MS) return { run: false, reason: 'signed out' };
286
288
  if (!note || note.state !== 'recording') return { run: false, reason: 'not recording' };
287
289
  if (st.pending && st.last_run_at && now - st.last_run_at < (opts.timeoutMs || 20 * 60000)) return { run: false, reason: 'busy' };
288
290
  if (!(note.lines > (st.seen || 0))) return { run: false, reason: 'no new lines' };
@@ -129,6 +129,7 @@ function settleRun(ctx, key, done, now) {
129
129
  const res = done && done.result;
130
130
  if (done && done.stopped) return append(ctx.runtimeDir, key + '.jsonl', { k: 'fail', at, error: 'The run was stopped before it answered.' });
131
131
  if (done && done.timedOut) return append(ctx.runtimeDir, key + '.jsonl', { k: 'fail', at, error: 'The companion took too long and was stopped.' });
132
+ if (res && headless.signedOut(res.text)) return append(ctx.runtimeDir, key + '.jsonl', { k: 'fail', at, auth: true, error: headless.SIGNED_OUT_WHY });
132
133
  if (!res || !res.ok || res.isError) return append(ctx.runtimeDir, key + '.jsonl', { k: 'fail', at, error: 'The companion did not answer (' + ((done && done.note) || 'no result') + ').' });
133
134
  const parsed = F.parseCompanion(res.text);
134
135
  // a malformed answer is dropped, not shown
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.243.0",
3
+ "version": "0.244.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.243.0",
9
+ "version": "0.244.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@xterm/addon-fit": "^0.11.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.243.0",
3
+ "version": "0.244.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",