@grainulation/mill 1.0.0 → 1.0.2
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.
- package/CODE_OF_CONDUCT.md +25 -0
- package/CONTRIBUTING.md +101 -0
- package/README.md +90 -42
- package/bin/mill.js +233 -67
- package/lib/exporters/csv.js +35 -30
- package/lib/exporters/json-ld.js +19 -13
- package/lib/exporters/markdown.js +83 -44
- package/lib/exporters/pdf.js +15 -15
- package/lib/formats/bibtex.mjs +83 -0
- package/lib/formats/{changelog.js → changelog.mjs} +27 -26
- package/lib/formats/confluence-adf.mjs +312 -0
- package/lib/formats/{csv.js → csv.mjs} +41 -37
- package/lib/formats/{dot.js → dot.mjs} +45 -34
- package/lib/formats/{evidence-matrix.js → evidence-matrix.mjs} +17 -16
- package/lib/formats/executive-summary.mjs +178 -0
- package/lib/formats/github-issues.mjs +96 -0
- package/lib/formats/{graphml.js → graphml.mjs} +45 -32
- package/lib/formats/html-report.mjs +228 -0
- package/lib/formats/{jira-csv.js → jira-csv.mjs} +30 -29
- package/lib/formats/{json-ld.js → json-ld.mjs} +6 -6
- package/lib/formats/{markdown.js → markdown.mjs} +53 -36
- package/lib/formats/{ndjson.js → ndjson.mjs} +6 -6
- package/lib/formats/{obsidian.js → obsidian.mjs} +43 -35
- package/lib/formats/{opml.js → opml.mjs} +38 -28
- package/lib/formats/ris.mjs +76 -0
- package/lib/formats/{rss.js → rss.mjs} +31 -28
- package/lib/formats/{sankey.js → sankey.mjs} +16 -15
- package/lib/formats/slide-deck.mjs +288 -0
- package/lib/formats/sql.mjs +120 -0
- package/lib/formats/{static-site.js → static-site.mjs} +64 -52
- package/lib/formats/{treemap.js → treemap.mjs} +16 -15
- package/lib/formats/typescript-defs.mjs +150 -0
- package/lib/formats/{yaml.js → yaml.mjs} +58 -40
- package/lib/formats.js +16 -16
- package/lib/index.js +5 -5
- package/lib/json-ld-common.js +37 -31
- package/lib/publishers/clipboard.js +21 -19
- package/lib/publishers/static.js +27 -12
- package/lib/serve-mcp.js +158 -83
- package/lib/server.js +252 -142
- package/package.json +6 -3
- package/lib/formats/bibtex.js +0 -76
- package/lib/formats/executive-summary.js +0 -130
- package/lib/formats/github-issues.js +0 -89
- package/lib/formats/html-report.js +0 -181
- package/lib/formats/ris.js +0 -70
- package/lib/formats/slide-deck.js +0 -200
- package/lib/formats/sql.js +0 -116
- package/lib/formats/typescript-defs.js +0 -147
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* mill format: slide-deck
|
|
3
|
-
*
|
|
4
|
-
* Self-contained HTML with CSS scroll-snap. One slide per claim type group.
|
|
5
|
-
* Dark theme matching grainulation design tokens.
|
|
6
|
-
* Zero dependencies — node built-in only.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export const name = 'slide-deck';
|
|
10
|
-
export const extension = '.html';
|
|
11
|
-
export const mimeType = 'text/html; charset=utf-8';
|
|
12
|
-
export const description = 'Scroll-snap slide deck: one slide per type group with keyboard navigation';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Convert a compilation object to a slide deck HTML page.
|
|
16
|
-
* @param {object} compilation - The compilation.json content
|
|
17
|
-
* @returns {string} HTML output
|
|
18
|
-
*/
|
|
19
|
-
export function convert(compilation) {
|
|
20
|
-
const meta = compilation.meta || {};
|
|
21
|
-
const claims = compilation.claims || [];
|
|
22
|
-
const conflicts = compilation.conflicts || [];
|
|
23
|
-
const certificate = compilation.certificate || {};
|
|
24
|
-
|
|
25
|
-
const title = meta.sprint || meta.question || 'Sprint Deck';
|
|
26
|
-
const compiled = certificate.compiled_at || new Date().toISOString();
|
|
27
|
-
const active = claims.filter(c => c.status === 'active').length;
|
|
28
|
-
|
|
29
|
-
// Group by type (skip reverted)
|
|
30
|
-
const byType = {};
|
|
31
|
-
for (const c of claims) {
|
|
32
|
-
if (c.status === 'reverted') continue;
|
|
33
|
-
const t = c.type || 'unknown';
|
|
34
|
-
if (!byType[t]) byType[t] = [];
|
|
35
|
-
byType[t].push(c);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const typeOrder = ['constraint', 'factual', 'recommendation', 'risk', 'estimate', 'feedback'];
|
|
39
|
-
const sortedTypes = typeOrder.filter(t => byType[t]);
|
|
40
|
-
for (const t of Object.keys(byType)) {
|
|
41
|
-
if (!sortedTypes.includes(t)) sortedTypes.push(t);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const typeColors = {
|
|
45
|
-
constraint: '#e74c3c',
|
|
46
|
-
factual: '#3498db',
|
|
47
|
-
recommendation: '#2ecc71',
|
|
48
|
-
risk: '#f39c12',
|
|
49
|
-
estimate: '#9b59b6',
|
|
50
|
-
feedback: '#1abc9c',
|
|
51
|
-
unknown: '#95a5a6',
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// Title slide
|
|
55
|
-
const slides = [];
|
|
56
|
-
slides.push(`
|
|
57
|
-
<section class="slide">
|
|
58
|
-
<div class="slide-content title-slide">
|
|
59
|
-
<h1>${esc(title)}</h1>
|
|
60
|
-
${meta.question ? `<p class="question">${esc(meta.question)}</p>` : ''}
|
|
61
|
-
<p class="meta">${esc(compiled)} | ${claims.length} claims</p>
|
|
62
|
-
</div>
|
|
63
|
-
</section>`);
|
|
64
|
-
|
|
65
|
-
// Summary slide
|
|
66
|
-
const typeStats = sortedTypes.map(t => {
|
|
67
|
-
const color = typeColors[t] || typeColors.unknown;
|
|
68
|
-
return `<div class="type-stat"><span class="dot" style="background:${color}"></span>${capitalize(t)}: ${byType[t].length}</div>`;
|
|
69
|
-
}).join('\n ');
|
|
70
|
-
|
|
71
|
-
slides.push(`
|
|
72
|
-
<section class="slide">
|
|
73
|
-
<div class="slide-content">
|
|
74
|
-
<h2>Summary</h2>
|
|
75
|
-
<div class="summary-grid">
|
|
76
|
-
<div class="big-stat"><span class="num">${claims.length}</span><span class="label">Total</span></div>
|
|
77
|
-
<div class="big-stat"><span class="num">${active}</span><span class="label">Active</span></div>
|
|
78
|
-
${conflicts.length ? `<div class="big-stat"><span class="num">${conflicts.length}</span><span class="label">Conflicts</span></div>` : ''}
|
|
79
|
-
</div>
|
|
80
|
-
<div class="type-stats">
|
|
81
|
-
${typeStats}
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
</section>`);
|
|
85
|
-
|
|
86
|
-
// One slide per type group
|
|
87
|
-
for (const t of sortedTypes) {
|
|
88
|
-
const group = byType[t];
|
|
89
|
-
const color = typeColors[t] || typeColors.unknown;
|
|
90
|
-
const items = group.map(c => {
|
|
91
|
-
const body = esc(c.content || c.text || '');
|
|
92
|
-
const conf = c.confidence != null ? ` (${Math.round(c.confidence * 100)}%)` : '';
|
|
93
|
-
return `<li><strong>${esc(c.id)}</strong>${conf}: ${body}</li>`;
|
|
94
|
-
}).join('\n ');
|
|
95
|
-
|
|
96
|
-
slides.push(`
|
|
97
|
-
<section class="slide">
|
|
98
|
-
<div class="slide-content">
|
|
99
|
-
<h2 style="border-left:4px solid ${color};padding-left:12px">${capitalize(t)}s (${group.length})</h2>
|
|
100
|
-
<ul class="claim-list">
|
|
101
|
-
${items}
|
|
102
|
-
</ul>
|
|
103
|
-
</div>
|
|
104
|
-
</section>`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Conflicts slide (if any)
|
|
108
|
-
if (conflicts.length > 0) {
|
|
109
|
-
const conflictItems = conflicts.map(c => {
|
|
110
|
-
const ids = c.ids?.join(' vs ') || 'unknown';
|
|
111
|
-
const resolved = c.resolution ? ' [resolved]' : '';
|
|
112
|
-
return `<li><strong>${esc(ids)}</strong>${resolved}: ${esc(c.description || c.reason || '')}</li>`;
|
|
113
|
-
}).join('\n ');
|
|
114
|
-
|
|
115
|
-
slides.push(`
|
|
116
|
-
<section class="slide">
|
|
117
|
-
<div class="slide-content">
|
|
118
|
-
<h2 style="border-left:4px solid #e74c3c;padding-left:12px">Conflicts (${conflicts.length})</h2>
|
|
119
|
-
<ul class="claim-list">
|
|
120
|
-
${conflictItems}
|
|
121
|
-
</ul>
|
|
122
|
-
</div>
|
|
123
|
-
</section>`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Certificate slide
|
|
127
|
-
slides.push(`
|
|
128
|
-
<section class="slide">
|
|
129
|
-
<div class="slide-content title-slide">
|
|
130
|
-
<h2>Certificate</h2>
|
|
131
|
-
<p class="mono">${certificate.claim_count || claims.length} claims</p>
|
|
132
|
-
<p class="mono">sha256:${esc((certificate.sha256 || 'unknown').slice(0, 24))}</p>
|
|
133
|
-
<p class="meta">${esc(compiled)}</p>
|
|
134
|
-
</div>
|
|
135
|
-
</section>`);
|
|
136
|
-
|
|
137
|
-
return `<!DOCTYPE html>
|
|
138
|
-
<html lang="en">
|
|
139
|
-
<head>
|
|
140
|
-
<meta charset="utf-8">
|
|
141
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
142
|
-
<title>${esc(title)} — Slide Deck</title>
|
|
143
|
-
<style>
|
|
144
|
-
:root { --bg:#0a0e1a; --surface:#111827; --border:#1e293b; --text:#e2e8f0; --muted:#94a3b8; }
|
|
145
|
-
* { margin:0; padding:0; box-sizing:border-box; }
|
|
146
|
-
html { scroll-snap-type:y mandatory; overflow-y:scroll; }
|
|
147
|
-
body { background:var(--bg); color:var(--text); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; line-height:1.6; }
|
|
148
|
-
.slide { scroll-snap-align:start; height:100vh; display:flex; align-items:center; justify-content:center; padding:2rem; }
|
|
149
|
-
.slide-content { max-width:800px; width:100%; }
|
|
150
|
-
.title-slide { text-align:center; }
|
|
151
|
-
.title-slide h1 { font-size:2.4rem; margin-bottom:0.75rem; }
|
|
152
|
-
.title-slide h2 { font-size:1.8rem; margin-bottom:1rem; }
|
|
153
|
-
.question { font-size:1.1rem; color:var(--muted); margin-bottom:1rem; }
|
|
154
|
-
.meta { font-size:0.85rem; color:var(--muted); }
|
|
155
|
-
.mono { font-family:monospace; font-size:1rem; color:var(--muted); margin-bottom:0.5rem; }
|
|
156
|
-
h2 { font-size:1.5rem; margin-bottom:1.25rem; }
|
|
157
|
-
.summary-grid { display:flex; gap:2rem; justify-content:center; margin-bottom:2rem; }
|
|
158
|
-
.big-stat { display:flex; flex-direction:column; align-items:center; }
|
|
159
|
-
.big-stat .num { font-size:2.5rem; font-weight:700; }
|
|
160
|
-
.big-stat .label { font-size:0.8rem; color:var(--muted); text-transform:uppercase; }
|
|
161
|
-
.type-stats { display:flex; flex-wrap:wrap; gap:0.75rem; justify-content:center; }
|
|
162
|
-
.type-stat { display:flex; align-items:center; gap:0.4rem; font-size:0.9rem; }
|
|
163
|
-
.dot { width:10px; height:10px; border-radius:50%; display:inline-block; }
|
|
164
|
-
.claim-list { list-style:none; max-height:70vh; overflow-y:auto; }
|
|
165
|
-
.claim-list li { padding:0.6rem 0; border-bottom:1px solid var(--border); font-size:0.9rem; }
|
|
166
|
-
.claim-list li strong { font-family:monospace; font-size:0.8rem; }
|
|
167
|
-
</style>
|
|
168
|
-
</head>
|
|
169
|
-
<body>
|
|
170
|
-
${slides.join('\n')}
|
|
171
|
-
<script>
|
|
172
|
-
document.addEventListener('keydown', function(e) {
|
|
173
|
-
if (e.key === 'ArrowDown' || e.key === 'PageDown') {
|
|
174
|
-
e.preventDefault();
|
|
175
|
-
const slides = document.querySelectorAll('.slide');
|
|
176
|
-
const vh = window.innerHeight;
|
|
177
|
-
const current = Math.round(window.scrollY / vh);
|
|
178
|
-
const next = Math.min(current + 1, slides.length - 1);
|
|
179
|
-
slides[next].scrollIntoView({ behavior: 'smooth' });
|
|
180
|
-
} else if (e.key === 'ArrowUp' || e.key === 'PageUp') {
|
|
181
|
-
e.preventDefault();
|
|
182
|
-
const vh = window.innerHeight;
|
|
183
|
-
const current = Math.round(window.scrollY / vh);
|
|
184
|
-
const prev = Math.max(current - 1, 0);
|
|
185
|
-
document.querySelectorAll('.slide')[prev].scrollIntoView({ behavior: 'smooth' });
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
</script>
|
|
189
|
-
</body>
|
|
190
|
-
</html>`;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function esc(str) {
|
|
194
|
-
if (str == null) return '';
|
|
195
|
-
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function capitalize(str) {
|
|
199
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
200
|
-
}
|
package/lib/formats/sql.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* mill format: sql
|
|
3
|
-
*
|
|
4
|
-
* Converts compilation.json to SQL CREATE TABLE + INSERT statements.
|
|
5
|
-
* Compatible with SQLite and PostgreSQL.
|
|
6
|
-
* Zero dependencies — node built-in only.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export const name = 'sql';
|
|
10
|
-
export const extension = '.sql';
|
|
11
|
-
export const mimeType = 'application/sql; charset=utf-8';
|
|
12
|
-
export const description = 'SQL schema and INSERT statements for claims data';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Convert a compilation object to SQL statements.
|
|
16
|
-
* @param {object} compilation - The compilation.json content
|
|
17
|
-
* @returns {string} SQL output
|
|
18
|
-
*/
|
|
19
|
-
export function convert(compilation) {
|
|
20
|
-
const claims = compilation.claims || [];
|
|
21
|
-
const meta = compilation.meta || {};
|
|
22
|
-
const certificate = compilation.certificate || {};
|
|
23
|
-
|
|
24
|
-
const lines = [];
|
|
25
|
-
|
|
26
|
-
lines.push('-- Auto-generated by mill sql format');
|
|
27
|
-
if (meta.sprint) lines.push(`-- Sprint: ${meta.sprint}`);
|
|
28
|
-
if (certificate.compiled_at) lines.push(`-- Compiled: ${certificate.compiled_at}`);
|
|
29
|
-
lines.push('');
|
|
30
|
-
|
|
31
|
-
// Claims table
|
|
32
|
-
lines.push('CREATE TABLE IF NOT EXISTS claims (');
|
|
33
|
-
lines.push(' id TEXT PRIMARY KEY,');
|
|
34
|
-
lines.push(' type TEXT,');
|
|
35
|
-
lines.push(' content TEXT,');
|
|
36
|
-
lines.push(' evidence_tier TEXT,');
|
|
37
|
-
lines.push(' status TEXT,');
|
|
38
|
-
lines.push(' confidence REAL,');
|
|
39
|
-
lines.push(' source TEXT,');
|
|
40
|
-
lines.push(' tags TEXT,');
|
|
41
|
-
lines.push(' created TEXT');
|
|
42
|
-
lines.push(');');
|
|
43
|
-
lines.push('');
|
|
44
|
-
|
|
45
|
-
// Conflicts table
|
|
46
|
-
lines.push('CREATE TABLE IF NOT EXISTS conflicts (');
|
|
47
|
-
lines.push(' id INTEGER PRIMARY KEY AUTOINCREMENT,');
|
|
48
|
-
lines.push(' claim_ids TEXT,');
|
|
49
|
-
lines.push(' description TEXT,');
|
|
50
|
-
lines.push(' resolution TEXT');
|
|
51
|
-
lines.push(');');
|
|
52
|
-
lines.push('');
|
|
53
|
-
|
|
54
|
-
// Insert claims
|
|
55
|
-
if (claims.length > 0) {
|
|
56
|
-
lines.push('-- Claims');
|
|
57
|
-
for (const claim of claims) {
|
|
58
|
-
const id = esc(claim.id || '');
|
|
59
|
-
const type = esc(claim.type || '');
|
|
60
|
-
const content = esc(claim.content || claim.text || '');
|
|
61
|
-
const evidenceTier = esc(extractTier(claim));
|
|
62
|
-
const status = esc(claim.status || '');
|
|
63
|
-
const confidence = claim.confidence != null ? String(claim.confidence) : 'NULL';
|
|
64
|
-
const source = esc(extractSource(claim));
|
|
65
|
-
const tags = esc(Array.isArray(claim.tags) ? claim.tags.join(',') : '');
|
|
66
|
-
const created = esc(claim.created || '');
|
|
67
|
-
|
|
68
|
-
lines.push(
|
|
69
|
-
`INSERT INTO claims VALUES ('${id}', '${type}', '${content}', '${evidenceTier}', '${status}', ${confidence}, '${source}', '${tags}', '${created}');`
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
lines.push('');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Insert conflicts
|
|
76
|
-
const conflicts = compilation.conflicts || [];
|
|
77
|
-
if (conflicts.length > 0) {
|
|
78
|
-
lines.push('-- Conflicts');
|
|
79
|
-
for (const conflict of conflicts) {
|
|
80
|
-
const claimIds = esc(Array.isArray(conflict.ids) ? conflict.ids.join(',') : '');
|
|
81
|
-
const description = esc(conflict.description || conflict.reason || '');
|
|
82
|
-
const resolution = esc(conflict.resolution || '');
|
|
83
|
-
|
|
84
|
-
lines.push(
|
|
85
|
-
`INSERT INTO conflicts (claim_ids, description, resolution) VALUES ('${claimIds}', '${description}', '${resolution}');`
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
lines.push('');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return lines.join('\n');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function esc(value) {
|
|
95
|
-
if (value == null) return '';
|
|
96
|
-
return String(value).replace(/'/g, "''");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function extractTier(claim) {
|
|
100
|
-
if (typeof claim.evidence === 'string') return claim.evidence;
|
|
101
|
-
if (typeof claim.evidence === 'object' && claim.evidence !== null) {
|
|
102
|
-
return claim.evidence.tier || '';
|
|
103
|
-
}
|
|
104
|
-
return claim.evidence_tier || '';
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function extractSource(claim) {
|
|
108
|
-
if (typeof claim.source === 'string') return claim.source;
|
|
109
|
-
if (typeof claim.source === 'object' && claim.source !== null) {
|
|
110
|
-
return claim.source.origin || claim.source.artifact || '';
|
|
111
|
-
}
|
|
112
|
-
if (typeof claim.evidence === 'object' && claim.evidence?.source) {
|
|
113
|
-
return claim.evidence.source;
|
|
114
|
-
}
|
|
115
|
-
return '';
|
|
116
|
-
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* mill format: typescript-defs
|
|
3
|
-
*
|
|
4
|
-
* Converts compilation.json to TypeScript .d.ts definitions.
|
|
5
|
-
* Derives union types from actual claim data values.
|
|
6
|
-
* Zero dependencies — node built-in only.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export const name = 'typescript-defs';
|
|
10
|
-
export const extension = '.d.ts';
|
|
11
|
-
export const mimeType = 'text/plain; charset=utf-8';
|
|
12
|
-
export const description = 'TypeScript type definitions derived from compilation data';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Convert a compilation object to TypeScript .d.ts definitions.
|
|
16
|
-
* @param {object} compilation - The compilation.json content
|
|
17
|
-
* @returns {string} TypeScript definition output
|
|
18
|
-
*/
|
|
19
|
-
export function convert(compilation) {
|
|
20
|
-
const claims = compilation.claims || [];
|
|
21
|
-
const meta = compilation.meta || {};
|
|
22
|
-
const conflicts = compilation.conflicts || [];
|
|
23
|
-
const certificate = compilation.certificate || {};
|
|
24
|
-
|
|
25
|
-
const claimTypes = uniqueSorted(claims.map(c => c.type).filter(Boolean));
|
|
26
|
-
const evidenceTiers = uniqueSorted(
|
|
27
|
-
claims.map(c => {
|
|
28
|
-
if (typeof c.evidence === 'string') return c.evidence;
|
|
29
|
-
return c.evidence?.tier ?? c.evidence_tier ?? null;
|
|
30
|
-
}).filter(Boolean)
|
|
31
|
-
);
|
|
32
|
-
const statuses = uniqueSorted(claims.map(c => c.status).filter(Boolean));
|
|
33
|
-
|
|
34
|
-
const lines = [];
|
|
35
|
-
|
|
36
|
-
lines.push('// Auto-generated by mill typescript-defs format');
|
|
37
|
-
lines.push('// Derived from compilation data — do not edit manually');
|
|
38
|
-
lines.push('');
|
|
39
|
-
|
|
40
|
-
// ClaimType union
|
|
41
|
-
lines.push(`export type ClaimType = ${unionType(claimTypes)};`);
|
|
42
|
-
lines.push('');
|
|
43
|
-
|
|
44
|
-
// EvidenceTier union
|
|
45
|
-
lines.push(`export type EvidenceTier = ${unionType(evidenceTiers)};`);
|
|
46
|
-
lines.push('');
|
|
47
|
-
|
|
48
|
-
// ClaimStatus union
|
|
49
|
-
lines.push(`export type ClaimStatus = ${unionType(statuses)};`);
|
|
50
|
-
lines.push('');
|
|
51
|
-
|
|
52
|
-
// Evidence interface
|
|
53
|
-
lines.push('export interface Evidence {');
|
|
54
|
-
lines.push(' tier: EvidenceTier;');
|
|
55
|
-
lines.push(' source?: string;');
|
|
56
|
-
lines.push('}');
|
|
57
|
-
lines.push('');
|
|
58
|
-
|
|
59
|
-
// Source interface
|
|
60
|
-
lines.push('export interface ClaimSource {');
|
|
61
|
-
lines.push(' origin?: string;');
|
|
62
|
-
lines.push(' artifact?: string;');
|
|
63
|
-
lines.push('}');
|
|
64
|
-
lines.push('');
|
|
65
|
-
|
|
66
|
-
// Claim interface
|
|
67
|
-
lines.push('export interface Claim {');
|
|
68
|
-
lines.push(' id: string;');
|
|
69
|
-
lines.push(' type: ClaimType;');
|
|
70
|
-
lines.push(' content: string;');
|
|
71
|
-
lines.push(' evidence: EvidenceTier | Evidence;');
|
|
72
|
-
lines.push(' status: ClaimStatus;');
|
|
73
|
-
lines.push(' confidence?: number;');
|
|
74
|
-
lines.push(' source?: string | ClaimSource;');
|
|
75
|
-
lines.push(' tags?: string[];');
|
|
76
|
-
lines.push(' created?: string;');
|
|
77
|
-
lines.push('}');
|
|
78
|
-
lines.push('');
|
|
79
|
-
|
|
80
|
-
// Meta interface
|
|
81
|
-
lines.push('export interface Meta {');
|
|
82
|
-
lines.push(' sprint?: string;');
|
|
83
|
-
lines.push(' question?: string;');
|
|
84
|
-
lines.push(' audience?: string;');
|
|
85
|
-
for (const key of Object.keys(meta)) {
|
|
86
|
-
if (!['sprint', 'question', 'audience'].includes(key)) {
|
|
87
|
-
lines.push(` ${sanitizeKey(key)}?: ${inferTsType(meta[key])};`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
lines.push('}');
|
|
91
|
-
lines.push('');
|
|
92
|
-
|
|
93
|
-
// Conflict interface
|
|
94
|
-
lines.push('export interface Conflict {');
|
|
95
|
-
lines.push(' ids?: string[];');
|
|
96
|
-
lines.push(' description?: string;');
|
|
97
|
-
lines.push(' reason?: string;');
|
|
98
|
-
lines.push(' resolution?: string;');
|
|
99
|
-
lines.push('}');
|
|
100
|
-
lines.push('');
|
|
101
|
-
|
|
102
|
-
// Certificate interface
|
|
103
|
-
lines.push('export interface Certificate {');
|
|
104
|
-
lines.push(' compiled_at?: string;');
|
|
105
|
-
lines.push(' sha256?: string;');
|
|
106
|
-
lines.push(' claim_count?: number;');
|
|
107
|
-
for (const key of Object.keys(certificate)) {
|
|
108
|
-
if (!['compiled_at', 'sha256', 'claim_count'].includes(key)) {
|
|
109
|
-
lines.push(` ${sanitizeKey(key)}?: ${inferTsType(certificate[key])};`);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
lines.push('}');
|
|
113
|
-
lines.push('');
|
|
114
|
-
|
|
115
|
-
// Compilation interface
|
|
116
|
-
lines.push('export interface Compilation {');
|
|
117
|
-
lines.push(' meta: Meta;');
|
|
118
|
-
lines.push(' claims: Claim[];');
|
|
119
|
-
lines.push(' conflicts: Conflict[];');
|
|
120
|
-
lines.push(' certificate: Certificate;');
|
|
121
|
-
lines.push('}');
|
|
122
|
-
lines.push('');
|
|
123
|
-
|
|
124
|
-
return lines.join('\n');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function uniqueSorted(arr) {
|
|
128
|
-
return [...new Set(arr)].sort();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function unionType(values) {
|
|
132
|
-
if (values.length === 0) return 'string';
|
|
133
|
-
return values.map(v => `'${v}'`).join(' | ');
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function sanitizeKey(key) {
|
|
137
|
-
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `'${key}'`;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function inferTsType(value) {
|
|
141
|
-
if (value === null || value === undefined) return 'unknown';
|
|
142
|
-
if (typeof value === 'number') return 'number';
|
|
143
|
-
if (typeof value === 'boolean') return 'boolean';
|
|
144
|
-
if (typeof value === 'string') return 'string';
|
|
145
|
-
if (Array.isArray(value)) return 'unknown[]';
|
|
146
|
-
return 'Record<string, unknown>';
|
|
147
|
-
}
|