@shgroup/dsh-serenity-hooks 1.52.3 → 1.53.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/dsh.plugin.json +1 -1
- package/lib/index.js +240 -4
- package/lib/seams/system-prompt.d.ts +4 -1
- package/lib/skills/resource-index.d.ts +64 -0
- package/lib/skills-discovery.d.ts +2 -0
- package/package.json +1 -1
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.53.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——13 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/ontology/handyman/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1(deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
package/lib/index.js
CHANGED
|
@@ -2188,6 +2188,7 @@ function findEntrySkills(root) {
|
|
|
2188
2188
|
out.push({
|
|
2189
2189
|
name,
|
|
2190
2190
|
content: readFileSync(path, "utf-8"),
|
|
2191
|
+
dir: dirname(path),
|
|
2191
2192
|
source
|
|
2192
2193
|
});
|
|
2193
2194
|
};
|
|
@@ -2215,6 +2216,230 @@ function truncateContent(content, maxChars) {
|
|
|
2215
2216
|
return content.slice(0, maxChars) + `\n... (truncated, original length ${content.length} chars)`;
|
|
2216
2217
|
}
|
|
2217
2218
|
//#endregion
|
|
2219
|
+
//#region src/skills/resource-index.ts
|
|
2220
|
+
/**
|
|
2221
|
+
* resource-index.ts — **两层披露树的第二层**:skill 参考件索引(纯逻辑,零 DSH 依赖)
|
|
2222
|
+
*
|
|
2223
|
+
* 设计真相源:`docs/serenity-skill-design.md` §4.2(六条硬性规则)+ §5.1(目录布局)。
|
|
2224
|
+
* 一句话:**把 skill 的参考件"有什么"告诉模型,但不把"是什么"塞进去** ——
|
|
2225
|
+
* 正文留给第三层(模型按需 `read`)。
|
|
2226
|
+
*
|
|
2227
|
+
* 为什么由我们生成(R↓,读宿主取证):宿主 `@deepseek-ai/dsh-skill` 的 `renderSkillContent()`
|
|
2228
|
+
* (`lib/index.js` L57-81)**已经**产出 `<skill_resources>` 块,并逐字写着
|
|
2229
|
+
* "Load referenced resources only as needed." ⇒ **两层模型是宿主自己给的**;而
|
|
2230
|
+
* `SkillSummary` / `SkillDefinition` **没有任何列举子文件的位**(只有 `resourceBase` 一个基址)
|
|
2231
|
+
* ⇒ 宿主刻意留给 provider 的正是**这一半:枚举**。
|
|
2232
|
+
*
|
|
2233
|
+
* 六条硬性规则(逐条对应设计 §4.2,改这里先改那里):
|
|
2234
|
+
* ① **只枚举,不注入正文** —— 索引里**绝不**出现任何参考文件的正文;
|
|
2235
|
+
* ② **确定性排序** = 相对路径的 **UTF-8 字节序**(🔴 **不用 mtime**:mtime 随检出漂移,
|
|
2236
|
+
* 会让每请求的注入文本抖动;对照 `src/cro.ts:101` 那份**落盘快照**用 {name,bytes,mtime} ——
|
|
2237
|
+
* **同一件事两处的取舍相反,那个差异是判据不是巧合**);
|
|
2238
|
+
* ③ **有界 + 响亮** —— 条数/字符各有上限,超出**必须显式写出还剩多少项**,**绝不静默截断**;
|
|
2239
|
+
* ④ **摘要可复现** —— 取该文件首个 `#` 标题(剥 markdown 记号);取不到 ⇒ 留空,**不编**;
|
|
2240
|
+
* ⑤ **本体文件特殊标注** —— `skill.ttl` 存在 ⇒ 该行标 🧬 + 顶部加一句"已建模";
|
|
2241
|
+
* ⑥ **范围 = `references/` 一级条目 + `skill.ttl`** —— 子目录**给名字 + 计数、不展开、不读正文**
|
|
2242
|
+
* (🆕 2026-10-11 由**真实输入**逼出的修订:原写"不进子目录",实测 `references/` 下确实有内容目录,
|
|
2243
|
+
* 只报"有 N 个子目录"不给名字 = **不可行动**;折中仍在裁决 Q2「只披露名字、不注入正文」的意图内)。
|
|
2244
|
+
*
|
|
2245
|
+
* 🔴 本文件**只认 `<skillDir>/skill.ttl`**(owner 裁决 Q6:静默、只认固定名)。
|
|
2246
|
+
* `references/*-domain.ttl`(旧名)一律当**普通参考件**:不特殊对待、不告警 —— 那 14 份由 CCC 自行改名。
|
|
2247
|
+
* 🔴 「未建模」不是异常(裁决 Q6 / 判据 J9):无 `skill.ttl` 的**普通 skill** 照常产出索引,
|
|
2248
|
+
* **零告警、零误差** —— 增强必须对普通 skill 完全向后兼容。
|
|
2249
|
+
*/
|
|
2250
|
+
/** 第二层占位口:本 skill 的本体约束文件(固定名,设计 §5.1) */
|
|
2251
|
+
const SKILL_ONTOLOGY_FILE = "skill.ttl";
|
|
2252
|
+
/** 参考件根(一级;不进子目录) */
|
|
2253
|
+
const REFERENCE_DIR = "references";
|
|
2254
|
+
/** 摘要长度上限(防单行超长标题把表格撑坏) */
|
|
2255
|
+
const EXCERPT_MAX_CHARS = 60;
|
|
2256
|
+
/** 读文件头部字节数上限:摘标题只值读这么些(不为一行标题读整个大文件) */
|
|
2257
|
+
const HEAD_BYTES = 4096;
|
|
2258
|
+
/** 目录计数上限:防病态大树把一次注入拖慢(超出 ⇒ 报 `≥N`,仍然响亮) */
|
|
2259
|
+
const DIR_SCAN_MAX_FILES = 1e3;
|
|
2260
|
+
/**
|
|
2261
|
+
* 递归统计目录下的文件数与总字节(**只统计,不读内容**)。
|
|
2262
|
+
* 为什么递归:设计原写"不进子目录",而**真实输入**显示 `references/` 下确有内容目录
|
|
2263
|
+
* (`home-rhetoric/references/corpus-subject-own`/`family-profiles/references/baby-handlebook`/
|
|
2264
|
+
* `home-serenity/references/diagram-lab`)⇒ 只报"有 N 个子目录"而**不给名字**是不可行动的。
|
|
2265
|
+
* 折中:**给名字 + 计数,仍不展开、仍不读正文**(= 裁决 Q2「只披露名字、不注入正文」的意图内)。
|
|
2266
|
+
*/
|
|
2267
|
+
function scanDirStats(dir) {
|
|
2268
|
+
let files = 0;
|
|
2269
|
+
let bytes = 0;
|
|
2270
|
+
let capped = false;
|
|
2271
|
+
const walk = (d) => {
|
|
2272
|
+
if (capped || files >= DIR_SCAN_MAX_FILES) {
|
|
2273
|
+
capped = true;
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
let names = [];
|
|
2277
|
+
try {
|
|
2278
|
+
names = readdirSync(d);
|
|
2279
|
+
} catch {
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
for (const name of names) {
|
|
2283
|
+
if (files >= DIR_SCAN_MAX_FILES) {
|
|
2284
|
+
capped = true;
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
const abs = join(d, name);
|
|
2288
|
+
let isDir = false;
|
|
2289
|
+
let size = 0;
|
|
2290
|
+
try {
|
|
2291
|
+
const st = statSync(abs);
|
|
2292
|
+
isDir = st.isDirectory();
|
|
2293
|
+
size = st.size;
|
|
2294
|
+
} catch {
|
|
2295
|
+
continue;
|
|
2296
|
+
}
|
|
2297
|
+
if (isDir) walk(abs);
|
|
2298
|
+
else {
|
|
2299
|
+
files++;
|
|
2300
|
+
bytes += size;
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
};
|
|
2304
|
+
walk(dir);
|
|
2305
|
+
return {
|
|
2306
|
+
files,
|
|
2307
|
+
bytes,
|
|
2308
|
+
capped
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
/** 按 UTF-8 **字节序**比较两个相对路径(规则②;不是 locale 比较、不是 mtime) */
|
|
2312
|
+
function byByteOrder(a, b) {
|
|
2313
|
+
return Buffer.compare(a.key, b.key);
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
* 千分位分组(规则②的延伸:**不依赖 locale**)。
|
|
2317
|
+
* 为什么不用 `toLocaleString()`:它随宿主 locale 变 ⇒ 同一目录在不同机器上产出**不同的注入文本**
|
|
2318
|
+
* (本仓已有一条「locale 稳健性」的挂账,不再添一处)。
|
|
2319
|
+
*/
|
|
2320
|
+
function groupThousands(n) {
|
|
2321
|
+
return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
2322
|
+
}
|
|
2323
|
+
/**
|
|
2324
|
+
* 取 markdown 首个 `#` 标题作摘要(规则④)。
|
|
2325
|
+
*
|
|
2326
|
+
* 细节(都由"可复现"推导):
|
|
2327
|
+
* · **跳过 YAML frontmatter**(`---` 开头到下一 `---`)—— 否则题名行会被当成正文标题;
|
|
2328
|
+
* · 只读文件**头部** `HEAD_BYTES`(一行标题不值得读整个大文件);
|
|
2329
|
+
* · 剥掉行内记号(`*` `` ` `` `_` `#`)与首尾空白,压平内部连续空白;
|
|
2330
|
+
* · 取不到 ⇒ `''`(**留空,不编** —— 编一个摘要等于让索引说假话);
|
|
2331
|
+
* · 超长截断时**留省略号**(与规则③同一精神:截断要看得见)。
|
|
2332
|
+
*/
|
|
2333
|
+
function firstHeading(file) {
|
|
2334
|
+
let head;
|
|
2335
|
+
try {
|
|
2336
|
+
head = readFileSync(file, "utf-8").slice(0, HEAD_BYTES);
|
|
2337
|
+
} catch {
|
|
2338
|
+
return "";
|
|
2339
|
+
}
|
|
2340
|
+
const lines = head.split(/\r?\n/);
|
|
2341
|
+
let start = 0;
|
|
2342
|
+
if (lines[0]?.trim() === "---") {
|
|
2343
|
+
for (let i = 1; i < lines.length; i++) if (lines[i]?.trim() === "---") {
|
|
2344
|
+
start = i + 1;
|
|
2345
|
+
break;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
for (let i = start; i < lines.length; i++) {
|
|
2349
|
+
const m = /^#{1,6}\s+(.*)$/.exec(lines[i] ?? "");
|
|
2350
|
+
if (!m) continue;
|
|
2351
|
+
const clean = (m[1] ?? "").replace(/[*`_]/g, "").replace(/\s+/g, " ").trim();
|
|
2352
|
+
if (clean === "") continue;
|
|
2353
|
+
return clean.length > EXCERPT_MAX_CHARS ? `${clean.slice(0, 59)}…` : clean;
|
|
2354
|
+
}
|
|
2355
|
+
return "";
|
|
2356
|
+
}
|
|
2357
|
+
/** 收集候选(`references/` 一级条目 + 固定名本体),并按字节序排好 */
|
|
2358
|
+
function collectItems(skillDir) {
|
|
2359
|
+
const items = [];
|
|
2360
|
+
const add = (rel, abs, ontology) => {
|
|
2361
|
+
let bytes;
|
|
2362
|
+
try {
|
|
2363
|
+
bytes = statSync(abs).size;
|
|
2364
|
+
} catch {
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
items.push({
|
|
2368
|
+
rel,
|
|
2369
|
+
kind: "file",
|
|
2370
|
+
bytes,
|
|
2371
|
+
excerpt: rel.endsWith(".md") ? firstHeading(abs) : "",
|
|
2372
|
+
ontology,
|
|
2373
|
+
key: Buffer.from(rel, "utf-8")
|
|
2374
|
+
});
|
|
2375
|
+
};
|
|
2376
|
+
const refDir = join(skillDir, REFERENCE_DIR);
|
|
2377
|
+
if (existsSync(refDir)) {
|
|
2378
|
+
let names = [];
|
|
2379
|
+
try {
|
|
2380
|
+
names = readdirSync(refDir).sort();
|
|
2381
|
+
} catch {
|
|
2382
|
+
names = [];
|
|
2383
|
+
}
|
|
2384
|
+
for (const name of names) {
|
|
2385
|
+
const abs = join(refDir, name);
|
|
2386
|
+
let isDir = false;
|
|
2387
|
+
try {
|
|
2388
|
+
isDir = statSync(abs).isDirectory();
|
|
2389
|
+
} catch {
|
|
2390
|
+
continue;
|
|
2391
|
+
}
|
|
2392
|
+
const rel = `${REFERENCE_DIR}/${name}`;
|
|
2393
|
+
if (isDir) {
|
|
2394
|
+
const { files, bytes, capped } = scanDirStats(abs);
|
|
2395
|
+
items.push({
|
|
2396
|
+
rel,
|
|
2397
|
+
kind: "dir",
|
|
2398
|
+
bytes,
|
|
2399
|
+
excerpt: `子目录 · ${capped ? `≥${groupThousands(files)}` : groupThousands(files)} 个文件`,
|
|
2400
|
+
ontology: false,
|
|
2401
|
+
key: Buffer.from(rel, "utf-8")
|
|
2402
|
+
});
|
|
2403
|
+
continue;
|
|
2404
|
+
}
|
|
2405
|
+
add(rel, abs, false);
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
const ontoAbs = join(skillDir, SKILL_ONTOLOGY_FILE);
|
|
2409
|
+
if (existsSync(ontoAbs)) add(SKILL_ONTOLOGY_FILE, ontoAbs, true);
|
|
2410
|
+
items.sort(byByteOrder);
|
|
2411
|
+
return items;
|
|
2412
|
+
}
|
|
2413
|
+
/**
|
|
2414
|
+
* 生成第二层索引正文。
|
|
2415
|
+
*
|
|
2416
|
+
* @param skillDir skill 目录绝对路径
|
|
2417
|
+
* @param options 上限覆盖(缺省用本模块常量)
|
|
2418
|
+
* @returns 索引正文;**无可列项 ⇒ `''`**(规则⑥的一般化:`references/` 与 `skill.ttl` 都没有
|
|
2419
|
+
* ⇒ 整块不产出 —— 空标题是噪声)。调用方按"空串不产出该块"处理。
|
|
2420
|
+
*/
|
|
2421
|
+
function buildResourceIndex(skillDir, options = {}) {
|
|
2422
|
+
const maxItems = options.maxItems ?? 40;
|
|
2423
|
+
const maxChars = options.maxChars ?? 4096;
|
|
2424
|
+
const items = collectItems(skillDir);
|
|
2425
|
+
if (items.length === 0) return "";
|
|
2426
|
+
const modeled = items.some((i) => i.ontology);
|
|
2427
|
+
const shown = items.slice(0, maxItems);
|
|
2428
|
+
const hidden = items.length - shown.length;
|
|
2429
|
+
const lines = ["### 本 skill 的参考件(第二层 · 按需读取)", "> 这里只有**清单**,没有正文:需要时用 `read` 打开下列路径(相对本 skill 目录)。"];
|
|
2430
|
+
if (modeled) lines.push("> 🧬 **本 skill 已建模**:文字内容以 `skill.ttl` 为总约束。");
|
|
2431
|
+
lines.push("", "| 文件 | 摘要 | 字节 |", "|---|---|---|");
|
|
2432
|
+
for (const item of shown) {
|
|
2433
|
+
const label = item.kind === "dir" ? `${item.rel}/` : item.rel;
|
|
2434
|
+
const excerpt = item.ontology ? "🧬 本 skill 的本体约束(已建模)" : item.excerpt;
|
|
2435
|
+
lines.push(`| ${label} | ${excerpt} | ${groupThousands(item.bytes)} |`);
|
|
2436
|
+
}
|
|
2437
|
+
if (hidden > 0) lines.push("", `…**还有 ${hidden} 项未列出**(本层单次上限 ${maxItems} 项)。`);
|
|
2438
|
+
const body = lines.join("\n");
|
|
2439
|
+
if (body.length <= maxChars) return body;
|
|
2440
|
+
return `${body.slice(0, maxChars)}\n…(第二层索引已按 ${groupThousands(maxChars)} 字符上限截断,原文 ${groupThousands(body.length)} 字符)`;
|
|
2441
|
+
}
|
|
2442
|
+
//#endregion
|
|
2218
2443
|
//#region src/trajectory-skills.ts
|
|
2219
2444
|
/**
|
|
2220
2445
|
* trajectory-skills.ts — 轨迹 skill 声明解析 + 注入内容装配(纯逻辑,零 DSH 依赖)
|
|
@@ -2404,7 +2629,9 @@ function buildTrajectorySkillsSection(root, mdPath, maxChars = TRAJECTORY_SKILLS
|
|
|
2404
2629
|
continue;
|
|
2405
2630
|
}
|
|
2406
2631
|
try {
|
|
2407
|
-
|
|
2632
|
+
const index = buildResourceIndex(dirname(file));
|
|
2633
|
+
const body = readFileSync(file, "utf-8");
|
|
2634
|
+
blocks.push(`=== skill: ${name} ===\n${index ? `${body}\n\n${index}` : body}`);
|
|
2408
2635
|
} catch {
|
|
2409
2636
|
blocks.push(missingNotice(name));
|
|
2410
2637
|
}
|
|
@@ -3412,11 +3639,19 @@ function localstoreBlock(root) {
|
|
|
3412
3639
|
""
|
|
3413
3640
|
]).join("\n");
|
|
3414
3641
|
}
|
|
3415
|
-
/** 4) SKILL.md 全文:该 CCC 顶层入口 skill 原文(对齐 osp:原文直推,无包裹头;仅过滤治理内容)
|
|
3642
|
+
/** 4) SKILL.md 全文:该 CCC 顶层入口 skill 原文(对齐 osp:原文直推,无包裹头;仅过滤治理内容)
|
|
3643
|
+
* + 🆕 **第二层披露树**(serenity-skill P1,设计 §4.2):每个入口 skill 后附**参考件清单**
|
|
3644
|
+
* (只有文件名与一行标题,**不含正文** —— 正文留给模型按需 `read`)。
|
|
3645
|
+
* 索引为空(该 skill 无 `references/` 也无 `skill.ttl`)⇒ **不产出该块**(空标题是噪声)。 */
|
|
3416
3646
|
function entrySkillSectionText(root) {
|
|
3417
3647
|
const skills = findEntrySkills(root);
|
|
3418
3648
|
if (skills.length === 0) return "";
|
|
3419
|
-
return skills.map((s) =>
|
|
3649
|
+
return skills.map((s) => {
|
|
3650
|
+
const body = sanitizeSkillContent(s.content);
|
|
3651
|
+
if (body.length === 0) return "";
|
|
3652
|
+
const index = buildResourceIndex(s.dir);
|
|
3653
|
+
return index ? `${body}\n\n${index}` : body;
|
|
3654
|
+
}).filter((c) => c.length > 0).join("\n\n");
|
|
3420
3655
|
}
|
|
3421
3656
|
/** 活跃会话解析:按 dsh 会话 scope 读标记(隔离;不回退旧全局标记) */
|
|
3422
3657
|
function resolveActiveSessionInfo(root, scope) {
|
|
@@ -13990,6 +14225,7 @@ function registerOpencodeSkills(ctx) {
|
|
|
13990
14225
|
const dir = candidate.locator;
|
|
13991
14226
|
const skill = loadOpencodeSkill(dir);
|
|
13992
14227
|
if (!skill) return void 0;
|
|
14228
|
+
const index = buildResourceIndex(dir);
|
|
13993
14229
|
return {
|
|
13994
14230
|
name: skill.meta.name,
|
|
13995
14231
|
description: skill.meta.description || candidate.description,
|
|
@@ -13998,7 +14234,7 @@ function registerOpencodeSkills(ctx) {
|
|
|
13998
14234
|
source: candidate.source,
|
|
13999
14235
|
provider: OPENCODE_PROVIDER,
|
|
14000
14236
|
resourceBase: candidate.resourceBase,
|
|
14001
|
-
content: skill.content,
|
|
14237
|
+
content: index ? `${skill.content}\n\n${index}` : skill.content,
|
|
14002
14238
|
path: skill.path
|
|
14003
14239
|
};
|
|
14004
14240
|
}
|
|
@@ -84,7 +84,10 @@ export declare function safeModeBlock(root: string): string;
|
|
|
84
84
|
* 只限于该文件内(不外泄到其他文件/对话/日志)。
|
|
85
85
|
*/
|
|
86
86
|
export declare function localstoreBlock(root: string): string;
|
|
87
|
-
/** 4) SKILL.md 全文:该 CCC 顶层入口 skill 原文(对齐 osp:原文直推,无包裹头;仅过滤治理内容)
|
|
87
|
+
/** 4) SKILL.md 全文:该 CCC 顶层入口 skill 原文(对齐 osp:原文直推,无包裹头;仅过滤治理内容)
|
|
88
|
+
* + 🆕 **第二层披露树**(serenity-skill P1,设计 §4.2):每个入口 skill 后附**参考件清单**
|
|
89
|
+
* (只有文件名与一行标题,**不含正文** —— 正文留给模型按需 `read`)。
|
|
90
|
+
* 索引为空(该 skill 无 `references/` 也无 `skill.ttl`)⇒ **不产出该块**(空标题是噪声)。 */
|
|
88
91
|
export declare function entrySkillSectionText(root: string): string;
|
|
89
92
|
/**
|
|
90
93
|
* 5) Session 块:对齐 osp(活跃会话 + todowrite 首位约定);scope = dsh 会话维度。
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resource-index.ts — **两层披露树的第二层**:skill 参考件索引(纯逻辑,零 DSH 依赖)
|
|
3
|
+
*
|
|
4
|
+
* 设计真相源:`docs/serenity-skill-design.md` §4.2(六条硬性规则)+ §5.1(目录布局)。
|
|
5
|
+
* 一句话:**把 skill 的参考件"有什么"告诉模型,但不把"是什么"塞进去** ——
|
|
6
|
+
* 正文留给第三层(模型按需 `read`)。
|
|
7
|
+
*
|
|
8
|
+
* 为什么由我们生成(R↓,读宿主取证):宿主 `@deepseek-ai/dsh-skill` 的 `renderSkillContent()`
|
|
9
|
+
* (`lib/index.js` L57-81)**已经**产出 `<skill_resources>` 块,并逐字写着
|
|
10
|
+
* "Load referenced resources only as needed." ⇒ **两层模型是宿主自己给的**;而
|
|
11
|
+
* `SkillSummary` / `SkillDefinition` **没有任何列举子文件的位**(只有 `resourceBase` 一个基址)
|
|
12
|
+
* ⇒ 宿主刻意留给 provider 的正是**这一半:枚举**。
|
|
13
|
+
*
|
|
14
|
+
* 六条硬性规则(逐条对应设计 §4.2,改这里先改那里):
|
|
15
|
+
* ① **只枚举,不注入正文** —— 索引里**绝不**出现任何参考文件的正文;
|
|
16
|
+
* ② **确定性排序** = 相对路径的 **UTF-8 字节序**(🔴 **不用 mtime**:mtime 随检出漂移,
|
|
17
|
+
* 会让每请求的注入文本抖动;对照 `src/cro.ts:101` 那份**落盘快照**用 {name,bytes,mtime} ——
|
|
18
|
+
* **同一件事两处的取舍相反,那个差异是判据不是巧合**);
|
|
19
|
+
* ③ **有界 + 响亮** —— 条数/字符各有上限,超出**必须显式写出还剩多少项**,**绝不静默截断**;
|
|
20
|
+
* ④ **摘要可复现** —— 取该文件首个 `#` 标题(剥 markdown 记号);取不到 ⇒ 留空,**不编**;
|
|
21
|
+
* ⑤ **本体文件特殊标注** —— `skill.ttl` 存在 ⇒ 该行标 🧬 + 顶部加一句"已建模";
|
|
22
|
+
* ⑥ **范围 = `references/` 一级条目 + `skill.ttl`** —— 子目录**给名字 + 计数、不展开、不读正文**
|
|
23
|
+
* (🆕 2026-10-11 由**真实输入**逼出的修订:原写"不进子目录",实测 `references/` 下确实有内容目录,
|
|
24
|
+
* 只报"有 N 个子目录"不给名字 = **不可行动**;折中仍在裁决 Q2「只披露名字、不注入正文」的意图内)。
|
|
25
|
+
*
|
|
26
|
+
* 🔴 本文件**只认 `<skillDir>/skill.ttl`**(owner 裁决 Q6:静默、只认固定名)。
|
|
27
|
+
* `references/*-domain.ttl`(旧名)一律当**普通参考件**:不特殊对待、不告警 —— 那 14 份由 CCC 自行改名。
|
|
28
|
+
* 🔴 「未建模」不是异常(裁决 Q6 / 判据 J9):无 `skill.ttl` 的**普通 skill** 照常产出索引,
|
|
29
|
+
* **零告警、零误差** —— 增强必须对普通 skill 完全向后兼容。
|
|
30
|
+
*/
|
|
31
|
+
/** 第二层占位口:本 skill 的本体约束文件(固定名,设计 §5.1) */
|
|
32
|
+
export declare const SKILL_ONTOLOGY_FILE = "skill.ttl";
|
|
33
|
+
/** 参考件根(一级;不进子目录) */
|
|
34
|
+
export declare const REFERENCE_DIR = "references";
|
|
35
|
+
/** 条数上限(超出 ⇒ 写"还有 N 项未列出") */
|
|
36
|
+
export declare const RESOURCE_INDEX_MAX_ITEMS = 40;
|
|
37
|
+
/** 字符上限(超出 ⇒ 尾部响亮标记;宿主侧另有更外层的预算) */
|
|
38
|
+
export declare const RESOURCE_INDEX_MAX_CHARS: number;
|
|
39
|
+
export interface ResourceIndexOptions {
|
|
40
|
+
maxItems?: number;
|
|
41
|
+
maxChars?: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 取 markdown 首个 `#` 标题作摘要(规则④)。
|
|
45
|
+
*
|
|
46
|
+
* 细节(都由"可复现"推导):
|
|
47
|
+
* · **跳过 YAML frontmatter**(`---` 开头到下一 `---`)—— 否则题名行会被当成正文标题;
|
|
48
|
+
* · 只读文件**头部** `HEAD_BYTES`(一行标题不值得读整个大文件);
|
|
49
|
+
* · 剥掉行内记号(`*` `` ` `` `_` `#`)与首尾空白,压平内部连续空白;
|
|
50
|
+
* · 取不到 ⇒ `''`(**留空,不编** —— 编一个摘要等于让索引说假话);
|
|
51
|
+
* · 超长截断时**留省略号**(与规则③同一精神:截断要看得见)。
|
|
52
|
+
*/
|
|
53
|
+
export declare function firstHeading(file: string): string;
|
|
54
|
+
/** 该 skill 是否已建模(只有固定名算 —— 裁决 Q6) */
|
|
55
|
+
export declare function hasSkillOntology(skillDir: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* 生成第二层索引正文。
|
|
58
|
+
*
|
|
59
|
+
* @param skillDir skill 目录绝对路径
|
|
60
|
+
* @param options 上限覆盖(缺省用本模块常量)
|
|
61
|
+
* @returns 索引正文;**无可列项 ⇒ `''`**(规则⑥的一般化:`references/` 与 `skill.ttl` 都没有
|
|
62
|
+
* ⇒ 整块不产出 —— 空标题是噪声)。调用方按"空串不产出该块"处理。
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildResourceIndex(skillDir: string, options?: ResourceIndexOptions): string;
|
|
@@ -18,6 +18,8 @@ type EntrySkillSource = 'serenity-marker' | 'pointer' | 'opencode' | 'dsh';
|
|
|
18
18
|
interface EntrySkill {
|
|
19
19
|
name: string;
|
|
20
20
|
content: string;
|
|
21
|
+
/** skill 目录(= SKILL.md 的父目录)—— 第二层披露树要按目录扫 `references/`(serenity-skill P1) */
|
|
22
|
+
dir: string;
|
|
21
23
|
source: EntrySkillSource;
|
|
22
24
|
}
|
|
23
25
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.53.0",
|
|
4
4
|
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:13 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/ontology/handyman/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|