@lmzhen/dsh-evolution-core 0.1.0-rc.67 → 0.1.0-rc.69
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 +96 -3
- package/lib/types/evolution-events.d.ts +50 -0
- package/lib/types/index.d.ts +1 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -67,11 +67,14 @@ function nodeEvolutionIo() {
|
|
|
67
67
|
* the holder pid it carries (rc.66): a LIVE holder is never stolen, so a
|
|
68
68
|
* slow writer no longer loses its lock to a peer at the 5s mark (the
|
|
69
69
|
* takeover is the only best-effort surface; the retry budget fails loud —
|
|
70
|
-
* rc.65 — instead of ever proceeding unlocked).
|
|
70
|
+
* rc.65 — instead of ever proceeding unlocked). Budget = 40 * 50ms (~2s,
|
|
71
|
+
* rc.69): 8-writer contention bursts on a loaded CI runner exceed 10
|
|
72
|
+
* attempts (500ms), and a fail-loud throw was observed instead of a clean
|
|
73
|
+
* serialization.
|
|
71
74
|
*/
|
|
72
75
|
const withWriteLock = async (path, task) => {
|
|
73
76
|
const lock = `${path}.lock`;
|
|
74
|
-
for (let attempt = 0; attempt <
|
|
77
|
+
for (let attempt = 0; attempt < 40; attempt += 1) try {
|
|
75
78
|
await writeFile(lock, String(process.pid), { flag: "wx" });
|
|
76
79
|
try {
|
|
77
80
|
return await task();
|
|
@@ -716,6 +719,96 @@ function computeLifecycleTransitions(usage, config, now = /* @__PURE__ */ new Da
|
|
|
716
719
|
return result;
|
|
717
720
|
}
|
|
718
721
|
//#endregion
|
|
722
|
+
//#region lib/types/evolution-events.js
|
|
723
|
+
/**
|
|
724
|
+
* Self-evolution event log (rc.68): an append-only sidecar under
|
|
725
|
+
* `$DSH_HOME/evolution/events.json` that is the single source of truth for
|
|
726
|
+
* the self-improvement loop. Feedback increments and learn actions share one
|
|
727
|
+
* ordered timeline (`seq` is the ordering key), so "feedback before/after a
|
|
728
|
+
* learn on target X" is answerable. The aggregate `feedback.json` is a
|
|
729
|
+
* rebuildable boot cache, never the truth.
|
|
730
|
+
*
|
|
731
|
+
* The malformed-refusal posture matches the whole sidecar family (rc.65): an
|
|
732
|
+
* append NEVER rewrites a corrupt log — the bytes stay untouched.
|
|
733
|
+
*/
|
|
734
|
+
const EVENT_LOG_VERSION = 1;
|
|
735
|
+
function eventsFile(home) {
|
|
736
|
+
return join(home, "evolution", "events.json");
|
|
737
|
+
}
|
|
738
|
+
function isEventRecord(event) {
|
|
739
|
+
return typeof event === "object" && event !== null && typeof event.seq === "number";
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Parse an event log body. A missing file, a whitespace-only file (rc.69:
|
|
743
|
+
* rebuildable, NOT malformed) or a corrupt one reads as empty; corrupt content
|
|
744
|
+
* is still refused on append, never overwritten.
|
|
745
|
+
*/
|
|
746
|
+
function parseEvolutionEvents(raw) {
|
|
747
|
+
if (raw === null || raw.trim() === "") return [];
|
|
748
|
+
try {
|
|
749
|
+
const parsed = JSON.parse(raw);
|
|
750
|
+
if (!Array.isArray(parsed.events)) return [];
|
|
751
|
+
return parsed.events.filter(isEventRecord);
|
|
752
|
+
} catch {
|
|
753
|
+
return [];
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Append one event under the write lock (rc.68): `seq` = current max + 1
|
|
758
|
+
* computed inside the transact, so two processes appending concurrently never
|
|
759
|
+
* collide. A malformed log is refused (bytes preserved) and the append fails.
|
|
760
|
+
* Returns the assigned seq.
|
|
761
|
+
*/
|
|
762
|
+
async function appendEvolutionEvent(io, path, event) {
|
|
763
|
+
let assigned = 0;
|
|
764
|
+
await transactIo(io, path, (current) => {
|
|
765
|
+
if (current !== null && current.trim() !== "") try {
|
|
766
|
+
JSON.parse(current);
|
|
767
|
+
} catch {
|
|
768
|
+
return Promise.resolve(current);
|
|
769
|
+
}
|
|
770
|
+
const events = parseEvolutionEvents(current);
|
|
771
|
+
const maxSeq = events.reduce((max, entry) => Math.max(max, entry.seq), 0);
|
|
772
|
+
const record = {
|
|
773
|
+
...event,
|
|
774
|
+
seq: maxSeq + 1,
|
|
775
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
776
|
+
};
|
|
777
|
+
assigned = record.seq;
|
|
778
|
+
return Promise.resolve(JSON.stringify({
|
|
779
|
+
version: 1,
|
|
780
|
+
events: [...events, record]
|
|
781
|
+
}, null, 2));
|
|
782
|
+
});
|
|
783
|
+
if (assigned === 0) throw new Error(`evolution event log is malformed and was not touched: ${path}`);
|
|
784
|
+
return assigned;
|
|
785
|
+
}
|
|
786
|
+
/** Read the event log; a missing/whitespace-only file reads as empty,
|
|
787
|
+
* corrupt content is flagged (and refused on append). */
|
|
788
|
+
async function readEvolutionEvents(io, path) {
|
|
789
|
+
const raw = await io.readText(path);
|
|
790
|
+
if (raw === null || raw.trim() === "") return {
|
|
791
|
+
events: [],
|
|
792
|
+
malformed: false
|
|
793
|
+
};
|
|
794
|
+
try {
|
|
795
|
+
const parsed = JSON.parse(raw);
|
|
796
|
+
if (!Array.isArray(parsed.events)) return {
|
|
797
|
+
events: [],
|
|
798
|
+
malformed: true
|
|
799
|
+
};
|
|
800
|
+
return {
|
|
801
|
+
events: parsed.events.filter(isEventRecord),
|
|
802
|
+
malformed: false
|
|
803
|
+
};
|
|
804
|
+
} catch {
|
|
805
|
+
return {
|
|
806
|
+
events: [],
|
|
807
|
+
malformed: true
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
//#endregion
|
|
719
812
|
//#region lib/types/prompts.js
|
|
720
813
|
/**
|
|
721
814
|
* Review and curation prompts adapted from Hermes Agent
|
|
@@ -3032,4 +3125,4 @@ function evolutionHome(env = process.env) {
|
|
|
3032
3125
|
return join(env.DSH_HOME ?? join(homedir(), ".dsh"), "evolution");
|
|
3033
3126
|
}
|
|
3034
3127
|
//#endregion
|
|
3035
|
-
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_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EvolutionGateSet, LOW_QUALITY_THRESHOLD, MAX_DESCRIPTION_LENGTH, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MUTATIONS_FILE_VERSION, MemoryStore, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, SKILLS_GUIDANCE, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, applyCuratorFields, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, createGateSet, emptyRecord, evaluateThreat, evolutionHome, evolutionIoAdapter, foldCuratorFields, foldTurn, getRecord, latestActivityAt, lifecycleCandidate, loadMutations, loadSuppressedNames, loadUsage, markAgentCreated, memoryRoot, mutateUsage, mutationsFile, nodeEvolutionIo, normalizeUsageRecord, observeEvent, parseCuratorNominations, parseFrontmatter, recordMutation, relatedSkillNames, renderCuratorReportMarkdown, resolveOrigins, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, validateFrontmatter, verifyPromptBundle };
|
|
3128
|
+
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_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EVENT_LOG_VERSION, EvolutionGateSet, LOW_QUALITY_THRESHOLD, MAX_DESCRIPTION_LENGTH, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MUTATIONS_FILE_VERSION, MemoryStore, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, 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, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, createGateSet, emptyRecord, evaluateThreat, eventsFile, evolutionHome, evolutionIoAdapter, foldCuratorFields, foldTurn, getRecord, latestActivityAt, lifecycleCandidate, loadMutations, loadSuppressedNames, loadUsage, markAgentCreated, memoryRoot, mutateUsage, mutationsFile, nodeEvolutionIo, normalizeUsageRecord, observeEvent, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, readEvolutionEvents, recordMutation, relatedSkillNames, renderCuratorReportMarkdown, resolveOrigins, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, validateFrontmatter, verifyPromptBundle };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-evolution event log (rc.68): an append-only sidecar under
|
|
3
|
+
* `$DSH_HOME/evolution/events.json` that is the single source of truth for
|
|
4
|
+
* the self-improvement loop. Feedback increments and learn actions share one
|
|
5
|
+
* ordered timeline (`seq` is the ordering key), so "feedback before/after a
|
|
6
|
+
* learn on target X" is answerable. The aggregate `feedback.json` is a
|
|
7
|
+
* rebuildable boot cache, never the truth.
|
|
8
|
+
*
|
|
9
|
+
* The malformed-refusal posture matches the whole sidecar family (rc.65): an
|
|
10
|
+
* append NEVER rewrites a corrupt log — the bytes stay untouched.
|
|
11
|
+
*/
|
|
12
|
+
import { type EvolutionIoLike } from './io.ts';
|
|
13
|
+
export declare const EVENT_LOG_VERSION = 1;
|
|
14
|
+
export interface EvolutionEvent {
|
|
15
|
+
/** Global monotonic order key, assigned inside the append transact. */
|
|
16
|
+
seq: number;
|
|
17
|
+
/** ISO timestamp at append time. */
|
|
18
|
+
at: string;
|
|
19
|
+
/** Tagged-union discriminator: feedback increments vs learn actions. */
|
|
20
|
+
type: 'feedback' | 'learn';
|
|
21
|
+
target?: string | undefined;
|
|
22
|
+
kind?: 'skill' | 'session' | undefined;
|
|
23
|
+
rating?: 'positive' | 'negative' | undefined;
|
|
24
|
+
note?: string | undefined;
|
|
25
|
+
source?: string | undefined;
|
|
26
|
+
request?: string | undefined;
|
|
27
|
+
}
|
|
28
|
+
export declare function eventsFile(home: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Parse an event log body. A missing file, a whitespace-only file (rc.69:
|
|
31
|
+
* rebuildable, NOT malformed) or a corrupt one reads as empty; corrupt content
|
|
32
|
+
* is still refused on append, never overwritten.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseEvolutionEvents(raw: string | null): EvolutionEvent[];
|
|
35
|
+
/**
|
|
36
|
+
* Append one event under the write lock (rc.68): `seq` = current max + 1
|
|
37
|
+
* computed inside the transact, so two processes appending concurrently never
|
|
38
|
+
* collide. A malformed log is refused (bytes preserved) and the append fails.
|
|
39
|
+
* Returns the assigned seq.
|
|
40
|
+
*/
|
|
41
|
+
export declare function appendEvolutionEvent(io: EvolutionIoLike, path: string, event: Omit<EvolutionEvent, 'seq' | 'at'>): Promise<number>;
|
|
42
|
+
export interface EventLogRead {
|
|
43
|
+
events: EvolutionEvent[];
|
|
44
|
+
/** True when the file existed but could not be parsed (loss case; never overwritten). */
|
|
45
|
+
malformed: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** Read the event log; a missing/whitespace-only file reads as empty,
|
|
48
|
+
* corrupt content is flagged (and refused on append). */
|
|
49
|
+
export declare function readEvolutionEvents(io: EvolutionIoLike, path: string): Promise<EventLogRead>;
|
|
50
|
+
//# sourceMappingURL=evolution-events.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-core",
|
|
3
3
|
"description": "Shared stores, prompts, signals and lifecycle logic for the dsh-evolution plugin family (community build)",
|
|
4
|
-
"version": "0.1.0-rc.
|
|
4
|
+
"version": "0.1.0-rc.69",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|