From 5db0c978c7ff353a2b2e2fba1a61138da87ba8e7 Mon Sep 17 00:00:00 2001 From: qiansc Date: Mon, 26 Oct 2026 00:22:29 +0000 Subject: [PATCH] feat: CJK-friendly site emphasis, CLI-managed query Skill, cleaner Lark styles (0.7.86) Render website emphasis with markdown-it-cjk-friendly instead of the 0.7.85 built-in rule: any East Asian wide or full-width character beside a * run counts like whitespace, while _ and non-CJK runs keep CommonMark. context build now replaces a kb template's context-knowledge-query copy that differs from the shipped Skill in its language and reports a package-query-skill-refreshed hint, so a package never stamps an older Skill with the current Context version. Lark capture merges adjacent runs of the same inline style, keeps whitespace outside emphasis markers, and ends paragraphs inside table cells and list items with a line break, so split bold runs no longer produce **A**** ****B** and cell paragraphs no longer run together. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 6 ++ bun.lock | 51 +++++++++----- package.json | 2 +- .../context-workflow/provider.yaml | 2 +- .../manuals/guides/package-outputs.md | 10 ++- .../manuals/reference/package-templates.md | 3 +- packages/context-cli/package.json | 3 +- .../src/__tests__/documentLarkXmlV062.test.ts | 19 +++++ .../projectQuerySkillMigration.test.ts | 60 +++++++++++++++- .../src/__tests__/siteCjkEmphasis.test.ts | 7 +- .../src/__tests__/siteHeadingPairs.test.ts | 3 +- .../src/__tests__/siteLiteralCode.test.ts | 3 +- packages/context-cli/src/lib/larkDocxXml.ts | 43 ++++++++++-- .../src/project/indexerBaseContracts.ts | 2 +- .../context-cli/src/project/packageBuilder.ts | 15 ++-- .../src/project/packageQuerySkillMigration.ts | 70 +++++++++++++++++-- .../context-cli/src/project/packageSite.ts | 9 +-- .../src/project/packageSiteTheme.ts | 31 +++----- .../context/docs/guides/package-outputs.md | 10 ++- .../docs/reference/package-templates.md | 3 +- packages/context/package.json | 2 +- packages/core/package.json | 2 +- packages/dev-cli/package.json | 2 +- packages/extract-contract/package.json | 2 +- packages/extract-go/package.json | 2 +- packages/extract-mdx/package.json | 2 +- packages/extract-proto/package.json | 2 +- packages/extract-rush/package.json | 2 +- packages/extract-sql/package.json | 2 +- packages/extract-style/package.json | 2 +- packages/extract-thrift/package.json | 2 +- packages/extract-ts/package.json | 2 +- packages/extract/package.json | 2 +- packages/tui/package.json | 2 +- .../claude/.claude-plugin/plugin.json | 2 +- .../codex/.codex-plugin/plugin.json | 4 +- .../cursor/.cursor-plugin/plugin.json | 2 +- 37 files changed, 294 insertions(+), 94 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8f58aea..0dd95878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to Context are documented here. +## 0.7.86 - 2026-10-26 + +- Website emphasis now follows the CJK-friendly amendment to CommonMark through `markdown-it-cjk-friendly`, replacing 0.7.85's built-in rule. Any East Asian wide or full-width character beside a `*` run, including full-width punctuation, counts like whitespace when deciding whether the run opens or closes emphasis; `_` and runs with no CJK character beside them keep the CommonMark result. Clean content renders as in 0.7.85. +- `context build` keeps the shared `context-knowledge-query` Skill current. When a kb template's copy differs from the shipped Skill in the same language, the build replaces it and reports a `package-query-skill-refreshed` agent hint, so a package no longer declares one Context version while carrying an older version's Skill. Package-specific search hints stay in `others/context/search-guide.md`; a package that needs its own query Skill adds it under a different name. +- Lark capture merges adjacent runs of the same inline style and keeps whitespace outside emphasis markers, so `A B` becomes `**A B**` instead of `**A**** ****B**`. Paragraphs inside table cells and list items stay on separate lines (`
` in tables) instead of running together. Existing snapshots change only when they are captured again. + ## 0.7.85 - 2026-10-25 - Website bold and italic text now closes after CJK punctuation. CommonMark only ends `**…**` after punctuation such as `?`, `)` or `、` when whitespace or more punctuation follows, so Chinese like `**用 MCP 还是知识包?**不在…` showed the asterisks literally. The site renderer now treats a Chinese, Japanese or Korean letter on the outer side of the markers like whitespace. Delimiters with no CJK letter beside them render exactly as before, and sites built with an earlier CLI re-render on the next build. diff --git a/bun.lock b/bun.lock index 95c8f3be..6e0823f2 100644 --- a/bun.lock +++ b/bun.lock @@ -18,7 +18,7 @@ }, "packages/context": { "name": "@c4a/context", - "version": "0.7.85", + "version": "0.7.86", "dependencies": { "yaml": "^2.5.1", "zod": "^3.23.8", @@ -26,7 +26,7 @@ }, "packages/context-cli": { "name": "@c4a/context-cli", - "version": "0.7.85", + "version": "0.7.86", "bin": { "context": "dist/cli.js", }, @@ -49,6 +49,7 @@ "handlebars": "^4.7.8", "ink": "^5.0.0", "jiti": "^2.7.0", + "markdown-it-cjk-friendly": "3.0.0", "mdast-util-to-string": "^4.0.0", "mermaid": "11.12.0", "react": "^18.3.1", @@ -73,7 +74,7 @@ }, "packages/core": { "name": "@c4a/core", - "version": "0.7.85", + "version": "0.7.86", "dependencies": { "picomatch": "^4.0.4", "yaml": "^2.4.5", @@ -85,7 +86,7 @@ }, "packages/dev-cli": { "name": "@c4a/dev-cli", - "version": "0.7.85", + "version": "0.7.86", "dependencies": { "@c4a/context": "workspace:*", "@c4a/core": "workspace:*", @@ -97,7 +98,7 @@ }, "packages/extract": { "name": "@c4a/extract", - "version": "0.7.85", + "version": "0.7.86", "bin": { "c4a-extract-code": "./dist/bin/c4a-extract-code.js", }, @@ -112,7 +113,7 @@ }, "packages/extract-contract": { "name": "@c4a/extract-contract", - "version": "0.7.85", + "version": "0.7.86", "dependencies": { "@c4a/core": "workspace:*", "graphql": "^16.14.2", @@ -122,7 +123,7 @@ }, "packages/extract-go": { "name": "@c4a/extract-go", - "version": "0.7.85", + "version": "0.7.86", "dependencies": { "@c4a/core": "workspace:*", "@c4a/extract": "workspace:*", @@ -132,7 +133,7 @@ }, "packages/extract-mdx": { "name": "@c4a/extract-mdx", - "version": "0.7.85", + "version": "0.7.86", "dependencies": { "@c4a/core": "workspace:*", "remark-mdx": "^3.1.1", @@ -144,7 +145,7 @@ }, "packages/extract-proto": { "name": "@c4a/extract-proto", - "version": "0.7.85", + "version": "0.7.86", "dependencies": { "@c4a/core": "workspace:*", "zod": "^3.23.8", @@ -152,7 +153,7 @@ }, "packages/extract-rush": { "name": "@c4a/extract-rush", - "version": "0.7.85", + "version": "0.7.86", "dependencies": { "@c4a/core": "workspace:*", "typescript": "^5.5.4", @@ -162,7 +163,7 @@ }, "packages/extract-sql": { "name": "@c4a/extract-sql", - "version": "0.7.85", + "version": "0.7.86", "dependencies": { "@c4a/core": "workspace:*", "node-sql-parser": "^5.4.0", @@ -171,7 +172,7 @@ }, "packages/extract-style": { "name": "@c4a/extract-style", - "version": "0.7.85", + "version": "0.7.86", "dependencies": { "@c4a/core": "workspace:*", "postcss": "^8.5.26", @@ -183,7 +184,7 @@ }, "packages/extract-thrift": { "name": "@c4a/extract-thrift", - "version": "0.7.85", + "version": "0.7.86", "dependencies": { "@c4a/core": "workspace:*", "zod": "^3.23.8", @@ -191,7 +192,7 @@ }, "packages/extract-ts": { "name": "@c4a/extract-ts", - "version": "0.7.85", + "version": "0.7.86", "dependencies": { "@c4a/core": "workspace:*", "@c4a/extract": "workspace:*", @@ -202,7 +203,7 @@ }, "packages/tui": { "name": "@c4a/tui", - "version": "0.7.85", + "version": "0.7.86", "dependencies": { "ink": "^5.0.0", "react": "^18.3.1", @@ -699,7 +700,7 @@ "anynum": ["anynum@1.0.1", "", {}, "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A=="], - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "argparse": ["argparse@3.0.4", "", {}, "sha512-fwWK3Zq22l4XJJjx5mcBHGikIk+hJqO+32dPm0Jur7+rtG5x1scOjSfdT9ZKeWHPpsya/l0HHK4tDImBkTrtvA=="], "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], @@ -863,7 +864,7 @@ "emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="], - "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + "entities": ["entities@8.1.0", "", {}, "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA=="], "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], @@ -1013,6 +1014,8 @@ "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + "linkify-it": ["linkify-it@6.1.0", "", { "dependencies": { "uc.micro": "^3.0.0" } }, "sha512-wJ/TwpSDTLepCrQoYWYIExIKg5Zchex2Nn5yk2mFnB+6PtdkHtyLx742md9csRjjOnGkKIS/RrbY7l8D6gT9Vw=="], + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], @@ -1027,6 +1030,10 @@ "mark.js": ["mark.js@8.11.1", "", {}, "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ=="], + "markdown-it": ["markdown-it@15.0.3", "", { "dependencies": { "argparse": "^3.0.3", "entities": "^8.1.0", "linkify-it": "^6.1.0", "mdurl": "^2.1.0", "punycode.js": "^2.3.1", "uc.micro": "^3.0.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-+Az7Ojz3iRGu8KYMvJ6xVhvrxP3eE+vEtJX75Ow88XBpcQNV/0lGX5ZynIIBftREo4DC2jsveAMDaeltmm8QvQ=="], + + "markdown-it-cjk-friendly": ["markdown-it-cjk-friendly@3.0.0", "", { "dependencies": { "get-east-asian-width": "^1.4.0" }, "peerDependencies": { "@types/markdown-it": ">=14.2.0", "markdown-it": "*" }, "optionalPeers": ["@types/markdown-it"] }, "sha512-y4OkqwenFXmb1i3w7qfh9E60qNX6U4KzECke8N/7io9SARFSvbR/wde0q/l6/Az9doieLOIw7ZsbDSgl27DR4g=="], + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], "marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], @@ -1063,6 +1070,8 @@ "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + "mdurl": ["mdurl@2.1.0", "", {}, "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg=="], + "mermaid": ["mermaid@11.12.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", "@mermaid-js/parser": "^0.6.2", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.11", "dayjs": "^1.11.18", "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", "lodash-es": "^4.17.21", "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0" } }, "sha512-ZudVx73BwrMJfCFmSSJT84y6u5brEoV8DOItdHomNLz32uBjNrelm7mg95X7g+C6UoQH/W6mBLGDEDv73JdxBg=="], "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], @@ -1213,6 +1222,8 @@ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], + "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], "react-devtools-core": ["react-devtools-core@4.28.5", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA=="], @@ -1321,6 +1332,8 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "uc.micro": ["uc.micro@3.0.0", "", {}, "sha512-U3PppEkleoTnIfi8BozMx3yju3qc/L6SwqWo2Sw+54PX+PX0q9I+r1Um5HCmqD7n9VDX5/v3vQH/AjA6deDdtw=="], + "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], @@ -1415,6 +1428,8 @@ "@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "chevrotain/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], @@ -1439,6 +1454,8 @@ "ink/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], diff --git a/package.json b/package.json index 9d193fb3..524c50bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "context", - "version": "0.7.85", + "version": "0.7.86", "packageManager": "bun@1.3.9", "repository": { "type": "git", diff --git a/packages/context-cli/context-workflow/provider.yaml b/packages/context-cli/context-workflow/provider.yaml index 72df46e2..7dc9ebd8 100644 --- a/packages/context-cli/context-workflow/provider.yaml +++ b/packages/context-cli/context-workflow/provider.yaml @@ -1,6 +1,6 @@ schema: agent-graph.provider.v1 id: c4a/context -version: 0.7.85 +version: 0.7.86 name: Context workflow description: Internal work contract for Context knowledge workspaces. graphs: diff --git a/packages/context-cli/context-workflow/resources/manuals/guides/package-outputs.md b/packages/context-cli/context-workflow/resources/manuals/guides/package-outputs.md index be494b33..baf62f9f 100644 --- a/packages/context-cli/context-workflow/resources/manuals/guides/package-outputs.md +++ b/packages/context-cli/context-workflow/resources/manuals/guides/package-outputs.md @@ -278,7 +278,9 @@ records establish what the package actually says. Package authors edit the sourc template when project-specific terminology, entry points or task workflows are needed: put terms, synonyms, common questions and preferred entry pages in `others/context/search-guide.md`, which the Skill reads first, and keep the -shared Skill unchanged. Editing the Skill makes it package-specific; rename its +shared Skill unchanged. Context manages the shared Skill: `context build` +replaces a template copy that differs from the shipped one, so edits to it do +not survive. A package that needs its own query Skill adds it under a different directory and `name` so it installs separately. When a page's text does not settle a question, the Skill follows the page's `Sources` entries: repository links are read at their recorded commit through authorized host tools, document @@ -290,7 +292,11 @@ Workspaces created before the rename keep `skills/knowledge-query/` in their template. `context build` replaces it with `context-knowledge-query` in the old Skill's language, adds `others/context/search-guide.md` when it is missing, and reports the replacement as an agent hint. Move any package-specific wording the -old Skill carried into the search guide. +old Skill carried into the search guide. Every build also compares +`skills/context-knowledge-query/` with the shipped Skill in the same language +and replaces an older or edited copy, reporting a `package-query-skill-refreshed` +agent hint, so a package never declares one Context version while carrying +another version's Skill. When approved pages reference materialized resources, Context keeps their production copies in content-addressed `knowledge/assets/` paths and bundles diff --git a/packages/context-cli/context-workflow/resources/manuals/reference/package-templates.md b/packages/context-cli/context-workflow/resources/manuals/reference/package-templates.md index 362b19e9..a1050d63 100644 --- a/packages/context-cli/context-workflow/resources/manuals/reference/package-templates.md +++ b/packages/context-cli/context-workflow/resources/manuals/reference/package-templates.md @@ -247,7 +247,8 @@ The default kb template includes: attribution, follows repository sources at their recorded commit when the page text is not enough, and reports gaps instead of inventing unsupported answers. It uses no package-specific template values, so every package renders the same - file. The + file, and `context build` replaces a template copy that differs from the + shipped Skill in its language. The default entry OKF root is `wikis/`; packages that select additional internal collections expose `guides/`, `rules/`, or `feats/` indexes when those roots are selected. diff --git a/packages/context-cli/package.json b/packages/context-cli/package.json index 91053db0..db637563 100644 --- a/packages/context-cli/package.json +++ b/packages/context-cli/package.json @@ -1,7 +1,7 @@ { "name": "@c4a/context-cli", "description": "Local runtime and Agent integration for traceable knowledge production", - "version": "0.7.85", + "version": "0.7.86", "type": "module", "license": "MIT", "engines": { @@ -71,6 +71,7 @@ "handlebars": "^4.7.8", "ink": "^5.0.0", "jiti": "^2.7.0", + "markdown-it-cjk-friendly": "3.0.0", "mdast-util-to-string": "^4.0.0", "mermaid": "11.12.0", "react": "^18.3.1", diff --git a/packages/context-cli/src/__tests__/documentLarkXmlV062.test.ts b/packages/context-cli/src/__tests__/documentLarkXmlV062.test.ts index 8d8c0d56..36806e53 100644 --- a/packages/context-cli/src/__tests__/documentLarkXmlV062.test.ts +++ b/packages/context-cli/src/__tests__/documentLarkXmlV062.test.ts @@ -46,6 +46,25 @@ describe("Lark Docx XML readable projection", () => { expectClosedCounts(projection.fidelity); }); + test("merges split style runs and keeps paragraphs inside table cells apart", () => { + const projection = projectLarkDocxXml({ + sourceUrl: "https://example.larkoffice.com/wiki/source-token", + xml: [ + "Quarterly review", + "

整体总结:oncall 问题数维持在低位,完成了API 封装。

", + "

Plain split italic and padded text.

", + "", + "
AreaResult
质量

评分: 0.7

总结:

  • details
", + ].join("\n"), + }); + + expect(projection.markdown).toContain("**整体总结:oncall 问题数维持在低位**,完成了**API 封装**。"); + expect(projection.markdown).toContain("Plain *split italic* and **padded** text."); + expect(projection.markdown).toContain("| 质量 | **评分**: 0.7
**总结**:
"); + expect(projection.markdown).not.toContain("****"); + expectClosedCounts(projection.fidelity); + }); + test("distinguishes inline and token-backed diagrams without guessing from prose", () => { const projection = projectLarkDocxXml({ sourceUrl: "https://example.larkoffice.com/docx/source-token", diff --git a/packages/context-cli/src/__tests__/projectQuerySkillMigration.test.ts b/packages/context-cli/src/__tests__/projectQuerySkillMigration.test.ts index 0e8cbf90..d6f6c187 100644 --- a/packages/context-cli/src/__tests__/projectQuerySkillMigration.test.ts +++ b/packages/context-cli/src/__tests__/projectQuerySkillMigration.test.ts @@ -1,11 +1,12 @@ import { describe, expect, test } from "bun:test"; import { existsSync } from "node:fs"; -import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; -import { join } from "node:path"; +import { dirname, join } from "node:path"; import type { PackageDefinition } from "@c4a/context"; -import { migrateLegacyQuerySkills } from "../project/packageQuerySkillMigration.js"; +import { migrateLegacyQuerySkills, refreshSharedQuerySkills } from "../project/packageQuerySkillMigration.js"; import { inspectPackageTemplateReview, writeStarterTemplateReviewMarker } from "../project/packageTemplateReview.js"; +import { resolveContextPackageTemplatesRoot } from "../project/workspace.js"; const TEMPLATE_PATH = "src/package-templates/kb"; const pkg = { kind: "package.kb", name: "sample-kb", template: { path: TEMPLATE_PATH } } as unknown as PackageDefinition; @@ -76,3 +77,56 @@ describe("knowledge-query to context-knowledge-query migration", () => { } }); }); + +describe("shared context-knowledge-query refresh", () => { + const shippedSkill = (language: "en" | "zh-CN") => { + const root = resolveContextPackageTemplatesRoot(); + return join(language === "zh-CN" ? join(dirname(root), "package-templates.zh-CN") : root, "kb", "skills", "context-knowledge-query"); + }; + + async function workspaceWithSkill(language: "en" | "zh-CN", edit: (skillRoot: string) => Promise) { + const root = await mkdtemp(join(tmpdir(), "context-query-skill-refresh-")); + const templateRoot = join(root, ...TEMPLATE_PATH.split("/")); + const skillRoot = join(templateRoot, "skills", "context-knowledge-query"); + await mkdir(join(templateRoot, "skills"), { recursive: true }); + await cp(shippedSkill(language), skillRoot, { recursive: true }); + await writeFile(join(templateRoot, "AGENTS.md"), "# Package\n"); + await writeStarterTemplateReviewMarker(templateRoot); + await edit(skillRoot); + return { root, templateRoot, skillRoot }; + } + + for (const language of ["en", "zh-CN"] as const) { + test(`replaces an older ${language} copy with the shipped Skill in the same language`, async () => { + const { root, skillRoot } = await workspaceWithSkill(language, async (skill) => { + await writeFile(join(skill, "SKILL.md"), (await readFile(join(skill, "SKILL.md"), "utf8")).replace(/\n## [^\n]+\n/u, "\n")); + await writeFile(join(skill, "scripts", "stale.mjs"), "// removed by the refresh\n"); + }); + try { + expect(await refreshSharedQuerySkills(root, [pkg])).toEqual([{ packageName: "sample-kb", templatePath: TEMPLATE_PATH, language }]); + expect(await readFile(join(skillRoot, "SKILL.md"), "utf8")).toBe(await readFile(join(shippedSkill(language), "SKILL.md"), "utf8")); + expect(existsSync(join(skillRoot, "scripts", "stale.mjs"))).toBe(false); + expect((await inspectPackageTemplateReview(root, pkg)).state).toBe("review-required"); + expect(await refreshSharedQuerySkills(root, [pkg])).toEqual([]); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); + } + + test("leaves a current copy, the search guide and templates without the Skill alone", async () => { + const { root, templateRoot } = await workspaceWithSkill("en", async () => {}); + try { + await mkdir(join(templateRoot, "others", "context"), { recursive: true }); + await writeFile(join(templateRoot, "others", "context", "search-guide.md"), "# Our terms\n"); + expect(await refreshSharedQuerySkills(root, [pkg])).toEqual([]); + expect(await readFile(join(templateRoot, "others", "context", "search-guide.md"), "utf8")).toBe("# Our terms\n"); + await rm(join(templateRoot, "skills"), { recursive: true, force: true }); + const llms = { kind: "package.llms", name: "docs", template: { path: "src/package-templates/llms" } } as unknown as PackageDefinition; + expect(await refreshSharedQuerySkills(root, [pkg, llms])).toEqual([]); + expect(existsSync(join(templateRoot, "skills"))).toBe(false); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); +}); diff --git a/packages/context-cli/src/__tests__/siteCjkEmphasis.test.ts b/packages/context-cli/src/__tests__/siteCjkEmphasis.test.ts index 11c85b09..28c309d2 100644 --- a/packages/context-cli/src/__tests__/siteCjkEmphasis.test.ts +++ b/packages/context-cli/src/__tests__/siteCjkEmphasis.test.ts @@ -1,10 +1,11 @@ import { expect, test } from "bun:test"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; -import { siteMarkdownConfig } from "../project/packageSiteTheme.js"; +import { siteMarkdownConfig, siteMarkdownImports } from "../project/packageSiteTheme.js"; -test("site emphasis closes after CJK punctuation and leaves other text as CommonMark renders it", async () => { +test("site * emphasis closes beside CJK characters; _ and non-CJK text keep the CommonMark result", async () => { const script = ` + ${siteMarkdownImports(import.meta.resolve("markdown-it-cjk-friendly")).trimEnd()} import assert from 'node:assert/strict'; import {createMarkdownRenderer} from ${JSON.stringify(import.meta.resolve("vitepress"))}; const site = await createMarkdownRenderer(process.cwd(), ({${siteMarkdownConfig}})); @@ -16,11 +17,13 @@ test("site emphasis closes after CJK punctuation and leaves other text as Common ["*注意:*これは", "注意:これは"], ["**확인?**다음", "확인?다음"], ["**𠀀?**在", "𠀀?在"], + ["**评分**:0.7**总结**:", "评分:0.7总结:"], ])}; for (const [markdown, html] of fixed) assert.equal(site.renderInline(markdown), html, markdown); const unchanged = ${JSON.stringify([ "**Use MCP?**Then", "snake_case_name and 变量_名称_中", "a * b * c", "**bold** and *em*", "中文**加粗**中文", "**粗体** 后", "2*3*4", "价格 * 数量", "**未闭合?", "__init__ 方法", "a**b**c", "**中**", "* 列表", + "_注意?_这是", "说明_(见下)_继续", ])}; for (const markdown of unchanged) assert.equal(site.renderInline(markdown), stock.renderInline(markdown), markdown); console.log('cjk emphasis verified');`; diff --git a/packages/context-cli/src/__tests__/siteHeadingPairs.test.ts b/packages/context-cli/src/__tests__/siteHeadingPairs.test.ts index 0e8d83ac..36afc3ab 100644 --- a/packages/context-cli/src/__tests__/siteHeadingPairs.test.ts +++ b/packages/context-cli/src/__tests__/siteHeadingPairs.test.ts @@ -1,11 +1,12 @@ import { expect, test } from "bun:test"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; -import { siteMarkdownConfig } from "../project/packageSiteTheme.js"; +import { siteMarkdownConfig, siteMarkdownImports } from "../project/packageSiteTheme.js"; test("renderer pairs repeated translated headings only when section shapes agree", async () => { const block = (locale: string, body: string) => `\n\n\n\n${body}\n\n\n`; const script = ` + ${siteMarkdownImports(import.meta.resolve("markdown-it-cjk-friendly")).trimEnd()} import assert from 'node:assert/strict'; import {createMarkdownRenderer} from ${JSON.stringify(import.meta.resolve("vitepress"))}; const contextLocaleMarker = 'context-test'; diff --git a/packages/context-cli/src/__tests__/siteLiteralCode.test.ts b/packages/context-cli/src/__tests__/siteLiteralCode.test.ts index cf0d4f5b..289bbf0d 100644 --- a/packages/context-cli/src/__tests__/siteLiteralCode.test.ts +++ b/packages/context-cli/src/__tests__/siteLiteralCode.test.ts @@ -1,12 +1,13 @@ import { expect, test } from "bun:test"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; -import { siteMarkdownConfig } from "../project/packageSiteTheme.js"; +import { siteMarkdownConfig, siteMarkdownImports } from "../project/packageSiteTheme.js"; test("site prose and code preserve braces without compiling them as Vue expressions", async () => { // Isolate Vite from Jiti's process-wide Error hooks used by other integration // fixtures, and exercise the same Node runtime as installed builds. const script = ` + ${siteMarkdownImports(import.meta.resolve("markdown-it-cjk-friendly")).trimEnd()} import assert from 'node:assert/strict'; import {createRequire} from 'node:module'; import {createMarkdownRenderer} from ${JSON.stringify(import.meta.resolve("vitepress"))}; diff --git a/packages/context-cli/src/lib/larkDocxXml.ts b/packages/context-cli/src/lib/larkDocxXml.ts index efc557d4..40930bbe 100644 --- a/packages/context-cli/src/lib/larkDocxXml.ts +++ b/packages/context-cli/src/lib/larkDocxXml.ts @@ -116,8 +116,39 @@ function safeFence(value: string, language = ""): string { return `${fence}${language}\n${value.trim()}\n${fence}`; } +const INLINE_STYLES = new Set(["b", "strong", "i", "em", "s", "del", "strike"]); + +/** + * Lark splits one styled phrase into adjacent runs, for example + * `oncall 问题数`. Rendering each run separately yields + * `**oncall**** ****问题数**`, which no Markdown renderer reads as one bold + * phrase, so adjacent runs of the same style without attributes are merged. + */ +function mergeAdjacentStyles(nodes: readonly OrderedXmlNode[], ctx: RenderContext): OrderedXmlNode[] { + const merged: OrderedXmlNode[] = []; + for (const node of nodes) { + const name = elementName(node); + const previous = merged.at(-1); + if (name !== undefined && INLINE_STYLES.has(name) && previous !== undefined && elementName(previous) === name && + Object.keys(attributes(node)).length === 0 && Object.keys(attributes(previous)).length === 0) { + ctx.tracker.discover(name); + ctx.tracker.convert(name); + merged[merged.length - 1] = { [name]: [...elementChildren(previous, name), ...elementChildren(node, name)] } as OrderedXmlNode; + continue; + } + merged.push(node); + } + return merged; +} + +/** Keep whitespace outside emphasis markers; CommonMark does not open `** x` or close `x **`. */ +function wrapInline(body: string, marker: string): string { + const match = /^(\s*)([\s\S]*?)(\s*)$/u.exec(body)!; + return match[2] === "" ? body : `${match[1]}${marker}${match[2]}${marker}${match[3]}`; +} + function renderChildren(nodes: readonly OrderedXmlNode[], ctx: RenderContext): string { - return nodes.map((node) => renderNode(node, ctx)).join(""); + return mergeAdjacentStyles(nodes, ctx).map((node) => renderNode(node, ctx)).join(""); } function renderList(nodes: readonly OrderedXmlNode[], ctx: RenderContext, ordered: boolean): string { @@ -352,7 +383,9 @@ function renderNode(node: OrderedXmlNode, ctx: RenderContext): string { if (["p", "paragraph", "div", "section"].includes(name)) { ctx.tracker.convert(name); const body = renderChildren(nodes, { ...ctx, mode: "inline" }); - return ctx.mode === "inline" ? body : `\n\n${body}\n\n`; + // Paragraphs inside table cells and list items stay on separate lines; + // table cells turn the line break into
. + return ctx.mode === "inline" ? `${body}\n` : `\n\n${body}\n\n`; } if (["span", "mark", "u", "sub", "sup", "label"].includes(name)) { ctx.tracker.convert(name); @@ -360,15 +393,15 @@ function renderNode(node: OrderedXmlNode, ctx: RenderContext): string { } if (name === "b" || name === "strong") { ctx.tracker.convert(name); - return `**${renderChildren(nodes, { ...ctx, mode: "inline" })}**`; + return wrapInline(renderChildren(nodes, { ...ctx, mode: "inline" }), "**"); } if (name === "i" || name === "em") { ctx.tracker.convert(name); - return `*${renderChildren(nodes, { ...ctx, mode: "inline" })}*`; + return wrapInline(renderChildren(nodes, { ...ctx, mode: "inline" }), "*"); } if (name === "s" || name === "del" || name === "strike") { ctx.tracker.convert(name); - return `~~${renderChildren(nodes, { ...ctx, mode: "inline" })}~~`; + return wrapInline(renderChildren(nodes, { ...ctx, mode: "inline" }), "~~"); } if (name === "a") { ctx.tracker.convert(name); diff --git a/packages/context-cli/src/project/indexerBaseContracts.ts b/packages/context-cli/src/project/indexerBaseContracts.ts index 90b02b1c..7b0acd29 100644 --- a/packages/context-cli/src/project/indexerBaseContracts.ts +++ b/packages/context-cli/src/project/indexerBaseContracts.ts @@ -21,7 +21,7 @@ import { bundledMarkdownReaderQuestionContracts } from "./indexerBaseMarkdownAuthoringCatalog.js"; const BASE_CONTRACT_VERSION = "1.1.0"; -export const BUNDLED_INDEXER_PARSER_PACKAGE_VERSION = "0.7.85"; +export const BUNDLED_INDEXER_PARSER_PACKAGE_VERSION = "0.7.86"; const BUNDLED_PARSER_REQUIREMENTS = buildIndexerParserCapabilityRequirements( BUNDLED_INDEXER_PARSER_PACKAGE_VERSION, ); diff --git a/packages/context-cli/src/project/packageBuilder.ts b/packages/context-cli/src/project/packageBuilder.ts index 8097d017..181689f0 100644 --- a/packages/context-cli/src/project/packageBuilder.ts +++ b/packages/context-cli/src/project/packageBuilder.ts @@ -8,7 +8,7 @@ import { dispatchProductionStage, productionCapabilitiesSchema } from "./product import { readProductionStage } from "./productionStageStore.js"; import { assertProductionDeliveryReady, finishProductionDelivery } from "./productionDelivery.js"; import { withProjectWriteLock } from "./writeLock.js"; -import { LEGACY_QUERY_SKILL, QUERY_SKILL, migrateLegacyQuerySkills } from "./packageQuerySkillMigration.js"; +import { LEGACY_QUERY_SKILL, QUERY_SKILL, migrateLegacyQuerySkills, refreshSharedQuerySkills } from "./packageQuerySkillMigration.js"; import { assertDistinctPackageOutputs, packageOutputDirs, packageSiteOutputDir } from "./packageOutputPaths.js"; import { buildLlmsDocuments, writeLlmsDocuments, llmsArticles, PACKAGE_LLMS_VERSION } from "./packageLlms.js"; import { writePackageSite, PACKAGE_SITE_VERSION } from "./packageSite.js"; @@ -105,7 +105,7 @@ export interface ProjectBuildResult { } export type PackageBuildAgentHint = RuntimeEventPendingAgentHint | { - reason_code: "package-query-skill-migrated"; + reason_code: "package-query-skill-migrated" | "package-query-skill-refreshed"; severity: "info"; package_name: string; template_path: string; @@ -476,6 +476,7 @@ async function buildProjectPackagesInternal(projectRoot: string, options: { deli } const approved = await listApprovedKnowledge(projectRoot); const querySkillMigrations = await migrateLegacyQuerySkills(projectRoot, packages); + const querySkillRefreshes = await refreshSharedQuerySkills(projectRoot, packages); const templateReviews = await inspectPackageTemplateReviews(projectRoot, packages); const unresolvedTemplateReviews = templateReviews.filter((review) => review.state === "review-required" || review.state === "invalid" @@ -520,13 +521,19 @@ async function buildProjectPackagesInternal(projectRoot: string, options: { deli } } const summaries: PackageBuildSummary[] = []; - const agentHints: PackageBuildAgentHint[] = querySkillMigrations.map((migration) => ({ + const agentHints: PackageBuildAgentHint[] = [...querySkillMigrations.map((migration) => ({ reason_code: "package-query-skill-migrated" as const, severity: "info" as const, package_name: migration.packageName, template_path: migration.templatePath, message: `Replaced the retired ${LEGACY_QUERY_SKILL} Skill with ${QUERY_SKILL} in the package template; package-specific search hints belong in others/context/search-guide.md.`, - })); + })), ...querySkillRefreshes.map((refresh) => ({ + reason_code: "package-query-skill-refreshed" as const, + severity: "info" as const, + package_name: refresh.packageName, + template_path: refresh.templatePath, + message: `Updated the shared ${QUERY_SKILL} Skill in the package template to this Context version; it is managed by Context, and package-specific search hints belong in others/context/search-guide.md.`, + }))]; for (const pkg of packages) { assertPackageOutputDir(pkg); const selected = selectPackageKnowledge(approved, pkg); diff --git a/packages/context-cli/src/project/packageQuerySkillMigration.ts b/packages/context-cli/src/project/packageQuerySkillMigration.ts index 49ae2128..0115a2a6 100644 --- a/packages/context-cli/src/project/packageQuerySkillMigration.ts +++ b/packages/context-cli/src/project/packageQuerySkillMigration.ts @@ -1,6 +1,6 @@ import { existsSync } from "node:fs"; -import { cp, mkdir, readFile, rm } from "node:fs/promises"; -import { dirname, join, resolve } from "node:path"; +import { cp, mkdir, readdir, readFile, rm } from "node:fs/promises"; +import { dirname, join, relative, resolve } from "node:path"; import type { PackageDefinition } from "@c4a/context"; import { inspectPackageTemplateReview, refreshStarterTemplateDigest } from "./packageTemplateReview.js"; import { resolveContextPackageTemplatesRoot } from "./workspace.js"; @@ -15,11 +15,34 @@ export interface QuerySkillMigration { language: "en" | "zh-CN"; } -async function legacyLanguage(legacyRoot: string): Promise<"en" | "zh-CN"> { - const skill = await readFile(join(legacyRoot, "SKILL.md"), "utf8").catch(() => ""); +async function skillLanguage(skillRoot: string): Promise<"en" | "zh-CN"> { + const skill = await readFile(join(skillRoot, "SKILL.md"), "utf8").catch(() => ""); return /\p{Script=Han}/u.test(skill) ? "zh-CN" : "en"; } +function shippedKbRoot(language: "en" | "zh-CN"): string { + const shippedRoot = resolveContextPackageTemplatesRoot(); + return join(language === "zh-CN" ? join(dirname(shippedRoot), "package-templates.zh-CN") : shippedRoot, "kb"); +} + +async function treeFiles(root: string): Promise> { + const entries = await readdir(root, { recursive: true, withFileTypes: true }); + const files = new Map(); + for (const entry of entries) { + if (!entry.isFile()) continue; + // Node before 20.12 names the directory `path` instead of `parentPath`. + const parent = (entry as { parentPath?: string; path?: string }).parentPath ?? (entry as { path?: string }).path ?? root; + const path = join(parent, entry.name); + files.set(relative(root, path).split("\\").join("/"), await readFile(path, "utf8")); + } + return files; +} + +async function sameTree(left: string, right: string): Promise { + const [a, b] = await Promise.all([treeFiles(left), treeFiles(right)]); + return a.size === b.size && [...a].every(([path, content]) => b.get(path) === content); +} + /** * Replace the retired `knowledge-query` Skill in a kb package template with the * shipped `context-knowledge-query` Skill, in the old Skill's language, and add @@ -33,9 +56,8 @@ export async function migrateLegacyQuerySkill(projectRoot: string, pkg: PackageD const reviewState = (await inspectPackageTemplateReview(projectRoot, pkg)).state; const starterUntouched = reviewState === "review-required" || reviewState === "starter-accepted"; - const language = await legacyLanguage(legacyRoot); - const shippedRoot = resolveContextPackageTemplatesRoot(); - const shippedKb = join(language === "zh-CN" ? join(dirname(shippedRoot), "package-templates.zh-CN") : shippedRoot, "kb"); + const language = await skillLanguage(legacyRoot); + const shippedKb = shippedKbRoot(language); const target = join(templateRoot, "skills", QUERY_SKILL); await rm(legacyRoot, { recursive: true, force: true }); @@ -60,3 +82,37 @@ export async function migrateLegacyQuerySkills( } return results; } + +/** + * The shared query Skill is owned by the CLI: every Context package ships the + * same copy, and a package manager keeps one copy per version. When a kb + * template's `context-knowledge-query` differs from the shipped Skill in the + * same language, replace it so the packaged Skill always matches the Context + * version it declares. Package-specific hints belong in the search guide. + */ +export async function refreshSharedQuerySkill(projectRoot: string, pkg: PackageDefinition): Promise { + if (pkg.kind !== "package.kb") return undefined; + const templateRoot = resolve(projectRoot, pkg.template.path); + const target = join(templateRoot, "skills", QUERY_SKILL); + if (!existsSync(target)) return undefined; + const language = await skillLanguage(target); + const shipped = join(shippedKbRoot(language), "skills", QUERY_SKILL); + if (await sameTree(target, shipped)) return undefined; + const reviewState = (await inspectPackageTemplateReview(projectRoot, pkg)).state; + await rm(target, { recursive: true, force: true }); + await cp(shipped, target, { recursive: true }); + if (reviewState === "review-required" || reviewState === "starter-accepted") await refreshStarterTemplateDigest(templateRoot); + return { packageName: pkg.name, templatePath: pkg.template.path, language }; +} + +export async function refreshSharedQuerySkills( + projectRoot: string, + packages: readonly PackageDefinition[], +): Promise { + const results: QuerySkillMigration[] = []; + for (const pkg of packages) { + const result = await refreshSharedQuerySkill(projectRoot, pkg); + if (result !== undefined) results.push(result); + } + return results; +} diff --git a/packages/context-cli/src/project/packageSite.ts b/packages/context-cli/src/project/packageSite.ts index 4709da86..9fbecc10 100644 --- a/packages/context-cli/src/project/packageSite.ts +++ b/packages/context-cli/src/project/packageSite.ts @@ -18,7 +18,7 @@ import { knowledgeMapSectionAnchor } from "./packageKnowledgeMap.js"; import { markdownReaderLinks } from "./markdownLinks.js"; import type { ApprovedKnowledgeFile } from "./packageIndexes.js"; import { articleProvenanceMarkdown, siteArticleSources, siteSourceLinks } from "./packageSiteSources.js"; -import { siteMarkdownConfig, siteThemeCss, siteThemeScript, siteThemeLabels } from "./packageSiteTheme.js"; +import { siteMarkdownConfig, siteMarkdownImports, siteThemeCss, siteThemeScript, siteThemeLabels } from "./packageSiteTheme.js"; import { packageSiteBranding } from "./packageSiteBranding.js"; import { writeSiteExtensions, siteExtensionTargets, invalidSiteExtension } from "./packageSiteExtensions.js"; import { packageDistributionMetadataPath } from "./packageDistributionMetadata.js"; @@ -31,7 +31,7 @@ import { createLocaleMarker, createSiteLocalizer, escapeHtml, localizedHtml, res // knowledge and configuration are unchanged, because page and navigation // rendering live in the CLI itself. export const PACKAGE_SITE_VERSION = `vitepress-site-v50-light-shared-preferences:${createHash("sha256") - .update(JSON.stringify([readPackageVersion(), siteThemeVariables.toString(), siteMarkdownConfig, siteThemeCss, siteThemeScript, + .update(JSON.stringify([readPackageVersion(), siteThemeVariables.toString(), siteMarkdownImports(), siteMarkdownConfig, siteThemeCss, siteThemeScript, siteThemeLabels("zh"), siteThemeLabels("en")])) .digest("hex")}`; const require = createRequire(import.meta.url); @@ -230,7 +230,8 @@ export async function writePackageSite(input: { const vueRequire = createRequire(join(vitepressRoot, "package.json")); for (const [name, path] of [["vitepress", vitepressRoot], ["vue", dirname(vueRequire.resolve("vue/package.json"))], ["@mermaid-js/layout-elk", dirname(require.resolve("@mermaid-js/layout-elk/package.json"))], - ["mermaid", dirname(require.resolve("mermaid/package.json"))]]) { + ["mermaid", dirname(require.resolve("mermaid/package.json"))], + ["markdown-it-cjk-friendly", dirname(require.resolve("markdown-it-cjk-friendly/package.json"))]]) { await mkdir(dirname(join(temporary, "node_modules", name!)), { recursive: true }); await symlink(path!, join(temporary, "node_modules", name!), "dir"); } @@ -312,7 +313,7 @@ export async function writePackageSite(input: { ...(options.extensions?.slots?.brand ? { siteTitle: false } : {}), contextLocales: { primary: readers.primary, tags: readers.tags, labels: readers.labels, titles: readers.titles, preferenceKey: readers.preferenceKey }, } }; - await writeFile(join(configRoot, "config.mjs"), `const contextLocaleMarker = ${JSON.stringify(readers.translated ? marker : null)};\n` + + await writeFile(join(configRoot, "config.mjs"), `${siteMarkdownImports()}const contextLocaleMarker = ${JSON.stringify(readers.translated ? marker : null)};\n` + `export default { ...${JSON.stringify(config)}, markdown: { ${siteMarkdownConfig} },\n` + ` transformHtml: html => html.replace(/