@grainulation/barn 1.0.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 (50) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +87 -0
  4. package/bin/barn.js +98 -0
  5. package/lib/index.js +93 -0
  6. package/lib/server.js +368 -0
  7. package/package.json +52 -0
  8. package/public/grainulation-tokens.css +321 -0
  9. package/public/index.html +907 -0
  10. package/templates/README.md +48 -0
  11. package/templates/adr.html +223 -0
  12. package/templates/adr.json +29 -0
  13. package/templates/brief.html +297 -0
  14. package/templates/brief.json +26 -0
  15. package/templates/certificate.html +247 -0
  16. package/templates/certificate.json +23 -0
  17. package/templates/changelog.html +239 -0
  18. package/templates/changelog.json +19 -0
  19. package/templates/ci-workflow.yml +52 -0
  20. package/templates/comparison.html +248 -0
  21. package/templates/comparison.json +21 -0
  22. package/templates/conflict-map.html +240 -0
  23. package/templates/conflict-map.json +19 -0
  24. package/templates/dashboard.html +515 -0
  25. package/templates/dashboard.json +22 -0
  26. package/templates/email-digest.html +178 -0
  27. package/templates/email-digest.json +18 -0
  28. package/templates/evidence-matrix.html +232 -0
  29. package/templates/evidence-matrix.json +21 -0
  30. package/templates/explainer.html +342 -0
  31. package/templates/explainer.json +23 -0
  32. package/templates/handoff.html +343 -0
  33. package/templates/handoff.json +24 -0
  34. package/templates/one-pager.html +248 -0
  35. package/templates/one-pager.json +22 -0
  36. package/templates/postmortem.html +303 -0
  37. package/templates/postmortem.json +20 -0
  38. package/templates/rfc.html +199 -0
  39. package/templates/rfc.json +32 -0
  40. package/templates/risk-register.html +231 -0
  41. package/templates/risk-register.json +22 -0
  42. package/templates/slide-deck.html +239 -0
  43. package/templates/slide-deck.json +23 -0
  44. package/templates/template.schema.json +25 -0
  45. package/templates/wiki-page.html +222 -0
  46. package/templates/wiki-page.json +23 -0
  47. package/tools/README.md +31 -0
  48. package/tools/build-pdf.js +43 -0
  49. package/tools/detect-sprints.js +292 -0
  50. package/tools/generate-manifest.js +237 -0
@@ -0,0 +1,239 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{TITLE}}</title>
7
+ <style>
8
+ /* barn/templates/slide-deck.html — full-screen keyboard-navigated presentation
9
+ Arrow keys navigate, number keys jump to slide, progress bar tracks position.
10
+ System font stack, no external dependencies, fully self-contained. */
11
+
12
+ :root {
13
+ --bg: #0a0e1a;
14
+ --bg2: #111827;
15
+ --bg3: #1e293b;
16
+ --accent: #3b82f6;
17
+ --accent-light: #60a5fa;
18
+ --green: #22c55e;
19
+ --green-dim: rgba(34,197,94,0.12);
20
+ --orange: #f59e0b;
21
+ --orange-dim: rgba(245,158,11,0.12);
22
+ --red: #e11d48;
23
+ --red-dim: rgba(225,29,72,0.12);
24
+ --text: #f1f5f9;
25
+ --text-muted: #94a3b8;
26
+ --text-dim: #64748b;
27
+ }
28
+
29
+ * { margin: 0; padding: 0; box-sizing: border-box; }
30
+ html, body { height: 100%; overflow: hidden; }
31
+
32
+ body {
33
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', sans-serif;
34
+ background: var(--bg);
35
+ color: var(--text);
36
+ -webkit-font-smoothing: antialiased;
37
+ }
38
+
39
+ .deck { width: 100%; height: 100vh; overflow: hidden; position: relative; }
40
+
41
+ .slide {
42
+ width: 100%; height: 100vh; padding: 60px 80px;
43
+ display: none; flex-direction: column; justify-content: center;
44
+ position: absolute; top: 0; left: 0; overflow: hidden;
45
+ }
46
+
47
+ .slide.active { display: flex; }
48
+
49
+ .slide::before {
50
+ content: '';
51
+ position: absolute; top: 0; right: 0; width: 40%; height: 100%;
52
+ background: radial-gradient(ellipse at top right, rgba(59,130,246,0.06) 0%, transparent 70%);
53
+ pointer-events: none;
54
+ }
55
+
56
+ h1 {
57
+ font-size: 42pt; font-weight: 800;
58
+ letter-spacing: -0.03em; line-height: 1.1; margin-bottom: 16px;
59
+ background: linear-gradient(135deg, #fff 0%, #94a3b8 100%);
60
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
61
+ }
62
+
63
+ h2 {
64
+ font-size: 28pt; font-weight: 700;
65
+ letter-spacing: -0.02em; margin-bottom: 24px; color: #fff;
66
+ }
67
+
68
+ h3 {
69
+ font-size: 13pt; font-weight: 600;
70
+ color: var(--accent-light); margin-bottom: 12px;
71
+ text-transform: uppercase; letter-spacing: 0.08em;
72
+ }
73
+
74
+ p {
75
+ font-size: 14pt; color: var(--text-muted);
76
+ line-height: 1.6; margin-bottom: 12px; max-width: 85%;
77
+ }
78
+
79
+ .subtitle { font-size: 18pt; color: var(--text-muted); font-weight: 300; max-width: 70%; }
80
+ .meta-line { font-size: 10pt; color: var(--text-dim); margin-top: 16px; letter-spacing: 0.04em; }
81
+
82
+ .label {
83
+ display: inline-block; font-size: 9pt; font-weight: 600;
84
+ letter-spacing: 0.12em; text-transform: uppercase;
85
+ color: var(--accent-light); background: rgba(59,130,246,0.12);
86
+ padding: 4px 12px; border-radius: 4px; margin-bottom: 16px;
87
+ }
88
+
89
+ .label.green { color: var(--green); background: var(--green-dim); }
90
+ .label.orange { color: var(--orange); background: var(--orange-dim); }
91
+ .label.red { color: var(--red); background: var(--red-dim); }
92
+
93
+ .card {
94
+ background: var(--bg3); border-radius: 12px; padding: 24px;
95
+ border: 1px solid rgba(255,255,255,0.06); margin-bottom: 16px;
96
+ }
97
+
98
+ .card h4 {
99
+ font-size: 13pt; font-weight: 600;
100
+ color: var(--text); margin-bottom: 8px;
101
+ }
102
+
103
+ .card p {
104
+ font-size: 11pt; color: var(--text-muted); line-height: 1.5;
105
+ }
106
+
107
+ .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 16px; }
108
+ .grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; margin-top: 16px; }
109
+
110
+ .claim-ref { font-size: 8pt; color: var(--text-dim); font-family: monospace; }
111
+
112
+ .tier-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
113
+
114
+ .tier-bar-label {
115
+ min-width: 100px; color: var(--text-muted);
116
+ font-size: 10pt; text-transform: uppercase; letter-spacing: 0.06em;
117
+ }
118
+
119
+ .tier-bar-fill {
120
+ height: 8px; border-radius: 4px;
121
+ background: var(--accent); transition: width 0.4s ease;
122
+ }
123
+
124
+ .divider { width: 60px; height: 3px; background: var(--accent); border-radius: 2px; margin: 16px 0 24px 0; }
125
+
126
+ .slide-counter {
127
+ position: fixed; bottom: 24px; right: 40px;
128
+ font-size: 11pt; color: var(--text-dim);
129
+ font-family: monospace; z-index: 200;
130
+ }
131
+
132
+ .slide-progress {
133
+ position: fixed; top: 0; left: 0; height: 3px;
134
+ background: var(--accent); z-index: 200; transition: width 0.3s ease;
135
+ }
136
+
137
+ .barn-footer {
138
+ position: absolute; bottom: 24px; left: 80px;
139
+ font-size: 8pt; color: var(--text-dim);
140
+ }
141
+
142
+ @media (max-width: 768px) {
143
+ .slide { padding: 32px 20px; }
144
+ h1 { font-size: 24pt; }
145
+ h2 { font-size: 18pt; }
146
+ p, .subtitle { font-size: 12pt; max-width: 100%; }
147
+ .grid-2, .grid-3 { grid-template-columns: 1fr; }
148
+ .barn-footer { left: 20px; }
149
+ .slide-counter { right: 20px; }
150
+ }
151
+
152
+ @media print {
153
+ .slide {
154
+ display: flex !important; position: relative;
155
+ page-break-after: always; height: auto; min-height: 100vh;
156
+ }
157
+ .slide::before, .slide-counter, .slide-progress { display: none; }
158
+ body { background: #fff; color: #111; }
159
+ h1 { -webkit-text-fill-color: #111; background: none; }
160
+ h2 { color: #111; }
161
+ p, .subtitle { color: #333; }
162
+ .card { background: #f5f5f5; border-color: #ddd; }
163
+ .label { background: #eee; color: #333; }
164
+ }
165
+ </style>
166
+ </head>
167
+ <body>
168
+
169
+ <div class="deck" role="region" aria-label="Presentation slides">
170
+ <div class="slide-progress" role="progressbar" aria-label="Slide progress"></div>
171
+
172
+ <section class="slide active" aria-label="Title slide">
173
+ <span class="label">{{SPRINT_NAME}}</span>
174
+ <h1>{{TITLE}}</h1>
175
+ <p class="subtitle">{{SUBTITLE}}</p>
176
+ <p class="meta-line">{{DATE}}</p>
177
+ <div class="barn-footer">Built with barn</div>
178
+ </section>
179
+
180
+ {{TYPE_SLIDES}}
181
+
182
+ <section class="slide" aria-label="Evidence summary">
183
+ <h3>Evidence</h3>
184
+ <h2>Evidence Tier Breakdown</h2>
185
+ {{EVIDENCE_SLIDE}}
186
+ <div class="barn-footer">Built with barn</div>
187
+ </section>
188
+
189
+ <section class="slide" aria-label="Recommendations">
190
+ <h3>Recommendations</h3>
191
+ <h2>What We Recommend</h2>
192
+ {{RECOMMENDATION_SLIDE}}
193
+ <div class="barn-footer">Built with barn</div>
194
+ </section>
195
+
196
+ <section class="slide" aria-label="Questions and discussion">
197
+ <h3>Discussion</h3>
198
+ <h2>Questions</h2>
199
+ {{QA_SLIDE}}
200
+ <div class="barn-footer">Built with barn</div>
201
+ </section>
202
+
203
+ <div class="slide-counter" aria-live="polite">1 / 1</div>
204
+ </div>
205
+
206
+ <script>
207
+ (function () {
208
+ var slides = document.querySelectorAll('.slide');
209
+ var counter = document.querySelector('.slide-counter');
210
+ var progress = document.querySelector('.slide-progress');
211
+ var current = 0;
212
+
213
+ function show(i) {
214
+ if (i < 0 || i >= slides.length) return;
215
+ slides[current].classList.remove('active');
216
+ current = i;
217
+ slides[current].classList.add('active');
218
+ counter.textContent = (current + 1) + ' / ' + slides.length;
219
+ progress.style.width = ((current + 1) / slides.length * 100) + '%';
220
+ }
221
+
222
+ document.addEventListener('keydown', function (e) {
223
+ if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
224
+ e.preventDefault(); show(current + 1);
225
+ } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
226
+ e.preventDefault(); show(current - 1);
227
+ } else if (e.key >= '1' && e.key <= '9') {
228
+ show(parseInt(e.key, 10) - 1);
229
+ } else if (e.key === '0') {
230
+ show(9);
231
+ }
232
+ });
233
+
234
+ show(0);
235
+ })();
236
+ </script>
237
+
238
+ </body>
239
+ </html>
@@ -0,0 +1,23 @@
1
+ {
2
+ "title": "Slide Deck",
3
+ "description": "Full-screen presentation slides with keyboard navigation. Arrow keys and number keys for slide control, progress bar, and slide counter.",
4
+ "tags": ["presentation", "slides", "meeting", "fullscreen", "keyboard"],
5
+ "author": "grainulation",
6
+ "version": "1.0.0",
7
+ "exportPresets": [
8
+ { "name": "pdf", "format": "pdf" },
9
+ { "name": "static-site", "format": "html" }
10
+ ],
11
+ "seedPacks": [
12
+ "architecture"
13
+ ],
14
+ "scaffoldConfig": {
15
+ "defaultPlaceholders": {
16
+ "TITLE": "Sprint Presentation",
17
+ "SUBTITLE": "Key findings and recommendations",
18
+ "SPRINT_NAME": "Sprint",
19
+ "DATE": ""
20
+ },
21
+ "postInit": ["load-claims", "compile-brief"]
22
+ }
23
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "title": { "type": "string", "description": "Display name for the template" },
6
+ "description": { "type": "string", "description": "What this template is for" },
7
+ "tags": { "type": "array", "items": { "type": "string" }, "description": "Searchable tags" },
8
+ "author": { "type": "string" },
9
+ "version": { "type": "string", "description": "Semver version" },
10
+ "exportPresets": {
11
+ "type": "array",
12
+ "items": { "type": "object", "properties": { "name": { "type": "string" }, "format": { "type": "string" } } },
13
+ "description": "Mill export presets — defines how this template can be exported (consumed by mill)"
14
+ },
15
+ "seedPacks": {
16
+ "type": "array",
17
+ "items": { "type": "string" },
18
+ "description": "Silo knowledge packs to pre-load when instantiating this template (consumed by silo)"
19
+ },
20
+ "scaffoldConfig": {
21
+ "type": "object",
22
+ "description": "Orchard scaffold configuration — how to bootstrap a new sprint from this template (consumed by orchard)"
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,222 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{TITLE}} - Wiki</title>
7
+ <style>
8
+ /* barn/templates/wiki-page.html — wiki/docs paste template
9
+ Minimal styling that survives Confluence, Notion, GitHub Wiki paste.
10
+ Self-contained, no external dependencies. */
11
+
12
+ :root {
13
+ --bg: #0a0e1a;
14
+ --bg2: #111827;
15
+ --bg3: #1e293b;
16
+ --accent: #3b82f6;
17
+ --accent-light: #60a5fa;
18
+ --green: #22c55e;
19
+ --orange: #f59e0b;
20
+ --red: #e11d48;
21
+ --purple: #a78bfa;
22
+ --text: #f1f5f9;
23
+ --text-muted: #94a3b8;
24
+ --text-dim: #64748b;
25
+ --border: rgba(255,255,255,0.08);
26
+ }
27
+
28
+ * { margin: 0; padding: 0; box-sizing: border-box; }
29
+
30
+ body {
31
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', sans-serif;
32
+ background: var(--bg);
33
+ color: var(--text);
34
+ font-size: 11pt;
35
+ line-height: 1.6;
36
+ -webkit-font-smoothing: antialiased;
37
+ max-width: 860px;
38
+ margin: 0 auto;
39
+ padding: 40px 24px;
40
+ }
41
+
42
+ header { margin-bottom: 32px; border-bottom: 2px solid var(--accent); padding-bottom: 24px; }
43
+ h1 { font-size: 28pt; font-weight: 700; color: #fff; margin-bottom: 8px; }
44
+ h2 { font-size: 18pt; font-weight: 600; color: #fff; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 1px solid var(--border); }
45
+ h3 { font-size: 14pt; font-weight: 600; color: var(--accent-light); margin: 20px 0 8px; }
46
+ p { color: var(--text-muted); margin-bottom: 12px; }
47
+
48
+ .meta { font-size: 10pt; color: var(--text-dim); margin-bottom: 4px; }
49
+ .meta span { margin-right: 16px; }
50
+
51
+ nav[aria-label="Table of contents"] {
52
+ background: var(--bg2);
53
+ border: 1px solid var(--border);
54
+ border-radius: 8px;
55
+ padding: 20px 24px;
56
+ margin-bottom: 32px;
57
+ }
58
+
59
+ nav[aria-label="Table of contents"] h2 {
60
+ font-size: 12pt; margin: 0 0 12px; border: none; padding: 0;
61
+ color: var(--accent-light); text-transform: uppercase; letter-spacing: 0.08em;
62
+ }
63
+
64
+ nav[aria-label="Table of contents"] ul { list-style: none; padding: 0; }
65
+ nav[aria-label="Table of contents"] li { margin-bottom: 6px; }
66
+ nav[aria-label="Table of contents"] a {
67
+ color: var(--text-muted); text-decoration: none; font-size: 10pt;
68
+ }
69
+ nav[aria-label="Table of contents"] a:hover { color: var(--accent-light); }
70
+
71
+ details {
72
+ background: var(--bg2);
73
+ border: 1px solid var(--border);
74
+ border-radius: 8px;
75
+ margin-bottom: 12px;
76
+ overflow: hidden;
77
+ }
78
+
79
+ summary {
80
+ padding: 12px 16px;
81
+ cursor: pointer;
82
+ font-weight: 600;
83
+ color: var(--text);
84
+ font-size: 11pt;
85
+ user-select: none;
86
+ list-style: none;
87
+ }
88
+
89
+ summary::before {
90
+ content: "+ ";
91
+ color: var(--accent-light);
92
+ font-family: monospace;
93
+ margin-right: 4px;
94
+ }
95
+
96
+ details[open] summary::before { content: "- "; }
97
+
98
+ .claim-body {
99
+ padding: 4px 16px 16px;
100
+ color: var(--text-muted);
101
+ font-size: 10pt;
102
+ border-top: 1px solid var(--border);
103
+ }
104
+
105
+ .evidence-legend {
106
+ background: var(--bg3);
107
+ border-radius: 8px;
108
+ padding: 16px 20px;
109
+ margin-top: 32px;
110
+ }
111
+
112
+ .evidence-legend h3 { margin-top: 0; }
113
+
114
+ .tier-list { list-style: none; padding: 0; }
115
+
116
+ .tier-list li {
117
+ display: flex;
118
+ align-items: baseline;
119
+ gap: 12px;
120
+ padding: 4px 0;
121
+ font-size: 10pt;
122
+ color: var(--text-muted);
123
+ }
124
+
125
+ .tier-badge {
126
+ display: inline-block;
127
+ font-size: 8pt;
128
+ font-weight: 700;
129
+ text-transform: uppercase;
130
+ letter-spacing: 0.06em;
131
+ padding: 2px 8px;
132
+ border-radius: 3px;
133
+ min-width: 80px;
134
+ text-align: center;
135
+ }
136
+
137
+ .tier-stated { background: rgba(100,116,139,0.2); color: var(--text-dim); }
138
+ .tier-web { background: rgba(167,139,250,0.15); color: var(--purple); }
139
+ .tier-documented { background: rgba(59,130,246,0.15); color: var(--accent-light); }
140
+ .tier-tested { background: rgba(245,158,11,0.15); color: var(--orange); }
141
+ .tier-production { background: rgba(34,197,94,0.15); color: var(--green); }
142
+
143
+ .claim-ref { font-size: 8pt; color: var(--text-dim); font-family: monospace; }
144
+
145
+ footer {
146
+ margin-top: 40px;
147
+ padding-top: 16px;
148
+ border-top: 1px solid var(--border);
149
+ font-size: 8pt;
150
+ color: var(--text-dim);
151
+ }
152
+
153
+ a { color: var(--accent-light); }
154
+ a:hover { text-decoration: underline; }
155
+
156
+ @media print {
157
+ body { background: #fff; color: #1a1a1a; max-width: 100%; }
158
+ header { border-color: #333; }
159
+ h1, h2, h3 { color: #111; }
160
+ p, .claim-body, .tier-list li { color: #333; }
161
+ nav[aria-label="Table of contents"], details, .evidence-legend { background: #f5f5f5; border-color: #ddd; }
162
+ summary { color: #111; }
163
+ summary::before { color: #333; }
164
+ a { color: #0066cc; }
165
+ .tier-badge { border: 1px solid #ccc; }
166
+ }
167
+
168
+ @media (max-width: 600px) {
169
+ body { padding: 20px 16px; }
170
+ h1 { font-size: 20pt; }
171
+ h2 { font-size: 15pt; }
172
+ nav[aria-label="Table of contents"] { padding: 14px 16px; }
173
+ details summary { padding: 10px 12px; }
174
+ .claim-body { padding: 4px 12px 12px; }
175
+ }
176
+ </style>
177
+ </head>
178
+ <body>
179
+ <article aria-label="Wiki page: {{TITLE}}">
180
+
181
+ <header>
182
+ <div class="meta">
183
+ <span>Sprint: {{SPRINT_NAME}}</span>
184
+ <span>Audience: {{AUDIENCE}}</span>
185
+ </div>
186
+ <h1>{{TITLE}}</h1>
187
+ <p>{{QUESTION}}</p>
188
+ </header>
189
+
190
+ <nav aria-label="Table of contents">
191
+ <h2>Contents</h2>
192
+ {{TOC}}
193
+ </nav>
194
+
195
+ <section id="overview" aria-label="Sprint overview">
196
+ <h2>Overview</h2>
197
+ <p>{{QUESTION}}</p>
198
+ </section>
199
+
200
+ <section id="claims" aria-label="Claims">
201
+ <h2>Claims</h2>
202
+ {{CLAIM_SECTIONS}}
203
+ </section>
204
+
205
+ <section id="evidence" aria-label="Evidence tiers">
206
+ <div class="evidence-legend">
207
+ <h3>Evidence Tiers</h3>
208
+ <ul class="tier-list">
209
+ <li><span class="tier-badge tier-stated">stated</span> Stakeholder assertion, not independently verified</li>
210
+ <li><span class="tier-badge tier-web">web</span> Found online, not independently verified</li>
211
+ <li><span class="tier-badge tier-documented">documented</span> Present in source code, official docs, or ADRs</li>
212
+ <li><span class="tier-badge tier-tested">tested</span> Verified via prototype or benchmark</li>
213
+ <li><span class="tier-badge tier-production">production</span> Measured from live production systems</li>
214
+ </ul>
215
+ </div>
216
+ </section>
217
+
218
+ <footer>Built with barn -- grainulation</footer>
219
+
220
+ </article>
221
+ </body>
222
+ </html>
@@ -0,0 +1,23 @@
1
+ {
2
+ "title": "Wiki Page",
3
+ "description": "Clean HTML optimized for wiki and docs paste targets including Confluence, Notion, and GitHub Wiki. Features auto-generated table of contents, collapsible claim sections, and minimal styling that survives paste.",
4
+ "tags": ["wiki", "documentation", "confluence", "notion", "github-wiki"],
5
+ "author": "grainulation",
6
+ "version": "1.0.0",
7
+ "exportPresets": [
8
+ { "name": "static-site", "format": "html" },
9
+ { "name": "markdown-outline", "format": "md" }
10
+ ],
11
+ "seedPacks": [],
12
+ "scaffoldConfig": {
13
+ "defaultPlaceholders": {
14
+ "TITLE": "Untitled Wiki Page",
15
+ "SPRINT_NAME": "Sprint",
16
+ "QUESTION": "",
17
+ "AUDIENCE": "Team",
18
+ "TOC": "",
19
+ "CLAIM_SECTIONS": ""
20
+ },
21
+ "postInit": ["load-claims", "compile-brief"]
22
+ }
23
+ }
@@ -0,0 +1,31 @@
1
+ # barn/tools
2
+
3
+ Standalone utilities for structured research sprints. Each tool works independently and has zero npm dependencies.
4
+
5
+ ## detect-sprints.js
6
+
7
+ Scans a repo for `claims.json` files and determines which sprint is active using git history heuristics. No configuration file needed -- it derives everything from the filesystem and git log.
8
+
9
+ **Heuristic ranking:**
10
+ 1. Non-archived sprints beat archived ones
11
+ 2. Most recent git commit touching `claims.json` wins ties
12
+ 3. Falls back to `meta.initiated` date, then claim count
13
+
14
+ **Programmatic use:**
15
+ ```js
16
+ import { detectSprints } from '@grainulation/barn/detect-sprints';
17
+ const { active, sprints } = detectSprints('/path/to/repo');
18
+ ```
19
+
20
+ ## generate-manifest.js
21
+
22
+ Builds a `wheat-manifest.json` that maps topics to claims and files. Designed to give AI search tools a single entry point into a sprint repo instead of requiring full directory scans.
23
+
24
+ The manifest includes:
25
+ - Topic map with claim IDs and evidence levels
26
+ - Sprint detection results (via detect-sprints)
27
+ - File-to-topic associations
28
+
29
+ ## build-pdf.js
30
+
31
+ Thin wrapper around `npx md-to-pdf`. Validates the input file exists, runs the conversion, and reports the output path. Nothing more.
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * build-pdf.js — Convert markdown to PDF via npx md-to-pdf
4
+ *
5
+ * Usage:
6
+ * barn build-pdf <markdown-file>
7
+ * barn build-pdf output/brief.md
8
+ *
9
+ * Uses npx to invoke md-to-pdf, so no local install is required.
10
+ * Zero npm dependencies (Node built-in only).
11
+ */
12
+
13
+ import { existsSync } from 'node:fs';
14
+ import { execSync } from 'node:child_process';
15
+
16
+ const target = process.argv[2];
17
+
18
+ if (!target || target === '--help' || target === '-h') {
19
+ console.log(`build-pdf — convert markdown to PDF
20
+
21
+ Usage:
22
+ barn build-pdf <markdown-file>
23
+
24
+ Example:
25
+ barn build-pdf output/brief.md
26
+
27
+ Uses npx md-to-pdf under the hood. No local install needed.`);
28
+ process.exit(target ? 0 : 1);
29
+ }
30
+
31
+ if (!existsSync(target)) {
32
+ console.error(`File not found: ${target}`);
33
+ process.exit(1);
34
+ }
35
+
36
+ try {
37
+ execSync(`npx md-to-pdf "${target}"`, { stdio: 'inherit' });
38
+ const pdfPath = target.replace(/\.md$/, '.pdf');
39
+ console.log(`PDF generated: ${pdfPath}`);
40
+ } catch (e) {
41
+ console.error('PDF generation failed:', e.message);
42
+ process.exit(1);
43
+ }