@lmzhen/dsh-evolution-core 0.17.0 → 0.18.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/README.md +29 -0
- package/lib/index.js +511 -43
- package/lib/types/errors.d.ts +0 -1
- package/lib/types/events.d.ts +26 -1
- package/lib/types/fold-untrusted.d.ts +35 -0
- package/lib/types/index.d.ts +7 -2
- package/lib/types/memory-store.d.ts +3 -2
- package/lib/types/namespace-migration.d.ts +0 -1
- package/lib/types/numeric.d.ts +20 -0
- package/lib/types/params.d.ts +28 -2
- package/lib/types/payload.d.ts +32 -0
- package/lib/types/platform-services.d.ts +33 -0
- package/lib/types/probe.d.ts +28 -0
- package/lib/types/prompts.d.ts +2 -2
- package/lib/types/skill-reads.d.ts +13 -4
- package/lib/types/skill-store.d.ts +24 -2
- package/package.json +1 -1
- package/persisted-write-inventory.json +47 -6
package/lib/types/errors.d.ts
CHANGED
|
@@ -33,7 +33,6 @@ export declare const EVOLUTION_ERRORS: Readonly<{
|
|
|
33
33
|
readonly 'e-309-revision-conflict-you-sent': "E-309: revision conflict — you sent %a1%, the document stands at %a2%. Re-read with /evolution params --json and retry.";
|
|
34
34
|
readonly 'e-309-revision-conflict-message-Re': "E-309: revision conflict — %a1%. Re-read with /evolution params --json and retry.";
|
|
35
35
|
readonly 'e-310-the-settings-service-refused': "E-310: the settings service refused the write: %a1% — the owning plugin's rule stands (a cross-field pair, or a cap that may only be tightened). /evolution params shows the current value.";
|
|
36
|
-
readonly 'e-305-this-invocation-carries-no-2': "E-305: this invocation carries no agent — `%a1%` needs a session-backed call (run it from a session in the GUI or the CLI).";
|
|
37
36
|
readonly 'e-317-skill-write-not-confirmed': "E-317: skill \"%a1%\" was not %a2% — the confirmation prompt was declined or dismissed, so nothing was written. The prompt appears on every create and bare delete; repeat the call only if the operator asks for it.";
|
|
38
37
|
readonly 'e-318-skill-write-without-a-read': "E-318: skill \"%a1%\" was not read in this session, so this write is refused. Read it with the `skill` tool first (a read that failed does not count), then repeat the write.";
|
|
39
38
|
readonly 'e-320-the-profile-home-is-not': "E-320: the profile home is not reported by this host, so the legacy settings document cannot be located. /evolution migrate reads `<profile home>/settings.yaml.imported`; run it from a profile-launched session.";
|
package/lib/types/events.d.ts
CHANGED
|
@@ -85,14 +85,39 @@ interface EvolutionReviewErrorEvent {
|
|
|
85
85
|
}
|
|
86
86
|
declare module '@deepseek-ai/cordis' {
|
|
87
87
|
interface Events {
|
|
88
|
+
/**
|
|
89
|
+
* A review was scheduled and delivered on some channel. An ANNOUNCEMENT: consumers observe
|
|
90
|
+
* that the cadence fired, they do not decide anything (A98 / O-1).
|
|
91
|
+
* @param event - the scheduled review: owning session, kind, the turn's counts, delivery channel.
|
|
92
|
+
* @mode emit
|
|
93
|
+
*/
|
|
88
94
|
'evolution/review-scheduled'(event: EvolutionReviewScheduledEvent): void;
|
|
95
|
+
/**
|
|
96
|
+
* A reviewed plan finished its write leg, with the counts that describe what landed. An
|
|
97
|
+
* ANNOUNCEMENT: the family's own ledger of plan outcomes (A98 / O-1).
|
|
98
|
+
* @param event - the outcome counts, the first failure message, and the policy fingerprint.
|
|
99
|
+
* @mode emit
|
|
100
|
+
*/
|
|
89
101
|
'evolution/plan-applied'(event: EvolutionPlanAppliedEvent): void;
|
|
102
|
+
/**
|
|
103
|
+
* One skill mutation landed (the cache-invalidation signal every catalog consumer reads). An
|
|
104
|
+
* ANNOUNCEMENT: it names what changed, it decides nothing (A98 / O-1).
|
|
105
|
+
* @param event - the action, the skill name, and the file for support-file operations.
|
|
106
|
+
* @mode emit
|
|
107
|
+
*/
|
|
90
108
|
'evolution/skill-mutated'(event: EvolutionSkillMutatedEvent): void;
|
|
91
109
|
/** 0.3.18 (E-71): explicit catalog refresh request (`/evolution skills
|
|
92
110
|
* refresh`). Out-of-band tree edits (manual, git) may bypass the mutation
|
|
93
111
|
* event; listeners drop caches and invalidate downstream catalogs. No
|
|
94
|
-
* payload — it is a bare "re-read" signal, never a mutation record.
|
|
112
|
+
* payload — it is a bare "re-read" signal, never a mutation record.
|
|
113
|
+
* @mode emit */
|
|
95
114
|
'evolution/skills-refresh'(): void;
|
|
115
|
+
/**
|
|
116
|
+
* A review pipeline failure was caught (never an unhandled rejection). An ANNOUNCEMENT for
|
|
117
|
+
* operators and observability — the reason is already logged by the emitter (A98 / O-1).
|
|
118
|
+
* @param event - the session whose review pipeline failed.
|
|
119
|
+
* @mode emit
|
|
120
|
+
*/
|
|
96
121
|
'evolution/review-error'(event: EvolutionReviewErrorEvent): void;
|
|
97
122
|
}
|
|
98
123
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Untrusted text rendered as prompt lines (T3-03/A31) — ONE folding rule.
|
|
3
|
+
*
|
|
4
|
+
* The background-review digest embeds conversation text and tool output into a
|
|
5
|
+
* plain-text prompt whose PRODUCT is a plan with write permission. Anything the
|
|
6
|
+
* session ingested (a fetched page, a file body, command output) can therefore
|
|
7
|
+
* carry a newline plus a line shaped like the prompt's own structure — a
|
|
8
|
+
* `SYSTEM:`/`Review kind:` header, a `[result]` evidence line, a second copy
|
|
9
|
+
* of the closing instruction — and forge structure or inject instructions.
|
|
10
|
+
*
|
|
11
|
+
* FOLDING, not deleting: the reader (the model) must still SEE what the data
|
|
12
|
+
* said, it just must never become structure. Line separators become a visible
|
|
13
|
+
* `⏎`, and a line that would read as a role/evidence header gets a visible
|
|
14
|
+
* marker in front of it. Every original character stays in place.
|
|
15
|
+
*
|
|
16
|
+
* The boundary is the separator itself, not only a real newline: a payload can
|
|
17
|
+
* paste the separator verbatim, so neutralization matches after `⏎` whether it
|
|
18
|
+
* came from the fold or from the data. Both edits are idempotent (a second pass
|
|
19
|
+
* cannot add a second marker).
|
|
20
|
+
*
|
|
21
|
+
* Scope: text rendered as its OWN line(s) of a prompt. `render-facts`'
|
|
22
|
+
* sanitizer (evolution-maintenance) is the same posture for `key=value` fields
|
|
23
|
+
* and additionally escapes its block delimiter; it is not superseded here.
|
|
24
|
+
* @module @lmzhen/dsh-evolution-core
|
|
25
|
+
*/
|
|
26
|
+
/** Visible stand-in for a folded line separator: one character that cannot be read as line structure. */
|
|
27
|
+
export declare const UNTRUSTED_LINE_BREAK = " \u23CE ";
|
|
28
|
+
/** Visible marker prefixed to a line that would otherwise read as a role/structure header. */
|
|
29
|
+
export declare const UNTRUSTED_LINE_MARK = "\u00B7 ";
|
|
30
|
+
/**
|
|
31
|
+
* Fold untrusted `text` so it cannot become structure, then bound it to `limit`
|
|
32
|
+
* visible characters. A non-positive `limit` means "no truncation".
|
|
33
|
+
*/
|
|
34
|
+
export declare function foldUntrustedLines(text: string, limit: number): string;
|
|
35
|
+
//# sourceMappingURL=fold-untrusted.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -14,9 +14,11 @@
|
|
|
14
14
|
*
|
|
15
15
|
* - **Cross-cutting basics** — `state-store.ts` (env roots — the single
|
|
16
16
|
* source of DSH-home semantics), `serial.ts`, `numeric.ts`, `constants.ts`,
|
|
17
|
-
* `mutations.ts`, `events.ts`, `gates.ts`, `errors.ts` (the error-code table)
|
|
17
|
+
* `mutations.ts`, `events.ts`, `gates.ts`, `errors.ts` (the error-code table),
|
|
18
|
+
* `payload.ts` (which of `facts`/`content` is the payload — one rule for the whole family).
|
|
18
19
|
* - **Security primitives** — `threats.ts` (content threat scanner),
|
|
19
|
-
* `redact.ts` (credential masking at model boundaries)
|
|
20
|
+
* `redact.ts` (credential masking at model boundaries),
|
|
21
|
+
* `fold-untrusted.ts` (untrusted text must not become prompt structure). Consumers:
|
|
20
22
|
* evolution-policy/threat, both stores, review, maintenance.
|
|
21
23
|
* - **Core domain stores/logic** — `skill-store.ts` (skill tree engine +
|
|
22
24
|
* IO-seam consumer), `memory-store.ts`, `usage.ts`, `curator.ts`,
|
|
@@ -51,6 +53,7 @@ export * from './redact.ts';
|
|
|
51
53
|
export * from './review-channel.ts';
|
|
52
54
|
export * from './serial.ts';
|
|
53
55
|
export * from './probe.ts';
|
|
56
|
+
export * from './platform-services.ts';
|
|
54
57
|
export * from './instance-scope.ts';
|
|
55
58
|
export * from './write-inventory.ts';
|
|
56
59
|
export * from './scope.ts';
|
|
@@ -73,4 +76,6 @@ export * from './params.ts';
|
|
|
73
76
|
export * from './namespace-migration.ts';
|
|
74
77
|
export * from './numeric.ts';
|
|
75
78
|
export * from './opt-in.ts';
|
|
79
|
+
export * from './payload.ts';
|
|
80
|
+
export * from './fold-untrusted.ts';
|
|
76
81
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -9,8 +9,9 @@ export interface MemoryOperation {
|
|
|
9
9
|
action: 'add' | 'replace' | 'remove';
|
|
10
10
|
facts?: string | undefined;
|
|
11
11
|
/** PLAN-R2 P2-6 (2026-09-16): the dsh-memory contract's alias. applyBatchCore
|
|
12
|
-
*
|
|
13
|
-
* write the tool-memory schema shape without
|
|
12
|
+
* resolves the pair through core's `payloadText` (T3-06/A34), so a provider
|
|
13
|
+
* reusing this store directly can write the tool-memory schema shape without
|
|
14
|
+
* a normalization shim. */
|
|
14
15
|
content?: string | undefined;
|
|
15
16
|
old_text?: string | undefined;
|
|
16
17
|
}
|
package/lib/types/numeric.d.ts
CHANGED
|
@@ -35,4 +35,24 @@ export declare function clampedNumber(value: number | undefined, fallback: numbe
|
|
|
35
35
|
min?: number;
|
|
36
36
|
max?: number;
|
|
37
37
|
}): number;
|
|
38
|
+
/**
|
|
39
|
+
* Clamp one numeric knob and warn ONCE per key.
|
|
40
|
+
*
|
|
41
|
+
* S2.9 (T3-10): `evolution-review` and `evolution-curator` each carried their own copy of this
|
|
42
|
+
* helper — their own once-per-key set and their own message — so the two settings readers of ONE
|
|
43
|
+
* policy row could drift apart. The logic lives here; each caller passes its own sink (and its own
|
|
44
|
+
* package prefix). A live field is read at every use, so the warning cannot be per-read.
|
|
45
|
+
*
|
|
46
|
+
* @param warned - the keys this caller already warned about.
|
|
47
|
+
* @param warn - the warning sink (each package prefixes its own name).
|
|
48
|
+
* @param name - the config key, for the warning.
|
|
49
|
+
* @param value - the value as supplied, or its volatile reference.
|
|
50
|
+
* @param fallback - the package default a corrected value falls back to.
|
|
51
|
+
* @param min - the smallest value the engine can act on.
|
|
52
|
+
* @param max - the largest value the carrier can hold, when bounded.
|
|
53
|
+
* @returns the value the engine may use.
|
|
54
|
+
*/
|
|
55
|
+
export declare function clampOnce(warned: Set<string>, warn: (message: string) => void, name: string, value: number | {
|
|
56
|
+
get(): number | undefined;
|
|
57
|
+
} | undefined, fallback: number, min: number, max?: number): number;
|
|
38
58
|
//# sourceMappingURL=numeric.d.ts.map
|
package/lib/types/params.d.ts
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* the SAME name in two carriers (reviewMode, staleAfterDays, archiveAfterDays —
|
|
6
6
|
* resolved by the existing policy-shadows-row rule) and six use DIFFERENT names.
|
|
7
7
|
* This module owns the six: the policy/snapshot name is the canonical id, the
|
|
8
|
-
* plugin-row name is a deprecated alias kept readable
|
|
9
|
-
*
|
|
8
|
+
* plugin-row name is a deprecated alias kept readable since 0.6.x. Removal was
|
|
9
|
+
* planned for 0.7.0 and is deferred: the aliases still ship, so no later version
|
|
10
|
+
* is claimed here.
|
|
10
11
|
*
|
|
11
12
|
* Reading stays compatible (a carrier still spelling the legacy name resolves),
|
|
12
13
|
* writing is strict (the write path accepts canonical ids only, so no new
|
|
@@ -163,6 +164,31 @@ export declare function paramSettingsId(owner: string): string | undefined;
|
|
|
163
164
|
* @returns the entry id the platform reports for that row.
|
|
164
165
|
* @throws when the owner publishes no user layer.
|
|
165
166
|
*/
|
|
167
|
+
/** The keys the USER set in one row's settings namespace (G1 §8.4-A).
|
|
168
|
+
*
|
|
169
|
+
* The settings user layer is read in exactly ONE place, and it reads KEY NAMES, never values: the
|
|
170
|
+
* platform resolves the user layer into the row's live fields, so a value always comes from the
|
|
171
|
+
* row. The precedence rule (user > policy) needs to know WHICH keys the user set, or a deployment
|
|
172
|
+
* policy would silently override the user. S2.9 (T3-10): review and curator each had their own
|
|
173
|
+
* reader, so one key could be resolved two ways.
|
|
174
|
+
* @param ctx - the plugin context (a `get(name)` view is enough).
|
|
175
|
+
* @param rowId - the row's registry namespace (`paramRowId(<package>)`).
|
|
176
|
+
* @returns the keys present in that row's user layer.
|
|
177
|
+
*/
|
|
178
|
+
export declare function userSetKeys(ctx: {
|
|
179
|
+
get(name: string): unknown;
|
|
180
|
+
}, rowId: string): ReadonlySet<string>;
|
|
181
|
+
/** ONE key's effective value, in the design's precedence (G1 §8.4-A): the user layer (whose value
|
|
182
|
+
* already arrived through the row's live field) → the deployment policy snapshot → the row itself.
|
|
183
|
+
* S2.9 (T3-10): both readers of the policy row resolve through this, so a new key cannot get one
|
|
184
|
+
* answer on the review side and another on the curator side.
|
|
185
|
+
* @param userSet - the keys the user set (see {@link userSetKeys}).
|
|
186
|
+
* @param row - the row's live fields.
|
|
187
|
+
* @param snapshot - the deployment policy snapshot, when a policy row is mounted.
|
|
188
|
+
* @param key - the key being resolved.
|
|
189
|
+
* @returns the effective value.
|
|
190
|
+
*/
|
|
191
|
+
export declare function pickWithPolicy<R extends object, K extends keyof R & string>(userSet: ReadonlySet<string>, row: R, snapshot: Partial<Record<K, R[K]>> | undefined, key: K): R[K];
|
|
166
192
|
export declare function paramRowId(owner: string): string;
|
|
167
193
|
/**
|
|
168
194
|
* Structural view of the platform settings provider: only the members the family
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The memory op's payload field, resolved in ONE place (T3-06/A34).
|
|
3
|
+
*
|
|
4
|
+
* A memory op carries its body under two names — `facts` (the original) and
|
|
5
|
+
* `content` (the schema alias) — and every reader used to resolve the pair with
|
|
6
|
+
* `op.facts ?? op.content`. `??` only skips null/undefined, so
|
|
7
|
+
* `{ facts: '', content: '正文' }` counted as "has facts": the plan validator
|
|
8
|
+
* refused the op as empty (a FALSE refusal of a payload that is right there),
|
|
9
|
+
* and the writers that normalized the pair dropped the body to `''`.
|
|
10
|
+
*
|
|
11
|
+
* The payload is the first field whose TRIMMED value is non-empty — a blank
|
|
12
|
+
* string carries no payload, so it yields to its alias. Both blank (or absent)
|
|
13
|
+
* ⇒ `''`, the one shape every caller already reads as "no payload".
|
|
14
|
+
*
|
|
15
|
+
* Non-string values are SKIPPED, never trimmed: a truthy non-string `facts`
|
|
16
|
+
* used to reach `.trim()` and throw a TypeError out of callers that promise to
|
|
17
|
+
* be deterministic (the E-60 / P2-1 class). The field-type gates reject such an
|
|
18
|
+
* op on their own; this helper must not be the one that throws.
|
|
19
|
+
*
|
|
20
|
+
* The union posture matches evolution-threat's E-28a scan (BOTH fields are
|
|
21
|
+
* scanned, not only the first truthy one); the two agree on what counts as
|
|
22
|
+
* provided.
|
|
23
|
+
* @module @lmzhen/dsh-evolution-core
|
|
24
|
+
*/
|
|
25
|
+
/** Anything carrying the two payload aliases; `null` (a persisted or garbage op) resolves to `''`. */
|
|
26
|
+
export interface PayloadFields {
|
|
27
|
+
facts?: unknown;
|
|
28
|
+
content?: unknown;
|
|
29
|
+
}
|
|
30
|
+
/** The op's payload text: the first of `facts`/`content` whose trim is non-empty, trimmed; `''` when neither carries one. */
|
|
31
|
+
export declare function payloadText(op: PayloadFields | null | undefined): string;
|
|
32
|
+
//# sourceMappingURL=payload.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every platform service the family reaches for with `ctx.get`, and what degrades without it.
|
|
3
|
+
*
|
|
4
|
+
* O-2 (v46 audit): the family probes 26 platform services optionally — the platform has no
|
|
5
|
+
* formalized "optional dependency", so an ABSENT service is a silent downgrade and the diagnostic
|
|
6
|
+
* surface is the only place a reader can learn about it. `/evolution doctor` used to judge six of
|
|
7
|
+
* them; this table is the single source it judges the rest from, and the arch guard cross-checks it
|
|
8
|
+
* against the actual probe sites so a new `ctx.get` cannot stay undeclared.
|
|
9
|
+
*
|
|
10
|
+
* `side` decides who can judge it: `host` probes run in the plugin process (doctor judges those),
|
|
11
|
+
* `client` probes run in the browser half, where the host has no view of the seats.
|
|
12
|
+
* @module @lmzhen/dsh-evolution-core
|
|
13
|
+
*/
|
|
14
|
+
/** One platform service the family probes, with the feature that goes quiet without it. */
|
|
15
|
+
export interface PlatformServiceProbe {
|
|
16
|
+
/** The service key as `ctx.get` spells it. */
|
|
17
|
+
readonly service: string;
|
|
18
|
+
/** The family feature that silently degrades when the service is absent. */
|
|
19
|
+
readonly feature: string;
|
|
20
|
+
/** Where the probe runs. */
|
|
21
|
+
readonly side: 'host' | 'client';
|
|
22
|
+
}
|
|
23
|
+
/** The 26 probes, in service-name order (the guard checks this list against the tree). */
|
|
24
|
+
export declare const PLATFORM_SERVICE_PROBES: readonly PlatformServiceProbe[];
|
|
25
|
+
/**
|
|
26
|
+
* The HOST-side probes that do not resolve in this runtime — what `/evolution doctor` prints as
|
|
27
|
+
* "capability absence". Empty is the normal, healthy answer: every capability the family can use
|
|
28
|
+
* is mounted, and the section stays away.
|
|
29
|
+
* @param has - a `ctx.get(name) !== undefined` probe of the running context.
|
|
30
|
+
* @returns the absent host-side probes, in declaration order.
|
|
31
|
+
*/
|
|
32
|
+
export declare function absentHostServices(has: (name: string) => boolean): PlatformServiceProbe[];
|
|
33
|
+
//# sourceMappingURL=platform-services.d.ts.map
|
package/lib/types/probe.d.ts
CHANGED
|
@@ -52,10 +52,38 @@ export declare function isMissingPath(error: unknown): boolean;
|
|
|
52
52
|
* `catch { return [] }` served a broken store as an empty one (the N14 class).
|
|
53
53
|
*/
|
|
54
54
|
export declare function probeList(io: EvolutionIoLike, dir: string): Promise<Probe<string[]>>;
|
|
55
|
+
/**
|
|
56
|
+
* Three-state TEXT read of one file. A missing path (ENOENT/ENOTDIR, or a backend whose read
|
|
57
|
+
* answers null) is `absent`; every other failure is `unknown` with its reason. The distinction is
|
|
58
|
+
* the whole point (N14/O-7): "I could not read this file" and "this file is not there" lead to
|
|
59
|
+
* opposite decisions once a write is about to replace the bytes — treating the first as the
|
|
60
|
+
* second is how a broken store reads as empty and a rescue copy never happens.
|
|
61
|
+
* @param io - the IO seam.
|
|
62
|
+
* @param path - the file to read.
|
|
63
|
+
* @returns the probe.
|
|
64
|
+
*/
|
|
65
|
+
export declare function probeText(io: EvolutionIoLike, path: string): Promise<Probe<string>>;
|
|
55
66
|
/**
|
|
56
67
|
* Three-state mtime read. Absent covers both "the path is missing" and "this
|
|
57
68
|
* backend has no mtime probe" — the seam cannot tell those apart, so consumers
|
|
58
69
|
* that must know say so in their own log line. A stat that FAILS is unknown.
|
|
59
70
|
*/
|
|
60
71
|
export declare function probeMtime(io: EvolutionIoLike, path: string): Promise<Probe<number>>;
|
|
72
|
+
/** The timestamp a curator run report DECLARES: `startedAt` (the run's own start), then `at` (the
|
|
73
|
+
* stamp the error writer sets on every report — F-327/P2-4).
|
|
74
|
+
* @param parsed - the parsed report, or anything else.
|
|
75
|
+
* @returns epoch milliseconds, or null when neither field carries a parseable time.
|
|
76
|
+
*/
|
|
77
|
+
export declare function declaredReportStamp(parsed: unknown): number | null;
|
|
78
|
+
/** The time one curator run report is ordered by — ONE口径 for every reader (S2.9/T3-12): the
|
|
79
|
+
* declared stamp above, then the optional mtime probe as the fallback. `null` means "no usable
|
|
80
|
+
* time", and that single answer serves both consumers: such a report is never the LATEST (the
|
|
81
|
+
* panel) and is never DELETED (the retention sweep), which is exactly the pair of answers the two
|
|
82
|
+
* readers disagreed on when one ordered by the declared stamp and the other by mtime alone.
|
|
83
|
+
* @param io - the IO seam; a backend without `mtime` simply has no fallback.
|
|
84
|
+
* @param path - the report file the probe reads.
|
|
85
|
+
* @param parsed - the already-parsed report.
|
|
86
|
+
* @returns epoch milliseconds, or null.
|
|
87
|
+
*/
|
|
88
|
+
export declare function reportTime(io: EvolutionIoLike, path: string, parsed: unknown): Promise<number | null>;
|
|
61
89
|
//# sourceMappingURL=probe.d.ts.map
|
package/lib/types/prompts.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* changes semantically: the bundle digest is the fail-closed signal for
|
|
4
4
|
* review workers, so a stale id across deployments must be distinguishable.
|
|
5
5
|
*/
|
|
6
|
-
export declare const PROMPT_BUNDLE_VERSION =
|
|
7
|
-
export declare const PROMPT_BUNDLE_ID = "dsh-evolution@
|
|
6
|
+
export declare const PROMPT_BUNDLE_VERSION = 21;
|
|
7
|
+
export declare const PROMPT_BUNDLE_ID = "dsh-evolution@21";
|
|
8
8
|
export declare const MEMORY_REVIEW_PROMPT = "[Auto-review \u2014 Memory]\nReview the conversation above and consider saving to memory if appropriate.\n\nFocus on:\n1. Has the user revealed things about themselves \u2014 persona, desires, preferences, or personal details worth remembering?\n2. Has the user expressed expectations about how you should behave, their work style, or ways they want you to operate?\n\nIf something stands out, save it using the memory tool.\nIf nothing is worth saving, just say \"Nothing to save.\" and stop.";
|
|
9
9
|
export declare const SKILL_REVIEW_PROMPT = "[Auto-review \u2014 Skills]\nReview the conversation above and update the skill library. Be ACTIVE \u2014 most sessions produce at least one skill update, even if small. A pass that does nothing is a missed learning opportunity, not a neutral outcome.\n\nTarget shape of the library: CLASS-LEVEL skills, each with a rich SKILL.md and a references/ directory for session-specific detail. Not a long flat list of narrow one-session-one-skill entries. This shapes HOW you update, not WHETHER you update.\n\nSignals to look for (any one of these warrants action):\n \u2022 User corrected your style, tone, format, legibility, or verbosity. Frustration signals like 'stop doing X', 'this is too verbose', 'don't format like this', 'why are you explaining', 'just give me the answer', 'you always do Y and I hate it', or an explicit 'remember this' are FIRST-CLASS skill signals, not just memory signals. Update the relevant skill(s) to embed the preference so the next session starts already knowing.\n \u2022 User corrected your workflow, approach, or sequence of steps. Encode the correction as a pitfall or explicit step in the skill that governs that class of task.\n \u2022 Non-trivial technique, fix, workaround, debugging path, or tool-usage pattern emerged that a future session would benefit from. Capture it.\n \u2022 A skill that got loaded or consulted this session turned out to be wrong, missing a step, or outdated. Patch it NOW.\n\nRead-before-write: update, patch, delete, or write support files ONLY into skills you loaded or read in THIS session. On the plan channel ops on unread skills are rejected; direct writes have no such guard, so treat the rule as binding. CREATE of a brand-new umbrella is the only exception.\n\nPreference order \u2014 prefer the earliest action that fits, but do pick one when a signal above fired:\n 1. UPDATE A CURRENTLY-LOADED SKILL. Look back through the conversation for skills the user loaded or you read. If any of them covers the territory of the new learning, PATCH that one first. It is the skill that was in play, so it's the right one to extend.\n 2. UPDATE AN EXISTING UMBRELLA. If no loaded skill fits but an existing class-level skill does, patch it. Add a subsection, a pitfall, or broaden a trigger.\n 3. ADD A SUPPORT FILE under an existing umbrella. Skills can be packaged with three kinds of support files \u2014 use the right directory per kind:\n \u2022 references/<topic>.md \u2014 session-specific detail (error transcripts, reproduction recipes, provider quirks) AND condensed knowledge banks: quoted research, API docs, external authoritative excerpts, or domain notes you found while working on the problem. Write it concise and for the value of the task, not as a full mirror of upstream docs.\n \u2022 templates/<name>.<ext> \u2014 starter files meant to be copied and modified (boilerplate configs, scaffolding, a known-good example the agent can reproduce with modifications).\n \u2022 scripts/<name>.<ext> \u2014 statically re-runnable actions the skill can invoke directly (verification scripts, fixture generators, deterministic probes, anything the agent should run rather than hand-type each time).\n Add support files via skill_manage action=write_file with file_path starting 'references/', 'templates/', or 'scripts/'. The umbrella's SKILL.md should gain a one-line pointer to any new support file so future agents know it exists.\n 4. RESTRUCTURE a loaded skill whose body grew log-like \u2014 rc/sha/date-dense sections, session-detail spirals, or a fat body with no support files. Use skill_manage action=restructure with restructure: [{\"heading\": \"<the exact ## heading text>\", \"to_file\": \"references/<topic>.md\"}] \u2014 the ENTIRE ## section (from that heading to the next heading) moves into the support file and its position becomes a pointer line. The skill's name and directory never change. Only propose headings that exist verbatim in the body; never invent one, and never restructure a healthy small skill.\n 5. CREATE A NEW CLASS-LEVEL UMBRELLA SKILL when no existing skill covers the class. The name MUST be at the class level. The name MUST NOT be a specific PR number, error string, feature codename, library-alone name, or 'fix-X / debug-Y / audit-Z-today' session artifact. If the proposed name only makes sense for today's task, it's wrong \u2014 fall back to (1), (2), or (3).\n\nUser-preference embedding (important): when the user expressed a style/format/workflow preference, the update belongs in the SKILL.md body, not just in memory. Memory captures 'who the user is and what the current situation and state of your operations are'; skills capture 'how to do this class of task for this user'. When they complain about how you handled a task, the skill that governs that task needs to carry the lesson.\n\nIf you notice two existing skills that overlap, note it in your reply \u2014 the background curator handles consolidation at scale.\n\nTwo-tier deposition discipline (DSH addition, same spirit as the umbrella rule): before writing, classify the knowledge:\n \u2022 PATTERN (reusable \u2014 symptom \u2192 mechanism \u2192 fix \u2192 verification, still valuable next session) belongs in the SKILL.md body.\n \u2022 LOG (one-off \u2014 commit SHAs, npm/profile states, what this release changed, this session's process narrative) belongs in a references/ file, never the body. Body density IS reuse rate. Keep new entries tight: a pattern fits in 2-8 physical lines; prefer changing the current-state pointer over appending history.\n\nProtected skills (DO NOT edit these):\n \u2022 Bundled skills (shipped with the platform).\n \u2022 Hub-installed skills (installed from a hub).\nPinned skills are read-only to THIS background review pass \u2014 the pinned write guard refuses background changes, so this pass may not update them. They also cannot be archived by any writer (the foreground included): remove the .pinned marker first. Foreground and delegated-subagent update/patch writes to pinned skills remain allowed.\nIf the only skills that need updating are protected, say 'Nothing to save.' and stop.\n\nDo NOT capture (these become persistent self-imposed constraints that bite you later when the environment changes):\n \u2022 Environment-dependent failures: missing binaries, fresh-install errors, post-migration path mismatches, 'command not found', unconfigured credentials, uninstalled packages. The user can fix these \u2014 they are not durable rules.\n \u2022 Negative claims about tools or features ('browser tools do not work', 'X tool is broken', 'cannot use Y'). These harden into refusals the agent cites against itself for months after the actual problem was fixed.\n \u2022 Session-specific transient errors that resolved before the conversation ended. If retrying worked, the lesson is the retry pattern, not the original failure.\n \u2022 One-off task narratives. A user asking 'summarize today's market' or 'analyze this PR' is not a class of work that warrants a skill.\n\nIf a tool failed because of setup state, capture the FIX (install command, config step, env var to set) under an existing setup or troubleshooting skill \u2014 never 'this tool does not work' as a standalone constraint.\n\n'Nothing to save.' is a real option but should NOT be the default. If the session ran smoothly with no corrections and produced no new technique, just say 'Nothing to save.' and stop. Otherwise, act.";
|
|
10
10
|
export declare const COMBINED_REVIEW_PROMPT = "[Auto-review]\nReview the conversation above and update two things:\n\n**Memory**: who the user is. Did the user reveal persona, desires, preferences, personal details, or expectations about how you should behave? Save facts about the user and durable preferences with the memory tool.\n\n**Skills**: how to do this class of task. Be ACTIVE \u2014 most sessions produce at least one skill update. A pass that does nothing is a missed learning opportunity, not a neutral outcome.\n\nTarget shape of the skill library: CLASS-LEVEL skills with a rich SKILL.md and a references/ directory for session-specific detail. Not a long flat list of narrow one-session-one-skill entries.\n\nSignals that warrant a skill update (any one is enough):\n \u2022 User corrected your style, tone, format, legibility, verbosity, or approach. Frustration is a FIRST-CLASS skill signal, not just a memory signal. 'stop doing X', 'don't format like this', 'I hate when you Y' \u2014 embed the lesson in the skill that governs that task so the next session starts fixed.\n \u2022 Non-trivial technique, fix, workaround, or debugging path emerged.\n \u2022 A skill that was loaded or consulted turned out wrong, missing, or outdated \u2014 patch it now.\n\nRead-before-write: update, patch, delete, or write support files ONLY into skills you loaded or read in THIS session. On the plan channel ops on unread skills are rejected; direct writes have no such guard, so treat the rule as binding. CREATE of a brand-new umbrella is the only exception.\n\nPreference order for skills \u2014 pick the earliest that fits:\n 1. UPDATE A CURRENTLY-LOADED SKILL. Check what skills were loaded or read in the conversation. If one of them covers the learning, PATCH it first. It was in play; it's the right place.\n 2. UPDATE AN EXISTING UMBRELLA. Patch it.\n 3. ADD A SUPPORT FILE under an existing umbrella via skill_manage action=write_file. Three kinds: references/<topic>.md for session-specific detail OR condensed knowledge banks (quoted research, API docs excerpts, domain notes) written concise and task-focused; templates/<name>.<ext> for starter files meant to be copied and modified; scripts/<name>.<ext> for statically re-runnable actions (verification, fixture generators, probes). Add a one-line pointer in SKILL.md so future agents find them.\n 4. RESTRUCTURE a loaded skill whose body grew log-like (rc/sha/date-dense sections, session-detail spirals, fat body with no support files) via skill_manage action=restructure with restructure: [{\"heading\": \"<the exact ## heading text>\", \"to_file\": \"references/<topic>.md\"}] \u2014 the ENTIRE ## section moves into the support file and its position becomes a pointer line; the skill's name and directory never change. Only propose headings that exist verbatim in the body.\n 5. CREATE A NEW CLASS-LEVEL UMBRELLA when nothing exists. Name at the class level \u2014 NOT a PR number, error string, codename, library-alone name, or 'fix-X / debug-Y' session artifact. If the name only fits today's task, fall back to (1), (2), or (3).\n\nTwo-tier deposition discipline (DSH addition): classify before writing \u2014 PATTERN (symptom \u2192 mechanism \u2192 fix \u2192 verification) goes in the SKILL.md body; LOG (commit SHAs, npm/profile states, this release's change list, this session's narrative) goes in a references/ file. Body density IS reuse rate; a pattern fits in 2-8 physical lines.\n\nUser-preference embedding: when the user complains about how you handled a task, update the skill that governs that task \u2014 memory alone isn't enough. Memory says 'who the user is and what the current situation and state of your operations are'; skills say 'how to do this class of task for this user'. Both should carry user-preference lessons when relevant.\n\nIf you notice overlapping existing skills, mention it \u2014 the background curator handles consolidation.\n\nProtected skills (DO NOT edit these):\n \u2022 Bundled skills (shipped with the platform).\n \u2022 Hub-installed skills (installed from a hub).\nPinned skills are read-only to THIS background review pass \u2014 the pinned write guard refuses background changes, so this pass may not update them. They also cannot be archived by any writer (the foreground included): remove the .pinned marker first. Foreground and delegated-subagent update/patch writes to pinned skills remain allowed.\nIf the only skills that need updating are protected, say 'Nothing to save.' and stop.\n\nDo NOT capture as skills (these become persistent self-imposed constraints that bite you later when the environment changes):\n \u2022 Environment-dependent failures: missing binaries, fresh-install errors, post-migration path mismatches, 'command not found', unconfigured credentials, uninstalled packages. The user can fix these \u2014 they are not durable rules.\n \u2022 Negative claims about tools or features ('browser tools do not work', 'X tool is broken', 'cannot use Y'). These harden into refusals the agent cites against itself for months after the actual problem was fixed.\n \u2022 Session-specific transient errors that resolved before the conversation ended. If retrying worked, the lesson is the retry pattern, not the original failure.\n \u2022 One-off task narratives. A user asking 'summarize today's market' or 'analyze this PR' is not a class of work that warrants a skill.\n\nIf a tool failed because of setup state, capture the FIX (install command, config step, env var to set) under an existing setup or troubleshooting skill \u2014 never 'this tool does not work' as a standalone constraint.\n\nAct on whichever of the two dimensions has real signal. If genuinely nothing stands out on either, say 'Nothing to save.' and stop \u2014 but don't reach for that conclusion as a default.";
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* path's gate, whose exec view may be a bare stub): it answers "which accessor, and what does an
|
|
14
14
|
* unreadable session mean" — `undefined`, so the gate keeps its previous behavior instead of
|
|
15
15
|
* refusing every write. The review path reads a real platform `Session` and calls the reader
|
|
16
|
-
* directly; a fallback there would be dead code for an impossible case.
|
|
16
|
+
* directly; a fallback there would be dead code for an impossible case. The structural accessor is
|
|
17
|
+
* the family's OWN `readEvents` (A69): production reads no deprecated platform log accessor.
|
|
17
18
|
*
|
|
18
19
|
* v32 REV-06(a) is preserved by the dispatch normalizer: a skill counts as READ only when its read
|
|
19
20
|
* did not fail, so a failed/timeout read cannot pass the gate and let a writer blind-overwrite
|
|
@@ -23,10 +24,18 @@
|
|
|
23
24
|
* @module @lmzhen/dsh-evolution-core
|
|
24
25
|
*/
|
|
25
26
|
import type { Context } from '@deepseek-ai/cordis';
|
|
26
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* The family's OWN structural view of a session log, for a caller that holds no platform `Session`
|
|
29
|
+
* (the tool path's exec view may be a bare stub). The CALLER supplies it — the family reads no
|
|
30
|
+
* deprecated platform accessor (`snapshotEvents`/`eventAt`/`ownEvents`): those are the platform's own
|
|
31
|
+
* migration debt, and a family that reads them keeps the deprecated surface alive in every
|
|
32
|
+
* composition (A69 / 6-2). A platform `Session` does not carry this member and therefore takes the
|
|
33
|
+
* projection path below; this view exists so the fallback still has a basis instead of being deleted
|
|
34
|
+
* (deleting it would leave a stub session's read-before-write gate with nothing to fold).
|
|
35
|
+
*/
|
|
27
36
|
export interface EvolutionSessionLogView {
|
|
28
|
-
/** @returns the
|
|
29
|
-
|
|
37
|
+
/** @returns the events this view can see, oldest first; absent when the caller has no log. */
|
|
38
|
+
readEvents?: () => Iterable<{
|
|
30
39
|
type: string;
|
|
31
40
|
data?: unknown;
|
|
32
41
|
}>;
|
|
@@ -64,6 +64,13 @@ export interface SkillSummary {
|
|
|
64
64
|
* drift scans) saves the second per-skill read; the default stays body-free so
|
|
65
65
|
* the common listing does not hold a whole tree in memory. */
|
|
66
66
|
content?: string;
|
|
67
|
+
/** T2-07/A22: the parsed frontmatter record, present only for
|
|
68
|
+
* {@link SkillLibrary.list} calls that pass `{ withFrontmatter: true }`. The
|
|
69
|
+
* listing parses the file it already read, so a consumer that derives a policy
|
|
70
|
+
* from frontmatter (the catalog’s invocation map) shares that ONE read instead
|
|
71
|
+
* of paying a second per-skill read. Absent when the file carries no parseable
|
|
72
|
+
* frontmatter — the same posture as its description. */
|
|
73
|
+
frontmatter?: Record<string, unknown>;
|
|
67
74
|
}
|
|
68
75
|
/**
|
|
69
76
|
* Stage-time anchor for a full-content write (v35 C9): the sha256 the caller read
|
|
@@ -326,12 +333,20 @@ export declare class SkillLibrary {
|
|
|
326
333
|
/**
|
|
327
334
|
* Summarize the skill tree.
|
|
328
335
|
* @param options - `withContent` attaches each skill's whole SKILL.md body to
|
|
329
|
-
* its summary (v35 C11)
|
|
330
|
-
*
|
|
336
|
+
* its summary (v35 C11), `withFrontmatter` its parsed frontmatter record
|
|
337
|
+
* (T2-07/A22): the read this listing already performs is the one either would
|
|
338
|
+
* cost again, so a caller pays no second pass over the tree.
|
|
331
339
|
* @returns one summary per readable skill directory.
|
|
332
340
|
*/
|
|
341
|
+
/** T2-V2/A27: the skill names the TREE holds — the same listing rule {@link list} applies (dot
|
|
342
|
+
* entries skipped, a SKILL.md must exist), WITHOUT reading any SKILL.md. A consumer that needs the
|
|
343
|
+
* member set rather than the summaries (the learning graph's node set) pays one directory listing
|
|
344
|
+
* instead of a whole-tree read pass.
|
|
345
|
+
* @returns the sorted skill directory names. */
|
|
346
|
+
treeNames(): Promise<string[]>;
|
|
333
347
|
list(options?: {
|
|
334
348
|
withContent?: boolean;
|
|
349
|
+
withFrontmatter?: boolean;
|
|
335
350
|
}): Promise<SkillSummary[]>;
|
|
336
351
|
read(rawName: string): Promise<string | null>;
|
|
337
352
|
/**
|
|
@@ -390,6 +405,13 @@ export declare class SkillLibrary {
|
|
|
390
405
|
* @returns path -> character count (possibly empty), or null when the listing or
|
|
391
406
|
* a size probe cannot answer — unknown is never an empty map.
|
|
392
407
|
*/
|
|
408
|
+
/** A plan-time read whose bytes become a tree change's CAS baseline (`expected`). A failed read is
|
|
409
|
+
* NAMED and answers `null`, which is FAIL-CLOSED at commit: the baseline is then "absent", so an
|
|
410
|
+
* existing file counts as drift and the commit refuses instead of overwriting bytes nobody read
|
|
411
|
+
* (N36: the failure is not silently "the file is not there").
|
|
412
|
+
* @param target - the file a tree change is about to write.
|
|
413
|
+
* @returns its bytes, or `null` for both "absent" and "could not read". */
|
|
414
|
+
private readForCas;
|
|
393
415
|
supportFileChars(rawName: string): Promise<Record<string, number> | null>;
|
|
394
416
|
/**
|
|
395
417
|
* Structure-health facts for one skill (rc.73 A1, 008 design): body
|
package/package.json
CHANGED
|
@@ -50,12 +50,8 @@
|
|
|
50
50
|
"writer": "evolution-state-json/src/index.ts",
|
|
51
51
|
"serializedBy": "transact",
|
|
52
52
|
"marker": "transactIo(io(), join(root, file)",
|
|
53
|
-
"state": [
|
|
54
|
-
|
|
55
|
-
"evolution-state-json/src/index.ts :: corruptWritten",
|
|
56
|
-
"evolution-state-json/src/index.ts :: corruptWriteWarned"
|
|
57
|
-
],
|
|
58
|
-
"note": "The durable state stack (review/curator/pending). Its three warn sets are per-process de-duplication and are registered under N12."
|
|
53
|
+
"state": [],
|
|
54
|
+
"note": "The durable state stack (review/curator/pending). S4.4/O-6 moved its three warn/rewrite sets into the provider instance (QuarantineMemory), so no module-scope N12 store remains here."
|
|
59
55
|
},
|
|
60
56
|
{
|
|
61
57
|
"id": "curator-reports",
|
|
@@ -74,5 +70,50 @@
|
|
|
74
70
|
"marker": "LOCK_SUFFIX",
|
|
75
71
|
"state": [],
|
|
76
72
|
"note": "The skill tree itself. Per-target `<path>.lock` claims (pid:token body) guard every byte writer; destructive movers refuse a live lock."
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "activity-log",
|
|
76
|
+
"path": "<evolutionRoot>/activity.json",
|
|
77
|
+
"writer": "evolution-activity/src/index.ts",
|
|
78
|
+
"serializedBy": "transact",
|
|
79
|
+
"marker": "transactIo(io, activityFile(root)",
|
|
80
|
+
"state": [],
|
|
81
|
+
"note": "The plan-applied activity sidecar. Declared by v46 S1.12: the file carried a transactional write that no inventory row vouched for, and the N20 reverse scan is what named it."
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "skill-history-index",
|
|
85
|
+
"path": "<historyRoot>/skills/<name>/index.json",
|
|
86
|
+
"writer": "evolution-core/src/skill-history.ts",
|
|
87
|
+
"serializedBy": "transact",
|
|
88
|
+
"marker": "transactIo(io, file,",
|
|
89
|
+
"state": [],
|
|
90
|
+
"note": "One skill's recorded-version index (content chain + blob references). Declared by v46 S1.12: the replace path was already transactional but undeclared, so a regression to a plain write would have been invisible."
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": "skill-store-transact-primitive",
|
|
94
|
+
"path": "<caller-supplied>",
|
|
95
|
+
"writer": "evolution-core/src/skill-store.ts",
|
|
96
|
+
"serializedBy": "transact",
|
|
97
|
+
"marker": "transactIo(ioLike, path, task)",
|
|
98
|
+
"state": [],
|
|
99
|
+
"note": "The store's transaction helper: it prefers the backend's own transact and falls back to the seam's transactIo. The target is the caller's path — each durable target has its own row above; this row vouches for the primitive itself."
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"id": "feedback-events",
|
|
103
|
+
"path": "<dshHome>/evolution/events.json",
|
|
104
|
+
"writer": "evolution-feedback/src/index.ts",
|
|
105
|
+
"serializedBy": "transact",
|
|
106
|
+
"marker": "transactIo(io, eventsPath,",
|
|
107
|
+
"state": [],
|
|
108
|
+
"note": "Feedback appends to the family event log under the cross-process lock. Declared by v46 S1.12 (N20 reverse)."
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "pending-archive",
|
|
112
|
+
"path": "<stateHome>/pending-state-archive.json",
|
|
113
|
+
"writer": "evolution-state-json/src/index.ts",
|
|
114
|
+
"serializedBy": "transact",
|
|
115
|
+
"marker": "transactIo(io(), pathOf(PENDING_ARCHIVE_FILE)",
|
|
116
|
+
"state": [],
|
|
117
|
+
"note": "The resolved-pending archive (the legacy merge target). The store already declared its record map; v46 S1.12 declared the archive append as well — the second site in one file is exactly what the reverse scan exists to find."
|
|
77
118
|
}
|
|
78
119
|
]
|