@lmzhen/dsh-evolution-core 0.3.62 → 0.3.64
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 +14 -12
- package/lib/index.js +561 -174
- package/lib/types/constants.d.ts +10 -1
- package/lib/types/events.d.ts +3 -1
- package/lib/types/evolution-events.d.ts +11 -0
- package/lib/types/io.d.ts +15 -0
- package/lib/types/memory-store.d.ts +4 -0
- package/lib/types/quality.d.ts +2 -2
- package/lib/types/skill-store.d.ts +80 -1
- package/lib/types/usage.d.ts +14 -1
- package/package.json +1 -1
package/lib/types/constants.d.ts
CHANGED
|
@@ -19,7 +19,13 @@
|
|
|
19
19
|
* threshold, which are intentionally left where they are used.
|
|
20
20
|
* @module @lmzhen/dsh-evolution-core
|
|
21
21
|
*/
|
|
22
|
-
/** Skill frontmatter `name` validated for the file name (lowercase + hyphen).
|
|
22
|
+
/** Skill frontmatter `name` validated for the file name (lowercase + hyphen).
|
|
23
|
+
* 计划 B-4 (v18): tightened to the UPSTREAM `SKILL_NAME` shape
|
|
24
|
+
* (`/^[a-z0-9]+(?:-[a-z0-9]+)*$/`, packages/skill/skill/src/index.ts:20). The
|
|
25
|
+
* old form admitted trailing/consecutive hyphens, which upstream
|
|
26
|
+
* `validateCandidate` throws on — and that throw aborts the WHOLE `ctx.skills`
|
|
27
|
+
* collection. The catalog provider still filters such legacy tree entries so
|
|
28
|
+
* an existing tree cannot break a session. */
|
|
23
29
|
export declare const SKILL_NAME_RE: RegExp;
|
|
24
30
|
/** Allowed skill support-file subdirectories (path-traversal boundary). */
|
|
25
31
|
export declare const SUPPORT_DIRS: readonly ["references", "templates", "scripts", "assets"];
|
|
@@ -53,6 +59,9 @@ export declare const DEFAULT_MEMORY_CHAR_LIMIT = 2200;
|
|
|
53
59
|
export declare const DEFAULT_USER_CHAR_LIMIT = 1375;
|
|
54
60
|
/** Consolidation-failure backoff cap, shared by MemoryStore and memory-files' Config default. */
|
|
55
61
|
export declare const DEFAULT_CONSOLIDATION_FAILURES = 3;
|
|
62
|
+
/** F-20 (v18): the authored-body budget and the hard ceiling are the same
|
|
63
|
+
* number today. Derive it so a future divergence is one edit, not two names
|
|
64
|
+
* that silently disagree. */
|
|
56
65
|
export declare const DEFAULT_SKILL_CONTENT_CHARS = 100000;
|
|
57
66
|
/** P3-19 (v14): defaults that were written twice (schema `.default()` AND the
|
|
58
67
|
* clamp fallback literal) now have one home per value. */
|
package/lib/types/events.d.ts
CHANGED
|
@@ -12,10 +12,12 @@
|
|
|
12
12
|
* these events off `session.append`). Plan-outcome durability lives in the
|
|
13
13
|
* evolution-activity store, not the session log.
|
|
14
14
|
*/
|
|
15
|
+
import type { ReviewKind } from './signals.ts';
|
|
15
16
|
export interface EvolutionReviewScheduledEvent {
|
|
16
17
|
/** Owning session (payload v2): process events carry no session envelope. */
|
|
17
18
|
sessionId: string;
|
|
18
|
-
|
|
19
|
+
/** F-20 (v18): single definition point — `ReviewKind` in signals.ts. */
|
|
20
|
+
kind: ReviewKind;
|
|
19
21
|
toolCalls: number;
|
|
20
22
|
userChars: number;
|
|
21
23
|
assistantChars: number;
|
|
@@ -68,6 +68,17 @@ export interface EvolutionEvent {
|
|
|
68
68
|
} | undefined;
|
|
69
69
|
}
|
|
70
70
|
export declare function eventsFile(home: string): string;
|
|
71
|
+
/** I-5 (v18): one lightweight description of the durable event payload
|
|
72
|
+
* contract. The log is a FILE boundary (a host, a script or an older version
|
|
73
|
+
* can write it), so `appendEvolutionEvent` refuses a record no consumer can
|
|
74
|
+
* fold instead of persisting it and failing silently later. The process event
|
|
75
|
+
* bus stays unvalidated — that is a typed same-process boundary.
|
|
76
|
+
* @param event - the candidate event record.
|
|
77
|
+
* @returns a human-readable issue, or null when the record is well-formed.
|
|
78
|
+
*/
|
|
79
|
+
export declare function evolutionEventPayloadIssue(event: {
|
|
80
|
+
type?: unknown;
|
|
81
|
+
} & Partial<EvolutionEvent>): string | null;
|
|
71
82
|
export declare function parseEvolutionEvents(raw: string | null): EvolutionEvent[];
|
|
72
83
|
/**
|
|
73
84
|
* List the numeric archives under the log's directory, sorted ascending by
|
package/lib/types/io.d.ts
CHANGED
|
@@ -106,6 +106,21 @@ export declare function renameWithRetry(tmp: string, target: string, fn?: (from:
|
|
|
106
106
|
* failing `handle.sync()` deterministically.
|
|
107
107
|
*/
|
|
108
108
|
export declare function writeDurableTmp(target: string, content: string, openImpl?: typeof open): Promise<string>;
|
|
109
|
+
/**
|
|
110
|
+
* True when the pid is alive (EPERM = alive but unowned; ESRCH = gone).
|
|
111
|
+
* V18 single source: the node backend's lock takeover and SkillLibrary's
|
|
112
|
+
* stranded-lock sweep must use the same liveness rule.
|
|
113
|
+
*/
|
|
114
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
115
|
+
/** F-17 (v18): the write-lock protocol is a cross-module contract — the lock
|
|
116
|
+
* file is `<target>.lock` and its body is `<pid>:<token>`. This module creates
|
|
117
|
+
* them (`withWriteLock`) and `skill-store`'s probes/sweepers parse them, so both
|
|
118
|
+
* consume these two constants instead of repeating the literals. */
|
|
119
|
+
export declare const LOCK_SUFFIX = ".lock";
|
|
120
|
+
/** Writer-lock body shape: a decimal pid, a colon, then the claim token. A
|
|
121
|
+
* torn body (no parsable pid) still matches the `\\d+:` prefix rule only when
|
|
122
|
+
* the pid part is intact, which is what the takeover probe needs. */
|
|
123
|
+
export declare const LOCK_BODY_RE: RegExp;
|
|
109
124
|
/**
|
|
110
125
|
* Build the Node IO backend. `lockAttempts` scales the write-lock retry budget
|
|
111
126
|
* (attempts × 50ms); the default 40 (~2s, rc.69) covers production contention,
|
|
@@ -57,6 +57,10 @@ export declare class MemoryStore {
|
|
|
57
57
|
* label (scanMemoryThreats already embeds it) plus the self-heal hint. */
|
|
58
58
|
private memoryThreatBlock;
|
|
59
59
|
limitFor(target: MemoryTarget): number;
|
|
60
|
+
/** P2-1 (v18): the generated date prefix participates in duplicate detection
|
|
61
|
+
* only when THIS store writes it. With addDatePrefix=false a fact's own
|
|
62
|
+
* leading `## YYYY-MM-DD\n` is content, not a generated prefix. */
|
|
63
|
+
private dedupeKey;
|
|
60
64
|
/**
|
|
61
65
|
* Read-guard probe: `{ size, limit }` when the on-disk file exceeds
|
|
62
66
|
* `limit * READ_GUARD_FACTOR` bytes, `null` when it is absent, unknown
|
package/lib/types/quality.d.ts
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { UsageMap } from './usage.ts';
|
|
13
13
|
export interface QualityFactors {
|
|
14
|
-
/** 0.25 —
|
|
14
|
+
/** 0.25 — skill LOADS per day of age (view_count + use_count), capped at 1. */
|
|
15
15
|
usageFrequency: number;
|
|
16
|
-
/** 0.20 — 1 − patch/
|
|
16
|
+
/** 0.20 — 1 − patch/load (zero loads = stable). */
|
|
17
17
|
stability: number;
|
|
18
18
|
/** 0.20 — 1 under 30 idle days, linear decay to 0 at 180. */
|
|
19
19
|
recency: number;
|
|
@@ -22,8 +22,15 @@ export interface SkillSummary {
|
|
|
22
22
|
description: string;
|
|
23
23
|
path: string;
|
|
24
24
|
protectedBy: string | null;
|
|
25
|
+
/** A1-17 (v18): the marker probe itself failed (EACCES/EIO), so "no marker"
|
|
26
|
+
* cannot be told apart from "directory unreadable". Consumers must treat this
|
|
27
|
+
* as protected, never as unprotected. */
|
|
28
|
+
protectionUnknown: boolean;
|
|
25
29
|
managed: boolean;
|
|
26
|
-
|
|
30
|
+
/** E-11 (v18): the frontmatter `whenToUse` routing hint, published so the
|
|
31
|
+
* platform catalog keeps it while this provider shadows the upstream
|
|
32
|
+
* filesystem provider. Absent when the frontmatter has none. */
|
|
33
|
+
whenToUse?: string;
|
|
27
34
|
}
|
|
28
35
|
export interface SkillActionResult {
|
|
29
36
|
ok: boolean;
|
|
@@ -99,6 +106,21 @@ export declare function skillsRoot(env?: NodeJS.ProcessEnv): string;
|
|
|
99
106
|
export declare function resolveSkillsRoot(config?: {
|
|
100
107
|
root?: string | undefined;
|
|
101
108
|
}): string;
|
|
109
|
+
/** E-7 (v18): every family row reads ONE root key. `root` is canonical;
|
|
110
|
+
* `skillsRoot` is a deprecated alias honoured only while `root` is empty (so a
|
|
111
|
+
* deployment that sets both keeps the canonical one) and removed after 0.3.65.
|
|
112
|
+
* Callers log their own deprecation warning.
|
|
113
|
+
* @param config - the raw plugin config, carrying `root` and/or `skillsRoot`.
|
|
114
|
+
* @returns the effective root (empty when neither key is set) and whether the
|
|
115
|
+
* deprecated alias supplied it.
|
|
116
|
+
*/
|
|
117
|
+
export declare function resolveRootConfig(config?: {
|
|
118
|
+
root?: string | undefined;
|
|
119
|
+
skillsRoot?: string | undefined;
|
|
120
|
+
}): {
|
|
121
|
+
root: string;
|
|
122
|
+
usedDeprecatedAlias: boolean;
|
|
123
|
+
};
|
|
102
124
|
/**
|
|
103
125
|
* Map a requesting session onto the two origin surfaces (rc.44 plan M2-2.3):
|
|
104
126
|
* the APPROVAL surface treats every delegated subagent as the autonomous
|
|
@@ -228,6 +250,11 @@ export interface AuthoringFeedback {
|
|
|
228
250
|
* truncated or route-poor instead of silently shipping it.
|
|
229
251
|
*/
|
|
230
252
|
export declare function authoringFeedback(frontmatter: Frontmatter): AuthoringFeedback;
|
|
253
|
+
/** A1-6 (v18): the regex alone admits `references/nul.md` (a Windows device
|
|
254
|
+
* stem), which the support-file layer refuses. Restructure must use the same
|
|
255
|
+
* rule, or it creates an orphan the later patch/write/remove paths refuse.
|
|
256
|
+
* Single source shared with the plan validator. */
|
|
257
|
+
export declare function validateRestructureTarget(filePath: string): string | null;
|
|
231
258
|
export declare class SkillLibrary {
|
|
232
259
|
readonly root: string;
|
|
233
260
|
readonly limits: SkillLimits;
|
|
@@ -333,12 +360,64 @@ export declare class SkillLibrary {
|
|
|
333
360
|
* marker write is the only state change; content is untouched.
|
|
334
361
|
*/
|
|
335
362
|
setPinned(name: string, pinned: boolean, origin?: WriteOrigin): Promise<SkillActionResult>;
|
|
363
|
+
private setPinnedCore;
|
|
336
364
|
create(name: string, content: string, origin?: WriteOrigin): Promise<SkillActionResult>;
|
|
337
365
|
private createCore;
|
|
338
366
|
update(rawName: string, content: string, origin?: WriteOrigin): Promise<SkillActionResult>;
|
|
339
367
|
private updateCore;
|
|
340
368
|
patch(rawName: string, oldString: string, newString: string, filePath?: string, replaceAll?: boolean, origin?: WriteOrigin): Promise<SkillActionResult>;
|
|
341
369
|
private patchCore;
|
|
370
|
+
/**
|
|
371
|
+
* P2-9 (v15): the destructive directory move shared by archive and
|
|
372
|
+
* restoreFromArchive — rename first, copy+remove fallback when the backend
|
|
373
|
+
* cannot rename across media (V5-35), with the E-14 rollback when the
|
|
374
|
+
* fallback's source removal fails. Returns a failure MESSAGE on a failed
|
|
375
|
+
* move (caller wraps into a structured result) or undefined on success.
|
|
376
|
+
*/
|
|
377
|
+
private moveDir;
|
|
378
|
+
/**
|
|
379
|
+
* P2 (v16): the write-lock probe for the DESTRUCTIVE MOVERS (archive /
|
|
380
|
+
* restoreFromArchive). A byte-writer mid-flight is the ghost-generator —
|
|
381
|
+
* after the move its transact commit re-creates `<dir>/…` (mkdir
|
|
382
|
+
* recursive) and the tree ends half-archived. The signal is the writer's
|
|
383
|
+
* own lock file, and its PLACEMENT (inside the moved directory) is why the
|
|
384
|
+
* mover must PROBE-and-REFUSE instead of acquiring it: an acquired lock
|
|
385
|
+
* would be renamed into `.archive` with the tree, stranding a phantom live
|
|
386
|
+
* lock (the v16 audit proved the probe→rename TOCTOU does exactly that,
|
|
387
|
+
* and restore would later move the residue back into the live root).
|
|
388
|
+
* Coverage: `SKILL.md.lock` (update/patch of the body) plus one level of
|
|
389
|
+
* each support dir (write_file's lock sits next to its file). Residual:
|
|
390
|
+
* NESTED support-subdir locks and the probe→rename TOCTOU itself remain
|
|
391
|
+
* fail-safe (renameWithRetry rides the write out; the writer's locked
|
|
392
|
+
* re-read refuses on the moved-away file), and a residue `.lock` from a
|
|
393
|
+
* CRASHED writer also refuses — correct: inspect, don't archive.
|
|
394
|
+
*/
|
|
395
|
+
private hasWriteLock;
|
|
396
|
+
/** P2 (v17): a file only counts as a writer lock when its body has the
|
|
397
|
+
* `pid:token` shape the io layer writes. User support files legitimately
|
|
398
|
+
* named `*.lock` (allowed by SUPPORT_FILE_NAME_RE) must not trip the probe
|
|
399
|
+
* or be swept as residue — the v16 first cut matched on suffix alone,
|
|
400
|
+
* which permanently refused archiving and deleted user content on restore. */
|
|
401
|
+
private isWriterLock;
|
|
402
|
+
/** P2 (v16): best-effort removal of lock residue inside a RESTORED tree.
|
|
403
|
+
* A1-2/A1-7 (v18): the sweep now covers the marker locks the probe checks
|
|
404
|
+
* (`SKILL.md.lock`/`.pinned.lock`/`.hermes-managed.lock`) and only removes
|
|
405
|
+
* a lock whose holder pid is NOT alive — a live writer's lock is never
|
|
406
|
+
* stolen by the sweep. A dead-pid residue would otherwise permanently
|
|
407
|
+
* refuse archive/restore. */
|
|
408
|
+
private deleteStrandedLocks;
|
|
409
|
+
/** Remove `lockPath` only when its body has the writer-lock `pid:token`
|
|
410
|
+
* shape AND the holder pid is not alive; anything else (a user support file
|
|
411
|
+
* or a live writer's lock) is left untouched. */
|
|
412
|
+
private sweepLockIfStranded;
|
|
413
|
+
/** A1-4 (v18): a manifest-declared name is copied with `join(root, name)`;
|
|
414
|
+
* only a single, non-traversing path component is safe. Dotfiles
|
|
415
|
+
* (`.usage.json`) stay allowed — sidecars are legitimately dot-prefixed. */
|
|
416
|
+
private safeSnapshotEntryName;
|
|
417
|
+
/** A1-7 (v18): a root-level lock whose holder is alive must refuse the
|
|
418
|
+
* restore; a dead residue is swept so a crashed writer cannot block
|
|
419
|
+
* recovery. A non-lock body shape is left alone (user file). */
|
|
420
|
+
private refuseLiveLockOrSweep;
|
|
342
421
|
archive(rawName: string, options?: ArchiveOptions): Promise<SkillActionResult>;
|
|
343
422
|
/**
|
|
344
423
|
* Merge the bodies of `sources` into `target` and archive the sources with
|
package/lib/types/usage.d.ts
CHANGED
|
@@ -18,6 +18,16 @@ export interface UsageRecord {
|
|
|
18
18
|
archived_at: string | null;
|
|
19
19
|
quality_score?: number | undefined;
|
|
20
20
|
quality_warn?: boolean | undefined;
|
|
21
|
+
/** P1-1 (v15): feedback-owned quality signal. Field ownership contract —
|
|
22
|
+
* `quality_score`/`quality_warn` are written ONLY by the curator's
|
|
23
|
+
* six-factor `scoreTree`; `feedback_score`/`feedback_warn` are written ONLY
|
|
24
|
+
* by the feedback channel (`SkillUsageRegistry.setFeedbackQuality`);
|
|
25
|
+
* `foldCuratorFields` refreshes the quality_* pair tree-wide and must never
|
|
26
|
+
* touch feedback_*. The lifecycle engine and the scope view read the UNION
|
|
27
|
+
* of both warn flags, which is what makes negative feedback decision-
|
|
28
|
+
* relevant again. */
|
|
29
|
+
feedback_score?: number | undefined;
|
|
30
|
+
feedback_warn?: boolean | undefined;
|
|
21
31
|
}
|
|
22
32
|
export type UsageMap = Map<string, UsageRecord>;
|
|
23
33
|
export declare function usageFile(root: string): string;
|
|
@@ -60,6 +70,9 @@ export declare function applyCuratorLifecycleFields(disk: UsageRecord, curated:
|
|
|
60
70
|
* Copy the recomputed meta pair (quality_score/quality_warn + the
|
|
61
71
|
* marker-mirrored pin flag) — refreshed tree-wide each run by design, so a
|
|
62
72
|
* concurrent curator run's lifecycle changes are never reverted by them.
|
|
73
|
+
* P1-1 (v15): the feedback pair (`feedback_score`/`feedback_warn`) is
|
|
74
|
+
* deliberately NOT copied — it is feedback-owned (see the field-ownership
|
|
75
|
+
* contract on {@link UsageRecord}) and must survive curator runs untouched.
|
|
63
76
|
*/
|
|
64
77
|
export declare function applyCuratorMetaFields(disk: UsageRecord, curated: UsageRecord): void;
|
|
65
78
|
/**
|
|
@@ -72,7 +85,7 @@ export declare function applyCuratorMetaFields(disk: UsageRecord, curated: Usage
|
|
|
72
85
|
* transitioned — a concurrent curator run's archive/restore is never reverted
|
|
73
86
|
* by a stale snapshot; without it both pairs apply everywhere.
|
|
74
87
|
*/
|
|
75
|
-
export declare function foldCuratorFields(disk: UsageMap, curated: UsageMap, stateOwned?: ReadonlySet<string>):
|
|
88
|
+
export declare function foldCuratorFields(disk: UsageMap, curated: UsageMap, stateOwned?: ReadonlySet<string>, runStartStates?: ReadonlyMap<string, string>): string[];
|
|
76
89
|
/** Whole-file usage write (V6-37, 0.3.37): this is the ONE path that bypasses
|
|
77
90
|
* the malformed-defense and the transact lock — prefer `mutateUsage` for any
|
|
78
91
|
* read-modify-write so a concurrent writer cannot lose its update and a
|
package/package.json
CHANGED