Commit graph

13 commits

Author SHA1 Message Date
Aaron Bockelie
8bb580cc56 fix: .mcpignore semantics, TLS 1.3 startup, rename extension (#250, #252, #253)
Bundles the three reported bugs plus an unreported under-blocking bug found
while writing the tests for #250, and adds the coverage tooling that would
have caught all four.

#250 — .mcpignore negation was inverted
The parser stripped the leading '!' before Minimatch saw it, so .negate was
always false and the branch reading it was dead code. '!folder/*' compiled
into a positive exclusion: negation did not merely fail, it inverted.

Under-blocking (found while testing the above, not reported)
Patterns were handed to Minimatch raw, so two gitignore rules were never
implemented: a slash-less pattern matches at any depth ('*.secret' must hide
'a/b/creds.secret'), and a directory match covers its contents ('private/'
must hide 'private/notes.md'). No consumer of isExcluded() checks ancestors —
every caller passes a full file path — so nothing compensated downstream. The
plugin's own shipped .mcpignore template promises both behaviours, so a user
following it believed private files were hidden while they were being served
to the model. Patterns are now translated into the globs that implement
gitignore semantics, and negation is interpreted here rather than by Minimatch.

#252 — min TLS 1.3 crashed the server
secureProtocol mapped 1.3 to 'TLSv1_3_method', which OpenSSL never shipped, so
context creation threw "Unknown method: TLSv1_3_method". Switched to minVersion,
which also fixes a second latent bug: secureProtocol PINS one version, so
"minimum TLS 1.2" was silently refusing TLS 1.3 clients. The setting now
behaves as the floor it is labelled.

#253 — rename dropped the extension
'newName' was concatenated onto the source directory verbatim, so renaming
'note.md' to 'renamed' produced an extension-less file that drops out of
markdown views. The source extension is now carried over when newName omits
one, before the overwrite guard so it checks the right path.

Coverage + test contract
All four bugs shipped through code paths with zero coverage. Adds `make
coverage` / `coverage-map` / `coverage-gate`, a ratcheting coverageThreshold
(security boundary held to a far higher floor than global, since a hole there
leaks vault content), and tests/test-contract.test.ts enforcing that a green
run means something: no .only, no .skip, every file asserts, no tautologies.
The contract immediately caught an `expect(true).toBe(true)` placeholder in the
path-validator suite, now replaced with real assertions.

BREAKING (.mcpignore, both intended):
- The double-'!' workaround for #250 now correctly parses as a double negation
  (net exclude) and will stop working.
- A bare '*' now excludes at every depth, per gitignore, rather than top-level
  only. This is what makes the '*' + '!keep/**' whitelist idiom work.
2026-07-13 17:46:50 -05:00
Aaron Bockelie
bf1aa6319a chore(worker): remove dormant worker-offload path (ADR-105, closes #197)
The worker-thread pool ratified/extended by ADR-104 has never executed a
single request since introduction (v0.5.8b, 22742bd): ConnectionPool's
action-level workerOps gate (tool.vault.search, …) never matched the
operation-level method strings the pool emits (tool.vault, tool.edit, …),
so processWithWorker was unreachable. Independently, the worker built to
dist/workers/workers/semantic-worker.js while WorkerManager loaded
dist/workers/semantic-worker.js — load would have failed anyway.

Verified dead against current main; no CPU-starvation field reports.
Fixing forward was rejected: it would ship an untested concurrency path
that widens the #139 parallel-edit race via a new TOCTOU window for an
unproven perf need (see #197 analysis).

- delete src/workers/semantic-worker.ts, src/utils/worker-manager.ts,
  build-worker.js; drop build:worker + the build-worker.js build step
- strip worker wiring from ConnectionPool (retained as a main-thread
  bounded queue — the only behaviour that ever ran) and the dead
  prepareWorkerContext/workerScript from mcp-server.ts
- ADR-105 records the removal as a partial reversal of ADR-104; ADR-104
  stays Accepted (SSE-route deconfliction half, shipped #196, still
  governs) with a banner pointing to ADR-105

Behaviour-preserving by construction: the deleted path never ran.
make check green (build + lint 0 errors + 225/225 tests).
2026-05-18 22:26:14 -05:00
Aaron Bockelie
f1cc9d63ba feat(scorecard): scheduled regression gate vs committed baseline (#165)
Standalone weekly watch (+ workflow_dispatch) that FAILS only on a real
portal regression vs scripts/scorecard-baseline.json:
- Health/Review score-ratio downgrade
- automated issue count increase
- a new behaviour/permission ("**Title**:") finding

Never inspects advisory wording — structured deltas only. Scraper drift
→ exit 3, reported distinctly (fix scorecard.mjs, not a regression).
Portal unreachable → exit 0 (inconclusive, never a false fail). Gates
nothing in the release path; no PR/release triggers; no dependency
install (Node builtins only).

`make scorecard-gate` runs it; `make scorecard-baseline` re-snapshots
deliberately (refuses on drift) — a named act, since an accidental
re-baseline silently disarms the gate. Baseline captured for 0.11.25
(Health Excellent 4/4, Review Satisfactory 3/4, 5 issues, 12 findings)
— still shows Dynamic Code Execution; it clears (an improvement, not a
fail) when a post-ADR-201 release is scanned.

Validated: happy path exit 0, simulated regression exit 1 (all 3
classes), drift refusal, idempotent re-baseline.

Closes #165
2026-05-16 23:42:05 -05:00
Aaron Bockelie
9cfd70353b fix(release): align MIN_OBSIDIAN with manifest minAppVersion (1.6.6)
_update-versions-json wrote MIN_OBSIDIAN into versions.json, but it was
stale at 0.15.0 while manifest.json minAppVersion is 1.6.6 (raised in the
0.11.21 release, where versions.json was hand-corrected). Left as-is, the
next make release-* would write an inconsistent versions.json entry
(version -> 0.15.0) that disagrees with the shipped manifest. Align the
Makefile to the actual floor so the automation is correct without manual
patching.
2026-05-16 12:14:27 -05:00
Aaron Bockelie
2738a557ac Merge remote-tracking branch 'origin/main' into docs/plugin-description
# Conflicts:
#	Makefile
2026-05-16 12:11:53 -05:00
Aaron Bockelie
46ed67361a chore(meta): make pattern for plugin description; rewrite copy
package.json is now the single source of truth for the plugin description
(as it already effectively is for version). sync-version.mjs propagates it
into manifest.json; mcpb/manifest.json keeps its Claude-Desktop-specific
description and is intentionally not synced.

Adds scripts/set-description.mjs + 'make set-description DESC=...' so neither
JSON file is hand-edited (250-char Obsidian limit enforced).

Rewrites the word-salad description to reality-based copy naming the actual
client and mechanism:
  'Give Claude Desktop and other AI assistants semantic access to your notes
   through a built-in Model Context Protocol (MCP) server.'
2026-05-16 12:07:52 -05:00
Aaron Bockelie
86eb4df569 feat(scorecard): pull Obsidian portal scorecard as free post-release signal
Add scripts/scorecard.mjs + 'make scorecard'. The community portal renders a
deterministic Health/Review scan; the page is server-rendered (summary in DOM,
findings in the Next.js RSC payload) so we can pull it without a browser.

- Computes a freshness delta (portal version vs manifest.json) since a fresh
  scan only triggers from the authenticated dev portal — STALE means a
  logged-in re-scan is still needed.
- Scraper drift guard: missing critical anchors / empty findings on a
  non-clean Review exits non-zero with a SCRAPER DRIFT banner so the parser
  rots loudly, not silently. Scorecard body never gates.
- --json mode for diffing findings across releases.

CLAUDE.md: document make scorecard, the freshness caveat, the drift guard,
and the known accepted cautions (.mcpb additional-files is ADR-102 by design;
'scan not available' disclosures are neutral). Actionable findings tracked in
#163 (rejectUnauthorized), #164 (attestation); CI-gate in #165.
2026-05-16 11:53:47 -05:00
Aaron Bockelie
6705d5aeff build: Add make promote target for prerelease → stable + Latest
The release pipeline always emits prereleases (per the standing
convention in CLAUDE.md). GitHub's /releases/latest/download/<asset>
URL pattern doesn't resolve to prereleases, so the MCPB download
link in plugin Settings would always 404 without an explicit
promotion gesture.

make promote (no args) flips the current package.json version from
prerelease → stable and marks it Latest. make promote TAG=X.Y.Z
targets a specific older release. Documented inline in CLAUDE.md
next to the existing prerelease convention.

First use: just promoted 0.11.17 manually with the same gh command
this target wraps.
2026-05-15 12:46:00 -05:00
Aaron Bockelie
e5808a3a61 fix(build): Stage mcpb/manifest.json in release commit
Without this, make release-patch leaves mcpb/manifest.json one
version behind on main after each release — sync-version updates it
on disk via the npm version hook but _commit-and-publish never
stages it. The built .mcpb artifact in CI is correct (build runs
sync-version fresh), but main drifts until the next release
self-heals.

Direct consequence of the earlier "build: Sync mcpb/manifest.json
version with package.json" commit in this PR.
2026-05-15 11:51:57 -05:00
Aaron Bockelie
4d46014b8a fix(build): Use stable latest-release URL for MCPB download
The Settings UI was building a versioned releases/download/<version>/
URL that would 404 for any plugin version not yet associated with a
GitHub release — the exact case for users on a fresh BRAT install
running ahead of the release pipeline.

build-mcpb.mjs now emits both the versioned filename (for archival)
and an unversioned obsidian-mcp.mcpb alias. release.yml uploads both.
The Settings UI links to releases/latest/download/obsidian-mcp.mcpb,
which GitHub resolves to the most recent release's asset by name —
stable regardless of plugin version.

Makefile clean target updated to remove the new artifact.

Refs PR #155 review.
2026-05-15 11:46:06 -05:00
Aaron Bockelie
7ca31c4685 build: Add make mcpb target and attach .mcpb to release assets
scripts/build-mcpb.mjs is a zero-dep pure-Node zip builder
(stored-only container, manual CRC32) that packs mcpb/manifest.json
and mcpb/server.js into obsidian-mcp-<version>.mcpb. Works on any
Node ≥18, doesn't require system zip/7z, runs identically on dev
machines and in CI.

Makefile gains a "mcpb" target so the local workflow is "make mcpb"
when iterating. clean now also removes built .mcpb files.

release.yml builds the bundle as part of the release job and attaches
it to the GitHub release alongside main.js/manifest.json/styles.css,
so BRAT users and the future plugin Settings "download" affordance
both resolve to the canonical release asset.

Refs ADR-102.
2026-05-15 11:30:50 -05:00
Aaron Bockelie
9f91241445 fix: Address code review findings from PR #107
- shared-tags: connectionStrength is a count, not a ratio — use
  binary 1.0/0 to avoid "300% similar" display
- advanced-traverse: add normalizer to build snippetChain from
  details.traversalChain (prevents TypeError on missing field)
- traverse: simplify edge-to-links derivation (single pass)
- Makefile: use explicit `git push origin HEAD`
2026-03-14 21:07:13 -05:00
Aaron Bockelie
57e28988ac chore: Add Makefile for convenient project operations
Wraps npm scripts with a discoverable `make help` interface.
Release targets (release-patch/minor/major) handle the full cycle:
check → bump → sync versions.json → commit → push → trigger workflow.

No clever tricks — just convenience.
2026-03-14 21:00:47 -05:00