@waynesutton/agent-memory 0.0.1

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 (201) hide show
  1. package/.claude/settings.json +9 -0
  2. package/.claude/settings.local.json +7 -0
  3. package/AGENTS.md +113 -0
  4. package/CLAUDE.md +79 -0
  5. package/README.md +1 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +192 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/parsers/claude-code.d.ts +3 -0
  11. package/dist/cli/parsers/claude-code.d.ts.map +1 -0
  12. package/dist/cli/parsers/claude-code.js +75 -0
  13. package/dist/cli/parsers/claude-code.js.map +1 -0
  14. package/dist/cli/parsers/codex.d.ts +3 -0
  15. package/dist/cli/parsers/codex.d.ts.map +1 -0
  16. package/dist/cli/parsers/codex.js +42 -0
  17. package/dist/cli/parsers/codex.js.map +1 -0
  18. package/dist/cli/parsers/conductor.d.ts +3 -0
  19. package/dist/cli/parsers/conductor.d.ts.map +1 -0
  20. package/dist/cli/parsers/conductor.js +43 -0
  21. package/dist/cli/parsers/conductor.js.map +1 -0
  22. package/dist/cli/parsers/cursor.d.ts +3 -0
  23. package/dist/cli/parsers/cursor.d.ts.map +1 -0
  24. package/dist/cli/parsers/cursor.js +50 -0
  25. package/dist/cli/parsers/cursor.js.map +1 -0
  26. package/dist/cli/parsers/index.d.ts +12 -0
  27. package/dist/cli/parsers/index.d.ts.map +1 -0
  28. package/dist/cli/parsers/index.js +27 -0
  29. package/dist/cli/parsers/index.js.map +1 -0
  30. package/dist/cli/parsers/opencode.d.ts +3 -0
  31. package/dist/cli/parsers/opencode.d.ts.map +1 -0
  32. package/dist/cli/parsers/opencode.js +72 -0
  33. package/dist/cli/parsers/opencode.js.map +1 -0
  34. package/dist/cli/parsers/parsers.test.d.ts +2 -0
  35. package/dist/cli/parsers/parsers.test.d.ts.map +1 -0
  36. package/dist/cli/parsers/parsers.test.js +151 -0
  37. package/dist/cli/parsers/parsers.test.js.map +1 -0
  38. package/dist/cli/parsers/pi.d.ts +3 -0
  39. package/dist/cli/parsers/pi.d.ts.map +1 -0
  40. package/dist/cli/parsers/pi.js +43 -0
  41. package/dist/cli/parsers/pi.js.map +1 -0
  42. package/dist/cli/parsers/types.d.ts +25 -0
  43. package/dist/cli/parsers/types.d.ts.map +1 -0
  44. package/dist/cli/parsers/types.js +2 -0
  45. package/dist/cli/parsers/types.js.map +1 -0
  46. package/dist/cli/parsers/vscode-copilot.d.ts +3 -0
  47. package/dist/cli/parsers/vscode-copilot.d.ts.map +1 -0
  48. package/dist/cli/parsers/vscode-copilot.js +69 -0
  49. package/dist/cli/parsers/vscode-copilot.js.map +1 -0
  50. package/dist/cli/parsers/zed.d.ts +3 -0
  51. package/dist/cli/parsers/zed.d.ts.map +1 -0
  52. package/dist/cli/parsers/zed.js +43 -0
  53. package/dist/cli/parsers/zed.js.map +1 -0
  54. package/dist/cli/sync.d.ts +21 -0
  55. package/dist/cli/sync.d.ts.map +1 -0
  56. package/dist/cli/sync.js +78 -0
  57. package/dist/cli/sync.js.map +1 -0
  58. package/dist/cli/type-extractor.d.ts +25 -0
  59. package/dist/cli/type-extractor.d.ts.map +1 -0
  60. package/dist/cli/type-extractor.js +254 -0
  61. package/dist/cli/type-extractor.js.map +1 -0
  62. package/dist/cli/type-extractor.test.d.ts +2 -0
  63. package/dist/cli/type-extractor.test.d.ts.map +1 -0
  64. package/dist/cli/type-extractor.test.js +173 -0
  65. package/dist/cli/type-extractor.test.js.map +1 -0
  66. package/dist/client/http.d.ts +44 -0
  67. package/dist/client/http.d.ts.map +1 -0
  68. package/dist/client/http.js +311 -0
  69. package/dist/client/http.js.map +1 -0
  70. package/dist/client/index.d.ts +158 -0
  71. package/dist/client/index.d.ts.map +1 -0
  72. package/dist/client/index.js +256 -0
  73. package/dist/client/index.js.map +1 -0
  74. package/dist/component/_generated/api.d.ts +12 -0
  75. package/dist/component/_generated/api.d.ts.map +1 -0
  76. package/dist/component/_generated/api.js +13 -0
  77. package/dist/component/_generated/api.js.map +1 -0
  78. package/dist/component/_generated/dataModel.d.ts +18 -0
  79. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  80. package/dist/component/_generated/dataModel.js +11 -0
  81. package/dist/component/_generated/dataModel.js.map +1 -0
  82. package/dist/component/_generated/server.d.ts +42 -0
  83. package/dist/component/_generated/server.d.ts.map +1 -0
  84. package/dist/component/_generated/server.js +39 -0
  85. package/dist/component/_generated/server.js.map +1 -0
  86. package/dist/component/actions.d.ts +42 -0
  87. package/dist/component/actions.d.ts.map +1 -0
  88. package/dist/component/actions.js +405 -0
  89. package/dist/component/actions.js.map +1 -0
  90. package/dist/component/apiKeyMutations.d.ts +29 -0
  91. package/dist/component/apiKeyMutations.d.ts.map +1 -0
  92. package/dist/component/apiKeyMutations.js +149 -0
  93. package/dist/component/apiKeyMutations.js.map +1 -0
  94. package/dist/component/apiKeyQueries.d.ts +37 -0
  95. package/dist/component/apiKeyQueries.d.ts.map +1 -0
  96. package/dist/component/apiKeyQueries.js +127 -0
  97. package/dist/component/apiKeyQueries.js.map +1 -0
  98. package/dist/component/checksum.d.ts +6 -0
  99. package/dist/component/checksum.d.ts.map +1 -0
  100. package/dist/component/checksum.js +14 -0
  101. package/dist/component/checksum.js.map +1 -0
  102. package/dist/component/checksum.test.d.ts +2 -0
  103. package/dist/component/checksum.test.d.ts.map +1 -0
  104. package/dist/component/checksum.test.js +27 -0
  105. package/dist/component/checksum.test.js.map +1 -0
  106. package/dist/component/convex.config.d.ts +3 -0
  107. package/dist/component/convex.config.d.ts.map +1 -0
  108. package/dist/component/convex.config.js +4 -0
  109. package/dist/component/convex.config.js.map +1 -0
  110. package/dist/component/cronActions.d.ts +3 -0
  111. package/dist/component/cronActions.d.ts.map +1 -0
  112. package/dist/component/cronActions.js +38 -0
  113. package/dist/component/cronActions.js.map +1 -0
  114. package/dist/component/cronQueries.d.ts +6 -0
  115. package/dist/component/cronQueries.d.ts.map +1 -0
  116. package/dist/component/cronQueries.js +38 -0
  117. package/dist/component/cronQueries.js.map +1 -0
  118. package/dist/component/crons.d.ts +3 -0
  119. package/dist/component/crons.d.ts.map +1 -0
  120. package/dist/component/crons.js +18 -0
  121. package/dist/component/crons.js.map +1 -0
  122. package/dist/component/format.d.ts +11 -0
  123. package/dist/component/format.d.ts.map +1 -0
  124. package/dist/component/format.js +175 -0
  125. package/dist/component/format.js.map +1 -0
  126. package/dist/component/format.test.d.ts +2 -0
  127. package/dist/component/format.test.d.ts.map +1 -0
  128. package/dist/component/format.test.js +118 -0
  129. package/dist/component/format.test.js.map +1 -0
  130. package/dist/component/mutations.d.ts +158 -0
  131. package/dist/component/mutations.d.ts.map +1 -0
  132. package/dist/component/mutations.js +745 -0
  133. package/dist/component/mutations.js.map +1 -0
  134. package/dist/component/queries.d.ts +94 -0
  135. package/dist/component/queries.d.ts.map +1 -0
  136. package/dist/component/queries.js +574 -0
  137. package/dist/component/queries.js.map +1 -0
  138. package/dist/component/schema.d.ts +278 -0
  139. package/dist/component/schema.d.ts.map +1 -0
  140. package/dist/component/schema.js +161 -0
  141. package/dist/component/schema.js.map +1 -0
  142. package/dist/mcp/server.d.ts +11 -0
  143. package/dist/mcp/server.d.ts.map +1 -0
  144. package/dist/mcp/server.js +571 -0
  145. package/dist/mcp/server.js.map +1 -0
  146. package/dist/shared.d.ts +126 -0
  147. package/dist/shared.d.ts.map +1 -0
  148. package/dist/shared.js +67 -0
  149. package/dist/shared.js.map +1 -0
  150. package/dist/test.d.ts +23 -0
  151. package/dist/test.d.ts.map +1 -0
  152. package/dist/test.js +21 -0
  153. package/dist/test.js.map +1 -0
  154. package/eslint.config.js +15 -0
  155. package/example/convex/convex.config.ts +7 -0
  156. package/example/convex/memory.ts +129 -0
  157. package/llms.md +175 -0
  158. package/llms.txt +126 -0
  159. package/package.json +72 -0
  160. package/prds/API-REFERENCE.md +935 -0
  161. package/prds/README.md +988 -0
  162. package/prds/SETUP.md +682 -0
  163. package/src/cli/index.ts +254 -0
  164. package/src/cli/parsers/claude-code.ts +80 -0
  165. package/src/cli/parsers/codex.ts +45 -0
  166. package/src/cli/parsers/conductor.ts +47 -0
  167. package/src/cli/parsers/cursor.ts +55 -0
  168. package/src/cli/parsers/index.ts +30 -0
  169. package/src/cli/parsers/opencode.ts +84 -0
  170. package/src/cli/parsers/parsers.test.ts +201 -0
  171. package/src/cli/parsers/pi.ts +47 -0
  172. package/src/cli/parsers/types.ts +26 -0
  173. package/src/cli/parsers/vscode-copilot.ts +78 -0
  174. package/src/cli/parsers/zed.ts +47 -0
  175. package/src/cli/sync.ts +110 -0
  176. package/src/cli/type-extractor.test.ts +241 -0
  177. package/src/cli/type-extractor.ts +331 -0
  178. package/src/client/http.ts +415 -0
  179. package/src/client/index.ts +519 -0
  180. package/src/component/_generated/api.ts +14 -0
  181. package/src/component/_generated/dataModel.ts +20 -0
  182. package/src/component/_generated/server.ts +64 -0
  183. package/src/component/actions.ts +558 -0
  184. package/src/component/apiKeyMutations.ts +175 -0
  185. package/src/component/apiKeyQueries.ts +156 -0
  186. package/src/component/checksum.test.ts +31 -0
  187. package/src/component/checksum.ts +13 -0
  188. package/src/component/convex.config.ts +5 -0
  189. package/src/component/cronActions.ts +52 -0
  190. package/src/component/cronQueries.ts +42 -0
  191. package/src/component/crons.ts +34 -0
  192. package/src/component/format.test.ts +133 -0
  193. package/src/component/format.ts +232 -0
  194. package/src/component/mutations.ts +824 -0
  195. package/src/component/queries.ts +684 -0
  196. package/src/component/schema.ts +207 -0
  197. package/src/mcp/server.ts +695 -0
  198. package/src/shared.ts +251 -0
  199. package/src/test.ts +32 -0
  200. package/tsconfig.json +21 -0
  201. package/vitest.config.ts +8 -0
@@ -0,0 +1,241 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { extractTypeMemories } from "./type-extractor.js";
6
+
7
+ let tempDir: string;
8
+
9
+ beforeEach(async () => {
10
+ tempDir = await mkdtemp(join(tmpdir(), "agent-memory-types-test-"));
11
+ });
12
+
13
+ afterEach(async () => {
14
+ await rm(tempDir, { recursive: true, force: true });
15
+ });
16
+
17
+ describe("extractTypeMemories", () => {
18
+ it("extracts exported interfaces", async () => {
19
+ await writeFile(
20
+ join(tempDir, "models.ts"),
21
+ `
22
+ export interface User {
23
+ id: string;
24
+ name: string;
25
+ email: string;
26
+ createdAt: number;
27
+ }
28
+
29
+ export interface Post {
30
+ id: string;
31
+ title: string;
32
+ content: string;
33
+ authorId: string;
34
+ }
35
+ `,
36
+ );
37
+
38
+ const result = await extractTypeMemories({
39
+ globPattern: "**/*.ts",
40
+ cwd: tempDir,
41
+ });
42
+
43
+ expect(result.filesProcessed).toBe(1);
44
+ expect(result.memories.length).toBeGreaterThanOrEqual(1);
45
+
46
+ const allContent = result.memories.map((m) => m.content).join("\n");
47
+ expect(allContent).toContain("User");
48
+ expect(allContent).toContain("Post");
49
+
50
+ for (const memory of result.memories) {
51
+ expect(memory.memoryType).toBe("reference");
52
+ expect(memory.scope).toBe("project");
53
+ expect(memory.source).toBe("ingest-types");
54
+ expect(memory.tags).toContain("typescript");
55
+ expect(memory.tags).toContain("types");
56
+ expect(memory.tags).toContain("auto-generated");
57
+ expect(memory.checksum).toBeTruthy();
58
+ expect(memory.title).toMatch(/^types\//);
59
+ }
60
+ });
61
+
62
+ it("extracts exported type aliases", async () => {
63
+ await writeFile(
64
+ join(tempDir, "types.ts"),
65
+ `
66
+ export type Status = "active" | "inactive" | "pending";
67
+
68
+ export type UserId = string;
69
+ `,
70
+ );
71
+
72
+ const result = await extractTypeMemories({
73
+ globPattern: "**/*.ts",
74
+ cwd: tempDir,
75
+ });
76
+
77
+ expect(result.filesProcessed).toBe(1);
78
+ expect(result.memories.length).toBeGreaterThanOrEqual(1);
79
+
80
+ const allContent = result.memories.map((m) => m.content).join("\n");
81
+ expect(allContent).toContain("Status");
82
+ });
83
+
84
+ it("extracts exported functions", async () => {
85
+ await writeFile(
86
+ join(tempDir, "utils.ts"),
87
+ `
88
+ /** Compute hash of a string */
89
+ export function computeHash(input: string): string {
90
+ return input;
91
+ }
92
+
93
+ export const double = (n: number): number => n * 2;
94
+ `,
95
+ );
96
+
97
+ const result = await extractTypeMemories({
98
+ globPattern: "**/*.ts",
99
+ cwd: tempDir,
100
+ });
101
+
102
+ expect(result.filesProcessed).toBe(1);
103
+ const allContent = result.memories.map((m) => m.content).join("\n");
104
+ expect(allContent).toContain("computeHash");
105
+ });
106
+
107
+ it("returns empty for no matching files", async () => {
108
+ const result = await extractTypeMemories({
109
+ globPattern: "**/*.ts",
110
+ cwd: tempDir,
111
+ });
112
+
113
+ expect(result.filesProcessed).toBe(0);
114
+ expect(result.memories).toHaveLength(0);
115
+ });
116
+
117
+ it("returns empty for files with no exports", async () => {
118
+ await writeFile(
119
+ join(tempDir, "internal.ts"),
120
+ `
121
+ const x = 42;
122
+ function helper() { return x; }
123
+ `,
124
+ );
125
+
126
+ const result = await extractTypeMemories({
127
+ globPattern: "**/*.ts",
128
+ cwd: tempDir,
129
+ });
130
+
131
+ expect(result.filesProcessed).toBe(1);
132
+ expect(result.memories).toHaveLength(0);
133
+ });
134
+
135
+ it("handles multiple files", async () => {
136
+ await mkdir(join(tempDir, "src"));
137
+ await writeFile(
138
+ join(tempDir, "src", "auth.ts"),
139
+ `export interface Session { token: string; userId: string; }`,
140
+ );
141
+ await writeFile(
142
+ join(tempDir, "src", "db.ts"),
143
+ `export interface DatabaseConfig { host: string; port: number; }`,
144
+ );
145
+
146
+ const result = await extractTypeMemories({
147
+ globPattern: "src/**/*.ts",
148
+ cwd: tempDir,
149
+ });
150
+
151
+ expect(result.filesProcessed).toBe(2);
152
+ expect(result.memories.length).toBeGreaterThanOrEqual(2);
153
+
154
+ const allContent = result.memories.map((m) => m.content).join("\n");
155
+ expect(allContent).toContain("Session");
156
+ expect(allContent).toContain("DatabaseConfig");
157
+ });
158
+
159
+ it("respects exclude patterns", async () => {
160
+ await mkdir(join(tempDir, "src"));
161
+ await mkdir(join(tempDir, "dist"));
162
+ await writeFile(
163
+ join(tempDir, "src", "api.ts"),
164
+ `export interface Api { endpoint: string; }`,
165
+ );
166
+ await writeFile(
167
+ join(tempDir, "dist", "api.ts"),
168
+ `export interface Api { endpoint: string; }`,
169
+ );
170
+
171
+ const result = await extractTypeMemories({
172
+ globPattern: "**/*.ts",
173
+ cwd: tempDir,
174
+ exclude: ["**/dist/**"],
175
+ });
176
+
177
+ expect(result.filesProcessed).toBe(1);
178
+ });
179
+
180
+ it("applies custom tags", async () => {
181
+ await writeFile(
182
+ join(tempDir, "schema.ts"),
183
+ `export interface Table { id: string; }`,
184
+ );
185
+
186
+ const result = await extractTypeMemories({
187
+ globPattern: "**/*.ts",
188
+ cwd: tempDir,
189
+ tags: ["convex", "schema"],
190
+ });
191
+
192
+ expect(result.memories.length).toBeGreaterThanOrEqual(1);
193
+ for (const memory of result.memories) {
194
+ expect(memory.tags).toContain("convex");
195
+ expect(memory.tags).toContain("schema");
196
+ }
197
+ });
198
+
199
+ it("applies custom priority", async () => {
200
+ await writeFile(
201
+ join(tempDir, "core.ts"),
202
+ `export interface CoreConfig { debug: boolean; }`,
203
+ );
204
+
205
+ const result = await extractTypeMemories({
206
+ globPattern: "**/*.ts",
207
+ cwd: tempDir,
208
+ priority: 0.9,
209
+ });
210
+
211
+ expect(result.memories.length).toBeGreaterThanOrEqual(1);
212
+ for (const memory of result.memories) {
213
+ expect(memory.priority).toBe(0.9);
214
+ }
215
+ });
216
+
217
+ it("extracts JSDoc comments with types", async () => {
218
+ await writeFile(
219
+ join(tempDir, "documented.ts"),
220
+ `
221
+ /** Configuration for the application */
222
+ export interface AppConfig {
223
+ /** The port to listen on */
224
+ port: number;
225
+ /** Whether to enable debug mode */
226
+ debug: boolean;
227
+ }
228
+ `,
229
+ );
230
+
231
+ const result = await extractTypeMemories({
232
+ globPattern: "**/*.ts",
233
+ cwd: tempDir,
234
+ });
235
+
236
+ expect(result.memories.length).toBeGreaterThanOrEqual(1);
237
+ const allContent = result.memories.map((m) => m.content).join("\n");
238
+ expect(allContent).toContain("AppConfig");
239
+ expect(allContent).toContain("Configuration for the application");
240
+ });
241
+ });
@@ -0,0 +1,331 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { basename, relative } from "node:path";
3
+ import { computeChecksum } from "../component/checksum.js";
4
+ import type { ParsedMemory } from "./parsers/types.js";
5
+
6
+
7
+ // ── Types ────────────────────────────────────────────────────────────
8
+
9
+ export interface IngestTypesOptions {
10
+ /** Glob pattern for TypeScript files, e.g. "src/models.ts" */
11
+ globPattern: string;
12
+ /** Working directory for glob resolution */
13
+ cwd?: string;
14
+ /** Additional tags to attach to generated memories */
15
+ tags?: string[];
16
+ /** Priority for generated memories (0-1, default 0.6) */
17
+ priority?: number;
18
+ /** Exclude patterns (defaults to node_modules, dist, _generated) */
19
+ exclude?: string[];
20
+ }
21
+
22
+ export interface IngestTypesResult {
23
+ memories: ParsedMemory[];
24
+ filesProcessed: number;
25
+ }
26
+
27
+ // ── Public API ───────────────────────────────────────────────────────
28
+
29
+ /**
30
+ * Extract TypeScript type definitions from files matching a glob pattern
31
+ * and convert them into ParsedMemory objects suitable for agent-memory.
32
+ *
33
+ * Uses `types-not-docs` under the hood to generate markdown from types.
34
+ */
35
+ export async function extractTypeMemories(
36
+ opts: IngestTypesOptions,
37
+ ): Promise<IngestTypesResult> {
38
+ const cwd = opts.cwd ?? process.cwd();
39
+ const priority = opts.priority ?? 0.6;
40
+ const extraTags = opts.tags ?? [];
41
+ const exclude = opts.exclude ?? [
42
+ "**/node_modules/**",
43
+ "**/dist/**",
44
+ "**/_generated/**",
45
+ "**/*.test.ts",
46
+ "**/*.spec.ts",
47
+ ];
48
+
49
+ // Dynamic import — keeps types-not-docs optional at install time
50
+ const fg = await import("fast-glob");
51
+ const files = await fg.default(opts.globPattern, {
52
+ cwd,
53
+ absolute: true,
54
+ ignore: exclude,
55
+ });
56
+
57
+ if (files.length === 0) {
58
+ return { memories: [], filesProcessed: 0 };
59
+ }
60
+
61
+ // Generate markdown from type definitions
62
+ const markdown = await generateTypeDocumentation(files);
63
+
64
+ if (!markdown.trim()) {
65
+ return { memories: [], filesProcessed: files.length };
66
+ }
67
+
68
+ // Split the generated markdown into individual type memories
69
+ const sections = splitByHeadings(markdown, files, cwd);
70
+
71
+ const memories: ParsedMemory[] = sections.map((section) => ({
72
+ title: `types/${section.title}`,
73
+ content: section.content,
74
+ memoryType: "reference" as const,
75
+ scope: "project" as const,
76
+ tags: ["typescript", "types", "auto-generated", ...extraTags],
77
+ paths: section.sourcePaths,
78
+ priority,
79
+ source: "ingest-types",
80
+ checksum: computeChecksum(section.content),
81
+ }));
82
+
83
+ return { memories, filesProcessed: files.length };
84
+ }
85
+
86
+ // ── Type documentation generation ────────────────────────────────────
87
+
88
+ /**
89
+ * Generate markdown documentation from TypeScript files using types-not-docs.
90
+ * Falls back to a built-in extractor if types-not-docs is not installed.
91
+ */
92
+ async function generateTypeDocumentation(files: string[]): Promise<string> {
93
+ try {
94
+ // Try types-not-docs first (optional dependency)
95
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
96
+ const tnd = await import(/* webpackIgnore: true */ "types-not-docs" as string) as Record<string, unknown>;
97
+ if (typeof tnd.generateDocs === "function") {
98
+ return (tnd.generateDocs as (files: string[]) => string)(files);
99
+ }
100
+ if (typeof tnd.default === "function") {
101
+ return (tnd.default as (files: string[]) => string)(files);
102
+ }
103
+ } catch {
104
+ // types-not-docs not installed — use built-in extractor
105
+ }
106
+
107
+ // Built-in fallback: extract exported types/interfaces/functions from TS files
108
+ return await builtInTypeExtractor(files);
109
+ }
110
+
111
+ /**
112
+ * Built-in TypeScript type extractor that doesn't require external dependencies.
113
+ * Parses exported types, interfaces, type aliases, and function signatures.
114
+ */
115
+ async function builtInTypeExtractor(files: string[]): Promise<string> {
116
+ const sections: string[] = [];
117
+
118
+ for (const filePath of files) {
119
+ const source = await readFile(filePath, "utf-8");
120
+ const extracted = extractExportedTypes(source, filePath);
121
+ if (extracted) {
122
+ sections.push(extracted);
123
+ }
124
+ }
125
+
126
+ return sections.join("\n\n");
127
+ }
128
+
129
+ /**
130
+ * Extract exported type definitions from a single TypeScript file.
131
+ */
132
+ function extractExportedTypes(source: string, filePath: string): string | null {
133
+ const lines = source.split("\n");
134
+ const blocks: string[] = [];
135
+ let currentBlock: string[] = [];
136
+ let braceDepth = 0;
137
+ let inExport = false;
138
+ let currentKind = "";
139
+ let currentName = "";
140
+ let pendingJsDoc: string[] = [];
141
+
142
+ function flushBlock() {
143
+ if (currentName && currentBlock.length > 0) {
144
+ blocks.push(formatBlock(currentKind, currentName, currentBlock));
145
+ }
146
+ inExport = false;
147
+ currentBlock = [];
148
+ braceDepth = 0;
149
+ currentKind = "";
150
+ currentName = "";
151
+ }
152
+
153
+ function countBraces(line: string) {
154
+ for (const ch of line) {
155
+ if (ch === "{") braceDepth++;
156
+ if (ch === "}") braceDepth--;
157
+ }
158
+ }
159
+
160
+ for (let i = 0; i < lines.length; i++) {
161
+ const line = lines[i];
162
+ const trimmed = line.trim();
163
+
164
+ // Collect JSDoc comments (only when not inside an export block)
165
+ if (!inExport && trimmed.startsWith("/**")) {
166
+ pendingJsDoc = [line];
167
+ if (!trimmed.endsWith("*/")) {
168
+ for (let j = i + 1; j < lines.length; j++) {
169
+ pendingJsDoc.push(lines[j]);
170
+ if (lines[j].trim().endsWith("*/")) {
171
+ i = j;
172
+ break;
173
+ }
174
+ }
175
+ }
176
+ continue;
177
+ }
178
+
179
+ // Match exported type definitions
180
+ if (!inExport) {
181
+ const exportMatch = trimmed.match(
182
+ /^export\s+(interface|type|function|const|class|enum|abstract\s+class)\s+(\w+)/,
183
+ );
184
+
185
+ if (exportMatch) {
186
+ currentKind = exportMatch[1];
187
+ currentName = exportMatch[2];
188
+ currentBlock = [...pendingJsDoc, line];
189
+ pendingJsDoc = [];
190
+ braceDepth = 0;
191
+ countBraces(line);
192
+
193
+ // Check if statement is complete on this line
194
+ const isComplete =
195
+ // Single-line with no braces, ending in semicolon (type alias)
196
+ (braceDepth === 0 && !line.includes("{") && trimmed.endsWith(";")) ||
197
+ // Single-line with balanced braces
198
+ (braceDepth === 0 && line.includes("{"));
199
+
200
+ if (isComplete) {
201
+ flushBlock();
202
+ } else {
203
+ inExport = true;
204
+ }
205
+ continue;
206
+ }
207
+
208
+ // No match — clear pending JSDoc
209
+ if (trimmed && !trimmed.startsWith("//") && !trimmed.startsWith("*")) {
210
+ pendingJsDoc = [];
211
+ }
212
+ continue;
213
+ }
214
+
215
+ // Inside a multi-line export — keep collecting
216
+ currentBlock.push(line);
217
+ countBraces(line);
218
+
219
+ if (braceDepth <= 0) {
220
+ flushBlock();
221
+ }
222
+ }
223
+
224
+ // Flush any remaining block
225
+ if (inExport) {
226
+ flushBlock();
227
+ }
228
+
229
+ if (blocks.length === 0) return null;
230
+
231
+ const fileName = basename(filePath);
232
+ return `## ${fileName}\n\n${blocks.join("\n\n")}`;
233
+ }
234
+
235
+ function formatBlock(
236
+ kind: string,
237
+ name: string,
238
+ lines: string[],
239
+ ): string {
240
+ const code = lines.join("\n").trim();
241
+ return `### ${name}\n\n\`${kind}\`\n\n\`\`\`typescript\n${code}\n\`\`\``;
242
+ }
243
+
244
+ // ── Markdown splitting ───────────────────────────────────────────────
245
+
246
+ interface Section {
247
+ title: string;
248
+ content: string;
249
+ sourcePaths: string[];
250
+ }
251
+
252
+ /**
253
+ * Split generated markdown into sections by ## headings.
254
+ * Each ## heading becomes a separate memory entry.
255
+ */
256
+ function splitByHeadings(
257
+ markdown: string,
258
+ sourceFiles: string[],
259
+ cwd: string,
260
+ ): Section[] {
261
+ const lines = markdown.split("\n");
262
+ const sections: Section[] = [];
263
+ let currentTitle = "";
264
+ let currentLines: string[] = [];
265
+
266
+ const relPaths = sourceFiles.map((f) => relative(cwd, f));
267
+
268
+ for (const line of lines) {
269
+ const h2Match = line.match(/^##\s+(.+)$/);
270
+ if (h2Match) {
271
+ // Flush previous section
272
+ if (currentTitle && currentLines.length > 0) {
273
+ const content = currentLines.join("\n").trim();
274
+ if (content) {
275
+ sections.push({
276
+ title: sanitizeTitle(currentTitle),
277
+ content,
278
+ sourcePaths: findMatchingPaths(currentTitle, relPaths),
279
+ });
280
+ }
281
+ }
282
+ currentTitle = h2Match[1];
283
+ currentLines = [];
284
+ } else {
285
+ currentLines.push(line);
286
+ }
287
+ }
288
+
289
+ // Flush last section
290
+ if (currentTitle && currentLines.length > 0) {
291
+ const content = currentLines.join("\n").trim();
292
+ if (content) {
293
+ sections.push({
294
+ title: sanitizeTitle(currentTitle),
295
+ content,
296
+ sourcePaths: findMatchingPaths(currentTitle, relPaths),
297
+ });
298
+ }
299
+ }
300
+
301
+ // Fallback: if no ## headings, treat the whole thing as one section
302
+ if (sections.length === 0 && markdown.trim()) {
303
+ sections.push({
304
+ title: "api-reference",
305
+ content: markdown.trim(),
306
+ sourcePaths: relPaths,
307
+ });
308
+ }
309
+
310
+ return sections;
311
+ }
312
+
313
+ function sanitizeTitle(title: string): string {
314
+ return title
315
+ .toLowerCase()
316
+ .replace(/\.tsx?$/, "")
317
+ .replace(/[^a-z0-9-_./]/g, "-")
318
+ .replace(/-+/g, "-")
319
+ .replace(/^-|-$/g, "");
320
+ }
321
+
322
+ function findMatchingPaths(title: string, relPaths: string[]): string[] {
323
+ // Try to match the heading (usually a filename) back to source paths
324
+ const match = relPaths.filter(
325
+ (p) =>
326
+ p.includes(title) ||
327
+ basename(p, ".ts") === title ||
328
+ basename(p, ".tsx") === title,
329
+ );
330
+ return match.length > 0 ? match : relPaths.slice(0, 1);
331
+ }