doc-freshness-checker 2.0.5 → 2.0.7

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 (56) hide show
  1. package/README.md +2 -0
  2. package/dist/runner.js +35 -16
  3. package/dist/runner.js.map +1 -1
  4. package/dist/runner.test.js +433 -1
  5. package/dist/runner.test.js.map +1 -1
  6. package/dist/setupTests.d.ts +0 -3
  7. package/dist/setupTests.js +1 -7
  8. package/dist/setupTests.js.map +1 -1
  9. package/dist/utils/incremental.d.ts +14 -18
  10. package/dist/utils/incremental.js +281 -39
  11. package/dist/utils/incremental.js.map +1 -1
  12. package/dist/utils/incremental.test.js +385 -74
  13. package/dist/utils/incremental.test.js.map +1 -1
  14. package/dist/utils/manifestPaths.d.ts +2 -0
  15. package/dist/utils/manifestPaths.js +6 -0
  16. package/dist/utils/manifestPaths.js.map +1 -0
  17. package/dist/validators/codePatternValidator.d.ts +3 -0
  18. package/dist/validators/codePatternValidator.js +31 -3
  19. package/dist/validators/codePatternValidator.js.map +1 -1
  20. package/dist/validators/codePatternValidator.test.js +64 -0
  21. package/dist/validators/codePatternValidator.test.js.map +1 -1
  22. package/dist/validators/dependencyValidator.js +9 -6
  23. package/dist/validators/dependencyValidator.js.map +1 -1
  24. package/dist/validators/dependencyValidator.test.js +6 -0
  25. package/dist/validators/dependencyValidator.test.js.map +1 -1
  26. package/dist/validators/directoryValidator.d.ts +1 -0
  27. package/dist/validators/directoryValidator.js +17 -4
  28. package/dist/validators/directoryValidator.js.map +1 -1
  29. package/dist/validators/directoryValidator.test.js +7 -0
  30. package/dist/validators/directoryValidator.test.js.map +1 -1
  31. package/dist/validators/fileValidator.d.ts +1 -0
  32. package/dist/validators/fileValidator.js +16 -14
  33. package/dist/validators/fileValidator.js.map +1 -1
  34. package/dist/validators/fileValidator.test.js +5 -0
  35. package/dist/validators/fileValidator.test.js.map +1 -1
  36. package/dist/validators/incrementalInputs.d.ts +1 -0
  37. package/dist/validators/incrementalInputs.js +6 -0
  38. package/dist/validators/incrementalInputs.js.map +1 -0
  39. package/dist/validators/urlValidator.d.ts +2 -1
  40. package/dist/validators/urlValidator.integration.test.d.ts +1 -0
  41. package/dist/validators/urlValidator.integration.test.js +39 -0
  42. package/dist/validators/urlValidator.integration.test.js.map +1 -0
  43. package/dist/validators/urlValidator.js +99 -100
  44. package/dist/validators/urlValidator.js.map +1 -1
  45. package/dist/validators/urlValidator.test.js +187 -40
  46. package/dist/validators/urlValidator.test.js.map +1 -1
  47. package/dist/validators/validationEngine.d.ts +2 -0
  48. package/dist/validators/validationEngine.js +89 -0
  49. package/dist/validators/validationEngine.js.map +1 -1
  50. package/dist/validators/validationEngine.test.js +93 -1
  51. package/dist/validators/validationEngine.test.js.map +1 -1
  52. package/dist/validators/versionValidator.js +9 -6
  53. package/dist/validators/versionValidator.js.map +1 -1
  54. package/dist/validators/versionValidator.test.js +6 -0
  55. package/dist/validators/versionValidator.test.js.map +1 -1
  56. package/package.json +10 -11
package/README.md CHANGED
@@ -134,6 +134,8 @@ Options:
134
134
  --vector-search Enable semantic vector search for doc-code mismatches
135
135
  ```
136
136
 
137
+ Incremental mode skips unchanged documents only after a clean run when the effective configuration and project inventory are unchanged. It conservatively revalidates all documents when filesystem or external inputs cannot be fingerprinted safely.
138
+
137
139
  ### Common Examples
138
140
 
139
141
  ```bash
package/dist/runner.js CHANGED
@@ -27,6 +27,12 @@ import { FreshnessScorer } from './scoring/freshnessScorer.js';
27
27
  import { IncrementalChecker } from './utils/incremental.js';
28
28
  import { VectorSearch } from './semantic/vectorSearch.js';
29
29
  import { loadConfig } from './config/loader.js';
30
+ const REPORTER_OUTPUT = {
31
+ console: 'console',
32
+ json: 'stable',
33
+ markdown: 'timestamped',
34
+ enhanced: 'timestamped',
35
+ };
30
36
  /**
31
37
  * Main entry point - run with config object
32
38
  */
@@ -94,34 +100,39 @@ export async function run(config) {
94
100
  if (config.verbose) {
95
101
  console.log('Scanning documentation files...');
96
102
  }
97
- let documents = await parser.scanDocuments();
103
+ const allDocuments = await parser.scanDocuments();
104
+ let documentsToValidate = allDocuments;
98
105
  if (config.verbose) {
99
- console.log(`Found ${documents.length} documentation files.`);
106
+ console.log(`Found ${allDocuments.length} documentation files.`);
100
107
  }
101
108
  // Apply incremental checking if enabled
102
109
  let incrementalChecker = null;
103
- if (config.incremental?.enabled) {
104
- incrementalChecker = new IncrementalChecker(config.cache?.dir || '.doc-freshness-cache');
105
- const allDocs = documents;
106
- documents = await incrementalChecker.filterChanged(documents);
110
+ if (config.incremental?.enabled && allDocuments.length > 0) {
111
+ const stateDir = path.resolve(config.rootDir || process.cwd(), config.cache?.dir || config.graph?.cacheDir || '.doc-freshness-cache');
112
+ const incrementalInputs = await validationEngine.captureIncrementalInputs(allDocuments);
113
+ const finalFileReporter = (config.reporters || ['console']).findLast((reporter) => REPORTER_OUTPUT[reporter] === 'stable' || REPORTER_OUTPUT[reporter] === 'timestamped');
114
+ const inventoryExclusions = config.outputPath && finalFileReporter && REPORTER_OUTPUT[finalFileReporter] === 'timestamped'
115
+ ? [path.resolve(config.outputPath)]
116
+ : [];
117
+ incrementalChecker = new IncrementalChecker(stateDir);
118
+ documentsToValidate = await incrementalChecker.filterChanged(allDocuments, config, incrementalInputs, inventoryExclusions);
107
119
  if (config.verbose) {
108
- const stats = incrementalChecker.getStats(allDocs.length, documents.length);
120
+ const stats = incrementalChecker.getStats(allDocuments.length, documentsToValidate.length);
109
121
  console.log(`Incremental mode: checking ${stats.changed} changed files, skipping ${stats.skipped} unchanged.`);
110
122
  }
111
123
  }
124
+ else if (config.incremental?.enabled && config.verbose) {
125
+ console.log('Incremental mode: checking 0 changed files, skipping 0 unchanged.');
126
+ }
112
127
  if (config.verbose) {
113
- const totalRefs = documents.reduce((sum, doc) => sum + doc.references.length, 0);
128
+ const totalRefs = allDocuments.reduce((sum, doc) => sum + doc.references.length, 0);
114
129
  console.log(`Extracted ${totalRefs} references.`);
115
130
  }
116
131
  // Validate references
117
132
  if (config.verbose) {
118
133
  console.log('Validating references...');
119
134
  }
120
- const results = await validationEngine.validate(documents);
121
- // Save incremental state
122
- if (incrementalChecker) {
123
- await incrementalChecker.saveState();
124
- }
135
+ const results = await validationEngine.validate(documentsToValidate);
125
136
  // Build graph if enabled
126
137
  let graph = null;
127
138
  let gitTracker = null;
@@ -133,7 +144,7 @@ export async function run(config) {
133
144
  const codeIndex = codePatternValidator.getSourceIndex();
134
145
  // Build graph
135
146
  const graphBuilder = new GraphBuilder(config);
136
- graph = await graphBuilder.buildGraph(documents, codeIndex);
147
+ graph = await graphBuilder.buildGraph(allDocuments, codeIndex);
137
148
  if (gitTracker.isGitRepo()) {
138
149
  graph.gitCommit = gitTracker.getCurrentCommit();
139
150
  }
@@ -146,7 +157,7 @@ export async function run(config) {
146
157
  // Calculate freshness scores if enabled
147
158
  if (config.freshnessScoring?.enabled) {
148
159
  const scorer = new FreshnessScorer(config);
149
- freshnessScores = scorer.calculateProjectScores(documents, results, gitTracker, graph);
160
+ freshnessScores = scorer.calculateProjectScores(allDocuments, results, gitTracker, graph);
150
161
  }
151
162
  }
152
163
  // Run vector search if enabled
@@ -159,7 +170,7 @@ export async function run(config) {
159
170
  if (config.verbose) {
160
171
  console.log(' Indexing documentation sections...');
161
172
  }
162
- await vectorSearch.indexDocumentation(documents);
173
+ await vectorSearch.indexDocumentation(allDocuments);
163
174
  // Ensure source index is built (may already be built if graph is enabled)
164
175
  if (!codePatternValidator.getSourceIndex()) {
165
176
  if (config.verbose) {
@@ -197,6 +208,14 @@ export async function run(config) {
197
208
  }
198
209
  // Generate reports
199
210
  await generateReports(results, config, graph, gitTracker, freshnessScores, vectorMismatches);
211
+ // Persist only after validation and reporting complete; findings make the next run revalidate everything.
212
+ if (incrementalChecker) {
213
+ const clean = results.documents.length === 0 &&
214
+ !results.vectorMismatches?.length &&
215
+ !validationEngine.hadInvalidResults() &&
216
+ !validationEngine.hadIncompleteValidation();
217
+ await incrementalChecker.saveState(clean);
218
+ }
200
219
  return results;
201
220
  }
202
221
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,10 @@
1
1
  import fs from 'fs';
2
+ import os from 'os';
2
3
  import path from 'path';
4
+ import { glob } from 'glob';
3
5
  import { run, runWithConfig } from './runner.js';
6
+ import { ValidationEngine } from './validators/validationEngine.js';
7
+ import { IncrementalChecker } from './utils/incremental.js';
4
8
  import { withOutputFile } from './test-utils/tempFiles.js';
5
9
  import { captureConsoleLog, captureConsoleWarn } from './test-utils/console.js';
6
10
  vi.mock('fastembed', () => ({
@@ -33,6 +37,19 @@ describe('runner', () => {
33
37
  ];
34
38
  const captureLog = captureConsoleLog;
35
39
  const captureWarn = captureConsoleWarn;
40
+ const mockDocumentScan = (docPath) => {
41
+ vi.mocked(glob).mockResolvedValueOnce([docPath]);
42
+ };
43
+ const withIncrementalRoot = async (name, test) => {
44
+ const rootDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), `runner-inc-${name}-`));
45
+ try {
46
+ await test(rootDir);
47
+ }
48
+ finally {
49
+ vi.mocked(glob).mockResolvedValue([]);
50
+ await fs.promises.rm(rootDir, { recursive: true, force: true });
51
+ }
52
+ };
36
53
  const baseConfig = {
37
54
  rootDir: process.cwd(),
38
55
  include: [],
@@ -55,6 +72,17 @@ describe('runner', () => {
55
72
  reporters: [],
56
73
  verbose: false,
57
74
  };
75
+ const incrementalConfig = (rootDir, overrides = {}) => ({
76
+ ...baseConfig,
77
+ rootDir,
78
+ include: ['*.md'],
79
+ sourcePatterns: [],
80
+ manifestFiles: [],
81
+ incremental: { enabled: true },
82
+ cache: { enabled: false, dir: '.cache' },
83
+ ...overrides,
84
+ rules: { ...baseConfig.rules, ...overrides.rules },
85
+ });
58
86
  afterAll(async () => {
59
87
  await Promise.all(transientCacheDirs.map((dir) => fs.promises.rm(path.join(process.cwd(), dir), { recursive: true, force: true }).catch(() => { })));
60
88
  });
@@ -245,7 +273,7 @@ describe('runner', () => {
245
273
  });
246
274
  });
247
275
  describe('incremental mode', () => {
248
- it('filters changed files with verbose logging', async () => {
276
+ it('reports changed files in verbose mode', async () => {
249
277
  const spy = captureLog();
250
278
  await run({
251
279
  ...baseConfig,
@@ -255,6 +283,410 @@ describe('runner', () => {
255
283
  });
256
284
  expect(spy.mock.calls.flat().join('\n')).toContain('Incremental');
257
285
  });
286
+ it('skips a second clean run with default validator configuration', async () => {
287
+ await withIncrementalRoot('defaults', async (rootDir) => {
288
+ const docPath = path.join(rootDir, 'guide.md');
289
+ const targetPath = path.join(rootDir, 'target.ts');
290
+ await fs.promises.writeFile(docPath, '[target](target.ts)');
291
+ await fs.promises.writeFile(targetPath, 'export const target = true;');
292
+ const config = {
293
+ rootDir,
294
+ include: ['*.md'],
295
+ incremental: { enabled: true },
296
+ graph: { enabled: false },
297
+ cache: { enabled: false, dir: '.cache' },
298
+ reporters: [],
299
+ };
300
+ mockDocumentScan(docPath);
301
+ expect((await run(config)).summary.total).toBe(1);
302
+ mockDocumentScan(docPath);
303
+ expect((await run(config)).summary.total).toBe(0);
304
+ });
305
+ });
306
+ it('allows reuse when git tracking is enabled without freshness scoring', async () => {
307
+ await withIncrementalRoot('git-only', async (rootDir) => {
308
+ const docPath = path.join(rootDir, 'guide.md');
309
+ const targetPath = path.join(rootDir, 'target.ts');
310
+ await fs.promises.writeFile(docPath, '[target](target.ts)');
311
+ await fs.promises.writeFile(targetPath, 'export const target = true;');
312
+ const config = incrementalConfig(rootDir, {
313
+ git: { enabled: true },
314
+ rules: { 'file-path': { enabled: true } },
315
+ });
316
+ mockDocumentScan(docPath);
317
+ expect((await run(config)).summary.total).toBe(1);
318
+ mockDocumentScan(docPath);
319
+ expect((await run(config)).summary.total).toBe(0);
320
+ });
321
+ });
322
+ it('reuses expected skips but revalidates when a disabled unsafe rule is enabled', async () => {
323
+ await withIncrementalRoot('disabled-rule', async (rootDir) => {
324
+ const docPath = path.join(rootDir, 'guide.md');
325
+ await fs.promises.writeFile(docPath, '[site](https://example.com)');
326
+ const config = incrementalConfig(rootDir, {
327
+ rules: { 'external-url': { enabled: false } },
328
+ });
329
+ mockDocumentScan(docPath);
330
+ expect((await run(config)).summary.skipped).toBe(1);
331
+ mockDocumentScan(docPath);
332
+ expect((await run(config)).summary.total).toBe(0);
333
+ mockDocumentScan(docPath);
334
+ expect((await run({ ...config, rules: { ...config.rules, 'external-url': { enabled: true } } })).summary.total).toBe(1);
335
+ });
336
+ });
337
+ it('reuses a clean run containing an illustrative safe-rule skip', async () => {
338
+ await withIncrementalRoot('illustrative-skip', async (rootDir) => {
339
+ const docPath = path.join(rootDir, 'guide.md');
340
+ await fs.promises.writeFile(docPath, '[example](YourProject/file.ts)');
341
+ const config = incrementalConfig(rootDir, {
342
+ rules: { 'file-path': { enabled: true, skipIllustrative: true } },
343
+ });
344
+ mockDocumentScan(docPath);
345
+ expect((await run(config)).summary.skipped).toBe(1);
346
+ mockDocumentScan(docPath);
347
+ expect((await run(config)).summary.total).toBe(0);
348
+ });
349
+ });
350
+ it('keeps state dirty when a validator is missing or throws', async () => {
351
+ await withIncrementalRoot('incomplete', async (rootDir) => {
352
+ const docPath = path.join(rootDir, 'guide.md');
353
+ const cacheDir = path.join(rootDir, '.cache');
354
+ await fs.promises.writeFile(docPath, 'unknown input');
355
+ const unknownExtractor = {
356
+ type: 'unknown-type',
357
+ supportedFormats: ['markdown'],
358
+ supportsFormat: () => true,
359
+ extract: (document) => [{ type: 'unknown-type', value: 'value', lineNumber: 1, raw: 'value', sourceFile: document.path }],
360
+ findLineNumber: () => 1,
361
+ getContext: () => '',
362
+ };
363
+ const missingConfig = incrementalConfig(rootDir, { customExtractors: [unknownExtractor] });
364
+ mockDocumentScan(docPath);
365
+ await run(missingConfig);
366
+ expect(JSON.parse(await fs.promises.readFile(path.join(cacheDir, 'file-hashes.json'), 'utf-8')).clean).toBe(false);
367
+ const throwingValidator = {
368
+ async validateBatch() {
369
+ throw new Error('validator failed');
370
+ },
371
+ };
372
+ const throwingConfig = incrementalConfig(rootDir, {
373
+ customValidators: { 'file-path': throwingValidator },
374
+ rules: { 'file-path': { enabled: true } },
375
+ });
376
+ await fs.promises.writeFile(docPath, '[target](target.ts)');
377
+ mockDocumentScan(docPath);
378
+ await run(throwingConfig);
379
+ expect(JSON.parse(await fs.promises.readFile(path.join(cacheDir, 'file-hashes.json'), 'utf-8')).clean).toBe(false);
380
+ });
381
+ });
382
+ it('revalidates unchanged docs when a referenced source file is renamed', async () => {
383
+ await withIncrementalRoot('source', async (rootDir) => {
384
+ const docsDir = path.join(rootDir, 'docs');
385
+ const sourceDir = path.join(rootDir, 'src');
386
+ const docPath = path.join(docsDir, 'guide.md');
387
+ const sourcePath = path.join(sourceDir, 'server.ts');
388
+ const renamedSourcePath = path.join(sourceDir, 'renamed.ts');
389
+ const cacheDir = path.join(rootDir, '.cache');
390
+ await fs.promises.mkdir(docsDir, { recursive: true });
391
+ await fs.promises.mkdir(sourceDir, { recursive: true });
392
+ await fs.promises.writeFile(docPath, '[source](../src/server.ts)');
393
+ await fs.promises.writeFile(sourcePath, 'export const server = true;');
394
+ const config = incrementalConfig(rootDir, {
395
+ include: ['docs/**/*.md'],
396
+ sourcePatterns: ['src/**/*.ts'],
397
+ rules: { 'file-path': { enabled: true } },
398
+ });
399
+ mockDocumentScan(docPath);
400
+ expect((await run(config)).summary.errors).toBe(0);
401
+ await expect(fs.promises.access(path.join(cacheDir, 'file-hashes.json'))).resolves.toBeUndefined();
402
+ mockDocumentScan(docPath);
403
+ expect((await run(config)).summary.total).toBe(0);
404
+ await fs.promises.rename(sourcePath, renamedSourcePath);
405
+ mockDocumentScan(docPath);
406
+ expect((await run(config)).summary.errors).toBe(1);
407
+ const stateFile = path.join(cacheDir, 'file-hashes.json');
408
+ expect(JSON.parse(await fs.promises.readFile(stateFile, 'utf-8')).clean).toBe(false);
409
+ mockDocumentScan(docPath);
410
+ expect((await run(config)).summary.errors).toBe(1);
411
+ });
412
+ });
413
+ it('reuses the code-pattern source index and fingerprints excluded source matches', async () => {
414
+ await withIncrementalRoot('code-pattern-source', async (rootDir) => {
415
+ const docPath = path.join(rootDir, 'guide.md');
416
+ const sourceDir = path.join(rootDir, '.git');
417
+ const sourcePath = path.join(sourceDir, 'source.ts');
418
+ await fs.promises.mkdir(sourceDir);
419
+ await fs.promises.writeFile(docPath, ['```typescript', 'class StableService {}', '```'].join('\n'));
420
+ await fs.promises.writeFile(sourcePath, 'export class StableService {}');
421
+ const config = incrementalConfig(rootDir, {
422
+ sourcePatterns: ['.g{it,noop}/**/*.ts'],
423
+ rules: { 'code-pattern': { enabled: true, severity: 'warning' } },
424
+ });
425
+ const mockScans = () => {
426
+ mockDocumentScan(docPath);
427
+ vi.mocked(glob).mockResolvedValueOnce([sourcePath]);
428
+ };
429
+ mockScans();
430
+ expect((await run(config)).summary.warnings).toBe(0);
431
+ mockScans();
432
+ expect((await run(config)).summary.total).toBe(0);
433
+ await fs.promises.writeFile(sourcePath, 'export class ReplacementService {}');
434
+ mockScans();
435
+ expect((await run(config)).summary.warnings).toBe(1);
436
+ });
437
+ });
438
+ it('captures graph-only source inputs once per run and skips unchanged validation', async () => {
439
+ await withIncrementalRoot('graph-source', async (rootDir) => {
440
+ const docPath = path.join(rootDir, 'guide.md');
441
+ const sourceDir = path.join(rootDir, 'src');
442
+ const sourcePath = path.join(sourceDir, 'source.ts');
443
+ await fs.promises.mkdir(sourceDir);
444
+ await fs.promises.writeFile(docPath, 'No code-pattern references');
445
+ await fs.promises.writeFile(sourcePath, 'export class GraphSource {}');
446
+ const config = incrementalConfig(rootDir, {
447
+ graph: { enabled: true },
448
+ sourcePatterns: ['src/**/*.ts'],
449
+ });
450
+ const mockScans = () => {
451
+ mockDocumentScan(docPath);
452
+ vi.mocked(glob).mockResolvedValueOnce([sourcePath]);
453
+ };
454
+ const validateSpy = vi.spyOn(ValidationEngine.prototype, 'validate');
455
+ const readSpy = vi.spyOn(fs.promises, 'readFile');
456
+ vi.mocked(glob).mockClear();
457
+ try {
458
+ mockScans();
459
+ await run(config);
460
+ mockScans();
461
+ await run(config);
462
+ expect(validateSpy.mock.calls[0][0]).toHaveLength(1);
463
+ expect(validateSpy.mock.calls[1][0]).toEqual([]);
464
+ expect(vi.mocked(glob).mock.calls.filter(([pattern]) => pattern === 'src/**/*.ts')).toHaveLength(2);
465
+ expect(readSpy.mock.calls.filter(([file]) => file === sourcePath)).toHaveLength(2);
466
+ }
467
+ finally {
468
+ validateSpy.mockRestore();
469
+ readSpy.mockRestore();
470
+ }
471
+ });
472
+ });
473
+ it('bypasses incremental inventory for an empty graph-enabled scan', async () => {
474
+ await withIncrementalRoot('empty-graph', async (rootDir) => {
475
+ const sourceDir = path.join(rootDir, 'src');
476
+ const sourcePath = path.join(sourceDir, 'source.ts');
477
+ await fs.promises.mkdir(sourceDir);
478
+ await fs.promises.writeFile(sourcePath, 'export class GraphSource {}');
479
+ const config = incrementalConfig(rootDir, {
480
+ graph: { enabled: true },
481
+ sourcePatterns: ['src/**/*.ts'],
482
+ });
483
+ const filterSpy = vi.spyOn(IncrementalChecker.prototype, 'filterChanged');
484
+ const readSpy = vi.spyOn(fs.promises, 'readFile');
485
+ vi.mocked(glob).mockClear();
486
+ try {
487
+ vi.mocked(glob).mockResolvedValueOnce([]).mockResolvedValueOnce([sourcePath]);
488
+ await run(config);
489
+ expect(filterSpy).not.toHaveBeenCalled();
490
+ await expect(fs.promises.access(path.join(rootDir, '.cache', 'file-hashes.json'))).rejects.toThrow();
491
+ expect(vi.mocked(glob).mock.calls.filter(([pattern]) => pattern === 'src/**/*.ts')).toHaveLength(1);
492
+ expect(readSpy.mock.calls.filter(([file]) => file === sourcePath)).toHaveLength(1);
493
+ }
494
+ finally {
495
+ filterSpy.mockRestore();
496
+ readSpy.mockRestore();
497
+ }
498
+ });
499
+ });
500
+ it('revalidates code-snippet fallbacks under excluded directories', async () => {
501
+ await withIncrementalRoot('snippet', async (rootDir) => {
502
+ const docsDir = path.join(rootDir, 'docs');
503
+ const dependencyDir = path.join(rootDir, 'node_modules');
504
+ const docPath = path.join(docsDir, 'guide.md');
505
+ const sourcePath = path.join(dependencyDir, 'generated.js');
506
+ await fs.promises.mkdir(docsDir);
507
+ await fs.promises.mkdir(dependencyDir);
508
+ await fs.promises.writeFile(docPath, "```js\nimport * as generated from '../node_modules/generated';\n```");
509
+ await fs.promises.writeFile(sourcePath, 'export const generated = true;');
510
+ const config = incrementalConfig(rootDir, {
511
+ include: ['docs/**/*.md'],
512
+ rules: { 'code-snippet': { enabled: true } },
513
+ });
514
+ mockDocumentScan(docPath);
515
+ expect((await run(config)).summary.warnings).toBe(0);
516
+ mockDocumentScan(docPath);
517
+ expect((await run(config)).summary.total).toBe(1);
518
+ await fs.promises.rm(sourcePath);
519
+ mockDocumentScan(docPath);
520
+ expect((await run(config)).summary.warnings).toBe(1);
521
+ });
522
+ });
523
+ it('revalidates unchanged docs after an unreported info finding', async () => {
524
+ await withIncrementalRoot('info', async (rootDir) => {
525
+ const docPath = path.join(rootDir, 'guide.md');
526
+ const cacheDir = path.join(rootDir, '.cache');
527
+ await fs.promises.writeFile(docPath, 'Install `missingpkg`.');
528
+ await fs.promises.writeFile(path.join(rootDir, 'package.json'), '{}');
529
+ const config = incrementalConfig(rootDir, {
530
+ manifestFiles: ['package.json'],
531
+ rules: { dependency: { enabled: true, severity: 'info' } },
532
+ cache: { enabled: false, dir: cacheDir },
533
+ });
534
+ mockDocumentScan(docPath);
535
+ expect((await run(config)).summary.total).toBe(1);
536
+ mockDocumentScan(docPath);
537
+ expect((await run(config)).summary.total).toBe(1);
538
+ expect(JSON.parse(await fs.promises.readFile(path.join(cacheDir, 'file-hashes.json'), 'utf-8')).clean).toBe(false);
539
+ });
540
+ });
541
+ it('revalidates after generated output changes the project inventory', async () => {
542
+ await withIncrementalRoot('output', async (rootDir) => {
543
+ const docPath = path.join(rootDir, 'guide.md');
544
+ const targetPath = path.join(rootDir, 'target.ts');
545
+ const outputPath = path.join(rootDir, 'report.json');
546
+ await fs.promises.writeFile(docPath, '[target](target.ts)');
547
+ await fs.promises.writeFile(targetPath, 'export const target = true;');
548
+ const config = incrementalConfig(rootDir, {
549
+ rules: { 'file-path': { enabled: true } },
550
+ reporters: ['json'],
551
+ outputPath,
552
+ });
553
+ mockDocumentScan(docPath);
554
+ expect((await run(config)).summary.total).toBe(1);
555
+ mockDocumentScan(docPath);
556
+ expect((await run(config)).summary.total).toBe(1);
557
+ mockDocumentScan(docPath);
558
+ expect((await run(config)).summary.total).toBe(0);
559
+ mockDocumentScan(docPath);
560
+ expect((await run(config)).summary.total).toBe(1);
561
+ });
562
+ });
563
+ it.each(['markdown', 'enhanced'])('skips unchanged docs when %s output is inside rootDir', async (reporter) => {
564
+ await withIncrementalRoot(`output-${reporter}`, async (rootDir) => {
565
+ const docPath = path.join(rootDir, 'guide.md');
566
+ const targetPath = path.join(rootDir, 'target.ts');
567
+ const outputPath = path.join(rootDir, `${reporter}.out`);
568
+ await fs.promises.writeFile(docPath, '[target](target.ts)');
569
+ await fs.promises.writeFile(targetPath, 'export const target = true;');
570
+ const config = incrementalConfig(rootDir, {
571
+ rules: { 'file-path': { enabled: true } },
572
+ reporters: [reporter],
573
+ outputPath,
574
+ });
575
+ mockDocumentScan(docPath);
576
+ expect((await run(config)).summary.total).toBe(1);
577
+ await expect(fs.promises.access(outputPath)).resolves.toBeUndefined();
578
+ mockDocumentScan(docPath);
579
+ expect((await run(config)).summary.total).toBe(0);
580
+ mockDocumentScan(docPath);
581
+ expect((await run(config)).summary.total).toBe(0);
582
+ });
583
+ });
584
+ it.each([
585
+ { reporters: ['markdown', 'json'], expectedTotals: [1, 1, 0, 1] },
586
+ { reporters: ['json', 'markdown'], expectedTotals: [1, 0, 0, 0] },
587
+ ])('keeps mixed reporter ordering stable for incremental output', async ({ reporters, expectedTotals }) => {
588
+ await withIncrementalRoot(`mixed-output-${reporters.join('-')}`, async (rootDir) => {
589
+ const docPath = path.join(rootDir, 'guide.md');
590
+ const targetPath = path.join(rootDir, 'target.ts');
591
+ const outputPath = path.join(rootDir, 'report.out');
592
+ await fs.promises.writeFile(docPath, '[target](target.ts)');
593
+ await fs.promises.writeFile(targetPath, 'export const target = true;');
594
+ const config = incrementalConfig(rootDir, {
595
+ rules: { 'file-path': { enabled: true } },
596
+ reporters,
597
+ outputPath,
598
+ });
599
+ const totals = [];
600
+ for (let runNumber = 0; runNumber < expectedTotals.length; runNumber++) {
601
+ mockDocumentScan(docPath);
602
+ totals.push((await run(config)).summary.total);
603
+ }
604
+ expect(totals).toEqual(expectedTotals);
605
+ });
606
+ });
607
+ it('does not throw for markdown reporting without outputPath', async () => {
608
+ await withIncrementalRoot('markdown-stdout', async (rootDir) => {
609
+ const docPath = path.join(rootDir, 'guide.md');
610
+ await fs.promises.writeFile(docPath, 'No references');
611
+ const config = incrementalConfig(rootDir, { reporters: ['markdown'] });
612
+ const log = captureLog();
613
+ try {
614
+ mockDocumentScan(docPath);
615
+ await expect(run(config)).resolves.toBeDefined();
616
+ }
617
+ finally {
618
+ log.mockRestore();
619
+ }
620
+ });
621
+ });
622
+ it('revalidates when reporting mutates a validation input', async () => {
623
+ await withIncrementalRoot('output-overlap', async (rootDir) => {
624
+ const docPath = path.join(rootDir, 'guide.md');
625
+ const targetPath = path.join(rootDir, 'target.ts');
626
+ await fs.promises.writeFile(docPath, '[target](target.ts)');
627
+ await fs.promises.writeFile(targetPath, 'export const target = true;');
628
+ const config = incrementalConfig(rootDir, {
629
+ rules: { 'file-path': { enabled: true } },
630
+ reporters: ['json'],
631
+ outputPath: targetPath,
632
+ });
633
+ mockDocumentScan(docPath);
634
+ expect((await run(config)).summary.total).toBe(1);
635
+ mockDocumentScan(docPath);
636
+ expect((await run(config)).summary.total).toBe(1);
637
+ });
638
+ });
639
+ it('excludes lexical and real symlinked cache directories', async () => {
640
+ await withIncrementalRoot('cache-link', async (rootDir) => {
641
+ const docPath = path.join(rootDir, 'guide.md');
642
+ const targetPath = path.join(rootDir, 'target.ts');
643
+ const realCacheDir = path.join(rootDir, '.real-cache');
644
+ await fs.promises.writeFile(docPath, '[target](target.ts)');
645
+ await fs.promises.writeFile(targetPath, 'export const target = true;');
646
+ await fs.promises.mkdir(realCacheDir);
647
+ await fs.promises.symlink('.real-cache', path.join(rootDir, '.cache'), 'dir');
648
+ const config = incrementalConfig(rootDir, { rules: { 'file-path': { enabled: true } } });
649
+ mockDocumentScan(docPath);
650
+ expect((await run(config)).summary.total).toBe(1);
651
+ await expect(fs.promises.access(path.join(realCacheDir, 'file-hashes.json'))).resolves.toBeUndefined();
652
+ mockDocumentScan(docPath);
653
+ expect((await run(config)).summary.total).toBe(0);
654
+ });
655
+ });
656
+ it('does not advance state when validation or reporting throws', async () => {
657
+ await withIncrementalRoot('report', async (rootDir) => {
658
+ const docPath = path.join(rootDir, 'guide.md');
659
+ const cacheDir = path.join(rootDir, '.cache');
660
+ await fs.promises.writeFile(docPath, 'No references');
661
+ const config = incrementalConfig(rootDir, {
662
+ rules: { 'file-path': { enabled: true } },
663
+ cache: { enabled: false, dir: cacheDir },
664
+ });
665
+ mockDocumentScan(docPath);
666
+ await run(config);
667
+ const stateFile = path.join(cacheDir, 'file-hashes.json');
668
+ const baselineState = await fs.promises.readFile(stateFile, 'utf-8');
669
+ await fs.promises.writeFile(docPath, '[missing](missing.ts)');
670
+ const validateSpy = vi.spyOn(ValidationEngine.prototype, 'validate').mockRejectedValueOnce(new Error('validation failed'));
671
+ try {
672
+ mockDocumentScan(docPath);
673
+ await expect(run(config)).rejects.toThrow('validation failed');
674
+ expect(await fs.promises.readFile(stateFile, 'utf-8')).toBe(baselineState);
675
+ }
676
+ finally {
677
+ validateSpy.mockRestore();
678
+ }
679
+ mockDocumentScan(docPath);
680
+ await expect(run({
681
+ ...config,
682
+ reporters: ['json'],
683
+ outputPath: rootDir,
684
+ })).rejects.toThrow();
685
+ expect(await fs.promises.readFile(stateFile, 'utf-8')).toBe(baselineState);
686
+ mockDocumentScan(docPath);
687
+ expect((await run(config)).summary.errors).toBe(1);
688
+ });
689
+ });
258
690
  });
259
691
  it('loads URL cache when cache is enabled', async () => {
260
692
  const cacheDir = '.doc-freshness-cache/runner-url';