dsh-plugin-wiki-tools 0.14.1 → 0.14.2

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.
Files changed (3) hide show
  1. package/README.md +11 -0
  2. package/lib/vault.js +27 -12
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -1,11 +1,22 @@
1
1
  # dsh-plugin-wiki-tools
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/dsh-plugin-wiki-tools)](https://www.npmjs.com/package/dsh-plugin-wiki-tools)
4
+ [![npm weekly downloads](https://img.shields.io/npm/dw/dsh-plugin-wiki-tools)](https://www.npmjs.com/package/dsh-plugin-wiki-tools)
5
+ [![License: MIT](https://img.shields.io/npm/l/dsh-plugin-wiki-tools)](LICENSE)
6
+ [![Awesome DSH Plugin](https://img.shields.io/badge/listed-awesome--dsh--plugin-4a90d9)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
7
+
3
8
  English | [中文](#中文)
4
9
 
5
10
  Native [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) tools for an Obsidian wiki vault: `wiki_query`, `wiki_write`, and `wiki_lint` implement the mechanical core of the wiki skill suite — path routing, frontmatter completion, index/log bookkeeping, source delta tracking, and health checks — so the model spends its turns on synthesis instead of filesystem chores.
6
11
 
12
+ ```sh
13
+ dsh plugin --profile web add dsh-plugin-wiki-tools
14
+ ```
15
+
7
16
  Pair with **[dsh-plugin-wiki-skills](https://github.com/Lion-1209/dsh-plugin-wiki-skills)** for the prompt-level skills (`wiki`, `wiki-ingest`, `wiki-query`, `wiki-lint`, `save`).
8
17
 
18
+ *If this toolchain saves you time maintaining a wiki vault, a ⭐ helps other dsh users find it. 如果这个工具帮到了你,欢迎点个 Star。*
19
+
9
20
  ## Attribution
10
21
 
11
22
  The vault layout and operation contracts follow the LLM Wiki pattern (Andrej Karpathy) as embodied by [claude-obsidian](https://github.com/AgriciDaniel/claude-obsidian) (MIT, © 2026 AgriciDaniel). This package is an independent plain-ESM implementation of the mechanical core; it contains no code or skill text from claude-obsidian.
package/lib/vault.js CHANGED
@@ -214,17 +214,21 @@ export class Vault {
214
214
  /**
215
215
  * Run one mutation under a cross-process advisory lock (the wiki-lock
216
216
  * contract): writers in other processes or sessions serialize on the same
217
- * lock file under `.vault-meta/locks/`. A held lock is retried once after
218
- * 2s and then reported; a lock older than lockStaleSeconds is treated as
219
- * crashed and broken.
217
+ * lock file under `.vault-meta/locks/`. Default semantics retry once after
218
+ * 2s and then report; pass `{ waitMs }` for patient polling (used by
219
+ * internal bookkeeping, where failing the write after the page file is
220
+ * already on disk would be worse than waiting). A lock older than
221
+ * lockStaleSeconds is treated as crashed and broken either way.
220
222
  * @param {string} key - lock key; vault-relative page path, or `__vault__`
221
223
  * for vault-wide mutations (rename, archive) and `__manifest__` for the
222
224
  * ingest manifest.
223
225
  * @param {() => Promise<T>} operation - the mutation to guard.
226
+ * @param {{ waitMs?: number }} [options] - poll until waitMs elapsed
227
+ * instead of the legacy retry-once.
224
228
  * @returns {Promise<T>}
225
229
  * @template T
226
230
  */
227
- async withFileLock(key, operation) {
231
+ async withFileLock(key, operation, { waitMs = 0 } = {}) {
228
232
  const locksDir = join(this.root, '.vault-meta', 'locks')
229
233
  await mkdir(locksDir, { recursive: true })
230
234
  const lockPath = join(locksDir, `${createHash('sha1').update(key).digest('hex')}.lock`)
@@ -244,11 +248,14 @@ export class Vault {
244
248
  return false
245
249
  }
246
250
  }
247
- if (!(await acquire())) {
248
- await new Promise(resolve => setTimeout(resolve, 2000))
249
- if (!(await acquire())) {
251
+ const deadline = Date.now() + waitMs
252
+ let attempt = 0
253
+ while (!(await acquire())) {
254
+ attempt += 1
255
+ if (Date.now() >= deadline && !(waitMs === 0 && attempt < 2)) {
250
256
  throw new Error(`wiki-tools: ${key} is locked by another writer; skipped (retry once it releases)`)
251
257
  }
258
+ await new Promise((resolve) => setTimeout(resolve, waitMs > 0 ? 200 : 2000))
252
259
  }
253
260
  try {
254
261
  return await operation()
@@ -357,11 +364,19 @@ export class Vault {
357
364
  await mkdir(join(path, '..'), { recursive: true })
358
365
  await writeFile(path, file, 'utf8')
359
366
  this.invalidateCollectCache()
360
- await this.updateIndex(type, cleanTitle, summary ?? firstContentLine(content))
361
- await this.updateFolderIndex(type, cleanTitle, summary ?? firstContentLine(content))
362
- await this.prependLog(`## [${date}] ${existing === undefined ? 'create' : 'update'} | ${cleanTitle}`, [
363
- `- ${existing === undefined ? 'Created' : 'Updated'}: [[${cleanTitle}]]`,
364
- ])
367
+ // Index and log are shared state across every writer in the vault: the
368
+ // per-page lock covers this page's file, but two concurrent writes to
369
+ // DIFFERENT pages would otherwise read-modify-write index.md and log.md
370
+ // against each other (entries visibly lost). Serialize the bookkeeping
371
+ // under the vault-wide lock; lock ordering is page → __vault__, and no
372
+ // other path acquires a page lock while holding __vault__, so no cycle.
373
+ await this.withFileLock('__vault__', async () => {
374
+ await this.updateIndex(type, cleanTitle, summary ?? firstContentLine(content))
375
+ await this.updateFolderIndex(type, cleanTitle, summary ?? firstContentLine(content))
376
+ await this.prependLog(`## [${date}] ${existing === undefined ? 'create' : 'update'} | ${cleanTitle}`, [
377
+ `- ${existing === undefined ? 'Created' : 'Updated'}: [[${cleanTitle}]]`,
378
+ ])
379
+ }, { waitMs: this.lockStaleMs * 1000 })
365
380
  this.gitCommit(`wiki: ${existing === undefined ? 'create' : 'update'} ${cleanTitle}`)
366
381
  if (sourcePath !== undefined) {
367
382
  await this.trackSource({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-wiki-tools",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
4
4
  "description": "Native DeepSeek Harness tools for an Obsidian wiki vault: wiki_query, wiki_write, and wiki_lint implement the mechanical core (path routing, delta tracking, index/log bookkeeping, health checks) of the wiki skill suite.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -11,7 +11,8 @@
11
11
  "./lib/search.js": "./lib/search.js",
12
12
  "./lib/lint.js": "./lib/lint.js",
13
13
  "./cordis.patch.yml": "./cordis.patch.yml",
14
- "./package.json": "./package.json"
14
+ "./package.json": "./package.json",
15
+ "./lib/scaffold.js": "./lib/scaffold.js"
15
16
  },
16
17
  "files": [
17
18
  "index.js",