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.
Incorporates the open dependabot dev-dependency PRs as one coherent, tightly
verified upgrade rather than five separate rebase/CI cycles, since they interact
across the build/lint toolchain:
- eslint 9.39.2 → 10.5.0 and @eslint/js 9.39.2 → 10.0.1 (#235 + #236 — these are
coupled: @eslint/js@10 peer-requires eslint@10, so #236 could only ever resolve
alongside #235; neither lands alone)
- typescript 5.9.3 → 6.0.3 (#237)
- typescript-eslint / @typescript-eslint/utils 8.60.1 → 8.62.0, esbuild
0.28.0 → 0.28.1, globals 17.6.0 → 17.7.0, obsidian 1.13.0 → 1.13.1 (#246,
which also subsumes the standalone esbuild bump in #233)
- actions/checkout v6 → v7 across all workflows (#240)
TypeScript 6 turns two tsconfig deprecations into errors. Fixed by modernizing
the config, not suppressing — Obsidian's community-plugin source review forbids
`ignoreDeprecations`-style escape hatches and rewards tight configs:
- dropped `baseUrl: "."` (TS5101) — unused; every local import is relative
- `moduleResolution: "node"` → `"bundler"` (TS5107) — the correct modern value
for an esbuild-bundled project with extensionless imports (node16/nodenext
would force `.js` extensions and break every relative import)
Also ran `npm audit fix` (security fixes are hold-exempt) clearing two dev/test
transitive advisories (@babel/core file-read, js-yaml merge-key DoS) that were
pre-existing on main via the jest/istanbul chain — `npm audit` now reports 0.
Verified tight: `npm run build && npm run lint && npm test` all green, 352/352
tests pass, no rule suppressions added.
The .mcpb bridge ships inside the GitHub release bundle, and BRAT updates
only the Obsidian plugin — never the Claude Desktop connector. Users had no
signal that a plugin update doesn't deliver bridge-side fixes (#243/#247).
Add a standing "Claude Desktop (.mcpb connector)" section to the release
workflow's notes template so every release tells .mcpb users to re-import,
rather than depending on per-release notes remembering to.
Claude-Session: https://claude.ai/code/session_011yowKCMFCBp61DRBu5xQm4
The previous template inlined ${{ inputs.release_notes }} directly into
a double-quoted bash NOTES="..." assignment, so backticks (or $vars) in
the notes were evaluated by the shell as command substitution. Today's
0.11.30 release hit this — backticks in the ADR-107 release notes blew
up the heredoc and failed the workflow before tag creation.
Switch the templated values to env-var bindings (RELEASE_NAME,
RELEASE_VERSION, RELEASE_NOTES_INPUT) and assemble the markdown via
printf so the notes are treated as data, not code.
Reimplemented from PR #126 (djsplice, who also reported #125), reduced to
the parts that are verified-clean. The worker-offload half of #126 is split
out to a tracking issue (inert as wired + correctness divergences — see PR
discussion / the follow-up issue).
SSE route deconfliction
In @modelcontextprotocol/sdk@1.29.0 (the pinned version), `GET /mcp` is
the standalone server->client SSE stream for server-initiated messages —
verified in webStandardStreamableHttp.js:handleGetRequest (opens
`_GET_stream`, Content-Type text/event-stream, gated on
Accept: text/event-stream + session + protocol version). A debug route
returning application/json on `GET /mcp` shadowed it, so any client that
opens the standalone stream (mcp-remote-class bridges — #128's logs show
one in use here) received JSON instead of an SSE stream and treated it as
failed. Moved debug to `GET /mcp-info`; `GET /mcp` and `POST /mcp` both
reach handleMCPRequest. Unlike #126's `app.all('/mcp')`, GET/POST are
registered individually so the existing explicit `app.delete('/mcp')`
session-close handler is preserved.
Scope note: this restores the server-initiated-notification channel (a
real latent defect on its own). It is NOT claimed to fix#128 — per the
#190 investigation that is a separate SDK-1.29 compat-init problem — and
#125's "SSE reconnection loop" is the reporter's un-reproduced diagnosis.
Two-row Levenshtein
fuzzy-match.ts rewritten from a full (m+1)x(n+1) matrix to a two-row
formulation (O(n) memory, no per-line array-of-arrays allocation) plus
length heuristics and a 0.95 early-exit. Pure CPU/memory win on the
main-thread edit.window path; behaviour-preserving. Adds the first
fuzzy-match tests (classic Levenshtein distances, symmetry,
heuristic-preservation).
build.yml: PR-status-comment step set continue-on-error so it cannot red an
otherwise-green build on fork/limited-token PRs.
Co-authored-by: djsplice <barrows.jeff@gmail.com>
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
Follow-up to #164. Obsidian's portal flags every installable release asset
lacking a build-provenance attestation. #164 attested main.js + the .mcpb
bundles but missed styles.css and manifest.json, leaving a standing
'styles.css release asset does not have a GitHub artifact attestation'
recommendation. Attest the full installable set.
Closes#164. Adds actions/attest-build-provenance@v2 (gated like Create
Release) plus id-token/attestations write permissions, attesting main.js and
both .mcpb bundles. Clears the scorecard 'release assets missing a GitHub
artifact attestation' finding without changing what ships.
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.
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.
Instead of auto-releasing on every push to main, releases are now
triggered manually via workflow_dispatch. This reduces noise from
incremental commits during development.
To create a release:
1. Go to Actions → Create Release
2. Click "Run workflow"
3. Optionally add release notes
4. Run
Configure Dependabot to:
- Check npm dependencies weekly (Mondays at 9am)
- Group patch/minor updates by dependency type
- Check GitHub Actions versions weekly
- Auto-label PRs with 'dependencies' and 'automated'
Also enabled vulnerability alerts and security updates via GitHub API.
Remove the community engagement and release download statistics from README and delete the automated stats update workflow. This simplifies the repository and removes unnecessary automation.
Critical bug fix:
- The awk script in update-stats.yml had a logic error that caused
repeated appending of <!-- STATS:END --> markers on each run
- This resulted in the README growing from 192 lines to 4.2M lines
over 22 workflow runs
Changes:
1. Restored README.md to clean state from commit 37a640c (192 lines)
2. Rewrote awk logic to properly replace content between markers:
- Now tracks in_stats state correctly
- Skips all content between START and END markers
- Inserts new stats content only once
- Prevents duplication on subsequent runs
Tested:
- First run: correctly inserts stats (192 → 208 lines)
- Second run: correctly replaces stats (stays at 208 lines)
- Only 1 STATS:END marker after multiple runs
- Created GitHub Action to update stats daily at 2 AM UTC
- Added dynamic badges to README for stars, forks, downloads, version
- Added placeholder section for detailed statistics
- Workflow also supports manual trigger via workflow_dispatch
- Add .github/FUNDING.yml for GitHub Sponsors button
- Add sponsorship section to README with badge
- Manifest already has fundingUrl configured
This enables the GitHub Sponsors button on the repository and
provides clear sponsorship information for users who want to
support the plugin's development.
- Implement recursive directory copying with security layer compliance
- Add automatic file vs directory detection using Obsidian API
- Support nested directory structures with proper error handling
- Include comprehensive test suite for recursive copy operations
- Maintain backward compatibility with existing file copy operations
- Handle overwrite protection and skip binary/image files appropriately
Resolves directory copying limitations in vault operations.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add CONTRIBUTING.md with development guidelines
- Add SECURITY.md with vulnerability reporting policy
- Create GitHub issue templates for bugs and features
- Add PR template with checklist
- Set up GitHub Actions for testing and security scanning
- Document all security findings from code audit
- Create detailed GitHub issues for all vulnerabilities
- Update CHANGELOG with security audit results
- Add project structure documentation
This establishes proper open-source project structure and documents
all critical security issues that need to be addressed.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
- Change tag format from 'v0.5.3' to '0.5.3'
- Update release title and notes formatting
- Aligns with Obsidian community plugin standards
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add explicit permissions for pull-requests and issues write access
- Fixes "Resource not accessible by integration" error
- Allows workflow to post build status comments on PRs
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create separate build.yml for all branches (main, feat/**, feature/**, fix/**, hotfix/**)
- Keep release.yml focused on main branch releases only
- Add feature-release.yml for manual pre-releases from feature branches
- Upload build artifacts for all builds
- Add PR commenting for build status
- Fix HTTP 403 error when creating releases
- Restore lint and test steps (they were passing)
- GitHub token now has proper permissions for release creation
- Add ESLint configuration with TypeScript support
- Set up Jest testing framework with Obsidian mocks
- Add basic MCP server tests
- Create DEVELOPMENT.md with Claude Code integration guide
- Update GitHub Action to include lint and test steps
- Use modern GitHub CLI for releases instead of deprecated actions
Testing and linting now part of CI/CD pipeline for quality assurance.
- Triggers on main branch pushes (excluding doc changes)
- Builds plugin and creates release with required assets
- Only creates release if version tag doesn't exist
- Marks releases as prerelease for beta testing
- Includes BRAT installation instructions in release notes