@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,36 @@
1
+ <!--
2
+ weekly.task.md — the per-surface task block for the WEEKLY recap.
3
+
4
+ Higher altitude than standup.task.md: themes, not events. Composed after cpo-persona.md and the
5
+ accumulated lessons, before the evidence pack.
6
+
7
+ Written by the Claude Routine in-context (README D1).
8
+
9
+ Everything above the first `---` is stripped before sending.
10
+ -->
11
+
12
+ ---
13
+
14
+ ## Task: write the WEEKLY RECAP
15
+
16
+ For someone who was not watching this week and wants the through-line, not the log.
17
+
18
+ **Length: up to 160 words.** That is a budget, not a target — and it is emphatically **not**
19
+ permission to enumerate. A list of merges is not a report.
20
+
21
+ The question you are answering: **what is now possible that was not possible on Monday, and what is
22
+ still owed?**
23
+
24
+ - **Group by theme, never by commit or by repo.** Three changes that together made one flow work are
25
+ one theme and one sentence. Repos are an implementation detail the reader does not think in.
26
+ - **Lead with shipped and closed work.** The evidence pack gives you epics whose status flipped this
27
+ week, with their retrospectives — that is the highest-altitude product input available, written in
28
+ product language by the people who did the work. Use it before you touch commit subjects.
29
+ - **Name the decision that shaped the week**, if there was one. Something chosen over an obvious
30
+ alternative, something deliberately declined, something that turned out to be false and changed
31
+ the plan. A week usually has exactly one of these and it is usually the most valuable sentence in
32
+ the report.
33
+ - **State what is still owed** — unverified work, gaps a person has to close by hand, anything
34
+ built but not yet live. The owed ledger is sacred here: a weekly that quietly omits a known gap is
35
+ the one unforgivable error in this house.
36
+ - **A quiet week is a fact.** Report it in a sentence and stop.
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+ // prose-draft.mjs — delegate FILE-DERIVED internal prose to a cheaper, different-family model.
3
+ //
4
+ // The cross-agent rail (cross-review / cross-panel) proved the primitive: pipe context into a
5
+ // foreign CLI, get one advisory pass back. This extends it from *reviewing code* to *drafting
6
+ // prose* — but only for artifacts whose inputs are FILES (epic docs, sprint docs, git log), not
7
+ // the builder's in-head context. That's the honest scope boundary: a PR body is cheapest written
8
+ // by the agent that just built the PR; a retrospective/poster entry is a synthesis of docs that
9
+ // are already on disk, which a cheap fast model drafts well and the coordinating agent edits.
10
+ //
11
+ // Modes (v1):
12
+ // node scripts/prose-draft.mjs --kind retro --epic Roadmap/<area>/<epic-dir>
13
+ // node scripts/prose-draft.mjs --kind poster --epic Roadmap/<area>/<epic-dir>
14
+ // node scripts/prose-draft.mjs --kind sprint-wrap --sprint Roadmap/<area>/<epic>/sprint-N.md
15
+ //
16
+ // Output: the draft on stdout (synchronous flush — LEARNINGS pipe-truncation rule). The caller
17
+ // reviews/edits/commits; this tool NEVER writes repo files itself (editorial control stays with
18
+ // the coordinating agent — same "advisory only" stance as cross-review).
19
+ //
20
+ // Model: agy with a cheap-fast pair by default (prose doesn't need Pro-tier reasoning; the
21
+ // coordinating agent is the editor). Override via PROSE_MODEL / PROSE_FALLBACK_MODEL.
22
+
23
+ import { spawnSync } from 'node:child_process';
24
+ import { readFileSync, readdirSync, existsSync, writeSync } from 'node:fs';
25
+ import { join, dirname, resolve } from 'node:path';
26
+ import { fileURLToPath } from 'node:url';
27
+ import { hasCmd, loadPromptBody, die, need } from './lib/cross-agent-cli.mjs';
28
+ import { writeProse, buildWriterPrompt, loadLessons } from './lib/prose-writer.mjs';
29
+ import { projectAsset, projectRoot } from './lib/project-root.mjs';
30
+
31
+ const __dirname = dirname(fileURLToPath(import.meta.url));
32
+ const REPO_ROOT = projectRoot(); // D2
33
+
34
+ // The model/router lives in scripts/lib/prose-writer.mjs now — deliberately NOT here.
35
+ //
36
+ // This file used to default to `gemini-3.5-flash-high`. That is the exact constant a consuming
37
+ // project identified as having silently destroyed the register of every report it wrote: the accepted
38
+ // drafts had come from GPT-OSS on the occasions Gemini's quota was exhausted, so the "fallback" was
39
+ // actually the good writer. The rail now pins a single non-Gemini model and routes devin first. Do not
40
+ // reintroduce a model constant here.
41
+
42
+ export const KINDS = ['retro', 'poster', 'sprint-wrap'];
43
+
44
+ // ── mode task blocks (pure) ─────────────────────────────────────────────────────────────────
45
+
46
+ export function taskBlock(kind) {
47
+ switch (kind) {
48
+ case 'retro':
49
+ return `## Task: draft RETROSPECTIVE.md for the epic whose docs follow.
50
+ Shape (exactly these sections): a one-line header with the epic name + close date placeholder
51
+ (_Closed: YYYY-MM-DD_ — leave the placeholder if no date is in the sources); **## What shipped**
52
+ (per sprint, outcome-first, with PR/commit refs from the sources); **## What worked**;
53
+ **## What didn't / incidents** (plain, unsoftened); **## Gaps / follow-ups** (the owed ledger —
54
+ every unverified item, pending migration, human smoke); **## Durable learnings** (only the
55
+ *transferable* rules, each a bold one-liner + why + date/source, written so it can be pasted
56
+ into Roadmap/LEARNINGS.md — dedupe-aware: if a learning sharpens an existing well-known rule,
57
+ say "sharpens: <rule>"). Length budget: 60-90 lines.`;
58
+ case 'poster':
59
+ return `## Task: draft the product-poster entry for this epic (Roadmap/README.md house shape).
60
+ One dated bold-led bullet: **YYYY-MM-DD — <Epic name> (<n> sprint(s); <risk>, <surface>).**
61
+ followed by 3-8 sentences: the problem, what shipped (with PR refs), the load-bearing technical
62
+ decision(s), what's owed. Then one table-row line for the area README:
63
+ | [<slug>](<slug>/) | <one-line what> | ✅ **Shipped YYYY-MM-DD** (<refs + owed note>) |
64
+ Length budget: 12 lines max.`;
65
+ case 'sprint-wrap':
66
+ return `## Task: draft the sprint-wrap terminal summary (SESSION-KICKOFFS §7 shape) for the
67
+ sprint doc that follows. It is a THIN POINTER, never a re-summary: 4-8 lines — sprint name +
68
+ status, the one-line outcome per story (ref'd), the owed list, and "next:" with the next sprint
69
+ or close-out step. No headings, no code fences.`;
70
+ default:
71
+ return die(`unknown --kind "${kind}" (expected: ${KINDS.join(' | ')})`);
72
+ }
73
+ }
74
+
75
+ // ── source gathering (I/O, thin) ────────────────────────────────────────────────────────────
76
+
77
+ function read(p) {
78
+ return readFileSync(p, 'utf8');
79
+ }
80
+
81
+ function gitLogFor(paths) {
82
+ // argv-array spawn, never a shell-built string — a path can't smuggle command substitution.
83
+ const r = spawnSync('git', ['log', '--oneline', '-20', '--', ...paths], {
84
+ cwd: REPO_ROOT,
85
+ encoding: 'utf8',
86
+ });
87
+ return r.status === 0 ? (r.stdout || '').trim() : '(git log unavailable)';
88
+ }
89
+
90
+ export function gatherEpicSources(epicDir, { readFile = read, listDir = readdirSync, log = gitLogFor } = {}) {
91
+ const files = listDir(epicDir)
92
+ .filter((f) => f.endsWith('.md'))
93
+ .sort();
94
+ if (!files.includes('README.md')) die(`${epicDir} has no README.md — is this an epic directory?`);
95
+ const parts = files.map((f) => `\n\n### FILE: ${f}\n\n${readFile(join(epicDir, f))}`);
96
+ return `## Source material — epic directory ${epicDir}${parts.join('')}\n\n### GIT LOG (epic paths)\n${log([epicDir])}`;
97
+ }
98
+
99
+ export function gatherSprintSources(
100
+ sprintPath,
101
+ { readFile = read, log = gitLogFor, exists = existsSync } = {}
102
+ ) {
103
+ const epicDir = dirname(sprintPath);
104
+ const readme = join(epicDir, 'README.md');
105
+ let out = `## Source material — sprint doc ${sprintPath}\n\n### FILE: ${sprintPath}\n\n${readFile(sprintPath)}`;
106
+ if (exists(readme)) out += `\n\n### FILE (context): ${readme}\n\n${readFile(readme)}`;
107
+ return `${out}\n\n### GIT LOG (epic paths)\n${log([epicDir])}`;
108
+ }
109
+
110
+ // ── prompt assembly (pure) ──────────────────────────────────────────────────────────────────
111
+
112
+ // The house voice now comes from the SHARED persona + the internal-artifact task file, so a change to
113
+ // the register reaches this surface and the two scheduled reports at once. What is genuinely local to
114
+ // this tool — its three mode shapes — lives in taskBlock. (The old prose-draft.prompt.md is retired;
115
+ // its internal-artifact rules moved into prose/internal.task.md.)
116
+ export function loadStylePrompt() {
117
+ return [
118
+ loadPromptBody(projectAsset('prose/cpo-persona.md')), // a TEMPLATE FILL-IN the project owns
119
+ loadPromptBody(join(__dirname, 'prose', 'internal.task.md')),
120
+ ].join('\n\n');
121
+ }
122
+
123
+ export function buildPrompt({ style, kind, sources, lessons = '' }) {
124
+ // buildWriterPrompt owns the style→lessons→task ordering for BOTH backends, so the lessons file
125
+ // cannot reach one surface and silently miss the other.
126
+ return buildWriterPrompt({ style, lessons, task: `${taskBlock(kind)}\n\n${sources}` });
127
+ }
128
+
129
+ // ── main ────────────────────────────────────────────────────────────────────────────────────
130
+
131
+ async function main() {
132
+ const args = process.argv.slice(2);
133
+ let kind, epic, sprint;
134
+ for (let i = 0; i < args.length; i++) {
135
+ if (args[i] === '--kind') kind = need(args[++i], '--kind');
136
+ else if (args[i] === '--epic') epic = need(args[++i], '--epic');
137
+ else if (args[i] === '--sprint') sprint = need(args[++i], '--sprint');
138
+ else die(`unknown arg ${args[i]}`);
139
+ }
140
+ if (!kind || !KINDS.includes(kind)) die(`--kind is required (${KINDS.join(' | ')})`);
141
+
142
+ if (!hasCmd('devin') && !hasCmd('agy')) {
143
+ die('no prose writer available — this rail needs devin or agy on PATH (see scripts/README.md).');
144
+ }
145
+
146
+ let sources;
147
+ if (kind === 'sprint-wrap') {
148
+ if (!sprint) die('--sprint <path/to/sprint-N.md> is required for --kind sprint-wrap');
149
+ sources = gatherSprintSources(resolve(REPO_ROOT, sprint));
150
+ } else {
151
+ if (!epic) die(`--epic <path/to/epic-dir> is required for --kind ${kind}`);
152
+ sources = gatherEpicSources(resolve(REPO_ROOT, epic));
153
+ }
154
+
155
+ const prompt = buildPrompt({ style: loadStylePrompt(), kind, sources, lessons: loadLessons() });
156
+
157
+ // These are INTERNAL engineering artifacts, so two guard rules that fit a chat-message report would
158
+ // misfire here: refs/dates are required (not invented), and the length budget is a document, not 60
159
+ // words. `allowsBeneficiary` stays TRUE because a retro legitimately discusses merchants and buyers.
160
+ // The size cap is gone with the argv path — devin takes the prompt in a file.
161
+ const result = await writeProse({
162
+ prompt,
163
+ evidence: {
164
+ allowsFixClaim: true,
165
+ allowsBeneficiary: true,
166
+ allowsMarkdown: true,
167
+ maxWords: 4000,
168
+ minWords: 40,
169
+ },
170
+ });
171
+ if (!result.text) die(result.error || 'no prose writer produced a draft.');
172
+
173
+ // Advisory banner: names the writer AND the model that actually ran, plus whether the guard passed
174
+ // clean — so a paste-without-reading stays self-identifying in review.
175
+ const decided = result.guard?.decider ? ` by ${result.guard.decider}` : '';
176
+ const verdict = result.ok
177
+ ? `guard: clean${decided}`
178
+ : `guard: FLAGGED${decided} (${result.guard.findings.map((f) => f.code).join(', ')})`;
179
+ writeSync(
180
+ 1,
181
+ `<!-- draft: prose-draft.mjs --kind ${kind} · writer ${result.writer}/${result.model} · ${verdict} · EDIT BEFORE COMMITTING -->\n${result.text}\n`
182
+ );
183
+
184
+ if (!result.ok) {
185
+ process.stderr.write(
186
+ `\n⚠ The guard rejected this draft and it did NOT converge after a revision pass:\n` +
187
+ result.guard.findings.map((f) => ` · ${f.code}: ${f.note}`).join('\n') +
188
+ `\n Read it carefully before committing.\n\n`
189
+ );
190
+ }
191
+ }
192
+
193
+ const isMain = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
194
+ if (isMain)
195
+ main().catch((e) => {
196
+ process.stderr.write(`prose-draft: ${e?.message || e}\n`);
197
+ process.exit(1);
198
+ });
@@ -0,0 +1,59 @@
1
+ <!--
2
+ prose-lessons.md — accumulated corrections, injected into EVERY writer prompt on BOTH backends
3
+ (scripts/lib/prose-writer.mjs's loadLessons, and the routine brief for the standup/weekly).
4
+
5
+ HOW TO USE THIS FILE
6
+ Every line below is a mistake a real draft actually made and a human had to catch. When you catch a
7
+ new one, add a line — that is the whole mechanism by which this rail gets better. Two rules:
8
+
9
+ 1. Quote the ACTUAL bad sentence. A rule stated abstractly ("be accurate") changes nothing; the
10
+ verbatim failure is what a model can pattern-match against.
11
+ 2. If the failure is mechanically detectable, ALSO add it to scripts/lib/prose-guard.mjs with a
12
+ test. A lesson reduces a mistake; a guard catches it. Prefer both.
13
+
14
+ Keep it short. A long file dilutes every line in it, and the guard is where hard enforcement lives.
15
+
16
+ Provenance: the first six lessons are inherited from golden-beans, where they were measured on a
17
+ live rail rather than imagined — a cheap model given a dense engineering commit produced material
18
+ falsehoods on two of its first three runs. They are kept verbatim because the failures they
19
+ describe are model behaviour, not repo-specific. The flag-state lesson is ours.
20
+ -->
21
+
22
+ ---
23
+
24
+ **Never invent a beneficiary.** A real draft wrote _"Tenants now benefit from a new pure-logic
25
+ unit-test suite"_ about internal test tooling. Tenants do not run our tests. If a change is
26
+ internal, say so plainly and name the real beneficiary — usually whoever builds here next — and the
27
+ real effect: a bug class that can no longer reach production unnoticed, a mistake caught in seconds
28
+ instead of after a deploy.
29
+
30
+ **Never report a fix this change did not make.** A real draft wrote _"the previous backslash bypass
31
+ is blocked, eliminating a potential open-redirect attack"_ about a commit that only ADDED TESTS for
32
+ a fix shipped weeks earlier. Commit messages and roadmap docs here routinely cite a past incident to
33
+ explain why present work matters — that is context, never the outcome. If the verb belongs to an
34
+ earlier change, it is background.
35
+
36
+ **Never claim a capability is live when its flag is off.** In the project these lessons were first
37
+ learned in, five flag-gated epics shipped in a fortnight, every one of them born OFF. "Accounts can
38
+ now be handed to a partner" is FALSE when `partner_handoff_enabled` is off — the correct sentence is that
39
+ the rail is built and stays dark until the flag is flipped. This is our highest-risk falsehood
40
+ because it reads as good news and nobody checks a flag before believing a report.
41
+
42
+ **Never invent a deadline, a due date or a sign-off.** A real standup draft ended _"design sign-off
43
+ on the Sprint 2 layout is owed before tomorrow"_ — no such commitment existed anywhere in the source
44
+ data. Commits and roadmap docs contain no deadlines, so any date you attach is fabricated by
45
+ construction. This is the most corrosive mistake on this list: it reads as perfectly ordinary
46
+ standup language and it makes people chase work nobody agreed to. If something is genuinely owed,
47
+ name what and to whom, with no date.
48
+
49
+ **Never name tools, frameworks or models.** Real drafts leaked _"Playwright"_, _"Gemini"_ and
50
+ _"Antigravity"_ despite the brief forbidding it. The reader either knows the stack or does not need
51
+ it. Describe the effect, not the machinery.
52
+
53
+ **Finish the last sentence.** A real draft ended _"…broader error handling is planned"_ mid-thought
54
+ after running out of room. A trailing fragment reads as a broken tool, not as brevity. Plan the
55
+ ending before starting it; stop at the previous full stop instead.
56
+
57
+ **Say "no user-visible effect" when that is the truth.** It is a correct and useful report. Reaching
58
+ for a vague benefit to avoid an honest "this is internal" is the failure that produces every other
59
+ one on this list.