create-pmos 0.38.3 → 0.38.4

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.38.3",
3
+ "version": "0.38.4",
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@58c817d
1
+ kernel-source: pmos@fb3d4bc
2
2
  assembled: 2026-10-02
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-02T06:19:09Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-02T08:57:21Z"}
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-02T06:19:09Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-02T08:57:21Z"}
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-02T06:19:09Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-02T08:57:21Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.38.3",
3
+ "version": "0.38.4",
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.38.3",
3
+ "version": "0.38.4",
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",
@@ -15,7 +15,7 @@
15
15
  "test:parity": "node design-parity.js --states",
16
16
  "test:all": "npm test && npm run test:panels && npm run test:ui && npm run test:parity && npm run test:offline",
17
17
  "doctor": "node doctor.js",
18
- "test:panels": "node pm-shell-test.js && node pm-repos-test.js && node pm-initiatives-test.js && node pm-detail-test.js && node pm-tabs-test.js && node pm-term-test.js && node pm-modes-test.js && node pm-r1-test.js && node pm-r2-test.js && node pm-r3-test.js && node pm-r4-test.js && node pm-r5-test.js && node pm-r6-test.js && node pm-r7-test.js && node pm-r8-test.js && node pm-r9-test.js && node pm-r10-test.js && node pm-r11-test.js && node pm-r12-test.js && node pm-r13-test.js && node pm-r14-test.js && node pm-r15-test.js",
18
+ "test:panels": "node pm-shell-test.js && node pm-repos-test.js && node pm-initiatives-test.js && node pm-detail-test.js && node pm-tabs-test.js && node pm-term-test.js && node pm-modes-test.js && node pm-r1-test.js && node pm-r2-test.js && node pm-r3-test.js && node pm-r4-test.js && node pm-r5-test.js && node pm-r6-test.js && node pm-r7-test.js && node pm-r8-test.js && node pm-r9-test.js && node pm-r10-test.js && node pm-r11-test.js && node pm-r12-test.js && node pm-r13-test.js && node pm-r14-test.js && node pm-r15-test.js && node pm-r16-test.js",
19
19
  "test:offline": "node offline-tarball.test.js"
20
20
  },
21
21
  "engines": {
@@ -588,6 +588,7 @@ function foldWorkspaces(rows) {
588
588
  // the PM's own name for the repository (overhaul-r1 R3.1) lives on the main tree's row
589
589
  if (w.label) p.name = w.label;
590
590
  p.initials = w.initials || null;
591
+ p.profile = w.profile === 'pm-artifacts' ? 'pm-artifacts' : 'product-code';
591
592
  p.kind = w.kind === 'github' ? 'github' : 'local';
592
593
  p.remote = w.remote || null;
593
594
  }
@@ -1057,6 +1058,25 @@ export async function removeRepository(name) {
1057
1058
  /** The PM's own abbreviation for the rail, or null (the rail derives one from the name). */
1058
1059
  export function repoInitials(name) { const p = products.get(name); return (p && p.initials) || null; }
1059
1060
 
1061
+ /** panel-repos.js — the repository's profile (D68): 'pm-artifacts' (documents) or 'product-code'. */
1062
+ export function repoProfile(name) { const p = products.get(name); return (p && p.profile) || 'product-code'; }
1063
+
1064
+ /** panel-repos.js — write or remove `.pmos-profile`. Refusals come back as { ok:false, error }. */
1065
+ export async function setRepoProfile(name, profile) {
1066
+ if (FIXTURE) return { ok: false, error: 'fixture mode — nothing is registered' };
1067
+ const p = products.get(name);
1068
+ if (!p) return { ok: false, reason: 'unknown', error: 'unknown repository' };
1069
+ const out = await postJson('/api/workspaces/profile', { id: p.ws, profile });
1070
+ if (out && out.ok) {
1071
+ p.profile = profile;
1072
+ // the lane picker and the ladder read the profile: drop every cached record and re-read the open one
1073
+ for (const k of Object.keys(hydrated)) delete hydrated[k];
1074
+ try { await loadBoard(p); publishBoards(); } catch { /* the chip already shows the new profile */ }
1075
+ if (state.sel) await hydrate(state.sel);
1076
+ }
1077
+ return out || { ok: false, error: 'the workspace server did not answer' };
1078
+ }
1079
+
1060
1080
  /** panel-repos.js — `renameRepository(name, label)`. A display name only (R3.1). */
1061
1081
  export async function renameRepository(name, label, initials) {
1062
1082
  if (FIXTURE) return { ok: false, error: 'fixture mode — nothing is registered' };
@@ -274,6 +274,24 @@ async function renameRepo(name) {
274
274
  setState({ product: String(label).trim(), repoNotice: null });
275
275
  }
276
276
 
277
+ /* The repository's profile (D68). `pm-artifacts` offers document lanes and no build rungs; `product-code` is the
278
+ * default. The confirm states the consequence before anything is written. */
279
+ const DOC_LANES = 'strategy, prd, project-plan, discovery, decision-record, report, chore';
280
+ const CODE_LANES = 'feature, discovery, chore, incident, content, prototype';
281
+ async function switchProfile(name) {
282
+ if (!dataMod || !dataMod.repoProfile) return;
283
+ const toDocs = dataMod.repoProfile(name) !== 'pm-artifacts';
284
+ const msg = toDocs
285
+ ? 'Switch “' + name + '” to document work?\n\nLanes offered: ' + DOC_LANES + '.\nThe ladder becomes route, anchor, brief, draft, review, approved, handed off — no contract, build, eval or explainer.'
286
+ : 'Switch “' + name + '” to code work?\n\nLanes offered: ' + CODE_LANES + '.\nThe ladder returns to spec, contract, build, eval, explainer, acceptance.';
287
+ const tail = '\n\nAn initiative whose lane is not in the new set is treated as the strictest lane (' + (toDocs ? 'a governed prd' : 'feature') + '). No initiative record is edited.\n\nThis ' + (toDocs ? 'writes' : 'removes') + ' the file .pmos-profile in the repository. It is a change for you to commit.';
288
+ const ok = typeof window === 'undefined' || !window.confirm ? true : window.confirm(msg + tail);
289
+ if (!ok) return;
290
+ const out = await dataMod.setRepoProfile(name, toDocs ? 'pm-artifacts' : 'product-code');
291
+ if (!out || out.ok === false) { setState({ repoNotice: { name, tone: 'bad', text: (out && out.error) || 'the profile was not changed' } }); return; }
292
+ setState({ repoNotice: { name, tone: null, text: 'now ' + (toDocs ? 'document work (pm-artifacts)' : 'code work (product-code)') + ' — .pmos-profile ' + (toDocs ? 'written' : 'removed') + '; commit it' } });
293
+ }
294
+
277
295
  async function removeRepo(name) {
278
296
  const ok = typeof window === 'undefined' || !window.confirm ? true
279
297
  : window.confirm('Remove “' + name + '” from PMOS?\n\nThis forgets the repository and its worktree rows. No folder is deleted.');
@@ -303,6 +321,11 @@ function repoEntry(tuple, product) {
303
321
  }, ip('⑂ ' + trees.length)));
304
322
  if (product === name) {
305
323
  { const sb = syncButton(name); if (sb) tools.push(sb); }
324
+ { const docs = dataMod.repoProfile && dataMod.repoProfile(name) === 'pm-artifacts';
325
+ tools.push(el('button', { onClick: () => switchProfile(name), style: chipStyle, hover: 'color:var(--foreground)',
326
+ title: docs ? 'document work (pm-artifacts): lanes are strategy, prd, project-plan … with no build rungs. Press to switch to code work.'
327
+ : 'code work (product-code): lanes are feature, discovery, chore … with the full build ladder. Press to switch to document work.',
328
+ 'data-r16': 'profile', 'data-profile': docs ? 'pm-artifacts' : 'product-code' }, ip(docs ? 'docs' : 'code'))); }
306
329
  tools.push(el('button', { onClick: () => renameRepo(name), style: actStyle, hover: 'color:var(--foreground)', title: 'rename this repository', 'data-r1': 'rename' }, '✎'));
307
330
  tools.push(el('button', { onClick: () => removeRepo(name), style: actStyle, hover: 'color:var(--destructive)', title: 'remove this repository from PMOS (no folder is deleted)', 'data-r1': 'remove' }, '✕'));
308
331
  }
@@ -584,11 +584,18 @@ function treeNoticeFor(s, mates) {
584
584
  role: holder === s.id ? 'writer' : 'reader', writerName: held, writerId: holder, tree: s.tree,
585
585
  });
586
586
  }
587
+ /** The repository's declared profile (D68), read fresh from `.pmos-profile`; anything else is `product-code`. */
588
+ function profileOfRoot(root) {
589
+ try {
590
+ const line = fs.readFileSync(path.join(root, '.pmos-profile'), 'utf8').split('\n').map((l) => l.replace(/#.*$/, '').trim()).find(Boolean);
591
+ return line === 'pm-artifacts' ? 'pm-artifacts' : 'product-code';
592
+ } catch { return 'product-code'; }
593
+ }
587
594
  function wsList() {
588
595
  return [...workspaces.values()].map((w) => {
589
596
  const rid = repoIdentity(w.root);
590
597
  return {
591
- id: w.id, name: w.name, label: w.label || null, initials: w.initials || null, root: w.root, home: HOME_ENABLED && w.root === SELF_ROOT,
598
+ id: w.id, name: w.name, label: w.label || null, initials: w.initials || null, profile: profileOfRoot(w.root), root: w.root, home: HOME_ENABLED && w.root === SELF_ROOT,
592
599
  shape: wsShape(w.root), addedAt: w.addedAt,
593
600
  sessions: [...sessions.values()].filter((s) => s.ws === w.id).length,
594
601
  // the repository this working tree belongs to, and the branch it is on. `branch_code`
@@ -4331,6 +4338,25 @@ async function handleRequest(req, res) {
4331
4338
  saveWorkspaces(); pushWorkspaces();
4332
4339
  return json(res, { ok: true, workspace: { id: w.id, name, label: name, initials: w.initials || null } });
4333
4340
  }
4341
+ if (p === '/api/workspaces/profile' && req.method === 'POST') {
4342
+ /* Switch a repository between document work (`pm-artifacts`) and code work (`product-code`), D68. The profile
4343
+ * is the one-line `.pmos-profile` file at the repository root, so this writes or removes that file and nothing
4344
+ * else. No initiative record is edited: a lane that is not in the new set is read as the profile's strictest
4345
+ * lane by the engine. The PM commits the file. */
4346
+ let body = {}; try { body = JSON.parse((await readBody(req)) || '{}'); } catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
4347
+ const w = workspaces.get(body.id);
4348
+ if (!w) return json(res, { ok: false, reason: 'unknown', error: 'unknown repository' }, 404);
4349
+ const want = String(body.profile || '');
4350
+ if (want !== 'pm-artifacts' && want !== 'product-code') return json(res, { ok: false, error: `“${want || '(none)'}” is not a profile — one of: pm-artifacts, product-code` }, 400);
4351
+ const file = path.join(w.root, '.pmos-profile');
4352
+ try {
4353
+ if (want === 'pm-artifacts') { fs.writeFileSync(file + '.tmp', 'pm-artifacts\n'); fs.renameSync(file + '.tmp', file); }
4354
+ else fs.rmSync(file, { force: true });
4355
+ } catch (e) { return json(res, { ok: false, error: 'could not write .pmos-profile — ' + String((e && e.message) || e).slice(0, 120) }, 500); }
4356
+ boardCache.invalidate(w.root);
4357
+ pushWorkspaces();
4358
+ return json(res, { ok: true, id: w.id, profile: want, file: '.pmos-profile', commit: 'the file is a change in the repository — commit it' });
4359
+ }
4334
4360
  if (p === '/api/git/branch' && req.method === 'POST') {
4335
4361
  let body = {};
4336
4362
  try { body = JSON.parse((await readBody(req)) || '{}'); } catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }