@grainulation/mill 1.0.0 → 1.0.1
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.js +41 -34
- package/lib/formats/changelog.js +27 -26
- package/lib/formats/confluence-adf.js +312 -0
- package/lib/formats/csv.js +41 -37
- package/lib/formats/dot.js +45 -34
- package/lib/formats/evidence-matrix.js +17 -16
- package/lib/formats/executive-summary.js +89 -41
- package/lib/formats/github-issues.js +40 -33
- package/lib/formats/graphml.js +45 -32
- package/lib/formats/html-report.js +110 -63
- package/lib/formats/jira-csv.js +30 -29
- package/lib/formats/json-ld.js +6 -6
- package/lib/formats/markdown.js +53 -36
- package/lib/formats/ndjson.js +6 -6
- package/lib/formats/obsidian.js +43 -35
- package/lib/formats/opml.js +38 -28
- package/lib/formats/ris.js +29 -23
- package/lib/formats/rss.js +31 -28
- package/lib/formats/sankey.js +16 -15
- package/lib/formats/slide-deck.js +145 -57
- package/lib/formats/sql.js +57 -53
- package/lib/formats/static-site.js +64 -52
- package/lib/formats/treemap.js +16 -15
- package/lib/formats/typescript-defs.js +79 -76
- package/lib/formats/yaml.js +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 +7 -3
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Self-contained HTML with CSS scroll-snap. One slide per claim type group.
|
|
5
5
|
* Dark theme matching grainulation design tokens.
|
|
6
|
+
* Accessible: slide landmarks, aria-roledescription, live region, reduced-motion.
|
|
6
7
|
* Zero dependencies — node built-in only.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
export const name =
|
|
10
|
-
export const extension =
|
|
11
|
-
export const mimeType =
|
|
12
|
-
export const description =
|
|
10
|
+
export const name = "slide-deck";
|
|
11
|
+
export const extension = ".html";
|
|
12
|
+
export const mimeType = "text/html; charset=utf-8";
|
|
13
|
+
export const description =
|
|
14
|
+
"Scroll-snap slide deck: one slide per type group with keyboard navigation";
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Convert a compilation object to a slide deck HTML page.
|
|
@@ -22,60 +24,76 @@ export function convert(compilation) {
|
|
|
22
24
|
const conflicts = compilation.conflicts || [];
|
|
23
25
|
const certificate = compilation.certificate || {};
|
|
24
26
|
|
|
25
|
-
const title = meta.sprint || meta.question ||
|
|
27
|
+
const title = meta.sprint || meta.question || "Sprint Deck";
|
|
26
28
|
const compiled = certificate.compiled_at || new Date().toISOString();
|
|
27
|
-
const active = claims.filter(c => c.status ===
|
|
29
|
+
const active = claims.filter((c) => c.status === "active").length;
|
|
28
30
|
|
|
29
31
|
// Group by type (skip reverted)
|
|
30
32
|
const byType = {};
|
|
31
33
|
for (const c of claims) {
|
|
32
|
-
if (c.status ===
|
|
33
|
-
const t = c.type ||
|
|
34
|
+
if (c.status === "reverted") continue;
|
|
35
|
+
const t = c.type || "unknown";
|
|
34
36
|
if (!byType[t]) byType[t] = [];
|
|
35
37
|
byType[t].push(c);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
const typeOrder = [
|
|
39
|
-
|
|
40
|
+
const typeOrder = [
|
|
41
|
+
"constraint",
|
|
42
|
+
"factual",
|
|
43
|
+
"recommendation",
|
|
44
|
+
"risk",
|
|
45
|
+
"estimate",
|
|
46
|
+
"feedback",
|
|
47
|
+
];
|
|
48
|
+
const sortedTypes = typeOrder.filter((t) => byType[t]);
|
|
40
49
|
for (const t of Object.keys(byType)) {
|
|
41
50
|
if (!sortedTypes.includes(t)) sortedTypes.push(t);
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
const typeColors = {
|
|
45
|
-
constraint:
|
|
46
|
-
factual:
|
|
47
|
-
recommendation:
|
|
48
|
-
risk:
|
|
49
|
-
estimate:
|
|
50
|
-
feedback:
|
|
51
|
-
unknown:
|
|
54
|
+
constraint: "#e74c3c",
|
|
55
|
+
factual: "#3498db",
|
|
56
|
+
recommendation: "#2ecc71",
|
|
57
|
+
risk: "#f39c12",
|
|
58
|
+
estimate: "#9b59b6",
|
|
59
|
+
feedback: "#1abc9c",
|
|
60
|
+
unknown: "#95a5a6",
|
|
52
61
|
};
|
|
53
62
|
|
|
63
|
+
let slideNum = 0;
|
|
64
|
+
// Calculate total slides: title + summary + types + optional conflicts + certificate
|
|
65
|
+
const totalSlides =
|
|
66
|
+
2 + sortedTypes.length + (conflicts.length > 0 ? 1 : 0) + 1;
|
|
67
|
+
|
|
54
68
|
// Title slide
|
|
55
69
|
const slides = [];
|
|
70
|
+
slideNum++;
|
|
56
71
|
slides.push(`
|
|
57
|
-
<section class="slide">
|
|
72
|
+
<section class="slide" role="group" aria-roledescription="slide" aria-label="Slide ${slideNum} of ${totalSlides}: Title">
|
|
58
73
|
<div class="slide-content title-slide">
|
|
59
74
|
<h1>${esc(title)}</h1>
|
|
60
|
-
${meta.question ? `<p class="question">${esc(meta.question)}</p>` :
|
|
75
|
+
${meta.question ? `<p class="question">${esc(meta.question)}</p>` : ""}
|
|
61
76
|
<p class="meta">${esc(compiled)} | ${claims.length} claims</p>
|
|
62
77
|
</div>
|
|
63
78
|
</section>`);
|
|
64
79
|
|
|
65
80
|
// Summary slide
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
slideNum++;
|
|
82
|
+
const typeStats = sortedTypes
|
|
83
|
+
.map((t) => {
|
|
84
|
+
const color = typeColors[t] || typeColors.unknown;
|
|
85
|
+
return `<div class="type-stat"><span class="dot" style="background:${color}" aria-hidden="true"></span>${capitalize(t)}: ${byType[t].length}</div>`;
|
|
86
|
+
})
|
|
87
|
+
.join("\n ");
|
|
70
88
|
|
|
71
89
|
slides.push(`
|
|
72
|
-
<section class="slide">
|
|
90
|
+
<section class="slide" role="group" aria-roledescription="slide" aria-label="Slide ${slideNum} of ${totalSlides}: Summary">
|
|
73
91
|
<div class="slide-content">
|
|
74
92
|
<h2>Summary</h2>
|
|
75
93
|
<div class="summary-grid">
|
|
76
94
|
<div class="big-stat"><span class="num">${claims.length}</span><span class="label">Total</span></div>
|
|
77
95
|
<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>` :
|
|
96
|
+
${conflicts.length ? `<div class="big-stat"><span class="num">${conflicts.length}</span><span class="label">Conflicts</span></div>` : ""}
|
|
79
97
|
</div>
|
|
80
98
|
<div class="type-stats">
|
|
81
99
|
${typeStats}
|
|
@@ -85,16 +103,20 @@ export function convert(compilation) {
|
|
|
85
103
|
|
|
86
104
|
// One slide per type group
|
|
87
105
|
for (const t of sortedTypes) {
|
|
106
|
+
slideNum++;
|
|
88
107
|
const group = byType[t];
|
|
89
108
|
const color = typeColors[t] || typeColors.unknown;
|
|
90
|
-
const items = group
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
109
|
+
const items = group
|
|
110
|
+
.map((c) => {
|
|
111
|
+
const body = esc(c.content || c.text || "");
|
|
112
|
+
const conf =
|
|
113
|
+
c.confidence != null ? ` (${Math.round(c.confidence * 100)}%)` : "";
|
|
114
|
+
return `<li><strong>${esc(c.id)}</strong>${conf}: ${body}</li>`;
|
|
115
|
+
})
|
|
116
|
+
.join("\n ");
|
|
95
117
|
|
|
96
118
|
slides.push(`
|
|
97
|
-
<section class="slide">
|
|
119
|
+
<section class="slide" role="group" aria-roledescription="slide" aria-label="Slide ${slideNum} of ${totalSlides}: ${capitalize(t)}s">
|
|
98
120
|
<div class="slide-content">
|
|
99
121
|
<h2 style="border-left:4px solid ${color};padding-left:12px">${capitalize(t)}s (${group.length})</h2>
|
|
100
122
|
<ul class="claim-list">
|
|
@@ -106,14 +128,17 @@ export function convert(compilation) {
|
|
|
106
128
|
|
|
107
129
|
// Conflicts slide (if any)
|
|
108
130
|
if (conflicts.length > 0) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
131
|
+
slideNum++;
|
|
132
|
+
const conflictItems = conflicts
|
|
133
|
+
.map((c) => {
|
|
134
|
+
const ids = c.ids?.join(" vs ") || "unknown";
|
|
135
|
+
const resolved = c.resolution ? " [resolved]" : "";
|
|
136
|
+
return `<li><strong>${esc(ids)}</strong>${resolved}: ${esc(c.description || c.reason || "")}</li>`;
|
|
137
|
+
})
|
|
138
|
+
.join("\n ");
|
|
114
139
|
|
|
115
140
|
slides.push(`
|
|
116
|
-
<section class="slide">
|
|
141
|
+
<section class="slide" role="group" aria-roledescription="slide" aria-label="Slide ${slideNum} of ${totalSlides}: Conflicts">
|
|
117
142
|
<div class="slide-content">
|
|
118
143
|
<h2 style="border-left:4px solid #e74c3c;padding-left:12px">Conflicts (${conflicts.length})</h2>
|
|
119
144
|
<ul class="claim-list">
|
|
@@ -124,12 +149,13 @@ export function convert(compilation) {
|
|
|
124
149
|
}
|
|
125
150
|
|
|
126
151
|
// Certificate slide
|
|
152
|
+
slideNum++;
|
|
127
153
|
slides.push(`
|
|
128
|
-
<section class="slide">
|
|
154
|
+
<section class="slide" role="group" aria-roledescription="slide" aria-label="Slide ${slideNum} of ${totalSlides}: Certificate">
|
|
129
155
|
<div class="slide-content title-slide">
|
|
130
156
|
<h2>Certificate</h2>
|
|
131
157
|
<p class="mono">${certificate.claim_count || claims.length} claims</p>
|
|
132
|
-
<p class="mono">sha256:${esc((certificate.sha256 ||
|
|
158
|
+
<p class="mono">sha256:${esc((certificate.sha256 || "unknown").slice(0, 24))}</p>
|
|
133
159
|
<p class="meta">${esc(compiled)}</p>
|
|
134
160
|
</div>
|
|
135
161
|
</section>`);
|
|
@@ -145,7 +171,10 @@ export function convert(compilation) {
|
|
|
145
171
|
* { margin:0; padding:0; box-sizing:border-box; }
|
|
146
172
|
html { scroll-snap-type:y mandatory; overflow-y:scroll; }
|
|
147
173
|
body { background:var(--bg); color:var(--text); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; line-height:1.6; }
|
|
174
|
+
a.skip-nav { position:absolute; top:-40px; left:0; background:var(--surface); color:var(--text); padding:0.5rem 1rem; z-index:100; border-radius:0 0 4px 0; text-decoration:none; font-size:0.9rem; }
|
|
175
|
+
a.skip-nav:focus { top:0; outline:2px solid #3b82f6; }
|
|
148
176
|
.slide { scroll-snap-align:start; height:100vh; display:flex; align-items:center; justify-content:center; padding:2rem; }
|
|
177
|
+
.slide:focus { outline:2px solid #3b82f6; outline-offset:-2px; }
|
|
149
178
|
.slide-content { max-width:800px; width:100%; }
|
|
150
179
|
.title-slide { text-align:center; }
|
|
151
180
|
.title-slide h1 { font-size:2.4rem; margin-bottom:0.75rem; }
|
|
@@ -164,35 +193,94 @@ export function convert(compilation) {
|
|
|
164
193
|
.claim-list { list-style:none; max-height:70vh; overflow-y:auto; }
|
|
165
194
|
.claim-list li { padding:0.6rem 0; border-bottom:1px solid var(--border); font-size:0.9rem; }
|
|
166
195
|
.claim-list li strong { font-family:monospace; font-size:0.8rem; }
|
|
196
|
+
.slide-counter { position:fixed; bottom:1rem; right:1rem; background:var(--surface); color:var(--muted); padding:0.25rem 0.6rem; border-radius:4px; font-size:0.75rem; font-family:monospace; z-index:10; }
|
|
197
|
+
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }
|
|
198
|
+
.nav-hint { position:fixed; bottom:1rem; left:1rem; background:var(--surface); color:var(--muted); padding:0.25rem 0.6rem; border-radius:4px; font-size:0.7rem; font-family:monospace; z-index:10; opacity:0.6; }
|
|
199
|
+
@media (prefers-reduced-motion:reduce) { html { scroll-snap-type:none; scroll-behavior:auto; } }
|
|
200
|
+
@media print { .skip-nav, .slide-counter, .nav-hint { display:none; } .slide { height:auto; page-break-after:always; } }
|
|
167
201
|
</style>
|
|
168
202
|
</head>
|
|
169
203
|
<body>
|
|
170
|
-
|
|
204
|
+
<a class="skip-nav" href="#slide-1">Skip to first slide</a>
|
|
205
|
+
<main role="main" aria-label="Slide deck: ${esc(title)}">
|
|
206
|
+
${slides.join("\n")}
|
|
207
|
+
</main>
|
|
208
|
+
<div class="slide-counter" aria-live="polite" aria-atomic="true"></div>
|
|
209
|
+
<div class="nav-hint" aria-hidden="true">Arrow keys / PgUp / PgDn / Home / End to navigate</div>
|
|
171
210
|
<script>
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
211
|
+
(function() {
|
|
212
|
+
var slides = document.querySelectorAll('.slide');
|
|
213
|
+
var counter = document.querySelector('.slide-counter');
|
|
214
|
+
var total = slides.length;
|
|
215
|
+
var prefersReduced = window.matchMedia('(prefers-reduced-motion:reduce)').matches;
|
|
216
|
+
var scrollBehavior = prefersReduced ? 'auto' : 'smooth';
|
|
217
|
+
|
|
218
|
+
// Give slides tabindex for focus management
|
|
219
|
+
slides.forEach(function(slide, i) {
|
|
220
|
+
slide.id = slide.id || 'slide-' + (i + 1);
|
|
221
|
+
slide.setAttribute('tabindex', '-1');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
function currentSlideIndex() {
|
|
225
|
+
var vh = window.innerHeight;
|
|
226
|
+
return Math.round(window.scrollY / vh);
|
|
186
227
|
}
|
|
187
|
-
|
|
228
|
+
|
|
229
|
+
function goToSlide(index) {
|
|
230
|
+
var target = Math.max(0, Math.min(index, slides.length - 1));
|
|
231
|
+
slides[target].scrollIntoView({ behavior: scrollBehavior });
|
|
232
|
+
slides[target].focus({ preventScroll: true });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function updateCounter() {
|
|
236
|
+
var current = currentSlideIndex() + 1;
|
|
237
|
+
counter.textContent = current + ' / ' + total;
|
|
238
|
+
}
|
|
239
|
+
updateCounter();
|
|
240
|
+
window.addEventListener('scroll', updateCounter, { passive: true });
|
|
241
|
+
|
|
242
|
+
document.addEventListener('keydown', function(e) {
|
|
243
|
+
var current = currentSlideIndex();
|
|
244
|
+
switch (e.key) {
|
|
245
|
+
case 'ArrowDown':
|
|
246
|
+
case 'PageDown':
|
|
247
|
+
case 'ArrowRight':
|
|
248
|
+
e.preventDefault();
|
|
249
|
+
goToSlide(current + 1);
|
|
250
|
+
break;
|
|
251
|
+
case 'ArrowUp':
|
|
252
|
+
case 'PageUp':
|
|
253
|
+
case 'ArrowLeft':
|
|
254
|
+
e.preventDefault();
|
|
255
|
+
goToSlide(current - 1);
|
|
256
|
+
break;
|
|
257
|
+
case 'Home':
|
|
258
|
+
e.preventDefault();
|
|
259
|
+
goToSlide(0);
|
|
260
|
+
break;
|
|
261
|
+
case 'End':
|
|
262
|
+
e.preventDefault();
|
|
263
|
+
goToSlide(slides.length - 1);
|
|
264
|
+
break;
|
|
265
|
+
case 'Escape':
|
|
266
|
+
// Move focus to body (exit slide focus)
|
|
267
|
+
document.body.focus();
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
})();
|
|
188
272
|
</script>
|
|
189
273
|
</body>
|
|
190
274
|
</html>`;
|
|
191
275
|
}
|
|
192
276
|
|
|
193
277
|
function esc(str) {
|
|
194
|
-
if (str == null) return
|
|
195
|
-
return String(str)
|
|
278
|
+
if (str == null) return "";
|
|
279
|
+
return String(str)
|
|
280
|
+
.replace(/&/g, "&")
|
|
281
|
+
.replace(/</g, "<")
|
|
282
|
+
.replace(/>/g, ">")
|
|
283
|
+
.replace(/"/g, """);
|
|
196
284
|
}
|
|
197
285
|
|
|
198
286
|
function capitalize(str) {
|
package/lib/formats/sql.js
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
* Zero dependencies — node built-in only.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
export const name =
|
|
10
|
-
export const extension =
|
|
11
|
-
export const mimeType =
|
|
12
|
-
export const description =
|
|
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
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Convert a compilation object to SQL statements.
|
|
@@ -23,94 +23,98 @@ export function convert(compilation) {
|
|
|
23
23
|
|
|
24
24
|
const lines = [];
|
|
25
25
|
|
|
26
|
-
lines.push(
|
|
26
|
+
lines.push("-- Auto-generated by mill sql format");
|
|
27
27
|
if (meta.sprint) lines.push(`-- Sprint: ${meta.sprint}`);
|
|
28
|
-
if (certificate.compiled_at)
|
|
29
|
-
|
|
28
|
+
if (certificate.compiled_at)
|
|
29
|
+
lines.push(`-- Compiled: ${certificate.compiled_at}`);
|
|
30
|
+
lines.push("");
|
|
30
31
|
|
|
31
32
|
// Claims table
|
|
32
|
-
lines.push(
|
|
33
|
-
lines.push(
|
|
34
|
-
lines.push(
|
|
35
|
-
lines.push(
|
|
36
|
-
lines.push(
|
|
37
|
-
lines.push(
|
|
38
|
-
lines.push(
|
|
39
|
-
lines.push(
|
|
40
|
-
lines.push(
|
|
41
|
-
lines.push(
|
|
42
|
-
lines.push(
|
|
43
|
-
lines.push(
|
|
33
|
+
lines.push("CREATE TABLE IF NOT EXISTS claims (");
|
|
34
|
+
lines.push(" id TEXT PRIMARY KEY,");
|
|
35
|
+
lines.push(" type TEXT,");
|
|
36
|
+
lines.push(" content TEXT,");
|
|
37
|
+
lines.push(" evidence_tier TEXT,");
|
|
38
|
+
lines.push(" status TEXT,");
|
|
39
|
+
lines.push(" confidence REAL,");
|
|
40
|
+
lines.push(" source TEXT,");
|
|
41
|
+
lines.push(" tags TEXT,");
|
|
42
|
+
lines.push(" created TEXT");
|
|
43
|
+
lines.push(");");
|
|
44
|
+
lines.push("");
|
|
44
45
|
|
|
45
46
|
// Conflicts table
|
|
46
|
-
lines.push(
|
|
47
|
-
lines.push(
|
|
48
|
-
lines.push(
|
|
49
|
-
lines.push(
|
|
50
|
-
lines.push(
|
|
51
|
-
lines.push(
|
|
52
|
-
lines.push(
|
|
47
|
+
lines.push("CREATE TABLE IF NOT EXISTS conflicts (");
|
|
48
|
+
lines.push(" id INTEGER PRIMARY KEY AUTOINCREMENT,");
|
|
49
|
+
lines.push(" claim_ids TEXT,");
|
|
50
|
+
lines.push(" description TEXT,");
|
|
51
|
+
lines.push(" resolution TEXT");
|
|
52
|
+
lines.push(");");
|
|
53
|
+
lines.push("");
|
|
53
54
|
|
|
54
55
|
// Insert claims
|
|
55
56
|
if (claims.length > 0) {
|
|
56
|
-
lines.push(
|
|
57
|
+
lines.push("-- Claims");
|
|
57
58
|
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 ||
|
|
59
|
+
const id = esc(claim.id || "");
|
|
60
|
+
const type = esc(claim.type || "");
|
|
61
|
+
const content = esc(claim.content || claim.text || "");
|
|
61
62
|
const evidenceTier = esc(extractTier(claim));
|
|
62
|
-
const status = esc(claim.status ||
|
|
63
|
-
const confidence =
|
|
63
|
+
const status = esc(claim.status || "");
|
|
64
|
+
const confidence =
|
|
65
|
+
claim.confidence != null ? String(claim.confidence) : "NULL";
|
|
64
66
|
const source = esc(extractSource(claim));
|
|
65
|
-
const tags = esc(Array.isArray(claim.tags) ? claim.tags.join(
|
|
66
|
-
const created = esc(claim.created ||
|
|
67
|
+
const tags = esc(Array.isArray(claim.tags) ? claim.tags.join(",") : "");
|
|
68
|
+
const created = esc(claim.created || "");
|
|
67
69
|
|
|
68
70
|
lines.push(
|
|
69
|
-
`INSERT INTO claims VALUES ('${id}', '${type}', '${content}', '${evidenceTier}', '${status}', ${confidence}, '${source}', '${tags}', '${created}')
|
|
71
|
+
`INSERT INTO claims VALUES ('${id}', '${type}', '${content}', '${evidenceTier}', '${status}', ${confidence}, '${source}', '${tags}', '${created}');`,
|
|
70
72
|
);
|
|
71
73
|
}
|
|
72
|
-
lines.push(
|
|
74
|
+
lines.push("");
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
// Insert conflicts
|
|
76
78
|
const conflicts = compilation.conflicts || [];
|
|
77
79
|
if (conflicts.length > 0) {
|
|
78
|
-
lines.push(
|
|
80
|
+
lines.push("-- Conflicts");
|
|
79
81
|
for (const conflict of conflicts) {
|
|
80
|
-
const claimIds = esc(
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
const claimIds = esc(
|
|
83
|
+
Array.isArray(conflict.ids) ? conflict.ids.join(",") : "",
|
|
84
|
+
);
|
|
85
|
+
const description = esc(conflict.description || conflict.reason || "");
|
|
86
|
+
const resolution = esc(conflict.resolution || "");
|
|
83
87
|
|
|
84
88
|
lines.push(
|
|
85
|
-
`INSERT INTO conflicts (claim_ids, description, resolution) VALUES ('${claimIds}', '${description}', '${resolution}')
|
|
89
|
+
`INSERT INTO conflicts (claim_ids, description, resolution) VALUES ('${claimIds}', '${description}', '${resolution}');`,
|
|
86
90
|
);
|
|
87
91
|
}
|
|
88
|
-
lines.push(
|
|
92
|
+
lines.push("");
|
|
89
93
|
}
|
|
90
94
|
|
|
91
|
-
return lines.join(
|
|
95
|
+
return lines.join("\n");
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
function esc(value) {
|
|
95
|
-
if (value == null) return
|
|
99
|
+
if (value == null) return "";
|
|
96
100
|
return String(value).replace(/'/g, "''");
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
function extractTier(claim) {
|
|
100
|
-
if (typeof claim.evidence ===
|
|
101
|
-
if (typeof claim.evidence ===
|
|
102
|
-
return claim.evidence.tier ||
|
|
104
|
+
if (typeof claim.evidence === "string") return claim.evidence;
|
|
105
|
+
if (typeof claim.evidence === "object" && claim.evidence !== null) {
|
|
106
|
+
return claim.evidence.tier || "";
|
|
103
107
|
}
|
|
104
|
-
return claim.evidence_tier ||
|
|
108
|
+
return claim.evidence_tier || "";
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
function extractSource(claim) {
|
|
108
|
-
if (typeof claim.source ===
|
|
109
|
-
if (typeof claim.source ===
|
|
110
|
-
return claim.source.origin || claim.source.artifact ||
|
|
112
|
+
if (typeof claim.source === "string") return claim.source;
|
|
113
|
+
if (typeof claim.source === "object" && claim.source !== null) {
|
|
114
|
+
return claim.source.origin || claim.source.artifact || "";
|
|
111
115
|
}
|
|
112
|
-
if (typeof claim.evidence ===
|
|
116
|
+
if (typeof claim.evidence === "object" && claim.evidence?.source) {
|
|
113
117
|
return claim.evidence.source;
|
|
114
118
|
}
|
|
115
|
-
return
|
|
119
|
+
return "";
|
|
116
120
|
}
|