@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/src/query.test.ts CHANGED
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
2
  import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
4
  import * as os from 'node:os';
5
- import { openDb, upsertSymbol, setMeta, getMeta, upsertLibSymbol, searchLibSymbols } from './db.ts';
5
+ import { openDb, upsertSymbol, setMeta, getMeta, upsertLibSymbol, upsertNote, upsertFinding, searchLibSymbols } from './db.ts';
6
6
  import { runQuery } from './query.ts';
7
7
 
8
8
  describe('query.ts', () => {
@@ -166,4 +166,155 @@ describe('query.ts', () => {
166
166
  const result = runQuery(dbPath, { query: '', source: 'all', limit: 1 });
167
167
  expect(result.rows).toHaveLength(1);
168
168
  });
169
+
170
+ // ── notes source ───────────────────────────────────────────
171
+ it('source="notes" returns only notes', () => {
172
+ const db = openDb(dbPath);
173
+ upsertSymbol(db, {
174
+ name: 'myFunc', kind: 'function', file_path: 'src/a.ts',
175
+ line_start: 1, line_end: 1, signature: '', doc: '', summary: '', card_path: '',
176
+ });
177
+ upsertNote(db, {
178
+ note_kind: 'glossary', title: 'Glossary Term', body: 'A term', tags: '', related: '', card_path: '',
179
+ });
180
+ db.close();
181
+
182
+ const result = runQuery(dbPath, { query: '', source: 'notes' });
183
+ expect(result.rows).toHaveLength(1);
184
+ expect(result.rows[0]!.name).toBe('Glossary Term');
185
+ expect(result.rows[0]!.source).toBe('note');
186
+ });
187
+
188
+ it('source="all" includes notes interleaved with code and libs', () => {
189
+ const db = openDb(dbPath);
190
+ upsertSymbol(db, {
191
+ name: 'myFunc', kind: 'function', file_path: 'src/a.ts',
192
+ line_start: 1, line_end: 1, signature: '', doc: 'refresh token', summary: '', card_path: '',
193
+ });
194
+ upsertLibSymbol(db, {
195
+ lib: 'hono', version: '4.6.3', name: 'honoFunc',
196
+ kind: 'function', signature: '', doc: 'refresh token', summary: '', card_path: '',
197
+ });
198
+ upsertNote(db, {
199
+ note_kind: 'glossary', title: 'Refresh Token', body: 'A token used to refresh auth without re-login.', tags: 'auth', related: 'myFunc', card_path: '',
200
+ });
201
+ db.close();
202
+
203
+ const result = runQuery(dbPath, { query: 'refresh', source: 'all' });
204
+ expect(result.rows.length).toBeGreaterThanOrEqual(2);
205
+ // Should have note + lib + code (code rows have source undefined)
206
+ const sources = result.rows.map(r => r.source);
207
+ expect(sources).toContain('note');
208
+ expect(sources).toContain('lib');
209
+ expect(sources).toContain(undefined); // code rows have no source
210
+ });
211
+
212
+ it('source kind filter works for notes source', () => {
213
+ const db = openDb(dbPath);
214
+ upsertNote(db, {
215
+ note_kind: 'glossary', title: 'Term', body: 'A glossary term', tags: '', related: '', card_path: '',
216
+ });
217
+ upsertNote(db, {
218
+ note_kind: 'decision', title: 'Decision', body: 'A decision note', tags: '', related: '', card_path: '',
219
+ });
220
+ db.close();
221
+
222
+ const result = runQuery(dbPath, { query: '', source: 'notes', kind: 'glossary' });
223
+ expect(result.rows).toHaveLength(1);
224
+ expect(result.rows[0]!.name).toBe('Term');
225
+ });
226
+
227
+ it('source=all with note in query works (note matches alongside code)', () => {
228
+ const db = openDb(dbPath);
229
+ upsertSymbol(db, {
230
+ name: 'myFunc', kind: 'function', file_path: 'src/payments.ts',
231
+ line_start: 1, line_end: 10, signature: '', doc: 'processes charges', summary: '', card_path: '',
232
+ });
233
+ upsertNote(db, {
234
+ note_kind: 'glossary', title: 'charge', body: 'A payment processing concept.',
235
+ tags: 'payments', related: 'myFunc', card_path: '',
236
+ });
237
+ db.close();
238
+
239
+ const result = runQuery(dbPath, { query: 'charge', source: 'all' });
240
+ expect(result.rows.length).toBeGreaterThanOrEqual(1);
241
+ const sources = result.rows.map(r => r.source);
242
+ expect(sources).toContain('note');
243
+ });
244
+
245
+ // ── analysis source ────────────────────────────────────────
246
+ it('source="analysis" returns only findings', () => {
247
+ const db = openDb(dbPath);
248
+ upsertFinding(db, {
249
+ finding_kind: 'debt', title: 'TODO: fix', severity: 'info',
250
+ file_path: 'src/a.ts', line_start: 1, line_end: 1,
251
+ metric: 'TODO', body: 'Fix this', related: '', card_path: '',
252
+ });
253
+ upsertSymbol(db, {
254
+ name: 'myFunc', kind: 'function', file_path: 'src/a.ts',
255
+ line_start: 1, line_end: 1, signature: '', doc: '', summary: '', card_path: '',
256
+ });
257
+ db.close();
258
+
259
+ const result = runQuery(dbPath, { query: '', source: 'analysis' });
260
+ expect(result.rows).toHaveLength(1);
261
+ expect(result.rows[0]!.source).toBe('analysis');
262
+ expect(result.rows[0]!.finding_kind).toBe('debt');
263
+ });
264
+
265
+ it('source="all" includes analysis findings interleaved with code, libs, notes', () => {
266
+ const db = openDb(dbPath);
267
+ upsertSymbol(db, {
268
+ name: 'myFunc', kind: 'function', file_path: 'src/a.ts',
269
+ line_start: 1, line_end: 1, signature: '', doc: '', summary: '', card_path: '',
270
+ });
271
+ upsertFinding(db, {
272
+ finding_kind: 'coverage', title: 'Low coverage: a.ts', severity: 'warn',
273
+ file_path: 'src/a.ts', line_start: 0, line_end: 0,
274
+ metric: '50%', body: 'Half covered', related: '', card_path: '',
275
+ });
276
+ db.close();
277
+
278
+ const result = runQuery(dbPath, { query: '', source: 'all' });
279
+ expect(result.rows.length).toBeGreaterThanOrEqual(2);
280
+ const sources = result.rows.map(r => r.source);
281
+ expect(sources).toContain('analysis');
282
+ });
283
+
284
+ it('source="analysis" with kind filter narrows by finding_kind', () => {
285
+ const db = openDb(dbPath);
286
+ upsertFinding(db, {
287
+ finding_kind: 'coverage', title: 'Coverage gap', severity: 'warn',
288
+ file_path: 'src/a.ts', line_start: 0, line_end: 0,
289
+ metric: '50%', body: '', related: '', card_path: '',
290
+ });
291
+ upsertFinding(db, {
292
+ finding_kind: 'debt', title: 'Debt item', severity: 'high',
293
+ file_path: 'src/b.ts', line_start: 1, line_end: 1,
294
+ metric: 'TODO', body: '', related: '', card_path: '',
295
+ });
296
+ db.close();
297
+
298
+ const result = runQuery(dbPath, { query: '', source: 'analysis', kind: 'coverage' });
299
+ expect(result.rows).toHaveLength(1);
300
+ expect(result.rows[0]!.name).toBe('Coverage gap');
301
+ });
302
+
303
+ it('source="all" with analysis data respects limit', () => {
304
+ const db = openDb(dbPath);
305
+ upsertSymbol(db, {
306
+ name: 'myFunc', kind: 'function', file_path: 'src/a.ts',
307
+ line_start: 1, line_end: 1, signature: '', doc: '', summary: '', card_path: '',
308
+ });
309
+ upsertFinding(db, {
310
+ finding_kind: 'debt', title: 'Debt', severity: 'info',
311
+ file_path: 'src/a.ts', line_start: 1, line_end: 1,
312
+ metric: 'TODO', body: '', related: '', card_path: '',
313
+ });
314
+ db.close();
315
+
316
+ const result = runQuery(dbPath, { query: '', source: 'all', limit: 1 });
317
+ expect(result.rows).toHaveLength(1);
318
+ });
319
+
169
320
  });
package/src/query.ts CHANGED
@@ -2,16 +2,16 @@
2
2
  * Query orchestration: wraps DB search with staleness detection.
3
3
  */
4
4
 
5
- import type { QueryResult, QueryResultRow, StalenessInfo } from "./types.ts";
6
- import { openDb, searchSymbols, searchLibSymbols, getMeta } from "./db.ts";
5
+ import type { QueryResult, QueryResultRow, StalenessInfo, NoteKind, FindingKind } from "./types.ts";
6
+ import { openDb, searchSymbols, searchLibSymbols, searchNotes, searchFindings, getMeta } from "./db.ts";
7
7
  import { getHeadSha, changedFilesSince } from "./git.ts";
8
8
 
9
9
  export interface QueryParams {
10
10
  query: string;
11
11
  kind?: string;
12
12
  limit?: number;
13
- /** Source scope: "code" (default, only code symbols), "libs" (only lib symbols), or "all" (both). */
14
- source?: "code" | "libs" | "all";
13
+ /** Source scope: "code" (default, only code symbols), "libs" (only lib symbols), "notes" (only notes), "analysis" (only findings), or "all" (all four). */
14
+ source?: "code" | "libs" | "notes" | "analysis" | "all";
15
15
  }
16
16
 
17
17
  /**
@@ -36,13 +36,22 @@ export function runQuery(
36
36
 
37
37
  if (source === "libs") {
38
38
  rows = searchLibSymbols(db, params.query, params.kind, limit) as unknown as QueryResultRow[];
39
+ } else if (source === "notes") {
40
+ const noteRows = searchNotes(db, params.query, params.kind as NoteKind | undefined, limit);
41
+ rows = noteRows as unknown as QueryResultRow[];
42
+ } else if (source === "analysis") {
43
+ const kindFilter = params.kind && ["coverage", "debt", "practice"].includes(params.kind) ? params.kind : undefined;
44
+ const analysisRows = searchFindings(db, params.query, kindFilter, limit) as unknown as QueryResultRow[];
45
+ rows = analysisRows as unknown as QueryResultRow[];
39
46
  } else if (source === "all") {
40
- // Run both code and lib searches, merge, sort by score, apply limit
47
+ // Run code + lib + note + analysis searches, merge, sort by score, apply limit
41
48
  const codeRows = searchSymbols(db, params.query, params.kind, limit);
42
49
  const libRows = searchLibSymbols(db, params.query, params.kind, limit) as unknown as QueryResultRow[];
50
+ const noteRows = searchNotes(db, params.query, params.kind as NoteKind | undefined, limit * 2) as unknown as QueryResultRow[];
51
+ const analysisRows = searchFindings(db, params.query, undefined, limit * 2) as unknown as QueryResultRow[];
43
52
 
44
53
  // Merge and sort by score ascending (lower bm25 = better match)
45
- const merged: QueryResultRow[] = [...codeRows, ...libRows];
54
+ const merged: QueryResultRow[] = [...codeRows, ...libRows, ...noteRows, ...analysisRows];
46
55
  merged.sort((a, b) => a.score - b.score);
47
56
  rows = merged.slice(0, limit);
48
57
  } else {
package/src/types.ts CHANGED
@@ -64,6 +64,44 @@ export interface CardHead {
64
64
  summary: string;
65
65
  }
66
66
 
67
+ /** Note kind discriminator for bridge cards. */
68
+ export type NoteKind = "glossary" | "decision" | "convention";
69
+
70
+ /** A glossary/decision note (bridge card) for conceptual knowledge. */
71
+ export interface Note {
72
+ note_kind: NoteKind;
73
+ title: string;
74
+ body: string;
75
+ tags: string;
76
+ related: string;
77
+ card_path: string;
78
+ }
79
+
80
+ /** Analysis finding kind discriminator. */
81
+ export type FindingKind = "coverage" | "debt" | "practice";
82
+
83
+ /** A single analysis finding (coverage gap, technical debt, or best-practice finding). */
84
+ export interface Finding {
85
+ finding_kind: FindingKind;
86
+ title: string;
87
+ severity?: "info" | "warn" | "high";
88
+ file_path?: string;
89
+ line_start?: number;
90
+ line_end?: number;
91
+ metric?: string;
92
+ body?: string;
93
+ related?: string;
94
+ card_path?: string;
95
+ }
96
+
97
+ /** Per-file coverage data from lcov or coverage-final.json. */
98
+ export interface FileCoverage {
99
+ file: string;
100
+ lines_total: number;
101
+ lines_covered: number;
102
+ pct: number;
103
+ }
104
+
67
105
  /** A single row returned from a query. */
68
106
  export interface QueryResultRow {
69
107
  name: string;
@@ -75,10 +113,16 @@ export interface QueryResultRow {
75
113
  summary: string;
76
114
  score: number;
77
115
  id: number;
78
- /** Origin fields — code rows omit these; lib rows set them. */
79
- source?: "code" | "lib";
116
+ /** Origin fields — code rows omit these; lib / note / analysis rows set them. */
117
+ source?: "code" | "lib" | "note" | "analysis";
118
+ finding_kind?: FindingKind;
119
+ severity?: string;
120
+ metric?: string;
80
121
  lib?: string;
81
122
  version?: string;
123
+ /** Note-specific fields — only for source === "note" rows. */
124
+ note_kind?: NoteKind;
125
+ tags?: string;
82
126
  }
83
127
 
84
128
  /** The full result of a query, including staleness info. */