@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
|
@@ -6,18 +6,19 @@
|
|
|
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 = "jira-csv";
|
|
10
|
+
export const extension = ".csv";
|
|
11
|
+
export const mimeType = "text/csv; charset=utf-8";
|
|
12
|
+
export const description =
|
|
13
|
+
"Claims as Jira-compatible CSV (Summary, Description, Issue Type, Priority, Labels, Status)";
|
|
13
14
|
|
|
14
15
|
const COLUMNS = [
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
"Summary",
|
|
17
|
+
"Description",
|
|
18
|
+
"Issue Type",
|
|
19
|
+
"Priority",
|
|
20
|
+
"Labels",
|
|
21
|
+
"Status",
|
|
21
22
|
];
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -29,23 +30,23 @@ export function convert(compilation) {
|
|
|
29
30
|
const claims = compilation.claims || [];
|
|
30
31
|
|
|
31
32
|
if (claims.length === 0) {
|
|
32
|
-
return COLUMNS.join(
|
|
33
|
+
return COLUMNS.join(",") + "\n";
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
const header = COLUMNS.join(
|
|
36
|
+
const header = COLUMNS.join(",");
|
|
36
37
|
const rows = claims.map(claimToRow);
|
|
37
38
|
|
|
38
|
-
return [header, ...rows].join(
|
|
39
|
+
return [header, ...rows].join("\n") + "\n";
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
function claimToRow(claim) {
|
|
42
|
-
const type = claim.type ||
|
|
43
|
-
const content = claim.content || claim.text ||
|
|
44
|
-
const summary = `${claim.id ||
|
|
43
|
+
const type = claim.type || "";
|
|
44
|
+
const content = claim.content || claim.text || "";
|
|
45
|
+
const summary = `${claim.id || ""}: ${truncate(content, 120)}`;
|
|
45
46
|
const description = content;
|
|
46
47
|
const { issueType, priority } = mapTypeToJira(type);
|
|
47
|
-
const tags = Array.isArray(claim.tags) ? claim.tags.join(
|
|
48
|
-
const status = claim.status ||
|
|
48
|
+
const tags = Array.isArray(claim.tags) ? claim.tags.join(" ") : "";
|
|
49
|
+
const status = claim.status || "";
|
|
49
50
|
|
|
50
51
|
return [
|
|
51
52
|
escapeField(summary),
|
|
@@ -54,35 +55,35 @@ function claimToRow(claim) {
|
|
|
54
55
|
escapeField(priority),
|
|
55
56
|
escapeField(tags),
|
|
56
57
|
escapeField(status),
|
|
57
|
-
].join(
|
|
58
|
+
].join(",");
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
function mapTypeToJira(type) {
|
|
61
62
|
switch (type) {
|
|
62
|
-
case
|
|
63
|
-
return { issueType:
|
|
64
|
-
case
|
|
65
|
-
return { issueType:
|
|
66
|
-
case
|
|
67
|
-
return { issueType:
|
|
63
|
+
case "risk":
|
|
64
|
+
return { issueType: "Bug", priority: "High" };
|
|
65
|
+
case "recommendation":
|
|
66
|
+
return { issueType: "Task", priority: "Medium" };
|
|
67
|
+
case "constraint":
|
|
68
|
+
return { issueType: "Task", priority: "High" };
|
|
68
69
|
default:
|
|
69
|
-
return { issueType:
|
|
70
|
+
return { issueType: "Task", priority: "Low" };
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
function truncate(str, max) {
|
|
74
75
|
if (str.length <= max) return str;
|
|
75
|
-
return str.slice(0, max - 3) +
|
|
76
|
+
return str.slice(0, max - 3) + "...";
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
function escapeField(value) {
|
|
79
|
-
if (value == null) return
|
|
80
|
+
if (value == null) return "";
|
|
80
81
|
let str = String(value);
|
|
81
82
|
// CWE-1236: Prevent CSV injection by prefixing formula-triggering characters
|
|
82
83
|
if (/^[=+\-@\t\r]/.test(str)) {
|
|
83
84
|
str = "'" + str;
|
|
84
85
|
}
|
|
85
|
-
if (str.includes(
|
|
86
|
+
if (str.includes(",") || str.includes('"') || str.includes("\n")) {
|
|
86
87
|
return `"${str.replace(/"/g, '""')}"`;
|
|
87
88
|
}
|
|
88
89
|
return str;
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* Zero dependencies — node built-in only.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { createRequire } from
|
|
8
|
+
import { createRequire } from "node:module";
|
|
9
9
|
const require = createRequire(import.meta.url);
|
|
10
|
-
const { buildReport } = require(
|
|
10
|
+
const { buildReport } = require("../json-ld-common.js");
|
|
11
11
|
|
|
12
|
-
export const name =
|
|
13
|
-
export const extension =
|
|
14
|
-
export const mimeType =
|
|
15
|
-
export const description =
|
|
12
|
+
export const name = "json-ld";
|
|
13
|
+
export const extension = ".jsonld";
|
|
14
|
+
export const mimeType = "application/ld+json; charset=utf-8";
|
|
15
|
+
export const description = "JSON-LD semantic export (schema.org/Report)";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Convert a compilation object to JSON-LD.
|
|
@@ -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 = "markdown";
|
|
10
|
+
export const extension = ".md";
|
|
11
|
+
export const mimeType = "text/markdown; charset=utf-8";
|
|
12
|
+
export const description = "Clean Markdown document from compilation data";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Convert a compilation object to Markdown.
|
|
@@ -24,48 +24,55 @@ export function convert(compilation) {
|
|
|
24
24
|
const certificate = compilation.certificate || {};
|
|
25
25
|
|
|
26
26
|
// Title
|
|
27
|
-
const title = meta.sprint || meta.question ||
|
|
27
|
+
const title = meta.sprint || meta.question || "Sprint Export";
|
|
28
28
|
lines.push(`# ${title}`);
|
|
29
|
-
lines.push(
|
|
29
|
+
lines.push("");
|
|
30
30
|
|
|
31
31
|
// Meta block
|
|
32
32
|
if (meta.question) {
|
|
33
33
|
lines.push(`> **Question:** ${meta.question}`);
|
|
34
|
-
lines.push(
|
|
34
|
+
lines.push("");
|
|
35
35
|
}
|
|
36
36
|
if (meta.audience) {
|
|
37
37
|
lines.push(`**Audience:** ${meta.audience}`);
|
|
38
|
-
lines.push(
|
|
38
|
+
lines.push("");
|
|
39
39
|
}
|
|
40
40
|
if (certificate.compiled_at) {
|
|
41
41
|
lines.push(`*Compiled: ${certificate.compiled_at}*`);
|
|
42
|
-
lines.push(
|
|
42
|
+
lines.push("");
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// Summary stats
|
|
46
|
-
const active = claims.filter(c => c.status ===
|
|
47
|
-
const superseded = claims.filter(c => c.status ===
|
|
48
|
-
const reverted = claims.filter(c => c.status ===
|
|
49
|
-
lines.push(
|
|
50
|
-
lines.push(
|
|
46
|
+
const active = claims.filter((c) => c.status === "active").length;
|
|
47
|
+
const superseded = claims.filter((c) => c.status === "superseded").length;
|
|
48
|
+
const reverted = claims.filter((c) => c.status === "reverted").length;
|
|
49
|
+
lines.push("## Summary");
|
|
50
|
+
lines.push("");
|
|
51
51
|
lines.push(`- **Total claims:** ${claims.length}`);
|
|
52
52
|
lines.push(`- **Active:** ${active}`);
|
|
53
53
|
if (superseded) lines.push(`- **Superseded:** ${superseded}`);
|
|
54
54
|
if (reverted) lines.push(`- **Reverted:** ${reverted}`);
|
|
55
55
|
if (conflicts.length) lines.push(`- **Conflicts:** ${conflicts.length}`);
|
|
56
|
-
lines.push(
|
|
56
|
+
lines.push("");
|
|
57
57
|
|
|
58
58
|
// Group claims by type
|
|
59
59
|
const byType = {};
|
|
60
60
|
for (const claim of claims) {
|
|
61
|
-
if (claim.status ===
|
|
62
|
-
const type = claim.type ||
|
|
61
|
+
if (claim.status === "reverted") continue;
|
|
62
|
+
const type = claim.type || "unknown";
|
|
63
63
|
if (!byType[type]) byType[type] = [];
|
|
64
64
|
byType[type].push(claim);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const typeOrder = [
|
|
68
|
-
|
|
67
|
+
const typeOrder = [
|
|
68
|
+
"constraint",
|
|
69
|
+
"factual",
|
|
70
|
+
"recommendation",
|
|
71
|
+
"risk",
|
|
72
|
+
"estimate",
|
|
73
|
+
"feedback",
|
|
74
|
+
];
|
|
75
|
+
const sortedTypes = typeOrder.filter((t) => byType[t]);
|
|
69
76
|
for (const t of Object.keys(byType)) {
|
|
70
77
|
if (!sortedTypes.includes(t)) sortedTypes.push(t);
|
|
71
78
|
}
|
|
@@ -75,42 +82,52 @@ export function convert(compilation) {
|
|
|
75
82
|
if (!group || group.length === 0) continue;
|
|
76
83
|
|
|
77
84
|
lines.push(`## ${capitalize(type)}s (${group.length})`);
|
|
78
|
-
lines.push(
|
|
85
|
+
lines.push("");
|
|
79
86
|
|
|
80
87
|
for (const claim of group) {
|
|
81
|
-
const conf =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
88
|
+
const conf =
|
|
89
|
+
claim.confidence != null
|
|
90
|
+
? ` [${Math.round(claim.confidence * 100)}%]`
|
|
91
|
+
: "";
|
|
92
|
+
const evidenceStr =
|
|
93
|
+
typeof claim.evidence === "string"
|
|
94
|
+
? claim.evidence
|
|
95
|
+
: claim.evidence?.tier;
|
|
96
|
+
const tier = evidenceStr ? ` (${evidenceStr})` : "";
|
|
97
|
+
const status = claim.status === "superseded" ? " ~~superseded~~" : "";
|
|
98
|
+
const body = claim.content || claim.text || "";
|
|
86
99
|
lines.push(`- **${claim.id}**${conf}${tier}${status}: ${body}`);
|
|
87
100
|
}
|
|
88
|
-
lines.push(
|
|
101
|
+
lines.push("");
|
|
89
102
|
}
|
|
90
103
|
|
|
91
104
|
// Conflicts
|
|
92
105
|
if (conflicts.length > 0) {
|
|
93
|
-
lines.push(
|
|
94
|
-
lines.push(
|
|
106
|
+
lines.push("## Conflicts");
|
|
107
|
+
lines.push("");
|
|
95
108
|
for (const conflict of conflicts) {
|
|
96
|
-
const resolved = conflict.resolution ?
|
|
97
|
-
lines.push(
|
|
109
|
+
const resolved = conflict.resolution ? " (resolved)" : "";
|
|
110
|
+
lines.push(
|
|
111
|
+
`- **${conflict.ids?.join(" vs ") || "unknown"}**${resolved}: ${conflict.description || conflict.reason || "No description"}`,
|
|
112
|
+
);
|
|
98
113
|
if (conflict.resolution) {
|
|
99
114
|
lines.push(` - *Resolution:* ${conflict.resolution}`);
|
|
100
115
|
}
|
|
101
116
|
}
|
|
102
|
-
lines.push(
|
|
117
|
+
lines.push("");
|
|
103
118
|
}
|
|
104
119
|
|
|
105
120
|
// Certificate
|
|
106
121
|
if (certificate.sha256 || certificate.claim_count) {
|
|
107
|
-
lines.push(
|
|
108
|
-
lines.push(
|
|
109
|
-
lines.push(
|
|
110
|
-
|
|
122
|
+
lines.push("---");
|
|
123
|
+
lines.push("");
|
|
124
|
+
lines.push(
|
|
125
|
+
`*Certificate: ${certificate.claim_count || claims.length} claims, sha256:${(certificate.sha256 || "unknown").slice(0, 12)}*`,
|
|
126
|
+
);
|
|
127
|
+
lines.push("");
|
|
111
128
|
}
|
|
112
129
|
|
|
113
|
-
return lines.join(
|
|
130
|
+
return lines.join("\n");
|
|
114
131
|
}
|
|
115
132
|
|
|
116
133
|
function capitalize(str) {
|
|
@@ -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 = "ndjson";
|
|
10
|
+
export const extension = ".ndjson";
|
|
11
|
+
export const mimeType = "application/x-ndjson";
|
|
12
|
+
export const description = "Newline-delimited JSON — one claim object per line";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Convert a compilation object to NDJSON.
|
|
@@ -19,7 +19,7 @@ export const description = 'Newline-delimited JSON — one claim object per line
|
|
|
19
19
|
export function convert(compilation) {
|
|
20
20
|
const claims = compilation.claims || [];
|
|
21
21
|
|
|
22
|
-
if (claims.length === 0) return
|
|
22
|
+
if (claims.length === 0) return "";
|
|
23
23
|
|
|
24
|
-
return claims.map(c => JSON.stringify(c)).join(
|
|
24
|
+
return claims.map((c) => JSON.stringify(c)).join("\n") + "\n";
|
|
25
25
|
}
|
|
@@ -6,10 +6,11 @@
|
|
|
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 = "obsidian";
|
|
10
|
+
export const extension = ".md";
|
|
11
|
+
export const mimeType = "text/markdown; charset=utf-8";
|
|
12
|
+
export const description =
|
|
13
|
+
"Claims as Obsidian vault page (YAML front matter, wikilinks, backlinks)";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Convert a compilation object to Obsidian markdown.
|
|
@@ -19,37 +20,44 @@ export const description = 'Claims as Obsidian vault page (YAML front matter, wi
|
|
|
19
20
|
export function convert(compilation) {
|
|
20
21
|
const meta = compilation.meta || {};
|
|
21
22
|
const claims = compilation.claims || [];
|
|
22
|
-
const sprint = meta.sprint ||
|
|
23
|
-
const question = meta.question ||
|
|
24
|
-
const audience = meta.audience ||
|
|
23
|
+
const sprint = meta.sprint || "unknown";
|
|
24
|
+
const question = meta.question || "";
|
|
25
|
+
const audience = meta.audience || "";
|
|
25
26
|
|
|
26
27
|
const grouped = groupByType(claims);
|
|
27
28
|
const lines = [];
|
|
28
29
|
|
|
29
30
|
// YAML front matter
|
|
30
|
-
lines.push(
|
|
31
|
+
lines.push("---");
|
|
31
32
|
lines.push(`sprint: ${sprint}`);
|
|
32
33
|
if (question) lines.push(`question: "${escapeFrontMatter(question)}"`);
|
|
33
34
|
if (audience) lines.push(`audience: "${escapeFrontMatter(audience)}"`);
|
|
34
35
|
lines.push(`claim_count: ${claims.length}`);
|
|
35
|
-
lines.push(
|
|
36
|
-
lines.push(
|
|
37
|
-
lines.push(
|
|
36
|
+
lines.push("tags: [wheat, sprint, export]");
|
|
37
|
+
lines.push("---");
|
|
38
|
+
lines.push("");
|
|
38
39
|
|
|
39
40
|
// Title
|
|
40
41
|
lines.push(`# Sprint: ${sprint}`);
|
|
41
|
-
lines.push(
|
|
42
|
+
lines.push("");
|
|
42
43
|
|
|
43
44
|
if (question) {
|
|
44
45
|
lines.push(`> ${question}`);
|
|
45
|
-
lines.push(
|
|
46
|
+
lines.push("");
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// Claims grouped by type
|
|
49
|
-
lines.push(
|
|
50
|
-
lines.push(
|
|
51
|
-
|
|
52
|
-
const typeOrder = [
|
|
50
|
+
lines.push("## Claims");
|
|
51
|
+
lines.push("");
|
|
52
|
+
|
|
53
|
+
const typeOrder = [
|
|
54
|
+
"constraint",
|
|
55
|
+
"factual",
|
|
56
|
+
"estimate",
|
|
57
|
+
"risk",
|
|
58
|
+
"recommendation",
|
|
59
|
+
"feedback",
|
|
60
|
+
];
|
|
53
61
|
const seen = new Set();
|
|
54
62
|
|
|
55
63
|
for (const type of typeOrder) {
|
|
@@ -66,22 +74,22 @@ export function convert(compilation) {
|
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
// Connections section
|
|
69
|
-
lines.push(
|
|
70
|
-
lines.push(
|
|
71
|
-
lines.push(
|
|
77
|
+
lines.push("## Connections");
|
|
78
|
+
lines.push("");
|
|
79
|
+
lines.push("- Related: [[compilation]] [[claims]]");
|
|
72
80
|
|
|
73
81
|
if (compilation.certificate) {
|
|
74
|
-
lines.push(
|
|
82
|
+
lines.push("- Certificate: [[certificate]]");
|
|
75
83
|
}
|
|
76
84
|
|
|
77
|
-
lines.push(
|
|
78
|
-
return lines.join(
|
|
85
|
+
lines.push("");
|
|
86
|
+
return lines.join("\n") + "\n";
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
function groupByType(claims) {
|
|
82
90
|
const groups = {};
|
|
83
91
|
for (const claim of claims) {
|
|
84
|
-
const type = claim.type ||
|
|
92
|
+
const type = claim.type || "other";
|
|
85
93
|
if (!groups[type]) groups[type] = [];
|
|
86
94
|
groups[type].push(claim);
|
|
87
95
|
}
|
|
@@ -89,14 +97,14 @@ function groupByType(claims) {
|
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
function renderGroup(type, claims) {
|
|
92
|
-
const label = capitalize(type) +
|
|
100
|
+
const label = capitalize(type) + "s";
|
|
93
101
|
const lines = [];
|
|
94
102
|
|
|
95
103
|
lines.push(`### ${label}`);
|
|
96
104
|
|
|
97
105
|
for (const claim of claims) {
|
|
98
|
-
const id = claim.id ||
|
|
99
|
-
const content = claim.content || claim.text ||
|
|
106
|
+
const id = claim.id || "???";
|
|
107
|
+
const content = claim.content || claim.text || "";
|
|
100
108
|
const summary = truncate(content, 120);
|
|
101
109
|
const evidence = getEvidence(claim);
|
|
102
110
|
const tags = formatTags(claim);
|
|
@@ -104,22 +112,22 @@ function renderGroup(type, claims) {
|
|
|
104
112
|
lines.push(`- [[${id}]] ${summary} #${type} #${evidence}${tags}`);
|
|
105
113
|
}
|
|
106
114
|
|
|
107
|
-
lines.push(
|
|
115
|
+
lines.push("");
|
|
108
116
|
return lines;
|
|
109
117
|
}
|
|
110
118
|
|
|
111
119
|
function getEvidence(claim) {
|
|
112
|
-
if (typeof claim.evidence ===
|
|
113
|
-
if (typeof claim.evidence ===
|
|
114
|
-
return claim.evidence.tier || claim.evidence_tier ||
|
|
120
|
+
if (typeof claim.evidence === "string") return claim.evidence;
|
|
121
|
+
if (typeof claim.evidence === "object" && claim.evidence !== null) {
|
|
122
|
+
return claim.evidence.tier || claim.evidence_tier || "stated";
|
|
115
123
|
}
|
|
116
|
-
return claim.evidence_tier ||
|
|
124
|
+
return claim.evidence_tier || "stated";
|
|
117
125
|
}
|
|
118
126
|
|
|
119
127
|
function formatTags(claim) {
|
|
120
128
|
const tags = Array.isArray(claim.tags) ? claim.tags : [];
|
|
121
|
-
if (tags.length === 0) return
|
|
122
|
-
return
|
|
129
|
+
if (tags.length === 0) return "";
|
|
130
|
+
return " " + tags.map((t) => `#${t.replace(/\s+/g, "-")}`).join(" ");
|
|
123
131
|
}
|
|
124
132
|
|
|
125
133
|
function capitalize(str) {
|
|
@@ -128,7 +136,7 @@ function capitalize(str) {
|
|
|
128
136
|
|
|
129
137
|
function truncate(str, max) {
|
|
130
138
|
if (str.length <= max) return str;
|
|
131
|
-
return str.slice(0, max - 3) +
|
|
139
|
+
return str.slice(0, max - 3) + "...";
|
|
132
140
|
}
|
|
133
141
|
|
|
134
142
|
function escapeFrontMatter(str) {
|
|
@@ -6,10 +6,11 @@
|
|
|
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 = "opml";
|
|
10
|
+
export const extension = ".opml";
|
|
11
|
+
export const mimeType = "text/x-opml; charset=utf-8";
|
|
12
|
+
export const description =
|
|
13
|
+
"Claims as OPML 2.0 outline grouped by type (for RSS readers and outliners)";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Convert a compilation object to OPML XML.
|
|
@@ -19,7 +20,7 @@ export const description = 'Claims as OPML 2.0 outline grouped by type (for RSS
|
|
|
19
20
|
export function convert(compilation) {
|
|
20
21
|
const meta = compilation.meta || {};
|
|
21
22
|
const claims = compilation.claims || [];
|
|
22
|
-
const sprint = meta.sprint ||
|
|
23
|
+
const sprint = meta.sprint || "unknown";
|
|
23
24
|
|
|
24
25
|
const grouped = groupByType(claims);
|
|
25
26
|
const lines = [];
|
|
@@ -27,9 +28,16 @@ export function convert(compilation) {
|
|
|
27
28
|
lines.push('<?xml version="1.0" encoding="utf-8"?>');
|
|
28
29
|
lines.push('<opml version="2.0">');
|
|
29
30
|
lines.push(` <head><title>${esc(sprint)}</title></head>`);
|
|
30
|
-
lines.push(
|
|
31
|
-
|
|
32
|
-
const typeOrder = [
|
|
31
|
+
lines.push(" <body>");
|
|
32
|
+
|
|
33
|
+
const typeOrder = [
|
|
34
|
+
"constraint",
|
|
35
|
+
"factual",
|
|
36
|
+
"estimate",
|
|
37
|
+
"risk",
|
|
38
|
+
"recommendation",
|
|
39
|
+
"feedback",
|
|
40
|
+
];
|
|
33
41
|
const seen = new Set();
|
|
34
42
|
|
|
35
43
|
for (const type of typeOrder) {
|
|
@@ -46,16 +54,16 @@ export function convert(compilation) {
|
|
|
46
54
|
}
|
|
47
55
|
}
|
|
48
56
|
|
|
49
|
-
lines.push(
|
|
50
|
-
lines.push(
|
|
57
|
+
lines.push(" </body>");
|
|
58
|
+
lines.push("</opml>");
|
|
51
59
|
|
|
52
|
-
return lines.join(
|
|
60
|
+
return lines.join("\n") + "\n";
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
function groupByType(claims) {
|
|
56
64
|
const groups = {};
|
|
57
65
|
for (const claim of claims) {
|
|
58
|
-
const type = claim.type ||
|
|
66
|
+
const type = claim.type || "other";
|
|
59
67
|
if (!groups[type]) groups[type] = [];
|
|
60
68
|
groups[type].push(claim);
|
|
61
69
|
}
|
|
@@ -63,29 +71,31 @@ function groupByType(claims) {
|
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
function renderGroup(type, claims) {
|
|
66
|
-
const label = capitalize(type) +
|
|
74
|
+
const label = capitalize(type) + "s";
|
|
67
75
|
const lines = [];
|
|
68
76
|
|
|
69
77
|
lines.push(` <outline text="${esc(label)} (${claims.length})">`);
|
|
70
78
|
|
|
71
79
|
for (const claim of claims) {
|
|
72
|
-
const id = claim.id ||
|
|
73
|
-
const content = claim.content || claim.text ||
|
|
80
|
+
const id = claim.id || "???";
|
|
81
|
+
const content = claim.content || claim.text || "";
|
|
74
82
|
const summary = truncate(content, 100);
|
|
75
83
|
const evidence = getEvidence(claim);
|
|
76
|
-
lines.push(
|
|
84
|
+
lines.push(
|
|
85
|
+
` <outline text="${esc(id)}: ${esc(summary)}" _note="evidence: ${esc(evidence)}"/>`,
|
|
86
|
+
);
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
lines.push(
|
|
89
|
+
lines.push(" </outline>");
|
|
80
90
|
return lines;
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
function getEvidence(claim) {
|
|
84
|
-
if (typeof claim.evidence ===
|
|
85
|
-
if (typeof claim.evidence ===
|
|
86
|
-
return claim.evidence.tier || claim.evidence_tier ||
|
|
94
|
+
if (typeof claim.evidence === "string") return claim.evidence;
|
|
95
|
+
if (typeof claim.evidence === "object" && claim.evidence !== null) {
|
|
96
|
+
return claim.evidence.tier || claim.evidence_tier || "stated";
|
|
87
97
|
}
|
|
88
|
-
return claim.evidence_tier ||
|
|
98
|
+
return claim.evidence_tier || "stated";
|
|
89
99
|
}
|
|
90
100
|
|
|
91
101
|
function capitalize(str) {
|
|
@@ -94,15 +104,15 @@ function capitalize(str) {
|
|
|
94
104
|
|
|
95
105
|
function truncate(str, max) {
|
|
96
106
|
if (str.length <= max) return str;
|
|
97
|
-
return str.slice(0, max - 3) +
|
|
107
|
+
return str.slice(0, max - 3) + "...";
|
|
98
108
|
}
|
|
99
109
|
|
|
100
110
|
function esc(str) {
|
|
101
|
-
if (str == null) return
|
|
111
|
+
if (str == null) return "";
|
|
102
112
|
return String(str)
|
|
103
|
-
.replace(/&/g,
|
|
104
|
-
.replace(/</g,
|
|
105
|
-
.replace(/>/g,
|
|
106
|
-
.replace(/"/g,
|
|
107
|
-
.replace(/'/g,
|
|
113
|
+
.replace(/&/g, "&")
|
|
114
|
+
.replace(/</g, "<")
|
|
115
|
+
.replace(/>/g, ">")
|
|
116
|
+
.replace(/"/g, """)
|
|
117
|
+
.replace(/'/g, "'");
|
|
108
118
|
}
|