create-pmos 0.99.0 → 0.100.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.99.0",
3
+ "version": "0.100.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@206cc37
1
+ kernel-source: pmos@42d0a34
2
2
  assembled: 2026-10-08
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-08T00:18:17Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-08T00:23:24Z"}
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-08T00:18:17Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-08T00:23:24Z"}
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-08T00:18:17Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-08T00:23:24Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -39,7 +39,7 @@ Settings holds settings only, in eight sections. On a wide screen they are cards
39
39
  - **Updates**: the version of the workspace app and the version of the kit in the current repository. Press **Check for updates** at the top of the page to compare them with the latest published version of PMOS. This is the only time the workspace asks the internet for a version, and it asks only when you press. When a newer version is available, one button shows: **Update to** and the version. It updates every part that is older, in this order:
40
40
  - **The kit** in the current repository, when it is older. This does what `npx create-pmos@latest --update` does in a terminal. It changes only the kit's own files under `pmos/` (skills, scripts, templates and guides). Your records in `state/`, `planning/` and `okf/product/` do not change. The last lines of the output show under the button. The changed files are under `pmos/`, which Sync treats as records, so Sync (or automatic sync) sends them with your other records. To read them first, look at `git diff pmos/` before you sync.
41
41
  - **The workspace app**, when it is older. This does what `pmos upgrade` does in a terminal. The button asks you first. The workspace stops, installs the new version and starts again, and the page reloads when the new version starts. The reloaded page runs the new version's code: each start gives the page files a new address, so neither the browser nor the Online address's tunnel can keep the old ones. You do not need a terminal or a hard refresh. Sessions keep running in tmux. It works on the computer that runs the workspace and at its Online address, because the page comes back there after the restart. It also works on a phone or public address that signs in with a token set in `PMOS_TOKEN`, which the workspace keeps. When the workspace made its own token at start, the token changes on the restart, so the page refuses and says where to press it. When the workspace runs from a PMOS checkout, update it with `git pull` instead.
42
- - **Testing an update before it is published.** Set `PMOS_UPDATE_SOURCE` to a built create-pmos package on the same computer: its folder, or the `.tgz` that `npm pack` makes. Then **Check for updates** reads that package's version and does not ask npm, and both updates install that package. The Updates card names the package it checks. A checkout may update the app this way. When the path is wrong, the card says why and does not fall back to npm. Unset the variable, or set it to `npm`, to use the registry again.
42
+ - **Testing an update before it is published.** In a PMOS checkout, run `bash scripts/build-local-package.sh`. It builds the package that a publish would build, in `~/.pmos-local-package`, with a version newer than the published one, and prints the command that starts the workspace on it. That command sets `PMOS_UPDATE_SOURCE` to the package on the same computer: its folder, or the `.tgz` that `npm pack` makes. Then **Check for updates** reads that package's version and does not ask npm, and both updates install that package. The Updates card names the package it checks. A checkout may update the app this way. When the path is wrong, the card says why and does not fall back to npm. Unset the variable, or set it to `npm`, to use the registry again.
43
43
 
44
44
  A new screen or a renamed menu comes with the workspace app, not with the kit.
45
45
 
@@ -100,6 +100,8 @@ When the job is a reply or a review, you can finish it in the card. Write your a
100
100
 
101
101
  A meeting that needs prep is a row of **Needs you**, with the verb **Prepare**. Its detail shows the prep, how many steps are ready, the free time before the meeting, and the steps before it. A step that reads a source shows what the source says under it. A step that asks for your view, such as **Write your recommendation**, has a box for your note. The note is saved when you leave the box. It stays with the meeting on this computer and is not sent. A step that the brief already marked done stays checked.
102
102
 
103
+ When a meeting ends, its detail stays as it was. A line at the top says **ended** and the time it ended. The prep and the steps stay, in a quieter box, so you can still read what you prepared. The meeting leaves **Needs you**, and its notes show under it.
104
+
103
105
  When you finish or put off a job or a meeting, mark it:
104
106
 
105
107
  | Press | What it does |
@@ -17,7 +17,7 @@
17
17
  * app npx -y create-pmos@<version> workspace --reinstall <repo> (what `pmos upgrade` runs)
18
18
  * LOCAL SOURCE. PMOS_UPDATE_SOURCE names a built create-pmos package on this computer: its folder (with package.json, bin/
19
19
  * and payload/ or workspace/) or its .tgz from `npm pack`. The check then reads that package's version instead of asking
20
- * the registry, and the update runs `npx -y <that path> ...` in place of `npx -y create-pmos@<version> ...`. Nothing
20
+ * the registry, and the update runs `npx -y --package=<that path> -- create-pmos ...` in place of `npx -y create-pmos@<version> ...`. Nothing
21
21
  * reaches the network, so an update can be tried before it is published. Unset means npm (the default).
22
22
  * The version is the one the check found, so what is installed is what the PM was shown, not whatever `latest` is a
23
23
  * minute later. The app upgrade stops this server (create-pmos runs `pmos stop`), so it runs DETACHED, writes its output
@@ -108,8 +108,13 @@ function source(env = process.env) {
108
108
  return { kind: 'local', path: p, version: m[1] };
109
109
  }
110
110
 
111
- /** What npx installs: the registry package at a version, or the local path. */
112
- const specOf = (src, version) => (src && src.kind === 'local' ? src.path : `${PKG_NAME}@${version}`);
111
+ /**
112
+ * The npx arguments that run create-pmos: the registry package at a version, or the local path. A bare path is not
113
+ * enough: npx would try to run it as a command, so a local package goes in with --package.
114
+ */
115
+ const npxArgs = (src, version, rest) => (src && src.kind === 'local'
116
+ ? ['-y', `--package=${src.path}`, '--', PKG_NAME, ...rest]
117
+ : ['-y', `${PKG_NAME}@${version}`, ...rest]);
113
118
 
114
119
  function status({ root, appVersion, appDir, env }) {
115
120
  const src = source(env);
@@ -175,7 +180,7 @@ async function updateKit(root, { version, exec = cp.execFile, source: src = sour
175
180
  if (!validVersion(version)) return { ok: false, error: 'Check for updates first' };
176
181
  if (src.kind === 'local' && src.error) return { ok: false, error: src.error };
177
182
  if (!readKit(root).present) return { ok: false, error: 'This repository has no PMOS kit folder (pmos/KIT-VERSION)' };
178
- const r = await run(exec, NPX, ['-y', specOf(src, version), '--update', String(root)], { timeout: UPDATE_TIMEOUT_MS, cwd: String(root) });
183
+ const r = await run(exec, NPX, npxArgs(src, version, ['--update', String(root)]), { timeout: UPDATE_TIMEOUT_MS, cwd: String(root) });
179
184
  const output = tail(r.stdout + (r.stderr ? '\n' + r.stderr : ''));
180
185
  if (r.err && notServedYet(r.stdout + r.stderr, version)) return { ok: false, error: notServedYetError(version), output };
181
186
  const bad = why(r, 'The kit update');
@@ -221,7 +226,7 @@ function upgradeApp(root, { version, logFile, spawn = cp.spawn, env = updateEnv(
221
226
  fd = fs.openSync(logFile, 'w');
222
227
  fs.writeSync(fd, `${new Date().toISOString()} clearing npm cache…\n`);
223
228
  fs.writeSync(fd, `${new Date().toISOString()} ${NPM} cache clean --force\n`);
224
- fs.writeSync(fd, `${new Date().toISOString()} ${NPX} -y ${specOf(src, version)} workspace --reinstall ${root}\n`);
229
+ fs.writeSync(fd, `${new Date().toISOString()} ${NPX} ${npxArgs(src, version, ['workspace', '--reinstall', String(root)]).join(' ')}\n`);
225
230
  } catch (e) { return { ok: false, error: `Could not write the log file: ${e.message}` }; }
226
231
  try {
227
232
  // Clear npm cache synchronously before spawning the update, so npx downloads the fresh version
@@ -230,7 +235,7 @@ function upgradeApp(root, { version, logFile, spawn = cp.spawn, env = updateEnv(
230
235
  try { fs.appendFileSync(logFile, `Note: cache clear had an issue, but continuing: ${e.message}\n`); } catch { /* silent */ }
231
236
  }
232
237
  try {
233
- const child = spawn(NPX, ['-y', specOf(src, version), 'workspace', '--reinstall', String(root)],
238
+ const child = spawn(NPX, npxArgs(src, version, ['workspace', '--reinstall', String(root)]),
234
239
  { cwd: String(root), env, detached: true, stdio: ['ignore', fd, fd], windowsHide: true, shell: IS_WIN });
235
240
  // A child that could not start writes nothing itself: say why in the log, which the page names when it gives up.
236
241
  if (child && typeof child.on === 'function') {
@@ -245,4 +250,4 @@ function upgradeApp(root, { version, logFile, spawn = cp.spawn, env = updateEnv(
245
250
  }
246
251
  }
247
252
 
248
- module.exports = { PKG_NAME, cmpVer, notServedYet, validVersion, source, specOf, readKit, fromCheckout, status, compare, latest, updateKit, updateEnv, upgradeApp, whereFrom };
253
+ module.exports = { PKG_NAME, cmpVer, notServedYet, validVersion, source, npxArgs, readKit, fromCheckout, status, compare, latest, updateKit, updateEnv, upgradeApp, whereFrom };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.99.0",
3
+ "version": "0.100.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.99.0",
9
+ "version": "0.100.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.99.0",
3
+ "version": "0.100.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",
@@ -3583,6 +3583,9 @@
3583
3583
  .td-day__mark[data-day-mark-state="done"] { border-color: var(--foreground); }
3584
3584
  .td-day__ready { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; font-size: 12px; }
3585
3585
  .td-day__prep { display: grid; gap: 8px; padding: 10px 12px; border: 1px solid var(--border); background: var(--background); }
3586
+ .td-day__ended { margin: 0; padding: 8px 12px; border-left: 2px solid var(--foreground); background: var(--muted); font-size: 14px; }
3587
+ /* an ended meeting keeps its prep, quieter: the steps stay readable, the past is not a call to act */
3588
+ .td-day__detail:has(.td-day__ended) .td-day__prep { background: var(--muted); }
3586
3589
  .td-day__fold { border-top: 1px solid var(--border); padding-top: 6px; }
3587
3590
  .td-day__fold > summary { cursor: pointer; font-size: 12px; color: var(--muted-foreground); }
3588
3591
  .td-day__fold[open] > summary { margin-bottom: 6px; }
@@ -366,6 +366,7 @@ export const COPY = {
366
366
  from: (who, where) => (who && where ? `${who} in ${where}` : who || where || ''),
367
367
  howTitle: (d, n) => `How to do it · ${d} of ${n}`,
368
368
  beforeTitle: (at) => `Before ${at}`,
369
+ endedAt: (at) => `This meeting ended at ${at}. Its prep and steps stay below.`,
369
370
  more: 'More context',
370
371
  moreMeeting: (during, after) => `In the meeting (${during}) · After (${after})`,
371
372
  readOnly: 'Read only. Nothing is sent before you press a button that says so.',
@@ -176,10 +176,12 @@ function model(d, view) {
176
176
  const ended = Number.isFinite(f) && f <= now;
177
177
  // a meeting needs prep when the brief gave a prep line or Before steps (the explainer's contradiction: Before steps
178
178
  // with no prep line read "Nothing to prepare" while the Agenda counted them as prep left)
179
- const needsPrep = !ended && (!!e.prep || before.length > 0);
179
+ // the prep stays with the meeting once it ends (the PM, 2026-10-07): hadPrep keeps the detail, needsPrep drives the lists
180
+ const hadPrep = !!e.prep || before.length > 0;
181
+ const needsPrep = !ended && hadPrep;
180
182
  const st = mark && mark.state;
181
183
  const group = !needsPrep ? null : st === 'done' || st === 'dismissed' ? 'done' : st === 'snoozed' ? 'later' : 'needs';
182
- return { ...e, id: e.id || 'ev-' + i, s, f, time: hhmm(e.start), ended, mark, before, needsPrep, group };
184
+ return { ...e, id: e.id || 'ev-' + i, s, f, time: hhmm(e.start), ended, mark, before, hadPrep, needsPrep, group };
183
185
  }).sort((a, b) => a.s - b.s);
184
186
  const settings = (d && d.settings) || null;
185
187
  const scopes = (settings && settings.scopes) || {};
@@ -845,17 +847,17 @@ function prepBlock(ctx, e) {
845
847
  const d = steps.filter((x) => x.done).length; const total = steps.length;
846
848
  const left = total - d;
847
849
  const free = freeBefore(m, e);
848
- const fit = !total ? null : !left ? A.fit.ok : free >= left * 15 ? A.fit.time(left, free) : A.fit.short(left, free);
850
+ const fit = !total || e.ended ? null : !left ? A.fit.ok : free >= left * 15 ? A.fit.time(left, free) : A.fit.short(left, free);
849
851
  const open = !e.mark || !e.mark.state;
850
852
  const link = Array.isArray(e.links) ? e.links.find((l) => isUrl(l.url)) : null;
851
853
  return el('div', { class: 'td-day__prep', 'data-day': 'prep' },
852
- el('div', { class: 'td-day__jobtop' }, verbChip('prepare'), dueChip(m, e.mark, e.s, true)),
854
+ el('div', { class: 'td-day__jobtop' }, verbChip('prepare'), e.ended ? null : dueChip(m, e.mark, e.s, true)),
853
855
  e.prep ? el('p', { class: 'td-day__todo', 'data-day': 'todo' }, e.prep) : null,
854
856
  markBanner(ctx, e.id, e.mark, true),
855
857
  total ? el('div', { class: 'td-day__ready', 'data-day': 'readiness' }, meter({ done: d, total, label: A.readiness(d, total) }), fit ? el('span', { class: 'td-day__dim' }, fit) : null) : null,
856
858
  total ? el('div', { class: 'td-day__phase' }, el('h4', null, A.beforeTitle(e.time)), stepChecks(ctx, e.id, steps, { refs, mark: e.mark })) : null,
857
859
  refsBlock(refs, steps),
858
- open && link && left ? el('div', { class: 'td-day__acts', 'data-day': 'cta' }, primaryLink((link.label || link.url) + ' ↗', link.url, { 'data-day-link': 'prep' })) : null,
860
+ open && link && left && !e.ended ? el('div', { class: 'td-day__acts', 'data-day': 'cta' }, primaryLink((link.label || link.url) + ' ↗', link.url, { 'data-day-link': 'prep' })) : null,
859
861
  marksBar(ctx, { id: e.id, title: e.title, kind: 'event', start: e.s, strong: total > 0 && !left }));
860
862
  }
861
863
 
@@ -896,10 +898,11 @@ function eventDetail(ctx, e) {
896
898
  onClick: async () => { if (await addGoal(ctx, e.title, ini ? ini.id : null)) toast({ text: D.goalAdded }); } })) : null];
897
899
  return [
898
900
  dhead(D.kind.meeting, e.title),
899
- el('p', { class: 'td-day__dim td-day__mono' }, `${e.time}${e.end ? '–' + hhmm(e.end) : ''}${e.with ? ' · ' + e.with : ''}${e.ended ? ' · ' + D.ended : ''}`),
901
+ e.ended ? el('p', { class: 'td-day__ended', 'data-day': 'ended', role: 'status' }, pill(D.pill.ended, ''), ' ', A.endedAt(hhmm(e.end || e.f))) : null,
902
+ el('p', { class: 'td-day__dim td-day__mono' }, `${e.time}${e.end ? '–' + hhmm(e.end) : ''}${e.with ? ' · ' + e.with : ''}`),
900
903
  e.with ? peopleLinks(ctx.ws, e.with, { 'data-day': 'people' }) : null,
901
904
  e.purpose ? el('p', null, el('b', null, D.purpose + ': '), e.purpose) : null,
902
- e.needsPrep ? prepBlock(ctx, e) : null,
905
+ e.hadPrep ? prepBlock(ctx, e) : null,
903
906
  ini ? el('div', { class: 'td-day__ini', 'data-day': 'initiative' }, el('span', null, D.initiative + ': ', el('b', { class: 'td-day__mono' }, ini.id), ini.why ? el('span', { class: 'td-day__dim' }, ' (' + D.suggested(ini.why) + ')') : null),
904
907
  ctx.openIni ? button({ label: D.openInitiative, size: 'sm', variant: 'ghost', attrs: { 'data-day-act': 'open-initiative' }, onClick: () => ctx.openIni(ini.id) }) : null) : null,
905
908
  tie,
@@ -907,10 +910,11 @@ function eventDetail(ctx, e) {
907
910
  after,
908
911
  notesBlock(e, { ws: ctx.ws, initiative: ini }),
909
912
  // with prep, the Before steps are the checklist above; During, After and the mail fold away (PRD D2)
910
- !e.ended && e.needsPrep && ((steps.during || []).length || (steps.after || []).length || mailBlock)
913
+ // once it ends, the plan stays where it was; the mail moves up into the notes above, so it is not shown twice
914
+ e.hadPrep && ((steps.during || []).length || (steps.after || []).length || (!e.ended && mailBlock))
911
915
  ? el('details', { class: 'td-day__fold', 'data-day': 'more' }, el('summary', null, A.moreMeeting((steps.during || []).length, (steps.after || []).length)),
912
- list(D.phases.during, (steps.during || []).map(stepItem)), list(D.phases.after, (steps.after || []).map(stepItem)), mailBlock) : null,
913
- !e.ended && !e.needsPrep ? [list(D.phases.during, (steps.during || []).map(stepItem)), list(D.phases.after, (steps.after || []).map(stepItem)), mailBlock] : null,
916
+ list(D.phases.during, (steps.during || []).map(stepItem)), list(D.phases.after, (steps.after || []).map(stepItem)), e.ended ? null : mailBlock) : null,
917
+ !e.hadPrep ? [list(D.phases.during, (steps.during || []).map(stepItem)), list(D.phases.after, (steps.after || []).map(stepItem)), e.ended ? null : mailBlock] : null,
914
918
  Array.isArray(e.links) && e.links.length ? el('div', { class: 'td-day__acts', 'data-day': 'links' }, ...e.links.map((l) => extLink((l.label || l.url) + ' ↗', l.url))) : null,
915
919
  ];
916
920
  }