diff --git a/.githooks/pre-commit b/.githooks/pre-commit index f2b0666..d8eddb8 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -8,10 +8,30 @@ set -u # Files whose change can break consistency. Keep in sync with -# scripts/check-consistency.sh sources. -WATCHED_REGEX='^(references/(articles\.md|AGENTS\.md)|README\.md|README\.en\.md|AGENTS\.md|prompts/deep-research-tracker\.md|(concepts|thinking|feedback|works)/[^/]+\.md)$' +# scripts/check-consistency.sh sources: C1-C13 read the README/AGENTS/tracker, +# content markdown (any depth — nested works/practice/tools files count), and +# the images referenced by translations (C10 checks works/imgs/* existence); +# C14 additionally reads index.md, .vitepress/**, and the check script itself. +WATCHED_REGEX='^(references/(articles|AGENTS)\.md$|README(\.en)?\.md$|AGENTS\.md$|index\.md$|\.vitepress/|works/imgs/|scripts/check-consistency\.sh$|(concepts|thinking|feedback|works|practice|tools|prompts)/.+\.md$)' + +# --no-renames: a rename lists only the destination path by default, letting +# `git mv README.md elsewhere` slip past the watch list unseen. +# T (typechange) included: replacing a tracked file with a symlink in place +# stages as T, not A/M — excluding it would blind both branches below. +staged=$(git diff --cached --no-renames --name-only --diff-filter=ACMRDT) + +# Symlinks are banned repo-wide (C14 invariant c). Reject a staged symlink +# directly from the INDEX — running the full checks instead would scan the +# working tree, which can be swapped back to a regular file after staging +# (TOCTOU): the staged state is what gets committed, so it is what we judge. +staged_symlinks=$(git diff --cached --no-renames --raw --diff-filter=ACMRT | awk -F'\t' '$1 ~ / 120000 / { print $2 }') +if [ -n "$staged_symlinks" ]; then + echo "pre-commit blocked: symlinks are forbidden in this repo (C14) — staged:" + echo "$staged_symlinks" | sed 's/^/ /' + echo "Vite dereferences symlinks into the published site; commit the real file instead." + exit 1 +fi -staged=$(git diff --cached --name-only --diff-filter=ACMRD) if ! echo "$staged" | grep -qE "$WATCHED_REGEX"; then exit 0 fi diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..2ddc527 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,73 @@ +name: deploy-docs + +# 构建 VitePress 站点并部署到 GitHub Pages(自定义域名 harness.dyu.sh 在 +# 仓库 Settings → Pages 配置,DNS 侧为 harness CNAME → deusyu.github.io)。 +# 工作流骨架吸收自 PR #21(@Doraemonblogs),本版差异:npm ci(锁定依赖)、 +# Node 22(与本地验证环境一致)。 + +on: + push: + branches: [main] + workflow_dispatch: + +# 最小权限:build job 只读源码;pages/id-token 写权限只授给 deploy job, +# 构建(会执行仓库内脚本)不持有任何部署凭据。 +permissions: + contents: read + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + pages: read # configure-pages 读取 Pages 配置(GET /pages)需要 + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 # lastUpdated 与 RSS 条目时间都需要完整 git 历史 + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Setup Node + uses: actions/setup-node@v5 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Verify sidebar completeness (C14) + run: npm run docs:verify + + - name: Build site + run: npm run docs:build + + - name: Verify dist contract (page ↔ markdown-copy parity) + run: npm run docs:verify:dist + + - name: Disable Jekyll + run: touch .vitepress/dist/.nojekyll + + - name: Upload artifact + uses: actions/upload-pages-artifact@v4 + with: + path: .vitepress/dist + + deploy: + needs: build + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml new file mode 100644 index 0000000..574a33a --- /dev/null +++ b/.github/workflows/docs-build.yml @@ -0,0 +1,42 @@ +name: docs-build + +# PR 上的只读构建门:完整 VitePress 构建(死链检查随构建执行)+ 侧栏完整性 +# + 产物契约(html ↔ .md 副本一一对应、副本自足、无 symlink)。 +# 部署仍只走 deploy-docs.yml(合并到 main 之后);本工作流不持有任何写权限。 + +on: + pull_request: + +permissions: + contents: read + +# 同一 PR 连续 push 时取消上一次未完成的构建,不浪费 runner。 +concurrency: + group: docs-build-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 # gitDate/lastUpdated 需要完整历史,与部署构建保持同参 + + - name: Setup Node + uses: actions/setup-node@v5 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Verify sidebar completeness (C14) + run: npm run docs:verify + + - name: Build site + run: npm run docs:build + + - name: Verify dist contract (page ↔ markdown-copy parity) + run: npm run docs:verify:dist diff --git a/.gitignore b/.gitignore index ca4182d..fcbd504 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,12 @@ CLAUDE.md .baoyu-skills/ translate/ +# VitePress 本地缓存(构建产物已被上面的 dist/ 规则覆盖) +.vitepress/cache/ + # 私密商务资料(内训洽谈、聊天截图等,永不入库) private/ + +# 本地过程产物(审计报告、发布稿、课程大纲 PDF 等,站点侧已由 SRC_EXCLUDE +# 排除;这里补 git 侧防线,防止 git add -A 把商务产物带进公开仓库) +output/ diff --git a/.vitepress/config.ts b/.vitepress/config.ts new file mode 100644 index 0000000..8753827 --- /dev/null +++ b/.vitepress/config.ts @@ -0,0 +1,475 @@ +import { defineConfig } from 'vitepress' +import { execFileSync } from 'node:child_process' +import fs from 'node:fs' +import path from 'node:path' +// @ts-ignore — 纯 ESM 生成器(Node 标准库,无类型声明) +import { + ROOT, + SRC_EXCLUDE, + assertContentFile, + buildSidebar, + collectPages, + collectPublishedPages, + computeStats, + findForbiddenSymlinks, + mapMarkdownLinks, + SITE_HOST, +} from './sidebar.mjs' +// @ts-ignore — 首页文案唯一事实源(HomeArchive.vue 消费同一模块) +import { HOME_TITLE, homeMarkdown } from './theme/home-copy.mjs' + +const HOST = SITE_HOST +const REPO_URL = 'https://github.com/deusyu/harness-engineering' +const RAW_URL = 'https://raw.githubusercontent.com/deusyu/harness-engineering/main' +const SITE_TITLE = 'Harness Engineering' +const SITE_DESC = '驭缰工程中文学习档案——概念笔记、独立思考、系统性翻译与实践记录' + +// 门闩:config 一被加载(dev 与 build 皆然)就拒绝任何 symlink。Vite 会解引用 +// public/ 下的软链,Markdown 图片管线会读取链接目标——参与发布的每一个字节都 +// 必须来自仓库本身。 +const forbiddenSymlinks = findForbiddenSymlinks() +if (forbiddenSymlinks.length) { + throw new Error( + `symlinks are forbidden in this repo (they can leak external files into the published site): ${forbiddenSymlinks.join(', ')}` + ) +} + +// git 跟踪集:只有跟踪中的目标才配改写成 GitHub/raw 链接——本地存在但未跟踪 +// 的文件改写过去只会得到 404。git 不可用(如脱离仓库的裸目录)时放行兜底。 +const TRACKED = (() => { + try { + const files = new Set( + execFileSync('git', ['ls-files', '-z'], { + cwd: ROOT, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + maxBuffer: 32 * 1024 * 1024, + }) + .split('\0') + .filter(Boolean) + ) + const dirs = new Set() + for (const f of files) { + let d = path.posix.dirname(f) + while (d !== '.' && !dirs.has(d)) { + dirs.add(d) + d = path.posix.dirname(d) + } + } + return { files, dirs } as { files: Set | null; dirs: Set } + } catch { + return { files: null, dirs: new Set() } + } +})() + +const isTracked = (rel: string, isDir: boolean) => + TRACKED.files === null || (isDir ? TRACKED.dirs.has(rel) : TRACKED.files.has(rel)) + +/** URL 路径段编码(CJK/空格/& 等合法文件名安全;'#'/'?' 文件名被 C14 verify 禁止)。 + * 括号等 RFC3986 sub-delims 也强制编码——它们会截断 Markdown 行内链接语法。 */ +const encodePath = (rel: string) => + rel + .split('/') + .map((seg) => + encodeURIComponent(seg).replace(/[()!'*]/g, (ch) => `%${ch.charCodeAt(0).toString(16).toUpperCase()}`) + ) + .join('/') + +/** 容错解码:非法转义序列按原文返回(幂等编码的前置步骤)。 */ +const safeDecode = (s: string) => { + try { + return decodeURIComponent(s) + } catch { + return s + } +} + +/** 锚点编码:改写产出的 URL 若透传原始锚点,空格等字符会截断 Markdown 链接。 + * 先解码再编码保证幂等——源里已写成 %20 / UTF-8 转义的锚点不会被二次编码。 */ +const encodeAnchor = (anchor: string) => (anchor ? `#${encodeURIComponent(safeDecode(anchor.slice(1)))}` : '') + +/** 站内页面路由 → 对外绝对 URL(llms/RSS/og 的唯一出口,统一做路径段编码)。 */ +const pageUrl = (link: string) => `${HOST}${encodePath(link)}` + +const PUBLISHED_FILES = new Set(collectPublishedPages().map((p) => p.file)) + +type LinkClass = + | { kind: 'skip' } // 协议链接/锚点/站内绝对路径/无法解析 —— 原样保留 + | { kind: 'published'; rel: string; anchor: string } // 相对链接指向已发布 md + | { kind: 'dir-readme'; rel: string; anchor: string } // 目录且其 README 已发布 + | { kind: 'github'; rel: string; anchor: string; isDir: boolean } // 已跟踪的仓库资产 + | { kind: 'unknown' } // 不存在/未跟踪 —— 留给死链检查大声报错 + +/** 站点渲染与 .md 副本共用的链接判定:一个决策表,两个消费端。 */ +function classifyLink(pageRel: string, href: string): LinkClass { + if (!href || /^(?:[a-z][a-z0-9+.-]*:|\/\/|#|\/)/i.test(href)) return { kind: 'skip' } + const hash = href.indexOf('#') + const anchor = hash === -1 ? '' : href.slice(hash) + let target = hash === -1 ? href : href.slice(0, hash) + // query string 不是文件路径的一部分(仓库文件对 query 无语义,解析时剥离)。 + const q = target.indexOf('?') + if (q !== -1) target = target.slice(0, q) + if (!target) return { kind: 'skip' } + // 按路径段 decodeURIComponent(decodeURI 不解 %26/%3A 等保留字符,源里写 + // 成 a%26b.md 的链接会匹配不上文件名含 & 的已发布页)。 + const decoded = target.split('/').map(safeDecode).join('/') + const rel = path.posix + .normalize(path.posix.join(path.posix.dirname(pageRel), decoded)) + .replace(/\/+$/, '') + if (!rel || rel.startsWith('..')) return { kind: 'skip' } + if (PUBLISHED_FILES.has(rel)) return { kind: 'published', rel, anchor } + let stat + try { + stat = fs.statSync(path.join(ROOT, rel)) + } catch { + return { kind: 'unknown' } + } + if (stat.isDirectory() && PUBLISHED_FILES.has(path.posix.join(rel, 'README.md'))) { + return { kind: 'dir-readme', rel, anchor } + } + if (!isTracked(rel, stat.isDirectory())) return { kind: 'unknown' } + return { kind: 'github', rel, anchor, isDir: stat.isDirectory() } +} + +export default defineConfig({ + lang: 'zh-CN', + base: '/', + title: SITE_TITLE, + description: SITE_DESC, + + cleanUrls: true, + lastUpdated: true, + + // 站点只发布内容页:智能体导航文件(AGENTS.md)、仓库门面(根 README)、 + // 本地过程稿(translate/、output/)与私密资料(private/)都不属于站点。 + // 排除清单的唯一事实源在 sidebar.mjs(SRC_EXCLUDE),与发布页面模型同源。 + srcExclude: SRC_EXCLUDE, + + // 死链阻断保持开启:仓库内为 GitHub 浏览而写的交叉链接(目录链接、指向 + // .py/AGENTS.md 的链接)由下方 markdown.config 的构建期改写规则统一转成 + // GitHub 链接,站内不应残留任何死链。 + sitemap: { hostname: HOST }, + + markdown: { + image: { lazy: true }, + config(md) { + // 构建期链接改写:相对链接若指向站点未发布的目标(AGENTS.md、源码文件、 + // 目录……),改写为 GitHub 链接,内容在站点与 GitHub 两个语境下都可读; + // 指向的目录若有已发布的 README,则直接路由到站内该页。改写发生在 parse + // 阶段,VitePress 的死链检查看到的已是改写后的链接,因此无需关闭检查。 + // 判定逻辑与 .md 副本改写共用 classifyLink 一张决策表。 + // (PUBLISHED_FILES 在 config 加载时快照一次;dev 模式新增页面需重启才 + // 会进集合,生产构建每次全新快照,不受影响。) + md.core.ruler.push('ha_rewrite_repo_links', (state) => { + const pagePath = (state.env as { relativePath?: string })?.relativePath + if (!pagePath) return + for (const block of state.tokens) { + if (block.type !== 'inline' || !block.children) continue + for (const token of block.children) { + if (token.type !== 'link_open') continue + const href = token.attrGet('href') + if (!href) continue + const c = classifyLink(pagePath, href) + if (c.kind === 'dir-readme') { + token.attrSet('href', `/${c.rel}/README${encodeAnchor(c.anchor)}`) + } else if (c.kind === 'github') { + token.attrSet( + 'href', + `${REPO_URL}/${c.isDir ? 'tree' : 'blob'}/main/${encodePath(c.rel)}${encodeAnchor(c.anchor)}` + ) + } + // skip/published/unknown:原样保留(unknown 留给死链检查大声报错) + } + } + }) + }, + }, + + head: [ + ['link', { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }], + // 标题衬线(Noto Serif SC):Google Fonts 按 unicode-range 切片按需加载, + // 不可达时回退系统宋体(Songti SC / STSong)。 + ['link', { rel: 'preconnect', href: 'https://fonts.googleapis.com' }], + ['link', { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' }], + ['link', { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@600;700;900&display=swap' }], + ['meta', { name: 'theme-color', content: '#f5f1e8' }], + ['meta', { property: 'og:site_name', content: SITE_TITLE }], + ['meta', { property: 'og:type', content: 'website' }], + ['link', { rel: 'alternate', type: 'application/rss+xml', title: `${SITE_TITLE} RSS`, href: `${HOST}/feed.xml` }], + ], + + transformPageData(pageData) { + const cleanPath = pageData.relativePath.replace(/(^|\/)index\.md$/, '$1').replace(/\.md$/, '') + pageData.frontmatter.head ??= [] + pageData.frontmatter.head.push( + ['meta', { property: 'og:title', content: pageData.title ? `${pageData.title} | ${SITE_TITLE}` : `${SITE_TITLE} 学习档案` }], + ['meta', { property: 'og:description', content: pageData.description || SITE_DESC }], + ['meta', { property: 'og:url', content: pageUrl(`/${cleanPath}`) }], + ['meta', { name: 'twitter:card', content: 'summary' }], + ) + // 构建时估算阅读时长(中文按字数计),供 DocMeta 文档页头使用。 + if (pageData.relativePath !== 'index.md') { + try { + const raw = fs + .readFileSync(assertContentFile(pageData.relativePath), 'utf8') + .replace(/^---[\s\S]*?\n---/, '') + .replace(/```[\s\S]*?```/g, '') + const cjk = (raw.match(/[\u4e00-\u9fff]/g) ?? []).length + const words = (raw.match(/[A-Za-z0-9]+/g) ?? []).length + pageData.frontmatter.haReadingTime = Math.max(1, Math.round((cjk + words * 1.5) / 400)) + } catch { + /* 文件不可读时跳过阅读时长 */ + } + } + }, + + themeConfig: { + logo: '/favicon.svg', + siteTitle: '驭缰工程', + + nav: [ + { text: '概念', link: '/concepts/00-overview' }, + { text: '思考', link: '/thinking/why-this-project-exists' }, + { text: '实践', link: '/practice/01-ralph-demo/README' }, + { text: '作品', link: '/works/harness-engineering-chinese-interpretation' }, + { text: '资料库', link: '/references/articles' }, + ], + + // 侧边栏由 .vitepress/sidebar.mjs 从文件系统生成(C14 守卫),不手写。 + sidebar: buildSidebar(), + + outline: { label: '本页目录 · ON THIS PAGE', level: [2, 3] }, + + search: { + provider: 'local', + options: { + translations: { + button: { buttonText: '搜索', buttonAriaLabel: '搜索文档' }, + modal: { + noResultsText: '未找到相关结果', + resetButtonTitle: '清除查询条件', + footer: { selectText: '选择', navigateText: '切换', closeText: '关闭' }, + }, + }, + }, + }, + + socialLinks: [{ icon: 'github', link: 'https://github.com/deusyu/harness-engineering' }], + + footer: { + message: '人类掌舵,智能体执行 · Released under the MIT License', + copyright: 'Copyright © 2026 deusyu', + }, + + lastUpdated: { text: '最后更新于' }, + docFooter: { prev: '上一篇', next: '下一篇' }, + + editLink: { + pattern: 'https://github.com/deusyu/harness-engineering/edit/main/:path', + text: '在 GitHub 上编辑此页', + }, + + externalLinkIcon: true, + }, + + /** + * 构建尾钩子(Node 标准库实现,零运行时依赖): + * 1. 每个内容页生成同路径 .md 纯文本副本(URL + `.md` 即得); + * 2. /llms.txt 与 /llms-full.txt —— 面向智能体的站点索引与全文(llms.txt 约定); + * 3. /feed.xml —— RSS 2.0,条目时间取自 git 提交历史。 + */ + async buildEnd(siteConfig) { + const out = siteConfig.outDir + const pages = collectPages() // 侧栏内容页:llms 分组索引与 RSS 用 + const published = collectPublishedPages() // 发布全集(含首页与附属页):md 副本与全文用 + const stats = computeStats() + + // 首页的 Markdown 版本由 home-copy.mjs(与 HomeArchive.vue 同源)生成—— + // index.md 源文件只是组件壳,直接复制对机器不可读。副本带 frontmatter; + // llms-full 用无 frontmatter 的正文(那里已有生成的元数据块)。 + const homeMd = homeMarkdown(stats, HOST) + const copyOf = (p: { file: string; link: string }) => + p.file === 'index.md' + ? `---\ntitle: ${JSON.stringify(HOME_TITLE)}\n---\n\n${homeMd}` + : transformCopy(p.file, fs.readFileSync(assertContentFile(p.file), 'utf8')) + + // 1) 每个发布页面伴生同路径 Markdown 副本(首页 → /index.md)。副本内的 + // 仓库专用链接与图片同步改写为 GitHub/raw 绝对地址,保证脱离仓库语境 + // 也能解析。与产物的一一对应由 scripts/verify-dist.mjs 在构建后机械校验。 + const copies = new Map() + for (const p of published) copies.set(p.file, copyOf(p)) + for (const p of published) { + const dest = path.join(out, p.link === '/' ? 'index.md' : `${p.link.slice(1)}.md`) + fs.mkdirSync(path.dirname(dest), { recursive: true }) + fs.writeFileSync(dest, copies.get(p.file)!) + } + + // 2) llms.txt / llms-full.txt + const sidebarFiles = new Set(pages.map((p) => p.file)) + const extras = published.filter((p) => p.file !== 'index.md' && !sidebarFiles.has(p.file)) + const model = groupedForLlms(pages) + const llms = [ + `# ${SITE_TITLE} 学习档案`, + '', + `> 中文 Harness Engineering(驭缰工程)知识库:${stats.concepts} 篇概念笔记、${stats.thinking} 篇独立思考、${stats.translations} 篇一手翻译,以及收录 ${stats.articles} 篇文章的深度摘要索引。人类掌舵,智能体执行。`, + '', + '本站每个页面都有同路径的 Markdown 版本:在页面 URL 后追加 `.md` 即可获取纯文本(首页为 `/index.md`)。', + '', + ...model, + ...(extras.length + ? ['## 附属页面', '', ...extras.map((p) => `- [${p.text}](${pageUrl(p.link)}.md)`), ''] + : []), + '## 完整内容', + '', + `- [llms-full.txt](${HOST}/llms-full.txt):全站正文合并版`, + '', + ].join('\n') + fs.writeFileSync(path.join(out, 'llms.txt'), llms) + + // 全文 = 首页 + 侧栏内容页(按分区顺序)+ 附属页。合并文档没有「所在 + // 目录」,因此不能复用同路径副本的内容:改用 absolute 模式重新改写 + //(相对链接 → 各页 .md 副本的绝对 URL),并剥离源 frontmatter——生成的 + // 元数据块后紧跟第二个 --- 块会让按块解析的消费端产生歧义。标题经 + // JSON.stringify 保证元数据块是合法 YAML。 + const fullEntries = [ + { text: HOME_TITLE, link: '/', content: homeMd }, + ...[...pages, ...extras].map((p) => ({ + text: p.text, + link: p.link, + content: stripFrontmatter( + transformCopy(p.file, fs.readFileSync(assertContentFile(p.file), 'utf8'), true) + ), + })), + ] + const full = fullEntries + .map((p) => `\n\n---\ntitle: ${JSON.stringify(p.text)}\nurl: ${pageUrl(p.link)}\n---\n\n${p.content}`) + .join('') + fs.writeFileSync(path.join(out, 'llms-full.txt'), `# ${SITE_TITLE} 学习档案 — 全站正文\n${full}`) + + // 3) RSS(feed.xml) + const dated = pages + .map((p) => ({ ...p, date: gitDate(p.file) })) + .sort((a, b) => b.date.getTime() - a.date.getTime()) + .slice(0, 30) + const items = dated + .map((p) => + [ + ' ', + ` ${xmlEscape(p.text)}`, + ` ${xmlEscape(pageUrl(p.link))}`, + ` ${xmlEscape(pageUrl(p.link))}`, + ` ${p.date.toUTCString()}`, + ' ', + ].join('\n') + ) + .join('\n') + const rss = [ + '', + '', + ' ', + ` ${SITE_TITLE} 学习档案`, + ` ${HOST}`, + ` ${xmlEscape(SITE_DESC)}`, + ' zh-cn', + ` ${new Date().toUTCString()}`, + items, + ' ', + '', + '', + ].join('\n') + fs.writeFileSync(path.join(out, 'feed.xml'), rss) + }, +}) + +const IMAGE_EXT_RE = /\.(png|jpe?g|gif|svg|webp|avif)$/i + +/** + * .md 副本 / llms-full.txt 的构建期改写:链接解析共用 sidebar.mjs 的 + * mapMarkdownLinks(围栏/行内代码/嵌套徽章/引用式定义都在那里统一处理), + * 判定共用 classifyLink 决策表。 + * - 图片:已跟踪的仓库图片改写为 raw.githubusercontent 绝对地址——站点把 + * 图片打包成哈希资产,原相对路径在副本语境是死的;引用式定义按目标扩展名 + * 判定是否图片(定义处看不到使用侧语法); + * - 链接:指向已发布 md 的相对链接在同路径副本里原样保留(副本目录结构与 + * 站点一致,依然成立);目录改写为其 README 副本;其余已跟踪资产改写为 + * GitHub 链接。 + * - absolute 模式(llms-full.txt 专用):合并文档没有「所在目录」,任何 + * 相对/根相对链接都无从解析——已发布页改写为其 .md 副本的绝对 URL, + * 根相对路由补上 host。 + */ +function transformCopy(pageRel: string, raw: string, absolute = false): string { + return mapMarkdownLinks(raw, (kind: 'image' | 'link' | 'def', href: string) => { + const c = classifyLink(pageRel, href) + const asImage = kind === 'image' || (kind === 'def' && c.kind === 'github' && IMAGE_EXT_RE.test(c.rel)) + if (asImage) { + if (c.kind === 'github' && !c.isDir) return `${RAW_URL}/${encodePath(c.rel)}` + if (absolute && /^\/[^/]/.test(href)) return `${HOST}${href}` // 根相对图片同样要补 host + return null + } + if (c.kind === 'dir-readme') return `${HOST}/${encodePath(c.rel)}/README.md${encodeAnchor(c.anchor)}` + if (c.kind === 'github') { + return `${REPO_URL}/${c.isDir ? 'tree' : 'blob'}/main/${encodePath(c.rel)}${encodeAnchor(c.anchor)}` + } + if (absolute) { + if (c.kind === 'published') { + return `${pageUrl(`/${c.rel.replace(/\.md$/, '')}`)}.md${encodeAnchor(c.anchor)}` + } + if (/^\/[^/]/.test(href)) return `${HOST}${href}` // 根相对路由补 host + } + return null // skip/published/unknown:原样保留 + }) +} + +/** 剥离源 frontmatter(llms-full 专用——生成的元数据块后再跟一个 --- 块会产生解析歧义)。 */ +const stripFrontmatter = (raw: string) => raw.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '') + +function groupedForLlms(pages: Array<{ text: string; link: string; file: string }>): string[] { + const sections = new Map() + const sectionOf = (file: string) => { + const top = file.split('/')[0] + const names: Record = { + concepts: '概念笔记', + thinking: '独立思考', + practice: '动手实践', + feedback: '反馈记录', + works: '翻译与作品', + tools: '工具库', + prompts: '提示词', + references: '资源索引', + } + return names[top] ?? top + } + for (const p of pages) { + const key = sectionOf(p.file) + if (!sections.has(key)) sections.set(key, []) + sections.get(key)!.push(`- [${p.text}](${pageUrl(p.link)}.md)`) + } + const lines: string[] = [] + for (const [name, links] of sections) { + lines.push(`## ${name}`, '', ...links, '') + } + return lines +} + +function gitDate(file: string): Date { + try { + // 参数数组 + `--` 分隔符:文件名永远只是参数,不进 shell,杜绝命令注入。 + const iso = execFileSync('git', ['log', '-1', '--format=%cI', '--', file], { + cwd: ROOT, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }).trim() + if (iso) return new Date(iso) + } catch { + /* 无 git 历史(浅克隆/未跟踪文件)时回退到构建时间 */ + } + return new Date() +} + +function xmlEscape(s: string): string { + return s + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') +} diff --git a/.vitepress/sidebar.mjs b/.vitepress/sidebar.mjs new file mode 100644 index 0000000..e1fc775 --- /dev/null +++ b/.vitepress/sidebar.mjs @@ -0,0 +1,473 @@ +/** + * 站点导航与统计的唯一生成器 —— 一切从文件系统派生,杜绝手写漂移。 + * + * 注意:本文件不能带 shebang——VitePress 用 esbuild 打包 config.ts 时会把 + * 本文件拼接进 bundle,`#!` 不在文件首字节即语法错误。 + * + * 本仓库的方法论是机械化一致性检查(C1–C14),手写侧边栏会成为检查覆盖 + * 不到的漂移面。因此: + * - 侧边栏永远不手写。新增内容文件自动进入侧边栏;works/ 下未匹配到 + * 任何分组前缀的新文件落入「社区博客」兜底组 —— 宁可分组不准, + * 不可静默丢失。 + * - 站点源码不写裸计数。所有展示数字由 computeStats() 构建时统计。 + * - `node .vitepress/sidebar.mjs --verify` 断言每个一等内容页在侧边栏 + * 恰好出现一次,是 scripts/check-consistency.sh C14 的机械化入口。 + * + * 仅依赖 Node 标准库,可独立执行: + * node .vitepress/sidebar.mjs # 打印生成的侧边栏 JSON + * node .vitepress/sidebar.mjs --verify # 完整性校验(C14 调用) + * node .vitepress/sidebar.mjs --stats # 打印构建时统计 + */ +import { execFileSync } from 'node:child_process' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +export const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const REAL_ROOT = fs.realpathSync(ROOT) + +/** 站点对外域名——config.ts(页面渲染/llms 输出)与 verify-dist(副本校验)同源。 */ +export const SITE_HOST = 'https://harness.dyu.sh' + +/** + * 站点不发布的源文件(VitePress srcExclude 的唯一事实源,config.ts 直接 import)。 + * collectPublishedFiles() 的排除规则与此保持同构;两者的最终一致性由 + * scripts/verify-dist.mjs 在构建产物上机械校验。 + */ +export const SRC_EXCLUDE = [ + '**/AGENTS.md', + 'README.md', + 'README.en.md', + 'CLAUDE.md', + 'translate/**', + 'private/**', + 'output/**', + '.claude/**', +] + +/** + * 安全解析内容文件:必须是仓库根内的普通文件。拒绝 symlink 与越界路径, + * 防止仓库外内容(哪怕通过一个恶意/失误的软链)被读入并复制进公开产物。 + */ +export function assertContentFile(rel) { + const expected = path.join(REAL_ROOT, rel) + if (!expected.startsWith(REAL_ROOT + path.sep)) throw new Error(`refusing path outside repo root: ${rel}`) + const abs = path.join(ROOT, rel) + const st = fs.lstatSync(abs, { throwIfNoEntry: false }) + if (!st || !st.isFile()) throw new Error(`refusing non-regular file (symlink/missing): ${rel}`) + if (fs.realpathSync(abs) !== expected) throw new Error(`refusing symlinked path: ${rel}`) + return abs +} + +function readText(rel) { + return fs.readFileSync(assertContentFile(rel), 'utf8') +} + +/** 列出目录下的内容 md 文件(排除 AGENTS.md 与一切非普通文件),返回仓库相对路径 + * (统一 `/` 分隔——排除规则与 URL 逻辑都以此为标识),按文件名排序。 */ +function listMd(dir, { recursive = false } = {}) { + const abs = path.join(ROOT, dir) + if (!fs.existsSync(abs)) return [] + const out = [] + for (const ent of fs.readdirSync(abs, { withFileTypes: true })) { + if (ent.isDirectory()) { + if (recursive) out.push(...listMd(`${dir}/${ent.name}`, { recursive })) + continue + } + if (!ent.isFile()) continue // symlink 一律不进内容集 + if (!ent.name.endsWith('.md') || ent.name === 'AGENTS.md') continue + out.push(`${dir}/${ent.name}`) + } + return out.sort() +} + +/** 子目录形态的作品/实验:以其 README.md 作为入口页(必须是普通文件,symlink 不算)。 */ +function subdirReadmes(dir) { + const abs = path.join(ROOT, dir) + if (!fs.existsSync(abs)) return [] + const out = [] + for (const ent of fs.readdirSync(abs, { withFileTypes: true })) { + if (!ent.isDirectory()) continue + const rel = `${dir}/${ent.name}/README.md` + const st = fs.lstatSync(path.join(ROOT, rel), { throwIfNoEntry: false }) + if (st?.isFile()) out.push(rel) + } + return out.sort() +} + +/** + * 遍历 Markdown 源里「真实导航」位置的链接——行内链接、图片、引用式定义—— + * 跳过围栏代码块(含 4+ 反引号嵌套围栏)与行内代码里的语法示例。 + * fn(kind, href) 返回字符串则把该链接目标替换为返回值,返回 null 保持原样。 + * 改写侧(config.ts transformCopy)用本函数;校验侧(scripts/verify-dist.mjs) + * 用 VitePress 自带渲染器的 AST 独立提取——两条解析路径互为对方的探测器。 + * + * 已知边界(逐行正则解析的固有限制,与生产解析器对拍基线 171/171 一致): + * 跨行行内代码、4 空格缩进代码块、打断段落的伪引用定义行会被当普通文本处理。 + * 这些形态改坏真实链接时会被 AST 校验以断链形式抓住;改坏代码示例属静默面, + * 仓库内容约定不在正文用这些形态(现存内容为零)。 + */ +export function mapMarkdownLinks(raw, fn) { + // 目标形态:<尖括号目标>(可含空格)或普通目标(允许一层平衡括号)。 + const DEST = /(?:<([^<>\n]*)>|((?:\([^()\s]*\)|[^()\s])+?))/.source + const TITLE = /( +("[^"]*"|'[^']*'))?/.source + const IMG_RE = new RegExp(`!\\[([^\\]]*)\\]\\(${DEST}${TITLE}\\)`, 'g') + // 链接文本允许嵌套一个图片(徽章形态 [![alt](img)](target));(?\n]*)>|(\S+))(.*)$/ + const mapSeg = (seg) => + seg + .replace(IMG_RE, (m, text, angle, plain, titlePart) => { + const r = fn('image', angle ?? plain) + return r == null ? m : `![${text}](${r}${titlePart ?? ''})` + }) + .replace(LINK_RE, (m, text, angle, plain, titlePart) => { + const r = fn('link', angle ?? plain) + return r == null ? m : `[${text}](${r}${titlePart ?? ''})` + }) + let fence = null + return raw + .split('\n') + .map((line) => { + if (fence) { + const close = line.match(/^\s*(`{3,}|~{3,})\s*$/) + if (close && close[1][0] === fence[0] && close[1].length >= fence.length) fence = null + return line + } + const open = line.match(/^\s*(`{3,}|~{3,})/) + if (open) { + fence = open[1] + return line + } + // 行内代码先挖空成占位符:`...` 里的链接是语法示例、不参与解析, + // 但链接文本里的行内代码([\`file.md\`](path) 形态)不能阻断链接识别, + // 所以不能按代码段切开整行——挖空后整行统一匹配,最后回填。 + // 行内已含 NUL 的病态输入(文本文件不该有)直接跳过挖空,防止占位符冲突。 + const codes = [] + const masked = line.includes('\x00') + ? line + : line.replace(/`+[^`\n]*`+/g, (m) => `\x00${codes.push(m) - 1}\x00`) + const def = masked.match(DEF_RE) + let mapped + if (def) { + // 定义处看不到使用侧是链接还是图片,交给消费端按 'def' 自行判定 + //(config.ts 按目标扩展名区分 raw 图片地址与 GitHub 页面地址)。 + const r = fn('def', def[2] ?? def[3]) + mapped = r == null ? masked : `${def[1]}${r}${def[4]}` + } else { + mapped = mapSeg(masked) + } + return mapped.replace(/\x00(\d+)\x00/g, (_, i) => codes[+i]) + }) + .join('\n') +} + +/** + * 全仓库禁止 symlink(构建产物与依赖目录除外):Vite 会解引用 public/ 下的 + * symlink,Markdown 图片管线会读取链接目标,随后整个 dist 被原样发布—— + * 一条恶意或失误的软链即可把仓库外文件带进公开站点。本仓库没有任何合法 + * symlink,因此一律拒绝,而不是逐目录白名单。 + */ +export function findForbiddenSymlinks() { + const found = new Set() + // 第一道:git index。被跟踪的 symlink(mode 120000)无论藏在哪个路径—— + // 包括下面工作树扫描豁免的目录(有人 git add -f node_modules/x 也逃不掉) + // ——都会出现在 CI 的 checkout 里,必须从索引侧兜住。git 不可用时退化为 + // 纯工作树扫描。 + try { + for (const line of execFileSync('git', ['ls-files', '-s', '-z'], { + cwd: ROOT, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + maxBuffer: 32 * 1024 * 1024, + }) + .split('\0') + .filter(Boolean)) { + if (line.startsWith('120000 ')) found.add(line.split('\t')[1]) + } + } catch { + /* 非 git 环境:仅工作树扫描 */ + } + // 第二道:工作树扫描,抓未跟踪的 symlink。豁免只认精确的仓库根路径—— + // 按目录名任意层级豁免会留出 public/node_modules/ 这类被 Vite 原样复制、 + // 却躲过扫描的死角。 + const SKIP_REL = new Set([ + '.git', + 'node_modules', + '.vitepress/dist', + '.vitepress/cache', + '.vitepress/.temp', + ]) + const walk = (dir) => { + for (const ent of fs.readdirSync(path.join(ROOT, dir || '.'), { withFileTypes: true })) { + const rel = dir ? `${dir}/${ent.name}` : ent.name + if (ent.isSymbolicLink()) { + found.add(rel) + continue + } + if (ent.isDirectory()) { + if (SKIP_REL.has(rel)) continue + walk(rel) + } + } + } + walk('') + return [...found].sort() +} + +/** SRC_EXCLUDE 的谓词形式:collectPublishedFiles 用它判断一个文件是否被站点排除。 */ +function isExcludedFromSite(rel) { + if (path.basename(rel) === 'AGENTS.md') return true + if (rel === 'README.md' || rel === 'README.en.md' || rel === 'CLAUDE.md') return true + return ['translate/', 'private/', 'output/', '.claude/'].some((p) => rel.startsWith(p)) +} + +/** + * 站点实际发布的全部 md 源文件(含首页、PROMPT.md、poster/style.md 等不进侧栏的 + * 附属页)。遍历规则与 VitePress 的源扫描一致:跳过点目录与 node_modules,再应用 + * SRC_EXCLUDE。md 副本、llms 输出与产物校验共享这一个模型,杜绝「发布了却没有 + * 机器可读副本」的缝隙。 + */ +export function collectPublishedFiles() { + const out = [] + const walk = (dir) => { + for (const ent of fs.readdirSync(path.join(ROOT, dir || '.'), { withFileTypes: true })) { + const rel = dir ? `${dir}/${ent.name}` : ent.name + if (ent.isDirectory()) { + // 与 VitePress 源扫描的忽略规则对齐:点目录、node_modules、任意层级的 dist + if (ent.name.startsWith('.') || ent.name === 'node_modules' || ent.name === 'dist') continue + walk(rel) + continue + } + if (!ent.isFile() || !ent.name.endsWith('.md')) continue + if (ent.name.startsWith('.')) continue // glob dot:false,点文件不被 VitePress 构建 + if (/\[\w+?\]/.test(rel)) continue // VitePress 视作动态路由,无 .paths 时不产出 html + if (isExcludedFromSite(rel)) continue + out.push(rel) + } + } + walk('') + return out.sort() +} + +/** 标题优先级:frontmatter title > 首个 H1 > 文件名;去掉结尾全角括注以适配侧边栏宽度。 */ +export function extractTitle(rel) { + const text = readText(rel) + let title = null + const fm = text.match(/^---\r?\n([\s\S]*?)\r?\n---/) + if (fm) { + const m = fm[1].match(/^title:\s*(.+)\s*$/m) + if (m) title = m[1].trim().replace(/^["'](.*)["']$/, '$1') + } + if (!title) { + const h1 = text.match(/^# (.+)$/m) + if (h1) title = h1[1].trim() + } + if (!title) title = path.basename(rel, '.md') + return title.replace(/([^()]*)\s*$/, '').trim() +} + +function page(rel) { + return { text: extractTitle(rel), link: '/' + rel.replace(/\.md$/, ''), file: rel } +} + +/** + * works/ 分组规则:按文件名(子目录作品按目录名)前缀归入来源系列(分组式信息 + * 架构吸收自 PR #21,by @Doraemonblogs)。match 按数组顺序生效,最后一组恒真 + * 兜底——未匹配任何前缀的新作品落入「社区博客」,宁可分组不准,不可静默丢失。 + */ +const WORKS_GROUPS = [ + { + text: '原创分析', + match: (n) => + n === 'harness-engineering-chinese-interpretation.md' || n === 'harness-engineering-intro-deck', + }, + { text: 'Martin Fowler 系列', match: (n) => n.startsWith('fowler-') }, + { text: 'Anthropic 系列', match: (n) => n.startsWith('anthropic-') }, + { text: 'LangChain 系列', match: (n) => /^(langchain|langsmith|deep-agents)-/.test(n) }, + { text: '学术论文', match: (n) => /^(arxiv-|meta-harness-paper|inside-the-scaffold-paper)/.test(n) }, + { text: '工程实践', match: (n) => /^(openai|github|cursor|metr|bun)-/.test(n) }, + { text: '中文收录', match: (n) => /-(zh-cn-repost|original)\.md$/.test(n) }, + { text: '社区博客', match: () => true }, +] + +function worksSection() { + const groups = WORKS_GROUPS.map((g) => ({ text: g.text, match: g.match, items: [] })) + for (const rel of listMd('works')) { + groups.find((g) => g.match(path.basename(rel))).items.push(page(rel)) + } + // 子目录作品与平铺文件走同一套匹配规则(按目录名),同样受兜底组保护。 + for (const rel of subdirReadmes('works')) { + groups.find((g) => g.match(path.basename(path.dirname(rel)))).items.push(page(rel)) + } + return { + text: '翻译与作品', + collapsed: true, + items: groups + .filter((g) => g.items.length > 0) + .map((g) => ({ text: g.text, collapsed: true, items: g.items })), + } +} + +export function articlesCount() { + return (readText('references/articles.md').match(/^### \d+\./gm) ?? []).length +} + +/** 内部模型:与最终侧边栏同构,但每个页面节点额外带 file 字段供校验/构建用。 */ +export function buildModel() { + return [ + { text: '概念笔记', en: 'CONCEPTS', collapsed: false, items: listMd('concepts').map(page) }, + { text: '独立思考', en: 'THINKING', collapsed: false, items: listMd('thinking').map(page) }, + { text: '动手实践', en: 'PRACTICE', collapsed: false, items: subdirReadmes('practice').map(page) }, + { text: '反馈记录', en: 'FEEDBACK', collapsed: false, items: listMd('feedback').map(page) }, + { en: 'WORKS', ...worksSection() }, + { text: '工具库', en: 'TOOLS', collapsed: false, items: listMd('tools', { recursive: true }).map(page) }, + { text: '提示词', en: 'PROMPTS', collapsed: false, items: listMd('prompts').map(page) }, + { + text: '资料库', + en: 'REFERENCES', + countValue: articlesCount(), + collapsed: false, + items: [{ ...page('references/articles.md'), text: '文章深度摘要' }], + }, + ] +} + +function countLinks(nodes) { + let n = 0 + for (const node of nodes) { + if (node.link) n += 1 + if (node.items) n += countLinks(node.items) + } + return n +} + +/** 展示层装饰(仅 buildSidebar 使用,collectPages/llms/RSS 拿到的仍是干净文本): + * 顶级分组加英文微标签 + 右对齐计数徽标;带数字前缀的文件名在条目前显示编号。 */ +function decorateItem(node) { + if (node.items) { + return { text: node.text, collapsed: node.collapsed, items: node.items.map(decorateItem) } + } + const m = node.file.match(/(?:^|\/)(\d+)-[^/]*\.md$/) + const text = m ? `${m[1]}${node.text}` : node.text + return { text, link: node.link } +} + +/** VitePress themeConfig.sidebar 直接消费的形态。 */ +export function buildSidebar() { + return buildModel().map((section) => ({ + text: `${section.text}${section.en}${ + section.countValue ?? countLinks(section.items) + }`, + collapsed: section.collapsed, + items: section.items.map(decorateItem), + })) +} + +/** 展平出侧栏全部页面节点(含 file),供 --verify、llms.txt 分组索引与 RSS 使用。 */ +export function collectPages() { + const out = [] + const walk = (nodes) => { + for (const n of nodes) { + if (n.link) out.push(n) + if (n.items) walk(n.items) + } + } + walk(buildModel()) + return out +} + +/** + * 发布页面全集的页面对象(含首页与附属页),供 buildEnd 生成 .md 副本与 + * llms 全文使用——「每个页面都有同路径 Markdown 版本」这句对外承诺以此为准。 + */ +export function collectPublishedPages() { + return collectPublishedFiles().map((rel) => + rel === 'index.md' ? { text: '首页', link: '/', file: rel } : page(rel) + ) +} + +/** 首页与 llms.txt 使用的构建时统计 —— 站点里出现的每个数字都来自这里。 */ +export function computeStats() { + return { + articles: articlesCount(), + translations: listMd('works').filter((f) => f.endsWith('-translation.md')).length, + concepts: listMd('concepts').length, + thinking: listMd('thinking').length, + feedback: listMd('feedback').length, + checks: (readText('scripts/check-consistency.sh').match(/^echo "\[C\d+\]/gm) ?? []).length, + } +} + +/** 一等内容页集合:这些文件必须出现在侧边栏中(PROMPT.md、style.md 等附属材料不在此列)。 */ +function requiredPages() { + const req = new Set() + for (const d of ['concepts', 'thinking', 'feedback', 'prompts', 'works']) { + for (const f of listMd(d)) req.add(f) + } + for (const f of listMd('tools', { recursive: true })) req.add(f) + for (const f of subdirReadmes('practice')) req.add(f) + for (const f of subdirReadmes('works')) req.add(f) + req.add('references/articles.md') + return req +} + +export function verify() { + const files = collectPages().map((p) => p.file) + const seen = new Set() + const dups = [] + for (const f of files) (seen.has(f) ? dups.push(f) : seen.add(f)) + const req = requiredPages() + const missing = [...req].filter((f) => !seen.has(f)) + const orphans = files.filter((f) => !fs.existsSync(path.join(ROOT, f))) + // 侧栏页必须是发布全集的子集——srcExclude 误伤侧栏页会在这里现形。 + const published = new Set(collectPublishedFiles()) + const unpublished = files.filter((f) => !published.has(f)) + const symlinks = findForbiddenSymlinks() + // '#'/'?' 在 URL 中是定界符:VitePress 对这类文件名会静默产出 NotFound 壳页 + //(fail-open),对外出口的 URL 也无法与文件一一对应,机械禁止。 + const badNames = [...published].filter((f) => /[#?]/.test(f)) + const problems = [] + if (badNames.length) + problems.push(`publishable filenames must not contain '#' or '?': ${badNames.join(', ')}`) + if (missing.length) problems.push(`missing from generated sidebar: ${missing.join(', ')}`) + if (dups.length) problems.push(`duplicated in generated sidebar: ${dups.join(', ')}`) + if (orphans.length) problems.push(`sidebar links to nonexistent files: ${orphans.join(', ')}`) + if (unpublished.length) problems.push(`sidebar links to files excluded from the site: ${unpublished.join(', ')}`) + if (symlinks.length) + problems.push(`symlinks are forbidden in this repo (they can leak external files into the published site): ${symlinks.join(', ')}`) + return { + ok: problems.length === 0, + problems, + pageCount: files.length, + requiredCount: req.size, + publishedCount: published.size, + } +} + +const invokedDirectly = + process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url) + +if (invokedDirectly) { + const mode = process.argv[2] + if (mode === '--verify') { + const r = verify() + if (r.ok) { + console.log( + `sidebar derives ${r.pageCount} pages from the filesystem; all ${r.requiredCount} first-class content files present exactly once; site publishes ${r.publishedCount} markdown sources` + ) + process.exit(0) + } + for (const p of r.problems) console.error(p) + process.exit(1) + } else if (mode === '--stats') { + console.log(JSON.stringify(computeStats(), null, 2)) + } else { + console.log(JSON.stringify(buildSidebar(), null, 2)) + } +} diff --git a/.vitepress/theme/AsideMark.vue b/.vitepress/theme/AsideMark.vue new file mode 100644 index 0000000..d8e4a97 --- /dev/null +++ b/.vitepress/theme/AsideMark.vue @@ -0,0 +1,31 @@ + + + diff --git a/.vitepress/theme/DocMeta.vue b/.vitepress/theme/DocMeta.vue new file mode 100644 index 0000000..9c097ec --- /dev/null +++ b/.vitepress/theme/DocMeta.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/.vitepress/theme/HomeArchive.vue b/.vitepress/theme/HomeArchive.vue new file mode 100644 index 0000000..f4fd51f --- /dev/null +++ b/.vitepress/theme/HomeArchive.vue @@ -0,0 +1,792 @@ + + + + + diff --git a/.vitepress/theme/custom.css b/.vitepress/theme/custom.css new file mode 100644 index 0000000..e712d65 --- /dev/null +++ b/.vitepress/theme/custom.css @@ -0,0 +1,308 @@ +/** + * Harness Engineering 文档站 —— 「纸墨缰绳」视觉系统 + * + * 设计语境直接延续仓库自产海报(works/harness-engineering-intro-deck): + * 暖纸底 #F5F1E8 × 深墨 #1F1C17 × 缰绳橙红 #C2481D(唯一强调色), + * 视觉 DNA 是一条缰绳曲线——墨色锚点是人类,箭头指向智能体。 + * 方向:编辑部 / 档案馆气质,宋体系衬线做标题,长中文阅读优先。 + * 设计流程按 baoyu-design(Claude Design 引擎)方法论执行。 + */ + +/* ── 品牌 token ─────────────────────────────────────────────── */ + +:root { + --he-paper: #f5f1e8; + --he-paper-2: #ede7d8; + --he-paper-3: #e5ddc9; + --he-ink: #1f1c17; + --he-ink-2: rgba(31, 28, 23, 0.74); + --he-ink-3: rgba(31, 28, 23, 0.52); + --he-rein: #c2481d; + --he-rein-strong: #a63a14; + --he-rein-soft: rgba(194, 72, 29, 0.12); + --he-hairline: rgba(31, 28, 23, 0.16); + --he-hairline-soft: rgba(31, 28, 23, 0.09); + + --he-serif: 'Noto Serif SC', 'Songti SC', 'STSong', 'SimSun', serif; +} + +.dark { + --he-paper: #171310; + --he-paper-2: #1f1a15; + --he-paper-3: #282218; + --he-ink: #f0eadf; + --he-ink-2: rgba(240, 234, 223, 0.76); + --he-ink-3: rgba(240, 234, 223, 0.55); + --he-rein: #e0602f; + --he-rein-strong: #ef7a4a; + --he-rein-soft: rgba(224, 96, 47, 0.16); + --he-hairline: rgba(240, 234, 223, 0.18); + --he-hairline-soft: rgba(240, 234, 223, 0.1); +} + +/* ── 映射到 VitePress 主题变量 ─────────────────────────────── */ + +:root { + --vp-c-bg: var(--he-paper); + --vp-c-bg-alt: var(--he-paper-2); + --vp-c-bg-soft: var(--he-paper-2); + --vp-c-bg-elv: var(--he-paper); + + --vp-c-text-1: var(--he-ink); + --vp-c-text-2: var(--he-ink-2); + --vp-c-text-3: var(--he-ink-3); + + --vp-c-divider: var(--he-hairline); + --vp-c-gutter: var(--he-hairline); + --vp-c-border: var(--he-hairline); + + --vp-c-brand-1: var(--he-rein); + --vp-c-brand-2: var(--he-rein-strong); + --vp-c-brand-3: var(--he-rein); + --vp-c-brand-soft: var(--he-rein-soft); + + --vp-font-family-base: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', + 'PingFang SC', 'Hiragino Sans GB', 'Noto Sans SC', 'Microsoft YaHei', sans-serif; + + --vp-nav-bg-color: var(--he-paper); + --vp-sidebar-bg-color: var(--he-paper-2); + + --vp-code-block-bg: #eee8d9; + --vp-code-bg: var(--he-paper-3); + --vp-code-color: var(--he-rein-strong); + + --vp-custom-block-tip-border: transparent; + --vp-custom-block-tip-text: var(--he-ink); + --vp-custom-block-tip-bg: var(--he-rein-soft); + --vp-custom-block-info-bg: var(--he-paper-2); + + --vp-button-brand-bg: var(--he-ink); + --vp-button-brand-text: var(--he-paper); + --vp-button-brand-border: var(--he-ink); + --vp-button-brand-hover-bg: var(--he-rein); + --vp-button-brand-hover-text: #fff; + --vp-button-brand-hover-border: var(--he-rein); + --vp-button-brand-active-bg: var(--he-rein-strong); + --vp-button-brand-active-border: var(--he-rein-strong); +} + +.dark { + --vp-code-block-bg: #221d16; + --vp-code-bg: var(--he-paper-3); + --vp-code-color: var(--he-rein-strong); + --vp-button-brand-bg: var(--he-ink); + --vp-button-brand-text: #171310; + --vp-button-brand-border: var(--he-ink); +} + +::selection { + background: var(--he-rein-soft); +} + +/* ── 纸张颗粒(低调的档案质感) ───────────────────────────── */ + +body::after { + content: ''; + position: fixed; + inset: 0; + z-index: 999; + pointer-events: none; + opacity: 0.05; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E"); +} + +.dark body::after { + opacity: 0.07; +} + +/* ── 导航栏 ──────────────────────────────────────────────────── */ + +.VPNav { + border-bottom: 1px solid var(--he-hairline-soft); +} + +.VPNavBar:not(.home) { + background: var(--he-paper); +} + +.VPNavBarTitle .title { + font-family: var(--he-serif); + font-weight: 700; + letter-spacing: 0.02em; +} + +.VPNavBarMenuLink.active { + color: var(--he-rein); +} + +/* ── 侧边栏:档案目录气质 ────────────────────────────────────── */ + +.VPSidebar { + border-right: 1px solid var(--he-hairline-soft); +} + +/* 分组标头:中文名 + 英文微标签 + 右对齐计数徽标(由 sidebar.mjs 构建时注入) */ +.VPSidebarItem.level-0 > .item > .text { + display: flex; + align-items: baseline; + width: 100%; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.16em; + color: var(--he-ink-2); +} + +.ha-side-en { + margin-left: 7px; + font-size: 9px; + font-weight: 400; + letter-spacing: 0.22em; + color: var(--he-ink-3); +} + +.ha-side-count { + margin-left: auto; + font-size: 10px; + font-weight: 400; + letter-spacing: 0; + color: var(--he-ink-3); + font-variant-numeric: tabular-nums; +} + +.ha-side-num { + display: inline-block; + min-width: 26px; + font-size: 11px; + letter-spacing: 0.06em; + color: var(--he-ink-3); + font-variant-numeric: tabular-nums; +} + +.VPSidebarItem .link:hover .ha-side-num, +.VPSidebarItem .is-active .ha-side-num { + color: inherit; +} + +.VPSidebarItem .item .text { + line-height: 1.6; +} + +/* ── 页内目录(outline) ─────────────────────────────────────── */ + +.VPDocAsideOutline .content { + border-left: 1px solid var(--he-hairline); +} + +.VPDocAsideOutline .outline-marker { + background-color: var(--he-rein); +} + +/* ── 正文排版:衬线标题 + 长中文阅读 ────────────────────────── */ + +.vp-doc h1, +.vp-doc h2, +.vp-doc h3, +.vp-doc h4 { + font-family: var(--he-serif); + letter-spacing: 0.015em; +} + +.vp-doc h1 { + font-weight: 900; +} + +/* 小节自动编号(01 02 …):源文件不写编号,编号由 CSS 计数器机械生成 */ +.vp-doc { + counter-reset: ha-sec; +} + +.vp-doc h2 { + font-weight: 700; + border-top: 1px solid var(--he-hairline); + padding-top: 30px; + margin-top: 56px; +} + +.vp-doc h2::before { + counter-increment: ha-sec; + content: counter(ha-sec, decimal-leading-zero); + display: block; + font-family: var(--vp-font-family-base); + font-size: 12px; + font-weight: 700; + letter-spacing: 0.18em; + color: var(--he-rein); + margin-bottom: 10px; +} + +.vp-doc h3 { + font-weight: 700; +} + +.vp-doc ul > li::marker { + color: var(--he-rein); +} + +.vp-doc p, +.vp-doc li { + line-height: 1.85; +} + +.vp-doc a { + text-underline-offset: 4px; + text-decoration-thickness: 1px; +} + +.vp-doc blockquote { + border: 1px solid var(--he-hairline); + border-left: 2px solid var(--he-rein); + background: var(--he-paper-2); + padding: 14px 20px; + color: var(--he-ink-2); +} + +.vp-doc blockquote p { + font-size: 15px; +} + +/* 宽表格横向滚动;表头压纸色 */ +.vp-doc table { + display: block; + overflow-x: auto; +} + +.vp-doc th { + background: var(--he-paper-2); + font-family: var(--he-serif); + font-weight: 600; + letter-spacing: 0.04em; +} + +.vp-doc tr:nth-child(2n) { + background: transparent; +} + +div[class*='language-'] { + border-radius: 4px; + border: 1px solid var(--he-hairline-soft); +} + +/* 上一篇 / 下一篇 */ +.pager-link { + border-radius: 0 !important; +} + +.pager-link .title { + font-family: var(--he-serif); + font-weight: 700; +} + +.pager-link:hover { + border-color: var(--he-rein) !important; +} + +/* ── 本地搜索弹层贴合纸色 ───────────────────────────────────── */ + +.VPLocalSearchBox .shell { + background: var(--he-paper); +} diff --git a/.vitepress/theme/home-copy.mjs b/.vitepress/theme/home-copy.mjs new file mode 100644 index 0000000..3626f5d --- /dev/null +++ b/.vitepress/theme/home-copy.mjs @@ -0,0 +1,167 @@ +/** + * 首页文案的唯一事实源 —— HomeArchive.vue(渲染层)与 config.ts buildEnd + * (/index.md 机器可读副本 + llms-full.txt 首页正文)共同消费本模块, + * 保证「人看到的首页」与「智能体读到的首页」永不漂移。 + * + * 纪律(C14):本模块不得出现任何硬编码计数——数字一律来自入参 stats + * (computeStats() 的返回值)。纯数据模块,可被 Node 与浏览器两端打包。 + */ + +/** 首页标题——/index.md 副本 frontmatter 与 llms-full 首页条目共用,避免三处硬编码。 */ +export const HOME_TITLE = '驭缰工程 · 中文学习档案' + +export function homeCopy(stats) { + return { + hero: { + kicker: 'HARNESS ENGINEERING —— 学习档案 · 中文', + titleLead: '人类掌舵,', + titleEm: '智能体执行', + lede: '一座从概念理解到独立实践的 Harness Engineering 深度学习档案。工程师不再逐行写代码——设计约束、明确意图、构建反馈回路,让智能体可靠地交付。', + actions: [ + { text: '从概念开始', link: '/concepts/00-overview' }, + { text: '读一手翻译', link: '/works/harness-engineering-chinese-interpretation', sup: stats.translations }, + { text: '浏览档案总目', link: '#ledger' }, + ], + // 语义化缰绳图:从人类的约束设计到智能体的如约交付 + rein: { + aria: '从人类设计约束到智能体如约交付的路径', + start: { title: '人类', sub: '设计约束 · 握住缰绳' }, + milestones: ['AGENTS.md', '自定义 linter', 'CI 反馈回路'], + end: { title: '智能体', sub: '如约交付' }, + }, + }, + shift: { + title: '一句话理解', + en: 'THE PARADIGM SHIFT', + rows: [ + { label: '传统工程', chips: ['人类写代码', '机器执行代码'], hot: false }, + { label: 'HARNESS ENG.', chips: ['人类设计约束', '智能体写代码', '机器执行代码'], hot: true }, + ], + noteLead: '核心转变:工程师的产出,从「代码」变成了', + noteEm: '「约束系统」', + noteTail: '——AGENTS.md、架构规则、自定义 linter、反馈回路。', + }, + conceptsSection: { + title: '六大核心概念', + en: 'SIX CORE CONCEPTS', + intro: '像档案卡一样编号归档——每一张都指向仓库里一篇可追溯的概念笔记。', + cards: [ + { num: '01', tag: 'REPO = RECORD', title: '仓库即记录系统', desc: '不在仓库里的东西,对智能体不存在。决策、规范、计划一律以版本化工件入库。', path: 'concepts/01', link: '/concepts/01-repo-as-source-of-truth' }, + { num: '02', tag: 'MAP, NOT MANUAL', title: '地图而非手册', desc: 'AGENTS.md 是目录页,不是百科全书。渐进式披露,从小入口点指向更深的文档。', path: 'concepts/00', link: '/concepts/00-overview' }, + { num: '03', tag: 'MECHANICAL', title: '机械化执行', desc: '文档会腐烂,lint 规则不会。自定义 linter 与结构测试,是不变量的守护者。', path: 'concepts/02', link: '/concepts/02-mechanical-enforcement' }, + { num: '04', tag: 'AGENT READABLE', title: '智能体可读性', desc: '优先为智能体的推理优化。选「无聊」的稳定技术,让应用可按 worktree 隔离启动。', path: 'concepts/04', link: '/concepts/04-agent-readability' }, + { num: '05', tag: 'THROUGHPUT', title: '吞吐量改变合并理念', desc: '纠错成本低、等待成本高。PR 生命周期很短,偶发失败靠续跑重跑解决。', path: 'concepts/05', link: '/concepts/05-throughput-changes-merge' }, + { num: '06', tag: 'ENTROPY & GC', title: '熵管理 = 垃圾回收', desc: '技术债是高息贷款。把「黄金规则」编码进仓库,后台任务定期扫描并修复偏差。', path: 'concepts/03', link: '/concepts/03-entropy-and-garbage-collection' }, + ], + }, + ledgerSection: { + title: '档案总目', + en: 'BUILD-TIME · 自动清点', + intro: `导航与下面每一个数字,都在构建时由脚本从仓库文件系统清点生成,再由 C1–C${stats.checks} 一致性检查守护,不随文档腐烂而漂移。这不是营销数据,是一座档案馆的总目。`, + rows: [ + { title: '文章索引', sub: 'references/articles.md · 深度摘要', value: stats.articles, unit: '篇', link: '/references/articles' }, + { title: '一手翻译', sub: 'works/*-translation.md', value: stats.translations, unit: '篇', link: '/works/harness-engineering-chinese-interpretation' }, + { title: '概念笔记', sub: 'concepts/', value: stats.concepts, unit: '篇', link: '/concepts/00-overview' }, + { title: '独立思考', sub: 'thinking/', value: stats.thinking, unit: '篇', link: '/thinking/why-this-project-exists' }, + { + title: '一致性检查', + sub: `scripts/check-consistency.sh · C1–C${stats.checks}`, + value: stats.checks, + unit: '项', + accent: true, + link: 'https://github.com/deusyu/harness-engineering/blob/main/scripts/check-consistency.sh', + }, + ], + }, + band: { + title: '仓库即 harness · 自我指涉', + en: 'THE ARCHIVE RUNS ON WHAT IT RECORDS', + leadLead: '这个仓库,', + leadEm: '开始策展自己了', + leadTail: '。', + pillars: [ + { tag: 'HUMAN GATE', title: '人类闸门', desc: '「收不收进来」始终是一道人类闸门。人类掌舵,决定什么值得进入档案。' }, + { tag: 'MECHANICAL RAIL', title: '机械护栏', desc: `C1–C${stats.checks} 一致性检查守着计数与保真,不让任何数字悄悄腐烂。` }, + { tag: 'FEEDBACK LOOP', title: '反馈回路', desc: '外部调研的评审,由智能体沿一条固化成 skill 的流水线自动完成。' }, + ], + noteLead: '于是约束本身成了产品——正是', + noteLinkText: '「约束即产品」', + noteLink: '/concepts/07-spec-as-product', + noteTail: '讲的东西,只不过这一次,实验的对象是仓库自己。', + }, + routeSection: { + title: '从哪里开始', + en: 'A READING ROUTE · 5 PHASES', + phases: [ + { n: '1', meta: `concepts/ · ${stats.concepts} 篇`, title: '理解核心概念', desc: '覆盖 OpenAI 六大概念,加上控制论扩展与「约束即产品」的延伸。', link: '/concepts/00-overview' }, + { n: '2', meta: `thinking/ · ${stats.thinking} 篇`, title: '形成自己的观点', desc: '质疑、延伸与跨文章洞察——把别人的范式变成自己能用的判断(持续中)。', link: '/thinking/why-this-project-exists' }, + { n: '3', meta: 'practice/ · Ralph Demo', title: '选一个小项目实践', desc: '跑通一个自主循环:321 秒 · $0.31——用最小成本亲手验证方法论。', link: '/practice/01-ralph-demo/README' }, + { n: '4', meta: `feedback/ · ${stats.feedback} 篇`, title: '记录反馈迭代', desc: '把踩坑与修正留成轨迹——「翻译即 harness」是第一篇(持续中)。', link: '/feedback/2026-04-14-translation-as-harness' }, + { n: '5', meta: `works/ · ${stats.translations} 篇翻译 + 原创`, title: '输出可展示的作品', desc: '专业一手翻译加原创综合分析——学习闭环在这里交付。', link: '/works/harness-engineering-chinese-interpretation', last: true }, + ], + }, + } +} + +/** + * 首页的 Markdown 渲染(/index.md 副本正文与 llms-full.txt 首页条目): + * 与 HomeArchive.vue 消费同一份 homeCopy,站内路由输出为 host 绝对链接, + * 让抓走这份 Markdown 的智能体拿到可直接跟进的 URL。 + * 不含 frontmatter——/index.md 副本的 frontmatter 由 buildEnd 统一加 + * (llms-full 里该内容紧跟生成的元数据块,再带 frontmatter 会形成歧义双块)。 + */ +export function homeMarkdown(stats, host) { + const c = homeCopy(stats) + const abs = (link) => + link.startsWith('/') ? `${host}${link}` : link.startsWith('#') ? `${host}/${link}` : link + const rein = c.hero.rein + const lines = [ + `# ${c.hero.titleLead}${c.hero.titleEm}`, + '', + c.hero.kicker, + '', + c.hero.lede, + '', + ...c.hero.actions.map((a) => `- [${a.text}${a.sup != null ? `(${a.sup} 篇)` : ''}](${abs(a.link)})`), + '', + `${rein.aria}:${rein.start.title}(${rein.start.sub})→ ${rein.milestones.join(' → ')} → ${rein.end.title}(${rein.end.sub})`, + '', + `## § 01 ${c.shift.title}(${c.shift.en})`, + '', + ...c.shift.rows.map((r) => `- ${r.label}:${r.chips.join(' → ')}`), + '', + `${c.shift.noteLead}${c.shift.noteEm}${c.shift.noteTail}`, + '', + `## § 02 ${c.conceptsSection.title}(${c.conceptsSection.en})`, + '', + c.conceptsSection.intro, + '', + ...c.conceptsSection.cards.map( + (card) => `- **${card.num} ${card.title}**(${card.tag}):${card.desc}([${card.path}](${abs(card.link)}))` + ), + '', + `## § 03 ${c.ledgerSection.title}(${c.ledgerSection.en})`, + '', + c.ledgerSection.intro, + '', + ...c.ledgerSection.rows.map( + (row) => `- [${row.title}](${abs(row.link)})(${row.sub}):${row.value} ${row.unit}` + ), + '', + `## § 04 ${c.band.title}(${c.band.en})`, + '', + `${c.band.leadLead}${c.band.leadEm}${c.band.leadTail}`, + '', + ...c.band.pillars.map((p) => `- **${p.title}**(${p.tag}):${p.desc}`), + '', + `${c.band.noteLead}[${c.band.noteLinkText}](${abs(c.band.noteLink)})${c.band.noteTail}`, + '', + `## § 05 ${c.routeSection.title}(${c.routeSection.en})`, + '', + ...c.routeSection.phases.map( + (p) => `${p.n}. **${p.title}**(PHASE ${p.n} · ${p.meta}):${p.desc}([阅读](${abs(p.link)}))` + ), + '', + ] + return lines.join('\n') +} diff --git a/.vitepress/theme/home-stats.data.mjs b/.vitepress/theme/home-stats.data.mjs new file mode 100644 index 0000000..7cf79f7 --- /dev/null +++ b/.vitepress/theme/home-stats.data.mjs @@ -0,0 +1,17 @@ +// 首页统计的数据加载器:构建/开发时执行 computeStats(), +// 让首页数字与仓库文件系统永远同步(C14 禁止站点源码手写计数)。 +import { computeStats } from '../sidebar.mjs' + +export default { + watch: [ + '../../references/articles.md', + '../../works/*.md', + '../../concepts/*.md', + '../../thinking/*.md', + '../../feedback/*.md', + '../../scripts/check-consistency.sh', + ], + load() { + return computeStats() + }, +} diff --git a/.vitepress/theme/index.ts b/.vitepress/theme/index.ts new file mode 100644 index 0000000..d5fc1e2 --- /dev/null +++ b/.vitepress/theme/index.ts @@ -0,0 +1,19 @@ +import { h } from 'vue' +import type { Theme } from 'vitepress' +import DefaultTheme from 'vitepress/theme' +import HomeArchive from './HomeArchive.vue' +import DocMeta from './DocMeta.vue' +import AsideMark from './AsideMark.vue' +import './custom.css' + +export default { + extends: DefaultTheme, + Layout: () => + h(DefaultTheme.Layout, null, { + 'doc-before': () => h(DocMeta), + 'aside-outline-after': () => h(AsideMark), + }), + enhanceApp({ app }) { + app.component('HomeArchive', HomeArchive) + }, +} satisfies Theme diff --git a/AGENTS.md b/AGENTS.md index 4cf30ca..83631ea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,6 +49,7 @@ - **C11** — markdown 表格形状:README ×2、`references/AGENTS.md`、`references/articles.md`、`works/AGENTS.md` 里每一行表格的单元格数须与表头一致 - **C12** — 条目字段完整性:`references/articles.md` 每个 `### N.` 编号条目必须带 **作者:** 与 **日期:** 字段 - **C13** — 零插图声明须留痕:C10 只能证伪"多报"(嵌图数 < 声明数才 FAIL),因此 `sourceFigureCount: 0` 在本地**永远无法被证伪**——不管你有没有真去核对原文,它都是绿的。2026-07-27 就是这个洞放行了一个假 0(原文实有 4 张配图)。C10 刻意零网络、无法回查原文,所以改为要求留痕:**声明 0 的译文必须同时带 `sourceFigureAudit` 字段,值里要有 `YYYY-MM-DD` 核对日期**,写清怎么核对的、结论是什么。`null` 仍然 SKIP——它本来就自陈未审计 +- **C14** — 文档站防漂移:VitePress 站点(部署到 harness.dyu.sh)的侧边栏与首页统计一律由 `.vitepress/sidebar.mjs` 在构建时从文件系统生成,不手写。三条不变量:站点源码(`index.md`、`.vitepress/`)不得出现裸计数("N 篇"),数字只能来自 `computeStats()`;每个一等内容文件必须在生成的侧边栏中恰好出现一次;仓库内禁止任何 symlink——Vite 会解引用 `public/` 下的软链、图片管线会读取链接目标,一条软链即可把仓库外文件带进公开产物(后两条经 `node .vitepress/sidebar.mjs --verify` 执行)。构建产物侧另有 `scripts/verify-dist.mjs` 断言页面与 `.md` 副本一一对应、副本链接可达、dist 无 symlink。站点脚手架不存在时 SKIP;node 不可用时 verify 半边 SKIP、裸计数 grep 照常执行 执行:`bash scripts/check-consistency.sh`(仓库根目录) 启用 pre-commit 阻断:`git config core.hooksPath .githooks` diff --git a/README.en.md b/README.en.md index 86ad777..6355fdd 100644 --- a/README.en.md +++ b/README.en.md @@ -3,6 +3,7 @@ ![License: MIT](https://img.shields.io/badge/license-MIT-blue) ![Articles](https://img.shields.io/badge/articles-74-green) ![Translations](https://img.shields.io/badge/translations-34-orange) +[![Read online](https://img.shields.io/badge/read%20online-harness.dyu.sh-c2481d)](https://harness.dyu.sh) # Harness Engineering Study Guide @@ -227,7 +228,7 @@ The "Ralph Wiggum Loop" is the core implementation pattern of Harness Engineerin ## 🛠️ Development Notes -The repo ships with a consistency checker, `scripts/check-consistency.sh`, guarding against count and fidelity drift across thirteen layers of checks: +The repo ships with a consistency checker, `scripts/check-consistency.sh`, guarding against count and fidelity drift across fourteen layers of checks: - **C1-C2** — `references/articles.md` article count + its 4 downstream claim sites (README × 2 badges, `prompts/deep-research-tracker.md` header, `references/AGENTS.md` overview) - **C3** — actual `*.md` file counts in `concepts/` / `thinking/` / `feedback/` match the README "X 篇" claims @@ -241,6 +242,7 @@ The repo ships with a consistency checker, `scripts/check-consistency.sh`, guard - **C11** — markdown table shape: in the checked files, every table row must carry the same cell count as its header - **C12** — every numbered entry in `references/articles.md` must carry the **作者:** and **日期:** fields - **C13** — zero-figure claims need an audit trail. C10 can only falsify OVER-claiming, so `sourceFigureCount: 0` is unfalsifiable locally — that hole shipped a false 0 on 2026-07-27 (the source had 4 body figures). Any translation claiming 0 must therefore also carry `sourceFigureAudit` containing a `YYYY-MM-DD` date, stating how the claim was verified +- **C14** — docs-site harness integrity: the VitePress sidebar and every displayed count must be derived from the filesystem at build time by `.vitepress/sidebar.mjs`; site sources (`index.md`, `.vitepress/**`) must not hardcode counts, and `node .vitepress/sidebar.mjs --verify` asserts every first-class content file appears in the generated sidebar exactly once and rejects any symlink in the repo (symlinks can leak external files into the published artifact). On the artifact side, `scripts/verify-dist.mjs` asserts published pages and their `.md` copies correspond one-to-one, that relative links and images inside the copies resolve, and that dist contains no symlinks **Enable the pre-commit hook after first clone:** @@ -248,7 +250,7 @@ The repo ships with a consistency checker, `scripts/check-consistency.sh`, guard git config core.hooksPath .githooks ``` -Once enabled, every commit touching the README, `AGENTS.md`, `references/articles.md`, `references/AGENTS.md`, `prompts/deep-research-tracker.md`, or any `*.md` under `concepts/` / `thinking/` / `feedback/` / `works/` runs the checks automatically; unrelated commits are left alone. +Once enabled, every commit touching the README, `AGENTS.md`, `references/articles.md`, `references/AGENTS.md`, `index.md`, `.vitepress/`, `scripts/check-consistency.sh`, or any `*.md` (nested included) under `concepts/` / `thinking/` / `feedback/` / `works/` / `practice/` / `tools/` / `prompts/` runs the checks automatically; staging a symlink at any path is rejected outright (the repo-wide C14 ban, judged on the staged state). Unrelated commits are left alone. **Run manually:** `bash scripts/check-consistency.sh` @@ -260,7 +262,7 @@ See the "机械化检查" section of the root `AGENTS.md` for details. > This archive now curates itself. > -> Bringing in outside research no longer runs on vibes — it follows a pipeline frozen into a skill, [`curate-research`](.claude/skills/curate-research/SKILL.md): review is automated by parallel agents (the feedback loop), `scripts/check-consistency.sh` keeps counts and fidelity from drifting via C1–C13 (the mechanical rail), and whether something gets in is always a human gate (humans steer, agents execute). +> Bringing in outside research no longer runs on vibes — it follows a pipeline frozen into a skill, [`curate-research`](.claude/skills/curate-research/SKILL.md): review is automated by parallel agents (the feedback loop), `scripts/check-consistency.sh` keeps counts and fidelity from drifting via C1–C14 (the mechanical rail), and whether something gets in is always a human gate (humans steer, agents execute). > > So the constraints themselves became the product — exactly what [concepts/07-spec-as-product.md](concepts/07-spec-as-product.md) argues, except this time the subject is the repo itself. diff --git a/README.md b/README.md index 9e98dd9..6d55270 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ ![License: MIT](https://img.shields.io/badge/license-MIT-blue) ![Articles](https://img.shields.io/badge/articles-74-green) ![Translations](https://img.shields.io/badge/translations-34-orange) +[![在线阅读](https://img.shields.io/badge/在线阅读-harness.dyu.sh-c2481d)](https://harness.dyu.sh) # Harness Engineering 学习指南 @@ -226,7 +227,7 @@ harness-engineering/ ## 🛠️ 开发须知 -仓库自带一致性检查脚本 `scripts/check-consistency.sh`,守护数量与保真类漂移,覆盖十三层校验: +仓库自带一致性检查脚本 `scripts/check-consistency.sh`,守护数量与保真类漂移,覆盖十四层校验: - **C1-C2** — `references/articles.md` 文章数 + 下游 4 处引用(README × 2 badges、`prompts/deep-research-tracker.md` 头部、`references/AGENTS.md` 概览) - **C3** — `concepts/` / `thinking/` / `feedback/` 三个目录的 `*.md` 实际数与 README "X 篇" 声明一致 @@ -240,6 +241,7 @@ harness-engineering/ - **C11** — markdown 表格形状:受检文件里每行表格的单元格数须与表头一致 - **C12** — `references/articles.md` 每个编号条目必须带 **作者:** 与 **日期:** 字段 - **C13** — 零插图声明须留痕。C10 只能证伪"多报",`sourceFigureCount: 0` 在本地永远无法被证伪——2026-07-27 就是这个洞放行了一个假 0(原文实有 4 张图)。因此声明 0 的译文必须同时带 `sourceFigureAudit`,值里要有 `YYYY-MM-DD` 核对日期,写清怎么核对、结论是什么 +- **C14** — 文档站驭缰完整性:站点侧边栏与所有展示计数必须由 `.vitepress/sidebar.mjs` 构建时从文件系统派生,站点源码(`index.md`、`.vitepress/**`)不得手写计数;`node .vitepress/sidebar.mjs --verify` 断言每个一等内容页在生成的侧边栏中恰好出现一次,并拒绝仓库内出现任何 symlink(软链会把仓库外文件带进公开产物)。构建产物侧另有 `scripts/verify-dist.mjs` 断言发布页面与 `.md` 副本一一对应、副本内相对链接与图片可达、dist 无 symlink **首次 clone 后启用 pre-commit hook:** @@ -247,7 +249,7 @@ harness-engineering/ git config core.hooksPath .githooks ``` -启用后,每次 commit 涉及 README、`AGENTS.md`、`references/articles.md`、`references/AGENTS.md`、`prompts/deep-research-tracker.md`、或 `concepts/` / `thinking/` / `feedback/` / `works/` 中的 `*.md` 时会自动跑检查;不涉及则不打扰。 +启用后,每次 commit 涉及 README、`AGENTS.md`、`references/articles.md`、`references/AGENTS.md`、`index.md`、`.vitepress/`、`scripts/check-consistency.sh`、或 `concepts/` / `thinking/` / `feedback/` / `works/` / `practice/` / `tools/` / `prompts/` 下(含嵌套目录)的 `*.md` 时会自动跑检查;此外任何路径下 stage 了 symlink 会被直接拒绝提交(C14 全仓禁令,按暂存区状态判定)。不涉及则不打扰。 **手动跑:** `bash scripts/check-consistency.sh` @@ -259,7 +261,7 @@ git config core.hooksPath .githooks > 这个仓库开始策展自己了。 > -> 收录外部调研不再靠手感——它走一条固化成 skill 的流水线 [`curate-research`](.claude/skills/curate-research/SKILL.md):评审由并行 agent 自动完成(反馈回路),`scripts/check-consistency.sh` 的 C1–C13 守着计数与保真不漂移(机械护栏),而"收不收进来"始终是一道人类闸门(人类掌舵、智能体执行)。 +> 收录外部调研不再靠手感——它走一条固化成 skill 的流水线 [`curate-research`](.claude/skills/curate-research/SKILL.md):评审由并行 agent 自动完成(反馈回路),`scripts/check-consistency.sh` 的 C1–C14 守着计数与保真不漂移(机械护栏),而"收不收进来"始终是一道人类闸门(人类掌舵、智能体执行)。 > > 于是约束本身成了产品——正是本仓库 [concepts/07-spec-as-product.md](concepts/07-spec-as-product.md) 讲的东西,只不过这次的实验对象是仓库自己。 diff --git a/index.md b/index.md new file mode 100644 index 0000000..1efe6e6 --- /dev/null +++ b/index.md @@ -0,0 +1,7 @@ +--- +layout: page +title: 驭缰工程 · 中文学习档案 +sidebar: false +--- + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a6cd75b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2537 @@ +{ + "name": "harness-engineering-docs", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "harness-engineering-docs", + "version": "1.0.0", + "devDependencies": { + "vitepress": "^1.6.4" + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.22.0.tgz", + "integrity": "sha512-BFR6zNowNKcY7Ou7TaJc9QWexES4YKPbmf/OTFofpdsdhz4x6q0lbxp3duO0EHnyrN7rE4ba/TSXuY+BDGu4+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.56.0.tgz", + "integrity": "sha512-7r4Z3NC7yU1oAQVWJNA2HX7tX481F3pJvCGyLIXiTdBcthz4Q/o21jwcMYDFkuI92UWTNBQQmHYgwHo1zS5dzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.56.0.tgz", + "integrity": "sha512-avmjXQSq+jadFO8Xl2em05/uQdQnEmHsJyOAdVbZkmVgpMfxL12aJwVVfGNwYr9nulcpuJN1X0lTaQ5wxuNGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.56.0.tgz", + "integrity": "sha512-v2TPStUhY//ripPjIVclZ8AWc7DEGooXULZGFlFu37zNatgHjw34oZZ+OSbbc/YHO+xZwPl62I1k8xH1m4S2eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.56.0.tgz", + "integrity": "sha512-P0ehROpM4Sem3Sqo5x2cKPgj67D3G3jy0rh1Amwkcvsfr6tkvIcdCmerieanqTF7NxUMPNFLkpIFeMO8Rpa50w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.56.0.tgz", + "integrity": "sha512-SXK3Vn3WVxyzbm31oePZBJkp1wpOyuWdd4B/Pv7n0aXDxmeSWhC1R1FC1517mMrFAIaPH4Rt0x6RUe7ZNjz8FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.56.0.tgz", + "integrity": "sha512-5+ZdX8garFnmycnZgKhtXHePEaLj5zqDxI/0lkhhluzCcvTn0/PvvTirTg8hHYetQHvn7GDyeAiqTAieMvMW4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.56.0.tgz", + "integrity": "sha512-+mKUdYvqOi0BcvpAEyCEw49vSBptufIcfibtHz2bdr1pI789M46Yt0uQEk/sxtK3teh71OQvVFHaTDzShUWewQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.56.0.tgz", + "integrity": "sha512-9g/zj+AZx5moFcdFIrYQoVrueXivjUcc3MQHtCYT8WhIuk1lUh1AyEhvJCS0XBZld09cLvd1AZ3BvDBpVpX2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.56.0.tgz", + "integrity": "sha512-Qf3Sr6f9A9uxCZUf3MXS0d2b877uYzEB5yxqpVGXAhcJnBCQjrRRon0KvefpGkxy+BshrIJs96OUoMtGqXTFDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.56.0.tgz", + "integrity": "sha512-GXWG1rWc5wu8hY4N33Y3b6ernY6sAdAvmKWN/zHAiACOx40WnpG0TVX5YazCAr/9gOYGInSiM2A0y2jy2xbiDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.56.0.tgz", + "integrity": "sha512-7t24cBxaInS3mZb7ddEaZT/tp6q+/aR4YttsQVyP1/i+LmwPR34atO35KjaLFCcRVrlP7sYOAqkCfg6lIRB+ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.56.0.tgz", + "integrity": "sha512-R7ePHgVYmDFjZpvrsVAfbDz/d4RxKAYZ5/vgLfIsCVRZRryjWl/3INOxpOICzitehQ5FjNtNjcLQTrmHPTcHBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.56.0.tgz", + "integrity": "sha512-PIOUXlSnrqM0S+WOgDRb4RzotydJH7ZoT6tOyL7tAO7qJOfvX5wsEW8Pe+PMKMwvuI4/gIyK9cg2H7lJXqnc4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.93", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.93.tgz", + "integrity": "sha512-/XhANjfGYOuqvSR3TmUnkQkINvQ4GVjVuukvymRbxtVFBvIq/yiXJqCDycKcQPT401OYT9H2vIY6ihAlz1QIAw==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", + "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", + "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^3.1.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", + "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", + "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", + "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", + "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", + "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.41.tgz", + "integrity": "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.41", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.41.tgz", + "integrity": "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.41.tgz", + "integrity": "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.41", + "@vue/compiler-dom": "3.5.41", + "@vue/compiler-ssr": "3.5.41", + "@vue/shared": "3.5.41", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.41.tgz", + "integrity": "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.10.tgz", + "integrity": "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.10" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz", + "integrity": "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.10", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz", + "integrity": "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.41.tgz", + "integrity": "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.41.tgz", + "integrity": "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.41.tgz", + "integrity": "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.41", + "@vue/runtime-core": "3.5.41", + "@vue/shared": "3.5.41", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.41.tgz", + "integrity": "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.41", + "@vue/runtime-dom": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", + "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", + "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vueuse/core": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/algoliasearch": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.56.0.tgz", + "integrity": "sha512-PrqppUmhT4ENdas2pH9caE7efUcxy6EcSFhWzosiVuQBzu2tQ5yLTI6jwomT/1cuBnivzGfxiJCqDNN9FRRh+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.22.0", + "@algolia/client-abtesting": "5.56.0", + "@algolia/client-analytics": "5.56.0", + "@algolia/client-common": "5.56.0", + "@algolia/client-insights": "5.56.0", + "@algolia/client-personalization": "5.56.0", + "@algolia/client-query-suggestions": "5.56.0", + "@algolia/client-search": "5.56.0", + "@algolia/ingestion": "1.56.0", + "@algolia/monitoring": "1.56.0", + "@algolia/recommend": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/focus-trap": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", + "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tabbable": "^6.4.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", + "dev": true, + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/oniguruma-to-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", + "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.29.8", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.8.tgz", + "integrity": "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact-render-to-string": ">=5" + }, + "peerDependenciesMeta": { + "preact-render-to-string": { + "optional": true + } + } + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/shiki": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", + "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/langs": "2.5.0", + "@shikijs/themes": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tabbable": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitepress": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", + "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.41.tgz", + "integrity": "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.41", + "@vue/compiler-sfc": "3.5.41", + "@vue/runtime-dom": "3.5.41", + "@vue/server-renderer": "3.5.41", + "@vue/shared": "3.5.41" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5497103 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "harness-engineering-docs", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "docs:dev": "vitepress dev", + "docs:build": "vitepress build", + "docs:preview": "vitepress preview", + "docs:verify": "node .vitepress/sidebar.mjs --verify", + "docs:verify:dist": "node scripts/verify-dist.mjs" + }, + "devDependencies": { + "vitepress": "^1.6.4" + } +} diff --git a/public/CNAME b/public/CNAME new file mode 100644 index 0000000..90d9d1c --- /dev/null +++ b/public/CNAME @@ -0,0 +1 @@ +harness.dyu.sh diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..c6b5372 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..c31e77e --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://harness.dyu.sh/sitemap.xml diff --git a/scripts/check-consistency.sh b/scripts/check-consistency.sh index 1597e4b..e497e56 100755 --- a/scripts/check-consistency.sh +++ b/scripts/check-consistency.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # check-consistency.sh — guard against drift between articles.md and downstream caches. # -# Twelve checks: +# Fourteen checks: # C1 — articles.md heading numbering is contiguous 1..N # C2 — that N matches every downstream count claim # (README.md / README.en.md / prompts/deep-research-tracker.md / references/AGENTS.md) @@ -45,6 +45,19 @@ # sourceFigureAudit field containing a YYYY-MM-DD date, recording when # and how the "no figures" claim was verified. null keeps SKIPping — # it already self-declares as unaudited. +# C14 — docs-site harness integrity. The VitePress site must derive its +# sidebar and every displayed count from the filesystem at build time +# (.vitepress/sidebar.mjs), never from hand-written config. Three +# invariants: (a) site sources (index.md, .vitepress/) must not +# hardcode library counts ("N 篇") — numbers belong to computeStats(); +# (b) every first-class content file must appear in the generated +# sidebar exactly once; (c) no symlink may exist anywhere in the repo +# — Vite dereferences symlinks under public/ and the markdown image +# pipeline follows link targets, so a single symlink can leak files +# from outside the repo into the published artifact. (b) and (c) run +# via node .vitepress/sidebar.mjs --verify. SKIPs when the site +# scaffold is absent; the verify half additionally SKIPs when node is +# unavailable. # # Locale pitfall (do NOT reintroduce): bracket expressions containing multibyte # characters — e.g. [├└] or [^。] — silently break under LC_ALL=C with BSD grep: @@ -509,6 +522,46 @@ if [ "$c13_fail" -eq 0 ]; then echo " $(green PASS) — $c13_zero zero-figure claim(s), all carrying a dated audit trail" fi +# ─── C14 ─────────────────────────────────────────────────────────────── +# Docs-site harness integrity: the sidebar and all displayed counts are +# generated from the filesystem (.vitepress/sidebar.mjs). Hand-written nav +# or hardcoded counts would be a drift surface no other check covers. +echo "[C14] docs site derives nav/counts from the filesystem" +if [ ! -f .vitepress/sidebar.mjs ]; then + echo " $(yellow SKIP) — no .vitepress/sidebar.mjs (docs site scaffold not present)" +else + c14_ok=1 + # 扫描整个 .vitepress/(AGENTS.md 的约定范围),构建产物与缓存除外—— + # 新增任何站点源码文件都自动落入裸计数检查,无需回来改这行。 + c14_hits=$(grep -rnE --exclude-dir=dist --exclude-dir=cache --exclude-dir=.temp '[0-9]+ ?篇' index.md .vitepress 2>/dev/null || true) + if [ -n "$c14_hits" ]; then + while IFS= read -r c14_hit; do + [ -z "$c14_hit" ] && continue + echo " $(red FAIL) — hardcoded count in site source: $c14_hit" + done </dev/null 2>&1; then + c14_verify=$(node .vitepress/sidebar.mjs --verify 2>&1) + if [ $? -ne 0 ]; then + while IFS= read -r c14_line; do + [ -z "$c14_line" ] && continue + echo " $(red FAIL) — $c14_line" + done < (p.link === '/' ? 'index.html' : `${p.link.slice(1)}.html`) +const mdOf = (p) => (p.link === '/' ? 'index.md' : `${p.link.slice(1)}.md`) + +const expected = new Set(pages.map(htmlOf)) +expected.add('404.html') + +const actual = [] +const symlinks = [] +const walk = (dir) => { + for (const ent of fs.readdirSync(dir, { withFileTypes: true })) { + const abs = path.join(dir, ent.name) + if (ent.isSymbolicLink()) { + symlinks.push(path.relative(DIST, abs)) + continue + } + if (ent.isDirectory()) walk(abs) + else if (ent.name.endsWith('.html')) actual.push(path.relative(DIST, abs)) + } +} +walk(DIST) + +for (const s of symlinks) problems.push(`symlink in dist (would upload external content): ${s}`) + +const actualSet = new Set(actual) +for (const h of actual) { + if (!expected.has(h)) problems.push(`unexpected page in dist (source leak?): ${h}`) +} +for (const h of expected) { + if (h !== '404.html' && !actualSet.has(h)) problems.push(`page missing from dist: ${h}`) +} + +// .md 副本存在性 + 自足性:副本内的相对链接/图片,以及指向本站的绝对链接 +// (生成式首页副本与目录改写都输出 host 绝对 URL),都必须在 dist 内可达。 +// 围栏与行内代码里的语法示例由 markdown 解析天然排除在校验之外。 +const MD = await createMarkdownRenderer(path.resolve(HERE, '..')) +const extractLinks = (raw) => { + const found = [] + const walk = (tokens) => { + for (const t of tokens) { + if (t.type === 'link_open') found.push({ kind: 'link', href: t.attrGet('href') }) + if (t.type === 'image') found.push({ kind: 'image', href: t.attrGet('src') }) + // 裸 HTML 标签里的 src/href/srcset 不产出 link/image token——改写器也 + // 看不见它们,必须在这里补上校验面,否则两侧同盲。属性值带引号或不带 + // 引号都要认;srcset 是逗号分隔的 "URL 描述符" 对,逐项取 URL。 + if ((t.type === 'html_inline' || t.type === 'html_block') && t.content) { + for (const m of t.content.matchAll( + /(?:src|href)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/gi + )) { + found.push({ kind: 'html', href: m[1] ?? m[2] ?? m[3] }) + } + for (const m of t.content.matchAll(/srcset\s*=\s*(?:"([^"]*)"|'([^']*)')/gi)) { + for (const entry of (m[1] ?? m[2]).split(',')) { + const url = entry.trim().split(/\s+/)[0] + if (url) found.push({ kind: 'html', href: url }) + } + } + } + if (t.children) walk(t.children) + } + } + walk(MD.parse(raw, {})) + return found +} + +/** + * href 分类:{ scope: 'external' } 不校验;{ scope: 'site', target } 在 dist 内 + * 校验;{ scope: 'relative', target } 相对链接(target 相对 baseDir 已解析, + * baseDir 为 null 表示无所在目录的语境——llms-full——此时相对链接本身即违规)。 + * query 与锚点都不是文件路径的一部分,解析前剥离。 + */ +const classifyHref = (baseDir, href) => { + if (!href) return { scope: 'external' } + const strip = (s) => s.split('#')[0].split('?')[0] + if (href.startsWith(`${SITE_HOST}/`) || href === SITE_HOST) { + return { scope: 'site', target: strip(href.slice(SITE_HOST.length + 1)) } + } + if (href.startsWith('/') && !href.startsWith('//')) { + return { scope: 'site', target: strip(href.slice(1)) } + } + if (/^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i.test(href)) return { scope: 'external' } + const t = strip(href) + if (!t) return { scope: 'external' } + return { + scope: 'relative', + target: baseDir === null ? null : path.posix.normalize(path.posix.join(baseDir, t)), + } +} +for (const p of pages) { + const copyRel = mdOf(p) + const copyAbs = path.join(DIST, copyRel) + if (!fs.existsSync(copyAbs)) { + problems.push(`page has no same-path markdown copy: ${p.link} (expected /${copyRel})`) + continue + } + checkLinks(fs.readFileSync(copyAbs, 'utf8'), path.posix.dirname(copyRel), `/${copyRel}`) +} + +// llms-full.txt:合并文档没有所在目录,其中不允许任何相对链接(baseDir=null +// 时 classifyHref 直接判违规);站内绝对链接照常在 dist 内校验。 +const llmsFullPath = path.join(DIST, 'llms-full.txt') +if (fs.existsSync(llmsFullPath)) { + checkLinks(fs.readFileSync(llmsFullPath, 'utf8'), null, '/llms-full.txt') +} + +// 代码完整性:构建期链接改写是逐行正则实现,理论上存在把缩进代码块/复杂 +// code span 里的链接语法当真实导航改写的盲区(AST 校验器恰恰会忽略代码, +// 发现不了这种损坏)。这里用生产解析器对比源文件与副本的全部代码 token +// 序列——改写器动了任何代码内容,就在这儿大声失败,静默面清零。 +const ROOT = path.resolve(HERE, '..') +const codeTokens = (raw) => { + const out = [] + const walk = (tokens) => { + for (const t of tokens) { + if (t.type === 'fence' || t.type === 'code_block' || t.type === 'code_inline') { + out.push(t.content) + } + if (t.children) walk(t.children) + } + } + walk(MD.parse(raw, {})) + return out.join('\u0000') +} +for (const p of pages) { + if (p.file === 'index.md') continue // 首页副本为生成物,无源可比 + const srcAbs = path.join(ROOT, p.file) + const copyAbs = path.join(DIST, mdOf(p)) + if (!fs.existsSync(srcAbs) || !fs.existsSync(copyAbs)) continue // 存在性已由前面断言 + if (codeTokens(fs.readFileSync(srcAbs, 'utf8')) !== codeTokens(fs.readFileSync(copyAbs, 'utf8'))) { + problems.push(`copy rewrite corrupted a code region: /${mdOf(p)} (differs from ${p.file})`) + } +} + +function checkLinks(raw, baseDir, label) { + for (const { kind, href } of extractLinks(raw)) { + const c = classifyHref(baseDir, href) + if (c.scope === 'external') continue + if (c.scope === 'relative' && c.target === null) { + problems.push(`llms-full.txt must not contain relative links (no base dir): ${href}`) + continue + } + // 按路径段 decodeURIComponent——与编码出口(config.ts encodePath 的 + // encodeURIComponent)互为逆运算;decodeURI 不解码 %26 等保留字符,会把 + // 含 & 等合法文件名误报为 unreachable。 + const target = c.target + .split('/') + .map((seg) => { + try { + return decodeURIComponent(seg) + } catch { + return seg // 非法转义:按原文校验 + } + }) + .join('/') + // 页面路由(无扩展名)对应 .html;带扩展名的按文件本体校验;根 → index.html。 + const cand = target === '' ? 'index.html' : /\.[a-z0-9]+$/i.test(target) ? target : `${target}.html` + // 解码后重新锁定 DIST 边界:编码过的 ../ 在解码后才现形,statSync 在 + // 仓库根找到文件不等于部署后可达——目标必须严格位于 dist 内。 + const abs = path.resolve(DIST, cand) + const relToDist = path.relative(DIST, abs) + let st = null + try { + st = fs.statSync(abs) + } catch { + /* missing */ + } + if (relToDist.startsWith('..') || path.isAbsolute(relToDist) || !st || !st.isFile()) { + problems.push(`unreachable ${kind}: ${label} → ${href}`) + } + } +} + +for (const f of ['llms.txt', 'llms-full.txt', 'feed.xml', 'sitemap.xml']) { + if (!fs.existsSync(path.join(DIST, f))) problems.push(`missing agent-facing output: /${f}`) +} + +if (problems.length) { + for (const p of problems) console.error(`verify-dist FAIL — ${p}`) + process.exit(1) +} +console.log( + `verify-dist: ${pages.length} published pages ↔ ${actual.length - 1} html pages (+404); every page has a self-contained same-path .md copy; no symlinks in dist; llms/feed/sitemap present` +) diff --git a/works/inside-the-scaffold-paper-translation.md b/works/inside-the-scaffold-paper-translation.md index 0e89888..c1773ee 100644 --- a/works/inside-the-scaffold-paper-translation.md +++ b/works/inside-the-scaffold-paper-translation.md @@ -181,7 +181,7 @@ Table 2:控制循环策略。智能体按探索策略的灵活性从低到高 在最简单的一端,Agentless 使用独立采样:在定位到相关代码后,它提示 LLM 独立生成候选补丁(默认配置生成 20 个;原始论文使用 4 个定位样本 × 每个 10 个补丁,共计 ≈$ 40 个)。每次生成看到相同的上下文,但可能产生不同的修复方案。然后通过对这些候选方案进行多数投票来选择最终补丁。不存在树结构,候选方案之间也没有交互;每个补丁都是独立生成的。 -DARS-Agent 在其主执行循环中引入了树结构搜索。与 Agentless 将定位和修复分为不同阶段不同,DARS-Agent 没有阶段分离:搜索命令与编辑和执行命令在整个过程中并行可用。该智能体构建一棵搜索树,其中每个节点代表一个动作(如编辑文件、创建文件或提交补丁)。在每个分支点,智能体生成多个备选动作,然后使用一个 LLM 评论者在其中进行选择:评论者接收这些备选方案作为提示,并以 标签响应以指示其选择。然而,与经典树搜索不同,DARS-Agent 没有数值奖励信号,也没有将结果反向传播到较早节点;评论者做出贪心的局部决策,不考虑较早的选择如何影响了后续结果。 +DARS-Agent 在其主执行循环中引入了树结构搜索。与 Agentless 将定位和修复分为不同阶段不同,DARS-Agent 没有阶段分离:搜索命令与编辑和执行命令在整个过程中并行可用。该智能体构建一棵搜索树,其中每个节点代表一个动作(如编辑文件、创建文件或提交补丁)。在每个分支点,智能体生成多个备选动作,然后使用一个 LLM 评论者在其中进行选择:评论者接收这些备选方案作为提示,并以 `` 标签响应以指示其选择。然而,与经典树搜索不同,DARS-Agent 没有数值奖励信号,也没有将结果反向传播到较早节点;评论者做出贪心的局部决策,不考虑较早的选择如何影响了后续结果。 Moatless Tools 实现了完整的蒙特卡洛树搜索 (MCTS) [^11],这与 AlphaGo [^49] 等博弈系统使用的算法相同。搜索树中的每个节点接收数值奖励值(范围从 -100 到 +100),算法维护访问计数以平衡对未尝试路径的探索与对有前景路径的利用。在扩展节点后,奖励沿树反向传播以更新祖先节点,使搜索能够从后续结果中学习,并将精力重新导向更有前景的分支(search_tree.py:326--345)。 @@ -279,7 +279,7 @@ Aider 以 13 种注册编辑格式成为异类,每种格式作为独立的 cod Agentless 在其 Anthropic 路径中的"模拟工具使用"在架构上是独特的。当使用 Anthropic 的 API 时,LLM 调用 str_replace_editor 工具,但每次调用都收到相同的硬编码响应:"File is successfully edited",无论输入是什么。LLM 在编辑后从不看到实际的文件状态;工具调用被提取并在事后应用。这将工具调用 API 用作结构化输出提取技术,而非用于实际执行。 -其余智能体使用同一方法的变体。AutoCodeRover 使用自定义的类 XML 标签 /,而 Gemini CLI 和 Cline 将全文件写入工具与功能上类似于 str_replace_editor 的搜索替换工具相结合。mini-swe-agent 是唯一通过 shell 命令直接编辑文件而非生成补丁供脚手架应用的智能体。 +其余智能体使用同一方法的变体。AutoCodeRover 使用自定义的类 XML 标签 ``/``,而 Gemini CLI 和 Cline 将全文件写入工具与功能上类似于 str_replace_editor 的搜索替换工具相结合。mini-swe-agent 是唯一通过 shell 命令直接编辑文件而非生成补丁供脚手架应用的智能体。 #### 4.2.3 工具发现策略