@shgroup/dsh-serenity-hooks 1.51.5 → 1.52.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
2
  import { a as hostSessions, c as hostWeb, i as hostService, l as hostWebServer, n as hostInjected, o as hostSettings, r as hostLlm, s as hostSubagents, t as hostAgents } from "./access-CNccNtgf.js";
3
- import { _ as resolveInside, a as findSerenityRoot, c as matchBlacklist, d as readCccName$1, f as readExclusiveTools, h as readTrajectorySkills, i as findGitRoot, l as pathInside, m as readSafeModeHiddenTools, n as SAFE_MODE_MARKER, o as isSafeModeOn, p as readHandymanConfig, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-DXky9UZu.js";
3
+ import { _ as resolveInside, a as findSerenityRoot$1, c as matchBlacklist, d as readCccName$1, f as readExclusiveTools, h as readTrajectorySkills, i as findGitRoot, l as pathInside, m as readSafeModeHiddenTools, n as SAFE_MODE_MARKER, o as isSafeModeOn, p as readHandymanConfig, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-DXky9UZu.js";
4
4
  import { i as cccRootForExec, n as agentCwdFor, o as listCccs, r as cccRootForCwd, t as NO_CCC_FROM_AGENT_CWD } from "./ccc-roots-Bo0HKLlM.js";
5
5
  import { A as localIdStamp, C as setActiveSessionInfo, D as isoLocal, E as useSession, O as localFileStamp, S as sessionsRoot, T as summarize, _ as namingTitleFor, a as pruneMissingBindings, b as resolveSessionByTitle, c as setBindingStore, d as clearActiveSessionInfo, f as createSession, g as listSessions, h as getActiveSessionInfo, i as listBoundSessionIds, k as localHuman, l as DEFAULT_SESSION_SCOPE, m as findSession, n as PLUGIN_SOURCE, o as readLastBound, p as extractSessionMdPathFromText, r as appendBound, s as resolveSessionTrajectoryLabel, t as ACC_MESSAGE_KIND, u as TRAJECTORY_ACTIONS, v as parseSessionContextFromEvents, w as showSession, x as sessionEvents$1, y as readActiveSessionMd } from "./message-source-BExngUPX.js";
6
6
  import { a as readWeixinCredential, c as weixinInboundDir, d as LOCALSTORE_SCOPES, f as checkLocalstoreGitCompliance, g as runLocalStore, h as readStore, i as matchWeixinRoute, l as weixinSessionIdFor, m as readGitTrack, n as extractWeixinText, o as readWeixinSettings, p as localstorePath, r as hasVoiceItem, s as sanitizeFileName$1, t as extractWeixinMedia } from "./weixin-route-BNyH5ghC.js";
@@ -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$9(value) {
427
+ function renderText$10(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$9(value)
499
+ render: (args, value) => renderText$10(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$8(value) {
575
+ function renderText$9(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$8(value)
603
+ render: (args, value) => renderText$9(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$7(value) {
918
+ function renderText$8(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$7(value)
966
+ render: (args, value) => renderText$8(value)
967
967
  },
968
968
  async execute(args, exec) {
969
969
  const root = cccRootForExec(exec);
@@ -1078,6 +1078,8 @@ Directory of ACC capabilities — each area lists where to go for details (guide
1078
1078
  原 logbook 已并入(v1.33 用户裁决:废除 logbook 词);close/archive 删(归档走 container_fs mv),
1079
1079
  health/qa/summary 分别并入 use / list,hook-develop-guide 并进 container_admin msm guide
1080
1080
  ② 认知质量框架 → praxis(eap/neat/cce/ontology 四合一——可实践理论注入,section 渐进披露,无参即目录)
1081
+ ontology(v1.52 D121:本体工程**引擎**——init/check/explain/verify/review/compliance;
1082
+ 与 praxis ontology 的分工 = 那个是**方法文本**、这个是**能跑的那个**)
1081
1083
  ③ 工具与执行 → container_fs(容器文件系统 15 子命令)/ container_git(git)/ dashboard(health 含注册表检查/time/wait)
1082
1084
  acc_msm 已并入 msm(执行+发现)与 container_admin(管理)——msm("<name>", ["<args>"]);inspect=true 查用法
1083
1085
  handyman(杂工 agent 编排——guide 子命令:handyman guide)
@@ -3146,6 +3148,7 @@ function toolsBlock() {
3146
3148
  " container_git— git operations (status/commit/push/log)",
3147
3149
  " msm — execute a registered CCC MSM: msm(name, args); partial name returns candidates; inspect=true shows usage",
3148
3150
  " praxis — actionable theory injection: praxis (index) / praxis eap / praxis neat / praxis cce / praxis ontology",
3151
+ " 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",
3149
3152
  " 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",
3150
3153
  " localstore — ACC local credential/config storage (CCC-root localstore.json; git policy localstore.gitTrack default deny)",
3151
3154
  " container_admin — container administration (the maintenance bay): role (Skiff roles: guide/validate/apply/list) / msm (register/deregister/check/guide/catalog/ccc-config) / config (view)",
@@ -4141,7 +4144,7 @@ function skiffMsmGate(root, sessionId, action, name) {
4141
4144
  function agentSessionId$1(exec) {
4142
4145
  return exec.agent?.session?.id ?? "";
4143
4146
  }
4144
- function renderText$6(value) {
4147
+ function renderText$7(value) {
4145
4148
  return [{
4146
4149
  type: "text",
4147
4150
  text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
@@ -4191,7 +4194,7 @@ const msmTool = defineTool({
4191
4194
  },
4192
4195
  output: {
4193
4196
  schema: { type: "json" },
4194
- render: (args, value) => renderText$6(value)
4197
+ render: (args, value) => renderText$7(value)
4195
4198
  },
4196
4199
  async execute(args, exec) {
4197
4200
  const root = cccRootForExec(exec);
@@ -4531,6 +4534,26 @@ O ── Ontology
4531
4534
  ⑤ register it through the container's registry (never by editing the registry file directly)
4532
4535
  \`\`\`
4533
4536
 
4537
+ **Retrofit — modelling a system that already exists** (the reverse direction; the three workflows above
4538
+ assume the model comes first, which is not how it usually goes)
4539
+
4540
+ \`\`\`
4541
+ ① draw the boundary as an ACTIVITY, not as a system ("developing X", not "the whole of X")
4542
+ ② enumerate the artefacts by MEASURING, never by recall — list the files, the registry entries, the
4543
+ running processes; a model built from memory reproduces memory's errors
4544
+ ③ classify each artefact: type · instance · property. Only types get a class
4545
+ ④ for every type: rigidity + identity. A role/stage is anti-rigid; a thing that persists is rigid
4546
+ ⑤ write the relations that carry OWNERSHIP and CAUSALITY first (owner · produces · guards · proves)
4547
+ ⑥ write axioms from the rules you ALREADY follow but never wrote down — that is where the payout is
4548
+ ⑦ record known failure modes as INSTANCES, not as prose paragraphs
4549
+ ⑧ strict check → 0 errors; then the semantic review
4550
+ \`\`\`
4551
+
4552
+ 🔴 **The tell that a retrofit model is worth keeping**: running it surfaces at least one
4553
+ **contradiction in the existing system** — a thing owned in two places, a declared value that does not
4554
+ match reality, a rule everyone follows that nobody wrote down. A model that only restates what was
4555
+ already obvious has not earned its maintenance cost.
4556
+
4534
4557
  ## 10. Universal axioms (violating any of these is a defect, not a style choice)
4535
4558
 
4536
4559
  | Axiom | Content | Consequence of violation |
@@ -4544,6 +4567,51 @@ O ── Ontology
4544
4567
  **Axiom discipline**: axioms are named (\`AX-*\`), individually citable, and each carries a rationale.
4545
4568
  Breaking an axiom is allowed only by stating the break explicitly and recording the reason.
4546
4569
 
4570
+ ### The axiom rule expression (what \`M8\` parses, and what \`M9\` resolves against)
4571
+
4572
+ \`sr:rule\` is **not free text** — it is a small expression language:
4573
+
4574
+ \`\`\`
4575
+ expr ::= implication
4576
+ implication ::= or_expr ('implies' or_expr)?
4577
+ or_expr ::= and_expr ('or' and_expr)*
4578
+ and_expr ::= not_expr ('and' not_expr)*
4579
+ not_expr ::= 'not' not_expr | comparison
4580
+ comparison ::= additive (cmp additive)? ← at most ONE comparison; it does not chain
4581
+ cmp ::= == | != | > | >= | < | <= | in ← there is no 'not in'; see the traps below
4582
+ additive ::= primary (('+' | '-') primary)*
4583
+ primary ::= quantifier | func_call | prop_access | literal | '(' expr ')'
4584
+ quantifier ::= ('exists' | 'forall') ident 'in' Type ':' expr
4585
+ func_call ::= ident '(' (expr (',' expr)*)? ')'
4586
+ prop_access ::= ident ('.' ident)*
4587
+ literal ::= number | 'string' | true | false | null
4588
+ \`\`\`
4589
+
4590
+ 🔴 **The property that makes every other trap dangerous**: \`M8\` accepts a rule as soon as a **prefix**
4591
+ parses. It never requires the input to be consumed to the end, so whatever follows the longest parsable
4592
+ prefix is **silently dropped** — and \`M9\` then checks only the AST that survived, so the dropped tail is
4593
+ invisible to **both** checks. Measured, with its control: \`Thing.count >= 1 NoSuchType.x == 1\` passes
4594
+ **clean**, while the same fragment alone (\`NoSuchType.x == 1\`) raises an M9 warning. And
4595
+ \`Thing.count >= 1 garbage here\` passes clean. ⇒ **A green check does not mean the whole rule was
4596
+ understood.** Keep one rule to a single clause; if you suspect a fragment is being dropped, put it alone
4597
+ in a throwaway axiom — a fragment that complains alone but not in place is a fragment being dropped.
4598
+
4599
+ **Traps, each measured against the engine** (\`check --level strict\`, one probe axiom per boundary):
4600
+
4601
+ | Trap | What the engine actually does |
4602
+ |---|---|
4603
+ | \`exists\` / \`forall\` / \`in\` are **reserved words** | they cannot be used as identifiers or property names: \`ReleaseTag.exists\` ⇒ \`Expected identifier after '.'\`. Express existence through a counted property (\`X.count >= 1\`) instead |
4604
+ | there is **no \`distinct\`** | \`count(distinct version)\` ⇒ \`Expected ')' after args of count\` (the argument parse stops at \`distinct\`, then hits \`version\`). Put the derived quantity in its own property (\`VersionTriple.distinctCount == 1\`) |
4605
+ | there is **no \`not in\`** | the operator is unreachable: \`x not in y\` parses as \`x\` and the rest vanishes **with no message**. Write \`not (x in y)\` — verified clean |
4606
+ | comparisons do **not chain** | \`a >= 1 >= 0\` parses as \`a >= 1\`; the tail is dropped silently. Use \`and\` |
4607
+ | \`M9\` checks **exactly two things** | (a) dotted paths of **two or more** segments ⇒ **warning** when the head is neither a declared type nor a relation; (b) a quantifier's **type name** ⇒ **error** when undeclared. Single bare identifiers and **function-call names are never checked**: \`noSuchFunction(X) == 1\` and \`noSuchBareThing == 1\` both pass clean ⇒ a mistyped relation inside \`occupies(…)\` is invisible |
4608
+ | \`M8\` errors are fatal, **M9's property tier is only a warning** | \`check\` can exit non-zero on M8 alone while a wrong head name slips past as a warning ⇒ read the warnings, not just the exit code |
4609
+
4610
+ **Rule of thumb**: if a constraint cannot be said in this grammar, it is probably a
4611
+ **description + rationale** (prose) rather than a rule — say it there, do not bend the syntax.
4612
+ ⚠️ Prose is also the honest place for anything the checks **cannot** see: a constraint the engine silently
4613
+ drops is worse than an unstated one, because it reads as verified.
4614
+
4547
4615
  ## 11. Dispatching a review subagent
4548
4616
 
4549
4617
  Both OEPE review stages run in an isolated context. Three rules make them work:
@@ -4600,7 +4668,7 @@ const PRAXIS_INDEX = `praxis — 可实践理论注入(认知框架,按需
4600
4668
  praxis ontology(本体论工程方法群——触发/编码/四组判据 M·L·D·V/OEPE 循环/公理/反模式)
4601
4669
  无参返回本目录;详情以对应框架原文为准(单一真相源)。
4602
4670
  `;
4603
- function renderText$5(value) {
4671
+ function renderText$6(value) {
4604
4672
  return [{
4605
4673
  type: "text",
4606
4674
  text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
@@ -4621,7 +4689,7 @@ const praxisTool = defineTool({
4621
4689
  } },
4622
4690
  output: {
4623
4691
  schema: { type: "string" },
4624
- render: (_args, value) => renderText$5(value)
4692
+ render: (_args, value) => renderText$6(value)
4625
4693
  },
4626
4694
  async execute(args) {
4627
4695
  if (args.section === "eap") return EAP_CONTENT;
@@ -4632,6 +4700,2773 @@ const praxisTool = defineTool({
4632
4700
  }
4633
4701
  });
4634
4702
  //#endregion
4703
+ //#region src/ontology/engine.ts
4704
+ /**
4705
+ * engine.ts — Ontology Definition, Validation & Semantic Review(ACC 侧引擎)
4706
+ *
4707
+ * 来源:宁静号 CCC 的 MSM `ontology-engineering`
4708
+ * (原 `.opencode/skills/home-ontology/scripts/ontology-engineering.ts`;
4709
+ * 2026-10-11 owner 裁「搬到 ACC」)。
4710
+ * 搬迁方式 = **复制 + 机械改写**,不是重写 —— 判据群(M1–M10/L1–L5/D1–D8/V1–V4)与
4711
+ * 全部输出文案**逐字保留**,只在"如何被调用/如何吐字"这一层做了三处改写(见下)。
4712
+ *
4713
+ * 本文件是**纯引擎**:只用 node 内建模块,不依赖宿主与 CCC。三段分工:
4714
+ * · 引擎(本文件)= 解析与检查;
4715
+ * · 工具壳 `src/tools/ontology-eng.ts` = 参数、路径守卫、把结果翻成回执;
4716
+ * · 方法手册 `src/tools/ontology.ts`(praxis 第四段)= 人/模型读的方法论。
4717
+ *
4718
+ * 🔴 搬迁改写清单(相对原 MSM 脚本,行为差异仅此三处):
4719
+ * ① **输出不再直写 `process.stdout`** ⇒ 经 `emit()`/`emitErr()` 汇聚。宿主是**长驻进程**,
4720
+ * 工具调用必须**返回文本**(且不能污染服务器自己的 stdout)。
4721
+ * ② **`main()` + `process.argv` 守卫 ⇒ 导出 `runOntologyCli(argv, opts)`**:**不再 `process.exit`**,
4722
+ * 退出码(0/1/2/4)改为**返回值**,由工具壳翻译成可读回执。
4723
+ * ③ **运行时工作目录由调用方给**(`opts.cwd` = CCC 根):引擎多处按相对路径解析
4724
+ * `.opencode/skills` ⇒ 改用**显式基准目录 `BASE`**(一律 `resolve(BASE, …)`),
4725
+ * **不用 `process.chdir`** —— chdir 在 **worker 线程里不被支持**
4726
+ * (`ERR_WORKER_UNSUPPORTED_OPERATION`;vitest 就用 worker 跑),且长驻进程
4727
+ * 改全局 cwd 是隐患。`BASE` 只在本进程内、由 `runOntologyCli` 的 try/finally
4728
+ * 保存与恢复;`resolve(BASE, x)` 对**绝对路径天然无害**(原样返回)⇒ 生产路径
4729
+ * (工具壳传绝对路径进来)行为不变。
4730
+ *
4731
+ * subcommands(与原脚本逐一对应):
4732
+ * init <path> [--template minimal|full] 写一份 Turtle 模板
4733
+ * check <path> [--level normal|strict] M1–M10 结构检查(strict 另出 L1–L5 提示)
4734
+ * explain <path> 自然语言摘要
4735
+ * verify <path> [--level operational|continuity|full] V1–V4 运行时合规
4736
+ * review <path> [--level semantic|onto-clean|full] [--format prompt|json] 语义审查提示词
4737
+ * compliance <path> [--skills <dir>] [--format prompt|json] 公理↔skill 合规清单
4738
+ *
4739
+ * 退出码:0 成功 / 1 user(参数或校验失败)/ 2 system(文件缺失/解析失败)/ 4 internal
4740
+ * 错误码:PARAMETER_MISSING / PARAMETER_INVALID_VALUE / ONTO_FILE_NOT_FOUND / ONTO_PARSE_ERROR
4741
+ * / ONTO_AXIOM_PARSE_ERROR / ONTO_VALIDATION_FAILED / INTERNAL_UNHANDLED_STATE
4742
+ */
4743
+ const __filename = fileURLToPath(import.meta.url);
4744
+ const __dirname = dirname(__filename);
4745
+ let OUT = { write: (s) => void process.stdout.write(s) };
4746
+ let ERR = { write: (s) => void process.stderr.write(s) };
4747
+ function emit(s) {
4748
+ OUT.write(s);
4749
+ }
4750
+ function emitErr(s) {
4751
+ ERR.write(s);
4752
+ }
4753
+ var MsmError = class extends Error {
4754
+ code;
4755
+ category;
4756
+ message;
4757
+ context;
4758
+ remediation;
4759
+ cause;
4760
+ constructor(code, category, message, context = {}, remediation, cause) {
4761
+ super(message);
4762
+ this.code = code;
4763
+ this.category = category;
4764
+ this.message = message;
4765
+ this.context = context;
4766
+ this.remediation = remediation;
4767
+ this.cause = cause;
4768
+ this.name = "MsmError";
4769
+ }
4770
+ toStderr() {
4771
+ const lines = [
4772
+ `[${this.code}]`,
4773
+ ` code: ${this.code}`,
4774
+ ` category: ${this.category}`,
4775
+ ` message: ${this.message}`,
4776
+ ` cause: ${this.cause !== void 0 && this.cause !== "" ? this.cause instanceof Error ? this.cause.message : String(this.cause) : "(no underlying cause)"}`
4777
+ ];
4778
+ if (this.remediation) lines.push(` remediation: ${this.remediation}`);
4779
+ if (Object.keys(this.context).length > 0) lines.push(` context: ${JSON.stringify(this.context)}`);
4780
+ return lines.join("\n");
4781
+ }
4782
+ exitCode() {
4783
+ return {
4784
+ user: 1,
4785
+ system: 2,
4786
+ operator: 3,
4787
+ internal: 4
4788
+ }[this.category];
4789
+ }
4790
+ };
4791
+ function emitError(err) {
4792
+ emitErr(err.toStderr() + "\n");
4793
+ }
4794
+ const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
4795
+ const RDFS = "http://www.w3.org/2000/01/rdf-schema#";
4796
+ const OWL = "http://www.w3.org/2002/07/owl#";
4797
+ const XSD = "http://www.w3.org/2001/XMLSchema#";
4798
+ /** `sr:` 命名空间 —— **容器约定**(宁静号及同源 CCC 用 `http://home-serenity/ontology#`)。
4799
+ * 可用 `runOntologyCli(argv, { namespace })` 覆盖;缺省与原 MSM 脚本**逐字相同**。 */
4800
+ let SR = "http://home-serenity/ontology#";
4801
+ /** 顶层 skill 名 —— 引擎有三处按它定位容器级资产(根标记 / MSM 注册表 / 合规扫描)。
4802
+ * 可用 `runOntologyCli(argv, { topSkill })` 覆盖;缺省 = 宁静号的顶层 skill。 */
4803
+ let TOP_SKILL = "home-serenity";
4804
+ /** 容器根(CCC root)—— 由调用方给定(改写 ③)。**ACC 侧必须给**:插件安装在宿主 profile 下,
4805
+ * 沿 `__dirname` 上溯只会走到安装路径,永远命不中 CCC 的 `.opencode/skills/...`。 */
4806
+ let ROOT = void 0;
4807
+ /** 相对路径的解析基准 —— 由调用方给(= CCC 根,见改写 ③)。**不用 `process.chdir`**:
4808
+ * chdir 在 worker 线程里不被支持(`ERR_WORKER_UNSUPPORTED_OPERATION`,vitest 就用 worker 跑),
4809
+ * 且在长驻进程里改全局 cwd 是隐患。缺省 = 进程启动时的工作目录。 */
4810
+ let BASE = process.cwd();
4811
+ const STD_PREFIXES = {
4812
+ rdf: RDF,
4813
+ rdfs: RDFS,
4814
+ owl: OWL,
4815
+ xsd: XSD,
4816
+ sr: SR
4817
+ };
4818
+ function tokenizeTurtle(input) {
4819
+ const tokens = [];
4820
+ const errors = [];
4821
+ let i = 0;
4822
+ let safety = 0;
4823
+ function peek(n = 0) {
4824
+ return i + n < input.length ? input[i + n] : "";
4825
+ }
4826
+ while (i < input.length) {
4827
+ if (++safety > 5e5) {
4828
+ errors.push(`Tokenizer safety limit exceeded at position ${i}`);
4829
+ break;
4830
+ }
4831
+ const ch = input[i];
4832
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
4833
+ i++;
4834
+ continue;
4835
+ }
4836
+ if (ch === "#") {
4837
+ while (i < input.length && input[i] !== "\n") i++;
4838
+ continue;
4839
+ }
4840
+ if (ch === "@") {
4841
+ const start = i;
4842
+ i++;
4843
+ let kw = "";
4844
+ while (i < input.length && /[a-z]/i.test(input[i])) {
4845
+ kw += input[i];
4846
+ i++;
4847
+ }
4848
+ if (kw === "prefix" || kw === "base") tokens.push({
4849
+ kind: kw,
4850
+ value: kw,
4851
+ pos: start
4852
+ });
4853
+ else errors.push(`Unknown directive @${kw} at ${start}`);
4854
+ continue;
4855
+ }
4856
+ if (ch === "<") {
4857
+ const start = i;
4858
+ let iri = "";
4859
+ i++;
4860
+ while (i < input.length && input[i] !== ">") {
4861
+ iri += input[i];
4862
+ i++;
4863
+ }
4864
+ if (i < input.length) i++;
4865
+ else errors.push(`Unclosed IRI at ${start}`);
4866
+ tokens.push({
4867
+ kind: "iri",
4868
+ value: iri,
4869
+ pos: start
4870
+ });
4871
+ continue;
4872
+ }
4873
+ if (ch === "\"" || ch === "'") {
4874
+ const start = i, quote = ch;
4875
+ let val = "";
4876
+ i++;
4877
+ if (input[i] === quote && input[i + 1] === quote) {
4878
+ i += 2;
4879
+ while (i < input.length && !(input[i] === quote && input[i + 1] === quote && input[i + 2] === quote)) {
4880
+ val += input[i];
4881
+ i++;
4882
+ }
4883
+ if (i < input.length) i += 3;
4884
+ else errors.push(`Unclosed triple-quoted string at ${start}`);
4885
+ } else {
4886
+ while (i < input.length && input[i] !== quote) {
4887
+ if (input[i] === "\\" && i + 1 < input.length) val += input[++i];
4888
+ else val += input[i];
4889
+ i++;
4890
+ }
4891
+ if (i < input.length) i++;
4892
+ else errors.push(`Unclosed string at ${start}`);
4893
+ }
4894
+ const tk = {
4895
+ kind: "literal",
4896
+ value: val,
4897
+ pos: start
4898
+ };
4899
+ if (input[i] === "@" && /[a-z]/i.test(input[i + 1])) {
4900
+ i++;
4901
+ let lang = "";
4902
+ while (i < input.length && /[a-z-]/i.test(input[i])) {
4903
+ lang += input[i];
4904
+ i++;
4905
+ }
4906
+ tk.lang = lang;
4907
+ } else if (input.slice(i, i + 2) === "^^") {
4908
+ i += 2;
4909
+ if (input[i] === "<") {
4910
+ i++;
4911
+ let dt = "";
4912
+ while (i < input.length && input[i] !== ">") {
4913
+ dt += input[i];
4914
+ i++;
4915
+ }
4916
+ if (i < input.length) i++;
4917
+ tk.datatype = dt;
4918
+ }
4919
+ }
4920
+ tokens.push(tk);
4921
+ continue;
4922
+ }
4923
+ if (/[0-9]/.test(ch) || ch === "-" && /[0-9]/.test(peek(1))) {
4924
+ const start = i;
4925
+ let num = "";
4926
+ if (ch === "-") {
4927
+ num += ch;
4928
+ i++;
4929
+ }
4930
+ if (input[i] === ".") {
4931
+ num += ".";
4932
+ i++;
4933
+ }
4934
+ while (i < input.length && /[0-9]/.test(input[i])) {
4935
+ num += input[i];
4936
+ i++;
4937
+ }
4938
+ if (input[i] === "." && !/[a-zA-Z]/.test(peek(1))) {
4939
+ num += ".";
4940
+ i++;
4941
+ while (i < input.length && /[0-9]/.test(input[i])) {
4942
+ num += input[i];
4943
+ i++;
4944
+ }
4945
+ }
4946
+ if (input[i] === "e" || input[i] === "E") {
4947
+ num += input[i];
4948
+ i++;
4949
+ if (input[i] === "-" || input[i] === "+") {
4950
+ num += input[i];
4951
+ i++;
4952
+ }
4953
+ while (i < input.length && /[0-9]/.test(input[i])) {
4954
+ num += input[i];
4955
+ i++;
4956
+ }
4957
+ }
4958
+ tokens.push({
4959
+ kind: "number",
4960
+ value: num,
4961
+ pos: start
4962
+ });
4963
+ continue;
4964
+ }
4965
+ if (/[a-zA-Z_:]/.test(ch)) {
4966
+ const start = i;
4967
+ let ident = "";
4968
+ while (i < input.length && /[a-zA-Z0-9_-]/.test(input[i])) {
4969
+ ident += input[i];
4970
+ i++;
4971
+ }
4972
+ if (i < input.length && input[i] === ":") {
4973
+ ident += ":";
4974
+ i++;
4975
+ while (i < input.length && /[a-zA-Z0-9_-]/.test(input[i])) {
4976
+ ident += input[i];
4977
+ i++;
4978
+ }
4979
+ tokens.push({
4980
+ kind: ident === "a" ? "a" : "qname",
4981
+ value: ident,
4982
+ pos: start
4983
+ });
4984
+ } else if (ident === "a") tokens.push({
4985
+ kind: "a",
4986
+ value: "a",
4987
+ pos: start
4988
+ });
4989
+ else if (ident === "true" || ident === "false") tokens.push({
4990
+ kind: "boolean",
4991
+ value: ident,
4992
+ pos: start
4993
+ });
4994
+ else tokens.push({
4995
+ kind: "qname",
4996
+ value: ident.startsWith(":") ? ident : ":" + ident,
4997
+ pos: start
4998
+ });
4999
+ continue;
5000
+ }
5001
+ switch (ch) {
5002
+ case ".":
5003
+ tokens.push({
5004
+ kind: "dot",
5005
+ value: ".",
5006
+ pos: i
5007
+ });
5008
+ i++;
5009
+ break;
5010
+ case ";":
5011
+ tokens.push({
5012
+ kind: "semi",
5013
+ value: ";",
5014
+ pos: i
5015
+ });
5016
+ i++;
5017
+ break;
5018
+ case ",":
5019
+ tokens.push({
5020
+ kind: "comma",
5021
+ value: ",",
5022
+ pos: i
5023
+ });
5024
+ i++;
5025
+ break;
5026
+ case "[":
5027
+ tokens.push({
5028
+ kind: "lbracket",
5029
+ value: "[",
5030
+ pos: i
5031
+ });
5032
+ i++;
5033
+ break;
5034
+ case "]":
5035
+ tokens.push({
5036
+ kind: "rbracket",
5037
+ value: "]",
5038
+ pos: i
5039
+ });
5040
+ i++;
5041
+ break;
5042
+ case "(":
5043
+ tokens.push({
5044
+ kind: "lparen",
5045
+ value: "(",
5046
+ pos: i
5047
+ });
5048
+ i++;
5049
+ break;
5050
+ case ")":
5051
+ tokens.push({
5052
+ kind: "rparen",
5053
+ value: ")",
5054
+ pos: i
5055
+ });
5056
+ i++;
5057
+ break;
5058
+ default:
5059
+ errors.push(`Unexpected character '${ch}' at position ${i}`);
5060
+ i++;
5061
+ }
5062
+ }
5063
+ tokens.push({
5064
+ kind: "eof",
5065
+ value: "",
5066
+ pos: i
5067
+ });
5068
+ return {
5069
+ tokens,
5070
+ errors
5071
+ };
5072
+ }
5073
+ function parseTurtle(raw) {
5074
+ const { tokens, errors } = tokenizeTurtle(raw);
5075
+ if (errors.length > 0) return {
5076
+ prefixes: /* @__PURE__ */ new Map(),
5077
+ triples: [],
5078
+ errors
5079
+ };
5080
+ const prefixes = /* @__PURE__ */ new Map();
5081
+ const p2q = /* @__PURE__ */ new Map();
5082
+ let baseIri = "http://example.org/onto#";
5083
+ let pos = 0;
5084
+ let blankCounter = 0;
5085
+ function nextBnode() {
5086
+ return `_:b${blankCounter++}`;
5087
+ }
5088
+ function peek() {
5089
+ return tokens[pos];
5090
+ }
5091
+ function advance() {
5092
+ return tokens[pos++];
5093
+ }
5094
+ function resolveIri(tk) {
5095
+ if (tk.kind === "iri") return tk.value;
5096
+ if (tk.kind === "a") return "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
5097
+ if (tk.kind === "qname") {
5098
+ const colon = tk.value.indexOf(":");
5099
+ const prefix = tk.value.slice(0, colon);
5100
+ const local = tk.value.slice(colon + 1);
5101
+ if (prefix === "") return baseIri + local;
5102
+ const ns = prefixes.get(prefix);
5103
+ return ns ? ns + local : `unknown:${tk.value}`;
5104
+ }
5105
+ return tk.value;
5106
+ }
5107
+ function parseObject() {
5108
+ const tk = peek();
5109
+ if (tk.kind === "iri") {
5110
+ advance();
5111
+ return {
5112
+ value: tk.value,
5113
+ type: "iri"
5114
+ };
5115
+ }
5116
+ if (tk.kind === "qname" || tk.kind === "a") {
5117
+ advance();
5118
+ return {
5119
+ value: resolveIri(tk),
5120
+ type: "iri"
5121
+ };
5122
+ }
5123
+ if (tk.kind === "literal") {
5124
+ advance();
5125
+ return {
5126
+ value: tk.value,
5127
+ type: "literal",
5128
+ datatype: tk.datatype
5129
+ };
5130
+ }
5131
+ if (tk.kind === "number") {
5132
+ advance();
5133
+ return {
5134
+ value: tk.value.includes(".") ? parseFloat(tk.value) : parseInt(tk.value, 10),
5135
+ type: "literal",
5136
+ datatype: tk.value.includes(".") ? "http://www.w3.org/2001/XMLSchema#double" : "http://www.w3.org/2001/XMLSchema#integer"
5137
+ };
5138
+ }
5139
+ if (tk.kind === "boolean") {
5140
+ advance();
5141
+ return {
5142
+ value: tk.value === "true",
5143
+ type: "literal",
5144
+ datatype: "http://www.w3.org/2001/XMLSchema#boolean"
5145
+ };
5146
+ }
5147
+ if (tk.kind === "lbracket") {
5148
+ const bnode = nextBnode();
5149
+ advance();
5150
+ while (peek().kind !== "rbracket" && peek().kind !== "eof") {
5151
+ const pred = peek();
5152
+ if (pred.kind !== "qname" && pred.kind !== "a") {
5153
+ errors.push(`Expected predicate in blank node at ${pred.pos}`);
5154
+ break;
5155
+ }
5156
+ advance();
5157
+ const obj = parseObject();
5158
+ if (!obj) {
5159
+ errors.push(`Expected object in blank node at ${peek().pos}`);
5160
+ break;
5161
+ }
5162
+ obj.type === "iri" ? obj.value : `${obj.value}`;
5163
+ triples.push({
5164
+ subject: bnode,
5165
+ predicate: resolveIri(pred),
5166
+ object: obj.value,
5167
+ objectType: obj.type,
5168
+ objectDatatype: obj.datatype
5169
+ });
5170
+ if (peek().kind === "semi") advance();
5171
+ else if (peek().kind === "dot") {
5172
+ advance();
5173
+ break;
5174
+ } else break;
5175
+ }
5176
+ if (peek().kind === "dot") advance();
5177
+ if (peek().kind === "rbracket") advance();
5178
+ else errors.push(`Expected ] at position ${peek().pos}`);
5179
+ return {
5180
+ value: bnode,
5181
+ type: "blank"
5182
+ };
5183
+ }
5184
+ if (tk.kind === "lparen") {
5185
+ advance();
5186
+ const values = [];
5187
+ while (peek().kind !== "rparen" && peek().kind !== "eof") {
5188
+ if (peek().kind === "comma") {
5189
+ advance();
5190
+ continue;
5191
+ }
5192
+ const obj = parseObject();
5193
+ if (obj) values.push(obj);
5194
+ else break;
5195
+ }
5196
+ if (peek().kind === "rparen") advance();
5197
+ return {
5198
+ value: values.map((v) => v.type === "iri" ? `<${v.value}>` : `"${v.value}"`).join(" "),
5199
+ type: "literal"
5200
+ };
5201
+ }
5202
+ return null;
5203
+ }
5204
+ const triples = [];
5205
+ let safetyLoop = 0;
5206
+ while (peek().kind !== "eof") {
5207
+ if (++safetyLoop > 1e5) {
5208
+ errors.push("Parser safety limit exceeded");
5209
+ break;
5210
+ }
5211
+ const tk = peek();
5212
+ if (tk.kind === "prefix") {
5213
+ advance();
5214
+ const nameTk = peek();
5215
+ if (nameTk.kind !== "qname") {
5216
+ errors.push(`Expected prefix name after @prefix at ${nameTk.pos}`);
5217
+ break;
5218
+ }
5219
+ const nameStr = nameTk.value.replace(/:$/, "");
5220
+ advance();
5221
+ const iriTk = peek();
5222
+ if (iriTk.kind !== "iri") {
5223
+ errors.push(`Expected IRI after prefix name at ${iriTk.pos}`);
5224
+ break;
5225
+ }
5226
+ advance();
5227
+ prefixes.set(nameStr, iriTk.value);
5228
+ p2q.set(iriTk.value, nameStr);
5229
+ if (peek().kind === "dot") advance();
5230
+ continue;
5231
+ }
5232
+ if (tk.kind === "base") {
5233
+ advance();
5234
+ const iriTk = peek();
5235
+ if (iriTk.kind !== "iri") {
5236
+ errors.push(`Expected IRI after @base at ${iriTk.pos}`);
5237
+ break;
5238
+ }
5239
+ baseIri = iriTk.value;
5240
+ advance();
5241
+ if (peek().kind === "dot") advance();
5242
+ continue;
5243
+ }
5244
+ let subject = "";
5245
+ if (tk.kind === "lbracket") {
5246
+ subject = nextBnode();
5247
+ advance();
5248
+ const subjPred = peek();
5249
+ if (subjPred.kind !== "qname" && subjPred.kind !== "a") {
5250
+ errors.push(`Expected predicate in blank node subject at ${subjPred.pos}`);
5251
+ break;
5252
+ }
5253
+ advance();
5254
+ const subjObj = parseObject();
5255
+ if (!subjObj) {
5256
+ errors.push(`Expected object in blank node at ${peek().pos}`);
5257
+ break;
5258
+ }
5259
+ triples.push({
5260
+ subject,
5261
+ predicate: resolveIri(subjPred),
5262
+ object: subjObj.value,
5263
+ objectType: subjObj.type,
5264
+ objectDatatype: subjObj.datatype
5265
+ });
5266
+ if (peek().kind === "rbracket") advance();
5267
+ else {
5268
+ errors.push(`Expected ] at ${peek().pos}`);
5269
+ break;
5270
+ }
5271
+ } else if (tk.kind === "qname" || tk.kind === "iri") {
5272
+ subject = resolveIri(tk);
5273
+ advance();
5274
+ } else {
5275
+ errors.push(`Expected subject at position ${tk.pos}, got ${tk.kind}`);
5276
+ break;
5277
+ }
5278
+ let subjDone = false;
5279
+ let currentPred = "";
5280
+ while (!subjDone && peek().kind !== "eof") {
5281
+ const pk = peek();
5282
+ if (pk.kind === "dot") {
5283
+ advance();
5284
+ subjDone = true;
5285
+ continue;
5286
+ }
5287
+ if (pk.kind === "semi") {
5288
+ advance();
5289
+ currentPred = "";
5290
+ continue;
5291
+ }
5292
+ if (pk.kind === "comma") {
5293
+ advance();
5294
+ if (!currentPred) {
5295
+ errors.push(`Comma without preceding predicate at ${pk.pos}`);
5296
+ break;
5297
+ }
5298
+ const obj = parseObject();
5299
+ if (!obj) {
5300
+ errors.push(`Expected object after comma at ${peek().pos}`);
5301
+ break;
5302
+ }
5303
+ triples.push({
5304
+ subject,
5305
+ predicate: currentPred,
5306
+ object: obj.value,
5307
+ objectType: obj.type,
5308
+ objectDatatype: obj.datatype
5309
+ });
5310
+ continue;
5311
+ }
5312
+ if (pk.kind === "qname" || pk.kind === "a") {
5313
+ currentPred = resolveIri(pk);
5314
+ advance();
5315
+ const obj = parseObject();
5316
+ if (!obj) {
5317
+ errors.push(`Expected object after predicate at ${peek().pos}`);
5318
+ break;
5319
+ }
5320
+ triples.push({
5321
+ subject,
5322
+ predicate: currentPred,
5323
+ object: obj.value,
5324
+ objectType: obj.type,
5325
+ objectDatatype: obj.datatype
5326
+ });
5327
+ continue;
5328
+ }
5329
+ if (pk.kind === "rbracket") break;
5330
+ errors.push(`Unexpected token ${pk.kind} at ${pk.pos}`);
5331
+ break;
5332
+ }
5333
+ }
5334
+ return {
5335
+ prefixes,
5336
+ triples,
5337
+ errors
5338
+ };
5339
+ }
5340
+ function i2q(iri, prefixes) {
5341
+ let best = null;
5342
+ let bestLen = 0;
5343
+ for (const [p, ns] of prefixes) if (iri.startsWith(ns) && ns.length > bestLen) {
5344
+ best = p;
5345
+ bestLen = ns.length;
5346
+ }
5347
+ if (best !== null) return best + ":" + iri.slice(bestLen);
5348
+ for (const [p, ns] of Object.entries(STD_PREFIXES)) if (iri.startsWith(ns)) return p + ":" + iri.slice(ns.length);
5349
+ return "<" + iri + ">";
5350
+ }
5351
+ function stripDefaultPrefix(name) {
5352
+ if (name.startsWith(":")) return name.slice(1);
5353
+ return name;
5354
+ }
5355
+ function turtleToOnto(r) {
5356
+ const { prefixes, triples, errors } = r;
5357
+ const onto = {
5358
+ types: {},
5359
+ relations: {},
5360
+ axioms: []
5361
+ };
5362
+ const subjectMap = /* @__PURE__ */ new Map();
5363
+ for (const t of triples) {
5364
+ const list = subjectMap.get(t.subject) || [];
5365
+ list.push(t);
5366
+ subjectMap.set(t.subject, list);
5367
+ }
5368
+ const typeIris = /* @__PURE__ */ new Set();
5369
+ const propIris = /* @__PURE__ */ new Set();
5370
+ const ontologyIris = /* @__PURE__ */ new Set();
5371
+ for (const [subj, stmts] of subjectMap) for (const s of stmts) if (s.predicate === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type") {
5372
+ const obj = typeof s.object === "string" ? s.object : "";
5373
+ if (obj === "http://www.w3.org/2002/07/owl#Class" || obj === "http://www.w3.org/2000/01/rdf-schema#Class") typeIris.add(subj);
5374
+ else if (obj === "http://www.w3.org/2002/07/owl#ObjectProperty" || obj === "http://www.w3.org/2002/07/owl#DatatypeProperty" || obj === "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property") propIris.add(subj);
5375
+ else if (obj === "http://www.w3.org/2002/07/owl#Ontology") ontologyIris.add(subj);
5376
+ else if (obj === "http://www.w3.org/2002/07/owl#FunctionalProperty") {
5377
+ const pname = stripDefaultPrefix(i2q(subj, prefixes));
5378
+ if (!onto.relations[pname]) onto.relations[pname] = {};
5379
+ onto.relations[pname].functional = true;
5380
+ } else if (obj === "http://www.w3.org/2002/07/owl#TransitiveProperty") {
5381
+ const pname = stripDefaultPrefix(i2q(subj, prefixes));
5382
+ if (!onto.relations[pname]) onto.relations[pname] = {};
5383
+ onto.relations[pname].transitive = true;
5384
+ } else if (obj === "http://www.w3.org/2002/07/owl#SymmetricProperty") {
5385
+ const pname = stripDefaultPrefix(i2q(subj, prefixes));
5386
+ if (!onto.relations[pname]) onto.relations[pname] = {};
5387
+ onto.relations[pname].symmetric = true;
5388
+ }
5389
+ }
5390
+ for (const [subj, stmts] of subjectMap) if (ontologyIris.has(subj)) for (const s of stmts) {
5391
+ if (s.predicate === SR + "name" || s.predicate === "http://www.w3.org/2000/01/rdf-schema#label") onto.name = String(s.object);
5392
+ if (s.predicate === "http://www.w3.org/2002/07/owl#versionInfo") onto.version = String(s.object);
5393
+ if (s.predicate === "http://www.w3.org/2002/07/owl#imports") {
5394
+ if (!onto.imports) onto.imports = [];
5395
+ onto.imports.push(String(s.object));
5396
+ }
5397
+ }
5398
+ for (const [iri, stmts] of subjectMap) {
5399
+ if (!typeIris.has(iri)) continue;
5400
+ const name = stripDefaultPrefix(i2q(iri, prefixes));
5401
+ const td = {};
5402
+ for (const s of stmts) {
5403
+ if (s.predicate === "http://www.w3.org/2000/01/rdf-schema#subClassOf") td.superTypes = [...td.superTypes || [], stripDefaultPrefix(i2q(String(s.object), prefixes))];
5404
+ if (s.predicate === "http://www.w3.org/2000/01/rdf-schema#label" || s.predicate === "http://www.w3.org/2000/01/rdf-schema#comment") td.description = String(s.object);
5405
+ if (s.predicate === SR + "rigidity") td.rigidity = String(s.object);
5406
+ if (s.predicate === SR + "identity") td.identity = [...td.identity || [], String(s.object)];
5407
+ if (s.predicate === SR + "dependsOn") td.dependsOn = [...td.dependsOn || [], stripDefaultPrefix(i2q(String(s.object), prefixes))];
5408
+ }
5409
+ for (const s of stmts) if (s.predicate === SR + "property" && s.objectType === "blank") {
5410
+ const pnode = String(s.object);
5411
+ const pstmts = subjectMap.get(pnode) || [];
5412
+ const pdef = { type: "string" };
5413
+ for (const ps of pstmts) {
5414
+ if (ps.predicate === SR + "name") {}
5415
+ if (ps.predicate === SR + "propType" || ps.predicate === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type") pdef.type = String(ps.object);
5416
+ if (ps.predicate === SR + "cardinality") pdef.cardinality = String(ps.object);
5417
+ if (ps.predicate === SR + "values") {
5418
+ if (ps.objectType === "literal") pdef.values = String(ps.object).split(/\s+/).filter(Boolean);
5419
+ }
5420
+ }
5421
+ const propName = pstmts.find((ps) => ps.predicate === SR + "name");
5422
+ if (propName && typeof propName.object === "string") {
5423
+ if (!td.properties) td.properties = {};
5424
+ td.properties[propName.object] = pdef;
5425
+ }
5426
+ }
5427
+ onto.types[name] = td;
5428
+ }
5429
+ for (const [iri, stmts] of subjectMap) {
5430
+ if (!propIris.has(iri)) continue;
5431
+ const name = stripDefaultPrefix(i2q(iri, prefixes));
5432
+ const rd = {};
5433
+ for (const s of stmts) {
5434
+ if (s.predicate === "http://www.w3.org/2000/01/rdf-schema#domain") rd.domain = stripDefaultPrefix(i2q(String(s.object), prefixes));
5435
+ if (s.predicate === "http://www.w3.org/2000/01/rdf-schema#range") rd.range = stripDefaultPrefix(i2q(String(s.object), prefixes));
5436
+ if (s.predicate === SR + "cardinality") rd.cardinality = String(s.object);
5437
+ }
5438
+ onto.relations[name] = rd;
5439
+ }
5440
+ for (const [subj, stmts] of subjectMap) for (const s of stmts) if (s.predicate === SR + "axiom" && s.objectType === "blank") {
5441
+ const anode = String(s.object);
5442
+ const astmts = subjectMap.get(anode) || [];
5443
+ const ad = { rule: "" };
5444
+ for (const as_ of astmts) {
5445
+ if (as_.predicate === SR + "description" || as_.predicate === "http://www.w3.org/2000/01/rdf-schema#label") ad.description = String(as_.object);
5446
+ if (as_.predicate === SR + "rule") ad.rule = String(as_.object);
5447
+ if (as_.predicate === SR + "axiomType") ad.type = String(as_.object);
5448
+ }
5449
+ onto.axioms.push(ad);
5450
+ }
5451
+ return onto;
5452
+ }
5453
+ function ontoToTurtle(onto, baseIri = "http://example.org/onto#") {
5454
+ const lines = [];
5455
+ const p = (s) => lines.push(s);
5456
+ p(`@prefix rdf: <${RDF}> .`);
5457
+ p(`@prefix rdfs: <${RDFS}> .`);
5458
+ p(`@prefix owl: <${OWL}> .`);
5459
+ p(`@prefix xsd: <${XSD}> .`);
5460
+ p(`@prefix sr: <${SR}> .`);
5461
+ p(`@prefix : <${baseIri}> .`);
5462
+ p("");
5463
+ function lit(v) {
5464
+ if (typeof v === "string") return JSON.stringify(v);
5465
+ return String(v);
5466
+ }
5467
+ function semicolon(i, total) {
5468
+ return i < total - 1 ? " ;" : " .";
5469
+ }
5470
+ function qname(s) {
5471
+ if (s.includes(":") || s.startsWith("<") || s.startsWith("_:")) return s;
5472
+ return ":" + s;
5473
+ }
5474
+ const headerStmts = [];
5475
+ headerStmts.push("a owl:Ontology");
5476
+ if (onto.name) headerStmts.push(`sr:name ${lit(onto.name)}`);
5477
+ if (onto.version) headerStmts.push(`owl:versionInfo ${lit(onto.version)}`);
5478
+ if (onto.imports && onto.imports.length > 0) for (const imp of onto.imports) headerStmts.push(`owl:imports <${imp}>`);
5479
+ for (let i = 0; i < headerStmts.length; i++) if (i === 0) p(`:onto ${headerStmts[i]} ;`);
5480
+ else p(` ${headerStmts[i]}${semicolon(i, headerStmts.length)}`);
5481
+ p("");
5482
+ const types = onto.types || {};
5483
+ for (const [name, td] of Object.entries(types)) {
5484
+ const stmts = [];
5485
+ stmts.push("a owl:Class");
5486
+ if (td.description) stmts.push(`rdfs:label ${lit(td.description)}`);
5487
+ if (td.superTypes) for (const st of td.superTypes) stmts.push(`rdfs:subClassOf ${qname(st)}`);
5488
+ if (td.rigidity) stmts.push(`sr:rigidity ${lit(td.rigidity)}`);
5489
+ if (td.identity) for (const id of td.identity) stmts.push(`sr:identity ${qname(id)}`);
5490
+ if (td.dependsOn) for (const dep of td.dependsOn) stmts.push(`sr:dependsOn ${qname(dep)}`);
5491
+ if (td.properties) for (const [pname, pd] of Object.entries(td.properties)) {
5492
+ const pstmts = [];
5493
+ pstmts.push(`sr:name ${lit(pname)}`);
5494
+ pstmts.push(`sr:propType ${lit(pd.type)}`);
5495
+ if (pd.cardinality) pstmts.push(`sr:cardinality ${lit(pd.cardinality)}`);
5496
+ if (pd.values && pd.values.length > 0) pstmts.push(`sr:values (${pd.values.map(lit).join(" ")})`);
5497
+ stmts.push(`sr:property [\n ${pstmts.join(" ;\n ")}\n ]`);
5498
+ }
5499
+ if (td.axioms) for (const ax of td.axioms) {
5500
+ const astmts = [];
5501
+ astmts.push(`sr:rule ${lit(ax)}`);
5502
+ stmts.push(`sr:axiom [\n ${astmts.join(" ;\n ")}\n ]`);
5503
+ }
5504
+ for (let i = 0; i < stmts.length; i++) if (i === 0) p(`:${name} ${stmts[i]} ;`);
5505
+ else p(` ${stmts[i]}${semicolon(i, stmts.length)}`);
5506
+ p("");
5507
+ }
5508
+ const relations = onto.relations || {};
5509
+ for (const [name, rd] of Object.entries(relations)) {
5510
+ const stmts = [];
5511
+ const flags = [];
5512
+ stmts.push("a owl:ObjectProperty");
5513
+ if (rd.functional) flags.push("owl:FunctionalProperty");
5514
+ if (rd.transitive) flags.push("owl:TransitiveProperty");
5515
+ if (rd.symmetric) flags.push("owl:SymmetricProperty");
5516
+ if (flags.length > 0) stmts.push(`a ${flags.join(", ")}`);
5517
+ if (rd.domain) stmts.push(`rdfs:domain ${qname(rd.domain)}`);
5518
+ if (rd.range) stmts.push(`rdfs:range ${qname(rd.range)}`);
5519
+ if (rd.cardinality) stmts.push(`sr:cardinality ${lit(rd.cardinality)}`);
5520
+ for (let i = 0; i < stmts.length; i++) if (i === 0) p(`:${name} ${stmts[i]} ;`);
5521
+ else p(` ${stmts[i]}${semicolon(i, stmts.length)}`);
5522
+ p("");
5523
+ }
5524
+ const axioms = onto.axioms || [];
5525
+ for (const ax of axioms) {
5526
+ const astmts = [];
5527
+ if (ax.description) astmts.push(`sr:description ${lit(ax.description)}`);
5528
+ astmts.push(`sr:rule ${lit(ax.rule)}`);
5529
+ if (ax.type) astmts.push(`sr:axiomType ${lit(ax.type)}`);
5530
+ p(`:onto sr:axiom [`);
5531
+ for (let i = 0; i < astmts.length; i++) p(` ${astmts[i]}${i < astmts.length - 1 ? " ;" : ""}`);
5532
+ p(` ] .\n`);
5533
+ }
5534
+ return lines.join("\n");
5535
+ }
5536
+ const KEYWORDS = {
5537
+ "and": "and",
5538
+ "or": "or",
5539
+ "not": "not",
5540
+ "implies": "implies",
5541
+ "exists": "exists",
5542
+ "forall": "forall",
5543
+ "in": "in",
5544
+ "true": "boolean",
5545
+ "false": "boolean",
5546
+ "null": "null"
5547
+ };
5548
+ function tokenize(input) {
5549
+ const tokens = [];
5550
+ const errors = [];
5551
+ let i = 0;
5552
+ function advance(n) {
5553
+ i += n;
5554
+ }
5555
+ function pos() {
5556
+ return i;
5557
+ }
5558
+ while (i < input.length) {
5559
+ const ch = input[i];
5560
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
5561
+ advance(1);
5562
+ continue;
5563
+ }
5564
+ if (ch === "'") {
5565
+ const start = pos();
5566
+ advance(1);
5567
+ let val = "";
5568
+ while (i < input.length && input[i] !== "'") {
5569
+ val += input[i];
5570
+ advance(1);
5571
+ }
5572
+ if (i >= input.length) errors.push({
5573
+ message: "Unterminated string literal",
5574
+ pos: start
5575
+ });
5576
+ else advance(1);
5577
+ tokens.push({
5578
+ kind: "string",
5579
+ value: val,
5580
+ pos: start
5581
+ });
5582
+ continue;
5583
+ }
5584
+ if (/[0-9]/.test(ch)) {
5585
+ const start = pos();
5586
+ let num = "";
5587
+ while (i < input.length && /[0-9]/.test(input[i])) {
5588
+ num += input[i];
5589
+ advance(1);
5590
+ }
5591
+ if (i < input.length && input[i] === "." && i + 1 < input.length && /[0-9]/.test(input[i + 1])) {
5592
+ num += ".";
5593
+ advance(1);
5594
+ while (i < input.length && /[0-9]/.test(input[i])) {
5595
+ num += input[i];
5596
+ advance(1);
5597
+ }
5598
+ }
5599
+ tokens.push({
5600
+ kind: "number",
5601
+ value: num,
5602
+ pos: start
5603
+ });
5604
+ continue;
5605
+ }
5606
+ if (/[a-zA-Z_]/.test(ch)) {
5607
+ const start = pos();
5608
+ let ident = "";
5609
+ while (i < input.length && /[a-zA-Z0-9_]/.test(input[i])) {
5610
+ ident += input[i];
5611
+ advance(1);
5612
+ }
5613
+ const kw = KEYWORDS[ident];
5614
+ tokens.push({
5615
+ kind: kw || "ident",
5616
+ value: ident,
5617
+ pos: start
5618
+ });
5619
+ continue;
5620
+ }
5621
+ if (ch === ">" || ch === "<" || ch === "=" || ch === "!") {
5622
+ const start = pos();
5623
+ const op = input.slice(i, i + 2);
5624
+ if (op === ">=") {
5625
+ tokens.push({
5626
+ kind: "gte",
5627
+ value: ">=",
5628
+ pos: start
5629
+ });
5630
+ advance(2);
5631
+ } else if (op === "<=") {
5632
+ tokens.push({
5633
+ kind: "lte",
5634
+ value: "<=",
5635
+ pos: start
5636
+ });
5637
+ advance(2);
5638
+ } else if (op === "==") {
5639
+ tokens.push({
5640
+ kind: "eq",
5641
+ value: "==",
5642
+ pos: start
5643
+ });
5644
+ advance(2);
5645
+ } else if (op === "!=") {
5646
+ tokens.push({
5647
+ kind: "neq",
5648
+ value: "!=",
5649
+ pos: start
5650
+ });
5651
+ advance(2);
5652
+ } else if (ch === ">") {
5653
+ tokens.push({
5654
+ kind: "gt",
5655
+ value: ">",
5656
+ pos: start
5657
+ });
5658
+ advance(1);
5659
+ } else if (ch === "<") {
5660
+ tokens.push({
5661
+ kind: "lt",
5662
+ value: "<",
5663
+ pos: start
5664
+ });
5665
+ advance(1);
5666
+ } else {
5667
+ errors.push({
5668
+ message: `Unexpected character: ${ch}`,
5669
+ pos: start
5670
+ });
5671
+ advance(1);
5672
+ }
5673
+ continue;
5674
+ }
5675
+ switch (ch) {
5676
+ case "(":
5677
+ tokens.push({
5678
+ kind: "lparen",
5679
+ value: "(",
5680
+ pos: pos()
5681
+ });
5682
+ advance(1);
5683
+ break;
5684
+ case ")":
5685
+ tokens.push({
5686
+ kind: "rparen",
5687
+ value: ")",
5688
+ pos: pos()
5689
+ });
5690
+ advance(1);
5691
+ break;
5692
+ case ",":
5693
+ tokens.push({
5694
+ kind: "comma",
5695
+ value: ",",
5696
+ pos: pos()
5697
+ });
5698
+ advance(1);
5699
+ break;
5700
+ case ":":
5701
+ tokens.push({
5702
+ kind: "colon",
5703
+ value: ":",
5704
+ pos: pos()
5705
+ });
5706
+ advance(1);
5707
+ break;
5708
+ case ".":
5709
+ tokens.push({
5710
+ kind: "dot",
5711
+ value: ".",
5712
+ pos: pos()
5713
+ });
5714
+ advance(1);
5715
+ break;
5716
+ case "+":
5717
+ tokens.push({
5718
+ kind: "plus",
5719
+ value: "+",
5720
+ pos: pos()
5721
+ });
5722
+ advance(1);
5723
+ break;
5724
+ case "-":
5725
+ tokens.push({
5726
+ kind: "minus",
5727
+ value: "-",
5728
+ pos: pos()
5729
+ });
5730
+ advance(1);
5731
+ break;
5732
+ default:
5733
+ errors.push({
5734
+ message: `Unexpected character: ${ch}`,
5735
+ pos: pos()
5736
+ });
5737
+ advance(1);
5738
+ }
5739
+ }
5740
+ tokens.push({
5741
+ kind: "eof",
5742
+ value: "",
5743
+ pos: i
5744
+ });
5745
+ return {
5746
+ tokens,
5747
+ errors
5748
+ };
5749
+ }
5750
+ var AxiomParser = class {
5751
+ tokens;
5752
+ idx;
5753
+ errors;
5754
+ constructor(tokens) {
5755
+ this.tokens = tokens;
5756
+ this.idx = 0;
5757
+ this.errors = [];
5758
+ }
5759
+ current() {
5760
+ return this.tokens[this.idx];
5761
+ }
5762
+ advance() {
5763
+ return this.tokens[this.idx++];
5764
+ }
5765
+ expect(kind, msg) {
5766
+ if (this.current().kind === kind) return this.advance();
5767
+ this.errors.push({
5768
+ message: msg,
5769
+ pos: this.current().pos
5770
+ });
5771
+ return null;
5772
+ }
5773
+ parse() {
5774
+ const ast = this.parseExpr();
5775
+ return {
5776
+ ast: this.errors.length === 0 ? ast : null,
5777
+ errors: this.errors
5778
+ };
5779
+ }
5780
+ parseExpr() {
5781
+ return this.parseImplication();
5782
+ }
5783
+ parseImplication() {
5784
+ const left = this.parseLogicalOr();
5785
+ if (this.current().kind === "implies") {
5786
+ const pos = this.current().pos;
5787
+ this.advance();
5788
+ return {
5789
+ kind: "implication",
5790
+ antecedent: left,
5791
+ consequent: this.parseLogicalOr(),
5792
+ pos
5793
+ };
5794
+ }
5795
+ return left;
5796
+ }
5797
+ parseLogicalOr() {
5798
+ let left = this.parseLogicalAnd();
5799
+ while (this.current().kind === "or") {
5800
+ const pos = this.current().pos;
5801
+ this.advance();
5802
+ const right = this.parseLogicalAnd();
5803
+ left = {
5804
+ kind: "binaryOp",
5805
+ op: "or",
5806
+ left,
5807
+ right,
5808
+ pos
5809
+ };
5810
+ }
5811
+ return left;
5812
+ }
5813
+ parseLogicalAnd() {
5814
+ let left = this.parseUnary();
5815
+ while (this.current().kind === "and") {
5816
+ const pos = this.current().pos;
5817
+ this.advance();
5818
+ const right = this.parseUnary();
5819
+ left = {
5820
+ kind: "binaryOp",
5821
+ op: "and",
5822
+ left,
5823
+ right,
5824
+ pos
5825
+ };
5826
+ }
5827
+ return left;
5828
+ }
5829
+ parseUnary() {
5830
+ if (this.current().kind === "not") {
5831
+ const pos = this.current().pos;
5832
+ this.advance();
5833
+ return {
5834
+ kind: "unaryOp",
5835
+ op: "not",
5836
+ operand: this.parseUnary(),
5837
+ pos
5838
+ };
5839
+ }
5840
+ return this.parseComparison();
5841
+ }
5842
+ parseComparison() {
5843
+ const left = this.parseAdditive();
5844
+ if ([
5845
+ "gt",
5846
+ "gte",
5847
+ "lt",
5848
+ "lte",
5849
+ "eq",
5850
+ "neq",
5851
+ "in"
5852
+ ].includes(this.current().kind)) {
5853
+ const pos = this.current().pos;
5854
+ let op = this.advance().value;
5855
+ if (op === "in" && this.tokens[this.idx - 2]?.kind === "not") op = "not in";
5856
+ const right = this.parseAdditive();
5857
+ return {
5858
+ kind: "binaryOp",
5859
+ op,
5860
+ left,
5861
+ right,
5862
+ pos
5863
+ };
5864
+ }
5865
+ return left;
5866
+ }
5867
+ parseAdditive() {
5868
+ let left = this.parsePrimary();
5869
+ while (this.current().kind === "plus" || this.current().kind === "minus") {
5870
+ const pos = this.current().pos;
5871
+ const op = this.advance().value;
5872
+ const right = this.parsePrimary();
5873
+ left = {
5874
+ kind: "binaryOp",
5875
+ op,
5876
+ left,
5877
+ right,
5878
+ pos
5879
+ };
5880
+ }
5881
+ return left;
5882
+ }
5883
+ parsePrimary() {
5884
+ const tok = this.current();
5885
+ if (tok.kind === "lparen") {
5886
+ const pos = tok.pos;
5887
+ this.advance();
5888
+ const expr = this.parseExpr();
5889
+ this.expect("rparen", "Expected ')' after expression");
5890
+ return {
5891
+ kind: "parens",
5892
+ expr,
5893
+ pos
5894
+ };
5895
+ }
5896
+ if (tok.kind === "exists" || tok.kind === "forall") {
5897
+ const pos = tok.pos;
5898
+ const quant = tok.kind;
5899
+ this.advance();
5900
+ const varTok = this.expect("ident", "Expected variable after " + quant);
5901
+ if (!varTok) return {
5902
+ kind: "literal",
5903
+ value: null,
5904
+ raw: "null",
5905
+ pos
5906
+ };
5907
+ this.expect("in", "Expected 'in' after variable");
5908
+ const typeTok = this.expect("ident", "Expected type after 'in'");
5909
+ if (!typeTok) return {
5910
+ kind: "literal",
5911
+ value: null,
5912
+ raw: "null",
5913
+ pos
5914
+ };
5915
+ this.expect("colon", "Expected ':' after type in quantifier");
5916
+ const body = this.parseExpr();
5917
+ return {
5918
+ kind: "quantifier",
5919
+ quant,
5920
+ variable: varTok.value,
5921
+ typeName: typeTok.value,
5922
+ body,
5923
+ pos
5924
+ };
5925
+ }
5926
+ if (tok.kind === "ident" && this.tokens[this.idx + 1]?.kind === "lparen") {
5927
+ const pos = tok.pos;
5928
+ const name = this.advance().value;
5929
+ this.advance();
5930
+ const args = [];
5931
+ if (this.current().kind !== "rparen") {
5932
+ args.push(this.parseExpr());
5933
+ while (this.current().kind === "comma") {
5934
+ this.advance();
5935
+ args.push(this.parseExpr());
5936
+ }
5937
+ }
5938
+ this.expect("rparen", `Expected ')' after args of ${name}`);
5939
+ return {
5940
+ kind: "funcCall",
5941
+ name,
5942
+ args,
5943
+ pos
5944
+ };
5945
+ }
5946
+ if (tok.kind === "ident") {
5947
+ const pos = tok.pos;
5948
+ const path = [this.advance().value];
5949
+ while (this.current().kind === "dot") {
5950
+ this.advance();
5951
+ const next = this.expect("ident", "Expected identifier after '.'");
5952
+ if (next) path.push(next.value);
5953
+ }
5954
+ return {
5955
+ kind: "propAccess",
5956
+ path,
5957
+ pos
5958
+ };
5959
+ }
5960
+ if (tok.kind === "number") {
5961
+ const pos = tok.pos;
5962
+ const raw = tok.value;
5963
+ this.advance();
5964
+ return {
5965
+ kind: "literal",
5966
+ value: raw.includes(".") ? parseFloat(raw) : parseInt(raw, 10),
5967
+ raw,
5968
+ pos
5969
+ };
5970
+ }
5971
+ if (tok.kind === "string") {
5972
+ const pos = tok.pos;
5973
+ const val = tok.value;
5974
+ this.advance();
5975
+ return {
5976
+ kind: "literal",
5977
+ value: val,
5978
+ raw: "'" + val + "'",
5979
+ pos
5980
+ };
5981
+ }
5982
+ if (tok.kind === "boolean") {
5983
+ const pos = tok.pos;
5984
+ const val = tok.value === "true";
5985
+ this.advance();
5986
+ return {
5987
+ kind: "literal",
5988
+ value: val,
5989
+ raw: tok.value,
5990
+ pos
5991
+ };
5992
+ }
5993
+ if (tok.kind === "null") {
5994
+ const pos = tok.pos;
5995
+ this.advance();
5996
+ return {
5997
+ kind: "literal",
5998
+ value: null,
5999
+ raw: "null",
6000
+ pos
6001
+ };
6002
+ }
6003
+ this.errors.push({
6004
+ message: `Unexpected token: ${tok.value}`,
6005
+ pos: tok.pos
6006
+ });
6007
+ return {
6008
+ kind: "literal",
6009
+ value: null,
6010
+ raw: "null",
6011
+ pos: tok.pos
6012
+ };
6013
+ }
6014
+ };
6015
+ function collectRefs(node, boundVars = /* @__PURE__ */ new Set()) {
6016
+ if (!node) return [];
6017
+ const refs = [];
6018
+ function walk(n, bv) {
6019
+ switch (n.kind) {
6020
+ case "propAccess":
6021
+ if (n.path.length >= 1) {
6022
+ const first = n.path[0];
6023
+ if (bv.has(first)) break;
6024
+ refs.push({
6025
+ kind: "property",
6026
+ name: n.path.join("."),
6027
+ path: n.path,
6028
+ pos: n.pos
6029
+ });
6030
+ }
6031
+ break;
6032
+ case "funcCall":
6033
+ if (!bv.has(n.name)) refs.push({
6034
+ kind: "unknown",
6035
+ name: n.name,
6036
+ path: [n.name],
6037
+ pos: n.pos
6038
+ });
6039
+ n.args.forEach((a) => walk(a, bv));
6040
+ break;
6041
+ case "quantifier": {
6042
+ refs.push({
6043
+ kind: "type",
6044
+ name: n.typeName,
6045
+ path: [n.typeName],
6046
+ pos: n.pos
6047
+ });
6048
+ const inner = new Set(bv);
6049
+ inner.add(n.variable);
6050
+ walk(n.body, inner);
6051
+ break;
6052
+ }
6053
+ case "binaryOp":
6054
+ walk(n.left, bv);
6055
+ walk(n.right, bv);
6056
+ break;
6057
+ case "unaryOp":
6058
+ walk(n.operand, bv);
6059
+ break;
6060
+ case "implication":
6061
+ walk(n.antecedent, bv);
6062
+ walk(n.consequent, bv);
6063
+ break;
6064
+ case "parens": walk(n.expr, bv);
6065
+ }
6066
+ }
6067
+ walk(node, boundVars);
6068
+ return refs;
6069
+ }
6070
+ function findSerenityRoot() {
6071
+ if (ROOT !== void 0) return ROOT;
6072
+ let dir = resolve(__dirname, "..", "..", "..");
6073
+ const maxDepth = 20;
6074
+ for (let i = 0; i < maxDepth; i++) {
6075
+ const marker = resolve(dir, ".opencode", "skills", TOP_SKILL, "SKILL.md");
6076
+ if (existsSync(marker)) return dir;
6077
+ const parent = dirname(dir);
6078
+ if (parent === dir) break;
6079
+ dir = parent;
6080
+ }
6081
+ throw new MsmError("HOME_ROOT_NOT_FOUND", "system", "Cannot locate serenity root", {
6082
+ startDir: __dirname,
6083
+ topSkill: TOP_SKILL
6084
+ }, "Pass the container root via runOntologyCli(argv, { cwd }) — or run from inside the container workspace");
6085
+ }
6086
+ function loadOntoFile(filePath) {
6087
+ const absPath = resolve(BASE, filePath);
6088
+ if (!existsSync(absPath)) throw new MsmError("ONTO_FILE_NOT_FOUND", "system", `Ontology file not found: ${absPath}`, { path: absPath }, "Check the file path");
6089
+ let raw;
6090
+ try {
6091
+ raw = readFileSync(absPath, "utf-8");
6092
+ } catch (err) {
6093
+ throw new MsmError("ONTO_PARSE_ERROR", "system", `Failed to read file: ${absPath}`, { path: absPath }, void 0, err);
6094
+ }
6095
+ const result = parseTurtle(raw);
6096
+ if (result.errors.length > 0) throw new MsmError("ONTO_PARSE_ERROR", "system", `Failed to parse Turtle: ${absPath}\n ${result.errors.join("\n ")}`, {
6097
+ path: absPath,
6098
+ errors: result.errors
6099
+ }, "Check Turtle syntax");
6100
+ return {
6101
+ onto: turtleToOnto(result),
6102
+ raw
6103
+ };
6104
+ }
6105
+ function loadOntoFileWithImports(filePath, visited = /* @__PURE__ */ new Set()) {
6106
+ const absPath = resolve(BASE, filePath);
6107
+ if (visited.has(absPath)) throw new MsmError("ONTO_VALIDATION_FAILED", "user", `Circular import detected: ${absPath}`, {
6108
+ path: absPath,
6109
+ chain: [...visited, absPath]
6110
+ }, "Remove the circular dependency in imports");
6111
+ visited.add(absPath);
6112
+ const { onto: local } = loadOntoFile(absPath);
6113
+ const baseDir = dirname(absPath);
6114
+ return deepMergeOnto(local.imports && local.imports.length > 0 ? deepMergeOntoList(local.imports.map((imp) => {
6115
+ return loadOntoFileWithImports(resolve(baseDir, imp), new Set(visited));
6116
+ })) : emptyOnto(), local);
6117
+ }
6118
+ function emptyOnto() {
6119
+ return {
6120
+ types: {},
6121
+ relations: {},
6122
+ axioms: [],
6123
+ imports: []
6124
+ };
6125
+ }
6126
+ function deepMergeOnto(base, overlay) {
6127
+ return {
6128
+ name: overlay.name !== void 0 ? overlay.name : base.name,
6129
+ version: overlay.version !== void 0 ? overlay.version : base.version,
6130
+ imports: [...base.imports || [], ...overlay.imports || []],
6131
+ types: {
6132
+ ...base.types || {},
6133
+ ...overlay.types || {}
6134
+ },
6135
+ relations: {
6136
+ ...base.relations || {},
6137
+ ...overlay.relations || {}
6138
+ },
6139
+ axioms: [...base.axioms || [], ...overlay.axioms || []]
6140
+ };
6141
+ }
6142
+ function deepMergeOntoList(ontos) {
6143
+ return ontos.reduce((acc, o) => deepMergeOnto(acc, o), emptyOnto());
6144
+ }
6145
+ function validateOnto(onto, level) {
6146
+ const issues = [];
6147
+ const types = onto.types || {};
6148
+ const relations = onto.relations || {};
6149
+ const axioms = onto.axioms || [];
6150
+ const typeNames = new Set(Object.keys(types));
6151
+ const relNames = new Set(Object.keys(relations));
6152
+ for (const [name, td] of Object.entries(types)) {
6153
+ for (const st of td.superTypes || []) if (!typeNames.has(st)) issues.push({
6154
+ check: "M1",
6155
+ severity: "error",
6156
+ message: `Type "${name}" superType "${st}" does not exist`
6157
+ });
6158
+ for (const dep of td.dependsOn || []) if (!typeNames.has(dep)) issues.push({
6159
+ check: "M1",
6160
+ severity: "error",
6161
+ message: `Type "${name}" dependsOn "${dep}" which does not exist`
6162
+ });
6163
+ }
6164
+ for (const [name, rd] of Object.entries(relations)) {
6165
+ if (rd.domain && !typeNames.has(rd.domain)) issues.push({
6166
+ check: "M1",
6167
+ severity: "error",
6168
+ message: `Relation "${name}" domain "${rd.domain}" does not exist`
6169
+ });
6170
+ if (rd.range && !typeNames.has(rd.range)) issues.push({
6171
+ check: "M1",
6172
+ severity: "error",
6173
+ message: `Relation "${name}" range "${rd.range}" does not exist`
6174
+ });
6175
+ }
6176
+ function detectCycle(typeName, visiting = /* @__PURE__ */ new Set()) {
6177
+ if (visiting.has(typeName)) return true;
6178
+ visiting.add(typeName);
6179
+ const td = types[typeName];
6180
+ if (td?.superTypes) {
6181
+ for (const st of td.superTypes) if (typeNames.has(st) && detectCycle(st, new Set(visiting))) return true;
6182
+ }
6183
+ return false;
6184
+ }
6185
+ for (const tn of typeNames) if (detectCycle(tn)) {
6186
+ issues.push({
6187
+ check: "M2",
6188
+ severity: "error",
6189
+ message: `Inheritance cycle detected involving type "${tn}"`
6190
+ });
6191
+ break;
6192
+ }
6193
+ for (const [name, td] of Object.entries(types)) if (td.superTypes && td.rigidity) for (const st of td.superTypes) {
6194
+ const parent = types[st];
6195
+ if (parent?.rigidity) {
6196
+ if (td.rigidity === "rigid" && parent.rigidity !== "rigid") issues.push({
6197
+ check: "M3",
6198
+ severity: "error",
6199
+ message: `Type "${name}" is rigid but parent "${st}" is ${parent.rigidity} — child cannot be more rigid than parent`
6200
+ });
6201
+ }
6202
+ }
6203
+ function getIdentity(typeName) {
6204
+ const td = types[typeName];
6205
+ if (!td) return void 0;
6206
+ if (td.identity && td.identity.length > 0) return td.identity;
6207
+ if (td.superTypes) for (const st of td.superTypes) {
6208
+ const inherited = typeNames.has(st) ? getIdentity(st) : void 0;
6209
+ if (inherited) return inherited;
6210
+ }
6211
+ }
6212
+ for (const [name, td] of Object.entries(types)) if (td.rigidity === "rigid") {
6213
+ const id = getIdentity(name);
6214
+ if (!id || id.length === 0) issues.push({
6215
+ check: "M4",
6216
+ severity: "warning",
6217
+ message: `Type "${name}" is rigid but has no identity criteria (own or inherited)`
6218
+ });
6219
+ }
6220
+ function detectDepCycle(typeName, visiting = /* @__PURE__ */ new Set()) {
6221
+ if (visiting.has(typeName)) return true;
6222
+ visiting.add(typeName);
6223
+ const td = types[typeName];
6224
+ if (td?.dependsOn) {
6225
+ for (const dep of td.dependsOn) if (typeNames.has(dep) && detectDepCycle(dep, new Set(visiting))) return true;
6226
+ }
6227
+ return false;
6228
+ }
6229
+ for (const tn of typeNames) if (detectDepCycle(tn)) {
6230
+ issues.push({
6231
+ check: "M5",
6232
+ severity: "error",
6233
+ message: `Dependency cycle detected involving type "${tn}"`
6234
+ });
6235
+ break;
6236
+ }
6237
+ for (const [name, rd] of Object.entries(relations)) if (rd.functional) {
6238
+ const card = rd.cardinality || "";
6239
+ if (/min\s+(\d+)/.test(card) && parseInt(card.match(/min\s+(\d+)/)[1], 10) >= 2 || /^[2-9]/.test(card) || /\.\.\*/.test(card) || /\.\.[2-9]/.test(card)) issues.push({
6240
+ check: "M6",
6241
+ severity: "error",
6242
+ message: `Relation "${name}" is functional but cardinality "${card}" allows multiple values`
6243
+ });
6244
+ }
6245
+ for (const [typeName, td] of Object.entries(types)) if (td.properties) {
6246
+ for (const [propName, pd] of Object.entries(td.properties)) if (pd.type === "enum" && (!pd.values || pd.values.length === 0)) issues.push({
6247
+ check: "M7",
6248
+ severity: "error",
6249
+ message: `Type "${typeName}" property "${propName}" is enum but has no values`
6250
+ });
6251
+ }
6252
+ for (let i = 0; i < axioms.length; i++) {
6253
+ const ax = axioms[i];
6254
+ if (!ax.rule || ax.rule.trim() === "") continue;
6255
+ const { errors: parseErrors } = tokenize(ax.rule);
6256
+ if (parseErrors.length > 0) {
6257
+ issues.push({
6258
+ check: "M8",
6259
+ severity: "error",
6260
+ message: `Axiom #${i + 1} tokenization error: ${parseErrors[0].message}`
6261
+ });
6262
+ continue;
6263
+ }
6264
+ const { tokens } = tokenize(ax.rule);
6265
+ const parser = new AxiomParser(tokens);
6266
+ const result = parser.parse();
6267
+ if (parser.errors.length > 0 || !result.ast) for (const e of parser.errors) issues.push({
6268
+ check: "M8",
6269
+ severity: "error",
6270
+ message: `Axiom #${i + 1} parse error at pos ${e.pos}: ${e.message}`
6271
+ });
6272
+ }
6273
+ for (let i = 0; i < axioms.length; i++) {
6274
+ const ax = axioms[i];
6275
+ if (!ax.rule || ax.rule.trim() === "") continue;
6276
+ const { tokens } = tokenize(ax.rule);
6277
+ const parser = new AxiomParser(tokens);
6278
+ const result = parser.parse();
6279
+ if (!result.ast || parser.errors.length > 0) continue;
6280
+ const refs = collectRefs(result.ast);
6281
+ for (const ref of refs) {
6282
+ const name = ref.path[0];
6283
+ if (ref.kind === "property" && ref.path.length >= 2) {
6284
+ if (name && !typeNames.has(name) && !relNames.has(name)) issues.push({
6285
+ check: "M9",
6286
+ severity: "warning",
6287
+ message: `Axiom #${i + 1} references unknown type "${name}" in "${ref.path.join(".")}"`
6288
+ });
6289
+ }
6290
+ if (ref.kind === "type") {
6291
+ if (name && !typeNames.has(name)) issues.push({
6292
+ check: "M9",
6293
+ severity: "error",
6294
+ message: `Axiom #${i + 1} quantifier references unknown type "${name}"`
6295
+ });
6296
+ }
6297
+ if (ref.kind === "unknown") {
6298
+ if (name && !typeNames.has(name) && !relNames.has(name)) {}
6299
+ }
6300
+ }
6301
+ }
6302
+ return issues;
6303
+ }
6304
+ const HOSPITAL_TEMPLATE = ontoToTurtle({
6305
+ name: "hospital-domain",
6306
+ version: "0.1.0",
6307
+ types: {
6308
+ Person: {
6309
+ description: "A human being",
6310
+ rigidity: "rigid",
6311
+ identity: ["nhsNumber"],
6312
+ properties: {
6313
+ nhsNumber: {
6314
+ type: "string",
6315
+ cardinality: "exactly 1"
6316
+ },
6317
+ name: {
6318
+ type: "string",
6319
+ cardinality: "exactly 1"
6320
+ },
6321
+ dateOfBirth: {
6322
+ type: "date",
6323
+ cardinality: "exactly 1"
6324
+ }
6325
+ }
6326
+ },
6327
+ Patient: {
6328
+ superTypes: ["Person"],
6329
+ rigidity: "anti-rigid",
6330
+ dependsOn: ["Admission"],
6331
+ properties: { status: {
6332
+ type: "enum",
6333
+ values: [
6334
+ "admitted",
6335
+ "under_treatment",
6336
+ "discharged",
6337
+ "closed"
6338
+ ],
6339
+ cardinality: "exactly 1"
6340
+ } }
6341
+ },
6342
+ Ward: {
6343
+ description: "A physical location housing beds",
6344
+ rigidity: "rigid",
6345
+ identity: ["wardCode"],
6346
+ properties: {
6347
+ wardCode: {
6348
+ type: "string",
6349
+ cardinality: "exactly 1"
6350
+ },
6351
+ bedCapacity: {
6352
+ type: "integer",
6353
+ cardinality: "exactly 1"
6354
+ },
6355
+ status: {
6356
+ type: "enum",
6357
+ values: [
6358
+ "open",
6359
+ "full",
6360
+ "closed"
6361
+ ],
6362
+ cardinality: "exactly 1"
6363
+ }
6364
+ }
6365
+ },
6366
+ Bed: {
6367
+ description: "A bed within a ward",
6368
+ rigidity: "rigid",
6369
+ identity: ["ward", "bedNumber"],
6370
+ properties: { bedNumber: {
6371
+ type: "integer",
6372
+ cardinality: "exactly 1"
6373
+ } }
6374
+ },
6375
+ Admission: {
6376
+ description: "An event of a Patient being admitted",
6377
+ rigidity: "rigid",
6378
+ properties: {
6379
+ admissionDate: {
6380
+ type: "datetime",
6381
+ cardinality: "exactly 1"
6382
+ },
6383
+ dischargeDate: {
6384
+ type: "datetime",
6385
+ cardinality: "max 1"
6386
+ }
6387
+ }
6388
+ }
6389
+ },
6390
+ relations: {
6391
+ admittedTo: {
6392
+ domain: "Patient",
6393
+ range: "Ward",
6394
+ cardinality: "exactly 1",
6395
+ functional: true
6396
+ },
6397
+ admittedAs: {
6398
+ domain: "Admission",
6399
+ range: "Patient",
6400
+ cardinality: "exactly 1",
6401
+ functional: true
6402
+ },
6403
+ contains: {
6404
+ domain: "Ward",
6405
+ range: "Bed",
6406
+ cardinality: "min 0"
6407
+ },
6408
+ occupies: {
6409
+ domain: "Patient",
6410
+ range: "Bed",
6411
+ cardinality: "max 1"
6412
+ }
6413
+ },
6414
+ axioms: [
6415
+ {
6416
+ description: "Discharge date must be after admission date",
6417
+ rule: "Admission.dischargeDate > Admission.admissionDate",
6418
+ type: "temporal"
6419
+ },
6420
+ {
6421
+ description: "Only admitted patients can occupy a bed",
6422
+ rule: "occupies(patient, bed) implies admittedTo(patient, ward) and contains(ward, bed)",
6423
+ type: "dependency"
6424
+ },
6425
+ {
6426
+ description: "Closed ward has no patients",
6427
+ rule: "Ward.status == 'closed' implies not exists p in Patient: admittedTo(p, ward)",
6428
+ type: "state"
6429
+ }
6430
+ ]
6431
+ });
6432
+ const MINIMAL_TEMPLATE = ontoToTurtle({
6433
+ name: "example-domain",
6434
+ version: "0.1.0",
6435
+ types: { Thing: {
6436
+ description: "A generic entity",
6437
+ rigidity: "rigid",
6438
+ identity: ["name"],
6439
+ properties: { name: {
6440
+ type: "string",
6441
+ cardinality: "exactly 1"
6442
+ } }
6443
+ } },
6444
+ relations: {},
6445
+ axioms: []
6446
+ });
6447
+ function cmdInit(args) {
6448
+ if (args.length === 0) throw new MsmError("PARAMETER_MISSING", "user", "init requires a file path", { validArgs: ["<path>"] }, "Usage: ontology init <path> [--template minimal|full]");
6449
+ let filePath = resolve(BASE, args[0]);
6450
+ if (!filePath.endsWith(".ttl")) filePath += ".ttl";
6451
+ let template = "full";
6452
+ for (let i = 1; i < args.length; i++) if (args[i] === "--template" && i + 1 < args.length) {
6453
+ template = args[i + 1];
6454
+ i++;
6455
+ } else if (args[i].startsWith("--template=")) template = args[i].slice(11);
6456
+ else if (args[i].startsWith("-")) throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown flag: ${args[i]}`, {
6457
+ flag: args[i],
6458
+ validFlags: ["--template <minimal|full>"]
6459
+ }, "Usage: ontology init <path> [--template minimal|full]");
6460
+ if (template !== "minimal" && template !== "full") throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown template: ${template}`, {
6461
+ template,
6462
+ valid: ["minimal", "full"]
6463
+ }, "Use --template minimal or --template full");
6464
+ const content = template === "minimal" ? MINIMAL_TEMPLATE : HOSPITAL_TEMPLATE;
6465
+ if (existsSync(filePath)) throw new MsmError("PARAMETER_INVALID_VALUE", "user", `File already exists: ${filePath}`, { path: filePath }, "Use a different path or remove the existing file");
6466
+ try {
6467
+ writeFileSync(filePath, content, "utf-8");
6468
+ } catch (err) {
6469
+ throw new MsmError("ONTO_PARSE_ERROR", "system", `Failed to write file: ${filePath}`, { path: filePath }, void 0, err);
6470
+ }
6471
+ emit(`Ontology template created at: ${filePath}\n`);
6472
+ emit(`Template: ${template}\n`);
6473
+ }
6474
+ function cmdCheck(args) {
6475
+ if (args.length === 0) throw new MsmError("PARAMETER_MISSING", "user", "check requires a file path", { validArgs: ["<path>"] }, "Usage: ontology check <path> [--level normal|strict]");
6476
+ const filePath = args[0];
6477
+ let level = "normal";
6478
+ for (let i = 1; i < args.length; i++) if (args[i] === "--level" && i + 1 < args.length) {
6479
+ const v = args[i + 1];
6480
+ if (v !== "normal" && v !== "strict") throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown level: ${v}`, {
6481
+ level: v,
6482
+ valid: ["normal", "strict"]
6483
+ }, "Use --level normal or --level strict");
6484
+ level = v;
6485
+ i++;
6486
+ } else if (args[i].startsWith("--level=")) {
6487
+ const v = args[i].slice(8);
6488
+ if (v !== "normal" && v !== "strict") throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown level: ${v}`, {
6489
+ level: v,
6490
+ valid: ["normal", "strict"]
6491
+ }, "Use --level normal or --level strict");
6492
+ level = v;
6493
+ } else if (args[i].startsWith("-")) throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown flag: ${args[i]}`, {
6494
+ flag: args[i],
6495
+ validFlags: ["--level <normal|strict>"]
6496
+ }, "Usage: ontology check <path> [--level normal|strict]");
6497
+ let onto;
6498
+ let issues = [];
6499
+ try {
6500
+ onto = loadOntoFileWithImports(filePath);
6501
+ } catch (err) {
6502
+ if (err instanceof MsmError && err.code === "ONTO_VALIDATION_FAILED" && err.message.startsWith("Circular import")) throw err;
6503
+ issues.push({
6504
+ check: "M10",
6505
+ severity: "error",
6506
+ message: err instanceof MsmError ? err.message : String(err)
6507
+ });
6508
+ onto = loadOntoFile(filePath).onto;
6509
+ }
6510
+ issues = [...issues, ...validateOnto(onto, level)];
6511
+ emit(`\nOntology Validation: ${onto.name || filePath}\n`);
6512
+ emit("=".repeat(60) + "\n");
6513
+ if (issues.length === 0) {
6514
+ emit("All checks passed.\n");
6515
+ return;
6516
+ }
6517
+ const errors = issues.filter((i) => i.severity === "error");
6518
+ const warnings = issues.filter((i) => i.severity === "warning");
6519
+ emit(`\nErrors: ${errors.length}, Warnings: ${warnings.length}\n\n`);
6520
+ for (const issue of issues) emit(` ${issue.severity === "error" ? "ERR" : "WRN"} [${issue.check}] ${issue.message}\n`);
6521
+ if (level === "strict") {
6522
+ emit(`\n── Strict mode — LLM review suggestions ──\n`);
6523
+ emit(` (Run through LLM for L1-L5 semantic checks)\n`);
6524
+ printLlmReviewHints(onto, issues);
6525
+ }
6526
+ if (errors.length > 0) throw new MsmError("ONTO_VALIDATION_FAILED", "user", `Validation failed: ${errors.length} error(s), ${warnings.length} warning(s)`, {
6527
+ errors: errors.length,
6528
+ warnings: warnings.length
6529
+ }, "Fix the errors listed above and re-run check");
6530
+ }
6531
+ function printLlmReviewHints(onto, issues) {
6532
+ const types = onto.types || {};
6533
+ for (const [name, td] of Object.entries(types)) if (!td.rigidity) emit(` [L1] Type "${name}" has no rigidity label — should it be rigid or anti-rigid?\n`);
6534
+ emit(` [L2] Review axioms for domain completeness — are there missing temporal or state constraints?\n`);
6535
+ for (const [name, td] of Object.entries(types)) if (td.rigidity === "rigid" && td.identity && td.identity.length === 1) emit(` [L3] Type "${name}" identity: ${td.identity.join(", ")} — is this sufficient for uniqueness?\n`);
6536
+ const typeNames = Object.keys(types);
6537
+ if (typeNames.length > 0) emit(` [L4] Review types: ${typeNames.join(", ")} — are there missing distinctions?\n`);
6538
+ if ((onto.axioms || []).map((a) => a.description || a.rule).join("; ")) emit(` [L5] Axioms reference concepts — review if all referenced entities are defined\n`);
6539
+ }
6540
+ function cmdExplain(args) {
6541
+ if (args.length === 0) throw new MsmError("PARAMETER_MISSING", "user", "explain requires a file path", { validArgs: ["<path>"] }, "Usage: ontology explain <path>");
6542
+ const filePath = args[0];
6543
+ const { onto } = loadOntoFile(filePath);
6544
+ const merged = onto.imports && onto.imports.length > 0 ? loadOntoFileWithImports(filePath) : onto;
6545
+ emit(`## Ontology: ${onto.name || filePath}\n`);
6546
+ if (onto.version) emit(`Version: ${onto.version}\n`);
6547
+ if (onto.imports && onto.imports.length > 0) emit(`Imports: ${onto.imports.join(", ")}\n`);
6548
+ const types = merged.types || {};
6549
+ emit(`\n### Types (${Object.keys(types).length})\n\n`);
6550
+ emit("| Type | SuperTypes | Rigidity | Properties |\n");
6551
+ emit("|------|-----------|----------|------------|\n");
6552
+ for (const [name, td] of Object.entries(types)) emit(`| ${name} | ${(td.superTypes || []).join(", ") || "-"} | ${td.rigidity || "-"} | ${td.properties ? Object.keys(td.properties).join(", ") : "-"} |\n`);
6553
+ const relations = merged.relations || {};
6554
+ if (Object.keys(relations).length > 0) {
6555
+ emit(`\n### Relations\n\n`);
6556
+ emit("| Name | Domain | Range | Cardinality | Flags |\n");
6557
+ emit("|------|--------|-------|-------------|-------|\n");
6558
+ for (const [name, rd] of Object.entries(relations)) {
6559
+ const flags = [];
6560
+ if (rd.functional) flags.push("functional");
6561
+ if (rd.transitive) flags.push("transitive");
6562
+ if (rd.symmetric) flags.push("symmetric");
6563
+ emit(`| ${name} | ${rd.domain || "-"} | ${rd.range || "-"} | ${rd.cardinality || "-"} | ${flags.join(", ") || "-"} |\n`);
6564
+ }
6565
+ }
6566
+ const axioms = merged.axioms || [];
6567
+ if (axioms.length > 0) {
6568
+ emit(`\n### Axioms (${axioms.length})\n\n`);
6569
+ for (const ax of axioms) emit(`- ${ax.description || "(no description)"}: \`${ax.rule}\` [${ax.type || "rule"}]\n`);
6570
+ }
6571
+ emit(`\n### Dependency Graph\n\n`);
6572
+ for (const [name, td] of Object.entries(merged.types || {})) {
6573
+ const deps = [];
6574
+ if (td.superTypes) deps.push(...td.superTypes.map((s) => `"${name}" -> "${s}" [label="isa"]`));
6575
+ if (td.dependsOn) deps.push(...td.dependsOn.map((d) => `"${name}" -> "${d}" [label="depends"]`));
6576
+ deps.forEach((d) => emit(` ${d}\n`));
6577
+ }
6578
+ }
6579
+ function pascalToKebab(name) {
6580
+ return name.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
6581
+ }
6582
+ function isConcreteSkill(typeName, onto) {
6583
+ if (typeName === "Skill" || typeName === "SystemComponent" || typeName === "AccBuiltin") return false;
6584
+ const td = onto.types?.[typeName];
6585
+ if (!td) return false;
6586
+ return (td.superTypes || []).includes("Skill");
6587
+ }
6588
+ function getOntoSkillList(onto) {
6589
+ return Object.keys(onto.types || {}).filter((n) => isConcreteSkill(n, onto));
6590
+ }
6591
+ function loadMechRegistry(rootPath) {
6592
+ const regPath = join(rootPath, ".opencode", "skills", TOP_SKILL, "mech-registry.json");
6593
+ if (!existsSync(regPath)) return {};
6594
+ try {
6595
+ const raw = JSON.parse(readFileSync(regPath, "utf-8"));
6596
+ const entries = raw.msms || raw;
6597
+ const result = {};
6598
+ for (const e of Array.isArray(entries) ? entries : Object.values(entries)) if (e.name) result[e.name] = { skill: e.skill || "" };
6599
+ return result;
6600
+ } catch {
6601
+ return {};
6602
+ }
6603
+ }
6604
+ function cmdVerify(args) {
6605
+ if (args.length === 0) throw new MsmError("PARAMETER_MISSING", "user", "verify requires a file path", { validArgs: ["<path>"] }, "Usage: ontology verify <path> [--level operational|continuity|full]");
6606
+ const filePath = args[0];
6607
+ let level = "operational";
6608
+ for (let i = 1; i < args.length; i++) if (args[i] === "--level" && i + 1 < args.length) {
6609
+ const v = args[i + 1];
6610
+ if (v !== "operational" && v !== "continuity" && v !== "full") throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown level: ${v}`, {}, "Use --level operational|continuity|full");
6611
+ level = v;
6612
+ i++;
6613
+ } else if (args[i].startsWith("--level=")) {
6614
+ const v = args[i].slice(8);
6615
+ if (v !== "operational" && v !== "continuity" && v !== "full") throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown level: ${v}`, {}, "Use --level operational|continuity|full");
6616
+ level = v;
6617
+ }
6618
+ const runOperational = level === "operational" || level === "full";
6619
+ const runContinuity = level === "continuity" || level === "full";
6620
+ const rootPath = findSerenityRoot();
6621
+ let onto;
6622
+ try {
6623
+ onto = loadOntoFileWithImports(filePath);
6624
+ } catch (err) {
6625
+ onto = loadOntoFile(filePath).onto;
6626
+ }
6627
+ const issues = [];
6628
+ const ontoSkills = new Set(getOntoSkillList(onto));
6629
+ const skillsDir = join(rootPath, ".opencode", "skills");
6630
+ const actualDirs = existsSync(skillsDir) ? readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name) : [];
6631
+ const declaredSkills = [];
6632
+ for (const typeName of ontoSkills) {
6633
+ const kebab = pascalToKebab(typeName);
6634
+ declaredSkills.push(kebab);
6635
+ const skillMdPath = join(skillsDir, kebab, "SKILL.md");
6636
+ if (runOperational && !existsSync(skillMdPath)) issues.push({
6637
+ check: "V1",
6638
+ severity: "warning",
6639
+ message: `Ontology declares "${typeName}" but "${kebab}/SKILL.md" not found`
6640
+ });
6641
+ }
6642
+ if (runOperational) {
6643
+ for (const dir of actualDirs) if (!declaredSkills.includes(dir)) issues.push({
6644
+ check: "V2",
6645
+ severity: "warning",
6646
+ message: `Directory ".opencode/skills/${dir}/" exists but no ontology type declares it`
6647
+ });
6648
+ }
6649
+ if (runOperational) {
6650
+ const reg = loadMechRegistry(rootPath);
6651
+ if (Object.keys(reg).length > 0) {
6652
+ for (const [msmName, info] of Object.entries(reg)) if (info.skill) {
6653
+ const kebab = info.skill;
6654
+ if (!declaredSkills.includes(kebab) && !actualDirs.includes(kebab)) {}
6655
+ if (!actualDirs.includes(kebab)) issues.push({
6656
+ check: "V3",
6657
+ severity: "error",
6658
+ message: `MSM "${msmName}" registered under skill "${kebab}" but directory not found`
6659
+ });
6660
+ }
6661
+ } else issues.push({
6662
+ check: "V3",
6663
+ severity: "info",
6664
+ message: "No mech-registry.json found — MSM registration check skipped (MSMs managed by ACC at runtime)"
6665
+ });
6666
+ }
6667
+ if (runContinuity) {
6668
+ const sessionsDir = join(rootPath, "AGENT_SESSIONS");
6669
+ if (existsSync(sessionsDir)) {
6670
+ const sessionDirs = readdirSync(sessionsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
6671
+ const goals = [];
6672
+ for (const sd of sessionDirs) {
6673
+ const smPath = join(sessionsDir, sd, "SESSION.md");
6674
+ if (existsSync(smPath)) try {
6675
+ const goalMatch = readFileSync(smPath, "utf-8").match(/## Goal\n(.+)/);
6676
+ if (goalMatch) goals.push(goalMatch[1].trim());
6677
+ } catch {}
6678
+ }
6679
+ const seen = /* @__PURE__ */ new Set();
6680
+ for (const g of goals) {
6681
+ if (seen.has(g)) issues.push({
6682
+ check: "V4",
6683
+ severity: "warning",
6684
+ message: `Duplicate session goal: "${g}" — potential entropy duplication`
6685
+ });
6686
+ seen.add(g);
6687
+ }
6688
+ issues.push({
6689
+ check: "V4",
6690
+ severity: sessionDirs.length > 100 ? "warning" : "info",
6691
+ message: `Session count: ${sessionDirs.length} — ` + (sessionDirs.length > 100 ? "consider archiving old sessions to reduce H_op" : "within normal range")
6692
+ });
6693
+ let staleCount = 0;
6694
+ for (const sd of sessionDirs) {
6695
+ const smPath = join(sessionsDir, sd, "SESSION.md");
6696
+ if (existsSync(smPath)) try {
6697
+ readFileSync(smPath);
6698
+ } catch {
6699
+ staleCount++;
6700
+ }
6701
+ }
6702
+ }
6703
+ }
6704
+ emit(`\nOntology Compliance Verification: ${onto.name || filePath}\n`);
6705
+ emit("=".repeat(60) + "\n");
6706
+ emit(`Level: ${level}\n`);
6707
+ emit(`Ontology skills declared: ${ontoSkills.size}\n`);
6708
+ emit(`Actual skill dirs found: ${actualDirs.length}\n\n`);
6709
+ if (issues.length === 0) {
6710
+ emit("All compliance checks passed.\n");
6711
+ return;
6712
+ }
6713
+ const errors = issues.filter((i) => i.severity === "error");
6714
+ const warnings = issues.filter((i) => i.severity === "warning");
6715
+ const info = issues.filter((i) => i.severity === "info");
6716
+ emit(`Errors: ${errors.length}, Warnings: ${warnings.length}, Info: ${info.length}\n\n`);
6717
+ for (const issue of issues) emit(` ${issue.severity === "error" ? "ERR" : issue.severity === "warning" ? "WRN" : "INF"} [${issue.check}] ${issue.message}\n`);
6718
+ if (errors.length > 0) throw new MsmError("ONTO_VALIDATION_FAILED", "user", `Compliance verification found ${errors.length} error(s)`, {
6719
+ errors: errors.length,
6720
+ warnings: warnings.length
6721
+ }, "Fix the errors listed above");
6722
+ }
6723
+ const REVIEW_DIMENSIONS = [
6724
+ {
6725
+ id: "D1",
6726
+ name: "Type Semantics",
6727
+ description: "Are type names, descriptions, and domain boundaries clear and correct?",
6728
+ checklist: [
6729
+ "Are all type names unambiguous within the domain vocabulary?",
6730
+ "Do descriptions clearly demarcate what IS and IS NOT an instance of this type?",
6731
+ "Are there types that should be split into finer-grained subtypes (under-modeled)?",
6732
+ "Are there types that should be merged (over-modeled, redundant distinctions)?",
6733
+ "Are type names consistent with domain conventions (e.g. singular nouns, no verb phrases)?"
6734
+ ]
6735
+ },
6736
+ {
6737
+ id: "D2",
6738
+ name: "Taxonomy Quality",
6739
+ description: "Is the subclass hierarchy logically sound and semantically defensible?",
6740
+ checklist: [
6741
+ "Does every superType relation satisfy the 'is-a' test (every X is-a Y in all contexts)?",
6742
+ "Are there missing superType links (concepts that should be generalized)?",
6743
+ "Are there incorrect superType links (passes structural check but fails semantic 'is-a')?",
6744
+ "Are types that should be disjoint from each other declared as disjoint?",
6745
+ "Does the taxonomy depth justify itself (3-7 levels is typical for domain ontologies)?"
6746
+ ]
6747
+ },
6748
+ {
6749
+ id: "D3",
6750
+ name: "Rigidity Audit",
6751
+ description: "Is every rigidity assignment (rigid/anti-rigid/semi-rigid) justified?",
6752
+ checklist: [
6753
+ "For each 'rigid' type: would removing this property destroy the entity's identity?",
6754
+ "For each 'anti-rigid' type: is it a role/stage/state that the same entity can stop being?",
6755
+ "For types with no rigidity label: what should it be and why?",
6756
+ "Does the rigidity inheritance hold — is any child more rigid than its parent? (M3 catches this)",
6757
+ "Are there 'semi-rigid' types where rigid or anti-rigid would be more appropriate?"
6758
+ ]
6759
+ },
6760
+ {
6761
+ id: "D4",
6762
+ name: "Identity Criteria",
6763
+ description: "Are identity criteria sufficient to guarantee uniqueness for rigid types?",
6764
+ checklist: [
6765
+ "For each rigid type: do the identity keys actually guarantee uniqueness?",
6766
+ "Are identity keys external (natural keys like NHS number) or internal (surrogate)? Prefer natural.",
6767
+ "Are there types that depend on mutable properties for identity (anti-pattern)?",
6768
+ "For types with a single identity property: could there be edge cases where it's not unique?",
6769
+ "Are identity criteria inherited correctly from superTypes?"
6770
+ ]
6771
+ },
6772
+ {
6773
+ id: "D5",
6774
+ name: "Relation Semantics",
6775
+ description: "Do relations accurately model real-world connections between entities?",
6776
+ checklist: [
6777
+ "Does every relation have a clear real-world interpretation (not just a technical link)?",
6778
+ "Are domain and range constraints correct — could any non-domain instance legitimately participate?",
6779
+ "Are property characteristics (functional/transitive/symmetric/etc.) consistent with reality?",
6780
+ "Are there missing relations between types that should be connected?",
6781
+ "Do cardinality constraints match what's physically possible in the domain?"
6782
+ ]
6783
+ },
6784
+ {
6785
+ id: "D6",
6786
+ name: "Axiom Completeness & Correctness",
6787
+ description: "Do axioms capture the right constraints, and are there gaps?",
6788
+ checklist: [
6789
+ "Are all axioms logically sound (no contradictions, no tautologies)?",
6790
+ "Are there missing temporal constraints (sequencing rules like 'discharge must follow admission')?",
6791
+ "Are there missing state constraints (mutual exclusion, preconditions, invariants)?",
6792
+ "Are there missing dependency constraints (an entity's existence depends on another)?",
6793
+ "Are there missing cardinality constraints on the instance level (not just relation level)?",
6794
+ "Do axioms reference only defined types and relations (M9 catches this)?"
6795
+ ]
6796
+ },
6797
+ {
6798
+ id: "D7",
6799
+ name: "Domain Completeness",
6800
+ description: "What important concepts or constraints are missing from this ontology?",
6801
+ checklist: [
6802
+ "What real-world entities in this domain have NOT been modeled?",
6803
+ "What relationships between existing types are unrepresented?",
6804
+ "What temporal aspects (lifecycle stages, state machines) are missing?",
6805
+ "What edge cases (exceptions, rare events) should be representable?",
6806
+ "Is the ontology wide enough for its stated purpose, or too narrow?",
6807
+ "Is it deep enough (sufficient type granularity), or too shallow?"
6808
+ ]
6809
+ },
6810
+ {
6811
+ id: "D8",
6812
+ name: "OntoClean Deep Review",
6813
+ description: "Apply OntoClean meta-property analysis to the full taxonomy.",
6814
+ checklist: [
6815
+ "Identity (+I/-I): For each sortal type, what is the identity criterion? Is it consistent down the hierarchy?",
6816
+ "Unity (+U/~U/-U): Can all instances form wholes? Any anti-unity types (e.g. 'pile of sand')?",
6817
+ "Rigidity inheritance: Run the full OntoClean constraint checker — rigid types cannot subsume anti-rigid.",
6818
+ "Dependence (+D/-D): Does any type's existence necessarily imply another type's existence?",
6819
+ "Sortal vs. Non-sortal: Are there types that should be non-sortal (no identity criteria possible)?",
6820
+ "Backbone taxonomy: Does the rigid-only backbone form a consistent tree?"
6821
+ ]
6822
+ }
6823
+ ];
6824
+ function cmdReview(args) {
6825
+ if (args.length === 0) throw new MsmError("PARAMETER_MISSING", "user", "review requires a file path", { validArgs: ["<path>"] }, "Usage: ontology review <path> [--level semantic|onto-clean|full] [--format prompt|json]");
6826
+ const filePath = args[0];
6827
+ let level = "full";
6828
+ let format = "prompt";
6829
+ for (let i = 1; i < args.length; i++) if (args[i] === "--level" && i + 1 < args.length) {
6830
+ const v = args[i + 1];
6831
+ if (v !== "semantic" && v !== "onto-clean" && v !== "full") throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown level: ${v}`, {
6832
+ level: v,
6833
+ valid: [
6834
+ "semantic",
6835
+ "onto-clean",
6836
+ "full"
6837
+ ]
6838
+ });
6839
+ level = v;
6840
+ i++;
6841
+ } else if (args[i].startsWith("--level=")) {
6842
+ const v = args[i].slice(8);
6843
+ if (v !== "semantic" && v !== "onto-clean" && v !== "full") throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown level: ${v}`);
6844
+ level = v;
6845
+ } else if (args[i] === "--format" && i + 1 < args.length) {
6846
+ const v = args[i + 1];
6847
+ if (v !== "prompt" && v !== "json") throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown format: ${v}`);
6848
+ format = v;
6849
+ i++;
6850
+ } else if (args[i].startsWith("--format=")) {
6851
+ const v = args[i].slice(9);
6852
+ if (v !== "prompt" && v !== "json") throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown format: ${v}`);
6853
+ format = v;
6854
+ } else if (args[i].startsWith("-")) throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown flag: ${args[i]}`);
6855
+ const { onto } = loadOntoFile(filePath);
6856
+ const merged = onto.imports && onto.imports.length > 0 ? loadOntoFileWithImports(filePath) : onto;
6857
+ let dimensions;
6858
+ if (level === "semantic") dimensions = REVIEW_DIMENSIONS.filter((d) => parseInt(d.id.slice(1)) <= 6);
6859
+ else if (level === "onto-clean") dimensions = REVIEW_DIMENSIONS.filter((d) => d.id === "D7" || d.id === "D8");
6860
+ else dimensions = REVIEW_DIMENSIONS;
6861
+ if (format === "json") {
6862
+ const types = merged.types || {};
6863
+ const relations = merged.relations || {};
6864
+ const axioms = merged.axioms || [];
6865
+ const out = {
6866
+ ontology: {
6867
+ name: onto.name || filePath,
6868
+ version: onto.version,
6869
+ imports: onto.imports || [],
6870
+ types: Object.entries(types).map(([n, t]) => ({
6871
+ name: n,
6872
+ description: t.description,
6873
+ superTypes: t.superTypes,
6874
+ rigidity: t.rigidity,
6875
+ identity: t.identity,
6876
+ dependsOn: t.dependsOn,
6877
+ properties: t.properties
6878
+ })),
6879
+ relations: Object.entries(relations).map(([n, r]) => ({
6880
+ name: n,
6881
+ ...r
6882
+ })),
6883
+ axioms
6884
+ },
6885
+ reviewLevel: level,
6886
+ dimensions: dimensions.map((d) => ({
6887
+ id: d.id,
6888
+ name: d.name,
6889
+ checklist: d.checklist
6890
+ })),
6891
+ instruction: "For each checklist item in each dimension, provide: {severity: 'P0'|'P1'|'P2'|'P3', finding: string, location: string, evidence: string, suggestion: string}"
6892
+ };
6893
+ emit(JSON.stringify(out, null, 2) + "\n");
6894
+ return;
6895
+ }
6896
+ const types = merged.types || {};
6897
+ const relations = merged.relations || {};
6898
+ const axioms = merged.axioms || [];
6899
+ emit(`# Ontology Semantic Review\n`);
6900
+ emit(`**Ontology**: ${onto.name || filePath}\n`);
6901
+ if (onto.version) emit(`**Version**: ${onto.version}\n`);
6902
+ if (onto.imports && onto.imports.length > 0) emit(`**Imports**: ${onto.imports.join(", ")}\n`);
6903
+ emit(`**Review Level**: ${level}\n\n`);
6904
+ emit(`## Context: Ontology to Review\n\n`);
6905
+ emit(`### Types (${Object.keys(types).length})\n\n`);
6906
+ emit("| Type | SuperTypes | Rigidity | Identity | Properties |\n");
6907
+ emit("|------|-----------|----------|----------|------------|\n");
6908
+ for (const [name, td] of Object.entries(types)) emit(`| ${name} | ${(td.superTypes || []).join(", ") || "-"} | ${td.rigidity || "-"} | ${(td.identity || []).join(", ") || "-"} | ${td.properties ? Object.keys(td.properties).join(", ") : "-"} |\n`);
6909
+ if (Object.keys(relations).length > 0) {
6910
+ emit(`\n### Relations\n\n`);
6911
+ emit("| Name | Domain | Range | Cardinality | Flags |\n");
6912
+ emit("|------|--------|-------|-------------|-------|\n");
6913
+ for (const [name, rd] of Object.entries(relations)) {
6914
+ const flags = [];
6915
+ if (rd.functional) flags.push("functional");
6916
+ if (rd.transitive) flags.push("transitive");
6917
+ if (rd.symmetric) flags.push("symmetric");
6918
+ emit(`| ${name} | ${rd.domain || "-"} | ${rd.range || "-"} | ${rd.cardinality || "-"} | ${flags.join(", ") || "-"} |\n`);
6919
+ }
6920
+ }
6921
+ if (axioms.length > 0) {
6922
+ emit(`\n### Axioms (${axioms.length})\n\n`);
6923
+ for (const ax of axioms) emit(`- \`${ax.rule}\` — ${ax.description || "(no description)"} [${ax.type || "rule"}]\n`);
6924
+ }
6925
+ emit(`\n### Type Hierarchy\n\n`);
6926
+ for (const [name, td] of Object.entries(types)) if (td.superTypes && td.superTypes.length > 0) emit(`- ${td.superTypes.join(", ")} → ${name}\n`);
6927
+ emit(`\n---\n\n`);
6928
+ emit(`## Review Dimensions (${dimensions.length})\n\n`);
6929
+ emit(`Review this ontology systematically. For each dimension, examine every checklist item.\n`);
6930
+ emit(`Classify findings by severity:\n`);
6931
+ emit(`- **P0 (Critical)**: Would cause incorrect behavior, data corruption, or violates domain laws\n`);
6932
+ emit(`- **P1 (High)**: Makes the ontology misleading or unusable for its stated purpose\n`);
6933
+ emit(`- **P2 (Medium)**: Reduces quality, clarity, or reusability\n`);
6934
+ emit(`- **P3 (Low)**: Minor improvement or style concern\n\n`);
6935
+ for (const dim of dimensions) {
6936
+ emit(`### ${dim.id}: ${dim.name}\n\n`);
6937
+ emit(`${dim.description}\n\n`);
6938
+ emit("**Checklist**:\n\n");
6939
+ for (let ci = 0; ci < dim.checklist.length; ci++) emit(`${ci + 1}. ${dim.checklist[ci]}\n`);
6940
+ emit(`\n`);
6941
+ }
6942
+ emit(`---\n\n`);
6943
+ emit(`## Expected Output Format\n\n`);
6944
+ emit(`For each finding, produce a row in this format:\n\n`);
6945
+ emit("| Dimension | Severity | Type/Relation/Axiom | Finding | Evidence | Suggestion |\n");
6946
+ emit("|-----------|----------|---------------------|---------|----------|------------|\n\n");
6947
+ emit(`At the end, provide a summary section:\n`);
6948
+ emit("1. **Top 3 issues** — most critical findings that MUST be addressed\n");
6949
+ emit("2. **Ontology health assessment** — overall quality rating (A-F) with justification\n");
6950
+ emit("3. **Recommendations** — ordered list of concrete changes\n");
6951
+ }
6952
+ function cmdCompliance(args) {
6953
+ if (args.length === 0) throw new MsmError("PARAMETER_MISSING", "user", "compliance requires a file path", { validArgs: ["<path>"] }, "Usage: ontology compliance <path> [--skills <dir>] [--format prompt|json]");
6954
+ const filePath = args[0];
6955
+ let skillsDir = resolve(BASE, ".opencode", "skills");
6956
+ let format = "prompt";
6957
+ for (let i = 1; i < args.length; i++) if (args[i] === "--skills" && i + 1 < args.length) {
6958
+ skillsDir = resolve(BASE, args[i + 1]);
6959
+ i++;
6960
+ } else if (args[i].startsWith("--skills=")) skillsDir = resolve(BASE, args[i].slice(9));
6961
+ else if (args[i] === "--format" && i + 1 < args.length) {
6962
+ const v = args[i + 1];
6963
+ if (v !== "prompt" && v !== "json") throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown format: ${v}`);
6964
+ format = v;
6965
+ i++;
6966
+ } else if (args[i].startsWith("--format=")) {
6967
+ const v = args[i].slice(9);
6968
+ if (v !== "prompt" && v !== "json") throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown format: ${v}`);
6969
+ format = v;
6970
+ } else if (args[i].startsWith("-")) throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown flag: ${args[i]}`);
6971
+ let skillNames = [];
6972
+ if (existsSync(skillsDir)) skillNames = readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
6973
+ const { onto } = loadOntoFile(filePath);
6974
+ const merged = onto.imports && onto.imports.length > 0 ? loadOntoFileWithImports(filePath) : onto;
6975
+ const axioms = merged.axioms || [];
6976
+ const types = merged.types || {};
6977
+ merged.relations;
6978
+ const typeNames = new Set(Object.keys(types));
6979
+ const skillTypeNames = /* @__PURE__ */ new Set();
6980
+ const skillSubtypeNames = /* @__PURE__ */ new Set();
6981
+ for (const [name, td] of Object.entries(types)) if (name === "Skill" || name.endsWith("Skill")) {
6982
+ skillTypeNames.add(name);
6983
+ for (const [sn, sd] of Object.entries(types)) if (sd.superTypes?.includes(name)) skillSubtypeNames.add(sn);
6984
+ }
6985
+ if (skillTypeNames.size === 0) for (const name of typeNames) skillTypeNames.add(name);
6986
+ const checks = [];
6987
+ for (let ai = 0; ai < axioms.length; ai++) {
6988
+ const ax = axioms[ai];
6989
+ if (!ax.rule || ax.rule.trim() === "") continue;
6990
+ const ruleText = ax.rule;
6991
+ const { tokens, errors: tokErrors } = tokenize(ax.rule);
6992
+ if (tokErrors.length > 0) continue;
6993
+ const parser = new AxiomParser(tokens);
6994
+ const result = parser.parse();
6995
+ if (!result.ast || parser.errors.length > 0) continue;
6996
+ const refs = collectRefs(result.ast);
6997
+ const axiomTypeRefs = refs.filter((r) => r.kind === "type").map((r) => r.name);
6998
+ refs.filter((r) => r.kind === "property").map((r) => r.name);
6999
+ let targets = [];
7000
+ let pattern = "unknown";
7001
+ if (axiomTypeRefs.filter((t) => skillTypeNames.has(t) || skillSubtypeNames.has(t)).length > 0) {
7002
+ pattern = "universal-skill";
7003
+ if (skillNames.length > 0) targets = [...skillNames];
7004
+ else targets = ["*"];
7005
+ }
7006
+ for (const ref of refs) if (ref.kind === "unknown" && ref.name === "skillDependsOn") {
7007
+ /**
7008
+ * 只为每条依赖子句取**第一参数**(= 声明义务承担方)。
7009
+ *
7010
+ * 2026-09-14 S188 修(缺陷④):旧实现把 `skillDependsOn(A, B)` 的两端都收进 targets,
7011
+ * 于是按生成指引逐 target 审会得到一半**方向错误的伪 Fail**(如"home-memory 未声明
7012
+ * 依赖 zhaocai-memory"——B 端根本不欠任何声明)。同时修缺陷⑤:合取式
7013
+ * (`dependsOn(X,A) and dependsOn(X,B)`)里 X 出现两次,旧实现不去重。
7014
+ */
7015
+ function findDependsOnDeclarers(node) {
7016
+ if (!node) return [];
7017
+ switch (node.kind) {
7018
+ case "funcCall":
7019
+ if (node.name === "skillDependsOn" && node.args.length >= 1) {
7020
+ const a = node.args[0];
7021
+ return [a.kind === "propAccess" ? a.path.join(".") : ""];
7022
+ }
7023
+ return node.args.flatMap(findDependsOnDeclarers);
7024
+ case "quantifier": return findDependsOnDeclarers(node.body);
7025
+ case "binaryOp": return [...findDependsOnDeclarers(node.left), ...findDependsOnDeclarers(node.right)];
7026
+ case "unaryOp": return findDependsOnDeclarers(node.operand);
7027
+ case "implication": return [...findDependsOnDeclarers(node.antecedent), ...findDependsOnDeclarers(node.consequent)];
7028
+ case "parens": return findDependsOnDeclarers(node.expr);
7029
+ default: return [];
7030
+ }
7031
+ }
7032
+ const depArgs = findDependsOnDeclarers(result.ast);
7033
+ /**
7034
+ * 2026-09-14 S188 修(缺陷③):旧实现用 `pascalToKebab(a) === s` 做**过滤**,
7035
+ * 却输出 `a`(原 PascalCase 类名),导致生成的 verification 路径
7036
+ * `.opencode/skills/HomeMemory/SKILL.md` **根本不存在**。此处输出**匹配到的目录名**。
7037
+ */
7038
+ const renameMap = /* @__PURE__ */ new Map();
7039
+ for (const s of skillNames) renameMap.set(pascalToKebab(s), s);
7040
+ const depTarget = Array.from(new Set(depArgs.filter((a) => !!a).map((a) => skillNames.includes(a) ? a : renameMap.get(pascalToKebab(a))).filter((s) => !!s)));
7041
+ if (depTarget.length > 0) {
7042
+ pattern = "specific-dependency";
7043
+ targets = depTarget;
7044
+ }
7045
+ break;
7046
+ }
7047
+ if (/forall\s+\w+\s+in\s+Skill\b/.test(ruleText) && skillNames.length > 0) targets = [...skillNames];
7048
+ targets = Array.from(new Set(targets));
7049
+ if (ruleText.includes("skillPrecedes") || ruleText.includes("precedes")) pattern = "precedence";
7050
+ const mentionsThisFile = /\bthisFile\b/.test(ruleText) || /本(文件|注册表)/.test(ax.description || "");
7051
+ const mentionsMsmInventory = /MSMInventory|MSM 清单|mech-registry/.test(ruleText + (ax.description || "")) || /forall\s+\w+\s+in\s+MSM\b/.test(ruleText);
7052
+ let verification = "";
7053
+ let question = "";
7054
+ if (mentionsThisFile || mentionsMsmInventory) {
7055
+ const subject = mentionsMsmInventory ? `MSM 注册表(.opencode/skills/${TOP_SKILL}/references/mech-registry.json)` : "本注册表文件自身";
7056
+ pattern = "self-check";
7057
+ targets = [subject];
7058
+ question = `Does ${subject} satisfy this axiom?`;
7059
+ verification = `Read ${subject},核对该约束(**不是**读 skill 文档)`;
7060
+ } else if (ax.type === "structural" || ruleText.includes("Skill.hasSkillMd") || ruleText.includes("SKILL.md")) {
7061
+ pattern = "structural-hasSkillMd";
7062
+ if (targets.length === 0 || targets[0] === "*") targets = skillNames.length > 0 ? [...skillNames] : ["*"];
7063
+ question = `Does this skill have a SKILL.md file?`;
7064
+ verification = `Check if .opencode/skills/{skill}/SKILL.md exists`;
7065
+ } else if (ax.type === "dependency" || pattern === "specific-dependency") {
7066
+ question = `Is this dependency declared in the skill's SKILL.md?`;
7067
+ verification = `Read .opencode/skills/{skill}/SKILL.md, check \"相关技能\" / \"Related Skills\" section for the required dependency`;
7068
+ } else if (ax.type === "cce" || ax.type === "eap") {
7069
+ question = `Does this skill's implementation satisfy the CCE/EAP constraint?`;
7070
+ verification = `Read .opencode/skills/{skill}/SKILL.md and references/, evaluate against the axiom`;
7071
+ pattern = pattern === "unknown" ? ax.type : pattern;
7072
+ } else if (ax.type === "constraint" || ax.type === "domain") {
7073
+ question = `Does this skill satisfy the constraint?`;
7074
+ verification = `Read .opencode/skills/{skill}/SKILL.md and check constraint compliance`;
7075
+ } else if (pattern === "universal-skill") {
7076
+ question = `Does this skill satisfy the axiom?`;
7077
+ verification = `Read .opencode/skills/{skill}/SKILL.md, evaluate compliance`;
7078
+ } else {
7079
+ question = `Is this axiom satisfied?`;
7080
+ verification = `Read relevant skill files and evaluate`;
7081
+ }
7082
+ let severity = "P1";
7083
+ if (ax.type === "structural") severity = "P0";
7084
+ else if (ax.type === "cce") severity = "P0";
7085
+ else if (ruleText.includes("Skill.hasSkillMd")) severity = "P0";
7086
+ else if (ruleText.includes("not exists")) severity = "P1";
7087
+ else if (ax.type === "eap") severity = "P2";
7088
+ if (targets.length === 0) continue;
7089
+ /**
7090
+ * 2026-09-14 S188 修(缺陷①):检查 ID 由**位置派生**(`C{axiomIndex+1}`)改为
7091
+ * **规则文本稳定哈希**。
7092
+ *
7093
+ * 旧行为之害(实测):axiom 集合是 `cce ← ccc ← serenity-skills` 三者合并,
7094
+ * 注册表一增删公理,**全体 ID 平移** —— v0.2.0(55 公理/33 检查)与 v0.3.0(53/29)
7095
+ * 之间,同一字母数字指向不同公理;历史报告引用的 C055 在现行清单中已不存在。
7096
+ * 任何「关闭 C0xx」式的跨会话交接都会失准。
7097
+ *
7098
+ * 新 ID = `C-` + 规则文本 sha256 前 6 位十六进制:与公理内容绑定,与顺序无关;
7099
+ * 同一规则在任意版本生成同一 ID,跨报告可比。旧的**序号**另存为 `axiomIndex` 字段
7100
+ * (仅供排序/展示,不用于引用)。
7101
+ */
7102
+ const idHash = createHash("sha256").update(ax.rule.trim().replace(/\s+/g, " ")).digest("hex").slice(0, 6);
7103
+ checks.push({
7104
+ id: `C-${idHash}`,
7105
+ axiomIndex: ai,
7106
+ axiomRule: ax.rule,
7107
+ axiomDescription: ax.description || "(no description)",
7108
+ axiomType: ax.type || "rule",
7109
+ pattern,
7110
+ severity,
7111
+ targets,
7112
+ question,
7113
+ verification
7114
+ });
7115
+ }
7116
+ /**
7117
+ * 2026-09-14 S188 修(缺陷①收尾):按**规范化规则**去重。
7118
+ *
7119
+ * 为什么要去重而不是保留重复项:ID 现在绑定规则文本,重复公理会产生**同名 ID 的两条
7120
+ * 检查**——对下游审计是毒药(同一 ID 两条记录,无法引用)。去重后「一个 ID = 一条公理」,
7121
+ * 冗余则通过 stderr 显式报告,交给注册表维护者决定是否清理(不静默吞掉)。
7122
+ */
7123
+ const byRule = /* @__PURE__ */ new Map();
7124
+ const dupReports = [];
7125
+ for (const c of checks) {
7126
+ const key = c.axiomRule.trim().replace(/\s+/g, " ");
7127
+ const prev = byRule.get(key);
7128
+ if (prev) {
7129
+ dupReports.push(` ${c.id} 重复公理:${c.axiomRule}`);
7130
+ prev.targets = Array.from(/* @__PURE__ */ new Set([...prev.targets, ...c.targets]));
7131
+ continue;
7132
+ }
7133
+ byRule.set(key, c);
7134
+ }
7135
+ const dedupedChecks = Array.from(byRule.values());
7136
+ if (dupReports.length > 0) emitErr(`[compliance] 警告:注册表内存在 ${dupReports.length} 条重复公理(同一规则写了多遍)。\n已按规则合并为单一检查项;建议清理注册表以消除冗余:\n` + dupReports.join("\n") + "\n");
7137
+ checks.length = 0;
7138
+ checks.push(...dedupedChecks);
7139
+ if (format === "json") {
7140
+ const output = {
7141
+ ontology: merged.name || filePath,
7142
+ skills_dir: skillsDir,
7143
+ skill_count: skillNames.length,
7144
+ axiom_count: axioms.length,
7145
+ check_count: checks.length,
7146
+ checks: checks.map((c) => ({
7147
+ ...c,
7148
+ targets: c.targets.filter((t) => t !== "*")
7149
+ }))
7150
+ };
7151
+ emit(JSON.stringify(output, null, 2) + "\n");
7152
+ return;
7153
+ }
7154
+ emit(`# Compliance Checklist — ${merged.name || filePath}\n\n`);
7155
+ emit(`**Generated by ontology-engineering compliance**\n\n`);
7156
+ emit(`- Skills directory: \`${skillsDir}\`\n`);
7157
+ emit(`- Skills found: ${skillNames.length}\n`);
7158
+ emit(`- Axioms analyzed: ${axioms.length}\n`);
7159
+ emit(`- Compliance checks generated: ${checks.length}\n\n`);
7160
+ emit(`---\n\n`);
7161
+ emit(`## Instructions for Subagent\n\n`);
7162
+ emit(`You are auditing skill implementations against an ontology. For each check item:\n\n`);
7163
+ emit(`1. **Read the relevant files** as specified in the verification column\n`);
7164
+ emit(`2. **Determine pass/fail** based on evidence from the files\n`);
7165
+ emit(`3. **Report** in table format: Check ID | Skill | Pass/Fail | Evidence | Notes\n\n`);
7166
+ emit(`---\n\n`);
7167
+ emit(`## Checks\n\n`);
7168
+ const byTarget = /* @__PURE__ */ new Map();
7169
+ for (const c of checks) for (const t of c.targets) {
7170
+ const key = t === "*" ? "ALL" : t;
7171
+ if (!byTarget.has(key)) byTarget.set(key, []);
7172
+ byTarget.get(key).push(c);
7173
+ }
7174
+ for (const [target, items] of byTarget.entries()) {
7175
+ emit(`### ${target === "ALL" ? "All Skills" : target}\n\n`);
7176
+ emit("| ID | Severity | Pattern | Axiom | What to Verify |\n");
7177
+ emit("|----|----------|---------|-------|----------------|\n");
7178
+ for (const item of items) {
7179
+ const axShort = item.axiomDescription.length > 60 ? item.axiomDescription.slice(0, 57) + "..." : item.axiomDescription;
7180
+ const verifyShort = item.verification.length > 80 ? item.verification.slice(0, 77) + "..." : item.verification;
7181
+ emit(`| ${item.id} | **${item.severity}** | ${item.pattern} | ${axShort} | ${verifyShort} |\n`);
7182
+ }
7183
+ emit("\n");
7184
+ for (const item of items) {
7185
+ emit(`<details>\n<summary>**${item.id}** — ${item.axiomDescription}</summary>\n\n`);
7186
+ emit(`- **Axiom:** \`${item.axiomRule}\`\n`);
7187
+ emit(`- **Type:** ${item.axiomType}\n`);
7188
+ emit(`- **Severity:** ${item.severity}\n`);
7189
+ emit(`- **Target skills:** ${item.targets.join(", ")}\n\n`);
7190
+ emit(`**Verification:** ${item.verification}\n\n`);
7191
+ if (item.targets.length === 1 && item.targets[0] !== "*") emit(`**File path:** \`${item.verification.replace(/\{skill\}/g, item.targets[0])}\`\n\n`);
7192
+ emit(`</details>\n\n`);
7193
+ }
7194
+ }
7195
+ emit(`---\n\n`);
7196
+ emit(`## Summary\n\n`);
7197
+ emit(`- **P0 (blocking):** ${checks.filter((c) => c.severity === "P0").length} checks\n`);
7198
+ emit(`- **P1 (important):** ${checks.filter((c) => c.severity === "P1").length} checks\n`);
7199
+ emit(`- **P2 (nice-to-have):** ${checks.filter((c) => c.severity === "P2").length} checks\n`);
7200
+ }
7201
+ function rejectProtocolFlags(args) {
7202
+ for (const arg of args) if (arg === "--json" || arg === "--help" || arg === "-h" || arg === "--log") throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Business MSM does not accept protocol flag: ${arg}`, {
7203
+ arg,
7204
+ validSubcommands: [
7205
+ "init",
7206
+ "check",
7207
+ "explain",
7208
+ "verify",
7209
+ "review",
7210
+ "compliance"
7211
+ ]
7212
+ }, "Use the `ontology` tool (see its description for flags) — e.g. ontology(command=\"check\", path=…)");
7213
+ }
7214
+ const VALID_SUBCOMMANDS = [
7215
+ "init",
7216
+ "check",
7217
+ "explain",
7218
+ "verify",
7219
+ "review",
7220
+ "compliance"
7221
+ ];
7222
+ function dispatch$1(argv) {
7223
+ if (argv.length === 0) throw new MsmError("PARAMETER_MISSING", "user", "Missing subcommand", { validSubcommands: VALID_SUBCOMMANDS }, "Usage: ontology <init|check|explain|verify|review|compliance> [args...]");
7224
+ rejectProtocolFlags(argv);
7225
+ const subcommand = argv[0];
7226
+ const subArgs = argv.slice(1);
7227
+ switch (subcommand) {
7228
+ case "init":
7229
+ cmdInit(subArgs);
7230
+ return 0;
7231
+ case "check":
7232
+ cmdCheck(subArgs);
7233
+ return 0;
7234
+ case "explain":
7235
+ cmdExplain(subArgs);
7236
+ return 0;
7237
+ case "verify":
7238
+ cmdVerify(subArgs);
7239
+ return 0;
7240
+ case "review":
7241
+ cmdReview(subArgs);
7242
+ return 0;
7243
+ case "compliance":
7244
+ cmdCompliance(subArgs);
7245
+ return 0;
7246
+ default: throw new MsmError("PARAMETER_INVALID_VALUE", "user", `Unknown subcommand: ${subcommand}`, {
7247
+ subcommand,
7248
+ validSubcommands: VALID_SUBCOMMANDS
7249
+ }, "Usage: ontology <init|check|explain|verify|review|compliance> [args...]");
7250
+ }
7251
+ }
7252
+ /**
7253
+ * runOntologyCli —— 同步跑一次引擎调用,**返回**输出与退出码(不打印、不退出)。
7254
+ *
7255
+ * 调用方(工具壳 `src/tools/ontology-eng.ts`)负责:路径守卫(必须落在 CCC 内)、
7256
+ * 以及把 `code !== 0` 翻成响亮的回执(引擎的 stderr 文案**逐字保留**,不重写)。
7257
+ */
7258
+ function runOntologyCli(argv, opts = {}) {
7259
+ let out = "";
7260
+ let err = "";
7261
+ const prevOut = OUT;
7262
+ const prevErr = ERR;
7263
+ const prevRoot = ROOT;
7264
+ const prevTopSkill = TOP_SKILL;
7265
+ const prevSr = SR;
7266
+ const prevBase = BASE;
7267
+ OUT = { write: (s) => {
7268
+ out += s;
7269
+ } };
7270
+ ERR = { write: (s) => {
7271
+ err += s;
7272
+ } };
7273
+ let code = 0;
7274
+ try {
7275
+ if (opts.cwd !== void 0) {
7276
+ ROOT = opts.cwd;
7277
+ BASE = opts.cwd;
7278
+ }
7279
+ if (opts.topSkill !== void 0) TOP_SKILL = opts.topSkill;
7280
+ if (opts.namespace !== void 0) SR = opts.namespace;
7281
+ code = dispatch$1(argv);
7282
+ } catch (e) {
7283
+ if (e instanceof MsmError) {
7284
+ emitError(e);
7285
+ code = e.exitCode();
7286
+ } else {
7287
+ const ie = e instanceof Error ? e : new Error(String(e));
7288
+ const fallback = new MsmError("INTERNAL_UNHANDLED_STATE", "internal", `Unhandled exception: ${ie.message}`, { stack: ie.stack }, "Report this bug to the Agent", ie);
7289
+ emitError(fallback);
7290
+ code = fallback.exitCode();
7291
+ }
7292
+ } finally {
7293
+ BASE = prevBase;
7294
+ ROOT = prevRoot;
7295
+ TOP_SKILL = prevTopSkill;
7296
+ SR = prevSr;
7297
+ OUT = prevOut;
7298
+ ERR = prevErr;
7299
+ }
7300
+ return {
7301
+ code,
7302
+ stdout: out,
7303
+ stderr: err
7304
+ };
7305
+ }
7306
+ //#endregion
7307
+ //#region src/tools/ontology-eng.ts
7308
+ /**
7309
+ * ontology-eng.ts(工具面)— 本体工程**引擎**(v1.52.0,S142 D121)
7310
+ *
7311
+ * 来源:宁静号 CCC 的 MSM `ontology-engineering`,owner 2026-10-11 裁「**搬到 ACC**」。
7312
+ * 判据:ACC 侧此前只有 `praxis ontology` 的**文本手册**(说得出判据 M1–M10 的编号与含义),
7313
+ * 却**一行可执行代码都没有** —— 能真跑 `check/verify/compliance` 的是引擎,不是手册。
7314
+ *
7315
+ * 🔴 **三段分工,别混**:
7316
+ * · **引擎** `src/ontology/engine.ts` —— 解析 Turtle、M1–M10 结构检查、L1–L5 提示、
7317
+ * V1–V4 运行时合规、review/compliance 提示词生成。纯函数、只用 node 内建模块。
7318
+ * · **本文件(工具壳)** —— 参数 → argv、**路径守卫**、把 `(code, stdout, stderr)` 翻成回执。
7319
+ * · **方法手册** `src/tools/ontology.ts`(praxis 第四段)—— 给人/模型读的"怎么做本体工程"。
7320
+ *
7321
+ * 🔴 **为什么路径必须先过 `resolveInside`**:引擎会 `process.chdir(cccRoot)` 再按路径读写,
7322
+ * 且 `init` **会写文件** ⇒ 入参路径不先证"在容器内",就等于绕过 CCC 边界(P3)。
7323
+ *
7324
+ * 🔴 **退出码是返回值不是进程退出**(搬迁改写 ②):引擎不再 `process.exit`,
7325
+ * 本壳把 `code !== 0` 连同引擎**逐字保留**的 stderr 文案一起回执(不重写引擎的报错)。
7326
+ */
7327
+ const COMMANDS = [
7328
+ "init",
7329
+ "check",
7330
+ "explain",
7331
+ "verify",
7332
+ "review",
7333
+ "compliance"
7334
+ ];
7335
+ const LEVELS = [
7336
+ "normal",
7337
+ "strict",
7338
+ "operational",
7339
+ "continuity",
7340
+ "full",
7341
+ "semantic",
7342
+ "onto-clean"
7343
+ ];
7344
+ const TEMPLATES = ["minimal", "full"];
7345
+ const FORMATS = ["prompt", "json"];
7346
+ /** 各子命令接受的 level 取值(否则引擎会以 PARAMETER_INVALID_VALUE 拒掉——这里先拦,报错更直白) */
7347
+ const LEVELS_BY_COMMAND = {
7348
+ check: ["normal", "strict"],
7349
+ verify: [
7350
+ "operational",
7351
+ "continuity",
7352
+ "full"
7353
+ ],
7354
+ review: [
7355
+ "semantic",
7356
+ "onto-clean",
7357
+ "full"
7358
+ ]
7359
+ };
7360
+ function renderText$5(value) {
7361
+ return [{
7362
+ type: "text",
7363
+ text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
7364
+ }];
7365
+ }
7366
+ /**
7367
+ * 把引擎结果翻成**一段文本回执**(本工具返回 string —— 见 `schema: 'string'` 的理由)。
7368
+ *
7369
+ * 🔴 **为什么返回字符串而不是 JSON 对象**:引擎的报告是**多行 Markdown 式**(表格 + 逐条 ERR/WRN),
7370
+ * 走 `schema:'json'` 会被 `JSON.stringify` 把换行转义成 `\n` ⇒ 模型读到的是**一坨转义串**,
7371
+ * 可读性直接没了。字符串形态还能原样保留引擎文案("逐字"是搬迁的硬要求)。
7372
+ * 退出码印在**首行**(回执自带判据,不必让调用方去数文本)。
7373
+ */
7374
+ function receiptOf(v) {
7375
+ const parts = [`[ontology ${v.command}] ${v.ok ? "✓" : "✗"} exit ${v.code} — ${v.path}`];
7376
+ if (v.stdout.trim() !== "") parts.push(v.stdout.trimEnd());
7377
+ if (!v.ok && v.stderr.trim() !== "") parts.push(v.stderr.trimEnd());
7378
+ if (!v.ok) parts.push(v.code === 1 ? "(exit 1 = 参数或校验失败:修完再跑;引擎的 ERR 行在正文里)" : v.code === 2 ? "(exit 2 = 文件缺失/解析失败:先确认路径与 .ttl 语法)" : "(exit 4 = 引擎内部错误:把上面整段报给 ACC 维护者)");
7379
+ return parts.join("\n\n");
7380
+ }
7381
+ /**
7382
+ * 本体工程引擎(`ontology`)—— 六个子命令:init/check/explain/verify/review/compliance。
7383
+ * 只读的五个不写任何文件;`init` 会在容器内**新建** .ttl 模板。
7384
+ */
7385
+ function createOntologyTool() {
7386
+ return defineTool({
7387
+ name: "ontology",
7388
+ description: "Ontology engineering engine (parses Turtle .ttl, runs the judgement sets, emits reports). Subcommands: init (write a template) / check (M1-M10 structural validation; level=strict adds L1-L5 hints) / explain (natural-language summary) / verify (V1-V4 runtime compliance: declaration vs reality) / review (semantic review prompt for a read-only subagent; level=semantic|onto-clean|full) / compliance (axiom-vs-skill compliance checklist; skill by skill). path must stay inside this container (relative to the container root, or an absolute path inside it). Exit code is returned in the receipt: 0 = clean, 1 = parameter/validation failure, 2 = file/parse failure, 4 = internal. The engine text is passed through verbatim — read it, do not assume success from the call alone.",
7389
+ parameters: {
7390
+ command: {
7391
+ type: "string",
7392
+ enum: [...COMMANDS],
7393
+ description: "Subcommand: init / check / explain / verify / review / compliance."
7394
+ },
7395
+ path: {
7396
+ type: "string",
7397
+ description: "Path to the .ttl file, inside this container (relative to the container root, or an absolute path that stays inside it)."
7398
+ },
7399
+ level: {
7400
+ type: "string",
7401
+ enum: [...LEVELS],
7402
+ description: "Depth knob, meaning depends on the subcommand: check → normal|strict; verify → operational|continuity|full; review → semantic|onto-clean|full."
7403
+ },
7404
+ template: {
7405
+ type: "string",
7406
+ enum: [...TEMPLATES],
7407
+ description: "init only: template size, minimal (default) or full."
7408
+ },
7409
+ skills: {
7410
+ type: "string",
7411
+ description: "compliance only: skills directory to scan (default .opencode/skills inside the container)."
7412
+ },
7413
+ format: {
7414
+ type: "string",
7415
+ enum: [...FORMATS],
7416
+ description: "review / compliance only: prompt (LLM-readable checklist, default) or json."
7417
+ }
7418
+ },
7419
+ output: {
7420
+ schema: { type: "string" },
7421
+ render: (_args, value) => renderText$5(value)
7422
+ },
7423
+ async execute(args, exec) {
7424
+ const root = cccRootForExec(exec);
7425
+ if (!root) throw new Error(NO_CCC_FROM_AGENT_CWD);
7426
+ const command = typeof args.command === "string" ? args.command.trim() : "";
7427
+ if (!COMMANDS.includes(command)) throw new Error(`ontology: \`command\` must be one of ${COMMANDS.join(" / ")} (got: ${command === "" ? "(missing)" : command})`);
7428
+ const rawPath = typeof args.path === "string" ? args.path.trim() : "";
7429
+ if (rawPath === "") throw new Error("ontology: `path` is required (a .ttl file inside this container)");
7430
+ const absPath = resolveInside(root, rawPath);
7431
+ const relPath = relative(root, absPath) || ".";
7432
+ const argv = [command, absPath];
7433
+ const level = typeof args.level === "string" ? args.level.trim() : "";
7434
+ if (level !== "") {
7435
+ const allowed = LEVELS_BY_COMMAND[command];
7436
+ if (allowed === void 0) throw new Error(`ontology: \`level\` does not apply to \`${command}\` (it applies to: check / verify / review)`);
7437
+ if (!allowed.includes(level)) throw new Error(`ontology: level "${level}" is not valid for \`${command}\` — use one of ${allowed.join(" / ")}`);
7438
+ argv.push("--level", level);
7439
+ }
7440
+ const template = typeof args.template === "string" ? args.template.trim() : "";
7441
+ if (template !== "") {
7442
+ if (command !== "init") throw new Error(`ontology: \`template\` applies to \`init\` only (got command: ${command})`);
7443
+ if (!TEMPLATES.includes(template)) throw new Error(`ontology: template "${template}" is not valid — use one of ${TEMPLATES.join(" / ")}`);
7444
+ argv.push("--template", template);
7445
+ }
7446
+ const format = typeof args.format === "string" ? args.format.trim() : "";
7447
+ if (format !== "") {
7448
+ if (command !== "review" && command !== "compliance") throw new Error(`ontology: \`format\` applies to review / compliance only (got command: ${command})`);
7449
+ if (!FORMATS.includes(format)) throw new Error(`ontology: format "${format}" is not valid — use one of ${FORMATS.join(" / ")}`);
7450
+ argv.push("--format", format);
7451
+ }
7452
+ const skills = typeof args.skills === "string" ? args.skills.trim() : "";
7453
+ if (skills !== "") {
7454
+ if (command !== "compliance") throw new Error(`ontology: \`skills\` applies to \`compliance\` only (got command: ${command})`);
7455
+ argv.push("--skills", resolveInside(root, skills));
7456
+ }
7457
+ const res = runOntologyCli(argv, { cwd: root });
7458
+ return receiptOf({
7459
+ ok: res.code === 0,
7460
+ code: res.code,
7461
+ command,
7462
+ path: relPath,
7463
+ stdout: res.stdout,
7464
+ stderr: res.stderr
7465
+ });
7466
+ }
7467
+ });
7468
+ }
7469
+ //#endregion
4635
7470
  //#region src/handyman-preset-inherit.ts
4636
7471
  /**
4637
7472
  * 解析 handyman worker 从父 agent 继承的 preset,并组装创建 setup 钩子。
@@ -17696,7 +20531,7 @@ function matchCcc(input, candidates) {
17696
20531
  error: "empty ccc"
17697
20532
  };
17698
20533
  if (trimmed.startsWith("/")) {
17699
- const root = findSerenityRoot(trimmed);
20534
+ const root = findSerenityRoot$1(trimmed);
17700
20535
  if (!root) return {
17701
20536
  ok: false,
17702
20537
  error: `no CCC found from path: ${trimmed}`,
@@ -20764,6 +23599,7 @@ function apply(ctx, config) {
20764
23599
  ctx.tools.register(gitTool);
20765
23600
  ctx.tools.register(msmTool);
20766
23601
  ctx.tools.register(praxisTool);
23602
+ ctx.tools.register(createOntologyTool());
20767
23603
  ctx.tools.register(createHandymanTool(ctx));
20768
23604
  ctx.tools.register(localstoreTool);
20769
23605
  ctx.tools.register(createContainerAdminTool(ctx));