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/);
+});