@noteplanco/noteplan-mcp 1.1.23 → 1.1.24
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 +7 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/noteplan/attachments-paths.d.ts +13 -0
- package/dist/noteplan/attachments-paths.d.ts.map +1 -0
- package/dist/noteplan/attachments-paths.js +27 -0
- package/dist/noteplan/attachments-paths.js.map +1 -0
- package/dist/noteplan/embeddings.js +1 -1
- package/dist/noteplan/embeddings.js.map +1 -1
- package/dist/noteplan/file-reader.d.ts +37 -46
- package/dist/noteplan/file-reader.d.ts.map +1 -1
- package/dist/noteplan/file-reader.js +200 -202
- package/dist/noteplan/file-reader.js.map +1 -1
- package/dist/noteplan/file-reader.test.d.ts +2 -0
- package/dist/noteplan/file-reader.test.d.ts.map +1 -0
- package/dist/noteplan/file-reader.test.js +67 -0
- package/dist/noteplan/file-reader.test.js.map +1 -0
- package/dist/noteplan/file-writer.d.ts +35 -31
- package/dist/noteplan/file-writer.d.ts.map +1 -1
- package/dist/noteplan/file-writer.js +280 -164
- package/dist/noteplan/file-writer.js.map +1 -1
- package/dist/noteplan/file-writer.test.js +704 -191
- package/dist/noteplan/file-writer.test.js.map +1 -1
- package/dist/noteplan/filter-store.d.ts +5 -5
- package/dist/noteplan/filter-store.d.ts.map +1 -1
- package/dist/noteplan/filter-store.js +94 -79
- package/dist/noteplan/filter-store.js.map +1 -1
- package/dist/noteplan/ripgrep-search.d.ts +25 -2
- package/dist/noteplan/ripgrep-search.d.ts.map +1 -1
- package/dist/noteplan/ripgrep-search.js +75 -2
- package/dist/noteplan/ripgrep-search.js.map +1 -1
- package/dist/noteplan/space-row-utils.d.ts +20 -0
- package/dist/noteplan/space-row-utils.d.ts.map +1 -0
- package/dist/noteplan/space-row-utils.js +78 -0
- package/dist/noteplan/space-row-utils.js.map +1 -0
- package/dist/noteplan/space-row-utils.test.d.ts +2 -0
- package/dist/noteplan/space-row-utils.test.d.ts.map +1 -0
- package/dist/noteplan/space-row-utils.test.js +123 -0
- package/dist/noteplan/space-row-utils.test.js.map +1 -0
- package/dist/noteplan/sqlite-reader.d.ts +12 -27
- package/dist/noteplan/sqlite-reader.d.ts.map +1 -1
- package/dist/noteplan/sqlite-reader.js +315 -221
- package/dist/noteplan/sqlite-reader.js.map +1 -1
- package/dist/noteplan/sqlite-writer.d.ts +1 -1
- package/dist/noteplan/sqlite-writer.d.ts.map +1 -1
- package/dist/noteplan/sqlite-writer.js +2 -2
- package/dist/noteplan/sqlite-writer.js.map +1 -1
- package/dist/noteplan/unified-store.d.ts +41 -30
- package/dist/noteplan/unified-store.d.ts.map +1 -1
- package/dist/noteplan/unified-store.js +257 -159
- package/dist/noteplan/unified-store.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +142 -61
- package/dist/server.js.map +1 -1
- package/dist/tools/attachments.d.ts +9 -9
- package/dist/tools/attachments.d.ts.map +1 -1
- package/dist/tools/attachments.js +74 -83
- package/dist/tools/attachments.js.map +1 -1
- package/dist/tools/attachments.test.js +170 -129
- package/dist/tools/attachments.test.js.map +1 -1
- package/dist/tools/calendar.d.ts +16 -13
- package/dist/tools/calendar.d.ts.map +1 -1
- package/dist/tools/calendar.js +17 -16
- package/dist/tools/calendar.js.map +1 -1
- package/dist/tools/embeddings.d.ts +6 -6
- package/dist/tools/embeddings.d.ts.map +1 -1
- package/dist/tools/embeddings.js +6 -6
- package/dist/tools/embeddings.js.map +1 -1
- package/dist/tools/events.d.ts +7 -3
- package/dist/tools/events.d.ts.map +1 -1
- package/dist/tools/events.js +51 -16
- package/dist/tools/events.js.map +1 -1
- package/dist/tools/filters.d.ts +28 -33
- package/dist/tools/filters.d.ts.map +1 -1
- package/dist/tools/filters.js +42 -105
- package/dist/tools/filters.js.map +1 -1
- package/dist/tools/notes.d.ts +80 -218
- package/dist/tools/notes.d.ts.map +1 -1
- package/dist/tools/notes.js +180 -177
- package/dist/tools/notes.js.map +1 -1
- package/dist/tools/notes.test.js +242 -21
- package/dist/tools/notes.test.js.map +1 -1
- package/dist/tools/search.d.ts +4 -3
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +9 -5
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/search.test.d.ts +2 -0
- package/dist/tools/search.test.d.ts.map +1 -0
- package/dist/tools/search.test.js +37 -0
- package/dist/tools/search.test.js.map +1 -0
- package/dist/tools/spaces.d.ts +20 -20
- package/dist/tools/spaces.d.ts.map +1 -1
- package/dist/tools/spaces.js +28 -28
- package/dist/tools/spaces.js.map +1 -1
- package/dist/tools/tasks.d.ts +22 -22
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +22 -22
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/templates.d.ts +7 -7
- package/dist/tools/templates.d.ts.map +1 -1
- package/dist/tools/templates.js +4 -4
- package/dist/tools/templates.js.map +1 -1
- package/dist/tools/themes.js +1 -1
- package/dist/tools/themes.js.map +1 -1
- package/dist/transport/bridge-availability.d.ts +5 -0
- package/dist/transport/bridge-availability.d.ts.map +1 -0
- package/dist/transport/bridge-availability.js +92 -0
- package/dist/transport/bridge-availability.js.map +1 -0
- package/dist/transport/bridge-cascade.d.ts +18 -0
- package/dist/transport/bridge-cascade.d.ts.map +1 -0
- package/dist/transport/bridge-cascade.js +78 -0
- package/dist/transport/bridge-cascade.js.map +1 -0
- package/dist/transport/bridge-cascade.test.d.ts +2 -0
- package/dist/transport/bridge-cascade.test.d.ts.map +1 -0
- package/dist/transport/bridge-cascade.test.js +160 -0
- package/dist/transport/bridge-cascade.test.js.map +1 -0
- package/dist/transport/bridge-client.d.ts +197 -0
- package/dist/transport/bridge-client.d.ts.map +1 -0
- package/dist/transport/bridge-client.js +288 -0
- package/dist/transport/bridge-client.js.map +1 -0
- package/dist/transport/bridge-client.test.d.ts +2 -0
- package/dist/transport/bridge-client.test.d.ts.map +1 -0
- package/dist/transport/bridge-client.test.js +384 -0
- package/dist/transport/bridge-client.test.js.map +1 -0
- package/dist/transport/bridge-context.d.ts +10 -0
- package/dist/transport/bridge-context.d.ts.map +1 -0
- package/dist/transport/bridge-context.js +18 -0
- package/dist/transport/bridge-context.js.map +1 -0
- package/dist/transport/bridge-fs.d.ts +25 -0
- package/dist/transport/bridge-fs.d.ts.map +1 -0
- package/dist/transport/bridge-fs.js +129 -0
- package/dist/transport/bridge-fs.js.map +1 -0
- package/dist/utils/date-utils.d.ts +24 -0
- package/dist/utils/date-utils.d.ts.map +1 -1
- package/dist/utils/date-utils.js +55 -0
- package/dist/utils/date-utils.js.map +1 -1
- package/dist/utils/date-utils.test.d.ts +2 -0
- package/dist/utils/date-utils.test.d.ts.map +1 -0
- package/dist/utils/date-utils.test.js +109 -0
- package/dist/utils/date-utils.test.js.map +1 -0
- package/dist/utils/folder-access.d.ts +23 -0
- package/dist/utils/folder-access.d.ts.map +1 -0
- package/dist/utils/folder-access.js +131 -0
- package/dist/utils/folder-access.js.map +1 -0
- package/dist/utils/folder-access.test.d.ts +2 -0
- package/dist/utils/folder-access.test.d.ts.map +1 -0
- package/dist/utils/folder-access.test.js +182 -0
- package/dist/utils/folder-access.test.js.map +1 -0
- package/dist/utils/folder-matcher.d.ts.map +1 -1
- package/dist/utils/folder-matcher.js +16 -0
- package/dist/utils/folder-matcher.js.map +1 -1
- package/dist/utils/folder-matcher.test.js +42 -0
- package/dist/utils/folder-matcher.test.js.map +1 -1
- package/dist/utils/server-config.d.ts +10 -2
- package/dist/utils/server-config.d.ts.map +1 -1
- package/dist/utils/server-config.js +16 -2
- package/dist/utils/server-config.js.map +1 -1
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -1
- package/dist/utils/version.js +5 -1
- package/dist/utils/version.js.map +1 -1
- package/package.json +4 -3
- package/scripts/calendar-helper +0 -0
- package/scripts/reminders-helper +0 -0
package/dist/tools/notes.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
// Mock preferences before importing markdown-parser (which reads UserDefaults at runtime)
|
|
3
3
|
vi.mock('../noteplan/preferences.js', () => ({
|
|
4
4
|
getTaskMarkerConfigCached: vi.fn(() => ({
|
|
@@ -1384,7 +1384,7 @@ describe('matchesFrontmatterProperties – property filter edge cases', () => {
|
|
|
1384
1384
|
// ---------------------------------------------------------------------------
|
|
1385
1385
|
// We test the getNote logic by importing the function and mocking the readers.
|
|
1386
1386
|
// Since getNote depends on sqliteReader and fileReader, we use vi.mock.
|
|
1387
|
-
import { getNote } from '../noteplan/unified-store.js';
|
|
1387
|
+
import { getNote, listNotes } from '../noteplan/unified-store.js';
|
|
1388
1388
|
import * as sqliteReader from '../noteplan/sqlite-reader.js';
|
|
1389
1389
|
import * as fileReader from '../noteplan/file-reader.js';
|
|
1390
1390
|
vi.mock('../noteplan/sqlite-reader.js', () => ({
|
|
@@ -1438,36 +1438,36 @@ describe('getNote id/filename resolution', () => {
|
|
|
1438
1438
|
beforeEach(() => {
|
|
1439
1439
|
vi.clearAllMocks();
|
|
1440
1440
|
});
|
|
1441
|
-
it('retrieves a local note via id when SQLite lookup returns null', () => {
|
|
1442
|
-
vi.mocked(sqliteReader.getSpaceNote).
|
|
1443
|
-
vi.mocked(fileReader.readNoteFile).
|
|
1444
|
-
const result = getNote({ id: 'Notes/Books/my-book.md' });
|
|
1441
|
+
it('retrieves a local note via id when SQLite lookup returns null', async () => {
|
|
1442
|
+
vi.mocked(sqliteReader.getSpaceNote).mockResolvedValue(null);
|
|
1443
|
+
vi.mocked(fileReader.readNoteFile).mockResolvedValue(localNote);
|
|
1444
|
+
const result = await getNote({ id: 'Notes/Books/my-book.md' });
|
|
1445
1445
|
expect(result).toEqual(localNote);
|
|
1446
1446
|
expect(sqliteReader.getSpaceNote).toHaveBeenCalledWith('Notes/Books/my-book.md');
|
|
1447
1447
|
expect(fileReader.readNoteFile).toHaveBeenCalledWith('Notes/Books/my-book.md');
|
|
1448
1448
|
});
|
|
1449
|
-
it('retrieves a local note via filename', () => {
|
|
1450
|
-
vi.mocked(fileReader.readNoteFile).
|
|
1451
|
-
const result = getNote({ filename: 'Notes/Books/my-book.md' });
|
|
1449
|
+
it('retrieves a local note via filename', async () => {
|
|
1450
|
+
vi.mocked(fileReader.readNoteFile).mockResolvedValue(localNote);
|
|
1451
|
+
const result = await getNote({ filename: 'Notes/Books/my-book.md' });
|
|
1452
1452
|
expect(result).toEqual(localNote);
|
|
1453
1453
|
});
|
|
1454
|
-
it('id and filename return the same note for local notes', () => {
|
|
1455
|
-
vi.mocked(sqliteReader.getSpaceNote).
|
|
1456
|
-
vi.mocked(fileReader.readNoteFile).
|
|
1457
|
-
const byId = getNote({ id: localNote.filename });
|
|
1458
|
-
const byFilename = getNote({ filename: localNote.filename });
|
|
1454
|
+
it('id and filename return the same note for local notes', async () => {
|
|
1455
|
+
vi.mocked(sqliteReader.getSpaceNote).mockResolvedValue(null);
|
|
1456
|
+
vi.mocked(fileReader.readNoteFile).mockResolvedValue(localNote);
|
|
1457
|
+
const byId = await getNote({ id: localNote.filename });
|
|
1458
|
+
const byFilename = await getNote({ filename: localNote.filename });
|
|
1459
1459
|
expect(byId).toEqual(byFilename);
|
|
1460
1460
|
});
|
|
1461
|
-
it('returns null for non-existent id', () => {
|
|
1462
|
-
vi.mocked(sqliteReader.getSpaceNote).
|
|
1463
|
-
vi.mocked(fileReader.readNoteFile).
|
|
1464
|
-
const result = getNote({ id: 'Notes/nonexistent.md' });
|
|
1461
|
+
it('returns null for non-existent id', async () => {
|
|
1462
|
+
vi.mocked(sqliteReader.getSpaceNote).mockResolvedValue(null);
|
|
1463
|
+
vi.mocked(fileReader.readNoteFile).mockResolvedValue(null);
|
|
1464
|
+
const result = await getNote({ id: 'Notes/nonexistent.md' });
|
|
1465
1465
|
expect(result).toBeNull();
|
|
1466
1466
|
});
|
|
1467
|
-
it('prefers space note when SQLite lookup succeeds', () => {
|
|
1467
|
+
it('prefers space note when SQLite lookup succeeds', async () => {
|
|
1468
1468
|
const spaceNote = { ...localNote, source: 'space', id: 'space-uuid-123' };
|
|
1469
|
-
vi.mocked(sqliteReader.getSpaceNote).
|
|
1470
|
-
const result = getNote({ id: 'space-uuid-123' });
|
|
1469
|
+
vi.mocked(sqliteReader.getSpaceNote).mockResolvedValue(spaceNote);
|
|
1470
|
+
const result = await getNote({ id: 'space-uuid-123' });
|
|
1471
1471
|
expect(result).toEqual(spaceNote);
|
|
1472
1472
|
expect(fileReader.readNoteFile).not.toHaveBeenCalled();
|
|
1473
1473
|
});
|
|
@@ -2477,4 +2477,225 @@ describe('title resolution for search – frontmatter title takes priority', ()
|
|
|
2477
2477
|
expect(matchesTitleOrFilename(note, 'knuth reviewer')).toBe(true);
|
|
2478
2478
|
});
|
|
2479
2479
|
});
|
|
2480
|
+
// ── listNotes — folder scope inside a space (regression: PR #6 by @steinwaywhw) ──
|
|
2481
|
+
// Ported from PR #6 to the post-bridge async architecture.
|
|
2482
|
+
describe('listNotes – folder scope inside a space', () => {
|
|
2483
|
+
const spaceId = 'space-001';
|
|
2484
|
+
const folderId = 'folder-001';
|
|
2485
|
+
const noteInFolder = {
|
|
2486
|
+
id: 'note-in-folder',
|
|
2487
|
+
title: 'Project Alpha',
|
|
2488
|
+
filename: `%%NotePlanCloud%%/${spaceId}/${folderId}/note-in-folder`,
|
|
2489
|
+
type: 'note',
|
|
2490
|
+
source: 'space',
|
|
2491
|
+
folder: folderId,
|
|
2492
|
+
content: '# Project Alpha',
|
|
2493
|
+
modifiedAt: new Date(),
|
|
2494
|
+
createdAt: new Date(),
|
|
2495
|
+
spaceId,
|
|
2496
|
+
};
|
|
2497
|
+
const noteInOtherFolder = {
|
|
2498
|
+
...noteInFolder,
|
|
2499
|
+
id: 'note-other',
|
|
2500
|
+
title: 'Other Note',
|
|
2501
|
+
folder: 'folder-002',
|
|
2502
|
+
filename: `%%NotePlanCloud%%/${spaceId}/folder-002/note-other`,
|
|
2503
|
+
};
|
|
2504
|
+
beforeEach(() => {
|
|
2505
|
+
vi.clearAllMocks();
|
|
2506
|
+
vi.mocked(sqliteReader.listSpaces).mockResolvedValue([
|
|
2507
|
+
{ id: spaceId, name: 'My Space', noteCount: 2 },
|
|
2508
|
+
]);
|
|
2509
|
+
vi.mocked(sqliteReader.listSpaceNotes).mockResolvedValue([noteInFolder, noteInOtherFolder]);
|
|
2510
|
+
});
|
|
2511
|
+
it('returns only notes in the resolved folder', async () => {
|
|
2512
|
+
vi.mocked(sqliteReader.resolveSpaceFolder).mockResolvedValue({
|
|
2513
|
+
id: folderId,
|
|
2514
|
+
path: 'Active Projects',
|
|
2515
|
+
name: 'Active Projects',
|
|
2516
|
+
source: 'space',
|
|
2517
|
+
spaceId,
|
|
2518
|
+
});
|
|
2519
|
+
const result = await listNotes({ folder: 'Active Projects', space: 'My Space' });
|
|
2520
|
+
expect(result.map((n) => n.title)).toEqual(['Project Alpha']);
|
|
2521
|
+
expect(sqliteReader.resolveSpaceFolder).toHaveBeenCalledWith(spaceId, 'Active Projects');
|
|
2522
|
+
});
|
|
2523
|
+
it('returns empty when the folder does not resolve in the space', async () => {
|
|
2524
|
+
vi.mocked(sqliteReader.resolveSpaceFolder).mockResolvedValue(null);
|
|
2525
|
+
const result = await listNotes({ folder: 'Nonexistent', space: 'My Space' });
|
|
2526
|
+
expect(result).toHaveLength(0);
|
|
2527
|
+
});
|
|
2528
|
+
it('returns all space notes when no folder is specified', async () => {
|
|
2529
|
+
const result = await listNotes({ space: 'My Space' });
|
|
2530
|
+
expect(result).toHaveLength(2);
|
|
2531
|
+
expect(sqliteReader.resolveSpaceFolder).not.toHaveBeenCalled();
|
|
2532
|
+
});
|
|
2533
|
+
});
|
|
2534
|
+
// ---------------------------------------------------------------------------
|
|
2535
|
+
// Folder access rules — read-side filtering in unified-store
|
|
2536
|
+
// ---------------------------------------------------------------------------
|
|
2537
|
+
import { searchNotes, listFolders, listTags, getCalendarNote, invalidateListingCaches } from '../noteplan/unified-store.js';
|
|
2538
|
+
import { __resetFolderAccessConfigForTests } from '../utils/folder-access.js';
|
|
2539
|
+
describe('folder-access read-side filtering', () => {
|
|
2540
|
+
beforeEach(() => {
|
|
2541
|
+
vi.clearAllMocks();
|
|
2542
|
+
delete process.env.NOTEPLAN_ALLOWED_FOLDERS;
|
|
2543
|
+
delete process.env.NOTEPLAN_DENIED_FOLDERS;
|
|
2544
|
+
__resetFolderAccessConfigForTests();
|
|
2545
|
+
// The listNotes cache is module-level so a previous test's filtered
|
|
2546
|
+
// result would leak into this one. Reset before every case.
|
|
2547
|
+
invalidateListingCaches();
|
|
2548
|
+
// Reset readers to safe defaults — individual tests override.
|
|
2549
|
+
vi.mocked(sqliteReader.listSpaces).mockResolvedValue([]);
|
|
2550
|
+
vi.mocked(sqliteReader.listSpaceNotes).mockResolvedValue([]);
|
|
2551
|
+
vi.mocked(sqliteReader.searchSpaceNotesFTS).mockResolvedValue({ results: [], backend: 'sqlite' });
|
|
2552
|
+
vi.mocked(fileReader.listProjectNotes).mockResolvedValue([]);
|
|
2553
|
+
vi.mocked(fileReader.listCalendarNotes).mockResolvedValue([]);
|
|
2554
|
+
vi.mocked(fileReader.searchLocalNotes).mockResolvedValue([]);
|
|
2555
|
+
vi.mocked(fileReader.readNoteFile).mockResolvedValue(null);
|
|
2556
|
+
});
|
|
2557
|
+
afterEach(() => {
|
|
2558
|
+
delete process.env.NOTEPLAN_ALLOWED_FOLDERS;
|
|
2559
|
+
delete process.env.NOTEPLAN_DENIED_FOLDERS;
|
|
2560
|
+
__resetFolderAccessConfigForTests();
|
|
2561
|
+
});
|
|
2562
|
+
it('listNotes drops notes inside a denied folder', async () => {
|
|
2563
|
+
process.env.NOTEPLAN_DENIED_FOLDERS = 'Notes/Personal';
|
|
2564
|
+
__resetFolderAccessConfigForTests();
|
|
2565
|
+
const personal = {
|
|
2566
|
+
id: 'Notes/Personal/Diary.md',
|
|
2567
|
+
title: 'Diary',
|
|
2568
|
+
filename: 'Notes/Personal/Diary.md',
|
|
2569
|
+
type: 'note',
|
|
2570
|
+
source: 'local',
|
|
2571
|
+
folder: 'Notes/Personal',
|
|
2572
|
+
content: '',
|
|
2573
|
+
modifiedAt: new Date(),
|
|
2574
|
+
createdAt: new Date(),
|
|
2575
|
+
spaceId: undefined,
|
|
2576
|
+
};
|
|
2577
|
+
const work = { ...personal, id: 'Notes/Work/Plan.md', filename: 'Notes/Work/Plan.md', folder: 'Notes/Work', title: 'Plan' };
|
|
2578
|
+
vi.mocked(fileReader.listProjectNotes).mockResolvedValue([personal, work]);
|
|
2579
|
+
const result = await listNotes();
|
|
2580
|
+
expect(result.map((n) => n.filename)).toEqual(['Notes/Work/Plan.md']);
|
|
2581
|
+
});
|
|
2582
|
+
it('getNote returns null for a denied filename even if the reader has it', async () => {
|
|
2583
|
+
process.env.NOTEPLAN_DENIED_FOLDERS = 'Notes/Personal';
|
|
2584
|
+
__resetFolderAccessConfigForTests();
|
|
2585
|
+
const personal = {
|
|
2586
|
+
id: 'Notes/Personal/Diary.md',
|
|
2587
|
+
title: 'Diary',
|
|
2588
|
+
filename: 'Notes/Personal/Diary.md',
|
|
2589
|
+
type: 'note',
|
|
2590
|
+
source: 'local',
|
|
2591
|
+
folder: 'Notes/Personal',
|
|
2592
|
+
content: '',
|
|
2593
|
+
modifiedAt: new Date(),
|
|
2594
|
+
createdAt: new Date(),
|
|
2595
|
+
spaceId: undefined,
|
|
2596
|
+
};
|
|
2597
|
+
vi.mocked(fileReader.readNoteFile).mockResolvedValue(personal);
|
|
2598
|
+
vi.mocked(sqliteReader.getSpaceNote).mockResolvedValue(null);
|
|
2599
|
+
const result = await getNote({ filename: 'Notes/Personal/Diary.md' });
|
|
2600
|
+
expect(result).toBeNull();
|
|
2601
|
+
});
|
|
2602
|
+
it('searchNotes filters denied folders out of the result set', async () => {
|
|
2603
|
+
process.env.NOTEPLAN_DENIED_FOLDERS = 'Notes/Personal';
|
|
2604
|
+
__resetFolderAccessConfigForTests();
|
|
2605
|
+
const personal = {
|
|
2606
|
+
id: 'Notes/Personal/Diary.md',
|
|
2607
|
+
title: 'Diary about work',
|
|
2608
|
+
filename: 'Notes/Personal/Diary.md',
|
|
2609
|
+
type: 'note',
|
|
2610
|
+
source: 'local',
|
|
2611
|
+
folder: 'Notes/Personal',
|
|
2612
|
+
content: 'meeting work plan',
|
|
2613
|
+
modifiedAt: new Date(),
|
|
2614
|
+
createdAt: new Date(),
|
|
2615
|
+
spaceId: undefined,
|
|
2616
|
+
};
|
|
2617
|
+
const work = { ...personal, id: 'Notes/Work/Plan.md', filename: 'Notes/Work/Plan.md', folder: 'Notes/Work', title: 'Diary at Work' };
|
|
2618
|
+
// Use the metadata-search branch (searchField=title) so we don't need to
|
|
2619
|
+
// wrangle the ripgrep mock — same filter site applies.
|
|
2620
|
+
vi.mocked(fileReader.listProjectNotes).mockResolvedValue([personal, work]);
|
|
2621
|
+
const result = await searchNotes('diary', { searchField: 'title' });
|
|
2622
|
+
// Both notes have "diary" in their title; the denied one must be filtered out.
|
|
2623
|
+
expect(result.results.map((r) => r.note.filename)).toEqual(['Notes/Work/Plan.md']);
|
|
2624
|
+
});
|
|
2625
|
+
// Folder *listings* themselves used to leak — `noteplan_folders(action: list/find/resolve)`
|
|
2626
|
+
// would return a denied folder by name. The fix prepends `Notes/` to the folder's relative
|
|
2627
|
+
// path so the same env-var match works ("Notes/Personal" rule against `Personal/Diary`).
|
|
2628
|
+
it('listFolders drops local folders inside a denied subtree', async () => {
|
|
2629
|
+
process.env.NOTEPLAN_DENIED_FOLDERS = 'Notes/Personal';
|
|
2630
|
+
__resetFolderAccessConfigForTests();
|
|
2631
|
+
vi.mocked(fileReader.listFolders).mockResolvedValue([
|
|
2632
|
+
{ path: 'Personal', name: 'Personal', source: 'local' },
|
|
2633
|
+
{ path: 'Personal/Finance', name: 'Finance', source: 'local' },
|
|
2634
|
+
{ path: 'Work', name: 'Work', source: 'local' },
|
|
2635
|
+
]);
|
|
2636
|
+
const result = await listFolders();
|
|
2637
|
+
expect(result.map((f) => f.path)).toEqual(['Work']);
|
|
2638
|
+
});
|
|
2639
|
+
// getCalendarNote / getTodayNote / per-day fetches in getNotesInRange used
|
|
2640
|
+
// to call fileReader.getCalendarNote directly, bypassing isFolderAllowed.
|
|
2641
|
+
// Important: a user can deny `Calendar` outright to hide all calendar notes.
|
|
2642
|
+
it('getCalendarNote returns null when Calendar/ is denied', async () => {
|
|
2643
|
+
process.env.NOTEPLAN_DENIED_FOLDERS = 'Calendar';
|
|
2644
|
+
__resetFolderAccessConfigForTests();
|
|
2645
|
+
vi.mocked(fileReader.getCalendarNote).mockResolvedValue({
|
|
2646
|
+
id: 'Calendar/20260507.txt',
|
|
2647
|
+
title: '20260507',
|
|
2648
|
+
filename: 'Calendar/20260507.txt',
|
|
2649
|
+
type: 'calendar',
|
|
2650
|
+
source: 'local',
|
|
2651
|
+
folder: 'Calendar',
|
|
2652
|
+
content: 'private',
|
|
2653
|
+
modifiedAt: new Date(),
|
|
2654
|
+
createdAt: new Date(),
|
|
2655
|
+
spaceId: undefined,
|
|
2656
|
+
});
|
|
2657
|
+
const result = await getCalendarNote('20260507');
|
|
2658
|
+
expect(result).toBeNull();
|
|
2659
|
+
});
|
|
2660
|
+
// listTags walked the bridge / ripgrep / filesystem indiscriminately, so
|
|
2661
|
+
// tag NAMES from denied notes leaked through `noteplan_search(action:
|
|
2662
|
+
// list_tags)` even though the notes themselves were filtered. When rules
|
|
2663
|
+
// are configured we now iterate the already-filtered listNotes() output.
|
|
2664
|
+
it('listTags excludes tags that exist only inside a denied folder', async () => {
|
|
2665
|
+
process.env.NOTEPLAN_DENIED_FOLDERS = 'Notes/Personal';
|
|
2666
|
+
__resetFolderAccessConfigForTests();
|
|
2667
|
+
const personal = {
|
|
2668
|
+
id: 'Notes/Personal/Diary.md',
|
|
2669
|
+
title: 'Diary',
|
|
2670
|
+
filename: 'Notes/Personal/Diary.md',
|
|
2671
|
+
type: 'note',
|
|
2672
|
+
source: 'local',
|
|
2673
|
+
folder: 'Notes/Personal',
|
|
2674
|
+
content: 'session with #therapist about #anxiety',
|
|
2675
|
+
modifiedAt: new Date(),
|
|
2676
|
+
createdAt: new Date(),
|
|
2677
|
+
spaceId: undefined,
|
|
2678
|
+
};
|
|
2679
|
+
const work = {
|
|
2680
|
+
...personal,
|
|
2681
|
+
id: 'Notes/Work/Plan.md',
|
|
2682
|
+
filename: 'Notes/Work/Plan.md',
|
|
2683
|
+
folder: 'Notes/Work',
|
|
2684
|
+
title: 'Plan',
|
|
2685
|
+
content: 'review #q2 plan with #team',
|
|
2686
|
+
};
|
|
2687
|
+
vi.mocked(fileReader.listProjectNotes).mockResolvedValue([personal, work]);
|
|
2688
|
+
const tags = await listTags();
|
|
2689
|
+
expect(tags).toEqual(expect.arrayContaining(['#q2', '#team']));
|
|
2690
|
+
expect(tags).not.toContain('#therapist');
|
|
2691
|
+
expect(tags).not.toContain('#anxiety');
|
|
2692
|
+
});
|
|
2693
|
+
// No rules configured → keep the fast bridge / ripgrep / extractAllTags path.
|
|
2694
|
+
it('listTags uses the fast extractAllTags path when no rules are configured', async () => {
|
|
2695
|
+
vi.mocked(fileReader.extractAllTags).mockResolvedValue(['#fast', '#path']);
|
|
2696
|
+
const tags = await listTags();
|
|
2697
|
+
expect(tags).toEqual(['#fast', '#path']);
|
|
2698
|
+
expect(fileReader.extractAllTags).toHaveBeenCalled();
|
|
2699
|
+
});
|
|
2700
|
+
});
|
|
2480
2701
|
//# sourceMappingURL=notes.test.js.map
|