@lmzhen/dsh-evolution-core 0.8.0 → 0.10.0

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
@@ -374,7 +374,7 @@ function describeReferenceRewrite(plan) {
374
374
  */
375
375
  /**
376
376
  * Required argument names per `skill_manage` action — the SINGLE SOURCE read
377
- * by the tool's argument gate (tool-skill-manage executeCore) and the plan
377
+ * by the tool's argument gate (tool-skill-manage write-gates.ts) and the plan
378
378
  * validator (evolution-plan-validator), so the two can never drift.
379
379
  * OPT-05 (2026-09): the plan validator used to accept a `write_file`/
380
380
  * `remove_file` op without `file_path` while the executor required it — the
@@ -429,6 +429,10 @@ const PROTECTED_BUILTIN_SKILLS = new Set(["plan"]);
429
429
  const MAX_SKILL_NAME_LENGTH = 64;
430
430
  const MAX_DESCRIPTION_LENGTH = 1024;
431
431
  const MAX_SKILL_CONTENT_CHARS = 1e5;
432
+ /** How many content versions to retain per skill (skill-history.ts). 20 mirrors the count
433
+ * KiroCrew settled on for the same job (its `MAX_SKILL_VERSIONS`), and is an E2 deployment
434
+ * parameter — the value is a retention policy, not a protocol constant. */
435
+ const DEFAULT_SKILL_VERSION_KEEP = 20;
432
436
  /** The authoring DISCIPLINE band, taken from the upstream standard (archive §5):
433
437
  * peer skills sit at 8-14k characters and a body pushing past 20k belongs in
434
438
  * `references/*.md`. Deliberately separate from `MAX_SKILL_CONTENT_CHARS`: the
@@ -512,6 +516,16 @@ const EVOLUTION_WRITE_TOOLS = ["memory", "skill_manage"];
512
516
  * 0.3.16 (T-4): moved here from skill-store.ts so drift-signals (pure, no IO)
513
517
  * can reference it without importing the skill-store module. */
514
518
  const AUTHORING_DESCRIPTION_BAR = 60;
519
+ /** The PLATFORM's default cap on the description a skill catalog shows —
520
+ * `catalogDescriptionMaxLength` on the platform's `tool-skill` row (default 500).
521
+ *
522
+ * Recorded here because it is the third number in the description-length relationship, and the one
523
+ * that is easiest to misread as a family rule: the family's own host/all rows SET that field to
524
+ * `AUTHORING_DESCRIPTION_BAR` (60), so what a family deployment truncates at is the ROW's value —
525
+ * this default only governs compositions that do not override it. It is NOT a refusal threshold:
526
+ * the only refusal is `MAX_DESCRIPTION_LENGTH` / the row's `maxDescriptionLength`, and the catalog
527
+ * cut is a view, not a validation. See tool-skill-manage/README.md for the three-number table. */
528
+ const PLATFORM_CATALOG_DESCRIPTION_DEFAULT = 500;
515
529
  /** The split hint both size refusals share (0.5.0 V1). The same sentence used to
516
530
  * be copied into validateFrontmatter AND the patch path, and neither copy named
517
531
  * where the content should go — the upstream cap message names the destination
@@ -590,7 +604,8 @@ const DEFAULT_SKILL_LIMITS = {
590
604
  maxNameLength: 64,
591
605
  maxDescriptionLength: MAX_DESCRIPTION_LENGTH,
592
606
  maxSkillContentChars: MAX_SKILL_CONTENT_CHARS,
593
- maxSkillFileBytes: MAX_SKILL_FILE_BYTES
607
+ maxSkillFileBytes: MAX_SKILL_FILE_BYTES,
608
+ versionKeep: 20
594
609
  };
595
610
  //#endregion
596
611
  //#region lib/types/frontmatter.js
@@ -2967,7 +2982,8 @@ function allowRowCollisions(env = process.env) {
2967
2982
  //#endregion
2968
2983
  //#region lib/types/errors.js
2969
2984
  /**
2970
- * THE error-code table: E-301…E-316 with the exact text each one emits.
2985
+ * THE error-code table: E-301…E-318 with the exact text each one emits (E-317 and E-318 are the
2986
+ * write-admission gates' own refusals).
2971
2987
  *
2972
2988
  * The codes used to be spelled out at 33 call sites across two packages, so one
2973
2989
  * code's wording could drift between the branches that answer it and nothing could
@@ -3000,7 +3016,9 @@ const EVOLUTION_ERRORS = Object.freeze({
3000
3016
  "e-309-revision-conflict-you-sent": "E-309: revision conflict — you sent %a1%, the document stands at %a2%. Re-read with /evolution params --json and retry.",
3001
3017
  "e-309-revision-conflict-message-Re": "E-309: revision conflict — %a1%. Re-read with /evolution params --json and retry.",
3002
3018
  "e-310-the-settings-service-refused": "E-310: the settings service refused the write: %a1% — the owning plugin's rule stands (a cross-field pair, or a cap that may only be tightened). /evolution params shows the current value.",
3003
- "e-305-this-invocation-carries-no-2": "E-305: this invocation carries no agent — `%a1%` needs a session-backed call (run it from a session in the GUI or the CLI)."
3019
+ "e-305-this-invocation-carries-no-2": "E-305: this invocation carries no agent — `%a1%` needs a session-backed call (run it from a session in the GUI or the CLI).",
3020
+ "e-317-skill-write-not-confirmed": "E-317: skill \"%a1%\" was not %a2% — the confirmation prompt was declined or dismissed, so nothing was written. The prompt appears on every create and bare delete; repeat the call only if the operator asks for it.",
3021
+ "e-318-skill-write-without-a-read": "E-318: skill \"%a1%\" was not read in this session, so this write is refused. Read it with the `skill` tool first (a read that failed does not count), then repeat the write."
3004
3022
  });
3005
3023
  /**
3006
3024
  * Render one table entry, substituting its interpolation slots.
@@ -4753,6 +4771,12 @@ Learn workflow (when the user asks you to learn a reusable skill, or you decide
4753
4771
  * mutation maturity is a documented DSH approximation (single per-month patch
4754
4772
  * trend ratio replaces the claw timestamp-trend formula, since DSH usage
4755
4773
  * records only carry the last patched timestamp).
4774
+ *
4775
+ * Batch C (2026-09-27): the similarity half of this module is now TWO NAMED
4776
+ * QUESTIONS plus a declared projection — {@link identity} ("is it the same
4777
+ * text?") and {@link affinity} ("how much vocabulary do these two share?") asked
4778
+ * over {@link projectionText}. Callers name the question and the projection
4779
+ * instead of growing a fourth unnamed ruler (design §3C / §1 I2).
4756
4780
  * @module @lmzhen/dsh-evolution-core
4757
4781
  */
4758
4782
  const QUALITY_WEIGHTS = {
@@ -4807,21 +4831,55 @@ function computeQualityScores(input) {
4807
4831
  function normalize(content) {
4808
4832
  return content.toLowerCase().replace(/\s+/g, " ").trim();
4809
4833
  }
4810
- /** C-26 (v10 audit): renamed from `contentHash` — mutations.ts exports a
4834
+ /** Named similarity question 1 — "is it the same text?" — as a normalized
4835
+ * content hash: case- and whitespace-insensitive, so two texts that differ only
4836
+ * in formatting are the SAME text here. Invariant under which projection the
4837
+ * caller read the text from.
4838
+ *
4839
+ * C-26 (v10 audit): NOT named `contentHash` — mutations.ts exports a
4811
4840
  * contentHash of RAW bytes, this one hashes the NORMALIZED text for dedup
4812
- * grouping. Same private helper, unambiguous name. */
4813
- function normalizedHash(content) {
4814
- return createHash("sha256").update(normalize(content)).digest("hex");
4815
- }
4816
- function tokenize(content) {
4817
- return new Set(normalize(content).split(/[^a-z0-9\u4e00-\u9fff]+/).filter(Boolean));
4841
+ * grouping. Same helper as that rename, now a named question. */
4842
+ function identity(text) {
4843
+ return createHash("sha256").update(normalize(text)).digest("hex");
4818
4844
  }
4819
- function jaccard(a, b) {
4845
+ /**
4846
+ * The declared projection: WHICH text a comparison weighs.
4847
+ *
4848
+ * `body` is the library-wide scan's projection (whole SKILL.md, read by the
4849
+ * caller). `summary` is name + description in one string — the projection a
4850
+ * listing already publishes (`SkillSummary` carries both fields, and a body is
4851
+ * attached only when the caller asks for it) — so a summary comparison never
4852
+ * reads a body OF ITS OWN: the create-time hint is fed by the listing pass the
4853
+ * tool already pays for its frontmatter, and calls no read of its own.
4854
+ */
4855
+ function projectionText(projection, input) {
4856
+ switch (projection) {
4857
+ case "body": return input.content ?? "";
4858
+ case "summary": return `${input.name ?? ""} ${input.description ?? ""}`;
4859
+ }
4860
+ }
4861
+ /** The comparable vocabulary of one text: lowercase alphanumeric runs, plus
4862
+ * whole CJK runs (a Chinese description tokenizes by punctuation, not by word —
4863
+ * deliberately: the ruler is one home, and a real tokenizer is a different
4864
+ * question than "do these two share vocabulary?"). */
4865
+ function vocabulary(text) {
4866
+ return new Set(normalize(text).split(/[^a-z0-9\u4e00-\u9fff]+/).filter(Boolean));
4867
+ }
4868
+ /** Named similarity question 2 — "how much vocabulary do these two share?" —
4869
+ * Jaccard over two {@link vocabulary} sets, 0 (nothing in common, or either side
4870
+ * empty) to 1 (identical vocabulary). Set-based on purpose: the bulk scan
4871
+ * memoizes each name's set once and only the comparisons are budgeted. */
4872
+ function affinity(a, b) {
4820
4873
  if (a.size === 0 || b.size === 0) return 0;
4821
4874
  let intersection = 0;
4822
4875
  for (const token of a) if (b.has(token)) intersection += 1;
4823
4876
  return intersection / (a.size + b.size - intersection);
4824
4877
  }
4878
+ /** The `affinity` level at which two bodies are treated as the same skill
4879
+ * (design §3C: the default is unchanged at 0.95 — it is now a NAME, because the
4880
+ * docblock below has linked it since PLAN-R2 while the code carried a bare
4881
+ * literal). Callers may still pass their own level. */
4882
+ const DEDUP_SIMILARITY_THRESHOLD = .95;
4825
4883
  /** PLAN-R2 P2-8 (2026-09-16): default cap on two-name comparisons in
4826
4884
  * {@link computeDedupGroups}. A 2000-skill library is ~2M pairs; the old
4827
4885
  * unbounded two-two Jaccard ran seconds to tens of seconds per
@@ -4848,7 +4906,7 @@ function computeDedupGroups(input) {
4848
4906
  const names = [...input.contents.keys()];
4849
4907
  const hashes = /* @__PURE__ */ new Map();
4850
4908
  for (const name of names) {
4851
- const hash = normalizedHash(input.contents.get(name) ?? "");
4909
+ const hash = identity(projectionText("body", { content: input.contents.get(name) ?? "" }));
4852
4910
  const bucket = hashes.get(hash);
4853
4911
  if (bucket) bucket.push(name);
4854
4912
  else hashes.set(hash, [name]);
@@ -4875,7 +4933,7 @@ function computeDedupGroups(input) {
4875
4933
  const tokenSet = (name) => {
4876
4934
  let set = tokens.get(name);
4877
4935
  if (!set) {
4878
- set = tokenize(input.contents.get(name) ?? "");
4936
+ set = vocabulary(projectionText("body", { content: input.contents.get(name) ?? "" }));
4879
4937
  tokens.set(name, set);
4880
4938
  }
4881
4939
  return set;
@@ -4895,7 +4953,7 @@ function computeDedupGroups(input) {
4895
4953
  compared += 1;
4896
4954
  const [ta, tb] = [tokenSet(a), tokenSet(b)];
4897
4955
  if (Math.max(ta.size, tb.size) / Math.max(1, Math.min(ta.size, tb.size)) > 5) continue;
4898
- if (jaccard(ta, tb) >= threshold) union(a, b);
4956
+ if (affinity(ta, tb) >= threshold) union(a, b);
4899
4957
  }
4900
4958
  }
4901
4959
  const groups = /* @__PURE__ */ new Map();
@@ -4911,6 +4969,40 @@ function computeDedupGroups(input) {
4911
4969
  };
4912
4970
  }
4913
4971
  /**
4972
+ * The create-time near-duplicate question (batch C, design §3C-2): WHICH
4973
+ * existing skills is this authoring candidate about to near-copy?
4974
+ *
4975
+ * It is the SAME {@link affinity} question as the library-wide scan, asked over
4976
+ * the `summary` projection — the one a listing already publishes
4977
+ * (`SkillSummary` carries `name` and `description`; nothing here can see a
4978
+ * body, which is the point: a create must not pay a SECOND whole-tree pass — the
4979
+ * listing's own frontmatter read is what feeds this comparison). Deliberately hint-only: the caller writes regardless, and the
4980
+ * matches are news, not a gate (G4 — the create-time foreground behavior is
4981
+ * unchanged).
4982
+ *
4983
+ * The default level is far below the scan's {@link DEDUP_SIMILARITY_THRESHOLD}
4984
+ * because the projection is thinner: a name plus one sentence shares far fewer
4985
+ * tokens than two bodies, and a hint that fires on every new skill is noise.
4986
+ * Exact same-name candidates are skipped — the library's own create refuses
4987
+ * those, and this line must not repeat that refusal.
4988
+ */
4989
+ const SUMMARY_DUPLICATE_HINT_THRESHOLD = .5;
4990
+ function nearDuplicateSummaries(input) {
4991
+ const threshold = input.threshold ?? .5;
4992
+ const limit = input.limit ?? 3;
4993
+ const candidateTokens = vocabulary(projectionText("summary", input.candidate));
4994
+ const matches = [];
4995
+ for (const existing of input.existing) {
4996
+ if (existing.name === input.candidate.name) continue;
4997
+ const score = affinity(candidateTokens, vocabulary(projectionText("summary", existing)));
4998
+ if (score >= threshold) matches.push({
4999
+ name: existing.name,
5000
+ score
5001
+ });
5002
+ }
5003
+ return matches.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name)).slice(0, limit);
5004
+ }
5005
+ /**
4914
5006
  * Prefix-cluster index over a name set (rc.67 merge heuristic, input side):
4915
5007
  * the curator prompt asks the model to identify "prefix clusters — skills
4916
5008
  * sharing a first word or domain keyword"; the deterministic index supplies
@@ -5899,6 +5991,141 @@ function foldTurn(session, fromSeq) {
5899
5991
  return signal;
5900
5992
  }
5901
5993
  //#endregion
5994
+ //#region lib/types/skill-reads.js
5995
+ /**
5996
+ * The read-before-write RULE: which writes need a prior read, and which targets count as read.
5997
+ *
5998
+ * ONE rule for TWO enforcement points (design `dsh-evolution-write-gate-design.md` §4.2): the
5999
+ * tool path refuses an unread mutating write (E-318), and the plan path drops such an op from a
6000
+ * background review's plan. Before this module the rule lived in `evolution-review` only, so the
6001
+ * tool path could not see it at all.
6002
+ *
6003
+ * The READER is not here either: `tool-dispatch.collectReadSkillNames(events)` folds a session log
6004
+ * into the names read through a non-failed dispatch — the review's old session-shaped copy was a
6005
+ * line-for-line duplicate of it. What this module adds over it is the session-shaped ADAPTER
6006
+ * (`sessionReadSkillNames`), for the ONE caller that holds a session only STRUCTURALLY (the tool
6007
+ * path's gate, whose exec view may be a bare stub): it answers "which accessor, and what does an
6008
+ * unreadable session mean" — `undefined`, so the gate keeps its previous behavior instead of
6009
+ * refusing every write. The review path reads a real platform `Session` and calls the reader
6010
+ * directly; a fallback there would be dead code for an impossible case.
6011
+ *
6012
+ * v32 REV-06(a) is preserved by the dispatch normalizer: a skill counts as READ only when its read
6013
+ * did not fail, so a failed/timeout read cannot pass the gate and let a writer blind-overwrite
6014
+ * content the model never saw. Matching `tool/call` directly (the pre-v37 form) meant every PTC
6015
+ * session (`tool/ptc-dispatch*`) collected an EMPTY set, so every mutating op the model HAD read
6016
+ * was dropped — and nothing reported the loss.
6017
+ * @module @lmzhen/dsh-evolution-core
6018
+ */
6019
+ /**
6020
+ * The skill names ONE session read, or `undefined` when its log is not readable.
6021
+ *
6022
+ * `undefined` is NOT an empty set: a caller that must decide (the tool path's read-before-write
6023
+ * gate) can then keep its previous behavior instead of refusing every write in a composition whose
6024
+ * session objects expose no log at all.
6025
+ * @param session - the session whose reads are wanted; `undefined` when the call carries none.
6026
+ * @returns the names read through a non-failed `skill` dispatch, or `undefined` when unreadable.
6027
+ */
6028
+ function sessionReadSkillNames(session) {
6029
+ const events = session?.snapshotEvents?.();
6030
+ return events === void 0 ? void 0 : collectReadSkillNames(events);
6031
+ }
6032
+ /** Actions whose target must have been read first (create is authorship, so it is exempt). */
6033
+ const READ_REQUIRED_ACTIONS = Object.freeze([
6034
+ "edit",
6035
+ "update",
6036
+ "patch",
6037
+ "delete",
6038
+ "write_file",
6039
+ "remove_file",
6040
+ "restructure"
6041
+ ]);
6042
+ /**
6043
+ * Does this one op write a skill the caller never read?
6044
+ *
6045
+ * V6-26 (0.3.37): the validator normalizes a missing action to `patch`, so a missing action is a
6046
+ * read-required write, never silently exempt.
6047
+ * @param op - one skill operation (from a tool call or a review plan).
6048
+ * @param readNames - names read this session, from {@link collectReadSkillNames}.
6049
+ * @returns true when the op is a read-required write to an unread target.
6050
+ */
6051
+ function isUnreadWrite(op, readNames) {
6052
+ if (!op.name) return false;
6053
+ return READ_REQUIRED_ACTIONS.includes(op.action ?? "patch") && !readNames.has(op.name);
6054
+ }
6055
+ /**
6056
+ * Drop mutating ops whose target was not read this session, in place.
6057
+ *
6058
+ * Covers the same mutating surface Hermes guards (edit/patch/write_file/remove_file), so a
6059
+ * background review cannot blind-touch support files or edits of skills it never loaded.
6060
+ * @param ops - the plan's ops; dropped entries are removed in place.
6061
+ * @param readNames - names read this session.
6062
+ * @returns the count of dropped ops, so the plan event can report them as rejected.
6063
+ */
6064
+ function filterUnreadSkillOps(ops, readNames) {
6065
+ let dropped = 0;
6066
+ for (let index = ops.length - 1; index >= 0; index -= 1) {
6067
+ const op = ops[index];
6068
+ if (!op) continue;
6069
+ if (isUnreadWrite(op, readNames)) {
6070
+ ops.splice(index, 1);
6071
+ dropped += 1;
6072
+ }
6073
+ }
6074
+ return dropped;
6075
+ }
6076
+ //#endregion
6077
+ //#region lib/types/evidence.js
6078
+ /**
6079
+ * Which session frames are EVIDENCE, and which are bookkeeping.
6080
+ *
6081
+ * A plan op must cite `evidence` seqs, and the validator's range rule
6082
+ * (`evolution-plan-validator`, rule `EVIDENCE_RANGE`) answers only "is this an integer inside
6083
+ * the window the plan was authored against". A boundary frame satisfies that while carrying
6084
+ * nothing a claim can rest on: the `turn/start` seq is the frame a claim about that turn was
6085
+ * most likely read out of, and citing it is indistinguishable from citing a real exchange. This
6086
+ * module owns the ONE classification of a session frame by what it CARRIES, so the plan path can
6087
+ * report an op whose whole citation list is bookkeeping (design
6088
+ * `dsh-evolution-skill-history-design.md` §3D, phase 1: report only — no op is refused here).
6089
+ *
6090
+ * The classification is a DENY list of the pure bookends, not an allow list of content frames.
6091
+ * The platform's own vocabulary (`KNOWN_SESSION_EVENT_TYPES`, 57 members at 0.1.5-rc.2) mixes
6092
+ * content frames with state-change frames, and out-of-repo plugins append types the family cannot
6093
+ * enumerate; an unknown frame is therefore treated as carrying content, because a report that
6094
+ * fires on an unclassifiable frame is a false positive and phase 1 exists to measure how many TRUE
6095
+ * ones there are. Tightening — a narrower definition, or an allow list built from the platform's
6096
+ * vocabulary — changes this table and nothing else (`evidenceKindIndex` is the only reader).
6097
+ * @module @lmzhen/dsh-evolution-core
6098
+ */
6099
+ /** The frame types that record a boundary and carry no content: the turn and step bookends. */
6100
+ const BOOKKEEPING_FRAME_TYPES = new Set([
6101
+ "turn/start",
6102
+ "turn/end",
6103
+ "step/start",
6104
+ "step/end"
6105
+ ]);
6106
+ /**
6107
+ * The seqs of the frames that carry content.
6108
+ *
6109
+ * @param events - the session's frames, oldest first (any iterable; nothing is retained).
6110
+ * @returns the substantive seqs, or `undefined` when NO frame carried a usable seq. The two
6111
+ * answers are different facts: an empty SET says "every sequenced frame is a boundary", while
6112
+ * `undefined` says "this log cannot tell a boundary from a message" (a stub session in a test,
6113
+ * a composition whose events carry no seq). A caller that cannot classify must stay silent
6114
+ * rather than report every op — the same three-state posture as `sessionReadSkillNames`.
6115
+ */
6116
+ function evidenceKindIndex(events) {
6117
+ const substantive = /* @__PURE__ */ new Set();
6118
+ let sequenced = 0;
6119
+ for (const event of events) {
6120
+ const seq = event.seq;
6121
+ if (typeof seq !== "number" || !Number.isInteger(seq)) continue;
6122
+ sequenced += 1;
6123
+ if (typeof event.type === "string" && BOOKKEEPING_FRAME_TYPES.has(event.type)) continue;
6124
+ substantive.add(seq);
6125
+ }
6126
+ return sequenced === 0 ? void 0 : substantive;
6127
+ }
6128
+ //#endregion
5902
6129
  //#region lib/types/drift-signals.js
5903
6130
  /**
5904
6131
  * Library-level drift signals for the maintenance subagent (design 011).
@@ -6164,6 +6391,296 @@ function findDriftSignal(signals, id) {
6164
6391
  return signals.find((signal) => signal.id === id);
6165
6392
  }
6166
6393
  //#endregion
6394
+ //#region lib/types/skill-history.js
6395
+ /**
6396
+ * Skill content history: content-addressed blobs plus a per-skill version index.
6397
+ *
6398
+ * Why it exists: `.mutations.json` records before/after HASHES and calls every automated edit
6399
+ * "reviewable and replayable" — but a hash cannot be replayed. With no content store, the only way
6400
+ * back was a whole-tree snapshot (`.backups`, keep=5) or `.archive` (removed skills only), so
6401
+ * reverting one skill cost the collateral of every other change in that window. This module keeps
6402
+ * the content, keyed by skill and version.
6403
+ *
6404
+ * Invariants (design `dsh-evolution-skill-history-design.md` §1):
6405
+ * - HISTORY IS HISTORY: the skill tree stays the single truth for "current content"; nothing here
6406
+ * feeds a judgment — callers read back content and version NUMBERS only.
6407
+ * - BEST-EFFORT, like the mutation audit: a failed history write warns and never fails the write
6408
+ * that already landed.
6409
+ * - Blobs are content-addressed, so identical bodies are stored once (across skills too).
6410
+ * - Versions are numbered by `v` (max+1), NOT by the clock: a moved system clock must not reorder
6411
+ * history (KiroCrew's `v<N>` choice, independently arrived at). Numbering and the index read-modify-write run
6412
+ * under the INDEX's own io lock, so two writers of one skill cannot mint the same number.
6413
+ * - The array's ORDER is append order, and the index is appended after the content lock is released, so two
6414
+ * writers of ONE skill can interleave their appends. Each entry therefore carries `beforeHash` — the content
6415
+ * it replaced, read under that writer's lock — and that link, not the position, is what `undo` follows
6416
+ * ({@link orderVersions} rebuilds the content order for display when the chain is complete).
6417
+ *
6418
+ * NOT here: blob garbage collection. Trimming drops INDEX entries; orphan blobs stay until a
6419
+ * sweeper collects them, because a reference count would have to span every skill's index and that
6420
+ * belongs to the curator's whole-library pass, not to the write path.
6421
+ * @module @lmzhen/dsh-evolution-core/skill-history
6422
+ */
6423
+ /** Root-level history directory. Dot-prefixed so skill discovery skips it. */
6424
+ const HISTORY_DIR = ".history";
6425
+ /** Versioned index shape, so a future field can migrate without guessing. */
6426
+ const HISTORY_INDEX_VERSION = 1;
6427
+ /** The action label for a predecessor the index had never seen (content found on disk before this
6428
+ * feature existed, or a hand-edited tree): it says "this is what was there", not what wrote it. */
6429
+ const BASELINE_ACTION = "baseline";
6430
+ /** The root-level history directory. */
6431
+ function historyRoot(root) {
6432
+ return join(root, HISTORY_DIR);
6433
+ }
6434
+ /** Content-addressed blob path: identical bodies share one file, across skills too. */
6435
+ function blobPath(root, hash) {
6436
+ return join(historyRoot(root), "blobs", hash);
6437
+ }
6438
+ /** One skill's index file. */
6439
+ function historyIndexFile(root, name) {
6440
+ return join(historyRoot(root), "skills", name, "index.json");
6441
+ }
6442
+ /**
6443
+ * The versions in CONTENT order (oldest first), rebuilt from the chain links, for display.
6444
+ *
6445
+ * The stored array is append order, which interleaves when two writers of one skill audit out of
6446
+ * order; the links say what actually replaced what. The rebuild runs only when it can account for
6447
+ * EVERY entry (exactly one start and a forward link for each step) — otherwise the stored order is
6448
+ * returned unchanged, because a partial reconstruction would be a worse answer than the honest one.
6449
+ * @param versions - the stored index, oldest first as recorded.
6450
+ * @returns the same entries, in content order when the chain is complete.
6451
+ */
6452
+ function orderVersions(versions) {
6453
+ if (versions.length < 2) return [...versions];
6454
+ const replaced = /* @__PURE__ */ new Set();
6455
+ for (const entry of versions) if (entry.beforeHash !== void 0) replaced.add(entry.beforeHash);
6456
+ const ends = versions.filter((entry) => !replaced.has(entry.hash));
6457
+ if (ends.length !== 1) return [...versions];
6458
+ const used = /* @__PURE__ */ new Set();
6459
+ const backward = [];
6460
+ let cursor = ends[0];
6461
+ while (cursor !== void 0 && !used.has(cursor)) {
6462
+ used.add(cursor);
6463
+ backward.push(cursor);
6464
+ const before = cursor.beforeHash;
6465
+ cursor = before === void 0 ? void 0 : [...versions].reverse().find((entry) => !used.has(entry) && entry.hash === before);
6466
+ }
6467
+ return backward.length === versions.length ? backward.reverse() : [...versions];
6468
+ }
6469
+ /** Keep nothing less than one version, whatever the deployment asks for. */
6470
+ function clampKeep(keep) {
6471
+ return Number.isFinite(keep) ? Math.max(1, Math.floor(keep)) : 1;
6472
+ }
6473
+ /** The next version number: max+1, never a timestamp. */
6474
+ function nextVersion(versions) {
6475
+ let max = 0;
6476
+ for (const entry of versions) if (entry.v > max) max = entry.v;
6477
+ return max + 1;
6478
+ }
6479
+ /** The last entry carrying this hash, or undefined (a hash may repeat after an undo). */
6480
+ function lastVersionOf(versions, hash) {
6481
+ for (let index = versions.length - 1; index >= 0; index -= 1) {
6482
+ const entry = versions[index];
6483
+ if (entry !== void 0 && entry.hash === hash) return entry.v;
6484
+ }
6485
+ }
6486
+ /** One entry as this reader accepts it, or null when the entry cannot be understood. */
6487
+ function readVersionEntry(entry) {
6488
+ if (typeof entry !== "object" || entry === null) return null;
6489
+ const candidate = entry;
6490
+ if (typeof candidate.v !== "number" || !Number.isInteger(candidate.v) || candidate.v <= 0) return null;
6491
+ if (typeof candidate.hash !== "string" || candidate.hash === "") return null;
6492
+ if (typeof candidate.at !== "string") return null;
6493
+ if (typeof candidate.action !== "string") return null;
6494
+ if (typeof candidate.chars !== "number") return null;
6495
+ return typeof candidate.beforeHash === "string" && candidate.beforeHash !== "" ? {
6496
+ ...candidate,
6497
+ beforeHash: candidate.beforeHash
6498
+ } : {
6499
+ v: candidate.v,
6500
+ at: candidate.at,
6501
+ action: candidate.action,
6502
+ hash: candidate.hash,
6503
+ chars: candidate.chars
6504
+ };
6505
+ }
6506
+ /**
6507
+ * Read one index body for a RECORDER: "understood", "no file", or "cannot be understood".
6508
+ * A file whose shape is right but which carries an entry this reader would have to drop counts as
6509
+ * unreadable — dropping it silently would be the overwrite the audit posture forbids.
6510
+ * @param raw - the index file's bytes, or null when absent.
6511
+ * @returns the three-state answer.
6512
+ */
6513
+ function readHistoryIndex(raw) {
6514
+ if (raw === null) return { kind: "absent" };
6515
+ if (raw.trim() === "") return { kind: "unreadable" };
6516
+ let parsed;
6517
+ try {
6518
+ parsed = JSON.parse(raw);
6519
+ } catch {
6520
+ return { kind: "unreadable" };
6521
+ }
6522
+ if (typeof parsed !== "object" || parsed === null) return { kind: "unreadable" };
6523
+ const holder = parsed;
6524
+ if (typeof holder.version === "number" && holder.version > 1) return { kind: "unreadable" };
6525
+ if (!Array.isArray(holder.versions)) return { kind: "unreadable" };
6526
+ const versions = [];
6527
+ for (const entry of holder.versions) {
6528
+ const version = readVersionEntry(entry);
6529
+ if (version === null) return { kind: "unreadable" };
6530
+ versions.push(version);
6531
+ }
6532
+ return {
6533
+ kind: "ok",
6534
+ versions
6535
+ };
6536
+ }
6537
+ /**
6538
+ * Parse one index body for a READER. Malformed content, a foreign shape, or a version NEWER than this
6539
+ * reader all read as empty — the audit posture: never guess. A recorder must use
6540
+ * {@link readHistoryIndex} instead, because it has to PRESERVE bytes it cannot understand.
6541
+ * @param raw - the index file's bytes, or null when absent.
6542
+ * @returns the versions, oldest first.
6543
+ */
6544
+ function parseHistoryIndex(raw) {
6545
+ const state = readHistoryIndex(raw);
6546
+ return state.kind === "ok" ? state.versions : [];
6547
+ }
6548
+ /**
6549
+ * The index after this write, and the version numbers it produced. Pure, so the versioning rules
6550
+ * are testable without io.
6551
+ *
6552
+ * Two rules matter here: a predecessor the index has never seen gets a BASELINE entry (so the state
6553
+ * before this mutation is recoverable even for a skill that predates the feature), and re-writing
6554
+ * identical content mints NO new version (the library's no-op updates must not grow history).
6555
+ * @param versions - the current index, oldest first.
6556
+ * @param input - the write being recorded.
6557
+ * @param keep - how many versions to retain per skill.
6558
+ * @returns the trimmed index and the recorded numbers.
6559
+ */
6560
+ function nextHistoryIndex(versions, input, keep) {
6561
+ const beforeHash = input.before === null ? null : contentHash(input.before);
6562
+ const afterHash = input.after === null ? null : contentHash(input.after);
6563
+ const grown = [...versions];
6564
+ if (beforeHash !== null && !grown.some((entry) => entry.hash === beforeHash)) grown.push({
6565
+ v: nextVersion(grown),
6566
+ at: input.at,
6567
+ action: BASELINE_ACTION,
6568
+ hash: beforeHash,
6569
+ chars: input.before?.length ?? 0
6570
+ });
6571
+ if (afterHash !== null) {
6572
+ if (grown[grown.length - 1]?.hash !== afterHash) grown.push({
6573
+ v: nextVersion(grown),
6574
+ at: input.at,
6575
+ action: input.action,
6576
+ hash: afterHash,
6577
+ chars: input.after?.length ?? 0,
6578
+ ...beforeHash === null ? {} : { beforeHash }
6579
+ });
6580
+ }
6581
+ const trimmed = grown.slice(Math.max(0, grown.length - clampKeep(keep)));
6582
+ const recorded = {};
6583
+ if (beforeHash !== null) {
6584
+ const v = lastVersionOf(trimmed, beforeHash);
6585
+ if (v !== void 0) recorded.beforeVersion = v;
6586
+ }
6587
+ if (afterHash !== null) {
6588
+ const v = lastVersionOf(trimmed, afterHash);
6589
+ if (v !== void 0) recorded.afterVersion = v;
6590
+ }
6591
+ return {
6592
+ versions: trimmed,
6593
+ recorded
6594
+ };
6595
+ }
6596
+ /** Shrink level at which the retention line speaks: a body that keeps less than this share of the
6597
+ * previous one is a rewrite, not an edit. */
6598
+ const RETENTION_FEEDBACK_KEEP_RATIO = .5;
6599
+ /** Floor on the REPLACED body: below this a ratio is noise (a 40-character skill cut in half is not
6600
+ * news), so the line stays off for small skills. */
6601
+ const RETENTION_FEEDBACK_MIN_CHARS = 200;
6602
+ /**
6603
+ * The anti-overwrite feedback line (design §4, item 3): says how much of the previous body a
6604
+ * replacement kept, so a destructive rewrite is visible in the same message that reports the write.
6605
+ *
6606
+ * Computed where the replaced bytes are still in hand — the write path's own in-lock read — instead of
6607
+ * re-reading the file from a caller (that second read would race the writers this library serializes).
6608
+ * It is FEEDBACK, never a gate: no ratio refuses a write, and the function owns no configuration
6609
+ * (a toggle nobody reads would be configuration that does nothing).
6610
+ *
6611
+ * @param input - the write being described.
6612
+ * @returns the line, or null when the write is not a significant whole-body replacement.
6613
+ */
6614
+ function contentRetentionFeedback(input) {
6615
+ if (input.action !== "update") return null;
6616
+ const before = input.before;
6617
+ const after = input.after;
6618
+ if (before === null || after === null) return null;
6619
+ if (before.length < 200) return null;
6620
+ if (after.length >= before.length * .5) return null;
6621
+ return `Content kept ${Math.round(after.length / before.length * 100)}% of the previous body (${after.length} of ${before.length} characters); the replaced version is preserved in this skill's history.`;
6622
+ }
6623
+ /**
6624
+ * Read one skill's versions, oldest first.
6625
+ * @param root - the skills root.
6626
+ * @param io - the io seam.
6627
+ * @param name - the skill name.
6628
+ * @returns the versions; an absent or unreadable index reads as empty (never a throw).
6629
+ */
6630
+ async function loadVersions(root, io, name) {
6631
+ return parseHistoryIndex(await io.readText(historyIndexFile(root, name)).catch(() => null));
6632
+ }
6633
+ /**
6634
+ * One version's content.
6635
+ * @param root - the skills root.
6636
+ * @param io - the io seam.
6637
+ * @param name - the skill name.
6638
+ * @param v - the version number.
6639
+ * @returns the content, or null when the version or its blob cannot be read (never '').
6640
+ */
6641
+ async function loadVersionContent(root, io, name, v) {
6642
+ const entry = (await loadVersions(root, io, name)).find((candidate) => candidate.v === v);
6643
+ if (entry === void 0) return null;
6644
+ return await io.readText(blobPath(root, entry.hash)).catch(() => null);
6645
+ }
6646
+ /**
6647
+ * Record the content one mutation produced, best-effort.
6648
+ *
6649
+ * Blobs are written FIRST: the index must never reference content that is not on disk, and a failed
6650
+ * blob write aborts the whole record (the caller warns; the mutation itself stands).
6651
+ * @param root - the skills root.
6652
+ * @param io - the io seam.
6653
+ * @param input - the write being recorded.
6654
+ * @param keep - how many versions to retain per skill.
6655
+ * @returns the version numbers, or null when there was no content to record.
6656
+ */
6657
+ async function recordVersions(root, io, input, keep) {
6658
+ if (input.before === null && input.after === null) return null;
6659
+ for (const side of [input.before, input.after]) {
6660
+ if (side === null) continue;
6661
+ const path = blobPath(root, contentHash(side));
6662
+ if (!await io.exists(path).catch(() => false)) await io.writeText(path, side);
6663
+ }
6664
+ const file = historyIndexFile(root, input.skillName);
6665
+ if (readHistoryIndex(await io.readText(file).catch(() => null)).kind === "unreadable") {
6666
+ const quarantine = `${file}.corrupt`;
6667
+ await io.copy(file, quarantine);
6668
+ console.warn(`skill-store: ${file} could not be understood (malformed, foreign, or written by a newer reader); its bytes were copied to ${quarantine} and a fresh index starts from this write`);
6669
+ }
6670
+ const recorded = {};
6671
+ await transactIo(io, file, (current) => {
6672
+ const state = readHistoryIndex(current);
6673
+ const { versions, recorded: numbers } = nextHistoryIndex(state.kind === "ok" ? state.versions : [], input, keep);
6674
+ recorded.beforeVersion = numbers.beforeVersion;
6675
+ recorded.afterVersion = numbers.afterVersion;
6676
+ return JSON.stringify({
6677
+ version: 1,
6678
+ versions
6679
+ }, null, 2);
6680
+ });
6681
+ return recorded;
6682
+ }
6683
+ //#endregion
6167
6684
  //#region lib/types/fuzzy-match.js
6168
6685
  /**
6169
6686
  * Fuzzy string matching and replacement for patch/restructure edits of skill files.
@@ -6684,6 +7201,9 @@ var SkillLibrary = class {
6684
7201
  /** V10-03 (P2-18): see the constructor's threatExemptLabels. Empty by
6685
7202
  * default — the strict ANY-hit-blocks policy is unchanged. */
6686
7203
  threatExemptLabels;
7204
+ /** skill-history.ts: the content history is best-effort, so a failure must not be SILENT —
7205
+ * but it is a deployment condition, not a per-write event, so it warns once per instance. */
7206
+ historyWarned = false;
6687
7207
  constructor(root = skillsRoot(), io = nodeEvolutionIo(), limits = DEFAULT_SKILL_LIMITS, onMutation, transact, threatExemptLabels) {
6688
7208
  this.root = root;
6689
7209
  this.io = io;
@@ -6748,11 +7268,24 @@ var SkillLibrary = class {
6748
7268
  message: "internal error: the write transaction did not invoke the task; no write was performed"
6749
7269
  };
6750
7270
  if (o.ghostDir === true) await this.cleanupGhostDir(path);
6751
- if (o.write !== null && o.audit) await this.audit(o.audit.skillName, o.audit.action, o.audit.before, o.audit.after, o.audit.summary);
7271
+ let result = o.result;
7272
+ if (o.write !== null && o.audit) {
7273
+ await this.audit(o.audit.skillName, o.audit.action, o.audit.before, o.audit.after, o.audit.summary);
7274
+ const retention = contentRetentionFeedback({
7275
+ name: o.audit.skillName,
7276
+ action: o.audit.action,
7277
+ before: o.audit.before,
7278
+ after: o.audit.after
7279
+ });
7280
+ if (retention !== null && result.ok) result = {
7281
+ ...result,
7282
+ message: `${result.message}\n${retention}`
7283
+ };
7284
+ }
6752
7285
  if (o.write !== null && o.event) this.notifyMutation(o.event);
6753
- return durabilityWarning === "" || !o.result.ok ? o.result : {
6754
- ...o.result,
6755
- message: `${o.result.message} (warning: the write landed but the directory fsync failed — durability unconfirmed: ${durabilityWarning})`
7286
+ return durabilityWarning === "" || !result.ok ? result : {
7287
+ ...result,
7288
+ message: `${result.message} (warning: the write landed but the directory fsync failed — durability unconfirmed: ${durabilityWarning})`
6756
7289
  };
6757
7290
  }
6758
7291
  /**
@@ -7055,16 +7588,41 @@ var SkillLibrary = class {
7055
7588
  return null;
7056
7589
  }
7057
7590
  }
7058
- /** Best-effort audit trail entry; never blocks the mutation. */
7591
+ /** Best-effort audit trail entry; never blocks the mutation.
7592
+ *
7593
+ * Two independent best-effort writes run here, because either one failing must not cost the
7594
+ * other: the CONTENT history (skill-history.ts, so a version can be read back at all) and the
7595
+ * mutation ledger that points at it. The history failure is not silent — a deployment whose
7596
+ * history stopped growing is a degradation worth seeing once (the ledger keeps hashes either
7597
+ * way). */
7059
7598
  async audit(skillName, action, before, after, summary) {
7599
+ const at = (/* @__PURE__ */ new Date()).toISOString();
7600
+ let versions = null;
7601
+ try {
7602
+ const keep = this.limits.versionKeep ?? 20;
7603
+ versions = await recordVersions(this.root, this.io, {
7604
+ skillName,
7605
+ action,
7606
+ before,
7607
+ after,
7608
+ at
7609
+ }, keep);
7610
+ } catch (error) {
7611
+ if (!this.historyWarned) {
7612
+ this.historyWarned = true;
7613
+ console.warn(`skill-store: content history not recorded for "${skillName}" (${error instanceof Error ? error.message : String(error)}); the mutation itself landed and the audit ledger keeps its hashes`);
7614
+ }
7615
+ }
7060
7616
  try {
7061
7617
  await recordMutation(this.root, this.io, {
7062
7618
  skillName,
7063
7619
  action,
7064
7620
  ...before === null ? {} : { beforeHash: contentHash(before) },
7065
7621
  ...after === null ? {} : { afterHash: contentHash(after) },
7622
+ ...versions?.beforeVersion === void 0 ? {} : { beforeVersion: versions.beforeVersion },
7623
+ ...versions?.afterVersion === void 0 ? {} : { afterVersion: versions.afterVersion },
7066
7624
  summary,
7067
- at: (/* @__PURE__ */ new Date()).toISOString()
7625
+ at
7068
7626
  });
7069
7627
  } catch {}
7070
7628
  }
@@ -7073,6 +7631,32 @@ var SkillLibrary = class {
7073
7631
  return await loadMutations(this.root, this.io);
7074
7632
  }
7075
7633
  /**
7634
+ * Content versions recorded for one skill, oldest first (skill-history.ts, batch A).
7635
+ *
7636
+ * Read-only: the version seam never feeds a judgment — callers use it to LIST and to read content
7637
+ * back. A malformed or unreadable index reads as empty, and a bad name reads as empty too (the
7638
+ * caller is a human command, so "no versions" is the honest answer for a name that cannot exist).
7639
+ * @param rawName - the skill's name.
7640
+ * @returns the versions, oldest first.
7641
+ */
7642
+ async listVersions(rawName) {
7643
+ const name = rawName.trim();
7644
+ if (this.badName(name) !== null) return [];
7645
+ return await loadVersions(this.root, this.io, name);
7646
+ }
7647
+ /**
7648
+ * One version's content, read back from the content-addressed blob store.
7649
+ * @param rawName - the skill's name.
7650
+ * @param v - the version number (see {@link SkillLibrary.listVersions}).
7651
+ * @returns the content, or null when the version does not exist, the name is unusable, or the
7652
+ * blob cannot be read — never an empty string.
7653
+ */
7654
+ async readVersion(rawName, v) {
7655
+ const name = rawName.trim();
7656
+ if (this.badName(name) !== null) return null;
7657
+ return await loadVersionContent(this.root, this.io, name, v);
7658
+ }
7659
+ /**
7076
7660
  * Pin or unpin a skill (`.pinned` marker). Pinned skills are protected from
7077
7661
  * deletion, from background-review writes, and from the lifecycle — a
7078
7662
  * protective mutation, so the autonomous pipeline may never call it. The
@@ -8845,7 +9429,7 @@ var SkillLibrary = class {
8845
9429
  throw new Error(`snapshot restore refused: the skill root could not be listed (${error instanceof Error ? error.message : String(error)}) — nothing was changed`);
8846
9430
  }
8847
9431
  for (const entry of rootEntries) {
8848
- if (entry === ".archive" || entry === ".backups" || entry === ".mutations.json" || entry === ".curator-suppressed.json") continue;
9432
+ if (entry === ".archive" || entry === ".backups" || entry === ".mutations.json" || entry === ".curator-suppressed.json" || entry === ".history") continue;
8849
9433
  if (entry.endsWith(".lock") || entry.endsWith(`.lock.next`)) {
8850
9434
  await this.refuseLiveLockOrSweep(join(this.root, entry), entry);
8851
9435
  continue;
@@ -8859,6 +9443,7 @@ var SkillLibrary = class {
8859
9443
  const restoresSuppressed = manifest.sidecars.includes(".curator-suppressed.json");
8860
9444
  for (const entry of rootEntries) {
8861
9445
  if (entry === ".archive" || entry === ".backups" || entry === ".mutations.json") continue;
9446
+ if (entry === ".history") continue;
8862
9447
  if (entry === ".curator-suppressed.json" && restoresSuppressed) continue;
8863
9448
  if (entry.endsWith(".lock") || entry.endsWith(`.lock.next`)) continue;
8864
9449
  await this.io.remove(join(this.root, entry));
@@ -8957,6 +9542,20 @@ function readParam(carrier, id) {
8957
9542
  for (const alias of ALIASES_BY_CANONICAL[canonical] ?? []) if (record[alias] !== void 0) return record[alias];
8958
9543
  }
8959
9544
  /**
9545
+ * Chinese display name per group, one entry per line (the scripts read this block
9546
+ * by text, the same way they read PARAM_NAMESPACES). The raw group id is what
9547
+ * `--group` accepts and what the JSON face reports; this only names it on screen.
9548
+ */
9549
+ const PARAM_GROUP_LABELS = Object.freeze({
9550
+ library: "技能库",
9551
+ "write-caps": "写入上限",
9552
+ review: "会话回顾",
9553
+ memory: "记忆",
9554
+ curator: "技能整理",
9555
+ deployment: "安装配置",
9556
+ internal: "内部"
9557
+ });
9558
+ /**
8960
9559
  * The parameter registry. G1/S1.1 seeds it with the review group (G-C); the
8961
9560
  * remaining groups land in S2.1. E3 = behaviour preference the user may change
8962
9561
  * (live); E2 = resource or identity knob that stays with the deployment.
@@ -8970,9 +9569,9 @@ const PARAM_EXPOSURE = Object.freeze([
8970
9569
  owner: "evolution-review",
8971
9570
  applies: "live",
8972
9571
  docAnchor: "PARAMETERS.md#review",
8973
- summary: "Activity units between skill-review injections.",
8974
- label: "技能审查间隔",
8975
- hint: "每多少活动单位触发一次技能审查注入。",
9572
+ summary: "Tool calls between skill-review injections: a turn advances by its tool-call count (a turn with none counts as one), or by 1 when the turn itself used a skill.",
9573
+ label: "技能检查间隔",
9574
+ hint: "每累计多少次工具调用检查一次技能:一轮按该轮工具调用数累加(一轮没有工具调用也算 1 次),本轮本身用到技能则只算 1 次。",
8976
9575
  control: "number",
8977
9576
  unit: "次",
8978
9577
  values: ""
@@ -8985,9 +9584,9 @@ const PARAM_EXPOSURE = Object.freeze([
8985
9584
  owner: "evolution-review",
8986
9585
  applies: "live",
8987
9586
  docAnchor: "PARAMETERS.md#review",
8988
- summary: "Activity units between memory-review injections.",
8989
- label: "记忆审查间隔",
8990
- hint: "每多少活动单位触发一次记忆审查注入。",
9587
+ summary: "Tool calls between memory-review injections: a turn advances by its tool-call count (a turn with none counts as one), or by 1 when the turn itself touched memory.",
9588
+ label: "记忆检查间隔",
9589
+ hint: "每累计多少次工具调用检查一次记忆:一轮按该轮工具调用数累加(一轮没有工具调用也算 1 次),本轮本身用到记忆则只算 1 次。",
8991
9590
  control: "number",
8992
9591
  unit: "次",
8993
9592
  values: ""
@@ -9001,11 +9600,12 @@ const PARAM_EXPOSURE = Object.freeze([
9001
9600
  applies: "live",
9002
9601
  docAnchor: "PARAMETERS.md#review",
9003
9602
  summary: "Which channel may inject a skill review (cadence, completion, both).",
9004
- label: "审查触发通道",
9005
- hint: "哪个通道可以注入技能审查:cadence=按间隔、completion=按完成、both=两者。",
9603
+ label: "技能检查时机",
9604
+ hint: "什么时候检查技能:按间隔/按任务完成/两者。",
9006
9605
  control: "select",
9007
9606
  unit: "",
9008
- values: "cadence|completion|both"
9607
+ values: "cadence|completion|both",
9608
+ valueLabels: "按间隔|按任务完成|两者"
9009
9609
  },
9010
9610
  {
9011
9611
  id: "skillReviewCompletionMinToolCalls",
@@ -9016,8 +9616,8 @@ const PARAM_EXPOSURE = Object.freeze([
9016
9616
  applies: "live",
9017
9617
  docAnchor: "PARAMETERS.md#review",
9018
9618
  summary: "Tool calls a task needs before the completion channel injects.",
9019
- label: "完成通道最少工具调用",
9020
- hint: "走完成通道时,任务至少经过多少次工具调用才注入。",
9619
+ label: "完成时的最少工具调用",
9620
+ hint: "选「按任务完成」时,任务至少用了几次工具才检查。",
9021
9621
  control: "number",
9022
9622
  unit: "次",
9023
9623
  values: ""
@@ -9031,8 +9631,8 @@ const PARAM_EXPOSURE = Object.freeze([
9031
9631
  applies: "live",
9032
9632
  docAnchor: "PARAMETERS.md#review",
9033
9633
  summary: "Master switch for the review plugin.",
9034
- label: "启用审查插件",
9035
- hint: "审查插件的总开关。",
9634
+ label: "启用自动检查",
9635
+ hint: "关闭后不再自动回顾会话。",
9036
9636
  control: "switch",
9037
9637
  unit: "",
9038
9638
  values: ""
@@ -9046,11 +9646,12 @@ const PARAM_EXPOSURE = Object.freeze([
9046
9646
  applies: "live",
9047
9647
  docAnchor: "PARAMETERS.md#review",
9048
9648
  summary: "Run the review in the parent session (inject) or on a subagent.",
9049
- label: "审查运行方式",
9050
- hint: "inject=在父会话内注入,subagent=交给子代理运行。",
9649
+ label: "运行位置",
9650
+ hint: "在哪里运行检查:在当前会话里/交给子代理。",
9051
9651
  control: "select",
9052
9652
  unit: "",
9053
- values: "subagent|inject"
9653
+ values: "subagent|inject",
9654
+ valueLabels: "交给子代理|在当前会话里"
9054
9655
  },
9055
9656
  {
9056
9657
  id: "reviewWakeInject",
@@ -9061,8 +9662,8 @@ const PARAM_EXPOSURE = Object.freeze([
9061
9662
  applies: "live",
9062
9663
  docAnchor: "PARAMETERS.md#review",
9063
9664
  summary: "Deliver the deferred review as a waking follow-up message.",
9064
- label: "审查后续唤醒注入",
9065
- hint: "把延后的审查作为唤醒消息投递。",
9665
+ label: "延后结果发提醒",
9666
+ hint: "会话空闲时,把延后的检查结果作为一条提醒发给你。",
9066
9667
  control: "switch",
9067
9668
  unit: "",
9068
9669
  values: ""
@@ -9136,8 +9737,8 @@ const PARAM_EXPOSURE = Object.freeze([
9136
9737
  applies: "live",
9137
9738
  docAnchor: "PARAMETERS.md#write-caps",
9138
9739
  summary: "Character cap on a SKILL.md body (tighten-only).",
9139
- label: "SKILL.md 正文字符上限",
9140
- hint: "SKILL.md 正文的字符上限(只许收紧)。",
9740
+ label: "技能文件正文上限",
9741
+ hint: "技能 Markdown 正文最多多少字符(只能调小)。",
9141
9742
  control: "number",
9142
9743
  unit: "字符",
9143
9744
  values: ""
@@ -9151,8 +9752,8 @@ const PARAM_EXPOSURE = Object.freeze([
9151
9752
  applies: "live",
9152
9753
  docAnchor: "PARAMETERS.md#write-caps",
9153
9754
  summary: "Byte cap on one support file (tighten-only).",
9154
- label: "支持文件字节上限",
9155
- hint: "单个支持文件的字节上限(只许收紧)。",
9755
+ label: "附带文件大小上限",
9756
+ hint: "技能附带文件单个最大多少字节(只能调小)。",
9156
9757
  control: "number",
9157
9758
  unit: "字节",
9158
9759
  values: ""
@@ -9166,8 +9767,8 @@ const PARAM_EXPOSURE = Object.freeze([
9166
9767
  applies: "live",
9167
9768
  docAnchor: "PARAMETERS.md#write-caps",
9168
9769
  summary: "Character cap on a skill name (tighten-only).",
9169
- label: "技能名字符上限",
9170
- hint: "技能名的字符上限(只许收紧)。",
9770
+ label: "技能名长度上限",
9771
+ hint: "技能名最多多少字符(只能调小)。",
9171
9772
  control: "number",
9172
9773
  unit: "字符",
9173
9774
  values: ""
@@ -9181,8 +9782,8 @@ const PARAM_EXPOSURE = Object.freeze([
9181
9782
  applies: "live",
9182
9783
  docAnchor: "PARAMETERS.md#write-caps",
9183
9784
  summary: "Character cap on a skill description (tighten-only).",
9184
- label: "技能描述字符上限",
9185
- hint: "技能描述的字符上限(只许收紧)。",
9785
+ label: "技能描述长度上限",
9786
+ hint: "技能描述最多多少字符(只能调小)。",
9186
9787
  control: "number",
9187
9788
  unit: "字符",
9188
9789
  values: ""
@@ -9196,8 +9797,8 @@ const PARAM_EXPOSURE = Object.freeze([
9196
9797
  applies: "live",
9197
9798
  docAnchor: "PARAMETERS.md#write-caps",
9198
9799
  summary: "Refuse a description over the authoring bar instead of advising.",
9199
- label: "描述严格档",
9200
- hint: "描述超过写作门槛时拒绝写入,而不是只提示。",
9800
+ label: "描述不合规即拒绝",
9801
+ hint: "描述不符合要求时直接拒绝写入(默认只提醒)。",
9201
9802
  control: "switch",
9202
9803
  unit: "",
9203
9804
  values: ""
@@ -9211,8 +9812,8 @@ const PARAM_EXPOSURE = Object.freeze([
9211
9812
  applies: "live",
9212
9813
  docAnchor: "PARAMETERS.md#write-caps",
9213
9814
  summary: "Refuse writes whose catalog entry resolves outside the family.",
9214
- label: "跨源严格档",
9215
- hint: "目录项解析到家族之外时拒绝写入。",
9815
+ label: "引用外部内容即拒绝",
9816
+ hint: "技能引用了本插件之外的文件时拒绝写入。",
9216
9817
  control: "switch",
9217
9818
  unit: "",
9218
9819
  values: ""
@@ -9226,11 +9827,12 @@ const PARAM_EXPOSURE = Object.freeze([
9226
9827
  applies: "live",
9227
9828
  docAnchor: "PARAMETERS.md#write-caps",
9228
9829
  summary: "Refuse a move that would leave a dangling reference, or verify it.",
9229
- label: "引用校验档",
9230
- hint: "移动或合并时对引用的处置:verify=校验、refuse=直接拒绝。",
9830
+ label: "引用检查方式",
9831
+ hint: "移动或合并技能时怎么处理引用:先检查/直接拒绝。",
9231
9832
  control: "select",
9232
9833
  unit: "",
9233
- values: "verify|refuse"
9834
+ values: "verify|refuse",
9835
+ valueLabels: "先检查|直接拒绝"
9234
9836
  },
9235
9837
  {
9236
9838
  id: "referenceRewrite",
@@ -9252,6 +9854,16 @@ const PARAM_EXPOSURE = Object.freeze([
9252
9854
  docAnchor: "PARAMETERS.md#write-caps",
9253
9855
  summary: "Report expired archives, or prune them."
9254
9856
  },
9857
+ {
9858
+ id: "skillVersionKeep",
9859
+ group: "write-caps",
9860
+ tier: "E2",
9861
+ authority: "cordis",
9862
+ owner: "tool-skill-manage",
9863
+ applies: "none",
9864
+ docAnchor: "PARAMETERS.md#write-caps",
9865
+ summary: "Content versions retained per skill; older ones leave the index. Their stored blobs stay on disk — no automatic sweep exists yet, so a long-lived library grows by the distinct bodies it has written."
9866
+ },
9255
9867
  {
9256
9868
  id: "supportFileCharPolicy",
9257
9869
  group: "write-caps",
@@ -9261,11 +9873,12 @@ const PARAM_EXPOSURE = Object.freeze([
9261
9873
  applies: "live",
9262
9874
  docAnchor: "PARAMETERS.md#write-caps",
9263
9875
  summary: "Warn about an oversize support file, or refuse the write.",
9264
- label: "支持文件超限档",
9265
- hint: "支持文件超出字符上限时:report=只报告、enforce=拒写。",
9876
+ label: "附带文件超限时",
9877
+ hint: "附带文件超出上限时:只提醒/拒绝写入。",
9266
9878
  control: "select",
9267
9879
  unit: "",
9268
- values: "report|enforce"
9880
+ values: "report|enforce",
9881
+ valueLabels: "只提醒|拒绝写入"
9269
9882
  },
9270
9883
  {
9271
9884
  id: "memoryChars",
@@ -9276,8 +9889,8 @@ const PARAM_EXPOSURE = Object.freeze([
9276
9889
  applies: "live",
9277
9890
  docAnchor: "PARAMETERS.md#memory",
9278
9891
  summary: "Character budget the memory store enforces for MEMORY.md.",
9279
- label: "MEMORY.md 字符预算",
9280
- hint: "memory 存储对 MEMORY.md 的字符上限。",
9892
+ label: "记忆库上限",
9893
+ hint: "长期记忆文件(MEMORY.md)最多多少字符。",
9281
9894
  control: "number",
9282
9895
  unit: "字符",
9283
9896
  values: ""
@@ -9291,8 +9904,8 @@ const PARAM_EXPOSURE = Object.freeze([
9291
9904
  applies: "live",
9292
9905
  docAnchor: "PARAMETERS.md#memory",
9293
9906
  summary: "Character budget the memory store enforces for USER.md.",
9294
- label: "USER.md 字符预算",
9295
- hint: "memory 存储对 USER.md 的字符上限。",
9907
+ label: "用户画像上限",
9908
+ hint: "用户画像文件(USER.md)最多多少字符。",
9296
9909
  control: "number",
9297
9910
  unit: "字符",
9298
9911
  values: ""
@@ -9316,8 +9929,8 @@ const PARAM_EXPOSURE = Object.freeze([
9316
9929
  applies: "live",
9317
9930
  docAnchor: "PARAMETERS.md#memory",
9318
9931
  summary: "Characters of one memory entry shown in a tool result preview.",
9319
- label: "条目预览字符数",
9320
- hint: "单条记忆预览保留多少字符。",
9932
+ label: "记忆预览长度",
9933
+ hint: "列表里每条记忆最多显示多少字符。",
9321
9934
  control: "number",
9322
9935
  unit: "字符",
9323
9936
  values: ""
@@ -9331,8 +9944,8 @@ const PARAM_EXPOSURE = Object.freeze([
9331
9944
  applies: "live",
9332
9945
  docAnchor: "PARAMETERS.md#memory",
9333
9946
  summary: "Prefix stored memory entries with their date heading.",
9334
- label: "记忆条目加日期前缀",
9335
- hint: "写入记忆时为条目加上日期标题。",
9947
+ label: "记忆条目加日期",
9948
+ hint: "写入时在条目开头加上日期。",
9336
9949
  control: "switch",
9337
9950
  unit: "",
9338
9951
  values: ""
@@ -9346,8 +9959,8 @@ const PARAM_EXPOSURE = Object.freeze([
9346
9959
  applies: "live",
9347
9960
  docAnchor: "PARAMETERS.md#memory",
9348
9961
  summary: "Consolidation failures one turn tolerates before the tool gives up.",
9349
- label: "合并失败容忍次数",
9350
- hint: "一轮内合并失败多少次后工具放弃。",
9962
+ label: "合并失败重试",
9963
+ hint: "合并记忆连续失败多少次后停止。",
9351
9964
  control: "number",
9352
9965
  unit: "次",
9353
9966
  values: ""
@@ -9361,8 +9974,8 @@ const PARAM_EXPOSURE = Object.freeze([
9361
9974
  applies: "live",
9362
9975
  docAnchor: "PARAMETERS.md#curator",
9363
9976
  summary: "Minimum hours between deterministic curation passes.",
9364
- label: "策展间隔",
9365
- hint: "两次确定性策展之间至少间隔多少小时。",
9977
+ label: "自动整理间隔",
9978
+ hint: "两次自动整理之间至少间隔多少小时。",
9366
9979
  control: "number",
9367
9980
  unit: "小时",
9368
9981
  values: ""
@@ -9376,8 +9989,8 @@ const PARAM_EXPOSURE = Object.freeze([
9376
9989
  applies: "live",
9377
9990
  docAnchor: "PARAMETERS.md#curator",
9378
9991
  summary: "Inactive days before a skill counts as stale.",
9379
- label: "判为陈旧的天数",
9380
- hint: "技能多少天没被使用即算陈旧。",
9992
+ label: "多久没用算过时",
9993
+ hint: "技能连续多少天没被用到就算过时。",
9381
9994
  control: "number",
9382
9995
  unit: "天",
9383
9996
  values: ""
@@ -9391,8 +10004,8 @@ const PARAM_EXPOSURE = Object.freeze([
9391
10004
  applies: "live",
9392
10005
  docAnchor: "PARAMETERS.md#curator",
9393
10006
  summary: "Inactive days before a stale skill is archived (must be >= staleAfterDays).",
9394
- label: "归档天数",
9395
- hint: "陈旧技能再过多少天归档(须 ≥ 判为陈旧的天数)。",
10007
+ label: "过时后归档",
10008
+ hint: "过时技能再过多少天移入归档(不能小于上一项)。",
9396
10009
  control: "number",
9397
10010
  unit: "天",
9398
10011
  values: ""
@@ -9406,8 +10019,8 @@ const PARAM_EXPOSURE = Object.freeze([
9406
10019
  applies: "live",
9407
10020
  docAnchor: "PARAMETERS.md#curator",
9408
10021
  summary: "Age at which a low quality score starts warning.",
9409
- label: "低质量告警天数",
9410
- hint: "质量分偏低时从多少天开始告警。",
10022
+ label: "质量偏低提醒",
10023
+ hint: "质量评分偏低时,从多少天开始提醒。",
9411
10024
  control: "number",
9412
10025
  unit: "天",
9413
10026
  values: ""
@@ -9421,8 +10034,8 @@ const PARAM_EXPOSURE = Object.freeze([
9421
10034
  applies: "live",
9422
10035
  docAnchor: "PARAMETERS.md#curator",
9423
10036
  summary: "Idle hours required before an automatic curation pass runs.",
9424
- label: "空闲门槛",
9425
- hint: "至少空闲多少小时才跑自动策展。",
10037
+ label: "最少空闲时长",
10038
+ hint: "你至少多少小时没用,才运行自动整理。",
9426
10039
  control: "number",
9427
10040
  unit: "小时",
9428
10041
  values: ""
@@ -9436,8 +10049,8 @@ const PARAM_EXPOSURE = Object.freeze([
9436
10049
  applies: "live",
9437
10050
  docAnchor: "PARAMETERS.md#curator",
9438
10051
  summary: "Let the idle gate open when the activity probe is unavailable.",
9439
- label: "空闲探针失败时放行",
9440
- hint: "活动探针不可用时,是否让空闲门槛直接放行。",
10052
+ label: "检测不到活动也运行",
10053
+ hint: "无法判断你是否空闲时,仍然运行自动整理。",
9441
10054
  control: "switch",
9442
10055
  unit: "",
9443
10056
  values: ""
@@ -9451,8 +10064,8 @@ const PARAM_EXPOSURE = Object.freeze([
9451
10064
  applies: "live",
9452
10065
  docAnchor: "PARAMETERS.md#curator",
9453
10066
  summary: "Enable the LLM nomination pass on top of the deterministic lifecycle.",
9454
- label: "启用 LLM 提名",
9455
- hint: "在确定性生命周期之上再跑一遍 LLM 提名。",
10067
+ label: "用模型挑选",
10068
+ hint: "在固定规则之外,再用模型挑一遍候选项。",
9456
10069
  control: "switch",
9457
10070
  unit: "",
9458
10071
  values: ""
@@ -9466,8 +10079,8 @@ const PARAM_EXPOSURE = Object.freeze([
9466
10079
  applies: "live",
9467
10080
  docAnchor: "PARAMETERS.md#curator",
9468
10081
  summary: "Token budget of the curator LLM review.",
9469
- label: "策展 LLM token 预算",
9470
- hint: "策展 LLM 审查可用的 token 预算。",
10082
+ label: "模型处理上限",
10083
+ hint: "每次用模型挑选时最多消耗多少 token。",
9471
10084
  control: "number",
9472
10085
  unit: "token",
9473
10086
  values: ""
@@ -9481,8 +10094,8 @@ const PARAM_EXPOSURE = Object.freeze([
9481
10094
  applies: "live",
9482
10095
  docAnchor: "PARAMETERS.md#curator",
9483
10096
  summary: "Wall-clock bound of the curator LLM review.",
9484
- label: "策展 LLM 超时",
9485
- hint: "策展 LLM 审查的墙钟上限。",
10097
+ label: "模型处理超时",
10098
+ hint: "一次模型挑选最长允许运行多久。",
9486
10099
  control: "number",
9487
10100
  unit: "毫秒",
9488
10101
  values: ""
@@ -9496,8 +10109,8 @@ const PARAM_EXPOSURE = Object.freeze([
9496
10109
  applies: "live",
9497
10110
  docAnchor: "PARAMETERS.md#curator",
9498
10111
  summary: "Body character line the health view judges against.",
9499
- label: "正文软带",
9500
- hint: "健康视图判定正文长度的软带。",
10112
+ label: "正文长度参考值",
10113
+ hint: "技能正文超过多少字符算偏长(只用于健康提示)。",
9501
10114
  control: "number",
9502
10115
  unit: "字符",
9503
10116
  values: ""
@@ -9511,8 +10124,8 @@ const PARAM_EXPOSURE = Object.freeze([
9511
10124
  applies: "live",
9512
10125
  docAnchor: "PARAMETERS.md#curator",
9513
10126
  summary: "Stamp density per KB that flags log-like content in a body.",
9514
- label: "标记密度阈值",
9515
- hint: "每 KB 出现多少个时间标记就算「像日志」。",
10127
+ label: "日志化判定阈值",
10128
+ hint: "每 KB 出现多少个时间标记,就判为「像日志」。",
9516
10129
  control: "number",
9517
10130
  unit: "处/KB",
9518
10131
  values: ""
@@ -9526,8 +10139,8 @@ const PARAM_EXPOSURE = Object.freeze([
9526
10139
  applies: "live",
9527
10140
  docAnchor: "PARAMETERS.md#curator",
9528
10141
  summary: "Patches without a read that flag a write-ghost skill.",
9529
- label: "空写判定补丁数",
9530
- hint: "多少次改动都没有任何读取,即判为「写幽灵」。",
10142
+ label: "只写不读判定次数",
10143
+ hint: "连续多少次改动都没有被读取,就判为「只写不读」。",
9531
10144
  control: "number",
9532
10145
  unit: "处",
9533
10146
  values: ""
@@ -10300,4 +10913,4 @@ function sessionAudited(ctx, sessionId, sessionScoped) {
10300
10913
  return false;
10301
10914
  }
10302
10915
  //#endregion
10303
- export { ALIVE_LOCK_TAKEOVER_MS, AUTHORING_DESCRIPTION_BAR, AUTHORING_SPLIT_LINE_CHARS, 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, DEAD_LOCK_TAKEOVER_MS, DEDUP_MAX_PAIR_COMPARISONS, 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_REFERENCE_REWRITE_POLICY, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_MODEL, DEFAULT_SKILL_REVIEW_TRIGGER, 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_LOCK_TAKEOVER_MS, 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, HEALTH_STAMP_RE, HOOK_LINE_RE, INSTANCE_KEYS, KEEP_LINE_RE, LOCK_BODY_RE, LOCK_SUFFIX, LOCK_TEAR_TAKEOVER_MS, LOW_QUALITY_THRESHOLD, LostWriteLock, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, 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, NATIVE_CALL_EVENT, NATIVE_RESULT_EVENT, PARAM_ALIASES, PARAM_EXPOSURE, PARAM_NAMESPACES, PATTERN_OVERLAP, PLATFORM_ESTIMATE_CHARS_PER_TOKEN, POLICY_STAGE_DEFAULTS, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, PTC_DISPATCH_EVENT, PTC_DISPATCH_START_EVENT, QUALITY_WEIGHTS, RESTRUCTURE_TARGET_RE, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, THREAT_EXEMPTION_HINT, TOKEN_ASCII_WEIGHT, TOKEN_CJK_WEIGHT, ToolDispatchNormalizer, ToolDispatchPayloadError, UPSTREAM_LIMIT_CHARS_PER_TOKEN, advanceReview, allowRowCollisions, appendEvolutionEvent, applyCuratorLifecycleFields, applyCuratorMetaFields, applyReferenceRewrite, assertDispatchPayload, assertSkillsRootAliasRetired, assessStructureHealth, authoringFeedback, bodyCost, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpSupportRead, bumpUse, bumpView, callingScope, canonicalWriteId, claimInstance, clampedNumber, clearReviewChannel, collectReadSkillNames, composePresetComposition, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, countDispatches, createGateSet, daysSinceIso, decideTakeover, describeReferenceRewrite, duplicateHeadings, emptyRecord, errorText, evaluateThreat, eventsFile, evolutionEventPayloadIssue, evolutionHome, evolutionIoAdapter, evolutionRoot, findDriftSignal, foldCuratorFields, foldToolDispatches, foldTurn, frontmatterBlock, frontmatterCatalogInvalid, getRecord, idleDays, installParamSection, instanceClaimKey, instanceClaimedWriteSites, instanceHolder, isAbsent, isCommittedWarning, isDeprecatedParamId, isFileShapedPath, isMissingPath, isPresent, isProcessAlive, isProgramToolName, isReviewChannelSession, isSkillReadToolName, isSkillToolName, isUnknown, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadRowOverrides, loadSuppressedNames, loadUsage, makeSerialQueue, mapProbe, markAgentCreated, markReviewChannel, markerEntryName, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, neutralizePromptVariables, newSkillLibrary, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, observeEvent, overlongLines, paramNamespace, paramSectionOverrides, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, parseLockBody, pendingSelfCleanup, persistedWriteSite, persistedWriteSites, planReferenceRewrite, planRehoming, policyStageLimits, probeAbsent, probeList, probeMtime, probePresent, probeReason, probeUnknown, readDispatchSignal, readEvolutionEvents, readEvolutionTimeline, readNumberParam, readParam, recordMutation, redactSecrets, relatedSkillNames, releaseInstance, renameWithRetry, renderCuratorReportMarkdown, resolveCitations, resolveExecOrigins, resolveOrigins, resolveParamId, resolveRootConfig, resolveSkillsRoot, retainEventArchives, retirementReport, reviewPrompt, saveSuppressedNames, saveUsage, scanBodyHooks, scanContentThreats, scanMemoryThreats, scanThreats, scopedProbeReport, sessionAudited, sessionSeesFamilyTools, skillReadNameOf, skillsRoot, supportFileReadOf, suppressedFile, sweepReviewChannelSessions, tokenLineFor, transactIo, transactTaskGuard, unhookedSupportPointers, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, validateRestructureTarget, valueOr, verifyPromptBundle, writeDurableTmp, yamlPlainScalarNeedsQuotes };
10916
+ export { ALIVE_LOCK_TAKEOVER_MS, 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, DEAD_LOCK_TAKEOVER_MS, DEDUP_MAX_PAIR_COMPARISONS, DEDUP_SIMILARITY_THRESHOLD, 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_REFERENCE_REWRITE_POLICY, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, 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_LOCK_TAKEOVER_MS, 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, HEALTH_STAMP_RE, HISTORY_DIR, HISTORY_INDEX_VERSION, HOOK_LINE_RE, INSTANCE_KEYS, KEEP_LINE_RE, LOCK_BODY_RE, LOCK_SUFFIX, LOCK_TEAR_TAKEOVER_MS, LOW_QUALITY_THRESHOLD, LostWriteLock, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, 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, 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, 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, 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, SUPPRESSED_FILE_VERSION, SkillLibrary, THREAT_EXEMPTION_HINT, TOKEN_ASCII_WEIGHT, TOKEN_CJK_WEIGHT, ToolDispatchNormalizer, ToolDispatchPayloadError, UPSTREAM_LIMIT_CHARS_PER_TOKEN, advanceReview, affinity, allowRowCollisions, appendEvolutionEvent, applyCuratorLifecycleFields, applyCuratorMetaFields, applyReferenceRewrite, assertDispatchPayload, assertSkillsRootAliasRetired, assessStructureHealth, authoringFeedback, blobPath, bodyCost, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpSupportRead, bumpUse, bumpView, callingScope, canonicalWriteId, claimInstance, clampedNumber, clearReviewChannel, collectReadSkillNames, composePresetComposition, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, contentRetentionFeedback, countDispatches, createGateSet, daysSinceIso, decideTakeover, describeReferenceRewrite, duplicateHeadings, emptyRecord, errorText, evaluateThreat, eventsFile, evidenceKindIndex, evolutionEventPayloadIssue, evolutionHome, evolutionIoAdapter, evolutionRoot, filterUnreadSkillOps, findDriftSignal, foldCuratorFields, foldToolDispatches, foldTurn, frontmatterBlock, frontmatterCatalogInvalid, getRecord, historyIndexFile, historyRoot, identity, idleDays, installParamSection, instanceClaimKey, instanceClaimedWriteSites, instanceHolder, isAbsent, isCommittedWarning, isDeprecatedParamId, isFileShapedPath, isMissingPath, isPresent, isProcessAlive, isProgramToolName, isReviewChannelSession, isSkillReadToolName, isSkillToolName, isUnknown, isUnreadWrite, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadRowOverrides, loadSuppressedNames, loadUsage, loadVersionContent, loadVersions, makeSerialQueue, mapProbe, markAgentCreated, markReviewChannel, markerEntryName, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nearDuplicateSummaries, neutralizePromptVariables, newSkillLibrary, nextHistoryIndex, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, observeEvent, orderVersions, overlongLines, paramNamespace, paramSectionOverrides, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, parseHistoryIndex, parseLockBody, pendingSelfCleanup, persistedWriteSite, persistedWriteSites, planReferenceRewrite, planRehoming, policyStageLimits, probeAbsent, probeList, probeMtime, probePresent, probeReason, probeUnknown, projectionText, readDispatchSignal, readEvolutionEvents, readEvolutionTimeline, readHistoryIndex, readNumberParam, readParam, recordMutation, recordVersions, redactSecrets, relatedSkillNames, releaseInstance, renameWithRetry, renderCuratorReportMarkdown, resolveCitations, resolveExecOrigins, resolveOrigins, resolveParamId, resolveRootConfig, resolveSkillsRoot, retainEventArchives, retirementReport, reviewPrompt, saveSuppressedNames, saveUsage, scanBodyHooks, scanContentThreats, scanMemoryThreats, scanThreats, scopedProbeReport, sessionAudited, sessionReadSkillNames, sessionSeesFamilyTools, skillReadNameOf, skillsRoot, supportFileReadOf, suppressedFile, sweepReviewChannelSessions, tokenLineFor, transactIo, transactTaskGuard, unhookedSupportPointers, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, validateRestructureTarget, valueOr, verifyPromptBundle, vocabulary, writeDurableTmp, yamlPlainScalarNeedsQuotes };