@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/cli.js ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @codex
4
+ * {
5
+ * "id": "pithy.codex.cli",
6
+ * "title": "CLI Entry Point",
7
+ * "category": "feature"
8
+ * }
9
+ *
10
+ * Main CLI dispatcher for codex commands. Provides an interactive readline
11
+ * prompt to create new codex entries with id, title, category, tags, and
12
+ * related fields, then writes the validated JSON to codex/.proposals/.
13
+ */
14
+ import { createInterface } from 'node:readline/promises';
15
+ import { stdin as input, stdout as output } from 'node:process';
16
+ import { join } from 'node:path';
17
+ import fs from 'node:fs/promises';
18
+ import { codexSchema } from './schema.js'; // NodeNext-friendly
19
+ const parseCSV = (s) => s
20
+ .split(',')
21
+ .map(t => t.trim())
22
+ .filter(Boolean);
23
+ const sanitizeId = (s) => s.trim().replace(/\s+/g, '-');
24
+ const isSafeId = (s) => /^[A-Za-z0-9._-]+$/.test(s);
25
+ async function main() {
26
+ const rl = createInterface({ input, output });
27
+ const idRaw = await rl.question('id: ');
28
+ const id = sanitizeId(idRaw);
29
+ if (!isSafeId(id)) {
30
+ console.error('Invalid id. Use letters, numbers, ".", "_" or "-". No spaces.');
31
+ rl.close();
32
+ process.exit(1);
33
+ }
34
+ const title = (await rl.question('title: ')).trim();
35
+ const category = (await rl.question('category: ')).trim();
36
+ const tags = await rl.question('tags (comma separated): ');
37
+ const updatedIn = (await rl.question('updated (ISO date, blank = now): ')).trim();
38
+ const updated = updatedIn || new Date().toISOString();
39
+ const related = await rl.question('related (comma separated): ');
40
+ rl.close();
41
+ const data = {
42
+ id,
43
+ title,
44
+ category,
45
+ tags: parseCSV(tags),
46
+ updated,
47
+ related: parseCSV(related),
48
+ };
49
+ const entry = codexSchema.parse(data); // throws on invalid
50
+ const outDir = join(process.cwd(), 'codex');
51
+ const filePath = join(outDir, `${entry.id}.json`);
52
+ try {
53
+ // prevent accidental overwrite
54
+ await fs.access(filePath);
55
+ console.error(`Entry already exists: ${filePath}`);
56
+ process.exit(1);
57
+ }
58
+ catch {
59
+ // OK, file doesn't exist
60
+ }
61
+ await fs.writeFile(filePath, JSON.stringify(entry, null, 2));
62
+ console.log(`āœ… Wrote ${filePath}`);
63
+ }
64
+ main().catch(err => {
65
+ console.error(err);
66
+ process.exit(1);
67
+ });
package/dist/env.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Loads environment variables from .env files using dotenv.
3
+ * @codexApi {"parent":"pithy.codex.env","name":"loadEnv","stability":"stable","signature":"() => void"}
4
+ */
5
+ export declare function loadEnv(): void;
package/dist/env.js ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @codex
3
+ * {
4
+ * "id": "pithy.codex.env",
5
+ * "title": "Environment Configuration",
6
+ * "category": "feature"
7
+ * }
8
+ *
9
+ * Loads environment variables for LLM API access.
10
+ */
11
+ import { config } from 'dotenv';
12
+ import { join, dirname } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const repoRoot = join(__dirname, '../../..');
16
+ // Load order: team defaults → .env → .env.local (later files win)
17
+ const files = [
18
+ join(repoRoot, '.env.team'),
19
+ join(repoRoot, '.env'),
20
+ join(repoRoot, '.env.local'),
21
+ ];
22
+ /**
23
+ * Loads environment variables from .env files using dotenv.
24
+ * @codexApi {"parent":"pithy.codex.env","name":"loadEnv","stability":"stable","signature":"() => void"}
25
+ */
26
+ export function loadEnv() {
27
+ for (const f of files) {
28
+ try {
29
+ config({ path: f, override: false });
30
+ }
31
+ catch {
32
+ void 0;
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @codex
4
+ * {
5
+ * "id": "pithy.codex.extraction.cli",
6
+ * "title": "Extraction CLI",
7
+ * "category": "plugin"
8
+ * }
9
+ *
10
+ * CLI for running the multi-source extraction pipeline.
11
+ *
12
+ * Usage:
13
+ * pnpm codex:extract [options]
14
+ *
15
+ * Options:
16
+ * --root <path> Project root directory (default: cwd)
17
+ * --output <path> Output file for results (default: codex/extracted.json)
18
+ * --report Generate human-readable report
19
+ * --no-tests Skip test file analysis
20
+ * --no-readme Skip README linking
21
+ * --verbose Show detailed progress
22
+ */
23
+ export {};
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @codex
4
+ * {
5
+ * "id": "pithy.codex.extraction.cli",
6
+ * "title": "Extraction CLI",
7
+ * "category": "plugin"
8
+ * }
9
+ *
10
+ * CLI for running the multi-source extraction pipeline.
11
+ *
12
+ * Usage:
13
+ * pnpm codex:extract [options]
14
+ *
15
+ * Options:
16
+ * --root <path> Project root directory (default: cwd)
17
+ * --output <path> Output file for results (default: codex/extracted.json)
18
+ * --report Generate human-readable report
19
+ * --no-tests Skip test file analysis
20
+ * --no-readme Skip README linking
21
+ * --verbose Show detailed progress
22
+ */
23
+ import { join, resolve, relative, isAbsolute } from 'node:path';
24
+ import fs from 'node:fs/promises';
25
+ import { runExtractionPipeline, generateExtractionReport, exportToCodexFormat, } from './extraction/index.js';
26
+ /**
27
+ * Validates and normalizes a path to prevent path traversal attacks
28
+ * @param basePath - The base directory that paths must stay within
29
+ * @param inputPath - The user-provided path to validate
30
+ * @returns Normalized absolute path
31
+ * @throws Error if path escapes the base directory
32
+ */
33
+ function validatePath(basePath, inputPath) {
34
+ const resolvedBase = resolve(basePath);
35
+ const resolvedPath = isAbsolute(inputPath)
36
+ ? resolve(inputPath)
37
+ : resolve(basePath, inputPath);
38
+ // Ensure the resolved path is within the base directory
39
+ const relativePath = relative(resolvedBase, resolvedPath);
40
+ if (relativePath.startsWith('..') || isAbsolute(relativePath)) {
41
+ throw new Error(`Security error: Path "${inputPath}" escapes the project root directory`);
42
+ }
43
+ return resolvedPath;
44
+ }
45
+ function parseArgs(args) {
46
+ const options = {
47
+ root: process.cwd(),
48
+ output: 'codex/extracted.json',
49
+ report: false,
50
+ analyzeTests: true,
51
+ linkReadmes: true,
52
+ verbose: false,
53
+ };
54
+ for (let i = 0; i < args.length; i++) {
55
+ const arg = args[i];
56
+ switch (arg) {
57
+ case '--root':
58
+ options.root = args[++i] || process.cwd();
59
+ break;
60
+ case '--output':
61
+ options.output = args[++i] || options.output;
62
+ break;
63
+ case '--report':
64
+ options.report = true;
65
+ break;
66
+ case '--no-tests':
67
+ options.analyzeTests = false;
68
+ break;
69
+ case '--no-readme':
70
+ options.linkReadmes = false;
71
+ break;
72
+ case '--verbose':
73
+ case '-v':
74
+ options.verbose = true;
75
+ break;
76
+ case '--help':
77
+ case '-h':
78
+ printHelp();
79
+ process.exit(0);
80
+ }
81
+ }
82
+ return options;
83
+ }
84
+ function printHelp() {
85
+ console.log(`
86
+ Codex Extraction Pipeline
87
+
88
+ Usage:
89
+ codex-extract [options]
90
+
91
+ Options:
92
+ --root <path> Project root directory (default: current directory)
93
+ --output <path> Output file for results (default: codex/extracted.json)
94
+ --report Generate human-readable report (stdout)
95
+ --no-tests Skip test file analysis
96
+ --no-readme Skip README linking
97
+ --verbose, -v Show detailed progress
98
+ --help, -h Show this help message
99
+
100
+ Examples:
101
+ # Extract from current directory
102
+ codex-extract
103
+
104
+ # Extract with custom output
105
+ codex-extract --output docs/api-data.json
106
+
107
+ # Generate report only
108
+ codex-extract --report
109
+
110
+ # Extract without test analysis (faster)
111
+ codex-extract --no-tests --no-readme
112
+ `);
113
+ }
114
+ async function main() {
115
+ const args = process.argv.slice(2);
116
+ const options = parseArgs(args);
117
+ // Validate and normalize the root path
118
+ options.root = resolve(options.root);
119
+ // Verify root directory exists
120
+ try {
121
+ const stat = await fs.stat(options.root);
122
+ if (!stat.isDirectory()) {
123
+ console.error(`āŒ Error: "${options.root}" is not a directory`);
124
+ process.exit(1);
125
+ }
126
+ }
127
+ catch {
128
+ console.error(`āŒ Error: Root directory "${options.root}" does not exist`);
129
+ process.exit(1);
130
+ }
131
+ if (options.verbose) {
132
+ console.log('šŸ” Codex Extraction Pipeline');
133
+ console.log(` Root: ${options.root}`);
134
+ console.log(` Output: ${options.output}`);
135
+ console.log(` Analyze tests: ${options.analyzeTests}`);
136
+ console.log(` Link READMEs: ${options.linkReadmes}`);
137
+ console.log('');
138
+ }
139
+ const startTime = performance.now();
140
+ try {
141
+ if (options.verbose) {
142
+ console.log('šŸ“‚ Scanning source files...');
143
+ }
144
+ const result = await runExtractionPipeline({
145
+ root: options.root,
146
+ analyzeTests: options.analyzeTests,
147
+ linkReadmes: options.linkReadmes,
148
+ extractRunnableExamples: true,
149
+ });
150
+ const elapsed = ((performance.now() - startTime) / 1000).toFixed(2);
151
+ if (options.verbose) {
152
+ console.log(`\nāœ… Extraction complete in ${elapsed}s`);
153
+ console.log('');
154
+ console.log('šŸ“Š Statistics:');
155
+ console.log(` Files scanned: ${result.stats.filesScanned}`);
156
+ console.log(` Components found: ${result.stats.componentsFound}`);
157
+ console.log(` APIs extracted: ${result.stats.apisExtracted}`);
158
+ console.log(` Examples extracted: ${result.stats.examplesExtracted}`);
159
+ console.log(` Tests analyzed: ${result.stats.testsAnalyzed}`);
160
+ console.log(` README sections linked: ${result.stats.readmeSectionsLinked}`);
161
+ console.log('');
162
+ }
163
+ // Generate report if requested
164
+ if (options.report) {
165
+ const report = generateExtractionReport(result);
166
+ console.log(report);
167
+ }
168
+ // Write output file (with path validation)
169
+ const validatedRoot = resolve(options.root);
170
+ const outputPath = validatePath(validatedRoot, options.output);
171
+ const outputDir = join(outputPath, '..');
172
+ await fs.mkdir(outputDir, { recursive: true });
173
+ const exportData = exportToCodexFormat(result);
174
+ await fs.writeFile(outputPath, JSON.stringify(exportData, null, 2));
175
+ if (options.verbose || !options.report) {
176
+ console.log(`šŸ“ Wrote extraction data to: ${outputPath}`);
177
+ }
178
+ // Summary (always shown unless report mode)
179
+ if (!options.report) {
180
+ console.log('');
181
+ console.log(`✨ Extracted ${result.stats.componentsFound} components with ${result.stats.apisExtracted} APIs`);
182
+ }
183
+ }
184
+ catch (error) {
185
+ console.error('āŒ Extraction failed:', error);
186
+ process.exit(1);
187
+ }
188
+ }
189
+ main().catch(err => {
190
+ console.error('Fatal error:', err);
191
+ process.exit(1);
192
+ });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @codex
3
+ * {
4
+ * "id": "pithy.codex.breaking-changes",
5
+ * "title": "Breaking Change Detection",
6
+ * "category": "feature"
7
+ * }
8
+ *
9
+ * Detects breaking changes between API snapshots by comparing
10
+ * extracted API signatures, stability levels, and type definitions.
11
+ * Generates migration guides and changelogs for version transitions.
12
+ */
13
+ import type { ExtendedExtractionResult, ExtractedTypeDefinition, ApiSnapshot, ApiChange, SnapshotDiff, ChangeClassification } from './types.js';
14
+ /**
15
+ * Create an API snapshot from extraction results at a given version.
16
+ * Captures all enriched API entries across all components.
17
+ *
18
+ * @codexApi {"parent":"pithy.codex.breaking-changes","name":"createApiSnapshot","stability":"stable","signature":"(result: ExtendedExtractionResult, version: string, timestamp?: string) => ApiSnapshot"}
19
+ */
20
+ export declare function createApiSnapshot(result: ExtendedExtractionResult, version: string, timestamp?: string): ApiSnapshot;
21
+ /**
22
+ * Create an API snapshot from extracted type definitions.
23
+ * Only includes exported types (public API surface).
24
+ *
25
+ * @codexApi {"parent":"pithy.codex.breaking-changes","name":"snapshotFromTypeDefinitions","stability":"stable","signature":"(types: ExtractedTypeDefinition[], version: string, timestamp?: string) => ApiSnapshot"}
26
+ */
27
+ export declare function snapshotFromTypeDefinitions(types: ExtractedTypeDefinition[], version: string, timestamp?: string): ApiSnapshot;
28
+ /**
29
+ * Compare two API snapshots and produce a structured diff.
30
+ * Matches entries by qualifiedName (parent.name).
31
+ *
32
+ * @codexApi {"parent":"pithy.codex.breaking-changes","name":"diffSnapshots","stability":"stable","signature":"(before: ApiSnapshot, after: ApiSnapshot) => SnapshotDiff"}
33
+ */
34
+ export declare function diffSnapshots(before: ApiSnapshot, after: ApiSnapshot): SnapshotDiff;
35
+ /**
36
+ * Classify a single API change as major, minor, or patch
37
+ * according to semver conventions.
38
+ *
39
+ * - **major**: removal or signature/member change of a stable API
40
+ * - **minor**: addition, removal of experimental/deprecated, or changes to experimental APIs
41
+ * - **patch**: stability level changes
42
+ *
43
+ * @codexApi {"parent":"pithy.codex.breaking-changes","name":"classifyChange","stability":"stable","signature":"(change: ApiChange) => ChangeClassification"}
44
+ */
45
+ export declare function classifyChange(change: ApiChange): ChangeClassification;
46
+ /**
47
+ * Filter a snapshot diff down to only breaking changes
48
+ * (those classified as "major").
49
+ *
50
+ * @codexApi {"parent":"pithy.codex.breaking-changes","name":"detectBreakingChanges","stability":"stable","signature":"(diff: SnapshotDiff) => ApiChange[]"}
51
+ */
52
+ export declare function detectBreakingChanges(diff: SnapshotDiff): ApiChange[];
53
+ /**
54
+ * Generate a human-readable migration guide for a set of breaking changes.
55
+ * Groups changes by parent component and provides before/after comparisons.
56
+ *
57
+ * @codexApi {"parent":"pithy.codex.breaking-changes","name":"generateMigrationGuide","stability":"stable","signature":"(changes: ApiChange[]) => string"}
58
+ */
59
+ export declare function generateMigrationGuide(changes: ApiChange[]): string;
60
+ /**
61
+ * Generate a changelog in Markdown from a snapshot diff.
62
+ * Sections: Breaking Changes, Added, Changed. Omits empty sections.
63
+ *
64
+ * @codexApi {"parent":"pithy.codex.breaking-changes","name":"generateChangelog","stability":"stable","signature":"(diff: SnapshotDiff) => string"}
65
+ */
66
+ export declare function generateChangelog(diff: SnapshotDiff): string;