All files / scripts/agentic/gate-checks per-document-coverage.ts

100% Statements 23/23
100% Branches 10/10
100% Functions 4/4
100% Lines 22/22

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88                                                    7x 7x   7x 2x     5x 5x   5x 1x         1x     4x 4x 9x 9x                   4x             9x 9x 9x 6x               9x           9x 22x 22x      
/**
 * @module scripts/agentic/gate-checks/per-document-coverage
 * @description Check 2 — Per-document coverage (Family E vs manifest).
 *
 * Extracts dok_ids from the data-download-manifest and verifies each has
 * a corresponding analysis document in the `documents/` subdirectory.
 *
 * @see .github/prompts/05-analysis-gate.md §Check 2
 * @author Hack23 AB
 * @license Apache-2.0
 */
 
import { readFile } from 'node:fs/promises';
import { existsSync, statSync } from 'node:fs';
import { join } from 'node:path';
 
import { DOK_ID_PATTERN } from '../artifact-inventory.js';
import type { GateCheckResult } from '../gate-shared/types.js';
 
/**
 * Extract dok_ids from the data-download-manifest and verify each has
 * a corresponding analysis document in the `documents/` subdirectory.
 */
export async function checkPerDocumentCoverage(
  analysisDir: string,
): Promise<GateCheckResult[]> {
  const results: GateCheckResult[] = [];
  const manifestPath = join(analysisDir, 'data-download-manifest.md');
 
  if (!existsSync(manifestPath)) {
    return results;
  }
 
  const content = await readFile(manifestPath, 'utf-8');
  const dokIds = extractDokIds(content);
 
  if (dokIds.length === 0) {
    results.push({
      checkId: 'per-document-coverage',
      passed: false,
      message: 'Manifest has no dok_id entries',
    });
    return results;
  }
 
  const documentsDir = join(analysisDir, 'documents');
  for (const dokId of dokIds) {
    const found = hasDocumentAnalysis(documentsDir, dokId);
    results.push({
      checkId: 'per-document-coverage',
      passed: found,
      message: found
        ? `Document analysis found for ${dokId}`
        : `documents/${dokId}.md or documents/${dokId}-analysis.md missing (any case)`,
      artifact: `documents/${dokId}-analysis.md`,
    });
  }
 
  return results;
}
 
/**
 * Extract unique dok_ids from markdown content.
 */
export function extractDokIds(content: string): string[] {
  const globalPattern = new RegExp(DOK_ID_PATTERN.source, 'g');
  const matches = content.match(globalPattern);
  if (!matches) return [];
  return [...new Set(matches)];
}
 
/**
 * Check if a document analysis file exists and is non-empty (any case variant).
 * Mirrors the prompt gate's `-s` check (file exists AND size > 0).
 */
function hasDocumentAnalysis(documentsDir: string, dokId: string): boolean {
  const variants = [
    `${dokId}.md`,
    `${dokId}-analysis.md`,
    `${dokId.toLowerCase()}.md`,
    `${dokId.toLowerCase()}-analysis.md`,
  ];
  return variants.some((v) => {
    const p = join(documentsDir, v);
    return existsSync(p) && statSync(p).size > 0;
  });
}