@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.
- package/LICENSE +21 -0
- package/README.md +4401 -0
- package/dist/annotations.d.ts +106 -0
- package/dist/annotations.js +306 -0
- package/dist/apply.d.ts +2 -0
- package/dist/apply.js +80 -0
- package/dist/changed-scope.d.ts +23 -0
- package/dist/changed-scope.js +31 -0
- package/dist/check.d.ts +2 -0
- package/dist/check.js +117 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +67 -0
- package/dist/env.d.ts +5 -0
- package/dist/env.js +35 -0
- package/dist/extract-cli.d.ts +23 -0
- package/dist/extract-cli.js +192 -0
- package/dist/extraction/breaking-changes.d.ts +66 -0
- package/dist/extraction/breaking-changes.js +352 -0
- package/dist/extraction/example-extractor.d.ts +55 -0
- package/dist/extraction/example-extractor.js +272 -0
- package/dist/extraction/index.d.ts +27 -0
- package/dist/extraction/index.js +31 -0
- package/dist/extraction/jsdoc-parser.d.ts +43 -0
- package/dist/extraction/jsdoc-parser.js +274 -0
- package/dist/extraction/pipeline.d.ts +54 -0
- package/dist/extraction/pipeline.js +526 -0
- package/dist/extraction/readme-sync.d.ts +108 -0
- package/dist/extraction/readme-sync.js +592 -0
- package/dist/extraction/snapshot-store.d.ts +40 -0
- package/dist/extraction/snapshot-store.js +153 -0
- package/dist/extraction/source-linker.d.ts +80 -0
- package/dist/extraction/source-linker.js +316 -0
- package/dist/extraction/test-example-extractor.d.ts +69 -0
- package/dist/extraction/test-example-extractor.js +400 -0
- package/dist/extraction/test-pattern-extractor.d.ts +68 -0
- package/dist/extraction/test-pattern-extractor.js +261 -0
- package/dist/extraction/testing-pyramid.d.ts +44 -0
- package/dist/extraction/testing-pyramid.js +163 -0
- package/dist/extraction/type-extractor.d.ts +34 -0
- package/dist/extraction/type-extractor.js +494 -0
- package/dist/extraction/types.d.ts +401 -0
- package/dist/extraction/types.js +34 -0
- package/dist/indexer.d.ts +1 -0
- package/dist/indexer.js +107 -0
- package/dist/llm.d.ts +8 -0
- package/dist/llm.js +75 -0
- package/dist/readme-sync-cli.d.ts +20 -0
- package/dist/readme-sync-cli.js +167 -0
- package/dist/review.d.ts +2 -0
- package/dist/review.js +93 -0
- package/dist/scan.d.ts +29 -0
- package/dist/scan.js +221 -0
- package/dist/schema.d.ts +169 -0
- package/dist/schema.js +70 -0
- package/dist/snapshot-cli.d.ts +45 -0
- package/dist/snapshot-cli.js +217 -0
- package/dist/sync-cli.d.ts +22 -0
- package/dist/sync-cli.js +154 -0
- package/dist/sync-pipeline.d.ts +58 -0
- package/dist/sync-pipeline.js +104 -0
- package/dist/sync.d.ts +2 -0
- package/dist/sync.js +318 -0
- package/dist/validate-cli.d.ts +20 -0
- package/dist/validate-cli.js +144 -0
- package/dist/validate.d.ts +76 -0
- package/dist/validate.js +183 -0
- package/dist/watch-cli.d.ts +21 -0
- package/dist/watch-cli.js +220 -0
- package/package.json +62 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @codex
|
|
3
|
+
* {
|
|
4
|
+
* "id": "pithy.codex.extraction.types",
|
|
5
|
+
* "title": "Extraction Types",
|
|
6
|
+
* "category": "types"
|
|
7
|
+
* }
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Source location information for linking docs to code
|
|
11
|
+
*/
|
|
12
|
+
export interface SourceLocation {
|
|
13
|
+
file: string;
|
|
14
|
+
line: number;
|
|
15
|
+
column?: number;
|
|
16
|
+
endLine?: number;
|
|
17
|
+
endColumn?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parsed JSDoc/TSDoc parameter documentation
|
|
21
|
+
*/
|
|
22
|
+
export interface ParamDoc {
|
|
23
|
+
name: string;
|
|
24
|
+
type?: string;
|
|
25
|
+
description: string;
|
|
26
|
+
optional?: boolean;
|
|
27
|
+
defaultValue?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parsed JSDoc/TSDoc return documentation
|
|
31
|
+
*/
|
|
32
|
+
export interface ReturnDoc {
|
|
33
|
+
type?: string;
|
|
34
|
+
description: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Extracted example block from documentation
|
|
38
|
+
*/
|
|
39
|
+
export interface ExtractedExample {
|
|
40
|
+
code: string;
|
|
41
|
+
language: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
runnable: boolean;
|
|
44
|
+
isDoctest: boolean;
|
|
45
|
+
location: SourceLocation;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parsed JSDoc/TSDoc block with all metadata
|
|
49
|
+
*/
|
|
50
|
+
export interface ParsedJSDoc {
|
|
51
|
+
description: string;
|
|
52
|
+
params: ParamDoc[];
|
|
53
|
+
returns?: ReturnDoc;
|
|
54
|
+
examples: ExtractedExample[];
|
|
55
|
+
throws?: string[];
|
|
56
|
+
see?: string[];
|
|
57
|
+
since?: string;
|
|
58
|
+
deprecated?: string | boolean;
|
|
59
|
+
tags: Record<string, string[]>;
|
|
60
|
+
location: SourceLocation;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Test case extracted from test files
|
|
64
|
+
*/
|
|
65
|
+
export interface ExtractedTestCase {
|
|
66
|
+
name: string;
|
|
67
|
+
description?: string;
|
|
68
|
+
type: 'describe' | 'it' | 'test';
|
|
69
|
+
location: SourceLocation;
|
|
70
|
+
parentDescribe?: string;
|
|
71
|
+
code: string;
|
|
72
|
+
assertions: string[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Test file analysis result
|
|
76
|
+
*/
|
|
77
|
+
export interface TestFileAnalysis {
|
|
78
|
+
file: string;
|
|
79
|
+
testCases: ExtractedTestCase[];
|
|
80
|
+
imports: string[];
|
|
81
|
+
coveredApis: string[];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Enriched API entry with extracted documentation
|
|
85
|
+
*/
|
|
86
|
+
export interface EnrichedApiEntry {
|
|
87
|
+
id: string;
|
|
88
|
+
name: string;
|
|
89
|
+
parent: string;
|
|
90
|
+
signature?: string;
|
|
91
|
+
stability: 'internal' | 'experimental' | 'stable' | 'deprecated';
|
|
92
|
+
jsdoc?: ParsedJSDoc;
|
|
93
|
+
examples: ExtractedExample[];
|
|
94
|
+
testRefs: TestReference[];
|
|
95
|
+
sourceLocation: SourceLocation;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Reference to a test that covers an API
|
|
99
|
+
*/
|
|
100
|
+
export interface TestReference {
|
|
101
|
+
file: string;
|
|
102
|
+
line: number;
|
|
103
|
+
name: string;
|
|
104
|
+
type: 'unit' | 'integration' | 'e2e';
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Enriched component with all extracted data
|
|
108
|
+
*/
|
|
109
|
+
export interface EnrichedComponent {
|
|
110
|
+
id: string;
|
|
111
|
+
title: string;
|
|
112
|
+
category: string;
|
|
113
|
+
risk?: RiskLevel;
|
|
114
|
+
testing?: TestingRequirements;
|
|
115
|
+
description?: string;
|
|
116
|
+
apis: EnrichedApiEntry[];
|
|
117
|
+
examples: ExtractedExample[];
|
|
118
|
+
testRefs: TestReference[];
|
|
119
|
+
sourceFiles: SourceLocation[];
|
|
120
|
+
readmeSections?: ReadmeSection[];
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Section extracted from README files
|
|
124
|
+
*/
|
|
125
|
+
export interface ReadmeSection {
|
|
126
|
+
heading: string;
|
|
127
|
+
level: number;
|
|
128
|
+
content: string;
|
|
129
|
+
codeBlocks: ExtractedExample[];
|
|
130
|
+
linkedEntryId?: string;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Configuration for the extraction pipeline
|
|
134
|
+
*/
|
|
135
|
+
export interface ExtractionConfig {
|
|
136
|
+
/** Root directory to scan */
|
|
137
|
+
root: string;
|
|
138
|
+
/** Glob patterns for source files */
|
|
139
|
+
sourcePatterns: string[];
|
|
140
|
+
/** Glob patterns for test files */
|
|
141
|
+
testPatterns: string[];
|
|
142
|
+
/** Glob patterns for README files */
|
|
143
|
+
readmePatterns: string[];
|
|
144
|
+
/** Whether to extract examples as runnable */
|
|
145
|
+
extractRunnableExamples: boolean;
|
|
146
|
+
/** Whether to parse test files for coverage */
|
|
147
|
+
analyzeTests: boolean;
|
|
148
|
+
/** Whether to link README sections */
|
|
149
|
+
linkReadmes: boolean;
|
|
150
|
+
/** Optional path to vitest JSON output for test status matching */
|
|
151
|
+
vitestOutputPath?: string;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Result of the extraction pipeline
|
|
155
|
+
*/
|
|
156
|
+
export interface ExtractionResult {
|
|
157
|
+
components: EnrichedComponent[];
|
|
158
|
+
testAnalysis: TestFileAnalysis[];
|
|
159
|
+
readmeSections: ReadmeSection[];
|
|
160
|
+
stats: ExtractionStats;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Statistics from the extraction process
|
|
164
|
+
*/
|
|
165
|
+
export interface ExtractionStats {
|
|
166
|
+
filesScanned: number;
|
|
167
|
+
componentsFound: number;
|
|
168
|
+
apisExtracted: number;
|
|
169
|
+
examplesExtracted: number;
|
|
170
|
+
testsAnalyzed: number;
|
|
171
|
+
readmeSectionsLinked: number;
|
|
172
|
+
processingTimeMs: number;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Default extraction configuration (all fields except root)
|
|
176
|
+
*/
|
|
177
|
+
export declare const DEFAULT_EXTRACTION_CONFIG: Omit<ExtractionConfig, 'root'>;
|
|
178
|
+
/**
|
|
179
|
+
* Extracted TypeScript type/interface definition
|
|
180
|
+
*/
|
|
181
|
+
export interface ExtractedTypeDefinition {
|
|
182
|
+
name: string;
|
|
183
|
+
kind: 'interface' | 'type' | 'enum' | 'class';
|
|
184
|
+
exported: boolean;
|
|
185
|
+
generics?: string[];
|
|
186
|
+
extends?: string[];
|
|
187
|
+
implements?: string[];
|
|
188
|
+
members: TypeMember[];
|
|
189
|
+
jsdoc?: ParsedJSDoc;
|
|
190
|
+
location: SourceLocation;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Member of a type/interface/class
|
|
194
|
+
*/
|
|
195
|
+
export interface TypeMember {
|
|
196
|
+
name: string;
|
|
197
|
+
type: string;
|
|
198
|
+
optional: boolean;
|
|
199
|
+
readonly: boolean;
|
|
200
|
+
description?: string;
|
|
201
|
+
signature?: string;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Test example extracted via @codex:example marker
|
|
205
|
+
*/
|
|
206
|
+
export interface TestExample {
|
|
207
|
+
/** The codex entry ID this example belongs to */
|
|
208
|
+
entryId: string;
|
|
209
|
+
/** Name/description of the example */
|
|
210
|
+
name: string;
|
|
211
|
+
/** The actual test code */
|
|
212
|
+
code: string;
|
|
213
|
+
/** Source location in the test file */
|
|
214
|
+
location: SourceLocation;
|
|
215
|
+
/** Test file path */
|
|
216
|
+
testFile: string;
|
|
217
|
+
/** Whether this test is passing */
|
|
218
|
+
status?: 'passing' | 'failing' | 'skipped' | 'unknown';
|
|
219
|
+
/** Error message if failing */
|
|
220
|
+
error?: string;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Test status from vitest/jest output
|
|
224
|
+
*/
|
|
225
|
+
export interface TestStatus {
|
|
226
|
+
file: string;
|
|
227
|
+
testName: string;
|
|
228
|
+
status: 'passed' | 'failed' | 'skipped';
|
|
229
|
+
duration?: number;
|
|
230
|
+
error?: string;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Extended extraction result with type definitions and test examples
|
|
234
|
+
*/
|
|
235
|
+
export interface ExtendedExtractionResult extends ExtractionResult {
|
|
236
|
+
typeDefinitions: ExtractedTypeDefinition[];
|
|
237
|
+
testExamples: TestExample[];
|
|
238
|
+
testStatuses: TestStatus[];
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Marker type for auto-populated README sections
|
|
242
|
+
*/
|
|
243
|
+
export type ReadmeMarkerType = 'install' | 'examples' | 'api' | 'bundle' | 'testing';
|
|
244
|
+
/**
|
|
245
|
+
* Parsed README marker with its attributes and position
|
|
246
|
+
*/
|
|
247
|
+
export interface ReadmeMarker {
|
|
248
|
+
/** Marker type: install, examples, api, bundle, testing */
|
|
249
|
+
type: ReadmeMarkerType;
|
|
250
|
+
/** Raw attributes from the marker tag */
|
|
251
|
+
attributes: Record<string, string>;
|
|
252
|
+
/** The original opening tag string (preserved for idempotent rewrites) */
|
|
253
|
+
openTag: string;
|
|
254
|
+
/** Start position (character index) in the README content */
|
|
255
|
+
startIndex: number;
|
|
256
|
+
/** End position (character index) after <!-- @codex:end --> */
|
|
257
|
+
endIndex: number;
|
|
258
|
+
/** The full original content between markers (including markers themselves) */
|
|
259
|
+
originalContent: string;
|
|
260
|
+
/** Just the content between markers (excluding marker tags) */
|
|
261
|
+
innerContent: string;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Result of syncing a single README file
|
|
265
|
+
*/
|
|
266
|
+
export interface ReadmeSyncResult {
|
|
267
|
+
/** Path to the README file */
|
|
268
|
+
filePath: string;
|
|
269
|
+
/** Whether any content was changed */
|
|
270
|
+
changed: boolean;
|
|
271
|
+
/** Number of markers processed */
|
|
272
|
+
markersProcessed: number;
|
|
273
|
+
/** Number of markers that generated new content */
|
|
274
|
+
markersUpdated: number;
|
|
275
|
+
/** Markers that could not be processed (with reasons) */
|
|
276
|
+
warnings: string[];
|
|
277
|
+
/** The updated content (only if changed) */
|
|
278
|
+
updatedContent?: string;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Generic logger interface used by codex CLI tools and pipelines.
|
|
282
|
+
*/
|
|
283
|
+
export interface CodexLogger {
|
|
284
|
+
log(message: string): void;
|
|
285
|
+
warn(message: string): void;
|
|
286
|
+
error(message: string): void;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Configuration for README sync.
|
|
290
|
+
* All fields are optional — `syncAllReadmes` provides sensible defaults.
|
|
291
|
+
*/
|
|
292
|
+
export interface ReadmeSyncConfig {
|
|
293
|
+
/** Project root directory (default: cwd) */
|
|
294
|
+
root?: string;
|
|
295
|
+
/** Glob patterns for README files to sync (default: `packages/*/README.md`, `README.md`) */
|
|
296
|
+
readmePatterns?: string[];
|
|
297
|
+
/** Whether to write changes to disk or just report (default: false) */
|
|
298
|
+
dryRun?: boolean;
|
|
299
|
+
/** Whether to show verbose output (default: false) */
|
|
300
|
+
verbose?: boolean;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Individual API entry within a snapshot
|
|
304
|
+
*/
|
|
305
|
+
export interface SnapshotEntry {
|
|
306
|
+
/** API name */
|
|
307
|
+
name: string;
|
|
308
|
+
/** Parent component/module id */
|
|
309
|
+
parent: string;
|
|
310
|
+
/** Qualified name: parent.name */
|
|
311
|
+
qualifiedName: string;
|
|
312
|
+
/** Function/type signature */
|
|
313
|
+
signature?: string;
|
|
314
|
+
/** Stability level */
|
|
315
|
+
stability: 'internal' | 'experimental' | 'stable' | 'deprecated';
|
|
316
|
+
/** Kind for type definitions (interface, type, enum, class) */
|
|
317
|
+
kind?: 'interface' | 'type' | 'enum' | 'class';
|
|
318
|
+
/** Members for type definitions */
|
|
319
|
+
members?: TypeMember[];
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* A point-in-time snapshot of all public APIs at a given version
|
|
323
|
+
*/
|
|
324
|
+
export interface ApiSnapshot {
|
|
325
|
+
/** Semantic version string */
|
|
326
|
+
version: string;
|
|
327
|
+
/** ISO timestamp when the snapshot was created */
|
|
328
|
+
timestamp: string;
|
|
329
|
+
/** All API entries captured in this snapshot */
|
|
330
|
+
entries: SnapshotEntry[];
|
|
331
|
+
}
|
|
332
|
+
/** Kind of change detected between two API versions */
|
|
333
|
+
export type ChangeKind = 'added' | 'removed' | 'signature-changed' | 'stability-changed' | 'members-changed';
|
|
334
|
+
/**
|
|
335
|
+
* A single detected change between two API snapshots
|
|
336
|
+
*/
|
|
337
|
+
export interface ApiChange {
|
|
338
|
+
/** API name */
|
|
339
|
+
name: string;
|
|
340
|
+
/** Parent component/module id */
|
|
341
|
+
parent: string;
|
|
342
|
+
/** Qualified name: parent.name */
|
|
343
|
+
qualifiedName: string;
|
|
344
|
+
/** Type of change */
|
|
345
|
+
changeKind: ChangeKind;
|
|
346
|
+
/** Entry from the old snapshot (absent for additions) */
|
|
347
|
+
before?: SnapshotEntry;
|
|
348
|
+
/** Entry from the new snapshot (absent for removals) */
|
|
349
|
+
after?: SnapshotEntry;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Result of diffing two API snapshots
|
|
353
|
+
*/
|
|
354
|
+
export interface SnapshotDiff {
|
|
355
|
+
/** Version of the older snapshot */
|
|
356
|
+
fromVersion: string;
|
|
357
|
+
/** Version of the newer snapshot */
|
|
358
|
+
toVersion: string;
|
|
359
|
+
/** APIs that were added */
|
|
360
|
+
added: ApiChange[];
|
|
361
|
+
/** APIs that were removed */
|
|
362
|
+
removed: ApiChange[];
|
|
363
|
+
/** APIs whose signature, members, or stability changed */
|
|
364
|
+
changed: ApiChange[];
|
|
365
|
+
/** Count of APIs that remain unchanged */
|
|
366
|
+
unchanged: number;
|
|
367
|
+
}
|
|
368
|
+
/** Semver bump classification for a change */
|
|
369
|
+
export type ChangeClassification = 'major' | 'minor' | 'patch';
|
|
370
|
+
/** Risk level for a codex entry, affects testing requirements */
|
|
371
|
+
export type RiskLevel = 'critical' | 'high' | 'medium' | 'low';
|
|
372
|
+
/**
|
|
373
|
+
* Testing level requirements and status
|
|
374
|
+
*/
|
|
375
|
+
export interface TestLevel {
|
|
376
|
+
/** Whether this test level is required for this feature */
|
|
377
|
+
required: boolean;
|
|
378
|
+
/** Whether tests currently exist */
|
|
379
|
+
covered?: boolean;
|
|
380
|
+
/** Minimum percentage coverage target */
|
|
381
|
+
coverage?: number;
|
|
382
|
+
/** Path to the test file */
|
|
383
|
+
file?: string;
|
|
384
|
+
/** Specific aspects to test */
|
|
385
|
+
focus?: string[];
|
|
386
|
+
/** Required scenarios (for e2e) */
|
|
387
|
+
scenarios?: string[];
|
|
388
|
+
/** Dependencies to integrate with (for integration tests) */
|
|
389
|
+
dependencies?: string[];
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Testing requirements for a codex entry across all pyramid levels
|
|
393
|
+
*/
|
|
394
|
+
export interface TestingRequirements {
|
|
395
|
+
/** Unit test requirements */
|
|
396
|
+
unit?: TestLevel;
|
|
397
|
+
/** Integration test requirements */
|
|
398
|
+
integration?: TestLevel;
|
|
399
|
+
/** End-to-end test requirements */
|
|
400
|
+
e2e?: TestLevel;
|
|
401
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @codex
|
|
3
|
+
* {
|
|
4
|
+
* "id": "pithy.codex.extraction.types",
|
|
5
|
+
* "title": "Extraction Types",
|
|
6
|
+
* "category": "types"
|
|
7
|
+
* }
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Default extraction configuration (all fields except root)
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_EXTRACTION_CONFIG = {
|
|
13
|
+
sourcePatterns: [
|
|
14
|
+
'packages/*/src/**/*.ts',
|
|
15
|
+
'packages/*/src/**/*.js',
|
|
16
|
+
'packages/*/src/**/*.mjs',
|
|
17
|
+
'apps/*/src/**/*.ts',
|
|
18
|
+
'apps/*/src/**/*.js',
|
|
19
|
+
'apps/*/src/**/*.mjs',
|
|
20
|
+
],
|
|
21
|
+
testPatterns: [
|
|
22
|
+
'packages/*/src/**/*.test.ts',
|
|
23
|
+
'packages/*/src/**/*.spec.ts',
|
|
24
|
+
'packages/*/tests/**/*.ts',
|
|
25
|
+
'apps/*/src/**/*.test.ts',
|
|
26
|
+
'apps/*/src/**/*.spec.ts',
|
|
27
|
+
'apps/*/tests/**/*.ts',
|
|
28
|
+
'e2e/tests/**/*.ts',
|
|
29
|
+
],
|
|
30
|
+
readmePatterns: ['packages/*/README.md', 'README.md'],
|
|
31
|
+
extractRunnableExamples: true,
|
|
32
|
+
analyzeTests: true,
|
|
33
|
+
linkReadmes: true,
|
|
34
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/indexer.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @codex
|
|
3
|
+
* {
|
|
4
|
+
* "id": "pithy.codex.indexer",
|
|
5
|
+
* "title": "Index Generator",
|
|
6
|
+
* "category": "feature"
|
|
7
|
+
* }
|
|
8
|
+
*
|
|
9
|
+
* Regenerates codex index files from entries.
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'node:fs/promises';
|
|
12
|
+
import { watch as fsWatch } from 'node:fs';
|
|
13
|
+
import { join, dirname, relative } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { codexSchema } from './schema.js';
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const repoRoot = join(__dirname, '../../..');
|
|
18
|
+
const codexDir = join(repoRoot, 'codex');
|
|
19
|
+
const indexPath = join(repoRoot, 'codex.index.json');
|
|
20
|
+
async function ensureDir(dir) {
|
|
21
|
+
try {
|
|
22
|
+
await fs.mkdir(dir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// ignore
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function getJsonFiles(dir) {
|
|
29
|
+
const dirents = await fs
|
|
30
|
+
.readdir(dir, { withFileTypes: true })
|
|
31
|
+
.catch(() => []);
|
|
32
|
+
const files = [];
|
|
33
|
+
for (const dirent of dirents) {
|
|
34
|
+
const res = join(dir, dirent.name);
|
|
35
|
+
if (dirent.isDirectory()) {
|
|
36
|
+
// Skip proposals directory — only index applied entries
|
|
37
|
+
if (dirent.name === '.proposals')
|
|
38
|
+
continue;
|
|
39
|
+
files.push(...(await getJsonFiles(res)));
|
|
40
|
+
}
|
|
41
|
+
else if (dirent.isFile() && res.endsWith('.json')) {
|
|
42
|
+
files.push(res);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return files;
|
|
46
|
+
}
|
|
47
|
+
async function buildIndex() {
|
|
48
|
+
await ensureDir(codexDir);
|
|
49
|
+
const files = await getJsonFiles(codexDir);
|
|
50
|
+
const entries = [];
|
|
51
|
+
for (const file of files) {
|
|
52
|
+
try {
|
|
53
|
+
const raw = await fs.readFile(file, 'utf8');
|
|
54
|
+
const data = JSON.parse(raw);
|
|
55
|
+
const parsed = codexSchema.safeParse(data);
|
|
56
|
+
if (!parsed.success) {
|
|
57
|
+
const issues = parsed.error.issues.map(i => i.message).join('; ');
|
|
58
|
+
console.warn(`Skipping ${file}: schema errors: ${issues}`);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
entries.push({ ...parsed.data, path: relative(repoRoot, file) });
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
65
|
+
console.warn(`Skipping ${file}: ${msg}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// deterministic sort
|
|
69
|
+
entries.sort((a, b) => (a.category || '').localeCompare(b.category || '') ||
|
|
70
|
+
(a.title || '').localeCompare(b.title || '') ||
|
|
71
|
+
a.id.localeCompare(b.id));
|
|
72
|
+
// quick lookup map
|
|
73
|
+
const byId = Object.fromEntries(entries.map(e => [e.id, e]));
|
|
74
|
+
const payload = {
|
|
75
|
+
updatedAt: new Date().toISOString(),
|
|
76
|
+
count: entries.length,
|
|
77
|
+
entries,
|
|
78
|
+
byId,
|
|
79
|
+
};
|
|
80
|
+
const tmp = `${indexPath}.tmp`;
|
|
81
|
+
await fs.writeFile(tmp, JSON.stringify(payload, null, 2));
|
|
82
|
+
await fs.rename(tmp, indexPath);
|
|
83
|
+
console.log(`Wrote ${indexPath} (${entries.length} entries)`);
|
|
84
|
+
}
|
|
85
|
+
function watch() {
|
|
86
|
+
let timer = null;
|
|
87
|
+
const debouncedBuild = () => {
|
|
88
|
+
if (timer)
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
timer = setTimeout(() => {
|
|
91
|
+
buildIndex().catch(console.error);
|
|
92
|
+
}, 100);
|
|
93
|
+
};
|
|
94
|
+
fsWatch(codexDir, { recursive: false }, debouncedBuild);
|
|
95
|
+
// fs.watch won’t see new subdirs on some platforms — periodic rescan is a cheap fix
|
|
96
|
+
setInterval(debouncedBuild, 2000);
|
|
97
|
+
void buildIndex().catch(console.error);
|
|
98
|
+
}
|
|
99
|
+
if (process.argv.includes('--watch')) {
|
|
100
|
+
watch();
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
buildIndex().catch(err => {
|
|
104
|
+
console.error(err);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
});
|
|
107
|
+
}
|
package/dist/llm.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sends a prompt to the OpenAI API and returns the response text.
|
|
3
|
+
* @codexApi {"parent":"pithy.codex.llm","name":"askLLM","stability":"stable","signature":"(messages: { role: 'system' | 'user'; content: string }[]) => Promise<string>"}
|
|
4
|
+
*/
|
|
5
|
+
export declare function askLLM(messages: {
|
|
6
|
+
role: 'system' | 'user';
|
|
7
|
+
content: string;
|
|
8
|
+
}[]): Promise<string>;
|
package/dist/llm.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @codex
|
|
3
|
+
* {
|
|
4
|
+
* "id": "pithy.codex.llm",
|
|
5
|
+
* "title": "LLM Integration",
|
|
6
|
+
* "category": "feature"
|
|
7
|
+
* }
|
|
8
|
+
*
|
|
9
|
+
* Sends prompts to OpenAI for codex generation.
|
|
10
|
+
*/
|
|
11
|
+
import OpenAI from 'openai';
|
|
12
|
+
import { loadEnv } from './env.js';
|
|
13
|
+
loadEnv();
|
|
14
|
+
const baseURL = process.env.OPENAI_BASE_URL || undefined;
|
|
15
|
+
const apiKey = process.env.OPENAI_API_KEY || 'EMPTY';
|
|
16
|
+
const model = process.env.CODEX_MODEL || 'gpt-5-mini';
|
|
17
|
+
// Optional temperature (omitted by default). We also retry if a model rejects it.
|
|
18
|
+
const tempEnv = process.env.CODEX_TEMPERATURE;
|
|
19
|
+
const parsedTemp = tempEnv !== undefined && tempEnv !== '' ? Number(tempEnv) : undefined;
|
|
20
|
+
const temperature = Number.isFinite(parsedTemp) ? parsedTemp : undefined;
|
|
21
|
+
const client = new OpenAI({ apiKey, baseURL });
|
|
22
|
+
function errStatus(e) {
|
|
23
|
+
if (e && typeof e === 'object') {
|
|
24
|
+
const obj = e;
|
|
25
|
+
return obj.status ?? obj.response?.status;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
function errBody(e) {
|
|
30
|
+
if (e && typeof e === 'object') {
|
|
31
|
+
const obj = e;
|
|
32
|
+
return obj.response?.data ?? obj.message ?? String(e);
|
|
33
|
+
}
|
|
34
|
+
return String(e);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Sends a prompt to the OpenAI API and returns the response text.
|
|
38
|
+
* @codexApi {"parent":"pithy.codex.llm","name":"askLLM","stability":"stable","signature":"(messages: { role: 'system' | 'user'; content: string }[]) => Promise<string>"}
|
|
39
|
+
*/
|
|
40
|
+
export async function askLLM(messages) {
|
|
41
|
+
// Build params and include temperature only if provided
|
|
42
|
+
const params = {
|
|
43
|
+
model,
|
|
44
|
+
messages,
|
|
45
|
+
};
|
|
46
|
+
if (typeof temperature === 'number')
|
|
47
|
+
params.temperature = temperature;
|
|
48
|
+
try {
|
|
49
|
+
const res = await client.chat.completions.create(params);
|
|
50
|
+
return res.choices[0]?.message?.content ?? '';
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
const status = errStatus(e);
|
|
54
|
+
const body = errBody(e);
|
|
55
|
+
console.error(`[codex:llm] request failed (model="${model}", status=${status})`);
|
|
56
|
+
console.error(body);
|
|
57
|
+
// Auto-retry if the error complains about temperature
|
|
58
|
+
const bodyText = typeof body === 'string' ? body : JSON.stringify(body);
|
|
59
|
+
if (status === 400 && /temperature/i.test(bodyText)) {
|
|
60
|
+
try {
|
|
61
|
+
delete params.temperature;
|
|
62
|
+
console.warn('[codex:llm] retrying without temperature…');
|
|
63
|
+
const res = await client.chat.completions.create(params);
|
|
64
|
+
return res.choices[0]?.message?.content ?? '';
|
|
65
|
+
}
|
|
66
|
+
catch (e2) {
|
|
67
|
+
const s2 = errStatus(e2);
|
|
68
|
+
const b2 = errBody(e2);
|
|
69
|
+
console.error(`[codex:llm] retry failed (model="${model}", status=${s2})`);
|
|
70
|
+
console.error(b2);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @codex
|
|
4
|
+
* {
|
|
5
|
+
* "id": "pithy.codex.extraction.readme-sync-cli",
|
|
6
|
+
* "title": "README Sync CLI",
|
|
7
|
+
* "category": "plugin"
|
|
8
|
+
* }
|
|
9
|
+
*
|
|
10
|
+
* CLI for syncing README files with codex extraction data.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* pnpm --filter @pithyjs/codex readme-sync [-- options]
|
|
14
|
+
*
|
|
15
|
+
* Options:
|
|
16
|
+
* --root <path> Project root directory (default: cwd)
|
|
17
|
+
* --dry-run Report changes without writing files
|
|
18
|
+
* --verbose Show detailed progress
|
|
19
|
+
*/
|
|
20
|
+
export {};
|