@pithyjs/codex 0.1.0-beta.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 (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4401 -0
  3. package/dist/annotations.d.ts +106 -0
  4. package/dist/annotations.js +306 -0
  5. package/dist/apply.d.ts +2 -0
  6. package/dist/apply.js +80 -0
  7. package/dist/changed-scope.d.ts +23 -0
  8. package/dist/changed-scope.js +31 -0
  9. package/dist/check.d.ts +2 -0
  10. package/dist/check.js +117 -0
  11. package/dist/cli.d.ts +2 -0
  12. package/dist/cli.js +67 -0
  13. package/dist/env.d.ts +5 -0
  14. package/dist/env.js +35 -0
  15. package/dist/extract-cli.d.ts +23 -0
  16. package/dist/extract-cli.js +192 -0
  17. package/dist/extraction/breaking-changes.d.ts +66 -0
  18. package/dist/extraction/breaking-changes.js +352 -0
  19. package/dist/extraction/example-extractor.d.ts +55 -0
  20. package/dist/extraction/example-extractor.js +272 -0
  21. package/dist/extraction/index.d.ts +27 -0
  22. package/dist/extraction/index.js +31 -0
  23. package/dist/extraction/jsdoc-parser.d.ts +43 -0
  24. package/dist/extraction/jsdoc-parser.js +274 -0
  25. package/dist/extraction/pipeline.d.ts +54 -0
  26. package/dist/extraction/pipeline.js +526 -0
  27. package/dist/extraction/readme-sync.d.ts +108 -0
  28. package/dist/extraction/readme-sync.js +592 -0
  29. package/dist/extraction/snapshot-store.d.ts +40 -0
  30. package/dist/extraction/snapshot-store.js +153 -0
  31. package/dist/extraction/source-linker.d.ts +80 -0
  32. package/dist/extraction/source-linker.js +316 -0
  33. package/dist/extraction/test-example-extractor.d.ts +69 -0
  34. package/dist/extraction/test-example-extractor.js +400 -0
  35. package/dist/extraction/test-pattern-extractor.d.ts +68 -0
  36. package/dist/extraction/test-pattern-extractor.js +261 -0
  37. package/dist/extraction/testing-pyramid.d.ts +44 -0
  38. package/dist/extraction/testing-pyramid.js +163 -0
  39. package/dist/extraction/type-extractor.d.ts +34 -0
  40. package/dist/extraction/type-extractor.js +494 -0
  41. package/dist/extraction/types.d.ts +401 -0
  42. package/dist/extraction/types.js +34 -0
  43. package/dist/indexer.d.ts +1 -0
  44. package/dist/indexer.js +107 -0
  45. package/dist/llm.d.ts +8 -0
  46. package/dist/llm.js +75 -0
  47. package/dist/readme-sync-cli.d.ts +20 -0
  48. package/dist/readme-sync-cli.js +167 -0
  49. package/dist/review.d.ts +2 -0
  50. package/dist/review.js +93 -0
  51. package/dist/scan.d.ts +29 -0
  52. package/dist/scan.js +221 -0
  53. package/dist/schema.d.ts +169 -0
  54. package/dist/schema.js +70 -0
  55. package/dist/snapshot-cli.d.ts +45 -0
  56. package/dist/snapshot-cli.js +217 -0
  57. package/dist/sync-cli.d.ts +22 -0
  58. package/dist/sync-cli.js +154 -0
  59. package/dist/sync-pipeline.d.ts +58 -0
  60. package/dist/sync-pipeline.js +104 -0
  61. package/dist/sync.d.ts +2 -0
  62. package/dist/sync.js +318 -0
  63. package/dist/validate-cli.d.ts +20 -0
  64. package/dist/validate-cli.js +144 -0
  65. package/dist/validate.d.ts +76 -0
  66. package/dist/validate.js +183 -0
  67. package/dist/watch-cli.d.ts +21 -0
  68. package/dist/watch-cli.js +220 -0
  69. package/package.json +62 -0
package/dist/sync.js ADDED
@@ -0,0 +1,318 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @codex
4
+ * {
5
+ * "id": "pithy.codex.sync",
6
+ * "title": "Codex Sync",
7
+ * "category": "feature"
8
+ * }
9
+ *
10
+ * Orchestrates full sync workflow (scan, generate, write entries).
11
+ */
12
+ import fs from 'node:fs/promises';
13
+ import { join, dirname } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { scan } from './scan.js';
16
+ import { codexSchema } from './schema.js';
17
+ import { askLLM } from './llm.js';
18
+ import { CHANGED_ONLY_PATHSPEC } from './changed-scope.js';
19
+ import { simpleGit } from 'simple-git';
20
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
+ const nowIso = () => new Date().toISOString();
22
+ async function loadIndex(root) {
23
+ try {
24
+ const raw = await fs.readFile(join(root, 'codex.index.json'), 'utf8');
25
+ const j = JSON.parse(raw);
26
+ return (j.byId || {});
27
+ }
28
+ catch {
29
+ return {};
30
+ }
31
+ }
32
+ async function loadProposalHashes(root) {
33
+ const proposalsDir = join(root, 'codex', '.proposals');
34
+ const hashes = {};
35
+ try {
36
+ const files = await fs.readdir(proposalsDir);
37
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
38
+ for (const file of jsonFiles) {
39
+ try {
40
+ const content = await fs.readFile(join(proposalsDir, file), 'utf8');
41
+ const proposal = JSON.parse(content);
42
+ if (proposal.id && proposal.meta?.hash) {
43
+ hashes[proposal.id] = proposal.meta.hash;
44
+ }
45
+ }
46
+ catch {
47
+ // Skip malformed proposals
48
+ }
49
+ }
50
+ }
51
+ catch {
52
+ // Proposals directory doesn't exist yet
53
+ }
54
+ return hashes;
55
+ }
56
+ function determineChangeStatus(currentHash, existingHash, proposalHash) {
57
+ // If no previous hash exists, it's new
58
+ if (!existingHash && !proposalHash) {
59
+ return 'NEW';
60
+ }
61
+ // Compare with most recent hash (proposal takes precedence over applied)
62
+ const prevHash = proposalHash || existingHash;
63
+ if (currentHash !== prevHash) {
64
+ return 'CHANGED';
65
+ }
66
+ return 'UNCHANGED';
67
+ }
68
+ async function lastModifiedISO(root, files) {
69
+ const git = simpleGit(root);
70
+ try {
71
+ const rel = files.map(f => {
72
+ const r = f.startsWith(root) ? f.slice(root.length + 1) : f;
73
+ return r.replace(/\\/g, '/');
74
+ });
75
+ const res = await git.raw(['log', '-1', '--format=%cI', '--', ...rel]);
76
+ const iso = res.trim();
77
+ return iso || null;
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
83
+ function needsUpdate(entryUpdatedISO, codeISO) {
84
+ if (!entryUpdatedISO && codeISO)
85
+ return true;
86
+ if (!codeISO)
87
+ return false;
88
+ return new Date(codeISO) > new Date(entryUpdatedISO || 0);
89
+ }
90
+ function buildPrompt(existing, id, category, tags, codeContext) {
91
+ const rules = `
92
+ Return a single JSON object for this schema:
93
+ { id, title, category, tags[], updated, related[] }
94
+
95
+ Rules:
96
+ - id must be exactly "${id}".
97
+ - category must be "${category}".
98
+ - Merge these tags first: [${tags.join(', ')}].
99
+ - updated must be current ISO datetime.
100
+ - related: propose 0–3 likely related ids (or [] if unsure).
101
+ - If an existing entry is provided, keep fields consistent and improve only where helpful (title/tags/related). Do not invent unrelated info.
102
+ Return ONLY JSON.
103
+ `;
104
+ const existingBlock = existing
105
+ ? `\nExisting entry:\n${JSON.stringify(existing, null, 2)}\n`
106
+ : '';
107
+ return [
108
+ {
109
+ role: 'system',
110
+ content: 'You are CodexSync. Keep entries concise and accurate.',
111
+ },
112
+ {
113
+ role: 'user',
114
+ content: `${rules}\n${existingBlock}\nCode context (truncated):\n${codeContext}`,
115
+ },
116
+ ];
117
+ }
118
+ async function extractContext(files) {
119
+ const chunks = [];
120
+ for (const f of files.slice(0, 20)) {
121
+ const src = await fs.readFile(f, 'utf8').catch(() => '');
122
+ if (!src)
123
+ continue;
124
+ const head = src.split('\n').slice(0, 200).join('\n');
125
+ chunks.push(`// ${f}\n${head}`);
126
+ if (chunks.join('\n\n').length > 120_000)
127
+ break;
128
+ }
129
+ return chunks.join('\n\n');
130
+ }
131
+ /**
132
+ * Deduplicates APIs by name+signature, keeping the first occurrence.
133
+ */
134
+ function deduplicateApis(apis) {
135
+ const seen = new Set();
136
+ return apis.filter(api => {
137
+ const key = `${api.name}::${api.signature || ''}`;
138
+ if (seen.has(key))
139
+ return false;
140
+ seen.add(key);
141
+ return true;
142
+ });
143
+ }
144
+ /**
145
+ * Builds a codex entry directly from scan data without LLM enrichment.
146
+ * All required fields (id, title, category) come from @codex annotations;
147
+ * APIs come from @codexApi annotations. No AI needed.
148
+ */
149
+ function buildEntryFromScan(c, existing) {
150
+ return {
151
+ id: c.id,
152
+ title: c.title || existing?.title || c.id.split('.').pop() || c.id,
153
+ category: c.category,
154
+ tags: Array.from(new Set([...(c.tags || []), ...(existing?.tags || [])])),
155
+ updated: nowIso(),
156
+ public: existing?.public ?? true,
157
+ status: existing?.status ?? 'draft',
158
+ version: existing?.version ?? '1.0',
159
+ related: existing?.related ?? [],
160
+ dependsOn: existing?.dependsOn ?? [],
161
+ apis: deduplicateApis(c.apis && c.apis.length > 0 ? c.apis : (existing?.apis ?? [])),
162
+ rids: existing?.rids ?? [],
163
+ acceptance: existing?.acceptance ?? [],
164
+ tests_expected: existing?.tests_expected ?? {
165
+ unit: [],
166
+ integration: [],
167
+ e2e: [],
168
+ property: [],
169
+ contract: [],
170
+ },
171
+ meta: c.hash ? { hash: c.hash } : (existing?.meta ?? {}),
172
+ };
173
+ }
174
+ async function main() {
175
+ const rootArg = process.argv.includes('--root')
176
+ ? process.argv[process.argv.indexOf('--root') + 1]
177
+ : '../../..';
178
+ // Resolve relative to __dirname (packages/codex/dist/)
179
+ const root = join(__dirname, rootArg);
180
+ const mode = process.argv.includes('--mode')
181
+ ? process.argv[process.argv.indexOf('--mode') + 1]
182
+ : 'batch'; // "batch" | "one"
183
+ const targetId = process.argv.includes('--id')
184
+ ? process.argv[process.argv.indexOf('--id') + 1]
185
+ : null;
186
+ const noLlm = process.argv.includes('--no-llm');
187
+ const direct = process.argv.includes('--direct');
188
+ const changedOnly = process.argv.includes('--changed-only');
189
+ const baseBranch = process.argv.includes('--base')
190
+ ? process.argv[process.argv.indexOf('--base') + 1]
191
+ : 'origin/master';
192
+ // --direct writes to codex/ directly (skips proposals/review/apply cycle)
193
+ // --no-llm without --direct still uses the proposals workflow
194
+ const outputDir = direct
195
+ ? join(root, 'codex')
196
+ : join(root, 'codex', '.proposals');
197
+ await fs.mkdir(outputDir, { recursive: true });
198
+ // When --changed-only, use git diff to find changed source files
199
+ let changedFiles;
200
+ if (changedOnly) {
201
+ const git = simpleGit(root);
202
+ try {
203
+ const diff = await git.raw([
204
+ 'diff',
205
+ '--name-only',
206
+ baseBranch + '...HEAD',
207
+ '--',
208
+ ...CHANGED_ONLY_PATHSPEC,
209
+ ]);
210
+ changedFiles = diff
211
+ .trim()
212
+ .split('\n')
213
+ .filter(Boolean)
214
+ .map(f => join(root, f));
215
+ console.log(`[Codex] Changed files: ${changedFiles.length} (vs ${baseBranch})`);
216
+ if (changedFiles.length === 0) {
217
+ console.log('No source files changed. Nothing to sync.');
218
+ return;
219
+ }
220
+ }
221
+ catch {
222
+ console.warn(`[Codex] Failed to detect changed files, falling back to full scan`);
223
+ changedFiles = undefined;
224
+ }
225
+ }
226
+ const indexById = await loadIndex(root);
227
+ const proposalHashes = await loadProposalHashes(root);
228
+ const candidates = (await scan(root, changedFiles)).filter(c => !targetId || c.id === targetId);
229
+ let generated = 0;
230
+ let skipped = 0;
231
+ for (const c of candidates) {
232
+ const existing = indexById[c.id] ?? null;
233
+ // Enhanced change detection using content hashes
234
+ if (c.hash) {
235
+ const existingHash = existing?.meta?.hash || null;
236
+ const proposalHash = proposalHashes[c.id] || null;
237
+ const changeStatus = determineChangeStatus(c.hash, existingHash, proposalHash);
238
+ if (changeStatus === 'UNCHANGED') {
239
+ skipped++;
240
+ if (process.env.NODE_ENV === 'development') {
241
+ console.log(`[Codex] Skipping unchanged: ${c.id}`);
242
+ }
243
+ continue;
244
+ }
245
+ if (process.env.NODE_ENV === 'development') {
246
+ console.log(`[Codex] Processing ${changeStatus}: ${c.id}`);
247
+ }
248
+ }
249
+ else {
250
+ // Fallback to legacy timestamp-based detection for glob-only candidates
251
+ const lastCodeISO = await lastModifiedISO(root, c.files);
252
+ const entryUpdated = existing?.updated ?? null;
253
+ if (existing && !needsUpdate(entryUpdated, lastCodeISO)) {
254
+ skipped++;
255
+ continue;
256
+ }
257
+ }
258
+ let json;
259
+ if (noLlm) {
260
+ // Build entry directly from annotations — no AI required
261
+ json = buildEntryFromScan(c, existing);
262
+ }
263
+ else {
264
+ const ctx = await extractContext(c.files);
265
+ const messages = buildPrompt(existing, c.id, c.category, c.tags, ctx);
266
+ const text = await askLLM(messages);
267
+ try {
268
+ json = JSON.parse(text);
269
+ }
270
+ catch {
271
+ continue;
272
+ }
273
+ json.id = c.id;
274
+ json.category = c.category;
275
+ json.updated = nowIso();
276
+ json.tags = Array.from(new Set([...(c.tags || []), ...(json.tags || [])]));
277
+ // Add discovered APIs if available
278
+ if (c.apis && c.apis.length > 0) {
279
+ json.apis = c.apis;
280
+ }
281
+ // Add content hash for change detection
282
+ if (c.hash) {
283
+ json.meta = { hash: c.hash };
284
+ }
285
+ }
286
+ const parsed = codexSchema.safeParse(json);
287
+ if (!parsed.success) {
288
+ if (process.env.NODE_ENV === 'development') {
289
+ console.warn(`[Codex] Schema validation failed for ${c.id}:`, parsed.error);
290
+ }
291
+ continue;
292
+ }
293
+ await fs.writeFile(join(outputDir, `${c.id}.json`), JSON.stringify(parsed.data, null, 2));
294
+ generated++;
295
+ if (mode === 'one')
296
+ break;
297
+ }
298
+ if (direct) {
299
+ console.log(`Codex entries written directly (generated ${generated}, skipped ${skipped} unchanged).`);
300
+ }
301
+ else {
302
+ // Regenerate review.md for whatever exists in .proposals
303
+ const { execa } = await import('execa');
304
+ try {
305
+ await execa('node', [join(__dirname, 'review.js'), '--root', root], {
306
+ stdio: 'inherit',
307
+ });
308
+ }
309
+ catch {
310
+ void 0;
311
+ }
312
+ console.log(`Proposals ready in codex/.proposals (generated ${generated}, skipped ${skipped} unchanged).`);
313
+ }
314
+ }
315
+ main().catch(e => {
316
+ console.error(e);
317
+ process.exit(1);
318
+ });
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @codex
4
+ * {
5
+ * "id": "pithy.codex.validate-cli",
6
+ * "title": "Validate CLI",
7
+ * "category": "plugin"
8
+ * }
9
+ *
10
+ * CLI for validating extracted documentation examples.
11
+ * Checks that TypeScript/JavaScript examples are syntactically valid.
12
+ *
13
+ * Usage:
14
+ * pnpm codex validate [options]
15
+ *
16
+ * Options:
17
+ * --root <path> Project root directory (default: cwd)
18
+ * --verbose Show detailed progress
19
+ */
20
+ export {};
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @codex
4
+ * {
5
+ * "id": "pithy.codex.validate-cli",
6
+ * "title": "Validate CLI",
7
+ * "category": "plugin"
8
+ * }
9
+ *
10
+ * CLI for validating extracted documentation examples.
11
+ * Checks that TypeScript/JavaScript examples are syntactically valid.
12
+ *
13
+ * Usage:
14
+ * pnpm codex validate [options]
15
+ *
16
+ * Options:
17
+ * --root <path> Project root directory (default: cwd)
18
+ * --verbose Show detailed progress
19
+ */
20
+ import { resolve } from 'node:path';
21
+ import fs from 'node:fs/promises';
22
+ import { runExtractionPipeline } from './extraction/pipeline.js';
23
+ import { validateExtractionResult, formatValidationReport, } from './validate.js';
24
+ function parseArgs(args) {
25
+ const options = {
26
+ root: process.cwd(),
27
+ verbose: false,
28
+ };
29
+ for (let i = 0; i < args.length; i++) {
30
+ const arg = args[i];
31
+ if (arg.startsWith('--root=')) {
32
+ options.root = arg.slice('--root='.length);
33
+ continue;
34
+ }
35
+ switch (arg) {
36
+ case '--root': {
37
+ const next = args[i + 1];
38
+ if (!next || next.startsWith('-')) {
39
+ console.error('Error: --root option requires a path argument.');
40
+ process.exit(1);
41
+ }
42
+ options.root = next;
43
+ i++;
44
+ break;
45
+ }
46
+ case '--verbose':
47
+ case '-v':
48
+ options.verbose = true;
49
+ break;
50
+ case '--help':
51
+ case '-h':
52
+ printHelp();
53
+ process.exit(0);
54
+ break;
55
+ default:
56
+ if (arg.startsWith('-')) {
57
+ console.warn(`Warning: unknown option "${arg}" (ignored)`);
58
+ }
59
+ }
60
+ }
61
+ return options;
62
+ }
63
+ function printHelp() {
64
+ console.log(`
65
+ Codex Validate
66
+
67
+ Validates that extracted documentation examples are syntactically correct.
68
+ TypeScript and JavaScript examples are parsed for syntax errors.
69
+
70
+ Usage:
71
+ pithy-codex-validate [options]
72
+ pnpm --filter @pithyjs/codex validate [-- options]
73
+
74
+ Options:
75
+ --root <path> Project root directory (default: current directory)
76
+ --verbose, -v Show detailed progress
77
+ --help, -h Show this help message
78
+
79
+ Examples:
80
+ # Validate examples in project
81
+ pithy-codex-validate --root ../..
82
+
83
+ # Validate with verbose output
84
+ pithy-codex-validate --root ../.. --verbose
85
+
86
+ Exit Codes:
87
+ 0 - All examples are valid
88
+ 1 - One or more examples have syntax errors
89
+ `);
90
+ }
91
+ async function main() {
92
+ const args = process.argv.slice(2);
93
+ const options = parseArgs(args);
94
+ options.root = resolve(options.root);
95
+ // Verify root exists
96
+ try {
97
+ const stat = await fs.stat(options.root);
98
+ if (!stat.isDirectory()) {
99
+ console.error(`❌ Error: "${options.root}" is not a directory`);
100
+ process.exit(1);
101
+ }
102
+ }
103
+ catch {
104
+ console.error(`❌ Error: Root directory "${options.root}" does not exist`);
105
+ process.exit(1);
106
+ }
107
+ if (options.verbose) {
108
+ console.log('🔍 Codex Validate');
109
+ console.log(` Root: ${options.root}`);
110
+ console.log('');
111
+ }
112
+ try {
113
+ // Run extraction first to get examples
114
+ if (options.verbose) {
115
+ console.log('📂 Running extraction pipeline...');
116
+ }
117
+ const extraction = await runExtractionPipeline({
118
+ root: options.root,
119
+ analyzeTests: true,
120
+ linkReadmes: true,
121
+ extractRunnableExamples: true,
122
+ });
123
+ if (options.verbose) {
124
+ console.log(` Found ${extraction.stats.componentsFound} components with ${extraction.stats.apisExtracted} APIs`);
125
+ console.log('');
126
+ console.log('📋 Validating examples...');
127
+ }
128
+ // Validate
129
+ const result = validateExtractionResult(extraction);
130
+ // Report
131
+ console.log(formatValidationReport(result));
132
+ if (result.invalidCount > 0) {
133
+ process.exit(1);
134
+ }
135
+ }
136
+ catch (error) {
137
+ console.error('❌ Validation failed:', error instanceof Error ? error.message : error);
138
+ process.exit(1);
139
+ }
140
+ }
141
+ main().catch(err => {
142
+ console.error('Fatal error:', err);
143
+ process.exit(1);
144
+ });
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @codex
3
+ * {
4
+ * "id": "pithy.codex.validate",
5
+ * "title": "Documentation Validator",
6
+ * "category": "feature"
7
+ * }
8
+ *
9
+ * Validates extracted documentation examples by checking they are
10
+ * syntactically valid TypeScript. Used by `pnpm codex validate` and CI drift checks.
11
+ */
12
+ import type { ExtendedExtractionResult } from './extraction/types.js';
13
+ /**
14
+ * Result of validating a single example
15
+ */
16
+ export interface ExampleValidationResult {
17
+ /** The example code that was validated */
18
+ code: string;
19
+ /** Component the example belongs to */
20
+ componentId: string;
21
+ /** API name (if applicable) */
22
+ apiName?: string;
23
+ /** Whether the example is syntactically valid */
24
+ valid: boolean;
25
+ /** Diagnostic messages if invalid */
26
+ diagnostics: string[];
27
+ }
28
+ /**
29
+ * Aggregate result of validating all examples
30
+ */
31
+ export interface ValidationResult {
32
+ /** Total examples checked */
33
+ totalExamples: number;
34
+ /** Number of valid examples */
35
+ validCount: number;
36
+ /** Number of invalid examples */
37
+ invalidCount: number;
38
+ /** Number of examples skipped (non-TypeScript/JavaScript) */
39
+ skippedCount: number;
40
+ /** Individual results for invalid examples only */
41
+ invalidExamples: ExampleValidationResult[];
42
+ /** Total validation time in ms */
43
+ totalTimeMs: number;
44
+ }
45
+ /**
46
+ * @codexApi {"parent":"pithy.codex.validate","name":"validateExampleSyntax","stability":"stable","signature":"(code: string, componentId?: string, apiName?: string, language?: string) => ExampleValidationResult"}
47
+ *
48
+ * Validates a single code example for syntactic correctness using TypeScript compiler.
49
+ * Does NOT type-check — only parses for syntax errors.
50
+ *
51
+ * @param code - The example code to validate
52
+ * @param componentId - The ID of the component the example belongs to (default: "")
53
+ * @param apiName - The name of the API the example belongs to
54
+ * @param language - The language of the example (default: "typescript")
55
+ * @returns Validation result with diagnostics if invalid
56
+ */
57
+ export declare function validateExampleSyntax(code: string, componentId?: string, apiName?: string, language?: string): ExampleValidationResult;
58
+ /**
59
+ * @codexApi {"parent":"pithy.codex.validate","name":"validateExtractionResult","stability":"stable","signature":"(result: ExtendedExtractionResult) => ValidationResult"}
60
+ *
61
+ * Validates all extracted examples from an extraction pipeline result.
62
+ * Checks that TypeScript/JavaScript examples are syntactically valid.
63
+ *
64
+ * @param result - The extraction result to validate
65
+ * @returns Aggregate validation result
66
+ */
67
+ export declare function validateExtractionResult(result: ExtendedExtractionResult): ValidationResult;
68
+ /**
69
+ * @codexApi {"parent":"pithy.codex.validate","name":"formatValidationReport","stability":"stable","signature":"(result: ValidationResult) => string"}
70
+ *
71
+ * Formats a human-readable validation report.
72
+ *
73
+ * @param result - Validation result to format
74
+ * @returns Formatted report string
75
+ */
76
+ export declare function formatValidationReport(result: ValidationResult): string;