@golden-frijoles/kit 0.0.0 → 0.2.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 (51) hide show
  1. package/README.md +25 -1
  2. package/bin.mjs +108 -0
  3. package/dist/LICENSE +202 -0
  4. package/dist/NOTICE +7 -0
  5. package/dist/babysit-pr.mjs +213 -0
  6. package/dist/build-order-sync.mjs +141 -0
  7. package/dist/build-order.mjs +178 -0
  8. package/dist/cross-panel.mjs +216 -0
  9. package/dist/cross-panel.prompt.md +115 -0
  10. package/dist/doc-format.enforced.json +4 -0
  11. package/dist/doc-format.mjs +856 -0
  12. package/dist/doc-hygiene.mjs +279 -0
  13. package/dist/lib/cross-agent-cli.mjs +695 -0
  14. package/dist/lib/gh-rest.mjs +229 -0
  15. package/dist/lib/golden-onboarding.mjs +179 -0
  16. package/dist/lib/jev.mjs +334 -0
  17. package/dist/lib/log-branch.mjs +106 -0
  18. package/dist/lib/pmo-benchmarks.mjs +61 -0
  19. package/dist/lib/pmo-delivery.mjs +102 -0
  20. package/dist/lib/pmo-metrics.mjs +200 -0
  21. package/dist/lib/pmo-templates.mjs +132 -0
  22. package/dist/lib/pmo-window-log.mjs +99 -0
  23. package/dist/lib/project-root.mjs +78 -0
  24. package/dist/lib/prose-brief.mjs +191 -0
  25. package/dist/lib/prose-guard.mjs +761 -0
  26. package/dist/lib/prose-writer.mjs +410 -0
  27. package/dist/lib/report-registry.mjs +469 -0
  28. package/dist/lib/reporting-config.mjs +266 -0
  29. package/dist/lib/roadmap-contract.mjs +305 -0
  30. package/dist/lib/roadmap-status-buckets.mjs +20 -0
  31. package/dist/lib/standup-deck.mjs +120 -0
  32. package/dist/lib/telegram-format.mjs +91 -0
  33. package/dist/live-smoke.mjs +303 -0
  34. package/dist/pmo/benchmarks.json +77 -0
  35. package/dist/pmo/templates/metrics-sheet.md +27 -0
  36. package/dist/pmo/templates/monthly-stakeholder-packet.md +53 -0
  37. package/dist/pmo/templates/weekly-story-deck.md +66 -0
  38. package/dist/pmo-report.mjs +338 -0
  39. package/dist/preflight.mjs +444 -0
  40. package/dist/prose/cpo-persona.md +108 -0
  41. package/dist/prose/internal.task.md +48 -0
  42. package/dist/prose/standup.task.md +38 -0
  43. package/dist/prose/weekly.task.md +36 -0
  44. package/dist/prose-draft.mjs +198 -0
  45. package/dist/prose-lessons.md +59 -0
  46. package/dist/roadmap-extract.mjs +410 -0
  47. package/dist/standup/templates/daily-story-deck.md +46 -0
  48. package/dist/standup.mjs +634 -0
  49. package/dist/vercel-prune-previews.mjs +164 -0
  50. package/dist/weekly-recap.mjs +526 -0
  51. package/package.json +33 -1
@@ -0,0 +1,191 @@
1
+ // prose-brief.mjs — the deterministic EVIDENCE PACK handed to a Claude Routine so it can write the
2
+ // standup / weekly recap in its own context (README D2).
3
+ //
4
+ // ── Why a brief instead of a writer call ──────────────────────────────────────────────────────
5
+ // The local surfaces (retro, poster, sprint-wrap) call devin/agy directly through prose-writer.mjs.
6
+ // The two SCHEDULED surfaces cannot: a Claude Routine runs on Anthropic-managed infra with no local
7
+ // CLI credentials, so `hasCmd('devin')` and `hasCmd('agy')` are both false there and `writeProse`
8
+ // would return "no prose writer available" — silently producing NOTHING on exactly the two surfaces
9
+ // the epic exists to improve. Same headless-auth wall that made cross-agent-review-always local-only.
10
+ //
11
+ // So the routine IS the writer, and the split is:
12
+ // phase 1 `--brief` → this module builds persona + lessons + task + evidence
13
+ // phase 2 the routine writes the prose in-context
14
+ // phase 3 `--post --prose-file` → the SAME pure guard runs; findings come back as a revision note
15
+ //
16
+ // Determinism stays in code; only sentence-making is model work. The guard is shared with the local
17
+ // rail, so a lesson learned on either backend protects both.
18
+ //
19
+ // ── Roadmap deltas lead, commits corroborate (README D4) ──────────────────────────────────────
20
+ // This is the whole reason our reports can be written at product altitude while golden-beans' are
21
+ // git-log-derived. Epic READMEs and sprint docs carry plain-language user stories and acceptance
22
+ // criteria — product language, written BEFORE the code. A report that leads with those can say what
23
+ // is now true for a person; one that leads with commit subjects can only ever be an engineering
24
+ // report with the adjectives swapped.
25
+
26
+ import { readFileSync, existsSync } from 'node:fs';
27
+ import { join } from 'node:path';
28
+ import { loadPromptBody } from './cross-agent-cli.mjs';
29
+ import { loadLessons, buildWriterPrompt } from './prose-writer.mjs';
30
+ import { projectAsset } from './project-root.mjs';
31
+
32
+ /**
33
+ * Read a sprint doc's `**Status:**` line. Pure-ish (single read), used to describe sprint movement in
34
+ * product terms rather than as a file diff.
35
+ */
36
+ export function statusLineFrom(markdown) {
37
+ const m = /^\*\*Status:\*\*\s*(.+)$/m.exec(String(markdown ?? ''));
38
+ return m ? m[1].trim() : null;
39
+ }
40
+
41
+ /**
42
+ * Turn the raw gathered facts into the evidence pack, in D4's order.
43
+ *
44
+ * PURE — takes already-gathered facts, returns a string. Kept pure so the pack's shape is unit-tested
45
+ * without git, gh, or a network.
46
+ */
47
+ export function buildEvidencePack({
48
+ windowLabel,
49
+ roadmapDeltas = [],
50
+ owed = null,
51
+ repoSignals = [],
52
+ areas = [],
53
+ liveFlags = [],
54
+ smoke = null,
55
+ }) {
56
+ const lines = [`## Evidence — ${windowLabel}`, ''];
57
+
58
+ // 1. Roadmap deltas FIRST (D4). This is the product-language input; everything below corroborates it.
59
+ lines.push('### What moved on the roadmap (product language — lead with this)');
60
+ if (roadmapDeltas.length) {
61
+ for (const d of roadmapDeltas) lines.push(`- ${d}`);
62
+ } else {
63
+ lines.push('- (no epic or sprint status changed in this window)');
64
+ }
65
+ lines.push('');
66
+
67
+ // 2. What is owed. Degrades cleanly when the generated ledger does not exist yet — the two epics
68
+ // must not hard-depend on each other's merge order.
69
+ lines.push('### What is owed (verification a person still has to do)');
70
+ if (owed && owed.total != null) {
71
+ lines.push(`- ${owed.total} check(s) owed${owed.byCategory ? ` — ${owed.byCategory}` : ''}.`);
72
+ } else {
73
+ lines.push('- (the owed ledger is not available in this run — do not guess a number)');
74
+ }
75
+ lines.push('');
76
+
77
+ // 3. Repo signals — corroboration, and the source of "what is stuck".
78
+ lines.push('### Repository signals (corroboration — do NOT enumerate these in your report)');
79
+ if (repoSignals.length) {
80
+ for (const s of repoSignals) lines.push(`- ${s}`);
81
+ } else {
82
+ lines.push('- (nothing changed)');
83
+ }
84
+ if (smoke) lines.push(`- Overnight smoke: ${smoke}`);
85
+ lines.push('');
86
+
87
+ // 4. Shape only — never file names.
88
+ if (areas.length) {
89
+ lines.push('### Where the work landed (shape only — never name files in your report)');
90
+ for (const a of areas) lines.push(`- ${a}`);
91
+ lines.push('');
92
+ }
93
+
94
+ // 5. Flag state. The `flag-state-claim` guard rule needs real input, and this is it.
95
+ // `liveFlags` may be an array (known) or {available:false} (unknown). The difference matters: "no
96
+ // flags are on" and "I could not check" are different facts, and telling the writer the former when
97
+ // the truth is the latter is exactly the kind of confident falsehood this rail exists to stop.
98
+ lines.push('### Flags currently ON (a capability is NOT live unless its flag is listed here)');
99
+ const flagList = Array.isArray(liveFlags) ? liveFlags : liveFlags?.flags || [];
100
+ const flagsKnown = Array.isArray(liveFlags) ? true : Boolean(liveFlags?.available);
101
+ if (!flagsKnown) {
102
+ lines.push('- (flag state UNKNOWN in this run — it could not be read. Do not describe anything as');
103
+ lines.push(' live, and do not state that anything is off either: you do not know.)');
104
+ } else if (flagList.length) {
105
+ for (const f of flagList) lines.push(`- ${f}`);
106
+ } else {
107
+ lines.push('- (none are on — do not describe any capability as live or available)');
108
+ }
109
+
110
+ return lines.join('\n');
111
+ }
112
+
113
+ /**
114
+ * The quiet-window instruction (README D7).
115
+ *
116
+ * Deliberately NOT an evidence pack. golden-beans' rail refuses to call a writer on an empty
117
+ * changelog because a model handed nothing produces plausible prose about nothing; our standup has
118
+ * the sibling of that bug on record in LEARNINGS (a missing baseline once enumerated 100+ PRs and
119
+ * crashed on Telegram's 4096-char limit). A routine reading this must post the existing one-line
120
+ * quiet message and write no prose at all.
121
+ */
122
+ export function buildQuietBrief(windowLabel) {
123
+ return [
124
+ `## Nothing to report — ${windowLabel}`,
125
+ '',
126
+ 'No roadmap movement and no repository activity in this window.',
127
+ '',
128
+ '**Do NOT write prose.** A quiet period is a fact, not a failure, and a report invented to fill',
129
+ 'the space is the failure this whole rail exists to prevent. Post the standard one-line quiet',
130
+ 'message and stop.',
131
+ ].join('\n');
132
+ }
133
+
134
+ /** Load the shared persona + a per-surface task file. */
135
+ export function loadPersonaAndTask(scriptsDir, surface) {
136
+ return [
137
+ // The persona is a TEMPLATE FILL-IN the project owns (D2); the task file is the kit's.
138
+ loadPromptBody(projectAsset('prose/cpo-persona.md', { root: scriptsDir })),
139
+ loadPromptBody(join(scriptsDir, 'prose', `${surface}.task.md`)),
140
+ ].join('\n\n');
141
+ }
142
+
143
+ /**
144
+ * Compose the full brief. Routes through `buildWriterPrompt` — the SAME assembler the local rail uses
145
+ * — so the lessons file cannot reach one backend and silently miss the other.
146
+ */
147
+ export function buildBrief({ scriptsDir, surface, pack, lessons }) {
148
+ return buildWriterPrompt({
149
+ style: loadPersonaAndTask(scriptsDir, surface),
150
+ lessons: lessons ?? loadLessons(),
151
+ task: pack,
152
+ });
153
+ }
154
+
155
+ /**
156
+ * Derive the guard's evidence flags FROM THE WINDOW — never assumed.
157
+ *
158
+ * `allowsFixClaim` only when something in the window actually claims a fix; `allowsBeneficiary` only
159
+ * when a customer-observable surface was touched. Both default closed, which is what makes the
160
+ * corresponding guard rules meaningful rather than decorative.
161
+ */
162
+ export function deriveEvidenceFlags({ subjects = [], areas = [], liveFlags = [], maxWords }) {
163
+ return {
164
+ allowsFixClaim: subjects.some((s) => /^(?:fix|hotfix|revert)(?:\([^)]*\))?!?:/i.test(s)),
165
+ allowsBeneficiary: areas.some((a) =>
166
+ /customer-facing|storefront|checkout|seller portal|admin|public page|shop page|listing/i.test(a)
167
+ ),
168
+ liveFlags,
169
+ // Chat-message surface: structural markdown is forbidden, so the strict `unfinished` rule applies.
170
+ allowsMarkdown: false,
171
+ maxWords,
172
+ minWords: 15,
173
+ };
174
+ }
175
+
176
+ /** Read a generated owed-ledger JSON if epic B's story 3 has landed; degrade cleanly when it hasn't. */
177
+ export function loadOwedLedger(path, deps = {}) {
178
+ const { read = readFileSync, exists = existsSync } = deps;
179
+ if (!exists(path)) return null;
180
+ try {
181
+ const j = JSON.parse(read(path, 'utf8'));
182
+ const byCategory = j.byCategory
183
+ ? Object.entries(j.byCategory)
184
+ .map(([k, v]) => `${v} ${k}`)
185
+ .join(', ')
186
+ : null;
187
+ return { total: j.total ?? null, byCategory };
188
+ } catch {
189
+ return null; // a malformed ledger is the same as an absent one: report nothing, invent nothing
190
+ }
191
+ }