Commit graph

529 commits

Author SHA1 Message Date
Aaron Bockelie
535dfd1c53 fix(mcp): stop debug route shadowing the GET /mcp SSE stream + two-row Levenshtein
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>
2026-05-18 21:05:22 -05:00
Aaron Bockelie
0db6b0e7b9
Merge pull request #193 from aaronsb/feat/combine-inline-return
feat(vault): combine returns inline when destination omitted (reimplements #146)
2026-05-18 16:28:22 -05:00
Aaron Bockelie
75b29be6e1
Merge pull request #192 from aaronsb/security/safe-mcp-config-docs
security(docs): safe MCP config approach (reimplements #143)
2026-05-18 16:27:51 -05:00
Aaron Bockelie
6d8b24d3e5
Merge pull request #191 from aaronsb/docs/adr-104-worker-offload-sse
docs(adr): ADR-104 — worker offload + SSE deconfliction (gate for #126)
2026-05-18 16:27:47 -05:00
Aaron Bockelie
e92173fa10 docs(adr): accept ADR-104 (decider review — gate for #126 cleared) 2026-05-18 16:27:38 -05:00
Aaron Bockelie
f55f469cd2 fix(vault): inline combine sourceFiles must match combined order
Code review (PR #193) caught that the inline branch returned
`sourceFiles: paths` (caller input order) while `content` is built from
the local sourceFiles array *after* sortFiles() mutates it in place. With
sortBy set — exactly the sorted multi-file retrieval case this feature
targets — a consumer mapping content sections back to files got the wrong
order. Return the post-sort order instead.

Adds a router-level regression test (the gap the reviewer flagged: the
formatter-only tests could not have caught this).

Co-authored-by: Earl Plak <5597016+laplaque@users.noreply.github.com>
2026-05-18 12:20:48 -05:00
Aaron Bockelie
f10aea7d54 feat(vault): return combined content inline when destination is omitted
`vault combine` without a `destination` now returns the combined content
inline in the response instead of erroring. Read-only consumers can use
combine for multi-file retrieval with no side effects.

- router.ts: no-destination path returns { inline, content, ... }; the
  destination-exists guard is skipped when there is no destination.
- formatters/vault.ts: FileCombineResponse gains optional destination +
  inline/content; formatFileCombine renders an inline block.
- semantic-tools.ts: combine counts as a write-op (blocked in read-only
  mode) only when a destination is given; param description updated.
- tests: formatter inline/destination/fallback coverage.

Reimplemented from PR #146 against current `main` (code only); supersedes
and closes out #146. Dropped the original PR's
`path: destination || '_inline_'` validator workaround — the batch.combine
validators (BatchLimitValidator, PathArrayValidator) never inspect `path`,
so the hack was unnecessary. The README/troubleshooting cert-trust docs
that #146 also carried are covered by the #143 reimplementation instead, to
avoid duplicate divergent copies.

Co-authored-by: Earl Plak <5597016+laplaque@users.noreply.github.com>
2026-05-18 08:10:12 -05:00
Aaron Bockelie
0bd8020804 security(docs): replace claude mcp add --header with safe config-file approach
`claude mcp add --header "Authorization: Bearer <token>"` defeats
secret-at-rest protection: the CLI resolves and echoes the header value to
stdout (captured by any parent process, incl. AI agents) and on macOS the
spawned MCP child argv is written to the unified log. Editing the MCP config
file directly avoids both vectors.

- Settings UI: new shared `renderClaudeCodeConnection()` renders a
  ready-to-paste JSON config + a security warning for the authenticated
  path, and the safe plain CLI command only when auth is disabled. Used by
  both the initial render and the live-refresh handler so they cannot drift
  (the previous two copies had already diverged).
- README / SECURITY.md / troubleshooting.md / issue-32 response template:
  drop `mcp-remote` + `NODE_TLS_REJECT_UNAUTHORIZED=0`, document native HTTP
  transport and proper self-signed-cert trust (macOS Keychain +
  NODE_EXTRA_CA_CERTS for Bun-based runtimes), add the `--header` warning.
- styles.css: `.mcp-security-warning`.

Reimplemented from PR #143 against current `main` (the settings UI had been
restructured since the PR was opened); supersedes and closes out #143.
ADR-100 left untouched — accepted ADRs are immutable records; the `--header`
deprecation is recorded in ADR-104's context instead.

Co-authored-by: Earl Plak <5597016+laplaque@users.noreply.github.com>
2026-05-18 08:10:00 -05:00
Aaron Bockelie
8f882e6bfe docs(adr): ADR-104 — worker-thread offload for CPU-bound ops + SSE route deconfliction
Ratifies the existing (undocumented, v0.5.8b) worker-thread pool as the
sanctioned execution model for CPU-bound semantic operations, and records
the decision to extend it to edit.window fuzzy matching plus deconflict the
GET /mcp SSE route. This ADR is the architectural gate for PR #126 (#125).

Status: Draft — pending decider review.

Refs: #125, #126
2026-05-18 07:59:24 -05:00
Aaron Bockelie
c46d5e13b2 chore: Bump version to 0.11.26 2026-05-16 23:51:53 -05:00
Aaron Bockelie
e6ea9cc1a6
Merge pull request #189 from aaronsb/feat/scorecard-ci-gate
feat(scorecard): scheduled regression gate vs committed baseline (#165)
2026-05-16 23:44:38 -05:00
Aaron Bockelie
024e9cb1b5 harden(scorecard): baseline refuses STALE portal; gate notes it (#165)
Advisor follow-up. Asymmetry by design:
- scorecard-gate.mjs TOLERATES a STALE portal (comparing an old scan
  is harmless) but now prints a non-gating note so a pass/fail is read
  in context.
- scorecard-baseline.mjs REFUSES on STALE (exit 4) the way it refuses
  on drift — anchoring the baseline to an outdated scan would freeze a
  stale truth and mis-calibrate every future gate run.

Validated: gate exit 0 + STALE note when manifest>portal; baseline
exit 4 (refused). Refs #165
2026-05-16 23:43:32 -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
0bf6098b9e
Merge pull request #188 from aaronsb/docs/adr-tls-cert-storage
docs(adr): ADR-103 — TLS cert/key storage strategy for localhost HTTPS (#7)
2026-05-16 23:36:38 -05:00
Aaron Bockelie
905a4d26f6
Merge pull request #187 from aaronsb/fix/scorecard-portal-redesign
fix(scorecard): re-anchor parser to redesigned Obsidian portal (#183)
2026-05-16 23:36:35 -05:00
Aaron Bockelie
92bd69013f docs(adr): ADR-103 — TLS cert/key storage strategy for localhost HTTPS (#7)
Records the decision to keep the per-instance, non-synced, auto-generated
self-signed cert/key and NOT move the private key into the synced vault,
and to dissolve the recurring "store the key in the vault?" question by
documenting the HTTPS scope (opt-in, loopback-only, asserts no identity;
zero-config already delivered by the HTTP-on-localhost default).

Rationale: localhost self-signed cert is low-sensitivity; the API key
(#135) is the real secret and already syncs; vault-shared key is a
defense-in-depth regression for shared/team vaults for marginal gain.

ADR only — no code change; certificate-manager.ts behaviour unchanged.
Independent of the accepted "Direct Filesystem Access" finding.

Refs #7
2026-05-16 23:34:39 -05:00
Aaron Bockelie
c59314d591 fix(scorecard): re-anchor parser to redesigned Obsidian portal (#183)
The 2026 portal redesign replaced the old DOM/RSC structure with a card
UI: Health/Review render as a coloured grade word + a segmented bar
meter, and findings are RSC JSX tuples. The old parser drifted (SCRAPER
DRIFT, null anchors).

- decodeDoc/toText split: keep tags for the structural bar meter, strip
  for prose fields.
- gradeAndScore(): grade = coloured span after the label; numeric score =
  filled/total bar-meter segments (#183 — the portal's visible trust
  signal), window bounded to the next label so Health can't bleed into
  Review.
- Grade vocab no longer enum-pinned (the hard-coded Excellent|Good|… list
  is exactly what drifted) — capture whatever word renders; drift guard
  catches a null.
- Findings re-extracted from ["$","div|details","<text>",{...}] tuples in
  the scorecard region; SIGNATURE re-anchored to the redesign wording
  (**Title**: + neutral scan/attestation sentences).
- Drift guard: numeric scores are now first-class anchors; findingsDrift
  = non-zero issue count with zero findings. Exit 2 on drift preserved.

Validated live: Health Excellent (4/4), Review Satisfactory (3/4), 5
issues, 12 findings, freshness current, integrity ok. Correctly surfaces
the residual "Dynamic Code Execution" finding on 0.11.25 (clears when a
post-ADR-201 release is scanned — the CLAUDE.md caveat).

Refs #183
2026-05-16 23:29:53 -05:00
Aaron Bockelie
b1a8ffe150
Merge pull request #186 from aaronsb/feat/sandboxed-bases-evaluator
feat(bases): sandboxed expression evaluator — replace new Function (ADR-201, PR 2/2)
2026-05-16 23:24:08 -05:00
Aaron Bockelie
73b3276453 harden(bases): runtime-computed-member escape test + scope scanner claim
Advisor review follow-up on #186:
- Add SECURITY cases note[("cons"+"tructor")] / file[("__pro"+"to__")]:
  runtime-computed member names our static pre-walk intentionally does NOT
  catch (property is BinaryExpression, not Identifier/Literal) — they must
  fail closed via expression-eval's own access guard. Test-grounds the
  defense-in-depth claim instead of asserting it. 60/60 green.
- CLAUDE.md 'Known accepted review findings': the Bases new Function is
  REMOVED (ADR-201, #180/#185/#186), not pending #175. Documents the
  honest caveat — grep 'new Function' main.js != 0 by design: residual is
  transitive ajv@6.14.0 validator codegen + a deprecation shim, a
  pre-existing library-internal class not reachable from vault content,
  present on the reviewed 0.11.25, non-gating. Scanner finding was
  attributed to the Bases path; this closes that path.

Refs #180, ADR-201
2026-05-16 23:03:43 -05:00
Aaron Bockelie
b9d99edfd7 feat(bases): replace new Function with expression-eval (ADR-201)
PR2 of 2 for #180 (ADR-201). Closes the arbitrary-code-execution vector
reachable through a synced/shared `.base` file.

- Drop `new Function('context', 'with(context){return ${expr}}')` (and its
  no-implied-eval eslint-disable) from expression-evaluator.ts.
- Parse with expression-eval@5.0.1 (jsep grammar: no eval/Function/new, no
  global scope; member denylist built in). Pinned exact; jsep 0.3.5 is its
  audited transitive parser. Both >7d old; npm audit 0 vulns.
- Defense-in-depth AST pre-walk (assertNoForbiddenAccess): rejects
  constructor/__proto__/prototype member access (Identifier AND computed
  ["constructor"] forms) and ThisExpression, independent of the library's
  internal list — ADR-201's explicit, tested no-globals property lives in
  our code, not a transitive dep.
- createEvalContext and all helpers untouched → behavioural parity.

Differential corpus (tests/bases-expression-evaluator.test.ts, from PR1):
57 EVAL_CASES still green = expression-eval == old new Function for every
realistic Bases construct; the formerly-executing escapes (2/42/99 under
new Function) now fail closed; SECURITY set never reaches a function, the
real global, or a value.

No behavioural change for legitimate `.base` expressions. Breaking only by
design: the RCE vectors, `this`, and prototype-chain access are now
refused. Clears the Obsidian "Dynamic Code Execution" finding.

209/209 tests; build + lint clean. Refs #180, ADR-201
2026-05-16 22:57:43 -05:00
Aaron Bockelie
0043e9acc9
Merge pull request #185 from aaronsb/test/bases-expr-corpus
test(bases): differential corpus + new Function baseline (ADR-201, PR 1/2)
2026-05-16 17:32:57 -05:00
Aaron Bockelie
5cdd131c41 test(bases): differential corpus + new Function baseline for ADR-201
PR1 of 2 for #180 (ADR-201). Lands the regression net independently of
library choice, against the *current* `new Function` evaluator as the
behavioural oracle.

- Expand tests/fixtures/base-corpus.ts: EVAL_CASES (~57 expr/expected
  pairs spanning every operator class + every fn/property the evaluator
  exposes), makeNoteContext() canonical fixed context, SECURITY_EXPRESSIONS
  (sandbox-escape set). FILTER_EXPRESSIONS kept as a derived export.
- tests/bases-expression-evaluator.test.ts: locks current behaviour over
  the corpus AND proves the live RCE — constructor.constructor reaches the
  Function constructor through the `with` scope chain (returns 2/42/99
  today). PR2 inverts the SECURITY block.

Time-dependent primitives (now/today) appear only inside relations stable
for all wall-clock times, so expectations never rot.

209/209 tests pass; build + lint clean.

Refs #180, ADR-201
2026-05-16 17:05:26 -05:00
Aaron Bockelie
aa346d4c9a chore: Bump version to 0.11.25 2026-05-16 14:23:03 -05:00
Aaron Bockelie
5b87d4d3f6
Merge pull request #182 from aaronsb/fix/yaml-migration
refactor(bases): migrate js-yaml → yaml (maintained), differential-tested
2026-05-16 14:22:32 -05:00
Aaron Bockelie
ed0ceac56d
Merge pull request #184 from aaronsb/fix/statusbar-orphan
fix(ui): stop orphaning status-bar elements on async startup
2026-05-16 14:21:32 -05:00
Aaron Bockelie
9309a618cb
Merge pull request #179 from aaronsb/chore/code-of-conduct
docs: add Contributor Covenant 2.1 code of conduct
2026-05-16 14:21:29 -05:00
Aaron Bockelie
cf3b4a6a04
Merge pull request #181 from aaronsb/adr-201-bases-expression-evaluator
docs(adr): ADR-201 — sandboxed expression evaluator for Bases
2026-05-16 14:21:26 -05:00
Aaron Bockelie
d9c7452c01 test(bases): address #182 review — accurate date-safety rationale + explicit divergences
Code review found the date-safety doc cited the wrong line (date() global,
not the real reconciliation). Correct it: (1) Obsidian metadata cache is the
primary frontmatter source, this parser is only a fallback; (2) when the
fallback runs, expression-evaluator auto-coerces date-like keys via
new Date(); (3) .base docs have no date scalars.

Surface the masked divergences instead of hiding them behind toBeFalsy:
- empty doc: yaml→null vs js-yaml→undefined — asserted concretely, proven
  neutralized by parseFrontmatter's typeof/!==null guard.
- merge keys: js-yaml resolves <<:*anchor, yaml keeps literal — asserted as
  a known, accepted boundary (.base/frontmatter never use merge keys).
- input anchors/aliases: confirmed both resolve structurally.
Documented all in yaml-bridge.ts. 149/149 green.
2026-05-16 14:20:23 -05:00
Aaron Bockelie
20679473b0 fix(ui): stop orphaning status-bar elements on async startup
Closes #178. updateStatusBar() previously remove()'d and re-addStatusBarItem()'d
on every call. It fires multiple times during async startup (port-conflict
paths, server-start callbacks, settings); concurrent calls could each add an
element while only the last was tracked, orphaning a transient 'Mcp: error'
element that persisted until the next Obsidian reload.

Create the element exactly once and mutate it thereafter (setText + class
swap). showConnectionStatus=false now hides the single element via the
existing mcp-hidden utility instead of leaving/removing DOM. Structurally
eliminates duplicate/orphaned indicators regardless of call ordering.
2026-05-16 14:14:57 -05:00
Aaron Bockelie
366d329140 refactor(bases): migrate js-yaml → maintained yaml package
Closes #174. Obsidian source-code review flagged js-yaml (unmaintained) for
replacement. Rewire bases-api.ts's 5 call sites onto yaml-bridge; drop
js-yaml + @types/js-yaml. yaml pinned to 2.8.4 (published 2026-05-02, clears
the project's 7-day supply-chain hold; 2.9.0 was 5 days old). Existing
overrides.yaml reconciled to $yaml so the direct dep governs transitive
resolution. Behaviour-equivalence proven by the differential corpus.
2026-05-16 14:08:46 -05:00
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