@oomkapwn/enquire-mcp 3.6.0 → 3.6.2
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 +185 -0
- package/README.md +6 -6
- package/assets/social-preview.png +0 -0
- package/dist/bases.d.ts +13 -3
- package/dist/bases.d.ts.map +1 -1
- package/dist/bases.js +64 -9
- package/dist/bases.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +12 -8
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +10 -2
- package/dist/doctor.js.map +1 -1
- package/dist/dql.d.ts +67 -0
- package/dist/dql.d.ts.map +1 -1
- package/dist/dql.js +45 -0
- package/dist/dql.js.map +1 -1
- package/dist/embed-db.d.ts +79 -0
- package/dist/embed-db.d.ts.map +1 -1
- package/dist/embed-db.js +87 -0
- package/dist/embed-db.js.map +1 -1
- package/dist/embeddings.d.ts +34 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +37 -1
- package/dist/embeddings.js.map +1 -1
- package/dist/fts5.d.ts +123 -0
- package/dist/fts5.d.ts.map +1 -1
- package/dist/fts5.js +130 -4
- package/dist/fts5.js.map +1 -1
- package/dist/hnsw.d.ts.map +1 -1
- package/dist/hnsw.js +11 -0
- package/dist/hnsw.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/parser.d.ts +80 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +48 -0
- package/dist/parser.js.map +1 -1
- package/dist/server.d.ts +34 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +45 -16
- package/dist/server.js.map +1 -1
- package/dist/tool-registry.js +1 -1
- package/dist/tool-registry.js.map +1 -1
- package/dist/tools/media.d.ts +1 -1
- package/dist/tools/media.js +1 -1
- package/dist/tools/meta.d.ts +2 -2
- package/dist/tools/meta.js +2 -2
- package/dist/tools/search.d.ts +32 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +51 -4
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/write.d.ts +2 -2
- package/dist/tools/write.js +2 -2
- package/dist/vault.d.ts +179 -0
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +157 -0
- package/dist/vault.js.map +1 -1
- package/docs/COMPARISON.md +5 -5
- package/docs/QUICKSTART.md +2 -2
- package/docs/api.md +11 -11
- package/docs/audits/findings/L1-code-quality.md +213 -0
- package/docs/audits/findings/L2-architecture.md +245 -0
- package/docs/audits/findings/L3-tests.md +339 -0
- package/docs/audits/findings/L4-cicd.md +290 -0
- package/docs/audits/findings/L5-security.md +350 -0
- package/docs/audits/findings/L6-documentation.md +347 -0
- package/docs/audits/findings/L7-operational.md +50 -0
- package/docs/audits/findings/L8-reproducibility.md +64 -0
- package/docs/audits/findings/L9-process.md +84 -0
- package/docs/audits/findings/baseline.json +19 -0
- package/docs/audits/v3.6.0-external-anonymous-audit.md +163 -0
- package/docs/audits/v3.6.0-final-audit.md +171 -0
- package/package.json +2 -2
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# L3 — Tests & Coverage Audit (v3.6.0)
|
|
2
|
+
|
|
3
|
+
**Audit date**: 2026-05-15
|
|
4
|
+
**Branch**: `v3.6.0/post-stable-audit`
|
|
5
|
+
**Package version**: `3.6.0` (latest)
|
|
6
|
+
**Reference**: `docs/audits/v3.6.0-system-audit-plan.md` §L3
|
|
7
|
+
|
|
8
|
+
## Summary
|
|
9
|
+
|
|
10
|
+
- Test count: **714** confirmed (713 passing + 1 documented env-gated skip). All 4 documented surfaces (README, package.json, social-preview.svg, CHANGELOG) agree.
|
|
11
|
+
- Per-file coverage: 6 files below 85% lines, 9 files below 75% branches, 6 files below 80% functions (excluding registration boilerplate and the barrel `tools/index.ts`). Most are external-dep or hard-to-reach error paths; one (`tools/index.ts` barrel) is a coverage-counter artifact.
|
|
12
|
+
- **Flake detection: 1 HIGH finding.** 3 npm-test runs produced 10, 11, 3 failures respectively; a 4th run with `--testTimeout=30000` produced 0 failures. Every "failure" is a 5000ms vitest default-timeout hit on `cli.test.ts` and `pdf.test.ts` and `ocr.test.ts` and `fts5.test.ts` tests that spawn child node processes or perform heavy disk/native work. **No deterministic test failures detected.**
|
|
13
|
+
- Snapshot integrity: no snapshot files exist (`tests/__snapshots__/` absent, no `toMatchSnapshot()` or `toMatchInlineSnapshot()` calls in any test file). NOT-APPLICABLE.
|
|
14
|
+
- Fixture freshness: `tests/fixtures/benchmark-queries.jsonl` (47 unique relevant paths) — every path exists in the synthetic vault built by `scripts/run-benchmarks.mjs`. Exact set match.
|
|
15
|
+
- Coverage thresholds: **branches threshold (74%) is THIN — actual 75.02%, margin +1.02pp.** Plan says ≥1pp is a flag-for-raise. Lines/statements/functions are safe.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Finding L3-01 — npm-test 5000ms timeouts cause non-deterministic failures under load
|
|
20
|
+
|
|
21
|
+
- **Severity**: HIGH
|
|
22
|
+
- **Class**: flake-prone test pattern — tests that spawn child processes or perform cold-load of native deps rely on vitest's default 5000ms per-test timeout. Under parallel test contention (multiple worker processes + concurrent disk I/O from other clones running test:coverage on the same machine) these blow past 5s and report as failures.
|
|
23
|
+
- **Evidence**:
|
|
24
|
+
- Run 1 (no override): `Test Files 2 failed | 30 passed | 1 skipped (33); Tests 10 failed | 703 passed | 1 skipped (714); Duration 169.21s`. Tail visible at `/private/tmp/claude-501/.../tasks/btl7fp0xp.output` shows `Error: Test timed out in 5000ms.` for `tests/cli.test.ts:277:3` and `tests/ocr.test.ts:82:3`.
|
|
25
|
+
- Run 2 (no override): `Test Files 3 failed | 29 passed | 1 skipped (33); Tests 11 failed | 702 passed | 1 skipped (714); Duration 200.26s`. Failures at `/tmp/L3-test-run-2.log`:
|
|
26
|
+
- `tests/cli.test.ts` lines 112, 187, 193, 203, 214, 225, 236, 263, 277 (9 distinct `it()` blocks)
|
|
27
|
+
- `tests/fts5.test.ts:171` (`chunkContent > heading parser is linear-time on pathological input (no polynomial-redos)` — `AssertionError: expected 649 to be less than 500`)
|
|
28
|
+
- `tests/pdf.test.ts:33` (`extractPdfText > extracts text from a single-page PDF`)
|
|
29
|
+
- Run 3 (no override): `Test Files 1 failed | 31 passed | 1 skipped (33); Tests 3 failed | 710 passed | 1 skipped (714); Duration 31.15s`. Failures at `/tmp/L3-test-run-3.log`:
|
|
30
|
+
- `tests/cli.test.ts:236, 263, 277` (3 distinct `it()` blocks)
|
|
31
|
+
- Run 4 with `--testTimeout=30000` (control): `Test Files 32 passed | 1 skipped (33); Tests 713 passed | 1 skipped (714); Duration 67.92s`. Zero failures. Output at `/private/tmp/claude-501/.../tasks/b243wxd6u.output`.
|
|
32
|
+
- Root cause: 14 `execFileSync(process.execPath, ...)` calls in `tests/cli.test.ts` (lines 120, 133, 180, 189, 195, 206, 217, 229, 240, 249, 267, 270, 281, 287) all spawn child node processes synchronously. On cold start with parallel native-dep imports, single-spawn cost has been observed at 4-15s. Vitest's default `testTimeout: 5000` is too tight.
|
|
33
|
+
- Secondary: `tests/fts5.test.ts:171` asserts `expect(elapsedMs).toBeLessThan(500)` on a heading parser perf bound — under load this slipped to 649ms (run 2). This is a perf-threshold flake, not a timeout flake.
|
|
34
|
+
- **Cited file:line**:
|
|
35
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/tests/cli.test.ts:112,187,193,203,214,225,236,263,277` — 9 `it()` blocks with no per-test timeout override
|
|
36
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/tests/fts5.test.ts:171` — perf bound
|
|
37
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/tests/pdf.test.ts:33` — cold pdfjs-dist load
|
|
38
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/tests/ocr.test.ts:82` — cold tesseract.js + canvas + pdfjs load
|
|
39
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/vitest.config.ts` — no `testTimeout` set in `test` block (defaults to 5000ms)
|
|
40
|
+
- **Class fix**:
|
|
41
|
+
1. Set a higher floor in `vitest.config.ts` for the suite-level `testTimeout` (e.g. `testTimeout: 15_000`). This is one line.
|
|
42
|
+
2. For the perf bound at `fts5.test.ts:171`, either widen the threshold (500 → 1500ms) or skip the bound under `process.env.CI === 'true'` with a separate marker test. Pre-fix polynomial was 1-2s; 1.5s preserves the regression-detection.
|
|
43
|
+
3. Document in `vitest.config.ts` comments why 15s is the floor — because of `execFileSync` cold-start and native-dep imports.
|
|
44
|
+
- **Per-instance backfill**: tests in `cli.test.ts` could also each set `, 30_000` as the test timeout argument (vitest 3rd param), but the suite-level config is simpler and refactor-resistant.
|
|
45
|
+
- **Recommended next action**: file as v3.6.1 patch. The flake is intermittent on dev laptops but worse on CI runners that share build hosts with other jobs — risk that 1-2 failures get rationalized as "macOS oddity" and ignored.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Finding L3-02 — coverage branches threshold within 1pp safety margin
|
|
50
|
+
|
|
51
|
+
- **Severity**: MEDIUM
|
|
52
|
+
- **Class**: threshold-vs-actual safety margin too tight; one regression test deletion drops below CI gate.
|
|
53
|
+
- **Evidence**: from `/Users/alex/Documents/Projects/obsidian-mcp/coverage/coverage-summary.json`:
|
|
54
|
+
```
|
|
55
|
+
Threshold vs Actual:
|
|
56
|
+
lines threshold=86 actual=89.20 margin=+3.20 [SAFE]
|
|
57
|
+
statements threshold=82 actual=85.79 margin=+3.79 [SAFE]
|
|
58
|
+
functions threshold=75 actual=82.15 margin=+7.15 [SAFE]
|
|
59
|
+
branches threshold=74 actual=75.02 margin=+1.02 [THIN]
|
|
60
|
+
```
|
|
61
|
+
Branches margin is +1.02pp — within the L3 plan's "<1pp" warn zone is borderline. v3.5.9 dropped branches threshold from 73→72 because local was at 72.94% (knife-edge against CI); v3.6.0 raised it back to 74 after the coverage uplift moved local to 75.29% (+1.3pp margin); the latest measurement at 75.02% leaves only +1.02pp.
|
|
62
|
+
- **Cited file:line**: `/Users/alex/Documents/Projects/obsidian-mcp/vitest.config.ts:46-51` (thresholds block) and `coverage-summary.json` `total.branches.pct = 75.02`.
|
|
63
|
+
- **Class fix**: per the plan, raise the threshold by 2pp where actual is within 1pp. Recommendation: keep `branches: 74` BUT add 8-12 new test cases targeting the 22 uncovered branch lines in `src/communities.ts` and the 12 uncovered branches in `src/watcher.ts` (both lower-effort than re-covering external-dep code). This brings actual to ~77% and creates >2pp margin without changing the threshold.
|
|
64
|
+
- **Per-instance backfill**: add tests for `communities.ts` `L83, L101, L107-L108, L116, L148, L164-L170, L174, L183-L186, L194, L208, L212, L216, L244, L255, L274, L280-L281` (22 uniq lines, 22 br instances) and `watcher.ts` `L38, L52, L74-L77, L95, L100-L101, L109, L121, L126-L128, L137` (12 uniq lines, 17 br instances). Together this is ~35 branches; covering half (~17) lifts total branch pct by ~0.6pp.
|
|
65
|
+
- **Recommended next action**: v3.6.2 — batched with other coverage uplifts.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Finding L3-03 — `src/embeddings.ts` 31.25% lines / 30.00% branches / 33.33% functions
|
|
70
|
+
|
|
71
|
+
- **Severity**: MEDIUM
|
|
72
|
+
- **Class**: external-dep code path — runtime functions require model download (~30-280 MB from HuggingFace) and are gated behind `ENQUIRE_LOAD_RERANKER_SMOKE=1`. The catalog/resolution layer IS covered (v3.5.11 added 40 tests); only the model-IO code is uncovered.
|
|
73
|
+
- **Evidence**: from `coverage-summary.json`:
|
|
74
|
+
```
|
|
75
|
+
embeddings.ts: lines=31.25 (covered/total ratio same shape as 392 source lines), branches=30.00 (12/40), functions=33.33
|
|
76
|
+
```
|
|
77
|
+
Uncovered branch lines (from `coverage/lcov.info`): L81, L87, L94, L121, L129, L141, L172, L179, L202, L359, L375.
|
|
78
|
+
Sample at `/Users/alex/Documents/Projects/obsidian-mcp/src/embeddings.ts:80-97`: `loadPipeline()` lazy dynamic import + clean-error fallback path — the success path requires `@huggingface/transformers` to actually load and run. L121-L143: `loadTransformersForRerank()` — same pattern. L358-L389: `score()` method that runs the reranker over batched pairs.
|
|
79
|
+
These are exercised by `tests/reranker-smoke.test.ts:36-85`, but that suite is `it.skip` unless `ENQUIRE_LOAD_RERANKER_SMOKE=1`.
|
|
80
|
+
- **Cited file:line**:
|
|
81
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/src/embeddings.ts:80-97` (loadPipeline)
|
|
82
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/src/embeddings.ts:117-144` (loadTransformersForRerank)
|
|
83
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/src/embeddings.ts:358-389` (score method)
|
|
84
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/tests/reranker-smoke.test.ts:36-85` (gated tests)
|
|
85
|
+
- **Classification**: (b) external-dep code paths — model download requires network + ~280 MB. Already documented in `reranker-smoke.test.ts:18-27` why this is gated.
|
|
86
|
+
- **Class fix**: not actionable unless CI gets a HuggingFace cache populated at build-image time. Existing approach (env-gated smoke + manual before major releases) is reasonable.
|
|
87
|
+
- **Recommended next action**: ACCEPT. Document the (intentional) low coverage in `vitest.config.ts` comments so a future auditor doesn't try to "fix" it. Optional: split `embeddings.ts` into `embeddings-runtime.ts` (uncoverable without model) + `embeddings-catalog.ts` (fully coverable) for cleaner per-file metrics.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Finding L3-04 — `src/ocr.ts` 33.33% lines / 24.00% branches / 45.45% functions
|
|
92
|
+
|
|
93
|
+
- **Severity**: MEDIUM
|
|
94
|
+
- **Class**: external-dep code path — requires Tesseract.js trained-data download + native canvas + pdfjs. Same pattern as embeddings.ts.
|
|
95
|
+
- **Evidence**: `coverage-summary.json` shows `ocr.ts`: lines=33.33, branches=24.00 (6/25), functions=45.45.
|
|
96
|
+
Uncovered branch lines: L75, L88, L101, L168, L219-L220, L241, L259, L270.
|
|
97
|
+
Sample at `/Users/alex/Documents/Projects/obsidian-mcp/src/ocr.ts:71-94`: `loadTesseract()` and `loadCanvas()` lazy-load with clean-error fallback. L97-L107: `loadPdfjs()` same pattern.
|
|
98
|
+
Existing test `tests/ocr.test.ts:82` exercises real load when all 3 deps install — but that's the test that timed out in run 1 (5000ms isn't enough for cold load).
|
|
99
|
+
- **Cited file:line**:
|
|
100
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/src/ocr.ts:71-107` (3 lazy-load functions)
|
|
101
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/src/ocr.ts:165-275` (extraction pipeline)
|
|
102
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/tests/ocr.test.ts:82` (only real-load test, timeout-prone)
|
|
103
|
+
- **Classification**: (b) external-dep code paths.
|
|
104
|
+
- **Class fix**: same as L3-03 — accept low coverage; document why. Bumping `testTimeout` per L3-01 will make the existing real-load test run reliably.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Finding L3-05 — `src/http-transport.ts` 79.91% lines / 66.86% branches / 58.97% functions
|
|
109
|
+
|
|
110
|
+
- **Severity**: LOW
|
|
111
|
+
- **Class**: hard-to-reach error paths — 500 internal server error handlers, session-id miss paths, max-sessions reached, transport.close() failure recovery.
|
|
112
|
+
- **Evidence**: `coverage-summary.json` shows `http-transport.ts`: lines=79.91, branches=66.86 (111/166), functions=58.97.
|
|
113
|
+
32 unique uncovered branch lines: L130, L188, L246, L313-L316, L336, L430, L438, L455, L471-L473, L511, L518, L523, L531-L533, L557, L571-L572, L607-L608, L612, L618, L630-L633, L639, L665-L671.
|
|
114
|
+
Sample uncovered branches: `L430-L444` (GET with no `Mcp-Session-Id` header → 400; with unknown session → 404; SSE transport error catch), `L511-L526` (server.connect failure path), `L527-L536` (outer-block catch-all 500), `L607-L612` (transport.close fallback), `L665-L671` (cors/rate-limit label formatting when both are disabled).
|
|
115
|
+
- **Cited file:line**:
|
|
116
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/src/http-transport.ts:430-445` (GET without session-id)
|
|
117
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/src/http-transport.ts:511-526` (initialize error)
|
|
118
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/src/http-transport.ts:527-536` (final safety net)
|
|
119
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/src/http-transport.ts:660-672` (banner formatting)
|
|
120
|
+
- **Classification**: (a) hard-to-reach error paths. Most are defensive `try { ... } catch (err) { write_error_log }` recovery paths that would require fault injection (mock transport.handleRequest to throw). Reachable via test-double, but cost/benefit is marginal for a v3.6.x patch.
|
|
121
|
+
- **Class fix**: existing pattern (test E2E via real `spawn()` at `tests/http-transport.test.ts:225`) covers happy-path well. Adding fault-injection coverage is a v3.7 candidate.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Finding L3-06 — `src/tools/search.ts` 80.89% lines / 69.75% branches / 70.00% functions
|
|
126
|
+
|
|
127
|
+
- **Severity**: LOW (single largest source file; ratio still acceptable)
|
|
128
|
+
- **Class**: genuinely undertested (c) — `search.ts` is 1565 lines, 52 unique uncovered branch lines spread across the file.
|
|
129
|
+
- **Evidence**: `coverage-summary.json` shows `tools/search.ts`: lines=80.89, branches=69.75 (196/281), functions=70.00.
|
|
130
|
+
Uncovered branch lines: L94, L118, L133, L239, L265, L447-L449, L457, L511, L522-L523, L528, L641, L652, L841, L845-L846, L857, L875, L880, L890, L895, L900, L925, L1156, L1182, L1208, L1213, L1237, L1254, L1269-L1271, L1295, L1320-L1325, L1354, L1362, L1375, L1380, L1384, L1411, L1424, L1450, L1475, L1483-L1488, L1505, L1515, L1557. (52 unique, 85 br instances.)
|
|
131
|
+
- **Cited file:line**: `/Users/alex/Documents/Projects/obsidian-mcp/src/tools/search.ts` — 52 line ranges as above.
|
|
132
|
+
- **Classification**: (c) genuinely undertested — `search.ts` houses the hybrid pipeline + reranker integration; some uncovered branches are around late-chunking flags, HyDE flags, reranker overrides, and result-merging edge cases (empty result set, single-source result, conflict resolution).
|
|
133
|
+
- **Class fix**: same as L3-02 — pair with v3.6.2 coverage uplift; target ~20 of the 52 uncovered lines for ~+0.7pp branches total.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Finding L3-07 — `src/tools/meta.ts` 80.93% lines / 67.66% branches / 70.96% functions
|
|
138
|
+
|
|
139
|
+
- **Severity**: LOW
|
|
140
|
+
- **Class**: genuinely undertested (c) — `meta.ts` is 1425 lines, 65 unique uncovered branch lines.
|
|
141
|
+
- **Evidence**: `coverage-summary.json` shows `tools/meta.ts`: lines=80.93, branches=67.66 (203/300), functions=70.96.
|
|
142
|
+
Uncovered branch lines: L114, L124-L127, L137-L143, L177-L179, L184, L215, L392, L404, L422-L426, L437, L449, L453, L484, L590, L597, L615, L693, L699, L723-L725, L846, L851, L887, L892-L893, L897, L920-L922, L932, L1103-L1107, L1118, L1126, L1135, L1147, L1151-L1155, L1165, L1169, L1173-L1176, L1225, L1273, L1316, L1357, L1362, L1367-L1373, L1381. (65 unique, 97 br instances.)
|
|
143
|
+
- **Cited file:line**: `/Users/alex/Documents/Projects/obsidian-mcp/src/tools/meta.ts` — 65 line ranges as above.
|
|
144
|
+
- **Classification**: (c) genuinely undertested — `meta.ts` covers metadata-related tools (frontmatter, tags, links, communities, periodic-notes, base files). Uncovered branches cluster around `L120-L185` (frontmatter parsing edge cases), `L420-L490` (link manipulation error paths), `L1100-L1180` (Bases query argument validation).
|
|
145
|
+
- **Class fix**: pair with v3.6.2 coverage uplift. Highest-ROI targets: `L137-L143` (7-line block, likely a single switch fall-through) and `L1367-L1373` (similar shape).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Finding L3-08 — `src/watcher.ts` 82.00% lines / 62.22% branches / 78.57% functions
|
|
150
|
+
|
|
151
|
+
- **Severity**: LOW
|
|
152
|
+
- **Class**: hard-to-reach error paths + concurrency races (a)+(c) — `watcher.ts` is only 142 lines but exercising chokidar event ordering deterministically is hard.
|
|
153
|
+
- **Evidence**: `coverage-summary.json` shows `watcher.ts`: lines=82.00, branches=62.22 (28/45), functions=78.57.
|
|
154
|
+
Uncovered branches: L38 (silent default), L52 (skip-dir match), L74-L77 (handle error catch), L95 (path safety), L100-L101 (no-ftsIndex branch), L109 (unlink branch), L121 (silent-flag), L126-L128 (read error catch), L137 (close idempotency).
|
|
155
|
+
- **Cited file:line**: `/Users/alex/Documents/Projects/obsidian-mcp/src/watcher.ts:38, 52, 74-77, 95, 100-101, 109, 121, 126-128, 137`.
|
|
156
|
+
- **Classification**: mostly (c). Many of these branches are reachable with a small additional test that pre-sets `silent: false` then triggers add/change/unlink events and asserts stderr output via `stderr` capture. ~30 lines of test code lifts branch pct from 62 → ~80.
|
|
157
|
+
- **Class fix**: targeted watcher.test.ts addition; recommended for v3.6.2.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Finding L3-09 — `tools/index.ts` 0.00% coverage (barrel artifact)
|
|
162
|
+
|
|
163
|
+
- **Severity**: INFO
|
|
164
|
+
- **Class**: coverage-counter artifact — `src/tools/index.ts` is 5 lines of `export * from "./media.js"` etc. (pure barrel). v8 coverage reports 0/0 because the file has no executable statements outside the imports. The barrel itself is excluded only because of the `**/*.test.ts` exclude in vitest.config.ts — it should be added to `src/tools/index.ts` or the brace-glob exclude pattern should add it.
|
|
165
|
+
- **Evidence**: `coverage-summary.json`:
|
|
166
|
+
```json
|
|
167
|
+
"tools/index": { "lines": {"total": 0, "covered": 0, "pct": 0}, ... }
|
|
168
|
+
```
|
|
169
|
+
`/Users/alex/Documents/Projects/obsidian-mcp/src/tools/index.ts:1-5`:
|
|
170
|
+
```
|
|
171
|
+
export * from "./media.js";
|
|
172
|
+
export * from "./meta.js";
|
|
173
|
+
export * from "./read.js";
|
|
174
|
+
export * from "./search.js";
|
|
175
|
+
export * from "./write.js";
|
|
176
|
+
```
|
|
177
|
+
- **Cited file:line**: `/Users/alex/Documents/Projects/obsidian-mcp/vitest.config.ts:35-38` — the exclude block uses `src/{index,cli,server,tool-registry,prompts,tool-manifest}.ts` which does NOT include `src/tools/index.ts`. So the v8 coverage tool reports it, but because it's a pure re-export, the metric is 0/0 = NaN%, displayed as 0%.
|
|
178
|
+
- **Classification**: presentation artifact, not a real coverage gap.
|
|
179
|
+
- **Class fix**: add `src/tools/index.ts` to the exclude pattern. One-line change:
|
|
180
|
+
```ts
|
|
181
|
+
exclude: [
|
|
182
|
+
"src/{index,cli,server,tool-registry,prompts,tool-manifest}.ts",
|
|
183
|
+
"src/tools/index.ts", // barrel re-export
|
|
184
|
+
"**/*.test.ts"
|
|
185
|
+
]
|
|
186
|
+
```
|
|
187
|
+
Or expand the glob: `"src/{index,cli,server,tool-registry,prompts,tool-manifest,tools/index}.ts"`.
|
|
188
|
+
- **Recommended next action**: include in v3.6.1 if patching anyway. Cosmetic but prevents future "tools/index has 0% coverage!" confusion.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Finding L3-10 — `src/pdf.ts` 58.33% branches (89.18% lines)
|
|
193
|
+
|
|
194
|
+
- **Severity**: LOW
|
|
195
|
+
- **Class**: external-dep + (a) hard-to-reach metadata branches.
|
|
196
|
+
- **Evidence**: `coverage-summary.json` shows `pdf.ts`: lines=89.18, branches=58.33 (14/24).
|
|
197
|
+
Uncovered branches: L94 (lazy-load fallback), L139, L168, L172-L177 (6-line block of metadata `typeof info.X === "string"` checks).
|
|
198
|
+
`/Users/alex/Documents/Projects/obsidian-mcp/src/pdf.ts:172-177`: subject, keywords, creator, producer, creationDate, modDate — all `typeof info.X === "string"` checks where test PDFs (synthesised via `pdf-lib` in `tests/helpers/make-pdf.ts`) only set `title`/`author`, not the rest. So the truthy-string branch never fires for those 5 metadata fields.
|
|
199
|
+
- **Cited file:line**: `/Users/alex/Documents/Projects/obsidian-mcp/src/pdf.ts:172-177`, `/Users/alex/Documents/Projects/obsidian-mcp/tests/helpers/make-pdf.ts` (PDF builder).
|
|
200
|
+
- **Classification**: (a) — easily reachable, just need a test PDF with all 8 metadata fields populated.
|
|
201
|
+
- **Class fix**: extend `tests/helpers/make-pdf.ts` to optionally accept all 8 metadata fields, then add one test PDF with all set + assert all 8 fields land in `extractPdfText().metadata`. ~10 lines, lifts branches from 58 → ~95.
|
|
202
|
+
- **Recommended next action**: v3.6.2 coverage uplift.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Finding L3-11 — fixture freshness verified (PASS)
|
|
207
|
+
|
|
208
|
+
- **Severity**: INFO
|
|
209
|
+
- **Evidence**:
|
|
210
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/tests/fixtures/benchmark-queries.jsonl` — 60 queries (q01-q60) reference 47 unique relevant paths.
|
|
211
|
+
- `/Users/alex/Documents/Projects/obsidian-mcp/scripts/run-benchmarks.mjs` — `VAULT_NOTES` object defines exactly the same 47 paths.
|
|
212
|
+
- `diff` between the two sorted unique-path sets returns 0.
|
|
213
|
+
- Path categories: `Reference/*.md` (32), `Projects/*.md` (6), `Inbox/*.md` (5), `Daily/*.md` (5), `INDEX.md` (1) — 47 total.
|
|
214
|
+
- **Status**: NO FINDING. Fixture is in sync with the synthetic-vault generator.
|
|
215
|
+
- **Notable**: the inline JSONL comment at line 6 explicitly states "When the vault layout changes the relevant-paths list here MUST change too — these are the binary ground-truth labels for NDCG / Recall / MRR." This invariant is currently held.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Finding L3-12 — no snapshot files (NOT APPLICABLE)
|
|
220
|
+
|
|
221
|
+
- **Severity**: INFO
|
|
222
|
+
- **Evidence**:
|
|
223
|
+
- `find /Users/alex/Documents/Projects/obsidian-mcp/tests -name "__snapshots__"` → no results
|
|
224
|
+
- `find /Users/alex/Documents/Projects/obsidian-mcp/tests -name "*.snap"` → no results
|
|
225
|
+
- `grep -rn "toMatchSnapshot\|toMatchInlineSnapshot" tests/` → no matches
|
|
226
|
+
- **Status**: NO FINDING. Snapshot integrity check is moot because the project uses no snapshots — all assertions are explicit `expect().toEqual()`, `.toMatch()`, etc.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Coverage table (full)
|
|
231
|
+
|
|
232
|
+
From `/Users/alex/Documents/Projects/obsidian-mcp/coverage/coverage-summary.json`:
|
|
233
|
+
|
|
234
|
+
| File | Lines | Branches | Funcs | Stmts | Flags |
|
|
235
|
+
|---|---:|---:|---:|---:|---|
|
|
236
|
+
| `tools/index` | 0.00 | 0.00 | 0.00 | 0.00 | barrel artifact (L3-09) |
|
|
237
|
+
| `embeddings` | 31.25 | 30.00 | 33.33 | 29.21 | external-dep (L3-03) |
|
|
238
|
+
| `ocr` | 33.33 | 24.00 | 45.45 | 30.30 | external-dep (L3-04) |
|
|
239
|
+
| `http-transport` | 79.91 | 66.86 | 58.97 | 78.57 | error paths (L3-05) |
|
|
240
|
+
| `tools/search` | 80.89 | 69.75 | 70.00 | 78.47 | (L3-06) |
|
|
241
|
+
| `tools/meta` | 80.93 | 67.66 | 70.96 | 76.88 | (L3-07) |
|
|
242
|
+
| `watcher` | 82.00 | 62.22 | 78.57 | 79.03 | (L3-08) |
|
|
243
|
+
| `pdf` | 89.18 | 58.33 | 100.00 | 90.00 | (L3-10) |
|
|
244
|
+
| `vault` | 92.63 | 80.00 | 75.38 | 83.52 | F<80 |
|
|
245
|
+
| `hnsw` | 94.25 | 75.00 | 100.00 | 91.57 | |
|
|
246
|
+
| `tools/media` | 94.30 | 67.93 | 92.30 | 91.11 | B<75 |
|
|
247
|
+
| `fts5` | 94.32 | 80.95 | 93.33 | 92.51 | |
|
|
248
|
+
| `doctor` | 94.54 | 66.35 | 100.00 | 92.50 | B<75 |
|
|
249
|
+
| `embed-db` | 95.56 | 81.30 | 88.00 | 93.78 | |
|
|
250
|
+
| `dql` | 95.95 | 86.12 | 89.65 | 90.28 | |
|
|
251
|
+
| `tools/read` | 96.11 | 85.43 | 91.17 | 93.97 | |
|
|
252
|
+
| `parser` | 98.00 | 84.61 | 100.00 | 96.42 | |
|
|
253
|
+
| `bases` | 98.21 | 73.17 | 91.30 | 92.27 | B<75 |
|
|
254
|
+
| `tools/write` | 98.51 | 84.83 | 96.15 | 95.30 | |
|
|
255
|
+
| `periodic` | 99.06 | 85.04 | 100.00 | 99.13 | |
|
|
256
|
+
| `communities` | 99.15 | 73.17 | 100.00 | 95.52 | B<75 |
|
|
257
|
+
| `cli-help` | 100.00 | 100.00 | 100.00 | 100.00 | |
|
|
258
|
+
| `eval` | 100.00 | 76.62 | 100.00 | 98.49 | |
|
|
259
|
+
| `rrf` | 100.00 | 93.33 | 100.00 | 96.66 | |
|
|
260
|
+
| **TOTAL** | **89.20** | **75.02** | **82.15** | **85.79** | branches THIN (L3-02) |
|
|
261
|
+
|
|
262
|
+
Thresholds: lines=86, statements=82, functions=75, branches=74. All passing; branches has +1.02pp margin (L3-02).
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Test count cross-surface verification (PASS)
|
|
267
|
+
|
|
268
|
+
| Surface | Path:line | Claimed |
|
|
269
|
+
|---|---|---|
|
|
270
|
+
| README | `/Users/alex/Documents/Projects/obsidian-mcp/README.md:13` (badge) | 714 passing |
|
|
271
|
+
| README | `/Users/alex/Documents/Projects/obsidian-mcp/README.md:32` (one-liner) | 714 unit tests |
|
|
272
|
+
| README | `/Users/alex/Documents/Projects/obsidian-mcp/README.md:98` (table) | 714 unit tests |
|
|
273
|
+
| README | `/Users/alex/Documents/Projects/obsidian-mcp/README.md:208` (code block) | 714 tests, ~5s |
|
|
274
|
+
| package.json | `/Users/alex/Documents/Projects/obsidian-mcp/package.json:5` (description) | 714 tests |
|
|
275
|
+
| social-preview.svg | `/Users/alex/Documents/Projects/obsidian-mcp/assets/social-preview.svg` | `<text>714</text>` |
|
|
276
|
+
| CHANGELOG | `/Users/alex/Documents/Projects/obsidian-mcp/CHANGELOG.md:70` (v3.6.0 entry) | 714 tests (713 passing + 1 env-gated smoke) |
|
|
277
|
+
| CHANGELOG | `/Users/alex/Documents/Projects/obsidian-mcp/CHANGELOG.md:167` (v3.6.0-rc.4) | 714 tests (713 passing + 1 skipped) |
|
|
278
|
+
|
|
279
|
+
**Actual measured**: `grep -rEh "^\s+(it|test)\(" tests/*.test.ts | wc -l` → **714**. Of those, 1 is `it.skip` at `tests/reranker-smoke.test.ts:38`. So **713 active + 1 skipped = 714 total**, matching every documented surface.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Per-test-file count (sanity)
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
tests/reranker-smoke.test.ts: 0 it() at indent 2 (1 it.skip)
|
|
287
|
+
tests/no-internal-imports.test.ts: 1
|
|
288
|
+
tests/chat-thread.test.ts: 7
|
|
289
|
+
tests/ocr.test.ts: 7
|
|
290
|
+
tests/watcher.test.ts: 7
|
|
291
|
+
tests/late-chunking.test.ts: 8
|
|
292
|
+
tests/canvas.test.ts: 10
|
|
293
|
+
tests/frontmatter-ops.test.ts: 11
|
|
294
|
+
tests/communities.test.ts: 13
|
|
295
|
+
tests/persistent-cache.test.ts: 13
|
|
296
|
+
tests/reranker.test.ts: 13
|
|
297
|
+
tests/rrf.test.ts: 13
|
|
298
|
+
tests/semantic.test.ts: 13
|
|
299
|
+
tests/lint.test.ts: 14
|
|
300
|
+
tests/v16.test.ts: 14
|
|
301
|
+
tests/doctor.test.ts: 15
|
|
302
|
+
tests/search-hybrid.test.ts: 15
|
|
303
|
+
tests/embeddings.test.ts: 17
|
|
304
|
+
tests/hnsw.test.ts: 17
|
|
305
|
+
tests/bases.test.ts: 21
|
|
306
|
+
tests/docs-consistency.test.ts: 21
|
|
307
|
+
tests/embed-db.test.ts: 22
|
|
308
|
+
tests/eval.test.ts: 25
|
|
309
|
+
tests/parser.test.ts: 25
|
|
310
|
+
tests/periodic.test.ts: 25
|
|
311
|
+
tests/pdf.test.ts: 26
|
|
312
|
+
tests/cli.test.ts: 31
|
|
313
|
+
tests/fts5.test.ts: 34
|
|
314
|
+
tests/security.test.ts: 36
|
|
315
|
+
tests/dql.test.ts: 43
|
|
316
|
+
tests/http-transport.test.ts: 49
|
|
317
|
+
tests/write.test.ts: 50
|
|
318
|
+
tests/tools.test.ts: 70
|
|
319
|
+
```
|
|
320
|
+
Sum of `it()` calls in indented form: 686 (excluding nested describes counted differently). Including all `it()` and `test()` variations: 714. Includes 1 `it.skip` at `tests/reranker-smoke.test.ts:38`.
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Constraints honored
|
|
325
|
+
|
|
326
|
+
- Audit-only: no source files modified.
|
|
327
|
+
- Test + coverage runs executed (read-only on src/).
|
|
328
|
+
- Every claim cites specific `file:line` per L3 plan.
|
|
329
|
+
|
|
330
|
+
## Recommendations summary
|
|
331
|
+
|
|
332
|
+
| Priority | Finding | Action |
|
|
333
|
+
|---|---|---|
|
|
334
|
+
| HIGH | L3-01 | Set `testTimeout: 15_000` in `vitest.config.ts`; widen `fts5.test.ts:171` perf bound to 1500ms. Ship as v3.6.1 patch. |
|
|
335
|
+
| MEDIUM | L3-02 | Add 8-12 test cases in `tests/communities.test.ts` + `tests/watcher.test.ts` to lift branches margin past 2pp; OR raise threshold to 75. |
|
|
336
|
+
| MEDIUM | L3-03, L3-04 | Document external-dep coverage gap in `vitest.config.ts` comments so future auditors don't try to "fix" it. |
|
|
337
|
+
| LOW | L3-05, L3-06, L3-07, L3-08, L3-10 | Batch into v3.6.2 coverage uplift; target the lowest-hanging branches. |
|
|
338
|
+
| INFO | L3-09 | Add `src/tools/index.ts` to vitest coverage exclude (one-line). |
|
|
339
|
+
| PASS | L3-11, L3-12, test count | No action — recorded for traceability. |
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# L4 — CI/CD pipeline audit
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-05-15
|
|
4
|
+
**Auditor**: Sub-agent C4 (background)
|
|
5
|
+
**Scope**: `.github/workflows/*.yml`, branch protection vs ruleset alignment, GH Pages enablement, deprecation hygiene
|
|
6
|
+
**Repo**: `oomkapwn/enquire-mcp` (despite the local `obsidian-mcp` working directory name)
|
|
7
|
+
|
|
8
|
+
## TL;DR — 2 HIGH, 1 MEDIUM, 2 LOW, 1 INFO
|
|
9
|
+
|
|
10
|
+
| Check | Status |
|
|
11
|
+
|---|---|
|
|
12
|
+
| CI workflow triggers + permissions | ✅ |
|
|
13
|
+
| CI Node matrix matches engines + reality | ⚠️ — Medium drift (engines `>=20`, CI runs `[22,24]`) |
|
|
14
|
+
| CI action versions current (`@v6`/`@v7` floor) | ✅ |
|
|
15
|
+
| CI required jobs exist + names match branch protection | ✅ — 7/7 |
|
|
16
|
+
| Release.yml SHA-on-main verification functional | ✅ |
|
|
17
|
+
| Release.yml REQUIRED contexts regex matches reality | ✅ |
|
|
18
|
+
| Release.yml npm publish uses `--provenance --access public --tag` | ✅ — provenance attestation present on `3.6.0` |
|
|
19
|
+
| Release.yml dist-tag regex handles all 3 prerelease patterns | ✅ — `-rc.N`, `-beta.N`, `-alpha.N` all routed correctly |
|
|
20
|
+
| publish-docs.yml permissions minimal | ✅ |
|
|
21
|
+
| publish-docs.yml action versions current | ✅ |
|
|
22
|
+
| publish-docs.yml concurrency: serialize but don't cancel | ✅ |
|
|
23
|
+
| **publish-docs.yml: has it run successfully on main yet?** | ❌ **HIGH — 0 of 2 runs succeeded** |
|
|
24
|
+
| dist-tag-cleanup.yml manual-only + idempotent | ✅ |
|
|
25
|
+
| Branch protection (classic) vs ruleset (modern): same 7 checks | ⚠️ **HIGH — both list 7 same checks but BOTH are configured, drift risk** |
|
|
26
|
+
| Recent CI runs: no `set-output` / `save-state` / `node12` deprecations | ✅ |
|
|
27
|
+
| npm-side deprecations in install logs | ℹ️ informational (`prebuild-install`, `boolean`) |
|
|
28
|
+
|
|
29
|
+
## Detailed findings
|
|
30
|
+
|
|
31
|
+
### L4-01 (HIGH) — `publish-docs.yml` has failed every run since rc.4 introduction
|
|
32
|
+
|
|
33
|
+
**Class**: workflow committed but prerequisite repo-level config not enabled. Workflow has run twice on main since rc.4 shipped GH Pages auto-publish (PR #68 merge + PR #69 merge) and failed both times. The TypeDoc-generated site at https://oomkapwn.github.io/enquire-mcp/ that README + audit plan reference does not exist.
|
|
34
|
+
|
|
35
|
+
**Evidence**:
|
|
36
|
+
- `gh run list --workflow=publish-docs.yml` → 2 runs, both `failure`:
|
|
37
|
+
- `25917950064` (rc.4 merge, 2026-05-15T12:32:36Z): failed at `actions/configure-pages@v6`
|
|
38
|
+
- `25918407027` (v3.6.0 stable merge, 2026-05-15T12:43:23Z): failed at `actions/configure-pages@v6`
|
|
39
|
+
- Failure message (run 25918407027, build job, line `Run actions/configure-pages@v6`):
|
|
40
|
+
> `##[error]Get Pages site failed. Please verify that the repository has Pages enabled and configured to build using GitHub Actions, or consider exploring the `enablement` parameter for this action. Error: Not Found`
|
|
41
|
+
- `gh api repos/oomkapwn/enquire-mcp/pages` → HTTP 404 (`Not Found`)
|
|
42
|
+
- `gh api repos/oomkapwn/enquire-mcp | jq .has_pages` → `false`
|
|
43
|
+
- Workflow body (`.github/workflows/publish-docs.yml:42`): `- uses: actions/configure-pages@v6` — no `with: enablement: true`, so it expects Pages to already be on.
|
|
44
|
+
|
|
45
|
+
**Impact**:
|
|
46
|
+
- The `Publish API docs` job has shown red ✘ next to every main-branch run since rc.4. Anyone visiting the Actions tab sees this as "CI is broken on main" even though the 7 required jobs all pass.
|
|
47
|
+
- The README and `v3.6.0-system-audit-plan.md` both reference TypeDoc pages at `oomkapwn.github.io/enquire-mcp/` that don't exist.
|
|
48
|
+
- The rc.4 CHANGELOG (line 4 of `publish-docs.yml`) advertises auto-publish of API reference — currently a no-op.
|
|
49
|
+
|
|
50
|
+
**Cross-cutting check**:
|
|
51
|
+
- Is GH Pages mentioned elsewhere as available?
|
|
52
|
+
- `README.md` — check needed in L6 audit (TypeDoc badge / link)
|
|
53
|
+
- `docs/audits/v3.6.0-system-audit-plan.md` line 5–10 references `github.io` site
|
|
54
|
+
- `package.json` doesn't add a `homepage` pointing to pages (it points to GitHub repo `#readme`)
|
|
55
|
+
- The `publish-docs.yml` workflow header comment (line 6) says "lives at https://oomkapwn.github.io/enquire-mcp/" — not yet true.
|
|
56
|
+
|
|
57
|
+
**Suggested class fix** (one of):
|
|
58
|
+
1. **Enable Pages once**: `gh api -X POST repos/oomkapwn/enquire-mcp/pages -f source.branch=main -f build_type=workflow` then re-run the workflow via `gh workflow run publish-docs.yml`. After first successful deploy, future runs will work.
|
|
59
|
+
2. **Set `enablement: true` on configure-pages**: `actions/configure-pages@v6` accepts a `with: enablement: true` input that auto-enables Pages on first run. Requires the `pages: write` permission already present.
|
|
60
|
+
3. **Don't merge this workflow until Pages is enabled** (already too late, but flag in pre-merge checklist for future workflows that depend on repo features).
|
|
61
|
+
|
|
62
|
+
**Per-instance backfill**: enable Pages + manually rerun `publish-docs.yml` workflow_dispatch → confirms first green run → README/audit-plan claims become true.
|
|
63
|
+
|
|
64
|
+
**Severity rationale**: HIGH because (a) workflow is shipping red status checks on every main push, polluting the dashboard; (b) public-facing claim (TypeDoc site live) is false; (c) trivial fix (one API call or one YAML line).
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### L4-02 (HIGH) — Branch protection: both legacy "branch protection" AND modern "ruleset" are configured, duplicate state
|
|
69
|
+
|
|
70
|
+
**Class**: GitHub has two ways to require status checks — `branches/main/protection` (legacy, classic) and `rulesets/15878550` (modern). Both endpoints return the same 7 required checks today, but having both configured means any future change must be made in two places or they will drift.
|
|
71
|
+
|
|
72
|
+
**Evidence**:
|
|
73
|
+
- `gh api repos/oomkapwn/enquire-mcp/branches/main/protection`:
|
|
74
|
+
```
|
|
75
|
+
contexts: ["lint","test (22)","test (24)","smoke","audit","coverage","version-consistency"]
|
|
76
|
+
```
|
|
77
|
+
- `gh api repos/oomkapwn/enquire-mcp/rulesets/15878550`:
|
|
78
|
+
```
|
|
79
|
+
required_status_checks: [lint, test (22), test (24), smoke, audit, coverage, version-consistency]
|
|
80
|
+
enforcement: active
|
|
81
|
+
bypass_actors: [{actor_id:5, actor_type:RepositoryRole, bypass_mode:pull_request}]
|
|
82
|
+
```
|
|
83
|
+
- Both APIs return the same 7 contexts. No drift today.
|
|
84
|
+
- Ruleset was last `updated_at: 2026-05-13T14:59:26` (the v3.5.11 Node-20 drop) — recent maintenance shows the maintainer remembers to update it.
|
|
85
|
+
- The legacy branch-protection API ALSO has dismiss-stale-reviews / restrictions that the ruleset doesn't seem to mirror. Suggests both are independently active.
|
|
86
|
+
|
|
87
|
+
**Impact**:
|
|
88
|
+
- Low impact today (both lists agree).
|
|
89
|
+
- Class risk: when the next CI job is added or renamed, the maintainer needs to update BOTH places. The audit plan only mentions checking the ruleset URL (`rulesets/15878550`) — would miss drift on the legacy `branches/main/protection` side.
|
|
90
|
+
- The release.yml regex `lint|test \(22\)|test \(24\)|smoke|audit|coverage|version-consistency` (release.yml:56) is implicitly the third source of truth — three places must stay synchronized.
|
|
91
|
+
|
|
92
|
+
**Cross-cutting check**:
|
|
93
|
+
- `release.yml:56` `REQUIRED="lint|test \(22\)|test \(24\)|smoke|audit|coverage|version-consistency"` — matches today.
|
|
94
|
+
- `release.yml:66` `REQ_COUNT=7` — matches today.
|
|
95
|
+
- `README.md` line referencing "7 required" — matches today (confirmed earlier).
|
|
96
|
+
- Total: 4 sources of truth (branch-protection contexts, ruleset required_status_checks, release.yml REQUIRED regex, release.yml REQ_COUNT, README badge text). All currently agree.
|
|
97
|
+
|
|
98
|
+
**Suggested class fix** (one of):
|
|
99
|
+
1. **Pick one**: GitHub recommends migrating off legacy branch protection to rulesets. Delete the legacy protection (`gh api -X DELETE repos/oomkapwn/enquire-mcp/branches/main/protection`) and rely on the ruleset alone. After deletion, only 1 GitHub-side source of truth.
|
|
100
|
+
2. **Document the dual-config in CLAUDE.md**: add a "when adding a CI job" checklist that mentions BOTH endpoints + release.yml regex + REQ_COUNT.
|
|
101
|
+
3. **Lint at audit time**: simple shell script that diffs the 4 sources of truth — could live in `scripts/check-required-checks-consistency.mjs` and become a 5th invariant gate. Class fix in the spirit of L-1 from prior audits.
|
|
102
|
+
|
|
103
|
+
**Per-instance backfill**: no drift today → no backfill needed. Just close the class.
|
|
104
|
+
|
|
105
|
+
**Severity rationale**: HIGH because the class is real (4 sources of truth that the maintainer must keep in sync by hand) and the audit plan in section "Branch protection vs ruleset alignment" explicitly flagged this as a check. Currently green; staying green requires either consolidation or an automated invariant.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### L4-03 (MEDIUM) — `package.json#engines.node` says `>=20` but CI dropped Node 20 in v3.5.11
|
|
110
|
+
|
|
111
|
+
**Class**: `engines` field in package.json drifts from what's actually CI-verified. Users on Node 20 may install successfully but hit untested code paths.
|
|
112
|
+
|
|
113
|
+
**Evidence**:
|
|
114
|
+
- `package.json:149-151`:
|
|
115
|
+
```json
|
|
116
|
+
"engines": {
|
|
117
|
+
"node": ">=20"
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
- `.github/workflows/ci.yml:45`: `matrix.node-version: [22, 24]` — Node 20 dropped.
|
|
121
|
+
- `CHANGELOG.md` v3.5.11 entry (lines 483, 501) explicitly says this is INTENTIONAL:
|
|
122
|
+
> "Engines `>=20` UNCHANGED for non-PDF users on prebuilt dist."
|
|
123
|
+
> "end users on Node 20 installing from the npm registry get the prebuilt `dist/` (no local tsc) and the PDF feature simply degrades to 'not available'"
|
|
124
|
+
|
|
125
|
+
**Impact**:
|
|
126
|
+
- Documented and intentional decision, NOT a bug per the CHANGELOG.
|
|
127
|
+
- But: users on Node 20 are running prebuilt code that was never CI-tested against Node 20. Drift risk: anything that landed after v3.5.11 (rc.1..stable) may use a Node 22+ API and silently break on Node 20 with no CI gate to catch it.
|
|
128
|
+
- Specifically `engines` doesn't have `engine-strict`, so npm won't refuse install on Node 20.
|
|
129
|
+
|
|
130
|
+
**Cross-cutting check**:
|
|
131
|
+
- `README.md` Node requirement section: check needed in L6 audit (does the README clearly say "Node 22+ required for PDF feature, Node 20 supported for non-PDF"?).
|
|
132
|
+
- `STABILITY.md` — would need to be checked for an explicit "Node 20 support tier" stability claim.
|
|
133
|
+
- `docs/QUICKSTART.md` — does it mention Node 22 requirement?
|
|
134
|
+
|
|
135
|
+
**Suggested class fix** (one of):
|
|
136
|
+
1. **Bump engines to `>=22`** to match reality. Aligns with EOL of Node 20 (2026-04). One-line change.
|
|
137
|
+
2. **Add a periodic Node 20 advisory job** (mirroring the existing test-macos pattern: `continue-on-error: true`, not required by branch protection, but catches regressions). Cheapest if maintaining Node 20 support is genuinely valuable.
|
|
138
|
+
3. **Document the tier explicitly in README** ("Node 22+ required for full feature set; Node 20 prebuilt-binary install path supported best-effort, not CI-tested").
|
|
139
|
+
|
|
140
|
+
**Per-instance backfill**: not blocking. Most users are already on Node ≥22 (per the npm distribution data). The risk is hypothetical until a real Node 20-incompatible API gets used.
|
|
141
|
+
|
|
142
|
+
**Severity rationale**: MEDIUM because it's a documented, intentional decision but adds technical debt (drift surface) every release. The CHANGELOG comment in v3.5.11 admits this is a deferred decision.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### L4-04 (LOW) — `dist-tag-cleanup.yml` is a one-shot that has not run
|
|
147
|
+
|
|
148
|
+
**Class**: workflow committed for a one-time cleanup but never executed; lives on as orphan code.
|
|
149
|
+
|
|
150
|
+
**Evidence**:
|
|
151
|
+
- `.github/workflows/dist-tag-cleanup.yml:1-48`: one-shot cleanup to remove stale `alpha` + `beta` dist-tags pointing at v2.0 prerelease versions.
|
|
152
|
+
- `npm view @oomkapwn/enquire-mcp dist-tags` → `{'latest': '3.6.0', 'rc': '3.6.0-rc.4'}` — only `latest` and `rc` exist today. No stale `alpha` or `beta`.
|
|
153
|
+
- Either (a) the cleanup ran via `workflow_dispatch` outside the audit window, or (b) the tags self-cleared somehow, or (c) the tags were never set on this scope after the package rename — checking via `npm view @oomkapwn/enquire-mcp@beta` would clarify but it's not critical.
|
|
154
|
+
|
|
155
|
+
**Impact**:
|
|
156
|
+
- Zero runtime impact (file just sits there).
|
|
157
|
+
- Repository hygiene: dead workflow file. If the cleanup already ran, the file should be deleted.
|
|
158
|
+
- `permissions: id-token: write` (line 22) is requested for OIDC but the actual cleanup commands don't need it (`npm dist-tag rm` uses `NPM_TOKEN`). Slightly over-broad permission.
|
|
159
|
+
|
|
160
|
+
**Cross-cutting check**:
|
|
161
|
+
- No other one-shot workflows in `.github/workflows/`.
|
|
162
|
+
- The file has the right safety pattern (`confirm: REMOVE` input) so even if accidentally triggered, it's gated.
|
|
163
|
+
|
|
164
|
+
**Suggested class fix**:
|
|
165
|
+
1. **Verify cleanup state**: `npm view @oomkapwn/enquire-mcp@beta version` — if 404, cleanup is done.
|
|
166
|
+
2. **Remove the workflow file** if cleanup is done. One commit, audit trail in CHANGELOG.
|
|
167
|
+
3. **OR if kept "in case"**: drop `id-token: write` to `contents: read` only — the file doesn't use OIDC.
|
|
168
|
+
|
|
169
|
+
**Per-instance backfill**: trivial cleanup or no-op decision. Low severity, low priority.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### L4-05 (LOW) — `coverage` job in `ci.yml` is NOT in `needs:` chain of `smoke`/`audit` (already by design, but worth a note)
|
|
174
|
+
|
|
175
|
+
**Class**: parallel job dependency graph — `coverage` runs after `test` (line 80 `needs: test`), but `audit` (line 120) and `version-consistency` (line 139) have no `needs:` so they run in parallel with everything else.
|
|
176
|
+
|
|
177
|
+
**Evidence**:
|
|
178
|
+
- `ci.yml:80`: `coverage` has `needs: test`
|
|
179
|
+
- `ci.yml:102`: `smoke` has `needs: test`
|
|
180
|
+
- `ci.yml:120`: `audit` — no `needs`, runs in parallel
|
|
181
|
+
- `ci.yml:139`: `version-consistency` — no `needs`, runs in parallel
|
|
182
|
+
|
|
183
|
+
**Impact**:
|
|
184
|
+
- This is actually CORRECT for fast-fail behavior — `audit` (npm audit) and `version-consistency` (script check) don't depend on test results, so running them in parallel saves wall-clock time and surfaces unrelated regressions independently.
|
|
185
|
+
- No actual bug here.
|
|
186
|
+
|
|
187
|
+
**Cross-cutting check**: none needed.
|
|
188
|
+
|
|
189
|
+
**Suggested class fix**: none — current setup is optimal. Filed as informational.
|
|
190
|
+
|
|
191
|
+
**Severity rationale**: LOW (effectively INFO) — the audit plan asked to verify all required jobs exist + reference correct check names; both confirmed. The parallel dependency graph is intentional and good.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### L4-06 (INFO) — npm-side deprecation noise in `npm ci` logs (not GH Actions deprecations)
|
|
196
|
+
|
|
197
|
+
**Class**: transitive deps emit `npm warn deprecated` lines in every CI install. Cosmetic, not blocking.
|
|
198
|
+
|
|
199
|
+
**Evidence** (from runs 25918411923, 25918407052, 25917953901, 25911650374):
|
|
200
|
+
```
|
|
201
|
+
npm warn deprecated prebuild-install@7.1.3: No longer maintained...
|
|
202
|
+
npm warn deprecated boolean@3.2.0: Package no longer supported...
|
|
203
|
+
```
|
|
204
|
+
- `prebuild-install` is a transitive dep of `better-sqlite3` / native modules. No newer version exists.
|
|
205
|
+
- `boolean` is a transitive dep (likely from one of the embeddings/HF tooling chains).
|
|
206
|
+
|
|
207
|
+
**No GH Actions deprecation warnings found**:
|
|
208
|
+
- Searched 4 recent CI runs (3 layers x 7 jobs each = 28 job logs) for `deprecat`. Only npm warnings. No `set-output`, `save-state`, `node12`, or other Action-runner deprecations.
|
|
209
|
+
- All actions are `@v6` or `@v7` floor — current major versions.
|
|
210
|
+
- `actions/checkout@v6` ← upstream latest `v6.0.2` ✓
|
|
211
|
+
- `actions/setup-node@v6` ← upstream latest `v6.4.0` ✓
|
|
212
|
+
- `actions/upload-artifact@v7` ← upstream latest `v7.0.1` ✓
|
|
213
|
+
- `actions/configure-pages@v6` ← upstream latest `v6.0.0` ✓
|
|
214
|
+
- `actions/upload-pages-artifact@v5` ← upstream latest `v5.0.0` ✓
|
|
215
|
+
- `actions/deploy-pages@v5` ← upstream latest `v5.0.0` ✓
|
|
216
|
+
|
|
217
|
+
**Impact**: cosmetic log noise only. Not a hygiene issue.
|
|
218
|
+
|
|
219
|
+
**Cross-cutting check**: none.
|
|
220
|
+
|
|
221
|
+
**Suggested class fix**: none — wait for upstream maintainers of `prebuild-install` / `boolean` to update their packages or for native deps to switch to a different prebuild tool. Out of our control.
|
|
222
|
+
|
|
223
|
+
**Severity rationale**: INFO — proactive note that the deprecation hygiene check passed cleanly. Recorded for traceability.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Workflow-by-workflow summary
|
|
228
|
+
|
|
229
|
+
### `.github/workflows/ci.yml` (148 lines)
|
|
230
|
+
|
|
231
|
+
| Aspect | Status | Notes |
|
|
232
|
+
|---|---|---|
|
|
233
|
+
| Trigger events (line 3–7) | ✅ | `push` + `pull_request` on `main`. Correct. |
|
|
234
|
+
| Permissions (line 9–10) | ✅ | `contents: read` only. Minimal. |
|
|
235
|
+
| Concurrency (line 12–14) | ✅ | `ci-${{github.ref}}`, cancel-in-progress. Standard. |
|
|
236
|
+
| Job: `lint` (line 17–27) | ✅ | Single-node 22, biome check, 5min timeout. |
|
|
237
|
+
| Job: `test` matrix (line 29–55) | ⚠️ | `[22, 24]` — matches v3.5.11 decision. Drift from `engines: >=20` (see L4-03). |
|
|
238
|
+
| Job: `test-macos` (line 57–75) | ✅ | Advisory only, `continue-on-error: true`. |
|
|
239
|
+
| Job: `coverage` (line 77–97) | ✅ | `needs: test`, includes `check:changelog-coverage` gate (line 92), uploads coverage artifact (`upload-artifact@v7`). |
|
|
240
|
+
| Job: `smoke` (line 99–118) | ✅ | `needs: test`, scan + FTS5 paths covered. |
|
|
241
|
+
| Job: `audit` (line 120–137) | ✅ | Prod `--audit-level=moderate`, dev `--audit-level=high`. |
|
|
242
|
+
| Job: `version-consistency` (line 139–147) | ✅ | Runs `scripts/check-version-consistency.mjs`. |
|
|
243
|
+
| Required-check names | ✅ | All 7 (`lint`, `test (22)`, `test (24)`, `smoke`, `audit`, `coverage`, `version-consistency`) appear in `check-runs` API. Match branch-protection list exactly. |
|
|
244
|
+
| Action versions | ✅ | All `@v6`/`@v7` floor. Match current upstream majors. |
|
|
245
|
+
|
|
246
|
+
### `.github/workflows/release.yml` (120 lines)
|
|
247
|
+
|
|
248
|
+
| Aspect | Status | Notes |
|
|
249
|
+
|---|---|---|
|
|
250
|
+
| Triggers (line 3–12) | ✅ | Tag push `v*` + `workflow_dispatch`. |
|
|
251
|
+
| Permissions (line 14–16) | ✅ | `contents: read` + `id-token: write` for OIDC provenance. |
|
|
252
|
+
| Checkout with `fetch-depth: 0` (line 28) | ✅ | Required for `git merge-base --is-ancestor`. |
|
|
253
|
+
| SHA-on-main verification (line 35–47) | ✅ | `git fetch origin main --depth=200` + `git merge-base --is-ancestor`. Tested green on v3.6.0 release. |
|
|
254
|
+
| Required-CI-checks verification (line 48–77) | ✅ | Regex `lint\|test \(22\)\|test \(24\)\|smoke\|audit\|coverage\|version-consistency` matches all 7 ruleset entries. `REQ_COUNT=7` matches. Polls up to 5min for in-flight CI. |
|
|
255
|
+
| Pre-publish gates (line 84–96) | ✅ | `npm ci`, lint, build, test, version-consistency, audit, smoke (scan + FTS5). Triple-redundant with the SHA-on-main verification — belt + suspenders. |
|
|
256
|
+
| Dist-tag derivation (line 97–115) | ✅ | Regex `/^\d+\.\d+\.\d+-([0-9A-Za-z-]+)/` correctly routes: `3.6.0-rc.4 → rc`, `2.0.0-beta.4 → beta`, `2.0.0-alpha.0 → alpha`, `3.6.0 → latest`. All 3 prerelease patterns we've used handled. Edge cases (build metadata `+`, no `.N` suffix) also handled per the v2.0.0-beta.2 P0 comment. |
|
|
257
|
+
| Publish step (line 116–119) | ✅ | `npm publish --provenance --access public --tag "${tag}"`. Verified: `npm view @oomkapwn/enquire-mcp@3.6.0` has `dist.attestations.provenance` populated with `predicateType: https://slsa.dev/provenance/v1`. SLSA-3 claim valid. |
|
|
258
|
+
|
|
259
|
+
### `.github/workflows/publish-docs.yml` (57 lines)
|
|
260
|
+
|
|
261
|
+
| Aspect | Status | Notes |
|
|
262
|
+
|---|---|---|
|
|
263
|
+
| Triggers (line 9–14) | ✅ | `push: branches: [main]` + `workflow_dispatch`. |
|
|
264
|
+
| Permissions (line 19–22) | ✅ | `contents: read` + `pages: write` + `id-token: write`. Minimum for GH Pages OIDC deploy. No over-broad scope. |
|
|
265
|
+
| Concurrency (line 26–28) | ✅ | `group: pages`, `cancel-in-progress: false`. Serializes deploys (good — aborted upload would corrupt site state). |
|
|
266
|
+
| Build job (line 30–45) | ✅ structure / ❌ runtime | YAML correct; fails at runtime because Pages is not enabled (see L4-01). |
|
|
267
|
+
| Deploy job (line 47–57) | ❌ runtime | Cannot run; depends on failed build job. |
|
|
268
|
+
| Action versions | ✅ | `actions/checkout@v6`, `actions/setup-node@v6`, `actions/configure-pages@v6`, `actions/upload-pages-artifact@v5`, `actions/deploy-pages@v5`. All match latest upstream majors. |
|
|
269
|
+
|
|
270
|
+
### `.github/workflows/dist-tag-cleanup.yml` (48 lines)
|
|
271
|
+
|
|
272
|
+
| Aspect | Status | Notes |
|
|
273
|
+
|---|---|---|
|
|
274
|
+
| Triggers (line 12–18) | ✅ | `workflow_dispatch` only, requires confirm input `REMOVE`. Cannot fire accidentally. |
|
|
275
|
+
| Permissions (line 20–22) | ⚠️ | `contents: read` + `id-token: write`. The `id-token` is unused — `npm dist-tag rm` authenticates via `NPM_TOKEN` env var. Slightly over-broad. |
|
|
276
|
+
| Guard (line 28) | ✅ | `if: ${{ inputs.confirm == 'REMOVE' }}`. |
|
|
277
|
+
| Idempotency (line 42, 46) | ✅ | `|| true` after each `npm dist-tag rm` — re-running on already-removed tag is safe. |
|
|
278
|
+
| Worth keeping? | ⚠️ | One-shot purpose served (no stale tags exist today). See L4-04. |
|
|
279
|
+
|
|
280
|
+
## Sign-off
|
|
281
|
+
|
|
282
|
+
L4 verdict: **YELLOW (2 HIGH for v3.6.1 patch)**.
|
|
283
|
+
|
|
284
|
+
- **L4-01** (HIGH, publish-docs not enabled): trivial fix, prevents red status on every main push, makes README claim true. Recommend fixing as part of v3.6.1.
|
|
285
|
+
- **L4-02** (HIGH, dual branch-protection state): no drift today, but class fix (consolidate to ruleset OR add invariant script) prevents future silent drift. Recommend v3.6.1 or v3.6.2.
|
|
286
|
+
- **L4-03** (MEDIUM, engines drift): defer to v3.7 or document tier explicitly.
|
|
287
|
+
- **L4-04**, **L4-05** (LOW): housekeeping, defer to v3.7.
|
|
288
|
+
- **L4-06** (INFO): clean bill of health on Actions-runner deprecations.
|
|
289
|
+
|
|
290
|
+
Pipeline is structurally sound. Release path is multi-gated (SHA-on-main + check-run verification + in-job re-run of lint/test/audit/smoke + provenance). The one shipping bug is GH Pages, which is repo-level config rather than workflow YAML.
|