@biaoo/tiangong-wiki 0.2.0 → 0.2.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.
- package/README.md +39 -50
- package/README.zh-CN.md +39 -50
- package/SKILL.md +75 -107
- package/assets/templates/achievement.md +8 -8
- package/assets/templates/bridge.md +8 -8
- package/assets/templates/concept.md +14 -18
- package/assets/templates/faq.md +8 -10
- package/assets/templates/lesson.md +8 -8
- package/assets/templates/method.md +16 -8
- package/assets/templates/misconception.md +10 -10
- package/assets/templates/person.md +8 -8
- package/assets/templates/research-note.md +10 -10
- package/assets/templates/resume.md +11 -10
- package/assets/templates/source-summary.md +8 -12
- package/assets/tiangong-wiki-framework.png +0 -0
- package/assets/wiki.config.default.json +6 -3
- package/dist/commands/asset.js +21 -0
- package/dist/commands/skill.js +78 -0
- package/dist/commands/template.js +30 -0
- package/dist/core/cli-env.js +34 -5
- package/dist/core/global-config.js +61 -0
- package/dist/core/onboarding.js +252 -102
- package/dist/core/workflow-context.js +58 -21
- package/dist/core/workspace-skills.js +496 -60
- package/dist/daemon/server.js +8 -0
- package/dist/index.js +36 -1
- package/dist/operations/asset.js +81 -0
- package/dist/operations/query.js +25 -1
- package/dist/operations/template-lint.js +160 -0
- package/dist/utils/asset.js +75 -0
- package/dist/utils/errors.js +6 -0
- package/package.json +2 -1
- package/references/cli-interface.md +32 -1
- package/references/template-design-guide.md +125 -113
- package/references/{env.md → troubleshooting.md} +64 -33
- package/references/vault-to-wiki-instruction.md +109 -51
- package/references/wiki-maintenance-instruction.md +15 -15
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { accessSync, constants, lstatSync, realpathSync, rmSync, symlinkSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { accessSync, constants, lstatSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, symlinkSync, unlinkSync, } from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
3
5
|
import path from "node:path";
|
|
4
6
|
import { AppError } from "../utils/errors.js";
|
|
5
|
-
import { ensureDirSync, pathExistsSync } from "../utils/fs.js";
|
|
7
|
+
import { copyDirectoryContentsSync, ensureDirSync, pathExistsSync, readTextFileSync, writeTextFileSync } from "../utils/fs.js";
|
|
8
|
+
import { resolveRuntimePaths } from "./paths.js";
|
|
9
|
+
import { toOffsetIso } from "../utils/time.js";
|
|
6
10
|
export const PARSER_SKILL_SOURCE = "https://github.com/anthropics/skills";
|
|
11
|
+
export const MANAGED_SKILL_STATE_FILE = ".tiangong-wiki-skill.json";
|
|
7
12
|
export const OPTIONAL_PARSER_SKILLS = [
|
|
8
13
|
{ name: "pdf", summary: "Process PDF files" },
|
|
9
14
|
{ name: "docx", summary: "Process DOCX files" },
|
|
@@ -11,6 +16,11 @@ export const OPTIONAL_PARSER_SKILLS = [
|
|
|
11
16
|
{ name: "xlsx", summary: "Process XLSX/CSV files" },
|
|
12
17
|
];
|
|
13
18
|
const OPTIONAL_PARSER_SKILL_NAMES = new Set(OPTIONAL_PARSER_SKILLS.map((skill) => skill.name));
|
|
19
|
+
const MANAGED_SKILL_SOURCE_KINDS = new Set([
|
|
20
|
+
"workspace-package",
|
|
21
|
+
"curated-parser",
|
|
22
|
+
"external-source",
|
|
23
|
+
]);
|
|
14
24
|
function canRead(filePath) {
|
|
15
25
|
accessSync(filePath, constants.R_OK);
|
|
16
26
|
return true;
|
|
@@ -105,49 +115,142 @@ export function inspectSkillInstall(skillPath, name = path.basename(skillPath))
|
|
|
105
115
|
};
|
|
106
116
|
}
|
|
107
117
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
sourcePath: packageRoot,
|
|
128
|
-
skillPath: paths.wikiSkillPath,
|
|
129
|
-
status: "updated",
|
|
130
|
-
};
|
|
118
|
+
function getManagedSkillStatePath(skillPath) {
|
|
119
|
+
return path.join(skillPath, MANAGED_SKILL_STATE_FILE);
|
|
120
|
+
}
|
|
121
|
+
function readManagedSkillMetadata(skillPath) {
|
|
122
|
+
const metadataPath = getManagedSkillStatePath(skillPath);
|
|
123
|
+
if (!pathExistsSync(metadataPath)) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const parsed = JSON.parse(readTextFileSync(metadataPath));
|
|
128
|
+
if (parsed.version !== 1 ||
|
|
129
|
+
typeof parsed.skillName !== "string" ||
|
|
130
|
+
typeof parsed.sourceKind !== "string" ||
|
|
131
|
+
!MANAGED_SKILL_SOURCE_KINDS.has(parsed.sourceKind) ||
|
|
132
|
+
typeof parsed.source !== "string" ||
|
|
133
|
+
typeof parsed.installedAt !== "string" ||
|
|
134
|
+
typeof parsed.baselineHash !== "string") {
|
|
135
|
+
return null;
|
|
131
136
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
137
|
+
return {
|
|
138
|
+
version: 1,
|
|
139
|
+
skillName: parsed.skillName,
|
|
140
|
+
sourceKind: parsed.sourceKind,
|
|
141
|
+
source: parsed.source,
|
|
142
|
+
installedAt: parsed.installedAt,
|
|
143
|
+
baselineHash: parsed.baselineHash,
|
|
144
|
+
...(typeof parsed.command === "string" ? { command: parsed.command } : {}),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function writeManagedSkillMetadata(skillPath, metadata) {
|
|
152
|
+
writeTextFileSync(getManagedSkillStatePath(skillPath), `${JSON.stringify(metadata, null, 2)}\n`);
|
|
153
|
+
}
|
|
154
|
+
function hashSkillDirectory(skillPath) {
|
|
155
|
+
const hash = createHash("sha256");
|
|
156
|
+
const visit = (dirPath, relativePrefix) => {
|
|
157
|
+
const entries = readdirSync(dirPath, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
if (entry.name === MANAGED_SKILL_STATE_FILE) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const absolutePath = path.join(dirPath, entry.name);
|
|
163
|
+
const relativePath = path.posix.join(relativePrefix, entry.name);
|
|
164
|
+
const stats = lstatSync(absolutePath);
|
|
165
|
+
if (stats.isSymbolicLink()) {
|
|
166
|
+
hash.update(`symlink:${relativePath}:${realpathSync(absolutePath)}\n`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (stats.isDirectory()) {
|
|
170
|
+
hash.update(`dir:${relativePath}\n`);
|
|
171
|
+
visit(absolutePath, relativePath);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
hash.update(`file:${relativePath}\n`);
|
|
175
|
+
hash.update(readFileSync(absolutePath));
|
|
140
176
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
177
|
+
};
|
|
178
|
+
visit(skillPath, "");
|
|
179
|
+
return hash.digest("hex");
|
|
180
|
+
}
|
|
181
|
+
function getWorkspaceRootForSkillPath(skillPath) {
|
|
182
|
+
return path.dirname(path.dirname(path.dirname(skillPath)));
|
|
183
|
+
}
|
|
184
|
+
function replaceSkillDirectory(targetPath, sourcePath) {
|
|
185
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
186
|
+
ensureDirSync(targetPath);
|
|
187
|
+
copyDirectoryContentsSync(sourcePath, targetPath);
|
|
188
|
+
}
|
|
189
|
+
function createManagedSkillMetadata(descriptor, baselineHash, command) {
|
|
190
|
+
return {
|
|
191
|
+
version: 1,
|
|
192
|
+
skillName: descriptor.name,
|
|
193
|
+
sourceKind: descriptor.sourceKind,
|
|
194
|
+
source: descriptor.source,
|
|
195
|
+
installedAt: toOffsetIso(),
|
|
196
|
+
baselineHash,
|
|
197
|
+
...(command ? { command } : {}),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function ensureParserSkillName(rawName) {
|
|
201
|
+
const normalized = rawName.trim().toLowerCase();
|
|
202
|
+
if (!OPTIONAL_PARSER_SKILL_NAMES.has(normalized)) {
|
|
203
|
+
throw new AppError(`Unsupported parser skill: ${rawName}`, "config");
|
|
145
204
|
}
|
|
146
|
-
|
|
205
|
+
return normalized;
|
|
206
|
+
}
|
|
207
|
+
function normalizeCustomSkillName(rawName) {
|
|
208
|
+
const name = rawName.trim();
|
|
209
|
+
if (!name) {
|
|
210
|
+
throw new AppError("Skill name must not be empty.", "config");
|
|
211
|
+
}
|
|
212
|
+
if (name === "." || name === ".." || name.includes("/") || name.includes("\\")) {
|
|
213
|
+
throw new AppError(`Skill name must be a single directory-friendly token, got ${rawName}`, "config");
|
|
214
|
+
}
|
|
215
|
+
if (name === "tiangong-wiki-skill") {
|
|
216
|
+
throw new AppError("Skill name tiangong-wiki-skill is reserved for the workspace package skill.", "config");
|
|
217
|
+
}
|
|
218
|
+
return name;
|
|
219
|
+
}
|
|
220
|
+
function normalizeManagedSource(rawSource) {
|
|
221
|
+
const source = rawSource.trim();
|
|
222
|
+
if (!source) {
|
|
223
|
+
throw new AppError("Skill source must not be empty.", "config");
|
|
224
|
+
}
|
|
225
|
+
return source;
|
|
226
|
+
}
|
|
227
|
+
function createParserDescriptor(workspaceRoot, name, configured) {
|
|
147
228
|
return {
|
|
148
|
-
|
|
229
|
+
name,
|
|
230
|
+
sourceKind: "curated-parser",
|
|
231
|
+
configured,
|
|
232
|
+
source: PARSER_SKILL_SOURCE,
|
|
233
|
+
skillPath: resolveWorkspaceSkillPath(workspaceRoot, name),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function createExternalDescriptor(workspaceRoot, name, source) {
|
|
237
|
+
const normalizedName = normalizeCustomSkillName(name);
|
|
238
|
+
return {
|
|
239
|
+
name: normalizedName,
|
|
240
|
+
sourceKind: "external-source",
|
|
241
|
+
configured: true,
|
|
242
|
+
source: normalizeManagedSource(source),
|
|
243
|
+
skillPath: resolveWorkspaceSkillPath(workspaceRoot, normalizedName),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function createWikiDescriptor(wikiPath, packageRoot) {
|
|
247
|
+
const paths = resolveWorkspaceSkillPaths(wikiPath);
|
|
248
|
+
return {
|
|
249
|
+
name: "tiangong-wiki-skill",
|
|
250
|
+
sourceKind: "workspace-package",
|
|
251
|
+
configured: true,
|
|
252
|
+
source: packageRoot,
|
|
149
253
|
skillPath: paths.wikiSkillPath,
|
|
150
|
-
status: "linked",
|
|
151
254
|
};
|
|
152
255
|
}
|
|
153
256
|
function renderCommand(command, args) {
|
|
@@ -155,44 +258,50 @@ function renderCommand(command, args) {
|
|
|
155
258
|
.map((part) => (/[A-Za-z0-9_./:@+-]+/.test(part) ? part : JSON.stringify(part)))
|
|
156
259
|
.join(" ");
|
|
157
260
|
}
|
|
158
|
-
export function
|
|
261
|
+
export function buildExternalSkillInstallInvocation(source, skillName) {
|
|
159
262
|
const command = getNpxCommand();
|
|
160
|
-
const args = ["-y", "skills", "add",
|
|
263
|
+
const args = ["-y", "skills", "add", source, "--skill", skillName, "-a", "codex", "-y"];
|
|
161
264
|
return {
|
|
162
265
|
command,
|
|
163
266
|
args,
|
|
164
267
|
rendered: renderCommand(command, args),
|
|
165
268
|
};
|
|
166
269
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
270
|
+
function installManagedExternalSkill(descriptor, options = {}) {
|
|
271
|
+
if (descriptor.sourceKind === "workspace-package") {
|
|
272
|
+
throw new AppError("Workspace package skills must be installed via ensureWikiSkillInstall.", "config");
|
|
273
|
+
}
|
|
274
|
+
const current = inspectSkillInstall(descriptor.skillPath, descriptor.name);
|
|
275
|
+
const invocation = buildExternalSkillInstallInvocation(descriptor.source, descriptor.name);
|
|
276
|
+
if (current.readable && options.skipIfExists !== false) {
|
|
172
277
|
return {
|
|
173
|
-
name:
|
|
174
|
-
|
|
278
|
+
name: descriptor.name,
|
|
279
|
+
source: descriptor.source,
|
|
280
|
+
skillPath: descriptor.skillPath,
|
|
175
281
|
skillMdPath: current.skillMdPath,
|
|
176
282
|
status: "existing",
|
|
177
283
|
command: invocation.rendered,
|
|
178
284
|
};
|
|
179
285
|
}
|
|
180
|
-
|
|
286
|
+
const workspaceRoot = getWorkspaceRootForSkillPath(descriptor.skillPath);
|
|
287
|
+
options.output?.write(`Installing skill ${descriptor.name} from ${descriptor.source}...\n`);
|
|
181
288
|
const result = spawnSync(invocation.command, invocation.args, {
|
|
182
289
|
cwd: workspaceRoot,
|
|
183
290
|
env: options.env ?? process.env,
|
|
184
291
|
encoding: "utf8",
|
|
185
292
|
});
|
|
186
293
|
if (result.error) {
|
|
187
|
-
throw new AppError(`failed to install
|
|
188
|
-
skillName,
|
|
294
|
+
throw new AppError(`failed to install skill ${descriptor.name}: ${result.error.message}`, "runtime", {
|
|
295
|
+
skillName: descriptor.name,
|
|
296
|
+
source: descriptor.source,
|
|
189
297
|
command: invocation.rendered,
|
|
190
298
|
cwd: workspaceRoot,
|
|
191
299
|
});
|
|
192
300
|
}
|
|
193
301
|
if (result.status !== 0) {
|
|
194
|
-
throw new AppError(`failed to install
|
|
195
|
-
skillName,
|
|
302
|
+
throw new AppError(`failed to install skill ${descriptor.name}`, "runtime", {
|
|
303
|
+
skillName: descriptor.name,
|
|
304
|
+
source: descriptor.source,
|
|
196
305
|
command: invocation.rendered,
|
|
197
306
|
cwd: workspaceRoot,
|
|
198
307
|
exitCode: result.status,
|
|
@@ -200,21 +309,348 @@ export function installParserSkill(skillName, workspaceRoot, options = {}) {
|
|
|
200
309
|
stderr: result.stderr.trim(),
|
|
201
310
|
});
|
|
202
311
|
}
|
|
203
|
-
const installed = inspectSkillInstall(skillPath,
|
|
312
|
+
const installed = inspectSkillInstall(descriptor.skillPath, descriptor.name);
|
|
204
313
|
if (!installed.readable) {
|
|
205
|
-
throw new AppError(`
|
|
206
|
-
skillName,
|
|
314
|
+
throw new AppError(`skill ${descriptor.name} was installed but SKILL.md is missing or unreadable`, "runtime", {
|
|
315
|
+
skillName: descriptor.name,
|
|
316
|
+
source: descriptor.source,
|
|
207
317
|
command: invocation.rendered,
|
|
208
318
|
cwd: workspaceRoot,
|
|
209
|
-
skillPath,
|
|
319
|
+
skillPath: descriptor.skillPath,
|
|
210
320
|
skillMdPath: installed.skillMdPath,
|
|
211
321
|
});
|
|
212
322
|
}
|
|
323
|
+
if (options.trackMetadata !== false) {
|
|
324
|
+
writeManagedSkillMetadata(descriptor.skillPath, createManagedSkillMetadata(descriptor, hashSkillDirectory(descriptor.skillPath), invocation.rendered));
|
|
325
|
+
}
|
|
213
326
|
return {
|
|
214
|
-
name:
|
|
215
|
-
|
|
327
|
+
name: descriptor.name,
|
|
328
|
+
source: descriptor.source,
|
|
329
|
+
skillPath: descriptor.skillPath,
|
|
216
330
|
skillMdPath: installed.skillMdPath,
|
|
217
331
|
status: "installed",
|
|
218
332
|
command: invocation.rendered,
|
|
219
333
|
};
|
|
220
334
|
}
|
|
335
|
+
function installManagedExternalSkillIntoTempWorkspace(descriptor, options = {}) {
|
|
336
|
+
const tempRoot = mkdtempSync(path.join(os.tmpdir(), "tiangong-wiki-skill-update-"));
|
|
337
|
+
try {
|
|
338
|
+
const result = installManagedExternalSkill({
|
|
339
|
+
...descriptor,
|
|
340
|
+
skillPath: resolveWorkspaceSkillPath(tempRoot, descriptor.name),
|
|
341
|
+
}, {
|
|
342
|
+
env: options.env,
|
|
343
|
+
trackMetadata: false,
|
|
344
|
+
});
|
|
345
|
+
return {
|
|
346
|
+
hash: hashSkillDirectory(resolveWorkspaceSkillPath(tempRoot, descriptor.name)),
|
|
347
|
+
tempRoot,
|
|
348
|
+
invocation: result.command,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function hasCompatibleManagedMetadata(metadata, descriptor) {
|
|
357
|
+
return metadata !== null &&
|
|
358
|
+
metadata.skillName === descriptor.name &&
|
|
359
|
+
metadata.sourceKind === descriptor.sourceKind &&
|
|
360
|
+
metadata.source === descriptor.source;
|
|
361
|
+
}
|
|
362
|
+
function readManagedSkillDescriptorsFromMetadata(workspaceRoot, options = {}) {
|
|
363
|
+
const skillsRoot = path.join(workspaceRoot, ".agents", "skills");
|
|
364
|
+
if (!pathExistsSync(skillsRoot)) {
|
|
365
|
+
return [];
|
|
366
|
+
}
|
|
367
|
+
const includeKinds = options.includeSourceKinds ? new Set(options.includeSourceKinds) : null;
|
|
368
|
+
const descriptors = [];
|
|
369
|
+
const entries = readdirSync(skillsRoot, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
|
|
370
|
+
for (const entry of entries) {
|
|
371
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const skillPath = path.join(skillsRoot, entry.name);
|
|
375
|
+
const metadata = readManagedSkillMetadata(skillPath);
|
|
376
|
+
if (!metadata || metadata.skillName !== entry.name) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (includeKinds && !includeKinds.has(metadata.sourceKind)) {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
descriptors.push({
|
|
383
|
+
name: metadata.skillName,
|
|
384
|
+
sourceKind: metadata.sourceKind,
|
|
385
|
+
configured: true,
|
|
386
|
+
source: metadata.source,
|
|
387
|
+
skillPath,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
return descriptors;
|
|
391
|
+
}
|
|
392
|
+
function createManagedSkillCatalog(env = process.env) {
|
|
393
|
+
const paths = resolveRuntimePaths(env);
|
|
394
|
+
const workspace = resolveWorkspaceSkillPaths(paths.wikiPath);
|
|
395
|
+
const configuredParserSkills = parseParserSkills(env.WIKI_PARSER_SKILLS, { strict: false });
|
|
396
|
+
return {
|
|
397
|
+
wikiDescriptor: createWikiDescriptor(paths.wikiPath, paths.packageRoot),
|
|
398
|
+
configuredParserDescriptors: configuredParserSkills.map((name) => createParserDescriptor(workspace.workspaceRoot, name, true)),
|
|
399
|
+
discoveredDescriptors: readManagedSkillDescriptorsFromMetadata(workspace.workspaceRoot),
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function resolveManagedSkillDescriptorByName(env, name) {
|
|
403
|
+
const catalog = createManagedSkillCatalog(env);
|
|
404
|
+
if (name === "tiangong-wiki-skill") {
|
|
405
|
+
return catalog.wikiDescriptor;
|
|
406
|
+
}
|
|
407
|
+
const discovered = catalog.discoveredDescriptors.find((descriptor) => descriptor.name === name);
|
|
408
|
+
if (discovered) {
|
|
409
|
+
return discovered;
|
|
410
|
+
}
|
|
411
|
+
if (OPTIONAL_PARSER_SKILL_NAMES.has(name)) {
|
|
412
|
+
return createParserDescriptor(getWorkspaceRootForSkillPath(catalog.wikiDescriptor.skillPath), name, catalog.configuredParserDescriptors.some((descriptor) => descriptor.name === name));
|
|
413
|
+
}
|
|
414
|
+
throw new AppError(`Unknown managed skill: ${name}`, "config");
|
|
415
|
+
}
|
|
416
|
+
function resolveManagedSkillDescriptors(env = process.env, names) {
|
|
417
|
+
if (names && names.length > 0) {
|
|
418
|
+
return names.map((name) => resolveManagedSkillDescriptorByName(env, name));
|
|
419
|
+
}
|
|
420
|
+
const catalog = createManagedSkillCatalog(env);
|
|
421
|
+
const descriptors = new Map();
|
|
422
|
+
for (const descriptor of catalog.discoveredDescriptors) {
|
|
423
|
+
if (descriptor.sourceKind === "external-source") {
|
|
424
|
+
descriptors.set(descriptor.name, descriptor);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
descriptors.set(catalog.wikiDescriptor.name, catalog.wikiDescriptor);
|
|
428
|
+
for (const descriptor of catalog.configuredParserDescriptors) {
|
|
429
|
+
if (!descriptors.has(descriptor.name)) {
|
|
430
|
+
descriptors.set(descriptor.name, descriptor);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return [...descriptors.values()];
|
|
434
|
+
}
|
|
435
|
+
function getWikiSkillStatus(descriptor) {
|
|
436
|
+
const current = inspectSkillInstall(descriptor.skillPath, descriptor.name);
|
|
437
|
+
if (!current.readable) {
|
|
438
|
+
return {
|
|
439
|
+
...descriptor,
|
|
440
|
+
state: "missing",
|
|
441
|
+
tracked: false,
|
|
442
|
+
message: "Skill is missing or unreadable.",
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
const stats = lstatSync(descriptor.skillPath);
|
|
446
|
+
if (!stats.isSymbolicLink()) {
|
|
447
|
+
return {
|
|
448
|
+
...descriptor,
|
|
449
|
+
state: "conflict",
|
|
450
|
+
tracked: false,
|
|
451
|
+
message: "Workspace skill path is a local directory, not the managed symlink target.",
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
if (realpathSync(descriptor.skillPath) !== realpathSync(descriptor.source)) {
|
|
455
|
+
return {
|
|
456
|
+
...descriptor,
|
|
457
|
+
state: "update_available",
|
|
458
|
+
tracked: true,
|
|
459
|
+
message: "Symlink points to an older or different package root.",
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
...descriptor,
|
|
464
|
+
state: "up_to_date",
|
|
465
|
+
tracked: true,
|
|
466
|
+
message: "Symlink points to the current package root.",
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function getExternalManagedSkillStatus(descriptor, env = process.env) {
|
|
470
|
+
const current = inspectSkillInstall(descriptor.skillPath, descriptor.name);
|
|
471
|
+
if (!current.readable) {
|
|
472
|
+
return {
|
|
473
|
+
...descriptor,
|
|
474
|
+
state: "missing",
|
|
475
|
+
tracked: false,
|
|
476
|
+
message: "Skill is missing or unreadable.",
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const currentHash = hashSkillDirectory(descriptor.skillPath);
|
|
480
|
+
const metadata = readManagedSkillMetadata(descriptor.skillPath);
|
|
481
|
+
const latest = installManagedExternalSkillIntoTempWorkspace(descriptor, { env });
|
|
482
|
+
try {
|
|
483
|
+
if (!hasCompatibleManagedMetadata(metadata, descriptor)) {
|
|
484
|
+
if (currentHash === latest.hash) {
|
|
485
|
+
return {
|
|
486
|
+
...descriptor,
|
|
487
|
+
state: "up_to_date",
|
|
488
|
+
tracked: false,
|
|
489
|
+
message: "Installed skill matches the latest source snapshot, but no compatible managed baseline metadata exists yet.",
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
...descriptor,
|
|
494
|
+
state: "conflict",
|
|
495
|
+
tracked: false,
|
|
496
|
+
message: "Installed skill differs from the latest source snapshot, but no compatible managed baseline metadata exists to separate local edits from source changes.",
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
if (currentHash === latest.hash) {
|
|
500
|
+
return {
|
|
501
|
+
...descriptor,
|
|
502
|
+
state: "up_to_date",
|
|
503
|
+
tracked: true,
|
|
504
|
+
message: "Installed skill matches the latest source snapshot.",
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
if (currentHash === metadata.baselineHash) {
|
|
508
|
+
return {
|
|
509
|
+
...descriptor,
|
|
510
|
+
state: "update_available",
|
|
511
|
+
tracked: true,
|
|
512
|
+
message: "A newer source snapshot is available and local files are unchanged.",
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
...descriptor,
|
|
517
|
+
state: "conflict",
|
|
518
|
+
tracked: true,
|
|
519
|
+
message: metadata.baselineHash === latest.hash
|
|
520
|
+
? "Local files differ from the managed baseline."
|
|
521
|
+
: "Local files and source snapshot both differ from the managed baseline.",
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
finally {
|
|
525
|
+
rmSync(latest.tempRoot, { recursive: true, force: true });
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
export function getManagedSkillStatus(env = process.env, names) {
|
|
529
|
+
return resolveManagedSkillDescriptors(env, names)
|
|
530
|
+
.map((descriptor) => descriptor.sourceKind === "workspace-package" ? getWikiSkillStatus(descriptor) : getExternalManagedSkillStatus(descriptor, env))
|
|
531
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
532
|
+
}
|
|
533
|
+
function updateWikiManagedSkill(descriptor, env = process.env, options = {}) {
|
|
534
|
+
const status = getWikiSkillStatus(descriptor);
|
|
535
|
+
if (status.state === "conflict" && !options.force) {
|
|
536
|
+
return {
|
|
537
|
+
...status,
|
|
538
|
+
action: "skipped",
|
|
539
|
+
message: `${status.message} Re-run with --force to replace it with the managed symlink.`,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
const paths = resolveRuntimePaths(env);
|
|
543
|
+
const installed = ensureWikiSkillInstall(paths.wikiPath, paths.packageRoot);
|
|
544
|
+
const nextStatus = getWikiSkillStatus(descriptor);
|
|
545
|
+
return {
|
|
546
|
+
...nextStatus,
|
|
547
|
+
action: status.state === "missing" ? "installed" : installed.status === "linked" ? "unchanged" : "updated",
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function updateExternalManagedSkill(descriptor, env = process.env, options = {}) {
|
|
551
|
+
const currentStatus = getExternalManagedSkillStatus(descriptor, env);
|
|
552
|
+
if (currentStatus.state === "missing") {
|
|
553
|
+
installManagedExternalSkill(descriptor, { env });
|
|
554
|
+
return {
|
|
555
|
+
...getExternalManagedSkillStatus(descriptor, env),
|
|
556
|
+
action: "installed",
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
if (currentStatus.state === "conflict" && !options.force) {
|
|
560
|
+
return {
|
|
561
|
+
...currentStatus,
|
|
562
|
+
action: "skipped",
|
|
563
|
+
message: `${currentStatus.message} Refusing to overwrite local changes without --force.`,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
const latest = installManagedExternalSkillIntoTempWorkspace(descriptor, { env });
|
|
567
|
+
try {
|
|
568
|
+
const latestSkillPath = resolveWorkspaceSkillPath(latest.tempRoot, descriptor.name);
|
|
569
|
+
const currentHash = hashSkillDirectory(descriptor.skillPath);
|
|
570
|
+
const action = currentHash === latest.hash ? "unchanged" : "updated";
|
|
571
|
+
if (action === "updated") {
|
|
572
|
+
replaceSkillDirectory(descriptor.skillPath, latestSkillPath);
|
|
573
|
+
}
|
|
574
|
+
writeManagedSkillMetadata(descriptor.skillPath, createManagedSkillMetadata(descriptor, latest.hash, latest.invocation));
|
|
575
|
+
return {
|
|
576
|
+
...getExternalManagedSkillStatus(descriptor, env),
|
|
577
|
+
action,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
finally {
|
|
581
|
+
rmSync(latest.tempRoot, { recursive: true, force: true });
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
export function updateManagedSkills(env = process.env, names, options = {}) {
|
|
585
|
+
return resolveManagedSkillDescriptors(env, names)
|
|
586
|
+
.map((descriptor) => descriptor.sourceKind === "workspace-package"
|
|
587
|
+
? updateWikiManagedSkill(descriptor, env, options)
|
|
588
|
+
: updateExternalManagedSkill(descriptor, env, options))
|
|
589
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
590
|
+
}
|
|
591
|
+
export function addManagedSkill(env = process.env, source, skillName, options = {}) {
|
|
592
|
+
const paths = resolveRuntimePaths(env);
|
|
593
|
+
const workspace = resolveWorkspaceSkillPaths(paths.wikiPath);
|
|
594
|
+
return updateExternalManagedSkill(createExternalDescriptor(workspace.workspaceRoot, skillName, source), env, options);
|
|
595
|
+
}
|
|
596
|
+
export function ensureWikiSkillInstall(wikiPath, packageRoot) {
|
|
597
|
+
const paths = resolveWorkspaceSkillPaths(wikiPath);
|
|
598
|
+
const packageRealPath = realpathSync(packageRoot);
|
|
599
|
+
const existing = inspectSkillInstall(paths.wikiSkillPath, "tiangong-wiki-skill");
|
|
600
|
+
ensureDirSync(paths.skillsRoot);
|
|
601
|
+
if (existing.exists) {
|
|
602
|
+
const stats = lstatSync(paths.wikiSkillPath);
|
|
603
|
+
if (stats.isSymbolicLink()) {
|
|
604
|
+
const currentRealPath = realpathSync(paths.wikiSkillPath);
|
|
605
|
+
if (currentRealPath === packageRealPath) {
|
|
606
|
+
return {
|
|
607
|
+
sourcePath: packageRoot,
|
|
608
|
+
skillPath: paths.wikiSkillPath,
|
|
609
|
+
status: "linked",
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
unlinkSync(paths.wikiSkillPath);
|
|
613
|
+
symlinkSync(packageRoot, paths.wikiSkillPath, "dir");
|
|
614
|
+
return {
|
|
615
|
+
sourcePath: packageRoot,
|
|
616
|
+
skillPath: paths.wikiSkillPath,
|
|
617
|
+
status: "updated",
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
if (existing.readable) {
|
|
621
|
+
rmSync(paths.wikiSkillPath, { recursive: true, force: true });
|
|
622
|
+
symlinkSync(packageRoot, paths.wikiSkillPath, "dir");
|
|
623
|
+
return {
|
|
624
|
+
sourcePath: packageRoot,
|
|
625
|
+
skillPath: paths.wikiSkillPath,
|
|
626
|
+
status: "updated",
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
throw new AppError(`workspace skill path is occupied and cannot be reused: ${paths.wikiSkillPath}`, "config", {
|
|
630
|
+
skillName: "tiangong-wiki-skill",
|
|
631
|
+
skillPath: paths.wikiSkillPath,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
symlinkSync(packageRoot, paths.wikiSkillPath, "dir");
|
|
635
|
+
return {
|
|
636
|
+
sourcePath: packageRoot,
|
|
637
|
+
skillPath: paths.wikiSkillPath,
|
|
638
|
+
status: "linked",
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
export function buildParserSkillInstallInvocation(skillName) {
|
|
642
|
+
return buildExternalSkillInstallInvocation(PARSER_SKILL_SOURCE, skillName);
|
|
643
|
+
}
|
|
644
|
+
export function installParserSkill(skillName, workspaceRoot, options = {}) {
|
|
645
|
+
const installed = installManagedExternalSkill(createParserDescriptor(workspaceRoot, skillName, true), {
|
|
646
|
+
env: options.env,
|
|
647
|
+
output: options.output,
|
|
648
|
+
});
|
|
649
|
+
return {
|
|
650
|
+
name: skillName,
|
|
651
|
+
skillPath: installed.skillPath,
|
|
652
|
+
skillMdPath: installed.skillMdPath,
|
|
653
|
+
status: installed.status,
|
|
654
|
+
command: installed.command,
|
|
655
|
+
};
|
|
656
|
+
}
|
package/dist/daemon/server.js
CHANGED
|
@@ -7,6 +7,7 @@ import { exportGraphContent, exportIndexContent } from "../operations/export.js"
|
|
|
7
7
|
import { getDashboardGraphOverview, getDashboardLintSummary, getDashboardPageDetail, getDashboardPageSource, getDashboardQueueItemDetail, getDashboardQueueSummary, getDashboardStatus, getDashboardVaultFileDetail, getDashboardVaultSummary, listDashboardLintIssues, listDashboardQueueItems, listDashboardVaultFiles, openDashboardPageSource, openDashboardVaultFile, retryDashboardQueueItem, searchDashboardGraph, } from "../operations/dashboard.js";
|
|
8
8
|
import { diffVaultFiles, findPages, ftsSearchPages, getPageInfo, getVaultQueue, getWikiStat, listPages, listVaultFiles, renderLintResult, runLint, searchPages, traverseGraph, } from "../operations/query.js";
|
|
9
9
|
import { createTemplate, listTemplates, listTypes, recommendTypes, showTemplate, showType, } from "../operations/type-template.js";
|
|
10
|
+
import { runTemplateLint } from "../operations/template-lint.js";
|
|
10
11
|
import { createPage, runSync, runSyncCommand } from "../operations/write.js";
|
|
11
12
|
import { AppError, asAppError } from "../utils/errors.js";
|
|
12
13
|
import { pathExistsSync } from "../utils/fs.js";
|
|
@@ -726,6 +727,13 @@ export async function runDaemonServer(options) {
|
|
|
726
727
|
writeJsonResponse(response, 200, showTemplate(env, pageType));
|
|
727
728
|
return;
|
|
728
729
|
}
|
|
730
|
+
if (method === "GET" && pathname === "/template/lint") {
|
|
731
|
+
writeJsonResponse(response, 200, runTemplateLint(env, {
|
|
732
|
+
pageType: url.searchParams.get("pageType") ?? undefined,
|
|
733
|
+
level: url.searchParams.get("level") ?? undefined,
|
|
734
|
+
}));
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
729
737
|
if (method === "POST" && pathname === "/template/create") {
|
|
730
738
|
const body = await readJsonBody(request);
|
|
731
739
|
const result = await runWriteTask("template-create", () => Promise.resolve(createTemplate(env, {
|