@lmzhen/dsh-evolution-core 0.3.25 → 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 +37 -15
- package/lib/types/io.d.ts +12 -0
- package/lib/types/skill-store.d.ts +12 -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 {
|
|
@@ -2937,7 +2951,9 @@ function skillDir(root, name) {
|
|
|
2937
2951
|
/** Dot-prefixed on-disk marker name. SINGLE source: `list()` matches directory
|
|
2938
2952
|
* entries against this name, and path builders must never hardcode a marker
|
|
2939
2953
|
* literal (N-1: the rc.49 exists()-probe convergence dropped the dot,
|
|
2940
|
-
* poisoning every protectedBy/managed report).
|
|
2954
|
+
* poisoning every protectedBy/managed report). Exported for cross-package
|
|
2955
|
+
* consumers that must probe markers without re-deriving the name (curator's
|
|
2956
|
+
* archive-copy bundled probe, 0.3.26 V4-02). */
|
|
2941
2957
|
function markerEntryName(marker) {
|
|
2942
2958
|
return `.${marker}`;
|
|
2943
2959
|
}
|
|
@@ -3323,8 +3339,11 @@ var SkillLibrary = class {
|
|
|
3323
3339
|
limits;
|
|
3324
3340
|
io;
|
|
3325
3341
|
onMutation;
|
|
3326
|
-
/** 0.3.21 (F-208):
|
|
3327
|
-
*
|
|
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). */
|
|
3328
3347
|
transact;
|
|
3329
3348
|
/** 0.3.21 (F-208): in-process serialize queue so two concurrent mutators on
|
|
3330
3349
|
* one skill never interleave their read-modify-write (the cross-process layer
|
|
@@ -3335,7 +3354,10 @@ var SkillLibrary = class {
|
|
|
3335
3354
|
this.io = io;
|
|
3336
3355
|
this.limits = limits;
|
|
3337
3356
|
this.onMutation = onMutation;
|
|
3338
|
-
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);
|
|
3339
3361
|
this.serial = makeSerialQueue();
|
|
3340
3362
|
}
|
|
3341
3363
|
/**
|
|
@@ -4695,4 +4717,4 @@ function clampedNumber(value, fallback, opts) {
|
|
|
4695
4717
|
return value;
|
|
4696
4718
|
}
|
|
4697
4719
|
//#endregion
|
|
4698
|
-
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, 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
|
|
@@ -108,6 +108,13 @@ export declare function resolveOrigins(headerOrigin: string | undefined, isRevie
|
|
|
108
108
|
approval: 'foreground' | 'background_review';
|
|
109
109
|
library: WriteOrigin;
|
|
110
110
|
};
|
|
111
|
+
/** Dot-prefixed on-disk marker name. SINGLE source: `list()` matches directory
|
|
112
|
+
* entries against this name, and path builders must never hardcode a marker
|
|
113
|
+
* literal (N-1: the rc.49 exists()-probe convergence dropped the dot,
|
|
114
|
+
* poisoning every protectedBy/managed report). Exported for cross-package
|
|
115
|
+
* consumers that must probe markers without re-deriving the name (curator's
|
|
116
|
+
* archive-copy bundled probe, 0.3.26 V4-02). */
|
|
117
|
+
export declare function markerEntryName(marker: 'bundled' | 'hub-installed' | 'pinned' | 'hermes-managed'): string;
|
|
111
118
|
export interface Frontmatter {
|
|
112
119
|
name?: string;
|
|
113
120
|
description?: string;
|
|
@@ -214,8 +221,11 @@ export declare class SkillLibrary {
|
|
|
214
221
|
readonly limits: SkillLimits;
|
|
215
222
|
private readonly io;
|
|
216
223
|
private readonly onMutation;
|
|
217
|
-
/** 0.3.21 (F-208):
|
|
218
|
-
*
|
|
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). */
|
|
219
229
|
private readonly transact;
|
|
220
230
|
/** 0.3.21 (F-208): in-process serialize queue so two concurrent mutators on
|
|
221
231
|
* one skill never interleave their read-modify-write (the cross-process layer
|
package/package.json
CHANGED