@aprimediet/codewalker 1.1.0 → 1.2.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.
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Library indexer for codewalker v1.2.
3
+ *
4
+ * Orchestrates discovery → extraction → card writing → DB population
5
+ * for third-party library dependencies installed in node_modules.
6
+ *
7
+ * - `indexLibraries()`: full pipeline — idempotent
8
+ * - `rebuildLibDbFromCards()`: disposable-index rebuild
9
+ *
10
+ * Uses the same atomic write pattern (tmp + rename, 0o600) as the v1.1 indexer.
11
+ */
12
+
13
+ import * as fs from "node:fs";
14
+ import * as path from "node:path";
15
+ import { openDb, upsertLibrary, upsertLibSymbol, deleteLibrary, setMeta } from "../db.ts";
16
+ import { locateLibrary } from "./resolve.ts";
17
+ import { extractDtsSymbols } from "./dts.ts";
18
+ import { renderLibCard } from "./cards.ts";
19
+ import { parseCard } from "../cards.ts";
20
+ import type { LibSymbol } from "../types.ts";
21
+
22
+ export interface IndexLibrariesOptions {
23
+ projectRoot: string;
24
+ libsDir: string;
25
+ dbPath: string;
26
+ includeDev?: boolean;
27
+ }
28
+
29
+ export interface IndexResult {
30
+ indexed: number; // library count indexed
31
+ symbols: number; // total symbols extracted
32
+ errors: number; // libraries that failed
33
+ }
34
+
35
+ /**
36
+ * Full library index pipeline:
37
+ * 1. Read project package.json → dependency names
38
+ * 2. For each dep, locate installed package (version, .d.ts, README)
39
+ * 3. Extract symbols from .d.ts
40
+ * 4. Write cards under entries/libs/<pkg>@<version>/
41
+ * 5. Populate libraries + lib_symbols tables
42
+ *
43
+ * Idempotent: version changes prune old data; re-running with no changes is stable.
44
+ */
45
+ export async function indexLibraries(options: IndexLibrariesOptions): Promise<IndexResult> {
46
+ const { projectRoot, libsDir, dbPath, includeDev } = options;
47
+
48
+ // Read project package.json
49
+ const pkgJsonPath = path.join(projectRoot, "package.json");
50
+ if (!fs.existsSync(pkgJsonPath)) {
51
+ return { indexed: 0, symbols: 0, errors: 0 };
52
+ }
53
+
54
+ let pkgJson: Record<string, any>;
55
+ try {
56
+ pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
57
+ } catch {
58
+ return { indexed: 0, symbols: 0, errors: 0 };
59
+ }
60
+
61
+ const depNames = parseDependenciesFromPkg(pkgJson, includeDev ?? false);
62
+ if (depNames.length === 0) {
63
+ return { indexed: 0, symbols: 0, errors: 0 };
64
+ }
65
+
66
+ const db = openDb(dbPath);
67
+ let indexedCount = 0;
68
+ let symbolCount = 0;
69
+ let errorCount = 0;
70
+
71
+ try {
72
+ for (const name of depNames) {
73
+ const lib = locateLibrary(projectRoot, name);
74
+ if (!lib) {
75
+ // Dep missing from node_modules — skip silently
76
+ continue;
77
+ }
78
+
79
+ const version = lib.version;
80
+ const libDirName = `${name}@${version}`;
81
+ const libCardDir = path.join(libsDir, libDirName);
82
+
83
+ // Check if this exact version is already indexed (idempotency check)
84
+ if (fs.existsSync(libCardDir) && isVersionIndexed(db, name, version)) {
85
+ // Already up-to-date — skip
86
+ indexedCount++;
87
+ continue;
88
+ }
89
+
90
+ // Prune old version if the version changed
91
+ pruneOldVersion(db, libsDir, name, version);
92
+
93
+ // Create card directory
94
+ fs.mkdirSync(libCardDir, { recursive: true });
95
+
96
+ // Upsert library record
97
+ upsertLibrary(db, {
98
+ name,
99
+ version,
100
+ source: "node_modules",
101
+ dts_path: lib.dtsPath,
102
+ readme: lib.readmePath ? readFirstLines(lib.readmePath, 5) : null,
103
+ });
104
+
105
+ const symbols: LibSymbol[] = [];
106
+
107
+ // Extract from .d.ts if available
108
+ if (lib.dtsPath) {
109
+ try {
110
+ const source = fs.readFileSync(lib.dtsPath, "utf-8");
111
+ const extracted = extractDtsSymbols(source, name, version);
112
+ symbols.push(...extracted);
113
+ } catch {
114
+ // d.ts parse error — skip silently
115
+ }
116
+ }
117
+
118
+ // If no .d.ts symbols and we have README, create a module overview card
119
+ if (symbols.length === 0 && lib.readmePath) {
120
+ const readmeText = fs.readFileSync(lib.readmePath, "utf-8");
121
+ const summary = readmeText.split("\n").slice(0, 5).join("\n").trim() || `${name} library`;
122
+ symbols.push({
123
+ lib: name,
124
+ version,
125
+ name,
126
+ kind: "module",
127
+ signature: "",
128
+ doc: readmeText.slice(0, 2000),
129
+ summary,
130
+ card_path: "",
131
+ });
132
+ }
133
+
134
+ // Write cards and insert symbols
135
+ for (const sym of symbols) {
136
+ const cardFileName = `${sanitizeName(sym.name)}.md`;
137
+ const cardPath = path.join(libCardDir, cardFileName);
138
+
139
+ const card = renderLibCard(sym);
140
+ const tmpPath = cardPath + ".tmp";
141
+ fs.writeFileSync(tmpPath, card, { encoding: "utf-8", mode: 0o600 });
142
+ fs.renameSync(tmpPath, cardPath);
143
+
144
+ sym.card_path = cardPath;
145
+ upsertLibSymbol(db, sym);
146
+ }
147
+
148
+ indexedCount++;
149
+ symbolCount += symbols.length;
150
+ }
151
+
152
+ setMeta(db, "last_libs_index", new Date().toISOString());
153
+ } catch (e) {
154
+ errorCount++;
155
+ } finally {
156
+ db.close();
157
+ }
158
+
159
+ return { indexed: indexedCount, symbols: symbolCount, errors: errorCount };
160
+ }
161
+
162
+ /**
163
+ * Rebuild the lib_symbols DB tables from cards alone.
164
+ * Used for the disposable-index property.
165
+ */
166
+ export function rebuildLibDbFromCards(
167
+ dbPath: string,
168
+ libsDir: string,
169
+ ): void {
170
+ if (!fs.existsSync(libsDir)) return;
171
+
172
+ const db = openDb(dbPath);
173
+
174
+ try {
175
+ db.exec("BEGIN TRANSACTION");
176
+
177
+ // Walk all <pkg>@<version>/ directories
178
+ for (const libDir of fs.readdirSync(libsDir, { withFileTypes: true })) {
179
+ if (!libDir.isDirectory()) continue;
180
+
181
+ const fullLibDir = path.join(libsDir, libDir.name);
182
+ const atIndex = libDir.name.lastIndexOf("@");
183
+ if (atIndex < 0) continue;
184
+
185
+ const libName = libDir.name.slice(0, atIndex);
186
+ const version = libDir.name.slice(atIndex + 1);
187
+
188
+ // Upsert library record
189
+ upsertLibrary(db, { name: libName, version, source: "node_modules", dts_path: null, readme: null });
190
+
191
+ // Walk .md cards
192
+ for (const entry of fs.readdirSync(fullLibDir, { withFileTypes: true })) {
193
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
194
+
195
+ const cardPath = path.join(fullLibDir, entry.name);
196
+ const cardText = fs.readFileSync(cardPath, "utf-8");
197
+ const parsed = parseCard(cardText);
198
+ if (!parsed) continue;
199
+
200
+ const { head } = parsed;
201
+
202
+ upsertLibSymbol(db, {
203
+ lib: libName,
204
+ version,
205
+ name: head.name,
206
+ kind: head.kind,
207
+ signature: head.signature,
208
+ doc: parsed.body,
209
+ summary: head.summary,
210
+ card_path: cardPath,
211
+ });
212
+ }
213
+ }
214
+
215
+ db.exec("COMMIT");
216
+ } catch (e) {
217
+ db.exec("ROLLBACK");
218
+ throw e;
219
+ } finally {
220
+ db.close();
221
+ }
222
+ }
223
+
224
+ // ── Internal helpers ───────────────────────────────────────────
225
+
226
+ function parseDependenciesFromPkg(
227
+ pkgJson: Record<string, any>,
228
+ includeDev: boolean,
229
+ ): string[] {
230
+ const deps: string[] = [];
231
+ if (pkgJson.dependencies) {
232
+ deps.push(...Object.keys(pkgJson.dependencies));
233
+ }
234
+ if (includeDev && pkgJson.devDependencies) {
235
+ deps.push(...Object.keys(pkgJson.devDependencies));
236
+ }
237
+ return deps;
238
+ }
239
+
240
+ /** Check if a library+version is already in the DB (idempotent guard). */
241
+ function isVersionIndexed(db: any, libName: string, version: string): boolean {
242
+ try {
243
+ const row = db.prepare(
244
+ "SELECT 1 FROM libraries WHERE name = ? AND version = ?",
245
+ ).get(libName, version);
246
+ return !!row;
247
+ } catch {
248
+ return false;
249
+ }
250
+ }
251
+
252
+ /** Remove old card dir and DB rows for a library when version changes. */
253
+ function pruneOldVersion(
254
+ db: any,
255
+ libsDir: string,
256
+ libName: string,
257
+ newVersion: string,
258
+ ): void {
259
+ // Find any existing card dir for this lib with a different version
260
+ if (!fs.existsSync(libsDir)) return;
261
+
262
+ for (const dir of fs.readdirSync(libsDir, { withFileTypes: true })) {
263
+ if (!dir.isDirectory()) continue;
264
+ const prefix = `${libName}@`;
265
+ if (dir.name.startsWith(prefix) && dir.name !== `${libName}@${newVersion}`) {
266
+ // Remove old card dir
267
+ const oldDir = path.join(libsDir, dir.name);
268
+ try {
269
+ fs.rmSync(oldDir, { recursive: true, force: true });
270
+ } catch { /* best effort */ }
271
+ }
272
+ }
273
+
274
+ // Delete old DB rows for this lib (all versions — we'll re-insert with new version)
275
+ try {
276
+ deleteLibrary(db, libName);
277
+ } catch { /* best effort */ }
278
+ }
279
+
280
+ function sanitizeName(name: string): string {
281
+ return name.replace(/[^a-zA-Z0-9_$]/g, "_");
282
+ }
283
+
284
+ function readFirstLines(filePath: string, n: number): string {
285
+ try {
286
+ const content = fs.readFileSync(filePath, "utf-8");
287
+ return content.split("\n").slice(0, n).join("\n").trim();
288
+ } catch {
289
+ return "";
290
+ }
291
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Tests for libs/resolve.ts — library dependency discovery.
3
+ *
4
+ * Covers:
5
+ * - PURE parseDependencies (deps / deps+devDeps)
6
+ * - PURE resolveTypesEntry (types → typings → index.d.ts → main)
7
+ * - Integration locateLibrary over a fixture node_modules
8
+ */
9
+
10
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
11
+ import * as fs from "node:fs";
12
+ import * as path from "node:path";
13
+ import * as os from "node:os";
14
+ import { parseDependencies, resolveTypesEntry, locateLibrary } from "./resolve.ts";
15
+
16
+ // ── PURE: parseDependencies ────────────────────────────────────
17
+ describe("parseDependencies", () => {
18
+ it("returns names from `dependencies`", () => {
19
+ const pkg = { dependencies: { express: "^4.0.0", lodash: "^4.17.0" } };
20
+ const result = parseDependencies(pkg);
21
+ expect(result).toEqual(["express", "lodash"]);
22
+ });
23
+
24
+ it("returns empty array when no dependencies", () => {
25
+ expect(parseDependencies({})).toEqual([]);
26
+ });
27
+
28
+ it("ignores devDependencies by default", () => {
29
+ const pkg = {
30
+ dependencies: { express: "^4.0.0" },
31
+ devDependencies: { vitest: "^1.0.0" },
32
+ };
33
+ expect(parseDependencies(pkg)).toEqual(["express"]);
34
+ });
35
+
36
+ it("includes devDependencies when includeDev=true", () => {
37
+ const pkg = {
38
+ dependencies: { express: "^4.0.0" },
39
+ devDependencies: { vitest: "^1.0.0", typescript: "^5.0.0" },
40
+ };
41
+ const result = parseDependencies(pkg, true);
42
+ expect(result).toContain("express");
43
+ expect(result).toContain("vitest");
44
+ expect(result).toContain("typescript");
45
+ expect(result).toHaveLength(3);
46
+ });
47
+
48
+ it("ignores peerDependencies and optionalDependencies", () => {
49
+ const pkg = {
50
+ dependencies: { express: "^4.0.0" },
51
+ peerDependencies: { react: "^18.0.0" },
52
+ optionalDependencies: { fsevents: "^2.0.0" },
53
+ };
54
+ expect(parseDependencies(pkg)).toEqual(["express"]);
55
+ });
56
+
57
+ it("returns empty array for null/undefined input", () => {
58
+ expect(parseDependencies(null as any)).toEqual([]);
59
+ expect(parseDependencies(undefined as any)).toEqual([]);
60
+ });
61
+ });
62
+
63
+ // ── PURE: resolveTypesEntry ────────────────────────────────────
64
+ describe("resolveTypesEntry", () => {
65
+ it("prefers `types` field", () => {
66
+ const pkg = { types: "dist/index.d.ts", typings: "lib/index.d.ts" };
67
+ expect(resolveTypesEntry(pkg)).toBe("dist/index.d.ts");
68
+ });
69
+
70
+ it("falls back to `typings` field", () => {
71
+ const pkg = { typings: "lib/index.d.ts" };
72
+ expect(resolveTypesEntry(pkg)).toBe("lib/index.d.ts");
73
+ });
74
+
75
+ it("falls back to `index.d.ts`", () => {
76
+ const pkg = {};
77
+ expect(resolveTypesEntry(pkg)).toBe("index.d.ts");
78
+ });
79
+
80
+ it("derives from `main` by swapping .js for .d.ts", () => {
81
+ const pkg = { main: "dist/main.js" };
82
+ expect(resolveTypesEntry(pkg)).toBe("dist/main.d.ts");
83
+ });
84
+
85
+ it("handles main with no extension by appending .d.ts", () => {
86
+ const pkg = { main: "dist/main" };
87
+ expect(resolveTypesEntry(pkg)).toBe("dist/main.d.ts");
88
+ });
89
+
90
+ it("returns index.d.ts when main is not present", () => {
91
+ const pkg = {};
92
+ expect(resolveTypesEntry(pkg)).toBe("index.d.ts");
93
+ });
94
+ });
95
+
96
+ // ── Integration: locateLibrary ─────────────────────────────────
97
+ describe("locateLibrary", () => {
98
+ let tmpDir: string;
99
+ let projectRoot: string;
100
+ let nodeModulesDir: string;
101
+
102
+ beforeEach(() => {
103
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "cw-resolve-"));
104
+ projectRoot = path.join(tmpDir, "my-project");
105
+ nodeModulesDir = path.join(projectRoot, "node_modules");
106
+ fs.mkdirSync(nodeModulesDir, { recursive: true });
107
+ });
108
+
109
+ afterEach(() => {
110
+ fs.rmSync(tmpDir, { recursive: true, force: true });
111
+ });
112
+
113
+ it("returns version, dtsPath, and readmePath for a typed package", () => {
114
+ // Create a typed package
115
+ const pkgDir = path.join(nodeModulesDir, "typed-pkg");
116
+ fs.mkdirSync(path.join(pkgDir, "dist"), { recursive: true });
117
+
118
+ fs.writeFileSync(
119
+ path.join(pkgDir, "package.json"),
120
+ JSON.stringify({
121
+ name: "typed-pkg",
122
+ version: "2.1.0",
123
+ types: "dist/index.d.ts",
124
+ main: "dist/index.js",
125
+ }),
126
+ );
127
+
128
+ fs.writeFileSync(
129
+ path.join(pkgDir, "dist", "index.d.ts"),
130
+ "export declare function hello(): void;\n",
131
+ );
132
+
133
+ fs.writeFileSync(
134
+ path.join(pkgDir, "README.md"),
135
+ "# typed-pkg\nA typed package.\n",
136
+ );
137
+
138
+ const result = locateLibrary(projectRoot, "typed-pkg");
139
+ expect(result).not.toBeNull();
140
+ expect(result!.version).toBe("2.1.0");
141
+ expect(result!.dtsPath).toBe(path.join(pkgDir, "dist", "index.d.ts"));
142
+ expect(result!.readmePath).toBe(path.join(pkgDir, "README.md"));
143
+ });
144
+
145
+ it("returns null for a non-existent package", () => {
146
+ const result = locateLibrary(projectRoot, "non-existent-pkg");
147
+ expect(result).toBeNull();
148
+ });
149
+
150
+ it("returns null when node_modules does not exist", () => {
151
+ const noNodeModules = path.join(tmpDir, "empty-project");
152
+ fs.mkdirSync(noNodeModules);
153
+ const result = locateLibrary(noNodeModules, "anything");
154
+ expect(result).toBeNull();
155
+ });
156
+
157
+ it("returns dtsPath=null for a package with no .d.ts file", () => {
158
+ // JS-only package, no types
159
+ const pkgDir = path.join(nodeModulesDir, "js-only");
160
+ fs.mkdirSync(pkgDir);
161
+
162
+ fs.writeFileSync(
163
+ path.join(pkgDir, "package.json"),
164
+ JSON.stringify({
165
+ name: "js-only",
166
+ version: "0.5.0",
167
+ main: "index.js",
168
+ }),
169
+ );
170
+
171
+ // Create the JS file but no .d.ts
172
+ fs.writeFileSync(path.join(pkgDir, "index.js"), "module.exports = {};\n");
173
+
174
+ // Also no README
175
+ const result = locateLibrary(projectRoot, "js-only");
176
+ expect(result).not.toBeNull();
177
+ expect(result!.version).toBe("0.5.0");
178
+ expect(result!.dtsPath).toBeNull();
179
+ expect(result!.readmePath).toBeNull();
180
+ });
181
+
182
+ it("finds README.md case-insensitively (README.md or readme.md)", () => {
183
+ const pkgDir = path.join(nodeModulesDir, "readme-case");
184
+ fs.mkdirSync(path.join(pkgDir, "dist"), { recursive: true });
185
+
186
+ fs.writeFileSync(
187
+ path.join(pkgDir, "package.json"),
188
+ JSON.stringify({ name: "readme-case", version: "1.0.0" }),
189
+ );
190
+
191
+ // Only readme.md (lowercase)
192
+ fs.writeFileSync(path.join(pkgDir, "readme.md"), "# Lowercase readme\n");
193
+
194
+ // Need index.d.ts so it doesn't return null
195
+ fs.writeFileSync(path.join(pkgDir, "index.d.ts"), "export const foo: number;\n");
196
+
197
+ const result = locateLibrary(projectRoot, "readme-case");
198
+ expect(result).not.toBeNull();
199
+ expect(result!.readmePath).toBe(path.join(pkgDir, "readme.md"));
200
+ });
201
+
202
+ it("resolves package.json even without types field (uses index.d.ts fallback)", () => {
203
+ const pkgDir = path.join(nodeModulesDir, "no-types-field");
204
+ fs.mkdirSync(pkgDir);
205
+
206
+ fs.writeFileSync(
207
+ path.join(pkgDir, "package.json"),
208
+ JSON.stringify({ name: "no-types-field", version: "3.0.0" }),
209
+ );
210
+
211
+ fs.writeFileSync(path.join(pkgDir, "index.d.ts"), "export const bar: boolean;\n");
212
+
213
+ const result = locateLibrary(projectRoot, "no-types-field");
214
+ expect(result).not.toBeNull();
215
+ expect(result!.version).toBe("3.0.0");
216
+ expect(result!.dtsPath).toBe(path.join(pkgDir, "index.d.ts"));
217
+ });
218
+ });
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Library dependency discovery for codewalker v1.2.
3
+ *
4
+ * - `parseDependencies(pkgJson, includeDev?)`: PURE — extract dep names from package.json.
5
+ * - `resolveTypesEntry(pkgJson)`: PURE — find the .d.ts entry point.
6
+ * - `locateLibrary(projectRoot, name)`: integration — read the installed package info.
7
+ */
8
+
9
+ import * as fs from "node:fs";
10
+ import * as path from "node:path";
11
+
12
+ export interface LocateResult {
13
+ version: string;
14
+ dtsPath: string | null;
15
+ readmePath: string | null;
16
+ }
17
+
18
+ /**
19
+ * Extract dependency names from a package.json object.
20
+ * By default returns only `dependencies`; set `includeDev=true` to add `devDependencies`.
21
+ * Ignores peerDependencies and optionalDependencies.
22
+ * PURE — no I/O.
23
+ */
24
+ export function parseDependencies(
25
+ pkgJson: Record<string, any> | null | undefined,
26
+ includeDev = false,
27
+ ): string[] {
28
+ if (!pkgJson) return [];
29
+
30
+ const deps: string[] = [];
31
+
32
+ if (pkgJson.dependencies) {
33
+ deps.push(...Object.keys(pkgJson.dependencies));
34
+ }
35
+
36
+ if (includeDev && pkgJson.devDependencies) {
37
+ deps.push(...Object.keys(pkgJson.devDependencies));
38
+ }
39
+
40
+ return deps;
41
+ }
42
+
43
+ /**
44
+ * Resolve the `.d.ts` entry point for a package.
45
+ * Priority: `types` → `typings` → `index.d.ts` → derive from `main` (swap .js for .d.ts).
46
+ * Returns a relative path string.
47
+ * PURE — no I/O.
48
+ */
49
+ export function resolveTypesEntry(
50
+ pkgJson: Record<string, any> | null | undefined,
51
+ ): string {
52
+ if (!pkgJson) return "index.d.ts";
53
+
54
+ if (pkgJson.types) return pkgJson.types;
55
+ if (pkgJson.typings) return pkgJson.typings;
56
+
57
+ // Derive from `main` if present
58
+ if (pkgJson.main) {
59
+ const main = pkgJson.main as string;
60
+ // Swap .js|.mjs|.cjs endings for .d.ts; otherwise append .d.ts
61
+ if (/\.(js|mjs|cjs)$/.test(main)) {
62
+ return main.replace(/\.(js|mjs|cjs)$/, ".d.ts");
63
+ }
64
+ return main + ".d.ts";
65
+ }
66
+
67
+ return "index.d.ts";
68
+ }
69
+
70
+ /**
71
+ * Locate an installed library in `node_modules/<name>`.
72
+ * Returns null if the package or its directory does not exist.
73
+ * Integration — reads the filesystem.
74
+ */
75
+ export function locateLibrary(
76
+ projectRoot: string,
77
+ name: string,
78
+ ): LocateResult | null {
79
+ const nmDir = path.join(projectRoot, "node_modules");
80
+ if (!fs.existsSync(nmDir)) return null;
81
+
82
+ const pkgDir = path.join(nmDir, name);
83
+ const pkgJsonPath = path.join(pkgDir, "package.json");
84
+
85
+ if (!fs.existsSync(pkgJsonPath)) return null;
86
+
87
+ let pkgJson: Record<string, any>;
88
+ try {
89
+ pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
90
+ } catch {
91
+ return null;
92
+ }
93
+
94
+ const version = pkgJson.version ?? "unknown";
95
+
96
+ // Resolve .d.ts path
97
+ const typesRel = resolveTypesEntry(pkgJson);
98
+ let dtsPath: string | null = path.join(pkgDir, typesRel);
99
+ if (!fs.existsSync(dtsPath)) {
100
+ // Try common alternative locations
101
+ const altDts = path.join(pkgDir, "index.d.ts");
102
+ if (fs.existsSync(altDts)) {
103
+ dtsPath = altDts;
104
+ } else {
105
+ dtsPath = null;
106
+ }
107
+ }
108
+
109
+ // Find README (case-insensitive)
110
+ let readmePath: string | null = null;
111
+ for (const name of ["README.md", "readme.md", "Readme.md"]) {
112
+ const candidate = path.join(pkgDir, name);
113
+ if (fs.existsSync(candidate)) {
114
+ readmePath = candidate;
115
+ break;
116
+ }
117
+ }
118
+
119
+ return { version, dtsPath, readmePath };
120
+ }
package/src/project.ts CHANGED
@@ -30,6 +30,7 @@ export interface ProjectPaths {
30
30
  metaFile: string;
31
31
  entriesDir: string;
32
32
  symbolsDir: string;
33
+ libsDir: string;
33
34
  }
34
35
 
35
36
  function piHome(): string {
@@ -123,6 +124,7 @@ function pathsForId(id: string, root: string, configDir: string, markerPath: str
123
124
  metaFile: path.join(globalDir, "codewalker", "meta.json"),
124
125
  entriesDir: path.join(globalDir, "codewalker", "entries"),
125
126
  symbolsDir: path.join(globalDir, "codewalker", "entries", "symbols"),
127
+ libsDir: path.join(globalDir, "codewalker", "entries", "libs"),
126
128
  };
127
129
  }
128
130
 
@@ -164,7 +166,7 @@ export async function ensureProject(cwd: string): Promise<ProjectPaths> {
164
166
  const p = resolveProject(cwd);
165
167
  const nowISO = new Date().toISOString();
166
168
 
167
- for (const dir of [p.codewalkerDir, p.entriesDir, p.symbolsDir]) {
169
+ for (const dir of [p.codewalkerDir, p.entriesDir, p.symbolsDir, p.libsDir]) {
168
170
  fs.mkdirSync(dir, { recursive: true });
169
171
  }
170
172