Skip to content
Open
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
24 changes: 24 additions & 0 deletions external_plugins/project-rules/.zcode-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "project-rules",
"version": "0.1.0",
"description": "Load CLAUDE.md and path-scoped rule files (.zcode/rules/*.md with paths: globs) into ZCode sessions, so projects coming from Claude Code or Cursor keep their instructions. Hooks only, no network.",
"description_i18n": {
"en": "Load CLAUDE.md and path-scoped rule files (.zcode/rules/*.md with paths: globs) into ZCode sessions, so projects coming from Claude Code or Cursor keep their instructions. Hooks only, no network.",
"zh-CN": "将 CLAUDE.md 与按路径生效的规则文件 (.zcode/rules/*.md, 支持 paths: glob) 注入 ZCode 会话, 让从 Claude Code 或 Cursor 迁移的项目保留原有指令. 仅使用 Hook, 不访问网络."
},
"author": {
"name": "qubn",
"url": "https://github.com/dominiclafleur"
},
"homepage": "https://github.com/dominiclafleur/zcode-project-rules",
"repository": "https://github.com/dominiclafleur/zcode-project-rules",
"license": "MIT",
"keywords": [
"rules",
"claude-md",
"context",
"hooks",
"migration",
"cursor-rules"
]
}
21 changes: 21 additions & 0 deletions external_plugins/project-rules/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 qubn

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
131 changes: 131 additions & 0 deletions external_plugins/project-rules/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# project-rules

[简体中文](./README_CN.md)

Brings `CLAUDE.md` and path-scoped rule files to ZCode. ZCode reads only `AGENTS.md` from the project root, so a project coming from Claude Code or Cursor loses its `CLAUDE.md` and per-path rules. This plugin adds them back as model context through two hooks. It never blocks, edits, or rewrites a tool call.

## What it does

| Hook | Matcher | What gets injected |
| --- | --- | --- |
| `SessionStart` | all sources (startup, resume, clear, compact) | `CLAUDE.md` from the project root, if there is no `AGENTS.md` next to it, plus every rule in `.zcode/rules/*.md` that has no `paths:` |
| `PreToolUse` | `Read\|Edit\|Write` | Rules whose `paths:` globs match the tool's `file_path`, plus any path-less rule not yet delivered in this session (for example, one that changed or did not fit the SessionStart budget) |

Each rule is delivered once per session. It is delivered again only when its file content changes, or after a new SessionStart (resume, clear, compaction), because the model context starts over then.

The project root is the session's ZCode working directory.

## Rule files

Put Markdown files directly in `.zcode/rules/` at the project root. Frontmatter is optional.

A rule without `paths:` always applies. It is injected at session start:

```markdown
Run `pnpm test` before finishing a task.
Never commit generated files under dist/.
```

A rule with `paths:` applies when the agent reads, edits, or writes a matching file. For example, `.zcode/rules/typescript.md`:

```markdown
---
paths: src/**/*.ts, src/**/*.tsx
---
Prefer `type` aliases over `interface`.
Use the logger in src/lib/log.ts instead of console.log.
```

`paths:` accepts any of these forms. Cursor's `globs:` key works as an alias, and trailing `# comments` are ignored.

```yaml
paths: src/**/*.{ts,tsx}, *.proto
paths: ["src/api/**", "docs/api/"]
paths:
- src/db/**/*.sql
- migrations/
globs: *.sql
```

### Glob syntax

| Pattern | Matches |
| --- | --- |
| `*` | any characters except `/` |
| `?` | exactly one character except `/` |
| `**` | any number of directories, including none: `src/**/*.ts` matches `src/a.ts` and `src/x/y/a.ts` |
| `{a,b}` | either alternative: `*.{ts,tsx}` |
| no `/` in the pattern | the file name at any depth: `*.proto` is the same as `**/*.proto` |
| leading `/` or `./` | anchored at the project root: `/README.md` |
| trailing `/` | everything below that directory: `docs/` |

The tool's `file_path` is made relative to the project root (absolute and relative paths both work) and matched with `/` separators, case-sensitively. A `file_path` that is outside the project both as written and after resolving symlinks never matches. Character classes (`[abc]`) and negation (`!`) are not supported. A rule uses its first 32 globs, and a glob longer than 256 characters is ignored.

## Install

From the GitHub repository, which is a standalone marketplace:

```shell
zcode plugins marketplace add https://github.com/dominiclafleur/zcode-project-rules
zcode plugins install project-rules@project-rules-marketplace
zcode plugins enable project-rules@project-rules-marketplace
```

In the desktop app, open **Plugin Marketplace -> New**, paste the same URL, then install and enable `project-rules`.

If the plugin is listed in the official marketplace, install it from **Settings -> Plugins** by searching for `project-rules`.

Start a **new session** after installing or enabling it. ZCode takes a snapshot of hooks when a session starts.

Requirements: `node` 16 or newer on `PATH`. There are no npm packages, and the plugin makes no network requests.

## Migrating from Claude Code or Cursor

- **CLAUDE.md**: works as-is when the project root has no `AGENTS.md`. If both exist, ZCode reads `AGENTS.md` and this plugin skips `CLAUDE.md` so the instructions are not loaded twice. Move anything you still need from `CLAUDE.md` into `AGENTS.md`.
- **Claude Code rules** (`.claude/rules/*.md`): copy them. Their `paths:` frontmatter is read as-is.

```shell
mkdir -p .zcode/rules && cp .claude/rules/*.md .zcode/rules/
```

- **Cursor rules** (`.cursor/rules/*.mdc`): copy them with a `.md` extension. `globs:` is read as `paths:`. Rules without globs become always-on, and Cursor's `alwaysApply` and `description` fields are ignored.
- **Not carried over**: `@path` imports inside `CLAUDE.md` (they stay as plain text), `CLAUDE.local.md`, `~/.claude/CLAUDE.md`, `.claude/CLAUDE.md`, `CLAUDE.md` files in subdirectories, and subdirectories of `.zcode/rules/`.

## Limits

- **Size**: each rule is cut at 4000 characters and `CLAUDE.md` at 8000. A truncated item ends with a note that tells the model to Read the file for the rest. One injection is capped at 12000 characters and at 30000 bytes of hook output, which keeps it under ZCode's 32 KiB hook stdout limit. Rules that do not fit are deferred to a later Read/Edit/Write call they apply to, and a note names up to five of them.
- **Timing**: PreToolUse context reaches the model together with the tool result. In the usual Read-then-Edit flow, the model sees a file's rules before it edits that file. A `Write` that creates a new file runs before the model sees the rule, so the rule applies to follow-up edits.
- **Triggers**: only `Read`, `Edit`, and `Write` calls that carry a `file_path`. Bash, Grep, Glob, and patch-style edits do not trigger path rules. A call whose hook input exceeds 1 MiB (for example a `Write` of a very large file) is skipped.
- **Discovery**: only `*.md` files directly inside `.zcode/rules/`, up to 200 files of at most 1 MiB each and 2 MiB in total; a file that would pass the total is skipped. Rule files are re-read on every Read/Edit/Write call, so edits apply without restarting.
- **Root**: `CLAUDE.md` and `.zcode/rules/` are looked up in the ZCode working directory only, not in parent directories.
- **Cost**: each Read/Edit/Write call starts a short-lived Node process, which usually takes tens of milliseconds.

## Data and side effects

- **Reads**: `CLAUDE.md`, whether `AGENTS.md` exists, and `.zcode/rules/*.md` in the project. Symlinked `CLAUDE.md` or rule files that resolve outside the project are skipped.
- **Writes**: `${ZCODE_PLUGIN_DATA}/state.json`, or `~/.zcode/project-rules/state.json` when `ZCODE_PLUGIN_DATA` is not set, and a short-lived `state.lock`. The state file holds session ids, rule file paths, and content hashes, and only your user can read it. A session is pruned 7 days after its last state write, which happens at SessionStart and whenever a rule is injected. Nothing is written to the project or to the plugin directory.
- **No** network access, MCP server, commands, or skills. Besides the `node` process itself, nothing is executed.
- The hook never returns deny, ask, or rewritten input, and it always exits 0, so tool calls always proceed. On an unexpected error it prints nothing. If the state file cannot be written, SessionStart context is still injected, but PreToolUse injects nothing.

## Troubleshooting

- **Nothing is injected**: start a new session after enabling the plugin. Check that **Settings -> Hooks** lists the two `project-rules` hooks, that rule files end in `.md`, and that each has a non-empty body below the frontmatter.
- **Diagnostics**: start ZCode with `PROJECT_RULES_DEBUG=1` in its environment, and the hook writes diagnostics to stderr.
- **Manual run** from your project directory, with `<plugin-dir>` set to the installed plugin path:

```shell
printf '%s\n' '{"hookEventName":"PreToolUse","sessionId":"manual","cwd":"'"$PWD"'","toolName":"Read","toolInput":{"file_path":"src/index.ts"}}' \
| ZCODE_PLUGIN_DATA=/tmp/project-rules-manual node <plugin-dir>/hooks/rules.js
```

## Development

```shell
node test/test.mjs
```

The self-test needs no ZCode install and no marketplace. It spawns `hooks/rules.js` with SessionStart and PreToolUse payloads and checks the output for these cases: matching and non-matching paths, dedupe, re-injection after a content change, the size and read budgets, hostile globs, symlinks that leave the project, parallel calls, stale and stuck locks, corrupt or oddly shaped state, stale-session pruning, oversized stdin, and an unwritable or unset data directory.

## License

MIT. See [LICENSE](./LICENSE).
131 changes: 131 additions & 0 deletions external_plugins/project-rules/README_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# project-rules

[English](./README.md)

为 ZCode 补上 `CLAUDE.md` 与按路径生效的规则文件. ZCode 只从项目根目录读取 `AGENTS.md`, 从 Claude Code 或 Cursor 迁移过来的项目会丢失 `CLAUDE.md` 和按路径划分的规则. 本插件通过两个 Hook 把它们重新注入为模型上下文, 不会阻止, 修改或改写任何工具调用.

## 功能

| Hook | Matcher | 注入内容 |
| --- | --- | --- |
| `SessionStart` | 所有来源 (startup, resume, clear, compact) | 项目根目录的 `CLAUDE.md` (仅当同级没有 `AGENTS.md` 时), 以及 `.zcode/rules/*.md` 中所有没有 `paths:` 的规则 |
| `PreToolUse` | `Read\|Edit\|Write` | `paths:` glob 与工具 `file_path` 匹配的规则, 以及本会话中尚未送达的无路径规则 (例如内容有变化, 或超出 SessionStart 预算而被推迟的规则) |

每条规则在每个会话中只送达一次. 只有当规则文件内容发生变化, 或者发生新的 SessionStart (resume, clear, 上下文压缩) 时才会再次送达, 因为此时模型上下文已重新开始.

项目根目录即该会话的 ZCode 工作目录.

## 规则文件

把 Markdown 文件直接放在项目根目录的 `.zcode/rules/` 下. frontmatter 可选.

没有 `paths:` 的规则始终生效, 在会话开始时注入:

```markdown
完成任务前运行 `pnpm test`.
不要提交 dist/ 下的生成文件.
```

带 `paths:` 的规则在 Agent 读取, 编辑或写入匹配文件时生效. 例如 `.zcode/rules/typescript.md`:

```markdown
---
paths: src/**/*.ts, src/**/*.tsx
---
优先使用 `type` 别名而不是 `interface`.
使用 src/lib/log.ts 中的 logger, 不要用 console.log.
```

`paths:` 支持以下任意写法. Cursor 的 `globs:` 键可作为别名, 行尾的 `# 注释` 会被忽略.

```yaml
paths: src/**/*.{ts,tsx}, *.proto
paths: ["src/api/**", "docs/api/"]
paths:
- src/db/**/*.sql
- migrations/
globs: *.sql
```

### Glob 语法

| 模式 | 匹配 |
| --- | --- |
| `*` | 除 `/` 以外的任意字符 |
| `?` | 除 `/` 以外的单个字符 |
| `**` | 任意层目录, 包括零层: `src/**/*.ts` 同时匹配 `src/a.ts` 和 `src/x/y/a.ts` |
| `{a,b}` | 任选其一: `*.{ts,tsx}` |
| 模式中没有 `/` | 任意深度的文件名: `*.proto` 等同于 `**/*.proto` |
| 以 `/` 或 `./` 开头 | 锚定到项目根目录: `/README.md` |
| 以 `/` 结尾 | 该目录下的所有文件: `docs/` |

工具的 `file_path` 会先转换为相对项目根目录的路径 (绝对路径和相对路径都可以), 再以 `/` 作为分隔符匹配, 区分大小写. 无论按原样还是解析符号链接后都位于项目之外的 `file_path` 永远不会匹配. 不支持字符类 (`[abc]`) 和取反 (`!`). 每条规则只使用前 32 个 glob, 超过 256 个字符的 glob 会被忽略.

## 安装

GitHub 仓库本身就是一个独立的插件市场:

```shell
zcode plugins marketplace add https://github.com/dominiclafleur/zcode-project-rules
zcode plugins install project-rules@project-rules-marketplace
zcode plugins enable project-rules@project-rules-marketplace
```

在桌面端, 打开 **插件市场 -> 新建**, 粘贴同一个 URL, 然后安装并启用 `project-rules`.

如果插件已收录到官方插件市场, 可在 **设置 -> 插件** 中搜索 `project-rules` 安装.

安装或启用后请 **新建会话**. ZCode 在会话开始时为 Hook 配置建立快照.

环境要求: `PATH` 中有 16 或更高版本的 `node`. 插件没有 npm 依赖, 也不发起任何网络请求.

## 从 Claude Code 或 Cursor 迁移

- **CLAUDE.md**: 项目根目录没有 `AGENTS.md` 时可直接使用. 如果两者同时存在, ZCode 读取 `AGENTS.md`, 本插件跳过 `CLAUDE.md`, 以免同一份指令被加载两次. 请把 `CLAUDE.md` 中仍需要的内容合并到 `AGENTS.md`.
- **Claude Code 规则** (`.claude/rules/*.md`): 直接复制即可, 其中的 `paths:` frontmatter 可原样读取.

```shell
mkdir -p .zcode/rules && cp .claude/rules/*.md .zcode/rules/
```

- **Cursor 规则** (`.cursor/rules/*.mdc`): 复制时把扩展名改为 `.md`. `globs:` 会被当作 `paths:` 读取. 没有 globs 的规则会变成始终生效, Cursor 的 `alwaysApply` 和 `description` 字段会被忽略.
- **不会迁移**: `CLAUDE.md` 中的 `@path` 导入 (保留为普通文本), `CLAUDE.local.md`, `~/.claude/CLAUDE.md`, `.claude/CLAUDE.md`, 子目录中的 `CLAUDE.md`, 以及 `.zcode/rules/` 的子目录.

## 限制

- **大小**: 每条规则截断为前 4000 个字符, `CLAUDE.md` 截断为前 8000 个字符. 被截断的内容末尾附有提示, 告诉模型 Read 原文件获取剩余内容. 单次注入上限为 12000 个字符, Hook 输出上限为 30000 字节, 低于 ZCode 32 KiB 的 Hook stdout 限制. 放不下的规则会推迟到之后适用于它们的 Read/Edit/Write 结果中送达, 并附有列出其中最多 5 条规则的提示.
- **时机**: PreToolUse 的上下文与工具结果一起送达模型. 在常见的先 Read 后 Edit 流程中, 模型会在编辑文件之前看到该文件的规则. 但新建文件的 `Write` 会在模型看到规则之前执行, 规则从后续编辑开始生效.
- **触发**: 只有带 `file_path` 的 `Read`, `Edit`, `Write` 调用会触发路径规则. Bash, Grep, Glob 和补丁式编辑不会触发. Hook 输入超过 1 MiB 的调用 (例如写入超大文件的 `Write`) 会被跳过.
- **发现**: 只读取 `.zcode/rules/` 目录下直接存放的 `*.md` 文件, 最多 200 个, 每个最大 1 MiB, 合计最多 2 MiB, 超出合计上限的文件会被跳过. 每次 Read/Edit/Write 调用都会重新读取规则文件, 因此修改无需重启即可生效.
- **根目录**: 只在 ZCode 工作目录中查找 `CLAUDE.md` 和 `.zcode/rules/`, 不会向上查找父目录.
- **开销**: 每次 Read/Edit/Write 调用都会启动一个短生命周期的 Node 进程, 通常耗时几十毫秒.

## 数据与副作用

- **读取**: 项目中的 `CLAUDE.md`, `AGENTS.md` 是否存在, 以及 `.zcode/rules/*.md`. 解析后指向项目之外的 `CLAUDE.md` 或规则文件符号链接会被跳过.
- **写入**: `${ZCODE_PLUGIN_DATA}/state.json` (未设置 `ZCODE_PLUGIN_DATA` 时为 `~/.zcode/project-rules/state.json`) 以及短暂存在的 `state.lock`. 状态文件保存会话 ID, 规则文件路径和内容哈希, 只有当前用户可以读取. 会话在最后一次写入状态 7 天后被清理, 写入发生在 SessionStart 时以及每次注入规则时. 不会向项目目录或插件目录写入任何内容.
- **没有** 网络访问, MCP 服务, 命令或技能. 除 `node` 进程本身外不执行任何程序.
- Hook 从不返回 deny, ask 或改写后的输入, 并且始终以 0 退出, 工具调用始终照常进行. 出现意外错误时不输出内容. 如果无法写入状态文件, SessionStart 的上下文仍会注入, 但 PreToolUse 不会注入任何内容.

## 故障排查

- **没有注入内容**: 启用插件后请新建会话. 确认 **设置 -> 钩子** 中列出了两个 `project-rules` Hook, 规则文件以 `.md` 结尾, 并且 frontmatter 之后的正文不为空.
- **诊断信息**: 在 ZCode 的启动环境中设置 `PROJECT_RULES_DEBUG=1`, Hook 会把诊断信息写到 stderr.
- **手动运行**: 在项目目录中执行以下命令, `<plugin-dir>` 为插件安装路径:

```shell
printf '%s\n' '{"hookEventName":"PreToolUse","sessionId":"manual","cwd":"'"$PWD"'","toolName":"Read","toolInput":{"file_path":"src/index.ts"}}' \
| ZCODE_PLUGIN_DATA=/tmp/project-rules-manual node <plugin-dir>/hooks/rules.js
```

## 开发

```shell
node test/test.mjs
```

自测不需要安装 ZCode, 也不需要插件市场. 它用 SessionStart 和 PreToolUse 负载启动 `hooks/rules.js`, 并检查以下场景的输出: 匹配与不匹配的路径, 去重, 内容变化后重新注入, 大小与读取预算, 恶意构造的 glob, 指向项目之外的符号链接, 并发调用, 过期或卡住的锁, 损坏或结构异常的状态文件, 过期会话清理, 超大的 stdin, 以及不可写或未设置的数据目录.

## 许可证

MIT. 见 [LICENSE](./LICENSE).
30 changes: 30 additions & 0 deletions external_plugins/project-rules/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"description": "project-rules: inject CLAUDE.md and .zcode/rules/*.md as model context. Never blocks or rewrites tool calls.",
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/rules.js\"",
"timeoutMs": 10000,
"statusMessage": "project-rules: loading CLAUDE.md and rules"
}
]
}
],
"PreToolUse": [
{
"matcher": "Read|Edit|Write",
"hooks": [
{
"type": "command",
"command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/rules.js\"",
"timeoutMs": 10000,
"statusMessage": "project-rules: matching path rules"
}
]
}
]
}
}
Loading