create-pmos 0.89.0 → 0.90.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.89.0",
3
+ "version": "0.90.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@5feb40c
1
+ kernel-source: pmos@a3abbf0
2
2
  assembled: 2026-10-07
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-07T17:12:14Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T17:45:30Z"}
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-07T17:12:14Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T17:45:30Z"}
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-07T17:12:14Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T17:45:30Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -37,13 +37,13 @@ A sighting of a new handle adds the person. A sighting with the full name of a p
37
37
 
38
38
  ## The list
39
39
 
40
- People has the same two-card layout as the Agenda. The title says how many people there are. **All**, **Team** and **External** at the top right filter the list. The left card holds the list. Type in **Find a person** to search names, handles and initiatives. **Latest**, **Most** and **Name** sort it by the last interaction, the number of interactions or the name. Each row shows initials, the name, the role and organisation, and the tone as a coloured dot (a ? means you have not confirmed it). Press a person to open their page in the right card. Each card scrolls by itself.
40
+ People has the same two-card layout as the Agenda. The title says how many people there are. **All**, **Team** and **External** at the top right filter the list. The left card holds the list. Type in **Find a person** to search names, handles and initiatives. **Latest**, **Most** and **Name** sort it by the last interaction, the number of interactions or the name. Each row shows initials, the name, the role and organisation, and the tone as a coloured dot (a ? means you have not confirmed it). Press a person to open their page in the right card. Each card scrolls by itself, and the search box keeps its cursor while you type.
41
41
 
42
42
  ## A person's page
43
43
 
44
44
  The top of the page shows the person's name and role, a **Confirm** button when a tone is waiting, and a **⋯** menu. Three tiles under it give the number of interactions, the tone and the shared work.
45
45
 
46
- - **Interactions.** Newest first. Each line shows the date, the kind (Mail, Chat, Meeting, 1:1, Jira, Doc, Call or Mention), the line, and where it came from with its pointer and tone.
46
+ - **Interactions.** Newest first. A meeting that comes up again is one line that says how often it happens (One-off, Daily, Weekly or Monthly meeting), how many times, and when it was last. A meeting the Agenda marks as key shows a **Key** label. Each other line shows the date, the kind (Mail, Chat, Meeting, 1:1, Jira, Doc, Call or Mention), the line, and where it came from with its pointer and tone.
47
47
  - **Tone.** The mix of tones (warm, neutral, cautious, tense) and a proposed overall tone: the most frequent tone of the latest 10 interactions. When two tones are tied, the latest one wins. Press **Confirm** (at the top or here) to keep the proposal, **Set another** to choose a different tone, or **Change** to clear your choice. Tone describes the exchanges, not the person.
48
48
  - **Shared work.** The initiatives your interactions name, the plan items the person owns (with **Blocked** when the item is blocked), and the coming meetings that name them when calendar names are on. Nothing here is typed by hand.
49
49
  - **Notes.** Type a note and press the **+** button (**Add note**). Press the **×** next to a note to remove it. Only you can add or remove a note. No command writes one.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.89.0",
3
+ "version": "0.90.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.89.0",
9
+ "version": "0.90.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.89.0",
3
+ "version": "0.90.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",
@@ -5898,10 +5898,14 @@
5898
5898
  display: grid;
5899
5899
  grid-template-columns: minmax(300px, 2fr) 3fr;
5900
5900
  gap: 16px;
5901
- align-items: start;
5901
+ grid-template-rows: minmax(0, 1fr);
5902
+ align-items: stretch;
5902
5903
  height: calc(100vh - 220px);
5903
5904
  min-height: 420px;
5904
5905
  }
5906
+ .rp-people-grid > * {
5907
+ min-height: 0;
5908
+ }
5905
5909
  .rp-people-card {
5906
5910
  display: flex;
5907
5911
  flex-direction: column;
@@ -5935,6 +5939,8 @@
5935
5939
  .rp-people-card__list {
5936
5940
  display: flex;
5937
5941
  flex-direction: column;
5942
+ flex: 1 1 0;
5943
+ min-height: 0;
5938
5944
  gap: 2px;
5939
5945
  overflow: auto;
5940
5946
  min-width: 0;
@@ -6092,6 +6098,8 @@
6092
6098
  .rp-people-page__scroll {
6093
6099
  display: flex;
6094
6100
  flex-direction: column;
6101
+ flex: 1 1 0;
6102
+ min-height: 0;
6095
6103
  gap: 14px;
6096
6104
  overflow: auto;
6097
6105
  min-width: 0;
@@ -6139,6 +6147,9 @@
6139
6147
  align-items: flex-start;
6140
6148
  padding: 4px 0;
6141
6149
  }
6150
+ .rp-people-int__key {
6151
+ margin-left: 8px;
6152
+ }
6142
6153
  .rp-people-int__icon {
6143
6154
  width: 28px;
6144
6155
  height: 28px;
@@ -245,10 +245,46 @@ function section(title, kids, attrs) {
245
245
  return el('section', { class: 'rp-people-sec', ...(attrs || {}) }, el('h4', { class: 'rp-people-sec__title' }, title), ...kids);
246
246
  }
247
247
 
248
+ const FREQ = { one: 'One-off meeting', daily: 'Daily meeting', weekly: 'Weekly meeting', monthly: 'Monthly meeting' };
249
+ const dayNo = (d) => Math.round(Date.parse(String(d).slice(0, 10) + 'T00:00:00Z') / 86400000);
250
+ /** How often one meeting happens, from the days it was seen: a single day is one-off, a gap of up to 3 days daily, up to 10 weekly, else monthly. */
251
+ function frequencyOf(dates) {
252
+ const days = [...new Set(dates.map(dayNo))].sort((a, b) => a - b);
253
+ if (days.length < 2) return 'one';
254
+ const gaps = days.slice(1).map((d, k) => d - days[k]).sort((a, b) => a - b);
255
+ const mid = gaps[Math.floor(gaps.length / 2)];
256
+ return mid <= 3 ? 'daily' : mid <= 10 ? 'weekly' : 'monthly';
257
+ }
258
+ const meetingKey = (t) => t.toLowerCase().replace(/\d+/g, ' ').replace(/[^a-z\u00c0-\u024f]+/g, ' ').replace(/\s+/g, ' ').trim();
259
+ /** The same calendar meeting seen again is one entry with its frequency; other interactions stay as they are. */
260
+ function foldInteractions(list) {
261
+ const out = [];
262
+ const groups = new Map();
263
+ for (const i of list) {
264
+ if (i.pointer !== 'calendar' || !/^Meeting: /.test(i.text)) { out.push(i); continue; }
265
+ const k = meetingKey(i.text.slice(9));
266
+ let g = groups.get(k);
267
+ if (!g) { g = { meeting: true, text: i.text.slice(9), source: i.source, dates: [], key: false, first: i }; groups.set(k, g); out.push(g); }
268
+ g.dates.push(i.date);
269
+ if (i.key) g.key = true;
270
+ }
271
+ return out;
272
+ }
273
+
248
274
  function interactionsSec(p) {
249
275
  if (!p.interactions.length) return section(COPY.interactions, [el('p', { class: 'rp-people-hint' }, COPY.noInteractions)], { 'data-people-sec': 'interactions' });
250
276
  const icons = { mail: 'mail', live: 'message-square', calendar: 'calendar', jira: 'ticket', gather: 'file-text', plan: 'file-text', notes: 'file-text' };
251
- return section(`${COPY.interactions} · ${p.count}`, [el('div', { class: 'rp-people-ints' }, p.interactions.slice(0, 50).map((i) => {
277
+ return section(`${COPY.interactions} · ${p.count}`, [el('div', { class: 'rp-people-ints' }, foldInteractions(p.interactions.slice(0, 50)).map((i) => {
278
+ if (i.meeting) {
279
+ const f = frequencyOf(i.dates);
280
+ const latest = i.dates.slice().sort().pop();
281
+ return el('div', { class: 'rp-people-int', 'data-people-int': latest, 'data-people-meeting': f },
282
+ el('span', { class: 'rp-people-int__icon' }, icon('calendar')),
283
+ el('div', {},
284
+ el('div', { class: 'rp-people-int__text' }, i.text, i.key ? el('span', { class: 'rp-people-int__key', 'data-people-key-meeting': '' }, badge({ text: 'Key', tone: 'outline' })) : null),
285
+ el('small', {}, `${FREQ[f]}${i.dates.length > 1 ? ` · ${i.dates.length} times` : ''} · last ${day(latest)}`),
286
+ el('small', { class: 'rp-people-int__src' }, SOURCE.calendar)));
287
+ }
252
288
  const iconName = icons[i.source] || 'file-text';
253
289
  return el('div', { class: 'rp-people-int', 'data-people-int': i.date },
254
290
  el('span', { class: 'rp-people-int__icon' }, icon(iconName)),
@@ -403,7 +439,11 @@ function draw(inst) {
403
439
  if (typing) { inst.last = ''; return; }
404
440
  const n = inst.view.people.length;
405
441
  inst.titleEl.textContent = n ? `${n} ${n === 1 ? 'person' : 'people'}` : COPY.title;
442
+ const find = inst.head.find.control;
443
+ const hadFocus = document.activeElement === find;
444
+ const at = hadFocus ? [find.selectionStart, find.selectionEnd] : null;
406
445
  const rev = review(inst);
407
446
  const grid = el('div', { class: 'rp-people-grid' }, listPane(inst), personPane(inst));
408
447
  inst.body.replaceChildren(...[rev, grid].filter(Boolean));
448
+ if (hadFocus) { find.focus({ preventScroll: true }); try { find.setSelectionRange(at[0], at[1]); } catch { /* not a text field */ } }
409
449
  }
@@ -6849,6 +6849,19 @@ async function handleRequest(req, res) {
6849
6849
  const meetings = calendar.filter((e) => e && e.start && Date.parse(e.end || e.start) >= now);
6850
6850
  const v = peopleStore.view(root, { now, meetings });
6851
6851
  if (!v.ok) return json(res, v, 500);
6852
+ // A meeting the Agenda marks as key (its own rule, or the PM's mark) is flagged on the person's calendar interactions.
6853
+ const marks = readCalMarks();
6854
+ const keyTitles = new Set();
6855
+ for (const e of calendar) {
6856
+ if (!e || !e.title) continue;
6857
+ const mk = calMemLib.markOf(marks, e);
6858
+ if (mk ? mk.on : calMemLib.isImportant(e)) keyTitles.add(calMemLib.normTitle(e.title));
6859
+ }
6860
+ for (const person of v.people || []) {
6861
+ for (const i of person.interactions || []) {
6862
+ if (i.pointer === 'calendar' && /^Meeting: /.test(i.text)) i.key = keyTitles.has(calMemLib.normTitle(i.text.slice(9)));
6863
+ }
6864
+ }
6852
6865
  return json(res, { ...v, ws: w ? w.id : null });
6853
6866
  }
6854
6867
  if (p === '/api/people/decide' && req.method === 'POST') {