@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 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 < 10; attempt += 1) try {
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
@@ -8,6 +8,7 @@
8
8
  * @module @deepseek-ai/dsh-evolution-core
9
9
  */
10
10
  export * from './curator.ts';
11
+ export * from './evolution-events.ts';
11
12
  export * from './gates.ts';
12
13
  export * from './events.ts';
13
14
  export * from './io.ts';
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.67",
4
+ "version": "0.1.0-rc.69",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },