dsh-plugin-wiki-tools 0.7.0 → 0.8.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 +43 -1
- package/lib/scaffold.js +235 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import z from '@deepseek-ai/schemastery'
|
|
|
16
16
|
import { Vault } from './lib/vault.js'
|
|
17
17
|
import { quickView, searchVault } from './lib/search.js'
|
|
18
18
|
import { lintVault } from './lib/lint.js'
|
|
19
|
+
import { scaffoldVault, SCAFFOLD_MODES } from './lib/scaffold.js'
|
|
19
20
|
|
|
20
21
|
export const name = 'wiki-tools'
|
|
21
22
|
export const inject = ['tools']
|
|
@@ -215,6 +216,47 @@ export function createTools(vault, options = {}) {
|
|
|
215
216
|
presentCall: args => ({ card: 'generic', title: `Rename wiki page: ${args.title} → ${args.new_title}`, kind: 'other', rawInput: { from: args.title, to: args.new_title } }),
|
|
216
217
|
})
|
|
217
218
|
|
|
219
|
+
const wikiScaffold = defineTool({
|
|
220
|
+
name: 'wiki_scaffold',
|
|
221
|
+
description:
|
|
222
|
+
'Scaffold a wiki vault in one call: the chosen mode\u2019s folder structure with per-folder _index.md, '
|
|
223
|
+
+ 'the core files (wiki/index.md, log.md, hot.md, overview.md), the mode\u2019s key seed pages, a raw-source '
|
|
224
|
+
+ 'manifest, and the vault AGENTS.md conventions file. Idempotent — existing files are kept. Modes: '
|
|
225
|
+
+ 'generic (matches wiki_write routing), sitemap, repository, business, personal, research, book. '
|
|
226
|
+
+ 'The result carries a suggested typeFolders config for non-generic modes to paste into the profile.',
|
|
227
|
+
parameters: {
|
|
228
|
+
mode: {
|
|
229
|
+
type: 'string',
|
|
230
|
+
required: true,
|
|
231
|
+
enum: Object.keys(SCAFFOLD_MODES),
|
|
232
|
+
description: 'Scaffold mode; pick by what the vault is for (generic for a general knowledge base).',
|
|
233
|
+
},
|
|
234
|
+
purpose: {
|
|
235
|
+
type: 'string',
|
|
236
|
+
description: 'One-line vault purpose, written into overview.md and AGENTS.md.',
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
output: {
|
|
240
|
+
schema: {
|
|
241
|
+
type: 'object',
|
|
242
|
+
additionalProperties: true,
|
|
243
|
+
},
|
|
244
|
+
render: (_args, value) => [{
|
|
245
|
+
type: 'text',
|
|
246
|
+
text: typeof value === 'object' && value !== null && 'created' in value
|
|
247
|
+
? `wiki_scaffold (${value.mode}): created ${value.created.length} files, skipped ${value.skipped.length} existing`
|
|
248
|
+
+ (Object.keys(value.suggestedTypeFolders ?? {}).length > 0
|
|
249
|
+
? `; suggested typeFolders: ${JSON.stringify(value.suggestedTypeFolders)}`
|
|
250
|
+
: '')
|
|
251
|
+
: 'wiki_scaffold: failed',
|
|
252
|
+
}],
|
|
253
|
+
},
|
|
254
|
+
async execute(args) {
|
|
255
|
+
return await scaffoldVault(vault.root, args)
|
|
256
|
+
},
|
|
257
|
+
presentCall: args => ({ card: 'generic', title: `Scaffold wiki vault (${args.mode})`, kind: 'other', rawInput: { mode: args.mode } }),
|
|
258
|
+
})
|
|
259
|
+
|
|
218
260
|
const wikiLint = defineTool({
|
|
219
261
|
name: 'wiki_lint',
|
|
220
262
|
description:
|
|
@@ -238,7 +280,7 @@ export function createTools(vault, options = {}) {
|
|
|
238
280
|
presentCall: () => ({ card: 'generic', title: 'Lint wiki vault', kind: 'other' }),
|
|
239
281
|
})
|
|
240
282
|
|
|
241
|
-
return [wikiQuery, wikiWrite, wikiRename, wikiLint]
|
|
283
|
+
return [wikiQuery, wikiWrite, wikiRename, wikiScaffold, wikiLint]
|
|
242
284
|
}
|
|
243
285
|
|
|
244
286
|
/**
|
package/lib/scaffold.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault scaffolding: the mechanical half of the wiki skill's SCAFFOLD
|
|
3
|
+
* operation as one call. Creates the mode's folder structure, the core wiki
|
|
4
|
+
* files (index, log, hot cache, overview), per-folder sub-indexes, the mode's
|
|
5
|
+
* key seed pages, the vault AGENTS.md conventions file, and the raw-source
|
|
6
|
+
* manifest. Idempotent: existing files are kept and reported as skipped.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-plugin-wiki-tools/lib/scaffold
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
12
|
+
import { join } from 'node:path'
|
|
13
|
+
import { readFile } from 'node:fs/promises'
|
|
14
|
+
import { today } from './vault.js'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Folder sets per scaffold mode. `generic` matches the wiki_write routing
|
|
18
|
+
* (TYPE_FOLDERS); the six named modes follow the wiki skill's modes reference.
|
|
19
|
+
* `stubFolder` holds the mode's overview page; `stubs` are the key pages the
|
|
20
|
+
* mode's reference lists, each seeded into the folder named alongside it.
|
|
21
|
+
*/
|
|
22
|
+
export const SCAFFOLD_MODES = {
|
|
23
|
+
generic: {
|
|
24
|
+
label: 'Generic knowledge base',
|
|
25
|
+
folders: ['wiki/sources', 'wiki/entities', 'wiki/concepts', 'wiki/domains', 'wiki/questions', 'wiki/comparisons', 'wiki/meta'],
|
|
26
|
+
stubs: [],
|
|
27
|
+
typeFolders: {},
|
|
28
|
+
},
|
|
29
|
+
sitemap: {
|
|
30
|
+
label: 'Website / sitemap',
|
|
31
|
+
folders: ['wiki/pages', 'wiki/structure', 'wiki/audits', 'wiki/keywords', 'wiki/entities'],
|
|
32
|
+
stubs: [
|
|
33
|
+
{ title: 'Site Overview', folder: 'wiki/structure', type: 'page' },
|
|
34
|
+
{ title: 'Navigation Structure', folder: 'wiki/structure', type: 'page' },
|
|
35
|
+
{ title: 'Content Gaps', folder: 'wiki/audits', type: 'page' },
|
|
36
|
+
{ title: 'Redirect Map', folder: 'wiki/audits', type: 'page' },
|
|
37
|
+
{ title: 'Keyword Clusters', folder: 'wiki/keywords', type: 'page' },
|
|
38
|
+
],
|
|
39
|
+
typeFolders: { source: 'wiki/pages', entity: 'wiki/entities' },
|
|
40
|
+
},
|
|
41
|
+
repository: {
|
|
42
|
+
label: 'GitHub / repository',
|
|
43
|
+
folders: ['wiki/modules', 'wiki/components', 'wiki/decisions', 'wiki/dependencies', 'wiki/flows'],
|
|
44
|
+
stubs: [
|
|
45
|
+
{ title: 'Architecture Overview', folder: 'wiki/modules', type: 'module' },
|
|
46
|
+
{ title: 'Data Flow', folder: 'wiki/flows', type: 'flow' },
|
|
47
|
+
{ title: 'Tech Stack', folder: 'wiki/dependencies', type: 'dependency' },
|
|
48
|
+
{ title: 'Dependency Graph', folder: 'wiki/dependencies', type: 'dependency' },
|
|
49
|
+
{ title: 'Key Decisions', folder: 'wiki/decisions', type: 'decision' },
|
|
50
|
+
],
|
|
51
|
+
typeFolders: { source: 'wiki/modules', comparison: 'wiki/decisions' },
|
|
52
|
+
},
|
|
53
|
+
business: {
|
|
54
|
+
label: 'Business / project',
|
|
55
|
+
folders: ['wiki/stakeholders', 'wiki/decisions', 'wiki/deliverables', 'wiki/intel', 'wiki/comms'],
|
|
56
|
+
stubs: [
|
|
57
|
+
{ title: 'Project Overview', folder: 'wiki/deliverables', type: 'deliverable' },
|
|
58
|
+
{ title: 'Stakeholder Map', folder: 'wiki/stakeholders', type: 'stakeholder' },
|
|
59
|
+
{ title: 'Decision Log', folder: 'wiki/decisions', type: 'decision' },
|
|
60
|
+
{ title: 'Competitor Landscape', folder: 'wiki/intel', type: 'competitor' },
|
|
61
|
+
],
|
|
62
|
+
typeFolders: { entity: 'wiki/stakeholders', source: 'wiki/comms', decision: 'wiki/decisions' },
|
|
63
|
+
},
|
|
64
|
+
personal: {
|
|
65
|
+
label: 'Personal / second brain',
|
|
66
|
+
folders: ['wiki/goals', 'wiki/learning', 'wiki/people', 'wiki/areas', 'wiki/resources'],
|
|
67
|
+
stubs: [
|
|
68
|
+
{ title: 'North Star', folder: 'wiki/goals', type: 'goal' },
|
|
69
|
+
{ title: 'Annual Goals', folder: 'wiki/goals', type: 'goal' },
|
|
70
|
+
],
|
|
71
|
+
typeFolders: { concept: 'wiki/learning', domain: 'wiki/areas', entity: 'wiki/people', source: 'wiki/resources' },
|
|
72
|
+
},
|
|
73
|
+
research: {
|
|
74
|
+
label: 'Research',
|
|
75
|
+
folders: ['wiki/papers', 'wiki/concepts', 'wiki/entities', 'wiki/thesis', 'wiki/gaps'],
|
|
76
|
+
stubs: [
|
|
77
|
+
{ title: 'Research Overview', folder: 'wiki/thesis', type: 'thesis' },
|
|
78
|
+
{ title: 'Open Questions', folder: 'wiki/gaps', type: 'gap' },
|
|
79
|
+
],
|
|
80
|
+
typeFolders: { source: 'wiki/papers', question: 'wiki/gaps', concept: 'wiki/concepts' },
|
|
81
|
+
},
|
|
82
|
+
book: {
|
|
83
|
+
label: 'Book / course',
|
|
84
|
+
folders: ['wiki/characters', 'wiki/themes', 'wiki/concepts', 'wiki/timeline', 'wiki/synthesis'],
|
|
85
|
+
stubs: [
|
|
86
|
+
{ title: 'Book Overview', folder: 'wiki/timeline', type: 'chapter' },
|
|
87
|
+
{ title: 'My Takeaways', folder: 'wiki/synthesis', type: 'synthesis' },
|
|
88
|
+
],
|
|
89
|
+
typeFolders: { concept: 'wiki/concepts', question: 'wiki/synthesis' },
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Scaffold one vault for a mode. Every artifact is written only when absent,
|
|
95
|
+
* so re-running after a partial scaffold or over an existing vault is safe.
|
|
96
|
+
* @param {string} root - absolute vault root.
|
|
97
|
+
* @param {object} input - scaffold options.
|
|
98
|
+
* @param {keyof typeof SCAFFOLD_MODES} input.mode - scaffold mode.
|
|
99
|
+
* @param {string} [input.purpose] - one-line vault purpose for overview and AGENTS.md.
|
|
100
|
+
* @returns {Promise<{ mode: string, created: string[], skipped: string[], suggestedTypeFolders: Record<string, string> }>}
|
|
101
|
+
*/
|
|
102
|
+
export async function scaffoldVault(root, { mode, purpose }) {
|
|
103
|
+
const spec = SCAFFOLD_MODES[mode]
|
|
104
|
+
if (spec === undefined) {
|
|
105
|
+
throw new Error(`wiki-tools: unknown scaffold mode "${mode}"; choose one of ${Object.keys(SCAFFOLD_MODES).join(', ')}`)
|
|
106
|
+
}
|
|
107
|
+
const date = today()
|
|
108
|
+
const created = []
|
|
109
|
+
const skipped = []
|
|
110
|
+
const writeIfAbsent = async (relPath, content) => {
|
|
111
|
+
const path = join(root, relPath)
|
|
112
|
+
if (await readFile(path, 'utf8').then(() => true, () => false)) {
|
|
113
|
+
skipped.push(relPath)
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
await mkdir(join(path, '..'), { recursive: true })
|
|
117
|
+
await writeFile(path, content, 'utf8')
|
|
118
|
+
created.push(relPath)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
await writeIfAbsent('.raw/.manifest.json', '{"sources":{}}\n')
|
|
122
|
+
for (const folder of spec.folders) {
|
|
123
|
+
await writeIfAbsent(`${folder}/_index.md`, [
|
|
124
|
+
'---',
|
|
125
|
+
'type: meta',
|
|
126
|
+
`title: "${folder.split('/').pop()} Index"`,
|
|
127
|
+
`updated: ${date}`,
|
|
128
|
+
'---',
|
|
129
|
+
'',
|
|
130
|
+
`# ${folder.split('/').pop()}`,
|
|
131
|
+
'',
|
|
132
|
+
].join('\n'))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
await writeIfAbsent('wiki/index.md', indexTemplate(date))
|
|
136
|
+
await writeIfAbsent('wiki/log.md', `# Wiki Log\n`)
|
|
137
|
+
await writeIfAbsent('wiki/hot.md', [
|
|
138
|
+
'---',
|
|
139
|
+
'type: meta',
|
|
140
|
+
'title: "Hot Cache"',
|
|
141
|
+
`updated: ${date}`,
|
|
142
|
+
'---',
|
|
143
|
+
'',
|
|
144
|
+
'# Recent Context',
|
|
145
|
+
'',
|
|
146
|
+
`Scaffolded ${date}. ${purpose ?? spec.label}.`,
|
|
147
|
+
'',
|
|
148
|
+
].join('\n'))
|
|
149
|
+
await writeIfAbsent('wiki/overview.md', [
|
|
150
|
+
'---',
|
|
151
|
+
'type: overview',
|
|
152
|
+
`title: "Overview"`,
|
|
153
|
+
`updated: ${date}`,
|
|
154
|
+
'---',
|
|
155
|
+
'',
|
|
156
|
+
'# Overview',
|
|
157
|
+
'',
|
|
158
|
+
purpose ?? spec.label,
|
|
159
|
+
'',
|
|
160
|
+
].join('\n'))
|
|
161
|
+
|
|
162
|
+
for (const stub of spec.stubs) {
|
|
163
|
+
await writeIfAbsent(`${stub.folder}/${stub.title}.md`, [
|
|
164
|
+
'---',
|
|
165
|
+
`type: ${stub.type}`,
|
|
166
|
+
`title: "${stub.title}"`,
|
|
167
|
+
'status: seed',
|
|
168
|
+
`created: ${date}`,
|
|
169
|
+
`updated: ${date}`,
|
|
170
|
+
'tags:',
|
|
171
|
+
` - ${stub.type}`,
|
|
172
|
+
'---',
|
|
173
|
+
'',
|
|
174
|
+
`# ${stub.title}`,
|
|
175
|
+
'',
|
|
176
|
+
`Seed page from ${mode} scaffold. Fill in.`,
|
|
177
|
+
'',
|
|
178
|
+
].join('\n'))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await writeIfAbsent('AGENTS.md', agentsTemplate(mode, spec.label, purpose, date))
|
|
182
|
+
|
|
183
|
+
return { mode, created, skipped, suggestedTypeFolders: spec.typeFolders }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Master-index template with one empty section per generic type, in catalog
|
|
188
|
+
* order; named-mode vaults keep the same sections for their mapped types.
|
|
189
|
+
* @param {string} date - scaffold date.
|
|
190
|
+
* @returns {string} the index file content.
|
|
191
|
+
*/
|
|
192
|
+
function indexTemplate(date) {
|
|
193
|
+
const sections = ['Entities', 'Concepts', 'Sources', 'Questions']
|
|
194
|
+
return [
|
|
195
|
+
'---',
|
|
196
|
+
'type: meta',
|
|
197
|
+
'title: "Wiki Index"',
|
|
198
|
+
`updated: ${date}`,
|
|
199
|
+
'---',
|
|
200
|
+
'',
|
|
201
|
+
'# Wiki Index',
|
|
202
|
+
'',
|
|
203
|
+
...sections.flatMap(section => [`## ${section}`, '']),
|
|
204
|
+
].join('\n')
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Vault conventions file: the rules every contributing agent follows.
|
|
209
|
+
* @param {string} mode - scaffold mode id.
|
|
210
|
+
* @param {string} label - human-readable mode label.
|
|
211
|
+
* @param {string | undefined} purpose - one-line vault purpose.
|
|
212
|
+
* @param {string} date - scaffold date.
|
|
213
|
+
* @returns {string} the AGENTS.md content.
|
|
214
|
+
*/
|
|
215
|
+
function agentsTemplate(mode, label, purpose, date) {
|
|
216
|
+
return `# Wiki Vault Conventions
|
|
217
|
+
|
|
218
|
+
Mode: ${mode} (${label})
|
|
219
|
+
Purpose: ${purpose ?? '(fill in)'}
|
|
220
|
+
Created: ${date}
|
|
221
|
+
|
|
222
|
+
## Rules
|
|
223
|
+
|
|
224
|
+
- Every page uses flat YAML frontmatter: type, title, status, created, updated, tags at minimum.
|
|
225
|
+
- status is one of seed | developing | mature | evergreen.
|
|
226
|
+
- Wikilinks use [[Note Name]]; filenames are unique across the vault, no paths needed.
|
|
227
|
+
- .raw/ holds immutable sources; never modify them.
|
|
228
|
+
- wiki/index.md is the master catalog; every page is listed in its section.
|
|
229
|
+
- wiki/log.md is append-only; new entries go at the TOP; never edit past entries.
|
|
230
|
+
- wiki/hot.md is a ~500-word cache of recent context; overwrite it completely each update.
|
|
231
|
+
- Prefer the wiki tools (wiki_query, wiki_write, wiki_rename, wiki_lint) over raw file edits;
|
|
232
|
+
they keep frontmatter, the index, the folder _index.md files, and the log consistent.
|
|
233
|
+
- Contradictions between pages get > [!contradiction] callouts on both pages, never silent edits.
|
|
234
|
+
`
|
|
235
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-wiki-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|