@shgroup/dsh-serenity-hooks 1.53.0 → 1.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dsh.plugin.json +4 -3
- package/lib/index.js +589 -12
- package/lib/invariant.d.ts +7 -1
- package/lib/invariant.js +8 -1
- package/lib/skills/skill-layering.d.ts +39 -0
- package/lib/skills/skill-ontology.d.ts +66 -0
- package/lib/tools/skill-creator.d.ts +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,7 @@ dsh plugin --profile web add link:$(pwd)/hooks/dsh-serenity-hooks
|
|
|
76
76
|
| `msm` | 小工具执行入口:`msm("名字", ["参数"])`;名字记不全就给候选;`inspect=true` 看用法 | 调用工作区里注册的任何小工具 |
|
|
77
77
|
| `praxis` | 按需给 AI 注入四套"做事方法":输出自检(eap)、设计对齐(neat)、认知连续性(cce)、本体工程(ontology) | 要它把话说清楚 / 先对齐再动手 / 动手前先把领域结构显式化时 |
|
|
78
78
|
| `ontology` | 本体工程**引擎**(能跑的那个):读 `.ttl` 本体文件,做结构校验(M1–M10,含公理表达式语法)、生成摘要、查声明与实况是否一致(V1–V4)、出语义审查提示词、出"公理 ↔ 技能"合规清单;还能写一份新本体的模板 | 想给某个领域建/校验本体时。⚠️ 与 `praxis ontology` 是**同一领域的两个面**:那个是**方法文本**(怎么建模),这个是**引擎**(真去检查)。路径必须在工作区内 |
|
|
79
|
+
| `serenity-skill-creator` | 增强型 skill 标准(**serenity-skill**)的 **creator**:`guide`(编写与优化方法,先读它)/`create`(生成 skill 骨架 = SKILL.md + skill.ttl + references/,**基础链的相对路径由它算**)/`check`(**A1 结构 + A2 链上基础链**,三态可分辨:已建模通过/普通 skill/建模了但检查没跑成)/`review`(**C 档**:文字与本体逐条的合规问题清单,半机械,要人回答)/`index`(预览**模型实际会看到**的第二层参考件清单)/`status`(全容器一览:哪些已建模) | 新建或改造 skill、给 skill 建本体、想知道"模型到底看不看得见我那些参考件"时 |
|
|
79
80
|
| `handyman` | 杂工:派一个便宜模型的助手干活。**默认模式(foreground)**= 一次串行委派、拿回结果;**background 模式**= 循环干活直到完成(完成码校验 / 轮次上限 / 自动重启 / 进度文件),也可一次派多个并行 | 大批量、重复性的活(扫描几十个技能、逐用例回归) |
|
|
80
81
|
| `localstore` | 存密钥和配置(凭据、偏好两个命名空间) | API key、密码集中放一处,不进 git |
|
|
81
82
|
| `container_admin` | 机务舱:管理子角色、管理小工具注册表、查看全部配置 | 定义"子角色"、注册新小工具、改配置时 |
|
package/dsh.plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.55.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
|
-
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——
|
|
5
|
+
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——14 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/handyman/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1(deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
|
7
7
|
"dsh": ">=0.1.7-rc.1"
|
|
8
8
|
},
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"im-bridge",
|
|
21
21
|
"acc-diag",
|
|
22
22
|
"diagram",
|
|
23
|
-
"ontology"
|
|
23
|
+
"ontology",
|
|
24
|
+
"serenity-skill-creator"
|
|
24
25
|
],
|
|
25
26
|
"skills": []
|
|
26
27
|
}
|
package/lib/index.js
CHANGED
|
@@ -424,7 +424,7 @@ function runCcFs(root, args) {
|
|
|
424
424
|
* 进程内注册(取代 v0.1 的 bash spawn runner):zod/schemastery 参数校验、
|
|
425
425
|
* 规范 JSON 输出、纯 render 投影。逻辑在 fs-ops.ts(可单测)。
|
|
426
426
|
*/
|
|
427
|
-
function renderText$
|
|
427
|
+
function renderText$11(value) {
|
|
428
428
|
return [{
|
|
429
429
|
type: "text",
|
|
430
430
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -496,7 +496,7 @@ const ccFsTool = defineTool({
|
|
|
496
496
|
},
|
|
497
497
|
output: {
|
|
498
498
|
schema: { type: "json" },
|
|
499
|
-
render: (args, value) => renderText$
|
|
499
|
+
render: (args, value) => renderText$11(value)
|
|
500
500
|
},
|
|
501
501
|
async execute(args, exec) {
|
|
502
502
|
const root = cccRootForExec(exec);
|
|
@@ -572,7 +572,7 @@ async function runKit(root, args, hostCtx) {
|
|
|
572
572
|
/**
|
|
573
573
|
* kit.ts — dashboard 真实 DSH 工具定义(defineTool)(v1.30:acc_kit → dashboard)
|
|
574
574
|
*/
|
|
575
|
-
function renderText$
|
|
575
|
+
function renderText$10(value) {
|
|
576
576
|
return [{
|
|
577
577
|
type: "text",
|
|
578
578
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -600,7 +600,7 @@ function createKitTool(ctx) {
|
|
|
600
600
|
},
|
|
601
601
|
output: {
|
|
602
602
|
schema: { type: "json" },
|
|
603
|
-
render: (args, value) => renderText$
|
|
603
|
+
render: (args, value) => renderText$10(value)
|
|
604
604
|
},
|
|
605
605
|
async execute(args, exec) {
|
|
606
606
|
return await runKit(cccRootForExec(exec), args, ctx);
|
|
@@ -915,7 +915,7 @@ function runGit(root, args) {
|
|
|
915
915
|
/**
|
|
916
916
|
* git.ts — container_git 真实 DSH 工具定义(defineTool)(v1.30:cc_git → container_git)
|
|
917
917
|
*/
|
|
918
|
-
function renderText$
|
|
918
|
+
function renderText$9(value) {
|
|
919
919
|
return [{
|
|
920
920
|
type: "text",
|
|
921
921
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -963,7 +963,7 @@ const gitTool = defineTool({
|
|
|
963
963
|
},
|
|
964
964
|
output: {
|
|
965
965
|
schema: { type: "json" },
|
|
966
|
-
render: (args, value) => renderText$
|
|
966
|
+
render: (args, value) => renderText$9(value)
|
|
967
967
|
},
|
|
968
968
|
async execute(args, exec) {
|
|
969
969
|
const root = cccRootForExec(exec);
|
|
@@ -3377,6 +3377,7 @@ function toolsBlock() {
|
|
|
3377
3377
|
" praxis — actionable theory injection: praxis (index) / praxis eap / praxis neat / praxis cce / praxis ontology",
|
|
3378
3378
|
" ontology — ontology engineering ENGINE (executable): init (write template) / check (M1-M10 structural; level=strict adds L1-L5 hints) / explain (summary) / verify (V1-V4 runtime compliance) / review (semantic-review prompt) / compliance (axiom-vs-skill checklist). Sibling of `praxis ontology` — that one is the METHOD TEXT, this one RUNS it. path must stay inside the container; the receipt carries the engine exit code (0/1/2/4) and the engine text verbatim, so read it rather than assuming success",
|
|
3379
3379
|
" handyman — delegate work to a worker agent on a CCC-whitelisted model; mode=\"foreground\" (default) = child agent(s) run once and return their final text (jobs=[...] fans out in parallel, cap 10, per-job model); mode=\"background\" = loop-validated worker (completion code + round cap + restart + progress file), jobs=[] orchestrates parallel work",
|
|
3380
|
+
" serenity-skill-creator — creator for serenity-skills (this container's enhanced skill standard): guide (authoring/optimization method — read first) / create <name> (scaffold SKILL.md + skill.ttl + references/, base-chain import path computed) / check <name> (A1 structural + A2 base-chain; three states: modeled-ok / plain-skill / modeled-but-check-could-not-run) / review <name> (C tier: axiom-vs-text compliance question list, half-mechanical) / index <name> (preview the second-layer resource list the model will see) / status (which skills are modeled). A skill is \"modeled\" iff it has a file named exactly skill.ttl next to SKILL.md; a plain skill is NOT an anomaly. Only `create` writes, and it refuses an existing directory",
|
|
3380
3381
|
" localstore — ACC local credential/config storage (CCC-root localstore.json; git policy localstore.gitTrack default deny)",
|
|
3381
3382
|
" container_admin — container administration (the maintenance bay): role (Skiff roles: guide/validate/apply/list) / msm (register/deregister/check/guide/catalog/ccc-config) / config (view)",
|
|
3382
3383
|
"",
|
|
@@ -4379,7 +4380,7 @@ function skiffMsmGate(root, sessionId, action, name) {
|
|
|
4379
4380
|
function agentSessionId$1(exec) {
|
|
4380
4381
|
return exec.agent?.session?.id ?? "";
|
|
4381
4382
|
}
|
|
4382
|
-
function renderText$
|
|
4383
|
+
function renderText$8(value) {
|
|
4383
4384
|
return [{
|
|
4384
4385
|
type: "text",
|
|
4385
4386
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -4429,7 +4430,7 @@ const msmTool = defineTool({
|
|
|
4429
4430
|
},
|
|
4430
4431
|
output: {
|
|
4431
4432
|
schema: { type: "json" },
|
|
4432
|
-
render: (args, value) => renderText$
|
|
4433
|
+
render: (args, value) => renderText$8(value)
|
|
4433
4434
|
},
|
|
4434
4435
|
async execute(args, exec) {
|
|
4435
4436
|
const root = cccRootForExec(exec);
|
|
@@ -4903,7 +4904,7 @@ const PRAXIS_INDEX = `praxis — 可实践理论注入(认知框架,按需
|
|
|
4903
4904
|
praxis ontology(本体论工程方法群——触发/编码/四组判据 M·L·D·V/OEPE 循环/公理/反模式)
|
|
4904
4905
|
无参返回本目录;详情以对应框架原文为准(单一真相源)。
|
|
4905
4906
|
`;
|
|
4906
|
-
function renderText$
|
|
4907
|
+
function renderText$7(value) {
|
|
4907
4908
|
return [{
|
|
4908
4909
|
type: "text",
|
|
4909
4910
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -4924,7 +4925,7 @@ const praxisTool = defineTool({
|
|
|
4924
4925
|
} },
|
|
4925
4926
|
output: {
|
|
4926
4927
|
schema: { type: "string" },
|
|
4927
|
-
render: (_args, value) => renderText$
|
|
4928
|
+
render: (_args, value) => renderText$7(value)
|
|
4928
4929
|
},
|
|
4929
4930
|
async execute(args) {
|
|
4930
4931
|
if (args.section === "eap") return EAP_CONTENT;
|
|
@@ -7608,7 +7609,7 @@ const LEVELS_BY_COMMAND = {
|
|
|
7608
7609
|
"full"
|
|
7609
7610
|
]
|
|
7610
7611
|
};
|
|
7611
|
-
function renderText$
|
|
7612
|
+
function renderText$6(value) {
|
|
7612
7613
|
return [{
|
|
7613
7614
|
type: "text",
|
|
7614
7615
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -7669,7 +7670,7 @@ function createOntologyTool() {
|
|
|
7669
7670
|
},
|
|
7670
7671
|
output: {
|
|
7671
7672
|
schema: { type: "string" },
|
|
7672
|
-
render: (_args, value) => renderText$
|
|
7673
|
+
render: (_args, value) => renderText$6(value)
|
|
7673
7674
|
},
|
|
7674
7675
|
async execute(args, exec) {
|
|
7675
7676
|
const root = cccRootForExec(exec);
|
|
@@ -7718,6 +7719,581 @@ function createOntologyTool() {
|
|
|
7718
7719
|
});
|
|
7719
7720
|
}
|
|
7720
7721
|
//#endregion
|
|
7722
|
+
//#region src/skills/skill-layering.ts
|
|
7723
|
+
/**
|
|
7724
|
+
* skill-layering.ts(库层,零 DSH 依赖)— **EAP 分层抽象树档(A3)的机械判据**
|
|
7725
|
+
*
|
|
7726
|
+
* 缘起(owner 2026-10-12 令「**根据本次经验完善 serenity-skill-creator;确保未来创建和改进 skill
|
|
7727
|
+
* 都保持 EAP 分层抽象树结构**」):本轮 dsp 开发 skill 的切片**把树倒置了** —— SKILL.md 被切成
|
|
7728
|
+
* 3.4 KB 的**导航层**(只指向下、不说顶层的话),而顶层该说的"身份/模型/约束"全沉到了 references/。
|
|
7729
|
+
* 那次事故的**形状是机械可查的**,所以把它固化成判据,而不是只写进方法正文(D119 同族:
|
|
7730
|
+
* 「写进文档的规则」不会拦住下一次,**能跑起来的判据才会**)。
|
|
7731
|
+
*
|
|
7732
|
+
* 🔴 **本档与 A1/A2 的分工**:
|
|
7733
|
+
* · A3 判的是**文字住在哪一层**(结构形态)—— **每个 skill 都适用**,与有没有 `skill.ttl` 无关。
|
|
7734
|
+
* · A1/A2 判的是**本体**(结构合法性 + 链上基础链)—— 只有已建模的 skill 适用(J9:未建模不是异常)。
|
|
7735
|
+
*
|
|
7736
|
+
* 🔴 **只报不改**:本档给的是判据与可行动修法;改写 SKILL.md 是作者/模型的活(语义判断不在机械档)。
|
|
7737
|
+
* 🔴 **不重写别人的规则**:references 参考件的**首标题**规则归 `resource-index.ts`(`firstHeading`),
|
|
7738
|
+
* 这里只做"列出 references/ 下一级 .md"这一件事。
|
|
7739
|
+
*
|
|
7740
|
+
* ⚠️ **编号消歧(必读)**:本档叫 **A3**,但**与 v1.54.0 记录里那个"已下线的 A3(引用可兑现)"
|
|
7741
|
+
* 不是同一档** —— 那一条是照引擎源码"意图"推断的,实测打假后**决定不写第二实现**。
|
|
7742
|
+
* 本条是 2026-10-12 **新立**的**分层形态**档(判"文字住在哪一层"),与"引用可兑现"无关。
|
|
7743
|
+
*/
|
|
7744
|
+
/** 顶层正文(去掉 frontmatter 与生成的导航块)的**下限**:低于它 = 顶层只是桩 */
|
|
7745
|
+
const TOP_LAYER_MIN_BYTES = 2048;
|
|
7746
|
+
/** 生成块(第二层清单)的哨兵 —— 它由 `skill-lint split` / 生成器产出,**不算顶层正文** */
|
|
7747
|
+
const NAV_BLOCK_BEGIN = "<!-- skill-lint:split-nav -->";
|
|
7748
|
+
const NAV_BLOCK_END = "<!-- /skill-lint:split-nav -->";
|
|
7749
|
+
/**
|
|
7750
|
+
* "本文件只是导航层"这类**自述** —— 它是树倒置的**直接症状**(顶层自己声明什么都不说)。
|
|
7751
|
+
* 正常 skill 不会写这句话 ⇒ 这条判据**几乎不会假阳**。
|
|
7752
|
+
*/
|
|
7753
|
+
const NAV_SELF_DESCRIPTION = /(?:只是|仅作|仅仅是|只留|只剩|纯)(?:一个)?导航(?:层|文件|块)|导航(?:层|块)(?:而已|罢了)/;
|
|
7754
|
+
/** 去掉 YAML frontmatter(`---` 包起来的那段) */
|
|
7755
|
+
function stripFrontmatter(text) {
|
|
7756
|
+
if (!text.startsWith("---")) return text;
|
|
7757
|
+
const end = text.indexOf("\n---", 3);
|
|
7758
|
+
if (end === -1) return text;
|
|
7759
|
+
const after = text.indexOf("\n", end + 1);
|
|
7760
|
+
return after === -1 ? "" : text.slice(after + 1);
|
|
7761
|
+
}
|
|
7762
|
+
/** 扣掉生成块;返回 [剩下的正文, 被扣掉的字节数] */
|
|
7763
|
+
function stripNavBlock(text) {
|
|
7764
|
+
const begin = text.indexOf(NAV_BLOCK_BEGIN);
|
|
7765
|
+
if (begin === -1) return [text, 0];
|
|
7766
|
+
const end = text.indexOf(NAV_BLOCK_END, begin);
|
|
7767
|
+
if (end === -1) return [text, 0];
|
|
7768
|
+
const cut = text.slice(begin, end + 30);
|
|
7769
|
+
return [text.slice(0, begin) + text.slice(end + 30), Buffer.byteLength(cut, "utf-8")];
|
|
7770
|
+
}
|
|
7771
|
+
/** references/ **一级**下的 .md(子目录不展开 —— 与第二层披露同口径:子目录按"名字+文件数"入表) */
|
|
7772
|
+
function listReferenceDocs(skillDir) {
|
|
7773
|
+
const dir = join(skillDir, REFERENCE_DIR);
|
|
7774
|
+
if (!existsSync(dir)) return [];
|
|
7775
|
+
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name).sort();
|
|
7776
|
+
}
|
|
7777
|
+
/**
|
|
7778
|
+
* 对一个 skill 目录跑 A3 档。
|
|
7779
|
+
* 🔴 三条判据各自**独立**(一条不过不掩盖另一条),全部机械、全部可复跑。
|
|
7780
|
+
*/
|
|
7781
|
+
function analyzeSkillLayering(skillDir) {
|
|
7782
|
+
const skillMd = join(skillDir, "SKILL.md");
|
|
7783
|
+
const findings = [];
|
|
7784
|
+
if (!existsSync(skillMd)) return {
|
|
7785
|
+
hasSkillMd: false,
|
|
7786
|
+
topBodyBytes: 0,
|
|
7787
|
+
navBlockBytes: 0,
|
|
7788
|
+
navSelfDescriptions: [],
|
|
7789
|
+
referenceDocs: listReferenceDocs(skillDir),
|
|
7790
|
+
notNamedInTop: [],
|
|
7791
|
+
findings: [{
|
|
7792
|
+
code: "A3-1",
|
|
7793
|
+
detail: "没有 SKILL.md —— skill 的身份与顶层都无处安放"
|
|
7794
|
+
}],
|
|
7795
|
+
ok: false
|
|
7796
|
+
};
|
|
7797
|
+
const raw = readFileSync(skillMd, "utf-8");
|
|
7798
|
+
const [withoutNav, navBlockBytes] = stripNavBlock(stripFrontmatter(raw));
|
|
7799
|
+
const topBodyBytes = Buffer.byteLength(withoutNav.trim(), "utf-8");
|
|
7800
|
+
if (topBodyBytes < 2048) findings.push({
|
|
7801
|
+
code: "A3-1",
|
|
7802
|
+
detail: `顶层正文只有 ${topBodyBytes} B(< ${TOP_LAYER_MIN_BYTES})—— 顶层必须自己说清「身份与边界 / 用途与触发 / 领域模型(形状)/ 永远成立的约束 / 抽象树读法」,不能只列文件名把读者转手给下层(EAP:顶层说顶层的话)`
|
|
7803
|
+
});
|
|
7804
|
+
const navSelfDescriptions = [];
|
|
7805
|
+
for (const line of withoutNav.split("\n")) if (NAV_SELF_DESCRIPTION.test(line)) {
|
|
7806
|
+
const t = line.trim();
|
|
7807
|
+
navSelfDescriptions.push(t.length > 160 ? `${t.slice(0, 160)}…` : t);
|
|
7808
|
+
}
|
|
7809
|
+
if (navSelfDescriptions.length > 0) findings.push({
|
|
7810
|
+
code: "A3-2",
|
|
7811
|
+
detail: `顶层自称"只是导航层/只剩下导航" ×${navSelfDescriptions.length} —— 这是**树倒置的直接症状**:顶层声明自己什么都不说,读者拿不到任何上层陈述。修法 = 把下层的**顶层陈述**上提(hoist:从原处**删除并留指针**,一条内容只有一个家),不是加一句话辩解`
|
|
7812
|
+
});
|
|
7813
|
+
const referenceDocs = listReferenceDocs(skillDir);
|
|
7814
|
+
const notNamedInTop = referenceDocs.filter((d) => !raw.includes(d));
|
|
7815
|
+
if (notNamedInTop.length > 0) findings.push({
|
|
7816
|
+
code: "A3-3",
|
|
7817
|
+
detail: `references/ 下有 ${notNamedInTop.length} 件没在 SKILL.md 里点名:${notNamedInTop.join(" / ")} —— 顶层是**抽象树的地图**:不点名,读者就不知道存在这棵子树(第二层清单会自动补,但顶层该说"什么时候该下潜到哪一件")`
|
|
7818
|
+
});
|
|
7819
|
+
return {
|
|
7820
|
+
hasSkillMd: true,
|
|
7821
|
+
topBodyBytes,
|
|
7822
|
+
navBlockBytes,
|
|
7823
|
+
navSelfDescriptions,
|
|
7824
|
+
referenceDocs,
|
|
7825
|
+
notNamedInTop,
|
|
7826
|
+
findings,
|
|
7827
|
+
ok: findings.length === 0
|
|
7828
|
+
};
|
|
7829
|
+
}
|
|
7830
|
+
/** 渲染给人/模型看的一段(工具面直接用) */
|
|
7831
|
+
function formatLayeringReport(skillName, r) {
|
|
7832
|
+
const lines = [r.ok ? `✅ A3 分层档:通过(顶层正文 ${r.topBodyBytes} B / 导航块 ${r.navBlockBytes} B / 参考件 ${r.referenceDocs.length} 件全部点名)` : `❌ A3 分层档:${r.findings.length} 条不过`];
|
|
7833
|
+
for (const f of r.findings) lines.push(` · [${f.code}] ${f.detail}`);
|
|
7834
|
+
if (r.ok) lines.push(` (判据:顶层非桩 ≥ ${TOP_LAYER_MIN_BYTES} B / 顶层不自述"只是导航" / references/ 一级 .md 全部被点名)`);
|
|
7835
|
+
if (!r.ok) lines.push(" 👉 修完重跑 `check`。方法正文见 `guide` 的「分层抽象树」一节。");
|
|
7836
|
+
return lines.join("\n");
|
|
7837
|
+
}
|
|
7838
|
+
//#endregion
|
|
7839
|
+
//#region src/skills/skill-ontology.ts
|
|
7840
|
+
/**
|
|
7841
|
+
* skill-ontology.ts — **本体预留口**的发现与检查(纯逻辑,零 DSH 依赖)
|
|
7842
|
+
*
|
|
7843
|
+
* 设计真相源:`docs/serenity-skill-design.md` §4.1(四个动作)/§5.1-5.2(固定名与骨架)/§6(J1~J3)。
|
|
7844
|
+
* 一句话:**一个 skill 若完成了本体建模,就在自己目录放固定名 `skill.ttl`**;
|
|
7845
|
+
* 本模块回答三件事:**有没有**(发现)/**合不合法**(A 档)/**引用能不能兑现**(B 档)。
|
|
7846
|
+
*
|
|
7847
|
+
* 🔴 只认**固定名** `<skillDir>/skill.ttl`(owner 裁决 Q6:静默、CCC 自行修正):
|
|
7848
|
+
* `references/*-domain.ttl` 这类**旧名一律当普通参考件** —— 不特殊对待、**不告警**、**不代读**。
|
|
7849
|
+
* 🔴 「无 `skill.ttl`」**不是错误也不是告警**(裁决 Q6 / 判据 J9):
|
|
7850
|
+
* 那是**普通 skill**,全程无声 —— 增强必须对普通 skill 完全向后兼容。
|
|
7851
|
+
*
|
|
7852
|
+
* ── 两档判据(**两档都锚在引擎自己的输出上**,我们不另写一遍 Turtle 解析)──────────
|
|
7853
|
+
* **A1 结构合法**:`check --level strict` exit 0(引擎的 M1–M10)
|
|
7854
|
+
* **A2 链上基础链**:`explain` exit 0 **且**报告里有 `Imports:` 行
|
|
7855
|
+
*
|
|
7856
|
+
* 🔴 **为什么必须有 A2(这是本模块存在的理由,且是实测逼出来的)**:
|
|
7857
|
+
* **A1 绿 ≠ 链上了**。实测对照(同一天、同一引擎):
|
|
7858
|
+
* · `home-rhetoric/references/home-rhetoric-domain.ttl`(写的是 `sr:imports`)
|
|
7859
|
+
* ⇒ `check --level strict` = **✓ exit 0 · All checks passed**,而 `explain` **没有 `Imports:` 行**;
|
|
7860
|
+
* · 注册表(写的是 `owl:imports`)⇒ `explain` **打印 `Imports: ccc-ontology.ttl`**(探针不瞎)。
|
|
7861
|
+
* ⇒ **「检查全绿」与「链上了」是两件事**;只看 A1 就会把 14 份没链上的本体判成合格。
|
|
7862
|
+
*
|
|
7863
|
+
* 🟢 **A3(引用可兑现)**:**本模块刻意不做** —— 引擎已经做了,而且是**响亮的**。实测两条:
|
|
7864
|
+
* · `check`:import 目标不存在 ⇒ **`ERR [M10] Ontology file not found: <abs>`**、**exit 1**;
|
|
7865
|
+
* · `explain`:同一个文件 ⇒ **exit 2 · `ONTO_FILE_NOT_FOUND`**。
|
|
7866
|
+
* ⇒ 再写一遍就是**同一件事的第二实现**(本仓的老账:"先问谁已经做了")。
|
|
7867
|
+
* 🔴 但**起因值得记**:我原本以为引擎对缺失 import 是**静默**的(照源码 1687 行那句 "Fall back to
|
|
7868
|
+
* loading just this file (no imports)" 推断)—— **实测把这条推断打假了**(两条路都响亮报错)。
|
|
7869
|
+
* ⇒ 又一次「**照源码的意图抄 ≠ 引擎实际怎么行为**」(D119 同族)。
|
|
7870
|
+
*/
|
|
7871
|
+
/** 从引擎 `explain` 的报告里取 `Imports:` 行(**锚在引擎自己的输出上**,见文件头 A2 注) */
|
|
7872
|
+
function parseImports(explainStdout) {
|
|
7873
|
+
const out = [];
|
|
7874
|
+
for (const line of explainStdout.split("\n")) {
|
|
7875
|
+
const m = /^Imports:\s*(.+)$/.exec(line.trim());
|
|
7876
|
+
if (!m) continue;
|
|
7877
|
+
for (const part of (m[1] ?? "").split(",")) {
|
|
7878
|
+
const t = part.trim();
|
|
7879
|
+
if (t !== "") out.push(t);
|
|
7880
|
+
}
|
|
7881
|
+
}
|
|
7882
|
+
return out;
|
|
7883
|
+
}
|
|
7884
|
+
/**
|
|
7885
|
+
* 检查一个 skill 的预留口。
|
|
7886
|
+
*
|
|
7887
|
+
* @param skillDir skill 目录(绝对路径)
|
|
7888
|
+
* @param opts.root CCC 根(引擎基准;**必须是 CCC 根**,否则引擎找不到容器级资产)
|
|
7889
|
+
* @returns 三态 + 逐档判据
|
|
7890
|
+
*/
|
|
7891
|
+
function inspectSkillOntology(skillDir, opts) {
|
|
7892
|
+
const abs = resolve(skillDir, SKILL_ONTOLOGY_FILE);
|
|
7893
|
+
if (!existsSync(abs)) return {
|
|
7894
|
+
state: "none",
|
|
7895
|
+
rel: "",
|
|
7896
|
+
modeled: false,
|
|
7897
|
+
checks: [],
|
|
7898
|
+
imports: []
|
|
7899
|
+
};
|
|
7900
|
+
const relOf = (p) => p.startsWith(opts.root) ? p.slice(opts.root.length).replace(/^[/\\]/, "") : p;
|
|
7901
|
+
const rel = relOf(abs);
|
|
7902
|
+
const checks = [];
|
|
7903
|
+
const c1 = runOntologyCli([
|
|
7904
|
+
"check",
|
|
7905
|
+
abs,
|
|
7906
|
+
"--level",
|
|
7907
|
+
"strict"
|
|
7908
|
+
], { cwd: opts.root });
|
|
7909
|
+
checks.push({
|
|
7910
|
+
id: "A1",
|
|
7911
|
+
ok: c1.code === 0,
|
|
7912
|
+
detail: c1.code === 0 ? "strict 结构校验通过" : c1.code === 1 ? "结构校验未通过(引擎 ERR 行见报告)" : c1.code === 2 ? `解析失败/读不到文件(code 2)${c1.stderr.trim() !== "" ? `:${c1.stderr.trim().split("\n")[0]}` : ""}` : `引擎内部错误(code ${c1.code})`
|
|
7913
|
+
});
|
|
7914
|
+
const e1 = runOntologyCli(["explain", abs], { cwd: opts.root });
|
|
7915
|
+
const imports = e1.code === 0 ? parseImports(e1.stdout) : [];
|
|
7916
|
+
const explainFirstErr = e1.stderr.trim().split("\n").find((l) => l.trim() !== "") ?? "";
|
|
7917
|
+
checks.push({
|
|
7918
|
+
id: "A2",
|
|
7919
|
+
ok: e1.code === 0 && imports.length > 0,
|
|
7920
|
+
detail: e1.code !== 0 ? `**探测没跑成**(引擎 explain exit ${e1.code})—— ${explainFirstErr !== "" ? explainFirstErr : "引擎未给原因"}` : imports.length > 0 ? `已链上基础链:${imports.join(", ")}` : "**未声明 `owl:imports`** —— 与基础链**没链上**(引擎只解析 `owl:imports`;写 `sr:imports` 不会被读 —— 这是实测过的静默形态,且 `check` 对它会**照样报全绿**)"
|
|
7921
|
+
});
|
|
7922
|
+
return {
|
|
7923
|
+
state: checks.every((c) => c.ok) ? "ok" : "error",
|
|
7924
|
+
rel,
|
|
7925
|
+
modeled: true,
|
|
7926
|
+
checks,
|
|
7927
|
+
imports
|
|
7928
|
+
};
|
|
7929
|
+
}
|
|
7930
|
+
/**
|
|
7931
|
+
* 渲染三态报告(**三态必须互可分辨** —— 判据 J3;措辞刻意不许糊)。
|
|
7932
|
+
*
|
|
7933
|
+
* 为什么单独一个函数:`check` 的呈现(P3 的工具)与测试看的是**同一段文本**,
|
|
7934
|
+
* 两处各写一遍必然漂移(本仓"路径写两遍必漂"的既有教训)。
|
|
7935
|
+
*/
|
|
7936
|
+
function formatSkillOntologyInspection(skillName, insp) {
|
|
7937
|
+
const lines = [insp.state === "ok" ? `✅ ${skillName}:**已建模且全部通过**` : insp.state === "none" ? `⬜ ${skillName}:**普通 skill(未建模)**—— 无 \`${SKILL_ONTOLOGY_FILE}\`` : `❌ ${skillName}:**建模了但检查没跑成**`];
|
|
7938
|
+
if (insp.state === "none") {
|
|
7939
|
+
lines.push(" (**这不是\"检查通过\",是\"没有这项检查\"**;也不是异常 —— 增强对普通 skill 完全向后兼容。)");
|
|
7940
|
+
return lines.join("\n");
|
|
7941
|
+
}
|
|
7942
|
+
lines.push(` ${insp.rel}`);
|
|
7943
|
+
for (const c of insp.checks) lines.push(` ${c.ok ? "✅" : "❌"} ${c.id} ${c.detail}`);
|
|
7944
|
+
return lines.join("\n");
|
|
7945
|
+
}
|
|
7946
|
+
//#endregion
|
|
7947
|
+
//#region src/tools/skill-creator.ts
|
|
7948
|
+
/**
|
|
7949
|
+
* skill-creator.ts(工具面)— `serenity-skill-creator`:**增强型 skill(serenity-skill)的 creator**
|
|
7950
|
+
*
|
|
7951
|
+
* 设计真相源:`docs/serenity-skill-design.md` §4.3(六个子命令)/§5(接口)/§6(判据 J9/J10)。
|
|
7952
|
+
* 来源:owner 2026-10-11 令 —— 「扩展 skill 相关 tool,将**基于本体论建模的 skill 编写和优化方法**进行交代,
|
|
7953
|
+
* 其实也就是我们这种增强型 skill 的 creator」,且**点名**要一个专门的工具、**名字由他给**。
|
|
7954
|
+
*
|
|
7955
|
+
* 🔴 **三段分工(别混)**:
|
|
7956
|
+
* · **方法文本** = 本文件的 `SKILL_CREATOR_GUIDE`(`guide` 子命令印它)—— 给人/模型读"怎么做"。
|
|
7957
|
+
* · **机械判据** = `src/skills/skill-ontology.ts`(P2 / **A1·A2 本体档**)、`src/skills/resource-index.ts`(P1)
|
|
7958
|
+
* 与 `src/skills/skill-layering.ts`(**A3 分层抽象树档** —— 2026-10-12 owner 令「确保未来创建和改进
|
|
7959
|
+
* skill 都保持 EAP 分层抽象树结构」)—— 本工具**只做转发与呈现,绝不重写一遍检查**(同一件事的
|
|
7960
|
+
* 第二实现是本仓的老账)。
|
|
7961
|
+
* · **语义档 C** = 引擎的 `compliance`(半机械:出逐条可回答问题清单,交 LLM/人在环回答)。
|
|
7962
|
+
*
|
|
7963
|
+
* 🔴 **J9:未建模 ≠ 异常。** 没有 `skill.ttl` 的**普通 skill** 全程无声 —— 本工具任何子命令都
|
|
7964
|
+
* 不许把它渲染成错误或告警,只是"少一项能力"。
|
|
7965
|
+
*
|
|
7966
|
+
* 🔴 **写文件只发生在 `create`**,且**目标已存在一律拒**(不许静默覆盖别人目录)。
|
|
7967
|
+
*/
|
|
7968
|
+
const ACTIONS$2 = [
|
|
7969
|
+
"guide",
|
|
7970
|
+
"create",
|
|
7971
|
+
"check",
|
|
7972
|
+
"review",
|
|
7973
|
+
"index",
|
|
7974
|
+
"status"
|
|
7975
|
+
];
|
|
7976
|
+
/** 基础链(skill.ttl 必须链上的那个文件)在**顶层 skill** 的 references/ 下 */
|
|
7977
|
+
const BASE_CHAIN_FILE = "serenity-skills-ontology.ttl";
|
|
7978
|
+
/** 缺省顶层 skill 名(与引擎的 TOP_SKILL 缺省一致;`create` 会优先读 `.serenity`) */
|
|
7979
|
+
const DEFAULT_TOP_SKILL = "home-serenity";
|
|
7980
|
+
/**
|
|
7981
|
+
* 《serenity-skill 编写与优化方法》(`guide` 印它)。
|
|
7982
|
+
*
|
|
7983
|
+
* 边界(D23):**本文只是方法**;判据在代码里(P1/P2),语义审查在引擎的 `compliance` 里 ——
|
|
7984
|
+
* 三者**不许互相抄**(同一件事写两遍必然漂移)。
|
|
7985
|
+
*/
|
|
7986
|
+
const SKILL_CREATOR_GUIDE = `# serenity-skill —— 增强型 skill 的编写与优化方法
|
|
7987
|
+
|
|
7988
|
+
## 何时该建模,何时不该
|
|
7989
|
+
|
|
7990
|
+
**该建模**:该领域有**稳定的类型与关系**(谁是谁、一对多、什么必须先于什么),且这些结构**会反复被引用**
|
|
7991
|
+
(排错、审查、写文档都要回去看它)—— 典型如"一个 skill 管哪些资产、资产之间什么约束"。
|
|
7992
|
+
|
|
7993
|
+
**不该建模**(**不建模不是缺陷**):
|
|
7994
|
+
· 纯操作规程("先跑 A 再跑 B")—— 那是 SKILL.md 的正文,给它套本体只是换个地方写散文;
|
|
7995
|
+
· 一次性/短命的事;
|
|
7996
|
+
· 你自己都还说不清领域里到底有哪几类东西时 —— **先写散文,等结构浮现再建模**(建模不是入场券)。
|
|
7997
|
+
|
|
7998
|
+
## 三个动作(做完即"已建模")
|
|
7999
|
+
|
|
8000
|
+
1. 在 skill 目录放**固定名** \`skill.ttl\`(与 \`SKILL.md\` 并列)—— 这是**唯一**被认的名字。
|
|
8001
|
+
⚠️ 旧写法 \`references/<name>-domain.ttl\` **不被认**(静默当普通参考件):不是报错,是**没有人会看它**。
|
|
8002
|
+
2. \`skill.ttl\` **必须链上基础链**,形式是 \`owl:imports <相对路径>\`:
|
|
8003
|
+
\`\`\`turtle
|
|
8004
|
+
:onto a owl:Ontology ;
|
|
8005
|
+
sr:name "<skill-name>" ;
|
|
8006
|
+
owl:versionInfo "0.1.0" ;
|
|
8007
|
+
owl:imports <../home-serenity/references/serenity-skills-ontology.ttl> .
|
|
8008
|
+
\`\`\`
|
|
8009
|
+
🔴 **相对路径由 \`create\` 算**(从本 skill 目录指向顶层 skill 的 references/),**不要人肉推**。
|
|
8010
|
+
🔴 引擎**只解析 \`owl:imports\`**;写 \`sr:imports\` **不会被读**,而且 \`check\` 对它**照样报全绿** ——
|
|
8011
|
+
所以别只看 \`check\` 绿就以为链上了(这正是本工具 \`check\` 要同时跑 A2 的原因)。
|
|
8012
|
+
3. 让 \`check\` 全绿(**A1 结构合法 + A2 链上基础链**)。另有一档 **A3 分层抽象树**对**每个** skill
|
|
8013
|
+
都适用(与有没有 ttl 无关)—— 见上面「分层抽象树」一节。
|
|
8014
|
+
|
|
8015
|
+
## 分层抽象树(EAP)—— 顶层说顶层的话(🔴 2026-10-12 事故沉淀,机械档 A3 查它)
|
|
8016
|
+
|
|
8017
|
+
> ⚠️ **编号消歧**:本档的 **A3** 与 v1.54.0 记录里那个**已下线的旧 A3("引用可兑现")不是同一档** ——
|
|
8018
|
+
> 旧那条是照引擎源码"意图"推断的,**实测打假后不写了**;本条是 2026-10-12 **新立**的**分层形态**档。
|
|
8019
|
+
|
|
8020
|
+
skill 不是"一个文件 + 一堆参考件",而是一棵**按抽象层排的树**:
|
|
8021
|
+
|
|
8022
|
+
\`\`\`
|
|
8023
|
+
第 0 层 SKILL.md ← 顶层:这是什么 / 边界 / 领域模型(形状)/ 永远成立的约束 / 怎么读下面
|
|
8024
|
+
第 1 层 references/*.md ← 各子树:一件事一件(细节 / 沿革 / 清单 / 逐条读数)
|
|
8025
|
+
第 2 层 各子树内的 H2/H3
|
|
8026
|
+
\`\`\`
|
|
8027
|
+
|
|
8028
|
+
四条硬规矩(**A3 查前三条**):
|
|
8029
|
+
|
|
8030
|
+
1. **顶层必须自己说顶层的话**:读 SKILL.md 就能重建「这是什么 / 骨架长什么样 / 哪些约束永远成立 /
|
|
8031
|
+
细节去哪找」。🔴 **反例(真实事故)**:把 SKILL.md 切成几 KB 的"导航层"、顶层只列文件名 ——
|
|
8032
|
+
这叫**树倒置**:顶层不再说顶层的话,读者拿不到任何上层陈述。
|
|
8033
|
+
2. **顶层不许自称"只是导航层"**(A3-2):顶层声明自己什么都不说 ≡ 顶层空缺。
|
|
8034
|
+
3. **顶层是地图**(A3-3):\`references/\` 下每一件都要**被点名**,并写清"什么时候该下潜到哪一件"。
|
|
8035
|
+
4. **一条内容只有一个家**:细节下沉时**原处删掉并留指针**;把下层的顶层陈述**上提(hoist)**时同理。
|
|
8036
|
+
⇒ 父层给**形状/抽象**、子层给**清单/细节**;父层**不复述**子层的清单,也**不含会漂的数**
|
|
8037
|
+
(版本 / 计数 / 测试基线 —— 那些留在单一真相源里)。
|
|
8038
|
+
|
|
8039
|
+
## 两层披露树的写法习惯(影响模型能不能找得到)
|
|
8040
|
+
|
|
8041
|
+
· \`references/\` 里的文件名**就是目录**:前缀数字(\`00-\`…\`90-\`)给稳定次序,名字**写成能读懂的一句**。
|
|
8042
|
+
· 每个参考文件的**首个 \`#\` 标题**会被当作索引里的摘要 ⇒ **给每个 .md 写一个说人话的首标题**(别写文件名复读)。
|
|
8043
|
+
· 索引**只列不读**:正文永远留给按需 \`read\` ⇒ 别把大段内容塞进 SKILL.md(它每请求都注入)。
|
|
8044
|
+
· \`references/\` 下的**子目录**会被列成一行(名字+文件数),**不展开** ⇒ 目录名要起得让人知道里面是什么。
|
|
8045
|
+
|
|
8046
|
+
## 从领域散文到 ttl(具体步骤)
|
|
8047
|
+
|
|
8048
|
+
1. 把 SKILL.md 里的名词圈出来 → 挑出**反复出现、有身份**的那些 = 候选类型;
|
|
8049
|
+
2. 给每个类型写:\`rdfs:label\` + \`sr:rigidity\`(\`rigid\` 身份固定/\`anti-rigid\` 会变)+ 身份属性(\`sr:identity\`);
|
|
8050
|
+
3. 把动词圈出来 → 有方向、有基数的 = 关系(\`rdfs:domain\`/\`rdfs:range\` + \`sr:cardinality\`);
|
|
8051
|
+
4. 把"永远成立的话"写成公理(\`sr:axiom\`)—— **只写真能判真假的**,别写口号;
|
|
8052
|
+
5. 补 \`sr:source\`/来源(对外部主张)。
|
|
8053
|
+
**方法正文不在这里重复**:见 \`praxis ontology\`(本体工程方法群:四组判据 M1–M10/L1–L5/D1–D8/V1–V4、OEPE 回路、反模式)。
|
|
8054
|
+
|
|
8055
|
+
## 优化回路(改完之后再走一遍)
|
|
8056
|
+
|
|
8057
|
+
\`\`\`
|
|
8058
|
+
serenity-skill-creator index <skill> ← 先看模型到底会看到什么(露没露、顺序对不对)
|
|
8059
|
+
serenity-skill-creator check <skill> ← A3 分层(顶层非桩 / 不自述导航 / 点名各子树)+ A1 结构 + A2 链上
|
|
8060
|
+
(机械,必须全绿)
|
|
8061
|
+
serenity-skill-creator review <skill> ← C 档:文字与 ttl 逐条的合规问题清单(要人在环答)
|
|
8062
|
+
(改 SKILL.md / skill.ttl / references)
|
|
8063
|
+
↑__________________________ 回到第一步
|
|
8064
|
+
\`\`\`
|
|
8065
|
+
|
|
8066
|
+
**判据**:改完**必须重跑** \`check\` —— 本仓的教训是"检查没跑"和"检查通过"在输出上必须分得开;
|
|
8067
|
+
\`review\` 出的是**问题清单**(半机械、要人回答),**不是**通过/不通过。
|
|
8068
|
+
`;
|
|
8069
|
+
function renderText$5(value) {
|
|
8070
|
+
return [{
|
|
8071
|
+
type: "text",
|
|
8072
|
+
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
8073
|
+
}];
|
|
8074
|
+
}
|
|
8075
|
+
/** 顶层 skill 名:优先 `.serenity` 记号文件(CCC 的权威语义),否则缺省 */
|
|
8076
|
+
function readTopSkill(root) {
|
|
8077
|
+
try {
|
|
8078
|
+
const marker = join(root, ".serenity");
|
|
8079
|
+
if (existsSync(marker)) {
|
|
8080
|
+
const name = readFileSync(marker, "utf-8").trim();
|
|
8081
|
+
if (name !== "") return name;
|
|
8082
|
+
}
|
|
8083
|
+
} catch {}
|
|
8084
|
+
return DEFAULT_TOP_SKILL;
|
|
8085
|
+
}
|
|
8086
|
+
/** skill 目录(容器内相对路径 → 绝对);名字先过安全门 */
|
|
8087
|
+
function skillDirOf(root, name) {
|
|
8088
|
+
if (!isSafeSkillName(name)) throw new Error(`serenity-skill-creator: 非法 skill 名 "${name}"(只允许简单目录名,不许含 / 或 ..)`);
|
|
8089
|
+
return resolveInside(root, join(".opencode", "skills", name));
|
|
8090
|
+
}
|
|
8091
|
+
/** 从 skill 目录算到基础链的相对路径(**由本工具算,不靠人肉推**) */
|
|
8092
|
+
function baseChainImport(root, skillDir) {
|
|
8093
|
+
const top = readTopSkill(root);
|
|
8094
|
+
const base = join(root, ".opencode", "skills", top, "references", BASE_CHAIN_FILE);
|
|
8095
|
+
return relative(skillDir, base).split("\\").join("/");
|
|
8096
|
+
}
|
|
8097
|
+
/** 列出 CCC 的全部 skill 目录名(有 SKILL.md 的一级目录) */
|
|
8098
|
+
function listSkillNames(root) {
|
|
8099
|
+
const skillsRoot = join(root, ".opencode", "skills");
|
|
8100
|
+
if (!existsSync(skillsRoot)) return [];
|
|
8101
|
+
return readdirSync(skillsRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync(join(skillsRoot, e.name, "SKILL.md"))).map((e) => e.name).sort();
|
|
8102
|
+
}
|
|
8103
|
+
/** `create` 的脚手架内容 */
|
|
8104
|
+
function scaffoldFiles(name, importPath) {
|
|
8105
|
+
return {
|
|
8106
|
+
"SKILL.md": `---
|
|
8107
|
+
name: ${name}
|
|
8108
|
+
description: (一句话说清这个 skill 管什么、什么时候该加载它)
|
|
8109
|
+
---
|
|
8110
|
+
|
|
8111
|
+
# Skill: ${name}
|
|
8112
|
+
|
|
8113
|
+
> 一句话身份:本 skill 是<谁的><什么面>。
|
|
8114
|
+
> 🔴 **本文件是第 0 层(顶层)**:读完它就能重建「这是什么 / 骨架长什么样 / 哪些约束永远成立 / 细节去哪找」;
|
|
8115
|
+
> **细节不进正文**,按 §5 的树按需 \`read\` \`${REFERENCE_DIR}/\` 各子树。
|
|
8116
|
+
|
|
8117
|
+
## 1. 身份与边界
|
|
8118
|
+
|
|
8119
|
+
| 项 | 内容 |
|
|
8120
|
+
|---|---|
|
|
8121
|
+
| **是什么** | (一句话) |
|
|
8122
|
+
| **管** | (本 skill 负责的领域) |
|
|
8123
|
+
| **不管** | (越界的事去哪 —— 指名上下游 skill) |
|
|
8124
|
+
|
|
8125
|
+
## 2. 用途与触发条件
|
|
8126
|
+
|
|
8127
|
+
**用途**:(把"什么是这里最该一次读全的"写清)
|
|
8128
|
+
|
|
8129
|
+
**何时加载**(任一命中):
|
|
8130
|
+
|
|
8131
|
+
- (具体场景 1)
|
|
8132
|
+
- (具体场景 2)
|
|
8133
|
+
|
|
8134
|
+
## 3. 领域模型(形状)
|
|
8135
|
+
|
|
8136
|
+
(这个领域的**顶层结构**:有哪几类东西、什么先于什么、什么永远成立。
|
|
8137
|
+
🔴 **只给形状,不给清单** —— 清单会漂,且只该住在一处;有本体时真相源是同级 \`${SKILL_ONTOLOGY_FILE}\`。)
|
|
8138
|
+
|
|
8139
|
+
## 4. 永远成立的约束
|
|
8140
|
+
|
|
8141
|
+
1. (违反了就算缺陷的规则,一条一行;每条给一句"为什么")
|
|
8142
|
+
2. (会漂的值**不写死** —— 版本 / 计数 / 基线一律指向它们的单一真相源)
|
|
8143
|
+
|
|
8144
|
+
## 5. 抽象树:怎么读这个 skill
|
|
8145
|
+
|
|
8146
|
+
第 0 层 = 本文件;第 1 层 = \`${REFERENCE_DIR}/\` 各子树。**建一件就补一行**(顶层是地图):
|
|
8147
|
+
|
|
8148
|
+
| 子树 | 覆盖什么 | 什么时候下潜 |
|
|
8149
|
+
|---|---|---|
|
|
8150
|
+
| \`${REFERENCE_DIR}/00-xxx.md\` | (覆盖范围) | (触发场景) |
|
|
8151
|
+
|
|
8152
|
+
> 参考件**首个 \`#\` 标题**会被当作第二层索引里的摘要 ⇒ 给每个 .md 写一个说人话的首标题。
|
|
8153
|
+
|
|
8154
|
+
## 相关技能
|
|
8155
|
+
|
|
8156
|
+
(上下游 skill:谁先于本 skill、本 skill 产出给谁)
|
|
8157
|
+
`,
|
|
8158
|
+
[SKILL_ONTOLOGY_FILE]: `@prefix sr: <http://home-serenity/ontology#> .
|
|
8159
|
+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
8160
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
8161
|
+
@prefix : <http://home-serenity/ontology/${name}#> .
|
|
8162
|
+
|
|
8163
|
+
# 令 1.3:**必须**链上基础链。形式 = owl:imports <相对路径>(相对本文件所在目录解析)。
|
|
8164
|
+
# 🔴 本行由 serenity-skill-creator 的 create 计算,别改成 sr:imports、也别手推路径。
|
|
8165
|
+
:onto a owl:Ontology ;
|
|
8166
|
+
sr:name "${name}" ;
|
|
8167
|
+
owl:versionInfo "0.1.0" ;
|
|
8168
|
+
owl:imports <${importPath}> .
|
|
8169
|
+
|
|
8170
|
+
# ── 类型(把领域里"有身份、会反复被提到"的名词放这里)──────────────
|
|
8171
|
+
:Thing a owl:Class ;
|
|
8172
|
+
rdfs:label "Thing" ;
|
|
8173
|
+
rdfs:comment "(一句话说清这类东西是什么)" ;
|
|
8174
|
+
sr:rigidity sr:rigid ;
|
|
8175
|
+
sr:identity ("thingId") ;
|
|
8176
|
+
sr:property [ sr:name "thingId" ; sr:type "string" ; sr:cardinality "exactly 1" ] .
|
|
8177
|
+
`
|
|
8178
|
+
};
|
|
8179
|
+
}
|
|
8180
|
+
function createSkillCreatorTool() {
|
|
8181
|
+
return defineTool({
|
|
8182
|
+
name: "serenity-skill-creator",
|
|
8183
|
+
description: "Creator for serenity-skills (this container's enhanced skill standard): write and optimize ontology-modeled skills. Subcommands: guide (the authoring/optimization method — read this first when creating or reworking a skill) / create <name> (scaffold a skill dir: SKILL.md + skill.ttl + references/, with the base-chain import path computed) / check <name> (A3 layering tier — applies to EVERY skill: the top layer must exist, must not be a bare nav stub, and must name every references/*.md; plus A1 structural + A2 base-chain for modeled skills) / review <name> (C tier: axiom-vs-text compliance question list, half-mechanical — a human/LLM answers it) / index <name> (preview the second-layer resource list the model will actually see) / status (whole container: which skills are modeled, which are plain). A skill is \"modeled\" iff it has a file named exactly skill.ttl next to SKILL.md; a plain skill (no skill.ttl) is NOT an anomaly — it simply has no ontology. Only `create` writes, and it refuses to touch an existing skill directory.",
|
|
8184
|
+
parameters: {
|
|
8185
|
+
action: {
|
|
8186
|
+
type: "string",
|
|
8187
|
+
enum: [...ACTIONS$2],
|
|
8188
|
+
description: "guide / create / check / review / index / status."
|
|
8189
|
+
},
|
|
8190
|
+
name: {
|
|
8191
|
+
type: "string",
|
|
8192
|
+
description: "Skill name (required for create/check/review/index; must be a simple directory name)."
|
|
8193
|
+
}
|
|
8194
|
+
},
|
|
8195
|
+
output: {
|
|
8196
|
+
schema: { type: "string" },
|
|
8197
|
+
render: (_args, value) => renderText$5(value)
|
|
8198
|
+
},
|
|
8199
|
+
async execute(args, exec) {
|
|
8200
|
+
const root = cccRootForExec(exec);
|
|
8201
|
+
if (!root) throw new Error(NO_CCC_FROM_AGENT_CWD);
|
|
8202
|
+
const action = typeof args.action === "string" ? args.action.trim() : "";
|
|
8203
|
+
if (!ACTIONS$2.includes(action)) throw new Error(`serenity-skill-creator: \`action\` must be one of ${ACTIONS$2.join(" / ")} (got: ${action === "" ? "(missing)" : action})`);
|
|
8204
|
+
const name = typeof args.name === "string" ? args.name.trim() : "";
|
|
8205
|
+
const needName = () => {
|
|
8206
|
+
if (name === "") throw new Error(`serenity-skill-creator: \`name\` is required for \`${action}\``);
|
|
8207
|
+
return name;
|
|
8208
|
+
};
|
|
8209
|
+
if (action === "guide") return SKILL_CREATOR_GUIDE;
|
|
8210
|
+
if (action === "status") {
|
|
8211
|
+
const names = listSkillNames(root);
|
|
8212
|
+
if (names.length === 0) return `(\`.opencode/skills\` 下没有带 SKILL.md 的 skill)`;
|
|
8213
|
+
const modeled = [];
|
|
8214
|
+
const plain = [];
|
|
8215
|
+
const broken = [];
|
|
8216
|
+
for (const n of names) {
|
|
8217
|
+
const dir = join(root, ".opencode", "skills", n);
|
|
8218
|
+
if (!existsSync(join(dir, "skill.ttl"))) {
|
|
8219
|
+
plain.push(n);
|
|
8220
|
+
continue;
|
|
8221
|
+
}
|
|
8222
|
+
const insp = inspectSkillOntology(dir, { root });
|
|
8223
|
+
if (insp.state === "ok") modeled.push(n);
|
|
8224
|
+
else broken.push(formatSkillOntologyInspection(n, insp));
|
|
8225
|
+
}
|
|
8226
|
+
const layeringFail = [];
|
|
8227
|
+
for (const n of names) {
|
|
8228
|
+
const rep = analyzeSkillLayering(join(root, ".opencode", "skills", n));
|
|
8229
|
+
if (!rep.ok) layeringFail.push(`${n}(${rep.findings.map((f) => f.code).join("/")})`);
|
|
8230
|
+
}
|
|
8231
|
+
return [
|
|
8232
|
+
`serenity-skill 一览(顶层 skill = ${readTopSkill(root)})`,
|
|
8233
|
+
`\n🧬 **已建模且通过** ${modeled.length} 个` + (modeled.length > 0 ? `:${modeled.join(", ")}` : ""),
|
|
8234
|
+
`\n⬜ **普通 skill(未建模)** ${plain.length} 个`,
|
|
8235
|
+
" ⚠️ 这**不是异常**,只是少一项能力(J9:增强对普通 skill 完全向后兼容)。",
|
|
8236
|
+
`\n📐 **A3 分层档**:${names.length - layeringFail.length} / ${names.length} 个通过` + (layeringFail.length > 0 ? `;未过 ${layeringFail.length} 个(**存量,非本次引入**):${layeringFail.slice(0, 12).join(", ")}${layeringFail.length > 12 ? ` …还有 ${layeringFail.length - 12} 个` : ""}
|
|
8237
|
+
逐个看:\`check <name>\`(判据与修法见 \`guide\` 的「分层抽象树」一节)` : "(全部通过)"),
|
|
8238
|
+
broken.length > 0 ? `\n❌ **建模了但检查没跑成** ${broken.length} 个:\n${broken.join("\n")}` : ""
|
|
8239
|
+
].filter((s) => s !== "").join("\n");
|
|
8240
|
+
}
|
|
8241
|
+
const dir = skillDirOf(root, needName());
|
|
8242
|
+
if (action === "create") {
|
|
8243
|
+
if (existsSync(dir)) throw new Error(`serenity-skill-creator: 目录已存在,拒绝覆盖 → ${relative(root, dir)}(改造既有 skill 请直接编辑它的 SKILL.md/skill.ttl;本工具不代劳,免得静默盖掉你的东西)`);
|
|
8244
|
+
const files = scaffoldFiles(name, baseChainImport(root, dir));
|
|
8245
|
+
mkdirSync(join(dir, REFERENCE_DIR), { recursive: true });
|
|
8246
|
+
for (const [rel, content] of Object.entries(files)) writeFileSync(join(dir, rel), content, "utf-8");
|
|
8247
|
+
return [
|
|
8248
|
+
`✅ 已生成 skill 脚手架:${relative(root, dir)}`,
|
|
8249
|
+
"",
|
|
8250
|
+
...Object.keys(files).map((f) => ` · ${f}`),
|
|
8251
|
+
` · ${REFERENCE_DIR}/(空目录)`,
|
|
8252
|
+
"",
|
|
8253
|
+
`🔗 skill.ttl 已链上基础链(相对路径由本工具算):\`${baseChainImport(root, dir)}\``,
|
|
8254
|
+
"👉 下一步:填 SKILL.md 的用途/触发条件 → 跑 `check` 让它全绿 → 需要时 `index` 看模型会看到什么。",
|
|
8255
|
+
" (方法论:`serenity-skill-creator guide`;本体工程方法群:`praxis ontology`。)"
|
|
8256
|
+
].join("\n");
|
|
8257
|
+
}
|
|
8258
|
+
if (!existsSync(dir)) throw new Error(`serenity-skill-creator: 找不到 skill 目录 → ${relative(root, dir)}(名字拼错?)`);
|
|
8259
|
+
if (action === "index") {
|
|
8260
|
+
const idx = buildResourceIndex(dir);
|
|
8261
|
+
return idx === "" ? `${name}:**无参考件可列**(无 \`${REFERENCE_DIR}/\` 且无 \`${SKILL_ONTOLOGY_FILE}\`)⇒ 注入时**不产出**第二层块。` : `以下就是模型在提示词里**真正会看到**的第二层(只有清单,没有正文):\n\n${idx}`;
|
|
8262
|
+
}
|
|
8263
|
+
if (action === "check") {
|
|
8264
|
+
const layering = formatLayeringReport(name, analyzeSkillLayering(dir));
|
|
8265
|
+
const insp = inspectSkillOntology(dir, { root });
|
|
8266
|
+
const body = formatSkillOntologyInspection(name, insp);
|
|
8267
|
+
if (insp.state === "none") return [
|
|
8268
|
+
layering,
|
|
8269
|
+
"",
|
|
8270
|
+
body,
|
|
8271
|
+
"",
|
|
8272
|
+
"(A1/A2 是**本体档**,未建模 ⇒ 不适用,这不是异常(J9)。要建模:`guide` 的「三个动作」,或 `create` 生成带骨架的 skill。)"
|
|
8273
|
+
].join("\n");
|
|
8274
|
+
return insp.state === "ok" ? `${layering}\n\n${body}\n\n👉 A3 + A1/A2 都过了。语义档(文字是否真遵守模型/公理)走 \`review\`。` : `${layering}\n\n${body}\n\n👉 修完**重跑本命令**(本仓教训:「检查没跑」与「检查通过」必须分得开)。`;
|
|
8275
|
+
}
|
|
8276
|
+
const ttl = join(dir, SKILL_ONTOLOGY_FILE);
|
|
8277
|
+
if (!existsSync(ttl)) return formatSkillOntologyInspection(name, inspectSkillOntology(dir, { root })) + `\n\n(\`review\` 需要 \`${SKILL_ONTOLOGY_FILE}\`;普通 skill 没有可审的模型 —— 这不是错误。)`;
|
|
8278
|
+
const r = runOntologyCli([
|
|
8279
|
+
"compliance",
|
|
8280
|
+
ttl,
|
|
8281
|
+
"--skills",
|
|
8282
|
+
join(root, ".opencode", "skills")
|
|
8283
|
+
], { cwd: root });
|
|
8284
|
+
return [
|
|
8285
|
+
`[review ${name}] 引擎 compliance ${r.code === 0 ? "✓" : "✗"} exit ${r.code}`,
|
|
8286
|
+
"",
|
|
8287
|
+
r.stdout.trim() !== "" ? r.stdout.trimEnd() : "(引擎无输出)",
|
|
8288
|
+
r.code !== 0 && r.stderr.trim() !== "" ? `\n${r.stderr.trimEnd()}` : "",
|
|
8289
|
+
"",
|
|
8290
|
+
"⚠️ 这是 **C 档(半机械)**:它给的是**逐条要回答的问题清单**,不是\"通过/不通过\"。",
|
|
8291
|
+
" 请**逐条回答**;发现文字与模型不符 ⇒ 改文字或改 ttl,然后重跑 `check`。"
|
|
8292
|
+
].filter((s) => s !== "").join("\n");
|
|
8293
|
+
}
|
|
8294
|
+
});
|
|
8295
|
+
}
|
|
8296
|
+
//#endregion
|
|
7721
8297
|
//#region src/handyman-preset-inherit.ts
|
|
7722
8298
|
/**
|
|
7723
8299
|
* 解析 handyman worker 从父 agent 继承的 preset,并组装创建 setup 钩子。
|
|
@@ -23852,6 +24428,7 @@ function apply(ctx, config) {
|
|
|
23852
24428
|
ctx.tools.register(msmTool);
|
|
23853
24429
|
ctx.tools.register(praxisTool);
|
|
23854
24430
|
ctx.tools.register(createOntologyTool());
|
|
24431
|
+
ctx.tools.register(createSkillCreatorTool());
|
|
23855
24432
|
ctx.tools.register(createHandymanTool(ctx));
|
|
23856
24433
|
ctx.tools.register(localstoreTool);
|
|
23857
24434
|
ctx.tools.register(createContainerAdminTool(ctx));
|
package/lib/invariant.d.ts
CHANGED
|
@@ -28,5 +28,11 @@ export declare function verifyToolConsistency(manifestPath: string, registeredTo
|
|
|
28
28
|
* v1.52(**D121**,owner 2026-10-11 裁「引擎搬进 ACC」):新增 **`ontology`** —— 本体工程
|
|
29
29
|
* **引擎**(原为 CCC 的 MSM `ontology-engineering`,逐字搬迁;见 `src/ontology/engine.ts` 文件头
|
|
30
30
|
* 的三处改写)。它与 `praxis ontology`(**方法文本**)是**同一领域的两个面**:一个说、一个跑 ⇒ **13**。
|
|
31
|
+
*
|
|
32
|
+
* v1.54(**serenity-skill P3**,owner 2026-10-11 令「要一个 serenity-skill-creator 的 tool」):
|
|
33
|
+
* 新增 **`serenity-skill-creator`** —— 增强型 skill 标准(serenity-skill)的 creator:
|
|
34
|
+
* `guide`(方法文本)/`create`(脚手架;**基础链的相对路径由它算**)/`check`(调 P2 的 A1/A2)/
|
|
35
|
+
* `review`(C 档,调引擎 `compliance`)/`index`(调 P1 的生成器)/`status` ⇒ **14**。
|
|
36
|
+
* 同规格:**条件可见与否不影响这里列它**(本常量 = "插件**具备**的工具面")。
|
|
31
37
|
*/
|
|
32
|
-
export declare const REGISTERED_TOOLS: readonly ["container_fs", "container_trajectory", "dashboard", "container_git", "msm", "praxis", "handyman", "localstore", "container_admin", "im-bridge", "acc-diag", "diagram", "ontology"];
|
|
38
|
+
export declare const REGISTERED_TOOLS: readonly ["container_fs", "container_trajectory", "dashboard", "container_git", "msm", "praxis", "handyman", "localstore", "container_admin", "im-bridge", "acc-diag", "diagram", "ontology", "serenity-skill-creator"];
|
package/lib/invariant.js
CHANGED
|
@@ -49,6 +49,12 @@ function verifyToolConsistency(manifestPath, registeredTools) {
|
|
|
49
49
|
* v1.52(**D121**,owner 2026-10-11 裁「引擎搬进 ACC」):新增 **`ontology`** —— 本体工程
|
|
50
50
|
* **引擎**(原为 CCC 的 MSM `ontology-engineering`,逐字搬迁;见 `src/ontology/engine.ts` 文件头
|
|
51
51
|
* 的三处改写)。它与 `praxis ontology`(**方法文本**)是**同一领域的两个面**:一个说、一个跑 ⇒ **13**。
|
|
52
|
+
*
|
|
53
|
+
* v1.54(**serenity-skill P3**,owner 2026-10-11 令「要一个 serenity-skill-creator 的 tool」):
|
|
54
|
+
* 新增 **`serenity-skill-creator`** —— 增强型 skill 标准(serenity-skill)的 creator:
|
|
55
|
+
* `guide`(方法文本)/`create`(脚手架;**基础链的相对路径由它算**)/`check`(调 P2 的 A1/A2)/
|
|
56
|
+
* `review`(C 档,调引擎 `compliance`)/`index`(调 P1 的生成器)/`status` ⇒ **14**。
|
|
57
|
+
* 同规格:**条件可见与否不影响这里列它**(本常量 = "插件**具备**的工具面")。
|
|
52
58
|
*/
|
|
53
59
|
const REGISTERED_TOOLS = [
|
|
54
60
|
"container_fs",
|
|
@@ -63,7 +69,8 @@ const REGISTERED_TOOLS = [
|
|
|
63
69
|
"im-bridge",
|
|
64
70
|
"acc-diag",
|
|
65
71
|
"diagram",
|
|
66
|
-
"ontology"
|
|
72
|
+
"ontology",
|
|
73
|
+
"serenity-skill-creator"
|
|
67
74
|
];
|
|
68
75
|
//#endregion
|
|
69
76
|
export { REGISTERED_TOOLS, verifyToolConsistency };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** 顶层正文(去掉 frontmatter 与生成的导航块)的**下限**:低于它 = 顶层只是桩 */
|
|
2
|
+
export declare const TOP_LAYER_MIN_BYTES = 2048;
|
|
3
|
+
/** 生成块(第二层清单)的哨兵 —— 它由 `skill-lint split` / 生成器产出,**不算顶层正文** */
|
|
4
|
+
export declare const NAV_BLOCK_BEGIN = "<!-- skill-lint:split-nav -->";
|
|
5
|
+
export declare const NAV_BLOCK_END = "<!-- /skill-lint:split-nav -->";
|
|
6
|
+
/**
|
|
7
|
+
* "本文件只是导航层"这类**自述** —— 它是树倒置的**直接症状**(顶层自己声明什么都不说)。
|
|
8
|
+
* 正常 skill 不会写这句话 ⇒ 这条判据**几乎不会假阳**。
|
|
9
|
+
*/
|
|
10
|
+
export declare const NAV_SELF_DESCRIPTION: RegExp;
|
|
11
|
+
export interface LayeringFinding {
|
|
12
|
+
code: 'A3-1' | 'A3-2' | 'A3-3';
|
|
13
|
+
detail: string;
|
|
14
|
+
}
|
|
15
|
+
export interface LayeringReport {
|
|
16
|
+
/** 是否有 SKILL.md */
|
|
17
|
+
hasSkillMd: boolean;
|
|
18
|
+
/** 顶层正文字节(已扣 frontmatter 与导航块) */
|
|
19
|
+
topBodyBytes: number;
|
|
20
|
+
/** 被扣掉的导航块字节(用于自证扣了什么) */
|
|
21
|
+
navBlockBytes: number;
|
|
22
|
+
/** A3-2 命中行(原文,截断展示) */
|
|
23
|
+
navSelfDescriptions: string[];
|
|
24
|
+
/** references/ 一级下的 .md 名单 */
|
|
25
|
+
referenceDocs: string[];
|
|
26
|
+
/** A3-3:顶层全文里**没被点名**的参考件 */
|
|
27
|
+
notNamedInTop: string[];
|
|
28
|
+
findings: LayeringFinding[];
|
|
29
|
+
ok: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** references/ **一级**下的 .md(子目录不展开 —— 与第二层披露同口径:子目录按"名字+文件数"入表) */
|
|
32
|
+
export declare function listReferenceDocs(skillDir: string): string[];
|
|
33
|
+
/**
|
|
34
|
+
* 对一个 skill 目录跑 A3 档。
|
|
35
|
+
* 🔴 三条判据各自**独立**(一条不过不掩盖另一条),全部机械、全部可复跑。
|
|
36
|
+
*/
|
|
37
|
+
export declare function analyzeSkillLayering(skillDir: string): LayeringReport;
|
|
38
|
+
/** 渲染给人/模型看的一段(工具面直接用) */
|
|
39
|
+
export declare function formatLayeringReport(skillName: string, r: LayeringReport): string;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skill-ontology.ts — **本体预留口**的发现与检查(纯逻辑,零 DSH 依赖)
|
|
3
|
+
*
|
|
4
|
+
* 设计真相源:`docs/serenity-skill-design.md` §4.1(四个动作)/§5.1-5.2(固定名与骨架)/§6(J1~J3)。
|
|
5
|
+
* 一句话:**一个 skill 若完成了本体建模,就在自己目录放固定名 `skill.ttl`**;
|
|
6
|
+
* 本模块回答三件事:**有没有**(发现)/**合不合法**(A 档)/**引用能不能兑现**(B 档)。
|
|
7
|
+
*
|
|
8
|
+
* 🔴 只认**固定名** `<skillDir>/skill.ttl`(owner 裁决 Q6:静默、CCC 自行修正):
|
|
9
|
+
* `references/*-domain.ttl` 这类**旧名一律当普通参考件** —— 不特殊对待、**不告警**、**不代读**。
|
|
10
|
+
* 🔴 「无 `skill.ttl`」**不是错误也不是告警**(裁决 Q6 / 判据 J9):
|
|
11
|
+
* 那是**普通 skill**,全程无声 —— 增强必须对普通 skill 完全向后兼容。
|
|
12
|
+
*
|
|
13
|
+
* ── 两档判据(**两档都锚在引擎自己的输出上**,我们不另写一遍 Turtle 解析)──────────
|
|
14
|
+
* **A1 结构合法**:`check --level strict` exit 0(引擎的 M1–M10)
|
|
15
|
+
* **A2 链上基础链**:`explain` exit 0 **且**报告里有 `Imports:` 行
|
|
16
|
+
*
|
|
17
|
+
* 🔴 **为什么必须有 A2(这是本模块存在的理由,且是实测逼出来的)**:
|
|
18
|
+
* **A1 绿 ≠ 链上了**。实测对照(同一天、同一引擎):
|
|
19
|
+
* · `home-rhetoric/references/home-rhetoric-domain.ttl`(写的是 `sr:imports`)
|
|
20
|
+
* ⇒ `check --level strict` = **✓ exit 0 · All checks passed**,而 `explain` **没有 `Imports:` 行**;
|
|
21
|
+
* · 注册表(写的是 `owl:imports`)⇒ `explain` **打印 `Imports: ccc-ontology.ttl`**(探针不瞎)。
|
|
22
|
+
* ⇒ **「检查全绿」与「链上了」是两件事**;只看 A1 就会把 14 份没链上的本体判成合格。
|
|
23
|
+
*
|
|
24
|
+
* 🟢 **A3(引用可兑现)**:**本模块刻意不做** —— 引擎已经做了,而且是**响亮的**。实测两条:
|
|
25
|
+
* · `check`:import 目标不存在 ⇒ **`ERR [M10] Ontology file not found: <abs>`**、**exit 1**;
|
|
26
|
+
* · `explain`:同一个文件 ⇒ **exit 2 · `ONTO_FILE_NOT_FOUND`**。
|
|
27
|
+
* ⇒ 再写一遍就是**同一件事的第二实现**(本仓的老账:"先问谁已经做了")。
|
|
28
|
+
* 🔴 但**起因值得记**:我原本以为引擎对缺失 import 是**静默**的(照源码 1687 行那句 "Fall back to
|
|
29
|
+
* loading just this file (no imports)" 推断)—— **实测把这条推断打假了**(两条路都响亮报错)。
|
|
30
|
+
* ⇒ 又一次「**照源码的意图抄 ≠ 引擎实际怎么行为**」(D119 同族)。
|
|
31
|
+
*/
|
|
32
|
+
/** 三态(**互可分辨**是硬要求,判据 J3) */
|
|
33
|
+
export type SkillOntologyState = 'none' | 'ok' | 'error';
|
|
34
|
+
export interface SkillOntologyCheck {
|
|
35
|
+
id: 'A1' | 'A2';
|
|
36
|
+
ok: boolean;
|
|
37
|
+
detail: string;
|
|
38
|
+
}
|
|
39
|
+
export interface SkillOntologyInspection {
|
|
40
|
+
state: SkillOntologyState;
|
|
41
|
+
/** 容器内相对路径(未建模 ⇒ '');调用方负责把 root 去掉 */
|
|
42
|
+
rel: string;
|
|
43
|
+
modeled: boolean;
|
|
44
|
+
checks: SkillOntologyCheck[];
|
|
45
|
+
/** A2/A3 用:引擎报告的 import 目标(原样,未解析) */
|
|
46
|
+
imports: string[];
|
|
47
|
+
}
|
|
48
|
+
export interface InspectOptions {
|
|
49
|
+
/** CCC 根(引擎的解析基准) */
|
|
50
|
+
root: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 检查一个 skill 的预留口。
|
|
54
|
+
*
|
|
55
|
+
* @param skillDir skill 目录(绝对路径)
|
|
56
|
+
* @param opts.root CCC 根(引擎基准;**必须是 CCC 根**,否则引擎找不到容器级资产)
|
|
57
|
+
* @returns 三态 + 逐档判据
|
|
58
|
+
*/
|
|
59
|
+
export declare function inspectSkillOntology(skillDir: string, opts: InspectOptions): SkillOntologyInspection;
|
|
60
|
+
/**
|
|
61
|
+
* 渲染三态报告(**三态必须互可分辨** —— 判据 J3;措辞刻意不许糊)。
|
|
62
|
+
*
|
|
63
|
+
* 为什么单独一个函数:`check` 的呈现(P3 的工具)与测试看的是**同一段文本**,
|
|
64
|
+
* 两处各写一遍必然漂移(本仓"路径写两遍必漂"的既有教训)。
|
|
65
|
+
*/
|
|
66
|
+
export declare function formatSkillOntologyInspection(skillName: string, insp: SkillOntologyInspection): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 《serenity-skill 编写与优化方法》(`guide` 印它)。
|
|
3
|
+
*
|
|
4
|
+
* 边界(D23):**本文只是方法**;判据在代码里(P1/P2),语义审查在引擎的 `compliance` 里 ——
|
|
5
|
+
* 三者**不许互相抄**(同一件事写两遍必然漂移)。
|
|
6
|
+
*/
|
|
7
|
+
export declare const SKILL_CREATOR_GUIDE = "# serenity-skill \u2014\u2014 \u589E\u5F3A\u578B skill \u7684\u7F16\u5199\u4E0E\u4F18\u5316\u65B9\u6CD5\n\n## \u4F55\u65F6\u8BE5\u5EFA\u6A21\uFF0C\u4F55\u65F6\u4E0D\u8BE5\n\n**\u8BE5\u5EFA\u6A21**\uFF1A\u8BE5\u9886\u57DF\u6709**\u7A33\u5B9A\u7684\u7C7B\u578B\u4E0E\u5173\u7CFB**\uFF08\u8C01\u662F\u8C01\u3001\u4E00\u5BF9\u591A\u3001\u4EC0\u4E48\u5FC5\u987B\u5148\u4E8E\u4EC0\u4E48\uFF09\uFF0C\u4E14\u8FD9\u4E9B\u7ED3\u6784**\u4F1A\u53CD\u590D\u88AB\u5F15\u7528**\n\uFF08\u6392\u9519\u3001\u5BA1\u67E5\u3001\u5199\u6587\u6863\u90FD\u8981\u56DE\u53BB\u770B\u5B83\uFF09\u2014\u2014 \u5178\u578B\u5982\"\u4E00\u4E2A skill \u7BA1\u54EA\u4E9B\u8D44\u4EA7\u3001\u8D44\u4EA7\u4E4B\u95F4\u4EC0\u4E48\u7EA6\u675F\"\u3002\n\n**\u4E0D\u8BE5\u5EFA\u6A21**\uFF08**\u4E0D\u5EFA\u6A21\u4E0D\u662F\u7F3A\u9677**\uFF09\uFF1A\n\u00B7 \u7EAF\u64CD\u4F5C\u89C4\u7A0B\uFF08\"\u5148\u8DD1 A \u518D\u8DD1 B\"\uFF09\u2014\u2014 \u90A3\u662F SKILL.md \u7684\u6B63\u6587\uFF0C\u7ED9\u5B83\u5957\u672C\u4F53\u53EA\u662F\u6362\u4E2A\u5730\u65B9\u5199\u6563\u6587\uFF1B\n\u00B7 \u4E00\u6B21\u6027/\u77ED\u547D\u7684\u4E8B\uFF1B\n\u00B7 \u4F60\u81EA\u5DF1\u90FD\u8FD8\u8BF4\u4E0D\u6E05\u9886\u57DF\u91CC\u5230\u5E95\u6709\u54EA\u51E0\u7C7B\u4E1C\u897F\u65F6 \u2014\u2014 **\u5148\u5199\u6563\u6587\uFF0C\u7B49\u7ED3\u6784\u6D6E\u73B0\u518D\u5EFA\u6A21**\uFF08\u5EFA\u6A21\u4E0D\u662F\u5165\u573A\u5238\uFF09\u3002\n\n## \u4E09\u4E2A\u52A8\u4F5C\uFF08\u505A\u5B8C\u5373\"\u5DF2\u5EFA\u6A21\"\uFF09\n\n1. \u5728 skill \u76EE\u5F55\u653E**\u56FA\u5B9A\u540D** `skill.ttl`\uFF08\u4E0E `SKILL.md` \u5E76\u5217\uFF09\u2014\u2014 \u8FD9\u662F**\u552F\u4E00**\u88AB\u8BA4\u7684\u540D\u5B57\u3002\n \u26A0\uFE0F \u65E7\u5199\u6CD5 `references/<name>-domain.ttl` **\u4E0D\u88AB\u8BA4**\uFF08\u9759\u9ED8\u5F53\u666E\u901A\u53C2\u8003\u4EF6\uFF09\uFF1A\u4E0D\u662F\u62A5\u9519\uFF0C\u662F**\u6CA1\u6709\u4EBA\u4F1A\u770B\u5B83**\u3002\n2. `skill.ttl` **\u5FC5\u987B\u94FE\u4E0A\u57FA\u7840\u94FE**\uFF0C\u5F62\u5F0F\u662F `owl:imports <\u76F8\u5BF9\u8DEF\u5F84>`\uFF1A\n ```turtle\n :onto a owl:Ontology ;\n sr:name \"<skill-name>\" ;\n owl:versionInfo \"0.1.0\" ;\n owl:imports <../home-serenity/references/serenity-skills-ontology.ttl> .\n ```\n \uD83D\uDD34 **\u76F8\u5BF9\u8DEF\u5F84\u7531 `create` \u7B97**\uFF08\u4ECE\u672C skill \u76EE\u5F55\u6307\u5411\u9876\u5C42 skill \u7684 references/\uFF09\uFF0C**\u4E0D\u8981\u4EBA\u8089\u63A8**\u3002\n \uD83D\uDD34 \u5F15\u64CE**\u53EA\u89E3\u6790 `owl:imports`**\uFF1B\u5199 `sr:imports` **\u4E0D\u4F1A\u88AB\u8BFB**\uFF0C\u800C\u4E14 `check` \u5BF9\u5B83**\u7167\u6837\u62A5\u5168\u7EFF** \u2014\u2014\n \u6240\u4EE5\u522B\u53EA\u770B `check` \u7EFF\u5C31\u4EE5\u4E3A\u94FE\u4E0A\u4E86\uFF08\u8FD9\u6B63\u662F\u672C\u5DE5\u5177 `check` \u8981\u540C\u65F6\u8DD1 A2 \u7684\u539F\u56E0\uFF09\u3002\n3. \u8BA9 `check` \u5168\u7EFF\uFF08**A1 \u7ED3\u6784\u5408\u6CD5 \uFF0B A2 \u94FE\u4E0A\u57FA\u7840\u94FE**\uFF09\u3002\u53E6\u6709\u4E00\u6863 **A3 \u5206\u5C42\u62BD\u8C61\u6811**\u5BF9**\u6BCF\u4E2A** skill\n \u90FD\u9002\u7528\uFF08\u4E0E\u6709\u6CA1\u6709 ttl \u65E0\u5173\uFF09\u2014\u2014 \u89C1\u4E0A\u9762\u300C\u5206\u5C42\u62BD\u8C61\u6811\u300D\u4E00\u8282\u3002\n\n## \u5206\u5C42\u62BD\u8C61\u6811\uFF08EAP\uFF09\u2014\u2014 \u9876\u5C42\u8BF4\u9876\u5C42\u7684\u8BDD\uFF08\uD83D\uDD34 2026-10-12 \u4E8B\u6545\u6C89\u6DC0\uFF0C\u673A\u68B0\u6863 A3 \u67E5\u5B83\uFF09\n\n> \u26A0\uFE0F **\u7F16\u53F7\u6D88\u6B67**\uFF1A\u672C\u6863\u7684 **A3** \u4E0E v1.54.0 \u8BB0\u5F55\u91CC\u90A3\u4E2A**\u5DF2\u4E0B\u7EBF\u7684\u65E7 A3\uFF08\"\u5F15\u7528\u53EF\u5151\u73B0\"\uFF09\u4E0D\u662F\u540C\u4E00\u6863** \u2014\u2014\n> \u65E7\u90A3\u6761\u662F\u7167\u5F15\u64CE\u6E90\u7801\"\u610F\u56FE\"\u63A8\u65AD\u7684\uFF0C**\u5B9E\u6D4B\u6253\u5047\u540E\u4E0D\u5199\u4E86**\uFF1B\u672C\u6761\u662F 2026-10-12 **\u65B0\u7ACB**\u7684**\u5206\u5C42\u5F62\u6001**\u6863\u3002\n\nskill \u4E0D\u662F\"\u4E00\u4E2A\u6587\u4EF6 \uFF0B \u4E00\u5806\u53C2\u8003\u4EF6\"\uFF0C\u800C\u662F\u4E00\u68F5**\u6309\u62BD\u8C61\u5C42\u6392\u7684\u6811**\uFF1A\n\n```\n\u7B2C 0 \u5C42 SKILL.md \u2190 \u9876\u5C42\uFF1A\u8FD9\u662F\u4EC0\u4E48 \uFF0F \u8FB9\u754C \uFF0F \u9886\u57DF\u6A21\u578B\uFF08\u5F62\u72B6\uFF09\uFF0F \u6C38\u8FDC\u6210\u7ACB\u7684\u7EA6\u675F \uFF0F \u600E\u4E48\u8BFB\u4E0B\u9762\n\u7B2C 1 \u5C42 references/*.md \u2190 \u5404\u5B50\u6811\uFF1A\u4E00\u4EF6\u4E8B\u4E00\u4EF6\uFF08\u7EC6\u8282 \uFF0F \u6CBF\u9769 \uFF0F \u6E05\u5355 \uFF0F \u9010\u6761\u8BFB\u6570\uFF09\n\u7B2C 2 \u5C42 \u5404\u5B50\u6811\u5185\u7684 H2/H3\n```\n\n\u56DB\u6761\u786C\u89C4\u77E9\uFF08**A3 \u67E5\u524D\u4E09\u6761**\uFF09\uFF1A\n\n1. **\u9876\u5C42\u5FC5\u987B\u81EA\u5DF1\u8BF4\u9876\u5C42\u7684\u8BDD**\uFF1A\u8BFB SKILL.md \u5C31\u80FD\u91CD\u5EFA\u300C\u8FD9\u662F\u4EC0\u4E48 \uFF0F \u9AA8\u67B6\u957F\u4EC0\u4E48\u6837 \uFF0F \u54EA\u4E9B\u7EA6\u675F\u6C38\u8FDC\u6210\u7ACB \uFF0F\n \u7EC6\u8282\u53BB\u54EA\u627E\u300D\u3002\uD83D\uDD34 **\u53CD\u4F8B\uFF08\u771F\u5B9E\u4E8B\u6545\uFF09**\uFF1A\u628A SKILL.md \u5207\u6210\u51E0 KB \u7684\"\u5BFC\u822A\u5C42\"\u3001\u9876\u5C42\u53EA\u5217\u6587\u4EF6\u540D \u2014\u2014\n \u8FD9\u53EB**\u6811\u5012\u7F6E**\uFF1A\u9876\u5C42\u4E0D\u518D\u8BF4\u9876\u5C42\u7684\u8BDD\uFF0C\u8BFB\u8005\u62FF\u4E0D\u5230\u4EFB\u4F55\u4E0A\u5C42\u9648\u8FF0\u3002\n2. **\u9876\u5C42\u4E0D\u8BB8\u81EA\u79F0\"\u53EA\u662F\u5BFC\u822A\u5C42\"**\uFF08A3-2\uFF09\uFF1A\u9876\u5C42\u58F0\u660E\u81EA\u5DF1\u4EC0\u4E48\u90FD\u4E0D\u8BF4 \u2261 \u9876\u5C42\u7A7A\u7F3A\u3002\n3. **\u9876\u5C42\u662F\u5730\u56FE**\uFF08A3-3\uFF09\uFF1A`references/` \u4E0B\u6BCF\u4E00\u4EF6\u90FD\u8981**\u88AB\u70B9\u540D**\uFF0C\u5E76\u5199\u6E05\"\u4EC0\u4E48\u65F6\u5019\u8BE5\u4E0B\u6F5C\u5230\u54EA\u4E00\u4EF6\"\u3002\n4. **\u4E00\u6761\u5185\u5BB9\u53EA\u6709\u4E00\u4E2A\u5BB6**\uFF1A\u7EC6\u8282\u4E0B\u6C89\u65F6**\u539F\u5904\u5220\u6389\u5E76\u7559\u6307\u9488**\uFF1B\u628A\u4E0B\u5C42\u7684\u9876\u5C42\u9648\u8FF0**\u4E0A\u63D0\uFF08hoist\uFF09**\u65F6\u540C\u7406\u3002\n \u21D2 \u7236\u5C42\u7ED9**\u5F62\u72B6/\u62BD\u8C61**\u3001\u5B50\u5C42\u7ED9**\u6E05\u5355/\u7EC6\u8282**\uFF1B\u7236\u5C42**\u4E0D\u590D\u8FF0**\u5B50\u5C42\u7684\u6E05\u5355\uFF0C\u4E5F**\u4E0D\u542B\u4F1A\u6F02\u7684\u6570**\n \uFF08\u7248\u672C \uFF0F \u8BA1\u6570 \uFF0F \u6D4B\u8BD5\u57FA\u7EBF \u2014\u2014 \u90A3\u4E9B\u7559\u5728\u5355\u4E00\u771F\u76F8\u6E90\u91CC\uFF09\u3002\n\n## \u4E24\u5C42\u62AB\u9732\u6811\u7684\u5199\u6CD5\u4E60\u60EF\uFF08\u5F71\u54CD\u6A21\u578B\u80FD\u4E0D\u80FD\u627E\u5F97\u5230\uFF09\n\n\u00B7 `references/` \u91CC\u7684\u6587\u4EF6\u540D**\u5C31\u662F\u76EE\u5F55**\uFF1A\u524D\u7F00\u6570\u5B57\uFF08`00-`\u2026`90-`\uFF09\u7ED9\u7A33\u5B9A\u6B21\u5E8F\uFF0C\u540D\u5B57**\u5199\u6210\u80FD\u8BFB\u61C2\u7684\u4E00\u53E5**\u3002\n\u00B7 \u6BCF\u4E2A\u53C2\u8003\u6587\u4EF6\u7684**\u9996\u4E2A `#` \u6807\u9898**\u4F1A\u88AB\u5F53\u4F5C\u7D22\u5F15\u91CC\u7684\u6458\u8981 \u21D2 **\u7ED9\u6BCF\u4E2A .md \u5199\u4E00\u4E2A\u8BF4\u4EBA\u8BDD\u7684\u9996\u6807\u9898**\uFF08\u522B\u5199\u6587\u4EF6\u540D\u590D\u8BFB\uFF09\u3002\n\u00B7 \u7D22\u5F15**\u53EA\u5217\u4E0D\u8BFB**\uFF1A\u6B63\u6587\u6C38\u8FDC\u7559\u7ED9\u6309\u9700 `read` \u21D2 \u522B\u628A\u5927\u6BB5\u5185\u5BB9\u585E\u8FDB SKILL.md\uFF08\u5B83\u6BCF\u8BF7\u6C42\u90FD\u6CE8\u5165\uFF09\u3002\n\u00B7 `references/` \u4E0B\u7684**\u5B50\u76EE\u5F55**\u4F1A\u88AB\u5217\u6210\u4E00\u884C\uFF08\u540D\u5B57\uFF0B\u6587\u4EF6\u6570\uFF09\uFF0C**\u4E0D\u5C55\u5F00** \u21D2 \u76EE\u5F55\u540D\u8981\u8D77\u5F97\u8BA9\u4EBA\u77E5\u9053\u91CC\u9762\u662F\u4EC0\u4E48\u3002\n\n## \u4ECE\u9886\u57DF\u6563\u6587\u5230 ttl\uFF08\u5177\u4F53\u6B65\u9AA4\uFF09\n\n1. \u628A SKILL.md \u91CC\u7684\u540D\u8BCD\u5708\u51FA\u6765 \u2192 \u6311\u51FA**\u53CD\u590D\u51FA\u73B0\u3001\u6709\u8EAB\u4EFD**\u7684\u90A3\u4E9B = \u5019\u9009\u7C7B\u578B\uFF1B\n2. \u7ED9\u6BCF\u4E2A\u7C7B\u578B\u5199\uFF1A`rdfs:label` \uFF0B `sr:rigidity`\uFF08`rigid` \u8EAB\u4EFD\u56FA\u5B9A\uFF0F`anti-rigid` \u4F1A\u53D8\uFF09\uFF0B \u8EAB\u4EFD\u5C5E\u6027\uFF08`sr:identity`\uFF09\uFF1B\n3. \u628A\u52A8\u8BCD\u5708\u51FA\u6765 \u2192 \u6709\u65B9\u5411\u3001\u6709\u57FA\u6570\u7684 = \u5173\u7CFB\uFF08`rdfs:domain`/`rdfs:range` \uFF0B `sr:cardinality`\uFF09\uFF1B\n4. \u628A\"\u6C38\u8FDC\u6210\u7ACB\u7684\u8BDD\"\u5199\u6210\u516C\u7406\uFF08`sr:axiom`\uFF09\u2014\u2014 **\u53EA\u5199\u771F\u80FD\u5224\u771F\u5047\u7684**\uFF0C\u522B\u5199\u53E3\u53F7\uFF1B\n5. \u8865 `sr:source`/\u6765\u6E90\uFF08\u5BF9\u5916\u90E8\u4E3B\u5F20\uFF09\u3002\n**\u65B9\u6CD5\u6B63\u6587\u4E0D\u5728\u8FD9\u91CC\u91CD\u590D**\uFF1A\u89C1 `praxis ontology`\uFF08\u672C\u4F53\u5DE5\u7A0B\u65B9\u6CD5\u7FA4\uFF1A\u56DB\u7EC4\u5224\u636E M1\u2013M10\uFF0FL1\u2013L5\uFF0FD1\u2013D8\uFF0FV1\u2013V4\u3001OEPE \u56DE\u8DEF\u3001\u53CD\u6A21\u5F0F\uFF09\u3002\n\n## \u4F18\u5316\u56DE\u8DEF\uFF08\u6539\u5B8C\u4E4B\u540E\u518D\u8D70\u4E00\u904D\uFF09\n\n```\nserenity-skill-creator index <skill> \u2190 \u5148\u770B\u6A21\u578B\u5230\u5E95\u4F1A\u770B\u5230\u4EC0\u4E48\uFF08\u9732\u6CA1\u9732\u3001\u987A\u5E8F\u5BF9\u4E0D\u5BF9\uFF09\nserenity-skill-creator check <skill> \u2190 A3 \u5206\u5C42\uFF08\u9876\u5C42\u975E\u6869 \uFF0F \u4E0D\u81EA\u8FF0\u5BFC\u822A \uFF0F \u70B9\u540D\u5404\u5B50\u6811\uFF09\uFF0B A1 \u7ED3\u6784 \uFF0B A2 \u94FE\u4E0A\n \uFF08\u673A\u68B0\uFF0C\u5FC5\u987B\u5168\u7EFF\uFF09\nserenity-skill-creator review <skill> \u2190 C \u6863\uFF1A\u6587\u5B57\u4E0E ttl \u9010\u6761\u7684\u5408\u89C4\u95EE\u9898\u6E05\u5355\uFF08\u8981\u4EBA\u5728\u73AF\u7B54\uFF09\n\uFF08\u6539 SKILL.md / skill.ttl / references\uFF09\n \u2191__________________________ \u56DE\u5230\u7B2C\u4E00\u6B65\n```\n\n**\u5224\u636E**\uFF1A\u6539\u5B8C**\u5FC5\u987B\u91CD\u8DD1** `check` \u2014\u2014 \u672C\u4ED3\u7684\u6559\u8BAD\u662F\"\u68C0\u67E5\u6CA1\u8DD1\"\u548C\"\u68C0\u67E5\u901A\u8FC7\"\u5728\u8F93\u51FA\u4E0A\u5FC5\u987B\u5206\u5F97\u5F00\uFF1B\n`review` \u51FA\u7684\u662F**\u95EE\u9898\u6E05\u5355**\uFF08\u534A\u673A\u68B0\u3001\u8981\u4EBA\u56DE\u7B54\uFF09\uFF0C**\u4E0D\u662F**\u901A\u8FC7/\u4E0D\u901A\u8FC7\u3002\n";
|
|
8
|
+
export declare function createSkillCreatorTool(): import("@deepseek-ai/dsh-tools").ToolDefinition;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.55.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": {
|