@aprimediet/codewalker 1.0.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.
Files changed (46) hide show
  1. package/README.md +44 -50
  2. package/index.ts +6 -42
  3. package/package.json +20 -39
  4. package/prompts/codewalker.md +7 -0
  5. package/skills/codewalker/SKILL.md +43 -0
  6. package/src/cards.test.ts +88 -0
  7. package/src/cards.ts +87 -0
  8. package/src/db.test.ts +343 -0
  9. package/src/db.ts +363 -0
  10. package/src/extract/ctags-parse.test.ts +108 -0
  11. package/src/extract/ctags-parse.ts +112 -0
  12. package/src/extract/ctags.ts +51 -0
  13. package/src/extract/docs.test.ts +81 -0
  14. package/src/extract/docs.ts +169 -0
  15. package/src/extract/regex.test.ts +202 -0
  16. package/src/extract/regex.ts +192 -0
  17. package/src/format.test.ts +123 -0
  18. package/src/format.ts +69 -0
  19. package/src/git.test.ts +75 -0
  20. package/src/git.ts +62 -0
  21. package/src/index.contract.test.ts +145 -0
  22. package/src/index.ts +173 -0
  23. package/src/indexer.test.ts +138 -0
  24. package/src/indexer.ts +352 -0
  25. package/src/libs/cards.test.ts +86 -0
  26. package/src/libs/cards.ts +53 -0
  27. package/src/libs/dts.test.ts +269 -0
  28. package/src/libs/dts.ts +213 -0
  29. package/src/libs/indexer.test.ts +236 -0
  30. package/src/libs/indexer.ts +291 -0
  31. package/src/libs/resolve.test.ts +218 -0
  32. package/src/libs/resolve.ts +120 -0
  33. package/src/project.test.ts +115 -0
  34. package/src/project.ts +206 -0
  35. package/src/query.test.ts +169 -0
  36. package/src/query.ts +89 -0
  37. package/src/sync.test.ts +116 -0
  38. package/src/types.ts +117 -0
  39. package/vitest.config.ts +28 -0
  40. package/LICENSE +0 -21
  41. package/agents.ts +0 -126
  42. package/compat.ts +0 -217
  43. package/detect.ts +0 -188
  44. package/docs/PRD.md +0 -78
  45. package/prd.ts +0 -106
  46. package/skills/learn-this/SKILL.md +0 -325
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Tests for libs/dts.ts — .d.ts symbol extraction.
3
+ *
4
+ * These are PURE unit tests (fixture strings only, no I/O).
5
+ * Test every export form listed in §10 of the v1.2 plan.
6
+ */
7
+
8
+ import { describe, it, expect } from "vitest";
9
+ import { extractDtsSymbols } from "./dts.ts";
10
+ import type { LibSymbol } from "../types.ts";
11
+
12
+ /**
13
+ * Helper: run extractDtsSymbols and return sorted results for deterministic assertions.
14
+ */
15
+ function extract(source: string, lib = "test-lib", version = "1.0.0"): LibSymbol[] {
16
+ return extractDtsSymbols(source, lib, version).sort((a, b) => a.name.localeCompare(b.name));
17
+ }
18
+
19
+ describe("extractDtsSymbols", () => {
20
+ // ── function forms ────────────────────────────────────────────
21
+ it("extracts `export declare function`", () => {
22
+ const src = `export declare function createMiddleware<E>(handler: Hono): MiddlewareHandler;\n`;
23
+ const result = extract(src);
24
+ expect(result).toHaveLength(1);
25
+ expect(result[0]!.name).toBe("createMiddleware");
26
+ expect(result[0]!.kind).toBe("function");
27
+ expect(result[0]!.signature).toContain("createMiddleware");
28
+ expect(result[0]!.lib).toBe("test-lib");
29
+ expect(result[0]!.version).toBe("1.0.0");
30
+ });
31
+
32
+ it("extracts `export function` (without declare)", () => {
33
+ const src = `export function greet(name: string): string;\n`;
34
+ const result = extract(src);
35
+ expect(result).toHaveLength(1);
36
+ expect(result[0]!.name).toBe("greet");
37
+ expect(result[0]!.kind).toBe("function");
38
+ });
39
+
40
+ it("extracts generic function `export function f<T>(...)`", () => {
41
+ const src = `export function identity<T>(arg: T): T;\n`;
42
+ const result = extract(src);
43
+ expect(result).toHaveLength(1);
44
+ expect(result[0]!.name).toBe("identity");
45
+ expect(result[0]!.kind).toBe("function");
46
+ });
47
+
48
+ // ── const forms ───────────────────────────────────────────────
49
+ it("extracts `export declare const`", () => {
50
+ const src = `export declare const BASE_URL: string;\n`;
51
+ const result = extract(src);
52
+ expect(result).toHaveLength(1);
53
+ expect(result[0]!.name).toBe("BASE_URL");
54
+ expect(result[0]!.kind).toBe("const");
55
+ });
56
+
57
+ it("extracts `export const` (without declare)", () => {
58
+ const src = `export const TIMEOUT_MS: number = 5000;\n`;
59
+ const result = extract(src);
60
+ expect(result).toHaveLength(1);
61
+ expect(result[0]!.name).toBe("TIMEOUT_MS");
62
+ expect(result[0]!.kind).toBe("const");
63
+ });
64
+
65
+ // ── class forms ───────────────────────────────────────────────
66
+ it("extracts `export declare class`", () => {
67
+ const src = `export declare class Router { handle(): void; }\n`;
68
+ const result = extract(src);
69
+ expect(result).toHaveLength(1);
70
+ expect(result[0]!.name).toBe("Router");
71
+ expect(result[0]!.kind).toBe("class");
72
+ expect(result[0]!.signature).toContain("Router");
73
+ });
74
+
75
+ it("extracts `export class` (without declare)", () => {
76
+ const src = `export class Database { constructor(path: string); }\n`;
77
+ const result = extract(src);
78
+ expect(result).toHaveLength(1);
79
+ expect(result[0]!.name).toBe("Database");
80
+ expect(result[0]!.kind).toBe("class");
81
+ });
82
+
83
+ it("extracts `export abstract class`", () => {
84
+ const src = `export abstract class Serializer { abstract serialize(data: unknown): string; }\n`;
85
+ const result = extract(src);
86
+ expect(result).toHaveLength(1);
87
+ expect(result[0]!.name).toBe("Serializer");
88
+ expect(result[0]!.kind).toBe("class");
89
+ });
90
+
91
+ // ── interface ─────────────────────────────────────────────────
92
+ it("extracts `export interface`", () => {
93
+ const src = `export interface User { id: string; name: string; }\n`;
94
+ const result = extract(src);
95
+ expect(result).toHaveLength(1);
96
+ expect(result[0]!.name).toBe("User");
97
+ expect(result[0]!.kind).toBe("interface");
98
+ });
99
+
100
+ // ── type ──────────────────────────────────────────────────────
101
+ it("extracts `export type`", () => {
102
+ const src = `export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };\n`;
103
+ const result = extract(src);
104
+ expect(result).toHaveLength(1);
105
+ expect(result[0]!.name).toBe("JsonValue");
106
+ expect(result[0]!.kind).toBe("type");
107
+ });
108
+
109
+ it("extracts `export type` with generic parameter", () => {
110
+ const src = `export type Result<T> = { ok: true; value: T } | { ok: false; error: string };\n`;
111
+ const result = extract(src);
112
+ expect(result).toHaveLength(1);
113
+ expect(result[0]!.name).toBe("Result");
114
+ expect(result[0]!.kind).toBe("type");
115
+ });
116
+
117
+ // ── enum ──────────────────────────────────────────────────────
118
+ it("extracts `export declare enum`", () => {
119
+ const src = `export declare enum Color { Red, Green, Blue }\n`;
120
+ const result = extract(src);
121
+ expect(result).toHaveLength(1);
122
+ expect(result[0]!.name).toBe("Color");
123
+ expect(result[0]!.kind).toBe("enum");
124
+ });
125
+
126
+ it("extracts `export enum` (without declare)", () => {
127
+ const src = `export enum Direction { Up, Down, Left, Right }\n`;
128
+ const result = extract(src);
129
+ expect(result).toHaveLength(1);
130
+ expect(result[0]!.name).toBe("Direction");
131
+ expect(result[0]!.kind).toBe("enum");
132
+ });
133
+
134
+ // ── namespace ─────────────────────────────────────────────────
135
+ it("extracts `export declare namespace`", () => {
136
+ const src = `export declare namespace Util { function parse(s: string): any; }\n`;
137
+ const result = extract(src);
138
+ expect(result).toHaveLength(1);
139
+ expect(result[0]!.name).toBe("Util");
140
+ expect(result[0]!.kind).toBe("namespace");
141
+ });
142
+
143
+ it("extracts `export namespace` (without declare)", () => {
144
+ const src = `export namespace Config { const port: number; }\n`;
145
+ const result = extract(src);
146
+ expect(result).toHaveLength(1);
147
+ expect(result[0]!.name).toBe("Config");
148
+ expect(result[0]!.kind).toBe("namespace");
149
+ });
150
+
151
+ // ── re-exports ────────────────────────────────────────────────
152
+ it("extracts re-export `export { a, b as c } from \"...\"`", () => {
153
+ const src = `export { parse, stringify as format } from "./serializer";\n`;
154
+ const result = extract(src);
155
+ expect(result).toHaveLength(2);
156
+ expect(result[0]!.name).toBe("format");
157
+ expect(result[0]!.kind).toBe("reexport");
158
+ expect(result[1]!.name).toBe("parse");
159
+ expect(result[1]!.kind).toBe("reexport");
160
+ });
161
+
162
+ it("extracts re-export `export { a, b }` (local module)", () => {
163
+ const src = `export { readFile, writeFile };\n`;
164
+ const result = extract(src);
165
+ expect(result).toHaveLength(2);
166
+ expect(result[0]!.name).toBe("readFile");
167
+ expect(result[0]!.kind).toBe("reexport");
168
+ expect(result[1]!.name).toBe("writeFile");
169
+ expect(result[1]!.kind).toBe("reexport");
170
+ });
171
+
172
+ it("extracts `export * from` as a single re-export entry", () => {
173
+ const src = `export * from "./helpers";\n`;
174
+ const result = extract(src);
175
+ expect(result).toHaveLength(1);
176
+ expect(result[0]!.name).toBe("*");
177
+ expect(result[0]!.kind).toBe("reexport");
178
+ });
179
+
180
+ // ── export default ────────────────────────────────────────────
181
+ it("extracts `export default function`", () => {
182
+ const src = `export default function createApp(opts?: Options): App;\n`;
183
+ const result = extract(src);
184
+ expect(result).toHaveLength(1);
185
+ expect(result[0]!.name).toBe("default");
186
+ expect(result[0]!.kind).toBe("function");
187
+ });
188
+
189
+ it("extracts `export default class`", () => {
190
+ const src = `export default class Logger { log(...args: unknown[]): void; }\n`;
191
+ const result = extract(src);
192
+ expect(result).toHaveLength(1);
193
+ expect(result[0]!.name).toBe("default");
194
+ expect(result[0]!.kind).toBe("class");
195
+ });
196
+
197
+ it("extracts `export default {…}` as const kind", () => {
198
+ const src = `export default { name: "app", version: "1.0" };\n`;
199
+ const result = extract(src);
200
+ expect(result).toHaveLength(1);
201
+ expect(result[0]!.name).toBe("default");
202
+ // Object literal default — kind "const" is a reasonable fallback
203
+ expect(result[0]!.kind).toBe("const");
204
+ });
205
+
206
+ // ── JSDoc / leading comments ──────────────────────────────────
207
+ it("captures leading JSDoc as doc and first line as summary", () => {
208
+ const src = [
209
+ '/** Create a typed middleware handler.',
210
+ ' * Use this to wrap Hono handlers with middleware. */',
211
+ 'export declare function createMiddleware<E>(handler: Hono): MiddlewareHandler;',
212
+ ].join("\n") + "\n";
213
+ const result = extract(src);
214
+ expect(result).toHaveLength(1);
215
+ expect(result[0]!.doc).toContain("Create a typed middleware handler");
216
+ expect(result[0]!.summary).toContain("Create a typed middleware handler");
217
+ });
218
+
219
+ it("sets summary to empty when no JSDoc", () => {
220
+ const src = `export declare function noDoc(): void;\n`;
221
+ const result = extract(src);
222
+ expect(result).toHaveLength(1);
223
+ expect(result[0]!.summary).toBe("");
224
+ });
225
+
226
+ // ── non-exported declarations are ignored ─────────────────────
227
+ it("ignores non-exported declarations", () => {
228
+ const src = `declare function internal(): void;\nfunction privateHelper(): void;\nconst secret = "hidden";\n`;
229
+ const result = extract(src);
230
+ expect(result).toHaveLength(0);
231
+ });
232
+
233
+ // ── multiple symbols in one file ──────────────────────────────
234
+ it("extracts multiple symbols from a .d.ts file", () => {
235
+ const src = [
236
+ `export interface User { id: string; }`,
237
+ `export declare function fetchUser(id: string): Promise<User>;`,
238
+ `export const MAX_RETRIES = 3;`,
239
+ `export enum Status { Active, Inactive }`,
240
+ ].join("\n");
241
+ const result = extract(src);
242
+ expect(result).toHaveLength(4);
243
+ });
244
+
245
+ // ── signature trimming ────────────────────────────────────────
246
+ it("trims trailing `{` from the signature", () => {
247
+ const src = `export interface User { id: string; name: string; }\n`;
248
+ const result = extract(src);
249
+ expect(result[0]!.signature).not.toMatch(/\{/);
250
+ });
251
+
252
+ // ── empty / no exports ────────────────────────────────────────
253
+ it("returns empty array for empty source", () => {
254
+ expect(extract("")).toHaveLength(0);
255
+ });
256
+
257
+ it("returns empty array for source with no export declarations", () => {
258
+ const src = `type Internal = string;\nconst secret = 42;\n`;
259
+ expect(extract(src)).toHaveLength(0);
260
+ });
261
+
262
+ // ── default export with JSDoc ─────────────────────────────────
263
+ it("captures JSDoc for default export", () => {
264
+ const src = `/** Main application factory. */\nexport default function createApp(opts: Options): App;\n`;
265
+ const result = extract(src);
266
+ expect(result).toHaveLength(1);
267
+ expect(result[0]!.doc).toContain("Main application factory");
268
+ });
269
+ });
@@ -0,0 +1,213 @@
1
+ /**
2
+ * .d.ts symbol extraction — regex/line-based, PURE (no I/O).
3
+ *
4
+ * Extracts top-level exported declarations from a `.d.ts` source string.
5
+ * Modeled on src/extract/regex.ts. Returns LibSymbol[] for a given library.
6
+ *
7
+ * Handles these export forms:
8
+ * export declare function|const|class|enum|namespace
9
+ * export function|const|class|interface|type|enum|namespace
10
+ * export abstract class
11
+ * export { a, b as c } from "..." (reexport)
12
+ * export { a, b } (local reexport)
13
+ * export * from "..." (star reexport → name: "*")
14
+ * export default … (name: "default")
15
+ *
16
+ * Non-exported declarations are ignored. Leading JSDoc is captured.
17
+ */
18
+
19
+ import type { LibSymbol, SymbolKind } from "../types.ts";
20
+ import { extractDocComment } from "../extract/docs.ts";
21
+
22
+ /**
23
+ * Extract top-level exported symbols from a .d.ts source string.
24
+ *
25
+ * @param source - Full .d.ts file content.
26
+ * @param lib - Library name.
27
+ * @param version - Installed version.
28
+ * @returns Array of extracted LibSymbol objects.
29
+ */
30
+ export function extractDtsSymbols(
31
+ source: string,
32
+ lib: string,
33
+ version: string,
34
+ ): LibSymbol[] {
35
+ const lines = source.split("\n");
36
+ const symbols: LibSymbol[] = [];
37
+ let inBlockComment = false;
38
+
39
+ for (let i = 0; i < lines.length; i++) {
40
+ const rawLine = lines[i] as string;
41
+ const trimmed = rawLine.trim();
42
+
43
+ // Track block comments
44
+ if (inBlockComment) {
45
+ if (trimmed.includes("*/")) inBlockComment = false;
46
+ continue;
47
+ }
48
+ if (trimmed.startsWith("/*") || trimmed.startsWith("/**")) {
49
+ if (!trimmed.includes("*/")) inBlockComment = true;
50
+ continue;
51
+ }
52
+ if (trimmed.startsWith("//")) continue;
53
+ if (!trimmed) continue;
54
+
55
+ // ── Named exports: export [declare] [abstract] <kind> <name> ──
56
+ const namedExport = tryExtractNamedExport(trimmed, lines, i, lib, version, source);
57
+ if (namedExport) {
58
+ symbols.push(namedExport);
59
+ continue;
60
+ }
61
+
62
+ // ── export default … (name always "default") ──
63
+ const defaultExport = tryExtractDefaultExport(trimmed, lines, i, lib, version, source);
64
+ if (defaultExport) {
65
+ symbols.push(defaultExport);
66
+ continue;
67
+ }
68
+
69
+ // ── Re-exports: export { … } or export * from … ──
70
+ const reExport = tryExtractReexport(trimmed, lines, i, lib, version, source);
71
+ if (reExport) {
72
+ symbols.push(...reExport);
73
+ continue;
74
+ }
75
+ }
76
+
77
+ return symbols;
78
+ }
79
+
80
+ /**
81
+ * Try to extract a named export declaration (function, const, class, interface, type, enum, namespace).
82
+ */
83
+ function tryExtractNamedExport(
84
+ trimmed: string,
85
+ lines: string[],
86
+ i: number,
87
+ lib: string,
88
+ version: string,
89
+ source: string,
90
+ ): LibSymbol | null {
91
+ // Order matters: more specific before less specific.
92
+ // Each pattern: regex with named-kind capture, and a name capture group index.
93
+ const patterns: Array<{ regex: RegExp; kind: SymbolKind; nameIdx: number }> = [
94
+ // function: export [declare] [abstract] [async] function [<T>] name
95
+ { regex: /^export\s+(?:declare\s+)?(?:abstract\s+)?(?:async\s+)?function\s+(?:<[^>]+>\s+)?(\w+)/, kind: "function", nameIdx: 1 },
96
+ // class: export [declare] [abstract] class name
97
+ { regex: /^export\s+(?:declare\s+)?(?:abstract\s+)?class\s+(\w+)/, kind: "class", nameIdx: 1 },
98
+ // interface: export interface name
99
+ { regex: /^export\s+interface\s+(\w+)/, kind: "interface", nameIdx: 1 },
100
+ // type: export type name [<...>] =
101
+ { regex: /^export\s+type\s+(\w+)/, kind: "type", nameIdx: 1 },
102
+ // enum: export [declare] enum name
103
+ { regex: /^export\s+(?:declare\s+)?enum\s+(\w+)/, kind: "enum", nameIdx: 1 },
104
+ // namespace: export [declare] namespace name
105
+ { regex: /^export\s+(?:declare\s+)?namespace\s+(\w+)/, kind: "namespace", nameIdx: 1 },
106
+ // const/let/var: export [declare] const name
107
+ { regex: /^export\s+(?:declare\s+)?(?:const|let|var)\s+(\w+)/, kind: "const", nameIdx: 1 },
108
+ ];
109
+
110
+ for (const p of patterns) {
111
+ const m = trimmed.match(p.regex);
112
+ if (m?.[p.nameIdx]) {
113
+ return makeSymbol(lines, i, p.kind, m[p.nameIdx]!, lib, version, source);
114
+ }
115
+ }
116
+
117
+ return null;
118
+ }
119
+
120
+ /**
121
+ * Try to extract `export default …`.
122
+ * Name is always "default". Kind is determined by what follows:
123
+ * function → "function", class → "class", object/expr → "const".
124
+ */
125
+ function tryExtractDefaultExport(
126
+ trimmed: string,
127
+ lines: string[],
128
+ i: number,
129
+ lib: string,
130
+ version: string,
131
+ source: string,
132
+ ): LibSymbol | null {
133
+ if (!trimmed.startsWith("export default")) return null;
134
+
135
+ let kind: SymbolKind = "const";
136
+
137
+ if (/^export\s+default\s+(?:async\s+)?function\s/.test(trimmed)) {
138
+ kind = "function";
139
+ } else if (/^export\s+default\s+(?:abstract\s+)?class\s/.test(trimmed)) {
140
+ kind = "class";
141
+ } else if (/^export\s+default\s+interface\s/.test(trimmed)) {
142
+ kind = "interface";
143
+ }
144
+
145
+ return makeSymbol(lines, i, kind, "default", lib, version, source);
146
+ }
147
+
148
+ /**
149
+ * Try to extract re-export declarations:
150
+ * export { a, b as c } [from "..."]
151
+ * export * from "..."
152
+ */
153
+ function tryExtractReexport(
154
+ trimmed: string,
155
+ lines: string[],
156
+ i: number,
157
+ lib: string,
158
+ version: string,
159
+ source: string,
160
+ ): LibSymbol[] | null {
161
+ // export { a, b as c } [from "..."]
162
+ const braceReexport = trimmed.match(/^export\s+\{([^}]+)\}(?:\s+from\s+["'][^"']*["'])?\s*;?\s*$/);
163
+ if (braceReexport) {
164
+ const names = braceReexport[1]!.split(",").map(s => s.trim()).filter(Boolean);
165
+ return names.map((entry) => {
166
+ const aliasMatch = entry.match(/^(\w+)\s+as\s+(\w+)$/);
167
+ const name = aliasMatch?.[2] ?? entry;
168
+ return makeSymbol(lines, i, "reexport", name, lib, version, source);
169
+ });
170
+ }
171
+
172
+ // export * from "..."
173
+ if (/^export\s+\*\s+from\s+["'][^"']*["']\s*;?\s*$/.test(trimmed)) {
174
+ return [makeSymbol(lines, i, "reexport", "*", lib, version, source)];
175
+ }
176
+
177
+ return null;
178
+ }
179
+
180
+ /**
181
+ * Build a LibSymbol from a declaration line.
182
+ * Signature is the declaration line with the body (from `{` onward) stripped.
183
+ */
184
+ function makeSymbol(
185
+ lines: string[],
186
+ lineIndex: number,
187
+ kind: SymbolKind,
188
+ name: string,
189
+ lib: string,
190
+ version: string,
191
+ source: string,
192
+ ): LibSymbol {
193
+ const rawLine = (lines[lineIndex] as string).trim();
194
+
195
+ // Signature: first line, strip everything from the first `{` onward
196
+ const sigStart = rawLine.indexOf("{");
197
+ const signature = (sigStart >= 0 ? rawLine.slice(0, sigStart) : rawLine).trim();
198
+
199
+ // Capture JSDoc
200
+ const doc = extractDocComment(source, lineIndex + 1);
201
+ const summary = (doc.split("\n")[0] || "").trim();
202
+
203
+ return {
204
+ lib,
205
+ version,
206
+ name,
207
+ kind,
208
+ signature,
209
+ doc,
210
+ summary,
211
+ card_path: "",
212
+ };
213
+ }