@lmzhen/dsh-evolution-core 0.3.63 → 0.3.65

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.
@@ -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. */
@@ -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
- kind: 'memory' | 'skill' | 'combined';
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
@@ -11,9 +11,9 @@
11
11
  */
12
12
  import type { UsageMap } from './usage.ts';
13
13
  export interface QualityFactors {
14
- /** 0.25 — use_count per day of age, capped at 1. */
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/use (zero use = stable). */
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
- archived: boolean;
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,13 +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;
231
- /** C-18: win32 reserves these stems with ANY extension (`nul.md` hits the
232
- * NUL device), and they are fully inside the charset above — so the reserved
233
- * set is checked on the first-dot prefix as well; the charset close alone
234
- * cannot refuse them. Exported single source: `badName` (skill directories,
235
- * P2-11/v15) and `validateSupportPath` (support-file stems, C-18) both
236
- * consume this one set — a third copy would drift. */
237
- export declare const WIN32_RESERVED_DEVICE_NAMES: ReadonlySet<string>;
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;
238
258
  export declare class SkillLibrary {
239
259
  readonly root: string;
240
260
  readonly limits: SkillLimits;
@@ -340,6 +360,7 @@ export declare class SkillLibrary {
340
360
  * marker write is the only state change; content is untouched.
341
361
  */
342
362
  setPinned(name: string, pinned: boolean, origin?: WriteOrigin): Promise<SkillActionResult>;
363
+ private setPinnedCore;
343
364
  create(name: string, content: string, origin?: WriteOrigin): Promise<SkillActionResult>;
344
365
  private createCore;
345
366
  update(rawName: string, content: string, origin?: WriteOrigin): Promise<SkillActionResult>;
@@ -378,15 +399,27 @@ export declare class SkillLibrary {
378
399
  * or be swept as residue — the v16 first cut matched on suffix alone,
379
400
  * which permanently refused archiving and deleted user content on restore. */
380
401
  private isWriterLock;
381
- /** P2 (v16): best-effort removal of lock residue inside a RESTORED tree
382
- * a `.lock` that a pre-restore crash or TOCTOU stranded in `.archive`
383
- * cannot have a live writer (restore refuses when the live root is
384
- * locked), and if left in place its body (a live pid on a single-host
385
- * deployment) structurally closes the writer's self-heal path. */
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. */
386
408
  private deleteStrandedLocks;
387
409
  /** Remove `lockPath` only when its body has the writer-lock `pid:token`
388
- * shape; anything else (a user support file) is left untouched. */
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. */
389
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
+ * P2-4 (v19): a non-string entry (`skills: [123]`) is refused structurally
417
+ * instead of throwing `name.includes is not a function`. */
418
+ private safeSnapshotEntryName;
419
+ /** A1-7 (v18): a root-level lock whose holder is alive must refuse the
420
+ * restore; a dead residue is swept so a crashed writer cannot block
421
+ * recovery. A non-lock body shape is left alone (user file). */
422
+ private refuseLiveLockOrSweep;
390
423
  archive(rawName: string, options?: ArchiveOptions): Promise<SkillActionResult>;
391
424
  /**
392
425
  * Merge the bodies of `sources` into `target` and archive the sources with
@@ -2,15 +2,25 @@
2
2
  * Threat scanning for agent-authored memory and skill content.
3
3
  *
4
4
  * Ported as a small, dependency-free subset of Hermes Agent's
5
- * `tools/threat_patterns.py` + hermes-claw `threats.ts`. The policy is the
6
- * load-bearing part: ANY in-scope hit blocks. Severity and category are
7
- * metadata for diagnostics only.
5
+ * `tools/threat_patterns.py` + hermes-claw `threats.ts`.
6
+ *
7
+ * Policy (P1-1, v19): a finding either BLOCKS the write or only REPORTS.
8
+ * Blocking is reserved for shapes with no legitimate use in stored knowledge
9
+ * (prompt-injection phrasing, credential exfiltration, the invisible-character
10
+ * smuggling core). Typography and presentation characters that are legitimate
11
+ * in ordinary prose — variation selectors (❤️), zero-width non-joiner, soft
12
+ * hyphen from PDF paste, Arabic letter mark, Mongolian vowel separator — are
13
+ * REPORT findings: they stay visible to operators and tests but never reject a
14
+ * write. Blocking them turned every emoji into a security event.
8
15
  */
9
16
  export type ThreatScope = 'all' | 'context' | 'strict';
10
17
  export interface ThreatFinding {
11
18
  label: string;
12
19
  category: string;
13
20
  scope: ThreatScope;
21
+ /** P1-1 (v19): `block` (default) refuses the write; `report` is an audit
22
+ * trail entry only. Absent means `block`. */
23
+ severity?: 'block' | 'report';
14
24
  }
15
25
  /**
16
26
  * Optional scan controls. Default behavior (`options` omitted) is unchanged:
@@ -39,7 +49,9 @@ export declare const PATTERN_OVERLAP = 4096;
39
49
  * characters (skill files may run to 100,000) is no longer a blind zone.
40
50
  */
41
51
  export declare function scanThreats(text: string, scope?: ThreatScope, maxScanChars?: number, options?: ScanOptions): ThreatFinding[];
42
- /** Blocking policy: any hit blocks. `severity` is deliberately not a gate. */
52
+ /** Blocking policy (P1-1, v19): a finding blocks unless it is explicitly
53
+ * `report`-only. Pattern findings carry no severity and therefore block as
54
+ * before. */
43
55
  export declare function evaluateThreat(text: string, scope?: ThreatScope, maxScanChars?: number, options?: ScanOptions): {
44
56
  blocked: boolean;
45
57
  findings: ThreatFinding[];
@@ -52,7 +52,13 @@ export declare function loadUsage(root: string, io?: EvolutionIoLike): Promise<U
52
52
  * cannot interleave its RMW and lose a counter update. Callers keep their own
53
53
  * single-process serialize chain as the second layer.
54
54
  */
55
- export declare function mutateUsage(root: string, io: EvolutionIoLike, task: (map: UsageMap) => void | Promise<void>): Promise<void>;
55
+ export interface UsageMutateOptions {
56
+ /** P2-9 (v19): called when malformed entries had to be quarantined before the
57
+ * task could run. The guard preserves bytes AND keeps the facility working;
58
+ * this callback is how that stays observable. */
59
+ onQuarantine?: ((message: string) => void) | undefined;
60
+ }
61
+ export declare function mutateUsage(root: string, io: EvolutionIoLike, task: (map: UsageMap) => void | Promise<void>, options?: UsageMutateOptions): Promise<void>;
56
62
  /**
57
63
  * Curator-owned usage fields (rc.67 K-2): the curator writes ONLY this set —
58
64
  * lifecycle state, archive stamp, the six-factor quality pair, and the
@@ -85,7 +91,7 @@ export declare function applyCuratorMetaFields(disk: UsageRecord, curated: Usage
85
91
  * transitioned — a concurrent curator run's archive/restore is never reverted
86
92
  * by a stale snapshot; without it both pairs apply everywhere.
87
93
  */
88
- export declare function foldCuratorFields(disk: UsageMap, curated: UsageMap, stateOwned?: ReadonlySet<string>): void;
94
+ export declare function foldCuratorFields(disk: UsageMap, curated: UsageMap, stateOwned?: ReadonlySet<string>, runStartStates?: ReadonlyMap<string, string>): string[];
89
95
  /** Whole-file usage write (V6-37, 0.3.37): this is the ONE path that bypasses
90
96
  * the malformed-defense and the transact lock — prefer `mutateUsage` for any
91
97
  * read-modify-write so a concurrent writer cannot lose its update and a
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-core",
3
3
  "description": "Shared stores, prompts, signals and lifecycle logic for the dsh-evolution plugin family (community build)",
4
- "version": "0.3.63",
4
+ "version": "0.3.65",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },