@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.
Files changed (41) hide show
  1. package/CODE_OF_CONDUCT.md +25 -0
  2. package/CONTRIBUTING.md +101 -0
  3. package/README.md +90 -42
  4. package/bin/mill.js +233 -67
  5. package/lib/exporters/csv.js +35 -30
  6. package/lib/exporters/json-ld.js +19 -13
  7. package/lib/exporters/markdown.js +83 -44
  8. package/lib/exporters/pdf.js +15 -15
  9. package/lib/formats/bibtex.js +41 -34
  10. package/lib/formats/changelog.js +27 -26
  11. package/lib/formats/confluence-adf.js +312 -0
  12. package/lib/formats/csv.js +41 -37
  13. package/lib/formats/dot.js +45 -34
  14. package/lib/formats/evidence-matrix.js +17 -16
  15. package/lib/formats/executive-summary.js +89 -41
  16. package/lib/formats/github-issues.js +40 -33
  17. package/lib/formats/graphml.js +45 -32
  18. package/lib/formats/html-report.js +110 -63
  19. package/lib/formats/jira-csv.js +30 -29
  20. package/lib/formats/json-ld.js +6 -6
  21. package/lib/formats/markdown.js +53 -36
  22. package/lib/formats/ndjson.js +6 -6
  23. package/lib/formats/obsidian.js +43 -35
  24. package/lib/formats/opml.js +38 -28
  25. package/lib/formats/ris.js +29 -23
  26. package/lib/formats/rss.js +31 -28
  27. package/lib/formats/sankey.js +16 -15
  28. package/lib/formats/slide-deck.js +145 -57
  29. package/lib/formats/sql.js +57 -53
  30. package/lib/formats/static-site.js +64 -52
  31. package/lib/formats/treemap.js +16 -15
  32. package/lib/formats/typescript-defs.js +79 -76
  33. package/lib/formats/yaml.js +58 -40
  34. package/lib/formats.js +16 -16
  35. package/lib/index.js +5 -5
  36. package/lib/json-ld-common.js +37 -31
  37. package/lib/publishers/clipboard.js +21 -19
  38. package/lib/publishers/static.js +27 -12
  39. package/lib/serve-mcp.js +158 -83
  40. package/lib/server.js +252 -142
  41. package/package.json +7 -3
@@ -6,10 +6,11 @@
6
6
  * Zero dependencies — node built-in only.
7
7
  */
8
8
 
9
- export const name = 'obsidian';
10
- export const extension = '.md';
11
- export const mimeType = 'text/markdown; charset=utf-8';
12
- export const description = 'Claims as Obsidian vault page (YAML front matter, wikilinks, backlinks)';
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 || 'unknown';
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('tags: [wheat, sprint, export]');
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('## Claims');
50
- lines.push('');
51
-
52
- const typeOrder = ['constraint', 'factual', 'estimate', 'risk', 'recommendation', 'feedback'];
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('## Connections');
70
- lines.push('');
71
- lines.push('- Related: [[compilation]] [[claims]]');
77
+ lines.push("## Connections");
78
+ lines.push("");
79
+ lines.push("- Related: [[compilation]] [[claims]]");
72
80
 
73
81
  if (compilation.certificate) {
74
- lines.push('- Certificate: [[certificate]]');
82
+ lines.push("- Certificate: [[certificate]]");
75
83
  }
76
84
 
77
- lines.push('');
78
- return lines.join('\n') + '\n';
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 || 'other';
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) + 's';
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 === 'string') return claim.evidence;
113
- if (typeof claim.evidence === 'object' && claim.evidence !== null) {
114
- return claim.evidence.tier || claim.evidence_tier || 'stated';
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 || 'stated';
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 ' ' + tags.map(t => `#${t.replace(/\s+/g, '-')}`).join(' ');
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 = 'opml';
10
- export const extension = '.opml';
11
- export const mimeType = 'text/x-opml; charset=utf-8';
12
- export const description = 'Claims as OPML 2.0 outline grouped by type (for RSS readers and outliners)';
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 || 'unknown';
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(' <body>');
31
-
32
- const typeOrder = ['constraint', 'factual', 'estimate', 'risk', 'recommendation', 'feedback'];
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(' </body>');
50
- lines.push('</opml>');
57
+ lines.push(" </body>");
58
+ lines.push("</opml>");
51
59
 
52
- return lines.join('\n') + '\n';
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 || 'other';
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) + 's';
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(` <outline text="${esc(id)}: ${esc(summary)}" _note="evidence: ${esc(evidence)}"/>`);
84
+ lines.push(
85
+ ` <outline text="${esc(id)}: ${esc(summary)}" _note="evidence: ${esc(evidence)}"/>`,
86
+ );
77
87
  }
78
88
 
79
- lines.push(' </outline>');
89
+ lines.push(" </outline>");
80
90
  return lines;
81
91
  }
82
92
 
83
93
  function getEvidence(claim) {
84
- if (typeof claim.evidence === 'string') return claim.evidence;
85
- if (typeof claim.evidence === 'object' && claim.evidence !== null) {
86
- return claim.evidence.tier || claim.evidence_tier || 'stated';
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 || 'stated';
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, '&amp;')
104
- .replace(/</g, '&lt;')
105
- .replace(/>/g, '&gt;')
106
- .replace(/"/g, '&quot;')
107
- .replace(/'/g, '&apos;');
113
+ .replace(/&/g, "&amp;")
114
+ .replace(/</g, "&lt;")
115
+ .replace(/>/g, "&gt;")
116
+ .replace(/"/g, "&quot;")
117
+ .replace(/'/g, "&apos;");
108
118
  }
@@ -6,10 +6,11 @@
6
6
  * Zero dependencies — node built-in only.
7
7
  */
8
8
 
9
- export const name = 'ris';
10
- export const extension = '.ris';
11
- export const mimeType = 'application/x-research-info-systems; charset=utf-8';
12
- export const description = 'Claims as RIS citation records (one TY-ER block per claim)';
9
+ export const name = "ris";
10
+ export const extension = ".ris";
11
+ export const mimeType = "application/x-research-info-systems; charset=utf-8";
12
+ export const description =
13
+ "Claims as RIS citation records (one TY-ER block per claim)";
13
14
 
14
15
  /**
15
16
  * Convert a compilation object to RIS.
@@ -19,33 +20,38 @@ export const description = 'Claims as RIS citation records (one TY-ER block per
19
20
  export function convert(compilation) {
20
21
  const claims = compilation.claims || [];
21
22
  const meta = compilation.meta || {};
22
- const author = meta.sprint ? `wheat sprint: ${meta.sprint}` : 'wheat sprint';
23
+ const author = meta.sprint ? `wheat sprint: ${meta.sprint}` : "wheat sprint";
23
24
  const year = new Date().getFullYear().toString();
24
25
 
25
26
  if (claims.length === 0) {
26
- return '';
27
+ return "";
27
28
  }
28
29
 
29
- const records = claims.map(claim => claimToRecord(claim, author, year));
30
- return records.join('\n') + '\n';
30
+ const records = claims.map((claim) => claimToRecord(claim, author, year));
31
+ return records.join("\n") + "\n";
31
32
  }
32
33
 
33
34
  function claimToRecord(claim, author, year) {
34
- const id = String(claim.id || 'unknown');
35
- const title = claim.content || claim.text || '';
36
- const type = claim.type || '';
37
- const evidence = typeof claim.evidence === 'string'
38
- ? claim.evidence
39
- : (claim.evidence?.tier ?? claim.evidence_tier ?? '');
40
- const status = claim.status || '';
35
+ const id = String(claim.id || "unknown");
36
+ const title = claim.content || claim.text || "";
37
+ const type = claim.type || "";
38
+ const evidence =
39
+ typeof claim.evidence === "string"
40
+ ? claim.evidence
41
+ : (claim.evidence?.tier ?? claim.evidence_tier ?? "");
42
+ const status = claim.status || "";
41
43
  const tags = Array.isArray(claim.tags) ? claim.tags : [];
42
- const confidence = claim.confidence != null ? `, confidence: ${claim.confidence}` : '';
44
+ const confidence =
45
+ claim.confidence != null ? `, confidence: ${claim.confidence}` : "";
43
46
 
44
- const noteParts = [
45
- type ? `type: ${type}` : '',
46
- evidence ? `evidence: ${evidence}` : '',
47
- status ? `status: ${status}` : '',
48
- ].filter(Boolean).join(', ') + confidence;
47
+ const noteParts =
48
+ [
49
+ type ? `type: ${type}` : "",
50
+ evidence ? `evidence: ${evidence}` : "",
51
+ status ? `status: ${status}` : "",
52
+ ]
53
+ .filter(Boolean)
54
+ .join(", ") + confidence;
49
55
 
50
56
  const lines = [
51
57
  `TY - GEN`,
@@ -64,7 +70,7 @@ function claimToRecord(claim, author, year) {
64
70
  }
65
71
 
66
72
  lines.push(`ER - `);
67
- lines.push('');
73
+ lines.push("");
68
74
 
69
- return lines.join('\n');
75
+ return lines.join("\n");
70
76
  }
@@ -6,10 +6,10 @@
6
6
  * Zero dependencies — node built-in only.
7
7
  */
8
8
 
9
- export const name = 'rss';
10
- export const extension = '.xml';
11
- export const mimeType = 'application/atom+xml; charset=utf-8';
12
- export const description = 'Claims as Atom XML feed (one entry per claim)';
9
+ export const name = "rss";
10
+ export const extension = ".xml";
11
+ export const mimeType = "application/atom+xml; charset=utf-8";
12
+ export const description = "Claims as Atom XML feed (one entry per claim)";
13
13
 
14
14
  /**
15
15
  * Convert a compilation object to Atom XML.
@@ -19,8 +19,8 @@ export const description = 'Claims as Atom XML feed (one entry per claim)';
19
19
  export function convert(compilation) {
20
20
  const claims = compilation.claims || [];
21
21
  const meta = compilation.meta || {};
22
- const sprintName = meta.sprint || 'unnamed';
23
- const updated = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
22
+ const sprintName = meta.sprint || "unnamed";
23
+ const updated = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
24
24
 
25
25
  const lines = [];
26
26
  lines.push(`<?xml version="1.0" encoding="utf-8"?>`);
@@ -38,27 +38,30 @@ export function convert(compilation) {
38
38
  }
39
39
 
40
40
  lines.push(`</feed>`);
41
- lines.push('');
41
+ lines.push("");
42
42
 
43
- return lines.join('\n');
43
+ return lines.join("\n");
44
44
  }
45
45
 
46
46
  function claimToEntry(claim) {
47
- const id = String(claim.id || 'unknown');
48
- const text = claim.content || claim.text || '';
49
- const type = claim.type || '';
50
- const status = claim.status || '';
51
- const evidence = typeof claim.evidence === 'string'
52
- ? claim.evidence
53
- : (claim.evidence?.tier ?? claim.evidence_tier ?? '');
47
+ const id = String(claim.id || "unknown");
48
+ const text = claim.content || claim.text || "";
49
+ const type = claim.type || "";
50
+ const status = claim.status || "";
51
+ const evidence =
52
+ typeof claim.evidence === "string"
53
+ ? claim.evidence
54
+ : (claim.evidence?.tier ?? claim.evidence_tier ?? "");
54
55
  const tags = Array.isArray(claim.tags) ? claim.tags : [];
55
- const updated = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
56
+ const updated = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
56
57
 
57
58
  const summaryParts = [
58
- type ? `type: ${type}` : '',
59
- evidence ? `evidence: ${evidence}` : '',
60
- status ? `status: ${status}` : '',
61
- ].filter(Boolean).join(', ');
59
+ type ? `type: ${type}` : "",
60
+ evidence ? `evidence: ${evidence}` : "",
61
+ status ? `status: ${status}` : "",
62
+ ]
63
+ .filter(Boolean)
64
+ .join(", ");
62
65
 
63
66
  const lines = [];
64
67
  lines.push(` <entry>`);
@@ -81,20 +84,20 @@ function claimToEntry(claim) {
81
84
 
82
85
  lines.push(` </entry>`);
83
86
 
84
- return lines.join('\n');
87
+ return lines.join("\n");
85
88
  }
86
89
 
87
90
  function escapeXml(value) {
88
- if (value == null) return '';
91
+ if (value == null) return "";
89
92
  return String(value)
90
- .replace(/&/g, '&amp;')
91
- .replace(/</g, '&lt;')
92
- .replace(/>/g, '&gt;')
93
- .replace(/"/g, '&quot;')
94
- .replace(/'/g, '&apos;');
93
+ .replace(/&/g, "&amp;")
94
+ .replace(/</g, "&lt;")
95
+ .replace(/>/g, "&gt;")
96
+ .replace(/"/g, "&quot;")
97
+ .replace(/'/g, "&apos;");
95
98
  }
96
99
 
97
100
  function truncate(str, max) {
98
101
  if (str.length <= max) return str;
99
- return str.slice(0, max - 3) + '...';
102
+ return str.slice(0, max - 3) + "...";
100
103
  }
@@ -6,10 +6,11 @@
6
6
  * Zero dependencies — node built-in only.
7
7
  */
8
8
 
9
- export const name = 'sankey';
10
- export const extension = '.json';
11
- export const mimeType = 'application/json; charset=utf-8';
12
- export const description = 'Claims as Sankey flow JSON (type -> evidence tier -> status)';
9
+ export const name = "sankey";
10
+ export const extension = ".json";
11
+ export const mimeType = "application/json; charset=utf-8";
12
+ export const description =
13
+ "Claims as Sankey flow JSON (type -> evidence tier -> status)";
13
14
 
14
15
  /**
15
16
  * Convert a compilation object to Sankey JSON.
@@ -25,9 +26,9 @@ export function convert(compilation) {
25
26
  const nodeSet = new Set();
26
27
 
27
28
  for (const claim of claims) {
28
- const type = 'type:' + (claim.type || 'other');
29
- const evidence = 'evidence:' + getEvidence(claim) || 'evidence:unknown';
30
- const status = 'status:' + (claim.status || 'unknown');
29
+ const type = "type:" + (claim.type || "other");
30
+ const evidence = "evidence:" + getEvidence(claim) || "evidence:unknown";
31
+ const status = "status:" + (claim.status || "unknown");
31
32
 
32
33
  nodeSet.add(type);
33
34
  nodeSet.add(evidence);
@@ -44,29 +45,29 @@ export function convert(compilation) {
44
45
 
45
46
  const nodes = Array.from(nodeSet)
46
47
  .sort()
47
- .map(id => ({ id }));
48
+ .map((id) => ({ id }));
48
49
 
49
50
  const links = [];
50
51
 
51
52
  for (const [key, value] of Object.entries(typeToEvidence)) {
52
- const [source, target] = key.split('|||');
53
+ const [source, target] = key.split("|||");
53
54
  links.push({ source, target, value });
54
55
  }
55
56
 
56
57
  for (const [key, value] of Object.entries(evidenceToStatus)) {
57
- const [source, target] = key.split('|||');
58
+ const [source, target] = key.split("|||");
58
59
  links.push({ source, target, value });
59
60
  }
60
61
 
61
62
  const result = { nodes, links };
62
63
 
63
- return JSON.stringify(result, null, 2) + '\n';
64
+ return JSON.stringify(result, null, 2) + "\n";
64
65
  }
65
66
 
66
67
  function getEvidence(claim) {
67
- if (typeof claim.evidence === 'string') return claim.evidence;
68
- if (typeof claim.evidence === 'object' && claim.evidence !== null) {
69
- return claim.evidence.tier || claim.evidence_tier || '';
68
+ if (typeof claim.evidence === "string") return claim.evidence;
69
+ if (typeof claim.evidence === "object" && claim.evidence !== null) {
70
+ return claim.evidence.tier || claim.evidence_tier || "";
70
71
  }
71
- return claim.evidence_tier || '';
72
+ return claim.evidence_tier || "";
72
73
  }