@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.
@@ -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,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
@@ -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>): void;
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
@@ -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.62",
4
+ "version": "0.3.64",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },