@aprimediet/codewalker 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -5
- package/package.json +1 -1
- package/prompts/codewalker.md +8 -1
- package/skills/codewalker/SKILL.md +165 -28
- package/src/analyze/analyzer.test.ts +214 -0
- package/src/analyze/analyzer.ts +290 -0
- package/src/analyze/cards.test.ts +156 -0
- package/src/analyze/cards.ts +110 -0
- package/src/analyze/coverage.test.ts +158 -0
- package/src/analyze/coverage.ts +98 -0
- package/src/analyze/debt.test.ts +111 -0
- package/src/analyze/debt.ts +180 -0
- package/src/analyze/review.test.ts +127 -0
- package/src/analyze/review.ts +127 -0
- package/src/cards.test.ts +123 -1
- package/src/cards.ts +53 -0
- package/src/db.test.ts +484 -8
- package/src/db.ts +398 -2
- package/src/enrich.test.ts +102 -0
- package/src/enrich.ts +107 -0
- package/src/format.test.ts +148 -0
- package/src/format.ts +13 -0
- package/src/index.contract.test.ts +62 -0
- package/src/index.ts +427 -9
- package/src/indexer.heal.test.ts +90 -0
- package/src/indexer.ts +9 -1
- package/src/notes-cards.test.ts +99 -0
- package/src/notes-cards.ts +92 -0
- package/src/notes.test.ts +172 -0
- package/src/notes.ts +151 -0
- package/src/project.test.ts +21 -1
- package/src/project.ts +9 -1
- package/src/query.test.ts +152 -1
- package/src/query.ts +15 -6
- package/src/types.ts +46 -2
package/src/format.test.ts
CHANGED
|
@@ -115,6 +115,154 @@ describe('formatCompact with lib rows', () => {
|
|
|
115
115
|
});
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
describe('formatCompact with note rows', () => {
|
|
119
|
+
it('renders a glossary note row with [glossary] prefix', () => {
|
|
120
|
+
const rows: QueryResultRow[] = [{
|
|
121
|
+
id: 1, name: 'Idempotency Key', kind: 'glossary',
|
|
122
|
+
file_path: '', line_start: 0, line_end: 0,
|
|
123
|
+
signature: '', summary: 'A client-supplied key that makes retries safe.',
|
|
124
|
+
score: 0.5, source: 'note', note_kind: 'glossary', tags: 'api',
|
|
125
|
+
}];
|
|
126
|
+
const result = formatCompact(rows, null);
|
|
127
|
+
expect(result).toContain('Idempotency Key');
|
|
128
|
+
expect(result).toContain('glossary');
|
|
129
|
+
expect(result).toContain('[glossary]');
|
|
130
|
+
expect(result).toContain('A client-supplied key that makes retries safe.');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('renders a decision note row with [decision] prefix', () => {
|
|
134
|
+
const rows: QueryResultRow[] = [{
|
|
135
|
+
id: 1, name: 'Use SQLite', kind: 'decision',
|
|
136
|
+
file_path: '', line_start: 0, line_end: 0,
|
|
137
|
+
signature: '', summary: 'Chosen for zero infra.',
|
|
138
|
+
score: 0.5, source: 'note', note_kind: 'decision', tags: '',
|
|
139
|
+
}];
|
|
140
|
+
const result = formatCompact(rows, null);
|
|
141
|
+
expect(result).toContain('Use SQLite');
|
|
142
|
+
expect(result).toContain('decision');
|
|
143
|
+
expect(result).toContain('[decision]');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('renders mixed code + lib + note rows all on separate lines', () => {
|
|
147
|
+
const codeRow: QueryResultRow = {
|
|
148
|
+
id: 1, name: 'myFunc', kind: 'function',
|
|
149
|
+
file_path: 'src/util/helper.ts', line_start: 10, line_end: 20,
|
|
150
|
+
signature: '', summary: 'Does something', score: 0.5,
|
|
151
|
+
};
|
|
152
|
+
const libRow = makeLibRow();
|
|
153
|
+
const noteRow: QueryResultRow = {
|
|
154
|
+
id: 1, name: 'Retry Key', kind: 'glossary',
|
|
155
|
+
file_path: '', line_start: 0, line_end: 0,
|
|
156
|
+
signature: '', summary: 'Key for idempotent retries.',
|
|
157
|
+
score: 0.5, source: 'note', note_kind: 'glossary', tags: '',
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const result = formatCompact([codeRow, libRow, noteRow], null);
|
|
161
|
+
expect(result).toContain('helper.ts:10-20');
|
|
162
|
+
expect(result).toContain('[hono@4.6.3]');
|
|
163
|
+
expect(result).toContain('[glossary]');
|
|
164
|
+
expect(result).toContain('Retry Key');
|
|
165
|
+
expect(result.split('\n')).toHaveLength(3);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('formatCompact with analysis rows', () => {
|
|
170
|
+
it('renders a coverage finding row with [coverage] prefix and severity', () => {
|
|
171
|
+
const rows: QueryResultRow[] = [{
|
|
172
|
+
id: 1, name: 'Low coverage: src/auth/token.ts', kind: 'coverage',
|
|
173
|
+
file_path: 'src/auth/token.ts', line_start: 0, line_end: 0,
|
|
174
|
+
signature: '', summary: 'Token path is under-tested.',
|
|
175
|
+
score: 0.5, source: 'analysis', finding_kind: 'coverage',
|
|
176
|
+
severity: 'warn', metric: '38% (24/63 lines)',
|
|
177
|
+
}];
|
|
178
|
+
const result = formatCompact(rows, null);
|
|
179
|
+
expect(result).toContain('Low coverage: src/auth/token.ts');
|
|
180
|
+
expect(result).toContain('[coverage]');
|
|
181
|
+
expect(result).toContain('warn');
|
|
182
|
+
expect(result).toContain('38%');
|
|
183
|
+
expect(result).toContain('token.ts');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('renders a debt finding row with [debt] prefix', () => {
|
|
187
|
+
const rows: QueryResultRow[] = [{
|
|
188
|
+
id: 1, name: 'TODO: fix this', kind: 'debt',
|
|
189
|
+
file_path: 'src/a.ts', line_start: 5, line_end: 5,
|
|
190
|
+
signature: '', summary: 'Need to handle edge case',
|
|
191
|
+
score: 0.5, source: 'analysis', finding_kind: 'debt',
|
|
192
|
+
severity: 'info', metric: 'TODO',
|
|
193
|
+
}];
|
|
194
|
+
const result = formatCompact(rows, null);
|
|
195
|
+
expect(result).toContain('[debt]');
|
|
196
|
+
expect(result).toContain('TODO: fix this');
|
|
197
|
+
expect(result).toContain('info');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('renders a practice finding row with [practice] prefix', () => {
|
|
201
|
+
const rows: QueryResultRow[] = [{
|
|
202
|
+
id: 1, name: 'Missing error handling', kind: 'practice',
|
|
203
|
+
file_path: 'src/api/route.ts', line_start: 15, line_end: 15,
|
|
204
|
+
signature: '', summary: 'No error handling in this function.',
|
|
205
|
+
score: 0.5, source: 'analysis', finding_kind: 'practice',
|
|
206
|
+
severity: 'high', metric: '',
|
|
207
|
+
}];
|
|
208
|
+
const result = formatCompact(rows, null);
|
|
209
|
+
expect(result).toContain('[practice]');
|
|
210
|
+
expect(result).toContain('high');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('renders mixed code + analysis rows', () => {
|
|
214
|
+
const codeRow: QueryResultRow = {
|
|
215
|
+
id: 1, name: 'myFunc', kind: 'function',
|
|
216
|
+
file_path: 'src/util/helper.ts', line_start: 10, line_end: 20,
|
|
217
|
+
signature: '', summary: 'Does something', score: 0.5,
|
|
218
|
+
};
|
|
219
|
+
const analysisRow: QueryResultRow = {
|
|
220
|
+
id: 2, name: 'Low coverage', kind: 'coverage',
|
|
221
|
+
file_path: 'src/util/helper.ts', line_start: 0, line_end: 0,
|
|
222
|
+
signature: '', summary: 'Low coverage.',
|
|
223
|
+
score: 0.3, source: 'analysis', finding_kind: 'coverage',
|
|
224
|
+
severity: 'warn', metric: '30%',
|
|
225
|
+
};
|
|
226
|
+
const result = formatCompact([codeRow, analysisRow], null);
|
|
227
|
+
expect(result).toContain('helper.ts:10-20');
|
|
228
|
+
expect(result).toContain('[coverage]');
|
|
229
|
+
expect(result.split('\n')).toHaveLength(2);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('renders mixed code + lib + note + analysis rows', () => {
|
|
233
|
+
const codeRow: QueryResultRow = {
|
|
234
|
+
id: 1, name: 'myFunc', kind: 'function',
|
|
235
|
+
file_path: 'src/util/helper.ts', line_start: 10, line_end: 20,
|
|
236
|
+
signature: '', summary: 'Does something', score: 0.5,
|
|
237
|
+
};
|
|
238
|
+
const libRow: QueryResultRow = {
|
|
239
|
+
id: 100, name: 'createMiddleware', kind: 'function',
|
|
240
|
+
file_path: 'hono/dist/helper.d.ts', line_start: 0, line_end: 0,
|
|
241
|
+
signature: '', summary: 'Define a typed middleware handler.',
|
|
242
|
+
score: 0.3, source: 'lib', lib: 'hono', version: '4.6.3',
|
|
243
|
+
};
|
|
244
|
+
const noteRow: QueryResultRow = {
|
|
245
|
+
id: 1, name: 'Retry Key', kind: 'glossary',
|
|
246
|
+
file_path: '', line_start: 0, line_end: 0,
|
|
247
|
+
signature: '', summary: 'Key for idempotent retries.',
|
|
248
|
+
score: 0.5, source: 'note', note_kind: 'glossary', tags: '',
|
|
249
|
+
};
|
|
250
|
+
const analysisRow: QueryResultRow = {
|
|
251
|
+
id: 2, name: 'Low coverage', kind: 'coverage',
|
|
252
|
+
file_path: 'src/util/helper.ts', line_start: 0, line_end: 0,
|
|
253
|
+
signature: '', summary: 'Low coverage.',
|
|
254
|
+
score: 0.3, source: 'analysis', finding_kind: 'coverage',
|
|
255
|
+
severity: 'warn', metric: '30%',
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const result = formatCompact([codeRow, libRow, noteRow, analysisRow], null);
|
|
259
|
+
expect(result).toContain('[coverage]');
|
|
260
|
+
expect(result).toContain('[hono@4.6.3]');
|
|
261
|
+
expect(result).toContain('[glossary]');
|
|
262
|
+
expect(result.split('\n')).toHaveLength(4);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
118
266
|
describe('formatCardBody', () => {
|
|
119
267
|
it('returns the card body text', () => {
|
|
120
268
|
const body = '# myFunc\n\nDoes something.\n';
|
package/src/format.ts
CHANGED
|
@@ -27,6 +27,19 @@ export function formatCompact(
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const lines = rows.map((row) => {
|
|
30
|
+
if (row.source === "note" && row.note_kind) {
|
|
31
|
+
const prefix = `[${row.note_kind}]`;
|
|
32
|
+
const summary = truncate(row.summary || "", SUMMARY_MAX);
|
|
33
|
+
return `${row.name} · ${row.note_kind} · ${prefix} · ${summary}`;
|
|
34
|
+
}
|
|
35
|
+
if (row.source === "analysis") {
|
|
36
|
+
const kindTag = `[${row.finding_kind || "finding"}]`;
|
|
37
|
+
const sev = row.severity ? ` · ${row.severity}` : "";
|
|
38
|
+
const metric = row.metric ? ` · ${row.metric}` : "";
|
|
39
|
+
const loc = row.file_path ? ` · ${basename(row.file_path)}` : "";
|
|
40
|
+
const summary = truncate(row.summary || row.name || "", SUMMARY_MAX);
|
|
41
|
+
return `${row.name}${loc}${sev}${metric} · ${kindTag} · ${summary}`;
|
|
42
|
+
}
|
|
30
43
|
if (row.source === "lib" && row.lib && row.version) {
|
|
31
44
|
const origin = `[${row.lib}@${row.version}]`;
|
|
32
45
|
const summary = truncate(row.summary || "", SUMMARY_MAX);
|
|
@@ -58,6 +58,35 @@ describe('index.ts contract', () => {
|
|
|
58
58
|
expect(queryTool).toBeDefined();
|
|
59
59
|
expect(queryTool!.description).toContain('code index');
|
|
60
60
|
|
|
61
|
+
// Check new v1.3 tools
|
|
62
|
+
const enrichTool = stub.tools.find(t => t.name === 'codewalker_enrich');
|
|
63
|
+
expect(enrichTool).toBeDefined();
|
|
64
|
+
expect(enrichTool!.description).toContain('summary');
|
|
65
|
+
|
|
66
|
+
const noteTool = stub.tools.find(t => t.name === 'codewalker_note');
|
|
67
|
+
expect(noteTool).toBeDefined();
|
|
68
|
+
expect(noteTool!.description).toContain('glossary');
|
|
69
|
+
|
|
70
|
+
// Check enrich tool parameters
|
|
71
|
+
const enrichParams = (enrichTool!.parameters as any);
|
|
72
|
+
expect(enrichParams.properties).toHaveProperty('card');
|
|
73
|
+
expect(enrichParams.properties).toHaveProperty('summary');
|
|
74
|
+
|
|
75
|
+
// Check note tool parameters
|
|
76
|
+
const noteParams = (noteTool!.parameters as any);
|
|
77
|
+
expect(noteParams.properties).toHaveProperty('type');
|
|
78
|
+
expect(noteParams.properties).toHaveProperty('title');
|
|
79
|
+
expect(noteParams.properties).toHaveProperty('body');
|
|
80
|
+
|
|
81
|
+
// Check v1.4 tools
|
|
82
|
+
const findingTool = stub.tools.find(t => t.name === 'codewalker_finding');
|
|
83
|
+
expect(findingTool).toBeDefined();
|
|
84
|
+
expect(findingTool!.description).toContain('finding');
|
|
85
|
+
const findingParams = (findingTool!.parameters as any);
|
|
86
|
+
expect(findingParams.properties).toHaveProperty('kind');
|
|
87
|
+
expect(findingParams.properties).toHaveProperty('title');
|
|
88
|
+
expect(findingParams.properties).toHaveProperty('body');
|
|
89
|
+
|
|
61
90
|
// Check tool has a source parameter
|
|
62
91
|
const toolParams = (queryTool!.parameters as any);
|
|
63
92
|
expect(toolParams.properties).toHaveProperty('source');
|
|
@@ -142,4 +171,37 @@ describe('index.ts contract', () => {
|
|
|
142
171
|
expect(cmd.description).toContain('libs [--dev]');
|
|
143
172
|
expect(cmd.description).toContain('lib <pkg>');
|
|
144
173
|
});
|
|
174
|
+
|
|
175
|
+
it('command description includes enrich, glossary, decisions subcommands', async () => {
|
|
176
|
+
const mod = await import('./index.ts');
|
|
177
|
+
const stub = createPiStub();
|
|
178
|
+
mod.default(stub.api as any);
|
|
179
|
+
|
|
180
|
+
const cmd = stub.commands.find(c => c.name === 'codewalker')!;
|
|
181
|
+
expect(cmd.description).toContain('enrich');
|
|
182
|
+
expect(cmd.description).toContain('glossary');
|
|
183
|
+
expect(cmd.description).toContain('decisions');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('command description includes v1.4 subcommands (analyze, review, findings, conventions)', async () => {
|
|
187
|
+
const mod = await import('./index.ts');
|
|
188
|
+
const stub = createPiStub();
|
|
189
|
+
mod.default(stub.api as any);
|
|
190
|
+
|
|
191
|
+
const cmd = stub.commands.find(c => c.name === 'codewalker')!;
|
|
192
|
+
expect(cmd.description).toContain('analyze');
|
|
193
|
+
expect(cmd.description).toContain('review');
|
|
194
|
+
expect(cmd.description).toContain('findings');
|
|
195
|
+
expect(cmd.description).toContain('conventions');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('codewalker_note tool accepts type="convention"', async () => {
|
|
199
|
+
const mod = await import('./index.ts');
|
|
200
|
+
const stub = createPiStub();
|
|
201
|
+
mod.default(stub.api as any);
|
|
202
|
+
|
|
203
|
+
const noteTool = stub.tools.find(t => t.name === 'codewalker_note')!;
|
|
204
|
+
const noteParams = (noteTool!.parameters as any);
|
|
205
|
+
expect(noteParams.properties.type.description).toContain('convention');
|
|
206
|
+
});
|
|
145
207
|
});
|