Skip to content

Commit da060ed

Browse files
authored
Merge pull request #12 from FlokeStudio/2.8.5/issue-1-cache-map
fix(2.8.5): read frontmatter tags from CachedMetadata
2 parents 9767a3f + e533082 commit da060ed

2 files changed

Lines changed: 174 additions & 110 deletions

File tree

services/glyph-mi-notes-adapter.js

Lines changed: 118 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,118 @@
1-
const { computeMetadataCached } = require('./metadata');
2-
const { extractiveSummary } = require('./summary');
3-
4-
const GLYPH_MI_NOTES_HINT = {
5-
moduleId: 'notes',
6-
sourceRepo: 'https://github.com/krwg/glyph-mi',
7-
modulePath: 'js/modules/notes/index.js',
8-
exportName: 'analyzeForNotes',
9-
};
10-
11-
let notesVendor = null;
12-
13-
function loadNotesVendor() {
14-
if (notesVendor) return notesVendor;
15-
try {
16-
notesVendor = require('../vendor/glyph-mi-notes.cjs');
17-
return notesVendor;
18-
} catch {
19-
return null;
20-
}
21-
}
22-
23-
function detectNotesModule() {
24-
const mod = loadNotesVendor();
25-
if (mod && typeof mod.analyzeNotesPipeline === 'function') {
26-
return {
27-
available: true,
28-
moduleId: 'notes',
29-
reason: 'glyph-mi notes vendored',
30-
hint: GLYPH_MI_NOTES_HINT,
31-
};
32-
}
33-
return {
34-
available: false,
35-
moduleId: null,
36-
reason: 'glyph-mi notes vendor missing; using local services',
37-
hint: GLYPH_MI_NOTES_HINT,
38-
};
39-
}
40-
41-
function mapVendorToMeta(vendorResult, file) {
42-
const fields = vendorResult.fields || {};
43-
const tagDetails = (fields.tagScores || []).map((row) => ({
44-
tag: row.tag,
45-
relevance: Math.min(1, row.score / 20),
46-
reasons: ['glyph-mi-notes'],
47-
}));
48-
return {
49-
tags: fields.tags || [],
50-
tagDetails,
51-
summary: fields.summary || '',
52-
wordCount: fields.wordCount || 0,
53-
linkCount: fields.linkCount || 0,
54-
title: fields.title || file?.basename || '',
55-
};
56-
}
57-
58-
function analyzeNote(file, body, cache, runtime) {
59-
const bridge = detectNotesModule();
60-
const mod = loadNotesVendor();
61-
62-
if (bridge.available && mod) {
63-
const cacheMeta = file && cache ? cache.get(file.path) : null;
64-
const frontTags = cacheMeta?.frontTags || [];
65-
const vendor = mod.analyzeNotesPipeline({
66-
title: file?.basename?.replace(/\.md$/i, '') || '',
67-
body: body || '',
68-
path: file?.path || '',
69-
frontTags,
70-
headings: mod.extractHeadingsFromBody ? mod.extractHeadingsFromBody(body) : [],
71-
});
72-
const meta = mapVendorToMeta(vendor, file);
73-
return {
74-
...meta,
75-
provider: 'glyph-mi/notes',
76-
hints: { integrated: true, glyphMiNotes: bridge.hint },
77-
confidence: vendor.confidence || {
78-
score: 0,
79-
reasons: [],
80-
},
81-
sources: vendor.sources || ['glyph-mi-notes'],
82-
summaryPreview: vendor.fields?.summary || extractiveSummary(body, meta),
83-
};
84-
}
85-
86-
const meta = computeMetadataCached(file, body, cache, runtime || {});
87-
return {
88-
...meta,
89-
provider: 'glyph-miO/local',
90-
hints: {
91-
fallbackReason: bridge.reason,
92-
glyphMiNotes: bridge.hint,
93-
},
94-
confidence: {
95-
score:
96-
meta.tagDetails && meta.tagDetails[0]
97-
? Math.round(meta.tagDetails[0].relevance * 100)
98-
: 0,
99-
reasons: (meta.tagDetails || []).slice(0, 3).flatMap((d) => d.reasons || []),
100-
},
101-
sources: ['local-metadata'],
102-
summaryPreview: extractiveSummary(body, meta),
103-
};
104-
}
105-
106-
module.exports = {
107-
GLYPH_MI_NOTES_HINT,
108-
detectNotesModule,
109-
analyzeNote,
110-
};
1+
const { computeMetadataCached } = require('./metadata');
2+
const { extractiveSummary } = require('./summary');
3+
4+
const GLYPH_MI_NOTES_HINT = {
5+
moduleId: 'notes',
6+
sourceRepo: 'https://github.com/krwg/glyph-mi',
7+
modulePath: 'js/modules/notes/index.js',
8+
exportName: 'analyzeForNotes',
9+
};
10+
11+
let notesVendor = null;
12+
13+
function loadNotesVendor() {
14+
if (notesVendor) return notesVendor;
15+
try {
16+
notesVendor = require('../vendor/glyph-mi-notes.cjs');
17+
return notesVendor;
18+
} catch {
19+
return null;
20+
}
21+
}
22+
23+
function detectNotesModule() {
24+
const mod = loadNotesVendor();
25+
if (mod && typeof mod.analyzeNotesPipeline === 'function') {
26+
return {
27+
available: true,
28+
moduleId: 'notes',
29+
reason: 'glyph-mi notes vendored',
30+
hint: GLYPH_MI_NOTES_HINT,
31+
};
32+
}
33+
return {
34+
available: false,
35+
moduleId: null,
36+
reason: 'glyph-mi notes vendor missing; using local services',
37+
hint: GLYPH_MI_NOTES_HINT,
38+
};
39+
}
40+
41+
/** Read YAML frontmatter tags from Obsidian CachedMetadata (not a Map). */
42+
function frontTagsFromCache(cache) {
43+
const rawTags = cache?.frontmatter?.tags;
44+
if (Array.isArray(rawTags)) return rawTags.map(String);
45+
if (rawTags != null && rawTags !== '') return [String(rawTags)];
46+
return [];
47+
}
48+
49+
function mapVendorToMeta(vendorResult, file) {
50+
const fields = vendorResult.fields || {};
51+
const tagDetails = (fields.tagScores || []).map((row) => ({
52+
tag: row.tag,
53+
relevance: Math.min(1, row.score / 20),
54+
reasons: ['glyph-mi-notes'],
55+
}));
56+
return {
57+
tags: fields.tags || [],
58+
tagDetails,
59+
summary: fields.summary || '',
60+
wordCount: fields.wordCount || 0,
61+
linkCount: fields.linkCount || 0,
62+
title: fields.title || file?.basename || '',
63+
};
64+
}
65+
66+
function analyzeNote(file, body, cache, runtime) {
67+
const bridge = detectNotesModule();
68+
const mod = loadNotesVendor();
69+
70+
if (bridge.available && mod) {
71+
const frontTags = frontTagsFromCache(cache);
72+
const vendor = mod.analyzeNotesPipeline({
73+
title: file?.basename?.replace(/\.md$/i, '') || '',
74+
body: body || '',
75+
path: file?.path || '',
76+
frontTags,
77+
headings: mod.extractHeadingsFromBody ? mod.extractHeadingsFromBody(body) : [],
78+
});
79+
const meta = mapVendorToMeta(vendor, file);
80+
return {
81+
...meta,
82+
provider: 'glyph-mi/notes',
83+
hints: { integrated: true, glyphMiNotes: bridge.hint },
84+
confidence: vendor.confidence || {
85+
score: 0,
86+
reasons: [],
87+
},
88+
sources: vendor.sources || ['glyph-mi-notes'],
89+
summaryPreview: vendor.fields?.summary || extractiveSummary(body, meta),
90+
};
91+
}
92+
93+
const meta = computeMetadataCached(file, body, cache, runtime || {});
94+
return {
95+
...meta,
96+
provider: 'glyph-miO/local',
97+
hints: {
98+
fallbackReason: bridge.reason,
99+
glyphMiNotes: bridge.hint,
100+
},
101+
confidence: {
102+
score:
103+
meta.tagDetails && meta.tagDetails[0]
104+
? Math.round(meta.tagDetails[0].relevance * 100)
105+
: 0,
106+
reasons: (meta.tagDetails || []).slice(0, 3).flatMap((d) => d.reasons || []),
107+
},
108+
sources: ['local-metadata'],
109+
summaryPreview: extractiveSummary(body, meta),
110+
};
111+
}
112+
113+
module.exports = {
114+
GLYPH_MI_NOTES_HINT,
115+
detectNotesModule,
116+
frontTagsFromCache,
117+
analyzeNote,
118+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { createRequire } from 'node:module';
3+
4+
const require = createRequire(import.meta.url);
5+
const { analyzeNote, frontTagsFromCache } = require('./glyph-mi-notes-adapter.js');
6+
7+
describe('frontTagsFromCache', () => {
8+
it('reads array tags from CachedMetadata frontmatter', () => {
9+
expect(frontTagsFromCache({ frontmatter: { tags: ['alpha', '#beta'] } })).toEqual([
10+
'alpha',
11+
'#beta',
12+
]);
13+
});
14+
15+
it('wraps scalar frontmatter tags', () => {
16+
expect(frontTagsFromCache({ frontmatter: { tags: 'solo' } })).toEqual(['solo']);
17+
});
18+
19+
it('returns empty when cache is missing or has no tags', () => {
20+
expect(frontTagsFromCache(null)).toEqual([]);
21+
expect(frontTagsFromCache({})).toEqual([]);
22+
expect(frontTagsFromCache({ frontmatter: {} })).toEqual([]);
23+
});
24+
25+
it('does not treat cache as a Map', () => {
26+
const cache = { frontmatter: { tags: ['yaml-tag'] } };
27+
expect(() => frontTagsFromCache(cache)).not.toThrow();
28+
expect(typeof cache.get).toBe('undefined');
29+
});
30+
});
31+
32+
describe('analyzeNote vendor path with CachedMetadata', () => {
33+
it('passes YAML frontmatter tags into vendor scoring', () => {
34+
const file = { path: 'notes/demo.md', basename: 'demo.md' };
35+
const body = 'This paragraph mentions projects and planning work for the week.';
36+
const cache = {
37+
frontmatter: { tags: ['yaml-priority', 'inbox'] },
38+
headings: [{ heading: 'Overview' }],
39+
links: [{ link: 'Other' }],
40+
};
41+
42+
const result = analyzeNote(file, body, cache, {});
43+
44+
expect(result.provider).toBe('glyph-mi/notes');
45+
expect(result.tags).toContain('yaml-priority');
46+
expect(result.tags).toContain('inbox');
47+
});
48+
49+
it('still works when cache has no frontmatter', () => {
50+
const file = { path: 'a.md', basename: 'a.md' };
51+
const body = 'Simple note about gardening tools and soil.';
52+
const result = analyzeNote(file, body, {}, {});
53+
expect(result.provider).toBe('glyph-mi/notes');
54+
expect(Array.isArray(result.tags)).toBe(true);
55+
});
56+
});

0 commit comments

Comments
 (0)