create-pmos 0.239.0 → 0.241.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.
Files changed (48) hide show
  1. package/package.json +1 -1
  2. package/payload/KIT-VERSION +1 -1
  3. package/payload/planning/evals/eval-calibration.md +1 -1
  4. package/payload/planning/okrs/health-budgets.md +1 -1
  5. package/payload/state/discoveries.md +1 -1
  6. package/workspace/c3-report-structure-real-test.js +211 -0
  7. package/workspace/c3-report-structure-test.js +382 -0
  8. package/workspace/guide/briefing.md +31 -3
  9. package/workspace/guide/index.md +2 -0
  10. package/workspace/guide/report-design.md +4 -0
  11. package/workspace/guide/report-structure.md +91 -0
  12. package/workspace/guide/reports-dashboards.md +1 -1
  13. package/workspace/guide/reports-set-up.md +2 -0
  14. package/workspace/lib/charts-store.js +124 -0
  15. package/workspace/lib/headless.js +17 -3
  16. package/workspace/lib/needs-providers.js +1 -0
  17. package/workspace/lib/report-structure-needs.js +64 -0
  18. package/workspace/lib/report-structure.js +515 -0
  19. package/workspace/lib/route-modules.js +3 -1
  20. package/workspace/lib/routes-brief-page.js +56 -5
  21. package/workspace/lib/routes-mentions.js +37 -0
  22. package/workspace/lib/routes-report-setups.js +11 -6
  23. package/workspace/lib/routes-report-structure.js +619 -0
  24. package/workspace/package-lock.json +2 -2
  25. package/workspace/package.json +1 -1
  26. package/workspace/public/pm/briefing-deck.js +1 -0
  27. package/workspace/public/pm/c3-brief-mentions.js +136 -0
  28. package/workspace/public/pm/c3-brief-needs.js +23 -0
  29. package/workspace/public/pm/c3-brief-pulse.js +1 -0
  30. package/workspace/public/pm/c3-brief-summary.js +139 -0
  31. package/workspace/public/pm/c3-demo-brief-page.js +20 -4
  32. package/workspace/public/pm/c3-demo-rd.js +4 -0
  33. package/workspace/public/pm/c3-design-studio.js +7 -3
  34. package/workspace/public/pm/c3-report-setup.js +3 -0
  35. package/workspace/public/pm/c3-report-structure.css +40 -0
  36. package/workspace/public/pm/c3-report-structure.js +455 -0
  37. package/workspace/public/pm/c3-reports.css +2 -0
  38. package/workspace/public/pm/c3-reports.js +22 -3
  39. package/workspace/public/pm/c3-shell.js +34 -2
  40. package/workspace/public/pm/console3.css +12 -0
  41. package/workspace/server.js +38 -9
  42. package/workspace/skills/report-source-check.skill +45 -0
  43. package/workspace/skills/report-structure-dry.skill +51 -0
  44. package/workspace/skills/report-structure.skill +61 -0
  45. package/workspace/skills/report-template.skill +10 -0
  46. package/workspace/test-files.json +2 -0
  47. package/workspace/test-lists/report-structure-backend.json +5 -0
  48. package/workspace/test-lists/report-structure-ui.json +11 -0
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+ /* routes-mentions.js — POST /api/mentions/read { id, read } → { ok } (initiative c3-brief-summary-mentions)
3
+ *
4
+ * The PM marked a mention of today's brief read on the Briefing (read: false undoes it). One appended line in <runtime>/mentions/read.jsonl:
5
+ * { id, read, at }. GET /api/brief-page reads the file back (routes-brief-page.js readMarks: the last line for an id wins).
6
+ * The page press only (pm-guard requirePagePress). Nothing is sent to Slack, Jira, Confluence or mail. */
7
+ const path = require('path');
8
+ const guard = require('./pm-guard.js');
9
+ const pmItems = require('./pm-items.js');
10
+ const { MENTION_ID } = require('./routes-brief-page.js');
11
+
12
+ /** The check of a body: null when it is good, else the reason. Pure. */
13
+ function badBody(b) {
14
+ if (!b || !MENTION_ID.test(String(b.id || ''))) return 'id must be a mention id (12 hex characters)';
15
+ if (typeof b.read !== 'boolean') return 'read must be true or false';
16
+ return null;
17
+ }
18
+
19
+ module.exports = {
20
+ prefixes: ['/api/mentions'],
21
+ badBody,
22
+ handle: async (req, res, url, ctx) => {
23
+ if (url.pathname !== '/api/mentions/read') return false;
24
+ if (req.method !== 'POST') { ctx.json(res, { ok: false, error: 'POST only' }, 405); return true; }
25
+ const got = await ctx.readJson(req);
26
+ if (!got.ok) { ctx.json(res, { ok: false, error: 'invalid JSON body' }, 400); return true; }
27
+ const b = got.body || {};
28
+ const g = guard.requirePagePress(req, b);
29
+ if (!g.ok) { ctx.json(res, { ok: false, error: g.error }, g.status); return true; }
30
+ const err = badBody(b);
31
+ if (err) { ctx.json(res, { ok: false, error: err }, 400); return true; }
32
+ if (!ctx.runtimeDir) { ctx.json(res, { ok: false, error: 'no runtime folder' }, 500); return true; }
33
+ pmItems.appendEvent(path.join(ctx.runtimeDir, 'mentions', 'read.jsonl'), { id: b.id, read: b.read }, new Date());
34
+ ctx.json(res, { ok: true });
35
+ return true;
36
+ },
37
+ };
@@ -2,7 +2,8 @@
2
2
  /* routes-report-setups.js — the Reports workspace's routes (initiative report-workspace, PRD sections 2 to 6).
3
3
  *
4
4
  * GET /api/report-setups?ws → { setups: [{ id, name, purpose, audience, source, output, schedule, label, next_draft, last_issued, status, cycles,
5
- * copy: { kind, state: none|in-step|pmos-ahead|copy-changed|not-checked, url } }],
5
+ * copy: { kind, state: none|in-step|pmos-ahead|copy-changed|not-checked, url },
6
+ * structure: { saved, at, charts, sections } | null the saved report structure (initiative report-structure) }],
6
7
  * findings: [{ id, name, why, evidence: [{ kind, text }], confidence: likely|not-sure, proposal }],
7
8
  * asks: [{ id, text, state: running|ready|failed, changed, proposal, error }],
8
9
  * finder: { cadence, sources: { calendar, slack, mail, drive }, last_run, next_run } }
@@ -119,7 +120,10 @@ function documentsOf(ctx, w) {
119
120
  return { docs, reports: r.reports };
120
121
  }
121
122
 
122
- function setupOut(s, now, docsOf, idx) {
123
+ /** The saved structure of a report (initiative report-structure): { saved, at, charts, sections }, else null. */
124
+ function structureOf(root, id) { try { return require('./report-structure.js').savedOf(root, id); } catch { return null; } }
125
+
126
+ function setupOut(s, now, docsOf, idx, root) {
123
127
  const mine = (docsOf.reports || []).filter((x) => x.cadence === s.id);
124
128
  const issued = mine.filter((x) => x.issued).map((x) => x.slot).sort().pop() || null;
125
129
  const latest = mine.slice().sort((a, b) => (a.slot < b.slot ? 1 : -1))[0] || null;
@@ -129,6 +133,7 @@ function setupOut(s, now, docsOf, idx) {
129
133
  id: s.id, name: s.name, purpose: s.purpose, audience: s.audience, source: s.source, output: s.output, schedule: s.schedule, label: RS.scheduleLabel(s.schedule),
130
134
  next_draft: next, last_issued: issued, status: s.status, cycles: mine.length,
131
135
  copy: copyOf(latest ? linkFor(idx, latest.path, s.output) : null, docSync.copyKindOf(s.output)),
136
+ structure: structureOf(root, s.id),
132
137
  };
133
138
  }
134
139
 
@@ -157,7 +162,7 @@ function listAnswer(ctx, w, now) {
157
162
  const docsOf = documentsOf(ctx, w);
158
163
  const idx = copyIndex(w.root);
159
164
  return {
160
- setups: [...st.setups.values()].map((s) => setupOut(s, new Date(now), docsOf, idx)),
165
+ setups: [...st.setups.values()].map((s) => setupOut(s, new Date(now), docsOf, idx, w.root)),
161
166
  findings: RS.openFindings(st).map((f) => ({ id: f.id, name: f.name, why: f.why, evidence: f.evidence, confidence: f.confidence, proposal: f.proposal })),
162
167
  asks: [...st.asks.values()].map((a) => ({ id: a.id, text: a.text, state: a.state, changed: !!a.changed, proposal: a.proposal, error: a.error })),
163
168
  finder: finderOut(ctx, st, now),
@@ -341,7 +346,7 @@ async function handle(req, res, url, ctx) {
341
346
  // in place and says why; Draft it now on the row tries again.
342
347
  const drafted = firstDraft(root, r, g.actor);
343
348
  if (drafted.path) ctx.invalidateNeedsAgg && ctx.invalidateNeedsAgg();
344
- return send({ status: 200, body: { ok: true, setup: setupOut(r.setup, new Date(), documentsOf(ctx, w), copyIndex(root)), first_draft: r.first_draft, drafted } });
349
+ return send({ status: 200, body: { ok: true, setup: setupOut(r.setup, new Date(), documentsOf(ctx, w), copyIndex(root), root), first_draft: r.first_draft, drafted } });
345
350
  }
346
351
  if (sub === 'dismiss') {
347
352
  const r = RS.dismissFinding(file, { id: text(body.finding), by: g.actor });
@@ -363,7 +368,7 @@ async function handle(req, res, url, ctx) {
363
368
  if (sub === 'pause' || sub === 'resume') {
364
369
  const r = RS[sub](root, text(body.id), opts);
365
370
  if (!r.ok) return fail(r);
366
- return send({ status: 200, body: { ok: true, setup: setupOut(r.setup, new Date(), documentsOf(ctx, w), copyIndex(root)) } });
371
+ return send({ status: 200, body: { ok: true, setup: setupOut(r.setup, new Date(), documentsOf(ctx, w), copyIndex(root), root) } });
367
372
  }
368
373
  if (sub === 'from-dashboard') {
369
374
  const key = text(body.key);
@@ -376,7 +381,7 @@ async function handle(req, res, url, ctx) {
376
381
  schedule: body.schedule, sources: [`The ${name} dashboard`], sections: RS.FRAME.slice(), learned_from: 'Set up from the dashboard.', fixes: [] };
377
382
  const r = RS.setUp(root, proposal, opts);
378
383
  if (!r.ok) return fail(r);
379
- return send({ status: 200, body: { ok: true, setup: setupOut(r.setup, new Date(), { reports: [] }, new Map()) } });
384
+ return send({ status: 200, body: { ok: true, setup: setupOut(r.setup, new Date(), { reports: [] }, new Map(), root) } });
380
385
  }
381
386
  if (sub === 'finder') {
382
387
  const r = RS.setFinder(file, { cadence: body.cadence, sources: body.sources, by: g.actor });