create-pmos 0.252.0 → 0.254.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 (54) hide show
  1. package/package.json +1 -1
  2. package/payload/KIT-VERSION +2 -2
  3. package/payload/planning/evals/eval-calibration.md +1 -1
  4. package/payload/planning/okrs/health-budgets.md +1 -1
  5. package/payload/scripts/workflow-state.js +6 -1
  6. package/payload/state/discoveries.md +1 -1
  7. package/payload/state/log.md +1 -1
  8. package/workspace/briefing-premium-board-test.js +275 -0
  9. package/workspace/briefing-premium-initiative-test.js +304 -0
  10. package/workspace/briefing-premium-products-test.js +164 -0
  11. package/workspace/briefing-premium-test.js +438 -0
  12. package/workspace/briefing-premium-work-test.js +281 -0
  13. package/workspace/fixtures/board-premium-facts.js +38 -0
  14. package/workspace/fixtures/briefing-premium-facts.js +32 -0
  15. package/workspace/fixtures/initiative-premium-facts.js +42 -0
  16. package/workspace/fixtures/my-work-premium-facts.js +31 -0
  17. package/workspace/guide/briefing-premium.md +151 -0
  18. package/workspace/guide/index.md +1 -0
  19. package/workspace/lib/board-premium.js +133 -0
  20. package/workspace/lib/briefing-premium-agent.js +134 -0
  21. package/workspace/lib/briefing-premium-products.js +121 -0
  22. package/workspace/lib/briefing-premium-writes.js +138 -0
  23. package/workspace/lib/briefing-premium.js +581 -0
  24. package/workspace/lib/engine/workflow-state.js +6 -1
  25. package/workspace/lib/headless.js +1 -1
  26. package/workspace/lib/initiative-premium.js +147 -0
  27. package/workspace/lib/my-work-premium.js +151 -0
  28. package/workspace/lib/route-modules.js +4 -0
  29. package/workspace/lib/routes-board-premium.js +61 -0
  30. package/workspace/lib/routes-briefing-premium.js +115 -0
  31. package/workspace/lib/routes-initiative-premium.js +68 -0
  32. package/workspace/lib/routes-my-work-premium.js +31 -0
  33. package/workspace/lib/settings-store.js +3 -0
  34. package/workspace/lib/vendor/scripts/workflow-state.js +6 -1
  35. package/workspace/package-lock.json +2 -2
  36. package/workspace/package.json +1 -1
  37. package/workspace/public/pm/premium-board.js +180 -0
  38. package/workspace/public/pm/premium-initiative.js +336 -0
  39. package/workspace/public/pm/premium-shell.js +184 -0
  40. package/workspace/public/pm/premium-work.js +198 -0
  41. package/workspace/public/pm/premium.css +1212 -0
  42. package/workspace/public/pm/premium.js +272 -0
  43. package/workspace/public/premium-board.html +73 -0
  44. package/workspace/public/premium-initiative.html +46 -0
  45. package/workspace/public/premium-work.html +84 -0
  46. package/workspace/public/premium.html +69 -0
  47. package/workspace/public/vendor/geist-mono-var.woff2 +0 -0
  48. package/workspace/public/vendor/geist-var.woff2 +0 -0
  49. package/workspace/server.js +5 -3
  50. package/workspace/test-files.json +14 -0
  51. package/workspace/test-lists/briefing-premium.json +19 -0
  52. package/workspace/third-party/README.md +12 -0
  53. package/workspace/third-party/geist-OFL.LICENSE +93 -0
  54. package/workspace/third-party/geist-mono-OFL.LICENSE +93 -0
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+ /* briefing-premium-writes.js — what the premium Briefing's buttons record (initiative briefing-premium-writes).
3
+ *
4
+ * decide({ root, key, choice, why, now }) → { ok, heading } | { ok: false, status, error } appends one entry to <root>/pm-decisions.md
5
+ * putOff({ runtimeDir, key, until, now }) → { ok, until } | … appends one line to briefing-premium-deferred.jsonl
6
+ * returnRun({ root, runtimeDir, run, reason, now }) → { ok } | … appends one correction to corrections.jsonl
7
+ * readDefers(runtimeDir, now) · decidedPrototypes(root)
8
+ *
9
+ * The caller has already proved the actor is the PM (lib/pm-guard.js requirePagePress). Every writer appends and never rewrites a line.
10
+ * File names are fixed here; no request chooses a path. A refused request changes no file. Nothing here sends anything outward. */
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const refuse = (status, error) => ({ ok: false, status, error });
15
+ const ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,80}$/;
16
+ const KEY_RE = /^(decision|blocked|waited|ev):[A-Za-z0-9][A-Za-z0-9._:-]{0,100}$/;
17
+ const PROTO_CHOICES = ['promote', 'iterate', 'kill'];
18
+ const TRIPWIRE_CHOICES = ['reshape', 'extend', 'retire'];
19
+ const UNTIL = ['later', 'tomorrow', 'nextweek'];
20
+ const DEFERRED = 'briefing-premium-deferred.jsonl';
21
+ const iso = (ms) => new Date(ms).toISOString();
22
+ const day = (ms) => { const d = new Date(ms); return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); };
23
+ const oneLine = (s) => String(s == null ? '' : s).replace(/\s+/g, ' ').trim();
24
+
25
+ function appendLine(file, obj) {
26
+ fs.mkdirSync(path.dirname(file), { recursive: true });
27
+ fs.appendFileSync(file, JSON.stringify(obj) + '\n');
28
+ }
29
+ function readLines(file) {
30
+ let t; try { t = fs.readFileSync(file, 'utf8'); } catch { return []; }
31
+ return t.split('\n').map((l) => l.trim()).filter(Boolean).map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
32
+ }
33
+ function readText(file) { try { return fs.readFileSync(file, 'utf8'); } catch { return ''; } }
34
+
35
+ /** The prototype ids that have a disposition entry in pm-decisions.md (heading "## YYYY-MM-DD — Prototype disposition: <id> — <choice>"). */
36
+ function decidedPrototypes(root) {
37
+ const out = new Set();
38
+ const re = /^## \d{4}-\d{2}-\d{2} — Prototype disposition: ([A-Za-z0-9._-]+) — (promote|iterate|kill)\s*$/gm;
39
+ const t = readText(path.join(root, 'pm-decisions.md'));
40
+ for (let m; (m = re.exec(t));) out.add(m[1]);
41
+ return out;
42
+ }
43
+
44
+ function frontLane(root, id) {
45
+ const t = readText(path.join(root, 'planning', 'initiatives', id + '.md'));
46
+ const m = /^---\n([\s\S]*?)\n---/.exec(t);
47
+ if (!m) return null;
48
+ const l = /^lane:\s*(.+)$/m.exec(m[1]);
49
+ return l ? l[1].trim().replace(/^"(.*)"$/, '$1') : '';
50
+ }
51
+ /** The date a tripwire fired on (its row in kill-conditions.md, the bold date), or null when the row is not there. */
52
+ function tripwireDate(root, id) {
53
+ const t = readText(path.join(root, 'planning', 'okrs', 'kill-conditions.md'));
54
+ const m = new RegExp('^\\|\\s*' + id + '\\s*\\|\\s*[^|]+?\\s*\\|\\s*[^|]+?\\*\\*(\\d{4}-\\d{2}-\\d{2})\\*\\*', 'm').exec(t);
55
+ return m ? m[1] : null;
56
+ }
57
+ function tripwireDecided(root, id, date) {
58
+ // The page's own reading: an entry dated on or after the fire date that names the id.
59
+ const t = readText(path.join(root, 'pm-decisions.md'));
60
+ return t.split(/^## /m).slice(1).some((b) => /^\d{4}-\d{2}-\d{2}/.test(b) && b.slice(0, 10) >= date && new RegExp('\\b' + id + '\\b').test(b));
61
+ }
62
+
63
+ function decide({ root, key, choice, why, now }) {
64
+ now = now || Date.now();
65
+ const m = /^decision:([A-Za-z0-9._-]+)$/.exec(String(key || ''));
66
+ if (!m || !ID_RE.test(m[1])) return refuse(400, 'key must name a decision row');
67
+ const id = m[1];
68
+ const reason = oneLine(why);
69
+ if (reason.length < 3) return refuse(400, 'a decision needs your reason');
70
+ if (reason.length > 1200) return refuse(400, 'the reason is too long (1200 characters at most)');
71
+ const isTrip = /^KC\d+$/.test(id);
72
+ if (isTrip) {
73
+ if (!TRIPWIRE_CHOICES.includes(choice)) return refuse(400, 'choose one of: ' + TRIPWIRE_CHOICES.join(', '));
74
+ const fired = tripwireDate(root, id);
75
+ if (!fired) return refuse(404, id + ' has no dated row in planning/okrs/kill-conditions.md');
76
+ if (tripwireDecided(root, id, fired)) return refuse(409, 'a decision that names ' + id + ' is already on record');
77
+ } else {
78
+ if (!PROTO_CHOICES.includes(choice)) return refuse(400, 'choose one of: ' + PROTO_CHOICES.join(', '));
79
+ const lane = frontLane(root, id);
80
+ if (lane === null) return refuse(404, id + ' is not an initiative in this repository');
81
+ if (lane !== 'prototype') return refuse(409, id + ' is not a prototype, so it takes no disposition');
82
+ if (decidedPrototypes(root).has(id)) return refuse(409, 'a disposition for ' + id + ' is already on record');
83
+ }
84
+ const file = path.join(root, 'pm-decisions.md');
85
+ const heading = day(now) + ' — ' + (isTrip ? 'Tripwire ' + id + ' decided' : 'Prototype disposition: ' + id) + ' — ' + choice;
86
+ const block = [
87
+ '## ' + heading,
88
+ '- context: ' + (isTrip ? 'tripwire ' + id + ' passed its date with no decision on record; recorded from the premium Briefing' : 'prototype ' + id + ' waited for a disposition (D60); recorded from the premium Briefing'),
89
+ '- decision: ' + (isTrip ? id + ' ' + choice : choice) + ' (chosen by the PM)',
90
+ '- rationale: ' + reason,
91
+ '- supersedes: none',
92
+ '',
93
+ ].join('\n');
94
+ let cur = readText(file);
95
+ const lead = cur === '' || cur.endsWith('\n\n') ? '' : cur.endsWith('\n') ? '\n' : '\n\n';
96
+ fs.appendFileSync(file, lead + block);
97
+ return { ok: true, heading };
98
+ }
99
+
100
+ function untilMs(until, now) {
101
+ const d = new Date(now);
102
+ if (until === 'later') return now + 3 * 3600 * 1000;
103
+ if (until === 'tomorrow') { d.setDate(d.getDate() + 1); d.setHours(9, 0, 0, 0); return d.getTime(); }
104
+ const add = ((8 - d.getDay()) % 7) || 7; // the next Monday
105
+ d.setDate(d.getDate() + add); d.setHours(9, 0, 0, 0); return d.getTime();
106
+ }
107
+ function putOff({ runtimeDir, key, until, now }) {
108
+ now = now || Date.now();
109
+ if (!runtimeDir) return refuse(409, 'this workspace has no state folder to keep the answer in');
110
+ if (!KEY_RE.test(String(key || ''))) return refuse(400, 'key must name a row');
111
+ if (!UNTIL.includes(until)) return refuse(400, 'choose one of: ' + UNTIL.join(', '));
112
+ const at = untilMs(until, now);
113
+ appendLine(path.join(runtimeDir, DEFERRED), { key, until: at, choice: until, at: iso(now), by: 'pm' });
114
+ return { ok: true, until: at };
115
+ }
116
+ /** key → the time (ms) the row returns, for lines still in the future. The latest line for a key wins. */
117
+ function readDefers(runtimeDir, now) {
118
+ const out = new Map();
119
+ if (!runtimeDir) return out;
120
+ for (const l of readLines(path.join(runtimeDir, DEFERRED))) if (l && typeof l.key === 'string' && Number.isFinite(l.until)) out.set(l.key, l.until);
121
+ for (const [k, v] of [...out]) if (v <= (now || Date.now())) out.delete(k);
122
+ return out;
123
+ }
124
+
125
+ const RETURN_REASONS = ['wrong facts', 'wrong scope', 'not now', 'too long']; // the reply contract's closed list (scripts/reply-contract)
126
+ function returnRun({ root, runtimeDir, run, reason, now, correctionEvent, appendOutbox }) {
127
+ now = now || Date.now();
128
+ if (!runtimeDir) return refuse(409, 'this workspace has no state folder to keep the correction in');
129
+ if (typeof run !== 'string' || !ID_RE.test(run)) return refuse(400, 'run must be a run id');
130
+ if (!RETURN_REASONS.includes(reason)) return refuse(400, 'choose one reason: ' + RETURN_REASONS.join(', '));
131
+ if (!readLines(path.join(root, 'planning', 'runs', 'agent-runs.jsonl')).some((r) => r && r.run_id === run)) return refuse(404, run + ' is not in planning/runs/agent-runs.jsonl');
132
+ let ev;
133
+ try { ev = correctionEvent('reject', reason, run, new Date(now)); } catch (e) { return refuse(400, e.message); }
134
+ appendOutbox(path.join(runtimeDir, 'corrections.jsonl'), ev);
135
+ return { ok: true };
136
+ }
137
+
138
+ module.exports = { decide, putOff, returnRun, readDefers, decidedPrototypes, untilMs, PROTO_CHOICES, TRIPWIRE_CHOICES, UNTIL, RETURN_REASONS };