@lmzhen/dsh-evolution-core 0.3.77 → 0.3.78
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 +254 -58
- package/lib/types/constants.d.ts +1 -1
- package/lib/types/index.d.ts +2 -0
- package/lib/types/instance-scope.d.ts +35 -0
- package/lib/types/preset-composition.d.ts +38 -0
- package/lib/types/probe.d.ts +23 -0
- package/lib/types/skill-store.d.ts +11 -6
- package/lib/types/write-inventory.d.ts +55 -0
- package/package.json +5 -3
- package/row-overrides.json +14 -0
package/lib/index.js
CHANGED
|
@@ -2,6 +2,8 @@ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
|
2
2
|
import { cp, lstat, mkdir, open, 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
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
5
7
|
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
6
8
|
import { load } from "js-yaml";
|
|
7
9
|
//#region lib/types/io.js
|
|
@@ -1027,7 +1029,7 @@ const SKILL_ACTION_REQUIRED_FIELDS = {
|
|
|
1027
1029
|
};
|
|
1028
1030
|
/** Skill frontmatter `name` validated for the file name (lowercase + hyphen).
|
|
1029
1031
|
* 计划 B-4 (v18): tightened to the UPSTREAM `SKILL_NAME` shape
|
|
1030
|
-
* (`/^[a-z0-9]+(?:-[a-z0-9]+)*$/`, packages/skill/skill/src/index.ts:
|
|
1032
|
+
* (`/^[a-z0-9]+(?:-[a-z0-9]+)*$/`, packages/skill/skill/src/index.ts:21). The
|
|
1031
1033
|
* old form admitted trailing/consecutive hyphens, which upstream
|
|
1032
1034
|
* `validateCandidate` throws on — and that throw aborts the WHOLE `ctx.skills`
|
|
1033
1035
|
* collection. The catalog provider still filters such legacy tree entries so
|
|
@@ -3042,21 +3044,43 @@ function composePresetComposition(standardComposition, deltaComposition) {
|
|
|
3042
3044
|
if (collisions.length > 0) console.warn(`evolution preset composition: warning — delta rows collide with standard rows (${collisions.join(", ")}); keeping both (DSH_EVOLUTION_ALLOW_ROW_COLLISIONS=1)`);
|
|
3043
3045
|
return applyRowOverrides(`${standardComposition.replace(/\s+$/, "")}\n\n${deltaComposition.trim()}\n`);
|
|
3044
3046
|
}
|
|
3045
|
-
/**
|
|
3046
|
-
*
|
|
3047
|
-
*
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3047
|
+
/**
|
|
3048
|
+
* One table, two generation paths: `row-overrides.json` at the PACKAGE ROOT
|
|
3049
|
+
* states what a generated preset must carry beyond the platform composition,
|
|
3050
|
+
* and the source installer (`scripts/install-layered.mjs`) reads the same file.
|
|
3051
|
+
* The path resolves from both `src/` and the built `lib/`, which is why the
|
|
3052
|
+
* file sits at the root and ships in `files`.
|
|
3053
|
+
*
|
|
3054
|
+
* Before 0.3.77 each side carried its own hand-kept copy of this table; they
|
|
3055
|
+
* happened to stay byte-identical, which is exactly the kind of agreement no
|
|
3056
|
+
* test can keep — the entries are now data, and a divergence is impossible.
|
|
3057
|
+
*/
|
|
3058
|
+
const ROW_OVERRIDES_URL = new URL("../row-overrides.json", import.meta.url);
|
|
3059
|
+
let cachedOverrides;
|
|
3060
|
+
/**
|
|
3061
|
+
* Read and validate the shared override table.
|
|
3062
|
+
* @returns the entries, in file order. A malformed table fails LOUD: a composer
|
|
3063
|
+
* that silently skipped an entry would ship a preset running the platform
|
|
3064
|
+
* default while nothing downstream reported it.
|
|
3065
|
+
*/
|
|
3066
|
+
function loadRowOverrides() {
|
|
3067
|
+
if (cachedOverrides !== void 0) return cachedOverrides;
|
|
3068
|
+
const parsed = JSON.parse(readFileSync(fileURLToPath(ROW_OVERRIDES_URL), "utf8"));
|
|
3069
|
+
if (!Array.isArray(parsed)) throw new Error("evolution-core: row-overrides.json must be an array of override entries");
|
|
3070
|
+
const entries = [];
|
|
3071
|
+
for (const [index, entry] of parsed.entries()) {
|
|
3072
|
+
const candidate = entry;
|
|
3073
|
+
if (candidate === null || typeof candidate !== "object" || typeof candidate.row !== "string" || candidate.row === "" || typeof candidate.key !== "string" || candidate.key === "" || !Array.isArray(candidate.lines) || candidate.lines.some((line) => typeof line !== "string") || typeof candidate.missingReason !== "string") throw new Error(`evolution-core: row-overrides.json entry ${index} is malformed (row/key/lines/missingReason are required)`);
|
|
3074
|
+
entries.push({
|
|
3075
|
+
row: candidate.row,
|
|
3076
|
+
key: candidate.key,
|
|
3077
|
+
lines: candidate.lines,
|
|
3078
|
+
missingReason: candidate.missingReason
|
|
3079
|
+
});
|
|
3080
|
+
}
|
|
3081
|
+
cachedOverrides = entries;
|
|
3082
|
+
return entries;
|
|
3083
|
+
}
|
|
3060
3084
|
/**
|
|
3061
3085
|
* Ensure every {@link RowOverride} inside its target row.
|
|
3062
3086
|
*
|
|
@@ -3066,7 +3090,7 @@ const ROW_OVERRIDES = [{
|
|
|
3066
3090
|
* override REPLACES `config` wholesale — the difference is why the composer can
|
|
3067
3091
|
* add a key without erasing the platform's own config defaults.
|
|
3068
3092
|
*/
|
|
3069
|
-
function applyRowOverrides(composition, overrides =
|
|
3093
|
+
function applyRowOverrides(composition, overrides = loadRowOverrides()) {
|
|
3070
3094
|
let lines = composition.split("\n");
|
|
3071
3095
|
for (const override of overrides) lines = applyOneOverride(lines, override);
|
|
3072
3096
|
return lines.join("\n");
|
|
@@ -3090,7 +3114,7 @@ function applyOneOverride(lines, override) {
|
|
|
3090
3114
|
lines.splice(end + 1, 0, ...override.lines);
|
|
3091
3115
|
i = end + override.lines.length;
|
|
3092
3116
|
}
|
|
3093
|
-
if (!found) console.warn(override.
|
|
3117
|
+
if (!found) console.warn("evolution preset composition: warning — " + override.missingReason);
|
|
3094
3118
|
return lines;
|
|
3095
3119
|
}
|
|
3096
3120
|
function compositionRowIds(composition) {
|
|
@@ -3807,6 +3831,177 @@ function valueOr(probe, fallback) {
|
|
|
3807
3831
|
function mapProbe(probe, transform) {
|
|
3808
3832
|
return probe.kind === "present" ? probePresent(transform(probe.value)) : probe;
|
|
3809
3833
|
}
|
|
3834
|
+
/** The reason an `unknown` probe carries (message, never a bare String(object)). */
|
|
3835
|
+
function probeReason(error) {
|
|
3836
|
+
return error instanceof Error ? error.message : String(error);
|
|
3837
|
+
}
|
|
3838
|
+
/**
|
|
3839
|
+
* ENOENT/ENOTDIR are the ONE read failure that means "it is not there"; every
|
|
3840
|
+
* other failure is an IO error and stays unknown (same split as the node
|
|
3841
|
+
* backend's own isMissing, V8-23⑨ for the size probe).
|
|
3842
|
+
*/
|
|
3843
|
+
function isMissingPath(error) {
|
|
3844
|
+
const code = error?.code;
|
|
3845
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
3846
|
+
}
|
|
3847
|
+
/**
|
|
3848
|
+
* Three-state directory listing. The node backend already answers `[]` for a
|
|
3849
|
+
* MISSING directory (its own rc.50 P2-4 contract) and THROWS for an unreadable
|
|
3850
|
+
* one, so the value this adds is the second half: a backend that throws ENOENT
|
|
3851
|
+
* reads as absent, an EACCES/EIO reads as unknown — where a bare
|
|
3852
|
+
* `catch { return [] }` served a broken store as an empty one (the N14 class).
|
|
3853
|
+
*/
|
|
3854
|
+
async function probeList(io, dir) {
|
|
3855
|
+
try {
|
|
3856
|
+
return probePresent(await io.list(dir));
|
|
3857
|
+
} catch (error) {
|
|
3858
|
+
return isMissingPath(error) ? probeAbsent() : probeUnknown(probeReason(error));
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
/**
|
|
3862
|
+
* Three-state mtime read. Absent covers both "the path is missing" and "this
|
|
3863
|
+
* backend has no mtime probe" — the seam cannot tell those apart, so consumers
|
|
3864
|
+
* that must know say so in their own log line. A stat that FAILS is unknown.
|
|
3865
|
+
*/
|
|
3866
|
+
async function probeMtime(io, path) {
|
|
3867
|
+
try {
|
|
3868
|
+
const value = await io.mtime?.(path) ?? null;
|
|
3869
|
+
return value === null ? probeAbsent() : probePresent(value);
|
|
3870
|
+
} catch (error) {
|
|
3871
|
+
return probeUnknown(probeReason(error));
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
//#endregion
|
|
3875
|
+
//#region lib/types/instance-scope.js
|
|
3876
|
+
/**
|
|
3877
|
+
* B3 / G4 (0.3.78): the family's single-instance contract, made explicit.
|
|
3878
|
+
*
|
|
3879
|
+
* Every persisted sidecar is written under TWO assumptions: the IO backend's
|
|
3880
|
+
* cross-process write lock serializes different processes, and exactly ONE
|
|
3881
|
+
* instance of each writer service exists per evolution home. The second half
|
|
3882
|
+
* was implicit — `makeSerialQueue()` and the module-scope stores are
|
|
3883
|
+
* per-instance, so two rows writing one home interleave inside one file (the
|
|
3884
|
+
* "two instances, one file" class). This module makes that half enforceable: a
|
|
3885
|
+
* writer CLAIMS its key while mounted and releases it on dispose, and a second
|
|
3886
|
+
* claimant is told who holds the key instead of silently racing it.
|
|
3887
|
+
*
|
|
3888
|
+
* The claim is keyed by the HOME, not by the process: the contended resource is
|
|
3889
|
+
* the sidecar directory, so two instances resolving different homes (an
|
|
3890
|
+
* isolated test fixture, a second DSH_HOME) do not contend, while two rows on
|
|
3891
|
+
* one profile do.
|
|
3892
|
+
* @module @lmzhen/dsh-evolution-core/src/instance-scope
|
|
3893
|
+
*/
|
|
3894
|
+
/** home+key -> holder. Single-process by construction: a claim can only
|
|
3895
|
+
* serialize instances inside this process, which is the whole point (the
|
|
3896
|
+
* cross-process half is the write lock, see persisted-write-inventory.json). */
|
|
3897
|
+
const claims = /* @__PURE__ */ new Map();
|
|
3898
|
+
/** `<home> :: <key>` — the registry key, exported so diagnostics name the
|
|
3899
|
+
* same unit the claim does. */
|
|
3900
|
+
function instanceClaimKey(home, key) {
|
|
3901
|
+
return `${home} :: ${key}`;
|
|
3902
|
+
}
|
|
3903
|
+
/** Take the claim for `key` at `home`. Grants while it is free or already
|
|
3904
|
+
* held BY `owner` — remounting the same instance is not a second instance. */
|
|
3905
|
+
function claimInstance(home, key, owner) {
|
|
3906
|
+
const id = instanceClaimKey(home, key);
|
|
3907
|
+
const current = claims.get(id);
|
|
3908
|
+
if (current === void 0 || current === owner) {
|
|
3909
|
+
claims.set(id, owner);
|
|
3910
|
+
return {
|
|
3911
|
+
granted: true,
|
|
3912
|
+
key: id,
|
|
3913
|
+
holder: owner
|
|
3914
|
+
};
|
|
3915
|
+
}
|
|
3916
|
+
return {
|
|
3917
|
+
granted: false,
|
|
3918
|
+
key: id,
|
|
3919
|
+
holder: current
|
|
3920
|
+
};
|
|
3921
|
+
}
|
|
3922
|
+
/** Release only the claim `owner` took — never another instance's. */
|
|
3923
|
+
function releaseInstance(home, key, owner) {
|
|
3924
|
+
const id = instanceClaimKey(home, key);
|
|
3925
|
+
if (claims.get(id) === owner) claims.delete(id);
|
|
3926
|
+
}
|
|
3927
|
+
/** The current holder of `key` at `home`, or undefined. */
|
|
3928
|
+
function instanceHolder(home, key) {
|
|
3929
|
+
return claims.get(instanceClaimKey(home, key));
|
|
3930
|
+
}
|
|
3931
|
+
//#endregion
|
|
3932
|
+
//#region lib/types/write-inventory.js
|
|
3933
|
+
/**
|
|
3934
|
+
* B3 / G4 (0.3.78): the persisted-write inventory — WHICH file the family
|
|
3935
|
+
* writes, WHO writes it, and WHAT keeps two writers apart.
|
|
3936
|
+
*
|
|
3937
|
+
* The table lives in `persisted-write-inventory.json` at the package root so
|
|
3938
|
+
* the TypeScript side, the architecture gate (`verify-arch-guards` rule N20)
|
|
3939
|
+
* and the regression specs read ONE file (the row-overrides.json pattern).
|
|
3940
|
+
*
|
|
3941
|
+
* "What keeps two writers apart" has exactly three answers in this family:
|
|
3942
|
+
* - `transact` — the IO backend's cross-process write lock (transactIo);
|
|
3943
|
+
* - `write-lock` — the per-target `<path>.lock` protocol of the skill tree;
|
|
3944
|
+
* - `instance-claim` — the per-home single-instance claim (instance-scope.ts),
|
|
3945
|
+
* for a writer whose sweeps cannot be expressed as one locked file.
|
|
3946
|
+
* A site that answers with NONE of them is the "two instances, one file" class:
|
|
3947
|
+
* a per-instance serial queue looks like serialization and is not.
|
|
3948
|
+
* @module @lmzhen/dsh-evolution-core/src/write-inventory
|
|
3949
|
+
*/
|
|
3950
|
+
const SITES_URL = new URL("../persisted-write-inventory.json", import.meta.url);
|
|
3951
|
+
const SERIALIZATIONS = [
|
|
3952
|
+
"transact",
|
|
3953
|
+
"write-lock",
|
|
3954
|
+
"instance-claim"
|
|
3955
|
+
];
|
|
3956
|
+
/** Parse + validate the table. A malformed table throws at import: a table the
|
|
3957
|
+
* gate cannot read must never degrade into "no declared write sites". */
|
|
3958
|
+
function parseSites(raw) {
|
|
3959
|
+
if (!Array.isArray(raw)) throw new Error("evolution-core: persisted-write-inventory.json must be an array of sites");
|
|
3960
|
+
const sites = [];
|
|
3961
|
+
for (const [index, entry] of raw.entries()) {
|
|
3962
|
+
const site = entry;
|
|
3963
|
+
const problems = [];
|
|
3964
|
+
if (typeof site.id !== "string" || site.id === "") problems.push("id");
|
|
3965
|
+
if (typeof site.path !== "string" || site.path === "") problems.push("path");
|
|
3966
|
+
if (typeof site.writer !== "string" || site.writer === "") problems.push("writer");
|
|
3967
|
+
if (typeof site.marker !== "string" || site.marker === "") problems.push("marker");
|
|
3968
|
+
if (typeof site.serializedBy !== "string" || !SERIALIZATIONS.includes(site.serializedBy)) problems.push("serializedBy");
|
|
3969
|
+
if (site.serializedBy === "instance-claim" && typeof site.instance !== "string") problems.push("instance");
|
|
3970
|
+
if (!Array.isArray(site.state)) problems.push("state");
|
|
3971
|
+
if (problems.length > 0) throw new Error(`evolution-core: persisted-write-inventory.json entry ${index} is malformed (missing/invalid: ${problems.join(", ")})`);
|
|
3972
|
+
sites.push({
|
|
3973
|
+
id: site.id,
|
|
3974
|
+
path: site.path,
|
|
3975
|
+
writer: site.writer,
|
|
3976
|
+
serializedBy: site.serializedBy,
|
|
3977
|
+
marker: site.marker,
|
|
3978
|
+
...typeof site.instance === "string" ? { instance: site.instance } : {},
|
|
3979
|
+
state: site.state,
|
|
3980
|
+
note: typeof site.note === "string" ? site.note : ""
|
|
3981
|
+
});
|
|
3982
|
+
}
|
|
3983
|
+
if (new Set(sites.map((site) => site.id)).size !== sites.length) throw new Error("evolution-core: persisted-write-inventory.json declares duplicate site ids");
|
|
3984
|
+
return sites;
|
|
3985
|
+
}
|
|
3986
|
+
/** Instance keys held by writer services — the single source shared by the
|
|
3987
|
+
* claim site and the inventory row that declares it (rule N20 checks the row's
|
|
3988
|
+
* `instance` names one of these). */
|
|
3989
|
+
const INSTANCE_KEYS = {
|
|
3990
|
+
/** The per-home curator: report writing + the retention sweep. */
|
|
3991
|
+
curator: "evolution-curator" };
|
|
3992
|
+
/** The declared persisted write sites, in file order. */
|
|
3993
|
+
const PERSISTED_WRITE_SITES = parseSites(JSON.parse(readFileSync(fileURLToPath(SITES_URL), "utf8")));
|
|
3994
|
+
/** Sites serialized by the per-home instance claim, with their instance keys. */
|
|
3995
|
+
function instanceClaimedWriteSites() {
|
|
3996
|
+
return PERSISTED_WRITE_SITES.filter((site) => site.serializedBy === "instance-claim");
|
|
3997
|
+
}
|
|
3998
|
+
/** One declared site by id. An undeclared id throws — a stale caller must fail
|
|
3999
|
+
* loud rather than read "nothing is declared". */
|
|
4000
|
+
function persistedWriteSite(id) {
|
|
4001
|
+
const site = PERSISTED_WRITE_SITES.find((candidate) => candidate.id === id);
|
|
4002
|
+
if (site === void 0) throw new Error(`evolution-core: no persisted write site "${id}" in persisted-write-inventory.json`);
|
|
4003
|
+
return site;
|
|
4004
|
+
}
|
|
3810
4005
|
//#endregion
|
|
3811
4006
|
//#region lib/types/scope.js
|
|
3812
4007
|
function callingScope(ctx, held) {
|
|
@@ -5666,48 +5861,40 @@ var SkillLibrary = class {
|
|
|
5666
5861
|
const name = rawName.trim();
|
|
5667
5862
|
if (this.badName(name) !== null) return 0;
|
|
5668
5863
|
const dir = this.dirOf(name);
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
entries = await this.io.list(dir);
|
|
5672
|
-
} catch {
|
|
5673
|
-
return 0;
|
|
5674
|
-
}
|
|
5864
|
+
const listed = await probeList(this.io, dir);
|
|
5865
|
+
if (!isPresent(listed)) return 0;
|
|
5675
5866
|
let count = 0;
|
|
5676
5867
|
for (const subdir of SUPPORT_DIRS) {
|
|
5677
|
-
if (!
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
} catch {}
|
|
5868
|
+
if (!listed.value.includes(subdir)) continue;
|
|
5869
|
+
const files = await probeList(this.io, join(dir, subdir));
|
|
5870
|
+
if (isPresent(files) && files.value.some((file) => file !== ".gitkeep")) count += 1;
|
|
5681
5871
|
}
|
|
5682
5872
|
return count;
|
|
5683
5873
|
}
|
|
5684
5874
|
/**
|
|
5685
|
-
*
|
|
5686
|
-
*
|
|
5687
|
-
*
|
|
5875
|
+
* Support-file paths (`references/x.md`) under SUPPORT_DIRS for one skill,
|
|
5876
|
+
* as a THREE-state read (N14): a missing skill directory is absent, an
|
|
5877
|
+
* unreadable one is unknown. A partial listing is unknown too — the union
|
|
5878
|
+
* promises the present branch is complete (011 §7 enrichment, probe reads).
|
|
5688
5879
|
*/
|
|
5689
5880
|
async listSupportFiles(rawName) {
|
|
5690
5881
|
const name = rawName.trim();
|
|
5691
|
-
if (this.badName(name) !== null) return
|
|
5882
|
+
if (this.badName(name) !== null) return probeAbsent();
|
|
5692
5883
|
const dir = this.dirOf(name);
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
entries = await this.io.list(dir);
|
|
5696
|
-
} catch {
|
|
5697
|
-
return [];
|
|
5698
|
-
}
|
|
5884
|
+
const listed = await probeList(this.io, dir);
|
|
5885
|
+
if (!isPresent(listed)) return listed;
|
|
5699
5886
|
const out = [];
|
|
5700
5887
|
for (const subdir of SUPPORT_DIRS) {
|
|
5701
|
-
if (!
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
}
|
|
5708
|
-
}
|
|
5888
|
+
if (!listed.value.includes(subdir)) continue;
|
|
5889
|
+
const files = await probeList(this.io, join(dir, subdir));
|
|
5890
|
+
if (isUnknown(files)) return files;
|
|
5891
|
+
if (!isPresent(files)) continue;
|
|
5892
|
+
for (const file of files.value) {
|
|
5893
|
+
if (file === ".gitkeep" || file.startsWith(".")) continue;
|
|
5894
|
+
out.push(`${subdir}/${file}`);
|
|
5895
|
+
}
|
|
5709
5896
|
}
|
|
5710
|
-
return out;
|
|
5897
|
+
return probePresent(out);
|
|
5711
5898
|
}
|
|
5712
5899
|
/**
|
|
5713
5900
|
* Structure-health facts for one skill (rc.73 A1, 008 design): body
|
|
@@ -7189,21 +7376,25 @@ var SkillLibrary = class {
|
|
|
7189
7376
|
}
|
|
7190
7377
|
/** Keep only the newest N snapshots (Hermes keep=5 parity); older ones are removed outright. */
|
|
7191
7378
|
async retainSnapshots(keep) {
|
|
7192
|
-
const
|
|
7379
|
+
const listed = await this.listSnapshots();
|
|
7380
|
+
if (isUnknown(listed)) {
|
|
7381
|
+
console.warn(`skill-store: snapshot retention skipped — the snapshot directory could not be listed (${listed.reason})`);
|
|
7382
|
+
return;
|
|
7383
|
+
}
|
|
7384
|
+
const snapshots = isPresent(listed) ? listed.value : [];
|
|
7193
7385
|
for (const snapshot of snapshots.slice(keep)) try {
|
|
7194
7386
|
await this.io.remove(snapshot.path);
|
|
7195
7387
|
} catch {}
|
|
7196
7388
|
}
|
|
7389
|
+
/** Snapshots as a THREE-state read (N14): absent = no .backups directory,
|
|
7390
|
+
* unknown = it exists but could not be listed (a caller must not read a
|
|
7391
|
+
* failed listing as "no snapshots to restore"). */
|
|
7197
7392
|
async listSnapshots() {
|
|
7198
7393
|
const backupRoot = join(this.root, ".backups");
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
entries = await this.io.list(backupRoot);
|
|
7202
|
-
} catch {
|
|
7203
|
-
return [];
|
|
7204
|
-
}
|
|
7394
|
+
const listed = await probeList(this.io, backupRoot);
|
|
7395
|
+
if (!isPresent(listed)) return listed;
|
|
7205
7396
|
const out = [];
|
|
7206
|
-
for (const name of
|
|
7397
|
+
for (const name of listed.value.sort().reverse()) {
|
|
7207
7398
|
if (!name.startsWith("skills-")) continue;
|
|
7208
7399
|
const manifest = await this.readSnapshotManifest(join(backupRoot, name));
|
|
7209
7400
|
if (manifest === null) {
|
|
@@ -7221,7 +7412,7 @@ var SkillLibrary = class {
|
|
|
7221
7412
|
});
|
|
7222
7413
|
}
|
|
7223
7414
|
out.sort((a, b) => (b.createdAt || "").localeCompare(a.createdAt || "") || b.path.localeCompare(a.path));
|
|
7224
|
-
return out;
|
|
7415
|
+
return probePresent(out);
|
|
7225
7416
|
}
|
|
7226
7417
|
/**
|
|
7227
7418
|
* Read the extras of a snapshot, restricted to the names declared in the
|
|
@@ -7249,7 +7440,12 @@ var SkillLibrary = class {
|
|
|
7249
7440
|
* snapshot so the rollback itself is undoable with the same state.
|
|
7250
7441
|
*/
|
|
7251
7442
|
async restoreLatestSnapshot(extras = []) {
|
|
7252
|
-
const
|
|
7443
|
+
const listed = await this.listSnapshots();
|
|
7444
|
+
if (isUnknown(listed)) return {
|
|
7445
|
+
ok: false,
|
|
7446
|
+
message: `Snapshot restore refused: the snapshot directory could not be read (${listed.reason}) — nothing was changed.`
|
|
7447
|
+
};
|
|
7448
|
+
const latest = isPresent(listed) ? listed.value[0] : void 0;
|
|
7253
7449
|
if (!latest) return {
|
|
7254
7450
|
ok: false,
|
|
7255
7451
|
message: "No skill snapshot available."
|
|
@@ -7411,4 +7607,4 @@ function sessionAudited(ctx, sessionId, sessionScoped) {
|
|
|
7411
7607
|
return sessionSeesFamilyTools(ctx, sessionId);
|
|
7412
7608
|
}
|
|
7413
7609
|
//#endregion
|
|
7414
|
-
export { AUTHORING_DESCRIPTION_BAR, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEAD_LOCK_TAKEOVER_MS, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_MODEL, 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, DISPATCH_EVENT_TYPES, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, EMPTY_LOCK_TAKEOVER_MS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FAMILY_SESSION_TOOL_NAMES, FORBIDDEN_CONTROL_KEYS, HEALTH_STAMP_RE, LOCK_BODY_RE, LOCK_SUFFIX, LOCK_TEAR_TAKEOVER_MS, LOW_QUALITY_THRESHOLD, LostWriteLock, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MAX_TIMER_DELAY_MS, MEMORY_GUIDANCE_SECTION_ORDER, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, NATIVE_CALL_EVENT, NATIVE_RESULT_EVENT, PATTERN_OVERLAP, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, PTC_DISPATCH_EVENT, PTC_DISPATCH_START_EVENT, QUALITY_WEIGHTS, RESTRUCTURE_TARGET_RE, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, THREAT_EXEMPTION_HINT, ToolDispatchNormalizer, ToolDispatchPayloadError, advanceReview, allowRowCollisions, appendEvolutionEvent, applyCuratorLifecycleFields, applyCuratorMetaFields, assertDispatchPayload, assertSkillsRootAliasRetired, assessStructureHealth, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, callingScope, clampedNumber, clearReviewChannel, collectReadSkillNames, composePresetComposition, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, countDispatches, createGateSet, decideTakeover, duplicateHeadings, emptyRecord, evaluateThreat, eventsFile, evolutionEventPayloadIssue, evolutionHome, evolutionIoAdapter, evolutionRoot, findDriftSignal, foldCuratorFields, foldToolDispatches, foldTurn, frontmatterBlock, frontmatterCatalogInvalid, getRecord, isAbsent, isCommittedWarning, isGlobalRead, isPresent, isProcessAlive, isProgramToolName, isReviewChannelSession, isSkillReadToolName, isSkillToolName, isUnknown, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadSuppressedNames, loadUsage, makeSerialQueue, mapProbe, markAgentCreated, markReviewChannel, markerEntryName, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, neutralizePromptVariables, newSkillLibrary, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, observeEvent, overlongLines, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, parseLockBody, pendingSelfCleanup, probeAbsent, probePresent, probeUnknown, readDispatchSignal, readEvolutionEvents, readEvolutionTimeline, recordMutation, redactSecrets, relatedSkillNames, renameWithRetry, renderCuratorReportMarkdown, resolveOrigins, resolveRootConfig, resolveSkillsRoot, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, sessionAudited, sessionSeesFamilyTools, skillReadNameOf, skillsRoot, suppressedFile, sweepReviewChannelSessions, transactIo, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, validateRestructureTarget, valueOr, verifyPromptBundle, writeDurableTmp, yamlPlainScalarNeedsQuotes };
|
|
7610
|
+
export { AUTHORING_DESCRIPTION_BAR, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEAD_LOCK_TAKEOVER_MS, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_MODEL, 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, DISPATCH_EVENT_TYPES, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, EMPTY_LOCK_TAKEOVER_MS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FAMILY_SESSION_TOOL_NAMES, FORBIDDEN_CONTROL_KEYS, HEALTH_STAMP_RE, INSTANCE_KEYS, LOCK_BODY_RE, LOCK_SUFFIX, LOCK_TEAR_TAKEOVER_MS, LOW_QUALITY_THRESHOLD, LostWriteLock, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MAX_TIMER_DELAY_MS, MEMORY_GUIDANCE_SECTION_ORDER, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, NATIVE_CALL_EVENT, NATIVE_RESULT_EVENT, PATTERN_OVERLAP, PERSISTED_WRITE_SITES, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, PTC_DISPATCH_EVENT, PTC_DISPATCH_START_EVENT, QUALITY_WEIGHTS, RESTRUCTURE_TARGET_RE, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, THREAT_EXEMPTION_HINT, ToolDispatchNormalizer, ToolDispatchPayloadError, advanceReview, allowRowCollisions, appendEvolutionEvent, applyCuratorLifecycleFields, applyCuratorMetaFields, assertDispatchPayload, assertSkillsRootAliasRetired, assessStructureHealth, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, callingScope, claimInstance, clampedNumber, clearReviewChannel, collectReadSkillNames, composePresetComposition, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, countDispatches, createGateSet, decideTakeover, duplicateHeadings, emptyRecord, evaluateThreat, eventsFile, evolutionEventPayloadIssue, evolutionHome, evolutionIoAdapter, evolutionRoot, findDriftSignal, foldCuratorFields, foldToolDispatches, foldTurn, frontmatterBlock, frontmatterCatalogInvalid, getRecord, instanceClaimKey, instanceClaimedWriteSites, instanceHolder, isAbsent, isCommittedWarning, isGlobalRead, isMissingPath, isPresent, isProcessAlive, isProgramToolName, isReviewChannelSession, isSkillReadToolName, isSkillToolName, isUnknown, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadRowOverrides, loadSuppressedNames, loadUsage, makeSerialQueue, mapProbe, markAgentCreated, markReviewChannel, markerEntryName, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, neutralizePromptVariables, newSkillLibrary, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, observeEvent, overlongLines, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, parseLockBody, pendingSelfCleanup, persistedWriteSite, probeAbsent, probeList, probeMtime, probePresent, probeReason, probeUnknown, readDispatchSignal, readEvolutionEvents, readEvolutionTimeline, recordMutation, redactSecrets, relatedSkillNames, releaseInstance, renameWithRetry, renderCuratorReportMarkdown, resolveOrigins, resolveRootConfig, resolveSkillsRoot, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, sessionAudited, sessionSeesFamilyTools, skillReadNameOf, skillsRoot, suppressedFile, sweepReviewChannelSessions, transactIo, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, validateRestructureTarget, valueOr, verifyPromptBundle, writeDurableTmp, yamlPlainScalarNeedsQuotes };
|
package/lib/types/constants.d.ts
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
export declare const SKILL_ACTION_REQUIRED_FIELDS: Readonly<Record<string, readonly string[]>>;
|
|
38
38
|
/** Skill frontmatter `name` validated for the file name (lowercase + hyphen).
|
|
39
39
|
* 计划 B-4 (v18): tightened to the UPSTREAM `SKILL_NAME` shape
|
|
40
|
-
* (`/^[a-z0-9]+(?:-[a-z0-9]+)*$/`, packages/skill/skill/src/index.ts:
|
|
40
|
+
* (`/^[a-z0-9]+(?:-[a-z0-9]+)*$/`, packages/skill/skill/src/index.ts:21). The
|
|
41
41
|
* old form admitted trailing/consecutive hyphens, which upstream
|
|
42
42
|
* `validateCandidate` throws on — and that throw aborts the WHOLE `ctx.skills`
|
|
43
43
|
* collection. The catalog provider still filters such legacy tree entries so
|
package/lib/types/index.d.ts
CHANGED
|
@@ -41,6 +41,8 @@ export * from './redact.ts';
|
|
|
41
41
|
export * from './review-channel.ts';
|
|
42
42
|
export * from './serial.ts';
|
|
43
43
|
export * from './probe.ts';
|
|
44
|
+
export * from './instance-scope.ts';
|
|
45
|
+
export * from './write-inventory.ts';
|
|
44
46
|
export * from './scope.ts';
|
|
45
47
|
export * from './skill-health.ts';
|
|
46
48
|
export * from './signals.ts';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* B3 / G4 (0.3.78): the family's single-instance contract, made explicit.
|
|
3
|
+
*
|
|
4
|
+
* Every persisted sidecar is written under TWO assumptions: the IO backend's
|
|
5
|
+
* cross-process write lock serializes different processes, and exactly ONE
|
|
6
|
+
* instance of each writer service exists per evolution home. The second half
|
|
7
|
+
* was implicit — `makeSerialQueue()` and the module-scope stores are
|
|
8
|
+
* per-instance, so two rows writing one home interleave inside one file (the
|
|
9
|
+
* "two instances, one file" class). This module makes that half enforceable: a
|
|
10
|
+
* writer CLAIMS its key while mounted and releases it on dispose, and a second
|
|
11
|
+
* claimant is told who holds the key instead of silently racing it.
|
|
12
|
+
*
|
|
13
|
+
* The claim is keyed by the HOME, not by the process: the contended resource is
|
|
14
|
+
* the sidecar directory, so two instances resolving different homes (an
|
|
15
|
+
* isolated test fixture, a second DSH_HOME) do not contend, while two rows on
|
|
16
|
+
* one profile do.
|
|
17
|
+
* @module @lmzhen/dsh-evolution-core/src/instance-scope
|
|
18
|
+
*/
|
|
19
|
+
/** Outcome of a claim. `holder` is the current owner either way. */
|
|
20
|
+
export interface InstanceClaimResult {
|
|
21
|
+
readonly granted: boolean;
|
|
22
|
+
readonly key: string;
|
|
23
|
+
readonly holder: string;
|
|
24
|
+
}
|
|
25
|
+
/** `<home> :: <key>` — the registry key, exported so diagnostics name the
|
|
26
|
+
* same unit the claim does. */
|
|
27
|
+
export declare function instanceClaimKey(home: string, key: string): string;
|
|
28
|
+
/** Take the claim for `key` at `home`. Grants while it is free or already
|
|
29
|
+
* held BY `owner` — remounting the same instance is not a second instance. */
|
|
30
|
+
export declare function claimInstance(home: string, key: string, owner: string): InstanceClaimResult;
|
|
31
|
+
/** Release only the claim `owner` took — never another instance's. */
|
|
32
|
+
export declare function releaseInstance(home: string, key: string, owner: string): void;
|
|
33
|
+
/** The current holder of `key` at `home`, or undefined. */
|
|
34
|
+
export declare function instanceHolder(home: string, key: string): string | undefined;
|
|
35
|
+
//# sourceMappingURL=instance-scope.d.ts.map
|
|
@@ -26,4 +26,42 @@
|
|
|
26
26
|
* @returns the composed preset composition (standard rows first, then delta).
|
|
27
27
|
*/
|
|
28
28
|
export declare function composePresetComposition(standardComposition: string, deltaComposition: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* V10-14 (P1-2), 0.3.53: inject the Hermes 60-char catalog cap onto the
|
|
31
|
+
* standard-sourced `- id: tool-skill` row of a composed preset.
|
|
32
|
+
*
|
|
33
|
+
* The session-visible `tool-skill` instance mounts in the agent preset's own
|
|
34
|
+
* standing scope; a profile-root patch (evolution-host/cordis.patch.yml)
|
|
35
|
+
* cannot reach it, so without this injection the catalog's read side runs the
|
|
36
|
+
* platform default (500). Text-level rewrite in the same line-scan style as
|
|
37
|
+
* compositionRowIds (no YAML library):
|
|
38
|
+
* - idempotent: a tool-skill item that already carries a `config:` key is
|
|
39
|
+
* left byte-identical, so re-running the installer never doubles the key;
|
|
40
|
+
* - the injected block carries a marker comment so a diff of the generated
|
|
41
|
+
* preset can tell composer-owned text from platform text;
|
|
42
|
+
* - a composition WITHOUT a tool-skill row is returned unchanged with a
|
|
43
|
+
* one-time warning (a renamed platform row must not brick the install,
|
|
44
|
+
* but the missed cap must be observable).
|
|
45
|
+
* The source installer reads the SAME table (`row-overrides.json`) through its
|
|
46
|
+
* own `injectToolSkillCap`, so the two paths cannot diverge (0.3.77).
|
|
47
|
+
*/
|
|
48
|
+
/** One entry of the shared override table (see {@link loadRowOverrides}). */
|
|
49
|
+
interface RowOverride {
|
|
50
|
+
/** Top-level row id this override targets (`- id: <row>`). */
|
|
51
|
+
row: string;
|
|
52
|
+
/** The child key whose presence makes the override inert (idempotence). */
|
|
53
|
+
key: string;
|
|
54
|
+
/** Rendered, already-indented YAML lines to ensure inside the row. */
|
|
55
|
+
lines: string[];
|
|
56
|
+
/** Warning tail shared by both consumers; each prefixes its own logger tag. */
|
|
57
|
+
missingReason: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Read and validate the shared override table.
|
|
61
|
+
* @returns the entries, in file order. A malformed table fails LOUD: a composer
|
|
62
|
+
* that silently skipped an entry would ship a preset running the platform
|
|
63
|
+
* default while nothing downstream reported it.
|
|
64
|
+
*/
|
|
65
|
+
export declare function loadRowOverrides(): RowOverride[];
|
|
66
|
+
export {};
|
|
29
67
|
//# sourceMappingURL=preset-composition.d.ts.map
|
package/lib/types/probe.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* unverifiable): constructors are prefixed so the type can never be confused
|
|
10
10
|
* with a value, and only `present` yields a value.
|
|
11
11
|
*/
|
|
12
|
+
import type { EvolutionIoLike } from './io.ts';
|
|
12
13
|
export type Probe<T> = {
|
|
13
14
|
kind: 'present';
|
|
14
15
|
value: T;
|
|
@@ -35,4 +36,26 @@ export declare function isUnknown<T>(probe: Probe<T>): probe is {
|
|
|
35
36
|
/** Only a PRESENT probe yields a value; absent and unknown both fall back. */
|
|
36
37
|
export declare function valueOr<T>(probe: Probe<T>, fallback: T): T;
|
|
37
38
|
export declare function mapProbe<T, U>(probe: Probe<T>, transform: (value: T) => U): Probe<U>;
|
|
39
|
+
/** The reason an `unknown` probe carries (message, never a bare String(object)). */
|
|
40
|
+
export declare function probeReason(error: unknown): string;
|
|
41
|
+
/**
|
|
42
|
+
* ENOENT/ENOTDIR are the ONE read failure that means "it is not there"; every
|
|
43
|
+
* other failure is an IO error and stays unknown (same split as the node
|
|
44
|
+
* backend's own isMissing, V8-23⑨ for the size probe).
|
|
45
|
+
*/
|
|
46
|
+
export declare function isMissingPath(error: unknown): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Three-state directory listing. The node backend already answers `[]` for a
|
|
49
|
+
* MISSING directory (its own rc.50 P2-4 contract) and THROWS for an unreadable
|
|
50
|
+
* one, so the value this adds is the second half: a backend that throws ENOENT
|
|
51
|
+
* reads as absent, an EACCES/EIO reads as unknown — where a bare
|
|
52
|
+
* `catch { return [] }` served a broken store as an empty one (the N14 class).
|
|
53
|
+
*/
|
|
54
|
+
export declare function probeList(io: EvolutionIoLike, dir: string): Promise<Probe<string[]>>;
|
|
55
|
+
/**
|
|
56
|
+
* Three-state mtime read. Absent covers both "the path is missing" and "this
|
|
57
|
+
* backend has no mtime probe" — the seam cannot tell those apart, so consumers
|
|
58
|
+
* that must know say so in their own log line. A stat that FAILS is unknown.
|
|
59
|
+
*/
|
|
60
|
+
export declare function probeMtime(io: EvolutionIoLike, path: string): Promise<Probe<number>>;
|
|
38
61
|
//# sourceMappingURL=probe.d.ts.map
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
* (OPT-07); the movers' probe→rename window is owned by the io.ts protocol.
|
|
34
34
|
*/
|
|
35
35
|
import { transactIo, type EvolutionIoLike } from './io.ts';
|
|
36
|
+
import { type Probe } from './probe.ts';
|
|
36
37
|
import { type MutationRecord } from './mutations.ts';
|
|
37
38
|
import { type SkillHealthAssessment, type SkillHealthThresholds } from './skill-health.ts';
|
|
38
39
|
import type { EvolutionSkillMutatedEvent } from './events.ts';
|
|
@@ -483,11 +484,12 @@ export declare class SkillLibrary {
|
|
|
483
484
|
/** Count non-empty support subdirectories (richness input for quality scoring). */
|
|
484
485
|
countSupportDirs(rawName: string): Promise<number>;
|
|
485
486
|
/**
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
487
|
+
* Support-file paths (`references/x.md`) under SUPPORT_DIRS for one skill,
|
|
488
|
+
* as a THREE-state read (N14): a missing skill directory is absent, an
|
|
489
|
+
* unreadable one is unknown. A partial listing is unknown too — the union
|
|
490
|
+
* promises the present branch is complete (011 §7 enrichment, probe reads).
|
|
489
491
|
*/
|
|
490
|
-
listSupportFiles(rawName: string): Promise<string[]
|
|
492
|
+
listSupportFiles(rawName: string): Promise<Probe<string[]>>;
|
|
491
493
|
/**
|
|
492
494
|
* Structure-health facts for one skill (rc.73 A1, 008 design): body
|
|
493
495
|
* chars/density from SKILL.md, support groups from countSupportDirs, plus
|
|
@@ -664,11 +666,14 @@ export declare class SkillLibrary {
|
|
|
664
666
|
readSnapshotManifest(path: string): Promise<SnapshotManifest | null>;
|
|
665
667
|
/** Keep only the newest N snapshots (Hermes keep=5 parity); older ones are removed outright. */
|
|
666
668
|
private retainSnapshots;
|
|
667
|
-
|
|
669
|
+
/** Snapshots as a THREE-state read (N14): absent = no .backups directory,
|
|
670
|
+
* unknown = it exists but could not be listed (a caller must not read a
|
|
671
|
+
* failed listing as "no snapshots to restore"). */
|
|
672
|
+
listSnapshots(): Promise<Probe<Array<{
|
|
668
673
|
path: string;
|
|
669
674
|
createdAt: string;
|
|
670
675
|
reason: string;
|
|
671
|
-
}
|
|
676
|
+
}>>>;
|
|
672
677
|
/**
|
|
673
678
|
* Read the extras of a snapshot, restricted to the names declared in the
|
|
674
679
|
* manifest — an `extras/` directory is never listed directly, so unknown
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* B3 / G4 (0.3.78): the persisted-write inventory — WHICH file the family
|
|
3
|
+
* writes, WHO writes it, and WHAT keeps two writers apart.
|
|
4
|
+
*
|
|
5
|
+
* The table lives in `persisted-write-inventory.json` at the package root so
|
|
6
|
+
* the TypeScript side, the architecture gate (`verify-arch-guards` rule N20)
|
|
7
|
+
* and the regression specs read ONE file (the row-overrides.json pattern).
|
|
8
|
+
*
|
|
9
|
+
* "What keeps two writers apart" has exactly three answers in this family:
|
|
10
|
+
* - `transact` — the IO backend's cross-process write lock (transactIo);
|
|
11
|
+
* - `write-lock` — the per-target `<path>.lock` protocol of the skill tree;
|
|
12
|
+
* - `instance-claim` — the per-home single-instance claim (instance-scope.ts),
|
|
13
|
+
* for a writer whose sweeps cannot be expressed as one locked file.
|
|
14
|
+
* A site that answers with NONE of them is the "two instances, one file" class:
|
|
15
|
+
* a per-instance serial queue looks like serialization and is not.
|
|
16
|
+
* @module @lmzhen/dsh-evolution-core/src/write-inventory
|
|
17
|
+
*/
|
|
18
|
+
/** How concurrent writers of one site are kept apart. */
|
|
19
|
+
export type WriteSerialization = 'transact' | 'write-lock' | 'instance-claim';
|
|
20
|
+
/** One declared persisted write site. */
|
|
21
|
+
export interface PersistedWriteSite {
|
|
22
|
+
/** Stable id; referenced by the gate's failure text and by the specs. */
|
|
23
|
+
readonly id: string;
|
|
24
|
+
/** The path(s) written, with the roots the family resolves at runtime. */
|
|
25
|
+
readonly path: string;
|
|
26
|
+
/** Repo-relative module that owns the write. */
|
|
27
|
+
readonly writer: string;
|
|
28
|
+
/** The serialization the writer implements (see the module docblock). */
|
|
29
|
+
readonly serializedBy: WriteSerialization;
|
|
30
|
+
/** Literal that must appear in `writer` — the gate's proof of the claim. */
|
|
31
|
+
readonly marker: string;
|
|
32
|
+
/** The instance key the writer holds when `serializedBy` is instance-claim. */
|
|
33
|
+
readonly instance?: string;
|
|
34
|
+
/** Module-scope state keys (N12 registry keys) this writer keeps, if any. */
|
|
35
|
+
readonly state: readonly string[];
|
|
36
|
+
/** One line on what the file holds; the inventory reads as a whole. */
|
|
37
|
+
readonly note: string;
|
|
38
|
+
}
|
|
39
|
+
/** Instance keys held by writer services — the single source shared by the
|
|
40
|
+
* claim site and the inventory row that declares it (rule N20 checks the row's
|
|
41
|
+
* `instance` names one of these). */
|
|
42
|
+
export declare const INSTANCE_KEYS: {
|
|
43
|
+
/** The per-home curator: report writing + the retention sweep. */
|
|
44
|
+
readonly curator: "evolution-curator";
|
|
45
|
+
};
|
|
46
|
+
/** The declared persisted write sites, in file order. */
|
|
47
|
+
export declare const PERSISTED_WRITE_SITES: readonly PersistedWriteSite[];
|
|
48
|
+
/** Sites serialized by the per-home instance claim, with their instance keys. */
|
|
49
|
+
export declare function instanceClaimedWriteSites(): readonly (PersistedWriteSite & {
|
|
50
|
+
readonly instance: string;
|
|
51
|
+
})[];
|
|
52
|
+
/** One declared site by id. An undeclared id throws — a stale caller must fail
|
|
53
|
+
* loud rather than read "nothing is declared". */
|
|
54
|
+
export declare function persistedWriteSite(id: string): PersistedWriteSite;
|
|
55
|
+
//# sourceMappingURL=write-inventory.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.3.
|
|
4
|
+
"version": "0.3.78",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -18,11 +18,13 @@
|
|
|
18
18
|
"types": "./lib/types/index.d.ts",
|
|
19
19
|
"default": "./lib/index.js"
|
|
20
20
|
},
|
|
21
|
-
"./package.json": "./package.json"
|
|
21
|
+
"./package.json": "./package.json",
|
|
22
|
+
"./row-overrides.json": "./row-overrides.json"
|
|
22
23
|
},
|
|
23
24
|
"files": [
|
|
24
25
|
"lib/*.js",
|
|
25
|
-
"lib/types/**/*.d.ts"
|
|
26
|
+
"lib/types/**/*.d.ts",
|
|
27
|
+
"row-overrides.json"
|
|
26
28
|
],
|
|
27
29
|
"license": "MIT",
|
|
28
30
|
"dependencies": {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"row": "tool-skill",
|
|
4
|
+
"key": "config",
|
|
5
|
+
"lines": [
|
|
6
|
+
" # V10-14: Hermes 60-char catalog cap — injected by the preset composer (P1-2);",
|
|
7
|
+
" # this preset-scope row is the session-visible instance and no profile",
|
|
8
|
+
" # patch can reach it. Remove only to run the platform default (500).",
|
|
9
|
+
" config:",
|
|
10
|
+
" catalogDescriptionMaxLength: 60"
|
|
11
|
+
],
|
|
12
|
+
"missingReason": "no `- id: tool-skill` row in the composed preset; the 60-char catalog cap was NOT injected (platform renamed the row? reconcile with the delta)"
|
|
13
|
+
}
|
|
14
|
+
]
|