@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
package/bin/mill.js CHANGED
@@ -1,34 +1,50 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- 'use strict';
3
+ "use strict";
4
4
 
5
- const path = require('node:path');
6
- const { parseArgs } = require('node:util');
7
- const { fork } = require('node:child_process');
5
+ const path = require("node:path");
6
+ const { parseArgs } = require("node:util");
7
+ const { fork } = require("node:child_process");
8
8
 
9
- const LIB_DIR = path.join(__dirname, '..', 'lib');
9
+ const LIB_DIR = path.join(__dirname, "..", "lib");
10
10
 
11
11
  // --version / -v: print version and exit
12
- if (process.argv.includes('--version') || process.argv.includes('-v')) {
13
- const pkg = require(path.join(__dirname, '..', 'package.json'));
12
+ if (process.argv.includes("--version") || process.argv.includes("-v")) {
13
+ const pkg = require(path.join(__dirname, "..", "package.json"));
14
14
  console.log(pkg.version);
15
15
  process.exit(0);
16
16
  }
17
17
 
18
- const verbose = process.argv.includes('--verbose');
18
+ const verbose = process.argv.includes("--verbose");
19
19
  function vlog(...a) {
20
20
  if (!verbose) return;
21
21
  const ts = new Date().toISOString();
22
- process.stderr.write(`[${ts}] mill: ${a.join(' ')}\n`);
22
+ process.stderr.write(`[${ts}] mill: ${a.join(" ")}\n`);
23
23
  }
24
24
 
25
25
  const COMMANDS = {
26
- export: { description: 'Export artifacts to a target format', handler: runExport },
27
- publish: { description: 'Publish sprint outputs to a destination', handler: runPublish },
28
- convert: { description: 'Convert between artifact formats', handler: runConvert },
29
- formats: { description: 'List available export formats', handler: runFormats },
30
- serve: { description: 'Start the export workbench UI', handler: runServe },
31
- 'serve-mcp': { description: 'Start the MCP server on stdio', handler: null },
26
+ export: {
27
+ description: "Export artifacts to a target format",
28
+ handler: runExport,
29
+ },
30
+ publish: {
31
+ description: "Publish sprint outputs to a destination",
32
+ handler: runPublish,
33
+ },
34
+ convert: {
35
+ description: "Convert between artifact formats",
36
+ handler: runConvert,
37
+ },
38
+ formats: {
39
+ description: "List available export formats",
40
+ handler: runFormats,
41
+ },
42
+ "ci-artifacts": {
43
+ description: "Generate CI artifacts from compilation",
44
+ handler: runCiArtifacts,
45
+ },
46
+ serve: { description: "Start the export workbench UI", handler: runServe },
47
+ "serve-mcp": { description: "Start the MCP server on stdio", handler: null },
32
48
  };
33
49
 
34
50
  const USAGE = `
@@ -41,6 +57,7 @@ Usage:
41
57
  mill publish --target <dest> <dir> Publish sprint outputs
42
58
  mill convert --from <fmt> --to <fmt> <file> Convert between formats
43
59
  mill formats List available formats
60
+ mill ci-artifacts <file> -o <dir> Generate CI artifacts (report, summary, slides)
44
61
 
45
62
  Export formats:
46
63
  pdf HTML or Markdown to PDF (via npx md-to-pdf)
@@ -59,14 +76,15 @@ Examples:
59
76
  npx @grainulation/mill export --format json-ld claims.json -o claims.jsonld
60
77
  npx @grainulation/mill publish --target static output/
61
78
  npx @grainulation/mill convert --from html --to markdown output/brief.html
79
+ npx @grainulation/mill ci-artifacts compilation.json -o ./artifacts
62
80
  `.trim();
63
81
 
64
82
  function main() {
65
83
  const args = process.argv.slice(2);
66
84
 
67
- vlog('startup', `command=${args[0] || '(none)'}`, `cwd=${process.cwd()}`);
85
+ vlog("startup", `command=${args[0] || "(none)"}`, `cwd=${process.cwd()}`);
68
86
 
69
- if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
87
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
70
88
  console.log(USAGE);
71
89
  process.exit(0);
72
90
  }
@@ -74,20 +92,20 @@ function main() {
74
92
  const command = args[0];
75
93
  const handler = COMMANDS[command];
76
94
 
77
- if (command === 'help') {
95
+ if (command === "help") {
78
96
  console.log(USAGE);
79
97
  process.exit(0);
80
98
  }
81
99
 
82
100
  // serve command forks the ESM server module
83
- if (command === 'serve') {
101
+ if (command === "serve") {
84
102
  runServe(args.slice(1));
85
103
  return;
86
104
  }
87
105
 
88
106
  // serve-mcp command starts the MCP server on stdio
89
- if (command === 'serve-mcp') {
90
- const serveMcp = require('../lib/serve-mcp.js');
107
+ if (command === "serve-mcp") {
108
+ const serveMcp = require("../lib/serve-mcp.js");
91
109
  serveMcp.run(process.cwd());
92
110
  return;
93
111
  }
@@ -107,44 +125,46 @@ async function runExport(args) {
107
125
  ({ values, positionals } = parseArgs({
108
126
  args,
109
127
  options: {
110
- format: { type: 'string', short: 'f' },
111
- output: { type: 'string', short: 'o' },
112
- json: { type: 'boolean', default: false },
128
+ format: { type: "string", short: "f" },
129
+ output: { type: "string", short: "o" },
130
+ json: { type: "boolean", default: false },
113
131
  },
114
132
  allowPositionals: true,
115
133
  }));
116
134
  } catch (err) {
117
- if (err.code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') {
118
- const flag = err.message.match(/option "([^"]+)"/)?.[1] || 'unknown';
119
- console.error(`mill: unknown option: ${flag}. Run "mill export --help" for usage.`);
135
+ if (err.code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") {
136
+ const flag = err.message.match(/option "([^"]+)"/)?.[1] || "unknown";
137
+ console.error(
138
+ `mill: unknown option: ${flag}. Run "mill export --help" for usage.`,
139
+ );
120
140
  process.exit(1);
121
141
  }
122
142
  throw err;
123
143
  }
124
144
 
125
145
  if (!values.format) {
126
- console.error('mill: missing --format. Options: pdf, csv, markdown, json-ld');
146
+ console.error(
147
+ "mill: missing --format. Options: pdf, csv, markdown, json-ld",
148
+ );
127
149
  process.exit(1);
128
150
  }
129
151
 
130
152
  const inputFile = positionals[0];
131
153
  if (!inputFile) {
132
- console.error('mill: missing input file.');
154
+ console.error("mill: missing input file.");
133
155
  process.exit(1);
134
156
  }
135
157
 
136
158
  const inputPath = path.resolve(inputFile);
137
159
  const format = values.format;
138
- const outputPath = values.output
139
- ? path.resolve(values.output)
140
- : null;
160
+ const outputPath = values.output ? path.resolve(values.output) : null;
141
161
 
142
- const formats = require('../lib/formats.js');
162
+ const formats = require("../lib/formats.js");
143
163
  const exporter = formats.getExporter(format);
144
164
 
145
165
  if (!exporter) {
146
166
  console.error(`mill: unknown format: ${format}`);
147
- console.error(`Available: ${formats.listExportFormats().join(', ')}`);
167
+ console.error(`Available: ${formats.listExportFormats().join(", ")}`);
148
168
  process.exit(1);
149
169
  }
150
170
 
@@ -171,29 +191,31 @@ async function runPublish(args) {
171
191
  ({ values, positionals } = parseArgs({
172
192
  args,
173
193
  options: {
174
- target: { type: 'string', short: 't' },
175
- output: { type: 'string', short: 'o' },
176
- json: { type: 'boolean', default: false },
194
+ target: { type: "string", short: "t" },
195
+ output: { type: "string", short: "o" },
196
+ json: { type: "boolean", default: false },
177
197
  },
178
198
  allowPositionals: true,
179
199
  }));
180
200
  } catch (err) {
181
- if (err.code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') {
182
- const flag = err.message.match(/option "([^"]+)"/)?.[1] || 'unknown';
183
- console.error(`mill: unknown option: ${flag}. Run "mill publish --help" for usage.`);
201
+ if (err.code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") {
202
+ const flag = err.message.match(/option "([^"]+)"/)?.[1] || "unknown";
203
+ console.error(
204
+ `mill: unknown option: ${flag}. Run "mill publish --help" for usage.`,
205
+ );
184
206
  process.exit(1);
185
207
  }
186
208
  throw err;
187
209
  }
188
210
 
189
211
  if (!values.target) {
190
- console.error('mill: missing --target. Options: static, clipboard');
212
+ console.error("mill: missing --target. Options: static, clipboard");
191
213
  process.exit(1);
192
214
  }
193
215
 
194
216
  const inputDir = positionals[0];
195
217
  if (!inputDir) {
196
- console.error('mill: missing input directory.');
218
+ console.error("mill: missing input directory.");
197
219
  process.exit(1);
198
220
  }
199
221
 
@@ -201,12 +223,12 @@ async function runPublish(args) {
201
223
  const target = values.target;
202
224
  const outputPath = values.output ? path.resolve(values.output) : null;
203
225
 
204
- const formats = require('../lib/formats.js');
226
+ const formats = require("../lib/formats.js");
205
227
  const publisher = formats.getPublisher(target);
206
228
 
207
229
  if (!publisher) {
208
230
  console.error(`mill: unknown target: ${target}`);
209
- console.error(`Available: ${formats.listPublishTargets().join(', ')}`);
231
+ console.error(`Available: ${formats.listPublishTargets().join(", ")}`);
210
232
  process.exit(1);
211
233
  }
212
234
 
@@ -233,30 +255,32 @@ async function runConvert(args) {
233
255
  ({ values, positionals } = parseArgs({
234
256
  args,
235
257
  options: {
236
- from: { type: 'string' },
237
- to: { type: 'string' },
238
- output: { type: 'string', short: 'o' },
239
- json: { type: 'boolean', default: false },
258
+ from: { type: "string" },
259
+ to: { type: "string" },
260
+ output: { type: "string", short: "o" },
261
+ json: { type: "boolean", default: false },
240
262
  },
241
263
  allowPositionals: true,
242
264
  }));
243
265
  } catch (err) {
244
- if (err.code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') {
245
- const flag = err.message.match(/option "([^"]+)"/)?.[1] || 'unknown';
246
- console.error(`mill: unknown option: ${flag}. Run "mill convert --help" for usage.`);
266
+ if (err.code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") {
267
+ const flag = err.message.match(/option "([^"]+)"/)?.[1] || "unknown";
268
+ console.error(
269
+ `mill: unknown option: ${flag}. Run "mill convert --help" for usage.`,
270
+ );
247
271
  process.exit(1);
248
272
  }
249
273
  throw err;
250
274
  }
251
275
 
252
276
  if (!values.from || !values.to) {
253
- console.error('mill: missing --from and/or --to format.');
277
+ console.error("mill: missing --from and/or --to format.");
254
278
  process.exit(1);
255
279
  }
256
280
 
257
281
  const inputFile = positionals[0];
258
282
  if (!inputFile) {
259
- console.error('mill: missing input file.');
283
+ console.error("mill: missing input file.");
260
284
  process.exit(1);
261
285
  }
262
286
 
@@ -264,7 +288,7 @@ async function runConvert(args) {
264
288
  const outputPath = values.output ? path.resolve(values.output) : null;
265
289
 
266
290
  // Convert is sugar: detect source, export to target
267
- const formats = require('../lib/formats.js');
291
+ const formats = require("../lib/formats.js");
268
292
  const exporter = formats.getExporter(values.to);
269
293
 
270
294
  if (!exporter) {
@@ -290,31 +314,173 @@ async function runConvert(args) {
290
314
  }
291
315
 
292
316
  function runFormats(args) {
293
- const jsonMode = (args || []).includes('--json');
294
- const formats = require('../lib/formats.js');
317
+ const jsonMode = (args || []).includes("--json");
318
+ const formats = require("../lib/formats.js");
295
319
  if (jsonMode) {
296
- console.log(JSON.stringify({
297
- export_formats: formats.listExportFormats(),
298
- publish_targets: formats.listPublishTargets(),
299
- }));
320
+ console.log(
321
+ JSON.stringify({
322
+ export_formats: formats.listExportFormats(),
323
+ publish_targets: formats.listPublishTargets(),
324
+ }),
325
+ );
300
326
  return;
301
327
  }
302
- console.log('Export formats:');
328
+ console.log("Export formats:");
303
329
  for (const f of formats.listExportFormats()) {
304
330
  console.log(` ${f}`);
305
331
  }
306
- console.log('\nPublish targets:');
332
+ console.log("\nPublish targets:");
307
333
  for (const t of formats.listPublishTargets()) {
308
334
  console.log(` ${t}`);
309
335
  }
310
336
  }
311
337
 
338
+ async function runCiArtifacts(args) {
339
+ const fs = require("node:fs");
340
+
341
+ let values, positionals;
342
+ try {
343
+ ({ values, positionals } = parseArgs({
344
+ args,
345
+ options: {
346
+ output: { type: "string", short: "o" },
347
+ formats: { type: "string", short: "f" },
348
+ summary: { type: "boolean", default: false },
349
+ json: { type: "boolean", default: false },
350
+ },
351
+ allowPositionals: true,
352
+ }));
353
+ } catch (err) {
354
+ if (err.code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") {
355
+ const flag = err.message.match(/option "([^"]+)"/)?.[1] || "unknown";
356
+ console.error(
357
+ `mill: unknown option: ${flag}. Run "mill ci-artifacts --help" for usage.`,
358
+ );
359
+ process.exit(1);
360
+ }
361
+ throw err;
362
+ }
363
+
364
+ const inputFile = positionals[0];
365
+ if (!inputFile) {
366
+ console.error(
367
+ "mill: missing input file (compilation.json or claims.json).",
368
+ );
369
+ process.exit(1);
370
+ }
371
+
372
+ const inputPath = path.resolve(inputFile);
373
+ const outputDir = values.output
374
+ ? path.resolve(values.output)
375
+ : path.resolve("artifacts");
376
+
377
+ // Default CI formats: the three HTML formats plus markdown for step summary
378
+ const defaultFormats = ["html-report", "executive-summary", "slide-deck"];
379
+ const requestedFormats = values.formats
380
+ ? values.formats.split(",").map((f) => f.trim())
381
+ : defaultFormats;
382
+
383
+ // Read and parse input
384
+ let data;
385
+ try {
386
+ data = JSON.parse(fs.readFileSync(inputPath, "utf8"));
387
+ } catch (err) {
388
+ console.error(`mill: failed to read ${inputPath}: ${err.message}`);
389
+ process.exit(1);
390
+ }
391
+
392
+ // Normalize compilation vs claims
393
+ if (data.resolved_claims && (!data.claims || data.claims.length === 0)) {
394
+ data.claims = data.resolved_claims;
395
+ }
396
+ if (data.sprint_meta && !data.meta) {
397
+ data.meta = data.sprint_meta;
398
+ }
399
+
400
+ // Create output dir
401
+ if (!fs.existsSync(outputDir)) {
402
+ fs.mkdirSync(outputDir, { recursive: true });
403
+ }
404
+
405
+ // Discover ESM formats
406
+ const FORMATS_DIR = path.join(LIB_DIR, "formats");
407
+ const formatFiles = fs
408
+ .readdirSync(FORMATS_DIR)
409
+ .filter((f) => f.endsWith(".js"));
410
+ const formatMap = {};
411
+ for (const file of formatFiles) {
412
+ try {
413
+ const mod = await import(path.join(FORMATS_DIR, file));
414
+ formatMap[mod.name || file.replace(".js", "")] = mod;
415
+ } catch {}
416
+ }
417
+
418
+ const results = [];
419
+
420
+ for (const fmt of requestedFormats) {
421
+ const mod = formatMap[fmt];
422
+ if (!mod || typeof mod.convert !== "function") {
423
+ console.error(`mill: unknown format: ${fmt}`);
424
+ continue;
425
+ }
426
+
427
+ try {
428
+ const output = mod.convert(data);
429
+ const ext = mod.extension || ".txt";
430
+ const outFile = path.join(outputDir, fmt + ext);
431
+ fs.writeFileSync(outFile, output);
432
+ results.push({
433
+ format: fmt,
434
+ file: outFile,
435
+ bytes: Buffer.byteLength(output),
436
+ });
437
+ vlog(`wrote ${fmt} -> ${outFile}`);
438
+ } catch (err) {
439
+ console.error(`mill: ${fmt} conversion failed: ${err.message}`);
440
+ }
441
+ }
442
+
443
+ // Generate step summary (markdown) if --summary or GITHUB_STEP_SUMMARY is set
444
+ const summaryPath = process.env.GITHUB_STEP_SUMMARY;
445
+ if (values.summary || summaryPath) {
446
+ const mdMod = formatMap["markdown"];
447
+ if (mdMod && typeof mdMod.convert === "function") {
448
+ try {
449
+ const md = mdMod.convert(data);
450
+ if (summaryPath) {
451
+ fs.appendFileSync(summaryPath, md + "\n");
452
+ vlog("appended markdown to GITHUB_STEP_SUMMARY");
453
+ }
454
+ // Also write to output dir
455
+ const mdFile = path.join(outputDir, "step-summary.md");
456
+ fs.writeFileSync(mdFile, md);
457
+ results.push({
458
+ format: "markdown",
459
+ file: mdFile,
460
+ bytes: Buffer.byteLength(md),
461
+ });
462
+ } catch (err) {
463
+ console.error(`mill: step summary generation failed: ${err.message}`);
464
+ }
465
+ }
466
+ }
467
+
468
+ if (values.json) {
469
+ console.log(JSON.stringify({ artifacts: results }));
470
+ } else {
471
+ console.log(`mill: generated ${results.length} artifacts in ${outputDir}`);
472
+ for (const r of results) {
473
+ console.log(` ${r.format}: ${r.file} (${r.bytes} bytes)`);
474
+ }
475
+ }
476
+ }
477
+
312
478
  function runServe(args) {
313
- const serverPath = path.join(LIB_DIR, 'server.js');
314
- const child = fork(serverPath, args, { stdio: 'inherit' });
315
- child.on('exit', (code) => process.exit(code ?? 0));
316
- process.on('SIGTERM', () => child.kill('SIGTERM'));
317
- process.on('SIGINT', () => child.kill('SIGINT'));
479
+ const serverPath = path.join(LIB_DIR, "server.js");
480
+ const child = fork(serverPath, args, { stdio: "inherit" });
481
+ child.on("exit", (code) => process.exit(code ?? 0));
482
+ process.on("SIGTERM", () => child.kill("SIGTERM"));
483
+ process.on("SIGINT", () => child.kill("SIGINT"));
318
484
  }
319
485
 
320
486
  main();
@@ -1,7 +1,7 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
- const fs = require('node:fs');
4
- const path = require('node:path');
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
5
 
6
6
  /**
7
7
  * Export claims.json to CSV.
@@ -9,25 +9,25 @@ const path = require('node:path');
9
9
  */
10
10
 
11
11
  const CSV_COLUMNS = [
12
- 'id',
13
- 'type',
14
- 'text',
15
- 'confidence',
16
- 'evidence_tier',
17
- 'source',
18
- 'status',
19
- 'created',
20
- 'tags',
12
+ "id",
13
+ "type",
14
+ "text",
15
+ "confidence",
16
+ "evidence_tier",
17
+ "source",
18
+ "status",
19
+ "created",
20
+ "tags",
21
21
  ];
22
22
 
23
23
  function escapeCsvField(value) {
24
- if (value == null) return '';
24
+ if (value == null) return "";
25
25
  let str = String(value);
26
26
  // CWE-1236: Prevent CSV injection by prefixing formula-triggering characters
27
27
  if (/^[=+\-@\t\r]/.test(str)) {
28
28
  str = "'" + str;
29
29
  }
30
- if (str.includes(',') || str.includes('"') || str.includes('\n')) {
30
+ if (str.includes(",") || str.includes('"') || str.includes("\n")) {
31
31
  return `"${str.replace(/"/g, '""')}"`;
32
32
  }
33
33
  return str;
@@ -35,17 +35,19 @@ function escapeCsvField(value) {
35
35
 
36
36
  function claimToRow(claim) {
37
37
  return CSV_COLUMNS.map((col) => {
38
- if (col === 'tags') {
39
- return escapeCsvField(Array.isArray(claim.tags) ? claim.tags.join('; ') : '');
38
+ if (col === "tags") {
39
+ return escapeCsvField(
40
+ Array.isArray(claim.tags) ? claim.tags.join("; ") : "",
41
+ );
40
42
  }
41
- if (col === 'evidence_tier') {
42
- return escapeCsvField(claim.evidence?.tier ?? claim.evidence_tier ?? '');
43
+ if (col === "evidence_tier") {
44
+ return escapeCsvField(claim.evidence?.tier ?? claim.evidence_tier ?? "");
43
45
  }
44
- if (col === 'source') {
45
- return escapeCsvField(claim.evidence?.source ?? claim.source ?? '');
46
+ if (col === "source") {
47
+ return escapeCsvField(claim.evidence?.source ?? claim.source ?? "");
46
48
  }
47
49
  return escapeCsvField(claim[col]);
48
- }).join(',');
50
+ }).join(",");
49
51
  }
50
52
 
51
53
  function deriveOutputPath(inputPath, explicit) {
@@ -56,28 +58,31 @@ function deriveOutputPath(inputPath, explicit) {
56
58
  }
57
59
 
58
60
  async function exportCsv(inputPath, outputPath) {
59
- const raw = fs.readFileSync(inputPath, 'utf-8');
61
+ const raw = fs.readFileSync(inputPath, "utf-8");
60
62
  const data = JSON.parse(raw);
61
63
 
62
64
  // Accept either an array or { claims: [...] }
63
- const claims = Array.isArray(data) ? data : (data.claims || []);
65
+ const claims = Array.isArray(data) ? data : data.claims || [];
64
66
 
65
67
  if (claims.length === 0) {
66
- throw new Error('No claims found in input file.');
68
+ throw new Error("No claims found in input file.");
67
69
  }
68
70
 
69
- const header = CSV_COLUMNS.join(',');
71
+ const header = CSV_COLUMNS.join(",");
70
72
  const rows = claims.map(claimToRow);
71
- const csv = [header, ...rows].join('\n') + '\n';
73
+ const csv = [header, ...rows].join("\n") + "\n";
72
74
 
73
75
  const out = deriveOutputPath(inputPath, outputPath);
74
- fs.writeFileSync(out, csv, 'utf-8');
76
+ fs.writeFileSync(out, csv, "utf-8");
75
77
 
76
- return { outputPath: out, message: `CSV written to ${out} (${claims.length} claims)` };
78
+ return {
79
+ outputPath: out,
80
+ message: `CSV written to ${out} (${claims.length} claims)`,
81
+ };
77
82
  }
78
83
 
79
84
  module.exports = {
80
- name: 'csv',
81
- description: 'Export claims JSON to CSV',
85
+ name: "csv",
86
+ description: "Export claims JSON to CSV",
82
87
  export: exportCsv,
83
88
  };
@@ -1,8 +1,8 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
- const { buildReport } = require('../json-ld-common.js');
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { buildReport } = require("../json-ld-common.js");
6
6
 
7
7
  /**
8
8
  * Export claims.json to JSON-LD format.
@@ -17,28 +17,34 @@ function deriveOutputPath(inputPath, explicit) {
17
17
  }
18
18
 
19
19
  async function exportJsonLd(inputPath, outputPath) {
20
- const raw = fs.readFileSync(inputPath, 'utf-8');
20
+ const raw = fs.readFileSync(inputPath, "utf-8");
21
21
  const data = JSON.parse(raw);
22
- const claims = Array.isArray(data) ? data : (data.claims || []);
22
+ const claims = Array.isArray(data) ? data : data.claims || [];
23
23
 
24
24
  if (claims.length === 0) {
25
- throw new Error('No claims found in input file.');
25
+ throw new Error("No claims found in input file.");
26
26
  }
27
27
 
28
- const meta = data.meta || { sprint: 'unknown', question: 'Wheat Sprint Claims' };
28
+ const meta = data.meta || {
29
+ sprint: "unknown",
30
+ question: "Wheat Sprint Claims",
31
+ };
29
32
  const certificate = data.certificate || {};
30
33
  const doc = buildReport(meta, claims, certificate);
31
34
 
32
35
  const out = deriveOutputPath(inputPath, outputPath);
33
- const tmp = out + '.tmp.' + process.pid;
34
- fs.writeFileSync(tmp, JSON.stringify(doc, null, 2) + '\n', 'utf-8');
36
+ const tmp = out + ".tmp." + process.pid;
37
+ fs.writeFileSync(tmp, JSON.stringify(doc, null, 2) + "\n", "utf-8");
35
38
  fs.renameSync(tmp, out);
36
39
 
37
- return { outputPath: out, message: `JSON-LD written to ${out} (${claims.length} claims)` };
40
+ return {
41
+ outputPath: out,
42
+ message: `JSON-LD written to ${out} (${claims.length} claims)`,
43
+ };
38
44
  }
39
45
 
40
46
  module.exports = {
41
- name: 'json-ld',
42
- description: 'Export claims JSON to JSON-LD for semantic web',
47
+ name: "json-ld",
48
+ description: "Export claims JSON to JSON-LD for semantic web",
43
49
  export: exportJsonLd,
44
50
  };