create-pmos 0.132.0 → 0.134.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 (64) 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/bin/pmos.js +18 -2
  7. package/workspace/guide/index.md +1 -0
  8. package/workspace/guide/notes.md +19 -1
  9. package/workspace/guide/welcome.md +38 -0
  10. package/workspace/lib/import-overlay.js +32 -0
  11. package/workspace/lib/notes-audio.js +28 -9
  12. package/workspace/lib/notes-capture.js +128 -0
  13. package/workspace/lib/notes-live-guard.js +117 -0
  14. package/workspace/lib/notes-whisper.js +132 -16
  15. package/workspace/lib/notes.js +4 -2
  16. package/workspace/lib/route-modules.js +1 -0
  17. package/workspace/lib/routes-notes.js +60 -8
  18. package/workspace/lib/routes-onboarding.js +215 -0
  19. package/workspace/lib/vendor/scripts/anchor-resolve.js +351 -0
  20. package/workspace/lib/vendor/scripts/delivery-index.js +356 -0
  21. package/workspace/lib/vendor/scripts/intake.js +78 -0
  22. package/workspace/lib/vendor/scripts/lane-defs.js +226 -0
  23. package/workspace/lib/vendor/scripts/migrate-layout.js +775 -0
  24. package/workspace/lib/vendor/scripts/nine-stages.js +81 -0
  25. package/workspace/lib/vendor/scripts/onboarding/apply.js +110 -0
  26. package/workspace/lib/vendor/scripts/onboarding/brief.js +154 -0
  27. package/workspace/lib/vendor/scripts/onboarding/first-briefing.js +41 -0
  28. package/workspace/lib/vendor/scripts/onboarding/first-look.js +84 -0
  29. package/workspace/lib/vendor/scripts/onboarding/infer-brief.js +70 -0
  30. package/workspace/lib/vendor/scripts/onboarding/proposals.js +98 -0
  31. package/workspace/lib/vendor/scripts/onboarding/scan-worker.js +27 -0
  32. package/workspace/lib/vendor/scripts/onboarding/scan.js +859 -0
  33. package/workspace/lib/vendor/scripts/onboarding/structure.js +217 -0
  34. package/workspace/lib/vendor/scripts/plan-items-stage.js +271 -0
  35. package/workspace/lib/vendor/scripts/read-jsonl.js +53 -0
  36. package/workspace/lib/vendor/scripts/signals/layout-paths.js +43 -0
  37. package/workspace/lib/vendor/scripts/status-facts.js +214 -0
  38. package/workspace/lib/vendor/scripts/trust/import-guard.js +36 -0
  39. package/workspace/lib/vendor/scripts/trust/ledger.js +53 -0
  40. package/workspace/lib/vendor/scripts/workflow-state.js +1888 -0
  41. package/workspace/lib/vendor/workspace/lib/actor.js +91 -0
  42. package/workspace/lib/vendor/workspace/lib/board-cache.js +156 -0
  43. package/workspace/lib/vendor/workspace/lib/console-state.js +295 -0
  44. package/workspace/lib/vendor/workspace/lib/doc-category.js +227 -0
  45. package/workspace/lib/vendor/workspace/lib/factory-join.js +158 -0
  46. package/workspace/lib/vendor/workspace/lib/git-info.js +306 -0
  47. package/workspace/lib/vendor/workspace/lib/plan-items.js +922 -0
  48. package/workspace/lib/vendor/workspace/lib/plan-tree.js +419 -0
  49. package/workspace/lib/vendor/workspace/lib/review-gate.js +39 -0
  50. package/workspace/lib/vendor/workspace/lib/sink.js +321 -0
  51. package/workspace/lib/vendor/workspace/lib/state-root.js +587 -0
  52. package/workspace/package-lock.json +2 -2
  53. package/workspace/package.json +1 -1
  54. package/workspace/public/pm/calendar-views.js +25 -12
  55. package/workspace/public/pm/components.css +280 -0
  56. package/workspace/public/pm/console3.css +19 -0
  57. package/workspace/public/pm/shell-recording.js +122 -12
  58. package/workspace/public/pm/view-notes.js +7 -2
  59. package/workspace/public/pm/welcome.js +283 -0
  60. package/workspace/public/welcome.html +20 -0
  61. package/workspace/server.js +2 -2
  62. package/workspace/test-files.json +2 -0
  63. package/workspace/test-lists/meeting-notes.json +5 -3
  64. package/workspace/test-lists/onboarding-firstrun.json +15 -0
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+ /* proposals.js: the proposal store for the onboarding scan (slice 1). Nothing is applied here.
3
+ *
4
+ * One append-only JSONL file per scan. For the real repo the convention is <repo>/onboarding/<scanId>/proposals.jsonl; this
5
+ * module takes the file as an argument and never writes into the folder that was scanned (propose refuses that).
6
+ * Every line goes through scripts/trust/ledger.js append, so ids are ULIDs in order, a line cannot carry its own `by`, and a
7
+ * line is never rewritten. Two events:
8
+ * proposed { id, kind:'proposed', proposal_id, scan_id, candidate, at, why } written by the scan process
9
+ * decided { id, kind:'decided', proposal_id, decision: approve|reject|revise, revision?, why, at } by (and via) from the call path
10
+ * fold(lines) gives each proposal its state: open | approved | rejected | revised. The latest decision wins; earlier ones stay in `history`.
11
+ * A decision is a PM press: decide() and approveBulk() take the actor as an argument and refuse anything that is not 'human:'.
12
+ */
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const ledger = require('../trust/ledger.js');
16
+
17
+ const DECISIONS = ['approve', 'reject', 'revise'];
18
+ const STATE_OF = { approve: 'approved', reject: 'rejected', revise: 'revised' };
19
+ const SCAN_ACTOR = { by: 'process:onboarding-scan', via: 'cli' };
20
+
21
+ const inside = (child, parent) => { const r = path.relative(path.resolve(parent), path.resolve(child)); return r === '' || (!r.startsWith('..') && !path.isAbsolute(r)); };
22
+
23
+ /** The actor of a decision as ledger.append wants it ({by, via}). A bare string 'human:wawan' is a PM press; anything else is refused. */
24
+ function humanActor(actor) {
25
+ const a = typeof actor === 'string' ? { by: actor, via: 'ui' } : actor;
26
+ if (!a || typeof a.by !== 'string' || !a.by.startsWith('human:') || a.by.length <= 'human:'.length) throw new Error('a decision is a PM press: the actor must be human:<name> (got ' + (a && a.by ? a.by : 'none') + ')');
27
+ return { by: a.by, via: a.via || 'ui' };
28
+ }
29
+
30
+ /** Write one `proposed` event per candidate. Idempotent: a proposal id already in the file is skipped. Returns {written, skipped}. */
31
+ function propose(file, scan, opts = {}) {
32
+ if (scan.root && inside(file, scan.root)) throw new Error('refusing to write proposals inside the scanned folder: ' + file);
33
+ fs.mkdirSync(path.dirname(file), { recursive: true });
34
+ const have = new Set(ledger.read(file).filter((l) => l.kind === 'proposed').map((l) => l.proposal_id));
35
+ const at = opts.now || new Date().toISOString();
36
+ let written = 0; let skipped = 0;
37
+ for (const c of scan.candidates) {
38
+ if (have.has(c.id)) { skipped++; continue; }
39
+ ledger.append(file, { id: ledger.newId(), kind: 'proposed', proposal_id: c.id, scan_id: scan.scan_id, candidate: c, at, why: 'scan ' + scan.scan_id + ': ' + c.kind + ' from ' + c.source + ' evidence' }, SCAN_ACTOR);
40
+ written++;
41
+ }
42
+ return { written, skipped };
43
+ }
44
+
45
+ /** fold(lines) -> [{ proposal_id, ...candidate, state, decision, why, by, at, revision, history[] }] in proposal order. */
46
+ function fold(lines) {
47
+ const by = new Map();
48
+ for (const l of lines) {
49
+ if (l.kind === 'proposed') { if (!by.has(l.proposal_id)) by.set(l.proposal_id, Object.assign({}, l.candidate, { proposal_id: l.proposal_id, scan_id: l.scan_id, proposed_at: l.at, state: 'open', decision: null, why: null, by: null, at: null, revision: null, history: [] })); }
50
+ else if (l.kind === 'decided' && by.has(l.proposal_id)) {
51
+ const p = by.get(l.proposal_id);
52
+ p.history.push({ id: l.id, decision: l.decision, why: l.why || null, by: l.by, at: l.at, revision: l.revision || null });
53
+ Object.assign(p, { state: STATE_OF[l.decision] || p.state, decision: l.decision, why: l.why || null, by: l.by, at: l.at, revision: l.revision || null });
54
+ }
55
+ }
56
+ return [...by.values()];
57
+ }
58
+ const load = (file) => fold(ledger.read(file));
59
+
60
+ /** One decision by the PM. approve may have no why; reject and revise must say why. */
61
+ function decide(file, { proposal_id, decision, revision, why }, actor, opts = {}) {
62
+ const a = humanActor(actor);
63
+ if (!DECISIONS.includes(decision)) throw new Error('decision must be one of ' + DECISIONS.join(', '));
64
+ const w = typeof why === 'string' ? why.trim() : '';
65
+ if (decision !== 'approve' && !w) throw new Error(decision + ' needs a why');
66
+ if (decision === 'revise' && !(revision && typeof revision === 'object' && Object.keys(revision).length)) throw new Error('revise needs a revision (the fields the PM changed)');
67
+ if (!load(file).some((p) => p.proposal_id === proposal_id)) throw new Error('unknown proposal ' + proposal_id);
68
+ const ev = { id: ledger.newId(), kind: 'decided', proposal_id, decision, why: w, at: opts.now || new Date().toISOString() };
69
+ if (decision === 'revise') ev.revision = revision;
70
+ return ledger.append(file, ev, a, { noWhy: decision === 'approve' });
71
+ }
72
+
73
+ /** Bulk approve is for a proposal that is high confidence AND mechanical, and still open. Says which were skipped and why. */
74
+ function approveBulk(file, ids, actor, opts = {}) {
75
+ const a = humanActor(actor);
76
+ const all = new Map(load(file).map((p) => [p.proposal_id, p]));
77
+ const approved = []; const skipped = [];
78
+ for (const id of ids) {
79
+ const p = all.get(id);
80
+ if (!p) { skipped.push({ id, reason: 'unknown proposal' }); continue; }
81
+ if (p.state !== 'open') { skipped.push({ id, reason: 'already ' + p.state }); continue; }
82
+ if (p.confidence !== 'high') { skipped.push({ id, reason: 'confidence is ' + p.confidence + ', not high' }); continue; }
83
+ if (p.source !== 'mechanical') { skipped.push({ id, reason: 'source is ' + p.source + ', not mechanical' }); continue; }
84
+ decide(file, { proposal_id: id, decision: 'approve', why: 'bulk approve: high confidence, mechanical' }, a, opts);
85
+ approved.push(id);
86
+ }
87
+ return { approved, skipped };
88
+ }
89
+
90
+ /** Counts by kind, state, confidence and source. */
91
+ function summary(lines) {
92
+ const ps = Array.isArray(lines) && lines.length && lines[0].proposal_id && lines[0].state ? lines : fold(lines);
93
+ const count = (key) => ps.reduce((o, p) => { o[p[key]] = (o[p[key]] || 0) + 1; return o; }, {});
94
+ const kinds = {}; for (const p of ps) { const k = (kinds[p.kind] = kinds[p.kind] || { total: 0, open: 0, approved: 0, rejected: 0, revised: 0 }); k.total++; k[p.state]++; }
95
+ return { total: ps.length, by_kind: kinds, by_state: count('state'), by_confidence: count('confidence'), by_source: count('source') };
96
+ }
97
+
98
+ module.exports = { propose, fold, load, decide, approveBulk, summary, humanActor, DECISIONS };
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /* scan-worker.js: the first-run scan in its own process, so a large folder never blocks the workspace server (slice 2).
4
+ * node scripts/onboarding/scan-worker.js <root> <overlayDir> <today>
5
+ * Reads <root> read-only (inferred brief, every initiative kept), writes brief.json, scan.json, firstlook.json and proposals.jsonl
6
+ * into <overlayDir>, and prints one JSON line { scan_id }. Refuses an overlay inside the scanned root. Exit 0 on success. */
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ function run(root, dir, today) {
11
+ const r = path.relative(path.resolve(root), path.resolve(dir));
12
+ if (r === '' || (!r.startsWith('..') && !path.isAbsolute(r))) throw new Error('refusing: the overlay is inside the scanned folder');
13
+ const { brief, scan } = require('./infer-brief.js').firstRunScan(root, { today });
14
+ const look = require('./first-look.js').firstLook(scan, today);
15
+ fs.mkdirSync(dir, { recursive: true });
16
+ fs.writeFileSync(path.join(dir, 'brief.json'), JSON.stringify(brief, null, 2));
17
+ fs.writeFileSync(path.join(dir, 'scan.json'), JSON.stringify(scan));
18
+ fs.writeFileSync(path.join(dir, 'firstlook.json'), JSON.stringify({ sentence: look.sentence, numbers: look.numbers, findings: look.findings }));
19
+ require('./proposals.js').propose(path.join(dir, 'proposals.jsonl'), scan);
20
+ return { scan_id: scan.scan_id };
21
+ }
22
+
23
+ if (require.main === module) {
24
+ try { process.stdout.write(JSON.stringify(run(process.argv[2], process.argv[3], process.argv[4])) + '\n'); }
25
+ catch (e) { process.stderr.write(String((e && e.message) || e).slice(0, 400) + '\n'); process.exitCode = 1; }
26
+ }
27
+ module.exports = { run };