@lmzhen/dsh-evolution-core 0.24.0 → 0.25.1

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
@@ -473,6 +473,13 @@ const DEFAULT_CURATOR_INTERVAL_HOURS = 168;
473
473
  const DEFAULT_MIN_IDLE_HOURS = 2;
474
474
  const DEFAULT_STALE_AFTER_DAYS = 30;
475
475
  const DEFAULT_ARCHIVE_AFTER_DAYS = 90;
476
+ /**
477
+ * Support-file count above which `references-sprawl` warns. **NOT CALIBRATED YET** — 60 is a
478
+ * placeholder: the platform corpus is empty (no platform skills available here), so the admission
479
+ * band cannot be checked. Until `gen-health-baseline` reports both corpora, the rule is
480
+ * REPORT-ONLY: its count must not be shown as a number on any surface (plan G4 ③).
481
+ */
482
+ const SUPPORT_SPRAWL_FILES = 60;
476
483
  const DEFAULT_MEMORY_CHAR_LIMIT = 2200;
477
484
  const DEFAULT_USER_CHAR_LIMIT = 1375;
478
485
  /** Consolidation-failure backoff cap, shared by MemoryStore and memory-files' Config default. */
@@ -1157,6 +1164,213 @@ function bodyCost(content) {
1157
1164
  };
1158
1165
  }
1159
1166
  //#endregion
1167
+ //#region lib/types/skill-lint.js
1168
+ const msg = (parts) => parts.map((p) => typeof p === "string" ? p : String(p)).join("");
1169
+ /** One rule yields AT MOST one finding (contract §3). `body-oversize-2x` is the only rule whose
1170
+ * severity can escalate a verdict to needs-restructure. */
1171
+ const RULES = [
1172
+ {
1173
+ id: "unreadable-skill",
1174
+ severity: "error",
1175
+ run: (facts) => facts.readable && facts.dims.bodyChars !== null ? null : {
1176
+ severity: "error",
1177
+ rule: "unreadable-skill",
1178
+ message: msg([facts.skillName, ": SKILL.md could not be read — no health claim is possible"]),
1179
+ count: 1
1180
+ }
1181
+ },
1182
+ {
1183
+ id: "body-oversize-2x",
1184
+ severity: "error",
1185
+ run: (facts) => facts.overTwiceSoftLine ? {
1186
+ severity: "error",
1187
+ rule: "body-oversize-2x",
1188
+ message: msg([
1189
+ facts.skillName,
1190
+ ": body is at or past twice the soft line (",
1191
+ facts.softLineChars,
1192
+ " chars)"
1193
+ ]),
1194
+ count: 1
1195
+ } : null
1196
+ },
1197
+ {
1198
+ id: "body-oversize",
1199
+ severity: "warn",
1200
+ run: (facts) => !facts.overTwiceSoftLine && facts.overSoftLine ? {
1201
+ severity: "warn",
1202
+ rule: "body-oversize",
1203
+ message: msg([
1204
+ facts.skillName,
1205
+ ": body is at or above the soft line (",
1206
+ facts.softLineChars,
1207
+ " chars)"
1208
+ ]),
1209
+ count: 1
1210
+ } : null
1211
+ },
1212
+ {
1213
+ id: "stamp-density",
1214
+ severity: "warn",
1215
+ run: (facts) => {
1216
+ const density = facts.dims.stampDensityPerKb;
1217
+ const hits = facts.dims.stampHits;
1218
+ if (density === null || hits === null || density < facts.stampLimitPerKb) return null;
1219
+ return {
1220
+ severity: "warn",
1221
+ rule: "stamp-density",
1222
+ message: msg([
1223
+ facts.skillName,
1224
+ ": stamp density ",
1225
+ density.toFixed(1),
1226
+ "/KB (rc/sha/date lines — log-like content)"
1227
+ ]),
1228
+ count: hits
1229
+ };
1230
+ }
1231
+ },
1232
+ {
1233
+ id: "body-without-support",
1234
+ severity: "warn",
1235
+ run: (facts) => facts.dims.supportGroups === 0 && facts.dims.bodyChars !== null && facts.dims.bodyChars >= facts.softLineChars / 2 ? {
1236
+ severity: "warn",
1237
+ rule: "body-without-support",
1238
+ message: msg([
1239
+ facts.skillName,
1240
+ ": large body (",
1241
+ facts.dims.bodyChars,
1242
+ " chars) with NO support files"
1243
+ ]),
1244
+ count: 1
1245
+ } : null
1246
+ },
1247
+ {
1248
+ id: "references-sprawl",
1249
+ severity: "warn",
1250
+ run: (facts) => {
1251
+ const files = facts.dims.supportFiles;
1252
+ if (files === null || files < 60) return null;
1253
+ return {
1254
+ severity: "warn",
1255
+ rule: "references-sprawl",
1256
+ message: msg([
1257
+ facts.skillName,
1258
+ ": ",
1259
+ files,
1260
+ " support files"
1261
+ ]),
1262
+ count: files
1263
+ };
1264
+ }
1265
+ },
1266
+ {
1267
+ id: "write-ghost",
1268
+ severity: "warn",
1269
+ run: (facts) => {
1270
+ const patches = facts.dims.churnPatches;
1271
+ const reads = facts.dims.churnReads;
1272
+ if (patches === null || reads === null || patches < facts.churnMinPatches || reads > 0) return null;
1273
+ return {
1274
+ severity: "warn",
1275
+ rule: "write-ghost",
1276
+ message: msg([
1277
+ facts.skillName,
1278
+ ": ",
1279
+ patches,
1280
+ " patch(es), never read"
1281
+ ]),
1282
+ count: patches
1283
+ };
1284
+ }
1285
+ }
1286
+ ];
1287
+ function lintSkill(facts) {
1288
+ const out = [];
1289
+ for (const rule of RULES) {
1290
+ const hit = rule.run(facts);
1291
+ if (hit !== null) out.push(hit);
1292
+ }
1293
+ return out;
1294
+ }
1295
+ /** What a write introduced, what got worse, what got fixed. A rule whose count grew (one site to
1296
+ * nine) counts as worsened — comparing by rule name alone reported nothing. */
1297
+ function diffFindings(before, after) {
1298
+ const beforeByRule = new Map(before.map((f) => [f.rule, f]));
1299
+ const afterByRule = new Map(after.map((f) => [f.rule, f]));
1300
+ return {
1301
+ introduced: after.filter((f) => !beforeByRule.has(f.rule)),
1302
+ worsened: after.filter((f) => {
1303
+ const old = beforeByRule.get(f.rule);
1304
+ return old !== void 0 && f.count > old.count;
1305
+ }),
1306
+ resolved: before.filter((f) => !afterByRule.has(f.rule)).map((f) => f.rule)
1307
+ };
1308
+ }
1309
+ /** Support files = markdown files under references/ (contract §4: unit frozen, underscore-prefixed
1310
+ * excluded). Paths are supplied by the caller — this module never touches the filesystem. */
1311
+ function countReferenceFiles(paths) {
1312
+ return paths.filter((p) => {
1313
+ const n = p.replace(/\\/g, "/");
1314
+ if (!(n.includes("/references/") || n.startsWith("references/")) || !n.endsWith(".md")) return false;
1315
+ return !n.slice(n.lastIndexOf("/") + 1).startsWith("_");
1316
+ }).length;
1317
+ }
1318
+ //#endregion
1319
+ //#region lib/types/health-baseline.js
1320
+ const DEFAULT_ADMISSION = {
1321
+ minUserHitRate: .05,
1322
+ maxUserHitRate: .25
1323
+ };
1324
+ function buildHealthBaseline(skills) {
1325
+ const values = {};
1326
+ for (const rule of RULES) values[rule.id] = {
1327
+ platformHits: 0,
1328
+ userHits: 0,
1329
+ userHitRate: 0
1330
+ };
1331
+ let user = 0;
1332
+ let platform = 0;
1333
+ for (const skill of skills) {
1334
+ if (skill.corpus === "user") user += 1;
1335
+ else platform += 1;
1336
+ for (const finding of lintSkill(skill.facts)) {
1337
+ const row = values[finding.rule];
1338
+ if (row === void 0) continue;
1339
+ if (skill.corpus === "user") row.userHits += 1;
1340
+ else row.platformHits += 1;
1341
+ }
1342
+ }
1343
+ for (const row of Object.values(values)) row.userHitRate = user === 0 ? 0 : row.userHits / user;
1344
+ return {
1345
+ unit: "chars",
1346
+ scope: "references markdown file count",
1347
+ corpora: {
1348
+ user,
1349
+ platform
1350
+ },
1351
+ values
1352
+ };
1353
+ }
1354
+ /** The admission verdict for ONE rule id (contract §4): platform corpus must not trip it, and the
1355
+ * user-library hit rate must land inside the band. Every failure names the number it failed on. */
1356
+ function admissionVerdict(baseline, ruleId, thresholds = DEFAULT_ADMISSION) {
1357
+ const row = baseline.values[ruleId];
1358
+ const failures = [];
1359
+ if (row === void 0) return {
1360
+ admitted: false,
1361
+ failures: ["rule " + ruleId + " has no baseline row"]
1362
+ };
1363
+ if (baseline.corpora.platform === 0) failures.push("no platform-corpus skills measured — a vacuous 0-hit count admits any rule");
1364
+ else if (row.platformHits !== 0) failures.push("platform corpus trips " + ruleId + " " + String(row.platformHits) + " time(s) — a rule the shipped library violates is unsatisfiable");
1365
+ if (baseline.corpora.user === 0) failures.push("no user-library skills measured — a rate over an empty corpus proves nothing");
1366
+ else if (row.userHitRate < thresholds.minUserHitRate) failures.push(ruleId + " hits " + row.userHitRate.toFixed(3) + " of the user library, below " + String(thresholds.minUserHitRate) + " — too rare to be worth a rule");
1367
+ else if (row.userHitRate > thresholds.maxUserHitRate) failures.push(ruleId + " hits " + row.userHitRate.toFixed(3) + " of the user library, above " + String(thresholds.maxUserHitRate) + " — furniture, no signal");
1368
+ return {
1369
+ admitted: failures.length === 0,
1370
+ failures
1371
+ };
1372
+ }
1373
+ //#endregion
1160
1374
  //#region lib/types/guards.js
1161
1375
  /**
1162
1376
  * The family's plain-object predicate, in ONE place (v46 G7.1 / audit A8).
@@ -6671,13 +6885,35 @@ const HEALTH_STAMP_RE = new RegExp(String.raw`\brc\.\d+\b|\b(?=[0-9a-f]{7,40}\b)
6671
6885
  * warned on a perfectly healthy body (audit 2026-08-31 X1).
6672
6886
  */
6673
6887
  const MIN_STAMP_BODY_CHARS = 2e3;
6888
+ /**
6889
+ * The findings for ONE body, computed by the engine that owns the judgement (G4).
6890
+ *
6891
+ * Deliberately a thin call into `assessStructureHealth`: a later consumer (the write receipt) needs
6892
+ * findings for a body it is about to store, and re-deriving the facts anywhere else would be a
6893
+ * SECOND judgement — the token-scale line, the /1024 stamp density and the soft/2 support threshold
6894
+ * all live in one place and must stay there.
6895
+ * @param skillName - the skill the body belongs to (used in messages).
6896
+ * @param content - the WHOLE SKILL.md text, frontmatter included (the engine's own convention).
6897
+ * @param supportGroups - non-empty support dirs; pass the real count, never a guess.
6898
+ * @param thresholds - the configured thresholds; defaults to the shipped ones.
6899
+ * @returns the findings, in rule order.
6900
+ */
6901
+ function lintBody(skillName, content, supportGroups, thresholds = DEFAULT_HEALTH_THRESHOLDS) {
6902
+ return assessStructureHealth({
6903
+ skillName,
6904
+ bodyChars: content.length,
6905
+ bodyText: content,
6906
+ supportGroups
6907
+ }, thresholds).findings;
6908
+ }
6674
6909
  function assessStructureHealth(snapshot, thresholds = DEFAULT_HEALTH_THRESHOLDS) {
6675
- const reasons = [];
6676
6910
  const dims = {
6677
6911
  bodyChars: snapshot.bodyChars,
6678
6912
  bodyTokens: Math.round(snapshot.bodyChars / UPSTREAM_LIMIT_CHARS_PER_TOKEN),
6679
6913
  stampDensityPerKb: null,
6914
+ stampHits: null,
6680
6915
  supportGroups: snapshot.supportGroups,
6916
+ supportFiles: snapshot.supportFiles ?? null,
6681
6917
  churnPatches: null,
6682
6918
  churnReads: null
6683
6919
  };
@@ -6687,25 +6923,30 @@ function assessStructureHealth(snapshot, thresholds = DEFAULT_HEALTH_THRESHOLDS)
6687
6923
  dims.bodyTokens = cost === null ? Math.round(snapshot.bodyChars / UPSTREAM_LIMIT_CHARS_PER_TOKEN) : cost.tokens;
6688
6924
  const overLine = cost === null || lineTokens === null ? snapshot.bodyChars >= thresholds.softBodyChars : cost.tokens >= lineTokens;
6689
6925
  const overTwice = cost === null || lineTokens === null ? snapshot.bodyChars >= thresholds.softBodyChars * 2 : cost.tokens >= lineTokens * 2;
6690
- const scale = cost === null || lineTokens === null ? `${snapshot.bodyChars} chars` : `${cost.tokens} tokens (${snapshot.bodyChars} chars)`;
6691
- const lineText = cost === null || lineTokens === null ? `${thresholds.softBodyChars} chars` : `${thresholds.softBodyChars} chars = ${lineTokens} tokens`;
6692
- if (overTwice) reasons.push(`body ${scale} is >= 2x the soft line (${lineText}) — consider splitting or offloading`);
6693
- else if (overLine) reasons.push(`body ${scale} at or above the soft line (${lineText})`);
6694
6926
  if (snapshot.bodyText && snapshot.bodyChars >= 2e3) {
6695
6927
  const kb = Math.max(1, snapshot.bodyChars / 1024);
6696
- dims.stampDensityPerKb = (snapshot.bodyText.match(HEALTH_STAMP_RE) ?? []).length / kb;
6697
- if (dims.stampDensityPerKb >= thresholds.stampDensityPerKb) reasons.push(`stamp density ${dims.stampDensityPerKb.toFixed(1)}/KB (rc/sha/date lines — log-like content in the body)`);
6928
+ const stamps = (snapshot.bodyText.match(HEALTH_STAMP_RE) ?? []).length;
6929
+ dims.stampDensityPerKb = stamps / kb;
6930
+ dims.stampHits = stamps;
6698
6931
  }
6699
- if (snapshot.supportGroups === 0 && snapshot.bodyChars >= thresholds.softBodyChars / 2) reasons.push(`large body (${snapshot.bodyChars} chars) with NO support files — session detail may belong in references/`);
6700
6932
  if (snapshot.patchCount !== void 0 && snapshot.readCount !== void 0) {
6701
6933
  dims.churnPatches = snapshot.patchCount;
6702
6934
  dims.churnReads = snapshot.readCount;
6703
- if (snapshot.patchCount >= thresholds.churnMinPatches && snapshot.readCount === 0) reasons.push(`patched ${snapshot.patchCount} times but never read (write-ghost — content may be dead)`);
6704
6935
  }
6936
+ const findings = lintSkill({
6937
+ skillName: snapshot.skillName,
6938
+ dims,
6939
+ softLineChars: thresholds.softBodyChars,
6940
+ overSoftLine: overLine,
6941
+ overTwiceSoftLine: overTwice,
6942
+ stampLimitPerKb: thresholds.stampDensityPerKb,
6943
+ churnMinPatches: thresholds.churnMinPatches,
6944
+ readable: true
6945
+ });
6705
6946
  return {
6706
- verdict: overTwice ? "needs-restructure" : reasons.length > 0 ? "warn" : "healthy",
6947
+ verdict: findings.some((f) => f.rule === "body-oversize-2x") ? "needs-restructure" : findings.length > 0 ? "warn" : "healthy",
6707
6948
  dims,
6708
- reasons
6949
+ findings
6709
6950
  };
6710
6951
  }
6711
6952
  //#endregion
@@ -7980,6 +8221,50 @@ function readingStats(full, shown) {
7980
8221
  truncated: shown.length < full.length
7981
8222
  };
7982
8223
  }
8224
+ /**
8225
+ * The H2 sections of a body, in document order — the ONE scan (G3).
8226
+ *
8227
+ * Both the restructure candidate list and the store anchor validation call this function, so a
8228
+ * candidate can only name a heading that exists EXACTLY ONCE and has content: the two rejections a
8229
+ * hand-written anchor used to hit ("no such heading", "ambiguous anchor") stop being reachable.
8230
+ * H2 is the section boundary (a section spans to the next H2 or EOF), matching the store.
8231
+ * @param body - the markdown after the frontmatter block.
8232
+ * @returns one row per H2 heading, with `count` filled in a second pass.
8233
+ */
8234
+ function h2Sections(body) {
8235
+ const lines = body.split(String.fromCharCode(10));
8236
+ const rows = [];
8237
+ for (let i = 0; i < lines.length; i += 1) {
8238
+ const match = /^#{2}\s+(.+?)\s*$/.exec(lines[i] ?? "");
8239
+ if (match === null) continue;
8240
+ const title = (match[1] ?? "").trim();
8241
+ let end = lines.length;
8242
+ for (let j = i + 1; j < lines.length; j += 1) if (/^#{2}\s/.test(lines[j] ?? "")) {
8243
+ end = j;
8244
+ break;
8245
+ }
8246
+ rows.push({
8247
+ title,
8248
+ start: i,
8249
+ end,
8250
+ count: 0
8251
+ });
8252
+ }
8253
+ for (const row of rows) row.count = rows.filter((other) => other.title === row.title).length;
8254
+ return rows;
8255
+ }
8256
+ /**
8257
+ * The sections a restructure proposal may name: heading unique, section non-empty. Derived from
8258
+ * `h2Sections`, i.e. from the SAME scan the store validates against.
8259
+ * @param body - the markdown after the frontmatter block.
8260
+ * @returns move-ready pairs in document order; empty when nothing qualifies.
8261
+ */
8262
+ function restructureCandidates(body) {
8263
+ return h2Sections(body).filter((section) => section.count === 1 && section.end > section.start + 1).map((section) => ({
8264
+ heading: section.title,
8265
+ toFile: "references/" + section.title.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "-").replace(/^-+|-+$/g, "") + ".md"
8266
+ }));
8267
+ }
7983
8268
  //#endregion
7984
8269
  //#region lib/types/drift-signals.js
7985
8270
  /**
@@ -9213,19 +9498,16 @@ function supportRefs(content) {
9213
9498
  }
9214
9499
  function planRestructureSections(body, moves) {
9215
9500
  const lines = body.split("\n");
9501
+ const sections = h2Sections(body);
9216
9502
  const spans = [];
9217
9503
  for (const move of moves) {
9218
9504
  const wanted = move.heading.trim();
9219
- const starts = [];
9220
- for (let i = 0; i < lines.length; i += 1) if ((/^#{2}\s+(.+?)\s*$/.exec(lines[i] ?? "")?.[1]?.trim() ?? "") === wanted) starts.push(i);
9221
- const [start] = starts;
9222
- if (start === void 0) return { error: `no "## ${wanted}" heading in the body` };
9223
- if (starts.length > 1) return { error: `heading "## ${wanted}" appears ${starts.length} times (ambiguous anchor)` };
9224
- let end = lines.length;
9225
- for (let i = start + 1; i < lines.length; i += 1) if (/^#{2}\s/.test(lines[i] ?? "")) {
9226
- end = i;
9227
- break;
9228
- }
9505
+ const matching = sections.filter((section) => section.title === wanted);
9506
+ if (matching.length === 0) return { error: `no "## ${wanted}" heading in the body` };
9507
+ if (matching.length > 1) return { error: `heading "## ${wanted}" appears ${matching.length} times (ambiguous anchor)` };
9508
+ const section = matching[0];
9509
+ if (section === void 0) return { error: `no "## ${wanted}" heading in the body` };
9510
+ const { start, end } = section;
9229
9511
  if (end === start + 1) return { error: `heading "## ${wanted}" has an empty section` };
9230
9512
  if (spans.some((span) => start === span.start)) return { error: `heading "## ${wanted}" is moved twice` };
9231
9513
  spans.push({
@@ -9572,6 +9854,32 @@ var SkillLibrary = class {
9572
9854
  return await this.io.exists(markerPath(dir, "pinned"));
9573
9855
  }
9574
9856
  /** Count non-empty support subdirectories (richness input for quality scoring). */
9857
+ /** G4 ⑤-d: the dangling targets THIS write introduced, as a receipt note (empty when none).
9858
+ *
9859
+ * Uses the SAME resolver as the drift signal, so a receipt and a drift row can never disagree;
9860
+ * an absent or unknown listing reports nothing rather than inventing a dead link.
9861
+ */
9862
+ async danglingNoteFor(skillName, before, after) {
9863
+ try {
9864
+ const listing = await this.listSupportFiles(skillName);
9865
+ if (!isPresent(listing)) return "";
9866
+ const files = listing.value;
9867
+ const owner = "SKILL.md";
9868
+ const seen = new Set(resolveCitations({
9869
+ content: before,
9870
+ file: owner,
9871
+ files
9872
+ }).dangling.map((ref) => ref.target ?? ref.raw));
9873
+ const introduced = resolveCitations({
9874
+ content: after,
9875
+ file: owner,
9876
+ files
9877
+ }).dangling.map((ref) => ref.target ?? ref.raw).filter((target) => !seen.has(target));
9878
+ return introduced.length > 0 ? " [dangling: " + introduced.join(",") + "]" : "";
9879
+ } catch {
9880
+ return "";
9881
+ }
9882
+ }
9575
9883
  async countSupportDirs(rawName) {
9576
9884
  const name = rawName.trim();
9577
9885
  if (this.badName(name) !== null) return 0;
@@ -9697,7 +10005,7 @@ var SkillLibrary = class {
9697
10005
  * mutation ledger that points at it. The history failure is not silent — a deployment whose
9698
10006
  * history stopped growing is a degradation worth seeing once (the ledger keeps hashes either
9699
10007
  * way). */
9700
- async audit(skillName, action, before, after, summary, path) {
10008
+ async audit(skillName, action, before, after, summary, path, origin) {
9701
10009
  const at = (/* @__PURE__ */ new Date()).toISOString();
9702
10010
  let versions = null;
9703
10011
  try {
@@ -9717,6 +10025,18 @@ var SkillLibrary = class {
9717
10025
  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`);
9718
10026
  }
9719
10027
  }
10028
+ let healthNote = "";
10029
+ const danglingNote = before === null || after === null || before === after ? "" : await this.danglingNoteFor(skillName, before, after);
10030
+ if (before !== null && after !== null && before !== after) try {
10031
+ const groups = await this.countSupportDirs(skillName);
10032
+ const delta = diffFindings(lintBody(skillName, before, groups), lintBody(skillName, after, groups));
10033
+ const parts = [];
10034
+ if (delta.introduced.length > 0) parts.push("+" + delta.introduced.map((f) => f.rule).join(","));
10035
+ if (delta.worsened.length > 0) parts.push("^" + delta.worsened.map((f) => f.rule + " x" + String(f.count)).join(","));
10036
+ if (parts.length > 0) healthNote = " [health: " + parts.join(" ") + "]";
10037
+ } catch {
10038
+ healthNote = "";
10039
+ }
9720
10040
  try {
9721
10041
  await recordMutation(this.root, this.io, {
9722
10042
  skillName,
@@ -9725,8 +10045,9 @@ var SkillLibrary = class {
9725
10045
  ...after === null ? {} : { afterHash: contentHash(after) },
9726
10046
  ...versions?.beforeVersion === void 0 ? {} : { beforeVersion: versions.beforeVersion },
9727
10047
  ...versions?.afterVersion === void 0 ? {} : { afterVersion: versions.afterVersion },
9728
- summary,
9729
- at
10048
+ summary: summary + healthNote + danglingNote,
10049
+ at,
10050
+ ...origin === void 0 ? {} : { origin }
9730
10051
  });
9731
10052
  } catch {}
9732
10053
  }
@@ -10844,7 +11165,7 @@ var SkillLibrary = class {
10844
11165
  const skillMdWrite = landing.find((entry) => entry.target.split(/[\\/]/).pop() === "SKILL.md");
10845
11166
  const auditedBefore = skillMdWrite === void 0 ? md : skillMdWrite.previous;
10846
11167
  const auditedAfter = skillMdWrite === void 0 ? md : skillMdWrite.content;
10847
- await this.audit(name, plan.auditAction, auditedBefore, auditedAfter, plan.auditSummary);
11168
+ await this.audit(name, plan.auditAction, auditedBefore, auditedAfter, plan.auditSummary, void 0, plan.origin);
10848
11169
  this.notifyMutation({
10849
11170
  action: plan.eventAction,
10850
11171
  name,
@@ -13438,4 +13759,4 @@ function foldUntrustedLines(text, limit) {
13438
13759
  return limit > 0 ? marked.slice(0, limit) : marked;
13439
13760
  }
13440
13761
  //#endregion
13441
- export { AGENT_PRESET_PACKAGE, AGENT_PRESET_ROOT_HINT, AUTHORING_DESCRIPTION_BAR, AUTHORING_SPLIT_LINE_CHARS, BASELINE_ACTION, CITATION_SUPPORT_DIRS, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CONTENT_SPLIT_HINT, COST_ASCII_TOKEN_HIGH, COST_ASCII_TOKEN_LOW, COST_CJK_TOKEN_HIGH, COST_CJK_TOKEN_LOW, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEDUP_MAX_PAIR_COMPARISONS, DEDUP_SIMILARITY_THRESHOLD, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_ARCHIVE_RETENTION_POLICY, DEFAULT_CITATION_POLICY, DEFAULT_CITATION_REF_BUDGET, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_PROBE_TIMEOUT_MS, DEFAULT_REFERENCE_REWRITE_POLICY, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_RUN_RECORDS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_MODEL, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_SKILL_VERSION_KEEP, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_SUPPORT_FILE_CHAR_POLICY, DEFAULT_SUPPORT_READ_TOOL_NAMES, DEFAULT_THREAT_MAX_SCAN_CHARS, DEFAULT_USER_CHAR_LIMIT, DISPATCH_EVENT_TYPES, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, EMPTY_PATCH_SEED, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_ERRORS, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FAMILY_SESSION_TOOL_NAMES, FORBIDDEN_CONTROL_KEYS, FUZZY_MAX_PATTERN_CHARS, FUZZY_MAX_WORK, HEALTH_STAMP_RE, HISTORY_DIR, HISTORY_INDEX_VERSION, HOOK_LINE_RE, INSTANCE_KEYS, KEEP_LINE_RE, LONG_RUNS, LOW_QUALITY_THRESHOLD, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_REQUEST_BODY_BYTES, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MAX_SUPPORT_READ_PATHS, MAX_TIMER_DELAY_MS, MEMORY_GUIDANCE_SECTION_ORDER, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, NAMESPACE_MIGRATIONS, NATIVE_CALL_EVENT, NATIVE_RESULT_EVENT, PARAM_ALIASES, PARAM_EXPOSURE, PARAM_GROUP_LABELS, PARAM_NAMESPACES, PATTERN_OVERLAP, PLATFORM_CATALOG_DESCRIPTION_DEFAULT, PLATFORM_ESTIMATE_CHARS_PER_TOKEN, PLATFORM_SERVICE_PROBES, POLICY_STAGE_DEFAULTS, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, PTC_DISPATCH_EVENT, PTC_DISPATCH_START_EVENT, QUALITY_WEIGHTS, READ_REQUIRED_ACTIONS, RESTRUCTURE_TARGET_RE, RETENTION_FEEDBACK_KEEP_RATIO, RETENTION_FEEDBACK_MIN_CHARS, RUNS_SCHEMA_VERSION, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUMMARY_DUPLICATE_HINT_THRESHOLD, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, TEXT_DIFF_MAX_CHARS, TEXT_DIFF_MAX_LINES, THREAT_EXEMPTION_HINT, TOKEN_ASCII_WEIGHT, TOKEN_CJK_WEIGHT, ToolDispatchNormalizer, ToolDispatchPayloadError, UNTRUSTED_LINE_BREAK, UNTRUSTED_LINE_MARK, UPSTREAM_LIMIT_CHARS_PER_TOKEN, VERSION_SUMMARY_MAX_CHARS, absentHostServices, advanceReview, affinity, ageCopyKey, allowRowCollisions, anchorVerdict, appendEvolutionEvent, applyCuratorLifecycleFields, applyCuratorMetaFields, applyNamespaceMigration, applyReferenceRewrite, asRecord, assertDispatchPayload, assertSkillsRootAliasRetired, assessStructureHealth, atomicWriteFiles, authoringFeedback, basePresetPlugins, blobPath, bodyCost, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpSupportRead, bumpUse, bumpView, callingScope, canonicalWriteId, claimInstance, clampOnce, clampedNumber, classifyNamespaceMigration, clearReviewChannel, collectReadSkillNames, composePresetComposition, composePresetEntry, composePresetInsert, composePresetRow, compositionRowIds, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, contentRetentionFeedback, countDispatches, createGateSet, daysSinceIso, declaredReportStamp, denseBlockSequence, describeReferenceRewrite, dispatchKeyOf, dispatchStepOf, displayBodyOf, duplicateHeadings, elapsedSince, emptyRecord, entryTarget, errorText, evaluateThreat, eventsFile, evidenceKindIndex, evolutionActivityProjection, evolutionEventPayloadIssue, evolutionEvidenceProjection, evolutionHome, evolutionIoAdapter, evolutionReadsProjection, evolutionRoot, evolutionSignalsProjection, filterUnreadSkillOps, findDriftSignal, foldCuratorFields, foldToolDispatches, foldTurnEvent, foldTurnEvents, foldUntrustedLines, frontmatterBlock, frontmatterCatalogInvalid, fuzzyPatch, getRecord, historyIndexFile, historyRoot, identity, idleDays, initialTurnFold, installEvolutionProjections, instanceClaimKey, instanceClaimedWriteSites, instanceHolder, isAbsent, isBookkeepingFrame, isCommittedWarning, isDeprecatedParamId, isFileShapedPath, isLoopbackRequest, isMissingPath, isPresent, isProcessAlive, isProgramToolName, isRecord, isReviewChannelSession, isSkillReadToolName, isSkillToolName, isUnknown, isUnreadWrite, latestActivityAt, latestVersionAt, lifecycleCandidate, listDirectories, listEventArchives, liveBool, liveEnum, liveNumber, liveString, loadMutations, loadRowOverrides, loadSuppressedNames, loadUsage, loadVersionContent, loadVersions, makeSerialQueue, mapProbe, markAgentCreated, markReviewChannel, markerEntryName, memoryRoot, mergePresetRow, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nearDuplicateSummaries, neutralizePromptVariables, newRunRegistry, newSkillLibrary, nextHistoryIndex, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, normalizeVersionSummary, observeEvent, orderVersions, overlongLines, paramNamespace, paramRowId, paramSettingsId, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, parseHistoryIndex, partitionVersions, payloadText, persistedWriteSite, persistedWriteSites, pickWithPolicy, planNamespaceMigration, planReferenceRewrite, planRehoming, policyStageLimits, presetPatchText, presetRowBlock, presetRowBody, presetRowId, probeAbsent, probeDesktopScope, probeList, probeMtime, probePresent, probeReason, probeText, probeUnknown, projectionText, readDispatchSignal, readEvolutionEvents, readEvolutionTimeline, readHistoryIndex, readJsonBody, readJsonObject, readNumberParam, readParam, readingStats, recordMutation, recordVersions, redactSecrets, relatedSkillNames, releaseInstance, removePresetRow, renameWithRetry, renderCuratorReportMarkdown, renderLongRunTable, reportTime, reportsSweepLockTarget, resolveBasePresetPatch, resolveCitations, resolveExecOrigins, resolveOrigins, resolveParamId, resolveRootConfig, resolveSkillsRoot, retainEventArchives, retirementReport, reviewPrompt, rowBlockSpan, rowSiblingEnd, runsFile, saveSuppressedNames, saveUsage, scanBodyHooks, scanContentThreats, scanMemoryThreats, scanThreats, scopedProbeReport, sessionAudited, sessionEvidenceIndex, sessionLastEventTime, sessionReadNames, sessionReadSkillNames, sessionSeesFamilyTools, sessionTurnSignals, skillReadNameOf, skillRowCells, skillStateKey, skillsRoot, supportFileReadOf, suppressedFile, sweepReports, sweepReviewChannelSessions, textDiffFacts, tokenLineFor, transactIo, transactTaskGuard, trimPatternBoundaries, trimTrailingBlankLines, unhookedSupportPointers, unwrapLive, updateSuppressedNames, usageFile, usageObserved, userSetKeys, validateFrontmatter, validateRestructureTarget, valueOr, verifyPromptBundle, versionActionKind, versionRowCells, versionSummaryPrompt, versionTarget, vocabulary, writeDurableTmp, writeJson, writePathSymbols, yamlPlainScalarNeedsQuotes };
13762
+ export { AGENT_PRESET_PACKAGE, AGENT_PRESET_ROOT_HINT, AUTHORING_DESCRIPTION_BAR, AUTHORING_SPLIT_LINE_CHARS, BASELINE_ACTION, CITATION_SUPPORT_DIRS, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CONTENT_SPLIT_HINT, COST_ASCII_TOKEN_HIGH, COST_ASCII_TOKEN_LOW, COST_CJK_TOKEN_HIGH, COST_CJK_TOKEN_LOW, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEDUP_MAX_PAIR_COMPARISONS, DEDUP_SIMILARITY_THRESHOLD, DEFAULT_ADMISSION, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_ARCHIVE_RETENTION_POLICY, DEFAULT_CITATION_POLICY, DEFAULT_CITATION_REF_BUDGET, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_PROBE_TIMEOUT_MS, DEFAULT_REFERENCE_REWRITE_POLICY, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_RUN_RECORDS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_MODEL, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_SKILL_VERSION_KEEP, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_SUPPORT_FILE_CHAR_POLICY, DEFAULT_SUPPORT_READ_TOOL_NAMES, DEFAULT_THREAT_MAX_SCAN_CHARS, DEFAULT_USER_CHAR_LIMIT, DISPATCH_EVENT_TYPES, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, EMPTY_PATCH_SEED, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_ERRORS, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FAMILY_SESSION_TOOL_NAMES, FORBIDDEN_CONTROL_KEYS, FUZZY_MAX_PATTERN_CHARS, FUZZY_MAX_WORK, HEALTH_STAMP_RE, HISTORY_DIR, HISTORY_INDEX_VERSION, HOOK_LINE_RE, INSTANCE_KEYS, KEEP_LINE_RE, LONG_RUNS, LOW_QUALITY_THRESHOLD, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_REQUEST_BODY_BYTES, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MAX_SUPPORT_READ_PATHS, MAX_TIMER_DELAY_MS, MEMORY_GUIDANCE_SECTION_ORDER, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, NAMESPACE_MIGRATIONS, NATIVE_CALL_EVENT, NATIVE_RESULT_EVENT, PARAM_ALIASES, PARAM_EXPOSURE, PARAM_GROUP_LABELS, PARAM_NAMESPACES, PATTERN_OVERLAP, PLATFORM_CATALOG_DESCRIPTION_DEFAULT, PLATFORM_ESTIMATE_CHARS_PER_TOKEN, PLATFORM_SERVICE_PROBES, POLICY_STAGE_DEFAULTS, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, PTC_DISPATCH_EVENT, PTC_DISPATCH_START_EVENT, QUALITY_WEIGHTS, READ_REQUIRED_ACTIONS, RESTRUCTURE_TARGET_RE, RETENTION_FEEDBACK_KEEP_RATIO, RETENTION_FEEDBACK_MIN_CHARS, RULES, RUNS_SCHEMA_VERSION, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUMMARY_DUPLICATE_HINT_THRESHOLD, SUPPORT_DIRS, SUPPORT_SPRAWL_FILES, SUPPRESSED_FILE_VERSION, SkillLibrary, TEXT_DIFF_MAX_CHARS, TEXT_DIFF_MAX_LINES, THREAT_EXEMPTION_HINT, TOKEN_ASCII_WEIGHT, TOKEN_CJK_WEIGHT, ToolDispatchNormalizer, ToolDispatchPayloadError, UNTRUSTED_LINE_BREAK, UNTRUSTED_LINE_MARK, UPSTREAM_LIMIT_CHARS_PER_TOKEN, VERSION_SUMMARY_MAX_CHARS, absentHostServices, admissionVerdict, advanceReview, affinity, ageCopyKey, allowRowCollisions, anchorVerdict, appendEvolutionEvent, applyCuratorLifecycleFields, applyCuratorMetaFields, applyNamespaceMigration, applyReferenceRewrite, asRecord, assertDispatchPayload, assertSkillsRootAliasRetired, assessStructureHealth, atomicWriteFiles, authoringFeedback, basePresetPlugins, blobPath, bodyCost, buildCuratorRunReport, buildHealthBaseline, buildLearnPrompt, bumpPatch, bumpSupportRead, bumpUse, bumpView, callingScope, canonicalWriteId, claimInstance, clampOnce, clampedNumber, classifyNamespaceMigration, clearReviewChannel, collectReadSkillNames, composePresetComposition, composePresetEntry, composePresetInsert, composePresetRow, compositionRowIds, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, contentRetentionFeedback, countDispatches, countReferenceFiles, createGateSet, daysSinceIso, declaredReportStamp, denseBlockSequence, describeReferenceRewrite, diffFindings, dispatchKeyOf, dispatchStepOf, displayBodyOf, duplicateHeadings, elapsedSince, emptyRecord, entryTarget, errorText, evaluateThreat, eventsFile, evidenceKindIndex, evolutionActivityProjection, evolutionEventPayloadIssue, evolutionEvidenceProjection, evolutionHome, evolutionIoAdapter, evolutionReadsProjection, evolutionRoot, evolutionSignalsProjection, filterUnreadSkillOps, findDriftSignal, foldCuratorFields, foldToolDispatches, foldTurnEvent, foldTurnEvents, foldUntrustedLines, frontmatterBlock, frontmatterCatalogInvalid, fuzzyPatch, getRecord, h2Sections, historyIndexFile, historyRoot, identity, idleDays, initialTurnFold, installEvolutionProjections, instanceClaimKey, instanceClaimedWriteSites, instanceHolder, isAbsent, isBookkeepingFrame, isCommittedWarning, isDeprecatedParamId, isFileShapedPath, isLoopbackRequest, isMissingPath, isPresent, isProcessAlive, isProgramToolName, isRecord, isReviewChannelSession, isSkillReadToolName, isSkillToolName, isUnknown, isUnreadWrite, latestActivityAt, latestVersionAt, lifecycleCandidate, lintBody, lintSkill, listDirectories, listEventArchives, liveBool, liveEnum, liveNumber, liveString, loadMutations, loadRowOverrides, loadSuppressedNames, loadUsage, loadVersionContent, loadVersions, makeSerialQueue, mapProbe, markAgentCreated, markReviewChannel, markerEntryName, memoryRoot, mergePresetRow, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nearDuplicateSummaries, neutralizePromptVariables, newRunRegistry, newSkillLibrary, nextHistoryIndex, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, normalizeVersionSummary, observeEvent, orderVersions, overlongLines, paramNamespace, paramRowId, paramSettingsId, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, parseHistoryIndex, partitionVersions, payloadText, persistedWriteSite, persistedWriteSites, pickWithPolicy, planNamespaceMigration, planReferenceRewrite, planRehoming, policyStageLimits, presetPatchText, presetRowBlock, presetRowBody, presetRowId, probeAbsent, probeDesktopScope, probeList, probeMtime, probePresent, probeReason, probeText, probeUnknown, projectionText, readDispatchSignal, readEvolutionEvents, readEvolutionTimeline, readHistoryIndex, readJsonBody, readJsonObject, readNumberParam, readParam, readingStats, recordMutation, recordVersions, redactSecrets, relatedSkillNames, releaseInstance, removePresetRow, renameWithRetry, renderCuratorReportMarkdown, renderLongRunTable, reportTime, reportsSweepLockTarget, resolveBasePresetPatch, resolveCitations, resolveExecOrigins, resolveOrigins, resolveParamId, resolveRootConfig, resolveSkillsRoot, restructureCandidates, retainEventArchives, retirementReport, reviewPrompt, rowBlockSpan, rowSiblingEnd, runsFile, saveSuppressedNames, saveUsage, scanBodyHooks, scanContentThreats, scanMemoryThreats, scanThreats, scopedProbeReport, sessionAudited, sessionEvidenceIndex, sessionLastEventTime, sessionReadNames, sessionReadSkillNames, sessionSeesFamilyTools, sessionTurnSignals, skillReadNameOf, skillRowCells, skillStateKey, skillsRoot, supportFileReadOf, suppressedFile, sweepReports, sweepReviewChannelSessions, textDiffFacts, tokenLineFor, transactIo, transactTaskGuard, trimPatternBoundaries, trimTrailingBlankLines, unhookedSupportPointers, unwrapLive, updateSuppressedNames, usageFile, usageObserved, userSetKeys, validateFrontmatter, validateRestructureTarget, valueOr, verifyPromptBundle, versionActionKind, versionRowCells, versionSummaryPrompt, versionTarget, vocabulary, writeDurableTmp, writeJson, writePathSymbols, yamlPlainScalarNeedsQuotes };
@@ -95,6 +95,13 @@ export declare const DEFAULT_CURATOR_INTERVAL_HOURS = 168;
95
95
  export declare const DEFAULT_MIN_IDLE_HOURS = 2;
96
96
  export declare const DEFAULT_STALE_AFTER_DAYS = 30;
97
97
  export declare const DEFAULT_ARCHIVE_AFTER_DAYS = 90;
98
+ /**
99
+ * Support-file count above which `references-sprawl` warns. **NOT CALIBRATED YET** — 60 is a
100
+ * placeholder: the platform corpus is empty (no platform skills available here), so the admission
101
+ * band cannot be checked. Until `gen-health-baseline` reports both corpora, the rule is
102
+ * REPORT-ONLY: its count must not be shown as a number on any surface (plan G4 ③).
103
+ */
104
+ export declare const SUPPORT_SPRAWL_FILES = 60;
98
105
  export declare const DEFAULT_MEMORY_CHAR_LIMIT = 2200;
99
106
  export declare const DEFAULT_USER_CHAR_LIMIT = 1375;
100
107
  /** Consolidation-failure backoff cap, shared by MemoryStore and memory-files' Config default. */
@@ -20,4 +20,37 @@ export interface ReadingStats {
20
20
  * @returns the counts, so the panel can say what it is not showing.
21
21
  */
22
22
  export declare function readingStats(full: string, shown: string): ReadingStats;
23
+ /**
24
+ * One H2 section of a body: its title, its line span and how many times that title occurs.
25
+ * @property count - occurrences of `title` in the SAME body; a candidate list may only offer 1.
26
+ */
27
+ export interface H2Section {
28
+ title: string;
29
+ start: number;
30
+ end: number;
31
+ count: number;
32
+ }
33
+ /**
34
+ * The H2 sections of a body, in document order — the ONE scan (G3).
35
+ *
36
+ * Both the restructure candidate list and the store anchor validation call this function, so a
37
+ * candidate can only name a heading that exists EXACTLY ONCE and has content: the two rejections a
38
+ * hand-written anchor used to hit ("no such heading", "ambiguous anchor") stop being reachable.
39
+ * H2 is the section boundary (a section spans to the next H2 or EOF), matching the store.
40
+ * @param body - the markdown after the frontmatter block.
41
+ * @returns one row per H2 heading, with `count` filled in a second pass.
42
+ */
43
+ export declare function h2Sections(body: string): H2Section[];
44
+ /** One move the panel can hand to `restructure` without the store refusing it. */
45
+ export interface RestructureCandidate {
46
+ heading: string;
47
+ toFile: string;
48
+ }
49
+ /**
50
+ * The sections a restructure proposal may name: heading unique, section non-empty. Derived from
51
+ * `h2Sections`, i.e. from the SAME scan the store validates against.
52
+ * @param body - the markdown after the frontmatter block.
53
+ * @returns move-ready pairs in document order; empty when nothing qualifies.
54
+ */
55
+ export declare function restructureCandidates(body: string): RestructureCandidate[];
23
56
  //# sourceMappingURL=document-body.d.ts.map
@@ -0,0 +1,37 @@
1
+ import { type LintFacts } from './skill-lint.ts';
2
+ export type Corpus = 'user' | 'platform';
3
+ export interface CorpusSkill {
4
+ name: string;
5
+ corpus: Corpus;
6
+ facts: LintFacts;
7
+ }
8
+ export interface RuleAdmission {
9
+ platformHits: number;
10
+ userHits: number;
11
+ userHitRate: number;
12
+ }
13
+ export interface HealthBaseline {
14
+ /** Frozen unit and scope: a rate is only comparable when both are named (contract §4). */
15
+ unit: 'chars';
16
+ scope: 'references markdown file count';
17
+ corpora: {
18
+ user: number;
19
+ platform: number;
20
+ };
21
+ values: Record<string, RuleAdmission>;
22
+ }
23
+ export interface AdmissionThresholds {
24
+ /** A user-library hit rate below this is too rare to be worth a rule. */
25
+ minUserHitRate: number;
26
+ /** …and above this it is furniture (every skill trips it, so it carries no signal). */
27
+ maxUserHitRate: number;
28
+ }
29
+ export declare const DEFAULT_ADMISSION: AdmissionThresholds;
30
+ export declare function buildHealthBaseline(skills: readonly CorpusSkill[]): HealthBaseline;
31
+ /** The admission verdict for ONE rule id (contract §4): platform corpus must not trip it, and the
32
+ * user-library hit rate must land inside the band. Every failure names the number it failed on. */
33
+ export declare function admissionVerdict(baseline: HealthBaseline, ruleId: string, thresholds?: AdmissionThresholds): {
34
+ admitted: boolean;
35
+ failures: string[];
36
+ };
37
+ //# sourceMappingURL=health-baseline.d.ts.map
@@ -34,6 +34,8 @@
34
34
  export * from './citations.ts';
35
35
  export * from './reference-rewrite.ts';
36
36
  export * from './cost.ts';
37
+ export * from './skill-lint.ts';
38
+ export * from './health-baseline.ts';
37
39
  export * from './curator.ts';
38
40
  export * from './evolution-events.ts';
39
41
  export * from './fuzzy-match.ts';
@@ -17,6 +17,9 @@ export interface MutationRecord {
17
17
  afterVersion?: number;
18
18
  summary: string;
19
19
  at: string;
20
+ /** G5: who caused this mutation — a write face ('foreground', 'background_review', …) or a
21
+ * migration step. Optional: records written before the ledger carried it stay readable. */
22
+ origin?: string;
20
23
  }
21
24
  export declare const DEFAULT_MUTATION_CAP = 500;
22
25
  /** Version of the `.mutations.json` file shape; writers always emit the current one. */
@@ -9,6 +9,7 @@
9
9
  * stay in the review/curator judgment layer. Never become a 7th factor of
10
10
  * `computeQualityScores` (different dimension, different consumers).
11
11
  */
12
+ import { type LintFinding } from './skill-lint.ts';
12
13
  export interface SkillHealthThresholds {
13
14
  /** Soft body limit: a body of `softBodyChars` or MORE -> 'warn'; >= 2x ->
14
15
  * 'needs-restructure'. V6-34 (0.3.37): the doc comment used to claim
@@ -60,6 +61,8 @@ interface SkillHealthSnapshot {
60
61
  bodyText?: string | undefined;
61
62
  /** Number of non-empty support groups (references/ templates/ scripts/). */
62
63
  supportGroups: number;
64
+ /** Support FILES under references/ (contract §4), when the caller counted them; null = not measured. */
65
+ supportFiles?: number | undefined;
63
66
  /** Usage-side patch count, when the caller has it (A2 churn dimension). */
64
67
  patchCount?: number | undefined;
65
68
  /** Usage-side view count, when the caller has it (A2 churn dimension). */
@@ -70,7 +73,11 @@ interface SkillHealthDim {
70
73
  /** The same body on the token scale (limit basis) — the judged quantity. */
71
74
  bodyTokens: number;
72
75
  stampDensityPerKb: number | null;
76
+ /** Raw stamp hits — the number the stamp rule reports as `count`. */
77
+ stampHits: number | null;
73
78
  supportGroups: number;
79
+ /** Support FILES under references/; null = the caller could not count them (never 0-as-unknown). */
80
+ supportFiles: number | null;
74
81
  /** Usage churn facts; null when the caller supplied no counts. */
75
82
  churnPatches: number | null;
76
83
  churnReads: number | null;
@@ -78,8 +85,23 @@ interface SkillHealthDim {
78
85
  export interface SkillHealthAssessment {
79
86
  verdict: SkillHealthVerdict;
80
87
  dims: SkillHealthDim;
81
- reasons: string[];
88
+ /** The same judgement as `reasons`, in diffable form: (rule id, severity, count). G1. */
89
+ findings: LintFinding[];
82
90
  }
91
+ /**
92
+ * The findings for ONE body, computed by the engine that owns the judgement (G4).
93
+ *
94
+ * Deliberately a thin call into `assessStructureHealth`: a later consumer (the write receipt) needs
95
+ * findings for a body it is about to store, and re-deriving the facts anywhere else would be a
96
+ * SECOND judgement — the token-scale line, the /1024 stamp density and the soft/2 support threshold
97
+ * all live in one place and must stay there.
98
+ * @param skillName - the skill the body belongs to (used in messages).
99
+ * @param content - the WHOLE SKILL.md text, frontmatter included (the engine's own convention).
100
+ * @param supportGroups - non-empty support dirs; pass the real count, never a guess.
101
+ * @param thresholds - the configured thresholds; defaults to the shipped ones.
102
+ * @returns the findings, in rule order.
103
+ */
104
+ export declare function lintBody(skillName: string, content: string, supportGroups: number, thresholds?: SkillHealthThresholds): LintFinding[];
83
105
  export declare function assessStructureHealth(snapshot: SkillHealthSnapshot, thresholds?: SkillHealthThresholds): SkillHealthAssessment;
84
106
  export {};
85
107
  //# sourceMappingURL=skill-health.d.ts.map
@@ -0,0 +1,59 @@
1
+ export type LintSeverity = 'warn' | 'error';
2
+ export interface LintFinding {
3
+ severity: LintSeverity;
4
+ /** Stable rule id; findings are diffed by (rule, count), never by message text. */
5
+ rule: string;
6
+ message: string;
7
+ /** How many times the rule bit — a name-only comparison hid one site becoming nine. */
8
+ count: number;
9
+ }
10
+ /** Size facts the deciding engine already measured. `null` means NOT MEASURED, never fine: a rule
11
+ * that needs a missing fact stays silent rather than inventing a pass or a failure. */
12
+ export interface SkillDims {
13
+ bodyChars: number | null;
14
+ bodyTokens: number | null;
15
+ stampDensityPerKb: number | null;
16
+ /** Support GROUPS (non-empty references/templates/scripts/assets dirs) — the old dimension. */
17
+ supportGroups: number;
18
+ /** Support FILES under references/ (contract §4), or null when the caller cannot count them. */
19
+ supportFiles: number | null;
20
+ /** Raw stamp hits behind `stampDensityPerKb` — the COUNT this rule reports (never a fixed 1). */
21
+ stampHits: number | null;
22
+ churnPatches: number | null;
23
+ churnReads: number | null;
24
+ }
25
+ /** The judgement itself, computed by `skill-health.ts` on its own scales; this module never
26
+ * recomputes it from raw numbers (that is exactly how a second set of thresholds appears). */
27
+ export interface LintFacts {
28
+ skillName: string;
29
+ dims: SkillDims;
30
+ softLineChars: number;
31
+ overSoftLine: boolean;
32
+ overTwiceSoftLine: boolean;
33
+ stampLimitPerKb: number;
34
+ /** The engine's own churn threshold — the rule must use the SAME number, not a literal. */
35
+ churnMinPatches: number;
36
+ /** True when the caller could read the body at all. */
37
+ readable: boolean;
38
+ }
39
+ interface LintRule {
40
+ readonly id: string;
41
+ readonly severity: LintSeverity;
42
+ run(facts: LintFacts): LintFinding | null;
43
+ }
44
+ /** One rule yields AT MOST one finding (contract §3). `body-oversize-2x` is the only rule whose
45
+ * severity can escalate a verdict to needs-restructure. */
46
+ export declare const RULES: readonly LintRule[];
47
+ export declare function lintSkill(facts: LintFacts): LintFinding[];
48
+ /** What a write introduced, what got worse, what got fixed. A rule whose count grew (one site to
49
+ * nine) counts as worsened — comparing by rule name alone reported nothing. */
50
+ export declare function diffFindings(before: readonly LintFinding[], after: readonly LintFinding[]): {
51
+ introduced: LintFinding[];
52
+ worsened: LintFinding[];
53
+ resolved: string[];
54
+ };
55
+ /** Support files = markdown files under references/ (contract §4: unit frozen, underscore-prefixed
56
+ * excluded). Paths are supplied by the caller — this module never touches the filesystem. */
57
+ export declare function countReferenceFiles(paths: readonly string[]): number;
58
+ export {};
59
+ //# sourceMappingURL=skill-lint.d.ts.map
@@ -385,6 +385,12 @@ export declare class SkillLibrary {
385
385
  /** Whether the skill carries the pinned marker (the marker is the factual source; usage.pinned mirrors it). */
386
386
  isPinned(rawName: string): Promise<boolean>;
387
387
  /** Count non-empty support subdirectories (richness input for quality scoring). */
388
+ /** G4 ⑤-d: the dangling targets THIS write introduced, as a receipt note (empty when none).
389
+ *
390
+ * Uses the SAME resolver as the drift signal, so a receipt and a drift row can never disagree;
391
+ * an absent or unknown listing reports nothing rather than inventing a dead link.
392
+ */
393
+ private danglingNoteFor;
388
394
  countSupportDirs(rawName: string): Promise<number>;
389
395
  /**
390
396
  * Support-file paths (`references/x.md`) under SUPPORT_DIRS for one skill,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-core",
3
3
  "description": "Shared stores, prompts, signals and lifecycle logic for the dsh-evolution plugin family (community build)",
4
- "version": "0.24.0",
4
+ "version": "0.25.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },