@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.
@@ -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
  });