@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.
- package/.claude/settings.json +9 -0
- package/.claude/settings.local.json +7 -0
- package/AGENTS.md +113 -0
- package/CLAUDE.md +79 -0
- package/README.md +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +192 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/parsers/claude-code.d.ts +3 -0
- package/dist/cli/parsers/claude-code.d.ts.map +1 -0
- package/dist/cli/parsers/claude-code.js +75 -0
- package/dist/cli/parsers/claude-code.js.map +1 -0
- package/dist/cli/parsers/codex.d.ts +3 -0
- package/dist/cli/parsers/codex.d.ts.map +1 -0
- package/dist/cli/parsers/codex.js +42 -0
- package/dist/cli/parsers/codex.js.map +1 -0
- package/dist/cli/parsers/conductor.d.ts +3 -0
- package/dist/cli/parsers/conductor.d.ts.map +1 -0
- package/dist/cli/parsers/conductor.js +43 -0
- package/dist/cli/parsers/conductor.js.map +1 -0
- package/dist/cli/parsers/cursor.d.ts +3 -0
- package/dist/cli/parsers/cursor.d.ts.map +1 -0
- package/dist/cli/parsers/cursor.js +50 -0
- package/dist/cli/parsers/cursor.js.map +1 -0
- package/dist/cli/parsers/index.d.ts +12 -0
- package/dist/cli/parsers/index.d.ts.map +1 -0
- package/dist/cli/parsers/index.js +27 -0
- package/dist/cli/parsers/index.js.map +1 -0
- package/dist/cli/parsers/opencode.d.ts +3 -0
- package/dist/cli/parsers/opencode.d.ts.map +1 -0
- package/dist/cli/parsers/opencode.js +72 -0
- package/dist/cli/parsers/opencode.js.map +1 -0
- package/dist/cli/parsers/parsers.test.d.ts +2 -0
- package/dist/cli/parsers/parsers.test.d.ts.map +1 -0
- package/dist/cli/parsers/parsers.test.js +151 -0
- package/dist/cli/parsers/parsers.test.js.map +1 -0
- package/dist/cli/parsers/pi.d.ts +3 -0
- package/dist/cli/parsers/pi.d.ts.map +1 -0
- package/dist/cli/parsers/pi.js +43 -0
- package/dist/cli/parsers/pi.js.map +1 -0
- package/dist/cli/parsers/types.d.ts +25 -0
- package/dist/cli/parsers/types.d.ts.map +1 -0
- package/dist/cli/parsers/types.js +2 -0
- package/dist/cli/parsers/types.js.map +1 -0
- package/dist/cli/parsers/vscode-copilot.d.ts +3 -0
- package/dist/cli/parsers/vscode-copilot.d.ts.map +1 -0
- package/dist/cli/parsers/vscode-copilot.js +69 -0
- package/dist/cli/parsers/vscode-copilot.js.map +1 -0
- package/dist/cli/parsers/zed.d.ts +3 -0
- package/dist/cli/parsers/zed.d.ts.map +1 -0
- package/dist/cli/parsers/zed.js +43 -0
- package/dist/cli/parsers/zed.js.map +1 -0
- package/dist/cli/sync.d.ts +21 -0
- package/dist/cli/sync.d.ts.map +1 -0
- package/dist/cli/sync.js +78 -0
- package/dist/cli/sync.js.map +1 -0
- package/dist/cli/type-extractor.d.ts +25 -0
- package/dist/cli/type-extractor.d.ts.map +1 -0
- package/dist/cli/type-extractor.js +254 -0
- package/dist/cli/type-extractor.js.map +1 -0
- package/dist/cli/type-extractor.test.d.ts +2 -0
- package/dist/cli/type-extractor.test.d.ts.map +1 -0
- package/dist/cli/type-extractor.test.js +173 -0
- package/dist/cli/type-extractor.test.js.map +1 -0
- package/dist/client/http.d.ts +44 -0
- package/dist/client/http.d.ts.map +1 -0
- package/dist/client/http.js +311 -0
- package/dist/client/http.js.map +1 -0
- package/dist/client/index.d.ts +158 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +256 -0
- package/dist/client/index.js.map +1 -0
- package/dist/component/_generated/api.d.ts +12 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +13 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +18 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +42 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +39 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/actions.d.ts +42 -0
- package/dist/component/actions.d.ts.map +1 -0
- package/dist/component/actions.js +405 -0
- package/dist/component/actions.js.map +1 -0
- package/dist/component/apiKeyMutations.d.ts +29 -0
- package/dist/component/apiKeyMutations.d.ts.map +1 -0
- package/dist/component/apiKeyMutations.js +149 -0
- package/dist/component/apiKeyMutations.js.map +1 -0
- package/dist/component/apiKeyQueries.d.ts +37 -0
- package/dist/component/apiKeyQueries.d.ts.map +1 -0
- package/dist/component/apiKeyQueries.js +127 -0
- package/dist/component/apiKeyQueries.js.map +1 -0
- package/dist/component/checksum.d.ts +6 -0
- package/dist/component/checksum.d.ts.map +1 -0
- package/dist/component/checksum.js +14 -0
- package/dist/component/checksum.js.map +1 -0
- package/dist/component/checksum.test.d.ts +2 -0
- package/dist/component/checksum.test.d.ts.map +1 -0
- package/dist/component/checksum.test.js +27 -0
- package/dist/component/checksum.test.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +4 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/cronActions.d.ts +3 -0
- package/dist/component/cronActions.d.ts.map +1 -0
- package/dist/component/cronActions.js +38 -0
- package/dist/component/cronActions.js.map +1 -0
- package/dist/component/cronQueries.d.ts +6 -0
- package/dist/component/cronQueries.d.ts.map +1 -0
- package/dist/component/cronQueries.js +38 -0
- package/dist/component/cronQueries.js.map +1 -0
- package/dist/component/crons.d.ts +3 -0
- package/dist/component/crons.d.ts.map +1 -0
- package/dist/component/crons.js +18 -0
- package/dist/component/crons.js.map +1 -0
- package/dist/component/format.d.ts +11 -0
- package/dist/component/format.d.ts.map +1 -0
- package/dist/component/format.js +175 -0
- package/dist/component/format.js.map +1 -0
- package/dist/component/format.test.d.ts +2 -0
- package/dist/component/format.test.d.ts.map +1 -0
- package/dist/component/format.test.js +118 -0
- package/dist/component/format.test.js.map +1 -0
- package/dist/component/mutations.d.ts +158 -0
- package/dist/component/mutations.d.ts.map +1 -0
- package/dist/component/mutations.js +745 -0
- package/dist/component/mutations.js.map +1 -0
- package/dist/component/queries.d.ts +94 -0
- package/dist/component/queries.d.ts.map +1 -0
- package/dist/component/queries.js +574 -0
- package/dist/component/queries.js.map +1 -0
- package/dist/component/schema.d.ts +278 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +161 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/mcp/server.d.ts +11 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +571 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/shared.d.ts +126 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +67 -0
- package/dist/shared.js.map +1 -0
- package/dist/test.d.ts +23 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +21 -0
- package/dist/test.js.map +1 -0
- package/eslint.config.js +15 -0
- package/example/convex/convex.config.ts +7 -0
- package/example/convex/memory.ts +129 -0
- package/llms.md +175 -0
- package/llms.txt +126 -0
- package/package.json +72 -0
- package/prds/API-REFERENCE.md +935 -0
- package/prds/README.md +988 -0
- package/prds/SETUP.md +682 -0
- package/src/cli/index.ts +254 -0
- package/src/cli/parsers/claude-code.ts +80 -0
- package/src/cli/parsers/codex.ts +45 -0
- package/src/cli/parsers/conductor.ts +47 -0
- package/src/cli/parsers/cursor.ts +55 -0
- package/src/cli/parsers/index.ts +30 -0
- package/src/cli/parsers/opencode.ts +84 -0
- package/src/cli/parsers/parsers.test.ts +201 -0
- package/src/cli/parsers/pi.ts +47 -0
- package/src/cli/parsers/types.ts +26 -0
- package/src/cli/parsers/vscode-copilot.ts +78 -0
- package/src/cli/parsers/zed.ts +47 -0
- package/src/cli/sync.ts +110 -0
- package/src/cli/type-extractor.test.ts +241 -0
- package/src/cli/type-extractor.ts +331 -0
- package/src/client/http.ts +415 -0
- package/src/client/index.ts +519 -0
- package/src/component/_generated/api.ts +14 -0
- package/src/component/_generated/dataModel.ts +20 -0
- package/src/component/_generated/server.ts +64 -0
- package/src/component/actions.ts +558 -0
- package/src/component/apiKeyMutations.ts +175 -0
- package/src/component/apiKeyQueries.ts +156 -0
- package/src/component/checksum.test.ts +31 -0
- package/src/component/checksum.ts +13 -0
- package/src/component/convex.config.ts +5 -0
- package/src/component/cronActions.ts +52 -0
- package/src/component/cronQueries.ts +42 -0
- package/src/component/crons.ts +34 -0
- package/src/component/format.test.ts +133 -0
- package/src/component/format.ts +232 -0
- package/src/component/mutations.ts +824 -0
- package/src/component/queries.ts +684 -0
- package/src/component/schema.ts +207 -0
- package/src/mcp/server.ts +695 -0
- package/src/shared.ts +251 -0
- package/src/test.ts +32 -0
- package/tsconfig.json +21 -0
- 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
|
+
}
|