@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/README.md +13 -3
- package/lib/index.js +706 -93
- package/lib/types/constants.d.ts +15 -1
- package/lib/types/errors.d.ts +4 -1
- package/lib/types/events.d.ts +7 -0
- package/lib/types/evidence.d.ts +42 -0
- package/lib/types/index.d.ts +7 -2
- package/lib/types/limits.d.ts +3 -0
- package/lib/types/mutations.d.ts +5 -0
- package/lib/types/params.d.ts +21 -7
- package/lib/types/quality.d.ts +91 -0
- package/lib/types/skill-history.d.ts +189 -0
- package/lib/types/skill-reads.d.ts +47 -0
- package/lib/types/skill-store.d.ts +29 -1
- package/package.json +1 -1
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
|
|
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-
|
|
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
|
-
/**
|
|
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
|
|
4813
|
-
function
|
|
4814
|
-
return createHash("sha256").update(normalize(
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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 === "" || !
|
|
6754
|
-
...
|
|
6755
|
-
message: `${
|
|
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
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
9140
|
-
hint: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
9280
|
-
hint: "
|
|
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: "
|
|
9295
|
-
hint: "
|
|
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: "
|
|
9455
|
-
hint: "
|
|
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: "
|
|
9470
|
-
hint: "
|
|
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: "
|
|
9485
|
-
hint: "
|
|
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 };
|