Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/`.

Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)`

---

Expand All @@ -89,7 +89,7 @@ npm run build:catalog
请务必遵守以下核心约束:

1. **不要移动根目录 `.mdc` 文件** — 文件路径是对外契约
2. **不要手改 `docs/assets/rules.json`** — 它由脚本自动生成
2. **不要手改 `docs/public/assets/rules.json`** — 它由脚本自动生成
3. **保持规则简洁高信噪比** — 避免 generic filler

---
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 读取这些规则文件即可。
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down
4 changes: 2 additions & 2 deletions docs/en/architecture/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -51,7 +51,7 @@ flowchart TB
end

subgraph Processing["Processing Layer"]
catalog["rule-catalog.mjs<br/>normalize catalog items"]
catalog["rule-processor.mjs<br/>process and normalize rules"]
build["build-rule-catalog.mjs<br/>generate artifacts"]
end

Expand Down
53 changes: 53 additions & 0 deletions docs/openspec/ai-tooling.md
Original file line number Diff line number Diff line change
@@ -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
```
4 changes: 2 additions & 2 deletions docs/openspec/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,7 +38,7 @@ flowchart TB
end

subgraph Processing["处理层"]
catalog["rule-catalog.mjs<br/>规范化目录项"]
catalog["rule-processor.mjs<br/>规则处理与目录项归一化"]
build["build-rule-catalog.mjs<br/>生成产物"]
end

Expand Down
2 changes: 1 addition & 1 deletion docs/public/assets/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions docs/zh/architecture/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. 展示层职责分离
Expand All @@ -51,7 +51,7 @@ flowchart TB
end

subgraph Processing["处理层"]
catalog["rule-catalog.mjs<br/>规范化目录项"]
catalog["rule-processor.mjs<br/>规则处理与目录项归一化"]
build["build-rule-catalog.mjs<br/>生成产物"]
end

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
4 changes: 2 additions & 2 deletions scripts/build-rule-catalog.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
58 changes: 58 additions & 0 deletions scripts/site-shell.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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*\{/);
Expand Down Expand Up @@ -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'/);
Expand All @@ -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'\)/);
Expand All @@ -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 = [];
Expand Down
5 changes: 2 additions & 3 deletions scripts/validate-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

// 处理所有目标文件
Expand All @@ -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);
}
Expand Down
31 changes: 31 additions & 0 deletions scripts/validate-rules.test.mjs
Original file line number Diff line number Diff line change
@@ -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/);
});
Loading