From 08122b1b33917c99acde50e5a6c406f7b813952a Mon Sep 17 00:00:00 2001 From: modusensus Date: Thu, 12 Nov 2026 03:55:29 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(choro):=20wiki=20=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=E4=B8=89=E5=A4=84=E3=80=8C=E7=BB=99=E4=BA=BA=E7=9C=8B=E3=80=8D?= =?UTF-8?q?=E5=8F=A3=E5=BE=84=E2=80=94=E2=80=94=E6=9C=BA=E5=99=A8=E6=A0=87?= =?UTF-8?q?=E8=AE=B0=E4=B8=8D=E9=9C=B2=E3=80=81=E6=96=87=E4=BB=B6=20status?= =?UTF-8?q?=20=E5=9B=9E=E5=86=99=E3=80=81=E6=A0=91=E6=8C=89=20parent=5Fid?= =?UTF-8?q?=20=E5=BB=BA=E5=B1=82=E7=BA=A7=EF=BC=88#582=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dsh-mneme/CHANGELOG.md | 3 +++ dsh-mneme/lib/client.js | 42 ++++++++++++++++++++++++++++++++-- dsh-mneme/lib/wiki/render.js | 5 ++++ dsh-mneme/lib/wiki/t1.js | 9 +++++++- dsh-mneme/src/wiki/render.js | 5 ++++ dsh-mneme/src/wiki/t1.js | 9 +++++++- dsh-mneme/test/client.test.js | 34 +++++++++++++++++++++++++++ dsh-mneme/test/wiki-t1.test.js | 10 ++++++++ 8 files changed, 113 insertions(+), 4 deletions(-) diff --git a/dsh-mneme/CHANGELOG.md b/dsh-mneme/CHANGELOG.md index f60e00e1..6aa265e0 100644 --- a/dsh-mneme/CHANGELOG.md +++ b/dsh-mneme/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +- **wiki 面板三处「给人看」的口径修正:机器标记不再露、文件 status 与库同步、树的缩进看真父子(#582)**:chorography 的价值主要在「人看」,但面板把三件「给程序看的东西」也摆了出来。**① `` 等机器标记原样显示**——`renderWikiMarkdown` 没有 HTML 注释分支,t0/t1 用来剥段与回填的段落边界成了一行行裸文本;现在渲染层跳过「整行以 `` 与 + // `` 是 t0/t1 剥段与回填用的段落边界, + // 给程序看的;排版里它们是一行裸文本噪音。只隐藏「整行以 "; export const FM_VERIFIED_BASELINE = "verifiedBaseline"; export const FM_VERIFIED_AT = "verifiedAt"; +// 页面生命周期档(#582):status 也是 frontmatter 键,但它 T0 / T1 都写——T0 +// 起骨架时写 skeleton,T1 成功时随 verified 两键一起推进 enriched。定义在这 +// 与另两个键同住:frontmatter 的形状属于页面格式。 +export const FM_STATUS = "status"; + /** frontmatter 边界:首行 --- 与下一个 --- 的行号;不存在返回 null。 */ function frontmatterBounds(lines) { if (String(lines[0] ?? "").trim() !== "---") return null; diff --git a/dsh-mneme/lib/wiki/t1.js b/dsh-mneme/lib/wiki/t1.js index d7161caa..fffab37d 100644 --- a/dsh-mneme/lib/wiki/t1.js +++ b/dsh-mneme/lib/wiki/t1.js @@ -25,7 +25,7 @@ import { tmpdir } from "node:os"; import { realpathSync } from "node:fs"; import { extractHumanBlocks, injectHumanBlocks, T1_SECTION_START, T1_SECTION_END, - FM_VERIFIED_AT, FM_VERIFIED_BASELINE, setFrontmatterKeys, + FM_VERIFIED_AT, FM_VERIFIED_BASELINE, FM_STATUS, setFrontmatterKeys, } from "./render.js"; import { gitHead } from "./t0.js"; // issue #607:拒绝文案里的 CLI 命令要给绝对路径入口(profile 装的插件没有全局 @@ -566,6 +566,13 @@ function enrichPageFile(pageFile, sections, { verifiedBaseline = null, verifiedA const finalContent = setFrontmatterKeys(withHuman, [ [FM_VERIFIED_BASELINE, verifiedBaseline], [FM_VERIFIED_AT, verifiedAt], + // 文件里的 status 与库里的 choro_pages.status 同步推进(#582):此前 T1 + // 成功只改库(markChoroPageEnriched),文件那行永远停在 T0 写的 skeleton + // ——页面文件自身与数据库状态不一致,看文件的人会以为这页从没富化过。 + // 失败路径不写它:文件停在 skeleton 与「首次失败不标 failed」的口径一致 + // (recordChoroPageFailure 改的是库)。T0 重跑整页重写、status 回 skeleton, + // 与「T0 丢弃语义段」同一件事。 + [FM_STATUS, "enriched"], ]); writeFileSync(pageFile, finalContent, "utf8"); } diff --git a/dsh-mneme/src/wiki/render.js b/dsh-mneme/src/wiki/render.js index 9b70fd67..5affcf05 100644 --- a/dsh-mneme/src/wiki/render.js +++ b/dsh-mneme/src/wiki/render.js @@ -30,6 +30,11 @@ export const T1_SECTION_END = ""; export const FM_VERIFIED_BASELINE = "verifiedBaseline"; export const FM_VERIFIED_AT = "verifiedAt"; +// 页面生命周期档(#582):status 也是 frontmatter 键,但它 T0 / T1 都写——T0 +// 起骨架时写 skeleton,T1 成功时随 verified 两键一起推进 enriched。定义在这 +// 与另两个键同住:frontmatter 的形状属于页面格式。 +export const FM_STATUS = "status"; + /** frontmatter 边界:首行 --- 与下一个 --- 的行号;不存在返回 null。 */ function frontmatterBounds(lines) { if (String(lines[0] ?? "").trim() !== "---") return null; diff --git a/dsh-mneme/src/wiki/t1.js b/dsh-mneme/src/wiki/t1.js index d7161caa..fffab37d 100644 --- a/dsh-mneme/src/wiki/t1.js +++ b/dsh-mneme/src/wiki/t1.js @@ -25,7 +25,7 @@ import { tmpdir } from "node:os"; import { realpathSync } from "node:fs"; import { extractHumanBlocks, injectHumanBlocks, T1_SECTION_START, T1_SECTION_END, - FM_VERIFIED_AT, FM_VERIFIED_BASELINE, setFrontmatterKeys, + FM_VERIFIED_AT, FM_VERIFIED_BASELINE, FM_STATUS, setFrontmatterKeys, } from "./render.js"; import { gitHead } from "./t0.js"; // issue #607:拒绝文案里的 CLI 命令要给绝对路径入口(profile 装的插件没有全局 @@ -566,6 +566,13 @@ function enrichPageFile(pageFile, sections, { verifiedBaseline = null, verifiedA const finalContent = setFrontmatterKeys(withHuman, [ [FM_VERIFIED_BASELINE, verifiedBaseline], [FM_VERIFIED_AT, verifiedAt], + // 文件里的 status 与库里的 choro_pages.status 同步推进(#582):此前 T1 + // 成功只改库(markChoroPageEnriched),文件那行永远停在 T0 写的 skeleton + // ——页面文件自身与数据库状态不一致,看文件的人会以为这页从没富化过。 + // 失败路径不写它:文件停在 skeleton 与「首次失败不标 failed」的口径一致 + // (recordChoroPageFailure 改的是库)。T0 重跑整页重写、status 回 skeleton, + // 与「T0 丢弃语义段」同一件事。 + [FM_STATUS, "enriched"], ]); writeFileSync(pageFile, finalContent, "utf8"); } diff --git a/dsh-mneme/test/client.test.js b/dsh-mneme/test/client.test.js index 1b86d169..9b6a03ac 100644 --- a/dsh-mneme/test/client.test.js +++ b/dsh-mneme/test/client.test.js @@ -4680,3 +4680,37 @@ test("conflict ledger mode warns when freeze overrides the chosen tier (#661)", assert.match(joined, /\u77db\u76fe\u51bb\u7ed3|conflict freeze/i, "the notice must name the freeze switch so the user knows what to turn off"); }); + +// ---- #582 wiki 渲染三处 ---- + +// 机器标记行(CHORO:T1 / CHORO:HUMAN 的 HTML 注释包装)是 t0/t1 剥段与回填用 +// 的段落边界,给人看的排版里是裸文本噪音。跳过判定必须挂在围栏分支**之后**: +// 代码块里的 `\n人写的批注\n\n"; @@ -124,6 +127,9 @@ test("快乐路径:富化 + claims 双落 + 保留区照旧页尾回填", asyn assert.ok(rePage.includes("人写的批注"), "重富化不抹保留区"); assert.ok(rePage.trimEnd().endsWith(""), "保留区照旧页尾回填"); assert.equal(rePage.match(new RegExp(T1_SECTION_START.replace(/[/*]/g, (c) => "\\" + c), "g")).length, 1, "旧 T1 段被剥掉、只有一份新段"); + // #582:重富化走同一写入口,status 幂等覆写——不能重复插行,也不能退回。 + assert.ok(/^status: enriched$/m.test(rePage), "重富化后 status 仍是 enriched"); + assert.equal((rePage.match(/^status: /gm) || []).length, 1, "frontmatter 里 status 只有一行"); } finally { store.close(); rmTempBestEffort(root); @@ -142,6 +148,10 @@ test("输出契约:坏 JSON → 失败计数;连续两次 → status=failed assert.equal(r2.failed[0].marked, true, "连续两次到阈值"); const st = store.db.prepare("SELECT p.status FROM choro_pages p JOIN choro_modules m ON p.module_id=m.id WHERE m.slug='src'").get(); assert.equal(st.status, "failed"); + // #582 的另一面:失败路径不动文件——status 停在 T0 写的 skeleton,与 + // 「首次失败不标 failed(骨架仍是有效产物)」的库侧口径一致。 + assert.ok(!/^status: enriched/m.test(readFileSync(join(root, ".chorography", "modules", "src.md"), "utf8")), + "失败路径不能把文件 frontmatter 的 status 推到 enriched"); respond = GOOD_OUTPUT; const r3 = await t1.runT1({ repoRoot: root, moduleSlug: "src", retryFailed: true }); From cdc9bc3f5f0e7a11a5d581e1d86ec18da4fa6e1e Mon Sep 17 00:00:00 2001 From: modusensus Date: Fri, 13 Nov 2026 10:50:32 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(panel):=20=E5=9B=B4=E6=A0=8F=E8=AF=86?= =?UTF-8?q?=E5=88=AB=E8=AE=B0=E5=BD=95=E5=AD=97=E7=AC=A6=E4=B8=8E=E9=95=BF?= =?UTF-8?q?=E5=BA=A6=EF=BC=8C~~~/=E5=9B=9B=E5=8F=8D=E5=BC=95=E5=8F=B7?= =?UTF-8?q?=E5=9D=97=E9=87=8C=E7=9A=84=E6=B3=A8=E9=87=8A=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E8=A2=AB=E9=9A=90=E8=97=8F=E8=A7=84=E5=88=99=E8=AF=AF=E5=88=A0?= =?UTF-8?q?=EF=BC=88PR=20#678=20=E8=AF=84=E5=AE=A1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dsh-mneme/lib/client.js | 25 ++++++++++++++++++++----- dsh-mneme/test/client.test.js | 11 +++++++++-- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/dsh-mneme/lib/client.js b/dsh-mneme/lib/client.js index 349a1964..2ad6919f 100644 --- a/dsh-mneme/lib/client.js +++ b/dsh-mneme/lib/client.js @@ -11586,6 +11586,11 @@ window.__ModuleLoader__.load({ }; let listItems = []; let inCode = false; + // 围栏要记「开启用的字符与长度」(PR #678 评审):只认三反引号会把 + // `~~~html` 整块当正文,于是一条 `` 注释行会被下面的隐藏规则 + // 删掉;四反引号块内部出现三反引号时旧写法也会提前退出代码态。 + // CommonMark:结束围栏必须与开启同字符、长度不小于开启、其后只有空白。 + let fence = null; let codeLines = []; // --- 表格(v0.11.1 美化):连续 `|` 行解析成真 table,不再按等宽行画竖线 --- const splitRow = (line) => String(line).trim().replace(/^\|/, "").replace(/\|$/, "") @@ -11627,14 +11632,24 @@ window.__ModuleLoader__.load({ }; for (; i < lines.length; i++) { const line = lines[i]; - if (line.trim().startsWith("```")) { - if (inCode) { + const fenceOpen = /^(`{3,}|~{3,})(.*)$/.exec(line.trim()); + if (!inCode && fenceOpen) { + flushList(); flushTable(); + inCode = true; + fence = { ch: fenceOpen[1][0], len: fenceOpen[1].length }; + codeLines = []; + continue; + } + if (inCode) { + if (fenceOpen && fenceOpen[1][0] === fence.ch && fenceOpen[1].length >= fence.len + && fenceOpen[2].trim() === "") { out.push(h("pre", { key: out.length, className: "mneme-wikipre" }, codeLines.join("\n"))); - codeLines = []; inCode = false; - } else { flushList(); flushTable(); inCode = true; } + codeLines = []; inCode = false; fence = null; + } else { + codeLines.push(line); + } continue; } - if (inCode) { codeLines.push(line); continue; } // 机器标记行不进正文(#582):`` 与 // `` 是 t0/t1 剥段与回填用的段落边界, // 给程序看的;排版里它们是一行裸文本噪音。只隐藏「整行以