@de-otio/bibcheck 0.1.0

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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/dist/cache/fs-cache.d.ts +55 -0
  4. package/dist/cache/fs-cache.d.ts.map +1 -0
  5. package/dist/cache/fs-cache.js +264 -0
  6. package/dist/cache/fs-cache.js.map +1 -0
  7. package/dist/canonical.d.ts +29 -0
  8. package/dist/canonical.d.ts.map +1 -0
  9. package/dist/canonical.js +132 -0
  10. package/dist/canonical.js.map +1 -0
  11. package/dist/check.d.ts +140 -0
  12. package/dist/check.d.ts.map +1 -0
  13. package/dist/check.js +646 -0
  14. package/dist/check.js.map +1 -0
  15. package/dist/cli.d.ts +19 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +357 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/config.d.ts +175 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js +180 -0
  22. package/dist/config.js.map +1 -0
  23. package/dist/databases/crossref.d.ts +53 -0
  24. package/dist/databases/crossref.d.ts.map +1 -0
  25. package/dist/databases/crossref.js +138 -0
  26. package/dist/databases/crossref.js.map +1 -0
  27. package/dist/databases/index.d.ts +12 -0
  28. package/dist/databases/index.d.ts.map +1 -0
  29. package/dist/databases/index.js +9 -0
  30. package/dist/databases/index.js.map +1 -0
  31. package/dist/databases/openalex.d.ts +29 -0
  32. package/dist/databases/openalex.d.ts.map +1 -0
  33. package/dist/databases/openalex.js +117 -0
  34. package/dist/databases/openalex.js.map +1 -0
  35. package/dist/databases/openlibrary.d.ts +26 -0
  36. package/dist/databases/openlibrary.d.ts.map +1 -0
  37. package/dist/databases/openlibrary.js +79 -0
  38. package/dist/databases/openlibrary.js.map +1 -0
  39. package/dist/databases/worldcat.d.ts +33 -0
  40. package/dist/databases/worldcat.d.ts.map +1 -0
  41. package/dist/databases/worldcat.js +145 -0
  42. package/dist/databases/worldcat.js.map +1 -0
  43. package/dist/doctor.d.ts +44 -0
  44. package/dist/doctor.d.ts.map +1 -0
  45. package/dist/doctor.js +386 -0
  46. package/dist/doctor.js.map +1 -0
  47. package/dist/existence.d.ts +70 -0
  48. package/dist/existence.d.ts.map +1 -0
  49. package/dist/existence.js +308 -0
  50. package/dist/existence.js.map +1 -0
  51. package/dist/http.d.ts +97 -0
  52. package/dist/http.d.ts.map +1 -0
  53. package/dist/http.js +543 -0
  54. package/dist/http.js.map +1 -0
  55. package/dist/identifiers.d.ts +44 -0
  56. package/dist/identifiers.d.ts.map +1 -0
  57. package/dist/identifiers.js +111 -0
  58. package/dist/identifiers.js.map +1 -0
  59. package/dist/index.d.ts +9 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +8 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/linkage.d.ts +29 -0
  64. package/dist/linkage.d.ts.map +1 -0
  65. package/dist/linkage.js +73 -0
  66. package/dist/linkage.js.map +1 -0
  67. package/dist/markdown/blocks.d.ts +19 -0
  68. package/dist/markdown/blocks.d.ts.map +1 -0
  69. package/dist/markdown/blocks.js +69 -0
  70. package/dist/markdown/blocks.js.map +1 -0
  71. package/dist/markdown/citekeys.d.ts +22 -0
  72. package/dist/markdown/citekeys.d.ts.map +1 -0
  73. package/dist/markdown/citekeys.js +100 -0
  74. package/dist/markdown/citekeys.js.map +1 -0
  75. package/dist/markdown/glob.d.ts +18 -0
  76. package/dist/markdown/glob.d.ts.map +1 -0
  77. package/dist/markdown/glob.js +26 -0
  78. package/dist/markdown/glob.js.map +1 -0
  79. package/dist/markdown/prose.d.ts +19 -0
  80. package/dist/markdown/prose.d.ts.map +1 -0
  81. package/dist/markdown/prose.js +81 -0
  82. package/dist/markdown/prose.js.map +1 -0
  83. package/dist/output/json.d.ts +21 -0
  84. package/dist/output/json.d.ts.map +1 -0
  85. package/dist/output/json.js +24 -0
  86. package/dist/output/json.js.map +1 -0
  87. package/dist/output/markdown.d.ts +21 -0
  88. package/dist/output/markdown.d.ts.map +1 -0
  89. package/dist/output/markdown.js +194 -0
  90. package/dist/output/markdown.js.map +1 -0
  91. package/dist/output/sarif.d.ts +31 -0
  92. package/dist/output/sarif.d.ts.map +1 -0
  93. package/dist/output/sarif.js +322 -0
  94. package/dist/output/sarif.js.map +1 -0
  95. package/dist/output/text.d.ts +27 -0
  96. package/dist/output/text.d.ts.map +1 -0
  97. package/dist/output/text.js +212 -0
  98. package/dist/output/text.js.map +1 -0
  99. package/dist/phrases/load.d.ts +34 -0
  100. package/dist/phrases/load.d.ts.map +1 -0
  101. package/dist/phrases/load.js +148 -0
  102. package/dist/phrases/load.js.map +1 -0
  103. package/dist/phrases.d.ts +27 -0
  104. package/dist/phrases.d.ts.map +1 -0
  105. package/dist/phrases.js +116 -0
  106. package/dist/phrases.js.map +1 -0
  107. package/dist/schema/csl.d.ts +429 -0
  108. package/dist/schema/csl.d.ts.map +1 -0
  109. package/dist/schema/csl.js +101 -0
  110. package/dist/schema/csl.js.map +1 -0
  111. package/dist/schema/output.d.ts +1116 -0
  112. package/dist/schema/output.d.ts.map +1 -0
  113. package/dist/schema/output.js +419 -0
  114. package/dist/schema/output.js.map +1 -0
  115. package/dist/suppression.d.ts +106 -0
  116. package/dist/suppression.d.ts.map +1 -0
  117. package/dist/suppression.js +134 -0
  118. package/dist/suppression.js.map +1 -0
  119. package/dist/version.d.ts +11 -0
  120. package/dist/version.d.ts.map +1 -0
  121. package/dist/version.js +14 -0
  122. package/dist/version.js.map +1 -0
  123. package/dist/worklist.d.ts +32 -0
  124. package/dist/worklist.d.ts.map +1 -0
  125. package/dist/worklist.js +211 -0
  126. package/dist/worklist.js.map +1 -0
  127. package/package.json +82 -0
@@ -0,0 +1,322 @@
1
+ /**
2
+ * SARIF v2.1.0 renderer for bibcheck output.
3
+ *
4
+ * Uses node-sarif-builder for the structural boilerplate (tool/driver, rules,
5
+ * results) and then post-processes the resulting object to add:
6
+ * - originalUriBaseIds so that relative artifact URIs resolve correctly
7
+ * - partialFingerprints (sha256-based, stable across runs) for deduplication
8
+ *
9
+ * The library does not expose a first-class API for those two fields, so they
10
+ * are injected directly onto the mutable run/result objects that the builders
11
+ * expose as public properties (`.run` and `.result`). This is the documented
12
+ * escape hatch in node-sarif-builder.
13
+ *
14
+ * Worklist items are NOT emitted as SARIF results. They are informational and
15
+ * would cause spurious CI failures if surfaced as findings.
16
+ */
17
+ import { createHash } from 'node:crypto';
18
+ import { SarifBuilder, SarifRunBuilder, SarifResultBuilder, SarifRuleBuilder, } from 'node-sarif-builder';
19
+ // ---------------------------------------------------------------------------
20
+ // Constants
21
+ // ---------------------------------------------------------------------------
22
+ const INFORMATION_URI = 'https://github.com/de-otio/bibcheck';
23
+ const DOCS_BASE = 'https://github.com/de-otio/bibcheck/blob/main/docs/output-schema.md';
24
+ const STATIC_RULES = [
25
+ {
26
+ id: 'bibcheck/existence/metadata-mismatch',
27
+ shortDescription: 'Existence check found a metadata mismatch against the databases.',
28
+ anchor: 'existence-metadata-mismatch',
29
+ },
30
+ {
31
+ id: 'bibcheck/existence/not-found-in-databases',
32
+ shortDescription: 'The entry was not found in any applicable bibliographic database (a fabrication signal).',
33
+ anchor: 'existence-not-found-in-databases',
34
+ },
35
+ {
36
+ id: 'bibcheck/existence/unverifiable',
37
+ shortDescription: 'Existence of the entry could not be verified in any database.',
38
+ anchor: 'existence-unverifiable',
39
+ },
40
+ {
41
+ id: 'bibcheck/identifiers/malformed',
42
+ shortDescription: 'A DOI/ISBN/URL identifier is malformed or has a bad checksum (a fabrication signal).',
43
+ anchor: 'identifiers-malformed',
44
+ },
45
+ {
46
+ id: 'bibcheck/canonical/dead-url',
47
+ shortDescription: 'The canonical URL for this entry is dead (no response).',
48
+ anchor: 'canonical-dead-url',
49
+ },
50
+ {
51
+ id: 'bibcheck/canonical/wrong-host',
52
+ shortDescription: 'The canonical URL points to a non-approved host.',
53
+ anchor: 'canonical-wrong-host',
54
+ },
55
+ {
56
+ id: 'bibcheck/canonical/live-url-not-archived-snapshot',
57
+ shortDescription: 'The URL is live but is not a stable archived snapshot.',
58
+ anchor: 'canonical-live-url-not-archived-snapshot',
59
+ },
60
+ {
61
+ id: 'bibcheck/canonical/no-url-on-pre-doi-entry',
62
+ shortDescription: 'A pre-DOI entry is missing a canonical URL.',
63
+ anchor: 'canonical-no-url-on-pre-doi-entry',
64
+ },
65
+ {
66
+ id: 'bibcheck/linkage/unresolved',
67
+ shortDescription: 'A @citekey reference in the prose has no matching bibliography entry.',
68
+ anchor: 'linkage-unresolved',
69
+ },
70
+ {
71
+ id: 'bibcheck/linkage/orphan',
72
+ shortDescription: 'A bibliography entry is never cited in any document (informational; reverse linkage).',
73
+ anchor: 'linkage-orphan',
74
+ },
75
+ ];
76
+ // ---------------------------------------------------------------------------
77
+ // Fingerprint helper
78
+ // ---------------------------------------------------------------------------
79
+ function fingerprint(ruleId, fileUri, startLine, messageText) {
80
+ const payload = JSON.stringify({ ruleId, fileUri, startLine, messageText });
81
+ return createHash('sha256').update(payload).digest('hex').slice(0, 16);
82
+ }
83
+ // ---------------------------------------------------------------------------
84
+ // Result builders
85
+ // ---------------------------------------------------------------------------
86
+ /** Add a result to the run with a partialFingerprint attached. */
87
+ function addResult(sarifRunBuilder, opts) {
88
+ const builder = new SarifResultBuilder();
89
+ const initOpts = {
90
+ level: opts.level,
91
+ messageText: opts.messageText,
92
+ ruleId: opts.ruleId,
93
+ fileUri: opts.fileUri,
94
+ };
95
+ // startLine must be >= 1 for the library; 0 means "no line info"
96
+ if (opts.startLine != null && opts.startLine >= 1) {
97
+ initOpts.startLine = opts.startLine;
98
+ }
99
+ builder.initSimple(initOpts);
100
+ // Inject uriBaseId so the relative URI resolves against PROJECTROOT.
101
+ const physLoc = builder.result.locations?.[0]?.physicalLocation;
102
+ if (physLoc?.artifactLocation) {
103
+ physLoc.artifactLocation.uriBaseId = 'PROJECTROOT';
104
+ }
105
+ // Inject partialFingerprints for deduplication.
106
+ const fp = fingerprint(opts.ruleId, opts.fileUri, opts.startLine ?? 0, opts.messageText);
107
+ builder.result['partialFingerprints'] = {
108
+ bibcheckV1: fp,
109
+ };
110
+ sarifRunBuilder.addResult(builder);
111
+ }
112
+ // ---------------------------------------------------------------------------
113
+ // Existence findings
114
+ // ---------------------------------------------------------------------------
115
+ function addExistenceResults(output, sarifRunBuilder) {
116
+ for (const entry of output.entries) {
117
+ const existence = entry.existence;
118
+ if (existence == null)
119
+ continue;
120
+ if (existence.status === 'metadata-mismatch') {
121
+ addResult(sarifRunBuilder, {
122
+ ruleId: 'bibcheck/existence/metadata-mismatch',
123
+ level: 'error',
124
+ messageText: `Existence metadata mismatch for @${entry.citekey}: database records do not match the bibliography entry.`,
125
+ fileUri: 'sources.json',
126
+ });
127
+ }
128
+ else if (existence.status === 'not-found-in-databases') {
129
+ // Absence is a fabrication signal and gates by default (Q1).
130
+ addResult(sarifRunBuilder, {
131
+ ruleId: 'bibcheck/existence/not-found-in-databases',
132
+ level: 'error',
133
+ messageText: `@${entry.citekey} was not found in any applicable bibliographic database.`,
134
+ fileUri: 'sources.json',
135
+ });
136
+ }
137
+ }
138
+ }
139
+ // ---------------------------------------------------------------------------
140
+ // Identifier findings (T21/T22): malformed/bad-checksum local identifiers
141
+ // ---------------------------------------------------------------------------
142
+ function addIdentifierResults(output, sarifRunBuilder) {
143
+ for (const entry of output.entries) {
144
+ const ids = entry.identifiers;
145
+ if (ids == null)
146
+ continue;
147
+ const problems = [];
148
+ if (ids.doi === 'malformed')
149
+ problems.push('DOI malformed');
150
+ if (ids.isbn === 'malformed')
151
+ problems.push('ISBN malformed');
152
+ if (ids.isbn === 'bad-checksum')
153
+ problems.push('ISBN bad checksum');
154
+ if (ids.url === 'malformed')
155
+ problems.push('URL malformed');
156
+ if (problems.length === 0)
157
+ continue;
158
+ addResult(sarifRunBuilder, {
159
+ ruleId: 'bibcheck/identifiers/malformed',
160
+ level: 'error',
161
+ messageText: `Malformed identifier for @${entry.citekey}: ${problems.join(', ')}.`,
162
+ fileUri: 'sources.json',
163
+ });
164
+ }
165
+ }
166
+ // ---------------------------------------------------------------------------
167
+ // Canonical findings
168
+ // ---------------------------------------------------------------------------
169
+ const CANONICAL_ERROR_STATUSES = new Set([
170
+ 'dead-url',
171
+ 'wrong-host',
172
+ 'live-url-not-archived-snapshot',
173
+ 'no-url-on-pre-doi-entry',
174
+ ]);
175
+ function addCanonicalResults(output, sarifRunBuilder) {
176
+ for (const entry of output.entries) {
177
+ const canonical = entry.canonical;
178
+ if (canonical == null)
179
+ continue;
180
+ if (!CANONICAL_ERROR_STATUSES.has(canonical.status))
181
+ continue;
182
+ const urlPart = canonical.url != null ? ` (${canonical.url})` : '';
183
+ addResult(sarifRunBuilder, {
184
+ ruleId: `bibcheck/canonical/${canonical.status}`,
185
+ level: 'error',
186
+ messageText: `Canonical URL issue [${canonical.status}] for @${entry.citekey}${urlPart}.`,
187
+ fileUri: 'sources.json',
188
+ });
189
+ }
190
+ }
191
+ // ---------------------------------------------------------------------------
192
+ // Linkage findings
193
+ // ---------------------------------------------------------------------------
194
+ function addLinkageResults(output, sarifRunBuilder) {
195
+ for (const entry of output.linkage) {
196
+ if (entry.status !== 'unresolved')
197
+ continue;
198
+ const sortedRefs = [...entry.references].sort((a, b) => {
199
+ const fc = a.file.localeCompare(b.file);
200
+ return fc !== 0 ? fc : a.line - b.line;
201
+ });
202
+ for (const ref of sortedRefs) {
203
+ addResult(sarifRunBuilder, {
204
+ ruleId: 'bibcheck/linkage/unresolved',
205
+ level: 'error',
206
+ messageText: `Unresolved @${entry.citekey} reference — no matching bibliography entry.`,
207
+ fileUri: ref.file,
208
+ startLine: ref.line,
209
+ });
210
+ }
211
+ }
212
+ }
213
+ /**
214
+ * Orphaned bibliography entries (reverse linkage, H2). Emitted at `note` level
215
+ * so they surface in Code Scanning as informational annotations and never
216
+ * cause a CI failure. Bibliography-level, so anchored to sources.json.
217
+ */
218
+ function addOrphanResults(output, sarifRunBuilder) {
219
+ for (const entry of output.linkage) {
220
+ if (entry.status !== 'orphan')
221
+ continue;
222
+ addResult(sarifRunBuilder, {
223
+ ruleId: 'bibcheck/linkage/orphan',
224
+ level: 'note',
225
+ messageText: `@${entry.citekey} is in the bibliography but is never cited in any document (informational).`,
226
+ fileUri: 'sources.json',
227
+ });
228
+ }
229
+ }
230
+ // ---------------------------------------------------------------------------
231
+ // Phrase flag findings
232
+ // ---------------------------------------------------------------------------
233
+ function addPhraseFlagResults(output, sarifRunBuilder) {
234
+ for (const flag of output.phraseFlags) {
235
+ if (flag.status !== 'flagged')
236
+ continue;
237
+ addResult(sarifRunBuilder, {
238
+ ruleId: `bibcheck/phrase/${flag.patternKey}`,
239
+ level: 'warning',
240
+ messageText: `phrase: "${flag.matchedText}"`,
241
+ fileUri: flag.file,
242
+ startLine: flag.line,
243
+ });
244
+ }
245
+ }
246
+ // ---------------------------------------------------------------------------
247
+ // Dynamic rule collection (phrase pattern rules)
248
+ // ---------------------------------------------------------------------------
249
+ function collectPhraseRules(output) {
250
+ const seen = new Set();
251
+ const rules = [];
252
+ for (const flag of output.phraseFlags) {
253
+ const id = `bibcheck/phrase/${flag.patternKey}`;
254
+ if (!seen.has(id)) {
255
+ seen.add(id);
256
+ rules.push({
257
+ id,
258
+ shortDescription: `Phrase denylist match for pattern key "${flag.patternKey}".`,
259
+ anchor: `phrase-${flag.patternKey}`,
260
+ });
261
+ }
262
+ }
263
+ return rules;
264
+ }
265
+ // ---------------------------------------------------------------------------
266
+ // Public API
267
+ // ---------------------------------------------------------------------------
268
+ /**
269
+ * Render a validated Output as a SARIF v2.1.0 JSON string.
270
+ *
271
+ * The SARIF document contains one run with:
272
+ * - tool.driver with all applicable rules defined
273
+ * - originalUriBaseIds.PROJECTROOT so relative file URIs resolve correctly
274
+ * - one result per finding (existence mismatches, canonical errors, linkage
275
+ * unresolved, phrase flags), with partialFingerprints for deduplication
276
+ *
277
+ * Worklist items are not emitted as SARIF results (they are informational).
278
+ * Acknowledged phrase flags are not emitted (only status: 'flagged' entries).
279
+ */
280
+ export function renderSarif(output) {
281
+ // Collect dynamic rules from phrase flags.
282
+ const phraseRules = collectPhraseRules(output);
283
+ const allRuleDefs = [...STATIC_RULES, ...phraseRules];
284
+ // Build the run.
285
+ const version = output.tool.version;
286
+ const sarifRunBuilder = new SarifRunBuilder().initSimple({
287
+ toolDriverName: 'bibcheck',
288
+ toolDriverVersion: version,
289
+ url: INFORMATION_URI,
290
+ });
291
+ // Add semanticVersion directly (node-sarif-builder doesn't expose a setter).
292
+ sarifRunBuilder.run.tool.driver['semanticVersion'] =
293
+ version;
294
+ // Register all rules.
295
+ for (const ruleDef of allRuleDefs) {
296
+ const ruleBuilder = new SarifRuleBuilder().initSimple({
297
+ ruleId: ruleDef.id,
298
+ shortDescriptionText: ruleDef.shortDescription,
299
+ helpUri: `${DOCS_BASE}#${ruleDef.anchor}`,
300
+ });
301
+ sarifRunBuilder.addRule(ruleBuilder);
302
+ }
303
+ // Add findings.
304
+ addIdentifierResults(output, sarifRunBuilder);
305
+ addExistenceResults(output, sarifRunBuilder);
306
+ addCanonicalResults(output, sarifRunBuilder);
307
+ addLinkageResults(output, sarifRunBuilder);
308
+ addOrphanResults(output, sarifRunBuilder);
309
+ addPhraseFlagResults(output, sarifRunBuilder);
310
+ // Inject originalUriBaseIds so relative URIs resolve to the project root.
311
+ sarifRunBuilder.run['originalUriBaseIds'] = {
312
+ PROJECTROOT: { uri: 'file:///$PROJECT_ROOT/' },
313
+ };
314
+ // Build the top-level SARIF document.
315
+ const sarifBuilder = new SarifBuilder();
316
+ sarifBuilder.addRun(sarifRunBuilder);
317
+ const sarifObj = sarifBuilder.buildSarifOutput();
318
+ // Override $schema to use https (the library defaults to http).
319
+ sarifObj['$schema'] = 'https://json.schemastore.org/sarif-2.1.0.json';
320
+ return JSON.stringify(sarifObj, null, 2) + '\n';
321
+ }
322
+ //# sourceMappingURL=sarif.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sarif.js","sourceRoot":"","sources":["../../src/output/sarif.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Compact text renderer for bibcheck output.
3
+ *
4
+ * Produces one line per finding in the format:
5
+ * <file>:<line>: <level>: <message>
6
+ *
7
+ * For findings without a specific file/line (existence-layer results tied to
8
+ * the bibliography source, not a prose file), uses "sources.json:0" as a
9
+ * stand-in artifact location.
10
+ *
11
+ * A summary line is appended at the end:
12
+ * <n> errors, <m> warnings, <k> notes
13
+ */
14
+ import type { Output } from '../schema/output.js';
15
+ /**
16
+ * Render a validated Output as a compact text string.
17
+ *
18
+ * Format: one line per finding as `<file>:<line>: <level>: <message>`,
19
+ * followed by a summary line with counts.
20
+ *
21
+ * Level mapping (same as SARIF):
22
+ * - error: existence metadata-mismatch, canonical dead-url/wrong-host/etc, linkage unresolved
23
+ * - warning: phrase flags (flagged, not acknowledged)
24
+ * - note: existence unverifiable, worklist items, orphaned bibliography entries
25
+ */
26
+ export declare function renderText(output: Output): string;
27
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/output/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAyKlD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA4BjD"}
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Compact text renderer for bibcheck output.
3
+ *
4
+ * Produces one line per finding in the format:
5
+ * <file>:<line>: <level>: <message>
6
+ *
7
+ * For findings without a specific file/line (existence-layer results tied to
8
+ * the bibliography source, not a prose file), uses "sources.json:0" as a
9
+ * stand-in artifact location.
10
+ *
11
+ * A summary line is appended at the end:
12
+ * <n> errors, <m> warnings, <k> notes
13
+ */
14
+ // ---------------------------------------------------------------------------
15
+ // Helpers
16
+ // ---------------------------------------------------------------------------
17
+ function fmt(f) {
18
+ return `${f.file}:${f.line}: ${f.level}: ${f.message}`;
19
+ }
20
+ // ---------------------------------------------------------------------------
21
+ // Finding collectors
22
+ // ---------------------------------------------------------------------------
23
+ function collectExistenceFindings(output) {
24
+ const findings = [];
25
+ for (const entry of output.entries) {
26
+ if (entry.existence?.status === 'metadata-mismatch') {
27
+ findings.push({
28
+ file: 'sources.json',
29
+ line: 0,
30
+ level: 'error',
31
+ message: `existence metadata-mismatch for @${entry.citekey}`,
32
+ });
33
+ }
34
+ else if (entry.existence?.status === 'not-found-in-databases') {
35
+ // Absence is a fabrication signal and gates by default (Q1).
36
+ findings.push({
37
+ file: 'sources.json',
38
+ line: 0,
39
+ level: 'error',
40
+ message: `existence not-found-in-databases for @${entry.citekey}`,
41
+ });
42
+ }
43
+ else if (entry.existence?.status === 'unverifiable') {
44
+ findings.push({
45
+ file: 'sources.json',
46
+ line: 0,
47
+ level: 'note',
48
+ message: `existence unverifiable for @${entry.citekey}`,
49
+ });
50
+ }
51
+ }
52
+ return findings;
53
+ }
54
+ /** Malformed/bad-checksum local identifiers (T21/T22). Gating → error level. */
55
+ function collectIdentifierFindings(output) {
56
+ const findings = [];
57
+ for (const entry of output.entries) {
58
+ const ids = entry.identifiers;
59
+ if (ids === null || ids === undefined)
60
+ continue;
61
+ const problems = [];
62
+ if (ids.doi === 'malformed')
63
+ problems.push('doi malformed');
64
+ if (ids.isbn === 'malformed')
65
+ problems.push('isbn malformed');
66
+ if (ids.isbn === 'bad-checksum')
67
+ problems.push('isbn bad-checksum');
68
+ if (ids.url === 'malformed')
69
+ problems.push('url malformed');
70
+ if (problems.length === 0)
71
+ continue;
72
+ findings.push({
73
+ file: 'sources.json',
74
+ line: 0,
75
+ level: 'error',
76
+ message: `identifier ${problems.join(', ')} for @${entry.citekey}`,
77
+ });
78
+ }
79
+ return findings;
80
+ }
81
+ function collectCanonicalFindings(output) {
82
+ const findings = [];
83
+ const errorStatuses = new Set([
84
+ 'dead-url',
85
+ 'wrong-host',
86
+ 'live-url-not-archived-snapshot',
87
+ 'no-url-on-pre-doi-entry',
88
+ ]);
89
+ for (const entry of output.entries) {
90
+ const canon = entry.canonical;
91
+ if (canon == null)
92
+ continue;
93
+ if (errorStatuses.has(canon.status)) {
94
+ const urlPart = canon.url != null ? ` (${canon.url})` : '';
95
+ findings.push({
96
+ file: 'sources.json',
97
+ line: 0,
98
+ level: 'error',
99
+ message: `canonical ${canon.status} for @${entry.citekey}${urlPart}`,
100
+ });
101
+ }
102
+ }
103
+ return findings;
104
+ }
105
+ function collectLinkageFindings(output) {
106
+ const findings = [];
107
+ for (const entry of output.linkage) {
108
+ if (entry.status !== 'unresolved')
109
+ continue;
110
+ const sortedRefs = [...entry.references].sort((a, b) => {
111
+ const fc = a.file.localeCompare(b.file);
112
+ return fc !== 0 ? fc : a.line - b.line;
113
+ });
114
+ for (const ref of sortedRefs) {
115
+ findings.push({
116
+ file: ref.file,
117
+ line: ref.line,
118
+ level: 'error',
119
+ message: `unresolved linkage @${entry.citekey}`,
120
+ });
121
+ }
122
+ }
123
+ return findings;
124
+ }
125
+ /**
126
+ * Orphaned bibliography entries (reverse linkage, H2). Informational only —
127
+ * `note` level, never gating. Bibliography-level finding, so uses the
128
+ * sources.json:0 stand-in location.
129
+ */
130
+ function collectOrphanFindings(output) {
131
+ const findings = [];
132
+ for (const entry of output.linkage) {
133
+ if (entry.status !== 'orphan')
134
+ continue;
135
+ findings.push({
136
+ file: 'sources.json',
137
+ line: 0,
138
+ level: 'note',
139
+ message: `orphaned bibliography entry @${entry.citekey} (never cited; informational)`,
140
+ });
141
+ }
142
+ return findings;
143
+ }
144
+ function collectPhraseFlagFindings(output) {
145
+ const findings = [];
146
+ for (const flag of output.phraseFlags) {
147
+ if (flag.status !== 'flagged')
148
+ continue;
149
+ findings.push({
150
+ file: flag.file,
151
+ line: flag.line,
152
+ level: 'warning',
153
+ message: `phrase [${flag.patternKey}]: "${flag.matchedText}"`,
154
+ });
155
+ }
156
+ return findings;
157
+ }
158
+ function collectWorklistFindings(output) {
159
+ const findings = [];
160
+ for (const item of output.worklist) {
161
+ findings.push({
162
+ file: item.file,
163
+ line: item.line,
164
+ level: 'note',
165
+ message: `worklist [${item.type}] ${item.citation} — ${item.recommendedAction}`,
166
+ });
167
+ }
168
+ return findings;
169
+ }
170
+ // ---------------------------------------------------------------------------
171
+ // Public API
172
+ // ---------------------------------------------------------------------------
173
+ /**
174
+ * Render a validated Output as a compact text string.
175
+ *
176
+ * Format: one line per finding as `<file>:<line>: <level>: <message>`,
177
+ * followed by a summary line with counts.
178
+ *
179
+ * Level mapping (same as SARIF):
180
+ * - error: existence metadata-mismatch, canonical dead-url/wrong-host/etc, linkage unresolved
181
+ * - warning: phrase flags (flagged, not acknowledged)
182
+ * - note: existence unverifiable, worklist items, orphaned bibliography entries
183
+ */
184
+ export function renderText(output) {
185
+ const allFindings = [
186
+ ...collectIdentifierFindings(output),
187
+ ...collectExistenceFindings(output),
188
+ ...collectCanonicalFindings(output),
189
+ ...collectLinkageFindings(output),
190
+ ...collectOrphanFindings(output),
191
+ ...collectPhraseFlagFindings(output),
192
+ ...collectWorklistFindings(output),
193
+ ];
194
+ // Sort for determinism: file, line, level, message.
195
+ allFindings.sort((a, b) => {
196
+ const fc = a.file.localeCompare(b.file);
197
+ if (fc !== 0)
198
+ return fc;
199
+ if (a.line !== b.line)
200
+ return a.line - b.line;
201
+ if (a.level !== b.level)
202
+ return a.level.localeCompare(b.level);
203
+ return a.message.localeCompare(b.message);
204
+ });
205
+ const errors = allFindings.filter((f) => f.level === 'error').length;
206
+ const warnings = allFindings.filter((f) => f.level === 'warning').length;
207
+ const notes = allFindings.filter((f) => f.level === 'note').length;
208
+ const lines = allFindings.map(fmt);
209
+ lines.push(`${errors} errors, ${warnings} warnings, ${notes} notes`);
210
+ return lines.join('\n') + '\n';
211
+ }
212
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/output/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,SAAS,GAAG,CAAC,CAAU;IACrB,OAAO,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;AACzD,CAAC;AAED,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,SAAS,wBAAwB,CAAC,MAAc;IAC9C,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,SAAS,EAAE,MAAM,KAAK,mBAAmB,EAAE,CAAC;YACpD,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,cAAc;gBACpB,IAAI,EAAE,CAAC;gBACP,KAAK,EAAE,OAAO;gBACd,OAAO,EAAE,oCAAoC,KAAK,CAAC,OAAO,EAAE;aAC7D,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,KAAK,CAAC,SAAS,EAAE,MAAM,KAAK,wBAAwB,EAAE,CAAC;YAChE,6DAA6D;YAC7D,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,cAAc;gBACpB,IAAI,EAAE,CAAC;gBACP,KAAK,EAAE,OAAO;gBACd,OAAO,EAAE,yCAAyC,KAAK,CAAC,OAAO,EAAE;aAClE,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,KAAK,CAAC,SAAS,EAAE,MAAM,KAAK,cAAc,EAAE,CAAC;YACtD,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,cAAc;gBACpB,IAAI,EAAE,CAAC;gBACP,KAAK,EAAE,MAAM;gBACb,OAAO,EAAE,+BAA+B,KAAK,CAAC,OAAO,EAAE;aACxD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,gFAAgF;AAChF,SAAS,yBAAyB,CAAC,MAAc;IAC/C,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,KAAK,CAAC,WAAW,CAAC;QAC9B,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS;YAAE,SAAS;QAChD,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,IAAI,GAAG,CAAC,GAAG,KAAK,WAAW;YAAE,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC5D,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW;YAAE,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC9D,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc;YAAE,QAAQ,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QACpE,IAAI,GAAG,CAAC,GAAG,KAAK,WAAW;YAAE,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC5D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACpC,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,cAAc;YACpB,IAAI,EAAE,CAAC;YACP,KAAK,EAAE,OAAO;YACd,OAAO,EAAE,cAAc,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,OAAO,EAAE;SACnE,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,wBAAwB,CAAC,MAAc;IAC9C,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;QAC5B,UAAU;QACV,YAAY;QACZ,gCAAgC;QAChC,yBAAyB;KAC1B,CAAC,CAAC;IACH,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC;QAC9B,IAAI,KAAK,IAAI,IAAI;YAAE,SAAS;QAC5B,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3D,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,cAAc;gBACpB,IAAI,EAAE,CAAC;gBACP,KAAK,EAAE,OAAO;gBACd,OAAO,EAAE,aAAa,KAAK,CAAC,MAAM,SAAS,KAAK,CAAC,OAAO,GAAG,OAAO,EAAE;aACrE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,sBAAsB,CAAC,MAAc;IAC5C,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,MAAM,KAAK,YAAY;YAAE,SAAS;QAC5C,MAAM,UAAU,GAAG,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACrD,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACxC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;QACzC,CAAC,CAAC,CAAC;QACH,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,KAAK,EAAE,OAAO;gBACd,OAAO,EAAE,uBAAuB,KAAK,CAAC,OAAO,EAAE;aAChD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;GAIG;AACH,SAAS,qBAAqB,CAAC,MAAc;IAC3C,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ;YAAE,SAAS;QACxC,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,cAAc;YACpB,IAAI,EAAE,CAAC;YACP,KAAK,EAAE,MAAM;YACb,OAAO,EAAE,gCAAgC,KAAK,CAAC,OAAO,+BAA+B;SACtF,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,yBAAyB,CAAC,MAAc;IAC/C,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACtC,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,SAAS;QACxC,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,WAAW,IAAI,CAAC,UAAU,OAAO,IAAI,CAAC,WAAW,GAAG;SAC9D,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,uBAAuB,CAAC,MAAc;IAC7C,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACnC,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,MAAM;YACb,OAAO,EAAE,aAAa,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,QAAQ,MAAM,IAAI,CAAC,iBAAiB,EAAE;SAChF,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,MAAM,WAAW,GAAc;QAC7B,GAAG,yBAAyB,CAAC,MAAM,CAAC;QACpC,GAAG,wBAAwB,CAAC,MAAM,CAAC;QACnC,GAAG,wBAAwB,CAAC,MAAM,CAAC;QACnC,GAAG,sBAAsB,CAAC,MAAM,CAAC;QACjC,GAAG,qBAAqB,CAAC,MAAM,CAAC;QAChC,GAAG,yBAAyB,CAAC,MAAM,CAAC;QACpC,GAAG,uBAAuB,CAAC,MAAM,CAAC;KACnC,CAAC;IAEF,oDAAoD;IACpD,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACxB,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,EAAE,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;YAAE,OAAO,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;QAC9C,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;YAAE,OAAO,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC/D,OAAO,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC;IACrE,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;IACzE,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IAEnE,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnC,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,YAAY,QAAQ,cAAc,KAAK,QAAQ,CAAC,CAAC;IAErE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Phrase denylist loader.
3
+ *
4
+ * Reads a project-supplied TOML file, validates it with Zod, compiles each
5
+ * pattern with RE2JS (linear-time guarantees; ReDoS-safe), and returns the
6
+ * resulting CompiledPattern[].
7
+ */
8
+ import { RE2JS } from 're2js';
9
+ export interface DenylistEntry {
10
+ key: string;
11
+ regex: string;
12
+ flags: string;
13
+ referenceUrl: string | null;
14
+ description?: string;
15
+ }
16
+ export interface CompiledPattern {
17
+ key: string;
18
+ regex: string;
19
+ flags: string;
20
+ compiled: ReturnType<typeof RE2JS.compile>;
21
+ referenceUrl: string | null;
22
+ description?: string;
23
+ }
24
+ export interface LoadDenylistOptions {
25
+ path: string;
26
+ cwd?: string;
27
+ }
28
+ export declare class PhraseLoaderError extends Error {
29
+ readonly cause?: unknown | undefined;
30
+ name: "PhraseLoaderError";
31
+ constructor(message: string, cause?: unknown | undefined);
32
+ }
33
+ export declare function loadDenylist(opts: LoadDenylistOptions): Promise<CompiledPattern[]>;
34
+ //# sourceMappingURL=load.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../src/phrases/load.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAM9B,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3C,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAMD,qBAAa,iBAAkB,SAAQ,KAAK;aAGY,KAAK,CAAC,EAAE,OAAO;IAF5D,IAAI,EAAG,mBAAmB,CAAU;gBAEjC,OAAO,EAAE,MAAM,EAA2B,KAAK,CAAC,EAAE,OAAO,YAAA;CAMtE;AA4DD,wBAAsB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAiFxF"}