@lmzhen/dsh-evolution-core 0.1.0-rc.9 → 0.1.0
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 +2175 -473
- package/lib/types/constants.d.ts +53 -0
- package/lib/types/curator.d.ts +71 -4
- package/lib/types/events.d.ts +18 -10
- package/lib/types/evolution-events.d.ts +111 -0
- package/lib/types/gates.d.ts +38 -0
- package/lib/types/index.d.ts +6 -0
- package/lib/types/io.d.ts +31 -4
- package/lib/types/learn-prompt.d.ts +19 -0
- package/lib/types/memory-store.d.ts +55 -12
- package/lib/types/mutations.d.ts +24 -0
- package/lib/types/prompts.d.ts +26 -6
- package/lib/types/quality.d.ts +70 -0
- package/lib/types/skill-store.d.ts +153 -21
- package/lib/types/state-store.d.ts +2 -15
- package/lib/types/threats.d.ts +16 -4
- package/lib/types/usage.d.ts +69 -2
- package/package.json +5 -5
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for the dsh-evolution plugin family.
|
|
3
|
+
*
|
|
4
|
+
* Two classes of value live here, deliberately separated by section so future
|
|
5
|
+
* edits do not blur the semantic boundary:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Fixed protocol/format/security invariants** — changing these breaks an
|
|
8
|
+
* on-disk format, a naming/format contract, a path-security boundary, or a
|
|
9
|
+
* cross-component invariant. They are NOT exposed as deployment config.
|
|
10
|
+
*
|
|
11
|
+
* 2. **Cross-package shared tunable defaults** — the same semantic default is
|
|
12
|
+
* read (with a config override path) by more than one package (e.g.
|
|
13
|
+
* `evolution-policy` and `evolution-curator` both default `staleAfterDays`
|
|
14
|
+
* to 30). Centralizing them here means one authoritative default: a config
|
|
15
|
+
* override still applies per package, but the fallback is single-sourced.
|
|
16
|
+
*
|
|
17
|
+
* Package-private tunables (used by exactly one package) stay in that package,
|
|
18
|
+
* not here — see evolution-replay's `DEFAULT_WEIGHTS` and evolution-feedback's
|
|
19
|
+
* threshold, which are intentionally left where they are used.
|
|
20
|
+
* @module @deepseek-ai/dsh-evolution-core
|
|
21
|
+
*/
|
|
22
|
+
/** Skill frontmatter `name` validated for the file name (lowercase + hyphen). */
|
|
23
|
+
export declare const SKILL_NAME_RE: RegExp;
|
|
24
|
+
/** Allowed skill support-file subdirectories (path-traversal boundary). */
|
|
25
|
+
export declare const SUPPORT_DIRS: readonly ["references", "templates", "scripts", "assets"];
|
|
26
|
+
/** Delimiter between durable memory entries (on-disk storage format). */
|
|
27
|
+
export declare const ENTRY_DELIMITER = "\n\u00A7\n";
|
|
28
|
+
/** Built-in skill names the curator must never lifecycle-manage. */
|
|
29
|
+
export declare const PROTECTED_BUILTIN_SKILLS: ReadonlySet<string>;
|
|
30
|
+
export declare const MAX_SKILL_NAME_LENGTH = 64;
|
|
31
|
+
export declare const MAX_DESCRIPTION_LENGTH = 1024;
|
|
32
|
+
export declare const MAX_SKILL_CONTENT_CHARS = 100000;
|
|
33
|
+
export declare const MAX_SKILL_FILE_BYTES = 1048576;
|
|
34
|
+
export declare const DEFAULT_REVIEW_MEMORY_INTERVAL = 10;
|
|
35
|
+
export declare const DEFAULT_REVIEW_SKILL_INTERVAL = 10;
|
|
36
|
+
/** Skill-review completion channel trigger mode: 'cadence' | 'completion' | 'both'. */
|
|
37
|
+
export declare const DEFAULT_SKILL_REVIEW_TRIGGER: "both";
|
|
38
|
+
/** Cumulative session tool calls before a session counts as "proven long" for the completion channel. */
|
|
39
|
+
export declare const DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS = 20;
|
|
40
|
+
export declare const DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS = 3;
|
|
41
|
+
export declare const DEFAULT_SUBSTANTIVE_MIN_USER_CHARS = 200;
|
|
42
|
+
export declare const DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS = 500;
|
|
43
|
+
export declare const DEFAULT_MAX_OPS_PER_PLAN = 32;
|
|
44
|
+
export declare const DEFAULT_CURATOR_INTERVAL_HOURS = 168;
|
|
45
|
+
export declare const DEFAULT_MIN_IDLE_HOURS = 2;
|
|
46
|
+
export declare const DEFAULT_STALE_AFTER_DAYS = 30;
|
|
47
|
+
export declare const DEFAULT_ARCHIVE_AFTER_DAYS = 90;
|
|
48
|
+
export declare const DEFAULT_MEMORY_CHAR_LIMIT = 2200;
|
|
49
|
+
export declare const DEFAULT_USER_CHAR_LIMIT = 1375;
|
|
50
|
+
/** Consolidation-failure backoff cap, shared by MemoryStore and memory-files' Config default. */
|
|
51
|
+
export declare const DEFAULT_CONSOLIDATION_FAILURES = 3;
|
|
52
|
+
export declare const DEFAULT_SKILL_CONTENT_CHARS = 100000;
|
|
53
|
+
//# sourceMappingURL=constants.d.ts.map
|
package/lib/types/curator.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Deterministic skill curator: active → stale → archived transitions.
|
|
3
|
-
* Pure function
|
|
3
|
+
* Pure function with one deliberate side effect: records in the passed
|
|
4
|
+
* `usage` map are MUTATED (state/archived_at) to carry the transition — the
|
|
5
|
+
* caller owns the map and decides whether to clone first (dry-run) or persist
|
|
6
|
+
* after. File moves are performed by SkillLibrary.
|
|
4
7
|
*/
|
|
5
|
-
import type { UsageMap } from './usage.ts';
|
|
8
|
+
import type { UsageMap, UsageRecord } from './usage.ts';
|
|
9
|
+
import { EvolutionGateSet } from './gates.ts';
|
|
10
|
+
export { PROTECTED_BUILTIN_SKILLS } from './constants.ts';
|
|
6
11
|
export interface CuratorConfig {
|
|
7
12
|
staleAfterDays: number;
|
|
8
13
|
archiveAfterDays: number;
|
|
@@ -12,6 +17,14 @@ export interface CuratorConfig {
|
|
|
12
17
|
excludeSkillNames?: ReadonlySet<string>;
|
|
13
18
|
/** When true, usage records without created_by='agent' also enter the lifecycle. */
|
|
14
19
|
manageUnmanaged?: boolean;
|
|
20
|
+
/** When true, bundled skills (in `bundledNames`) are curation candidates like agent-created ones. */
|
|
21
|
+
pruneBuiltins?: boolean;
|
|
22
|
+
/** Skill names carrying the bundled marker; only read when `pruneBuiltins` is true. */
|
|
23
|
+
bundledNames?: ReadonlySet<string>;
|
|
24
|
+
/** Skill names the curator archived once and must not fight across re-seeds. */
|
|
25
|
+
suppressedNames?: ReadonlySet<string>;
|
|
26
|
+
/** Skills referenced by scheduled/automated jobs: never auto-transitioned (idle clocks mislead for rarely-firing tasks). */
|
|
27
|
+
referencedSkillNames?: ReadonlySet<string>;
|
|
15
28
|
}
|
|
16
29
|
export interface CuratorTransition {
|
|
17
30
|
name: string;
|
|
@@ -25,7 +38,6 @@ export interface CuratorResult {
|
|
|
25
38
|
reactivate: string[];
|
|
26
39
|
markStale: string[];
|
|
27
40
|
}
|
|
28
|
-
export declare const PROTECTED_BUILTIN_SKILLS: ReadonlySet<string>;
|
|
29
41
|
export interface CuratorArchivedSkill {
|
|
30
42
|
name: string;
|
|
31
43
|
path: string;
|
|
@@ -36,6 +48,8 @@ export interface CuratorFailedSkill {
|
|
|
36
48
|
reason: string;
|
|
37
49
|
}
|
|
38
50
|
export interface CuratorRunReport {
|
|
51
|
+
/** Report shape version; readers may ignore unknown fields on later versions. */
|
|
52
|
+
schemaVersion: 1;
|
|
39
53
|
runId: string;
|
|
40
54
|
startedAt: string;
|
|
41
55
|
finishedAt: string;
|
|
@@ -44,7 +58,11 @@ export interface CuratorRunReport {
|
|
|
44
58
|
archiveCandidates: string[];
|
|
45
59
|
archived: CuratorArchivedSkill[];
|
|
46
60
|
failed: CuratorFailedSkill[];
|
|
61
|
+
/** Consolidations actually executed this run (source absorbed into target). */
|
|
62
|
+
consolidated?: CuratorConsolidation[];
|
|
47
63
|
snapshotPath?: string;
|
|
64
|
+
/** Whether the LLM nomination pass was enabled for this run (decision visibility). */
|
|
65
|
+
llmReviewEnabled?: boolean;
|
|
48
66
|
}
|
|
49
67
|
export interface CuratorReportInput {
|
|
50
68
|
runId: string;
|
|
@@ -55,8 +73,57 @@ export interface CuratorReportInput {
|
|
|
55
73
|
archiveCandidates: readonly string[];
|
|
56
74
|
archived: readonly CuratorArchivedSkill[];
|
|
57
75
|
failed: readonly CuratorFailedSkill[];
|
|
76
|
+
consolidated?: readonly CuratorConsolidation[];
|
|
58
77
|
snapshotPath?: string;
|
|
78
|
+
llmReviewEnabled?: boolean;
|
|
59
79
|
}
|
|
60
80
|
export declare function buildCuratorRunReport(input: CuratorReportInput): CuratorRunReport;
|
|
61
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Render a curator run report as a compact human-readable markdown digest
|
|
83
|
+
* (G6): run metadata first, then the notable sections (archived / failed /
|
|
84
|
+
* stale candidates / LLM nominations).
|
|
85
|
+
*/
|
|
86
|
+
export declare function renderCuratorReportMarkdown(report: CuratorRunReport): string;
|
|
87
|
+
/** One LLM-nominated consolidation: `from` merges into the umbrella `into`. */
|
|
88
|
+
export interface CuratorConsolidation {
|
|
89
|
+
from: string;
|
|
90
|
+
into: string;
|
|
91
|
+
}
|
|
92
|
+
/** Structured result of the optional curator LLM nomination pass. */
|
|
93
|
+
export interface CuratorNominations {
|
|
94
|
+
prunings: string[];
|
|
95
|
+
consolidations: CuratorConsolidation[];
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Parse the curator LLM's YAML nomination block (consolidations + prunings).
|
|
99
|
+
* Line-oriented and lenient by design: the LLM output is advisory, every name
|
|
100
|
+
* is re-validated against the tree before any file move happens downstream.
|
|
101
|
+
*/
|
|
102
|
+
export declare function parseCuratorNominations(text: string): CuratorNominations;
|
|
103
|
+
/**
|
|
104
|
+
* The lifecycle-candidate gate, shared by the transition engine and the scope
|
|
105
|
+
* view so the two can never disagree: records failing ANY of these gates are
|
|
106
|
+
* outside the managed scope.
|
|
107
|
+
*/
|
|
108
|
+
export declare function lifecycleCandidate(name: string, record: UsageRecord, config: CuratorConfig, bundled: boolean, gates?: EvolutionGateSet): boolean;
|
|
109
|
+
export interface ScopeView {
|
|
110
|
+
/** Skills inside the lifecycle scope right now (candidate gate + active state). */
|
|
111
|
+
managed: string[];
|
|
112
|
+
/** Managed skills already flagged stale or quality-warned — the ones to watch. */
|
|
113
|
+
watched: string[];
|
|
114
|
+
/** Managed skills flagged low quality (subset of `watched`) — consolidation candidates. */
|
|
115
|
+
qualityWarned: string[];
|
|
116
|
+
/** Explicitly exempted by excludeSkillNames / referencedSkillNames. */
|
|
117
|
+
exempted: string[];
|
|
118
|
+
/** Carrying a protection marker (pinned / bundled / hub-installed). */
|
|
119
|
+
protected: string[];
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Read-only scope classification, derived from the SAME gate the transition
|
|
123
|
+
* engine uses (`lifecycleCandidate`), so the view always predicts what a
|
|
124
|
+
* curator pass may touch. `protectedNames` carries the marker info the usage
|
|
125
|
+
* records lack (bundled / hub-installed / pinned from `SkillLibrary.list()`).
|
|
126
|
+
*/
|
|
127
|
+
export declare function computeScopeView(usage: UsageMap, config: CuratorConfig, protectedNames?: ReadonlyMap<string, string>, gates?: EvolutionGateSet): ScopeView;
|
|
128
|
+
export declare function computeLifecycleTransitions(usage: UsageMap, config: CuratorConfig, now?: Date, gates?: EvolutionGateSet): CuratorResult;
|
|
62
129
|
//# sourceMappingURL=curator.d.ts.map
|
package/lib/types/events.d.ts
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Process-local events emitted by the evolution family on the cordis event
|
|
3
|
+
* bus. Consumers subscribe with `ctx.on(...)`; producers dispatch with
|
|
4
|
+
* `ctx.emit(...)`.
|
|
5
|
+
*
|
|
6
|
+
* These are deliberately NOT session events: a persisted session log may only
|
|
7
|
+
* contain types from the host's generated `KNOWN_SESSION_EVENT_TYPES` set —
|
|
8
|
+
* the persistence read path refuses to interpret a log carrying any other
|
|
9
|
+
* type unless the envelope marks it `ignorable`, and `Session.append` offers
|
|
10
|
+
* no channel to write that marker. Appending any `evolution/*` type therefore
|
|
11
|
+
* made the whole session unresumable (A-line P0-1, fixed in rc.42 by moving
|
|
12
|
+
* these events off `session.append`). Plan-outcome durability lives in the
|
|
13
|
+
* evolution-activity store, not the session log.
|
|
5
14
|
*/
|
|
6
15
|
export interface EvolutionReviewScheduledEvent {
|
|
16
|
+
/** Owning session (payload v2): process events carry no session envelope. */
|
|
17
|
+
sessionId: string;
|
|
7
18
|
kind: 'memory' | 'skill' | 'combined';
|
|
8
19
|
toolCalls: number;
|
|
9
20
|
userChars: number;
|
|
10
21
|
assistantChars: number;
|
|
11
22
|
}
|
|
12
23
|
export interface EvolutionPlanAppliedEvent {
|
|
24
|
+
/** Owning session (payload v2): process events carry no session envelope. */
|
|
25
|
+
sessionId: string;
|
|
13
26
|
planId: string;
|
|
14
27
|
/** Stable fingerprint of the policy snapshot that produced this plan. */
|
|
15
28
|
policyFingerprint?: string | undefined;
|
|
@@ -25,15 +38,10 @@ export interface EvolutionSkillMutatedEvent {
|
|
|
25
38
|
filePath?: string;
|
|
26
39
|
archivedPath?: string;
|
|
27
40
|
}
|
|
28
|
-
declare module '@deepseek-ai/dsh-session/types' {
|
|
29
|
-
interface SessionEventMap {
|
|
30
|
-
'evolution/review-scheduled': EvolutionReviewScheduledEvent;
|
|
31
|
-
'evolution/plan-applied': EvolutionPlanAppliedEvent;
|
|
32
|
-
'evolution/skill-mutated': EvolutionSkillMutatedEvent;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
41
|
declare module '@deepseek-ai/cordis' {
|
|
36
42
|
interface Events {
|
|
43
|
+
'evolution/review-scheduled'(event: EvolutionReviewScheduledEvent): void;
|
|
44
|
+
'evolution/plan-applied'(event: EvolutionPlanAppliedEvent): void;
|
|
37
45
|
'evolution/skill-mutated'(event: EvolutionSkillMutatedEvent): void;
|
|
38
46
|
}
|
|
39
47
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
* 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).
|
|
16
|
+
*/
|
|
17
|
+
import { type EvolutionIoLike } from './io.ts';
|
|
18
|
+
export declare const EVENT_LOG_VERSION = 1;
|
|
19
|
+
/** Active-log split point (rc.71): when the active log reaches this many events
|
|
20
|
+
* the older half is rotated into an archive; the active stays bounded so a
|
|
21
|
+
* single append stays O(active) instead of O(total-history). Tunable default —
|
|
22
|
+
* callers may override per append (the tests use small values). */
|
|
23
|
+
export declare const EVENT_LOG_ROTATE_AT = 4000;
|
|
24
|
+
/** Number of archives retained (rc.71): older archives are pruned at rotation,
|
|
25
|
+
* mirroring retainReports. The horizon covers the loop-analysis window. */
|
|
26
|
+
export declare const EVENT_LOG_RETAIN_ARCHIVES = 10;
|
|
27
|
+
/** Archive file prefix: `events-<lastArchivedSeq>.json`. The active file is
|
|
28
|
+
* `events.json` and never matches this glob. */
|
|
29
|
+
export declare const EVENT_ARCHIVE_PREFIX = "events-";
|
|
30
|
+
export interface EvolutionEvent {
|
|
31
|
+
/** Global monotonic order key, assigned inside the append transact. */
|
|
32
|
+
seq: number;
|
|
33
|
+
/** ISO timestamp at append time. */
|
|
34
|
+
at: string;
|
|
35
|
+
/** Tagged-union discriminator: feedback increments vs learn actions. */
|
|
36
|
+
type: 'feedback' | 'learn';
|
|
37
|
+
target?: string | undefined;
|
|
38
|
+
kind?: 'skill' | 'session' | undefined;
|
|
39
|
+
rating?: 'positive' | 'negative' | undefined;
|
|
40
|
+
note?: string | undefined;
|
|
41
|
+
source?: string | undefined;
|
|
42
|
+
request?: string | undefined;
|
|
43
|
+
}
|
|
44
|
+
export declare function eventsFile(home: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Parse an event log body. A missing file, a whitespace-only file (rc.69:
|
|
47
|
+
* rebuildable, NOT malformed) or a corrupt one reads as empty; corrupt content
|
|
48
|
+
* is still refused on append, never overwritten.
|
|
49
|
+
*
|
|
50
|
+
* Per-entry normalization (rc.70 F-1): entries without a numeric `seq` are
|
|
51
|
+
* skipped here and dropped at the next append — valid entries survive, the
|
|
52
|
+
* damaged record is the only loss (self-heal semantics, matching the usage
|
|
53
|
+
* sidecar's per-field normalization on read).
|
|
54
|
+
*/
|
|
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[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Append one event under the write lock (rc.68): `seq` = current max + 1
|
|
64
|
+
* computed inside the transact, so two processes appending concurrently never
|
|
65
|
+
* collide. A malformed log is refused (bytes preserved) and the append fails.
|
|
66
|
+
* Returns the assigned seq.
|
|
67
|
+
*
|
|
68
|
+
* Rotation (rc.71, 007 design): when the active log reaches `rotateAt`, the
|
|
69
|
+
* older half is copied into an archive inside the SAME transact (the archive
|
|
70
|
+
* path has its own lock, so no recursion) and the active is replaced with the
|
|
71
|
+
* newer half + the new event. seqs stay globally monotonic; a crash between
|
|
72
|
+
* archive write and active write leaves both copies, which the timeline merge
|
|
73
|
+
* dedupes by seq. An archive-write failure aborts the append (active keeps the
|
|
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.
|
|
80
|
+
*/
|
|
81
|
+
export declare function appendEvolutionEvent(io: EvolutionIoLike, path: string, event: Omit<EvolutionEvent, 'seq' | 'at'>, rotateAt?: number): Promise<number>;
|
|
82
|
+
/**
|
|
83
|
+
* Prune old event archives (rc.71): keep the newest `EVENT_LOG_RETAIN_ARCHIVES`.
|
|
84
|
+
* The name's numeric part is the last archived seq, so ordering is NUMERIC —
|
|
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.
|
|
88
|
+
*/
|
|
89
|
+
export declare function retainEventArchives(io: EvolutionIoLike, path: string): Promise<void>;
|
|
90
|
+
export interface EventLogRead {
|
|
91
|
+
events: EvolutionEvent[];
|
|
92
|
+
/** True when the body is not valid JSON (syntax-level damage): refused on
|
|
93
|
+
* append, bytes untouched. Well-formed JSON with a damaged `events` field
|
|
94
|
+
* is REPLACEABLE garbage — reads as empty and is rewritten at the next
|
|
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). */
|
|
98
|
+
malformed: boolean;
|
|
99
|
+
}
|
|
100
|
+
/** Read the event log; a missing/whitespace-only file reads as empty,
|
|
101
|
+
* corrupt content is flagged (and refused on append). */
|
|
102
|
+
export declare function readEvolutionEvents(io: EvolutionIoLike, path: string): Promise<EventLogRead>;
|
|
103
|
+
/**
|
|
104
|
+
* Read the full timeline (rc.71): active log + all archives, merged by seq
|
|
105
|
+
* (active copy wins, duplicates only arise from the rotation crash window),
|
|
106
|
+
* sorted ascending. Per-file malformed flag as in `readEvolutionEvents`; a
|
|
107
|
+
* malformed (or unreadable) ARCHIVE is skipped — it never bricks the boot and
|
|
108
|
+
* it is still flagged.
|
|
109
|
+
*/
|
|
110
|
+
export declare function readEvolutionTimeline(io: EvolutionIoLike, path: string): Promise<EventLogRead>;
|
|
111
|
+
//# sourceMappingURL=evolution-events.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The control-plane protection sets, held once and queried everywhere
|
|
3
|
+
* (decision B, rc.44 plan M2): the lifecycle engine, the scope view, the LLM
|
|
4
|
+
* nomination gate and the control-plane consolidate all answer "is this name
|
|
5
|
+
* off limits — and why" from the same instance, so the gate sets can never
|
|
6
|
+
* drift apart the way the three pre-rc.46 implementations did.
|
|
7
|
+
*
|
|
8
|
+
* Scope boundary: a GateSet covers NAME-SET protections only. Marker-based
|
|
9
|
+
* protections (pinned / bundled / hub-installed) are file markers resolved by
|
|
10
|
+
* `SkillLibrary.writeProtection` / `deleteProtection` — they depend on the
|
|
11
|
+
* filesystem and the write origin, not on a name list.
|
|
12
|
+
* @module @deepseek-ai/dsh-evolution-core
|
|
13
|
+
*/
|
|
14
|
+
export type GateReason = 'excluded' | 'referenced' | 'suppressed' | 'protected-builtin';
|
|
15
|
+
export interface GateSetInputs {
|
|
16
|
+
exclude?: ReadonlySet<string> | undefined;
|
|
17
|
+
referenced?: ReadonlySet<string> | undefined;
|
|
18
|
+
suppressed?: ReadonlySet<string> | undefined;
|
|
19
|
+
}
|
|
20
|
+
export declare class EvolutionGateSet {
|
|
21
|
+
readonly exclude: ReadonlySet<string>;
|
|
22
|
+
readonly referenced: ReadonlySet<string>;
|
|
23
|
+
readonly suppressed: ReadonlySet<string>;
|
|
24
|
+
constructor(inputs?: GateSetInputs);
|
|
25
|
+
/**
|
|
26
|
+
* The first protection blocking this name, or null. Any hit blocks — the
|
|
27
|
+
* order is diagnostic only, so a name in two sets reports the first.
|
|
28
|
+
*/
|
|
29
|
+
blockReason(name: string): GateReason | null;
|
|
30
|
+
isBlocked(name: string): boolean;
|
|
31
|
+
}
|
|
32
|
+
/** Build a GateSet from the curator-style config field names. */
|
|
33
|
+
export declare function createGateSet(config: {
|
|
34
|
+
excludeSkillNames?: ReadonlySet<string>;
|
|
35
|
+
referencedSkillNames?: ReadonlySet<string>;
|
|
36
|
+
suppressedNames?: ReadonlySet<string>;
|
|
37
|
+
}): EvolutionGateSet;
|
|
38
|
+
//# sourceMappingURL=gates.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -8,13 +8,19 @@
|
|
|
8
8
|
* @module @deepseek-ai/dsh-evolution-core
|
|
9
9
|
*/
|
|
10
10
|
export * from './curator.ts';
|
|
11
|
+
export * from './evolution-events.ts';
|
|
12
|
+
export * from './gates.ts';
|
|
11
13
|
export * from './events.ts';
|
|
12
14
|
export * from './io.ts';
|
|
15
|
+
export * from './learn-prompt.ts';
|
|
13
16
|
export * from './memory-store.ts';
|
|
17
|
+
export * from './mutations.ts';
|
|
14
18
|
export * from './prompts.ts';
|
|
19
|
+
export * from './quality.ts';
|
|
15
20
|
export * from './signals.ts';
|
|
16
21
|
export * from './skill-store.ts';
|
|
17
22
|
export * from './state-store.ts';
|
|
18
23
|
export * from './threats.ts';
|
|
19
24
|
export * from './usage.ts';
|
|
25
|
+
export * from './constants.ts';
|
|
20
26
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/types/io.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Structural IO seam for the
|
|
2
|
+
* Structural IO seam for the evolution plugin family.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* (and the facade's own tests) can use `nodeEvolutionIo`.
|
|
4
|
+
* Every evolution package passes `ctx.evolutionIo.provider()`; standalone
|
|
5
|
+
* consumers (and the core's own tests) can use `nodeEvolutionIo`.
|
|
7
6
|
*/
|
|
8
7
|
export interface EvolutionIoLike {
|
|
9
8
|
readText(path: string): Promise<string | null>;
|
|
@@ -13,7 +12,35 @@ export interface EvolutionIoLike {
|
|
|
13
12
|
exists(path: string): Promise<boolean>;
|
|
14
13
|
rename(path: string, destination: string): Promise<void>;
|
|
15
14
|
copy(path: string, destination: string): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Optional byte-size probe for the read guard. Return the file's size in
|
|
17
|
+
* bytes, or `null`/`undefined` when unknown (unsupported backend, missing
|
|
18
|
+
* file, stat failure). An implementation without this probe gets no guard:
|
|
19
|
+
* consumers treat an unknown size as "guard not applicable".
|
|
20
|
+
*/
|
|
21
|
+
size?(path: string): Promise<number | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Optional atomic read-modify-write on one file: the read and the write run
|
|
24
|
+
* inside a single cross-process lock, so two processes that share DSH_HOME
|
|
25
|
+
* cannot interleave their RMW sequences. `task` receives the current content
|
|
26
|
+
* (`null` when missing) and returns the next content; returning `null`
|
|
27
|
+
* deletes the file. A backend without it falls back to plain read+write and
|
|
28
|
+
* the caller keeps its single-process chain as the second layer.
|
|
29
|
+
*/
|
|
30
|
+
transact?(this: void, path: string, task: (current: string | null) => Promise<string | null>): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Optional symlink probe (G7). `true` = the path is a symlink, `false` = a
|
|
33
|
+
* real entry, `null` = guard not applicable (backend without the probe or
|
|
34
|
+
* the path does not exist). Consumers treat `null` as "let it through".
|
|
35
|
+
*/
|
|
36
|
+
isSymlink?(this: void, path: string): Promise<boolean | null>;
|
|
16
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Run `task` inside `io.transact` when the backend provides it; otherwise fall
|
|
40
|
+
* back to a plain read → task → write/remove sequence (no cross-process lock —
|
|
41
|
+
* callers keep their single-process serialize chain as the second layer).
|
|
42
|
+
*/
|
|
43
|
+
export declare function transactIo(io: EvolutionIoLike, path: string, task: (current: string | null) => Promise<string | null>): Promise<void>;
|
|
17
44
|
/** Lazy adapter over an IO provider registry, shared by every evolution consumer. */
|
|
18
45
|
export declare function evolutionIoAdapter(provider: () => EvolutionIoLike): EvolutionIoLike;
|
|
19
46
|
export declare function nodeEvolutionIo(): EvolutionIoLike;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open-ended `/evolution learn` prompt builder.
|
|
3
|
+
*
|
|
4
|
+
* `learn` is open-ended: the user can name anything they can describe — a
|
|
5
|
+
* directory of code, an API doc URL, a workflow they just walked the agent
|
|
6
|
+
* through, or pasted notes. The prompt instructs the live agent to gather the
|
|
7
|
+
* named sources with its existing tools, then author a single SKILL.md via
|
|
8
|
+
* `skill_manage` following `DSH_AUTHORING_STANDARDS`. There is no separate
|
|
9
|
+
* distillation engine and no model-tool footprint.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Build the agent prompt for an open-ended `/evolution learn` request.
|
|
13
|
+
*
|
|
14
|
+
* @param userRequest free-text the user gave after `/evolution learn`; an
|
|
15
|
+
* empty string falls back to "the workflow we just went through".
|
|
16
|
+
* @returns a complete instruction the agent runs as a normal turn.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildLearnPrompt(userRequest: string): string;
|
|
19
|
+
//# sourceMappingURL=learn-prompt.d.ts.map
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Stores are MEMORY.md and USER.md under $DSH_HOME/memories (~/.dsh/memories).
|
|
4
4
|
*/
|
|
5
5
|
import { type EvolutionIoLike } from './io.ts';
|
|
6
|
-
export
|
|
6
|
+
export { ENTRY_DELIMITER } from './constants.ts';
|
|
7
7
|
export type MemoryTarget = 'memory' | 'user';
|
|
8
8
|
export interface MemoryOperation {
|
|
9
9
|
action: 'add' | 'replace' | 'remove';
|
|
@@ -35,26 +35,69 @@ export declare class MemoryStore {
|
|
|
35
35
|
private readonly maxFailures;
|
|
36
36
|
private readonly io;
|
|
37
37
|
private failureCount;
|
|
38
|
+
private lastFailureAt;
|
|
38
39
|
constructor(options?: MemoryStoreOptions);
|
|
39
40
|
limitFor(target: MemoryTarget): number;
|
|
41
|
+
/**
|
|
42
|
+
* Read-guard probe: `{ size, limit }` when the on-disk file exceeds
|
|
43
|
+
* `limit * READ_GUARD_FACTOR` bytes, `null` when it is absent, unknown
|
|
44
|
+
* (backend without a size probe), under the bound, or the target has no
|
|
45
|
+
* limit configured.
|
|
46
|
+
*/
|
|
47
|
+
private oversizedFile;
|
|
40
48
|
read(target: MemoryTarget): Promise<string[]>;
|
|
41
49
|
write(target: MemoryTarget, entries: string[]): Promise<void>;
|
|
42
50
|
resetFailures(): void;
|
|
43
51
|
private failure;
|
|
52
|
+
/**
|
|
53
|
+
* StorageHint percentage must clamp at 100 like the render header: a drifted
|
|
54
|
+
* entry can push chars past the limit, and "Storage at 125%" contradicts the
|
|
55
|
+
* clamped usage indicator.
|
|
56
|
+
*/
|
|
57
|
+
private storageHint;
|
|
58
|
+
/**
|
|
59
|
+
* Best-effort raw-copy backup of the on-disk file to `<file>.bak.<stamp>`
|
|
60
|
+
* before a refusal, so an externally modified (or oversized) file stays
|
|
61
|
+
* recoverable. Copies bytes instead of reading them so a pathologically
|
|
62
|
+
* large file is never loaded just to back it up. Failure to back up does
|
|
63
|
+
* not change the refusal semantics.
|
|
64
|
+
*/
|
|
65
|
+
private backupFile;
|
|
66
|
+
/**
|
|
67
|
+
* Read-guard refusal for write paths. Returns the refusal result when the
|
|
68
|
+
* target file is oversized, `null` otherwise. The file is skipped for
|
|
69
|
+
* reading (never loaded), backed up by raw copy, and the model is told to
|
|
70
|
+
* fix it manually — mirroring the drift refusal so corrupted state is never
|
|
71
|
+
* silently overwritten.
|
|
72
|
+
*/
|
|
73
|
+
private oversizedRefusal;
|
|
44
74
|
add(target: MemoryTarget, facts: string): Promise<MemoryApplyResult>;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Single-entry add inside the transaction: shared checks (oversized,
|
|
77
|
+
* drift, threat) and the content computation. `raw` is the locked view
|
|
78
|
+
* (`current`) — never a second IO read. `write: null` means "no change".
|
|
79
|
+
*/
|
|
80
|
+
private addCore;
|
|
81
|
+
/** Canonical-form drift check derived from the locked view (same formula as `detectDrift`, no second read). */
|
|
82
|
+
private driftFromRaw;
|
|
48
83
|
applyBatch(target: MemoryTarget, operations: MemoryOperation[]): Promise<MemoryApplyResult>;
|
|
84
|
+
/** Batch RMW inside the transaction. `write: null` = failure/no-op, disk untouched. */
|
|
85
|
+
private applyBatchCore;
|
|
49
86
|
renderContext(): Promise<string>;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Detect on-disk drift: true when the file is not in the canonical
|
|
89
|
+
* `render(normalizeEntries(raw))` form. This catches structural anomalies
|
|
90
|
+
* the writer would quietly normalize away (empty/`§`-only entries, stray
|
|
91
|
+
* blank lines, leading/trailing delimiters) that indicate the file was
|
|
92
|
+
* edited outside MemoryStore. Purely single-canonical content reaches the
|
|
93
|
+
* same serialization and returns false, so a normal write is never flagged.
|
|
94
|
+
*
|
|
95
|
+
* An absent, empty, or whitespace-only file is the "never written" state
|
|
96
|
+
* (rc.42 audit P1-6): it parses to zero entries, so the canonical form
|
|
97
|
+
* `'\n'` can never byte-match it and every write path was permanently
|
|
98
|
+
* refused with "External drift detected" — including the repairs the model
|
|
99
|
+
* would need to make. Such files are adopted instead of flagged.
|
|
100
|
+
*/
|
|
58
101
|
detectDrift(target: MemoryTarget): Promise<boolean>;
|
|
59
102
|
}
|
|
60
103
|
//# sourceMappingURL=memory-store.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curator/author audit trail: `.mutations.json` records every skill mutation
|
|
3
|
+
* with before/after content hashes so any automated edit is reviewable and
|
|
4
|
+
* replayable. Best-effort persistence, mirroring the usage sidecar posture.
|
|
5
|
+
* @module @deepseek-ai/dsh-evolution-core
|
|
6
|
+
*/
|
|
7
|
+
import { type EvolutionIoLike } from './io.ts';
|
|
8
|
+
export interface MutationRecord {
|
|
9
|
+
skillName: string;
|
|
10
|
+
action: string;
|
|
11
|
+
beforeHash?: string;
|
|
12
|
+
afterHash?: string;
|
|
13
|
+
summary: string;
|
|
14
|
+
at: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const DEFAULT_MUTATION_CAP = 500;
|
|
17
|
+
/** Version of the `.mutations.json` file shape; writers always emit the current one. */
|
|
18
|
+
export declare const MUTATIONS_FILE_VERSION = 1;
|
|
19
|
+
export declare function mutationsFile(root: string): string;
|
|
20
|
+
export declare function contentHash(content: string): string;
|
|
21
|
+
export declare function loadMutations(root: string, io?: EvolutionIoLike): Promise<MutationRecord[]>;
|
|
22
|
+
/** Append one record, trim to `cap`, and write atomically (versioned shape). */
|
|
23
|
+
export declare function recordMutation(root: string, io: EvolutionIoLike, record: MutationRecord, cap?: number): Promise<void>;
|
|
24
|
+
//# sourceMappingURL=mutations.d.ts.map
|