aaronsb_obsidian-mcp-plugin/Makefile
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

114 lines
4 KiB
Makefile

.PHONY: help build dev test coverage coverage-map coverage-gate lint lint-fix check clean install \
release-patch release-minor release-major release publish promote sync-version mcpb scorecard set-description
MIN_OBSIDIAN := 1.6.6
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
# --- Development ---
install: ## Install dependencies
npm install
dev: ## Start dev mode (watch + rebuild)
npm run dev
build: ## Build plugin (typecheck + bundle)
npm run build
# --- Quality ---
lint: ## Run ESLint
npm run lint
lint-fix: ## Run ESLint with auto-fix
npm run lint:fix
test: ## Run test suite
npm test
coverage: ## Run tests with coverage + print the per-file map (html report in coverage/)
npm run test:coverage
@node scripts/coverage-map.mjs
coverage-map: ## Print the per-file coverage map from the last run (no re-run)
@node scripts/coverage-map.mjs
coverage-gate: ## Enforce the coverage floors in jest.config.js (exit 1 on backslide)
npm run test:coverage
check: build lint coverage-gate ## Run all quality gates (build + lint + tests + coverage floors)
# --- Release ---
# Full cycle: bump → sync → update versions.json → commit → push → trigger workflow
release-patch: ## Release patch (0.0.x) — check, bump, publish
$(MAKE) check
npm version patch --no-git-tag-version
$(MAKE) _update-versions-json
$(MAKE) _commit-and-publish
release-minor: ## Release minor (0.x.0) — check, bump, publish
$(MAKE) check
npm version minor --no-git-tag-version
$(MAKE) _update-versions-json
$(MAKE) _commit-and-publish
release-major: ## Release major (x.0.0) — check, bump, publish
$(MAKE) check
npm version major --no-git-tag-version
$(MAKE) _update-versions-json
$(MAKE) _commit-and-publish
publish: ## Trigger GitHub Actions release for current version
@VERSION=$$(jq -r .version package.json); \
echo "Publishing version $$VERSION..."; \
gh workflow run release.yml --field release_notes="$${RELEASE_NOTES:-}"
promote: ## Promote a release from prerelease to stable+Latest (defaults to current package.json version; override with TAG=X.Y.Z)
@TAG=$${TAG:-$$(jq -r .version package.json)}; \
echo "Promoting release $$TAG to stable + Latest..."; \
gh release edit "$$TAG" --prerelease=false --latest; \
echo "$$TAG is now the Latest release"
# Internal targets (not in help)
_update-versions-json:
@VERSION=$$(jq -r .version package.json); \
jq --arg v "$$VERSION" --arg m "$(MIN_OBSIDIAN)" '. + {($$v): $$m}' versions.json > versions.json.tmp && \
mv versions.json.tmp versions.json; \
echo "Updated versions.json with $$VERSION → $(MIN_OBSIDIAN)"
_commit-and-publish:
@VERSION=$$(jq -r .version package.json); \
git add package.json package-lock.json manifest.json mcpb/manifest.json src/version.ts versions.json; \
git commit -m "chore: Bump version to $$VERSION"; \
git push origin HEAD; \
echo "Triggering release for $$VERSION..."; \
gh workflow run release.yml --field release_notes="$${RELEASE_NOTES:-}"
# --- Utility ---
clean: ## Remove build artifacts
rm -rf main.js main.js.map dist/ obsidian-mcp-*.mcpb obsidian-mcp.mcpb
sync-version: ## Sync version + description from package.json to manifest.json, mcpb, version.ts
node sync-version.mjs
mcpb: ## Build MCPB bundle (obsidian-mcp-<version>.mcpb) for Claude Desktop
node scripts/build-mcpb.mjs
scorecard: ## Pull the Obsidian community portal scorecard + freshness delta (free post-release signal)
@node scripts/scorecard.mjs
scorecard-gate: ## Diff the live scorecard vs the committed baseline (exit 1 = regression, 3 = drift)
@node scripts/scorecard-gate.mjs
scorecard-baseline: ## DELIBERATELY re-snapshot scorecard-baseline.json (only after an ACCEPTED change; commit it)
@node scripts/scorecard-baseline.mjs
set-description: ## Set plugin description (SoT: package.json) + sync. Usage: make set-description DESC='...'
@node scripts/set-description.mjs "$(DESC)"
@node sync-version.mjs