@lmzhen/dsh-evolution-core 0.3.26 → 0.3.27
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 +34 -14
- package/lib/types/io.d.ts +12 -0
- package/lib/types/skill-store.d.ts +5 -2
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -58,6 +58,10 @@ function evolutionIoAdapter(provider) {
|
|
|
58
58
|
* us, so a leftover is stale by definition. Module-level by design: it must
|
|
59
59
|
* survive across `nodeEvolutionIo()` instances for the self-heal to be
|
|
60
60
|
* effective. (Not a pure function — the cross-call state is the intent.)
|
|
61
|
+
* V4-05: this set — not an mtime heuristic — is the ONLY signal that a
|
|
62
|
+
* same-pid lock is a leftover rather than a live in-process task, so it is the
|
|
63
|
+
* sole gate for the self-pid recycle branch. Exported (read-only in practice)
|
|
64
|
+
* so `io.spec.ts` can drive the self-heal path deterministically.
|
|
61
65
|
*/
|
|
62
66
|
const pendingSelfCleanup = /* @__PURE__ */ new Set();
|
|
63
67
|
/**
|
|
@@ -127,13 +131,19 @@ function nodeEvolutionIo() {
|
|
|
127
131
|
* retry budget (budget >= 2 x threshold), so a dead holder's lock is
|
|
128
132
|
* actually recoverable within one budget instead of being arithmetically
|
|
129
133
|
* unreachable.
|
|
130
|
-
* 0.3.21 (F-101): takeover re-reads the lock right before
|
|
131
|
-
* only
|
|
134
|
+
* 0.3.21 (F-101), V4-04: takeover re-reads the lock right before acting and
|
|
135
|
+
* only proceeds when the content still names the dead pid — a peer that
|
|
132
136
|
* acquired the lock after our stale probe wrote its own pid, and deleting a
|
|
133
|
-
* LIVE lock is the double-hold (concurrent task) the probe must prevent.
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
+
* LIVE lock is the double-hold (concurrent task) the probe must prevent. The
|
|
138
|
+
* re-read is necessary but not sufficient: two peers can both pass it, so the
|
|
139
|
+
* commit itself is an atomic rename to a unique name (only one peer's rename
|
|
140
|
+
* can succeed; the loser's source is gone). A naive rm lets the later peer
|
|
141
|
+
* delete the winner's freshly re-acquired live lock.
|
|
142
|
+
* 0.3.21 (F-367), V4-05: a self-pid lock is recycled ONLY when the failed
|
|
143
|
+
* release was recorded in pendingSelfCleanup — never on age alone, because an
|
|
144
|
+
* mtime over 1s is indistinguishable from a long task still executing in this
|
|
145
|
+
* process, and recycling that live lock would double-hold it. A failure to
|
|
146
|
+
* release in finally is recorded so the next write self-heals.
|
|
137
147
|
*/
|
|
138
148
|
const withWriteLock = async (path, task) => {
|
|
139
149
|
const lock = `${path}.lock`;
|
|
@@ -148,7 +158,7 @@ function nodeEvolutionIo() {
|
|
|
148
158
|
const holderContent = await readFile(lock, "utf8").catch(() => "");
|
|
149
159
|
const holder = Number(holderContent);
|
|
150
160
|
const holderAlive = Number.isInteger(holder) && holder > 0 && isAlive(holder);
|
|
151
|
-
if (holder === process.pid &&
|
|
161
|
+
if (holder === process.pid && pendingSelfCleanup.has(lock)) {
|
|
152
162
|
if (await readFile(lock, "utf8").catch(() => "") === holderContent) {
|
|
153
163
|
try {
|
|
154
164
|
await rm(lock, { force: true });
|
|
@@ -158,9 +168,13 @@ function nodeEvolutionIo() {
|
|
|
158
168
|
continue;
|
|
159
169
|
}
|
|
160
170
|
if (Date.now() - st.mtimeMs > 1e3 && !holderAlive) {
|
|
161
|
-
if (await readFile(lock, "utf8").catch(() => "") === holderContent)
|
|
162
|
-
|
|
163
|
-
|
|
171
|
+
if (await readFile(lock, "utf8").catch(() => "") === holderContent) {
|
|
172
|
+
const takeover = `${lock}.takeover-${process.pid}-${randomBytes(6).toString("hex")}`;
|
|
173
|
+
try {
|
|
174
|
+
await rename(lock, takeover);
|
|
175
|
+
await rm(takeover, { force: true }).catch(() => {});
|
|
176
|
+
} catch {}
|
|
177
|
+
}
|
|
164
178
|
continue;
|
|
165
179
|
}
|
|
166
180
|
} catch {
|
|
@@ -3325,8 +3339,11 @@ var SkillLibrary = class {
|
|
|
3325
3339
|
limits;
|
|
3326
3340
|
io;
|
|
3327
3341
|
onMutation;
|
|
3328
|
-
/** 0.3.21 (F-208):
|
|
3329
|
-
*
|
|
3342
|
+
/** 0.3.21 (F-208) + V4-20: cross-process RMW transactor. An explicitly
|
|
3343
|
+
* injected value wins; otherwise the IO backend's own `transact` is bound
|
|
3344
|
+
* when it provides one (cross-process atomicity on by default for any
|
|
3345
|
+
* transact-capable backend), and a backend without `transact` leaves this
|
|
3346
|
+
* undefined (each write falls back to the plain read→task→write path). */
|
|
3330
3347
|
transact;
|
|
3331
3348
|
/** 0.3.21 (F-208): in-process serialize queue so two concurrent mutators on
|
|
3332
3349
|
* one skill never interleave their read-modify-write (the cross-process layer
|
|
@@ -3337,7 +3354,10 @@ var SkillLibrary = class {
|
|
|
3337
3354
|
this.io = io;
|
|
3338
3355
|
this.limits = limits;
|
|
3339
3356
|
this.onMutation = onMutation;
|
|
3340
|
-
this.transact = transact
|
|
3357
|
+
this.transact = transact ?? (io.transact ? (ioLike, path, task) => {
|
|
3358
|
+
const t = ioLike.transact;
|
|
3359
|
+
return t ? t(path, task) : transactIo(ioLike, path, task);
|
|
3360
|
+
} : void 0);
|
|
3341
3361
|
this.serial = makeSerialQueue();
|
|
3342
3362
|
}
|
|
3343
3363
|
/**
|
|
@@ -4697,4 +4717,4 @@ function clampedNumber(value, fallback, opts) {
|
|
|
4697
4717
|
return value;
|
|
4698
4718
|
}
|
|
4699
4719
|
//#endregion
|
|
4700
|
-
export { AUTHORING_DESCRIPTION_BAR, COMBINED_REVIEW_PLAN_PROMPT, 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_HEALTH_THRESHOLDS, 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, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FORBIDDEN_CONTROL_KEYS, HEALTH_STAMP_RE, LOW_QUALITY_THRESHOLD, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, RESTRUCTURE_TARGET_RE, SKILLS_GUIDANCE, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, appendEvolutionEvent, applyCuratorFields, applyCuratorLifecycleFields, applyCuratorMetaFields, assessStructureHealth, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, clampedNumber, composePresetComposition, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, createGateSet, duplicateHeadings, emptyRecord, evaluateThreat, eventsFile, evolutionHome, evolutionIoAdapter, evolutionRoot, findDriftSignal, foldCuratorFields, foldTurn, frontmatterBlock, frontmatterYamlUnsafeValues, getRecord, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadSuppressedNames, loadUsage, makeSerialQueue, markAgentCreated, markerEntryName, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, observeEvent, overlongLines, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, readEvolutionEvents, readEvolutionTimeline, recordMutation, redactSecrets, relatedSkillNames, renameWithRetry, renderCuratorReportMarkdown, resolveOrigins, resolveSkillsRoot, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, verifyPromptBundle, yamlPlainScalarNeedsQuotes };
|
|
4720
|
+
export { AUTHORING_DESCRIPTION_BAR, COMBINED_REVIEW_PLAN_PROMPT, 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_HEALTH_THRESHOLDS, 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, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FORBIDDEN_CONTROL_KEYS, HEALTH_STAMP_RE, LOW_QUALITY_THRESHOLD, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, RESTRUCTURE_TARGET_RE, SKILLS_GUIDANCE, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, appendEvolutionEvent, applyCuratorFields, applyCuratorLifecycleFields, applyCuratorMetaFields, assessStructureHealth, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, clampedNumber, composePresetComposition, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, createGateSet, duplicateHeadings, emptyRecord, evaluateThreat, eventsFile, evolutionHome, evolutionIoAdapter, evolutionRoot, findDriftSignal, foldCuratorFields, foldTurn, frontmatterBlock, frontmatterYamlUnsafeValues, getRecord, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadSuppressedNames, loadUsage, makeSerialQueue, markAgentCreated, markerEntryName, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, observeEvent, overlongLines, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, pendingSelfCleanup, readEvolutionEvents, readEvolutionTimeline, recordMutation, redactSecrets, relatedSkillNames, renameWithRetry, renderCuratorReportMarkdown, resolveOrigins, resolveSkillsRoot, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, verifyPromptBundle, yamlPlainScalarNeedsQuotes };
|
package/lib/types/io.d.ts
CHANGED
|
@@ -53,6 +53,18 @@ export interface EvolutionIoLike {
|
|
|
53
53
|
export declare function transactIo(io: EvolutionIoLike, path: string, task: (current: string | null) => string | null | Promise<string | null>): Promise<void>;
|
|
54
54
|
/** Lazy adapter over an IO provider registry, shared by every evolution consumer. */
|
|
55
55
|
export declare function evolutionIoAdapter(provider: () => EvolutionIoLike): EvolutionIoLike;
|
|
56
|
+
/**
|
|
57
|
+
* F-367 (②): lock paths whose release (the finally `rm`) failed. The next write
|
|
58
|
+
* to the same file proactively recycles our own leftover lock — the holder is
|
|
59
|
+
* us, so a leftover is stale by definition. Module-level by design: it must
|
|
60
|
+
* survive across `nodeEvolutionIo()` instances for the self-heal to be
|
|
61
|
+
* effective. (Not a pure function — the cross-call state is the intent.)
|
|
62
|
+
* V4-05: this set — not an mtime heuristic — is the ONLY signal that a
|
|
63
|
+
* same-pid lock is a leftover rather than a live in-process task, so it is the
|
|
64
|
+
* sole gate for the self-pid recycle branch. Exported (read-only in practice)
|
|
65
|
+
* so `io.spec.ts` can drive the self-heal path deterministically.
|
|
66
|
+
*/
|
|
67
|
+
export declare const pendingSelfCleanup: Set<string>;
|
|
56
68
|
/**
|
|
57
69
|
* Retry a rename that a peer is temporarily holding on Windows (EPERM/EBUSY):
|
|
58
70
|
* a short 50ms backoff, at most 3 retries (~150ms budget), matching the
|
|
@@ -221,8 +221,11 @@ export declare class SkillLibrary {
|
|
|
221
221
|
readonly limits: SkillLimits;
|
|
222
222
|
private readonly io;
|
|
223
223
|
private readonly onMutation;
|
|
224
|
-
/** 0.3.21 (F-208):
|
|
225
|
-
*
|
|
224
|
+
/** 0.3.21 (F-208) + V4-20: cross-process RMW transactor. An explicitly
|
|
225
|
+
* injected value wins; otherwise the IO backend's own `transact` is bound
|
|
226
|
+
* when it provides one (cross-process atomicity on by default for any
|
|
227
|
+
* transact-capable backend), and a backend without `transact` leaves this
|
|
228
|
+
* undefined (each write falls back to the plain read→task→write path). */
|
|
226
229
|
private readonly transact;
|
|
227
230
|
/** 0.3.21 (F-208): in-process serialize queue so two concurrent mutators on
|
|
228
231
|
* one skill never interleave their read-modify-write (the cross-process layer
|
package/package.json
CHANGED