@lmzhen/dsh-evolution-core 0.1.0-rc.71 → 0.1.0-rc.72
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 +74 -20
- package/lib/types/evolution-events.d.ts +26 -6
- package/lib/types/usage.d.ts +14 -2
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -294,8 +294,21 @@ async function mutateUsage(root, io, task) {
|
|
|
294
294
|
* folds copy this set only.
|
|
295
295
|
*/
|
|
296
296
|
function applyCuratorFields(disk, curated) {
|
|
297
|
+
applyCuratorMetaFields(disk, curated);
|
|
298
|
+
applyCuratorLifecycleFields(disk, curated);
|
|
299
|
+
}
|
|
300
|
+
/** Copy only the lifecycle pair (state/archived_at) — see the ownership split
|
|
301
|
+
* rationale on {@link applyCuratorMetaFields}. */
|
|
302
|
+
function applyCuratorLifecycleFields(disk, curated) {
|
|
297
303
|
disk.state = curated.state;
|
|
298
304
|
disk.archived_at = curated.archived_at;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Copy the recomputed meta pair (quality_score/quality_warn + the
|
|
308
|
+
* marker-mirrored pin flag) — refreshed tree-wide each run by design, so a
|
|
309
|
+
* concurrent curator run's lifecycle changes are never reverted by them.
|
|
310
|
+
*/
|
|
311
|
+
function applyCuratorMetaFields(disk, curated) {
|
|
299
312
|
disk.quality_score = curated.quality_score;
|
|
300
313
|
disk.quality_warn = curated.quality_warn;
|
|
301
314
|
disk.pinned = curated.pinned;
|
|
@@ -305,13 +318,20 @@ function applyCuratorFields(disk, curated) {
|
|
|
305
318
|
* each curated record is projected onto its disk peer by copying only the
|
|
306
319
|
* curator-owned fields, so a concurrent tool-side bump between snapshot and
|
|
307
320
|
* save survives. Records absent from the snapshot are left untouched; a
|
|
308
|
-
* curated record with no disk peer is seeded from the snapshot.
|
|
321
|
+
* curated record with no disk peer is seeded from the snapshot. `stateOwned`
|
|
322
|
+
* (rc.72 H-1) restricts the lifecycle pair to the names this run ACTUALLY
|
|
323
|
+
* transitioned — a concurrent curator run's archive/restore is never reverted
|
|
324
|
+
* by a stale snapshot; without it both pairs apply everywhere.
|
|
309
325
|
*/
|
|
310
|
-
function foldCuratorFields(disk, curated) {
|
|
326
|
+
function foldCuratorFields(disk, curated, stateOwned) {
|
|
311
327
|
for (const [name, record] of curated) {
|
|
312
328
|
const diskRecord = disk.get(name);
|
|
313
|
-
if (diskRecord)
|
|
314
|
-
|
|
329
|
+
if (!diskRecord) {
|
|
330
|
+
disk.set(name, { ...record });
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
applyCuratorMetaFields(diskRecord, record);
|
|
334
|
+
if (stateOwned === void 0 || stateOwned.has(name)) applyCuratorLifecycleFields(diskRecord, record);
|
|
315
335
|
}
|
|
316
336
|
}
|
|
317
337
|
async function saveUsage(root, map, io = nodeEvolutionIo()) {
|
|
@@ -728,8 +748,13 @@ function computeLifecycleTransitions(usage, config, now = /* @__PURE__ */ new Da
|
|
|
728
748
|
* learn on target X" is answerable. The aggregate `feedback.json` is a
|
|
729
749
|
* rebuildable boot cache, never the truth.
|
|
730
750
|
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
751
|
+
* Rotation (rc.71, 007 design): when the active log reaches
|
|
752
|
+
* `EVENT_LOG_ROTATE_AT` the older half is split into an archive
|
|
753
|
+
* (`events-<lastArchivedSeq>.json`); the boot timeline merges active +
|
|
754
|
+
* archives and dedupes by seq (active copy wins), so the rotation crash window
|
|
755
|
+
* yields the identical timeline. Archival naming is STRICTLY numeric
|
|
756
|
+
* (`/^events-\d+\.json$/`) — user files under the same directory are never
|
|
757
|
+
* read as archives and never pruned (rc.72 G-2).
|
|
733
758
|
*/
|
|
734
759
|
const EVENT_LOG_VERSION = 1;
|
|
735
760
|
/** Active-log split point (rc.71): when the active log reaches this many events
|
|
@@ -743,6 +768,9 @@ const EVENT_LOG_RETAIN_ARCHIVES = 10;
|
|
|
743
768
|
/** Archive file prefix: `events-<lastArchivedSeq>.json`. The active file is
|
|
744
769
|
* `events.json` and never matches this glob. */
|
|
745
770
|
const EVENT_ARCHIVE_PREFIX = "events-";
|
|
771
|
+
/** Archive naming is strictly numeric: a user file such as `events-backup.json`
|
|
772
|
+
* under the same directory is neither read into the timeline nor pruned. */
|
|
773
|
+
const EVENT_ARCHIVE_RE = /^events-(\d+)\.json$/;
|
|
746
774
|
function eventsFile(home) {
|
|
747
775
|
return join(home, "evolution", "events.json");
|
|
748
776
|
}
|
|
@@ -770,6 +798,17 @@ function parseEvolutionEvents(raw) {
|
|
|
770
798
|
}
|
|
771
799
|
}
|
|
772
800
|
/**
|
|
801
|
+
* List the numeric archives under the log's directory, sorted ascending by
|
|
802
|
+
* their last-archived seq. Single glob predicate for the timeline, the
|
|
803
|
+
* retention pass and the feedback migration check (rc.72 H-3).
|
|
804
|
+
*/
|
|
805
|
+
async function listEventArchives(io, path) {
|
|
806
|
+
const dir = dirname(path);
|
|
807
|
+
return (await io.list(dir)).filter((name) => EVENT_ARCHIVE_RE.test(name)).sort((a, b) => {
|
|
808
|
+
return Number.parseInt(a.slice(7, a.length - 5), 10) - Number.parseInt(b.slice(7, b.length - 5), 10);
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
773
812
|
* Append one event under the write lock (rc.68): `seq` = current max + 1
|
|
774
813
|
* computed inside the transact, so two processes appending concurrently never
|
|
775
814
|
* collide. A malformed log is refused (bytes preserved) and the append fails.
|
|
@@ -782,6 +821,11 @@ function parseEvolutionEvents(raw) {
|
|
|
782
821
|
* archive write and active write leaves both copies, which the timeline merge
|
|
783
822
|
* dedupes by seq. An archive-write failure aborts the append (active keeps the
|
|
784
823
|
* full old content — no loss) and the caller's best-effort handling applies.
|
|
824
|
+
*
|
|
825
|
+
* rc.72 G-1: when the ACTIVE is missing/whitespace but archives exist (a
|
|
826
|
+
* deleted active, or B-2 self-heal), seq derivation consults the archive names
|
|
827
|
+
* — the active restarts AFTER the highest archived seq, never at 1, so a new
|
|
828
|
+
* event can never shadow an archived one in the seq-deduped timeline.
|
|
785
829
|
*/
|
|
786
830
|
async function appendEvolutionEvent(io, path, event, rotateAt = EVENT_LOG_ROTATE_AT) {
|
|
787
831
|
let assigned = 0;
|
|
@@ -792,7 +836,8 @@ async function appendEvolutionEvent(io, path, event, rotateAt = EVENT_LOG_ROTATE
|
|
|
792
836
|
return current;
|
|
793
837
|
}
|
|
794
838
|
const nextEvents = await rotateIfDue(io, path, parseEvolutionEvents(current), rotateAt);
|
|
795
|
-
|
|
839
|
+
let maxSeq = nextEvents.reduce((max, entry) => Math.max(max, entry.seq), 0);
|
|
840
|
+
if (maxSeq === 0) for (const name of await listEventArchives(io, path)) maxSeq = Math.max(maxSeq, Number.parseInt(name.slice(7, name.length - 5), 10));
|
|
796
841
|
const record = {
|
|
797
842
|
...event,
|
|
798
843
|
seq: maxSeq + 1,
|
|
@@ -812,14 +857,16 @@ async function appendEvolutionEvent(io, path, event, rotateAt = EVENT_LOG_ROTATE
|
|
|
812
857
|
* `events-<lastArchivedSeq>.json` (await — a failed archive write aborts the
|
|
813
858
|
* append so the active is never truncated without its copy), old archives are
|
|
814
859
|
* pruned, and the newer half is returned as the next active body. No-op when
|
|
815
|
-
* under the threshold.
|
|
860
|
+
* under the threshold; `rotateAt < 2` is a guarded no-op (rc.72 G-1: a
|
|
861
|
+
* one-event rotate would archive everything and restart seqs at 1).
|
|
816
862
|
*/
|
|
817
863
|
async function rotateIfDue(io, path, events, rotateAt) {
|
|
818
|
-
if (events.length < rotateAt) return events;
|
|
864
|
+
if (rotateAt < 2 || events.length < rotateAt) return events;
|
|
819
865
|
const mid = Math.ceil(events.length / 2);
|
|
820
866
|
const head = events.slice(0, mid);
|
|
821
867
|
const tail = events.slice(mid);
|
|
822
|
-
|
|
868
|
+
if (tail.length === 0) return events;
|
|
869
|
+
const anchor = tail[0]?.seq ?? 0;
|
|
823
870
|
const archivePath = join(dirname(path), `${EVENT_ARCHIVE_PREFIX}${anchor - 1}.json`);
|
|
824
871
|
await io.writeText(archivePath, JSON.stringify({
|
|
825
872
|
version: 1,
|
|
@@ -831,21 +878,28 @@ async function rotateIfDue(io, path, events, rotateAt) {
|
|
|
831
878
|
/**
|
|
832
879
|
* Prune old event archives (rc.71): keep the newest `EVENT_LOG_RETAIN_ARCHIVES`.
|
|
833
880
|
* The name's numeric part is the last archived seq, so ordering is NUMERIC —
|
|
834
|
-
* lexicographic would rank `events-10` before `events-2`.
|
|
835
|
-
*
|
|
881
|
+
* lexicographic would rank `events-10` before `events-2`. Only strictly
|
|
882
|
+
* numeric names participate (rc.72 G-2: user files are never deleted).
|
|
883
|
+
* Best-effort per removal; exported for the retention test.
|
|
836
884
|
*/
|
|
837
885
|
async function retainEventArchives(io, path) {
|
|
838
886
|
const dir = dirname(path);
|
|
839
|
-
const names =
|
|
840
|
-
const archiveSeq = (name) => Number.parseInt(name.slice(7, name.length - 5), 10) || 0;
|
|
841
|
-
names.sort((a, b) => archiveSeq(a) - archiveSeq(b));
|
|
887
|
+
const names = await listEventArchives(io, path);
|
|
842
888
|
const excess = names.slice(0, Math.max(0, names.length - 10));
|
|
843
889
|
for (const name of excess) await io.remove(join(dir, name)).catch(() => {});
|
|
844
890
|
}
|
|
845
891
|
/** Read the event log; a missing/whitespace-only file reads as empty,
|
|
846
892
|
* corrupt content is flagged (and refused on append). */
|
|
847
893
|
async function readEvolutionEvents(io, path) {
|
|
848
|
-
|
|
894
|
+
let raw;
|
|
895
|
+
try {
|
|
896
|
+
raw = await io.readText(path);
|
|
897
|
+
} catch {
|
|
898
|
+
return {
|
|
899
|
+
events: [],
|
|
900
|
+
malformed: true
|
|
901
|
+
};
|
|
902
|
+
}
|
|
849
903
|
if (raw === null || raw.trim() === "") return {
|
|
850
904
|
events: [],
|
|
851
905
|
malformed: false
|
|
@@ -871,14 +925,14 @@ async function readEvolutionEvents(io, path) {
|
|
|
871
925
|
* Read the full timeline (rc.71): active log + all archives, merged by seq
|
|
872
926
|
* (active copy wins, duplicates only arise from the rotation crash window),
|
|
873
927
|
* sorted ascending. Per-file malformed flag as in `readEvolutionEvents`; a
|
|
874
|
-
* malformed ARCHIVE is skipped
|
|
928
|
+
* malformed (or unreadable) ARCHIVE is skipped — it never bricks the boot and
|
|
929
|
+
* it is still flagged.
|
|
875
930
|
*/
|
|
876
931
|
async function readEvolutionTimeline(io, path) {
|
|
877
932
|
const dir = dirname(path);
|
|
878
|
-
const names = (await io.list(dir)).filter((name) => name.startsWith("events-") && name.endsWith(".json")).sort();
|
|
879
933
|
let malformed = false;
|
|
880
934
|
const bySeq = /* @__PURE__ */ new Map();
|
|
881
|
-
for (const name of
|
|
935
|
+
for (const name of await listEventArchives(io, path)) {
|
|
882
936
|
const read = await readEvolutionEvents(io, join(dir, name));
|
|
883
937
|
if (read.malformed) malformed = true;
|
|
884
938
|
for (const event of read.events) bySeq.set(event.seq, event);
|
|
@@ -3208,4 +3262,4 @@ function evolutionHome(env = process.env) {
|
|
|
3208
3262
|
return join(env.DSH_HOME ?? join(homedir(), ".dsh"), "evolution");
|
|
3209
3263
|
}
|
|
3210
3264
|
//#endregion
|
|
3211
|
-
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_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, 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, readEvolutionTimeline, recordMutation, relatedSkillNames, renderCuratorReportMarkdown, resolveOrigins, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, validateFrontmatter, verifyPromptBundle };
|
|
3265
|
+
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_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, 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, applyCuratorLifecycleFields, applyCuratorMetaFields, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, createGateSet, emptyRecord, evaluateThreat, eventsFile, evolutionHome, evolutionIoAdapter, foldCuratorFields, foldTurn, getRecord, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadSuppressedNames, loadUsage, markAgentCreated, memoryRoot, mutateUsage, mutationsFile, nodeEvolutionIo, normalizeUsageRecord, observeEvent, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, readEvolutionEvents, readEvolutionTimeline, recordMutation, relatedSkillNames, renderCuratorReportMarkdown, resolveOrigins, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, validateFrontmatter, verifyPromptBundle };
|
|
@@ -6,8 +6,13 @@
|
|
|
6
6
|
* learn on target X" is answerable. The aggregate `feedback.json` is a
|
|
7
7
|
* rebuildable boot cache, never the truth.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* Rotation (rc.71, 007 design): when the active log reaches
|
|
10
|
+
* `EVENT_LOG_ROTATE_AT` the older half is split into an archive
|
|
11
|
+
* (`events-<lastArchivedSeq>.json`); the boot timeline merges active +
|
|
12
|
+
* archives and dedupes by seq (active copy wins), so the rotation crash window
|
|
13
|
+
* yields the identical timeline. Archival naming is STRICTLY numeric
|
|
14
|
+
* (`/^events-\d+\.json$/`) — user files under the same directory are never
|
|
15
|
+
* read as archives and never pruned (rc.72 G-2).
|
|
11
16
|
*/
|
|
12
17
|
import { type EvolutionIoLike } from './io.ts';
|
|
13
18
|
export declare const EVENT_LOG_VERSION = 1;
|
|
@@ -48,6 +53,12 @@ export declare function eventsFile(home: string): string;
|
|
|
48
53
|
* sidecar's per-field normalization on read).
|
|
49
54
|
*/
|
|
50
55
|
export declare function parseEvolutionEvents(raw: string | null): EvolutionEvent[];
|
|
56
|
+
/**
|
|
57
|
+
* List the numeric archives under the log's directory, sorted ascending by
|
|
58
|
+
* their last-archived seq. Single glob predicate for the timeline, the
|
|
59
|
+
* retention pass and the feedback migration check (rc.72 H-3).
|
|
60
|
+
*/
|
|
61
|
+
export declare function listEventArchives(io: EvolutionIoLike, path: string): Promise<string[]>;
|
|
51
62
|
/**
|
|
52
63
|
* Append one event under the write lock (rc.68): `seq` = current max + 1
|
|
53
64
|
* computed inside the transact, so two processes appending concurrently never
|
|
@@ -61,13 +72,19 @@ export declare function parseEvolutionEvents(raw: string | null): EvolutionEvent
|
|
|
61
72
|
* archive write and active write leaves both copies, which the timeline merge
|
|
62
73
|
* dedupes by seq. An archive-write failure aborts the append (active keeps the
|
|
63
74
|
* full old content — no loss) and the caller's best-effort handling applies.
|
|
75
|
+
*
|
|
76
|
+
* rc.72 G-1: when the ACTIVE is missing/whitespace but archives exist (a
|
|
77
|
+
* deleted active, or B-2 self-heal), seq derivation consults the archive names
|
|
78
|
+
* — the active restarts AFTER the highest archived seq, never at 1, so a new
|
|
79
|
+
* event can never shadow an archived one in the seq-deduped timeline.
|
|
64
80
|
*/
|
|
65
81
|
export declare function appendEvolutionEvent(io: EvolutionIoLike, path: string, event: Omit<EvolutionEvent, 'seq' | 'at'>, rotateAt?: number): Promise<number>;
|
|
66
82
|
/**
|
|
67
83
|
* Prune old event archives (rc.71): keep the newest `EVENT_LOG_RETAIN_ARCHIVES`.
|
|
68
84
|
* The name's numeric part is the last archived seq, so ordering is NUMERIC —
|
|
69
|
-
* lexicographic would rank `events-10` before `events-2`.
|
|
70
|
-
*
|
|
85
|
+
* lexicographic would rank `events-10` before `events-2`. Only strictly
|
|
86
|
+
* numeric names participate (rc.72 G-2: user files are never deleted).
|
|
87
|
+
* Best-effort per removal; exported for the retention test.
|
|
71
88
|
*/
|
|
72
89
|
export declare function retainEventArchives(io: EvolutionIoLike, path: string): Promise<void>;
|
|
73
90
|
export interface EventLogRead {
|
|
@@ -75,7 +92,9 @@ export interface EventLogRead {
|
|
|
75
92
|
/** True when the body is not valid JSON (syntax-level damage): refused on
|
|
76
93
|
* append, bytes untouched. Well-formed JSON with a damaged `events` field
|
|
77
94
|
* is REPLACEABLE garbage — reads as empty and is rewritten at the next
|
|
78
|
-
* append (rc.70 F-1: read and append agree on the same boundary).
|
|
95
|
+
* append (rc.70 F-1: read and append agree on the same boundary). A READ
|
|
96
|
+
* error (EISDIR etc.) also flags malformed — the file is unusable either
|
|
97
|
+
* way and is never overwritten (the append read would fail identically). */
|
|
79
98
|
malformed: boolean;
|
|
80
99
|
}
|
|
81
100
|
/** Read the event log; a missing/whitespace-only file reads as empty,
|
|
@@ -85,7 +104,8 @@ export declare function readEvolutionEvents(io: EvolutionIoLike, path: string):
|
|
|
85
104
|
* Read the full timeline (rc.71): active log + all archives, merged by seq
|
|
86
105
|
* (active copy wins, duplicates only arise from the rotation crash window),
|
|
87
106
|
* sorted ascending. Per-file malformed flag as in `readEvolutionEvents`; a
|
|
88
|
-
* malformed ARCHIVE is skipped
|
|
107
|
+
* malformed (or unreadable) ARCHIVE is skipped — it never bricks the boot and
|
|
108
|
+
* it is still flagged.
|
|
89
109
|
*/
|
|
90
110
|
export declare function readEvolutionTimeline(io: EvolutionIoLike, path: string): Promise<EventLogRead>;
|
|
91
111
|
//# sourceMappingURL=evolution-events.d.ts.map
|
package/lib/types/usage.d.ts
CHANGED
|
@@ -53,14 +53,26 @@ export declare function mutateUsage(root: string, io: EvolutionIoLike, task: (ma
|
|
|
53
53
|
* folds copy this set only.
|
|
54
54
|
*/
|
|
55
55
|
export declare function applyCuratorFields(disk: UsageRecord, curated: UsageRecord): void;
|
|
56
|
+
/** Copy only the lifecycle pair (state/archived_at) — see the ownership split
|
|
57
|
+
* rationale on {@link applyCuratorMetaFields}. */
|
|
58
|
+
export declare function applyCuratorLifecycleFields(disk: UsageRecord, curated: UsageRecord): void;
|
|
59
|
+
/**
|
|
60
|
+
* Copy the recomputed meta pair (quality_score/quality_warn + the
|
|
61
|
+
* marker-mirrored pin flag) — refreshed tree-wide each run by design, so a
|
|
62
|
+
* concurrent curator run's lifecycle changes are never reverted by them.
|
|
63
|
+
*/
|
|
64
|
+
export declare function applyCuratorMetaFields(disk: UsageRecord, curated: UsageRecord): void;
|
|
56
65
|
/**
|
|
57
66
|
* Fold a curator run-start snapshot onto the current on-disk map (rc.67 K-2):
|
|
58
67
|
* each curated record is projected onto its disk peer by copying only the
|
|
59
68
|
* curator-owned fields, so a concurrent tool-side bump between snapshot and
|
|
60
69
|
* save survives. Records absent from the snapshot are left untouched; a
|
|
61
|
-
* curated record with no disk peer is seeded from the snapshot.
|
|
70
|
+
* curated record with no disk peer is seeded from the snapshot. `stateOwned`
|
|
71
|
+
* (rc.72 H-1) restricts the lifecycle pair to the names this run ACTUALLY
|
|
72
|
+
* transitioned — a concurrent curator run's archive/restore is never reverted
|
|
73
|
+
* by a stale snapshot; without it both pairs apply everywhere.
|
|
62
74
|
*/
|
|
63
|
-
export declare function foldCuratorFields(disk: UsageMap, curated: UsageMap): void;
|
|
75
|
+
export declare function foldCuratorFields(disk: UsageMap, curated: UsageMap, stateOwned?: ReadonlySet<string>): void;
|
|
64
76
|
export declare function saveUsage(root: string, map: UsageMap, io?: EvolutionIoLike): Promise<void>;
|
|
65
77
|
export declare function getRecord(map: UsageMap, name: string): UsageRecord;
|
|
66
78
|
export declare function bumpView(map: UsageMap, name: string, when?: Date): void;
|
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.72",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|