@lmzhen/dsh-evolution-core 0.1.0-rc.49 → 0.1.0-rc.50
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 +135 -18
- package/lib/types/io.d.ts +21 -0
- package/lib/types/usage.d.ts +15 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { basename, dirname, join } from "node:path";
|
|
2
|
-
import { cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { cp, lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { createHash, randomBytes } from "node:crypto";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
@@ -11,6 +11,20 @@ import { readFileSync } from "node:fs";
|
|
|
11
11
|
* Native DSH packages pass `ctx.evolutionIo.provider()`; standalone consumers
|
|
12
12
|
* (and the facade's own tests) can use `nodeEvolutionIo`.
|
|
13
13
|
*/
|
|
14
|
+
/**
|
|
15
|
+
* Run `task` inside `io.transact` when the backend provides it; otherwise fall
|
|
16
|
+
* back to a plain read → task → write/remove sequence (no cross-process lock —
|
|
17
|
+
* callers keep their single-process serialize chain as the second layer).
|
|
18
|
+
*/
|
|
19
|
+
async function transactIo(io, path, task) {
|
|
20
|
+
if (io.transact) {
|
|
21
|
+
await io.transact(path, task);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const next = await task(await io.readText(path));
|
|
25
|
+
if (next === null) await io.remove(path);
|
|
26
|
+
else await io.writeText(path, next);
|
|
27
|
+
}
|
|
14
28
|
/** Lazy adapter over an IO provider registry, shared by every evolution consumer. */
|
|
15
29
|
function evolutionIoAdapter(provider) {
|
|
16
30
|
return {
|
|
@@ -24,6 +38,14 @@ function evolutionIoAdapter(provider) {
|
|
|
24
38
|
size: (path) => {
|
|
25
39
|
const io = provider();
|
|
26
40
|
return io.size ? io.size(path) : Promise.resolve(null);
|
|
41
|
+
},
|
|
42
|
+
transact: (path, task) => {
|
|
43
|
+
const io = provider();
|
|
44
|
+
return io.transact ? io.transact(path, task) : transactIo(io, path, task);
|
|
45
|
+
},
|
|
46
|
+
isSymlink: (path) => {
|
|
47
|
+
const io = provider();
|
|
48
|
+
return io.isSymlink ? io.isSymlink(path) : Promise.resolve(null);
|
|
27
49
|
}
|
|
28
50
|
};
|
|
29
51
|
}
|
|
@@ -82,6 +104,26 @@ function nodeEvolutionIo() {
|
|
|
82
104
|
await rename(tmp, path);
|
|
83
105
|
});
|
|
84
106
|
},
|
|
107
|
+
async transact(path, task) {
|
|
108
|
+
await mkdir(dirname(path), { recursive: true });
|
|
109
|
+
await withWriteLock(path, async () => {
|
|
110
|
+
let current;
|
|
111
|
+
try {
|
|
112
|
+
current = await readFile(path, "utf8");
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (isMissing(error)) current = null;
|
|
115
|
+
else throw error;
|
|
116
|
+
}
|
|
117
|
+
const next = await task(current);
|
|
118
|
+
if (next === null) {
|
|
119
|
+
await rm(path, { force: true });
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const tmp = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
123
|
+
await writeFile(tmp, next, "utf8");
|
|
124
|
+
await rename(tmp, path);
|
|
125
|
+
});
|
|
126
|
+
},
|
|
85
127
|
async remove(path) {
|
|
86
128
|
await rm(path, {
|
|
87
129
|
recursive: true,
|
|
@@ -91,8 +133,9 @@ function nodeEvolutionIo() {
|
|
|
91
133
|
async list(path) {
|
|
92
134
|
try {
|
|
93
135
|
return await readdir(path);
|
|
94
|
-
} catch {
|
|
95
|
-
return [];
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (isMissing(error)) return [];
|
|
138
|
+
throw error;
|
|
96
139
|
}
|
|
97
140
|
},
|
|
98
141
|
async exists(path) {
|
|
@@ -122,6 +165,13 @@ function nodeEvolutionIo() {
|
|
|
122
165
|
if (isMissing(error)) return null;
|
|
123
166
|
throw error;
|
|
124
167
|
}
|
|
168
|
+
},
|
|
169
|
+
async isSymlink(path) {
|
|
170
|
+
try {
|
|
171
|
+
return (await lstat(path)).isSymbolicLink();
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
125
175
|
}
|
|
126
176
|
};
|
|
127
177
|
}
|
|
@@ -181,15 +231,33 @@ function normalizeUsageRecord(record) {
|
|
|
181
231
|
quality_warn: typeof raw.quality_warn === "boolean" ? raw.quality_warn : void 0
|
|
182
232
|
};
|
|
183
233
|
}
|
|
184
|
-
|
|
234
|
+
/** Parse a raw usage sidecar; malformed content reads as empty (best-effort telemetry). */
|
|
235
|
+
function parseUsage(raw) {
|
|
185
236
|
const map = /* @__PURE__ */ new Map();
|
|
186
|
-
|
|
187
|
-
|
|
237
|
+
if (raw === null) return map;
|
|
238
|
+
try {
|
|
188
239
|
const parsed = JSON.parse(raw);
|
|
189
240
|
for (const [name, record] of Object.entries(parsed)) map.set(name, normalizeUsageRecord(record));
|
|
190
241
|
} catch {}
|
|
191
242
|
return map;
|
|
192
243
|
}
|
|
244
|
+
async function loadUsage(root, io = nodeEvolutionIo()) {
|
|
245
|
+
return parseUsage(await io.readText(usageFile(root)));
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Atomic read-modify-write on the usage sidecar (rc.50 P2-2): `task` receives
|
|
249
|
+
* the map parsed from the current on-disk state and may mutate it; the result
|
|
250
|
+
* is persisted inside the same transact so a second process sharing DSH_HOME
|
|
251
|
+
* cannot interleave its RMW and lose a counter update. Callers keep their own
|
|
252
|
+
* single-process serialize chain as the second layer.
|
|
253
|
+
*/
|
|
254
|
+
async function mutateUsage(root, io, task) {
|
|
255
|
+
await transactIo(io, usageFile(root), async (current) => {
|
|
256
|
+
const map = parseUsage(current);
|
|
257
|
+
await task(map);
|
|
258
|
+
return JSON.stringify(Object.fromEntries(map.entries()), null, 2);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
193
261
|
async function saveUsage(root, map, io = nodeEvolutionIo()) {
|
|
194
262
|
const obj = Object.fromEntries(map.entries());
|
|
195
263
|
await io.writeText(usageFile(root), JSON.stringify(obj, null, 2));
|
|
@@ -240,7 +308,9 @@ function suppressedFile(root) {
|
|
|
240
308
|
return join(root, ".curator-suppressed.json");
|
|
241
309
|
}
|
|
242
310
|
async function loadSuppressedNames(root, io = nodeEvolutionIo()) {
|
|
243
|
-
|
|
311
|
+
return parseSuppressed(await io.readText(suppressedFile(root)));
|
|
312
|
+
}
|
|
313
|
+
function parseSuppressed(raw) {
|
|
244
314
|
if (raw === null) return /* @__PURE__ */ new Set();
|
|
245
315
|
try {
|
|
246
316
|
const parsed = JSON.parse(raw);
|
|
@@ -256,6 +326,22 @@ async function saveSuppressedNames(root, names, io = nodeEvolutionIo()) {
|
|
|
256
326
|
names: [...names].sort()
|
|
257
327
|
}, null, 2));
|
|
258
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Atomic read-modify-write on the suppression sidecar (rc.50 P2-2): `task`
|
|
331
|
+
* receives the set parsed from the current on-disk state and may mutate it;
|
|
332
|
+
* the result is persisted inside the same transact so a second process
|
|
333
|
+
* sharing DSH_HOME cannot interleave its RMW. Best-effort posture unchanged.
|
|
334
|
+
*/
|
|
335
|
+
async function updateSuppressedNames(root, io, task) {
|
|
336
|
+
await transactIo(io, suppressedFile(root), async (current) => {
|
|
337
|
+
const names = parseSuppressed(current);
|
|
338
|
+
await task(names);
|
|
339
|
+
return JSON.stringify({
|
|
340
|
+
version: 1,
|
|
341
|
+
names: [...names].sort()
|
|
342
|
+
}, null, 2);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
259
345
|
//#endregion
|
|
260
346
|
//#region lib/types/constants.js
|
|
261
347
|
/**
|
|
@@ -1487,8 +1573,13 @@ function mutationsFile(root) {
|
|
|
1487
1573
|
function contentHash(content) {
|
|
1488
1574
|
return createHash("sha256").update(content).digest("hex");
|
|
1489
1575
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1576
|
+
/**
|
|
1577
|
+
* Parse a raw mutations sidecar; malformed content reads as empty (auditing is
|
|
1578
|
+
* best-effort). Versioned shape ({ version, records }) with legacy
|
|
1579
|
+
* plain-array compat, plus a field-level guard for records without the
|
|
1580
|
+
* required identity/timestamp fields (rc.42 audit P2-3).
|
|
1581
|
+
*/
|
|
1582
|
+
function parseMutationRecords(raw) {
|
|
1492
1583
|
if (raw === null) return [];
|
|
1493
1584
|
try {
|
|
1494
1585
|
const parsed = JSON.parse(raw);
|
|
@@ -1497,15 +1588,20 @@ async function loadMutations(root, io = nodeEvolutionIo()) {
|
|
|
1497
1588
|
return [];
|
|
1498
1589
|
}
|
|
1499
1590
|
}
|
|
1591
|
+
async function loadMutations(root, io = nodeEvolutionIo()) {
|
|
1592
|
+
return parseMutationRecords(await io.readText(mutationsFile(root)));
|
|
1593
|
+
}
|
|
1500
1594
|
/** Append one record, trim to `cap`, and write atomically (versioned shape). */
|
|
1501
1595
|
async function recordMutation(root, io, record, cap = 500) {
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1596
|
+
await transactIo(io, mutationsFile(root), (current) => {
|
|
1597
|
+
const existing = parseMutationRecords(current);
|
|
1598
|
+
existing.push(record);
|
|
1599
|
+
const trimmed = existing.length > cap ? existing.slice(existing.length - cap) : existing;
|
|
1600
|
+
return Promise.resolve(JSON.stringify({
|
|
1601
|
+
version: 1,
|
|
1602
|
+
records: trimmed
|
|
1603
|
+
}, null, 2));
|
|
1604
|
+
});
|
|
1509
1605
|
}
|
|
1510
1606
|
//#endregion
|
|
1511
1607
|
//#region lib/types/quality.js
|
|
@@ -2286,6 +2382,12 @@ var SkillLibrary = class {
|
|
|
2286
2382
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(0, 14);
|
|
2287
2383
|
dest = join(archiveRoot, `${name.trim()}-${stamp}`);
|
|
2288
2384
|
}
|
|
2385
|
+
if (this.io.isSymlink) {
|
|
2386
|
+
if (await this.io.isSymlink(dir) === true) return {
|
|
2387
|
+
ok: false,
|
|
2388
|
+
message: `Skill "${name}" is a symlink; refusing to archive it.`
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2289
2391
|
try {
|
|
2290
2392
|
await this.io.rename(dir, dest);
|
|
2291
2393
|
} catch {
|
|
@@ -2422,6 +2524,12 @@ var SkillLibrary = class {
|
|
|
2422
2524
|
};
|
|
2423
2525
|
const source = join(archiveRoot, chosen);
|
|
2424
2526
|
const dest = this.dirOf(name);
|
|
2527
|
+
if (this.io.isSymlink) {
|
|
2528
|
+
if (await this.io.isSymlink(source) === true) return {
|
|
2529
|
+
ok: false,
|
|
2530
|
+
message: `Archived entry "${chosen}" is a symlink; refusing to restore it.`
|
|
2531
|
+
};
|
|
2532
|
+
}
|
|
2425
2533
|
try {
|
|
2426
2534
|
await this.io.rename(source, dest);
|
|
2427
2535
|
} catch {
|
|
@@ -2648,7 +2756,16 @@ var SkillLibrary = class {
|
|
|
2648
2756
|
message: "No skill snapshot available."
|
|
2649
2757
|
};
|
|
2650
2758
|
await this.snapshotAll("pre-rollback", extras);
|
|
2651
|
-
|
|
2759
|
+
let rootEntries;
|
|
2760
|
+
try {
|
|
2761
|
+
rootEntries = await this.io.list(this.root);
|
|
2762
|
+
} catch {
|
|
2763
|
+
rootEntries = [];
|
|
2764
|
+
}
|
|
2765
|
+
for (const entry of rootEntries) {
|
|
2766
|
+
if (entry.startsWith(".")) continue;
|
|
2767
|
+
await this.io.remove(join(this.root, entry));
|
|
2768
|
+
}
|
|
2652
2769
|
const manifest = await this.readSnapshotManifest(latest.path);
|
|
2653
2770
|
if (manifest === null) for (const entry of await this.io.list(latest.path)) {
|
|
2654
2771
|
if (entry === "manifest.json" || entry === "extras") continue;
|
|
@@ -2742,4 +2859,4 @@ var JsonState = class JsonState {
|
|
|
2742
2859
|
}
|
|
2743
2860
|
};
|
|
2744
2861
|
//#endregion
|
|
2745
|
-
export { COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EvolutionGateSet, JsonState, LOW_QUALITY_THRESHOLD, MAX_DESCRIPTION_LENGTH, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MUTATIONS_FILE_VERSION, MemoryStore, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, SKILL_NAME_RE, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, computeDedupGroups, computeLifecycleTransitions, computeQualityScores, computeScopeView, contentHash, createGateSet, emptyRecord, evaluateThreat, evolutionHome, evolutionIoAdapter, foldTurn, getRecord, latestActivityAt, lifecycleCandidate, loadMutations, loadSuppressedNames, loadUsage, markAgentCreated, memoryRoot, mutationsFile, nodeEvolutionIo, normalizeUsageRecord, observeEvent, parseCuratorNominations, parseFrontmatter, recordMutation, relatedSkillNames, renderCuratorReportMarkdown, resolveOrigins, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, usageFile, validateFrontmatter, verifyPromptBundle };
|
|
2862
|
+
export { COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EvolutionGateSet, JsonState, LOW_QUALITY_THRESHOLD, MAX_DESCRIPTION_LENGTH, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MUTATIONS_FILE_VERSION, MemoryStore, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, SKILL_NAME_RE, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, computeDedupGroups, computeLifecycleTransitions, computeQualityScores, computeScopeView, contentHash, createGateSet, emptyRecord, evaluateThreat, evolutionHome, evolutionIoAdapter, foldTurn, getRecord, latestActivityAt, lifecycleCandidate, loadMutations, loadSuppressedNames, loadUsage, markAgentCreated, memoryRoot, mutateUsage, mutationsFile, nodeEvolutionIo, normalizeUsageRecord, observeEvent, parseCuratorNominations, parseFrontmatter, recordMutation, relatedSkillNames, renderCuratorReportMarkdown, resolveOrigins, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, validateFrontmatter, verifyPromptBundle };
|
package/lib/types/io.d.ts
CHANGED
|
@@ -20,7 +20,28 @@ export interface EvolutionIoLike {
|
|
|
20
20
|
* consumers treat an unknown size as "guard not applicable".
|
|
21
21
|
*/
|
|
22
22
|
size?(path: string): Promise<number | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Optional atomic read-modify-write on one file: the read and the write run
|
|
25
|
+
* inside a single cross-process lock, so two processes that share DSH_HOME
|
|
26
|
+
* cannot interleave their RMW sequences. `task` receives the current content
|
|
27
|
+
* (`null` when missing) and returns the next content; returning `null`
|
|
28
|
+
* deletes the file. A backend without it falls back to plain read+write and
|
|
29
|
+
* the caller keeps its single-process chain as the second layer.
|
|
30
|
+
*/
|
|
31
|
+
transact?(this: void, path: string, task: (current: string | null) => Promise<string | null>): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Optional symlink probe (G7). `true` = the path is a symlink, `false` = a
|
|
34
|
+
* real entry, `null` = guard not applicable (backend without the probe or
|
|
35
|
+
* the path does not exist). Consumers treat `null` as "let it through".
|
|
36
|
+
*/
|
|
37
|
+
isSymlink?(this: void, path: string): Promise<boolean | null>;
|
|
23
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Run `task` inside `io.transact` when the backend provides it; otherwise fall
|
|
41
|
+
* back to a plain read → task → write/remove sequence (no cross-process lock —
|
|
42
|
+
* callers keep their single-process serialize chain as the second layer).
|
|
43
|
+
*/
|
|
44
|
+
export declare function transactIo(io: EvolutionIoLike, path: string, task: (current: string | null) => Promise<string | null>): Promise<void>;
|
|
24
45
|
/** Lazy adapter over an IO provider registry, shared by every evolution consumer. */
|
|
25
46
|
export declare function evolutionIoAdapter(provider: () => EvolutionIoLike): EvolutionIoLike;
|
|
26
47
|
export declare function nodeEvolutionIo(): EvolutionIoLike;
|
package/lib/types/usage.d.ts
CHANGED
|
@@ -33,6 +33,14 @@ export declare function emptyRecord(): UsageRecord;
|
|
|
33
33
|
*/
|
|
34
34
|
export declare function normalizeUsageRecord(record: unknown): UsageRecord;
|
|
35
35
|
export declare function loadUsage(root: string, io?: EvolutionIoLike): Promise<UsageMap>;
|
|
36
|
+
/**
|
|
37
|
+
* Atomic read-modify-write on the usage sidecar (rc.50 P2-2): `task` receives
|
|
38
|
+
* the map parsed from the current on-disk state and may mutate it; the result
|
|
39
|
+
* is persisted inside the same transact so a second process sharing DSH_HOME
|
|
40
|
+
* cannot interleave its RMW and lose a counter update. Callers keep their own
|
|
41
|
+
* single-process serialize chain as the second layer.
|
|
42
|
+
*/
|
|
43
|
+
export declare function mutateUsage(root: string, io: EvolutionIoLike, task: (map: UsageMap) => void | Promise<void>): Promise<void>;
|
|
36
44
|
export declare function saveUsage(root: string, map: UsageMap, io?: EvolutionIoLike): Promise<void>;
|
|
37
45
|
export declare function getRecord(map: UsageMap, name: string): UsageRecord;
|
|
38
46
|
export declare function bumpView(map: UsageMap, name: string, when?: Date): void;
|
|
@@ -50,4 +58,11 @@ export declare const SUPPRESSED_FILE_VERSION = 1;
|
|
|
50
58
|
export declare function suppressedFile(root: string): string;
|
|
51
59
|
export declare function loadSuppressedNames(root: string, io?: EvolutionIoLike): Promise<ReadonlySet<string>>;
|
|
52
60
|
export declare function saveSuppressedNames(root: string, names: ReadonlySet<string>, io?: EvolutionIoLike): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Atomic read-modify-write on the suppression sidecar (rc.50 P2-2): `task`
|
|
63
|
+
* receives the set parsed from the current on-disk state and may mutate it;
|
|
64
|
+
* the result is persisted inside the same transact so a second process
|
|
65
|
+
* sharing DSH_HOME cannot interleave its RMW. Best-effort posture unchanged.
|
|
66
|
+
*/
|
|
67
|
+
export declare function updateSuppressedNames(root: string, io: EvolutionIoLike, task: (names: Set<string>) => void | Promise<void>): Promise<void>;
|
|
53
68
|
//# sourceMappingURL=usage.d.ts.map
|
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.50",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|