@shirayner/ace 0.1.7 → 0.2.0-SNAPSHOT
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -19
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/src/commands/doctor.js +12 -5
- package/src/commands/init.js +8 -3
- package/src/commands/uninstall.js +45 -12
- package/src/core/constants.js +8 -6
- package/src/core/installer.js +57 -4
- package/src/core/merger.js +15 -3
- package/templates/CLAUDE.md +24 -14
- package/templates/{rules/ace → ace/rules}/thinking.md +5 -0
- /package/templates/{rules/ace → ace/rules}/clean-code.md +0 -0
- /package/templates/{rules/ace → ace/rules}/code-quality.md +0 -0
- /package/templates/{rules/ace → ace/rules}/context-hygiene.md +0 -0
- /package/templates/{rules/ace → ace/rules}/interactive-clarify.md +0 -0
- /package/templates/{rules/ace → ace/rules}/memory-policy.md +0 -0
- /package/templates/{rules/ace → ace/rules}/reporting.md +0 -0
- /package/templates/{rules/ace → ace/rules}/task-recovery.md +0 -0
package/README.md
CHANGED
|
@@ -31,17 +31,27 @@ ACE 是一个**AI 开发环境配置工具**,基于 Claude Code 官方最佳
|
|
|
31
31
|
- 📝 **规范驱动工作流** — OpenSpec 集成的需求管理体系
|
|
32
32
|
- 🧩 **跨会话记忆系统** — 持久化的开发者画像与项目记忆
|
|
33
33
|
|
|
34
|
+
### 视频教程
|
|
35
|
+
|
|
36
|
+
从下载安装到实际使用的完整演示:
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
动图速度过快,另有示例视频:[ACE 使用示例](assets/ace-demo.mp4)
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## ✨ 一分钟速览
|
|
45
|
+
|
|
46
|
+
### 安装
|
|
47
|
+
|
|
34
48
|
```bash
|
|
35
49
|
# 一键安装,即刻拥有专业级 AI 开发环境
|
|
36
50
|
npm install -g @shirayner/ace
|
|
37
|
-
ace init
|
|
38
51
|
```
|
|
39
52
|
|
|
40
|
-
---
|
|
41
53
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
### 初始化向导
|
|
54
|
+
### 初始化
|
|
45
55
|
|
|
46
56
|
```bash
|
|
47
57
|
$ ace init
|
|
@@ -74,38 +84,64 @@ $ ace init
|
|
|
74
84
|
|
|
75
85
|
### Spec Coding 完整流程
|
|
76
86
|
|
|
87
|
+
进入工作目录
|
|
88
|
+
|
|
77
89
|
```bash
|
|
78
90
|
# 进入工作目录
|
|
79
91
|
$ mkdir my-project
|
|
80
92
|
$ cd my-project
|
|
81
93
|
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Spec 初始化
|
|
97
|
+
|
|
98
|
+
```bash
|
|
82
99
|
# 执行 aspec 初始化
|
|
83
100
|
$ ace spec init
|
|
84
101
|
✓ aspec 工作流已初始化
|
|
85
102
|
Done! 规范驱动开发已就绪。
|
|
86
103
|
|
|
87
|
-
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Spec驱动开发
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# 在 Claude Code 中体验Spec开发流程:
|
|
110
|
+
# 输入 /opsx:proposal 命令后,一路交互式澄清、确认,然后需求完成,Spec归档
|
|
88
111
|
$ claude
|
|
89
112
|
|
|
90
113
|
> /opsx:proposal 帮我实现用户积分系统
|
|
91
114
|
|
|
92
|
-
Claude:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
Claude: 【需求澄清】对需求不确定的地方提出疑问?→ 请求人工澄清
|
|
116
|
+
人工:选择选项,或者进行纠正,然后点击submit
|
|
117
|
+
|
|
118
|
+
Claude: 【需求对齐】Claude输出自己对本需求的理解 → 请求人工确认
|
|
119
|
+
人工:选择确认,或者纠正信息,然后点击submit
|
|
120
|
+
|
|
121
|
+
-- 人工确认之后,Cluade会创建提案
|
|
122
|
+
|
|
123
|
+
Claude: 【技术设计澄清】对技术不确定的地方提出疑问 → 请求人工确认
|
|
124
|
+
人工:选择确认,或者纠正信息,然后点击submit
|
|
125
|
+
|
|
126
|
+
Claude: 【技术设计对齐】Claude输出自己对本需求的技术方案设计的理解 → 请求人工确认
|
|
127
|
+
人工:选择确认,或者纠正信息,然后点击submit
|
|
128
|
+
|
|
129
|
+
-- 人工确认之后,Cluade会创建Design
|
|
130
|
+
|
|
131
|
+
Claude: 【Design审批并创建Tasks】Claude请求人工确认Design设计,然后创建Tasks → 请求人工审批
|
|
132
|
+
人工:选择确认,或者纠正信息,然后点击submit
|
|
133
|
+
|
|
134
|
+
-- 人工确认之后,Cluade会创建tasks
|
|
97
135
|
|
|
98
|
-
|
|
136
|
+
Claude: 【执行】Claude 请求按规划的任务进行代码实现 → 请求人工审批
|
|
137
|
+
人工:选择确认,或者纠正信息,然后点击submit
|
|
99
138
|
|
|
100
|
-
|
|
101
|
-
按 tasks.md 逐项实现,每步验证
|
|
102
|
-
✓ 所有任务完成,测试通过
|
|
139
|
+
-- 人工确认之后,Cluade会进行代码实现,然后进行经验收集
|
|
103
140
|
|
|
104
|
-
|
|
141
|
+
Claude: 【归档同步】Claude 会请求进行归档同步→ 请求人工审批
|
|
142
|
+
人工:选择确认,或者纠正信息,然后点击submit
|
|
105
143
|
|
|
106
|
-
|
|
107
|
-
spec 归档,收敛检查
|
|
108
|
-
✓ 归档完成
|
|
144
|
+
-- 人工确认之后,Cluade会对Spec进行归档同步
|
|
109
145
|
```
|
|
110
146
|
|
|
111
147
|
### 健康检查
|
package/package.json
CHANGED
package/src/commands/doctor.js
CHANGED
|
@@ -27,10 +27,10 @@ export async function doctorCommand() {
|
|
|
27
27
|
const ruleFiles = await fs.readdir(templateRulesDir);
|
|
28
28
|
for (const file of ruleFiles) {
|
|
29
29
|
if (!file.endsWith('.md')) continue;
|
|
30
|
-
checks.push(await check(`rules
|
|
30
|
+
checks.push(await check(`ace/rules/${file}`, fs.pathExists(path.join(CLAUDE_DIR, rulesDir, file))));
|
|
31
31
|
}
|
|
32
32
|
} catch {
|
|
33
|
-
checks.push({ name: 'rules/
|
|
33
|
+
checks.push({ name: 'ace/rules/ directory', ok: false });
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -91,14 +91,21 @@ export async function doctorCommand() {
|
|
|
91
91
|
checks.push({ name: 'settings.json parseable', ok: false });
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// 8. Validate CLAUDE.md @
|
|
94
|
+
// 8. Validate CLAUDE.md references (both @refs and path-index style)
|
|
95
95
|
try {
|
|
96
96
|
const claudeMd = await fs.readFile(path.join(CLAUDE_DIR, 'CLAUDE.md'), 'utf-8');
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
// Check @references (legacy format)
|
|
98
|
+
const atRefs = claudeMd.match(/@~?\/?\.?claude\/[^\s)]+/g) || [];
|
|
99
|
+
for (const ref of atRefs) {
|
|
99
100
|
const refPath = ref.replace(/^@/, '').replace(/^~/, process.env.HOME || process.env.USERPROFILE);
|
|
100
101
|
checks.push(await check(`@ref: ${path.basename(refPath)}`, fs.pathExists(refPath)));
|
|
101
102
|
}
|
|
103
|
+
// Check path-index style references (new format: lines starting with "- ~/.claude/...")
|
|
104
|
+
const pathRefs = claudeMd.match(/(?:^|\n)-\s+(~\/.claude\/[^\s—]+)/g) || [];
|
|
105
|
+
for (const match of pathRefs) {
|
|
106
|
+
const refPath = match.replace(/(?:^|\n)-\s+/, '').replace(/^~/, process.env.HOME || process.env.USERPROFILE);
|
|
107
|
+
checks.push(await check(`path: ${path.basename(refPath)}`, fs.pathExists(refPath)));
|
|
108
|
+
}
|
|
102
109
|
} catch {
|
|
103
110
|
checks.push({ name: 'CLAUDE.md readable', ok: false });
|
|
104
111
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -91,6 +91,11 @@ export async function initCommand(options) {
|
|
|
91
91
|
|
|
92
92
|
s.start('Installing...');
|
|
93
93
|
|
|
94
|
+
// Prepare: migrate legacy directory structure if needed
|
|
95
|
+
if (!options.dryRun) {
|
|
96
|
+
await installer.prepare();
|
|
97
|
+
}
|
|
98
|
+
|
|
94
99
|
for (const componentName of components) {
|
|
95
100
|
const component = COMPONENTS[componentName];
|
|
96
101
|
if (!component) continue;
|
|
@@ -149,7 +154,7 @@ export async function initCommand(options) {
|
|
|
149
154
|
'',
|
|
150
155
|
'Customize',
|
|
151
156
|
' Change role edit ~/.claude/memory/user_profile.md',
|
|
152
|
-
' Adjust rules edit ~/.claude/rules/
|
|
157
|
+
' Adjust rules edit ~/.claude/ace/rules/',
|
|
153
158
|
' Safety guards edit ~/.claude/hookify.ace.*.local.md',
|
|
154
159
|
' Verify setup ace doctor',
|
|
155
160
|
].join('\n'),
|
|
@@ -224,8 +229,8 @@ async function buildInstallPreview(installer, components) {
|
|
|
224
229
|
try {
|
|
225
230
|
const existing = await fs.readFile(path.join(installer.targetDir, item.dest), 'utf-8');
|
|
226
231
|
const template = await fs.readFile(path.join(installer.templatesDir, item.src), 'utf-8');
|
|
227
|
-
const {
|
|
228
|
-
item.detail =
|
|
232
|
+
const { content } = mergeClaudeMd(existing, template);
|
|
233
|
+
item.detail = content !== existing ? 'will update managed section' : 'up to date';
|
|
229
234
|
} catch {
|
|
230
235
|
item.detail = 'will merge';
|
|
231
236
|
}
|
|
@@ -30,16 +30,35 @@ export async function uninstallCommand(options) {
|
|
|
30
30
|
const skipped = [];
|
|
31
31
|
const errors = [];
|
|
32
32
|
|
|
33
|
-
// 1. Remove rules/ace/
|
|
33
|
+
// 1. Remove ace/rules/ directory (and legacy rules/ace/ if still present)
|
|
34
34
|
const spinner1 = ora('Removing rules...').start();
|
|
35
35
|
try {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
const newRulesDir = path.join(CLAUDE_DIR, 'ace', 'rules');
|
|
37
|
+
const newTeamDir = path.join(CLAUDE_DIR, 'ace', 'team');
|
|
38
|
+
const aceDir = path.join(CLAUDE_DIR, 'ace');
|
|
39
|
+
const legacyRulesDir = path.join(CLAUDE_DIR, 'rules', 'ace');
|
|
40
|
+
|
|
41
|
+
if (await fs.pathExists(newRulesDir)) {
|
|
42
|
+
await fs.remove(newRulesDir);
|
|
43
|
+
removed.push('ace/rules/');
|
|
44
|
+
}
|
|
45
|
+
if (await fs.pathExists(newTeamDir)) {
|
|
46
|
+
await fs.remove(newTeamDir);
|
|
47
|
+
removed.push('ace/team/');
|
|
48
|
+
}
|
|
49
|
+
// Remove ace/ parent if empty
|
|
50
|
+
if (await fs.pathExists(aceDir)) {
|
|
51
|
+
const remaining = await fs.readdir(aceDir);
|
|
52
|
+
if (remaining.length === 0) {
|
|
53
|
+
await fs.remove(aceDir);
|
|
54
|
+
}
|
|
42
55
|
}
|
|
56
|
+
// Also clean legacy directory if still exists
|
|
57
|
+
if (await fs.pathExists(legacyRulesDir)) {
|
|
58
|
+
await fs.remove(legacyRulesDir);
|
|
59
|
+
removed.push('rules/ace/ (legacy)');
|
|
60
|
+
}
|
|
61
|
+
|
|
43
62
|
spinner1.succeed('rules removed');
|
|
44
63
|
} catch (err) {
|
|
45
64
|
spinner1.fail('rules removal failed');
|
|
@@ -126,17 +145,31 @@ export async function uninstallCommand(options) {
|
|
|
126
145
|
await fs.remove(claudeBackup);
|
|
127
146
|
removed.push('CLAUDE.md (restored pre-ace backup)');
|
|
128
147
|
} else if (await fs.pathExists(claudeMdPath)) {
|
|
129
|
-
// Fallback: surgically remove ace @references
|
|
148
|
+
// Fallback: surgically remove ace @references and managed section
|
|
130
149
|
const content = await fs.readFile(claudeMdPath, 'utf-8');
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const
|
|
150
|
+
let cleaned = content;
|
|
151
|
+
// Remove the entire managed section if present
|
|
152
|
+
const managedStart = '<!-- ace:managed:start -->';
|
|
153
|
+
const managedEnd = '<!-- ace:managed:end -->';
|
|
154
|
+
const startIdx = cleaned.indexOf(managedStart);
|
|
155
|
+
const endIdx = cleaned.indexOf(managedEnd);
|
|
156
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
157
|
+
cleaned = cleaned.slice(0, startIdx) + cleaned.slice(endIdx + managedEnd.length);
|
|
158
|
+
}
|
|
159
|
+
// Remove any remaining ace @references (legacy or new format)
|
|
160
|
+
const lines = cleaned.split('\n');
|
|
161
|
+
const filtered = lines.filter(line =>
|
|
162
|
+
!line.includes('@~/.claude/rules/ace/') &&
|
|
163
|
+
!line.includes('@~/.claude/ace/') &&
|
|
164
|
+
!line.includes('hookify.ace.')
|
|
165
|
+
);
|
|
166
|
+
cleaned = filtered.join('\n')
|
|
134
167
|
.replace(/\n## Added by ace\n*/g, '\n')
|
|
135
168
|
.replace(/\n{3,}/g, '\n\n')
|
|
136
169
|
.trim() + '\n';
|
|
137
170
|
if (cleaned !== content) {
|
|
138
171
|
await fs.writeFile(claudeMdPath, cleaned, 'utf-8');
|
|
139
|
-
removed.push('CLAUDE.md ace
|
|
172
|
+
removed.push('CLAUDE.md ace content (surgical)');
|
|
140
173
|
}
|
|
141
174
|
}
|
|
142
175
|
|
package/src/core/constants.js
CHANGED
|
@@ -58,10 +58,12 @@ export const SPEC_TEMPLATE_FILES = [
|
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* Patterns for files owned by ACE - these are overwritten directly on init without prompting.
|
|
61
|
-
* Used to identify ACE-owned content in
|
|
61
|
+
* Used to identify ACE-owned content in ace/, hooks/, and hookify/.
|
|
62
62
|
*/
|
|
63
63
|
export const ACE_OWNED_PATTERNS = [
|
|
64
|
-
/^rules
|
|
64
|
+
/^ace\/rules\//, // ace/rules/*.md (v2.0+)
|
|
65
|
+
/^ace\/team\//, // ace/team/*.md (v2.0+)
|
|
66
|
+
/^rules\/ace\//, // rules/ace/*.md (legacy, for migration detection)
|
|
65
67
|
/^hooks\/ace\./, // hooks/ace.*.sh
|
|
66
68
|
/^hookify\.ace\./, // hookify.ace.*.local.md
|
|
67
69
|
];
|
|
@@ -77,12 +79,12 @@ export function isAceOwnedFile(relativePath) {
|
|
|
77
79
|
|
|
78
80
|
/**
|
|
79
81
|
* Check if an @reference path is owned by ACE.
|
|
80
|
-
* @param {string} refPath - Reference path like '
|
|
82
|
+
* @param {string} refPath - Reference path like '@~/.claude/rules/ace/thinking.md' or '~/.claude/hooks/ace.java-compile-check.sh'
|
|
81
83
|
* @returns {boolean}
|
|
82
84
|
*/
|
|
83
85
|
export function isAceOwnedRef(refPath) {
|
|
84
|
-
// Remove the ~/.claude/ prefix if present
|
|
85
|
-
const relativePath = refPath.replace(
|
|
86
|
+
// Remove the @~/.claude/ or ~/.claude/ prefix if present
|
|
87
|
+
const relativePath = refPath.replace(/^@?~\/\.claude\//, '');
|
|
86
88
|
return isAceOwnedFile(relativePath);
|
|
87
89
|
}
|
|
88
90
|
|
|
@@ -98,7 +100,7 @@ export const COMPONENTS = {
|
|
|
98
100
|
rules: {
|
|
99
101
|
description: 'Cognitive & code quality rules',
|
|
100
102
|
required: true,
|
|
101
|
-
rulesDir: 'rules
|
|
103
|
+
rulesDir: 'ace/rules',
|
|
102
104
|
},
|
|
103
105
|
plugin: {
|
|
104
106
|
description: 'Ace plugin (skills: auto-goal, coding, skill-creator, skill-optimize; commands: report)',
|
package/src/core/installer.js
CHANGED
|
@@ -94,6 +94,7 @@ export class Installer {
|
|
|
94
94
|
async run() {
|
|
95
95
|
if (!this.dryRun) {
|
|
96
96
|
await fs.ensureDir(this.targetDir);
|
|
97
|
+
await this.prepare();
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
for (const componentName of this.components) {
|
|
@@ -114,6 +115,57 @@ export class Installer {
|
|
|
114
115
|
return this.results;
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Prepare the target directory: migrate legacy structure and ensure new layout.
|
|
120
|
+
* Call this before installComponent() if not using run().
|
|
121
|
+
*/
|
|
122
|
+
async prepare() {
|
|
123
|
+
await fs.ensureDir(this.targetDir);
|
|
124
|
+
await this.migrateFromLegacy();
|
|
125
|
+
await this.ensureAceStructure();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Migrate from legacy directory structure (rules/ace/) to new (ace/rules/).
|
|
130
|
+
* Only runs if old directory exists.
|
|
131
|
+
*/
|
|
132
|
+
async migrateFromLegacy() {
|
|
133
|
+
const legacyDir = path.join(this.targetDir, 'rules', 'ace');
|
|
134
|
+
const newDir = path.join(this.targetDir, 'ace', 'rules');
|
|
135
|
+
|
|
136
|
+
if (!await fs.pathExists(legacyDir)) return;
|
|
137
|
+
|
|
138
|
+
// Move contents from legacy to new location
|
|
139
|
+
await fs.ensureDir(newDir);
|
|
140
|
+
const files = await fs.readdir(legacyDir);
|
|
141
|
+
for (const file of files) {
|
|
142
|
+
const src = path.join(legacyDir, file);
|
|
143
|
+
const dest = path.join(newDir, file);
|
|
144
|
+
await fs.move(src, dest, { overwrite: true });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Remove empty legacy directory
|
|
148
|
+
await fs.remove(legacyDir);
|
|
149
|
+
// Clean up parent if empty
|
|
150
|
+
const rulesParent = path.join(this.targetDir, 'rules');
|
|
151
|
+
if (await fs.pathExists(rulesParent)) {
|
|
152
|
+
const remaining = await fs.readdir(rulesParent);
|
|
153
|
+
if (remaining.length === 0) {
|
|
154
|
+
await fs.remove(rulesParent);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.results.merged.push({ file: 'ace/rules/ (migrated from rules/ace/)' });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Ensure the ace/ namespace directory structure exists.
|
|
163
|
+
*/
|
|
164
|
+
async ensureAceStructure() {
|
|
165
|
+
await fs.ensureDir(path.join(this.targetDir, 'ace', 'rules'));
|
|
166
|
+
await fs.ensureDir(path.join(this.targetDir, 'ace', 'team'));
|
|
167
|
+
}
|
|
168
|
+
|
|
117
169
|
async installComponent(name, component) {
|
|
118
170
|
if (component.isPlugin) {
|
|
119
171
|
await this.installPlugin();
|
|
@@ -330,15 +382,16 @@ export class Installer {
|
|
|
330
382
|
async mergeClaudeMdFile(srcPath, destPath, fileSpec) {
|
|
331
383
|
const existing = await fs.readFile(destPath, 'utf-8');
|
|
332
384
|
const template = await fs.readFile(srcPath, 'utf-8');
|
|
333
|
-
const { content, added } = mergeClaudeMd(existing, template);
|
|
385
|
+
const { content, added, removed } = mergeClaudeMd(existing, template);
|
|
334
386
|
|
|
335
|
-
if (added
|
|
387
|
+
// Skip if content is unchanged (no refs added/removed, managed section identical)
|
|
388
|
+
if (content === existing) {
|
|
336
389
|
this.results.skipped.push(fileSpec.dest);
|
|
337
390
|
return;
|
|
338
391
|
}
|
|
339
392
|
|
|
340
393
|
if (this.dryRun) {
|
|
341
|
-
!this.quiet && console.log(chalk.cyan(` [dry-run] Would merge CLAUDE.md
|
|
394
|
+
!this.quiet && console.log(chalk.cyan(` [dry-run] Would merge CLAUDE.md`));
|
|
342
395
|
this.results.merged.push({ file: fileSpec.dest, added });
|
|
343
396
|
return;
|
|
344
397
|
}
|
|
@@ -346,7 +399,7 @@ export class Installer {
|
|
|
346
399
|
await backupPreInstall(destPath);
|
|
347
400
|
await backupFile(destPath);
|
|
348
401
|
await fs.writeFile(destPath, content, 'utf-8');
|
|
349
|
-
this.results.merged.push({ file: fileSpec.dest, added });
|
|
402
|
+
this.results.merged.push({ file: fileSpec.dest, added, removed });
|
|
350
403
|
}
|
|
351
404
|
|
|
352
405
|
async mergeSettingsJsonFile(srcPath, destPath, fileSpec) {
|
package/src/core/merger.js
CHANGED
|
@@ -60,28 +60,40 @@ function mergeWithMarkers(existingContent, templateContent) {
|
|
|
60
60
|
let result = replaceManagedSection(existingContent, templateManaged);
|
|
61
61
|
|
|
62
62
|
// Clean up any obsolete ACE refs outside the managed section
|
|
63
|
+
// This includes old @~/.claude/rules/ace/ refs AND hookify @refs
|
|
63
64
|
const removed = [];
|
|
64
65
|
const lines = result.split('\n');
|
|
65
66
|
const cleanedLines = lines.map(line => {
|
|
66
67
|
const refs = extractRefs(line);
|
|
67
68
|
const hasObsoleteAceRef = refs.some(ref => {
|
|
68
|
-
// Check if this is an ACE-owned ref that's NOT in the new template
|
|
69
69
|
if (isAceOwnedRef(ref)) {
|
|
70
70
|
const refWithAt = `@${ref}`;
|
|
71
71
|
if (!templateRefs.includes(refWithAt)) {
|
|
72
72
|
removed.push(ref);
|
|
73
|
-
return true;
|
|
73
|
+
return true;
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
return false;
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
//
|
|
79
|
+
// Also remove lines with hookify @ references (these should not be in CLAUDE.md)
|
|
80
|
+
const hasHookifyRef = refs.some(ref => /hookify\.ace\./.test(ref));
|
|
81
|
+
if (hasHookifyRef) {
|
|
82
|
+
const refBare = refs.find(ref => /hookify\.ace\./.test(ref));
|
|
83
|
+
if (refBare) removed.push(refBare);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
80
87
|
return hasObsoleteAceRef ? null : line;
|
|
81
88
|
}).filter(line => line !== null);
|
|
82
89
|
|
|
83
90
|
result = cleanedLines.join('\n');
|
|
84
91
|
|
|
92
|
+
// Clean up empty "## Added by ace" section if all its refs were removed
|
|
93
|
+
result = result.replace(/\n## Added by ace\n*(?=\n|$)/g, '\n');
|
|
94
|
+
// Normalize multiple blank lines
|
|
95
|
+
result = result.replace(/\n{3,}/g, '\n\n');
|
|
96
|
+
|
|
85
97
|
// Get the new refs that were added (in the managed section)
|
|
86
98
|
const existingRefs = extractRefs(existingContent);
|
|
87
99
|
const added = templateRefs
|
package/templates/CLAUDE.md
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 交互语言
|
|
2
|
+
始终使用中文与用户交互。所有回复、解释、总结使用中文;代码和技术标识符保持英文。
|
|
2
3
|
|
|
3
4
|
<!-- ace:managed:start -->
|
|
4
|
-
|
|
5
|
-
- @~/.claude/rules/ace/thinking.md - 深度思考原则(序验深广辨简)
|
|
5
|
+
# ACE 配置
|
|
6
6
|
|
|
7
|
-
##
|
|
8
|
-
- @~/.claude/rules/ace/clean-code.md - Clean Code 核心原则(始终加载)
|
|
9
|
-
- @~/.claude/rules/ace/code-quality.md - 代码质量标准(编辑代码文件时加载)
|
|
7
|
+
## 核心原则(始终适用)
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
- @~/.claude/rules/ace/reporting.md - 报告输出规则
|
|
13
|
-
- @~/.claude/rules/ace/task-recovery.md - 任务恢复规则
|
|
14
|
-
- @~/.claude/rules/ace/context-hygiene.md - 上下文卫生与 Compaction 保护
|
|
9
|
+
**深度思考** — 理解先于规划,规划先于行动。用事实闭环,不以假设收尾。多问一层为什么,追问前提、追问替代、追问问题本身。在系统中定位局部。主动找反证,复杂度是负债。
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
- @~/.claude/rules/ace/memory-policy.md - Memory 质量策略
|
|
11
|
+
**Clean Code** — 意图清晰(命名即意图)、单一职责(一个理由改变)、最小 Surprise(做读者期望的事)、DRY(知识只表达一次)、简洁胜于复杂(KISS/YAGNI)、渐进改进(离开时更干净)。
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
**优先级** — 正确性 > 可读性 > 清晰 > 简单 > 显式。
|
|
14
|
+
|
|
15
|
+
## 编码规则(编辑代码前,先阅读对应规则文件)
|
|
16
|
+
- ~/.claude/ace/rules/code-quality.md — 代码质量标准(函数/命名/结构/SOLID 检查清单)
|
|
17
|
+
- ~/.claude/ace/rules/clean-code.md — Clean Code 详细原则与反模式速查
|
|
18
|
+
|
|
19
|
+
## 工作流规则(对应场景时参考)
|
|
20
|
+
- ~/.claude/ace/rules/context-hygiene.md — 上下文卫生与压缩保护(长任务时阅读)
|
|
21
|
+
- ~/.claude/ace/rules/task-recovery.md — 任务恢复流程(用户说"继续"时阅读)
|
|
22
|
+
- ~/.claude/ace/rules/reporting.md — 报告输出规则(生成报告前阅读)
|
|
23
|
+
- ~/.claude/ace/rules/memory-policy.md — 记忆质量策略(保存记忆前阅读)
|
|
24
|
+
- ~/.claude/ace/rules/interactive-clarify.md — 交互式澄清规则(需要提问时阅读)
|
|
25
|
+
|
|
26
|
+
## 安全策略
|
|
27
|
+
安全由外部机制保障,不占用上下文 token:
|
|
28
|
+
- settings.json deny 规则 → 硬拦截 rm -rf、sudo 等
|
|
29
|
+
- Shell hooks → 进程级检查(编译、类型检查)
|
|
30
|
+
- Hookify 插件 → 模式匹配拦截(危险命令、敏感文件)
|
|
21
31
|
<!-- ace:managed:end -->
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
|
|
13
13
|
**深** — 多问一层为什么。表面问题是深层问题的症状。抵达机制层和根因层才算理解。
|
|
14
14
|
|
|
15
|
+
苏格拉底式追问:
|
|
16
|
+
- 追问前提:"这个假设成立吗?依据是什么?"
|
|
17
|
+
- 追问替代:"还有没有其他解释?"
|
|
18
|
+
- 追问问题本身:"这是正确的问题吗?问题框架对吗?"
|
|
19
|
+
|
|
15
20
|
**广** — 在系统中定位局部。一切都有上下文、依赖和边界。改动一处,影响传向何方?
|
|
16
21
|
|
|
17
22
|
**辨** — 主动为自己的结论找反证。方案都有代价,判断都有前提。区分事实、推断与猜测,对不确定性诚实。
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|