create-pmos 0.135.0 → 0.136.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.135.0",
3
+ "version": "0.136.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@62ceee4
2
- assembled: 2026-10-11
1
+ kernel-source: pmos@2bbc680
2
+ assembled: 2026-10-12
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-11T23:50:45Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-12T00:52:51Z"}
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-11T23:50:45Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-12T00:52:51Z"}
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-11T23:50:45Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-12T00:52:51Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  Newest first, ISO dates.
4
4
 
5
- ## 2026-10-11
5
+ ## 2026-10-12
6
6
 
7
7
  - Adopted OKF v0.2 (D73).
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+ /* records-dir.js — the records folder (planning/records/<kind>.jsonl, scripts/records) of the workspace a request is about.
3
+ *
4
+ * recordsDirFor(ctx, ws) → an absolute path
5
+ *
6
+ * Order: ctx.recordsDir (a test's folder) → the workspace's own folder, from scripts/signals/layout-paths.js (a kit tenant keeps
7
+ * its records in pmos/state/records, the PMOS repo in planning/records) → the PMOS checkout this server runs from, the old
8
+ * default, for a server with no registered workspace. `ws` is the ?ws= id; null is the home workspace (server.js getWs).
9
+ *
10
+ * Before this, every records route read the PMOS checkout's planning/records whatever the workspace, so a tenant's Reports and
11
+ * Signals showed nothing (initiative reports-tenant-records).
12
+ */
13
+ const path = require('path');
14
+ const { repoScript } = require('./repo-script.js');
15
+
16
+ const CHECKOUT_RECORDS = path.join(__dirname, '..', '..', 'planning', 'records');
17
+
18
+ function recordsDirFor(ctx, ws) {
19
+ if (ctx && ctx.recordsDir) return ctx.recordsDir;
20
+ if (ctx && ctx.repoRoot) return path.join(ctx.repoRoot, 'planning', 'records');
21
+ let w = null;
22
+ try { w = ctx && typeof ctx.getWs === 'function' ? ctx.getWs(ws || null) : null; } catch { w = null; }
23
+ if (w && w.root) {
24
+ try { return require(repoScript('signals/layout-paths.js')).pathsFor(w.root).recordsDir; } catch { /* fall through */ }
25
+ }
26
+ return CHECKOUT_RECORDS;
27
+ }
28
+
29
+ module.exports = { recordsDirFor, CHECKOUT_RECORDS };
@@ -22,16 +22,16 @@ function readPeople(runtimeDir) {
22
22
  } catch { return []; }
23
23
  }
24
24
 
25
- function roadmapRead(ctx) {
26
- const root = roadmapOps.rootOf(ctx);
25
+ function roadmapRead(ctx, ws) {
26
+ const root = roadmapOps.rootOf(ctx, ws);
27
27
  const bets = records.list(root, 'bets');
28
28
  const people = readPeople(ctx.runtimeDir);
29
29
  const krs = records.list(root, 'kr-metrics').map((k) => ({ id: k.rid, title: k.metric }));
30
30
  return { status: 200, body: { ok: true, bets, capacity: roadmap.capacityLine(bets, people), people, krs } };
31
31
  }
32
32
 
33
- function breakdownRead(ctx) {
34
- const root = roadmapOps.rootOf(ctx);
33
+ function breakdownRead(ctx, ws) {
34
+ const root = roadmapOps.rootOf(ctx, ws);
35
35
  const items = breakdown.list(root);
36
36
  const people = readPeople(ctx.runtimeDir);
37
37
  const events = breakdown.readLines(root).filter((e) => ['add', 'edit', 'delete', 'dependency'].includes(e.kind))
@@ -46,7 +46,7 @@ module.exports = {
46
46
  handle: async (req, res, url, ctx) => {
47
47
  if (req.method !== 'GET') return false;
48
48
  const sub = url.pathname.slice('/api/c3-init'.length);
49
- const r = sub === '/roadmap' ? roadmapRead(ctx) : sub === '/breakdown' ? breakdownRead(ctx) : null;
49
+ const r = sub === '/roadmap' ? roadmapRead(ctx, url.searchParams.get('ws')) : sub === '/breakdown' ? breakdownRead(ctx, url.searchParams.get('ws')) : null;
50
50
  if (!r) return false;
51
51
  ctx.json(res, r.body, r.status);
52
52
  return true;
@@ -124,7 +124,7 @@ async function runDue(ctx, now = new Date()) {
124
124
  if (!w) throw new Error('no workspace is registered');
125
125
  const slug = w.id || (w.root ? path.basename(path.resolve(w.root)) : null);
126
126
  const facts = await (ctx.readFacts || readFacts)(w);
127
- const root = rootOf(ctx);
127
+ const root = rootOf(ctx, s.ws || null);
128
128
  let hypotheses = [];
129
129
  try { hypotheses = require(repoScript('records/index.js')).list(root, 'hypotheses'); } catch { hypotheses = []; }
130
130
  out = runOne(s, { facts, slug, hypotheses, runtimeDir: ctx.runtimeDir, today: ctx.today,
@@ -137,7 +137,8 @@ async function runDue(ctx, now = new Date()) {
137
137
  return done;
138
138
  }
139
139
 
140
- const rootOf = (ctx) => (ctx && ctx.recordsDir) || path.join((ctx && ctx.repoRoot) || path.join(__dirname, '..', '..'), 'planning', 'records');
140
+ const { recordsDirFor } = require('./records-dir.js');
141
+ const rootOf = (ctx, ws) => recordsDirFor(ctx, ws); // the workspace's records (lib/records-dir.js)
141
142
 
142
143
  async function readFacts(w) {
143
144
  return require('./facts-async.js').readFactsAsync(path.resolve(w.root));
@@ -166,7 +167,7 @@ module.exports = {
166
167
  let index = null; // the Initiatives board model; without it (no module, or a read error) boardOf decides
167
168
  try { index = require('./routes-boards.js').boardIndex(ctx, w); } catch { index = null; }
168
169
  if (p === '/api/report-dashboards') return send(list({ facts, runtimeDir: ctx.runtimeDir, slug, index }));
169
- const root = rootOf(ctx);
170
+ const root = rootOf(ctx, url.searchParams.get('ws'));
170
171
  let hypotheses = [];
171
172
  try { hypotheses = require(repoScript('records/index.js')).list(root, 'hypotheses'); } catch { hypotheses = []; }
172
173
  return send(data({ scope: url.searchParams.get('scope'), id: url.searchParams.get('id') }, {
@@ -30,7 +30,8 @@ const isDate = (s) => typeof s === 'string' && /^\d{4}-\d{2}-\d{2}/.test(s) && !
30
30
  const ms = (s) => Date.parse(String(s).slice(0, 10) + 'T00:00:00Z');
31
31
  const fin = (v) => typeof v === 'number' && Number.isFinite(v);
32
32
  const r2 = (v) => (fin(v) ? Math.round(v * 100) / 100 : null);
33
- const rootOf = (ctx) => (ctx && ctx.recordsDir) || path.join((ctx && ctx.repoRoot) || path.join(__dirname, '..', '..'), 'planning', 'records');
33
+ const { recordsDirFor } = require('./records-dir.js');
34
+ const rootOf = (ctx, ws) => recordsDirFor(ctx, ws); // the workspace's records (lib/records-dir.js)
34
35
  const monthKey = (t) => { const d = new Date(t); return d.getUTCFullYear() + '-' + pad(d.getUTCMonth() + 1); };
35
36
  const monthIdx = (k) => Number(k.slice(0, 4)) * 12 + Number(k.slice(5, 7)) - 1;
36
37
  const keyOfIdx = (i) => Math.floor(i / 12) + '-' + pad((i % 12) + 1);
@@ -162,7 +163,7 @@ module.exports = {
162
163
  if (url.pathname !== '/api/c3-strategy') return false;
163
164
  if (req.method !== 'GET') { ctx.json(res, { ok: false, error: 'GET only' }, 405); return true; }
164
165
  try {
165
- const body = build(rootOf(ctx), path.join(ctx.runtimeDir || '.', 'signals', 'signals.jsonl'), todayStr());
166
+ const body = build(rootOf(ctx, url.searchParams.get('ws')), path.join(ctx.runtimeDir || '.', 'signals', 'signals.jsonl'), todayStr());
166
167
  ctx.json(res, body, 200);
167
168
  } catch (e) { ctx.json(res, { ok: false, error: String(e && e.message || e) }, 500); }
168
169
  return true;
@@ -15,7 +15,9 @@ const { repoScript } = require('./repo-script.js');
15
15
  const records = require(repoScript('records/index.js'));
16
16
  const ledger = require(repoScript('trust/ledger.js'));
17
17
 
18
- const rootOf = (ctx) => (ctx && ctx.recordsDir) || path.join(ctx.repoRoot || path.join(__dirname, '..', '..'), 'planning', 'records');
18
+ const { recordsDirFor } = require('./records-dir.js');
19
+ const rootOf = (ctx, ws) => recordsDirFor(ctx, ws); // the workspace's records (lib/records-dir.js)
20
+ const writeRootOf = (ctx, body) => recordsDirFor(ctx, body && body.ws); // writes: the same folder the reads use
19
21
  const UI = () => ledger.actorFrom({ ui: true });
20
22
  const bad = (status, error, reasons) => ({ status, body: { ok: false, error, reasons } });
21
23
 
@@ -46,16 +48,16 @@ module.exports = {
46
48
  const m = url.pathname.match(/^\/api\/records\/([a-z-]+)(?:\/(add|edit|reading))?$/);
47
49
  if (!m) return false;
48
50
  const [, kind, verb] = m;
49
- if (req.method === 'GET' && !verb) { const r = listRecords(rootOf(ctx), kind); ctx.json(res, r.body, r.status); return true; }
51
+ if (req.method === 'GET' && !verb) { const r = listRecords(rootOf(ctx, url.searchParams.get('ws')), kind); ctx.json(res, r.body, r.status); return true; }
50
52
  if (req.method !== 'POST' || !verb) return false;
51
53
  const got = await ctx.readJson(req);
52
54
  if (!got.ok) { ctx.json(res, { ok: false, error: 'invalid JSON body' }, 400); return true; }
53
55
  const body = got.body || {};
54
56
  const g = guard.requirePagePress(req, body);
55
57
  if (!g.ok) { ctx.json(res, { ok: false, error: g.error }, g.status); return true; }
56
- const r = verb === 'add' ? addRecord(rootOf(ctx), kind, body)
57
- : verb === 'edit' ? editRecord(rootOf(ctx), kind, body)
58
- : kind === 'kr-metrics' ? readingRecord(rootOf(ctx), body) : bad(404, 'readings belong to kr-metrics');
58
+ const r = verb === 'add' ? addRecord(writeRootOf(ctx, body), kind, body)
59
+ : verb === 'edit' ? editRecord(writeRootOf(ctx, body), kind, body)
60
+ : kind === 'kr-metrics' ? readingRecord(writeRootOf(ctx, body), body) : bad(404, 'readings belong to kr-metrics');
59
61
  ctx.json(res, r.body, r.status);
60
62
  return true;
61
63
  },
@@ -26,7 +26,9 @@ const roadmap = require(repoScript('roadmap-ops.js'));
26
26
  const breakdown = require(repoScript('breakdown-ops.js'));
27
27
 
28
28
  const REPO = path.join(__dirname, '..', '..');
29
- const rootOf = (ctx) => (ctx && ctx.recordsDir) || path.join((ctx && ctx.repoRoot) || REPO, 'planning', 'records');
29
+ const { recordsDirFor } = require('./records-dir.js');
30
+ const rootOf = (ctx, ws) => recordsDirFor(ctx, ws); // the workspace's records (lib/records-dir.js)
31
+ const writeRootOf = (ctx, body) => recordsDirFor(ctx, body && body.ws); // writes: the same folder the reads use
30
32
  const UI = () => ledger.actorFrom({ ui: true });
31
33
  const out = (status, body) => ({ status, body });
32
34
  const refusal = (r, status = 400) => out(status, { ok: false, error: r.error, ...Object.fromEntries(Object.entries(r).filter(([k]) => !['ok', 'error'].includes(k))) });
@@ -108,14 +110,14 @@ function setPeople(runtimeDir, body) {
108
110
  }
109
111
 
110
112
  const POSTS = {
111
- '/bet/add': (c, b) => betAdd(rootOf(c), b, c),
112
- '/bet/move': (c, b) => betMove(rootOf(c), b, c),
113
- '/bet/take-off': (c, b) => betTakeOff(rootOf(c), b),
114
- '/breakdown/add': (c, b) => breakdownAdd(rootOf(c), b),
115
- '/breakdown/edit': (c, b) => breakdownEdit(rootOf(c), b),
116
- '/breakdown/delete': (c, b) => breakdownDelete(rootOf(c), b),
117
- '/breakdown/undo': (c, b) => breakdownUndo(rootOf(c), b),
118
- '/breakdown/dependency': (c, b) => breakdownDependency(rootOf(c), b),
113
+ '/bet/add': (c, b) => betAdd(writeRootOf(c, b), b, c),
114
+ '/bet/move': (c, b) => betMove(writeRootOf(c, b), b, c),
115
+ '/bet/take-off': (c, b) => betTakeOff(writeRootOf(c, b), b),
116
+ '/breakdown/add': (c, b) => breakdownAdd(writeRootOf(c, b), b),
117
+ '/breakdown/edit': (c, b) => breakdownEdit(writeRootOf(c, b), b),
118
+ '/breakdown/delete': (c, b) => breakdownDelete(writeRootOf(c, b), b),
119
+ '/breakdown/undo': (c, b) => breakdownUndo(writeRootOf(c, b), b),
120
+ '/breakdown/dependency': (c, b) => breakdownDependency(writeRootOf(c, b), b),
119
121
  '/people': (c, b) => setPeople(c.runtimeDir, b),
120
122
  };
121
123
 
@@ -125,7 +127,7 @@ module.exports = {
125
127
  handle: async (req, res, url, ctx) => {
126
128
  const sub = url.pathname.slice('/api/roadmap-ops'.length);
127
129
  if (req.method === 'GET' && sub === '/bet/anchor-suggestions') {
128
- const r = betAnchorSuggestions(rootOf(ctx), { id: url.searchParams.get('id') }, ctx);
130
+ const r = betAnchorSuggestions(rootOf(ctx, url.searchParams.get('ws')), { id: url.searchParams.get('id') }, ctx);
129
131
  ctx.json(res, r.body, r.status);
130
132
  return true;
131
133
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.135.0",
3
+ "version": "0.136.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.135.0",
9
+ "version": "0.136.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.135.0",
3
+ "version": "0.136.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",