@lmzhen/dsh-evolution-core 0.24.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +347 -26
- package/lib/types/constants.d.ts +7 -0
- package/lib/types/document-body.d.ts +33 -0
- package/lib/types/health-baseline.d.ts +37 -0
- package/lib/types/index.d.ts +2 -0
- package/lib/types/mutations.d.ts +3 -0
- package/lib/types/skill-health.d.ts +23 -1
- package/lib/types/skill-lint.d.ts +59 -0
- package/lib/types/skill-store.d.ts +6 -0
- package/package.json +1 -1
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
|
-
|
|
6697
|
-
|
|
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:
|
|
6947
|
+
verdict: findings.some((f) => f.rule === "body-oversize-2x") ? "needs-restructure" : findings.length > 0 ? "warn" : "healthy",
|
|
6707
6948
|
dims,
|
|
6708
|
-
|
|
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
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
if (
|
|
9224
|
-
|
|
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 };
|
package/lib/types/constants.d.ts
CHANGED
|
@@ -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
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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';
|
package/lib/types/mutations.d.ts
CHANGED
|
@@ -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:
|
|
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