@lmzhen/dsh-evolution-core 0.1.0-rc.34 → 0.1.0-rc.36
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 +51 -7
- package/lib/types/skill-store.d.ts +4 -2
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -32,6 +32,39 @@ function nodeEvolutionIo() {
|
|
|
32
32
|
const code = error?.code;
|
|
33
33
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
34
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* Cross-process write lock (claw `withFileLock` parity): an O_EXCL lock file
|
|
37
|
+
* guards the atomic write; a >5s-old lock is treated as stale and taken
|
|
38
|
+
* over. After the retry budget the write proceeds unlocked — the lock is a
|
|
39
|
+
* best-effort accommodation for multi-process deployments, never a read of
|
|
40
|
+
* availability.
|
|
41
|
+
*/
|
|
42
|
+
const withWriteLock = async (path, task) => {
|
|
43
|
+
const lock = `${path}.lock`;
|
|
44
|
+
for (let attempt = 0; attempt < 10; attempt += 1) try {
|
|
45
|
+
await writeFile(lock, String(process.pid), { flag: "wx" });
|
|
46
|
+
try {
|
|
47
|
+
return await task();
|
|
48
|
+
} finally {
|
|
49
|
+
await rm(lock, { force: true }).catch(() => {});
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error?.code !== "EEXIST") throw error;
|
|
53
|
+
try {
|
|
54
|
+
const st = await stat(lock);
|
|
55
|
+
if (Date.now() - st.mtimeMs > 5e3) {
|
|
56
|
+
try {
|
|
57
|
+
await rm(lock, { force: true });
|
|
58
|
+
} catch {}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
65
|
+
}
|
|
66
|
+
return await task();
|
|
67
|
+
};
|
|
35
68
|
return {
|
|
36
69
|
async readText(path) {
|
|
37
70
|
try {
|
|
@@ -43,9 +76,11 @@ function nodeEvolutionIo() {
|
|
|
43
76
|
},
|
|
44
77
|
async writeText(path, content) {
|
|
45
78
|
await mkdir(dirname(path), { recursive: true });
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
79
|
+
await withWriteLock(path, async () => {
|
|
80
|
+
const tmp = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
81
|
+
await writeFile(tmp, content, "utf8");
|
|
82
|
+
await rename(tmp, path);
|
|
83
|
+
});
|
|
49
84
|
},
|
|
50
85
|
async remove(path) {
|
|
51
86
|
await rm(path, {
|
|
@@ -1837,7 +1872,7 @@ var SkillLibrary = class {
|
|
|
1837
1872
|
path: dir
|
|
1838
1873
|
};
|
|
1839
1874
|
}
|
|
1840
|
-
async create(name, content, origin) {
|
|
1875
|
+
async create(name, content, origin = "foreground") {
|
|
1841
1876
|
const normalized = name.trim();
|
|
1842
1877
|
if (!SKILL_NAME_RE.test(normalized) || normalized.length > this.limits.maxNameLength) return {
|
|
1843
1878
|
ok: false,
|
|
@@ -1859,7 +1894,7 @@ var SkillLibrary = class {
|
|
|
1859
1894
|
message: `Skill "${normalized}" already exists.`
|
|
1860
1895
|
};
|
|
1861
1896
|
await this.io.writeText(join(dir, "SKILL.md"), content.trimEnd() + "\n");
|
|
1862
|
-
if (origin
|
|
1897
|
+
if (origin !== "foreground") await this.io.writeText(markerPath(dir, "hermes-managed"), "");
|
|
1863
1898
|
await this.audit(normalized, "create", null, content, "created");
|
|
1864
1899
|
return {
|
|
1865
1900
|
ok: true,
|
|
@@ -2212,8 +2247,9 @@ var SkillLibrary = class {
|
|
|
2212
2247
|
for (const name of names) await this.io.copy(skillDir(this.root, name), join(dest, name));
|
|
2213
2248
|
const sidecars = [];
|
|
2214
2249
|
for (const sidecar of [usageFile(this.root), suppressedFile(this.root)]) if (await this.io.exists(sidecar)) {
|
|
2215
|
-
|
|
2216
|
-
|
|
2250
|
+
const name = basename(sidecar);
|
|
2251
|
+
await this.io.copy(sidecar, join(dest, name));
|
|
2252
|
+
sidecars.push(name);
|
|
2217
2253
|
}
|
|
2218
2254
|
await this.io.writeText(join(dest, "manifest.json"), JSON.stringify({
|
|
2219
2255
|
reason,
|
|
@@ -2221,8 +2257,16 @@ var SkillLibrary = class {
|
|
|
2221
2257
|
skills: names,
|
|
2222
2258
|
sidecars
|
|
2223
2259
|
}, null, 2));
|
|
2260
|
+
await this.retainSnapshots(5);
|
|
2224
2261
|
return dest;
|
|
2225
2262
|
}
|
|
2263
|
+
/** Keep only the newest N snapshots (Hermes keep=5 parity); oldest folded into .backups history. */
|
|
2264
|
+
async retainSnapshots(keep) {
|
|
2265
|
+
const snapshots = await this.listSnapshots();
|
|
2266
|
+
for (const snapshot of snapshots.slice(keep)) try {
|
|
2267
|
+
await this.io.remove(snapshot.path);
|
|
2268
|
+
} catch {}
|
|
2269
|
+
}
|
|
2226
2270
|
async listSnapshots() {
|
|
2227
2271
|
const backupRoot = join(this.root, ".backups");
|
|
2228
2272
|
let entries;
|
|
@@ -29,7 +29,7 @@ export interface SkillActionResult {
|
|
|
29
29
|
path?: string;
|
|
30
30
|
}
|
|
31
31
|
/** Who is writing: a foreground user-directed tool call, or the autonomous review/curator pipeline. */
|
|
32
|
-
export type WriteOrigin = 'foreground' | 'background_review';
|
|
32
|
+
export type WriteOrigin = 'foreground' | 'subagent' | 'background_review';
|
|
33
33
|
/** Options for `SkillLibrary.archive`. The absorbed-into name and the archival reason are distinct fields. */
|
|
34
34
|
export interface ArchiveOptions {
|
|
35
35
|
/** Umbrella skill this one was consolidated into; when set it must exist (consolidate semantics). */
|
|
@@ -81,7 +81,7 @@ export declare class SkillLibrary {
|
|
|
81
81
|
* marker write is the only state change; content is untouched.
|
|
82
82
|
*/
|
|
83
83
|
setPinned(name: string, pinned: boolean, origin?: WriteOrigin): Promise<SkillActionResult>;
|
|
84
|
-
create(name: string, content: string, origin
|
|
84
|
+
create(name: string, content: string, origin?: WriteOrigin): Promise<SkillActionResult>;
|
|
85
85
|
update(name: string, content: string, origin?: WriteOrigin): Promise<SkillActionResult>;
|
|
86
86
|
patch(name: string, oldString: string, newString: string, filePath?: string, replaceAll?: boolean, origin?: WriteOrigin): Promise<SkillActionResult>;
|
|
87
87
|
archive(name: string, options?: ArchiveOptions): Promise<SkillActionResult>;
|
|
@@ -100,6 +100,8 @@ export declare class SkillLibrary {
|
|
|
100
100
|
writeSupportFile(name: string, filePath: string, content: string, origin?: WriteOrigin): Promise<SkillActionResult>;
|
|
101
101
|
removeSupportFile(name: string, filePath: string, origin?: WriteOrigin): Promise<SkillActionResult>;
|
|
102
102
|
snapshotAll(reason?: string): Promise<string>;
|
|
103
|
+
/** Keep only the newest N snapshots (Hermes keep=5 parity); oldest folded into .backups history. */
|
|
104
|
+
private retainSnapshots;
|
|
103
105
|
listSnapshots(): Promise<Array<{
|
|
104
106
|
path: string;
|
|
105
107
|
createdAt: 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.36",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|