Skip to content

Commit 496ed4c

Browse files
expand semantic index regression coverage
1 parent 4232260 commit 496ed4c

1 file changed

Lines changed: 62 additions & 18 deletions

File tree

global-template/docgen/test/semantic-index.test.mjs

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,61 @@ import assert from 'node:assert/strict';
33
import fs from 'node:fs';
44
import os from 'node:os';
55
import path from 'node:path';
6+
import { spawnSync } from 'node:child_process';
7+
import { fileURLToPath } from 'node:url';
68
import { buildInventory } from '../lib/inventory.mjs';
79
import { compileContext } from '../lib/context.mjs';
810
import { databaseStats, indexRepository } from '../lib/indexer.mjs';
9-
import { generate } from '../lib/pipeline.mjs';
11+
import { audit, generate } from '../lib/pipeline.mjs';
1012
import { projectPaths, readJson, writeJson } from '../lib/core.mjs';
1113

14+
const testDir = path.dirname(fileURLToPath(import.meta.url));
15+
const cli = path.resolve(testDir, '..', 'bin', 'docgen-v2.mjs');
16+
1217
function fixture() {
1318
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'docgen-v2-')); const p = projectPaths(root);
1419
fs.mkdirSync(path.join(root, 'src'), { recursive: true });
1520
fs.mkdirSync(path.dirname(p.config), { recursive: true });
1621
writeJson(p.project, { schemaVersion: '2.0', kitVersion: '2.0.0' });
17-
writeJson(p.config, { schemaVersion: '2.0', ignore: { binary: { enabled: true, maxTextFileBytes: 1024 * 1024 } }, context: { maxTokens: { default: 4000, generate: 4000 } }, execution: { generationBatchSize: 2 }, audit: { llmEnabled: false } });
22+
writeJson(p.config, { schemaVersion: '2.0', projectName: 'Fixture', ignore: { useGitignore: true, useDocgenignore: true, binary: { enabled: true, maxTextFileBytes: 1024 * 1024 } }, context: { maxTokens: { default: 4000, generate: 4000 } }, execution: { generationBatchSize: 2, maxPlannedPages: 30 }, audit: { llmEnabled: false } });
1823
writeJson(p.state, { schemaVersion: '2.0', kitVersion: '2.0.0', stages: {}, pages: {} });
1924
return root;
2025
}
2126

27+
function catalogFixture(root) {
28+
const p = projectPaths(root);
29+
fs.writeFileSync(path.join(root, 'src', 'Resource.java'), '@Path("/quotes")\nclass Resource { @POST void create() {} }\n');
30+
indexRepository(root, { force: true });
31+
fs.mkdirSync(p.model, { recursive: true });
32+
writeJson(path.join(p.model, 'catalogs.json'), { schemaVersion: '2.0', endpoints: [{ id: 'create-quote', name: 'Create quote', statement: 'Creates a quote.', classification: 'FACT', confidence: 1, method: 'POST', path: '/quotes', evidence: [{ path: 'src/Resource.java', startLine: 1 }] }], messageHandlers: [], externalDependencies: [], dataStores: [], scheduledJobs: [] });
33+
writeJson(p.plan, { schemaVersion: '2.0', pages: [{ id: 'endpoint-catalog', title: 'Endpoint Catalog', summary: 'HTTP API reference.', category: 'api', mode: 'reference', type: 'reference', order: 1, audience: ['engineer'], coverageTags: ['endpoint-catalog'], query: 'endpoints', requiredSections: [], relatedPages: [] }] });
34+
return p;
35+
}
36+
2237
test('inventory excludes binary and docgenignore paths', () => {
2338
const root = fixture();
2439
fs.writeFileSync(path.join(root, 'src', 'Resource.java'), '@Path("/quotes")\nclass Resource { @GET void get() {} }\n');
2540
fs.writeFileSync(path.join(root, 'secret.txt'), 'ignore me');
2641
fs.writeFileSync(path.join(root, 'image.png'), Buffer.from([0x89,0x50,0x4e,0x47,0,1]));
2742
fs.writeFileSync(path.join(root, '.docgenignore'), 'secret.txt\n');
28-
const inv = buildInventory(root, { force: true });
29-
assert(inv.files.some((x) => x.path === 'src/Resource.java'));
30-
assert(!inv.files.some((x) => x.path === 'secret.txt'));
31-
assert(inv.excluded.some((x) => x.path === 'image.png'));
43+
const inv = buildInventory(root);
44+
assert(inv.files.some((item) => item.path === 'src/Resource.java'));
45+
assert(!inv.files.some((item) => item.path === 'secret.txt'));
46+
assert(inv.excluded.some((item) => item.path === 'image.png'));
47+
});
48+
49+
test('non-git inventory respects nested gitignore files', () => {
50+
const root = fixture();
51+
fs.mkdirSync(path.join(root, 'src', 'generated'), { recursive: true });
52+
fs.writeFileSync(path.join(root, 'src', 'generated', '.gitignore'), '*.txt\n');
53+
fs.writeFileSync(path.join(root, 'src', 'generated', 'secret.txt'), 'ignored');
54+
fs.writeFileSync(path.join(root, 'src', 'generated', 'public.java'), 'class Public {}');
55+
const inv = buildInventory(root);
56+
assert(!inv.files.some((item) => item.path === 'src/generated/secret.txt'));
57+
assert(inv.files.some((item) => item.path === 'src/generated/public.java'));
3258
});
3359

34-
test('index is incremental and extracts repository facts', () => {
60+
test('index is incremental and extracts source chunks and facts', () => {
3561
const root = fixture();
3662
fs.writeFileSync(path.join(root, 'src', 'Resource.java'), '@Path("/quotes")\nclass Resource { @POST void create() {} }\n');
3763
const first = indexRepository(root, { force: true });
@@ -41,6 +67,7 @@ test('index is incremental and extracts repository facts', () => {
4167
assert.equal(second.changedFiles, 0);
4268
assert.equal(second.unchangedFiles > 0, true);
4369
assert.equal(stats.facts > 0, true);
70+
assert.equal(stats.sourceChunks > 0, true);
4471
});
4572

4673
test('context compiler stays within configured budget', () => {
@@ -53,17 +80,34 @@ test('context compiler stays within configured budget', () => {
5380
assert(payload.omissions.facts > 0);
5481
});
5582

56-
test('reference catalog page is rendered without a provider call', async () => {
57-
const root = fixture(); const p = projectPaths(root);
58-
fs.writeFileSync(path.join(root, 'src', 'Resource.java'), '@Path("/quotes")\nclass Resource { @POST void create() {} }\n');
59-
indexRepository(root, { force: true });
60-
fs.mkdirSync(p.model, { recursive: true });
61-
writeJson(path.join(p.model, 'catalogs.json'), { schemaVersion: '2.0', endpoints: [{ id: 'create-quote', name: 'Create quote', method: 'POST', path: '/quotes', evidence: [{ path: 'src/Resource.java', startLine: 1 }] }], messageHandlers: [], externalDependencies: [], dataStores: [], scheduledJobs: [] });
62-
writeJson(p.plan, { schemaVersion: '2.0', pages: [{ id: 'endpoint-catalog', title: 'Endpoint Catalog', summary: 'HTTP API reference.', category: 'api', mode: 'reference', type: 'reference', order: 1, audience: ['engineer'], coverageTags: ['endpoint-catalog'], query: 'endpoints', requiredSections: [], relatedPages: [] }] });
63-
const result = await generate(root);
64-
assert.equal(result.providerPages, 0);
83+
test('reference catalog page is deterministic, traced, auditable, and reusable', async () => {
84+
const root = fixture(); const p = catalogFixture(root);
85+
const first = await generate(root); const second = await generate(root);
86+
assert.equal(first.providerPages, 0); assert.equal(second.providerPages, 0);
6587
const output = fs.readFileSync(path.join(root, 'docs', 'api', 'endpoint-catalog.md'), 'utf8');
66-
assert.match(output, /POST/);
67-
assert.match(output, /\/quotes/);
88+
assert.match(output, /POST/); assert.match(output, /\/quotes/);
89+
const trace = readJson(path.join(p.traceability, 'pages', 'endpoint-catalog.json'));
90+
assert.equal(trace.claims.length, 1); assert.equal(trace.claims[0].classification, 'FACT'); assert.equal(trace.claims[0].evidence[0].path, 'src/Resource.java');
91+
const quality = await audit(root); assert.equal(quality.pass, true);
6892
assert.equal(fs.existsSync(path.join(p.telemetry, 'provider-runs.jsonl')), false);
6993
});
94+
95+
test('deterministic audit rejects FACT evidence outside inventory', async () => {
96+
const root = fixture(); const p = catalogFixture(root); await generate(root);
97+
const file = path.join(p.traceability, 'pages', 'endpoint-catalog.json'); const trace = readJson(file); trace.claims[0].evidence = [{ path: 'ignored/secret.java' }]; writeJson(file, trace);
98+
await assert.rejects(() => audit(root), /Quality failed/);
99+
});
100+
101+
test('v1 migration preserves docs and ignore policy while archiving workflow state', () => {
102+
const root = fixture(); const p = projectPaths(root);
103+
writeJson(p.config, { schemaVersion: '1.6', projectName: 'Migrated', commandCode: { executable: 'custom-cmdc', model: 'cheap-model' }, ignore: { useGitignore: true, binary: { maxTextFileBytes: 123456 } } });
104+
writeJson(p.project, { schemaVersion: '1.0', kitVersion: '1.0.0' });
105+
fs.mkdirSync(path.join(root, 'docs'), { recursive: true }); fs.writeFileSync(path.join(root, 'docs', 'keep.md'), '# Keep\n'); fs.writeFileSync(path.join(root, '.docgenignore'), 'private/**\n');
106+
fs.mkdirSync(path.join(p.base, 'evidence'), { recursive: true }); fs.writeFileSync(path.join(p.base, 'evidence', 'legacy.json'), '{}');
107+
const run = spawnSync(process.execPath, [cli, 'migrate'], { cwd: root, encoding: 'utf8' });
108+
assert.equal(run.status, 0, run.stderr);
109+
assert.equal(fs.readFileSync(path.join(root, 'docs', 'keep.md'), 'utf8'), '# Keep\n');
110+
assert.equal(fs.readFileSync(path.join(root, '.docgenignore'), 'utf8'), 'private/**\n');
111+
const next = readJson(p.config); assert.equal(next.schemaVersion, '2.0'); assert.equal(next.projectName, 'Migrated'); assert.equal(next.commandCode.executable, 'custom-cmdc'); assert.equal(next.ignore.binary.maxTextFileBytes, 123456);
112+
const marker = readJson(p.project); assert.match(marker.migrationBackup, /^\.docgen\/migration-backup\//); assert(fs.existsSync(path.join(root, marker.migrationBackup, 'evidence', 'legacy.json')));
113+
});

0 commit comments

Comments
 (0)