@lmzhen/dsh-evolution-core 0.26.15 → 0.26.26

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/lib/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import { parse } from "yaml";
2
- import { basename, dirname, join, resolve } from "node:path";
3
1
  import { copyFileSync, existsSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { parse } from "yaml";
4
5
  import { cp, lstat, mkdir, open, readFile, readdir, rename, rm, stat } from "node:fs/promises";
5
6
  import { createHash, randomBytes, randomUUID } from "node:crypto";
6
7
  import { homedir } from "node:os";
7
- import { fileURLToPath } from "node:url";
8
8
  import { scopeOf } from "@deepseek-ai/dsh-scope";
9
9
  import { z } from "zod";
10
10
  //#region lib/types/citations.js
@@ -204,6 +204,357 @@ function scanBodyHooks(content) {
204
204
  };
205
205
  }
206
206
  //#endregion
207
+ //#region lib/types/tree-write.js
208
+ /**
209
+ * G1/1.5 (S7-09): the TYPED critical section around a tree write.
210
+ *
211
+ * The control-plane lock used to wrap a whole pass, LLM call and all: an operator undo/restore waited up to
212
+ * one `curatorReviewTimeoutMs` (~120s) for a review that was merely thinking. The fix is to give the critical
213
+ * section a shape that a review cannot be squeezed into.
214
+ *
215
+ * WHAT THIS TYPE CAN AND CANNOT DO (the plan wording says "cannot be put inside the lock by TYPE" - that is
216
+ * only half true and the half that is false is the half people rely on): a callback is a CLOSURE, so even
217
+ * with `view` as its only parameter it can still reach `this.io` / `this.ctx` / `this.llm` from the enclosing
218
+ * scope. TypeScript cannot stop that, and a criterion claiming it would never go red.
219
+ *
220
+ * What the type DOES give: new code has no other entry - the callback is handed a `SnapshotView` and nothing
221
+ * else, so a phase that needs `io` or `llm` has to stay OUTSIDE and be passed its result in. What the type
222
+ * CANNOT give is covered by the companion source guard (see the family gates): an `io` / `llm` / `this.`
223
+ * / `ctx.` reference inside a `withTreeWrite` callback is a violation, and that guard is the executable form
224
+ * of "cannot be put inside the lock".
225
+ *
226
+ * @module @lmzhen/dsh-evolution-core/tree-write
227
+ */
228
+ /**
229
+ * Run one tree-writing section under a caller-owned serial queue, exposing ONLY a {@link SnapshotView}.
230
+ *
231
+ * The queue is passed in rather than owned here: the lock belongs to whoever serializes the control plane
232
+ * (the curator), and a second lock would be a second answer to one question.
233
+ * @param acquire - the caller serial queue (e.g. the curator mutex), returning its release.
234
+ * @param view - what this section sees while it holds the lock.
235
+ * @param fn - the section. It may only use the view.
236
+ * @returns whatever the section returns.
237
+ */
238
+ async function withTreeWrite(acquire, view, fn) {
239
+ const release = await acquire();
240
+ try {
241
+ return await fn(view);
242
+ } finally {
243
+ release();
244
+ }
245
+ }
246
+ //#endregion
247
+ //#region lib/types/package-identity.js
248
+ /**
249
+ * G1/1.8 (G-04): the SCOPE this build was published under, DERIVED from a manifest instead of written as a
250
+ * literal in running code.
251
+ *
252
+ * The family used to hard-code `@deepseek-ai/dsh-...` names and let `prepare-release.mjs --scope @lmzhen`
253
+ * patch the text afterwards. A published install therefore depended on a release-time string rewrite
254
+ * reaching every copy, and the copies had already drifted (doctor.ts, index.ts, preset-composition.ts and
255
+ * its .mjs mirror each spelled the identity). The scope is knowable at runtime: it is the scope segment of
256
+ * the nearest `package.json` above the calling module. Nothing to rewrite, one owner of the answer.
257
+ *
258
+ * @module @lmzhen/dsh-evolution-core/package-identity
259
+ */
260
+ /**
261
+ * The scope segment of the nearest `package.json` at or above `from`.
262
+ * @param from - the calling module URL (its own manifest is the answer).
263
+ * @returns the scope, e.g. `@deepseek-ai` or `@lmzhen`; an unscoped manifest yields an empty string.
264
+ */
265
+ function scopeOfNearestManifest(from) {
266
+ let dir = dirname(from instanceof URL ? fileURLToPath(from) : fileURLToPath(new URL(from)));
267
+ for (let hops = 0; hops < 32; hops += 1) {
268
+ const manifest = join(dir, "package.json");
269
+ if (existsSync(manifest)) {
270
+ const parsed = JSON.parse(readFileSync(manifest, "utf8"));
271
+ const name = typeof parsed.name === "string" ? parsed.name : "";
272
+ return name.startsWith("@") ? name.split("/")[0] ?? "" : "";
273
+ }
274
+ const parent = dirname(dir);
275
+ if (parent === dir) break;
276
+ dir = parent;
277
+ }
278
+ return "";
279
+ }
280
+ /**
281
+ * One family/platform package name under the RUNNING scope.
282
+ * @param bareName - the name without any scope, e.g. `dsh-agent-preset`.
283
+ * @param from - the calling module URL.
284
+ * @returns `scope/bareName`, or `bareName` when the manifest carries no scope.
285
+ */
286
+ function scopedPackageName(bareName, from) {
287
+ const scope = scopeOfNearestManifest(from);
288
+ return scope === "" ? bareName : scope + "/" + bareName;
289
+ }
290
+ //#endregion
291
+ //#region lib/types/write-authority.js
292
+ /** The mode a deployment that configures nothing gets. */
293
+ const DEFAULT_WRITE_CONFIRM_MODE = "auto";
294
+ /** Seconds a timeout-mode prompt waits before the write is cancelled. */
295
+ const DEFAULT_WRITE_CONFIRM_TIMEOUT_SECONDS = 120;
296
+ /**
297
+ * The batch-one migration aid: every call site becomes ONE of these instead of hand-building the
298
+ * object - so the shape cannot drift between callers, and each site still has to say its own
299
+ * origin out loud (origin is required here on purpose: the whole point is that no caller inherits
300
+ * a default identity).
301
+ */
302
+ function authorityFrom(input) {
303
+ return {
304
+ origin: input.origin,
305
+ threatExemptLabels: input.threatExemptLabels ?? [],
306
+ protectedNames: input.protectedNames,
307
+ readNames: input.readNames,
308
+ ...input.confirm === void 0 ? {} : { confirm: input.confirm },
309
+ ...input.confirmMode === void 0 ? {} : { confirmMode: input.confirmMode },
310
+ ...input.confirmTimeoutSeconds === void 0 ? {} : { confirmTimeoutSeconds: input.confirmTimeoutSeconds },
311
+ warn: input.warn ?? (() => {})
312
+ };
313
+ }
314
+ //#endregion
315
+ //#region lib/types/write-gates.js
316
+ /**
317
+ * The write-admission sequence: ONE ordered table of the reasons a skill write is refused.
318
+ *
319
+ * Two points read this table (design `dsh-evolution-write-gate-design.md` §3). The ADMISSION
320
+ * point (`execute`, BEFORE the approval seam) runs every gate that applies to `'admission'`, so a
321
+ * write that is refused is never staged and never spends the operator's attention twice. The
322
+ * EXECUTION point (the `executeCore` funnel, reached by the direct path and by the approval
323
+ * replay) runs the gates that apply to `'execution'`: the stored plan of a replayed write passed
324
+ * no schema, and the protected list can change while a record sits pending, so those verdicts are
325
+ * re-decided at the bytes.
326
+ *
327
+ * Order is part of the contract: a gate that can only refuse comes before one that needs a round
328
+ * trip with the operator, so a write that is refused anyway never asks anything.
329
+ *
330
+ * A gate decides whether a write may be ATTEMPTED. The library decides whether it can be
331
+ * PERFORMED — its own structured refusals (a restructure element that is not an object, a patch
332
+ * anchor that no longer matches, an `absorbed_into` that does not exist) stay where the bytes are
333
+ * read.
334
+ *
335
+ * Rule bodies live in `evolution-core` (the read-before-write rule in `skill-reads`, the
336
+ * required-argument table in `SKILL_ACTION_REQUIRED_FIELDS`); this module owns the ORDER and the
337
+ * applicability of each gate, which is an execution-point fact of this tool.
338
+ * @module @lmzhen/dsh-evolution-core/write-gates
339
+ */
340
+ /** The confirm question's stable id — one question per write, so an answer to an earlier prompt
341
+ * can never be read as the answer to a later one. */
342
+ const CONFIRM_QUESTION_ID = "evolution-skill-write";
343
+ /** The cancel label every confirm question offers. */
344
+ const CANCEL_LABEL = "取消";
345
+ /** What a `timeout`-mode deadline settles with: comparable by identity against the seam's
346
+ * boolean, so the two outcomes stay distinguishable without a mutable flag (which a linter
347
+ * cannot follow through a timer callback, and a reader cannot either). */
348
+ const CONFIRM_TIMED_OUT = "confirm-timed-out";
349
+ /**
350
+ * How the ONE confirmation behaves (registry `skillWriteConfirm`).
351
+ *
352
+ * `auto` writes without asking — the default, because an unattended run (a background pass, a
353
+ * scheduled review, a headless session) must not park a tool call on a question nobody will answer.
354
+ * `ask` waits for as long as it takes, which is the deployment that wants the gate in the loop.
355
+ * `timeout` asks and cancels the write on its own deadline.
356
+ */
357
+ /** A usable deadline: the configured value when it is a positive finite number, else the default. */
358
+ function timeoutSecondsOf(configured) {
359
+ return configured !== void 0 && Number.isFinite(configured) && configured >= 1 ? Math.floor(configured) : 120;
360
+ }
361
+ /** The scalar fields the tool schema types as strings; the replay channel has no schema.
362
+ * `action` is one of them: a non-string action used to fall through to the library as "Unknown
363
+ * action" while the read gate re-defaulted it to `patch` and refused with E-318 (review
364
+ * 2026-09-26, P2), so the shape gate refuses it by name instead. */
365
+ const SCALAR_ARG_FIELDS = [
366
+ "action",
367
+ "name",
368
+ "content",
369
+ "old_string",
370
+ "new_string",
371
+ "file_path",
372
+ "file_content",
373
+ "absorbed_into"
374
+ ];
375
+ /** The scalar fields the tool schema types as BOOLEANS; the replay channel has no schema either.
376
+ * T2-V3/A28: `args.replace_all === true` answered "false" for anything else, so a staged
377
+ * `replace_all: 'yes'` silently patched only the FIRST occurrence — a wrong count with no signal.
378
+ * The plan channel already refuses a non-boolean `replace_all` by name (S1-C1); this is the same
379
+ * rule on the replay channel, so the two channels cannot disagree about one flag. */
380
+ const FLAG_ARG_FIELDS = ["replace_all"];
381
+ /**
382
+ * Assemble a gate context from a caller's services. The protected list is read HERE, so no caller can
383
+ * forget it or read it from a second place.
384
+ * @param services - the four required services plus the optional registry knobs.
385
+ * @param request - the point asking, the arguments under test, and the write origin.
386
+ * @returns a context the shared sequence accepts.
387
+ */
388
+ function skillWriteGateContext(services, request) {
389
+ return {
390
+ point: request.point,
391
+ args: request.args,
392
+ origin: request.origin,
393
+ protectedNames: protectedSkillNamesOf(services.policy),
394
+ readNames: services.readNames,
395
+ confirm: services.confirm,
396
+ ...services.confirmMode === void 0 ? {} : { confirmMode: services.confirmMode },
397
+ ...services.confirmTimeoutSeconds === void 0 ? {} : { confirmTimeoutSeconds: services.confirmTimeoutSeconds },
398
+ ...services.describeTarget === void 0 ? {} : { describeTarget: services.describeTarget },
399
+ warn: services.warn
400
+ };
401
+ }
402
+ function protectedSkillNamesOf(policy) {
403
+ return (policy?.get?.())?.protectedSkillNames;
404
+ }
405
+ /**
406
+ * Narrow the arguments once for every gate.
407
+ *
408
+ * The admission point reaches the gates AFTER the schema, the replay channel BEFORE any
409
+ * validation, so a gate reads only what it can prove: anything else counts as absent, exactly as
410
+ * the missing-argument check already treated a non-string action.
411
+ * @param args - the arguments as the caller sent them.
412
+ * @returns the narrowed view.
413
+ */
414
+ function gateArgsOf(args) {
415
+ const scalars = typeof args === "object" && args !== null ? args : {};
416
+ const action = scalars.action;
417
+ const name = scalars.name;
418
+ const absorbedInto = scalars.absorbed_into;
419
+ return {
420
+ action: typeof action === "string" ? action : void 0,
421
+ name: typeof name === "string" ? name : "",
422
+ absorbedInto: typeof absorbedInto === "string" && absorbedInto.trim() !== "" ? absorbedInto : void 0,
423
+ scalars
424
+ };
425
+ }
426
+ /** The sequence, in order. A gate whose `appliesTo` omits the asking point is skipped. */
427
+ const GATES = [
428
+ {
429
+ id: "argument-shape",
430
+ appliesTo: ["admission", "execution"],
431
+ run: ({ view }) => {
432
+ for (const field of SCALAR_ARG_FIELDS) {
433
+ const value = view.scalars[field];
434
+ if (value === void 0 || value === null || typeof value === "string") continue;
435
+ return `skill_manage: "${field}" must be a string (got ${typeof value}); refusing the write.`;
436
+ }
437
+ for (const field of FLAG_ARG_FIELDS) {
438
+ const value = view.scalars[field];
439
+ if (value === void 0 || value === null || typeof value === "boolean") continue;
440
+ return `skill_manage: "${field}" must be a boolean (got ${typeof value}); refusing the write.`;
441
+ }
442
+ return null;
443
+ }
444
+ },
445
+ {
446
+ id: "missing-args",
447
+ appliesTo: ["admission", "execution"],
448
+ run: ({ view }) => {
449
+ const action = view.action;
450
+ const missing = (action === void 0 || !Object.hasOwn(SKILL_ACTION_REQUIRED_FIELDS, action) ? [] : SKILL_ACTION_REQUIRED_FIELDS[action] ?? []).filter((field) => view.scalars[field] === void 0 || view.scalars[field] === null);
451
+ if (missing.length === 0) return null;
452
+ return `skill_manage ${action} requires ${missing.join(", ")}; the tool description lists the arguments per action.`;
453
+ }
454
+ },
455
+ {
456
+ id: "policy-protection",
457
+ appliesTo: ["admission", "execution"],
458
+ run: ({ view, origin, protectedNames }) => {
459
+ if (origin === "foreground") return null;
460
+ if (view.name === "" || !protectedNames?.includes(view.name)) return null;
461
+ return `skill_manage: "${view.name}" is protected by the current policy (protectedSkillNames); replayed/autonomous writes are refused.`;
462
+ }
463
+ },
464
+ {
465
+ id: "read-before-write",
466
+ appliesTo: ["admission"],
467
+ run: ({ view, origin, readNames, warn }) => {
468
+ if (origin === "foreground") return null;
469
+ if (readNames === void 0) {
470
+ warn("skill_manage: the read-before-write check could not run for this call — the session log is not readable here, so the write is allowed.");
471
+ return null;
472
+ }
473
+ if (view.action === void 0) return null;
474
+ if (!isUnreadWrite({
475
+ action: view.action,
476
+ name: view.name
477
+ }, readNames)) return null;
478
+ return errorText("e-318-skill-write-without-a-read", { a1: view.name });
479
+ }
480
+ },
481
+ {
482
+ id: "human-confirm",
483
+ appliesTo: ["admission"],
484
+ run: async ({ view, origin, confirm, warn, confirmMode, confirmTimeoutSeconds, describeTarget }) => {
485
+ const action = view.action;
486
+ if (action === void 0 || origin !== "foreground" || view.name === "") return null;
487
+ if (!(action === "create" || action === "delete" && view.absorbedInto === void 0)) return null;
488
+ const mode = confirmMode ?? "auto";
489
+ if (mode === "auto") return null;
490
+ if (confirm === void 0) {
491
+ warn("skill_manage: no confirmation channel is mounted, so the write proceeds unconfirmed.");
492
+ return null;
493
+ }
494
+ const confirmLabel = action === "create" ? "创建" : "删除";
495
+ const seconds = timeoutSecondsOf(confirmTimeoutSeconds);
496
+ const cancel = new AbortController();
497
+ let timer;
498
+ const deadline = mode === "timeout" ? new Promise((resolve) => {
499
+ timer = setTimeout(() => {
500
+ cancel.abort();
501
+ resolve(CONFIRM_TIMED_OUT);
502
+ }, seconds * 1e3);
503
+ timer.unref();
504
+ }) : void 0;
505
+ const context = await describeTarget?.();
506
+ const details = [context?.description === void 0 || context.description === "" ? null : "功能:" + context.description, context?.versions === void 0 ? null : "已记录的内容版本数:" + String(context.versions)].filter((line) => line !== null);
507
+ const consequence = action === "create" ? `创建技能 "${view.name}"?会在家族目录树里新建一个技能目录。` : `删除技能 "${view.name}"?它会被归档到 .archive 并离开目录。`;
508
+ const asked = confirm({
509
+ action,
510
+ name: view.name,
511
+ confirmLabel,
512
+ question: {
513
+ id: CONFIRM_QUESTION_ID,
514
+ header: "确认",
515
+ question: details.length === 0 ? consequence : consequence + " " + details.join(" · "),
516
+ options: [{ label: confirmLabel }, { label: CANCEL_LABEL }]
517
+ },
518
+ ...mode === "timeout" ? { signal: cancel.signal } : {}
519
+ });
520
+ let outcome;
521
+ try {
522
+ outcome = deadline === void 0 ? await asked : await Promise.race([asked, deadline]);
523
+ } finally {
524
+ if (timer !== void 0) clearTimeout(timer);
525
+ }
526
+ if (outcome === true) return null;
527
+ if (outcome === CONFIRM_TIMED_OUT || cancel.signal.aborted) return errorText("e-319-skill-write-confirm-timed-out", {
528
+ a1: view.name,
529
+ a2: action === "create" ? "创建" : "删除",
530
+ a3: String(seconds)
531
+ });
532
+ return errorText("e-317-skill-write-not-confirmed", {
533
+ a1: view.name,
534
+ a2: action === "create" ? "创建" : "删除"
535
+ });
536
+ }
537
+ }
538
+ ];
539
+ /**
540
+ * Run the sequence for one write attempt.
541
+ * @param context - the asking point, the write, and the seams the human-facing gate needs.
542
+ * @returns the FIRST refusal, or null when every applicable gate passed. Both points return the
543
+ * message as the tool result unchanged, so the two entries refuse with identical wording.
544
+ */
545
+ async function runWriteGates(context) {
546
+ const input = {
547
+ ...context,
548
+ view: gateArgsOf(context.args)
549
+ };
550
+ for (const gate of GATES) {
551
+ if (!gate.appliesTo.includes(context.point)) continue;
552
+ const refusal = await gate.run(input);
553
+ if (refusal !== null) return refusal;
554
+ }
555
+ return null;
556
+ }
557
+ //#endregion
207
558
  //#region lib/types/reference-rewrite.js
208
559
  /**
209
560
  * Reference re-homing plans (0.5.0 V1, design §16.7).
@@ -2481,6 +2832,7 @@ function buildCuratorRunReport(input) {
2481
2832
  ...input.snapshotPath === void 0 ? {} : { snapshotPath: input.snapshotPath },
2482
2833
  ...input.llmReviewEnabled === void 0 ? {} : { llmReviewEnabled: input.llmReviewEnabled },
2483
2834
  ...input.nominationsWarnings === void 0 ? {} : { nominationsWarnings: [...input.nominationsWarnings] },
2835
+ ...input.scanWarnings === void 0 ? {} : { scanWarnings: [...input.scanWarnings] },
2484
2836
  ...input.wouldPrune === void 0 ? {} : { wouldPrune: input.wouldPrune === null ? null : [...input.wouldPrune] }
2485
2837
  };
2486
2838
  }
@@ -2505,7 +2857,8 @@ function renderCuratorReportMarkdown(report) {
2505
2857
  ...report.routeStubs === void 0 ? [] : [`- **Stale route stubs**: ${report.routeStubs.stubs}`],
2506
2858
  ...report.llmReviewEnabled === void 0 ? [] : [`- **llmReview**: ${report.llmReviewEnabled}`],
2507
2859
  ...report.wouldPrune === void 0 ? [] : [report.wouldPrune === null ? "- **Would prune (archiveRetention=report)**: unknown (archive listing unreadable)" : `- **Would prune (archiveRetention=report)**: ${report.wouldPrune.length}`],
2508
- ...report.nominationsWarnings === void 0 || report.nominationsWarnings.length === 0 ? [] : [`- **Nomination warnings**: ${report.nominationsWarnings.join("; ")}`]
2860
+ ...report.nominationsWarnings === void 0 || report.nominationsWarnings.length === 0 ? [] : [`- **Nomination warnings**: ${report.nominationsWarnings.join("; ")}`],
2861
+ ...report.scanWarnings === void 0 || report.scanWarnings.length === 0 ? [] : report.scanWarnings.map((warning) => "- **Dedup scan**: " + warning)
2509
2862
  ];
2510
2863
  const section = (title, items) => items.length === 0 ? [] : [
2511
2864
  "",
@@ -4750,7 +5103,7 @@ function compositionRowIds(composition) {
4750
5103
  return ids;
4751
5104
  }
4752
5105
  /** The platform package a DECLARATIVE preset row names (0.2.x replaced preset directories with it). */
4753
- const AGENT_PRESET_PACKAGE = "@deepseek-ai/dsh-agent-preset";
5106
+ const AGENT_PRESET_PACKAGE = scopedPackageName("dsh-agent-preset", import.meta.url);
4754
5107
  /**
4755
5108
  * Compose the DECLARATIVE preset row from the platform base composition and the family delta.
4756
5109
  *
@@ -4795,7 +5148,7 @@ function composePresetRow(baseComposition, deltaComposition, identity) {
4795
5148
  const plugins = denseBlockSequence(composePresetComposition(baseComposition, deltaComposition)).split("\n").map((line) => line === "" ? "" : " " + line).join("\n");
4796
5149
  return [
4797
5150
  "- id: " + identity.rowId,
4798
- " name: '@deepseek-ai/dsh-agent-preset'",
5151
+ " name: '" + AGENT_PRESET_PACKAGE + "'",
4799
5152
  " config:",
4800
5153
  " id: " + identity.id,
4801
5154
  " name: " + JSON.stringify(identity.name),
@@ -6529,11 +6882,6 @@ const PLATFORM_SERVICE_PROBES = Object.freeze([
6529
6882
  feature: "every durable read and write (the IO seam)",
6530
6883
  side: "host"
6531
6884
  },
6532
- {
6533
- service: "fs",
6534
- feature: "the platform filesystem the evolution-io-fs backend maps the seam onto (locks + writeText(expected) version CAS)",
6535
- side: "host"
6536
- },
6537
6885
  {
6538
6886
  service: "hmr",
6539
6887
  feature: "the preset install read-merge-write commit (the platform HMR lock serializes it)",
@@ -9352,6 +9700,16 @@ function staleRouteStubs(indexes) {
9352
9700
  * re-probes after its copies so a mid-copy writer demotes to `skipped`
9353
9701
  * (OPT-07); the movers' probe→rename window is owned by the io.ts protocol.
9354
9702
  */
9703
+ /**
9704
+ * The CLASS boundary default authority (G1/1.1, Stage 2).
9705
+ *
9706
+ * The FACTORY (newSkillLibrary) requires an explicit authority and the v35 A6 register pins every
9707
+ * production construction, so a production caller cannot omit one. The class is the INTERNAL
9708
+ * boundary: arch rule N7 forbids constructing it directly in production, while ~200 test fixtures do
9709
+ * exactly that and ARE the operator, so an omitted authority reads as foreground here - named and
9710
+ * stated instead of silent.
9711
+ */
9712
+ const CLASS_DEFAULT_AUTHORITY = authorityFrom({ origin: "foreground" });
9355
9713
  /** 0.3.16 (S1.13, T-6): the pointer-line prefix written into a body when a
9356
9714
  * section is moved to references/ — single literal, both restructure and
9357
9715
  * append-mode consolidation emit the same discoverability line. */
@@ -9721,6 +10079,28 @@ function planRestructureSections(body, moves) {
9721
10079
  }))
9722
10080
  };
9723
10081
  }
10082
+ /**
10083
+ * WHY MODULE SCOPE (S4.4): the serial queue must be shared ACROSS SkillLibrary instances. Six services
10084
+ * each build their own store over the SAME skill root (tool face / curator face / review face), and a
10085
+ * per-instance queue - which is what `this.serial = makeSerialQueue()` was - let two of them run
10086
+ * read->plan->commit against one tree at the same time. The mutex cannot live on an instance because
10087
+ * instances share nothing (S2-02, G1/1.4); it cannot be a file lock either, because the point is to
10088
+ * order IN-PROCESS phases before the io transact layer sees them. Keyed by root: two roots are
10089
+ * independent trees and must not wait on each other.
10090
+ */
10091
+ const ROOT_SERIAL = /* @__PURE__ */ new Map();
10092
+ /** The ONE queue for a skill root, created on first use (module scope, see above).
10093
+ * @param root - the resolved skill root; the key, so two spellings of one directory share a queue.
10094
+ * @returns the queue every instance over this root serializes through.
10095
+ */
10096
+ function serialForRoot(root) {
10097
+ const key = resolve(root);
10098
+ const existing = ROOT_SERIAL.get(key);
10099
+ if (existing !== void 0) return existing;
10100
+ const created = makeSerialQueue();
10101
+ ROOT_SERIAL.set(key, created);
10102
+ return created;
10103
+ }
9724
10104
  var SkillLibrary = class {
9725
10105
  root;
9726
10106
  limits;
@@ -9739,20 +10119,24 @@ var SkillLibrary = class {
9739
10119
  /** V10-03 (P2-18): see the constructor's threatExemptLabels. Empty by
9740
10120
  * default — the strict ANY-hit-blocks policy is unchanged. */
9741
10121
  threatExemptLabels;
10122
+ /** G1/1.1: default authority; a call may override (origin is per-invocation). */
10123
+ /** G1/1.1/Stage 2: the ONE default every mutator reads. Per-call overrides still win. */
10124
+ authority;
9742
10125
  /** skill-history.ts: the content history is best-effort, so a failure must not be SILENT —
9743
10126
  * but it is a deployment condition, not a per-write event, so it warns once per instance. */
9744
10127
  historyWarned = false;
9745
- constructor(root = skillsRoot(), io = nodeEvolutionIo(), limits = DEFAULT_SKILL_LIMITS, onMutation, transact, threatExemptLabels) {
10128
+ constructor(root = skillsRoot(), io = nodeEvolutionIo(), limits = DEFAULT_SKILL_LIMITS, onMutation, transact, threatExemptLabels, authority = CLASS_DEFAULT_AUTHORITY) {
9746
10129
  this.root = root;
9747
10130
  this.io = io;
9748
10131
  this.limits = limits;
9749
10132
  this.onMutation = onMutation;
9750
10133
  this.threatExemptLabels = threatExemptLabels ?? [];
10134
+ this.authority = authority;
9751
10135
  this.transact = transact ?? (io.transact ? (ioLike, path, task) => {
9752
10136
  const t = ioLike.transact;
9753
10137
  return t ? t(path, task) : transactIo(ioLike, path, task);
9754
10138
  } : void 0);
9755
- this.serial = makeSerialQueue();
10139
+ this.serial = serialForRoot(this.root);
9756
10140
  }
9757
10141
  /**
9758
10142
  * Run one single-file read-modify-write for a mutator. When `transact` was
@@ -9867,6 +10251,32 @@ var SkillLibrary = class {
9867
10251
  * a swallowed error would silently downgrade the anchor to
9868
10252
  * last-writer-wins.
9869
10253
  */
10254
+ /**
10255
+ * S4-01 (G0/0.7): the ONE three-state read the staging/replay anchors use.
10256
+ *
10257
+ * The two absorbing reads below absorb ENOENT/EISDIR and rethrow everything else, and the callers then
10258
+ * each wrote their own try/catch to turn a rethrow into a decision - three near-copies of one posture,
10259
+ * and the newest of them silently dropped the anchor instead of refusing. A read failure is its own
10260
+ * state, so it gets its own name here: `unknown` with the reason, next to `present` and `absent`.
10261
+ * Failing closed is then the CALLER decision (they refuse on unknown), not something each call site has
10262
+ * to re-derive from a thrown error.
10263
+ */
10264
+ async readSkillProbe(rawName) {
10265
+ return await this.probeOf(() => this.read(rawName));
10266
+ }
10267
+ /** The support-file face of {@link readSkillProbe}. */
10268
+ async readSupportFileProbe(rawName, filePath) {
10269
+ return await this.probeOf(() => this.readSupportFile(rawName, filePath));
10270
+ }
10271
+ /** One place that turns the absorbing reads into three states. `null` is absent; a throw is unknown. */
10272
+ async probeOf(read) {
10273
+ try {
10274
+ const value = await read();
10275
+ return value === null ? probeAbsent() : probePresent(value);
10276
+ } catch (error) {
10277
+ return probeUnknown(probeReason(error));
10278
+ }
10279
+ }
9870
10280
  async readSupportFile(name, filePath) {
9871
10281
  const bad = this.badName(name, { allowReserved: true });
9872
10282
  if (bad) throw new Error(bad);
@@ -9899,7 +10309,9 @@ var SkillLibrary = class {
9899
10309
  }
9900
10310
  async list(options = {}) {
9901
10311
  const summaries = [];
9902
- for (const name of await listNames(this.root, this.io)) {
10312
+ for (const entry of await this.io.list(this.root)) {
10313
+ if (entry.startsWith(".")) continue;
10314
+ const name = entry;
9903
10315
  const dir = this.dirOf(name);
9904
10316
  let md;
9905
10317
  try {
@@ -9996,7 +10408,7 @@ var SkillLibrary = class {
9996
10408
  * (P1-1/v14 — the name guard lives HERE as well as at every entry point, so
9997
10409
  * no caller can build a path from an unvalidated name).
9998
10410
  */
9999
- async writeProtection(rawName, origin = "foreground") {
10411
+ async writeProtection(rawName, origin = this.authority.origin) {
10000
10412
  const name = rawName.trim();
10001
10413
  const badName = this.badName(name);
10002
10414
  if (badName) return badName;
@@ -10274,7 +10686,7 @@ var SkillLibrary = class {
10274
10686
  * protective mutation, so the autonomous pipeline may never call it. The
10275
10687
  * marker write is the only state change; content is untouched.
10276
10688
  */
10277
- async setPinned(name, pinned, origin = "foreground") {
10689
+ async setPinned(name, pinned, origin = this.authority.origin) {
10278
10690
  return await this.serial(() => this.setPinnedCore(name, pinned, origin));
10279
10691
  }
10280
10692
  async setPinnedCore(name, pinned, origin) {
@@ -10329,7 +10741,7 @@ var SkillLibrary = class {
10329
10741
  path: dir
10330
10742
  };
10331
10743
  }
10332
- async create(name, content, origin = "foreground") {
10744
+ async create(name, content, origin = this.authority.origin) {
10333
10745
  const normalized = name.trim();
10334
10746
  return await this.serial(() => this.createCore(normalized, content, origin));
10335
10747
  }
@@ -10456,7 +10868,7 @@ var SkillLibrary = class {
10456
10868
  ...norm.changed ? { normalizedFrontmatterFields: norm.fields } : {}
10457
10869
  };
10458
10870
  }
10459
- async update(rawName, content, origin = "foreground", anchor) {
10871
+ async update(rawName, content, origin = this.authority.origin, anchor) {
10460
10872
  const name = rawName.trim();
10461
10873
  return await this.serial(() => this.updateCore(name, content, origin, anchor));
10462
10874
  }
@@ -10545,7 +10957,7 @@ var SkillLibrary = class {
10545
10957
  };
10546
10958
  }, anchor !== void 0 ? () => anchorUnverifiable(name, null) : (error) => unreadableTarget(name, error));
10547
10959
  }
10548
- async patch(rawName, oldString, newString, filePath = "", replaceAll = false, origin = "foreground") {
10960
+ async patch(rawName, oldString, newString, filePath = "", replaceAll = false, origin = this.authority.origin) {
10549
10961
  const name = rawName.trim();
10550
10962
  return await this.serial(() => this.patchCore(name, oldString, newString, filePath, replaceAll, origin));
10551
10963
  }
@@ -10767,7 +11179,7 @@ var SkillLibrary = class {
10767
11179
  if (typeof name !== "string") return false;
10768
11180
  return name !== "" && name !== "." && name !== ".." && !name.includes("/") && !name.includes("\\") && basename(name) === name;
10769
11181
  }
10770
- async archive(rawName, options = {}) {
11182
+ async archive(rawName, options = {}, origin = this.authority.origin) {
10771
11183
  const name = rawName.trim();
10772
11184
  const badName = this.badName(name, { allowReserved: true });
10773
11185
  if (badName) return {
@@ -10834,7 +11246,7 @@ var SkillLibrary = class {
10834
11246
  try {
10835
11247
  await this.io.writeText(join(dest, ".archive-name"), `${name}\n`);
10836
11248
  } catch {}
10837
- await this.audit(name, "archive", md, null, reason);
11249
+ await this.audit(name, "archive", md, null, reason, void 0, void 0, void 0, origin);
10838
11250
  this.notifyMutation({
10839
11251
  action: "archive",
10840
11252
  name,
@@ -10934,7 +11346,7 @@ var SkillLibrary = class {
10934
11346
  * still refuses, and the refusal names it. The archived source keeps whatever
10935
11347
  * the body did not need.
10936
11348
  */
10937
- async consolidate(target, sources, origin = "foreground", options = {}) {
11349
+ async consolidate(target, sources, origin = this.authority.origin, options = {}) {
10938
11350
  const targetName = target.trim();
10939
11351
  const normalizedSources = [...new Set(sources.map((name) => name.trim()))].filter((name) => name !== targetName);
10940
11352
  const mode = options.mode ?? "append";
@@ -11128,7 +11540,7 @@ var SkillLibrary = class {
11128
11540
  * support-directory links is refused (those links' files stay behind in the
11129
11541
  * same package; the moved text belongs in references/ beside them).
11130
11542
  */
11131
- async restructure(rawName, moves, origin = "foreground") {
11543
+ async restructure(rawName, moves, origin = this.authority.origin) {
11132
11544
  const name = rawName.trim();
11133
11545
  return await this.serial(() => this.restructureCore(name, moves, origin));
11134
11546
  }
@@ -11345,7 +11757,7 @@ var SkillLibrary = class {
11345
11757
  };
11346
11758
  return {
11347
11759
  ok: false,
11348
- message: `目录树变更失败:${reason}。回滚无法恢复 ${stuck.join(", ")}:请从 .backups 快照恢复这些目标(或重新应用这次变更)。`
11760
+ message: `目录树变更失败:${reason}。回滚无法恢复 ${stuck.join(", ")}:可恢复性由当前快照决定:${await this.snapshotRecoveryAvailability()} —— 若上面不是「最新快照」,请重新应用这次变更。`
11349
11761
  };
11350
11762
  }
11351
11763
  const unitId = plan.unitId ?? "u-" + Date.now().toString(36) + "-" + (nextUnitSeq += 1).toString(36);
@@ -11384,7 +11796,7 @@ var SkillLibrary = class {
11384
11796
  * recoverability: archival never deletes, and this is the control-plane
11385
11797
  * path back. The `.archive-reason` marker is dropped on restore.
11386
11798
  */
11387
- async restoreFromArchive(rawName) {
11799
+ async restoreFromArchive(rawName, origin = this.authority.origin) {
11388
11800
  const name = rawName.trim();
11389
11801
  const bad = this.badName(name, { allowReserved: true });
11390
11802
  if (bad) return {
@@ -11450,7 +11862,7 @@ var SkillLibrary = class {
11450
11862
  };
11451
11863
  return {
11452
11864
  ok: false,
11453
- message: `.archive 里没有「${name}」对应的 frontmatter 名,但有 ${unreadable.length} 个名字相似的归档项:${unreadable.join(", ")}。这些归档项没有可读的 SKILL.md(或名字对不上),按名字恢复无法区分它们:请把目录改成技能名后再次恢复,或改用快照恢复。`
11865
+ message: `.archive 里没有「${name}」对应的 frontmatter 名,但有 ${unreadable.length} 个名字相似的归档项:${unreadable.join(", ")}。这些归档项没有可读的 SKILL.md(或名字对不上),按名字恢复无法区分它们:请把目录改成技能名后再次恢复;快照这条路的当前状态是:${await this.snapshotRecoveryAvailability()}。`
11454
11866
  };
11455
11867
  }
11456
11868
  const source = join(archiveRoot, chosen);
@@ -11472,7 +11884,7 @@ var SkillLibrary = class {
11472
11884
  for (const marker of [".archive-reason", ".archive-name"]) if (await this.io.exists(join(dest, marker))) await this.io.remove(join(dest, marker));
11473
11885
  const restoredProbe = await probeText(this.io, join(dest, "SKILL.md"));
11474
11886
  if (isUnknown(restoredProbe)) console.warn(`evolution-skill-store: the restored SKILL.md of "${name}" could not be read back (${restoredProbe.reason}); the restore is recorded without after-bytes`);
11475
- await this.audit(name, "restore", null, isPresent(restoredProbe) ? restoredProbe.value : null, `restored from ${source}`);
11887
+ await this.audit(name, "restore", null, isPresent(restoredProbe) ? restoredProbe.value : null, `restored from ${source}`, void 0, void 0, void 0, origin);
11476
11888
  this.notifyMutation({
11477
11889
  action: "restore",
11478
11890
  name,
@@ -11484,7 +11896,7 @@ var SkillLibrary = class {
11484
11896
  path: dest
11485
11897
  };
11486
11898
  }
11487
- async writeSupportFile(rawName, filePath, content, origin = "foreground", anchor) {
11899
+ async writeSupportFile(rawName, filePath, content, origin = this.authority.origin, anchor) {
11488
11900
  const name = rawName.trim();
11489
11901
  return await this.serial(() => this.writeSupportFileCore(name, filePath, content, origin, anchor));
11490
11902
  }
@@ -11568,7 +11980,7 @@ var SkillLibrary = class {
11568
11980
  };
11569
11981
  }, anchor !== void 0 ? () => anchorUnverifiable(name, filePath) : (error) => unreadableTarget(name + "/" + filePath, error));
11570
11982
  }
11571
- async removeSupportFile(rawName, filePath, origin = "foreground", anchor, unitId) {
11983
+ async removeSupportFile(rawName, filePath, origin = this.authority.origin, anchor, unitId) {
11572
11984
  const name = rawName.trim();
11573
11985
  return await this.serial(() => this.removeSupportFileCore(name, filePath, origin, anchor, unitId));
11574
11986
  }
@@ -11827,6 +12239,22 @@ var SkillLibrary = class {
11827
12239
  }
11828
12240
  }
11829
12241
  /** Keep only the newest N snapshots (Hermes keep=5 parity); older ones are removed outright. */
12242
+ /**
12243
+ * G3/3.4: can a snapshot actually be used right now?
12244
+ *
12245
+ * The recovery messages used to promise "restore it from a snapshot" without asking whether a usable
12246
+ * snapshot exists (audit S1-01: the message promised recoverability from a chain that is five deep and
12247
+ * whose newest member held 9 of 35 skills). Three states, like every other reading here: a fragment
12248
+ * naming the usable snapshot, a fragment saying there is none, or a fragment saying the directory could
12249
+ * not be read - the caller embeds whichever it gets, so the sentence is generated by the capability.
12250
+ */
12251
+ async snapshotRecoveryAvailability() {
12252
+ const listed = await this.listSnapshots();
12253
+ if (isUnknown(listed)) return "快照目录读不出来(" + listed.reason + ")";
12254
+ const latest = isPresent(listed) ? listed.value[0] : void 0;
12255
+ if (!latest) return "当前没有可用快照";
12256
+ return "最新快照 " + latest.path;
12257
+ }
11830
12258
  async retainSnapshots(keep) {
11831
12259
  const listed = await this.listSnapshots();
11832
12260
  if (isUnknown(listed)) {
@@ -11990,12 +12418,15 @@ var SkillLibrary = class {
11990
12418
  }
11991
12419
  }
11992
12420
  const restoresSuppressed = manifest.sidecars.includes(".curator-suppressed.json");
11993
- for (const entry of rootEntries) {
11994
- if (entry === ".archive" || entry === ".backups" || entry === ".mutations.json") continue;
11995
- if (entry === ".history") continue;
11996
- if (entry === ".curator-suppressed.json" && restoresSuppressed) continue;
11997
- await this.io.remove(join(this.root, entry));
11998
- }
12421
+ const declared = new Set([...manifest.skills, ...manifest.sidecars]);
12422
+ const doomed = rootEntries.filter((entry) => {
12423
+ if (entry === ".archive" || entry === ".backups" || entry === ".mutations.json") return false;
12424
+ if (entry === ".history") return false;
12425
+ if (entry === ".curator-suppressed.json" && restoresSuppressed) return false;
12426
+ return !declared.has(entry);
12427
+ });
12428
+ if (doomed.length > 0) console.warn("skill-store: snapshot restore will delete " + String(doomed.length) + " live entr(y|ies) the snapshot does not declare: " + doomed.join(", "));
12429
+ for (const entry of doomed) await this.io.remove(join(this.root, entry));
11999
12430
  for (const name of manifest.skills) await this.io.copy(join(snapshotPath, name), join(this.root, name));
12000
12431
  for (const sidecar of manifest.sidecars) await this.io.copy(join(snapshotPath, sidecar), join(this.root, sidecar));
12001
12432
  {
@@ -12009,10 +12440,300 @@ var SkillLibrary = class {
12009
12440
  }
12010
12441
  };
12011
12442
  function newSkillLibrary(options) {
12012
- const { config, io, limits, ctx, transact, threatExemptLabels } = options;
12443
+ const { config, io, limits, ctx, transact, threatExemptLabels, authority } = options;
12013
12444
  return new SkillLibrary(resolveSkillsRoot(config ?? {}), io, limits, ctx ? (event) => {
12014
12445
  ctx.emit("evolution/skill-mutated", event);
12015
- } : void 0, transact, [...threatExemptLabels ?? []]);
12446
+ } : void 0, transact, [...threatExemptLabels ?? []], authority);
12447
+ }
12448
+ //#endregion
12449
+ //#region lib/types/store-ledger.js
12450
+ /**
12451
+ * G3/3.2 (audit 06-performance.md 接缝 C): ONE declaration for every bounded store.
12452
+ *
12453
+ * Before this file the family had eight retention rules scattered across four packages and no place
12454
+ * that could answer "how long does this record live under the current policy" (S1.md 4.3): six caps
12455
+ * were named constants, three lived inline (snapshots 5, reports 20/20/10, archive 365d). Each entry
12456
+ * below states its bound AND the code that declares it, so a gate can compare the two sets.
12457
+ *
12458
+ * `declare` is an ARRAY because one store may have several declaration sites (the report buckets are
12459
+ * kept by two packages). The `inline` branch matches a CODE SNIPPET rather than a line number: line
12460
+ * numbers rot on every edit above them, a snippet survives (and a snippet that stops matching is the
12461
+ * detector the gate wants). No naming convention is assumed anywhere - that is the whole point of a
12462
+ * ledger: the list IS the declaration.
12463
+ */
12464
+ /** The one list. A new bounded store that is not registered here is what the G3 gate refuses. */
12465
+ const STORE_LEDGER = [
12466
+ {
12467
+ id: "archive-entries",
12468
+ owner: "evolution-core",
12469
+ unit: "entries",
12470
+ bound: "365 days, report-only by default (the removal policy is report, not delete)",
12471
+ cutoff: "archivedAt older than the retention window",
12472
+ trigger: "run-end",
12473
+ artifact: "count of .archive entries and their ages",
12474
+ declare: [{
12475
+ kind: "inline",
12476
+ file: "packages/evolution-core/src/skill-store.ts",
12477
+ contains: "ARCHIVE_RETENTION_DAYS = 365",
12478
+ value: "365 days"
12479
+ }, {
12480
+ kind: "named",
12481
+ module: "evolution-core/src/limits.ts",
12482
+ name: "DEFAULT_ARCHIVE_RETENTION_POLICY",
12483
+ value: "report"
12484
+ }]
12485
+ },
12486
+ {
12487
+ id: "skill-snapshots",
12488
+ owner: "evolution-core",
12489
+ unit: "entries",
12490
+ bound: "5 snapshots",
12491
+ cutoff: "oldest snapshot beyond the keep window",
12492
+ trigger: "run-end",
12493
+ artifact: "snapshot count and total bytes",
12494
+ declare: [{
12495
+ kind: "inline",
12496
+ file: "packages/evolution-core/src/skill-store.ts",
12497
+ contains: "retainSnapshots(5)",
12498
+ value: "5"
12499
+ }]
12500
+ },
12501
+ {
12502
+ id: "skill-versions",
12503
+ owner: "evolution-core",
12504
+ unit: "entries",
12505
+ bound: "20 versions per skill",
12506
+ cutoff: "history index entries beyond versionKeep",
12507
+ trigger: "write",
12508
+ artifact: "history index length per skill",
12509
+ declare: [{
12510
+ kind: "named",
12511
+ module: "evolution-core/src/constants.ts",
12512
+ name: "DEFAULT_SKILL_VERSION_KEEP",
12513
+ value: 20
12514
+ }]
12515
+ },
12516
+ {
12517
+ id: "pending-resolved",
12518
+ owner: "evolution-state-storage",
12519
+ unit: "entries",
12520
+ bound: "200 resolved pending records",
12521
+ cutoff: "resolved records beyond the cap",
12522
+ trigger: "write",
12523
+ artifact: "resolved record count",
12524
+ declare: [{
12525
+ kind: "named",
12526
+ module: "evolution-state-storage/src/constants.ts",
12527
+ name: "PENDING_RESOLVED_CAP",
12528
+ value: 200
12529
+ }]
12530
+ },
12531
+ {
12532
+ id: "review-session",
12533
+ owner: "evolution-state-storage",
12534
+ unit: "entries",
12535
+ bound: "500 review session rows",
12536
+ cutoff: "rows beyond the session cap",
12537
+ trigger: "write",
12538
+ artifact: "review state row count",
12539
+ declare: [{
12540
+ kind: "named",
12541
+ module: "evolution-state-storage/src/constants.ts",
12542
+ name: "REVIEW_STATE_SESSION_CAP",
12543
+ value: 500
12544
+ }]
12545
+ },
12546
+ {
12547
+ id: "reports",
12548
+ owner: "evolution-commands",
12549
+ unit: "files",
12550
+ bound: "20/20/10 files across the three report buckets",
12551
+ cutoff: "reports beyond each bucket keep",
12552
+ trigger: "run-end",
12553
+ artifact: "report file count per bucket",
12554
+ declare: [{
12555
+ kind: "inline",
12556
+ file: "packages/evolution-commands/src/index.ts",
12557
+ contains: "maintain-",
12558
+ value: "20"
12559
+ }, {
12560
+ kind: "inline",
12561
+ file: "packages/evolution-curator/src/index.ts",
12562
+ contains: "errorKeep",
12563
+ value: "10"
12564
+ }]
12565
+ },
12566
+ {
12567
+ id: "event-archives",
12568
+ owner: "evolution-core",
12569
+ unit: "files",
12570
+ bound: "10 archived event logs",
12571
+ cutoff: "archives beyond the retention count",
12572
+ trigger: "write",
12573
+ artifact: "event archive count",
12574
+ declare: [{
12575
+ kind: "named",
12576
+ module: "evolution-core/src/evolution-events.ts",
12577
+ name: "EVENT_LOG_RETAIN_ARCHIVES",
12578
+ value: 10
12579
+ }]
12580
+ },
12581
+ {
12582
+ id: "run-records",
12583
+ owner: "evolution-core",
12584
+ unit: "entries",
12585
+ bound: "50 run records",
12586
+ cutoff: "runs beyond the record cap",
12587
+ trigger: "write",
12588
+ artifact: "runs.json record count",
12589
+ declare: [{
12590
+ kind: "named",
12591
+ module: "evolution-core/src/run-registry.ts",
12592
+ name: "DEFAULT_RUN_RECORDS",
12593
+ value: 50
12594
+ }]
12595
+ },
12596
+ {
12597
+ id: "support-reads",
12598
+ owner: "evolution-core",
12599
+ unit: "entries",
12600
+ bound: "200 support_reads keys",
12601
+ cutoff: "keys beyond the read-path cap",
12602
+ trigger: "write",
12603
+ artifact: "usage sidecar key count",
12604
+ declare: [{
12605
+ kind: "named",
12606
+ module: "evolution-core/src/usage.ts",
12607
+ name: "MAX_SUPPORT_READ_PATHS",
12608
+ value: 200
12609
+ }]
12610
+ }
12611
+ ];
12612
+ //#endregion
12613
+ //#region lib/types/reachability.js
12614
+ /**
12615
+ * G3/3.4: the READ-ONLY reachability reconciliation.
12616
+ *
12617
+ * Three resource domains have grown past their owners' view: content blobs whose index entries were
12618
+ * trimmed away (skill-history: "Trimming drops INDEX entries; orphan blobs stay"), report files no run
12619
+ * record points at, and snapshot directories whose manifest holds almost nothing. The audit measured
12620
+ * them once (233 blobs / 11.40 MiB / 75.4% unreachable; 52 orphan report .md; 4/5 empty snapshot trees,
12621
+ * the newest holding 9 of 35 skills); this module answers the same three questions, from the tree in
12622
+ * front of it, and NOTHING ELSE: it deletes nothing, repairs nothing, and adds no timer.
12623
+ *
12624
+ * Every path is a parameter. The reconciliation must not grow its own idea of where a domain lives -
12625
+ * the callers already know (historyRoot / the reports directory / the snapshot root), and a second
12626
+ * guess here would be a second owner of that fact.
12627
+ */
12628
+ const HISTORY_BASELINE = "233 blobs / 11.40 MiB unreachable (75.4%)";
12629
+ const REPORTS_BASELINE = "52 orphan report .md";
12630
+ const BACKUPS_BASELINE = "4/5 empty snapshot trees; the newest held 9/35 skills";
12631
+ /** Every sha256 any skill index references, across the whole history root. */
12632
+ async function referencedBlobs(io, historyRoot) {
12633
+ const referenced = /* @__PURE__ */ new Set();
12634
+ const skillsDir = join(historyRoot, "skills");
12635
+ const namesProbe = await probeList(io, skillsDir);
12636
+ if (isUnknown(namesProbe)) return void 0;
12637
+ const names = isPresent(namesProbe) ? namesProbe.value : [];
12638
+ for (const name of names) {
12639
+ const probe = await probeText(io, join(skillsDir, name, "index.json"));
12640
+ if (isUnknown(probe)) return void 0;
12641
+ if (!isPresent(probe)) continue;
12642
+ for (const match of probe.value.matchAll(/[a-f0-9]{64}/g)) referenced.add(match[0]);
12643
+ }
12644
+ return referenced;
12645
+ }
12646
+ /**
12647
+ * The history domain: blobs on disk minus the blobs the indexes still reference.
12648
+ * @param io - the tree reader.
12649
+ * @param historyRoot - the history root (`.history`), passed in, never guessed.
12650
+ * @returns the reading, with the audit baseline quoted for comparison.
12651
+ */
12652
+ async function historyReachability(io, historyRoot) {
12653
+ const blobsProbe = await probeList(io, join(historyRoot, "blobs"));
12654
+ const referenced = await referencedBlobs(io, historyRoot);
12655
+ if (isUnknown(blobsProbe) || referenced === void 0) return {
12656
+ domain: "history.blobs",
12657
+ unit: "bytes",
12658
+ total: 0,
12659
+ reachable: 0,
12660
+ unreachable: 0,
12661
+ baseline: HISTORY_BASELINE,
12662
+ unmeasured: "a blob listing or an index read failed; this domain was not measured"
12663
+ };
12664
+ const blobs = isPresent(blobsProbe) ? blobsProbe.value : [];
12665
+ const reachable = blobs.filter((blob) => referenced.has(blob)).length;
12666
+ return {
12667
+ domain: "history.blobs",
12668
+ unit: "bytes",
12669
+ total: blobs.length,
12670
+ reachable,
12671
+ unreachable: blobs.length - reachable,
12672
+ baseline: HISTORY_BASELINE
12673
+ };
12674
+ }
12675
+ /**
12676
+ * The reports domain: files no run record points at. A record WITHOUT a `resultRef` still owns the
12677
+ * conventional path (`reports/maintain-<id>.json`), so that path counts as reachable - counting only
12678
+ * explicit refs would report compliant files as orphans and the number could never match the baseline.
12679
+ * @param io - the tree reader.
12680
+ * @param reportsDir - the reports directory, passed in.
12681
+ * @param runIds - every run id the run records carry.
12682
+ * @param resultRefs - every explicit resultRef those records carry.
12683
+ */
12684
+ async function reportsReachability(io, reportsDir, runIds, resultRefs) {
12685
+ const listed = await probeList(io, reportsDir);
12686
+ if (isUnknown(listed)) return {
12687
+ domain: "reports.files",
12688
+ unit: "files",
12689
+ total: 0,
12690
+ reachable: 0,
12691
+ unreachable: 0,
12692
+ baseline: REPORTS_BASELINE,
12693
+ unmeasured: "the reports directory could not be listed; this domain was not measured"
12694
+ };
12695
+ const files = isPresent(listed) ? listed.value : [];
12696
+ const reachableNames = /* @__PURE__ */ new Set();
12697
+ for (const ref of resultRefs) reachableNames.add(ref.slice(ref.lastIndexOf("/") + 1));
12698
+ for (const id of runIds) reachableNames.add("maintain-" + id + ".json");
12699
+ const reachable = files.filter((file) => reachableNames.has(file)).length;
12700
+ return {
12701
+ domain: "reports.files",
12702
+ unit: "files",
12703
+ total: files.length,
12704
+ reachable,
12705
+ unreachable: files.length - reachable,
12706
+ baseline: REPORTS_BASELINE
12707
+ };
12708
+ }
12709
+ /**
12710
+ * The backups domain: snapshot trees and how much each holds. `skillsPerSnapshot` is measured by the
12711
+ * caller (it already reads manifests); an empty tree is one whose manifest lists no skill.
12712
+ * @param io - the tree reader.
12713
+ * @param backupsRoot - the snapshot root, passed in.
12714
+ * @param skillsPerSnapshot - the caller's manifest reading, one entry per snapshot directory.
12715
+ */
12716
+ async function backupsReachability(io, backupsRoot, skillsPerSnapshot) {
12717
+ const listed = await probeList(io, backupsRoot);
12718
+ if (isUnknown(listed)) return {
12719
+ domain: "backups.trees",
12720
+ unit: "files",
12721
+ total: 0,
12722
+ reachable: 0,
12723
+ unreachable: 0,
12724
+ baseline: BACKUPS_BASELINE,
12725
+ unmeasured: "the snapshot root could not be listed; this domain was not measured"
12726
+ };
12727
+ const dirs = isPresent(listed) ? listed.value : [];
12728
+ const empty = skillsPerSnapshot.filter((count) => count === 0).length;
12729
+ return {
12730
+ domain: "backups.trees",
12731
+ unit: "files",
12732
+ total: dirs.length,
12733
+ reachable: dirs.length - empty,
12734
+ unreachable: empty,
12735
+ baseline: BACKUPS_BASELINE
12736
+ };
12016
12737
  }
12017
12738
  //#endregion
12018
12739
  //#region lib/types/params.js
@@ -13983,4 +14704,4 @@ function foldUntrustedLines(text, limit) {
13983
14704
  return limit > 0 ? marked.slice(0, limit) : marked;
13984
14705
  }
13985
14706
  //#endregion
13986
- export { AGENT_PRESET_PACKAGE, AGENT_PRESET_ROOT_HINT, AUTHORING_DESCRIPTION_BAR, AUTHORING_SPLIT_LINE_CHARS, BASELINE_ACTION, CITATION_SUPPORT_DIRS, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CONTENT_SPLIT_HINT, COST_ASCII_TOKEN_HIGH, COST_ASCII_TOKEN_LOW, COST_CJK_TOKEN_HIGH, COST_CJK_TOKEN_LOW, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEDUP_MAX_PAIR_COMPARISONS, DEDUP_SIMILARITY_THRESHOLD, DEFAULT_ADMISSION, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_ARCHIVE_RETENTION_POLICY, DEFAULT_CITATION_POLICY, DEFAULT_CITATION_REF_BUDGET, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_PROBE_TIMEOUT_MS, DEFAULT_REFERENCE_REWRITE_POLICY, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_RUN_RECORDS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_MODEL, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_SKILL_VERSION_KEEP, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_SUPPORT_FILE_CHAR_POLICY, DEFAULT_SUPPORT_READ_TOOL_NAMES, DEFAULT_THREAT_MAX_SCAN_CHARS, DEFAULT_USER_CHAR_LIMIT, DISPATCH_EVENT_TYPES, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, EMPTY_PATCH_SEED, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_ERRORS, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FAMILY_SESSION_TOOL_NAMES, FORBIDDEN_CONTROL_KEYS, FUZZY_MAX_PATTERN_CHARS, FUZZY_MAX_WORK, HEALTH_STAMP_RE, HISTORY_DIR, HISTORY_INDEX_VERSION, HOOK_LINE_RE, INSTANCE_KEYS, KEEP_LINE_RE, LONG_RUNS, LOW_QUALITY_THRESHOLD, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_REQUEST_BODY_BYTES, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MAX_SUPPORT_READ_PATHS, MAX_TIMER_DELAY_MS, MEMORY_GUIDANCE_SECTION_ORDER, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, NAMESPACE_MIGRATIONS, NATIVE_CALL_EVENT, NATIVE_RESULT_EVENT, PARAM_ALIASES, PARAM_EXPOSURE, PARAM_GROUP_LABELS, PARAM_NAMESPACES, PATTERN_OVERLAP, PLATFORM_CATALOG_DESCRIPTION_DEFAULT, PLATFORM_ESTIMATE_CHARS_PER_TOKEN, PLATFORM_SERVICE_PROBES, POLICY_STAGE_DEFAULTS, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, PTC_DISPATCH_EVENT, PTC_DISPATCH_START_EVENT, QUALITY_WEIGHTS, READ_REQUIRED_ACTIONS, RESTRUCTURE_TARGET_RE, RETENTION_FEEDBACK_KEEP_RATIO, RETENTION_FEEDBACK_MIN_CHARS, RULES, RUNS_SCHEMA_VERSION, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUMMARY_DUPLICATE_HINT_THRESHOLD, SUPPORT_DIRS, SUPPORT_SPRAWL_FILES, SUPPRESSED_FILE_VERSION, SkillLibrary, TEXT_DIFF_MAX_CHARS, TEXT_DIFF_MAX_LINES, THREAT_EXEMPTION_HINT, TOKEN_ASCII_WEIGHT, TOKEN_CJK_WEIGHT, ToolDispatchNormalizer, ToolDispatchPayloadError, UNTRUSTED_LINE_BREAK, UNTRUSTED_LINE_MARK, UPSTREAM_LIMIT_CHARS_PER_TOKEN, VERSION_SUMMARY_MAX_CHARS, absentHostServices, admissionVerdict, advanceReview, affinity, ageCopyKey, allowRowCollisions, anchorVerdict, appendEvolutionEvent, applyCuratorLifecycleFields, applyCuratorMetaFields, applyNamespaceMigration, applyReferenceRewrite, asRecord, assertDispatchPayload, assertSkillsRootAliasRetired, assessStructureHealth, atomicWriteFiles, authoringFeedback, basePresetPlugins, blobPath, bodyCost, buildCuratorRunReport, buildHealthBaseline, buildLearnPrompt, bumpPatch, bumpSupportRead, bumpUse, bumpView, callingScope, candidateSkills, canonicalWriteId, claimInstance, clampOnce, clampedNumber, classifyNamespaceMigration, clearReviewChannel, collectReadSkillNames, composePresetComposition, composePresetEntry, composePresetInsert, composePresetRow, compositionRowIds, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, contentRetentionFeedback, countDispatches, countReferenceFiles, createGateSet, daysSinceIso, declaredReportStamp, denseBlockSequence, describeReferenceRewrite, diffFindings, dispatchKeyOf, dispatchStepOf, displayBodyOf, duplicateHeadings, elapsedSince, emptyRecord, entryTarget, errorText, evaluateThreat, eventsFile, evidenceKindIndex, evolutionActivityProjection, evolutionEventPayloadIssue, evolutionEvidenceProjection, evolutionHome, evolutionIoAdapter, evolutionReadsProjection, evolutionRoot, evolutionSignalsProjection, filterUnreadSkillOps, findDriftSignal, foldCuratorFields, foldToolDispatches, foldTurnEvent, foldTurnEvents, foldUntrustedLines, frontmatterBlock, frontmatterCatalogInvalid, fuzzyPatch, getRecord, h2Sections, historyIndexFile, historyRoot, identity, idleDays, initialTurnFold, installEvolutionProjections, instanceClaimKey, instanceClaimedWriteSites, instanceHolder, isAbsent, isBookkeepingFrame, isCommittedWarning, isDeprecatedParamId, isFileShapedPath, isLandmark, isLoopbackRequest, isMissingPath, isPresent, isProcessAlive, isProgramToolName, isRecord, isReviewChannelSession, isSkillReadToolName, isSkillToolName, isUnknown, isUnreadWrite, landmarkVerdict, latestActivityAt, latestVersionAt, lifecycleCandidate, lintBody, lintSkill, listDirectories, listEventArchives, liveBool, liveEnum, liveNumber, liveString, loadMutations, loadRowOverrides, loadSuppressedNames, loadUsage, loadVersionContent, loadVersions, makeSerialQueue, mapProbe, markAgentCreated, markReviewChannel, markerEntryName, memoryRoot, mergePresetRow, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nearDuplicateSummaries, neutralizePromptVariables, newRunRegistry, newSkillLibrary, nextHistoryIndex, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, normalizeVersionSummary, observeEvent, orderVersions, overlongLines, paramNamespace, paramRowId, paramSettingsId, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, parseHistoryIndex, partitionVersions, payloadText, persistedWriteSite, persistedWriteSites, pickWithPolicy, planNamespaceMigration, planReferenceRewrite, planRehoming, policyStageLimits, presetPatchText, presetRowBlock, presetRowBody, presetRowId, probeAbsent, probeDesktopScope, probeList, probeMtime, probePresent, probeReason, probeText, probeUnknown, projectionText, readDispatchSignal, readEvolutionEvents, readEvolutionTimeline, readHistoryIndex, readJsonBody, readJsonObject, readNumberParam, readParam, readingStats, recordMutation, recordVersions, redactSecrets, relatedSkillNames, releaseInstance, removePresetRow, renameWithRetry, renderCuratorReportMarkdown, renderLongRunTable, reportTime, reportsSweepLockTarget, resolveBasePresetPatch, resolveCitations, resolveExecOrigins, resolveOrigins, resolveParamId, resolveRootConfig, resolveSkillsRoot, resolveUnitMembers, resolveUnitMembersFromIndexes, restructureCandidates, retainEventArchives, retirementReport, reviewPrompt, rowBlockSpan, rowSiblingEnd, runsFile, saveSuppressedNames, saveUsage, scanBodyHooks, scanContentThreats, scanMemoryThreats, scanThreats, scopedProbeReport, sessionAudited, sessionEvidenceIndex, sessionLastEventTime, sessionReadNames, sessionReadSkillNames, sessionSeesFamilyTools, sessionTurnSignals, skillReadNameOf, skillRowCells, skillStateKey, skillsRoot, staleRouteStubs, supportFileReadOf, suppressedFile, sweepReports, sweepReviewChannelSessions, textDiffFacts, tokenLineFor, transactIo, transactTaskGuard, trimPatternBoundaries, trimTrailingBlankLines, unhookedSupportPointers, unitMembersAreComplete, unwrapLive, updateSuppressedNames, usageFile, usageObserved, userSetKeys, validateFrontmatter, validateRestructureTarget, valueOr, verifyPromptBundle, versionActionKind, versionRowCells, versionSummaryPrompt, versionTarget, vocabulary, writeDurableTmp, writeJson, writePathSymbols, yamlPlainScalarNeedsQuotes };
14707
+ export { AGENT_PRESET_PACKAGE, AGENT_PRESET_ROOT_HINT, AUTHORING_DESCRIPTION_BAR, AUTHORING_SPLIT_LINE_CHARS, BASELINE_ACTION, CITATION_SUPPORT_DIRS, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CONTENT_SPLIT_HINT, COST_ASCII_TOKEN_HIGH, COST_ASCII_TOKEN_LOW, COST_CJK_TOKEN_HIGH, COST_CJK_TOKEN_LOW, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEDUP_MAX_PAIR_COMPARISONS, DEDUP_SIMILARITY_THRESHOLD, DEFAULT_ADMISSION, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_ARCHIVE_RETENTION_POLICY, DEFAULT_CITATION_POLICY, DEFAULT_CITATION_REF_BUDGET, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_PROBE_TIMEOUT_MS, DEFAULT_REFERENCE_REWRITE_POLICY, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_RUN_RECORDS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_MODEL, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_SKILL_VERSION_KEEP, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_SUPPORT_FILE_CHAR_POLICY, DEFAULT_SUPPORT_READ_TOOL_NAMES, DEFAULT_THREAT_MAX_SCAN_CHARS, DEFAULT_USER_CHAR_LIMIT, DEFAULT_WRITE_CONFIRM_MODE, DEFAULT_WRITE_CONFIRM_TIMEOUT_SECONDS, DISPATCH_EVENT_TYPES, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, EMPTY_PATCH_SEED, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_ERRORS, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FAMILY_SESSION_TOOL_NAMES, FORBIDDEN_CONTROL_KEYS, FUZZY_MAX_PATTERN_CHARS, FUZZY_MAX_WORK, HEALTH_STAMP_RE, HISTORY_DIR, HISTORY_INDEX_VERSION, HOOK_LINE_RE, INSTANCE_KEYS, KEEP_LINE_RE, LONG_RUNS, LOW_QUALITY_THRESHOLD, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_REQUEST_BODY_BYTES, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MAX_SUPPORT_READ_PATHS, MAX_TIMER_DELAY_MS, MEMORY_GUIDANCE_SECTION_ORDER, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, NAMESPACE_MIGRATIONS, NATIVE_CALL_EVENT, NATIVE_RESULT_EVENT, PARAM_ALIASES, PARAM_EXPOSURE, PARAM_GROUP_LABELS, PARAM_NAMESPACES, PATTERN_OVERLAP, PLATFORM_CATALOG_DESCRIPTION_DEFAULT, PLATFORM_ESTIMATE_CHARS_PER_TOKEN, PLATFORM_SERVICE_PROBES, POLICY_STAGE_DEFAULTS, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, PTC_DISPATCH_EVENT, PTC_DISPATCH_START_EVENT, QUALITY_WEIGHTS, READ_REQUIRED_ACTIONS, RESTRUCTURE_TARGET_RE, RETENTION_FEEDBACK_KEEP_RATIO, RETENTION_FEEDBACK_MIN_CHARS, RULES, RUNS_SCHEMA_VERSION, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, STORE_LEDGER, SUMMARY_DUPLICATE_HINT_THRESHOLD, SUPPORT_DIRS, SUPPORT_SPRAWL_FILES, SUPPRESSED_FILE_VERSION, SkillLibrary, TEXT_DIFF_MAX_CHARS, TEXT_DIFF_MAX_LINES, THREAT_EXEMPTION_HINT, TOKEN_ASCII_WEIGHT, TOKEN_CJK_WEIGHT, ToolDispatchNormalizer, ToolDispatchPayloadError, UNTRUSTED_LINE_BREAK, UNTRUSTED_LINE_MARK, UPSTREAM_LIMIT_CHARS_PER_TOKEN, VERSION_SUMMARY_MAX_CHARS, absentHostServices, admissionVerdict, advanceReview, affinity, ageCopyKey, allowRowCollisions, anchorVerdict, appendEvolutionEvent, applyCuratorLifecycleFields, applyCuratorMetaFields, applyNamespaceMigration, applyReferenceRewrite, asRecord, assertDispatchPayload, assertSkillsRootAliasRetired, assessStructureHealth, atomicWriteFiles, authoringFeedback, authorityFrom, backupsReachability, basePresetPlugins, blobPath, bodyCost, buildCuratorRunReport, buildHealthBaseline, buildLearnPrompt, bumpPatch, bumpSupportRead, bumpUse, bumpView, callingScope, candidateSkills, canonicalWriteId, claimInstance, clampOnce, clampedNumber, classifyNamespaceMigration, clearReviewChannel, collectReadSkillNames, composePresetComposition, composePresetEntry, composePresetInsert, composePresetRow, compositionRowIds, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, contentRetentionFeedback, countDispatches, countReferenceFiles, createGateSet, daysSinceIso, declaredReportStamp, denseBlockSequence, describeReferenceRewrite, diffFindings, dispatchKeyOf, dispatchStepOf, displayBodyOf, duplicateHeadings, elapsedSince, emptyRecord, entryTarget, errorText, evaluateThreat, eventsFile, evidenceKindIndex, evolutionActivityProjection, evolutionEventPayloadIssue, evolutionEvidenceProjection, evolutionHome, evolutionIoAdapter, evolutionReadsProjection, evolutionRoot, evolutionSignalsProjection, filterUnreadSkillOps, findDriftSignal, foldCuratorFields, foldToolDispatches, foldTurnEvent, foldTurnEvents, foldUntrustedLines, frontmatterBlock, frontmatterCatalogInvalid, fuzzyPatch, getRecord, h2Sections, historyIndexFile, historyReachability, historyRoot, identity, idleDays, initialTurnFold, installEvolutionProjections, instanceClaimKey, instanceClaimedWriteSites, instanceHolder, isAbsent, isBookkeepingFrame, isCommittedWarning, isDeprecatedParamId, isFileShapedPath, isLandmark, isLoopbackRequest, isMissingPath, isPresent, isProcessAlive, isProgramToolName, isRecord, isReviewChannelSession, isSkillReadToolName, isSkillToolName, isUnknown, isUnreadWrite, landmarkVerdict, latestActivityAt, latestVersionAt, lifecycleCandidate, lintBody, lintSkill, listDirectories, listEventArchives, liveBool, liveEnum, liveNumber, liveString, loadMutations, loadRowOverrides, loadSuppressedNames, loadUsage, loadVersionContent, loadVersions, makeSerialQueue, mapProbe, markAgentCreated, markReviewChannel, markerEntryName, memoryRoot, mergePresetRow, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nearDuplicateSummaries, neutralizePromptVariables, newRunRegistry, newSkillLibrary, nextHistoryIndex, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, normalizeVersionSummary, observeEvent, orderVersions, overlongLines, paramNamespace, paramRowId, paramSettingsId, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, parseHistoryIndex, partitionVersions, payloadText, persistedWriteSite, persistedWriteSites, pickWithPolicy, planNamespaceMigration, planReferenceRewrite, planRehoming, policyStageLimits, presetPatchText, presetRowBlock, presetRowBody, presetRowId, probeAbsent, probeDesktopScope, probeList, probeMtime, probePresent, probeReason, probeText, probeUnknown, projectionText, protectedSkillNamesOf, readDispatchSignal, readEvolutionEvents, readEvolutionTimeline, readHistoryIndex, readJsonBody, readJsonObject, readNumberParam, readParam, readingStats, recordMutation, recordVersions, redactSecrets, referencedBlobs, relatedSkillNames, releaseInstance, removePresetRow, renameWithRetry, renderCuratorReportMarkdown, renderLongRunTable, reportTime, reportsReachability, reportsSweepLockTarget, resolveBasePresetPatch, resolveCitations, resolveExecOrigins, resolveOrigins, resolveParamId, resolveRootConfig, resolveSkillsRoot, resolveUnitMembers, resolveUnitMembersFromIndexes, restructureCandidates, retainEventArchives, retirementReport, reviewPrompt, rowBlockSpan, rowSiblingEnd, runWriteGates, runsFile, saveSuppressedNames, saveUsage, scanBodyHooks, scanContentThreats, scanMemoryThreats, scanThreats, scopeOfNearestManifest, scopedPackageName, scopedProbeReport, sessionAudited, sessionEvidenceIndex, sessionLastEventTime, sessionReadNames, sessionReadSkillNames, sessionSeesFamilyTools, sessionTurnSignals, skillReadNameOf, skillRowCells, skillStateKey, skillWriteGateContext, skillsRoot, staleRouteStubs, supportFileReadOf, suppressedFile, sweepReports, sweepReviewChannelSessions, textDiffFacts, tokenLineFor, transactIo, transactTaskGuard, trimPatternBoundaries, trimTrailingBlankLines, unhookedSupportPointers, unitMembersAreComplete, unwrapLive, updateSuppressedNames, usageFile, usageObserved, userSetKeys, validateFrontmatter, validateRestructureTarget, valueOr, verifyPromptBundle, versionActionKind, versionRowCells, versionSummaryPrompt, versionTarget, vocabulary, withTreeWrite, writeDurableTmp, writeJson, writePathSymbols, yamlPlainScalarNeedsQuotes };