[ { "version": "0.8.0", "date": "2026-09-19", "zh": { "title": "新增 obsidian_plugin_eval,并写下硬性验收规则", "items": [ "新增 obsidian_plugin_eval(高特权、必审):在运行中的 App 里执行 JavaScript 并返回结果,是实时状态的逃生舱——读 App 真正持有的东西(插件实例、workspace、metadataCache)、驱动一次交互,或不做重新构建就试一个修法。它是唯一的高特权工具,因此需用户审批;返回会把实际执行的代码回显出来作为审计,代码触及窗口焦点时(如 electron.remote.getCurrentWindow().focus())会明确警告,因为那会抢走用户的焦点。参数为 code(必填)、vault(可选目标库)、timeoutMs(可选,默认 30000);只读能回答的优先用 obsidian_plugin_inspect(免费、无需审批)。", "验收规则(硬性):obsidian_plugin_build 通过 ≠ 插件可用,obsidian_plugin_test 的 PASS ≠ UI 已验证。只要改动涉及界面、渲染或交互,就必须真正看到它——沙箱档 obsidian_plugin_e2e,或对着运行中的 App 用 obsidian_plugin_inspect action=screenshot——并在回复里说明看到了什么。这条规则来自真实任务:改动是全新 UI,build + test 全绿,却从未部署,用户什么也没看到。", "离线冒烟的报告现在点名「本次没有覆盖什么」:没有 DOM 宿主时会写明「注册为编辑器扩展的 UI 代码未被检验」,并指向 e2e / screenshot 做真正的验收,避免 PASS 被误读为 UI 已验证。" ] }, "en": { "title": "Add obsidian_plugin_eval, and write down the mandatory acceptance rule", "items": [ "New obsidian_plugin_eval (high privilege, approval-gated): runs JavaScript inside the running app and returns the result — the live-state escape hatch for reading what the app really holds (plugin instances, workspace, metadataCache), driving an interaction, or trying a fix without rebuilding. It is the only high-privilege tool, so it is approval-gated; the executed code is echoed back in the result as an audit trail, and the result warns when the code touches window focus (e.g. electron.remote.getCurrentWindow().focus()), because that steals the user's focus. Parameters: code (required), vault (optional target vault), timeoutMs (optional, default 30000); prefer obsidian_plugin_inspect (free, no approval) whenever a read-only action answers the question.", "Acceptance rule (mandatory): obsidian_plugin_build passing does not mean the plugin works, and obsidian_plugin_test PASSing does not mean the UI was verified. If a change touches interface, rendering or interaction, it must actually be seen — in the sandboxed e2e tier (obsidian_plugin_e2e), or against the running app (obsidian_plugin_inspect action=screenshot) — and the reply must say what was seen. The rule comes from a real session: a pure UI change passed build + test and was never deployed, so the user saw nothing.", "The offline smoke report now names what it did NOT cover: without a DOM host it says plainly that UI code registered as an editor extension was not exercised, and points at e2e / screenshot for real acceptance, so a PASS cannot be misread as verified UI." ] } }, { "version": "0.7.1", "date": "2026-09-19", "zh": { "title": "离线冒烟:修复工作区外项目的沙箱拒绝,并让覆盖范围可见", "items": [ "修复:harness 为绕过 \"type\":\"module\" 而把产物复制成同目录的 .cjs,当插件项目位于会话工作区之外时会被沙箱拒绝(EPERM),导致 obsidian_plugin_test 对这类项目直接失败;现在改为复制到可写临时目录。", "新增探测:注册的编辑器视图插件会被实例化一次,因此以 UI 为主要功能的插件,其视图/界面代码现在能被离线覆盖(需项目自带 jsdom)。", "结论文案按实际情况区分:无 DOM 宿主时明确写出「注册为编辑器扩展的 UI 代码未被检验」,不再让 PASS 被误读为 UI 已验证。", "环境缺口(缺少 DOM、桩未建模的 API)不再被误报为插件缺陷。" ] }, "en": { "title": "Offline smoke: fix the sandbox denial for out-of-workspace projects, and make coverage visible", "items": [ "Fix: to sidestep \"type\":\"module\" the harness copied the bundle to a .cjs file next to it, which the sandbox refuses when the plugin project lives outside the session workspace (EPERM) — obsidian_plugin_test failed outright for such projects. It now stages the copy in a writable temp directory.", "New probe: registered editor view plugins are instantiated once, so a plugin whose feature is its UI now gets that view/UI code covered offline (requires jsdom in the project).", "The conclusion is stated per run: without a DOM host it says plainly that UI code registered as an editor extension was NOT exercised, so a PASS cannot be misread as verified UI.", "Environment gaps (missing DOM, an API the stub does not model) are no longer reported as plugin defects." ] } }, { "version": "0.7.0", "date": "2026-09-19", "zh": { "title": "真机观测的失败上下文与窗口状态提示", "items": [ "inspect status 降噪:默认只报目标库与「实际应答库」并给出已注册库数量,完整列表改为 all=true 时才输出。", "reload 失败时直接带上「应答窗口的库是谁」,不必再为「命令为何 not found」额外排查一遍。", "vault ensure 增加恢复能力:目标库窗口已打开却未渲染时,显式把该窗口带到前台并复检,同时明确告知「本次操作移动了你的焦点」;失败则列出可选做法。", "inspect screenshot 先读窗口渲染状态:隐藏/最小化/未聚焦时直接说明截图可能是空白,并劝阻用 electron.remote 强拉窗口。", "技能补充显式禁令与排障行:禁止用 remote.show()/focus() 抢焦点;截图请使用语义化且互不相同的路径。" ] }, "en": { "title": "Failure context and window-state reporting for live observation", "items": [ "inspect status is quieter: it reports the target vault, which vault actually answered, and a count of registered vaults; the full list is opt-in via all=true.", "reload failures now name the vault the addressed window is showing, so \"why was the plugin not found\" no longer needs a separate investigation.", "vault ensure can recover a vault window that is open but not rendering: it raises that window explicitly, re-confirms, and says plainly that it moved your focus; when that fails it lists the options.", "inspect screenshot reads the window's render state first: when hidden, minimized or unfocused it says the capture may be blank and warns against forcing the window forward with electron.remote.", "The skill gains an explicit prohibition (never force focus with remote.show()/focus()) and the habit of giving each screenshot a distinct, meaningful path." ] } }, { "version": "0.6.0", "date": "2026-09-18", "zh": { "title": "验证四档:离线冒烟与沙箱化 E2E", "items": [ "新增 obsidian_plugin_test(离线冒烟,L2 档):在纯 Node 里用桩化的 Obsidian API 加载构建产物,真跑一遍生命周期——默认导出是 Plugin 子类、onload() 执行、注册动作发生、onunload() 清理、无未处理的 rejection,不需要安装 Obsidian。它会真实解析项目自身的 @codemirror/* 与 @lezer/*,DOM 相关插件可用项目自带的 jsdom;它不验证运行期行为:报告固定标注这是桩环境,并指向 inspect/e2e 做真实验证。", "新增 obsidian_plugin_e2e(沙箱化端到端测试,L4 档):init 用 WebdriverIO + wdio-obsidian-service 生成 wdio.conf.mts、tsconfig.e2e.json 与起始 spec(脚手架目录由 dir 指定,默认 e2e/),并加入 e2e / e2e:watch 脚本;它启动的独立 Obsidian 使用独立配置目录与库副本,因此不切用户窗口、不抢焦点。status 只报告现状与确切的安装命令,不写任何文件;runner 依赖留在用户项目里。", "验证明确为四档,并写明了默认档:L1 静态(build 内置)、L2 离线冒烟(test,无需 Obsidian)、L3 用户自己的 Obsidian(vault / reload / inspect / eval,会切换窗口、抢焦点)、L4 沙箱 Obsidian(e2e)。日常开发循环默认走 L1 + L2 + L4;L4 档的存在意义就是让默认循环不再打扰用户——抢焦点原本来自我们自己调用 vault-open,沙箱实例用「独立实例 + 库副本」把它从根上消除,用户窗口全程不动。L3 只在需要验证用户真实环境时使用。", "obsidian_plugin_build 的入口与产物改为从项目自身配置解析:入口按「显式 entry → src/main.ts → 仓库根 main.ts → 项目自身的 esbuild/rollup/vite 配置 → package.json main」,产物按「项目根 → 配置里的 outfile/outdir/file/dir → 项目内测试库布局 → 有界搜索(含 /.obsidian/plugins//)」,两者失败时都会列出全部尝试过的位置;新增 entry / outDir 参数。", "构建层级优先级按实测修正:只要存在打包配置就优先跑项目自身的 production build 脚本——配置里带的插件(如 esbuild-svelte)无法从命令行补齐;项目本地 esbuild 直调只在没有配置可遵循、或显式选择非 production 时使用,并会警告未应用项目插件。", "写入范围成为硬门禁:只允许修改会话工作区内创建的测试库,用户自己的库只读——包括沙箱拦不到的 CLI 侧间接写入(plugin:enable / unrestrict 改写的是当前活动窗口那个库),工具会拒绝对非测试库执行。默认路径绝不切换用户窗口、绝不抢焦点。" ] }, "en": { "title": "Four verification tiers: offline smoke and sandboxed e2e", "items": [ "New obsidian_plugin_test (offline smoke, tier L2): loads the built bundle in plain Node against a stubbed Obsidian API and exercises the real lifecycle — default export is a Plugin subclass, onload() runs, registrations happen, onunload() cleans up, no unhandled rejections — with no Obsidian installed. It resolves the project's own @codemirror/* and @lezer/* for real and can use the project's own jsdom for DOM-dependent plugins; it does not verify runtime behaviour, so the report always says it is a stub environment and points at inspect/e2e for real verification.", "New obsidian_plugin_e2e (sandboxed end-to-end tests, tier L4): init generates wdio.conf.mts, tsconfig.e2e.json and a starting spec with WebdriverIO + wdio-obsidian-service (scaffold directory via dir, default e2e/), and adds the e2e / e2e:watch scripts; the Obsidian it launches uses an isolated config directory and a copy of the vault, so nothing switches the user's window or steals focus. status reports only what exists and the exact install command, writing nothing; the runner dependencies stay in the user's project.", "Verification is now explicitly four tiers, with the default stated: L1 static (inside build), L2 offline smoke (test, no Obsidian needed), L3 the user's own Obsidian (vault / reload / inspect / eval — it switches the window and steals focus), L4 sandboxed Obsidian (e2e). The default development loop is L1 + L2 + L4, and the e2e tier exists so that the default loop never switches the user's window — the focus stealing came from our own vault-open calls, and a sandboxed instance with its own config directory and a copy of the vault removes it outright. L3 is used only when the user's real environment is the question.", "obsidian_plugin_build now resolves its entry point and artifact from the project's own configuration: entry via explicit entry → src/main.ts → a repository-root main.ts → the project's own esbuild/rollup/vite config → package.json main, artifact via project root → the config's outfile/outdir/file/dir → the project-local vault layout → a bounded search that also probes /.obsidian/plugins//; both list every location tried on failure, and the new entry / outDir parameters pin either one.", "Build tier precedence corrected by evidence: whenever a bundler config exists, the project's own production build script now runs first — a config can carry plugins (esbuild-svelte) that cannot be supplied from the command line; direct project-local esbuild is used only when there is no config to honour, or as an explicit non-production opt-out that warns the project's plugins were not applied.", "Write scope is now a hard gate: only test vaults created inside the session workspace may be modified, and the user's own vaults are read-only — including CLI-side indirect writes the sandbox cannot see (plugin:enable / unrestrict rewrite whichever vault is in the active window), which the tools refuse to run against a non-test vault. The default path never switches the user's window or steals focus." ] } }, { "version": "0.5.0", "date": "2026-09-16", "zh": { "title": "打通真机验证:观测、库管理与重载", "items": [ "新增 obsidian_plugin_inspect(只读观测):status 一次给出体检事实(App 版本、已注册的库、实际应答的库、该库的受限模式状态、目标插件是否已安装/启用/版本匹配、信任弹窗是否待确认),另有 errors / console / dom / css / screenshot / trustCheck;绝不改动 App。", "新增 obsidian_plugin_vault(真机验证的库管理):status 报告已注册的库、当前活动窗口与激活将要做什么;ensure 是两步确认——不带 confirm 只描述后果,带 confirm=true 才登记/打开该库(Obsidian 会切到前台),随后由 App 自证哪个库是活动库,并如实上报信任弹窗而不代为确认。", "新增 obsidian_plugin_reload(让改动生效):reload(默认)/ enable / disable;rescan 重扫插件清单索引——Obsidian 只在库加载时扫描一次插件目录,刚部署的插件在重扫前对所有插件命令都不可见。", "status 会说明究竟是哪个库在实际应答(窗口级命令跟随活动窗口,vault 参数并不可靠),并报告该库的受限模式状态;读数与目标库不符时,先 ensure 再观测。", "reload 重载后校验插件是否真的注册进 App 并回报插件日志——「命令被接受」不等于「插件已加载」;受限模式是逐库的安全设置,只能由显式的 reload action=unrestrict 关闭(会重载窗口),绝不作为副作用。" ] }, "en": { "title": "Live verification: observe, manage the vault, reload", "items": [ "New obsidian_plugin_inspect (read-only observation): status returns the health facts in one shot (app version, registered vaults, which vault actually answered, that vault's restricted-mode state, whether the target plugin is installed/enabled/version-matched, whether the trust modal is pending), plus errors / console / dom / css / screenshot / trustCheck; it never changes the app.", "New obsidian_plugin_vault (vault management for live verification): status reports registered vaults, the active window and what activation would take; ensure is a two-step confirmation — without confirm it only describes the consequences, with confirm=true it registers/opens the vault (Obsidian switches to the front) and then asks the app which vault is active, reporting Obsidian's trust modal instead of accepting it.", "New obsidian_plugin_reload (make a change take effect): reload (default) / enable / disable; rescan refreshes the plugin manifest index — Obsidian scans a vault's plugin directory only at vault load, so a freshly deployed plugin stays invisible to every plugin command until then.", "status states which vault actually answered (window-scoped commands follow the active window; vault= does not reliably redirect them) and reports that vault's restricted-mode state; when a reading does not match the target vault, run ensure first and observe again.", "After reloading, the plugin's registration in the app is verified and anything the plugin logged is reported — an accepted command is not a loaded plugin; restricted mode is a per-vault security setting that only an explicit reload action=unrestrict turns off (it reloads the window), and it is never a side effect." ] } }, { "version": "0.4.0", "date": "2026-09-16", "zh": { "title": "新增构建与部署工具", "items": [ "新增 obsidian_plugin_build:把插件项目打包成可加载的 main.js(CommonJS,obsidian 外部化)并做静态自检,补齐 build → deploy 这半程开发闭环。", "构建三级降级:项目本地 esbuild → 项目自身的 production build 脚本 → 给出可操作的报错;绝不启动 watch 进程,并在返回值中报告实际使用的层级。", "新增 obsidian_plugin_deploy:把构建产物装进 vault 的 .obsidian/plugins//,并把 id 合并进该库的 community-plugins.json(保留既有条目),同时把 vault 记入项目旁的 dsh.obsidian.json。", "部署三态分列报告、不混为一谈:written(已写入)/ enabled(已启用)/ active(是否被运行中的 Obsidian 加载,本阶段恒为 unknown)——写盘与启用不等于插件已加载。" ] }, "en": { "title": "Add build and deploy tools", "items": [ "New obsidian_plugin_build: bundles a plugin project into a loadable main.js (CommonJS, obsidian externalized) with static self-checks, completing the build → deploy half of the loop.", "Three-tier build degradation: project-local esbuild → the project's own production build script → an actionable refusal; never a watch process, and the tier actually used is reported.", "New obsidian_plugin_deploy: installs the build artifacts into the vault's .obsidian/plugins//, merges the id into that vault's community-plugins.json (existing entries preserved), and remembers the vault in dsh.obsidian.json.", "Deploy reports the three states separately and never conflates them: written / enabled / active (whether the running app loaded it stays unknown in this phase) — files on disk and an enable-list entry are not a loaded plugin." ] } }, { "version": "0.3.0", "date": "2026-09-13", "zh": { "title": "工具重构与 skill 内置", "items": [ "工具改名:obsidian_scaffold / obsidian_validate / obsidian_version → obsidian_plugin_scaffold / obsidian_plugin_validate / obsidian_plugin_version。", "scaffold 复用官方 obsidian-sample-plugin 模板(14 个文件),不再在代码内嵌模板字符串。", "validate 集成 eslint-plugin-obsidianmd(obsidianmd/eslint-plugin)检查代码。", "skill 内置为 assets/skills/obsidian-plugin,重组为 DSH Tools / Workflow / 插件编写规范 / 校验与提交。", "修复文件写入的会话沙箱策略传递(per-session sandboxPolicy)。" ] }, "en": { "title": "Tool rework and vendored skill", "items": [ "Rename tools: obsidian_scaffold / obsidian_validate / obsidian_version → obsidian_plugin_scaffold / obsidian_plugin_validate / obsidian_plugin_version.", "scaffold reuses the official obsidian-sample-plugin template (14 files) instead of inline template strings.", "validate integrates eslint-plugin-obsidianmd (obsidianmd/eslint-plugin) lint checks.", "skill vendored at assets/skills/obsidian-plugin and reorganized into DSH Tools / Workflow / authoring rules / validation & submission.", "Fix per-session sandboxPolicy propagation for file writes." ] } }, { "version": "0.2.0", "date": "2026-09-12", "zh": { "title": "新增 doc 目录与版本说明", "items": [ "新增 doc/ 目录:HARNESS 会话上下文、使用手册与版本说明随包分发。", "版本说明机制:内置 VERSION_NOTES,按语言输出历史版本更新说明。", "工具文档化:三个工具的能力与使用规则写入 HARNESS 上下文。" ] }, "en": { "title": "Add doc directory and version notes", "items": [ "New doc/ directory: HARNESS session context, usage manual and version notes ship with the package.", "Version-notes mechanism: built-in VERSION_NOTES with localized history.", "Tool documentation: the three tools' capabilities and usage rules are written into the HARNESS context." ] } }, { "version": "0.1.0", "date": "2026-09-12", "zh": { "title": "初始版本", "items": [ "obsidian_plugin_scaffold:生成合规插件骨架,内置命名/提交规则校验。", "obsidian_plugin_validate:校验 manifest 与版本一致性。", "obsidian_plugin_version:同步 manifest.json / versions.json / package.json 三处版本。" ] }, "en": { "title": "Initial release", "items": [ "obsidian_plugin_scaffold: generate a compliant plugin skeleton with built-in naming/submission checks.", "obsidian_plugin_validate: validate the manifest and version consistency.", "obsidian_plugin_version: bump the three version fields together." ] } } ]