From 955e414b381a6d099cfbc91f6f19c733e4fe2a11 Mon Sep 17 00:00:00 2001 From: shijiashuai Date: Tue, 26 May 2026 09:25:20 +0800 Subject: [PATCH] fix: repair docs and validation contracts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 2 +- AGENTS.md | 4 +-- CONTRIBUTING.md | 4 +-- README.md | 10 +++--- docs/en/architecture/index.md | 4 +-- docs/openspec/ai-tooling.md | 53 ++++++++++++++++++++++++++++++ docs/openspec/architecture.md | 4 +-- docs/public/assets/catalog.js | 2 +- docs/zh/architecture/index.md | 4 +-- package.json | 2 +- scripts/build-rule-catalog.mjs | 4 +-- scripts/site-shell.test.mjs | 58 +++++++++++++++++++++++++++++++++ scripts/validate-rules.mjs | 5 ++- scripts/validate-rules.test.mjs | 31 ++++++++++++++++++ 14 files changed, 164 insertions(+), 23 deletions(-) create mode 100644 docs/openspec/ai-tooling.md create mode 100644 scripts/validate-rules.test.mjs diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4644474..0a72e47 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -5,7 +5,7 @@ For comprehensive project-level instructions, see [AGENTS.md](../../AGENTS.md). ## Key Contracts 1. Root-level `.mdc` files are the product, must stay at root. -2. `docs/assets/rules.json` is generated; regenerate, don't hand-edit. +2. `docs/public/assets/rules.json` is generated; regenerate, don't hand-edit. 3. After changes, run: `npm test && npm run build:catalog` ## Language diff --git a/AGENTS.md b/AGENTS.md index ac5fc19..0f9657e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ You are acting as a maintainer and curator of this rule library. ## Non-negotiable Contracts 1. Do **not** move root-level `.mdc` files into subdirectories. -2. Do **not** hand-edit `docs/assets/rules.json`; regenerate it. +2. Do **not** hand-edit `docs/public/assets/rules.json`; regenerate it. 3. Do **not** add frameworks, package managers, or CI jobs unless they solve a repository-specific problem. 4. Keep README public-facing and keep project-control material in `docs/openspec/`. @@ -38,7 +38,7 @@ When taking over this repository, suggested reading order: 1. Read `AGENTS.md` (this file) first. 2. Then read `docs/openspec/architecture.md` and `workflow.md`. -3. Treat `docs/.vitepress/public/assets/rules.json` as generated output, not a hand-written data source. +3. Treat `docs/public/assets/rules.json` as generated output, not a hand-written data source. 4. After modifying `.mdc`, Pages, or AI config, re-run `npm test` and `npm run build:catalog`. ### Preferred Skills diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d59bc7..f712cdf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,7 +66,7 @@ npm run build:catalog **预期结果 / Expected Output**: - `Checked 26+ files: 0 errors, 0 warnings` -- `Wrote docs/assets/rules.json (N rules)` +- `Wrote docs/public/assets/rules.json (N rules)` --- @@ -89,7 +89,7 @@ npm run build:catalog 请务必遵守以下核心约束: 1. **不要移动根目录 `.mdc` 文件** — 文件路径是对外契约 -2. **不要手改 `docs/assets/rules.json`** — 它由脚本自动生成 +2. **不要手改 `docs/public/assets/rules.json`** — 它由脚本自动生成 3. **保持规则简洁高信噪比** — 避免 generic filler --- diff --git a/README.md b/README.md index 8495128..dc49ec0 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ This repository is intentionally small in scope: ```bash mkdir -p .cursor/rules -cp path/to/cursor-rules/clean-code.mdc .cursor/rules/ -cp path/to/cursor-rules/python.mdc .cursor/rules/ +curl -fsSL https://raw.githubusercontent.com/LessUp/cursor-rules/master/clean-code.mdc -o .cursor/rules/clean-code.mdc +curl -fsSL https://raw.githubusercontent.com/LessUp/cursor-rules/master/python.mdc -o .cursor/rules/python.mdc ``` 然后在你的项目里让 Cursor 读取这些规则文件即可。 @@ -59,14 +59,14 @@ npm run build:catalog ├── *.mdc # Public rule files ├── scripts/ # Validation and catalog generation ├── docs/index.html # GitHub Pages shell -├── docs/assets/rules.json # Generated rule catalog +├── docs/public/assets/rules.json └── docs/openspec/ # Project control documents ``` 请注意两条核心约束: 1. **不要移动根目录 `.mdc` 文件**。这是对外路径契约。 -2. **不要手改 `docs/assets/rules.json`**。它是由脚本生成的目录产物。 +2. **不要手改 `docs/public/assets/rules.json`**。它是由脚本生成的目录产物。 ## 项目文档 / Project docs @@ -90,7 +90,7 @@ npm run build:catalog - 校验所有 `.mdc` 文件结构 - 运行 Node 测试,确保规则目录生成与 Pages shell 约束未回归 -- 重新生成 GitHub Pages 使用的 `docs/assets/rules.json` +- 重新生成 GitHub Pages 使用的 `docs/public/assets/rules.json` ## 适用场景 / Intended use diff --git a/docs/en/architecture/index.md b/docs/en/architecture/index.md index 51b0551..d9a2722 100644 --- a/docs/en/architecture/index.md +++ b/docs/en/architecture/index.md @@ -29,7 +29,7 @@ Currently **26 rules** covering **6 categories**: The documentation site is a **projection** of the rule files, not an independently maintained content copy: - `scripts/validate-rules.mjs` validates `.mdc` structure -- `scripts/lib/rule-catalog.mjs` normalizes rule metadata into unified catalog items +- `scripts/lib/rule-processor.mjs` parses, validates, and normalizes rules into catalog entries - `scripts/build-rule-catalog.mjs` generates `rules.json`, `categories.json`, and localized rule pages ### 3. Presentation Layer Separation @@ -51,7 +51,7 @@ flowchart TB end subgraph Processing["Processing Layer"] - catalog["rule-catalog.mjs
normalize catalog items"] + catalog["rule-processor.mjs
process and normalize rules"] build["build-rule-catalog.mjs
generate artifacts"] end diff --git a/docs/openspec/ai-tooling.md b/docs/openspec/ai-tooling.md new file mode 100644 index 0000000..d1e947d --- /dev/null +++ b/docs/openspec/ai-tooling.md @@ -0,0 +1,53 @@ +--- +title: AI Tooling +description: Repository-level guidance for AI tools working in cursor-rules +--- + +# AI Tooling + +## Purpose + +This document records the repository contract that AI tools should follow when working in `cursor-rules`. + +## Shared instruction surfaces + +The project keeps its cross-tool instructions in two tracked files: + +1. `AGENTS.md` — repository-wide constraints and workflow. +2. `.github/copilot-instructions.md` — GitHub Copilot entry point that mirrors the same facts. + +These files should stay aligned instead of drifting into tool-specific policy forks. + +## Repository contract + +AI tools working in this repository should treat the following as non-negotiable: + +1. Root-level `.mdc` files are the product and must stay at the repository root. +2. `docs/public/assets/rules.json` is generated output and must be regenerated, not hand-edited. +3. Project-control material belongs in `docs/openspec/`, while README stays public-facing. +4. Rule, Pages, and automation changes must remain small, surgical, and easy to verify. + +## Preferred approach + +- Prefer repository scripts and lightweight CLI workflows. +- Prefer static control documents over sprawling tool-specific instructions. +- Avoid adding frameworks, package managers, or workflows that do not solve a repository-specific problem. +- Avoid duplicating generated data or re-describing the same contract in multiple places. + +## Handoff order + +When an AI tool takes over this repository, the preferred reading order is: + +1. `AGENTS.md` +2. `docs/openspec/architecture.md` +3. `docs/openspec/workflow.md` +4. This document + +## Verification + +After changing rules, Pages surfaces, or automation, run: + +```bash +npm test +npm run build:catalog +``` diff --git a/docs/openspec/architecture.md b/docs/openspec/architecture.md index 4e50346..1ceff7e 100644 --- a/docs/openspec/architecture.md +++ b/docs/openspec/architecture.md @@ -17,7 +17,7 @@ ### 2. Validation and catalog pipeline - `scripts/validate-rules.mjs` 校验 `.mdc` 结构 -- `scripts/lib/rule-catalog.mjs` 把规则元信息规范化为统一目录项 +- `scripts/lib/rule-processor.mjs` 把规则解析、校验并规范化为统一目录项 - `scripts/build-rule-catalog.mjs` 生成 `docs/public/assets/rules.json`、`docs/public/assets/categories.json`,以及 locale-aware 规则 Markdown 页面 ### 3. Static Pages surface @@ -38,7 +38,7 @@ flowchart TB end subgraph Processing["处理层"] - catalog["rule-catalog.mjs
规范化目录项"] + catalog["rule-processor.mjs
规则处理与目录项归一化"] build["build-rule-catalog.mjs
生成产物"] end diff --git a/docs/public/assets/catalog.js b/docs/public/assets/catalog.js index 7d99c87..ad871b7 100644 --- a/docs/public/assets/catalog.js +++ b/docs/public/assets/catalog.js @@ -409,7 +409,7 @@ async function copyInstall(fileName) { try { - const cmd = `mkdir -p .cursor/rules\ncp path/to/cursor-rules/${fileName} .cursor/rules/`; + const cmd = `mkdir -p .cursor/rules\ncurl -fsSL ${RAW_URL}/${fileName} -o .cursor/rules/${fileName}`; await copyText(cmd); showCopyStatus(`${TEXTS.copiedInstall}: ${fileName}`); } catch (e) { diff --git a/docs/zh/architecture/index.md b/docs/zh/architecture/index.md index dc4ca4b..e750b62 100644 --- a/docs/zh/architecture/index.md +++ b/docs/zh/architecture/index.md @@ -29,7 +29,7 @@ description: Cursor Rules 的核心架构设计、数据流与设计约束 文档站点是规则文件的**投影**,而非独立维护的内容副本: - `scripts/validate-rules.mjs` 校验 `.mdc` 结构 -- `scripts/lib/rule-catalog.mjs` 把规则元信息规范化为统一目录项 +- `scripts/lib/rule-processor.mjs` 负责规则解析、校验与目录项归一化 - `scripts/build-rule-catalog.mjs` 生成 `rules.json`、`categories.json` 以及本地化规则页面 ### 3. 展示层职责分离 @@ -51,7 +51,7 @@ flowchart TB end subgraph Processing["处理层"] - catalog["rule-catalog.mjs
规范化目录项"] + catalog["rule-processor.mjs
规则处理与目录项归一化"] build["build-rule-catalog.mjs
生成产物"] end diff --git a/package.json b/package.json index 5289209..663b74f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Archive-grade Cursor .mdc rule library with generated catalog, validation, and GitHub Pages gallery", "scripts": { "build:catalog": "node scripts/build-rule-catalog.mjs", - "test": "node scripts/validate-rules.mjs && node --test scripts/lib/category-resolver.test.mjs scripts/lib/frontmatter.test.mjs scripts/lib/rule-processor.test.mjs scripts/site-shell.test.mjs", + "test": "node scripts/validate-rules.mjs && node --test scripts/lib/category-resolver.test.mjs scripts/lib/frontmatter.test.mjs scripts/lib/rule-processor.test.mjs scripts/site-shell.test.mjs scripts/validate-rules.test.mjs", "validate": "npm test" }, "keywords": [ diff --git a/scripts/build-rule-catalog.mjs b/scripts/build-rule-catalog.mjs index 09fb4c0..fb3447b 100644 --- a/scripts/build-rule-catalog.mjs +++ b/scripts/build-rule-catalog.mjs @@ -33,13 +33,13 @@ const staticPages = [ { path: 'en/', changefreq: 'weekly', priority: '0.9' }, { path: 'zh/guides/reading-map.html', changefreq: 'monthly', priority: '0.9' }, { path: 'zh/academy/rule-philosophy.html', changefreq: 'monthly', priority: '0.8' }, - { path: 'zh/architecture/system-overview.html', changefreq: 'monthly', priority: '0.8' }, + { path: 'zh/architecture/', changefreq: 'monthly', priority: '0.8' }, { path: 'zh/research/related-work.html', changefreq: 'monthly', priority: '0.7' }, { path: 'zh/research/references.html', changefreq: 'monthly', priority: '0.6' }, { path: 'zh/research/evolution.html', changefreq: 'monthly', priority: '0.6' }, { path: 'en/guides/reading-map.html', changefreq: 'monthly', priority: '0.7' }, { path: 'en/academy/rule-philosophy.html', changefreq: 'monthly', priority: '0.6' }, - { path: 'en/architecture/system-overview.html', changefreq: 'monthly', priority: '0.7' }, + { path: 'en/architecture/', changefreq: 'monthly', priority: '0.7' }, { path: 'en/research/related-work.html', changefreq: 'monthly', priority: '0.6' }, { path: 'en/research/references.html', changefreq: 'monthly', priority: '0.5' }, { path: 'en/research/evolution.html', changefreq: 'monthly', priority: '0.5' }, diff --git a/scripts/site-shell.test.mjs b/scripts/site-shell.test.mjs index d7062b9..22ca675 100644 --- a/scripts/site-shell.test.mjs +++ b/scripts/site-shell.test.mjs @@ -18,6 +18,26 @@ const rootIndexMd = fs.readFileSync( 'utf8', ); +const readmeMd = fs.readFileSync( + new URL('../README.md', import.meta.url), + 'utf8', +); + +const contributingMd = fs.readFileSync( + new URL('../CONTRIBUTING.md', import.meta.url), + 'utf8', +); + +const agentsMd = fs.readFileSync( + new URL('../AGENTS.md', import.meta.url), + 'utf8', +); + +const copilotInstructionsMd = fs.readFileSync( + new URL('../.github/copilot-instructions.md', import.meta.url), + 'utf8', +); + const zhIndexMd = fs.readFileSync( new URL('../docs/zh/index.md', import.meta.url), 'utf8', @@ -57,6 +77,18 @@ const siteContentEnTs = fs.readFileSync(siteContentEnUrl, 'utf8'); const siteContentEn = await import( `data:text/javascript,${encodeURIComponent(siteContentEnTs)}`, ); +const openspecArchitectureMd = fs.readFileSync( + new URL('../docs/openspec/architecture.md', import.meta.url), + 'utf8', +); +const zhArchitectureMd = fs.readFileSync( + new URL('../docs/zh/architecture/index.md', import.meta.url), + 'utf8', +); +const enArchitectureMd = fs.readFileSync( + new URL('../docs/en/architecture/index.md', import.meta.url), + 'utf8', +); test('VitePress config is locale-first with zh and en trees', () => { assert.match(configTs, /locales:\s*\{/); @@ -271,10 +303,22 @@ test('resource group CTAs do not self-link back to the resources index', () => { ); }); +test('project control docs point at current generated assets and published OpenSpec docs', () => { + assert.equal(fs.existsSync(new URL('../docs/openspec/ai-tooling.md', import.meta.url)), true); + + for (const doc of [readmeMd, contributingMd, agentsMd, copilotInstructionsMd]) { + assert.match(doc, /docs\/public\/assets\/rules\.json/); + assert.doesNotMatch(doc, /docs\/assets\/rules\.json/); + } +}); + test('build script sitemap includes locale surfaces and key OpenSpec docs', () => { assert.match(buildScript, /path:\s*'zh\/'/); assert.match(buildScript, /path:\s*'en\/'/); assert.match(buildScript, /path:\s*'zh\/guides\/reading-map\.html'/); + assert.match(buildScript, /path:\s*'zh\/architecture\/'/); + assert.match(buildScript, /path:\s*'en\/architecture\/'/); + assert.doesNotMatch(buildScript, /system-overview\.html/); assert.match(buildScript, /routePrefix\}\/\$\{rule\.slug\}\.html/); assert.match(buildScript, /path:\s*'openspec\/architecture\.html'/); assert.match(buildScript, /path:\s*'openspec\/ai-tooling\.html'/); @@ -294,6 +338,13 @@ test('resources page raw HTML OpenSpec links stay static-export safe', () => { assert.ok(siteContent.resourcesSection.links.every(({ href }) => href.endsWith('.html'))); }); +test('architecture docs reference real generator modules', () => { + for (const doc of [openspecArchitectureMd, zhArchitectureMd, enArchitectureMd]) { + assert.match(doc, /rule-processor\.mjs/); + assert.doesNotMatch(doc, /scripts\/lib\/rule-catalog\.mjs/); + } +}); + test('catalog runtime asset contract stays in sync with homepage shell', () => { assert.match(catalogJs, /document\.getElementById\('search-input'\)/); assert.match(catalogJs, /document\.getElementById\('rule-cards'\)/); @@ -304,6 +355,13 @@ test('catalog runtime asset contract stays in sync with homepage shell', () => { assert.match(catalogJs, /Source view/); }); +test('catalog runtime copy install action emits a runnable download command', () => { + assert.match(catalogJs, /curl -fsSL/); + assert.match(catalogJs, /raw\.githubusercontent\.com/); + assert.match(catalogJs, /\.cursor\/rules\/\$\{fileName\}/); + assert.doesNotMatch(catalogJs, /path\/to\/cursor-rules/); +}); + test('catalog runtime stays inert when the catalog shell is absent', async () => { const fetchCalls = []; const documentEvents = []; diff --git a/scripts/validate-rules.mjs b/scripts/validate-rules.mjs index f95946f..20dbf60 100755 --- a/scripts/validate-rules.mjs +++ b/scripts/validate-rules.mjs @@ -126,6 +126,7 @@ function resolveTargets(args, rootDir) { async function main() { try { const args = process.argv.slice(2); + const shouldDetectOverlap = args.length === 0; const targets = resolveTargets(args, ROOT_DIR); // 处理所有目标文件 @@ -139,9 +140,7 @@ async function main() { `Checked ${targets.length} file${targets.length === 1 ? '' : 's'}: ${errors} error${errors === 1 ? '' : 's'}, ${warnings} warning${warnings === 1 ? '' : 's'}`, ); - // 如果是完整校验(无指定文件),执行重叠检测 - const defaultTargets = resolveTargets([], ROOT_DIR); - if (targets.length === defaultTargets.length) { + if (shouldDetectOverlap) { const overlapFindings = detectGlobOverlap(rules); printOverlapFindings(overlapFindings); } diff --git a/scripts/validate-rules.test.mjs b/scripts/validate-rules.test.mjs new file mode 100644 index 0000000..db44976 --- /dev/null +++ b/scripts/validate-rules.test.mjs @@ -0,0 +1,31 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import process from 'node:process'; +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.resolve(__dirname, '..'); +const validateScriptPath = path.join(rootDir, 'scripts/validate-rules.mjs'); + +test('validate-rules skips overlap reporting when every file is passed explicitly', () => { + const explicitTargets = fs + .readdirSync(rootDir, { withFileTypes: true }) + .filter((entry) => entry.isFile() && entry.name.endsWith('.mdc')) + .map((entry) => entry.name) + .sort((left, right) => left.localeCompare(right)); + + const result = spawnSync( + process.execPath, + [validateScriptPath, ...explicitTargets], + { + cwd: rootDir, + encoding: 'utf8', + }, + ); + + assert.equal(result.status, 0, result.stderr); + assert.doesNotMatch(result.stdout, /Glob Overlap Detection/); +});