@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,279 @@
1
+ #!/usr/bin/env node
2
+ // doc-hygiene.mjs — measure the always-read session-start doc set and flag dedupe/staleness
3
+ // candidates in LEARNINGS.md + the README poster. ADVISORY ONLY: this script never edits any
4
+ // Roadmap doc — it only reads them and (unless --check) writes ONE new dated report file under
5
+ // Roadmap/00-ideas/. Every flagged candidate is a hint for a human/model to verify, not a verdict —
6
+ // see the `doc-hygiene` skill (`golden-frijoles` plugin, golden-frijoles marketplace) for the
7
+ // judgment pass this script's output feeds.
8
+ //
9
+ // node scripts/doc-hygiene.mjs # print the report + write Roadmap/00-ideas/DOC-HYGIENE-REPORT-<date>.md
10
+ // node scripts/doc-hygiene.mjs --check # print only, write nothing (quick look / CI-safe)
11
+ //
12
+ // Reuse, don't rebuild: epic status comes from `roadmap-extract.mjs` (the same SSOT
13
+ // build-order.mjs reads) — this script does not re-parse frontmatter itself.
14
+
15
+ import { execFileSync } from 'node:child_process';
16
+ import { readFileSync, writeFileSync, existsSync, readdirSync } from 'node:fs';
17
+ import { fileURLToPath } from 'node:url';
18
+ import { dirname, join } from 'node:path';
19
+ import { projectRoot } from './lib/project-root.mjs';
20
+
21
+ const __dirname = dirname(fileURLToPath(import.meta.url));
22
+ const REPO = projectRoot(); // D2
23
+ const ROADMAP = join(REPO, 'Roadmap');
24
+ const IDEAS = join(ROADMAP, '00-ideas');
25
+
26
+ // App roots are DISCOVERED, not hardcoded. The origin project listed its two apps by name, which
27
+ // silently degraded to "no app roots" in any project laid out differently — and this script's
28
+ // dead-path check reads a missing root as "path not found", i.e. it would have flagged every
29
+ // app-relative bullet as stale. Discovery keeps absent and unknown distinguishable.
30
+ function discoverAppRoots() {
31
+ const appsDir = join(REPO, 'apps');
32
+ if (!existsSync(appsDir)) return [];
33
+ return readdirSync(appsDir, { withFileTypes: true })
34
+ .filter((e) => e.isDirectory() && !e.name.startsWith('.'))
35
+ .map((e) => join(appsDir, e.name))
36
+ .sort();
37
+ }
38
+ const APP_ROOTS = discoverAppRoots();
39
+
40
+ // The always-read set — the "Start here" orientation list. AGENTS.md is taken from the repo root,
41
+ // falling back to a single app's copy when the repo keeps it there instead.
42
+ function resolveAgentsMd() {
43
+ const rootDoc = join(REPO, 'AGENTS.md');
44
+ if (existsSync(rootDoc)) return rootDoc;
45
+ const fromApp = APP_ROOTS.map((r) => join(r, 'AGENTS.md')).find((p) => existsSync(p));
46
+ return fromApp || rootDoc; // report the root path as missing rather than inventing one
47
+ }
48
+ const ALWAYS_READ = [
49
+ { label: 'AGENTS.md', path: resolveAgentsMd() },
50
+ { label: 'WAYS-OF-WORKING.md', path: join(ROADMAP, 'WAYS-OF-WORKING.md') },
51
+ { label: 'LEARNINGS.md', path: join(ROADMAP, 'LEARNINGS.md') },
52
+ { label: 'README.md (poster)', path: join(ROADMAP, 'README.md') },
53
+ ];
54
+
55
+ // In-scope for candidate-flagging (per the doc-hygiene-learnings-sweep epic's v1 scope).
56
+ const FLAG_TARGETS = ['LEARNINGS.md', 'README.md (poster)'];
57
+
58
+ const STOPWORDS = new Set(
59
+ (
60
+ 'the a an and or but of to in on for with at by from as is are was were be been being this that ' +
61
+ 'it its own not no never every each any one two both same real still only just also once new'
62
+ ).split(' ')
63
+ );
64
+
65
+ function measure() {
66
+ return ALWAYS_READ.map(({ label, path }) => {
67
+ if (!existsSync(path)) return { label, path, lines: 0, bytes: 0, missing: true };
68
+ const text = readFileSync(path, 'utf8');
69
+ return { label, path, lines: text.split('\n').length, bytes: Buffer.byteLength(text, 'utf8') };
70
+ });
71
+ }
72
+
73
+ // Epic status via the existing extractor — never a second frontmatter parser.
74
+ function archivedEpicSlugs() {
75
+ try {
76
+ const json = execFileSync('node', [join(__dirname, 'roadmap-extract.mjs')], {
77
+ encoding: 'utf8',
78
+ maxBuffer: 64 * 1024 * 1024,
79
+ });
80
+ const rows = JSON.parse(json);
81
+ return rows
82
+ .filter((r) => r.grain === 'Epic' && r.status === 'Archived')
83
+ .map((r) => r.slug)
84
+ .filter(Boolean);
85
+ } catch {
86
+ return []; // extractor missing/broken shouldn't block the rest of the report
87
+ }
88
+ }
89
+
90
+ // Split a doc into { section, bullets: [{ text, line }] } by top-level "## " headers and "- " bullets.
91
+ function splitIntoBullets(text) {
92
+ const lines = text.split('\n');
93
+ let section = '(preamble)';
94
+ const bullets = [];
95
+ let current = null;
96
+ lines.forEach((line, i) => {
97
+ if (/^## /.test(line)) {
98
+ section = line.replace(/^## /, '').trim();
99
+ return;
100
+ }
101
+ if (/^- /.test(line)) {
102
+ if (current) bullets.push(current);
103
+ current = { section, line: i + 1, text: line };
104
+ } else if (current && /^\s+\S/.test(line)) {
105
+ current.text += ' ' + line.trim();
106
+ } else if (current && line.trim() === '') {
107
+ bullets.push(current);
108
+ current = null;
109
+ }
110
+ });
111
+ if (current) bullets.push(current);
112
+ return bullets;
113
+ }
114
+
115
+ function significantWords(text) {
116
+ const words = text
117
+ .toLowerCase()
118
+ .replace(/[`*_()[\]{}"'.,:;!?]/g, ' ')
119
+ .split(/\s+/)
120
+ .filter((w) => w.length > 3 && !STOPWORDS.has(w));
121
+ return new Set(words);
122
+ }
123
+
124
+ function jaccard(a, b) {
125
+ let inter = 0;
126
+ for (const w of a) if (b.has(w)) inter++;
127
+ const union = a.size + b.size - inter;
128
+ return union === 0 ? 0 : inter / union;
129
+ }
130
+
131
+ // Cheap near-duplicate detector: flag bullet pairs in the SAME section sharing a high fraction of
132
+ // significant words. Threshold is deliberately conservative — false negatives are cheap (a human
133
+ // reads the section anyway); false positives waste review time.
134
+ function findNearDuplicates(bullets, threshold = 0.5) {
135
+ const bySection = new Map();
136
+ for (const b of bullets) {
137
+ if (!bySection.has(b.section)) bySection.set(b.section, []);
138
+ bySection.get(b.section).push({ ...b, words: significantWords(b.text) });
139
+ }
140
+ const hits = [];
141
+ for (const [section, list] of bySection) {
142
+ for (let i = 0; i < list.length; i++) {
143
+ for (let j = i + 1; j < list.length; j++) {
144
+ const sim = jaccard(list[i].words, list[j].words);
145
+ if (sim >= threshold) {
146
+ hits.push({ section, a: list[i].line, b: list[j].line, sim: Math.round(sim * 100) });
147
+ }
148
+ }
149
+ }
150
+ }
151
+ return hits;
152
+ }
153
+
154
+ // Bullets reference app-relative paths (`lib/x.ts`) without an `apps/<app>/` prefix, since each app
155
+ // is its own repo. Check candidate paths against every known app root, plus the repo root for
156
+ // scripts/infra paths — a hit in ANY root clears the flag.
157
+ const SEARCH_ROOTS = [REPO, ...APP_ROOTS];
158
+
159
+ // Flag a bullet whose text names a backtick-quoted file path that doesn't exist in THIS checkout,
160
+ // under any known app root. This is a hint only — apps/* are separate, gitignored repos here and can
161
+ // be stale locally, and a bullet may legitimately describe a file deliberately deleted as part of the
162
+ // change it documents. Never treat this as proof of staleness; verify against the app repo's own history.
163
+ function findDeadPathRefs(bullets) {
164
+ const pathRe = /`((?:apps|lib|app|scripts|infra)\/[^\s`]+\.[a-zA-Z0-9]+)`/g;
165
+ const hits = [];
166
+ for (const b of bullets) {
167
+ let m;
168
+ pathRe.lastIndex = 0;
169
+ while ((m = pathRe.exec(b.text))) {
170
+ const rel = m[1];
171
+ const existsAnywhere = SEARCH_ROOTS.some((root) => existsSync(join(root, rel)));
172
+ if (!existsAnywhere) hits.push({ section: b.section, line: b.line, path: rel });
173
+ }
174
+ }
175
+ return hits;
176
+ }
177
+
178
+ function findArchivedEpicMentions(bullets, archivedSlugs) {
179
+ if (archivedSlugs.length === 0) return [];
180
+ const hits = [];
181
+ for (const b of bullets) {
182
+ for (const slug of archivedSlugs) {
183
+ if (b.text.includes(slug)) hits.push({ section: b.section, line: b.line, slug });
184
+ }
185
+ }
186
+ return hits;
187
+ }
188
+
189
+ function fmt(n) {
190
+ return n.toLocaleString('en-US');
191
+ }
192
+
193
+ function buildReport() {
194
+ const sizes = measure();
195
+ const totalBytes = sizes.reduce((a, s) => a + s.bytes, 0);
196
+ const totalLines = sizes.reduce((a, s) => a + s.lines, 0);
197
+ const archivedSlugs = archivedEpicSlugs();
198
+
199
+ const perFile = FLAG_TARGETS.map((label) => {
200
+ const entry = ALWAYS_READ.find((f) => f.label === label);
201
+ const text = existsSync(entry.path) ? readFileSync(entry.path, 'utf8') : '';
202
+ const bullets = splitIntoBullets(text);
203
+ return {
204
+ label,
205
+ dupes: findNearDuplicates(bullets),
206
+ deadPaths: findDeadPathRefs(bullets),
207
+ archivedMentions: findArchivedEpicMentions(bullets, archivedSlugs),
208
+ };
209
+ });
210
+
211
+ const date = new Date().toISOString().slice(0, 10);
212
+ const lines = [];
213
+ lines.push(
214
+ `<!-- Advisory artifact from the doc-hygiene skill (scripts/doc-hygiene.mjs). Findings are proposals`,
215
+ ` only — no LEARNINGS.md/README.md content was changed by this script. -->`,
216
+ '',
217
+ `# Doc hygiene report — ${date}`,
218
+ '',
219
+ '🧹 **doc-hygiene skill.** Advisory only — review by hand; nothing here gates or auto-edits.',
220
+ '',
221
+ '## Always-read set size',
222
+ ''
223
+ );
224
+ lines.push('| Doc | Lines | KB |', '|---|---|---|');
225
+ for (const s of sizes) {
226
+ lines.push(
227
+ `| ${s.label} | ${s.missing ? '—' : fmt(s.lines)} | ${s.missing ? 'missing' : (s.bytes / 1024).toFixed(1)} |`
228
+ );
229
+ }
230
+ lines.push(`| **Total** | **${fmt(totalLines)}** | **${(totalBytes / 1024).toFixed(1)}** |`, '');
231
+
232
+ for (const f of perFile) {
233
+ lines.push(`## ${f.label} — flagged candidates`, '');
234
+ if (f.dupes.length === 0 && f.deadPaths.length === 0 && f.archivedMentions.length === 0) {
235
+ lines.push('Nothing flagged this pass.', '');
236
+ continue;
237
+ }
238
+ if (f.dupes.length) {
239
+ lines.push(
240
+ `**Possible near-duplicate bullets** (same section, high word overlap — verify before merging):`,
241
+ ''
242
+ );
243
+ for (const d of f.dupes)
244
+ lines.push(
245
+ `- \`${f.label}\` section "${d.section}": line ${d.a} vs line ${d.b} (${d.sim}% shared words)`
246
+ );
247
+ lines.push('');
248
+ }
249
+ if (f.deadPaths.length) {
250
+ lines.push(
251
+ `**Referenced paths not found in this checkout** (verify against the app repo before treating as stale — this checkout can be behind the app's own \`main\`):`,
252
+ ''
253
+ );
254
+ for (const d of f.deadPaths) lines.push(`- \`${f.label}\` line ${d.line}: \`${d.path}\``);
255
+ lines.push('');
256
+ }
257
+ if (f.archivedMentions.length) {
258
+ lines.push(`**Mentions an archived epic** (check whether the lesson is superseded):`, '');
259
+ for (const d of f.archivedMentions)
260
+ lines.push(`- \`${f.label}\` line ${d.line}: mentions archived epic \`${d.slug}\``);
261
+ lines.push('');
262
+ }
263
+ }
264
+
265
+ lines.push('---', 'Advisory only — never auto-edits. Review, then hand-merge any accepted change.', '');
266
+ return { markdown: lines.join('\n'), sizes, totalBytes, totalLines };
267
+ }
268
+
269
+ const { markdown, totalBytes, totalLines } = buildReport();
270
+ console.log(markdown);
271
+
272
+ if (!process.argv.includes('--check')) {
273
+ const date = new Date().toISOString().slice(0, 10);
274
+ const outPath = join(IDEAS, `DOC-HYGIENE-REPORT-${date}.md`);
275
+ writeFileSync(outPath, markdown);
276
+ console.error(
277
+ `\nWrote ${outPath} (always-read set: ${fmt(totalLines)} lines / ${(totalBytes / 1024).toFixed(1)} KB)`
278
+ );
279
+ }