create-pmos 0.134.0 → 0.136.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 (211) 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/state/discoveries.md +1 -1
  6. package/payload/state/log.md +1 -1
  7. package/workspace/bin/notes-replay.js +235 -0
  8. package/workspace/c3-briefing-test.js +130 -0
  9. package/workspace/c3-foundation-test.js +159 -0
  10. package/workspace/c3-ib-items-test.js +105 -0
  11. package/workspace/c3-ib-more-test.js +120 -0
  12. package/workspace/c3-ib-triage-backlog-test.js +119 -0
  13. package/workspace/c3-ini-harness.js +66 -0
  14. package/workspace/c3-initiatives-test.js +140 -0
  15. package/workspace/c3-one-home-test.js +14 -1
  16. package/workspace/c3-reports-test.js +57 -18
  17. package/workspace/c3-settings-test.js +139 -0
  18. package/workspace/c3-work-test.js +42 -10
  19. package/workspace/fixtures/one-home-baseline.json +1 -9
  20. package/workspace/guide/briefing-cards.md +15 -3
  21. package/workspace/guide/briefing.md +12 -8
  22. package/workspace/guide/calendar-popup.md +2 -2
  23. package/workspace/guide/console-calendar.md +39 -10
  24. package/workspace/guide/index.md +1 -0
  25. package/workspace/guide/initiative-boards.md +65 -0
  26. package/workspace/guide/jira.md +3 -1
  27. package/workspace/guide/layout.md +2 -0
  28. package/workspace/guide/my-work.md +5 -3
  29. package/workspace/guide/notes.md +16 -1
  30. package/workspace/guide/reports-dashboards.md +29 -5
  31. package/workspace/guide/settings.md +68 -0
  32. package/workspace/guide/welcome.md +10 -10
  33. package/workspace/lib/calendar-cli.js +3 -2
  34. package/workspace/lib/calendar-layers.js +21 -10
  35. package/workspace/lib/calendar-tidy.js +230 -0
  36. package/workspace/lib/engine/boards.js +542 -0
  37. package/workspace/lib/engine/breakdown-ops.js +180 -0
  38. package/workspace/lib/engine/calendar-import.js +84 -0
  39. package/workspace/lib/engine/calendar-model.js +185 -0
  40. package/workspace/lib/engine/card-state.js +92 -0
  41. package/workspace/lib/engine/derived/derive.js +92 -0
  42. package/workspace/lib/engine/derived/plan.js +3 -0
  43. package/workspace/lib/engine/derived/trust.js +3 -0
  44. package/workspace/lib/engine/doc-health.js +142 -0
  45. package/workspace/lib/engine/doc-sheet.js +75 -0
  46. package/workspace/lib/engine/intake.js +78 -0
  47. package/workspace/lib/engine/outbox.js +86 -0
  48. package/workspace/lib/engine/plan-forecast.js +342 -0
  49. package/workspace/lib/engine/promises.js +29 -0
  50. package/workspace/lib/engine/records/index.js +114 -0
  51. package/workspace/lib/engine/releases.js +96 -0
  52. package/workspace/lib/engine/replies.js +99 -0
  53. package/workspace/lib/engine/reply-contract/reply-contract.js +372 -0
  54. package/workspace/lib/engine/report-families.js +175 -0
  55. package/workspace/lib/engine/roadmap-ops.js +150 -0
  56. package/workspace/lib/engine/sessions-list.js +27 -0
  57. package/workspace/lib/engine/signal-rules.js +95 -0
  58. package/workspace/lib/engine/signals/cards.js +57 -0
  59. package/workspace/lib/engine/signals/detectors.js +370 -0
  60. package/workspace/lib/engine/signals/facts-cli.js +8 -0
  61. package/workspace/lib/engine/signals/facts.js +197 -0
  62. package/workspace/lib/engine/signals/index.js +33 -0
  63. package/workspace/lib/engine/signals/kinds.js +110 -0
  64. package/workspace/lib/engine/signals/layout-paths.js +44 -0
  65. package/workspace/lib/engine/signals/options.js +24 -0
  66. package/workspace/lib/engine/signals/score.js +72 -0
  67. package/workspace/lib/engine/signals/select.js +47 -0
  68. package/workspace/lib/engine/step-planner.js +65 -0
  69. package/workspace/lib/engine/trust/claim-rules.json +93 -0
  70. package/workspace/lib/engine/trust/import-calendar.js +21 -0
  71. package/workspace/lib/engine/trust/import-guard.js +36 -0
  72. package/workspace/lib/engine/trust/ledger.js +53 -0
  73. package/workspace/lib/engine/trust/outbox.js +45 -0
  74. package/workspace/lib/engine/trust/plan.js +5 -0
  75. package/workspace/lib/engine/trust/trust.js +21 -0
  76. package/workspace/lib/engine/truth-table.js +57 -0
  77. package/workspace/lib/engine/workspace-lib.js +24 -0
  78. package/workspace/lib/factory-join.js +1 -1
  79. package/workspace/lib/facts-async.js +4 -4
  80. package/workspace/lib/headless.js +95 -0
  81. package/workspace/lib/import-overlay.js +2 -1
  82. package/workspace/lib/jira-status.js +30 -3
  83. package/workspace/lib/notes-capture.js +9 -1
  84. package/workspace/lib/notes-final.js +38 -13
  85. package/workspace/lib/notes-replay.js +125 -0
  86. package/workspace/lib/notes-screen.js +180 -0
  87. package/workspace/lib/notes-whisper.js +10 -3
  88. package/workspace/lib/outbox-store.js +10 -6
  89. package/workspace/lib/pmos-verbs.js +3 -1
  90. package/workspace/lib/proactive-cli.js +7 -0
  91. package/workspace/lib/proactive.js +15 -2
  92. package/workspace/lib/records-dir.js +29 -0
  93. package/workspace/lib/repo-script.js +36 -0
  94. package/workspace/lib/report-dashboards.js +5 -2
  95. package/workspace/lib/report-frame.js +122 -0
  96. package/workspace/lib/report-schedules.js +113 -0
  97. package/workspace/lib/route-modules.js +9 -2
  98. package/workspace/lib/routes-boards.js +734 -0
  99. package/workspace/lib/routes-brief-page.js +13 -4
  100. package/workspace/lib/routes-briefing.js +22 -11
  101. package/workspace/lib/routes-c3-init.js +9 -8
  102. package/workspace/lib/routes-c3-life.js +2 -1
  103. package/workspace/lib/routes-c3-reports.js +99 -15
  104. package/workspace/lib/routes-c3-strategy.js +7 -5
  105. package/workspace/lib/routes-c3-timeline.js +3 -2
  106. package/workspace/lib/routes-calendar-import.js +3 -2
  107. package/workspace/lib/routes-calendar-views.js +9 -4
  108. package/workspace/lib/routes-doc-health.js +2 -1
  109. package/workspace/lib/routes-doc-sheet.js +2 -1
  110. package/workspace/lib/routes-forecast.js +2 -1
  111. package/workspace/lib/routes-hooks.js +3 -2
  112. package/workspace/lib/routes-initiatives.js +2 -1
  113. package/workspace/lib/routes-intake.js +2 -1
  114. package/workspace/lib/routes-notes.js +55 -9
  115. package/workspace/lib/routes-outbox.js +23 -3
  116. package/workspace/lib/routes-pm-items.js +19 -6
  117. package/workspace/lib/routes-records.js +10 -7
  118. package/workspace/lib/routes-releases.js +2 -1
  119. package/workspace/lib/routes-report-facts.js +4 -3
  120. package/workspace/lib/routes-report-families.js +2 -1
  121. package/workspace/lib/routes-roadmap-ops.js +17 -14
  122. package/workspace/lib/routes-runs.js +6 -5
  123. package/workspace/lib/routes-settings.js +51 -0
  124. package/workspace/lib/routes-signals.js +3 -2
  125. package/workspace/lib/routes-stage-tools.js +220 -0
  126. package/workspace/lib/run-answers.js +2 -1
  127. package/workspace/lib/settings-store.js +226 -0
  128. package/workspace/lib/stage-tools.js +208 -0
  129. package/workspace/lib/vendor/scripts/signals/layout-paths.js +2 -1
  130. package/workspace/lib/vendor/scripts/workspace-lib.js +24 -0
  131. package/workspace/lib/work-evidence.js +73 -0
  132. package/workspace/package-lock.json +2 -2
  133. package/workspace/package.json +1 -1
  134. package/workspace/public/pm/briefing-deck.js +63 -17
  135. package/workspace/public/pm/briefing-model.js +36 -7
  136. package/workspace/public/pm/c3-ambient.js +100 -0
  137. package/workspace/public/pm/c3-brief-needs.js +26 -6
  138. package/workspace/public/pm/c3-brief-pulse.js +12 -4
  139. package/workspace/public/pm/c3-brief-today.js +56 -21
  140. package/workspace/public/pm/c3-brief.js +2 -1
  141. package/workspace/public/pm/c3-calendar.js +161 -42
  142. package/workspace/public/pm/c3-calpop.js +5 -5
  143. package/workspace/public/pm/c3-demo-boards.js +649 -0
  144. package/workspace/public/pm/c3-demo-brief-page.js +1 -1
  145. package/workspace/public/pm/c3-demo-cal4.js +3 -2
  146. package/workspace/public/pm/c3-demo-cal5.js +52 -0
  147. package/workspace/public/pm/c3-demo-data.js +17 -27
  148. package/workspace/public/pm/c3-demo-rep3.js +46 -5
  149. package/workspace/public/pm/c3-demo.js +2 -1
  150. package/workspace/public/pm/c3-dock.js +1 -1
  151. package/workspace/public/pm/c3-ib-docs.js +80 -0
  152. package/workspace/public/pm/c3-ib-initiatives.js +134 -0
  153. package/workspace/public/pm/c3-ib-items.js +201 -0
  154. package/workspace/public/pm/c3-ib-releases.js +51 -0
  155. package/workspace/public/pm/c3-ib-sprints.js +55 -0
  156. package/workspace/public/pm/c3-ib-triage.js +178 -0
  157. package/workspace/public/pm/c3-ii-tabs.js +240 -0
  158. package/workspace/public/pm/c3-ini-records.js +197 -0
  159. package/workspace/public/pm/c3-ini-timeline.js +278 -0
  160. package/workspace/public/pm/c3-ini-ui.js +229 -0
  161. package/workspace/public/pm/c3-ini-work.js +139 -0
  162. package/workspace/public/pm/c3-initiatives.css +218 -0
  163. package/workspace/public/pm/c3-initiatives.js +741 -0
  164. package/workspace/public/pm/c3-plan.js +2 -2
  165. package/workspace/public/pm/c3-reports.css +47 -47
  166. package/workspace/public/pm/c3-reports.js +223 -185
  167. package/workspace/public/pm/c3-settings.css +74 -0
  168. package/workspace/public/pm/c3-settings.js +703 -0
  169. package/workspace/public/pm/c3-shell.js +102 -36
  170. package/workspace/public/pm/c3-terminal-icons.js +2 -2
  171. package/workspace/public/pm/c3-terminal.js +2 -2
  172. package/workspace/public/pm/calendar-views.js +1 -1
  173. package/workspace/public/pm/components.css +319 -213
  174. package/workspace/public/pm/console3.css +99 -23
  175. package/workspace/public/pm/dest-notes.js +26 -4
  176. package/workspace/public/pm/notes-screen-capture.js +165 -0
  177. package/workspace/public/pm/notes-screen-view.js +98 -0
  178. package/workspace/public/pm/panel-trees.js +5 -2
  179. package/workspace/public/pm/pm-work-view.js +233 -146
  180. package/workspace/public/pm/shell-bell.js +3 -2
  181. package/workspace/public/pm/shell-recording.js +29 -8
  182. package/workspace/public/pm/tokens.css +2 -2
  183. package/workspace/public/pm/view-notes.js +46 -9
  184. package/workspace/public/pm/welcome.js +197 -89
  185. package/workspace/public/welcome.html +6 -4
  186. package/workspace/server.js +105 -6
  187. package/workspace/skills/day-check.skill +1 -1
  188. package/workspace/skills/jira-sync.skill +6 -4
  189. package/workspace/skills/morning-brief.skill +1 -1
  190. package/workspace/test-files.json +12 -3
  191. package/workspace/test-lists/c3-ib-items.json +9 -0
  192. package/workspace/test-lists/c3-ib-more.json +9 -0
  193. package/workspace/test-lists/c3-ib-triage-backlog.json +9 -0
  194. package/workspace/test-lists/c3-initiative-boards.json +6 -0
  195. package/workspace/test-lists/calendar-tidy-up.json +6 -0
  196. package/workspace/test-lists/console3-briefing.json +9 -0
  197. package/workspace/test-lists/console3-foundation.json +9 -0
  198. package/workspace/test-lists/console3-reports.json +3 -3
  199. package/workspace/test-lists/meeting-notes.json +10 -4
  200. package/workspace/test-lists/workspace-headless-jobs.json +5 -0
  201. package/workspace/test-lists/workspace-package-script-closure.json +6 -0
  202. package/workspace/lib/vendor/workspace/lib/actor.js +0 -91
  203. package/workspace/lib/vendor/workspace/lib/board-cache.js +0 -156
  204. package/workspace/lib/vendor/workspace/lib/console-state.js +0 -295
  205. package/workspace/lib/vendor/workspace/lib/factory-join.js +0 -158
  206. package/workspace/lib/vendor/workspace/lib/git-info.js +0 -306
  207. package/workspace/lib/vendor/workspace/lib/plan-items.js +0 -922
  208. package/workspace/lib/vendor/workspace/lib/plan-tree.js +0 -419
  209. package/workspace/lib/vendor/workspace/lib/review-gate.js +0 -39
  210. package/workspace/lib/vendor/workspace/lib/sink.js +0 -321
  211. package/workspace/lib/vendor/workspace/lib/state-root.js +0 -587
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
  /* routes-brief-page.js — GET /api/brief-page[?ws=<id>&now=ISO] (console3 Briefing as the brief, PRD briefing-brief R2, R3, R8).
3
3
  *
4
- * → { ok, date, today: [meeting], needs: [row], decisions_error? }
4
+ * → { ok, date, me?, today: [meeting], needs: [row], decisions_error? } me: the PM's first name from git config user.name, or null
5
5
  *
6
6
  * READ ONLY, and built only from what PMOS already holds. Nothing is invented.
7
7
  * today one row per meeting of the local day, in time order, from the cached calendar events (routes-calendar-import.js readEvents,
@@ -21,6 +21,7 @@
21
21
  * has it. A deferred item ('Not yet') is left out until its defer date or time. Writes go through the existing pm-items routes. */
22
22
  const path = require('path');
23
23
  const pmItems = require('./pm-items.js');
24
+ const { repoScript } = require('./repo-script.js');
24
25
 
25
26
  const pad = (n) => String(n).padStart(2, '0');
26
27
  const localDate = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
@@ -153,9 +154,17 @@ function notesAdapter(runtimeDir) {
153
154
  };
154
155
  }
155
156
 
157
+ /** The PM's first name for the greeting: the first word of `git config user.name` where PMOS runs. null when git holds none (the page then greets without a name). */
158
+ function firstName(cwd) {
159
+ try {
160
+ const n = String(require('child_process').execFileSync('git', ['config', 'user.name'], { cwd, encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'] })).trim().split(/\s+/)[0];
161
+ return n && n.length <= 40 ? n : null;
162
+ } catch { return null; }
163
+ }
164
+
156
165
  module.exports = {
157
166
  prefixes: ['/api/brief-page'],
158
- build, todayAgenda, needsOf, evidenceFor, deferred,
167
+ build, todayAgenda, needsOf, evidenceFor, deferred, firstName,
159
168
  handle: async (req, res, url, ctx) => {
160
169
  if (url.pathname !== '/api/brief-page') return false;
161
170
  if (req.method !== 'GET') { ctx.json(res, { ok: false, error: 'GET only' }, 405); return true; }
@@ -174,10 +183,10 @@ module.exports = {
174
183
  const root = (w && w.root) || path.resolve(__dirname, '..', '..');
175
184
  const iso = localDate(now);
176
185
  const facts = await require('./facts-async.js').readFactsAsync(root, { today: iso });
177
- const sig = path.resolve(__dirname, '..', '..', 'scripts', 'signals');
178
- cards = require(path.join(sig, 'index.js')).present(facts, { lens: 'Unblock', zoom: 'week', today: iso }).cards || [];
186
+ cards = require(repoScript('signals/index.js')).present(facts, { lens: 'Unblock', zoom: 'week', today: iso }).cards || [];
179
187
  } catch (e) { decisionsError = 'the decisions could not be read: ' + (e && e.message ? e.message : String(e)); }
180
188
  const out = build({ now: now.toISOString() }, { events, items, cards, decisionsError, notes: dir ? notesAdapter(dir) : {} });
189
+ if (out.body && out.body.ok) out.body.me = firstName(path.resolve(__dirname, '..', '..'));
181
190
  ctx.json(res, out.body, out.status);
182
191
  } catch (e) {
183
192
  ctx.json(res, { ok: false, error: 'the brief page could not be built: ' + (e && e.message ? e.message : String(e)) }, 500);
@@ -1,8 +1,12 @@
1
1
  'use strict';
2
- /* routes-briefing.js — GET /api/briefing?lens=Plan|Unblock|Close|Reflect&zoom=day|week|month[&now=ISO] (console3 wave C1).
2
+ /* routes-briefing.js — GET /api/briefing?lens=Plan|Unblock|Close|Reflect&zoom=day|week|month[&scope=brief|rep][&now=ISO] (console3 wave C1).
3
3
  *
4
- * { ok, lens, zoom, cards, ticker, banner, own_needs }
5
- * own_needs: the PM's own items overdue or due today, as needs rows (lib/needs-overdue.js), overdue first. Additive; the page shows them above the cards.
4
+ * { ok, scope, lens, zoom, cards, ticker, banner, own_needs }
5
+ * scope: whose deck asks. brief (or none) runs the engine's briefing detectors, rep its reports detectors (console3-deck-flow-fixes).
6
+ * Any other scope answers no cards, so a page never shows another page's cards.
7
+ * own_needs: the PM's own items overdue or due today, as needs rows (lib/needs-overdue.js), overdue first. Briefing scope only; the page shows them above the cards.
8
+ * notices: the drafts a dashboard schedule made in the last 7 days that are still in the outbox (lib/report-schedules.js noticesOf).
9
+ * Additive; the bell lists them after the cards, and each opens Reports > Drafts.
6
10
  *
7
11
  * `cards` are the engine's selected cards (scripts/signals present() over readRepoFacts of the repo root), with the card
8
12
  * anatomy untouched. `ticker` is one line per top card, built from the card's own headline and deadline. `banner` is the
@@ -10,9 +14,12 @@
10
14
  * { ok:false, error } with 500 and never throws out of the handler.
11
15
  */
12
16
  const path = require('path');
17
+ const { repoScript } = require('./repo-script.js');
13
18
 
14
19
  const LENSES = ['Plan', 'Unblock', 'Close', 'Reflect'];
15
20
  const ZOOMS = ['day', 'week', 'month'];
21
+ // the engine's detector pages for each deck that asks (scripts/signals/detectors.js `page`)
22
+ const SCOPE_PAGES = { brief: ['briefing'], rep: ['reports'] };
16
23
 
17
24
  function lensFor(date) {
18
25
  const h = date.getHours();
@@ -38,9 +45,8 @@ function factsOnce(eng, root, iso) {
38
45
 
39
46
  function loadEngine() {
40
47
  // the engine is this repo's; `root` only says which repo's initiatives it reads
41
- const sig = path.resolve(__dirname, '..', '..', 'scripts', 'signals');
42
- const { present } = require(path.join(sig, 'index.js'));
43
- const { readRepoFacts } = require(path.join(sig, 'facts.js'));
48
+ const { present } = require(repoScript('signals/index.js'));
49
+ const { readRepoFacts } = require(repoScript('signals/facts.js'));
44
50
  return { present, readRepoFacts };
45
51
  }
46
52
 
@@ -52,12 +58,15 @@ function briefing(query, opts = {}) {
52
58
  if (isNaN(now.getTime())) now = new Date();
53
59
  const lens = LENSES.includes(q.lens) ? q.lens : lensFor(now);
54
60
  const zoom = ZOOMS.includes(q.zoom) ? q.zoom : 'week';
61
+ const scope = q.scope ? String(q.scope) : 'brief';
62
+ const pages = SCOPE_PAGES[scope];
63
+ if (!pages) return { code: 200, body: { ok: true, scope, lens, zoom, cards: [], ticker: [], banner: null } };
55
64
  try {
56
65
  const eng = (opts.load || loadEngine)(root);
57
66
  const iso = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0');
58
67
  const facts = opts.load ? eng.readRepoFacts(root, { today: iso }) : factsOnce(eng, root, iso);
59
68
  let useLens = lens;
60
- let cards = eng.present(facts, { lens, zoom, today: iso }).cards || [];
69
+ let cards = eng.present(facts, { lens, zoom, today: iso, pages }).cards || [];
61
70
  // When the clock's lens has no card, show the lens that has the most (ties keep the clock's lens). On the real repo
62
71
  // this is usually all four empty: the briefing detectors read promised dates, forecasts, holders and the like, and no
63
72
  // initiative record carries them yet. The answer then stays empty and says so by fallback_lens being absent.
@@ -65,12 +74,12 @@ function briefing(query, opts = {}) {
65
74
  if (!cards.length && !q.lens) {
66
75
  for (const l of LENSES) {
67
76
  if (l === lens) continue;
68
- const c = eng.present(facts, { lens: l, zoom, today: iso }).cards || [];
77
+ const c = eng.present(facts, { lens: l, zoom, today: iso, pages }).cards || [];
69
78
  if (c.length > cards.length) { cards = c; useLens = l; fallback = true; }
70
79
  }
71
80
  }
72
81
  const banner = cards.find((c) => c.pinned && c.tag === 'Critical') || null;
73
- return { code: 200, body: { ok: true, lens: useLens, zoom, cards, ticker: tickerOf(cards), banner, ...(fallback ? { fallback_lens: true } : {}) } };
82
+ return { code: 200, body: { ok: true, scope, lens: useLens, zoom, cards, ticker: tickerOf(cards), banner, ...(fallback ? { fallback_lens: true } : {}) } };
74
83
  } catch (e) {
75
84
  return { code: 500, body: { ok: false, error: 'the signal engine failed: ' + (e && e.message ? e.message : String(e)) } };
76
85
  }
@@ -78,7 +87,7 @@ function briefing(query, opts = {}) {
78
87
 
79
88
  module.exports = {
80
89
  prefixes: ['/api/briefing'],
81
- briefing, lensFor, tickerOf,
90
+ briefing, lensFor, tickerOf, SCOPE_PAGES,
82
91
  handle: async (req, res, url, ctx) => {
83
92
  if (url.pathname !== '/api/briefing') return false;
84
93
  if (req.method !== 'GET') { ctx.json(res, { ok: false, error: 'GET only' }, 405); return true; }
@@ -96,12 +105,14 @@ module.exports = {
96
105
  const eng = loadEngine();
97
106
  const out = briefing(q, { root: useRoot, load: () => ({ present: eng.present, readRepoFacts: () => facts }) });
98
107
  // additive: the PM's own overdue and due-today items, as needs rows beside the engine's cards (lib/needs-overdue.js); a read failure leaves the list empty
99
- if (out.code === 200) {
108
+ if (out.code === 200 && out.body.scope === 'brief') {
100
109
  try {
101
110
  const pmItems = require('./pm-items.js');
102
111
  const file = path.join(ctx.runtimeDir, 'pm-items', 'items.jsonl');
103
112
  out.body.own_needs = require('./needs-overdue.js').overdueNeeds(pmItems.fold(pmItems.readLines(file)), iso);
104
113
  } catch { out.body.own_needs = []; }
114
+ // additive: a draft a dashboard schedule made in the last 7 days, still in the outbox, is a notice on the bell (lib/report-schedules.js). Nothing was sent.
115
+ try { out.body.notices = require('./report-schedules.js').noticesOf(require('./outbox-store.js').readOutbox(ctx.runtimeDir)); } catch { out.body.notices = []; }
105
116
  }
106
117
  ctx.json(res, out.body, out.code);
107
118
  return true;
@@ -10,9 +10,10 @@
10
10
  const fs = require('fs');
11
11
  const path = require('path');
12
12
  const roadmapOps = require('./routes-roadmap-ops.js');
13
- const records = require(path.join(__dirname, '..', '..', 'scripts', 'records', 'index.js'));
14
- const roadmap = require(path.join(__dirname, '..', '..', 'scripts', 'roadmap-ops.js'));
15
- const breakdown = require(path.join(__dirname, '..', '..', 'scripts', 'breakdown-ops.js'));
13
+ const { repoScript } = require('./repo-script.js');
14
+ const records = require(repoScript('records/index.js'));
15
+ const roadmap = require(repoScript('roadmap-ops.js'));
16
+ const breakdown = require(repoScript('breakdown-ops.js'));
16
17
 
17
18
  function readPeople(runtimeDir) {
18
19
  try {
@@ -21,16 +22,16 @@ function readPeople(runtimeDir) {
21
22
  } catch { return []; }
22
23
  }
23
24
 
24
- function roadmapRead(ctx) {
25
- const root = roadmapOps.rootOf(ctx);
25
+ function roadmapRead(ctx, ws) {
26
+ const root = roadmapOps.rootOf(ctx, ws);
26
27
  const bets = records.list(root, 'bets');
27
28
  const people = readPeople(ctx.runtimeDir);
28
29
  const krs = records.list(root, 'kr-metrics').map((k) => ({ id: k.rid, title: k.metric }));
29
30
  return { status: 200, body: { ok: true, bets, capacity: roadmap.capacityLine(bets, people), people, krs } };
30
31
  }
31
32
 
32
- function breakdownRead(ctx) {
33
- const root = roadmapOps.rootOf(ctx);
33
+ function breakdownRead(ctx, ws) {
34
+ const root = roadmapOps.rootOf(ctx, ws);
34
35
  const items = breakdown.list(root);
35
36
  const people = readPeople(ctx.runtimeDir);
36
37
  const events = breakdown.readLines(root).filter((e) => ['add', 'edit', 'delete', 'dependency'].includes(e.kind))
@@ -45,7 +46,7 @@ module.exports = {
45
46
  handle: async (req, res, url, ctx) => {
46
47
  if (req.method !== 'GET') return false;
47
48
  const sub = url.pathname.slice('/api/c3-init'.length);
48
- const r = sub === '/roadmap' ? roadmapRead(ctx) : sub === '/breakdown' ? breakdownRead(ctx) : null;
49
+ const r = sub === '/roadmap' ? roadmapRead(ctx, url.searchParams.get('ws')) : sub === '/breakdown' ? breakdownRead(ctx, url.searchParams.get('ws')) : null;
49
50
  if (!r) return false;
50
51
  ctx.json(res, r.body, r.status);
51
52
  return true;
@@ -13,7 +13,8 @@
13
13
  * Repo facts are not read here (readRepoFacts is synchronous and slow); only two small JSONL files and the existence of named files are. */
14
14
  const fs = require('fs');
15
15
  const path = require('path');
16
- const records = require(path.join(__dirname, '..', '..', 'scripts', 'records', 'index.js'));
16
+ const { repoScript } = require('./repo-script.js');
17
+ const records = require(repoScript('records/index.js'));
17
18
 
18
19
  const REPO = path.join(__dirname, '..', '..');
19
20
  const DAY = 86400000;
@@ -6,6 +6,9 @@
6
6
  * and scope, id, today echoed. 400 on a bad scope or a missing id; 404 for an id nothing holds.
7
7
  * POST /api/report-dashboards/layout { key, reports } → { ok, key, reports, layouts } reports: null resets the key to its default.
8
8
  * 400 on an unknown key or report id; 403 without a page press.
9
+ * POST /api/report-dashboards/schedule { key, plan, audience } → { ok, key, schedule, schedules } plan: none|weekly|monthly; none removes it.
10
+ * 400 on a bad key, plan or audience; 403 without a page press. (lib/report-schedules.js)
11
+ * runDue(ctx, now) the server tick: each due schedule appends ONE draft to the outbox. Nothing is sent.
9
12
  *
10
13
  * Initiatives come from the workspace's facts (lib/facts-async.js), like routes-calendar-views.js. Key results, bets and hypotheses
11
14
  * come from the same records as routes-c3-strategy.js. The handlers are pure over their inputs so a test needs no server.
@@ -14,24 +17,32 @@ const path = require('path');
14
17
  const guard = require('./pm-guard.js');
15
18
  const strategy = require('./routes-c3-strategy.js');
16
19
  const rd = require('./report-dashboards.js');
20
+ const rs = require('./report-schedules.js');
21
+ const RF = require('./report-frame.js');
22
+ const { repoScript } = require('./repo-script.js');
17
23
 
18
24
  const fail = (status, error, extra) => ({ status, body: { ok: false, error, ...(extra || {}) } });
19
25
  const pad = (n) => String(n).padStart(2, '0');
20
26
  const todayStr = () => { const d = new Date(); return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()); };
21
27
  const cap = (s) => (s ? s.charAt(0).toUpperCase() + s.slice(1) : s);
22
28
 
23
- /** boards and initiatives from the facts. A board is boardOf(ini, slug); its title is its id, capitalised. */
24
- function catalogue(facts, slug) {
29
+ /** boards and initiatives from the facts. With `index` (the Initiatives board model, routes-boards.js boardIndex) an
30
+ * initiative sits on the board the Initiatives page shows it on, and a board keeps its name there. Without it a board is
31
+ * boardOf(ini, slug) and its title is its id, capitalised. */
32
+ function catalogue(facts, slug, index) {
25
33
  const inis = ((facts && facts.inis) || []).filter((i) => i && i.id);
26
- const initiatives = inis.map((i) => ({ id: String(i.id), title: String(i.title || i.id), board: rd.boardOf(i, slug) }));
34
+ const boardOf = (i) => (index ? index.of(String(i.id)) : rd.boardOf(i, slug));
35
+ const initiatives = inis.map((i) => ({ id: String(i.id), title: String(i.title || i.id), board: boardOf(i) }));
36
+ const names = new Map(index ? index.boards.map((b) => [b.id, b.title]) : []);
27
37
  const seen = new Map();
28
- initiatives.forEach((i) => { if (i.board && !seen.has(i.board)) seen.set(i.board, { id: i.board, title: cap(i.board) }); });
38
+ initiatives.forEach((i) => { if (i.board && !seen.has(i.board)) seen.set(i.board, { id: i.board, title: names.get(i.board) || cap(i.board) }); });
29
39
  return { boards: [...seen.values()], initiatives };
30
40
  }
31
41
 
32
- function list({ facts, runtimeDir, slug }) {
33
- const c = catalogue(facts, slug);
34
- return { status: 200, body: { ok: true, library: rd.LIBRARY, layouts: rd.readLayouts(runtimeDir), defaults: rd.DEFAULTS, boards: c.boards, initiatives: c.initiatives } };
42
+ function list({ facts, runtimeDir, slug, index, now }) {
43
+ const c = catalogue(facts, slug, index);
44
+ return { status: 200, body: { ok: true, library: rd.LIBRARY, layouts: rd.readLayouts(runtimeDir), defaults: rd.DEFAULTS, boards: c.boards, initiatives: c.initiatives,
45
+ schedules: rs.viewOf(rs.readSchedules(runtimeDir), now || new Date()), plans: rs.PLANS } };
35
46
  }
36
47
 
37
48
  /** query { scope, id }; deps { facts, build() → the c3-strategy body, today, slug, hypotheses }. */
@@ -41,14 +52,14 @@ function data(query, deps) {
41
52
  if (scope !== 'portfolio') {
42
53
  if (typeof id !== 'string' || !id) return fail(400, 'id is required for the ' + scope + ' scope');
43
54
  if (!rd.validId(id)) return fail(400, 'id is not a valid id');
44
- const c = catalogue(deps.facts, deps.slug);
55
+ const c = catalogue(deps.facts, deps.slug, deps.index);
45
56
  const found = scope === 'board' ? c.boards.some((b) => b.id === id) : c.initiatives.some((i) => i.id === id);
46
57
  if (!found) return fail(404, 'no ' + scope + ' has the id ' + id);
47
58
  }
48
59
  const today = deps.today || todayStr();
49
60
  try {
50
61
  const body = deps.build(today);
51
- return { status: 200, body: rd.scopeFilter(body, deps.facts, scope, id || null, today, { slug: deps.slug, hypotheses: deps.hypotheses }) };
62
+ return { status: 200, body: rd.scopeFilter(body, deps.facts, scope, id || null, today, { slug: deps.slug, hypotheses: deps.hypotheses, boardOf: deps.index ? (i) => deps.index.of(String(i.id)) : undefined }) };
52
63
  } catch (e) { return fail(500, String((e && e.message) || e)); }
53
64
  }
54
65
 
@@ -62,7 +73,72 @@ function layout(req, body, runtimeDir) {
62
73
  return { status: 200, body: { ok: true, key: body.key, reports: r.layouts[body.key] || null, layouts: r.layouts } };
63
74
  }
64
75
 
65
- const rootOf = (ctx) => (ctx && ctx.recordsDir) || path.join((ctx && ctx.repoRoot) || path.join(__dirname, '..', '..'), 'planning', 'records');
76
+ function schedule(req, body, runtimeDir, now = new Date()) {
77
+ const g = guard.requirePagePress(req, body);
78
+ if (!g.ok) return fail(g.status, g.error);
79
+ const r = rs.saveSchedule(runtimeDir, { key: body.key, plan: body.plan, audience: body.audience, ws: body.ws == null || body.ws === '' ? null : body.ws }, now);
80
+ if (!r.ok) return fail(/could not be saved|no runtime/.test(r.error) ? 500 : 400, r.error);
81
+ const view = rs.viewOf(r.schedules, now);
82
+ return { status: 200, body: { ok: true, key: body.key, schedule: view[body.key] || null, schedules: view } };
83
+ }
84
+
85
+ /* The dashboard's title as the page names it (c3-reports.js dashTitle): Portfolio, Board: <title>, Initiative: <title>. */
86
+ function titleOf(key, c) {
87
+ if (key === 'portfolio') return 'Portfolio';
88
+ const i = key.indexOf(':'); const scope = key.slice(0, i); const id = key.slice(i + 1);
89
+ const x = (scope === 'board' ? c.boards : c.initiatives).find((b) => b.id === id);
90
+ return (scope === 'board' ? 'Board: ' : 'Initiative: ') + (x ? x.title || x.id : id);
91
+ }
92
+
93
+ /**
94
+ * Run one schedule: the dashboard's data for its scope, its current layout (or the scope's default), the frame and text the Share sheet
95
+ * makes, then ONE outbox draft. deps { facts, slug, build(today), hypotheses, runtimeDir, today }. Returns { ok, entry } | { ok:false, error }.
96
+ */
97
+ function runOne(sched, deps, now = new Date()) {
98
+ const key = sched.key;
99
+ const scope = key === 'portfolio' ? 'portfolio' : key.slice(0, key.indexOf(':'));
100
+ const id = scope === 'portfolio' ? null : key.slice(key.indexOf(':') + 1);
101
+ const r = data({ scope, id }, deps);
102
+ if (r.status !== 200) return { ok: false, error: r.body.error || 'the dashboard could not be read' };
103
+ const layouts = rd.readLayouts(deps.runtimeDir);
104
+ const ids = Object.prototype.hasOwnProperty.call(layouts, key) ? layouts[key] : rd.DEFAULTS[scope];
105
+ const reports = ids.map((x) => rd.LIBRARY.find((l) => l.id === x)).filter(Boolean);
106
+ const c = catalogue(deps.facts, deps.slug, deps.index);
107
+ const F = RF.frameOf({ title: titleOf(key, c), reports, raw: r.body, audience: sched.audience, today: r.body.today });
108
+ try { return { ok: true, entry: rs.makeDraft(deps.runtimeDir, sched, F, RF.draftBody(F), now) }; }
109
+ catch (e) { return { ok: false, error: 'the draft could not be saved: ' + String((e && e.message) || e) }; }
110
+ }
111
+
112
+ /** The tick. lastRun moves first, so a slow read never runs the same slot twice; the outcome is written after. */
113
+ let running = false;
114
+ async function runDue(ctx, now = new Date()) {
115
+ if (running || !ctx || !ctx.runtimeDir) return [];
116
+ running = true;
117
+ const done = [];
118
+ try {
119
+ for (const s of rs.dueOf(rs.readSchedules(ctx.runtimeDir), now)) {
120
+ rs.markRun(ctx.runtimeDir, s.key, { lastRun: now.toISOString() });
121
+ let out;
122
+ try {
123
+ const w = ctx.getWs(s.ws || null);
124
+ if (!w) throw new Error('no workspace is registered');
125
+ const slug = w.id || (w.root ? path.basename(path.resolve(w.root)) : null);
126
+ const facts = await (ctx.readFacts || readFacts)(w);
127
+ const root = rootOf(ctx, s.ws || null);
128
+ let hypotheses = [];
129
+ try { hypotheses = require(repoScript('records/index.js')).list(root, 'hypotheses'); } catch { hypotheses = []; }
130
+ out = runOne(s, { facts, slug, hypotheses, runtimeDir: ctx.runtimeDir, today: ctx.today,
131
+ build: ctx.build || ((today) => strategy.build(root, path.join(ctx.runtimeDir, 'signals', 'signals.jsonl'), today)) }, now);
132
+ } catch (e) { out = { ok: false, error: String((e && e.message) || e) }; }
133
+ rs.markRun(ctx.runtimeDir, s.key, out.ok ? { last_status: 'ok', last_error: null, last_draft: out.entry.id } : { last_status: 'failed', last_error: out.error });
134
+ done.push({ key: s.key, ...out });
135
+ }
136
+ } finally { running = false; }
137
+ return done;
138
+ }
139
+
140
+ const { recordsDirFor } = require('./records-dir.js');
141
+ const rootOf = (ctx, ws) => recordsDirFor(ctx, ws); // the workspace's records (lib/records-dir.js)
66
142
 
67
143
  async function readFacts(w) {
68
144
  return require('./facts-async.js').readFactsAsync(path.resolve(w.root));
@@ -70,7 +146,7 @@ async function readFacts(w) {
70
146
 
71
147
  module.exports = {
72
148
  prefixes: ['/api/report-dashboards'],
73
- catalogue, list, data, layout,
149
+ catalogue, list, data, layout, schedule, runOne, runDue,
74
150
  handle: async (req, res, url, ctx) => {
75
151
  const p = url.pathname;
76
152
  const send = (r) => { ctx.json(res, r.body, r.status); return true; };
@@ -88,12 +164,14 @@ module.exports = {
88
164
  let facts;
89
165
  try { facts = await readFacts(w); }
90
166
  catch (e) { return send(fail(500, 'the dashboards could not read the repository: ' + (e && e.message ? e.message : String(e)))); }
91
- if (p === '/api/report-dashboards') return send(list({ facts, runtimeDir: ctx.runtimeDir, slug }));
92
- const root = rootOf(ctx);
167
+ let index = null; // the Initiatives board model; without it (no module, or a read error) boardOf decides
168
+ try { index = require('./routes-boards.js').boardIndex(ctx, w); } catch { index = null; }
169
+ if (p === '/api/report-dashboards') return send(list({ facts, runtimeDir: ctx.runtimeDir, slug, index }));
170
+ const root = rootOf(ctx, url.searchParams.get('ws'));
93
171
  let hypotheses = [];
94
- try { hypotheses = require('../../scripts/records/index.js').list(root, 'hypotheses'); } catch { hypotheses = []; }
172
+ try { hypotheses = require(repoScript('records/index.js')).list(root, 'hypotheses'); } catch { hypotheses = []; }
95
173
  return send(data({ scope: url.searchParams.get('scope'), id: url.searchParams.get('id') }, {
96
- facts, slug, hypotheses,
174
+ facts, slug, hypotheses, index,
97
175
  build: (today) => strategy.build(root, path.join(ctx.runtimeDir || '.', 'signals', 'signals.jsonl'), today),
98
176
  }));
99
177
  }
@@ -103,6 +181,12 @@ module.exports = {
103
181
  if (!got.ok) return send(fail(400, 'invalid JSON body'));
104
182
  return send(layout(req, got.body || {}, ctx.runtimeDir));
105
183
  }
184
+ if (p === '/api/report-dashboards/schedule') {
185
+ if (req.method !== 'POST') return send(fail(405, 'POST only'));
186
+ const got = await ctx.readJson(req);
187
+ if (!got.ok) return send(fail(400, 'invalid JSON body'));
188
+ return send(schedule(req, got.body || {}, ctx.runtimeDir));
189
+ }
106
190
  return false;
107
191
  },
108
192
  };
@@ -17,9 +17,10 @@
17
17
  * reader is used, so the route never starts a cold repository read. A number that no record holds is null, never invented. */
18
18
  const path = require('path');
19
19
  const fs = require('fs');
20
- const records = require(path.join(__dirname, '..', '..', 'scripts', 'records', 'index.js'));
21
- const sigRules = require(path.join(__dirname, '..', '..', 'scripts', 'signal-rules.js'));
22
- const ledger = require(path.join(__dirname, '..', '..', 'scripts', 'trust', 'ledger.js'));
20
+ const { repoScript } = require('./repo-script.js');
21
+ const records = require(repoScript('records/index.js'));
22
+ const sigRules = require(repoScript('signal-rules.js'));
23
+ const ledger = require(repoScript('trust/ledger.js'));
23
24
 
24
25
  const EARLY = 0.10; // the repository's early-warning share (scripts/calendar-model.js EARLY_WARNING)
25
26
  const OFF = 0.50; // behind the line by half the span or more reads as off course
@@ -29,7 +30,8 @@ const isDate = (s) => typeof s === 'string' && /^\d{4}-\d{2}-\d{2}/.test(s) && !
29
30
  const ms = (s) => Date.parse(String(s).slice(0, 10) + 'T00:00:00Z');
30
31
  const fin = (v) => typeof v === 'number' && Number.isFinite(v);
31
32
  const r2 = (v) => (fin(v) ? Math.round(v * 100) / 100 : null);
32
- const rootOf = (ctx) => (ctx && ctx.recordsDir) || path.join((ctx && ctx.repoRoot) || path.join(__dirname, '..', '..'), 'planning', 'records');
33
+ const { recordsDirFor } = require('./records-dir.js');
34
+ const rootOf = (ctx, ws) => recordsDirFor(ctx, ws); // the workspace's records (lib/records-dir.js)
33
35
  const monthKey = (t) => { const d = new Date(t); return d.getUTCFullYear() + '-' + pad(d.getUTCMonth() + 1); };
34
36
  const monthIdx = (k) => Number(k.slice(0, 4)) * 12 + Number(k.slice(5, 7)) - 1;
35
37
  const keyOfIdx = (i) => Math.floor(i / 12) + '-' + pad((i % 12) + 1);
@@ -161,7 +163,7 @@ module.exports = {
161
163
  if (url.pathname !== '/api/c3-strategy') return false;
162
164
  if (req.method !== 'GET') { ctx.json(res, { ok: false, error: 'GET only' }, 405); return true; }
163
165
  try {
164
- const body = build(rootOf(ctx), path.join(ctx.runtimeDir || '.', 'signals', 'signals.jsonl'), todayStr());
166
+ const body = build(rootOf(ctx, url.searchParams.get('ws')), path.join(ctx.runtimeDir || '.', 'signals', 'signals.jsonl'), todayStr());
165
167
  ctx.json(res, body, 200);
166
168
  } catch (e) { ctx.json(res, { ok: false, error: String(e && e.message || e) }, 500); }
167
169
  return true;
@@ -23,9 +23,10 @@
23
23
  */
24
24
  const path = require('path');
25
25
  const planItems = require('./plan-items.js');
26
- const F = require('../../scripts/plan-forecast.js');
26
+ const { repoScript } = require('./repo-script.js');
27
+ const F = require(repoScript('plan-forecast.js'));
27
28
  const roadmapOps = require('./routes-roadmap-ops.js');
28
- const breakdown = require(path.join(__dirname, '..', '..', 'scripts', 'breakdown-ops.js'));
29
+ const breakdown = require(repoScript('breakdown-ops.js'));
29
30
 
30
31
  const localYmd = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
31
32
  const DAY = 86400000;
@@ -11,8 +11,9 @@
11
11
  * File: <runtime>/calendar-import/events.jsonl (append only, scripts/trust/ledger.js). */
12
12
  const path = require('path');
13
13
  const guard = require('./pm-guard.js');
14
- const CI = require('../../scripts/calendar-import.js');
15
- const L = require('../../scripts/trust/ledger.js');
14
+ const { repoScript } = require('./repo-script.js');
15
+ const CI = require(repoScript('calendar-import.js'));
16
+ const L = require(repoScript('trust/ledger.js'));
16
17
 
17
18
  const fileOf = (runtimeDir) => path.join(runtimeDir, 'calendar-import', 'events.jsonl');
18
19
  const fail = (status, error) => ({ status, body: { ok: false, error } });
@@ -2,7 +2,8 @@
2
2
  /* routes-calendar-views.js — GET /api/calendar-views?view=day|week|month|quarter|year&date=YYYY-MM-DD&ws=<id> (console3 wave C2).
3
3
  *
4
4
  * → { ok, view, range, cells, unit, gantt, strategy, events_source, today,
5
- * layers, layers_total, layers_note, free, context, context_total } (the last six: lib/calendar-layers.js)
5
+ * layers, layers_total, layers_note, free, context, context_total, (lib/calendar-layers.js)
6
+ * tidy, tidy_total, tidy_note } (lib/calendar-tidy.js: suggestions in words, no write)
6
7
  *
7
8
  * READ ONLY. Initiatives come from the signal engine's repo reader (scripts/signals/facts.js readRepoFacts), required inside
8
9
  * the handler so a failure answers 500 { ok:false } and never reaches the server. The date and the plan come from the records:
@@ -13,8 +14,10 @@
13
14
  * Until one is, events = [] and events_source is 'none'. KRS: no KR metric series is stored in the repo, so the strategy lens
14
15
  * gets none and says so. */
15
16
  const path = require('path');
16
- const model = require('../../scripts/calendar-model.js');
17
+ const { repoScript } = require('./repo-script.js');
18
+ const model = require(repoScript('calendar-model.js'));
17
19
  const layersLib = require('./calendar-layers.js');
20
+ const tidyLib = require('./calendar-tidy.js');
18
21
 
19
22
  const today = () => { const d = new Date(); const p = (n) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`; };
20
23
 
@@ -44,7 +47,9 @@ function handle(query, deps = {}) {
44
47
  const src = typeof deps.readContextSources === 'function' ? (deps.readContextSources() || {}) : {};
45
48
  const nowMs = Number.isFinite(deps.nowMs) ? deps.nowMs : (deps.today ? undefined : Date.now());
46
49
  const extra = layersLib.build({ range, today: nowDay, nowMs, events, dated, src, own: items.own, planItemsDue: items.planItems });
47
- return { status: 200, body: { ok: true, view, range, unit: b.unit, cells: b.cells, gantt, strategy, events_source: source, today: nowDay, ...extra } };
50
+ const tidy = tidyLib.tidyFrom({ range, today: nowDay, nowMs, events, eventsSource: source, inis, gantt,
51
+ planItems: dated.planItems || items.planItems || [] });
52
+ return { status: 200, body: { ok: true, view, range, unit: b.unit, cells: b.cells, gantt, strategy, events_source: source, today: nowDay, ...extra, ...tidy } };
48
53
  } catch (e) {
49
54
  return { status: 500, body: { ok: false, error: String(e && e.message || e) } };
50
55
  }
@@ -70,7 +75,7 @@ function readers(root, runtimeDir) {
70
75
  const out = { unavailable };
71
76
  try { out.planItems = planRows().map(planItem); } catch (e) { unavailable.plan_items = 'the plan ledger could not be read: ' + reason(e); }
72
77
  try {
73
- const R = require('../../scripts/releases.js');
78
+ const R = require(repoScript('releases.js'));
74
79
  const f = path.join(runtimeDir, 'releases', 'releases.jsonl');
75
80
  let lines = []; try { lines = require('fs').readFileSync(f, 'utf8').split('\n').filter(Boolean); } catch { lines = []; }
76
81
  out.releases = R.foldReleases(lines);
@@ -12,7 +12,8 @@
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
14
  const { spawnSync } = require('child_process');
15
- const H = require('../../scripts/doc-health.js');
15
+ const { repoScript } = require('./repo-script.js');
16
+ const H = require(repoScript('doc-health.js'));
16
17
 
17
18
  const ROOT = path.resolve(__dirname, '..', '..');
18
19
  const bad = (error, status = 400) => ({ status, body: { ok: false, error } });
@@ -19,7 +19,8 @@ const fs = require('fs');
19
19
  const path = require('path');
20
20
  const guard = require('./pm-guard.js');
21
21
  const applyChange = require('./apply-change.js');
22
- const S = require('../../scripts/doc-sheet.js');
22
+ const { repoScript } = require('./repo-script.js');
23
+ const S = require(repoScript('doc-sheet.js'));
23
24
 
24
25
  const ROOT = path.resolve(__dirname, '..', '..');
25
26
  const fail = (status, error, extra) => ({ status, body: { ok: false, error, ...(extra || {}) } });
@@ -15,7 +15,8 @@
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
17
  const planItems = require('./plan-items.js');
18
- const F = require('../../scripts/plan-forecast.js');
18
+ const { repoScript } = require('./repo-script.js');
19
+ const F = require(repoScript('plan-forecast.js'));
19
20
 
20
21
  const DEFAULT_PPW = 5;
21
22
  const localYmd = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
@@ -14,8 +14,9 @@ const fs = require('fs');
14
14
  const path = require('path');
15
15
  const actor = require('./actor.js');
16
16
  const runAnswers = require('./run-answers.js');
17
- const C = require('../../scripts/reply-contract/reply-contract.js');
18
- const R = require('../../scripts/replies.js');
17
+ const { repoScript } = require('./repo-script.js');
18
+ const C = require(repoScript('reply-contract/reply-contract.js'));
19
+ const R = require(repoScript('replies.js'));
19
20
 
20
21
  const EVENTS = ['SessionStart', 'PreToolUse', 'PermissionRequest', 'Stop', 'Notification', 'SessionEnd'];
21
22
  const ALIASES = { 'session-start': 'SessionStart', 'pre-tool': 'PreToolUse', 'permission-request': 'PermissionRequest', stop: 'Stop', notification: 'Notification', 'session-end': 'SessionEnd' };
@@ -57,7 +57,8 @@ const tidy = require('./tidy-documents.js');
57
57
  const jevDocs = require('./jev-documents.js');
58
58
  const tsSettings = require('./typesafe-settings');
59
59
  const docCatCorrections = require('./doc-category-corrections');
60
- const { nineStages } = require('../../scripts/nine-stages.js'); // pure label layer; never workflow-state.js (it runs its CLI on require)
60
+ const { repoScript } = require('./repo-script.js');
61
+ const { nineStages } = require(repoScript('nine-stages.js')); // pure label layer; never workflow-state.js (it runs its CLI on require)
61
62
 
62
63
  const HORIZON_RANK = { now: 0, next: 1, later: 2 };
63
64
  const FILE_KEYS = [['brief_path', 'brief', 'Brief'], ['prd_path', 'prd', 'PRD'], ['rubric_path', 'rubric', 'Rubric'], ['doc_path', 'doc', 'Document']];
@@ -4,7 +4,8 @@
4
4
  * Drafts only. Creating the initiative stays with createInitiative, which refuses an unanchored create. */
5
5
  const path = require('path');
6
6
  const guard = require('./pm-guard.js');
7
- const intake = require('../../scripts/intake.js');
7
+ const { repoScript } = require('./repo-script.js');
8
+ const intake = require(repoScript('intake.js'));
8
9
 
9
10
  const ROOT = path.resolve(__dirname, '..', '..');
10
11