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 +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +2 -2
- package/workspace/public/pm/data.js +20 -0
- package/workspace/public/pm/panel-repos.js +23 -0
- package/workspace/server.js +27 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.38.
|
|
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": {
|
package/payload/KIT-VERSION
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-02T08:57:21Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
package/workspace/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.38.
|
|
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
|
}
|
package/workspace/server.js
CHANGED
|
@@ -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); }
|