misakanet 2.39.0 → 2.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "misakanet",
3
- "version": "2.39.0",
3
+ "version": "2.41.0",
4
4
  "description": "Failure-memory network for AI coding agents: search evidence-rated failure lessons, submit new failures, and turn crashes into reusable knowledge.",
5
5
  "author": {
6
6
  "name": "Ikalus1988",
package/README.ja.md CHANGED
@@ -111,6 +111,14 @@ jobs:
111
111
  > **MisakaNetは1つのこと専用に構築されています:** エージェントが既知の障害を繰り返すのを防ぐこと。
112
112
  > これは汎用メモリレイヤーでもランタイムでもベクトルデータベースでもありません。
113
113
 
114
+ ### 答えてくれること、答えてくれないこと
115
+
116
+ ![4コマ漫画:マスコットは「すべてのAIエラーを防げる」と約束するが、猫がピザと石油缶を尋ねるとしょげてしまい、別の猫が npm ERESOLVE を見せると目を輝かせる。MisakaNet が知っているのは収録済みの障害であり、一般知識ではない。](promotional/misakanet-scope-comic.webp)
117
+
118
+ 答えてくれるのは**収録済みの障害**であり、一般知識ではありません。見つからない場合は `no_match` と、
119
+ そのまま呼び出せる `intake` の案内が返ります —— 未ヒットはギャップが記録される経路そのものなので、
120
+ これもまた答えです。
121
+
114
122
  ### v2.16.0の新機能
115
123
 
116
124
  | 機能 | 説明 |
package/README.md CHANGED
@@ -63,6 +63,70 @@ mcp-name: io.github.Ikalus1988/misakanet
63
63
 
64
64
  ---
65
65
 
66
+ ## Install (30 seconds)
67
+
68
+ | Your host | Command |
69
+ |---|---|
70
+ | **DeepSeek Harness** | `dsh plugin --profile web add misakanet` — or type `misakanet` in the host's **Add plugin** dialog |
71
+ | **Claude Code** | `/plugin marketplace add Ikalus1988/MisakaNet` then `/plugin install misakanet@misakanet` |
72
+ | **Codex, Cursor, Gemini CLI, Copilot CLI, OpenCode, …** | `npx @misaka-net/misakanet-setup` — writes the MCP row into each host's own config |
73
+ | **Any other MCP client** | point it at `https://misakanet.org/mcp` — the endpoint is public and reads are anonymous |
74
+ | **Your own code** | `pip install misakanet-core` (library) · `pip install misakanet` (stdio server) |
75
+
76
+ <p align="center">
77
+ <img src="docs/assets/dsh-plugin-add.png" width="760" alt="DeepSeek Harness plugin page: (1) the plugin icon in the sidebar, (2) the 添加插件 (Add plugin) button, (3) the Add-plugin dialog with the package name misakanet typed in"/>
78
+ </p>
79
+
80
+ <p align="center"><em>DeepSeek Harness: sidebar <b>插件</b> (1) → <b>添加插件</b> (2) → type <code>misakanet</code> (3) → <b>安装</b>.<br/>The dialog takes the same package name the CLI command above uses (its own hint: a package name, a GitHub URL, or a local path).</em></p>
81
+
82
+ Updates: `dsh plugin --profile web update misakanet@latest`.
83
+ Update the installer: `npx @misaka-net/misakanet-setup@latest` (its own command, its own flags).
84
+
85
+ No account, no token, no Python needed for the plugin path: the npm bundle mounts the hosted endpoint.
86
+ Declared hosts and what was measured: [compatibility](docs/compatibility.md). Every channel, the
87
+ prerequisites, and the two-package trap that costs people an install: [How to use it](#how-to-use-it).
88
+
89
+ ## What the DeepSeek Harness plugin adds
90
+
91
+ Version 2.40.0 ships the browser half, and 2.41.0 adds the rest of it: the surfaces below did not all land in
92
+ the same release, so this section lists them by the version that carries them. It is not a dialog: it puts
93
+ MisakaNet where the session already is.
94
+
95
+ **In 2.40.0 — published.** These six seats are what `npm view misakanet version` gives you today.
96
+
97
+ | Where | What you get |
98
+ | --- | --- |
99
+ | **Left column** | A permanent `MisakaNet` entry directly under `Plugins`. It is a shortcut, not the panel: clicking it opens a full page in the main column. Root scope — it does not come and go with a session. |
100
+ | **Conversation tab** | A `MisakaNet` tab beside Chat and Trajectory: what this session asked, what came back, and what you filed, rebuilt from the conversation's own rows. |
101
+ | **Right column** | The same panel as a pane, so it can sit next to the file tree, a terminal, or a document. |
102
+ | **Tool call rows** | Every `misakanet_search` and `misakanet_submit_intake` call gets its own row on the tool card: the query as it was sent, whether a lesson came back, and which lesson is on top, with the raw result one disclosure away. The row reports and never posts — the vote belongs on the answer's action row below. |
103
+ | **Assistant action row** | 👍 / 👎 on the answer that used a lesson. Those two are the only things the page ever sends — counters live in the browser, not on a server. |
104
+ | **Voice** | An off-by-default switch that explains both mechanisms: the cue the server names on the next search, and the local hook a page cannot read. |
105
+
106
+ <p align="center">
107
+ <img src="docs/assets/dsh-client-left-column.png" width="820" alt="DeepSeek Harness: a permanent MisakaNet entry in the left column under Plugins, and the full page it opens"/>
108
+ </p>
109
+
110
+ <p align="center">
111
+ <img src="docs/assets/dsh-client-right-panel.png" width="820" alt="The MisakaNet pane in the right column of a DeepSeek Harness session"/>
112
+ </p>
113
+
114
+ **With 2.41.0 — [release PR #2591](https://github.com/Ikalus1988/MisakaNet/pull/2591).** These six surfaces
115
+ are in `main` and ship in 2.41.0; a 2.40.0 install does not have them yet.
116
+
117
+ | Where | What you get |
118
+ | --- | --- |
119
+ | **`/misakanet` in the composer** | Type `/misakanet pip install timeout` and press Enter: the lessons come back in a card inside the composer, with no agent in the loop. The query is the only thing it sends. |
120
+ | **Frame-wide toast** | After a `/misakanet` search comes back with lessons, a card appears over every column with the top hit; dismiss it or click through to the lesson. |
121
+ | **Sidebar foot** | One action beside Settings: copy this session's MisakaNet activity as a summary for an issue or a PR body. |
122
+ | **中文 / English** | Every MisakaNet surface follows the host language: the panel, the `/misakanet` card, the settings row, and the plugin page's own title and description. |
123
+ | **Settings → General** | A MisakaNet preference row: play voice cues in this browser, and how much the surfaces show (compact / full). Both stay in the browser. |
124
+ | **Plugin page** | The MCP row's effective configuration — endpoint, transport, timeout — shown read-only, next to where it is edited (the profile's `cordis.patch.yml`). |
125
+
126
+ Which seats the half occupies and why they are `root` or `session` scope, with the host's own contract text
127
+ quoted: [compatibility](docs/compatibility.md). Running a host of your own and want a check that cannot
128
+ touch your profile: `python3 scripts/install_smoke.py dsh-client --serve`.
129
+
66
130
  ## What is MisakaNet?
67
131
 
68
132
  **Git-backed failure memory for AI coding agents.** An error shows up → the agent searches the lessons →
@@ -90,6 +154,13 @@ how much a lesson has been proven.
90
154
  | ❌ A cloud service requiring signup | ✅ `git clone` → search locally |
91
155
  | ❌ A skill marketplace | ✅ Debugging knowledge from real sessions |
92
156
 
157
+ ### What it can and cannot answer
158
+
159
+ ![Four-panel comic: the mascot promises to prevent every AI error; the cats ask about pizza and an oil barrel and it deflates — then a cat shows npm ERESOLVE and it lights up. MisakaNet knows the failures that have been indexed, not general knowledge.](promotional/misakanet-scope-comic.webp)
160
+
161
+ It answers for **the failures it has indexed**, not general knowledge. A query that finds nothing returns
162
+ `no_match` plus a ready-to-call intake — a miss is how a gap gets recorded, so a miss is an answer too.
163
+
93
164
  ### Lesson vs Skill
94
165
 
95
166
  A **skill** teaches an agent *how to do something*. A **lesson** records *what went wrong before, and how
@@ -98,21 +169,53 @@ a general memory layer, not a vector database. → [FAQ](FAQ.md)
98
169
 
99
170
  ## Benchmark: how much of a lesson does a model reproduce when handed one?
100
171
 
101
- Weekly benchmark (Cloudflare Workers AI, 2026-08-30). **Read the metric before the numbers** — measured
102
- 2026-09-25, the scenario in this benchmark is each lesson's own title, the "matching lesson" injected into the
103
- `with_lesson` arm is *that same lesson*, and the score is `lesson_hit_rate`: **the share of the injected lesson's commands reproduced in the answer**.
172
+ Weekly benchmark (Cloudflare Workers AI). **Read the metric before the numbers** — the scenario in this benchmark is each lesson's own title, the "matching lesson" injected into the `with_lesson` arm is *that same lesson*, and the
173
+ score is `lesson_hit_rate`: **the share of the injected lesson's commands reproduced in the answer**.
104
174
  No retrieval is called and correctness is not checked, so this is the **recitation** half of RAG, not
105
- evidence that search works:
175
+ evidence that search works.
106
176
 
107
- | Model | Lesson pasted in prompt: not pasted | Lesson pasted in prompt: pasted | Difference |
177
+ Latest aggregated data: [`docs/benchmarks/latest.json`](docs/benchmarks/latest.json) (2026-09-22, two
178
+ independent runs of ≈500 scenarios each):
179
+
180
+ | Condition | Run 1 hit rate | Run 2 hit rate | Avg | n (per run) | Actionable |
181
+ |---|---|---|---|---|---|
182
+ | plain (no lesson) | 0.239 | 0.233 | **23.3%** | ≈510 | 82–83% |
183
+ | with_lesson (pasted) | 0.464 | 0.461 | **46.1%** | ≈512 | 76–77% |
184
+
185
+ **Reproducibility.** Two runs with identical config produce hit rates within 0.3% of each other
186
+ (0.464 vs 0.461 for `with_lesson`; 0.239 vs 0.233 for `plain`), confirming the metric is stable.
187
+
188
+ **Aggregation.** Each run evaluates every lesson in the corpus as a scenario. The `with_lesson` arm pastes the matching lesson
189
+ into the prompt; `plain` uses no lesson. `actionable` is a boolean per scenario indicating whether the
190
+ model produced a usable answer. Actionable rates are stable across runs (76–77% with lesson, 82–83% plain).
191
+
192
+ **Trend** (weekly snapshots):
193
+
194
+ | Date | with_lesson hit rate | plain hit rate | n |
108
195
  |---|---|---|---|
109
- | llama-3.2-3b (light) | 21% of the lesson's commands reproduced | **43%** | **2×** |
110
- | llama-3.3-70b (strong) | 42% | **73%** | **+31%** |
196
+ | 2026-08-30 | 46.4% | 23.9% | 358 |
197
+ | 2026-08-31 | 49.1% | 25.1% | 398 |
198
+ | 2026-09-06 | 48.3% | 24.1% | 455 |
199
+ | 2026-09-14 | 46.6% | 23.4% | 494 |
200
+ | 2026-09-21 | 46.1% | 23.3% | 512 |
201
+
202
+ ```
203
+ with_lesson hit rate (weekly)
204
+ 49.1% │ ▄
205
+ 48.3% │ █ ▄
206
+ 46.6% │ █ █ ▄
207
+ 46.4% │ ▄ █ █ █ ▄
208
+ 46.1% │ █ █ █ █ █
209
+ └──────────────────
210
+ 08 08 09 09 09
211
+ 30 31 06 14 21
212
+ ```
111
213
 
112
214
  A model repeats more of a document it was handed, and the weaker the model the bigger the relative
113
215
  difference. That is *necessary* for the product to help and it is not sufficient — the claim "search finds the
114
216
  right lesson for a failure you described" is measured nowhere yet. Details:
115
- [benchmark-2026-08-30](docs/benchmarks/benchmark-2026-08-30.json) · metric definition: `METRIC_DEFINITION` in
217
+ [`docs/benchmarks/latest.json`](docs/benchmarks/latest.json) · per-run files in
218
+ [`docs/benchmarks/`](docs/benchmarks/) · metric definition: `METRIC_DEFINITION` in
116
219
  [`scripts/benchmark_workers_ai.py`](scripts/benchmark_workers_ai.py)
117
220
 
118
221
  → [Full changelog](CHANGELOG.md) · [Release notes](https://github.com/Ikalus1988/MisakaNet/releases)
@@ -124,6 +227,7 @@ and where this design loses:
124
227
  |---|---|---|
125
228
  | Hit rate | share of the **injected** lesson's commands reproduced in the answer — a recitation check; the scenario is that lesson's own title and no retrieval happens | it is the ceiling on usefulness, not the measure of it: a corpus can be recitable and still unfindable |
126
229
  | Gain (with − without) | how much more of that lesson appears when it is pasted in | separates "the model can use a lesson" from "the model guessed the same words" — it says nothing about finding the lesson |
230
+ | Actionable | whether the model produced a usable answer at all (boolean per scenario) | a high hit rate on an answer that is not actionable is noise; this tracks whether the model engages with the problem |
127
231
  | Cost / latency | tokens and wall-clock per answer | the whole premise is cheaper than re-debugging, so it has to stay cheap |
128
232
 
129
233
  **Where it loses on purpose:** BM25 matches words, not meaning. A failure described in vocabulary the
@@ -160,11 +264,12 @@ Supported agents — and what "supported" means per group (evidence levels in
160
264
  | MCP by hand | Cursor · Gemini CLI · Windsurf · OpenCode · Copilot · DeepSeek Harness | the endpoint is standard MCP over HTTP; add the URL in that client's own config. Cursor also has a rules-file mode |
161
265
  | Anything else that speaks MCP over HTTP | — | the endpoint is public, reads are anonymous and unmetered |
162
266
 
163
- Pick one channel — they are independent, and none of them needs an account:
267
+ Pick one channel — they are independent, and none of them needs an account (the Claude Code row needs a Claude Code version with plugin support):
164
268
 
165
269
  | I want… | Command | What it touches |
166
270
  |---|---|---|
167
271
  | my assistant to search the lessons | `npx @misaka-net/misakanet-setup` | writes the MCP endpoint into each assistant's own config; optionally a rules block and a hook |
272
+ | my **Claude Code** assistant to search the lessons, as a plugin | `/plugin marketplace add Ikalus1988/MisakaNet` then `/plugin install misakanet@misakanet` | adds the hosted MCP tools to Claude Code from this repository — no installer, no local process |
168
273
  | to call the endpoint myself | the `curl` below | nothing to install |
169
274
  | the library in my own code | `pip install misakanet-core` | nothing |
170
275
 
@@ -174,6 +279,7 @@ Pick one channel — they are independent, and none of them needs an account:
174
279
  |---|---|---|
175
280
  | `@misaka-net/misakanet-setup` (npm) | the **installer** — has `bin`, no plugin entry | teaching your assistant to search |
176
281
  | `misakanet` (npm) | the **DSH / Codex plugin** (`index.js`, `SKILL.md`) | `dsh plugin --profile web add misakanet` |
282
+ | this repository (git) | also a **Claude Code plugin marketplace** (`.claude-plugin/`) | `/plugin marketplace add Ikalus1988/MisakaNet` — the Claude channel is repo-based on purpose: a marketplace resolves the plugin from the repository, so the npm bundle stays the DSH/Codex artifact |
177
283
  | `misakanet` (PyPI) | ships the stdio **MCP server** | `python3 -m misakanet.server` |
178
284
  | `misakanet-core` (PyPI) | the **library** (stdlib-only BM25 — Python ≥ 3.10 required, no third-party packages) | `from misakanet.search import search_lessons` |
179
285
 
@@ -243,7 +349,7 @@ jobs:
243
349
  |---|---|
244
350
  | 🔴 Debugging a real failure | [Search existing lessons](https://ikalus1988.github.io/MisakaNet/search/) before retrying |
245
351
  | 🤖 Building an AI agent / tool | Use lessons as [failure-memory](docs/mcp-quickstart.md) for your workflow |
246
- | 🧪 Using DeepSeekHarness | Connect the [DeepSeekHarness MCP adapter](docs/integration/deepseek-harness.md) as a recovery-memory plugin |
352
+ | 🧪 Using DeepSeek Harness | `dsh plugin --profile web add misakanet`, then [what it registers](docs/integration/deepseek-harness.md) — skill + `mcp__misakanet__*` tools, no local Python |
247
353
  | 🔧 Contributing a fix | Read [CONTRIBUTING.md](CONTRIBUTING.md) for code style + PR checklist, check [related lessons](https://ikalus1988.github.io/MisakaNet/search/), then open a small PR |
248
354
  | 📝 Sharing a failure case | Submit a [5-line failure note](https://github.com/Ikalus1988/MisakaNet/issues/new?template=lesson-feedback.yml) — no polished PR required |
249
355
  | 📊 Evaluating agent learning | Run the [benchmarks](scripts/retrieval_noisebench.py) and compare reuse behavior |
package/README.zh-CN.md CHANGED
@@ -102,9 +102,9 @@ python3 search_knowledge.py "GitHub token 401"
102
102
 
103
103
  ```bash
104
104
  # npm 安装(推荐)
105
- dsh plugin add misakanet
105
+ dsh plugin --profile web add misakanet
106
106
  # 或直接从 git 安装(同一 bundle)
107
- # dsh plugin add git+https://github.com/Ikalus1988/MisakaNet.git
107
+ # dsh plugin --profile web add git+https://github.com/Ikalus1988/MisakaNet.git
108
108
 
109
109
  # 让 failure-memory SKILL 可被发现(DSH 扫描 ~/.dsh/skills 与项目 .dsh/skills)
110
110
  mkdir -p ~/.dsh/skills
@@ -164,6 +164,13 @@ MisakaNet 对不同用户的用途不同:
164
164
 
165
165
  > 👉 第一次来?[看 MisakaNet Journey →](https://misakanet.org/journey/)
166
166
 
167
+ ### 它能回答什么,不能回答什么
168
+
169
+ ![四格漫画:吉祥物承诺能避免所有 AI 错误;猫问起披萨和油桶,它就瘪了 —— 然后一只猫亮出 npm ERESOLVE,它立刻发光。MisakaNet 只认识已经收录的失败,不是通用知识。](promotional/misakanet-scope-comic.webp)
170
+
171
+ 它只回答**已经收录过的失败**,不是通用知识。查不到时会返回 `no_match` 和一条可直接调用的 `intake`
172
+ 指引 —— 未命中正是一条缺口被记下来的方式,所以它同样是一个答案。
173
+
167
174
  ### Lesson 和 Skill 有什么区别?
168
175
 
169
176
  MisakaNet 的 lesson **不是** skill。
package/icon.svg ADDED
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- MisakaNet plugin icon — the mark the DSH plugin manager renders (package.json "icon").
3
+ Same mark as docs/assets/misaka-avatar-fallback.svg (dark plate, cyan ring + "M", amber node);
4
+ tests/test_dsh_plugin_surface.py pins the shared path so the two copies cannot drift apart.
5
+ The host's own rules (dsh-app-boot iconOf): relative path, SVG/PNG/JPEG/WebP, <=256 KiB, inside
6
+ the package — an icon that breaks any of them makes plugin loading throw, which is why the test
7
+ next to this file asserts them instead of trusting the eye. -->
8
+ <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="MisakaNet">
9
+ <rect width="64" height="64" rx="12" fill="#070b15"/>
10
+ <rect x="2" y="2" width="60" height="60" rx="10" stroke="#00e5ff" stroke-opacity="0.55" stroke-width="2"/>
11
+ <path d="M16 44V20h7l9 14 9-14h7v24h-7V31.5L34.5 44h-5L23 31.5V44h-7Z" fill="#67e8f9"/>
12
+ <circle cx="50" cy="14" r="4" fill="#f59e0b"/>
13
+ </svg>
package/index.js CHANGED
@@ -74,22 +74,119 @@ export const DEFAULT_MCP_CONFIG = Object.freeze({
74
74
  */
75
75
  export async function apply(ctx, config = {}) {
76
76
  const options = { ...DEFAULT_MCP_CONFIG, ...config };
77
- if (typeof ctx?.plugin !== 'function') {
78
- throw new Error('misakanet: cordis context has no plugin() to mount the MCP client');
79
- }
80
77
 
81
- let client;
78
+ // NOTHING BELOW MAY THROW OUT OF THIS FUNCTION.
79
+ //
80
+ // A throw here is a failed activation, and a failed activation inside a profile
81
+ // bundle can take the whole host down: `dsh: startup failed: N required plugins
82
+ // did not activate` — the entire `dsh web` refuses to start, not just this
83
+ // server. That is a catastrophic outcome for a feature whose worst case should
84
+ // be "the tools are missing": the skill (SKILL.md) and every other plugin in
85
+ // the profile are unrelated to whether MisakaNet's MCP client mounted.
86
+ //
87
+ // So the whole body is guarded and reports through the host logger instead.
88
+ // `failOnStartupError` still works as documented — it asks the *MCP client* to
89
+ // treat a failed initial connection or tool sync as fatal (that is a config
90
+ // field of the client, `dsh-mcp-client/lib/index.js`), and when it is set the
91
+ // client's own fiber fails where the host can attribute it. What it must never
92
+ // do is make *our* wrapper the reason a profile will not boot.
82
93
  try {
83
- // Dynamic import: the client is ESM and only exists inside a DSH install.
84
- client = await import('@deepseek-ai/dsh-mcp-client');
94
+ if (typeof ctx?.plugin !== 'function') {
95
+ // A host that cannot mount child plugins: the skill still works, so say so and stop. This used to
96
+ // throw unconditionally — before the try — which is exactly the failure mode above. The explicit
97
+ // opt-in still gets its error, because that is what an opt-in is for.
98
+ const message = 'misakanet: cordis context has no plugin(); MCP tools not mounted';
99
+ if (options.failOnStartupError) throw new Error(message);
100
+ ctx?.logger?.warn?.(message);
101
+ return;
102
+ }
103
+
104
+ let client;
105
+ try {
106
+ // Dynamic import: the client is ESM and only exists inside a DSH install.
107
+ client = await import('@deepseek-ai/dsh-mcp-client');
108
+ } catch (error) {
109
+ if (options.failOnStartupError) throw error;
110
+ // Expected on npm skill-only installs: the skill (SKILL.md) is the payload,
111
+ // and a missing client must not take the whole profile down with it.
112
+ return;
113
+ }
114
+
115
+ // The Loader normalizes ESM/CJS/default export shapes before applying a plugin.
116
+ const component = ctx?.loader?.unwrapExports ? ctx.loader.unwrapExports(client) : client;
117
+ ctx.plugin(component, options);
85
118
  } catch (error) {
86
119
  if (options.failOnStartupError) throw error;
87
- // Expected on npm skill-only installs: the skill (SKILL.md) is the payload,
88
- // and a missing client must not take the whole profile down with it.
89
- return;
120
+ ctx?.logger?.warn?.(`misakanet: MCP client not mounted: ${error?.message ?? error}`);
90
121
  }
122
+ }
91
123
 
92
- // The Loader normalizes ESM/CJS/default export shapes before applying a plugin.
93
- const component = ctx?.loader?.unwrapExports ? ctx.loader.unwrapExports(client) : client;
94
- ctx.plugin(component, options);
124
+ /**
125
+ * The row's `Config`: what the Loader validates the bundle patch's `config:` against, and what the plugin
126
+ * page turns into a form — the host's own words for it are "the cordis `Config` validator and the
127
+ * Config-form generation's served schema" (`dsh-context`'s `index.d.ts` says the same thing about its own).
128
+ *
129
+ * Built with a **top-level `await` inside a `try`**, not a static import. `@deepseek-ai/schemastery` is a
130
+ * host dependency, but this package is also installed by skill-only consumers that never load this module,
131
+ * and a module-level import failure would take the whole row down rather than only its form. Without it the
132
+ * row keeps working from the patch and only the GUI form is lost — the same "absence is not failure" rule
133
+ * `apply()` follows for the MCP client.
134
+ */
135
+ /**
136
+ * Import schemastery from wherever this install can actually reach it.
137
+ *
138
+ * A profile that **installed** this package resolves the bare specifier on its own: Node walks up from
139
+ * `…/profiles/web/node_modules/misakanet/` and finds the profile's copy. A profile that **linked** a working
140
+ * tree does not — Node resolves the link to its real path, so the walk starts in the repository and never
141
+ * sees the profile. That is the shape this repository's own contributor profiles (and the owner's live one)
142
+ * use, so the second attempt resolves from the `dsh` entry point that started the host, where schemastery is
143
+ * a dependency of DSH itself.
144
+ */
145
+ async function loadSchemastery() {
146
+ const attempts = ['@deepseek-ai/schemastery'];
147
+ try {
148
+ const { createRequire } = await import('node:module');
149
+ const { realpathSync } = await import('node:fs');
150
+ const { pathToFileURL } = await import('node:url');
151
+ const hostEntry = process.argv[1];
152
+ // Both spellings matter: the bin is usually a **symlink**, and the package's own node_modules sits
153
+ // beside its real path — resolving from the link's directory finds the global root instead.
154
+ const from = [];
155
+ if (hostEntry) from.push(hostEntry);
156
+ try { from.push(realpathSync(hostEntry)); } catch (error) { /* no real path to add */ }
157
+ for (const entry of from) {
158
+ try {
159
+ const resolved = createRequire(entry).resolve('@deepseek-ai/schemastery');
160
+ // `require.resolve` hands back a filesystem path, and a Windows one is not a valid ESM specifier
161
+ // (`C:` parses as a scheme). The bare specifier above is left alone; only paths are wrapped.
162
+ const url = pathToFileURL(resolved).href;
163
+ if (!attempts.includes(url)) attempts.push(url);
164
+ } catch (error) { /* this entry cannot see it */ }
165
+ }
166
+ } catch (error) { /* keep the bare specifier as the only attempt */ }
167
+ for (const specifier of attempts) {
168
+ try {
169
+ const loaded = await import(specifier);
170
+ return loaded.default ?? loaded;
171
+ } catch (error) { /* try the next location */ }
172
+ }
173
+ return undefined;
95
174
  }
175
+
176
+ let Config;
177
+ try {
178
+ const z = await loadSchemastery();
179
+ if (z === undefined) throw new Error('schemastery is not resolvable from this install');
180
+ Config = z.object({
181
+ transport: z.string().default(DEFAULT_MCP_CONFIG.transport),
182
+ serverName: z.string().default(DEFAULT_MCP_CONFIG.serverName),
183
+ url: z.string().default(DEFAULT_MCP_CONFIG.url),
184
+ headers: z.dict(z.string()).default({ ...DEFAULT_MCP_CONFIG.headers }),
185
+ toolCallTimeoutMs: z.number().default(DEFAULT_MCP_CONFIG.toolCallTimeoutMs),
186
+ failOnStartupError: z.boolean().default(DEFAULT_MCP_CONFIG.failOnStartupError),
187
+ });
188
+ } catch (error) {
189
+ Config = undefined;
190
+ }
191
+
192
+ export { Config };