@lmzhen/dsh-evolution-core 0.1.0-rc.4 → 0.1.0-rc.6
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/index.js +111 -0
- package/lib/types/skill-store.d.ts +12 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1278,6 +1278,117 @@ var SkillLibrary = class {
|
|
|
1278
1278
|
path: dest
|
|
1279
1279
|
};
|
|
1280
1280
|
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Merge the bodies of `sources` into `target` and archive the sources with
|
|
1283
|
+
* an absorbed-into marker. Hermes-style consolidation: overlapping skills
|
|
1284
|
+
* collapse into one, and the originals stay recoverable under `.archive/`.
|
|
1285
|
+
*/
|
|
1286
|
+
async consolidate(target, sources) {
|
|
1287
|
+
const normalizedSources = [...new Set(sources)].filter((name) => name !== target);
|
|
1288
|
+
if (normalizedSources.length === 0) return {
|
|
1289
|
+
ok: false,
|
|
1290
|
+
message: "Consolidation requires at least one distinct source skill."
|
|
1291
|
+
};
|
|
1292
|
+
for (const name of [target, ...normalizedSources]) if (!SKILL_NAME_RE.test(name)) return {
|
|
1293
|
+
ok: false,
|
|
1294
|
+
message: `Invalid skill name "${name}". Use lowercase letters, digits, and hyphens.`
|
|
1295
|
+
};
|
|
1296
|
+
const targetDir = skillDir(this.root, target);
|
|
1297
|
+
const targetMd = await this.io.readText(join(targetDir, "SKILL.md"));
|
|
1298
|
+
if (!targetMd) return {
|
|
1299
|
+
ok: false,
|
|
1300
|
+
message: `Skill "${target}" not found.`
|
|
1301
|
+
};
|
|
1302
|
+
const targetProtection = await this.writeProtection(target);
|
|
1303
|
+
if (targetProtection) return {
|
|
1304
|
+
ok: false,
|
|
1305
|
+
message: `Skill "${target}" is protected (${targetProtection}).`
|
|
1306
|
+
};
|
|
1307
|
+
const parts = [];
|
|
1308
|
+
for (const source of normalizedSources) {
|
|
1309
|
+
const protection = await this.deleteProtection(source);
|
|
1310
|
+
if (protection) return {
|
|
1311
|
+
ok: false,
|
|
1312
|
+
message: `Skill "${source}" is protected (${protection}).`
|
|
1313
|
+
};
|
|
1314
|
+
const sourceMd = await this.io.readText(join(skillDir(this.root, source), "SKILL.md"));
|
|
1315
|
+
if (!sourceMd) return {
|
|
1316
|
+
ok: false,
|
|
1317
|
+
message: `Skill "${source}" not found.`
|
|
1318
|
+
};
|
|
1319
|
+
const parsed = parseFrontmatter(sourceMd);
|
|
1320
|
+
if (!parsed) return {
|
|
1321
|
+
ok: false,
|
|
1322
|
+
message: `Skill "${source}" has no valid frontmatter; refusing to merge.`
|
|
1323
|
+
};
|
|
1324
|
+
parts.push(`\n<!-- consolidated from ${source} at ${(/* @__PURE__ */ new Date()).toISOString()} -->\n${parsed.body.trim()}`);
|
|
1325
|
+
}
|
|
1326
|
+
const merged = targetMd.trimEnd() + parts.join("\n") + "\n";
|
|
1327
|
+
const validation = validateFrontmatter(merged, target, this.limits);
|
|
1328
|
+
if (validation) return {
|
|
1329
|
+
ok: false,
|
|
1330
|
+
message: `Consolidation rejected: ${validation}`
|
|
1331
|
+
};
|
|
1332
|
+
const threat = scanContentThreats(merged);
|
|
1333
|
+
if (threat) return {
|
|
1334
|
+
ok: false,
|
|
1335
|
+
message: threat
|
|
1336
|
+
};
|
|
1337
|
+
await this.io.writeText(join(targetDir, "SKILL.md"), merged);
|
|
1338
|
+
for (const source of normalizedSources) {
|
|
1339
|
+
const archived = await this.archive(source, target);
|
|
1340
|
+
if (!archived.ok) return archived;
|
|
1341
|
+
}
|
|
1342
|
+
return {
|
|
1343
|
+
ok: true,
|
|
1344
|
+
message: `Consolidated ${normalizedSources.join(", ")} into "${target}".`,
|
|
1345
|
+
path: targetDir
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Restore one skill from `.archive/` back to the active root. Hermes-style
|
|
1350
|
+
* recoverability: archival never deletes, and this is the control-plane
|
|
1351
|
+
* path back. The `.archive-reason` marker is dropped on restore.
|
|
1352
|
+
*/
|
|
1353
|
+
async restoreFromArchive(name) {
|
|
1354
|
+
if (!SKILL_NAME_RE.test(name)) return {
|
|
1355
|
+
ok: false,
|
|
1356
|
+
message: `Invalid skill name "${name}". Use lowercase letters, digits, and hyphens.`
|
|
1357
|
+
};
|
|
1358
|
+
if (await this.io.exists(join(skillDir(this.root, name), "SKILL.md"))) return {
|
|
1359
|
+
ok: false,
|
|
1360
|
+
message: `Skill "${name}" already exists in the active root; refusing to overwrite.`
|
|
1361
|
+
};
|
|
1362
|
+
const archiveRoot = join(this.root, ".archive");
|
|
1363
|
+
let entries;
|
|
1364
|
+
try {
|
|
1365
|
+
entries = await this.io.list(archiveRoot);
|
|
1366
|
+
} catch {
|
|
1367
|
+
return {
|
|
1368
|
+
ok: false,
|
|
1369
|
+
message: "No skill archive available."
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
const chosen = entries.filter((entry) => entry === name || entry.startsWith(`${name}-`)).sort().reverse()[0];
|
|
1373
|
+
if (!chosen) return {
|
|
1374
|
+
ok: false,
|
|
1375
|
+
message: `Skill "${name}" is not in .archive.`
|
|
1376
|
+
};
|
|
1377
|
+
const source = join(archiveRoot, chosen);
|
|
1378
|
+
const dest = skillDir(this.root, name);
|
|
1379
|
+
try {
|
|
1380
|
+
await this.io.rename(source, dest);
|
|
1381
|
+
} catch {
|
|
1382
|
+
await this.io.copy(source, dest);
|
|
1383
|
+
await this.io.remove(source);
|
|
1384
|
+
}
|
|
1385
|
+
if (await this.io.exists(join(dest, ".archive-reason"))) await this.io.remove(join(dest, ".archive-reason"));
|
|
1386
|
+
return {
|
|
1387
|
+
ok: true,
|
|
1388
|
+
message: `Skill "${name}" restored from .archive.`,
|
|
1389
|
+
path: dest
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1281
1392
|
async writeSupportFile(name, filePath, content) {
|
|
1282
1393
|
const dir = skillDir(this.root, name);
|
|
1283
1394
|
if (!await this.io.exists(join(dir, "SKILL.md"))) return {
|
|
@@ -58,6 +58,18 @@ export declare class SkillLibrary {
|
|
|
58
58
|
update(name: string, content: string): Promise<SkillActionResult>;
|
|
59
59
|
patch(name: string, oldString: string, newString: string, filePath?: string, replaceAll?: boolean): Promise<SkillActionResult>;
|
|
60
60
|
archive(name: string, absorbedInto?: string): Promise<SkillActionResult>;
|
|
61
|
+
/**
|
|
62
|
+
* Merge the bodies of `sources` into `target` and archive the sources with
|
|
63
|
+
* an absorbed-into marker. Hermes-style consolidation: overlapping skills
|
|
64
|
+
* collapse into one, and the originals stay recoverable under `.archive/`.
|
|
65
|
+
*/
|
|
66
|
+
consolidate(target: string, sources: string[]): Promise<SkillActionResult>;
|
|
67
|
+
/**
|
|
68
|
+
* Restore one skill from `.archive/` back to the active root. Hermes-style
|
|
69
|
+
* recoverability: archival never deletes, and this is the control-plane
|
|
70
|
+
* path back. The `.archive-reason` marker is dropped on restore.
|
|
71
|
+
*/
|
|
72
|
+
restoreFromArchive(name: string): Promise<SkillActionResult>;
|
|
61
73
|
writeSupportFile(name: string, filePath: string, content: string): Promise<SkillActionResult>;
|
|
62
74
|
removeSupportFile(name: string, filePath: string): Promise<SkillActionResult>;
|
|
63
75
|
snapshotAll(reason?: string): Promise<string>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-core",
|
|
3
3
|
"description": "Shared stores, prompts, signals and lifecycle logic for the dsh-evolution plugin family (community build)",
|
|
4
|
-
"version": "0.1.0-rc.
|
|
4
|
+
"version": "0.1.0-rc.6",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|