Commit graph

649 commits

Author SHA1 Message Date
Aaron Bockelie
860d527028 test(bases): add yaml seam + differential corpus vs js-yaml oracle
Introduces src/utils/yaml-bridge.ts as the single YAML parse/serialize seam
for the Bases subsystem, and a reusable .base corpus + differential tests
that run every fixture through both js-yaml (still resolvable transitively as
the behavioural oracle) and the new yaml-backed bridge, asserting
equivalence. The one intentional difference — js-yaml coerced bare/ISO dates
to Date, yaml keeps strings — is asserted explicitly and proven downstream-
safe (expression-evaluator already does new Date(value) on non-Date input).
Corpus is structured for ADR-201/#180 reuse.
2026-05-16 14:08:46 -05:00
Aaron Bockelie
638ae4bedb docs(adr): ADR-201 replace new Function in Bases evaluator with sandboxed lib
Closes #175. Records the decision to replace the arbitrary-JS new Function
in expression-evaluator.ts with a vetted no-eval expression library — driven
by the security reality (.base files are syncable/shareable, so the current
path is arbitrary code execution from untrusted files in a live plugin), not
the (non-gating) scanner finding.

Hard constraints recorded: no hand-rolled parser (its own risk class), and a
mandatory differential test corpus (new == old). Implementation tracked in
#180; rejected alternatives (accept-and-document, hand-rolled parser, heavy
sandbox) documented with rationale.
2026-05-16 13:58:18 -05:00
Aaron Bockelie
c274481bc3 docs: add Contributor Covenant 2.1 code of conduct
Closes a repo-health/hygiene gap surfaced by the Obsidian community
trust/health signal. Canonical Covenant 2.1 text; reporting contact set to
the maintainer email already public in git history.
2026-05-16 13:52:27 -05:00
Aaron Bockelie
164e44bf0b chore: Bump version to 0.11.24 2026-05-16 13:43:43 -05:00
Aaron Bockelie
1e66b399e7
Merge pull request #177 from aaronsb/docs/post-listing-refresh
docs: post-listing refresh + accepted review findings (#176)
2026-05-16 13:43:27 -05:00
Aaron Bockelie
9784be9e09 docs: post-listing refresh + accepted review findings (#176)
Plugin is live in the community directory (2026-05-16). Capture settled
knowledge so it isn't re-investigated:

- README: link to the public Obsidian listing; fix stale '(coming soon)'
  install section (it's live) and the search term to the real plugin name.
- CLAUDE.md: document the portal=source-of-truth -> bot-mirrors-to
  community-plugins.json -> in-app browser reads the mirror architecture,
  and the resulting post-approval propagation lag (normal, not a bug).
- CLAUDE.md: broaden 'accepted scorecard cautions' to 'accepted review
  findings' covering the full submit review — fs access, vault enumeration,
  clipboard, and the README placeholder false positive — each with its
  justification, so they aren't re-litigated. Refresh the issue tracker line.

Closes #176.
2026-05-16 13:20:41 -05:00
Aaron Bockelie
7323e00aac
Merge pull request #173 from aaronsb/fix/css-no-important
fix(css): remove !important via selector specificity
2026-05-16 13:17:19 -05:00
Aaron Bockelie
e6c52570a1 fix(css): remove !important via selector specificity
Obsidian CSS-lint flags !important. Replace with element-qualified selectors
that win on specificity instead:
- button.mcp-hidden out-specifies framework button display (was the reason
  for display:none !important)
- input.mcp-api-key-input out-specifies container user-select:none, keeping
  the API key selectable/copyable
Behavior-preserving by design; needs a visual confirm (see PR).
2026-05-16 12:58:40 -05:00
Aaron Bockelie
d673f68013
Merge pull request #171 from aaronsb/chore/review-easy-fixes
chore: low-risk Obsidian review fixes (builtin-modules, README title)
2026-05-16 12:48:33 -05:00
Aaron Bockelie
28cc1ea5e6 docs: align README title with manifest plugin name
Obsidian review flags README title != manifest.json name. Title was
'Obsidian MCP Plugin'; manifest name is 'Semantic Notes Vault MCP'. Match it.
2026-05-16 12:45:28 -05:00
Aaron Bockelie
abaa9106fb build: replace builtin-modules with native node:module builtinModules
Obsidian source-code review flags builtin-modules as a package to replace.
node:module has exposed builtinModules natively since Node 6.x; esbuild only
used it to externalize Node builtins from the bundle. Behavior-equivalent
swap, devDependency removed.
2026-05-16 12:45:28 -05:00
Aaron Bockelie
7a0051d301 chore: Bump version to 0.11.23 2026-05-16 12:26:28 -05:00
Aaron Bockelie
00d17a6a06
Merge pull request #170 from aaronsb/fix/attest-all-assets
fix(ci): attest all installable assets (styles.css/manifest.json), not just main.js
2026-05-16 12:26:14 -05:00
Aaron Bockelie
43d83ed061 fix(ci): attest styles.css and manifest.json, not just main.js
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.
2026-05-16 12:25:13 -05:00
Aaron Bockelie
70884bbcad chore: Bump version to 0.11.22 2026-05-16 12:15:51 -05:00
Aaron Bockelie
0c99c7e2f9
Merge pull request #169 from aaronsb/fix/min-obsidian-sync
fix(release): align MIN_OBSIDIAN with manifest minAppVersion
2026-05-16 12:15:33 -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
a9ade5298b
Merge pull request #168 from aaronsb/docs/plugin-description
chore(meta): make pattern for plugin description + reality-based copy
2026-05-16 12:13:15 -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
ef10b16ebf
Merge pull request #166 from aaronsb/delivery/scorecard-tooling
feat(scorecard): pull Obsidian portal scorecard as free post-release signal
2026-05-16 12:11:10 -05:00
Aaron Bockelie
8a7c9aadca
Merge pull request #167 from aaronsb/fix/scorecard-cautions
fix: clear actionable Obsidian scorecard cautions (#163, #164)
2026-05-16 12:11:00 -05:00
Aaron Bockelie
ead012c536 docs: clarify listing-text coupling — short is repo-driven, long About is portal-owned
The portal long 'About' matches nothing in the repo because Obsidian seeded
it portal-side during the community.obsidian.md migration; it's decoupled
from package.json/manifest and only editable in the authenticated dev portal.
Document so future work doesn't try to change it via the repo.
2026-05-16 12:08:51 -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
64827f7baa ci(release): attest build provenance for main.js + mcpb bundles
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.
2026-05-16 11:59:51 -05:00
Aaron Bockelie
fd53f45149 fix(tls): drop inert rejectUnauthorized:false default, clears scorecard Risk
Closes #163. The literal only existed in DEFAULT_SETTINGS and was passed to
the inbound https.createServer, which never sets requestCert — so Node ignored
it. certificate-manager already defaults the effective value to true via
`!== false`, so removing the default is behavior-preserving and removes the
exact token Obsidian's scorecard pattern-matches as 'disables SSL verification'.

Adds tests/security/tls-cert-verification.test.ts as a regression guard:
fails if the flagged literal returns or the secure default flips.
2026-05-16 11:59:51 -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
3cd6a1cb27
Merge pull request #162 from aaronsb/delivery/release-strategy-adr
docs: ADR-300 single-branch release strategy + retire defunct PR guidance
2026-05-16 11:40:32 -05:00
Aaron Bockelie
3c19cf5d2f docs: record single-branch release strategy (ADR-300), retire defunct PR submission guidance
Obsidian moved community-plugin submission from the obsidian-releases PR
process to the community.obsidian.md developer portal. Replace the now-defunct
'I'm Not Dead Yet' PR-refresh procedure in CLAUDE.md with the portal-based
distribution reality (stable Latest release matching manifest, no v-prefix,
versions.json, make promote).

ADR-300 records the decision to keep a single main branch with the existing
prerelease/promote flow rather than add a dedicated Obsidian release branch:
for 'same code, slower cadence' the prerelease/promote gap already provides
the decoupling, and a release branch would only add permanent version-file
divergence. Rejected alternatives documented.
2026-05-16 11:39:27 -05:00
Aaron Bockelie
b5d0c7c67f
Merge pull request #160 from aaronsb/fix/dataview-luxon-and-lint-compat
fix(dataview): Luxon dates, error propagation, and obsidianmd 0.3.0 compat (v0.11.21)
2026-05-15 15:03:14 -05:00
Aaron Bockelie
3608eac37b chore(release): bump version to 0.11.21 and raise minAppVersion to 1.6.6
The plugin code already calls APIs introduced in newer Obsidian versions
(FileManager.trashFile in 1.6.6, Workspace.getLeaf in 0.16.0,
Vault.createFolder in 1.4.0, ToggleComponent.setDisabled in 1.2.3,
AbstractTextComponent.setDisabled in 1.2.3, ButtonComponent.setTooltip
in 1.1.0). Declaring minAppVersion 0.15.0 was misleading — anyone
between 0.15 and 1.5 would have the plugin load and then crash at
runtime. Set the floor to 1.6.6 to match the highest API actually used.
2026-05-15 15:01:29 -05:00
Aaron Bockelie
f618cffcca chore(lint): support eslint-plugin-obsidianmd@0.3.0
The 0.3.0 release adds typed rules (no-plugin-as-component, no-view-references-in-plugin,
no-unsupported-api, prefer-file-manager-trash-file, prefer-instanceof) and wires
them into the hybrid recommended config without a **/*.ts files scope. As a
result they also try to run against package.json — which uses ESLint's JSON
language and has no @typescript-eslint parser services — and ESLint aborts
the whole run with "Error while loading rule 'obsidianmd/no-plugin-as-component'".

- eslint.config.mts: disable those five typed rules on JSON files, and
  ignore the data files the recommended config doesn't wire a JSON parser
  for (tsconfig.json, package-lock.json, manifest.json, versions.json,
  src/config/**/*.json, .claude/**).
- Keep the autofix for `setTimeout` → `window.setTimeout` and
  `globalThis` → `window` in code paths that only run inside Obsidian
  (main.ts, main-complex.ts, worker-manager.ts, connection-pool.ts,
  tools/fetch.ts) — that's the popout-window-compat behaviour the rule
  exists to enforce.
- Revert the autofix in code paths imported by Jest tests where `window`
  is undefined: session-manager.ts and obsidian-api.ts background timers,
  semantic/router.ts active-file timeout, debug.ts console reference.
  Each retains a `// eslint-disable-next-line` with the reason
  (background timer / Node test compat).
2026-05-15 15:01:03 -05:00
Aaron Bockelie
1c6fae133f chore(deps-dev): bump eslint-plugin-obsidianmd to ^0.3.0
Matches the version Dependabot is proposing in #153 so the lint-config
support below lands in main ahead of the broader dev-deps bundle merging.
2026-05-15 15:00:44 -05:00
Aaron Bockelie
b5b1ebdfd2 fix(dataview): serialize Luxon dates and propagate Dataview-internal errors
Closes #115. Closes #123.

The Dataview API returns Luxon DateTime objects (which expose toISO(), not
toISOString()) for file.ctime/mtime, so listPages() and getPageMetadata()
crashed with "toISOString is not a function" the moment any real Dataview
result reached them. The existing tests passed only because the mocks used
native Date, which exposes toISOString().

The query path had a separate envelope bug: executeQuery() hard-coded
success: true whenever dataviewAPI.query() didn't throw, while Dataview
itself returns { successful: false, error: "..." } for malformed queries
without throwing. The formatter then read response.successful (undefined),
hit the failure branch, and rendered every result as " Query failed:
Unknown error" — even when the query had succeeded.

- Add toIsoOptional() that prefers Luxon's toISO() and falls back to
  Date.toISOString(); apply at every page.file.ctime/mtime serialization
  site and inside convertDataviewValue().
- Propagate result.successful and result.error from the inner Dataview
  response to the outer tool envelope.
- Add a dataview.query normalizer case in the formatter dispatcher that
  flattens result.{type,values,headers} to the top level and maps
  success → successful, matching the formatter's contract.
- Regression tests for both bugs, including a Luxon-shaped mock distinct
  from the existing Date-shaped one.
2026-05-15 15:00:11 -05:00
Aaron Bockelie
dfa5affad7 chore: Bump version to 0.11.20 2026-05-15 13:58:25 -05:00
Aaron Bockelie
32bc611118
Merge pull request #159 from aaronsb/fix/router-pagination-param-coercion
fix(router): Numeric pagination params silently dropped (parseInt(paramStr) → paramNum)
2026-05-15 13:58:08 -05:00
Aaron Bockelie
c3ed7f6e7a fix(router): Use paramNum for numeric pagination params
Pagination was silently a no-op for the MCP-facing surface. MCP
clients send numeric arguments (page, pageSize, snippetLength) as
JSON numbers; paramStr returns undefined for non-string values, so
parseInt(paramStr(...) ?? '1') always collapsed to defaults.

Caught while live-verifying the #158 pagination work shipped in
0.11.19: a call with page=2 pageSize=50 returned page 1 pageSize 20.
The router was discarding the user's values before the call ever
reached listFilesPaginated.

Three call sites fixed in src/semantic/router.ts:
- vault.list (line 174-175)  — the immediate verifier
- vault.search (line 286-287)
- vault.search snippet length (line 306)

paramNum already existed at line 44; this commit just wires it in.
2026-05-15 13:56:52 -05:00
Aaron Bockelie
61417387d3 chore: Bump version to 0.11.19 2026-05-15 13:42:45 -05:00
Aaron Bockelie
ebb7f7faec
Merge pull request #158 from aaronsb/feat/vault-list-pagination-hint
feat(vault.list): Coherent pagination + agent next-call hints
2026-05-15 13:42:25 -05:00
Aaron Bockelie
e822d7607f fix(vault.list): Align paginated recursive sort with flat listFiles
Code-reviewer caught a real bug: listFiles (recursive flat) sorts by
full path, but listFilesPaginated in recursive mode was sorting
folders-first-then-by-basename. So the agent's "use page=2 pageSize=50"
hint anchored to a path-sorted universe, but page 2 returned a
basename-sorted universe — overlaps and gaps for any caller trying to
walk the listing.

Recursive paginated mode now sorts by full path, matching listFiles.
Level-only mode (the default for internal callers — cp recursion,
isDirectory probe) keeps the folder-first-then-basename order it
needs for tree navigation.

New test (`paged universe matches the flat listFiles universe`)
concatenates every page and asserts equality with the flat call —
exactly the agent-facing contract the formatter's next-page hint
promises.

Router comment also clarifies why root paginated stays recursive=false
(getAllLoadedFiles is already recursive — the path collapses to the
same answer either way).

Refs PR #158 review.
2026-05-15 13:40:35 -05:00
Aaron Bockelie
d3c025ada3 feat(vault.list): Coherent pagination + agent next-call hints
When the formatter truncated a directory listing with "... and N more",
the agent had no usable next call. \`page=2 pageSize=50\` would have
returned a fundamentally different shape (level-only folder entries
from listFilesPaginated, not the next 50 files from the recursive
listFiles), so any hint to "paginate" would have led the agent
somewhere broken.

This change makes pagination semantically consistent and gives the
agent an explicit, working escape hatch:

- listFilesPaginated gains a recursive flag (default false to
  preserve existing internal callers — cp at router.ts:1049 and the
  isDirectory probe at router.ts:1021 still get level-only).
- vault.list passes recursive=true when paginating with a non-root
  directory. Page N of vault.list('foo') now returns the Nth slice
  of the same flat universe vault.list('foo') (unpaginated) would
  return — no shape switch.
- The flat-list formatter, when it truncates at 50, now tells the
  agent exactly what to call next: page=2 pageSize=50, or raw:true
  for the full list in one response.
- The structured-response formatter shows "Page X of Y" and, when
  there are more pages, surfaces the next-page call with the
  current pageSize baked in.
- normalizeResponse gains a vault.list case translating the API
  response's \`type: 'file'|'folder'\` to the formatter's \`isFolder\`
  boolean. Previously the structured branch's filter on f.isFolder
  always failed silently, lumping every entry into "Files" even when
  half were folders.

Two new tests in tests/list-files-recursive.test.ts:
- recursive paginated mode produces the expected slice with no
  cross-page overlap
- recursive=false (default) preserves the level-only behavior the
  internal callers depend on

Refs #154 (extends the agent-facing fix).
2026-05-15 13:30:45 -05:00
Aaron Bockelie
6e8b6f2a3e chore: Bump version to 0.11.18 2026-05-15 13:20:32 -05:00
Aaron Bockelie
d2b083dd0f
Merge pull request #144 from laplaque/fix/system-info-http-https-ports
fix(system): report both HTTP and HTTPS ports in system info
2026-05-15 13:19:54 -05:00
Aaron Bockelie
6e65ef997b
Merge pull request #145 from laplaque/fix/fetch-web-error-formatter
fix(formatters): extract error text from content array in fetch_web response
2026-05-15 13:19:25 -05:00
Aaron Bockelie
e8798756ed
Merge pull request #157 from aaronsb/fix/vault-list-folders-154
fix(vault): Recurse listFiles into subfolders (#154)
2026-05-15 13:18:43 -05:00
Aaron Bockelie
d636fffe78 fix(vault): Recurse listFiles into subfolders (#154)
vault.list(directory) returned an empty array whenever the target
folder's direct children were all subfolders — listFiles used
folder.children and then filtered to TFile, dropping every
descendant. Meanwhile the root case used vault.getAllLoadedFiles()
which is recursive, so root and directory listings diverged.

listFiles now walks the folder tree when a directory is provided,
matching the recursive semantics of the root case. Callers that
were already iterating result paths get the same shape — full
vault-rooted paths to .md files — they just no longer hit the
empty-list cliff for folder-of-folders inputs.

The synchronous throw on missing directory is preserved; existing
try/catch sites at router.ts:538 and :1030 use it as a "does this
exist as a directory" probe.

Adds tests/list-files-recursive.test.ts with regression coverage:
- recursing through a folder of subfolders returns all descendant files
- recurses through arbitrary nesting depth
- still throws on a missing directory
- root call (no directory) still returns the full vault

Test fixtures: tests/__mocks__/obsidian.ts gained a TFolder class
and a stat field on TFile.

Closes #154.
2026-05-15 13:16:45 -05:00
Aaron Bockelie
b79ca5fdb9 chore(deps): Clear residual audit vulnerabilities + pin yaml
After merging the dependabot batch, npm audit still flagged 5
transitive vulns (4 high, 1 moderate). Running npm audit fix lifts
deep deps to patched ranges and resolves all of them — except it
tries to land yaml@2.9.0 which was published 2026-05-11, inside the
7-day hold window.

Added a package.json override to pin yaml to ~2.8.4 (published
2026-05-02, 13 days aged). 2.8.4 already fixes the CVE-flagged
"Stack Overflow via deeply nested YAML collections" (range
2.0.0-2.8.2), and we get the safety of an aged release.

Final state: 0 vulnerabilities, 127/127 tests still green, all
upgrades respect the 7-day rule.
2026-05-15 13:08:53 -05:00
Aaron Bockelie
30ab1e600b
Merge pull request #151 from aaronsb/dependabot/npm_and_yarn/hono-4.12.18
chore(deps): Bump hono from 4.12.7 to 4.12.18
2026-05-15 13:05:26 -05:00
Aaron Bockelie
3fbee05776
Merge pull request #149 from aaronsb/dependabot/npm_and_yarn/multi-7bdfbe8666
chore(deps): Bump ip-address and express-rate-limit
2026-05-15 13:04:50 -05:00
Aaron Bockelie
8d572ceb1d
Merge pull request #121 from aaronsb/dependabot/npm_and_yarn/hono/node-server-1.19.13
chore(deps): Bump @hono/node-server from 1.19.10 to 1.19.14
2026-05-15 13:04:44 -05:00
dependabot[bot]
9c604fce97
chore(deps): Bump hono from 4.12.7 to 4.12.18
Bumps [hono](https://github.com/honojs/hono) from 4.12.7 to 4.12.18.
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.7...v4.12.18)

---
updated-dependencies:
- dependency-name: hono
  dependency-version: 4.12.18
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-15 18:03:52 +00:00