@oomkapwn/enquire-mcp 3.7.12 → 3.7.13
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/CHANGELOG.md +57 -0
- package/README.md +5 -5
- package/dist/http-transport.d.ts +11 -0
- package/dist/http-transport.d.ts.map +1 -1
- package/dist/http-transport.js +34 -0
- package/dist/http-transport.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/pdf.d.ts +22 -1
- package/dist/pdf.d.ts.map +1 -1
- package/dist/pdf.js +9 -2
- package/dist/pdf.js.map +1 -1
- package/dist/tool-registry.d.ts.map +1 -1
- package/dist/tool-registry.js +26 -1
- package/dist/tool-registry.js.map +1 -1
- package/dist/tools/media.d.ts.map +1 -1
- package/dist/tools/media.js +15 -6
- package/dist/tools/media.js.map +1 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +46 -10
- package/dist/tools/write.js.map +1 -1
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +38 -14
- package/dist/vault.js.map +1 -1
- package/docs/COMPARISON.md +1 -1
- package/docs/QUICKSTART.md +2 -2
- package/docs/api-reference/functions/index.buildEmbedText.html +1 -1
- package/docs/api-reference/functions/index.buildMcpServer.html +1 -1
- package/docs/api-reference/functions/index.formatReadyBanner.html +1 -1
- package/docs/api-reference/functions/index.main.html +1 -1
- package/docs/api-reference/functions/index.parsePositiveInt.html +1 -1
- package/docs/api-reference/functions/index.parseQuantizationMode.html +1 -1
- package/docs/api-reference/functions/index.prepareServerDeps.html +1 -1
- package/docs/api-reference/functions/index.startServer.html +1 -1
- package/docs/api-reference/functions/tools.appendToNote.html +1 -1
- package/docs/api-reference/functions/tools.archiveNote.html +1 -1
- package/docs/api-reference/functions/tools.assertHnswModelMatchesEmbedder.html +1 -1
- package/docs/api-reference/functions/tools.chatThreadAppend.html +1 -1
- package/docs/api-reference/functions/tools.chatThreadRead.html +1 -1
- package/docs/api-reference/functions/tools.contextPack.html +1 -1
- package/docs/api-reference/functions/tools.createNote.html +1 -1
- package/docs/api-reference/functions/tools.dataviewQuery.html +1 -1
- package/docs/api-reference/functions/tools.embeddingsSearch.html +1 -1
- package/docs/api-reference/functions/tools.findPath.html +1 -1
- package/docs/api-reference/functions/tools.findSimilar.html +1 -1
- package/docs/api-reference/functions/tools.frontmatterGet.html +1 -1
- package/docs/api-reference/functions/tools.frontmatterSearch.html +1 -1
- package/docs/api-reference/functions/tools.frontmatterSet.html +1 -1
- package/docs/api-reference/functions/tools.getBacklinks.html +1 -1
- package/docs/api-reference/functions/tools.getNoteNeighbors.html +1 -1
- package/docs/api-reference/functions/tools.getOpenQuestions.html +1 -1
- package/docs/api-reference/functions/tools.getOutboundLinks.html +1 -1
- package/docs/api-reference/functions/tools.getRecentEdits.html +1 -1
- package/docs/api-reference/functions/tools.getUnresolvedWikilinks.html +1 -1
- package/docs/api-reference/functions/tools.getVaultStats.html +1 -1
- package/docs/api-reference/functions/tools.lintWiki.html +1 -1
- package/docs/api-reference/functions/tools.listCanvases.html +1 -1
- package/docs/api-reference/functions/tools.listNotes.html +1 -1
- package/docs/api-reference/functions/tools.listPdfs.html +1 -1
- package/docs/api-reference/functions/tools.listTags.html +1 -1
- package/docs/api-reference/functions/tools.ocrPdf.html +1 -1
- package/docs/api-reference/functions/tools.openInUi.html +1 -1
- package/docs/api-reference/functions/tools.paperAudit.html +1 -1
- package/docs/api-reference/functions/tools.pickEmbedTextForHyde.html +1 -1
- package/docs/api-reference/functions/tools.readCanvas.html +1 -1
- package/docs/api-reference/functions/tools.readNote.html +1 -1
- package/docs/api-reference/functions/tools.readPdf.html +1 -1
- package/docs/api-reference/functions/tools.renameNote.html +1 -1
- package/docs/api-reference/functions/tools.replaceInNotes.html +1 -1
- package/docs/api-reference/functions/tools.resolveTarget.html +1 -1
- package/docs/api-reference/functions/tools.resolveWikilink.html +1 -1
- package/docs/api-reference/functions/tools.searchHybrid.html +1 -1
- package/docs/api-reference/functions/tools.searchText.html +1 -1
- package/docs/api-reference/functions/tools.semanticSearch.html +1 -1
- package/docs/api-reference/functions/tools.validateNoteProposal.html +1 -1
- package/docs/api-reference/interfaces/index.ServeOptions.html +25 -25
- package/docs/api-reference/interfaces/index.ServerDeps.html +3 -3
- package/docs/api-reference/interfaces/tool-manifest.ToolManifestEntry.html +5 -5
- package/docs/api-reference/interfaces/tools.ArchiveNoteArgs.html +5 -5
- package/docs/api-reference/interfaces/tools.BacklinkHit.html +6 -6
- package/docs/api-reference/interfaces/tools.CanvasEdge.html +8 -8
- package/docs/api-reference/interfaces/tools.CanvasSummary.html +7 -7
- package/docs/api-reference/interfaces/tools.ChatThreadAppendArgs.html +5 -5
- package/docs/api-reference/interfaces/tools.ChatThreadMessage.html +6 -6
- package/docs/api-reference/interfaces/tools.ChatThreadReadResult.html +5 -5
- package/docs/api-reference/interfaces/tools.ContextPackArgs.html +6 -6
- package/docs/api-reference/interfaces/tools.ContextPackResult.html +7 -7
- package/docs/api-reference/interfaces/tools.EmbedHit.html +9 -9
- package/docs/api-reference/interfaces/tools.EmbedSearchResponse.html +2 -2
- package/docs/api-reference/interfaces/tools.FindPathResult.html +7 -7
- package/docs/api-reference/interfaces/tools.FrontmatterSearchArgs.html +7 -7
- package/docs/api-reference/interfaces/tools.FrontmatterSetArgs.html +5 -5
- package/docs/api-reference/interfaces/tools.HnswSearchContext.html +3 -3
- package/docs/api-reference/interfaces/tools.LintWikiArgs.html +6 -6
- package/docs/api-reference/interfaces/tools.LintWikiFinding.html +6 -6
- package/docs/api-reference/interfaces/tools.LintWikiResult.html +2 -2
- package/docs/api-reference/interfaces/tools.NoteNeighbors.html +5 -5
- package/docs/api-reference/interfaces/tools.NoteReadFull.html +9 -9
- package/docs/api-reference/interfaces/tools.NoteReadMap.html +11 -11
- package/docs/api-reference/interfaces/tools.NoteSummary.html +6 -6
- package/docs/api-reference/interfaces/tools.OcrPdfArgs.html +5 -5
- package/docs/api-reference/interfaces/tools.OcrPdfPage.html +6 -6
- package/docs/api-reference/interfaces/tools.OcrPdfResult.html +4 -4
- package/docs/api-reference/interfaces/tools.OpenInUiResult.html +5 -5
- package/docs/api-reference/interfaces/tools.OpenQuestion.html +8 -8
- package/docs/api-reference/interfaces/tools.OutboundLink.html +9 -9
- package/docs/api-reference/interfaces/tools.PaperAuditFinding.html +7 -7
- package/docs/api-reference/interfaces/tools.PathStep.html +4 -4
- package/docs/api-reference/interfaces/tools.PdfSummary.html +5 -5
- package/docs/api-reference/interfaces/tools.ReadCanvasResult.html +3 -3
- package/docs/api-reference/interfaces/tools.ReadPdfArgs.html +4 -4
- package/docs/api-reference/interfaces/tools.ReadPdfPage.html +5 -5
- package/docs/api-reference/interfaces/tools.ReadPdfResult.html +3 -3
- package/docs/api-reference/interfaces/tools.RenameNoteResult.html +6 -6
- package/docs/api-reference/interfaces/tools.RenameProposal.html +5 -5
- package/docs/api-reference/interfaces/tools.ReplaceInNotesArgs.html +6 -6
- package/docs/api-reference/interfaces/tools.ReplaceInNotesFileResult.html +3 -3
- package/docs/api-reference/interfaces/tools.ReplaceInNotesResult.html +4 -4
- package/docs/api-reference/interfaces/tools.SearchHit.html +6 -6
- package/docs/api-reference/interfaces/tools.SearchHybridHit.html +9 -9
- package/docs/api-reference/interfaces/tools.SearchHybridResponse.html +6 -6
- package/docs/api-reference/interfaces/tools.SearchResponse.html +5 -5
- package/docs/api-reference/interfaces/tools.SemanticHit.html +7 -7
- package/docs/api-reference/interfaces/tools.SimilarNote.html +7 -7
- package/docs/api-reference/interfaces/tools.TagSummary.html +5 -5
- package/docs/api-reference/interfaces/tools.UnresolvedWikilink.html +10 -10
- package/docs/api-reference/interfaces/tools.ValidateProposalArgs.html +4 -4
- package/docs/api-reference/interfaces/tools.ValidateProposalResult.html +2 -2
- package/docs/api-reference/interfaces/tools.VaultStats.html +11 -11
- package/docs/api-reference/types/tools.CanvasNode.html +1 -1
- package/docs/api-reference/types/tools.SearchMode.html +1 -1
- package/docs/api-reference/variables/index.VERSION.html +1 -1
- package/docs/api-reference/variables/tool-manifest.TOOL_MANIFEST.html +1 -1
- package/docs/api.md +1 -1
- package/docs/benchmarks.md +16 -9
- package/package.json +9 -4
- package/docs/audits/findings/L1-code-quality.md +0 -213
- package/docs/audits/findings/L2-architecture.md +0 -245
- package/docs/audits/findings/L3-tests.md +0 -339
- package/docs/audits/findings/L4-cicd.md +0 -290
- package/docs/audits/findings/L5-security.md +0 -350
- package/docs/audits/findings/L6-documentation.md +0 -347
- package/docs/audits/findings/L7-operational.md +0 -50
- package/docs/audits/findings/L8-reproducibility.md +0 -64
- package/docs/audits/findings/L9-process.md +0 -84
- package/docs/audits/findings/baseline.json +0 -19
- package/docs/audits/v3.6.0-external-anonymous-audit.md +0 -163
- package/docs/audits/v3.6.0-final-audit.md +0 -171
- package/docs/audits/v3.6.0-rc.4-rootcause.md +0 -134
- package/docs/audits/v3.6.0-system-audit-plan.md +0 -199
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
# L1 — Code Quality (v3.6.0 audit)
|
|
2
|
-
|
|
3
|
-
**Scope**: every `src/**/*.ts` and `tests/**/*.test.ts` file.
|
|
4
|
-
**Auditor**: sub-agent C1.
|
|
5
|
-
**Date**: 2026-05-15.
|
|
6
|
-
**Baseline**: 28 src modules, 33 test files, 369 TSDoc blocks already present, 17 `@internal` markers (all in `src/tools/*.ts`).
|
|
7
|
-
|
|
8
|
-
## Summary
|
|
9
|
-
|
|
10
|
-
The codebase is in good shape overall. `src/tools/*.ts` (the public tool implementations) is exemplary — every function has full TSDoc with `@param` / `@returns` / `@throws` / `@example`, internal helpers are tagged `@internal`, and tests are specific and edge-case-heavy.
|
|
11
|
-
|
|
12
|
-
The findings below cluster into 4 classes, ordered by severity:
|
|
13
|
-
|
|
14
|
-
1. **L1-01 (Medium)** — TSDoc drift in foundational modules (`parser.ts`, `dql.ts`, `rrf.ts`, `embeddings.ts`, `vault.ts`, `embed-db.ts`, etc.): types/interfaces/constants exported but undocumented while `src/tools/*.ts` sets a much higher bar.
|
|
15
|
-
2. **L1-02 (Low)** — Weak `rejects.toThrow()` calls without message regex (13 instances), so a regression that changes WHICH error fires would still pass.
|
|
16
|
-
3. **L1-03 (Low)** — `@internal` usage limited to `src/tools/*.ts`; equally module-scoped exports elsewhere (`safeFts5Query`, `chunkContent`, `cosineSim`, `buildEmbedText`, etc.) are not tagged, so TypeDoc surfaces them as if public.
|
|
17
|
-
4. **L1-04 (Info)** — No tests explicitly probe concurrent / race-condition behavior; the persistent-cache + watcher + HTTP-session paths look prone to it.
|
|
18
|
-
|
|
19
|
-
No Critical / High findings. No silent `try { } catch {}` swallows — every catch I inspected has an inline comment explaining intent. No `any` types in exported signatures (a single stray `any` appears only in a code-fenced prompt string in `prompts.ts:1007` and in a comment block on `tools/meta.ts:641`). No commented-out code blocks (`grep` for `^\s*//\s+(import|const|...)` found 3 lines, all genuine sentence fragments inside multi-line comments). No `.skip` / `.todo` without context (single `it.skip` in `tests/reranker-smoke.test.ts:38` is fully documented).
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
### Finding L1-01 (Medium)
|
|
24
|
-
|
|
25
|
-
**File**: multiple — see backfill list
|
|
26
|
-
**Class**: TSDoc drift between `src/tools/*.ts` (gold standard) and the rest of `src/`. Public exported types / interfaces / constants / class members exist without `/** */` blocks while sibling modules carry full TSDoc + `@example`. The README, STABILITY.md, and TypeDoc output all claim "44 tools fully TSDoc'd → public API reference at github.io"; for non-tool modules that claim doesn't hold uniformly.
|
|
27
|
-
**Description**: 25-ish public exports in foundational modules have either no TSDoc, or inline `// ...` comments above (not parsed by TypeDoc), even though they appear in TypeDoc's public output (`docs/api-reference/`). Most painful are `parser.ts` (5 exported functions, 2 interfaces — zero TSDoc), `dql.ts` (5 types, 1 class, `parseDql` — zero TSDoc), `rrf.ts` (`RRF_K` constant + 3 interfaces — only the function bodies have JSDoc; interface fields use inline `/** */` but the top-level interface declarations don't), `embeddings.ts` (`EmbeddingModel` / `Embedder` / `Reranker` / `RerankerModel` interfaces, `EMBEDDING_MODELS` / `RERANKER_MODELS` constants, `resolveModel` / `resolveRerankerModel` / `cosineSim` functions), `embed-db.ts` (`EmbedDb` class itself has no class-level TSDoc; `upsertNote` / `deleteNote` / `getSourceStates` / `search` / `totalChunks` / `getAllVectors` methods documented inline but no `@param`/`@returns`), `vault.ts` (`Vault` class, `DEFAULT_MAX_FILE_BYTES` / `DEFAULT_MAX_CACHE_ENTRIES` / `DEFAULT_MAX_DISK_CACHE_BYTES` constants, `FileEntry` / `CachedNote` / `VaultOptions` interfaces — Vault class has 25+ public methods, only some have TSDoc), `watcher.ts` (`WatcherOptions`, `VaultWatcher` class), `periodic.ts` (`PeriodicKind`, `PeriodicSpec`, `PeriodicConfig`).
|
|
28
|
-
|
|
29
|
-
**Evidence** (`src/parser.ts:1-50`):
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
import matter from "gray-matter";
|
|
33
|
-
|
|
34
|
-
export interface Wikilink {
|
|
35
|
-
raw: string;
|
|
36
|
-
target: string;
|
|
37
|
-
section?: string;
|
|
38
|
-
block?: string;
|
|
39
|
-
alias?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export type Embed = Wikilink;
|
|
43
|
-
|
|
44
|
-
export interface ParsedNote {
|
|
45
|
-
frontmatter: Record<string, unknown>;
|
|
46
|
-
body: string;
|
|
47
|
-
wikilinks: Wikilink[];
|
|
48
|
-
embeds: Embed[];
|
|
49
|
-
tags: string[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function parseNote(source: string): ParsedNote {
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Compare against `src/tools/search.ts:7-86` (gold standard — every export has a full TSDoc block with `@param` / `@returns` / `@throws` / `@example`).
|
|
56
|
-
|
|
57
|
-
**Other instances** (grep cross-cutting):
|
|
58
|
-
|
|
59
|
-
- `src/parser.ts` — `Wikilink`, `Embed`, `ParsedNote`, `parseNote`, `extractWikilinks`, `extractEmbeds`, `extractInlineTags`, `extractFrontmatterTags`
|
|
60
|
-
- `src/dql.ts` — `Source`, `Op`, `Predicate`, `WhereGroups`, `DataviewQuery`, `DqlParseError`, `parseDql`, `DEFAULT_DQL_ROW_LIMIT`
|
|
61
|
-
- `src/rrf.ts` — `RRF_K`, `RankedHit`, `SignalContribution`, `FusedHit` (interfaces themselves have no leading TSDoc — fields are doc'd inline)
|
|
62
|
-
- `src/embeddings.ts` — `EmbeddingModel`, `EMBEDDING_MODELS`, `DEFAULT_MODEL_ALIAS`, `resolveModel`, `Embedder`, `cosineSim`, `RerankerModel`, `RERANKER_MODELS`, `DEFAULT_RERANKER_ALIAS`, `resolveRerankerModel`, `Reranker`
|
|
63
|
-
- `src/vault.ts` — `DEFAULT_MAX_FILE_BYTES`, `DEFAULT_MAX_CACHE_ENTRIES`, `DEFAULT_MAX_DISK_CACHE_BYTES`, `FileEntry`, `CachedNote`, `VaultOptions`, `Vault` class header, most `Vault` methods (`ensureExists`, `loadDiskCache`, `clearDiskCache`, `saveDiskCache`, `resolveInside`, `listMarkdown`, `listFilesByExtension`, `readBinaryFile`, `readFile`, `readNote`, `writeNote`, `renameFile`, `appendNote`, `invalidateCache`, `invalidateOne`, `stat`, `toRel`, `findByTitle`)
|
|
64
|
-
- `src/embed-db.ts` — `EmbedSearchHit`, `EmbedSyncReport`, `EmbedChunkKind`, `EmbedQuantization`, `EmbedDbOptions`, `EmbedDb` class header, `open`, `close`, `clearOnDisk`, `totalChunks`, `defaultEmbedDbFile`
|
|
65
|
-
- `src/fts5.ts` — `ChunkKind`, `FtsSearchHit`, `FtsSyncReport`, `FtsIndex` class header, `safeFts5Query`, `defaultIndexFile`, `chunkContent` (has TSDoc but no `@param`/`@returns`)
|
|
66
|
-
- `src/eval.ts` — `EvalQuery`, `EvalQueryScore`, `EvalResult`, `recallAtK`, `reciprocalRank`, `RunEvalOptions`
|
|
67
|
-
- `src/hnsw.ts` — `LabeledVector`, `HnswBuildOptions`, `HnswQueryOptions` (interfaces have inline field docs but no leading TSDoc on the interface itself)
|
|
68
|
-
- `src/http-transport.ts` — `createSessionRegistry`, `createHttpHandler` (signature TSDoc-less; behavior described in nearby block comments only)
|
|
69
|
-
- `src/ocr.ts` — `OcrPdfPage`, `OcrPdfResult`, `ExtractPdfWithOcrOptions`
|
|
70
|
-
- `src/pdf.ts` — `PdfExtractionResult`
|
|
71
|
-
- `src/periodic.ts` — `PeriodicKind`, `PeriodicSpec`, `PeriodicConfig`, `resolvePeriodicNoteName`, `formatMoment`
|
|
72
|
-
- `src/server.ts` — `ServeOptions` (the most important interface in the project; the export itself has no TSDoc; each field has a `/** */`)
|
|
73
|
-
- `src/tool-manifest.ts` — `TOOL_MANIFEST` constant (the file header is excellent, but the export itself has no TSDoc)
|
|
74
|
-
- `src/tool-registry.ts` — `embedDbPath`, `registerFtsTools`, `registerReadTools`, `registerWriteTools`, `registerChunkResource`, `registerResources`, `parsePositiveInt`, `encodeNotePath`, `decodeNotePath`, `textResult` (the register* functions are 50-200 lines each and exported, but get just a single-line `//` comment above)
|
|
75
|
-
- `src/watcher.ts` — `WatcherOptions`, `VaultWatcher` (class itself, plus `start`, `close` methods)
|
|
76
|
-
- `src/communities.ts` — `WikilinkGraph`, `CommunityResult` (interfaces themselves — fields are doc'd inline)
|
|
77
|
-
- `src/bases.ts` — `BaseSummary`, `BaseDocument`, `BaseQueryHit`, `BaseQueryResult`, `QueryBaseArgs`, `readBase`
|
|
78
|
-
- `src/doctor.ts` — `CheckStatus`, `DoctorCheck`, `DoctorResult`, `RunDoctorOptions`
|
|
79
|
-
|
|
80
|
-
**Suggested class fix**: Add a TypeDoc / lint invariant that fails the build if any `export (function|class|interface|type|const|enum)` is not immediately preceded by a `/** ... */` TSDoc block. Easiest place: extend `tests/lint.test.ts` (already an AST-style invariant runner) with a regex sweep over `src/**/*.ts` that mirrors what `tests/docs-consistency.test.ts` does for `docs/api.md`. Alternative: configure TypeDoc with `requiredToBeDocumented` + treat warnings as errors in `npm run docs:api`.
|
|
81
|
-
|
|
82
|
-
**Suggested per-instance backfill**: 25 exported entities across the 18 files listed above. Each fix is mechanical (write a 2-5 line TSDoc block + `@param`/`@returns`/`@throws` for functions). Estimated ~3-4 hours total. Prioritize the highest-visibility surface first:
|
|
83
|
-
|
|
84
|
-
1. `src/parser.ts` (8 exports — referenced from every tool module)
|
|
85
|
-
2. `src/vault.ts` (class + 25+ methods — the central abstraction)
|
|
86
|
-
3. `src/server.ts` (`ServeOptions` — the canonical CLI surface)
|
|
87
|
-
4. `src/embed-db.ts`, `src/fts5.ts`, `src/embeddings.ts`, `src/hnsw.ts` (retrieval-stack public API)
|
|
88
|
-
5. Everything else.
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
### Finding L1-02 (Low)
|
|
93
|
-
|
|
94
|
-
**File**: `tests/pdf.test.ts:111,274,281`, `tests/ocr.test.ts:45,54,63,76,77,97`, `tests/bases.test.ts:163`, `tests/canvas.test.ts:168`, `tests/watcher.test.ts:108`, `tests/write.test.ts:343` (13 total; the `not.toThrow()` cases at `tests/dql.test.ts:310` and `tests/security.test.ts:477` are correctly used and excluded)
|
|
95
|
-
**Class**: `rejects.toThrow()` (or `expect(...).toThrow()`) called with NO message regex / matcher. The test passes as long as ANY error fires — a regression that changes the error message, error type, or even the failing line still passes. We have 55 strong `rejects.toThrow(/regex/)` calls already; the 13 weak ones below are the outliers.
|
|
96
|
-
**Description**: A common pattern in test files — when an action is expected to fail, but the failure mode isn't pinned. Several of these test "rejects negative paths", "rejects malformed input", or "rejects missing dependency". The downside is that the test would still pass if the rejection came from a totally different bug — e.g. a path-traversal error firing instead of a "missing file" error in `tests/pdf.test.ts:274`. Best practice across the rest of the suite is `rejects.toThrow(/specific message excerpt/)`.
|
|
97
|
-
|
|
98
|
-
**Evidence** (`tests/pdf.test.ts:271-282`):
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
|
-
it("rejects reading a PDF that doesn't exist", async () => {
|
|
102
|
-
const v = new Vault(root);
|
|
103
|
-
await v.ensureExists();
|
|
104
|
-
await expect(readPdf(v, { path: "missing.pdf" })).rejects.toThrow();
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("rejects reading a PDF outside the privacy filter", async () => {
|
|
108
|
-
const v = new Vault(root, { excludeGlobs: ["private/**"] });
|
|
109
|
-
await v.ensureExists();
|
|
110
|
-
await expect(readPdf(v, { path: "private/secret.pdf" })).rejects.toThrow();
|
|
111
|
-
});
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
**Other instances** (grep cross-cutting):
|
|
115
|
-
|
|
116
|
-
- `tests/bases.test.ts:163` — `rejects.toThrow()` on path traversal (should match `/escapes vault root/`)
|
|
117
|
-
- `tests/canvas.test.ts:168` — same
|
|
118
|
-
- `tests/pdf.test.ts:111,274,281` — should match `/PDF/` or `/missing/` or `/excluded/`
|
|
119
|
-
- `tests/ocr.test.ts:45,54,63,76,77,97` — should match `/path is required/` (already used at line 39!), `/missing/`, `/excluded/`, or `/invalid PDF/`
|
|
120
|
-
- `tests/watcher.test.ts:108` — should match `/excluded/`
|
|
121
|
-
- `tests/write.test.ts:343` — should match `/not found/` or `/Source/`
|
|
122
|
-
|
|
123
|
-
**Suggested class fix**: Add a `tests/lint.test.ts` invariant that fails if any `rejects.toThrow()` or `.toThrow()` is called with zero arguments. Alternative: ESLint rule `vitest/prefer-strict-equal` family + custom rule. (Vitest's own docs recommend always passing a matcher.)
|
|
124
|
-
|
|
125
|
-
**Suggested per-instance backfill**: Add a regex matching the actual error message to each of the 13 weak assertions. Mechanical edit — for each one, run the failing case manually to capture the message, then add `/excerpt/`. Estimated 30 minutes total.
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
### Finding L1-03 (Low)
|
|
130
|
-
|
|
131
|
-
**File**: 11 files across `src/` (non-tools)
|
|
132
|
-
**Class**: `@internal` marker discipline. `src/tools/*.ts` rigorously tags helpers used cross-module-but-not-public-API with `@internal` (17 uses across 3 files). Equivalent module-scoped utilities elsewhere — exported because `tests/` or sibling modules need them, but NOT part of the public `package.json#exports` surface — are not tagged.
|
|
133
|
-
**Description**: Per STABILITY.md, the public surface is defined by `package.json#exports` (which lists `./tool-registry`, `./tool-manifest`, `./server`, etc.). Many helpers exported from non-listed modules are de-facto private — `safeFts5Query` (used only by `fts5.ts` + tests), `chunkContent` (called by `server.ts` for embedding-sync), `cosineSim` (used by `embed-db.ts` + tests), `defaultEmbedDbFile` (mirrors `embedDbPath` in `tool-registry.ts`), `buildEmbedText` (re-exported through `index.ts` mainly for `tests/late-chunking.test.ts`). Without `@internal`, TypeDoc surfaces them as public API in `docs/api-reference/`, and downstream consumers might import them via the deep `./fts5` / `./embed-db` path (which is what `tests/no-internal-imports.test.ts` is built to prevent — but only at the project's own boundaries, not for external consumers).
|
|
134
|
-
|
|
135
|
-
**Evidence** (`src/fts5.ts:462` — `safeFts5Query` has helpful inline comment but no `@internal`):
|
|
136
|
-
|
|
137
|
-
```ts
|
|
138
|
-
// Quote-wrap any token containing non-alphanumerics so FTS5 doesn't interpret
|
|
139
|
-
// hyphens / colons / dots as operators (`claude-telegram` would otherwise
|
|
140
|
-
// parse as `claude NOT telegram`). Strip reserved keywords. Returns "" if the
|
|
141
|
-
// query has no usable tokens.
|
|
142
|
-
export function safeFts5Query(q: string): string {
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
Versus `src/tools/search.ts:417` (gold standard):
|
|
146
|
-
|
|
147
|
-
```ts
|
|
148
|
-
/**
|
|
149
|
-
* ...
|
|
150
|
-
* @internal
|
|
151
|
-
* @param text - Raw text to tokenize. Will be lowercased.
|
|
152
|
-
*/
|
|
153
|
-
export function tokenizeForTfidf(text: string): string[] {
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
**Other instances** (grep cross-cutting):
|
|
157
|
-
|
|
158
|
-
- `src/fts5.ts` — `safeFts5Query`, `chunkContent`, `defaultIndexFile` (used by `server.ts` + `tool-registry.ts` + tests; not in `package.json#exports`)
|
|
159
|
-
- `src/embeddings.ts` — `cosineSim`, `resolveModel`, `resolveRerankerModel` (test-only / cross-module helpers)
|
|
160
|
-
- `src/embed-db.ts` — `encodeInt8Vector`, `decodeInt8Vector`, `defaultEmbedDbFile` (test + module utilities)
|
|
161
|
-
- `src/server.ts` — `buildEmbedText`, `syncEmbedDb`, `syncPdfEmbedDb`, `syncFtsIndex`, `syncPdfFtsIndex` (re-exported via index.ts but only for CLI / test consumers)
|
|
162
|
-
- `src/tool-registry.ts` — `embedDbPath`, `encodeNotePath`, `decodeNotePath`, `textResult` (utility helpers)
|
|
163
|
-
- `src/vault.ts` — `globToRegex` (used by `tests/security.test.ts:16` and `tests/watcher.test.ts`; not part of public API)
|
|
164
|
-
- `src/parser.ts` — `extractInlineTags`, `extractFrontmatterTags`, `extractWikilinks`, `extractEmbeds` (called by `bases.ts` + `communities.ts` + write tools; arguably "internal but cross-module")
|
|
165
|
-
- `src/periodic.ts` — `resolvePeriodicNoteName`, `formatMoment`, `loadPeriodicConfig` (called from `vault.ts` + tools; not external API)
|
|
166
|
-
- `src/communities.ts` — `buildWikilinkGraph`, `detectCommunities` (called only by `tool-registry.ts`)
|
|
167
|
-
- `src/eval.ts` — `ndcgAtK`, `recallAtK`, `reciprocalRank`, `readQueriesJsonl`, `runEval`, `formatEvalResult`, `formatEvalMatrix` (only the CLI consumes them; not in package.json#exports)
|
|
168
|
-
- `src/http-transport.ts` — `RateLimiter`, `readJsonBody`, `verifyBearer`, `createSessionRegistry`, `createHttpHandler` (test exports, plus re-exports at file bottom)
|
|
169
|
-
|
|
170
|
-
**Suggested class fix**: Adopt the convention `if package.json#exports doesn't list this module path, every exported symbol from it carries @internal`. Enforce with a lint test that intersects `package.json#exports` paths against `src/**/*.ts` and flags any export from a non-listed file that lacks `@internal`. Pair with `typedoc.json#excludeInternal: true` so the public reference site only shows true public API.
|
|
171
|
-
|
|
172
|
-
**Suggested per-instance backfill**: Add `@internal` to each non-public export listed above (~30 symbols). Mechanical edit. Estimated 1 hour total. Validate by re-running `npm run docs:api` and confirming the rendered API reference no longer surfaces internal helpers.
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
### Finding L1-04 (Info)
|
|
177
|
-
|
|
178
|
-
**File**: all `tests/*.test.ts` collectively
|
|
179
|
-
**Class**: No tests are explicitly named or structured to exercise concurrent / race-condition paths, despite the project shipping several intrinsically concurrent components: persistent-cache flush under SIGINT/SIGTERM/beforeExit, watcher events firing during MCP tool calls, stateful HTTP sessions under `maxSessions` cap pressure, rate-limiter sliding-window under burst, idle-eviction sweep during active session, parallel `vault.listMarkdown()` walks during embedding sync, optional-dep lazy-load races during boot.
|
|
180
|
-
**Description**: The audit plan asks specifically about "concurrent access" coverage. Grepping for the word `concurrent` in tests yields zero hits; grepping `Promise.all` returns only a few cleanup loops (`tests/security.test.ts:110`). None of the tests structurally race two requests against the same handler / session / cache and assert correctness. This is INFO-level — no concrete regression spotted, but the audit reviewer should know that race-class bugs are not currently covered by the test suite. Examples of plausible races: (a) two HTTP requests on the same stateful session arriving before `onsessioninitialized` fires; (b) a watcher reindex landing exactly between `idx.diff()` and `idx.reindexFile()` in `syncFtsIndex`; (c) `vault.saveDiskCache()` racing with `vault.cacheSet()` (cache-dirty flag not protected); (d) `prepareServerDeps()` called twice from a script (the comment says "not designed to be called multiple times" — but there's no test that asserts what happens if it IS).
|
|
181
|
-
|
|
182
|
-
**Evidence**: see `src/server.ts:120` ("Idempotent on a per-call basis but NOT designed to be called multiple times in one process — the FTS5 sync would double-index. Stdio + HTTP each call this exactly once at startup."). No test verifies the double-call failure mode, and no test simulates concurrent stdio + HTTP startup against the same vault.
|
|
183
|
-
|
|
184
|
-
**Other instances** (grep cross-cutting): not applicable — this is an absence-of-coverage finding, not a code finding.
|
|
185
|
-
|
|
186
|
-
**Suggested class fix**: When a new patch touches a concurrent-sensitive path (cache flush, watcher event handling, session lifecycle, rate-limiter), include at least one test that calls the API twice in flight (`Promise.all` of two operations) and asserts the post-state. Document this expectation in `CONTRIBUTING.md` under a new "concurrency" section.
|
|
187
|
-
|
|
188
|
-
**Suggested per-instance backfill**: Add ~4 targeted tests over the next 2-3 patch releases — not a blocker for v3.6.1:
|
|
189
|
-
|
|
190
|
-
1. `tests/http-transport.test.ts` — two simultaneous initialize requests at `maxSessions = 1`; assert second gets 503.
|
|
191
|
-
2. `tests/watcher.test.ts` — fire a `change` event while a `vault.readNote()` is in flight on the same path; assert post-state is the new content.
|
|
192
|
-
3. `tests/persistent-cache.test.ts` — race `saveDiskCache()` with a `cacheSet()`; assert no data loss + flush idempotent.
|
|
193
|
-
4. `tests/embed-db.test.ts` — two `upsertNote` calls on the same `rel_path` in parallel; assert exactly one survives (transaction isolation).
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
## Files explicitly clean (no findings)
|
|
198
|
-
|
|
199
|
-
The following files were inspected and have no issues at this audit's bar:
|
|
200
|
-
|
|
201
|
-
- `src/cli-help.ts` — 3 exported constants, each with a clear TSDoc explaining what flag it's for and why it was hoisted (v3.5.12 audit context).
|
|
202
|
-
- `src/index.ts` — slim entry point; the file header is a model of clarity (audit context, sub-module map, version-const rationale).
|
|
203
|
-
- `src/cli.ts` — every `program.command(...)` invocation has a long-form `description()` explaining the subcommand's purpose; flags carry inline help strings.
|
|
204
|
-
- `src/tools/index.ts` — barrel re-export only.
|
|
205
|
-
- `src/tools/read.ts`, `src/tools/write.ts`, `src/tools/search.ts`, `src/tools/meta.ts`, `src/tools/media.ts` — gold standard; every export has TSDoc + `@param` + `@returns` + `@throws` + `@example`; internal helpers carry `@internal`.
|
|
206
|
-
- `src/prompts.ts` — every prompt has a leading TSDoc explaining the use case + args + example invocation.
|
|
207
|
-
- `src/tool-manifest.ts` — the file header explains its purpose as machine-readable single-source-of-truth; each entry has a 1-line `summary` field.
|
|
208
|
-
- `tests/setup.ts` — well-documented warmup file explaining the v3.5.6 timeout-flake root cause.
|
|
209
|
-
- `tests/helpers/make-pdf.ts` — module header lists exactly what's covered vs not.
|
|
210
|
-
- `tests/fixtures/benchmark-queries.jsonl` — comment header documents the schema + category taxonomy.
|
|
211
|
-
- `tests/reranker-smoke.test.ts` — the one skipped test in the suite, fully documented with rationale.
|
|
212
|
-
|
|
213
|
-
All other test files have specific test names (mean test description ~60 chars), exercise edge cases (empty input, oversized input, missing path, malformed YAML, schema mismatch), and assert error message regexes (55 strong assertions) — well above the bar.
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
# L2 — Architecture (v3.6.0 audit)
|
|
2
|
-
|
|
3
|
-
**Scope**: module dependency graph; `package.json#exports` resolution; `TOOL_MANIFEST` ↔ `registerTool()` ↔ `kind`/gating reality; `registerPrompt()` ↔ README + STABILITY; CLI flag ↔ handler ↔ `docs/api.md` ↔ `ServeOptions` mapping.
|
|
4
|
-
**Auditor**: sub-agent C2.
|
|
5
|
-
**Date**: 2026-05-15.
|
|
6
|
-
**Baseline**: 30 `src/**/*.ts` modules; `npm run build` clean; `dist/` present; 713 tests pass; 44 `TOOL_MANIFEST` entries; 44 `registerTool()` calls; 19 `registerPrompt()` calls; 7 `package.json#exports` sub-paths (+ `.` and `./package.json`).
|
|
7
|
-
|
|
8
|
-
## Summary
|
|
9
|
-
|
|
10
|
-
The architecture is in good shape. Module dependency graph is shallow and intentional (a `VERSION` hub via `index.ts` + a tightly-coupled `tools/*` peer cluster); both detected cycle classes are runtime-safe (cycled imports are only referenced inside function bodies). All 44 `TOOL_MANIFEST` entries match `registerTool()` calls exactly, and every tool's `kind` matches its registration context (read/write/fts/diagnostic). All 19 prompts are documented in both README and STABILITY. `package.json#exports` resolves cleanly to existing `dist/` files. `ServeOptions` ↔ CLI flag mapping is bidirectional and complete for stdio `serve`.
|
|
11
|
-
|
|
12
|
-
Findings cluster into 4 classes (1 medium, 2 low, 1 info):
|
|
13
|
-
|
|
14
|
-
1. **L2-01 (Medium)** — `serve-http` is missing 8 retrieval-quality / PDF / HNSW / late-chunking flags that `serve` accepts and that `docs/api.md` claims work for both transports. Passing `--use-hnsw` (or any of the others) to `serve-http` is rejected by commander.
|
|
15
|
-
2. **L2-02 (Low)** — drift class: `obsidian_full_text_search` is registered ONLY when both `--persistent-index` AND `--diagnostic-search-tools` are set (per `server.ts:402`), but four user-facing strings claim only `--persistent-index` is required (tool description in `tool-registry.ts:63`, `docs/api.md:3`, `docs/api.md:820–822`, `STABILITY.md:19`). The two places that get it right are `tool-manifest.ts:47` and `docs/api.md:55`.
|
|
16
|
-
3. **L2-03 (Low)** — 7 circular dependencies in the module graph. None break runtime (cycled symbols are only referenced inside function bodies) and the cycles are intentional (`VERSION` re-export hub + `tools/*` peer cluster). No regression guard exists, so a future top-level use of a cycled symbol would break loading and not be caught by CI.
|
|
17
|
-
4. **L2-04 (Info)** — `--reranker-model <alias>` and `--reranker-top-n <n>` are valid `serve` flags but appear nowhere in `docs/api.md` (the canonical API ref). Only `STABILITY.md:63` mentions `--reranker-model`. Real flags, real defaults, real behavior — just undocumented in the api ref.
|
|
18
|
-
|
|
19
|
-
No Critical / High findings. All 7 `package.json#exports` resolve to existing `dist/*.{js,d.ts}` files. The slim re-export surface at `src/index.ts` keeps the v3.5.x public API stable through the rc.2 split. All `tools/*` symbols imported by `tool-registry.ts` exist in `dist/tools/index.js`. The 19-prompt count is exact across `src/prompts.ts`, `README.md:154`, and `STABILITY.md:33`. The 44-tool kind/gating split is exact across `src/tool-manifest.ts` and the actual registration sites.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
### Finding L2-01 (Medium)
|
|
24
|
-
|
|
25
|
-
**File**: `src/cli.ts:122–177` (the `serve-http` command definition), `docs/api.md:102,110` (the contradicting claim), `src/http-transport.ts:41–102` (`HttpServeOptions extends ServeOptions`).
|
|
26
|
-
**Class**: CLI surface drift between `serve` and `serve-http` (commander `.option()` chains for the same `HttpServeOptions extends ServeOptions` type are out of sync — flags were added to `serve` after v2.8.0 but never back-ported to `serve-http`).
|
|
27
|
-
**Description**: `serve-http` (the remote-MCP transport) is missing eight `.option()` declarations that `serve` (stdio) has and that `docs/api.md` says both transports share. Because commander uses `.option()` chains, not type-driven CLI generation, the typed `HttpServeOptions extends ServeOptions` interface is honored at compile time but irrelevant at parse time — commander parses positionally from `.option()` declarations alone. Result: a `serve-http --use-hnsw` invocation fails fast with `error: unknown option '--use-hnsw'` despite `HttpServeOptions` having a `useHnsw?: boolean` field via `ServeOptions`.
|
|
28
|
-
|
|
29
|
-
**Evidence** (commander runtime rejection):
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
$ enquire-mcp serve-http --vault /tmp/foo --bearer-token <…32+ chars…> --use-hnsw
|
|
33
|
-
error: unknown option '--use-hnsw'
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Missing flags (each one is in `serve` at `src/cli.ts:42–117` but absent from `serve-http` at `src/cli.ts:122–177`):
|
|
37
|
-
|
|
38
|
-
| Flag | In `serve` | In `serve-http` | Effect in `ServeOptions` |
|
|
39
|
-
|---|---|---|---|
|
|
40
|
-
| `--include-pdfs` | `cli.ts:77` | (missing) | `includePdfs?: boolean` — PDF blend into hybrid search |
|
|
41
|
-
| `--enable-reranker` | `cli.ts:81` | (missing) | `enableReranker?: boolean` — BGE cross-encoder reranking |
|
|
42
|
-
| `--reranker-model <alias>` | `cli.ts:85` | (missing) | `rerankerModel?: string` |
|
|
43
|
-
| `--reranker-top-n <n>` | `cli.ts:89` | (missing) | `rerankerTopN?: string` |
|
|
44
|
-
| `--use-hnsw` | `cli.ts:93` | (missing) | `useHnsw?: boolean` |
|
|
45
|
-
| `--hnsw-ef <n>` | `cli.ts:97` | (missing) | `hnswEf?: string` |
|
|
46
|
-
| `--late-chunk-context <chars>` | `cli.ts:101` | (missing) | `lateChunkContext?: string` |
|
|
47
|
-
| `--no-hnsw-persist` | `cli.ts:105` | (missing) | `hnswPersist?: boolean` (negation) |
|
|
48
|
-
|
|
49
|
-
Conflicting documentation:
|
|
50
|
-
|
|
51
|
-
- `docs/api.md:102` — _"v2.13.0 — `serve` / `serve-http` flags: `--use-hnsw` builds an in-memory HNSW vector index on serve start … `--hnsw-ef <n>` tunes search-time accuracy."_ — claims both transports support `--use-hnsw` / `--hnsw-ef`.
|
|
52
|
-
- `docs/api.md:106` — _"v2.15.0 — `--late-chunk-context <chars>` on `serve` and `build-embeddings`."_ — at least concedes serve-http isn't included (so late-chunking is OK), but the v2.13.0 line above is wrong.
|
|
53
|
-
- `docs/api.md:108` — _"v2.16.0 — `--no-hnsw-persist`"_ — implies it applies to "when `--use-hnsw` is passed", which is documented as both transports.
|
|
54
|
-
- `docs/api.md:110` — _"v2.17.0 — `--quantize-embeddings <mode>` on `serve`, `serve-http`, `build-embeddings`, and `setup`."_ — `--quantize-embeddings` IS present on serve-http at `cli.ts:174–177`, so this one is correct. But it's the only late-feature flag that was actually back-ported.
|
|
55
|
-
- `src/http-transport.ts:38` — comment header: _"Extends ServeOptions so every stdio-mode flag (`--enable-write`, `--persistent-index`, `--watch`, etc.) is available over HTTP too."_ — overstates: HTTP transport's TYPE has every field, but the CLI surface drops 8 of them.
|
|
56
|
-
|
|
57
|
-
**Other instances** (grep cross-cutting):
|
|
58
|
-
|
|
59
|
-
- `README.md:62` — quickstart command is `enquire-mcp serve --vault <path> --persistent-index --enable-reranker --use-hnsw` — uses `serve`, doesn't claim it works for `serve-http`. Clean.
|
|
60
|
-
- `STABILITY.md:55–62` — lists CLI surface; doesn't claim serve-http parity beyond the shared serve flags. Clean.
|
|
61
|
-
- `docs/http-transport.md` — not inspected here (deferred to L6). If it claims `--use-hnsw` etc. work via serve-http, this same finding will surface there.
|
|
62
|
-
|
|
63
|
-
**Suggested class fix**: One of:
|
|
64
|
-
1. **Back-port the 8 missing `.option()` calls to `serve-http`** in `src/cli.ts` (line ~177, right before the `.action()`). Mechanical edit — copy-paste the 8 lines from the serve block. Then add an invariant test in `tests/cli.test.ts` that does `program._findCommand('serve').options` ∩ `program._findCommand('serve-http').options` and asserts every shared option (everything except `--port` / `--host` / `--bearer-token` / `--bearer-token-env` / `--mcp-path` / `--rate-limit` / `--cors-origin` / `--health-path` / `--stateful` / `--session-idle-timeout-ms` / `--max-sessions`) is present in both. This prevents future drift.
|
|
65
|
-
2. **Or factor the shared options into a helper** that takes a `Command` and chains the 23 `serve`/`serve-http` shared flags. Eliminates copy-paste drift entirely. `commander` v14 supports `Command.copyInheritedSettings` but not a clean shared-options pattern; a free function returning the chained command is the idiom. About a 30-line refactor of `cli.ts`.
|
|
66
|
-
|
|
67
|
-
**Suggested per-instance backfill**: After the class fix, no per-instance backfill needed — both transports converge. If the choice is option (1) without the invariant test, monitor over the next 2 releases for regression.
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
### Finding L2-02 (Low)
|
|
72
|
-
|
|
73
|
-
**File**: `src/tool-registry.ts:63`, `docs/api.md:3,820,822`, `STABILITY.md:19`. The truth-source is `src/server.ts:402` + `src/tool-manifest.ts:47`.
|
|
74
|
-
**Class**: Drift between user-visible documentation and runtime gating logic for `obsidian_full_text_search`. The actual gate is `if (deps.ftsIndex && opts.diagnosticSearchTools) registerFtsTools(...)` — needs BOTH `--persistent-index` (which builds the FTS5 index → makes `deps.ftsIndex` non-null) AND `--diagnostic-search-tools`. Four downstream strings drop the second flag.
|
|
75
|
-
**Description**: The single-ranker FTS5 search tool was demoted to diagnostic in v2.0.0-beta.3 (along with `obsidian_search_text`, `obsidian_semantic_search`, `obsidian_embeddings_search`). The umbrella `obsidian_search` became default. The manifest knows this (kind: `fts`, gating: `--persistent-index + --diagnostic-search-tools`) and `docs/api.md:55` knows this (table row reads `--persistent-index (+ --diagnostic-search-tools)`). But 4 other locations describe the gating as `--persistent-index` alone — including the description string the tool returns over MCP, which is the most user-visible. Users following `docs/api.md:822` who start `enquire-mcp serve --vault X --persistent-index` will be confused when `obsidian_full_text_search` does not appear in `tools/list`.
|
|
76
|
-
|
|
77
|
-
**Evidence** (truth-source — `src/server.ts:402`):
|
|
78
|
-
|
|
79
|
-
```ts
|
|
80
|
-
if (deps.ftsIndex && opts.diagnosticSearchTools) registerFtsTools(server, deps.ftsIndex, deps.vault);
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Conflicting strings (each says or implies just `--persistent-index`):
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
|
-
// src/tool-registry.ts:63 — returned to every MCP client in tools/list:
|
|
87
|
-
"… Use `obsidian_search_text` instead if the index isn't built yet — this tool is only registered when the server is started with `--persistent-index`."
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
```md
|
|
91
|
-
<!-- docs/api.md:3 — the intro paragraph: -->
|
|
92
|
-
… the 4 opt-ins are: 1 via `--persistent-index` (`obsidian_full_text_search`) + 3 via `--diagnostic-search-tools` …
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
```md
|
|
96
|
-
<!-- docs/api.md:820: -->
|
|
97
|
-
## `obsidian_full_text_search` _(opt-in, requires `--persistent-index`)_
|
|
98
|
-
|
|
99
|
-
<!-- docs/api.md:822: -->
|
|
100
|
-
BM25-ranked full-text search over a SQLite FTS5 inverted index … Only registered when the server is started with `--persistent-index`; otherwise use `obsidian_search_text`.
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
```md
|
|
104
|
-
<!-- STABILITY.md:19: -->
|
|
105
|
-
**Read — opt-in via `--persistent-index` (1):** `obsidian_full_text_search`.
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Two sources get it right (`docs/api.md:55`, `tool-manifest.ts:47`):
|
|
109
|
-
|
|
110
|
-
```md
|
|
111
|
-
| `obsidian_full_text_search` | read | `--persistent-index` (+ `--diagnostic-search-tools`) | BM25-ranked search … |
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
```ts
|
|
115
|
-
// tool-manifest.ts:45-48:
|
|
116
|
-
{
|
|
117
|
-
name: "obsidian_full_text_search",
|
|
118
|
-
kind: "fts",
|
|
119
|
-
gating: "--persistent-index + --diagnostic-search-tools",
|
|
120
|
-
summary: "BM25 full-text search backed by the SQLite FTS5 inverted index."
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
**Other instances** (grep cross-cutting): the four conflicting strings above. No other doc / test / code path describes the gating.
|
|
125
|
-
|
|
126
|
-
**Suggested class fix**: Two options, not mutually exclusive:
|
|
127
|
-
1. **Single source of truth, derived strings**: Have `src/tool-registry.ts` import the manifest entry for `obsidian_full_text_search` and use `TOOL_MANIFEST.find(t => t.name === '...').gating` to construct the description string. Then any future gating change updates the manifest once + the MCP description automatically. Same pattern works for `docs/api.md` — add a render step in `scripts/docs:api` (or in TypeDoc post-processing) that fills the gating column from the manifest.
|
|
128
|
-
2. **Tighten the docs-consistency test**: `tests/docs-consistency.test.ts` already checks tool surface coverage. Extend it to assert (for every manifest entry whose `gating` includes `--diagnostic-search-tools`) that the docs sections describing that tool mention BOTH flags. About 20 lines of test code. Catches manual drift even without a render-time fix.
|
|
129
|
-
|
|
130
|
-
**Suggested per-instance backfill**: 4 string edits:
|
|
131
|
-
- `src/tool-registry.ts:63` — change "only registered when the server is started with `--persistent-index`" to "only registered when the server is started with `--persistent-index --diagnostic-search-tools`".
|
|
132
|
-
- `docs/api.md:3` — clarify the intro: "1 via `--persistent-index + --diagnostic-search-tools` …".
|
|
133
|
-
- `docs/api.md:820–822` — change `_(opt-in, requires `--persistent-index`)_` to `_(opt-in, requires `--persistent-index --diagnostic-search-tools`)_`; same fix in line 822 body text.
|
|
134
|
-
- `STABILITY.md:19` — change "opt-in via `--persistent-index` (1)" to "opt-in via `--persistent-index --diagnostic-search-tools` (1)" or restructure (move it to the diagnostic-search-tools section at line 23).
|
|
135
|
-
|
|
136
|
-
Estimated 15 minutes for the per-instance fix, 1 hour for the class fix (test extension).
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
### Finding L2-03 (Low)
|
|
141
|
-
|
|
142
|
-
**File**: 8 modules participate in the 7 detected cycles: `src/index.ts`, `src/cli.ts`, `src/server.ts`, `src/tool-registry.ts`, `src/http-transport.ts`, `src/tools/meta.ts`, `src/tools/read.ts`, `src/tools/search.ts`, `src/tools/write.ts`.
|
|
143
|
-
**Class**: Module dependency cycles. ESM tolerates them when cycled symbols are only referenced inside function bodies (the bindings get hoisted and bind by the time the cycled function is called), but they're a smell: they make module-load order brittle, complicate refactoring (extracting a symbol can break loading), and any future move from inside-function to top-level usage of a cycled symbol introduces a `ReferenceError: Cannot access '<symbol>' before initialization` at load time that no current test would catch.
|
|
144
|
-
**Description**: `npx madge --circular --extensions ts src/` reports 7 cycles. They fall into 2 classes:
|
|
145
|
-
|
|
146
|
-
**Class A — `VERSION` re-export hub** (4 cycles, all transitive through `src/index.ts`):
|
|
147
|
-
```
|
|
148
|
-
1. cli.ts → http-transport.ts → index.ts (→ cli.ts via index re-export of main)
|
|
149
|
-
2. index.ts → server.ts (→ index.ts via server's `import { VERSION } from "./index.js"`)
|
|
150
|
-
3. index.ts → server.ts → tool-registry.ts (→ index.ts via tool-registry's `import { VERSION }`)
|
|
151
|
-
4. server.ts → tool-registry.ts (→ server.ts via tool-registry's `import type { ServerDeps }`)
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
Class A root cause: `VERSION = "3.6.0"` lives in `src/index.ts:37` (single source of truth so `scripts/check-version-consistency.mjs` can grep one file), and three modules import it: `cli.ts:6`, `server.ts:7`, `tool-registry.ts:5`. Combined with the v3.6.0-rc.2 split (`src/index.ts:47–57` re-exports `main`, `buildEmbedText`, `buildMcpServer`, `formatReadyBanner`, `prepareServerDeps`, `ServeOptions`, `ServerDeps`, `startServer`, `parsePositiveInt`, `parseQuantizationMode` from `cli.ts`, `server.ts`, `tool-registry.ts` to keep the public surface stable), every cross-import creates a cycle through index.
|
|
155
|
-
|
|
156
|
-
**Class B — `tools/*` peer cluster** (3 cycles, all inside `src/tools/`):
|
|
157
|
-
```
|
|
158
|
-
5. tools/meta.ts → tools/read.ts (→ meta.ts via read's `import { findBestMatch, ... } from "./meta.js"`)
|
|
159
|
-
6. tools/meta.ts → tools/read.ts → tools/search.ts (→ meta.ts via search's `import { findBestMatch, ... } from "./meta.js"`)
|
|
160
|
-
7. tools/meta.ts → tools/read.ts → tools/search.ts → tools/write.ts (→ meta.ts via write's `import { findBestMatch, ... } from "./meta.js"`)
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Class B root cause: `tools/meta.ts` exports cross-tool helpers (`findBestMatch`, `stripMd`, `jaccard`, `intersectionSize`, `ngrams`, `normalizeTag`, `indexFor`) used by `read.ts`, `search.ts`, `write.ts`; and `meta.ts` calls into `read.ts` (`getRecentEdits`), `search.ts` (`searchHybrid`), `write.ts` (`resolveTarget`, `suggestSimilar`) for its higher-level tools (`contextPack`, `paperAudit`). So they import each other, and `tools/meta.ts` is both a "leaf" (helpers) and an "aggregator" (multi-tool composer).
|
|
164
|
-
|
|
165
|
-
**Evidence — confirmed runtime-safe** (no cycled symbol is referenced at module-load time):
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
$ grep -nE "^(const|let|var|export const|export let|export var)\s+\w+\s*=\s*(findBestMatch|stripMd|jaccard|intersectionSize|ngrams|searchHybrid|resolveTarget|sliceSnippet|extractFrontmatterTagsLower|normalizeTag|listTags|getRecentEdits|getBacklinks|suggestSimilar|VERSION)" /Users/alex/Documents/Projects/obsidian-mcp/src/**/*.ts
|
|
169
|
-
# (empty)
|
|
170
|
-
|
|
171
|
-
$ node -e "import('./dist/index.js').then(m => console.log('VERSION:', m.VERSION))"
|
|
172
|
-
VERSION: 3.6.0
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
All 7 cycles load cleanly. All 713 tests pass. So this is a smell, not a bug.
|
|
176
|
-
|
|
177
|
-
**Other instances** (grep cross-cutting): not applicable — this finding IS the cross-cutting view.
|
|
178
|
-
|
|
179
|
-
**Suggested class fix**: Two options:
|
|
180
|
-
1. **Add a circular-dep invariant test**: `tests/no-circular-deps.test.ts` — invoke madge programmatically (`import madge from 'madge'; const result = await madge('src/', { fileExtensions: ['ts'] }); expect(result.circular()).toEqual([])` OR allow exactly the current 7 cycles and snapshot them). This catches NEW cycles introduced in PRs but tolerates the existing 2 classes. About 30 lines.
|
|
181
|
-
2. **Eliminate Class A** by extracting `VERSION` to its own micro-module (`src/version.ts`) that nobody else imports from. `src/index.ts`, `cli.ts`, `server.ts`, `tool-registry.ts` would all import from `./version.js` directly. Removes 4 of the 7 cycles. Trade-off: `scripts/check-version-consistency.mjs` regex must be updated to grep `src/version.ts` instead of `src/index.ts`. About 10 lines of refactor + 1 script change.
|
|
182
|
-
3. **Eliminate Class B** by extracting the cross-tool helpers (`findBestMatch`, `stripMd`, `jaccard`, `intersectionSize`, `ngrams`, `normalizeTag`, `indexFor`) from `tools/meta.ts` into a new `tools/_shared.ts`. Then `tools/meta.ts` becomes a pure aggregator (importing from peers but not exporting to them). Eliminates the 3 tools cycles. About a 50-line refactor.
|
|
183
|
-
|
|
184
|
-
Option (1) is the lowest-cost: keep the cycles, prevent new ones. Options (2) and (3) are nice-to-haves but not blocking.
|
|
185
|
-
|
|
186
|
-
**Suggested per-instance backfill**: not applicable — depends on which class fix is taken.
|
|
187
|
-
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
### Finding L2-04 (Info)
|
|
191
|
-
|
|
192
|
-
**File**: `docs/api.md` (canonical API reference) is missing two real CLI flags: `--reranker-model <alias>` and `--reranker-top-n <n>`.
|
|
193
|
-
**Class**: Doc completeness drift. The flags exist (`src/cli.ts:85–92` for `serve`, `src/cli.ts:580–581` for `eval`) and are honored at runtime (`src/server.ts` consumes `rerankerModel` + `rerankerTopN` from `ServeOptions`; `eval` consumes from its own opts). But they're not in the canonical CLI reference. A user reading `docs/api.md` end-to-end will not learn that the reranker model is configurable (`rerank-multilingual` is just one of 5 aliases — `rerank-bge`, `rerank-bge-large`, `rerank-jina-tiny`, `rerank-multilingual-large`) or how many candidates get reranked.
|
|
194
|
-
**Description**: `docs/api.md` describes `--enable-reranker` narratively in the line-3 intro and in section header "v2.9.0+ adds BGE cross-encoder reranking", but stops there. The serve-flag table at `docs/api.md:75–89` doesn't include any reranker flag (even `--enable-reranker` itself is missing from the table). The eval subcommand row at line 100 mentions `[--reranker]` but not `[--reranker-model]` / `[--reranker-top-n]`.
|
|
195
|
-
|
|
196
|
-
**Evidence**: `grep -nE "reranker-model|reranker-top-n" docs/api.md README.md` returns 0 matches. Only `STABILITY.md:63` mentions `--reranker-model` (it's the alias-stability promise). `docs/api-reference/` (TypeDoc-generated) has `rerankerModel` + `rerankerTopN` because they're `ServeOptions` fields — but that's not the place a CLI user looks.
|
|
197
|
-
|
|
198
|
-
**Other instances** (grep cross-cutting): same class as L2-01 (drift between `cli.ts` and `docs/api.md`), but L2-01 is about flag REJECTION (real bug); this is about flag UNDOCUMENTED (real gap). Other potentially undocumented flags worth a sweep when fixing this one: `--hnsw-ef` (described in narrative para at line 102 ✅), `--late-chunk-context` (line 106 ✅), `--no-hnsw-persist` (line 108 ✅), `--cors-origin` (mentioned in serve-http row at line 96 ✅), `--health-path` (line 96 ✅), `--mcp-path` (line 96 ✅). So this is JUST the 2 reranker tuning flags.
|
|
199
|
-
|
|
200
|
-
**Suggested class fix**: Add a docs-consistency invariant test that asserts every flag declared in `src/cli.ts` (regex `\.option\("(--[a-z][a-z-]+)`) appears at least once in `docs/api.md`. About 15 lines, similar shape to the existing tool-surface-coverage assertion in `tests/docs-consistency.test.ts`. Would catch future flag drift automatically.
|
|
201
|
-
|
|
202
|
-
**Suggested per-instance backfill**: Add a row (or a paragraph similar to the v2.13.0 narrative at line 102) covering `--reranker-model <alias>` and `--reranker-top-n <n>` in `docs/api.md`. Mention the 5 alias options + their size/quality trade-offs (already documented in `src/cli.ts:86–88` description string). About 5 lines.
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
## Files explicitly clean (no findings)
|
|
207
|
-
|
|
208
|
-
The following architecture surfaces were inspected and have no issues:
|
|
209
|
-
|
|
210
|
-
- **`package.json#exports`** — all 7 sub-paths (`.`, `./embed-db`, `./fts5`, `./vault`, `./hnsw`, `./bases`, `./communities`, `./package.json`) resolve to existing `dist/*.{js,d.ts}` files. Each subpath import loads cleanly (`embed-db` exports `EmbedDb` / `defaultEmbedDbFile` / `encodeInt8Vector` / `decodeInt8Vector`; `fts5` exports `FtsIndex` / `chunkContent` / `defaultIndexFile` / `safeFts5Query`; etc.). The slim `src/index.ts` re-export hub (`main`, `buildMcpServer`, `buildEmbedText`, `formatReadyBanner`, `prepareServerDeps`, `startServer`, `parsePositiveInt`, `parseQuantizationMode`, `ServeOptions`, `ServerDeps`, `VERSION`) preserves the v3.5.x public surface through the rc.2 module split.
|
|
211
|
-
|
|
212
|
-
- **`TOOL_MANIFEST` ↔ registration**: 44 manifest entries, 44 `server.registerTool()` calls, exact name-set equality (`Only in TOOL_MANIFEST (not in registry): [] / Only in registry (not in TOOL_MANIFEST): []`). Per-tool `kind` matches registration context: 1 fts (`registerFtsTools`), 33 read (`registerReadTools` outside the `if (diagnosticSearchTools)` block), 3 diagnostic (`registerReadTools` inside the `if (diagnosticSearchTools)` block), 7 write (`registerWriteTools`). Per-tool `gating` field exactly describes the runtime gate: `always` (33), `--diagnostic-search-tools` (3), `--persistent-index + --diagnostic-search-tools` (1), `--enable-write` (7) — see L2-02 for the one drift in the description strings.
|
|
213
|
-
|
|
214
|
-
- **`registerPrompt()` ↔ README + STABILITY**: 19 `server.registerPrompt()` calls in `src/prompts.ts`; 19 names in `README.md:154` (single `MCP prompts (...)` paragraph); 19 names in `STABILITY.md:33`. Set-equal across all three. (Detected names: `summarize_recent_edits`, `review_tag`, `find_orphans`, `weekly_review`, `extract_todos`, `process_inbox`, `consolidate_tags`, `find_duplicates`, `lint_wiki`, `monthly_review`, `search_with_query_expansion`, `vault_synth`, `vault_wiki_compile`, `vault_lint_extended`, `vault_capture`, `vault_persona_search`, `vault_automation_setup`, `vault_research`, `vault_synthesis_page`.)
|
|
215
|
-
|
|
216
|
-
- **`ServeOptions` ↔ CLI flag mapping** (bidirectional): 23 `ServeOptions` keys (`vault`, `enableWrite`, `maxFileBytes`, `cacheSize`, `persistentCache`, `cacheFile`, `persistentIndex`, `indexFile`, `tokenize`, `excludeGlob`, `readPaths`, `watch`, `disabledTools`, `enabledTools`, `diagnosticSearchTools`, `includePdfs`, `enableReranker`, `rerankerModel`, `rerankerTopN`, `useHnsw`, `hnswEf`, `lateChunkContext`, `hnswPersist`, `quantizeEmbeddings`) each map cleanly to a `serve`-command CLI flag (the negation `--no-hnsw-persist` → `hnswPersist` is the one camelCase exception, handled correctly by commander). Every `ServeOptions` key is referenced from at least 2 of `src/server.ts` / `src/cli.ts` / `src/http-transport.ts` / `src/tool-registry.ts` — none are dead.
|
|
217
|
-
|
|
218
|
-
- **CLI option handlers**: every `.option(...)` on every command (`serve`, `serve-http`, `gen-token`, `clear-cache`, `clear-index`, `index`, `install-model`, `build-embeddings`, `clear-embeddings`, `doctor`, `setup`, `eval`) is consumed by the corresponding `.action(opts => …)` handler. Spot-checked all 12 commands. No orphan flags.
|
|
219
|
-
|
|
220
|
-
- **Module graph shape**: shallow; max chain length ≤ 4 (`cli.ts → server.ts → tool-registry.ts → tools/index.ts → tools/*.ts`). `vault.ts` has the highest fan-in (14 importers) — it's the right hub (parsed-note cache + privacy filter + path resolution). `fts5.ts` (8 fan-in), `embed-db.ts` (4), `embeddings.ts` (4), `parser.ts` (4) are the right next-tier shared utilities. `tool-manifest.ts` has 0 fan-in inside `src/` by design — it's a spec file consumed only by `tests/docs-consistency.test.ts`. No suspicious orphans.
|
|
221
|
-
|
|
222
|
-
- **`tools/*` exports vs consumption**: 57 named exports from `src/tools/index.ts` (re-exports from `media.ts` + `meta.ts` + `read.ts` + `search.ts` + `write.ts`). 38 are imported by `src/tool-registry.ts` (the public tool handlers). The remaining 19 (`buildTfidfIndex`, `composeNote`, `extractFrontmatterTagsLower`, `findBestMatch`, `indexFor`, `intersectionSize`, `jaccard`, `ngrams`, `normalizeTag`, `pickEmbedTextForHyde`, `replaceStringOutsideCodeFences`, `resolvePeriodicAlias`, `resolveTarget`, `rewriteOutsideCodeFences`, `rewriteRawTarget`, `sliceSnippet`, `stripMd`, `suggestSimilar`, `tokenizeForTfidf`) are internal cross-module helpers used by sibling `tools/*` files — re-exported by `tools/index.ts` because `export * from` doesn't discriminate `@internal`. This intersects with L1-03 (`@internal` discipline) — adding `@internal` tags wouldn't hide them from JS consumers but would hide them from TypeDoc.
|
|
223
|
-
|
|
224
|
-
- **`tools/index.ts` cycle safety**: `export * from` chains through all 5 leaf files; despite cycles in the underlying graph, `import('./tools/index.js')` loads cleanly with 57 keys present.
|
|
225
|
-
|
|
226
|
-
- **`src/index.ts` CLI-entry guard**: realpath-comparison still functional; `if (isCliEntry) main().catch(...)` still gated. Module loads cleanly when imported as a library (no main() invoked); CLI-mode triggers correctly.
|
|
227
|
-
|
|
228
|
-
- **No dead code branches**: `grep -E "TODO|FIXME"` returns ~30 hits across `src/`, all of them descriptive context (e.g. "TODO: defer to v3.7" / "FIXME if multi-process needed") — none indicate broken / abandoned scaffolding. No `// @ts-expect-error` without explanation.
|
|
229
|
-
|
|
230
|
-
## Verification commands
|
|
231
|
-
|
|
232
|
-
```bash
|
|
233
|
-
cd /Users/alex/Documents/Projects/obsidian-mcp
|
|
234
|
-
npx madge --circular --extensions ts src/ # 7 cycles, all runtime-safe
|
|
235
|
-
npx madge --extensions ts --json src/ > /tmp/deps.json # dependency graph for analysis
|
|
236
|
-
node -e "import('./dist/index.js').then(m => console.log(Object.keys(m)))" # public surface
|
|
237
|
-
for sp in embed-db fts5 vault hnsw bases communities; do # all subpath exports
|
|
238
|
-
node -e "import('./dist/$sp.js').then(m => console.log('$sp:', Object.keys(m).length))"
|
|
239
|
-
done
|
|
240
|
-
node -e "import('./dist/tool-manifest.js').then(m => console.log(m.TOOL_MANIFEST.length))" # 44
|
|
241
|
-
grep -cE "server\.registerTool\(" src/tool-registry.ts # 44
|
|
242
|
-
grep -cE "server\.registerPrompt\(" src/prompts.ts # 19
|
|
243
|
-
enquire-mcp serve --help | grep -oE '^\s+--[a-z][a-z-]+' | sort -u | wc -l # 24 (serve flags, incl. --vault)
|
|
244
|
-
enquire-mcp serve-http --help | grep -oE '^\s+--[a-z][a-z-]+' | sort -u | wc -l # 27 (24 + 11 HTTP-only − 8 missing; see L2-01)
|
|
245
|
-
```
|