@lmzhen/dsh-evolution-core 0.3.67 → 0.3.69

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.
@@ -113,21 +113,31 @@ export declare function skillsRoot(env?: NodeJS.ProcessEnv): string;
113
113
  export declare function resolveSkillsRoot(config?: {
114
114
  root?: string | undefined;
115
115
  }): string;
116
- /** E-7 (v18): every family row reads ONE root key. `root` is canonical;
117
- * `skillsRoot` is a deprecated alias honoured only while `root` is empty (so a
118
- * deployment that sets both keeps the canonical one) and removed after 0.3.65.
119
- * Callers log their own deprecation warning.
120
- * @param config - the raw plugin config, carrying `root` and/or `skillsRoot`.
121
- * @returns the effective root (empty when neither key is set) and whether the
122
- * deprecated alias supplied it.
116
+ /** E-7 (v18) → V27 G2.4 (M-08): every family row reads ONE root key. `root` is
117
+ * canonical; the `skillsRoot` alias was honoured for one minor version and its
118
+ * window closed at 0.3.65 it is now two releases past expiry, so this
119
+ * resolver no longer reads it at all. A deployment that still sets the alias
120
+ * must fail LOUDLY at load (see {@link assertSkillsRootAliasRetired}): silently
121
+ * ignoring a config key leaves the deployment pointing at a root nobody reads,
122
+ * which is the worst form of compatibility.
123
+ * @param config - the raw plugin config.
124
+ * @returns the effective root (empty when the key is unset or blank).
123
125
  */
124
126
  export declare function resolveRootConfig(config?: {
125
127
  root?: string | undefined;
126
- skillsRoot?: string | undefined;
127
128
  }): {
128
129
  root: string;
129
- usedDeprecatedAlias: boolean;
130
130
  };
131
+ /** V27 G2.4 (M-08): the retirement gate for the expired `skillsRoot` alias.
132
+ * Called at each plugin's load boundary (before the root is resolved), it turns
133
+ * a stale key into an explicit load error naming the replacement — the
134
+ * fail-loud form the plan requires instead of a silent no-op.
135
+ * @param config - the raw plugin config (the alias field stays DECLARED in each
136
+ * schema so the loader can hand it here instead of dropping it).
137
+ */
138
+ export declare function assertSkillsRootAliasRetired(config?: {
139
+ skillsRoot?: string | undefined;
140
+ }): void;
131
141
  /**
132
142
  * Map a requesting session onto the two origin surfaces (rc.44 plan M2-2.3):
133
143
  * the APPROVAL surface treats every delegated subagent as the autonomous
@@ -156,10 +166,17 @@ export interface Frontmatter {
156
166
  }
157
167
  /**
158
168
  * Shared frontmatter block detection (P3-3 single owner): opening line `---`
159
- * and closing line exactly `---` (both trimmed). Used by `parseFrontmatter`,
160
- * `frontmatterYamlUnsafeValues` and `normalizeFrontmatter` so the three can
169
+ * and closing line exactly `---`. Used by `parseFrontmatter`,
170
+ * `frontmatterCatalogInvalid` and `normalizeFrontmatter` so the three can
161
171
  * never disagree about where the block ends (the loose `indexOf('\n---')`
162
172
  * form matched `\n----` and was replaced by this strict line rule).
173
+ *
174
+ * V27 G2.1: both fence lines are matched EXACTLY, tolerating only a trailing
175
+ * `\r` — the same rule the upstream filesystem catalog uses
176
+ * (`skill-filesystem.parseFrontmatter`). The former `.trim()` comparison
177
+ * accepted ` --- `, so an indented fence loaded in the family while the
178
+ * platform ignored the file: family visibility split from platform visibility,
179
+ * which is exactly what a strict-YAML frontmatter is supposed to prevent.
163
180
  */
164
181
  export declare function frontmatterBlock(content: string): {
165
182
  block: string;
@@ -167,10 +184,51 @@ export declare function frontmatterBlock(content: string): {
167
184
  end: number;
168
185
  nl: string;
169
186
  } | null;
170
- export declare function parseFrontmatter(content: string): {
187
+ /**
188
+ * One frontmatter read (V27 G2.1): the values, the body, and every signal the
189
+ * strict-YAML platform catalog derives from the same block. Returned by
190
+ * {@link parseFrontmatter} so a caller never has to parse the block twice to
191
+ * reach a description and the catalog verdict.
192
+ */
193
+ export interface FrontmatterRead {
171
194
  frontmatter: Frontmatter;
172
195
  body: string;
173
- } | null;
196
+ /** Raw entries whose UNQUOTED value the strict catalog cannot load as
197
+ * written. Quotes are included, so a value already normalized by the write
198
+ * path (`normalizeFrontmatter`) is never re-flagged. */
199
+ unsafeValues: Array<{
200
+ key: string;
201
+ value: string;
202
+ }>;
203
+ /** Whether the frontmatter is not valid AS WRITTEN for the strict platform
204
+ * catalog: the strict parser rejects the block, or an unquoted value would
205
+ * read as something other than its text (a dropped ` # ` comment, a
206
+ * number/bool shorthand the catalog refuses as a string field). The write
207
+ * path quotes such a value on its next edit. */
208
+ catalogInvalid: boolean;
209
+ }
210
+ /**
211
+ * Parse a SKILL.md: its frontmatter values and body, or `null` when the file
212
+ * has no frontmatter block or no body. Every consumer of frontmatter values
213
+ * goes through here — the write path's validation, `list()`'s published
214
+ * description, `relatedSkillNames` and the audit — so all of them read the same
215
+ * bytes the same way.
216
+ *
217
+ * @param content - the SKILL.md text.
218
+ * @returns the read, or `null` when there is no block or no body.
219
+ */
220
+ export declare function parseFrontmatter(content: string): FrontmatterRead | null;
221
+ /**
222
+ * Whether this file's frontmatter is valid as written for the strict platform
223
+ * catalog (see `FrontmatterRead.catalogInvalid`). Body-independent (a body-less
224
+ * file is still judged), and derived from the same read as `parseFrontmatter` —
225
+ * so the audit's verdict and the values the family publishes for one file can
226
+ * never disagree (V27 G2.1).
227
+ *
228
+ * @param content - the SKILL.md text.
229
+ * @returns `true` when the strict parser rejects the block or an unquoted value would read as something else.
230
+ */
231
+ export declare function frontmatterCatalogInvalid(content: string): boolean;
174
232
  /** YAML plain-scalar hazards that make an UNQUOTED frontmatter value
175
233
  * unloadable to the platform catalog (strict YAML parser): `: ` (mapping
176
234
  * separator), ` #` (comment start), a trailing `:` (a mapping marker),
@@ -186,15 +244,6 @@ export declare function parseFrontmatter(content: string): {
186
244
  * with the real YAML parser (see normalizeFrontmatter), so an incomplete
187
245
  * approximation can never corrupt a multiline flow value (P3-4). */
188
246
  export declare function yamlPlainScalarNeedsQuotes(value: string): boolean;
189
- /** Raw-line scan of the frontmatter block: entries whose UNQUOTED value is
190
- * YAML-unsafe for the strict platform catalog. Operates on the ORIGINAL line
191
- * value (quotes included), so a value already wrapped by
192
- * `normalizeFrontmatter` is never re-flagged — one source with the write
193
- * path. Single-line entries only; lines with embedded line breaks skip. */
194
- export declare function frontmatterYamlUnsafeValues(content: string): Array<{
195
- key: string;
196
- value: string;
197
- }>;
198
247
  export interface FrontmatterNormalizeResult {
199
248
  content: string;
200
249
  changed: boolean;
@@ -291,6 +340,17 @@ export declare class SkillLibrary {
291
340
  * never inflates the mutation-maturity counter.
292
341
  */
293
342
  private runSingleWrite;
343
+ /**
344
+ * v28 G1.1 (EVO-IO-02): shared compensating cleanup for a locked write that
345
+ * found no SKILL.md — remove the possibly-resurrected directory ONLY when it
346
+ * holds nothing but this path's own write-lock file. The BR-5 rule from
347
+ * setPinnedCore/createCore applies unchanged: a concurrent mover can land a
348
+ * full directory between the list probe and the remove, so anything beyond
349
+ * the lock file (support files, a fresh restore) must never be recursed
350
+ * away. Best-effort: a failed cleanup leaves the "not found" result
351
+ * unchanged (the operator-facing ghost-dir refusal is fail-loud already).
352
+ */
353
+ private cleanupGhostDir;
294
354
  /** Notify the mutation observer after a successful write; observers must never fail the mutation. */
295
355
  private notifyMutation;
296
356
  /** V10-03 (P2-18): ScanOptions shared by every write-path threat check —
@@ -300,6 +360,15 @@ export declare class SkillLibrary {
300
360
  * label (scanContentThreats already embeds it) plus the self-heal hint, so a
301
361
  * false-positive rewrite direction is actionable instead of a dead end. */
302
362
  private contentThreatBlock;
363
+ /**
364
+ * v30 REV-03: read a support file's bytes for staleness anchoring (the
365
+ * write/remove replay guard). Same validation as writeSupportFile; a
366
+ * missing file (or a directory squatting on the path) reads as `null`, any
367
+ * other failure RETHROWS — the callers are the staging/replay anchors, and
368
+ * a swallowed error would silently downgrade the anchor to
369
+ * last-writer-wins.
370
+ */
371
+ readSupportFile(name: string, filePath: string): Promise<string | null>;
303
372
  list(): Promise<SkillSummary[]>;
304
373
  read(rawName: string): Promise<string | null>;
305
374
  /**
@@ -508,6 +577,16 @@ export declare class SkillLibrary {
508
577
  * 64 chars each (the name-rule maximum — real skill names always fit).
509
578
  */
510
579
  private sanitizeSkippedNames;
580
+ /**
581
+ * V27 G0.4 (core-a-01): the per-entry gate `skipped` already has. A corrupted
582
+ * or hand-edited manifest could carry `extras: [123]`: the array check passed,
583
+ * `SNAPSHOT_EXTRA_NAME_RE.test(123)` coerced the number to the string "123"
584
+ * and matched, and the value then threw `TypeError` inside `path.join` — which
585
+ * `readSnapshotExtras` reached only AFTER a whole-tree restore had committed.
586
+ * Extras are path components under `extras/`, so they take the entry gate too;
587
+ * bounded like `skipped` so a hostile manifest cannot grow the read set.
588
+ */
589
+ private sanitizeExtraNames;
511
590
  readSnapshotManifest(path: string): Promise<SnapshotManifest | null>;
512
591
  /** Keep only the newest N snapshots (Hermes keep=5 parity); older ones are removed outright. */
513
592
  private retainSnapshots;
@@ -28,6 +28,9 @@ export interface ThreatFinding {
28
28
  * benign phrasing (e.g. a skill that legitimately opens with "You are now a ...")
29
29
  * can exclude that label by name here. This is opt-in and never widens strict
30
30
  * scope; it only permits callers to drop a known-innocent match.
31
+ * V27 G-1: an exclusion drops the FINDING for that label — it never removes the
32
+ * de-obfuscation the other patterns are matched against, so exempting the
33
+ * unicode rules cannot re-open a splitting bypass.
31
34
  */
32
35
  export interface ScanOptions {
33
36
  /** Pattern labels to skip during this scan. */
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.67",
4
+ "version": "0.3.69",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },