create-pmos 0.531.0 → 0.532.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.531.0",
3
+ "version": "0.532.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": {
@@ -32,6 +32,19 @@
32
32
  "audit"
33
33
  ],
34
34
  "pmosChanges": [
35
+ {
36
+ "v": "0.532.0",
37
+ "at": "2026-11-04T19:01:35.763Z",
38
+ "f": 4,
39
+ "prs": [
40
+ {
41
+ "n": 1248,
42
+ "t": "Report templates T02–T08",
43
+ "s": "Report templates T02–T08",
44
+ "k": "new"
45
+ }
46
+ ]
47
+ },
35
48
  {
36
49
  "v": "0.531.0",
37
50
  "at": "2026-11-04T18:44:50.726Z",
@@ -406,19 +419,6 @@
406
419
  "k": "new"
407
420
  }
408
421
  ]
409
- },
410
- {
411
- "v": "0.512.0",
412
- "at": "2026-11-03T15:33:10.629Z",
413
- "f": 4,
414
- "prs": [
415
- {
416
- "n": 1204,
417
- "t": "Suggest the next prompt and sharpen a rough one in the chat",
418
- "s": "Suggest the next prompt and sharpen a rough one in the chat",
419
- "k": "new"
420
- }
421
- ]
422
422
  }
423
423
  ]
424
424
  }
@@ -1,3 +1,3 @@
1
- kernel-source: pmos@add0a4c9
1
+ kernel-source: pmos@c8e1d796
2
2
  assembled: 2026-11-04
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-11-04T18:44:47Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-04T19:01:32Z"}
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-11-04T18:44:47Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-04T19:01:32Z"}
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-11-04T18:44:47Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-04T19:01:32Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -1,4 +1,17 @@
1
1
  [
2
+ {
3
+ "v": "0.532.0",
4
+ "at": "2026-11-04T19:01:35.763Z",
5
+ "f": 4,
6
+ "prs": [
7
+ {
8
+ "n": 1248,
9
+ "t": "Report templates T02–T08",
10
+ "s": "Report templates T02–T08",
11
+ "k": "new"
12
+ }
13
+ ]
14
+ },
2
15
  {
3
16
  "v": "0.531.0",
4
17
  "at": "2026-11-04T18:44:50.726Z",
@@ -92,6 +92,8 @@ pmos report issue biweekly 2026-10-16
92
92
 
93
93
  `pmos report library` writes one page with every slide layout of the report library, filled with placeholder words. Open it in a browser to see the layouts a report can use. Add `--out <file>` to choose the file.
94
94
 
95
+ The library also has seven report templates: weekly-review, program-kickoff, meeting-recap, portfolio-snapshot, quarterly-review, decision-brief and incident-review. A template is a default order of slides, not a fixed structure. `pmos report library --template <id>` draws one template with placeholder words. `pmos report make <id> --template <id>` starts a report from it: the writer uses its order and adds or drops slides when the findings say so. A recurring report keeps the outline its readers agreed, so the template is used only for a report that has none.
96
+
95
97
  Every command accepts `--root <dir>` to name the repository. `draft` and `list` also accept `--json`.
96
98
 
97
99
  ## The window
@@ -40,7 +40,7 @@ const { parseArgs } = require('./schedule-cli');
40
40
  const COMMANDS = { report: ['make', 'read', 'find', 'write', 'check', 'rule', 'draft', 'issue', 'list', 'propose', 'definitions', 'setups', 'finder', 'library'] };
41
41
  const USAGE = [
42
42
  'usage: pmos report make|read|find|write|check|rule — the report engine: brief, read, find, answer, render (any report, any shape)',
43
- ' pmos report make <id> [--ask "<the question>"] [--readers "<who>"] [--source <kind:ref>,…] [--slot <date>]',
43
+ ' pmos report make <id> [--ask "<the question>"] [--readers "<who>"] [--source <kind:ref>,…] [--template <id>] [--slot <date>]',
44
44
  ' Steps 1 to 3: derive the brief (readers, question, window, sources; no section list), read every source PMOS reaches,',
45
45
  ' and find what changed. An ad hoc report needs --ask. Sources: pmos · pack:<state file of readings> · connector:<name> · document:<path>.',
46
46
  ' pmos report read <id> [--slot <date>] --from <file|->',
@@ -54,8 +54,9 @@ const USAGE = [
54
54
  ' pmos report check <id> [--slot <date>] the checks of the last write: substance first, then form',
55
55
  ' pmos report rule <id> list | add <voice|wording|off|keep> "<text or part>" | remove <n>',
56
56
  ' What the readers taught this report: a voice or wording rule, or a part beyond the ask turned off or kept.',
57
- 'usage: pmos report library [--out <file>] — the slide library: every layout in the report frame, with placeholder words',
57
+ 'usage: pmos report library [--template <id>] [--out <file>] [--json] — the slide library: every layout in the report frame, with placeholder words',
58
58
  ' Writes one HTML page (default: report-library.html in the current folder). Open it in a browser to check the look.',
59
+ ' --template draws one report template (its card, then each part); --json lists the templates and the layouts.',
59
60
  'usage: pmos report draft|issue|list — cadence reports (for example the biweekly)',
60
61
  ' pmos report draft <cadence> [<slot>] [--now <time>] [--redraft]',
61
62
  ' Write the draft for a slot (default: today) with every number. Refuses a slot that has a draft;',
@@ -76,9 +77,9 @@ const USAGE = [
76
77
  ' every command: [--json] [--root <dir>]. The cadence is the report skill id without "report-", for example biweekly.',
77
78
  ];
78
79
  const BOOL = new Set(['json', 'redraft', 'help']);
79
- const VALUE = new Set(['now', 'root', 'ask', 'readers', 'source', 'slot', 'from', 'out']);
80
- const ALLOWED = { make: ['json', 'now', 'root', 'ask', 'readers', 'source', 'slot'], read: ['json', 'now', 'root', 'slot', 'from', 'source'], find: ['json', 'now', 'root', 'slot', 'from'],
81
- write: ['json', 'now', 'root', 'slot', 'from'], check: ['json', 'root', 'slot'], rule: ['json', 'root'], draft: ['json', 'redraft', 'now', 'root'], issue: ['root'], list: ['json', 'root'], propose: ['json', 'root'], definitions: ['json', 'root'], setups: ['json', 'root'], finder: ['json', 'root'], library: ['json', 'out'] };
80
+ const VALUE = new Set(['now', 'root', 'ask', 'readers', 'source', 'slot', 'from', 'out', 'template']);
81
+ const ALLOWED = { make: ['json', 'now', 'root', 'ask', 'readers', 'source', 'slot', 'template'], read: ['json', 'now', 'root', 'slot', 'from', 'source'], find: ['json', 'now', 'root', 'slot', 'from'],
82
+ write: ['json', 'now', 'root', 'slot', 'from'], check: ['json', 'root', 'slot'], rule: ['json', 'root'], draft: ['json', 'redraft', 'now', 'root'], issue: ['root'], list: ['json', 'root'], propose: ['json', 'root'], definitions: ['json', 'root'], setups: ['json', 'root'], finder: ['json', 'root'], library: ['json', 'out', 'template'] };
82
83
  const CADENCE_RE = /^[a-z][a-z0-9-]{0,39}$/;
83
84
  const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
84
85
 
@@ -151,9 +152,15 @@ async function run(argv, { say = (s) => console.log(s), cwd = process.cwd(), isT
151
152
  // the slide library as one page: sanitized as a report render is, with the workspace face inlined so the file shows it anywhere
152
153
  const out = path.resolve(cwd || process.cwd(), typeof flags.out === 'string' && flags.out ? flags.out : 'report-library.html');
153
154
  const D = require('./report-design.js');
155
+ const LB = require('./report-library.js');
156
+ if (flags.template !== undefined && !LB.templateOf(String(flags.template))) return fail(`there is no report template ${flags.template}. The templates: ${LB.TEMPLATES.map((t) => t.id).join(', ')}`, 2);
157
+ if (json && flags.out === undefined && flags.template === undefined) {
158
+ say(JSON.stringify({ ok: true, layouts: require('./report-slides.js').LAYOUTS.filter((x) => x !== 'blocks'), templates: LB.TEMPLATES.map((t) => ({ id: t.id, name: t.name, use: t.use, readers: t.readers, cadence: t.cadence, outline: t.outline })) }));
159
+ return 0;
160
+ }
154
161
  let face = '';
155
162
  try { face = `<style>@font-face{font-family:"Plus Jakarta Sans";font-weight:200 800;src:url(data:font/woff2;base64,${fs.readFileSync(path.join(__dirname, '..', 'public', 'vendor', 'plus-jakarta-sans-var.woff2')).toString('base64')}) format("woff2")}body{margin:0;background:#888}.pmos-slide{margin:0 auto 12px}</style>`; } catch { face = ''; }
156
- const html = D.sanitize(require('./report-library.js').libraryHtml());
163
+ const html = D.sanitize(LB.libraryHtml(flags.template ? { template: String(flags.template) } : {}));
157
164
  try { fs.writeFileSync(out, html.replace('<style>', face + '<style>')); } catch (e) { return fail(`could not write ${out}: ${String((e && e.message) || e).slice(0, 160)}`); }
158
165
  if (json) say(JSON.stringify({ ok: true, path: out })); else say(`Wrote ${out}`);
159
166
  return 0;
@@ -232,7 +239,7 @@ function engineVerb(verb, { pos, flags, root, json, say, fail, isTTY, env, stdin
232
239
  return 0;
233
240
  };
234
241
  if (verb === 'make') {
235
- const m = RE.make(root, id, { slot: day, ask: flags.ask, readers: flags.readers, sources, now: clock });
242
+ const m = RE.make(root, id, { slot: day, ask: flags.ask, readers: flags.readers, sources, template: flags.template, now: clock });
236
243
  if (!m.ok) return fail(m.error);
237
244
  return summary(m);
238
245
  }
@@ -205,7 +205,7 @@ function issuedSlots(root, id) {
205
205
  /** The headings of a document the engine did not write: the agreed outline of an older issue. */
206
206
  const headingsOf = (md) => str(md).replace(/^---[\s\S]*?\n---\n?/, '').split('\n').filter((l) => /^## /.test(l)).map((l) => l.slice(3).trim()).filter(Boolean);
207
207
 
208
- function briefOf(root, id, { slot, ask, readers, sources, now = Date.now() } = {}) {
208
+ function briefOf(root, id, { slot, ask, readers, sources, template, now = Date.now() } = {}) {
209
209
  if (!ID_RE.test(str(id))) return refuse(400, 'the report id is lower case letters, digits and dashes, at most 40, starting with a letter');
210
210
  const today = slot || dayOf(now);
211
211
  if (!isDate(today)) return refuse(400, 'the slot is a date, YYYY-MM-DD');
@@ -213,7 +213,7 @@ function briefOf(root, id, { slot, ask, readers, sources, now = Date.now() } = {
213
213
  const P = paths(abs, id, today);
214
214
  const from = [];
215
215
  const brief = { v: 1, id, slot: today, title: null, readers: null, question: null, questions: [], decision: null, window: null, sources: [], recurring: false,
216
- cycle_days: null, agreed_outline: [], earlier_outline: [], last_issue: null, carried_asks: [], rules: [], voice: VOICE.slice(), from };
216
+ cycle_days: null, agreed_outline: [], earlier_outline: [], template: null, last_issue: null, carried_asks: [], rules: [], voice: VOICE.slice(), from };
217
217
 
218
218
  // the definition: readers, the question (its purpose), the sources, the cadence
219
219
  let def = null; let defText = '';
@@ -280,6 +280,12 @@ function briefOf(root, id, { slot, ask, readers, sources, now = Date.now() } = {
280
280
  }
281
281
  from.push(LP.md.rel);
282
282
  }
283
+ // a report template (lib/report-library.js) is a default order of slide layouts for a report with no agreed outline; the agreed one wins
284
+ if (template) {
285
+ const t = require('./report-library.js').templateOf(str(template));
286
+ if (!t) return refuse(400, `there is no report template ${template}. The templates: ${require('./report-library.js').TEMPLATES.map((x) => x.id).join(', ')}`);
287
+ if (!brief.agreed_outline.length) { from.push(`template:${t.id}`); brief.template = { id: t.id, name: t.name, outline: t.outline.map((o) => ({ part: o.part, layout: o.layout })) }; }
288
+ }
283
289
  brief.rules = rulesOf(abs, id);
284
290
  for (const r of brief.rules) if (r.kind === 'voice' || r.kind === 'wording') brief.voice.push(r.text);
285
291
  return { ok: true, brief };
@@ -763,7 +769,7 @@ function drawBlock(b, r, byId) {
763
769
  // ── the steps, written ──────────────────────────────────────────────────────────────────────────────────────────────
764
770
  function isIssued(root, id, slot) { return issuedSlots(root, id).includes(slot); }
765
771
  /** Steps 1–3: the brief, the readings, the findings, written next to the draft. Filed readings and findings are kept across a re-read. */
766
- function make(root, id, { slot, ask, readers, sources, add, gaps, addFindings, now = Date.now() } = {}) {
772
+ function make(root, id, { slot, ask, readers, sources, template, add, gaps, addFindings, now = Date.now() } = {}) {
767
773
  const abs = path.resolve(String(root));
768
774
  const day = slot || dayOf(now);
769
775
  let P; try { P = paths(abs, id, day); } catch (e) { return refuse(400, e.message); }
@@ -771,6 +777,7 @@ function make(root, id, { slot, ask, readers, sources, add, gaps, addFindings, n
771
777
  const old = readJson(P.brief.abs);
772
778
  // a re-read keeps the brief's chat question and named sources unless new ones are given
773
779
  const b = briefOf(abs, id, { slot: day, now, ask: ask || (old && old.from && old.from.includes('chat') ? old.question : undefined), readers: readers || (old && old.from && old.from.includes('readers') ? old.readers : undefined),
780
+ template: template || (old && old.template ? old.template.id : undefined),
774
781
  sources: [...(old ? old.sources.filter((s) => s.kind === 'pack' || s.kind === 'connector' || s.kind === 'document') : []), ...(sources || [])] });
775
782
  if (!b.ok) return b;
776
783
  const rd = readingsOf(abs, b.brief, { add, gaps, keep: readJson(P.readings.abs), now });
@@ -4,7 +4,9 @@
4
4
  * look can be checked against the boards in a browser, and what the tests draw.
5
5
  *
6
6
  * sampleDeck({ today }) → { name, today, slides } (pure; today = YYYY-MM-DD, default 2026-11-04, the canvas's day)
7
- * libraryHtml(o) → the sample deck as one page (lib/report-slides.js deckHtml)
7
+ * libraryHtml(o) → the sample deck as one page (lib/report-slides.js deckHtml); o.template = one template's deck instead
8
+ * TEMPLATES · templateOf(id) → the 7 report templates (boards T02–T08): a default order of layouts, never a fixed structure
9
+ * templateDeck(id, o) → that template with placeholder words: its card, then each part in its layout
8
10
  *
9
11
  * Placeholder words only: nothing here is a reading, so it never goes into a report.
10
12
  */
@@ -69,8 +71,64 @@ function sampleDeck({ today = '2026-11-04' } = {}) {
69
71
  { date: '2026-11-10', time: '10:00', items: [{ length: '5 min', text: 'Item' }, { length: '15 min', text: 'Item' }, { length: '10 min', text: 'Item' }] }, 'How the agenda is built'),
70
72
  ];
71
73
  slides.forEach((s, i) => { s.n = i + 1; });
74
+ // the footer names the layout and the templates that use it (boards L01–L23)
75
+ for (const sl of slides) {
76
+ const uses = TEMPLATES.filter((t) => t.outline.some((o) => o.layout === sl.layout)).map((t) => t.name);
77
+ sl.source = ['Layout: ' + (NAMES[sl.layout] || sl.layout), uses.length ? uses.join(', ') : 'no template yet', 'placeholder data'].join(' · ');
78
+ }
72
79
  return { name: 'Slide layouts', today, slides };
73
80
  }
74
- const libraryHtml = (o) => RS.deckHtml(sampleDeck(o));
81
+ /* THE TEMPLATES (canvas boards T02–T08). A template is a DEFAULT ORDER of layouts, never a fixed structure: the writer keeps what is true and
82
+ * adds or drops a slide when the findings say so (Wawan, 2026-11-03: no fixed structure). Each part: { part, layout }. A recurring report keeps
83
+ * the outline its readers agreed; a template only starts a report that has none. */
84
+ const T = (id, name, title, sub, use, readers, cadence, parts) => Object.freeze({ id, name, title, sub, use, readers, cadence,
85
+ outline: Object.freeze(parts.map(([part, layout]) => Object.freeze({ part, layout }))) });
86
+ const TEMPLATES = Object.freeze([
87
+ T('weekly-review', 'Weekly Review', 'Weekly Review: what moved, what slipped, what waits on you', 'Eight slides. Read in five minutes.', 'Every Monday for a team lead or sponsor.', 'team lead, sponsor', 'weekly',
88
+ [['Title with agenda', 'overview'], ['This week', 'tiles'], ['Key results', 'ledger'], ['What changed', 'compare'], ['The week', 'schedule'], ['Asks', 'decisions'], ['Next week', 'agenda'], ['Sources', 'sources']]),
89
+ T('program-kickoff', 'Program Kick-off', 'Program Kick-off: why, who, when, risks, first thirty days', 'Ten slides. Presented once, then kept as the charter.', 'At the start of a program or initiative.', 'sponsor, leads, partners', 'once per program',
90
+ [['Title', 'overview'], ['Why', 'statement'], ['Scope', 'lists'], ['People', 'people'], ['Milestones', 'timeline'], ['Risks', 'risks'], ['Ways of working', 'agenda'], ['Success', 'tiles'], ['First 30 days', 'focus'], ['Decisions', 'decisions']]),
91
+ T('meeting-recap', 'Meeting Recap', 'Meeting Recap: decisions, actions, open questions', 'Seven slides. The two-minute read for people who were not there.', 'Within an hour of a meeting.', 'attendees and absentees', 'after each meeting',
92
+ [['Title', 'overview'], ['The short version', 'agenda'], ['Decisions', 'quotes'], ['Action items', 'actions'], ['Discussion', 'minutes'], ['Open and parked', 'lists'], ['Next', 'next']]),
93
+ T('portfolio-snapshot', 'Portfolio Snapshot', 'Portfolio Snapshot: health, money, dependencies, decisions', 'Nine slides. One sitting.', 'For leadership who own several programs.', 'leadership', 'every two weeks',
94
+ [['Title', 'overview'], ['Health', 'donut'], ['Heatmap', 'heatmap'], ['Timeline', 'timeline'], ['Money and outcome', 'bubbles'], ['Top risks', 'risks'], ['Dependencies', 'network'], ['Spotlight', 'compare'], ['Decisions', 'decisions']]),
95
+ T('quarterly-review', 'Quarterly Review', 'Quarterly Review: answer first, then goals, bets, decisions', 'Nine slides. Leads with the answer, ends with sources.', 'At the end of each quarter.', 'leadership, board', 'quarterly',
96
+ [['Title', 'overview'], ['Answer', 'statement'], ['Scorecard', 'tiles'], ['Timeline', 'timeline'], ['Wins and misses', 'lists'], ['Risks', 'risks'], ['Bets', 'ranked'], ['Decisions', 'decisions'], ['Sources', 'sources']]),
97
+ T('decision-brief', 'Decision Brief', 'Decision Brief: the question, the options, the pick', 'Six slides. For a single call that needs a yes.', 'When one person must decide and needs the facts.', 'the decider', 'ad hoc',
98
+ [['The question', 'statement'], ['Options', 'compare'], ['Ranked by impact', 'ranked'], ['Risks of the pick', 'risks'], ['The ask', 'decisions'], ['Sources', 'sources']]),
99
+ T('incident-review', 'Incident Review', 'Incident Review: what happened, why, what we change', 'Six slides. Blameless and short.', 'Within a week of an incident.', 'engineering, support, leadership', 'after incident',
100
+ [['What happened', 'statement'], ['Timeline of the day', 'timeline'], ['Causes ranked', 'ranked'], ['What went well and not', 'lists'], ['Actions', 'actions'], ['Sources', 'sources']]),
101
+ ]);
102
+ const templateOf = (id) => TEMPLATES.find((t) => t.id === id) || null;
103
+ const NAMES = { overview: 'Overview', tiles: 'Tiles', ledger: 'Ledger', timeline: 'Timeline', compare: 'Compare', lists: 'Two lists', risks: 'Risk table', ranked: 'Ranked list',
104
+ decisions: 'Decisions', quotes: 'Quotes', actions: 'Actions', agenda: 'Agenda', heatmap: 'Heatmap', network: 'Network', schedule: 'Schedule', donut: 'Donut', bubbles: 'Bubbles',
105
+ people: 'People', statement: 'Statement', sources: 'Sources', minutes: 'Minutes', focus: 'Focus', next: 'Next' };
106
+ /* One row per part (the agenda numbers them); past eight parts, two parts share a row ("Title · Why"), as the canvas cards pair them. */
107
+ function cardRows(outline) {
108
+ const one = outline.map((o) => ({ title: o.part, text: NAMES[o.layout] || o.layout }));
109
+ if (one.length <= 8) return one;
110
+ const rows = [];
111
+ for (let i = 0; i < one.length; i += 2) rows.push(one[i + 1] ? { title: `${one[i].title} · ${one[i + 1].title}`, text: `${one[i].text} · ${one[i + 1].text}` } : one[i]);
112
+ return rows;
113
+ }
114
+ /** The template's card (boards T02–T08): its default order as numbered rows, and when to use it in the insight column. */
115
+ function templateCard(t) {
116
+ return { layout: 'agenda', eyebrow: `${t.name} · Report template`, title: t.title, sub: t.sub,
117
+ data: { rows: cardRows(t.outline) },
118
+ insight: { head: 'When to use it', text: t.use, points: [{ tone: 'ok', text: `Readers: ${t.readers}` }, { tone: 'ok', text: `Cadence: ${t.cadence}` }, { tone: 'watch', text: 'Add or drop slides when the findings say so' }],
119
+ note: 'Order is the default; the writer may change it per report.' },
120
+ source: 'Template library · sample data' };
121
+ }
122
+ /** A template drawn with placeholder words: its card, then each part in its layout (the sample of that layout, titled by the part). */
123
+ function templateDeck(id, o = {}) {
124
+ const t = templateOf(id);
125
+ if (!t) return null;
126
+ const lib = sampleDeck(o).slides;
127
+ const slides = [templateCard(t)].concat(t.outline.map((p) => { const s = lib.find((x) => x.layout === p.layout); return { ...s, eyebrow: `${t.name} · ${p.part}`, title: p.part, sub: s.title, source: 'Template library · placeholder data' }; }));
128
+ slides.forEach((s, i) => { s.n = i + 1; });
129
+ return { name: t.name, today: o.today || '2026-11-04', slides };
130
+ }
131
+ /** The library page: every layout, or one template (o.template). */
132
+ const libraryHtml = (o = {}) => RS.deckHtml(o.template ? templateDeck(o.template, o) : sampleDeck(o));
75
133
 
76
- module.exports = { sampleDeck, libraryHtml };
134
+ module.exports = { sampleDeck, libraryHtml, TEMPLATES, templateOf, templateCard, templateDeck };
@@ -78,7 +78,7 @@ const SPEC = {
78
78
  decisions: { rows: { max: 3, min: 1, item: { question: 't', pick: 't', who: 't', by: 'date' } } },
79
79
  quotes: { agreed: 't', quotes: { max: 3, min: 1, item: { text: 't', who: 't', at: 't' } } },
80
80
  actions: { rows: { max: 6, min: 1, item: { text: 't', who: 't', due: 'date' } } },
81
- agenda: { rows: { max: 5, min: 1, item: { title: 't', text: 't' } } },
81
+ agenda: { rows: { max: 8, min: 1, item: { title: 't', text: 't' } } },
82
82
  heatmap: { cols: { max: 6, min: 1, text: true }, total: 't', rows: { max: 6, min: 1, item: { name: 't', total: 't', cells: { max: 6, min: 1, item: { text: 't', tone: 'tone' } } } } },
83
83
  network: { nodes: { max: 8, min: 2, item: { id: 'id', label: 't', tone: 'tone' } }, edges: { max: 12, min: 1, pair: true } },
84
84
  schedule: { cols: { max: 7, min: 1, text: true }, rows: { max: 6, min: 1, item: { name: 't', from: 'i', to: 'i', text: 't', tone: 'tone' } } },
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.531.0",
3
+ "version": "0.532.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.531.0",
9
+ "version": "0.532.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@anthropic-ai/sdk": "^1.3.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.531.0",
3
+ "version": "0.532.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",
@@ -26,12 +26,14 @@ Procedure:
26
26
  1. Run `pmos report make <id>`. For an ad hoc report add `--ask "<the question>"`, and `--readers "<who>"` when you know them. Name sources the brief must read with `--source`, comma-separated: `connector:<name>=<what it is>` (Jira, BigQuery for GA4, Confluence, …), `document:<path>`, `pack:<state file of readings>`, `pmos` (the PMOS ledgers). It writes `<slot>.brief.json`, `<slot>.readings.json` and `<slot>.findings.json` next to the draft, and prints the gaps.
27
27
  - If it refuses because the slot is issued, stop: an issued report is frozen.
28
28
  - If it says there is no report with that id, it needs a question: ask the PM once for the question, or take it from the chat line.
29
+ - When the PM names a kind of report that a template fits (a weekly review, a kick-off, a meeting recap, a portfolio snapshot, a quarterly review, a decision brief, an incident review), add `--template <id>`.
29
30
  2. Read `<slot>.brief.json`: the readers, the questions, the window, the sources, the last issue, the agreed outline, the carried asks, the voice and the rules.
30
31
  3. Read each source that the gaps name, through its connector or the file, read-only. Write what you read as readings in one JSON file: `{"readings":[{"id":"web-login-rate","value":81,"unit":"%","n":597,"of":733,"at":"2026-10-19","source":"ga4","how":"events_20261019, sessions with a password start","label":"Web password login, recorded success"}],"gaps":[{"source":"native-app","reason":"read only on the device"}]}`. Give `target` and `better` ("higher" or "lower") when the reading has a target. Give `flag` when the reading is off what the readers expect. Run `pmos report read <id> --from <file>`. A parameter of a method (a 5-second match window) is a reading too.
31
32
  4. Find what answers the readers. Write the findings that answer each question, each citing the readings it rests on, and run `pmos report find <id> --from <file>` with `{"findings":[{"text":"…","readings":["…"],"kind":"answers-question","question":"…"}]}`. The engine adds its own findings: changes since the last issue, misses against a target, initiatives off plan, items due within 14 days, the asks carried over. If `<slot>.findings.json` says `"none": true`, there is nothing to report: stop and say so in one line. Do not write a report about nothing.
32
33
  5. Choose the outline for these readers and this question. Any sections, any titles, any order.
33
34
  - A recurring report keeps `agreed_outline`, the sections of its last issued issue. Add a section only when a finding needs one, drop one only when it would be empty, and say so in `outline_note` (one line).
34
35
  - `earlier_outline` is the shape of an issue written before the engine (the old fixed frame). It is an example, not an agreement: keep what served the readers.
36
+ - `template` is set when the PM started the report from a report template (`pmos report make <id> --template <name>`; `pmos report library --json` lists them). Its `outline` is a default order of parts, each with its slide layout: use it as the starting outline, retitle each part as a finding, and add or drop a part when the findings say so. An agreed outline wins over a template.
35
37
  - Each section title is a finding, not a label: "Sign-up stalls before submit", not "Sign-up". Reading only the titles tells the story.
36
38
  - Shapes other reports took, as examples and never a menu: a decision (the lean · the options and what each costs · what could reverse it · your call); a watch (today against yesterday and the week · the drop that moved most · why); a stakeholder update (are the dates on track · what each team needs and by when · open decisions · what could stop it); a review (the summary for decision · the outcomes by segment · the challenges and answers · method); did-it-work (what was promised · what we got · the verdict).
37
39
  6. Write `<slot>.report.json` (in a scratch file, then `pmos report write <id> --from <file>`):