dsh-plugin-wiki-tools 0.12.0 → 0.14.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.
- package/index.js +6 -1
- package/lib/lint.js +19 -1
- package/lib/vault.js +25 -4
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -106,7 +106,9 @@ export function createTools(vault, options = {}) {
|
|
|
106
106
|
+ 'CONFIGURED VAULT (run wiki_query to see its absolute root), not the session workspace. '
|
|
107
107
|
+ 'The content is the '
|
|
108
108
|
+ 'markdown body only — frontmatter is managed. With source_path, records the source hash in '
|
|
109
|
-
+ 'the ingest manifest and reports already_ingested for unchanged content unless force is set.'
|
|
109
|
+
+ 'the ingest manifest and reports already_ingested for unchanged content unless force is set. '
|
|
110
|
+
+ 'The result lists unresolved wikilinks in the written page (targets that match no page or alias) '
|
|
111
|
+
+ 'so dead links are fixed at write time, not at the next lint.',
|
|
110
112
|
parameters: {
|
|
111
113
|
title: {
|
|
112
114
|
type: 'string',
|
|
@@ -168,6 +170,9 @@ export function createTools(vault, options = {}) {
|
|
|
168
170
|
? `wiki_write: skipped ${args.title} — source hash unchanged (pass force: true to re-ingest)`
|
|
169
171
|
: typeof value === 'object' && value !== null && 'path' in value
|
|
170
172
|
? `wiki_write: ${value.created ? 'created' : 'updated'} ${value.path}`
|
|
173
|
+
+ (Array.isArray(value.unresolvedLinks) && value.unresolvedLinks.length > 0
|
|
174
|
+
? `\nwiki_write: note — ${value.unresolvedLinks.length} unresolved wikilink(s) in this page: ${value.unresolvedLinks.slice(0, 8).join(', ')}${value.unresolvedLinks.length > 8 ? ', …' : ''}. Create those pages or fix the targets; until then lint reports them as dead links.`
|
|
175
|
+
: '')
|
|
171
176
|
: `wiki_write: skipped ${args.title} (source unchanged)`,
|
|
172
177
|
}],
|
|
173
178
|
},
|
package/lib/lint.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @module dsh-plugin-wiki-tools/lib/lint
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
9
|
+
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises'
|
|
10
10
|
import { join } from 'node:path'
|
|
11
11
|
import {
|
|
12
12
|
PAGE_STATUSES,
|
|
@@ -39,6 +39,7 @@ export async function lintVault(root) {
|
|
|
39
39
|
checkOrphans(pages, inbound, indexed, add)
|
|
40
40
|
checkFrontmatterGaps(pages, add)
|
|
41
41
|
checkStatusVocabulary(pages, add)
|
|
42
|
+
await checkStructurePages(root, add)
|
|
42
43
|
checkEmptySections(pages, add)
|
|
43
44
|
await checkHotCacheStaleness(root, pages, add)
|
|
44
45
|
|
|
@@ -193,6 +194,23 @@ function checkStatusVocabulary(pages, add) {
|
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
196
|
|
|
197
|
+
/**
|
|
198
|
+
* The narrative structure page every vault layout expects. `overview.md` is
|
|
199
|
+
* human-maintained, so its absence is info — a nudge, not a defect.
|
|
200
|
+
* @param {string} root - absolute vault root.
|
|
201
|
+
* @param {Add} add - issue recorder.
|
|
202
|
+
*/
|
|
203
|
+
async function checkStructurePages(root, add) {
|
|
204
|
+
const overview = join(root, 'wiki', 'overview.md')
|
|
205
|
+
const exists = await stat(overview).then(() => true, () => false)
|
|
206
|
+
if (!exists) {
|
|
207
|
+
add('missing-structure', 'info', 'overview',
|
|
208
|
+
'wiki/overview.md does not exist',
|
|
209
|
+
'Run wiki_scaffold (generic) or write a ~200-word vault overview: what the vault is for, its areas, and where things go',
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
196
214
|
/**
|
|
197
215
|
* Headings with no content before the next heading.
|
|
198
216
|
* @param {Pages} pages - collected pages.
|
package/lib/vault.js
CHANGED
|
@@ -346,7 +346,18 @@ export class Vault {
|
|
|
346
346
|
pagesUpdated: existing === undefined ? [] : [cleanTitle],
|
|
347
347
|
})
|
|
348
348
|
}
|
|
349
|
-
|
|
349
|
+
// Surface dead-on-arrival wikilinks now, not at the next lint: forward
|
|
350
|
+
// links to pages about to be created are legitimate, so this is a note,
|
|
351
|
+
// never a rejection.
|
|
352
|
+
const pagesNow = await collectMarkdown(join(this.root, 'wiki'))
|
|
353
|
+
const names = new Set(pagesNow.map(page => page.name))
|
|
354
|
+
const aliases = buildAliasMap(pagesNow)
|
|
355
|
+
const unresolvedLinks = [...new Set(extractWikilinks(content))]
|
|
356
|
+
.filter(target => resolveLinkTarget(target, names, aliases) === undefined)
|
|
357
|
+
return {
|
|
358
|
+
path, created: existing === undefined, title: cleanTitle,
|
|
359
|
+
...(unresolvedLinks.length > 0 ? { unresolvedLinks } : {}),
|
|
360
|
+
}
|
|
350
361
|
}))
|
|
351
362
|
}
|
|
352
363
|
|
|
@@ -406,6 +417,9 @@ export class Vault {
|
|
|
406
417
|
const pages = await collectMarkdown(join(this.root, 'wiki'))
|
|
407
418
|
const target = pages.find(page => page.name === title)
|
|
408
419
|
if (target === undefined) throw new Error(`wiki-tools: no page named "${title}" exists in the vault`)
|
|
420
|
+
if (isMachineryPage(title)) {
|
|
421
|
+
throw new Error(`wiki-tools: "${title}" is vault machinery (index, log, hot cache, overview, sub-index, or lint report); renames would break conventions — fix the pages linking to it instead`)
|
|
422
|
+
}
|
|
409
423
|
if (pages.some(page => page.name !== title && page.name.toLowerCase() === cleanNew.toLowerCase())) {
|
|
410
424
|
throw new Error(`wiki-tools: filename "${cleanNew}.md" already exists; wikilinks need unique filenames`)
|
|
411
425
|
}
|
|
@@ -425,7 +439,9 @@ export class Vault {
|
|
|
425
439
|
// `\]` / `\|` would be eaten by the template literal and leave an
|
|
426
440
|
// empty alternation branch matching every `[[title` prefix.
|
|
427
441
|
const pattern = new RegExp(`\\[\\[${escapeRegExp(title)}(\\]\\]|\\||#)`, 'g')
|
|
428
|
-
|
|
442
|
+
let updated = raw.replace(pattern, `[[${cleanNew}$1`)
|
|
443
|
+
// A page whose links changed today is not "current" as of yesterday.
|
|
444
|
+
if (updated !== raw) updated = updated.replace(/^(updated:\s*).+$/m, `$1${today()}`)
|
|
429
445
|
if (updated !== raw) {
|
|
430
446
|
await writeFile(page.path, updated, 'utf8')
|
|
431
447
|
rewritten.push(page.name)
|
|
@@ -829,7 +845,12 @@ export function extractWikilinks(content) {
|
|
|
829
845
|
* @returns {boolean} whether the page is machinery.
|
|
830
846
|
*/
|
|
831
847
|
export function isMachineryPage(name) {
|
|
832
|
-
|
|
848
|
+
const lowered = name.toLowerCase()
|
|
849
|
+
return META_FILENAMES.has(lowered)
|
|
833
850
|
|| name.startsWith('_')
|
|
834
|
-
||
|
|
851
|
+
|| lowered.startsWith('_')
|
|
852
|
+
// Case-insensitive with a real boundary: reports have been hand-retitled
|
|
853
|
+
// to "Lint Report …" in real sessions, and those are still machinery —
|
|
854
|
+
// but "Lint Reporter Profile" is content.
|
|
855
|
+
|| /^lint[- _]?report([- _].+)?$/i.test(name)
|
|
835
856
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-wiki-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
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",
|