@lmzhen/dsh-evolution-core 0.3.78 → 0.3.80
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 +104 -26
- package/lib/types/io.d.ts +7 -6
- package/lib/types/skill-store.d.ts +24 -0
- package/lib/types/tool-dispatch.d.ts +39 -10
- package/package.json +5 -3
- package/persisted-write-inventory.json +79 -0
package/lib/index.js
CHANGED
|
@@ -1249,6 +1249,7 @@ function parseCuratorNominations(text) {
|
|
|
1249
1249
|
let section = null;
|
|
1250
1250
|
let currentFrom = "";
|
|
1251
1251
|
let currentMode;
|
|
1252
|
+
let currentIntoSpoken = false;
|
|
1252
1253
|
for (const line of text.split("\n")) {
|
|
1253
1254
|
const header = /^\s*(consolidations|prunings)\s*:\s*$/.exec(line);
|
|
1254
1255
|
if (header) {
|
|
@@ -1257,9 +1258,11 @@ function parseCuratorNominations(text) {
|
|
|
1257
1258
|
}
|
|
1258
1259
|
const consolidated = /^\s*-\s*from:\s*([a-z0-9][a-z0-9-]*)(?:\s*#.*)?\s*$/.exec(line);
|
|
1259
1260
|
if (consolidated) {
|
|
1261
|
+
if (currentFrom !== "" && !currentIntoSpoken) warnings.push(`"- from: ${currentFrom}" dropped — no "into:" arrived before the next entry`);
|
|
1260
1262
|
section = "consolidations";
|
|
1261
1263
|
currentFrom = consolidated[1] ?? "";
|
|
1262
1264
|
currentMode = void 0;
|
|
1265
|
+
currentIntoSpoken = false;
|
|
1263
1266
|
continue;
|
|
1264
1267
|
}
|
|
1265
1268
|
const mode = /^\s*mode:\s*(append|reference)(?:\s*#.*)?\s*$/.exec(line);
|
|
@@ -1278,6 +1281,7 @@ function parseCuratorNominations(text) {
|
|
|
1278
1281
|
});
|
|
1279
1282
|
currentFrom = "";
|
|
1280
1283
|
currentMode = void 0;
|
|
1284
|
+
currentIntoSpoken = false;
|
|
1281
1285
|
continue;
|
|
1282
1286
|
}
|
|
1283
1287
|
const pruned = /^\s*-\s*name:\s*([a-z0-9][a-z0-9-]*)(?:\s*#.*)?\s*$/.exec(line);
|
|
@@ -1288,8 +1292,12 @@ function parseCuratorNominations(text) {
|
|
|
1288
1292
|
if (name) prunings.push(name);
|
|
1289
1293
|
continue;
|
|
1290
1294
|
}
|
|
1291
|
-
if (/^\s*(?:-\s*(?:from|name)\s*:|(?:into|mode)\s*:)/.test(line))
|
|
1295
|
+
if (/^\s*(?:-\s*(?:from|name)\s*:|(?:into|mode)\s*:)/.test(line)) {
|
|
1296
|
+
if (currentFrom !== "" && /^\s*into\s*:/.test(line)) currentIntoSpoken = true;
|
|
1297
|
+
warnings.push(`"${line.trim()}" ignored - not a usable nomination line (names are lowercase letters, digits and hyphens; one optional trailing "# comment" is allowed)`);
|
|
1298
|
+
}
|
|
1292
1299
|
}
|
|
1300
|
+
if (currentFrom !== "" && !currentIntoSpoken) warnings.push(`"- from: ${currentFrom}" dropped — the nomination output ended before its "into:"`);
|
|
1293
1301
|
const valid = (name) => NOMINATION_NAME_RE.test(name);
|
|
1294
1302
|
return {
|
|
1295
1303
|
prunings: prunings.filter(valid),
|
|
@@ -2167,12 +2175,16 @@ const TYPOGRAPHY_CHARS = /[\u00ad\u061c\u180e\u200c\ufe00-\ufe0f]/;
|
|
|
2167
2175
|
const BIDI_CHARS = /[\u202a-\u202e\u2066-\u2069]/;
|
|
2168
2176
|
const FORMAT_CONTROL_CLASS = "\\p{Cf}\\p{Zl}\\p{Zp}\\u0000\\u2065\\ufff0-\\ufff8\\u{e0080}-\\u{e00ff}";
|
|
2169
2177
|
const FORMAT_CONTROL_TEST = new RegExp(`[${FORMAT_CONTROL_CLASS}]`, "u");
|
|
2170
|
-
/** S0.4 (v37 P0-3): a format control the three finding sets above do NOT report.
|
|
2178
|
+
/** S0.4 (v37 P0-3): a format control the three finding sets above do NOT report.
|
|
2179
|
+
* S1-E3: the ZWJ exemption compares the real character. It used to read
|
|
2180
|
+
* `'\\u200d'` — a 6-character literal that never equals a code point — so the
|
|
2181
|
+
* branch was dead and every emoji ZWJ sequence (👨👩👧) raised the report-level
|
|
2182
|
+
* `unicode_format_control` finding the class comment above promises it will not. */
|
|
2171
2183
|
function hasUnreportedFormatControl(text) {
|
|
2172
2184
|
for (const character of text) {
|
|
2173
2185
|
if (!FORMAT_CONTROL_TEST.test(character)) continue;
|
|
2174
2186
|
if (ZERO_WIDTH_CHARS.test(character) || TYPOGRAPHY_CHARS.test(character) || BIDI_CHARS.test(character)) continue;
|
|
2175
|
-
if (character === "
|
|
2187
|
+
if (character === "") continue;
|
|
2176
2188
|
return true;
|
|
2177
2189
|
}
|
|
2178
2190
|
return false;
|
|
@@ -4212,8 +4224,19 @@ function readDispatchRecord(event) {
|
|
|
4212
4224
|
*/
|
|
4213
4225
|
var ToolDispatchNormalizer = class {
|
|
4214
4226
|
records = /* @__PURE__ */ new Map();
|
|
4215
|
-
/**
|
|
4216
|
-
|
|
4227
|
+
/** S2-P2-14: cap for the LEDGER (and the settle markers) — opt-in, meant for
|
|
4228
|
+
* the long-lived live listener (skill-usage) whose ledger is pure dedup
|
|
4229
|
+
* state and would otherwise grow with the process lifetime. `Infinity` (the
|
|
4230
|
+
* default, used by whole-log folding) keeps every dispatch. Eviction is
|
|
4231
|
+
* oldest-first: a call/result pair spans one tool call, so an evicted
|
|
4232
|
+
* dispatch can never be re-paired within any realistic window. */
|
|
4233
|
+
maxTracked;
|
|
4234
|
+
/** S2-P2-12: call ids whose outcome has already been reported to the settle
|
|
4235
|
+
* channel — a replayed result event must not settle the same dispatch twice. */
|
|
4236
|
+
settledIds = /* @__PURE__ */ new Set();
|
|
4237
|
+
constructor(options = {}) {
|
|
4238
|
+
this.maxTracked = options.maxTracked ?? Number.POSITIVE_INFINITY;
|
|
4239
|
+
}
|
|
4217
4240
|
/**
|
|
4218
4241
|
* Absorb one session event.
|
|
4219
4242
|
* @param event - the event to absorb; any non-dispatch event is ignored.
|
|
@@ -4235,7 +4258,7 @@ var ToolDispatchNormalizer = class {
|
|
|
4235
4258
|
return null;
|
|
4236
4259
|
}
|
|
4237
4260
|
const signal = {
|
|
4238
|
-
kind: record.kind
|
|
4261
|
+
kind: record.kind,
|
|
4239
4262
|
callId: record.callId,
|
|
4240
4263
|
rootCallId: record.rootCallId,
|
|
4241
4264
|
name: record.name,
|
|
@@ -4243,9 +4266,43 @@ var ToolDispatchNormalizer = class {
|
|
|
4243
4266
|
ok: record.outcome?.ok
|
|
4244
4267
|
};
|
|
4245
4268
|
this.records.set(record.callId, signal);
|
|
4246
|
-
|
|
4269
|
+
this.evict();
|
|
4270
|
+
return signal;
|
|
4271
|
+
}
|
|
4272
|
+
/**
|
|
4273
|
+
* S2-P2-12: the SETTLE channel. \`advance\` answers \`null\` for the paired
|
|
4274
|
+
* event that settles an already-emitted dispatch, so a consumer that must act
|
|
4275
|
+
* on the OUTCOME (e.g. count successful skill reads only) cannot tell which
|
|
4276
|
+
* dispatch a \`null\` belonged to. Call this with the same event AFTER
|
|
4277
|
+
* \`advance\`: it answers the settled dispatch exactly once (replayed settle
|
|
4278
|
+
* events answer \`null\`).
|
|
4279
|
+
* @param event - the event already absorbed by \`advance\`.
|
|
4280
|
+
* @returns the dispatch this event settled, or \`null\`.
|
|
4281
|
+
*/
|
|
4282
|
+
settledSignalOf(event) {
|
|
4283
|
+
const record = readDispatchRecord(event);
|
|
4284
|
+
if (record === null || record.outcome === void 0) return null;
|
|
4285
|
+
if (this.settledIds.has(record.callId)) return null;
|
|
4286
|
+
const signal = this.records.get(record.callId);
|
|
4287
|
+
if (signal === void 0) return null;
|
|
4288
|
+
this.settledIds.add(record.callId);
|
|
4289
|
+
this.evict();
|
|
4247
4290
|
return signal;
|
|
4248
4291
|
}
|
|
4292
|
+
/** Oldest-first eviction once the ledger (or the settle markers) overflows. */
|
|
4293
|
+
evict() {
|
|
4294
|
+
while (this.records.size > this.maxTracked) {
|
|
4295
|
+
const oldest = this.records.keys().next().value;
|
|
4296
|
+
if (oldest === void 0) break;
|
|
4297
|
+
this.records.delete(oldest);
|
|
4298
|
+
this.settledIds.delete(oldest);
|
|
4299
|
+
}
|
|
4300
|
+
while (this.settledIds.size > this.maxTracked) {
|
|
4301
|
+
const oldest = this.settledIds.values().next().value;
|
|
4302
|
+
if (oldest === void 0) break;
|
|
4303
|
+
this.settledIds.delete(oldest);
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4249
4306
|
/** Every emitted dispatch, in first-seen order. */
|
|
4250
4307
|
get signals() {
|
|
4251
4308
|
return [...this.records.values()];
|
|
@@ -4366,10 +4423,14 @@ function isProgramToolName(name) {
|
|
|
4366
4423
|
* Assert that a payload really is the dispatch event it claims to be.
|
|
4367
4424
|
*
|
|
4368
4425
|
* The normalizer is deliberately lenient (it also folds persisted logs, where a
|
|
4369
|
-
* payload may predate the current declaration)
|
|
4370
|
-
*
|
|
4371
|
-
*
|
|
4372
|
-
*
|
|
4426
|
+
* payload may predate the current declaration). S1-E7 (honesty fix): this
|
|
4427
|
+
* function is an OPT-IN gate — NO production consumer calls it; the shipped
|
|
4428
|
+
* listeners fold tolerant (`'malformed'` synthesis) precisely because a
|
|
4429
|
+
* throwing gate wired into every session event would brick the family on any
|
|
4430
|
+
* upstream payload drift. Deployments that want fail-loud behavior call this
|
|
4431
|
+
* from their own `session/event` listener. It never silently downgrades: an
|
|
4432
|
+
* unrecognized event type or a payload missing a declared field throws a named
|
|
4433
|
+
* \`ToolDispatchPayloadError\`.
|
|
4373
4434
|
* @param event - the session event to check.
|
|
4374
4435
|
* @returns nothing; throws when the payload violates the platform declaration.
|
|
4375
4436
|
*/
|
|
@@ -4856,6 +4917,18 @@ function resolveOrigins(headerOrigin, isReview = false) {
|
|
|
4856
4917
|
library: "foreground"
|
|
4857
4918
|
};
|
|
4858
4919
|
}
|
|
4920
|
+
/**
|
|
4921
|
+
* S1-E8 (0.3.80): ONE exec→origins resolution for the two write tools — reads
|
|
4922
|
+
* the session header origin AND the v37 S2.2 review-channel session mark, so a
|
|
4923
|
+
* tool cannot forget the mark half (tool-memory shipped without it, which
|
|
4924
|
+
* mislabeled every inject-mode review memory write as `foreground` and let it
|
|
4925
|
+
* bypass staging under `stageForeground: false`).
|
|
4926
|
+
* Single source: both tools call this instead of re-deriving the pair.
|
|
4927
|
+
*/
|
|
4928
|
+
function resolveExecOrigins(exec) {
|
|
4929
|
+
const session = exec?.agent?.session;
|
|
4930
|
+
return resolveOrigins(session?.header?.origin, isReviewChannelSession(typeof session?.id === "string" ? session.id : void 0));
|
|
4931
|
+
}
|
|
4859
4932
|
function skillDir(root, name) {
|
|
4860
4933
|
return join(root, name);
|
|
4861
4934
|
}
|
|
@@ -6194,7 +6267,7 @@ var SkillLibrary = class {
|
|
|
6194
6267
|
skillDir: dir
|
|
6195
6268
|
}
|
|
6196
6269
|
};
|
|
6197
|
-
}, anchor !== void 0 ?
|
|
6270
|
+
}, anchor !== void 0 ? anchorUnverifiable(name, null) : void 0);
|
|
6198
6271
|
}
|
|
6199
6272
|
async patch(rawName, oldString, newString, filePath = "", replaceAll = false, origin = "foreground") {
|
|
6200
6273
|
const name = rawName.trim();
|
|
@@ -6619,6 +6692,7 @@ var SkillLibrary = class {
|
|
|
6619
6692
|
return await this.serial(async () => {
|
|
6620
6693
|
const referenceWrites = [];
|
|
6621
6694
|
const parts = [];
|
|
6695
|
+
const plannedSourceBytes = /* @__PURE__ */ new Map();
|
|
6622
6696
|
if (mode === "append") for (const source of normalizedSources) {
|
|
6623
6697
|
const protection = await this.deleteProtection(source);
|
|
6624
6698
|
if (protection) return {
|
|
@@ -6645,6 +6719,7 @@ var SkillLibrary = class {
|
|
|
6645
6719
|
message: `Consolidation rejected: source "${source}" body references support files (${refs.join(", ")}) that would be left behind — use mode:'reference' or archive the whole package instead.`
|
|
6646
6720
|
};
|
|
6647
6721
|
parts.push(`\n<!-- consolidated from ${source} at ${(/* @__PURE__ */ new Date()).toISOString()} -->\n${parsed.body.trim()}`);
|
|
6722
|
+
plannedSourceBytes.set(source, sourceMd);
|
|
6648
6723
|
}
|
|
6649
6724
|
else for (const source of normalizedSources) {
|
|
6650
6725
|
const protection = await this.deleteProtection(source);
|
|
@@ -6672,6 +6747,7 @@ var SkillLibrary = class {
|
|
|
6672
6747
|
target,
|
|
6673
6748
|
content: `<!-- demoted from ${source} at ${(/* @__PURE__ */ new Date()).toISOString()} -->\n${parsed.body.trim()}\n`
|
|
6674
6749
|
});
|
|
6750
|
+
plannedSourceBytes.set(source, sourceMd);
|
|
6675
6751
|
}
|
|
6676
6752
|
const archived = [];
|
|
6677
6753
|
try {
|
|
@@ -6680,15 +6756,15 @@ var SkillLibrary = class {
|
|
|
6680
6756
|
message: `Skill "${targetName}" not found.`
|
|
6681
6757
|
};
|
|
6682
6758
|
for (const source of normalizedSources) {
|
|
6759
|
+
if (plannedSourceBytes.has(source)) {
|
|
6760
|
+
if (await this.io.readText(join(this.dirOf(source), "SKILL.md")) !== plannedSourceBytes.get(source)) throw new Error(`Consolidation aborted: source "${source}" changed while the consolidation ran (its planned bytes are no longer current).`);
|
|
6761
|
+
}
|
|
6683
6762
|
const result = await this.archive(source, { absorbedInto: targetName });
|
|
6684
6763
|
if (!result.ok) throw new Error(result.message);
|
|
6685
6764
|
archived.push(source);
|
|
6686
6765
|
}
|
|
6687
6766
|
const freshTargetMd = await this.io.readText(join(targetDir, "SKILL.md"));
|
|
6688
|
-
if (!freshTargetMd)
|
|
6689
|
-
ok: false,
|
|
6690
|
-
message: `Skill "${targetName}" not found.`
|
|
6691
|
-
};
|
|
6767
|
+
if (!freshTargetMd) throw new Error(`target "${targetName}" disappeared mid-consolidate`);
|
|
6692
6768
|
const writes = [];
|
|
6693
6769
|
for (const reference of referenceWrites) {
|
|
6694
6770
|
const previous = await this.io.readText(reference.target).catch(() => null);
|
|
@@ -6702,10 +6778,7 @@ var SkillLibrary = class {
|
|
|
6702
6778
|
if (mode === "append") {
|
|
6703
6779
|
const merged = freshTargetMd.trimEnd() + parts.join("\n") + "\n";
|
|
6704
6780
|
const validation = validateFrontmatter(merged, targetName, this.limits);
|
|
6705
|
-
if (validation)
|
|
6706
|
-
ok: false,
|
|
6707
|
-
message: `Consolidation rejected: ${validation}`
|
|
6708
|
-
};
|
|
6781
|
+
if (validation) throw new Error(`merge validation failed: ${validation}`);
|
|
6709
6782
|
writes.push({
|
|
6710
6783
|
target: join(targetDir, "SKILL.md"),
|
|
6711
6784
|
content: merged,
|
|
@@ -6715,10 +6788,7 @@ var SkillLibrary = class {
|
|
|
6715
6788
|
const pointerLines = normalizedSources.map((source) => `\n${POINTER_LINE_PREFIX}${source}.md`).join("");
|
|
6716
6789
|
const extended = freshTargetMd.trimEnd() + pointerLines + "\n";
|
|
6717
6790
|
const validation = validateFrontmatter(extended, targetName, this.limits);
|
|
6718
|
-
if (validation)
|
|
6719
|
-
ok: false,
|
|
6720
|
-
message: `Consolidation rejected: ${validation}`
|
|
6721
|
-
};
|
|
6791
|
+
if (validation) throw new Error(`merge validation failed: ${validation}`);
|
|
6722
6792
|
writes.push({
|
|
6723
6793
|
target: join(targetDir, "SKILL.md"),
|
|
6724
6794
|
content: extended,
|
|
@@ -7459,7 +7529,15 @@ var SkillLibrary = class {
|
|
|
7459
7529
|
message: `Snapshot restore was refused before anything was cleared: taking the pre-rollback snapshot failed (${error instanceof Error ? error.message : String(error)}) — the active tree is UNCHANGED. Resolve the snapshot write failure and retry.`
|
|
7460
7530
|
};
|
|
7461
7531
|
}
|
|
7462
|
-
|
|
7532
|
+
let snapshotExtras;
|
|
7533
|
+
try {
|
|
7534
|
+
snapshotExtras = await this.readSnapshotExtras(latest.path);
|
|
7535
|
+
} catch (error) {
|
|
7536
|
+
return {
|
|
7537
|
+
ok: false,
|
|
7538
|
+
message: `Snapshot restore was refused before anything was cleared: reading the snapshot manifest/extras failed (${error instanceof Error ? error.message : String(error)}) — the active tree is UNCHANGED. Resolve the read failure and retry.`
|
|
7539
|
+
};
|
|
7540
|
+
}
|
|
7463
7541
|
try {
|
|
7464
7542
|
await this.restoreSnapshotIntoRoot(latest.path);
|
|
7465
7543
|
} catch (error) {
|
|
@@ -7607,4 +7685,4 @@ function sessionAudited(ctx, sessionId, sessionScoped) {
|
|
|
7607
7685
|
return sessionSeesFamilyTools(ctx, sessionId);
|
|
7608
7686
|
}
|
|
7609
7687
|
//#endregion
|
|
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 };
|
|
7688
|
+
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, resolveExecOrigins, 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/io.d.ts
CHANGED
|
@@ -46,16 +46,17 @@ export interface EvolutionIoLike {
|
|
|
46
46
|
* missing path, stat failure). Intended as a cheap invalidation stamp for a
|
|
47
47
|
* cached directory listing; a backend without it keeps event-driven
|
|
48
48
|
* invalidation only. v20 correction: the former "NO in-tree consumer" note
|
|
49
|
-
* (V9-07, 0.3.51) went stale —
|
|
49
|
+
* (V9-07, 0.3.51) went stale — in-tree consumers exist, and a
|
|
50
50
|
* custom backend that omits `mtime` degrades them silently (every call
|
|
51
51
|
* site is optional-call + null-fallback, so omission stays legal):
|
|
52
|
-
* - evolution-skill-catalog: root-mtime stamp on the summaries cache —
|
|
53
|
-
* the second, out-of-band invalidation signal next to the
|
|
54
|
-
* `evolution/skill-mutated` / `evolution/skills-refresh` events;
|
|
55
52
|
* - evolution-curator: run-report recency ordering (2 call sites);
|
|
56
53
|
* - evolution-commands: `.bak` freshness probe in the preset installer.
|
|
57
|
-
*
|
|
58
|
-
*
|
|
54
|
+
* S3-P2-10: evolution-skill-catalog's summaries stamp — the third consumer —
|
|
55
|
+
* moved to a NAMES-based stamp over `list()` (a required seam method): the
|
|
56
|
+
* root-mtime stamp was invalidated by the family's own sidecar flushes in
|
|
57
|
+
* the watched root, so every usage-counter write paid a full rescan. The
|
|
58
|
+
* names stamp is sidecar-immune and works on mtime-less backends. Register
|
|
59
|
+
* new consumers here (the seam contract).
|
|
59
60
|
*/
|
|
60
61
|
mtime?(this: void, path: string): Promise<number | null>;
|
|
61
62
|
}
|
|
@@ -215,6 +215,30 @@ export declare function resolveOrigins(headerOrigin: string | undefined, isRevie
|
|
|
215
215
|
approval: 'foreground' | 'background_review';
|
|
216
216
|
library: WriteOrigin;
|
|
217
217
|
};
|
|
218
|
+
/** Minimal structural view of a tool execution context, so the helper below
|
|
219
|
+
* stays decoupled from the platform's exec type (extra fields are ignored). */
|
|
220
|
+
export interface EvolutionExecOriginView {
|
|
221
|
+
agent?: {
|
|
222
|
+
session?: {
|
|
223
|
+
id?: unknown;
|
|
224
|
+
header?: {
|
|
225
|
+
origin?: string;
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* S1-E8 (0.3.80): ONE exec→origins resolution for the two write tools — reads
|
|
232
|
+
* the session header origin AND the v37 S2.2 review-channel session mark, so a
|
|
233
|
+
* tool cannot forget the mark half (tool-memory shipped without it, which
|
|
234
|
+
* mislabeled every inject-mode review memory write as `foreground` and let it
|
|
235
|
+
* bypass staging under `stageForeground: false`).
|
|
236
|
+
* Single source: both tools call this instead of re-deriving the pair.
|
|
237
|
+
*/
|
|
238
|
+
export declare function resolveExecOrigins(exec: EvolutionExecOriginView | undefined): {
|
|
239
|
+
approval: 'foreground' | 'background_review';
|
|
240
|
+
library: WriteOrigin;
|
|
241
|
+
};
|
|
218
242
|
/** Dot-prefixed on-disk marker name. SINGLE source: `list()` matches directory
|
|
219
243
|
* entries against this name, and path builders must never hardcode a marker
|
|
220
244
|
* literal (N-1: the rc.49 exists()-probe convergence dropped the dot,
|
|
@@ -39,9 +39,7 @@ export type ToolDispatchKind =
|
|
|
39
39
|
/** A model-authored call, logged as \`tool/call\` and settled by \`tool/result\`. */
|
|
40
40
|
'native'
|
|
41
41
|
/** A sub-dispatch of a code program, logged as the PTC dispatch pair. */
|
|
42
|
-
| 'program'
|
|
43
|
-
/** A \`run_code\` call itself: a native call whose program dispatches sub-calls. */
|
|
44
|
-
| 'program-root';
|
|
42
|
+
| 'program';
|
|
45
43
|
/** The platform event type carrying a PTC sub-dispatch start. */
|
|
46
44
|
export declare const PTC_DISPATCH_START_EVENT = "tool/ptc-dispatch-start";
|
|
47
45
|
/** The platform event type carrying a PTC sub-dispatch settle. */
|
|
@@ -63,7 +61,7 @@ export declare const DISPATCH_EVENT_TYPES: readonly string[];
|
|
|
63
61
|
* so a consumer that reacted at start already holds the outcome.
|
|
64
62
|
*/
|
|
65
63
|
export interface ToolDispatchSignal {
|
|
66
|
-
/** \`native\` | \`program\` (a code-program sub-dispatch)
|
|
64
|
+
/** \`native\` | \`program\` (a code-program sub-dispatch); the \`run_code\` root itself reads as \`native\`. */
|
|
67
65
|
readonly kind: ToolDispatchKind;
|
|
68
66
|
/** \`callId\` for a native call, \`subCallId\` for a PTC sub-dispatch — the platform's own identity. */
|
|
69
67
|
readonly callId: string;
|
|
@@ -94,8 +92,19 @@ export declare class ToolDispatchPayloadError extends TypeError {
|
|
|
94
92
|
*/
|
|
95
93
|
export declare class ToolDispatchNormalizer {
|
|
96
94
|
private readonly records;
|
|
97
|
-
/**
|
|
98
|
-
|
|
95
|
+
/** S2-P2-14: cap for the LEDGER (and the settle markers) — opt-in, meant for
|
|
96
|
+
* the long-lived live listener (skill-usage) whose ledger is pure dedup
|
|
97
|
+
* state and would otherwise grow with the process lifetime. `Infinity` (the
|
|
98
|
+
* default, used by whole-log folding) keeps every dispatch. Eviction is
|
|
99
|
+
* oldest-first: a call/result pair spans one tool call, so an evicted
|
|
100
|
+
* dispatch can never be re-paired within any realistic window. */
|
|
101
|
+
private readonly maxTracked;
|
|
102
|
+
/** S2-P2-12: call ids whose outcome has already been reported to the settle
|
|
103
|
+
* channel — a replayed result event must not settle the same dispatch twice. */
|
|
104
|
+
private readonly settledIds;
|
|
105
|
+
constructor(options?: {
|
|
106
|
+
maxTracked?: number;
|
|
107
|
+
});
|
|
99
108
|
/**
|
|
100
109
|
* Absorb one session event.
|
|
101
110
|
* @param event - the event to absorb; any non-dispatch event is ignored.
|
|
@@ -107,6 +116,22 @@ export declare class ToolDispatchNormalizer {
|
|
|
107
116
|
type: string;
|
|
108
117
|
data?: unknown;
|
|
109
118
|
}): ToolDispatchSignal | null;
|
|
119
|
+
/**
|
|
120
|
+
* S2-P2-12: the SETTLE channel. \`advance\` answers \`null\` for the paired
|
|
121
|
+
* event that settles an already-emitted dispatch, so a consumer that must act
|
|
122
|
+
* on the OUTCOME (e.g. count successful skill reads only) cannot tell which
|
|
123
|
+
* dispatch a \`null\` belonged to. Call this with the same event AFTER
|
|
124
|
+
* \`advance\`: it answers the settled dispatch exactly once (replayed settle
|
|
125
|
+
* events answer \`null\`).
|
|
126
|
+
* @param event - the event already absorbed by \`advance\`.
|
|
127
|
+
* @returns the dispatch this event settled, or \`null\`.
|
|
128
|
+
*/
|
|
129
|
+
settledSignalOf(event: {
|
|
130
|
+
type: string;
|
|
131
|
+
data?: unknown;
|
|
132
|
+
}): ToolDispatchSignal | null;
|
|
133
|
+
/** Oldest-first eviction once the ledger (or the settle markers) overflows. */
|
|
134
|
+
private evict;
|
|
110
135
|
/** Every emitted dispatch, in first-seen order. */
|
|
111
136
|
get signals(): readonly ToolDispatchSignal[];
|
|
112
137
|
/**
|
|
@@ -188,10 +213,14 @@ export declare function isProgramToolName(name: string): boolean;
|
|
|
188
213
|
* Assert that a payload really is the dispatch event it claims to be.
|
|
189
214
|
*
|
|
190
215
|
* The normalizer is deliberately lenient (it also folds persisted logs, where a
|
|
191
|
-
* payload may predate the current declaration)
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
216
|
+
* payload may predate the current declaration). S1-E7 (honesty fix): this
|
|
217
|
+
* function is an OPT-IN gate — NO production consumer calls it; the shipped
|
|
218
|
+
* listeners fold tolerant (`'malformed'` synthesis) precisely because a
|
|
219
|
+
* throwing gate wired into every session event would brick the family on any
|
|
220
|
+
* upstream payload drift. Deployments that want fail-loud behavior call this
|
|
221
|
+
* from their own `session/event` listener. It never silently downgrades: an
|
|
222
|
+
* unrecognized event type or a payload missing a declared field throws a named
|
|
223
|
+
* \`ToolDispatchPayloadError\`.
|
|
195
224
|
* @param event - the session event to check.
|
|
196
225
|
* @returns nothing; throws when the payload violates the platform declaration.
|
|
197
226
|
*/
|
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.80",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -19,12 +19,14 @@
|
|
|
19
19
|
"default": "./lib/index.js"
|
|
20
20
|
},
|
|
21
21
|
"./package.json": "./package.json",
|
|
22
|
-
"./row-overrides.json": "./row-overrides.json"
|
|
22
|
+
"./row-overrides.json": "./row-overrides.json",
|
|
23
|
+
"./persisted-write-inventory.json": "./persisted-write-inventory.json"
|
|
23
24
|
},
|
|
24
25
|
"files": [
|
|
25
26
|
"lib/*.js",
|
|
26
27
|
"lib/types/**/*.d.ts",
|
|
27
|
-
"row-overrides.json"
|
|
28
|
+
"row-overrides.json",
|
|
29
|
+
"persisted-write-inventory.json"
|
|
28
30
|
],
|
|
29
31
|
"license": "MIT",
|
|
30
32
|
"dependencies": {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "usage",
|
|
4
|
+
"path": "<skillsRoot>/.usage.json",
|
|
5
|
+
"writer": "evolution-core/src/usage.ts",
|
|
6
|
+
"serializedBy": "transact",
|
|
7
|
+
"marker": "transactIo(io, usageFile(root)",
|
|
8
|
+
"state": [],
|
|
9
|
+
"note": "Skill usage counters. RMW under the IO backend's cross-process lock; the reader side rebuilds from the events log."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"id": "suppressed",
|
|
13
|
+
"path": "<skillsRoot>/.curator-suppressed.json",
|
|
14
|
+
"writer": "evolution-core/src/usage.ts",
|
|
15
|
+
"serializedBy": "transact",
|
|
16
|
+
"marker": "transactIo(io, suppressedFile(root)",
|
|
17
|
+
"state": [],
|
|
18
|
+
"note": "Curator suppression list (archived bundled skills that must not re-seed)."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "mutations",
|
|
22
|
+
"path": "<skillsRoot>/.mutations.json",
|
|
23
|
+
"writer": "evolution-core/src/mutations.ts",
|
|
24
|
+
"serializedBy": "transact",
|
|
25
|
+
"marker": "transactIo(io, mutationsFile(root)",
|
|
26
|
+
"state": [],
|
|
27
|
+
"note": "Skill mutation audit records (bounded ring)."
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "events",
|
|
31
|
+
"path": "<evolutionHome>/evolution/events.json",
|
|
32
|
+
"writer": "evolution-core/src/evolution-events.ts",
|
|
33
|
+
"serializedBy": "transact",
|
|
34
|
+
"marker": "transactIo(io, path",
|
|
35
|
+
"state": [],
|
|
36
|
+
"note": "The rc.68 evolution event log; feedback/skill-usage stats fold it."
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "memory",
|
|
40
|
+
"path": "<memoryRoot>/<target>.md",
|
|
41
|
+
"writer": "evolution-core/src/memory-store.ts",
|
|
42
|
+
"serializedBy": "transact",
|
|
43
|
+
"marker": "transactIo(this.io, fileFor(this.root, target)",
|
|
44
|
+
"state": [],
|
|
45
|
+
"note": "Durable memory documents written by the memory tool."
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "state",
|
|
49
|
+
"path": "<evolutionHome>/<review|curator|pending>-state.json",
|
|
50
|
+
"writer": "evolution-state-json/src/index.ts",
|
|
51
|
+
"serializedBy": "transact",
|
|
52
|
+
"marker": "transactIo(io(), join(root, file)",
|
|
53
|
+
"state": [
|
|
54
|
+
"evolution-state-json/src/index.ts :: recordGateWarned",
|
|
55
|
+
"evolution-state-json/src/index.ts :: corruptWritten",
|
|
56
|
+
"evolution-state-json/src/index.ts :: corruptWriteWarned"
|
|
57
|
+
],
|
|
58
|
+
"note": "The durable state stack (review/curator/pending). Its three warn sets are per-process de-duplication and are registered under N12."
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "curator-reports",
|
|
62
|
+
"path": "<evolutionHome>/reports/curator-*.json",
|
|
63
|
+
"writer": "evolution-curator/src/index.ts",
|
|
64
|
+
"serializedBy": "instance-claim",
|
|
65
|
+
"marker": "claimInstance(",
|
|
66
|
+
"instance": "evolution-curator",
|
|
67
|
+
"state": [],
|
|
68
|
+
"note": "Curator run reports + their retention sweep. No per-file lock: the writer holds the per-home instance claim, so a second curator yields instead of racing the sweep."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "skill-tree",
|
|
72
|
+
"path": "<skillsRoot>/**",
|
|
73
|
+
"writer": "evolution-core/src/skill-store.ts",
|
|
74
|
+
"serializedBy": "write-lock",
|
|
75
|
+
"marker": "LOCK_SUFFIX",
|
|
76
|
+
"state": [],
|
|
77
|
+
"note": "The skill tree itself. Per-target `<path>.lock` claims (pid:token body) guard every byte writer; destructive movers refuse a live lock."
|
|
78
|
+
}
|
|
79
|
+
]
|