feat(tag-vocab): #3 태그 vocab — prompt + telemetry (v0.2.3 6/7) #18
@@ -40,6 +40,8 @@ export interface ImportNoteResult {
|
||||
status: ImportNoteStatus;
|
||||
}
|
||||
|
||||
const KEBAB_CASE_RE = /^[a-z0-9-]+$/;
|
||||
|
||||
export class NoteRepository {
|
||||
constructor(private db: Database.Database) {}
|
||||
|
||||
@@ -219,11 +221,15 @@ export class NoteRepository {
|
||||
* v0.2.3 #3 — AI prompt 의 vocabulary 후보. 사용 빈도 높은 태그 top-N.
|
||||
* source 무시 (AI+user 통합), kebab-case 통과한 것만 (한글/공백/대문자 제외).
|
||||
* deleted_at IS NULL 만 (휴지통 노트 태그 제외).
|
||||
*
|
||||
* Note: LIMIT 가 SQL 단계에서 먼저 적용된 후 regex 필터링이 후처리 됨.
|
||||
* 따라서 반환 배열 length 가 limit 보다 작을 수 있음 (top-N 안에 비-kebab-case
|
||||
* 태그가 섞여 있을 때). v0.2.3 dogfood 규모에서는 실용적 영향 없음.
|
||||
*/
|
||||
getTopUsedTags(limit = 20): string[] {
|
||||
const rows = this.db
|
||||
.prepare(
|
||||
`SELECT t.name, COUNT(*) c
|
||||
`SELECT t.name, COUNT(*) AS c
|
||||
FROM tags t
|
||||
JOIN note_tags nt ON nt.tag_id = t.id
|
||||
JOIN notes n ON n.id = nt.note_id
|
||||
@@ -235,7 +241,7 @@ export class NoteRepository {
|
||||
.all(limit) as Array<{ name: string; c: number }>;
|
||||
return rows
|
||||
.map((r) => r.name)
|
||||
.filter((n) => /^[a-z0-9-]+$/.test(n));
|
||||
.filter((n) => KEBAB_CASE_RE.test(n));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -681,11 +681,17 @@ describe('NoteRepository — failed retry helpers', () => {
|
||||
it('getTopUsedTags counts AI + user sources together', () => {
|
||||
const a = repo.create({ rawText: 'a' }).id;
|
||||
const b = repo.create({ rawText: 'b' }).id;
|
||||
const c = repo.create({ rawText: 'c' }).id;
|
||||
// design: 1 AI (a) + 1 user (b) = 2 total; meeting: 1 AI (c) = 1 total
|
||||
// → design must rank first (proves source merging, not AI-only count)
|
||||
// Note: updateUserAiFields REPLACES tags (DELETE+reinsert), so each note
|
||||
// gets exactly the tags passed in the call.
|
||||
repo.updateAiResult(a, { title: 't', summary: 'x\ny\nz', tags: ['design'], provider: 'p' });
|
||||
repo.updateUserAiFields(b, { tags: ['design'] });
|
||||
// design count 는 AI 1 + user 1 = 2
|
||||
repo.updateAiResult(c, { title: 't', summary: 'x\ny\nz', tags: ['meeting'], provider: 'p' });
|
||||
const top = repo.getTopUsedTags();
|
||||
expect(top).toContain('design');
|
||||
expect(top[0]).toBe('design'); // 2 (AI+user) > 1 (AI only)
|
||||
expect(top.indexOf('meeting')).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('getTopUsedTags excludes tags from deleted notes', () => {
|
||||
|
||||
Reference in New Issue
Block a user