dsh-plugin-wiki-tools 0.3.0 → 0.4.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/lib/lint.js +19 -6
- package/lib/search.js +4 -4
- package/lib/vault.js +20 -2
- package/package.json +1 -1
package/lib/lint.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
10
10
|
import { join } from 'node:path'
|
|
11
|
-
import { collectMarkdown,
|
|
11
|
+
import { collectMarkdown, isMachineryPage, splitFrontmatter, today } from './vault.js'
|
|
12
12
|
|
|
13
13
|
/** Frontmatter fields every content page must carry. */
|
|
14
14
|
const REQUIRED_FIELDS = ['type', 'status', 'created', 'updated', 'tags']
|
|
@@ -53,10 +53,9 @@ export async function lintVault(root) {
|
|
|
53
53
|
* @param {Add} add - issue recorder.
|
|
54
54
|
*/
|
|
55
55
|
function checkDuplicateFilenames(pages, add) {
|
|
56
|
-
const isMachinery = page => META_FILENAMES.has(page.name.toLowerCase()) || page.name.startsWith('_')
|
|
57
56
|
const seen = new Map()
|
|
58
57
|
for (const page of pages) {
|
|
59
|
-
if (
|
|
58
|
+
if (isMachineryPage(page.name)) continue
|
|
60
59
|
const key = page.name.toLowerCase()
|
|
61
60
|
if (seen.has(key)) {
|
|
62
61
|
add('duplicate-filename', 'error', page.name,
|
|
@@ -103,6 +102,7 @@ function checkDeadLinks(pages, add) {
|
|
|
103
102
|
const names = new Set(pages.map(page => page.name))
|
|
104
103
|
const inbound = new Map(pages.map(page => [page.name, []]))
|
|
105
104
|
for (const page of pages) {
|
|
105
|
+
if (isMachineryPage(page.name)) continue
|
|
106
106
|
for (const target of page.links) {
|
|
107
107
|
if (names.has(target)) {
|
|
108
108
|
inbound.get(target)?.push(page.name)
|
|
@@ -126,7 +126,7 @@ function checkDeadLinks(pages, add) {
|
|
|
126
126
|
function checkOrphans(pages, inbound, indexed, add) {
|
|
127
127
|
for (const page of pages) {
|
|
128
128
|
const base = page.name.toLowerCase()
|
|
129
|
-
if (
|
|
129
|
+
if (isMachineryPage(page.name)) continue
|
|
130
130
|
const links = inbound.get(page.name) ?? []
|
|
131
131
|
const isIndexed = indexed.has(page.name)
|
|
132
132
|
if (links.length === 0 && !isIndexed) {
|
|
@@ -145,7 +145,7 @@ function checkOrphans(pages, inbound, indexed, add) {
|
|
|
145
145
|
function checkFrontmatterGaps(pages, add) {
|
|
146
146
|
for (const page of pages) {
|
|
147
147
|
const base = page.name.toLowerCase()
|
|
148
|
-
if (
|
|
148
|
+
if (isMachineryPage(page.name)) continue
|
|
149
149
|
const missing = REQUIRED_FIELDS.filter(field => page.fields?.[field] === undefined)
|
|
150
150
|
if (missing.length > 0) {
|
|
151
151
|
add('frontmatter-gap', 'warn', page.name,
|
|
@@ -162,12 +162,25 @@ function checkFrontmatterGaps(pages, add) {
|
|
|
162
162
|
*/
|
|
163
163
|
function checkEmptySections(pages, add) {
|
|
164
164
|
for (const page of pages) {
|
|
165
|
-
if (
|
|
165
|
+
if (isMachineryPage(page.name)) continue
|
|
166
|
+
// Fence-aware scan: lines inside code fences are content (a section whose
|
|
167
|
+
// body is only code is not empty) and heading-like comment lines inside
|
|
168
|
+
// fences never open sections.
|
|
166
169
|
const lines = [...page.content.split('\n'), '# __eof__ sentinel']
|
|
167
170
|
let heading
|
|
168
171
|
let headingLevel = 0
|
|
169
172
|
let hasContent = false
|
|
173
|
+
let inFence = false
|
|
170
174
|
for (const line of lines) {
|
|
175
|
+
if (line.startsWith('```')) {
|
|
176
|
+
inFence = !inFence
|
|
177
|
+
hasContent = true
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
if (inFence) {
|
|
181
|
+
hasContent = true
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
171
184
|
const match = /^(#{1,6}) (.+)$/.exec(line)
|
|
172
185
|
if (match !== null) {
|
|
173
186
|
const nextLevel = match[1].length
|
package/lib/search.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { readFile } from 'node:fs/promises'
|
|
10
10
|
import { join } from 'node:path'
|
|
11
|
-
import { collectMarkdown,
|
|
11
|
+
import { collectMarkdown, isMachineryPage } from './vault.js'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Answer the quick mode: the hot cache and master index verbatim. The caller
|
|
@@ -40,9 +40,9 @@ export async function searchVault(root, { query, limit = 10 }) {
|
|
|
40
40
|
const pages = await collectMarkdown(join(root, 'wiki'))
|
|
41
41
|
// The link graph covers every file, but results exclude vault machinery:
|
|
42
42
|
// the index matches nearly every term by construction and is already the
|
|
43
|
-
// quick-mode payload
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
// quick-mode payload, and dated lint reports quote past wikilinks as records,
|
|
44
|
+
// not graph edges — so they neither appear as hits nor count as inbound.
|
|
45
|
+
const searchable = pages.filter(page => !isMachineryPage(page.name))
|
|
46
46
|
const inbound = new Map(pages.map(page => [page.name, []]))
|
|
47
47
|
for (const page of searchable) {
|
|
48
48
|
// One inbound record per source page, however many times it links.
|
package/lib/vault.js
CHANGED
|
@@ -390,15 +390,33 @@ export async function collectMarkdown(directory) {
|
|
|
390
390
|
|
|
391
391
|
/**
|
|
392
392
|
* Extract wikilink targets from markdown, dropping aliases and heading anchors.
|
|
393
|
+
* Fenced code blocks and inline code spans are not links: examples written as
|
|
394
|
+
* `` `[[Target]]` `` must not join the graph.
|
|
393
395
|
* @param {string} content - markdown body.
|
|
394
396
|
* @returns {string[]} link targets in order of appearance.
|
|
395
397
|
*/
|
|
396
398
|
export function extractWikilinks(content) {
|
|
397
399
|
const links = []
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
+
const withoutCode = content
|
|
401
|
+
.replace(/```[\s\S]*?```/g, '')
|
|
402
|
+
.replace(/`[^`\n]*`/g, '')
|
|
403
|
+
for (const match of withoutCode.matchAll(/\[\[([^\]]+)\]\]/g)) {
|
|
400
404
|
const target = match[1].split('|')[0].split('#')[0].trim()
|
|
401
405
|
if (target.length > 0) links.push(target)
|
|
402
406
|
}
|
|
403
407
|
return links
|
|
404
408
|
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Whether a page basename is vault machinery rather than content: the index,
|
|
412
|
+
* log, hot cache, overview, per-folder sub-indexes, and dated lint reports.
|
|
413
|
+
* Lint reports quote past issues verbatim, so their wikilinks are records, not
|
|
414
|
+
* graph edges.
|
|
415
|
+
* @param {string} name - page basename without extension.
|
|
416
|
+
* @returns {boolean} whether the page is machinery.
|
|
417
|
+
*/
|
|
418
|
+
export function isMachineryPage(name) {
|
|
419
|
+
return META_FILENAMES.has(name.toLowerCase())
|
|
420
|
+
|| name.startsWith('_')
|
|
421
|
+
|| /^lint-report-/.test(name)
|
|
422
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-wiki-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|