cc-devflow 4.1.5 → 4.1.6
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/.claude/CLAUDE.md +87 -1091
- package/.claude/commands/core/architecture.md +2 -2
- package/.claude/commands/core/guidelines.md +2 -2
- package/.claude/commands/core/roadmap.md +4 -4
- package/.claude/commands/core/style.md +40 -268
- package/.claude/commands/flow/CLAUDE.md +28 -0
- package/.claude/commands/flow/archive.md +2 -2
- package/.claude/commands/flow/checklist.md +9 -251
- package/.claude/commands/flow/clarify.md +9 -127
- package/.claude/commands/flow/constitution.md +1 -1
- package/.claude/commands/flow/context.md +1 -1
- package/.claude/commands/flow/dev.md +19 -395
- package/.claude/commands/flow/ideate.md +13 -13
- package/.claude/commands/flow/init.md +19 -30
- package/.claude/commands/flow/new.md +12 -268
- package/.claude/commands/flow/quality.md +10 -153
- package/.claude/commands/flow/release.md +18 -81
- package/.claude/commands/flow/restart.md +15 -16
- package/.claude/commands/flow/spec.md +14 -164
- package/.claude/commands/flow/status.md +12 -12
- package/.claude/commands/flow/update.md +4 -4
- package/.claude/commands/flow/upgrade.md +6 -6
- package/.claude/commands/flow/verify.md +19 -78
- package/.claude/commands/flow/workspace.md +1 -1
- package/.claude/docs/guides/INIT_TROUBLESHOOTING.md +7 -7
- package/.claude/docs/guides/NEW_TROUBLESHOOTING.md +44 -96
- package/.claude/docs/guides/ROADMAP_TROUBLESHOOTING.md +1 -1
- package/.claude/docs/guides/TASK_COMPLETION_MARKING.md +5 -5
- package/.claude/docs/templates/ATTEMPT_TEMPLATE.md +1 -1
- package/.claude/docs/templates/BACKLOG_TEMPLATE.md +3 -3
- package/.claude/docs/templates/CLARIFICATION_REPORT_TEMPLATE.md +5 -5
- package/.claude/docs/templates/ERROR_LOG_TEMPLATE.md +2 -2
- package/.claude/docs/templates/INIT_FLOW_TEMPLATE.md +3 -3
- package/.claude/docs/templates/NEW_ORCHESTRATION_TEMPLATE.md +33 -64
- package/.claude/docs/templates/RESEARCH_TEMPLATE.md +3 -3
- package/.claude/docs/templates/ROADMAP_DIALOGUE_TEMPLATE.md +2 -2
- package/.claude/docs/templates/ROADMAP_TEMPLATE.md +2 -2
- package/.claude/docs/templates/STYLE_TEMPLATE.md +3 -3
- package/.claude/docs/templates/UI_PROTOTYPE_TEMPLATE.md +8 -9
- package/.claude/guides/workflow-guides/flow-orchestrator.md +31 -265
- package/.claude/hooks/CLAUDE.md +1 -1
- package/.claude/hooks/checklist-gate.js +4 -4
- package/.claude/hooks/inject-agent-context.ts +2 -2
- package/.claude/scripts/calculate-checklist-completion.sh +2 -2
- package/.claude/scripts/check-prerequisites.sh +2 -2
- package/.claude/scripts/checklist-errors.sh +4 -4
- package/.claude/scripts/flow-quality-full.sh +5 -5
- package/.claude/scripts/flow-quality-quick.sh +4 -4
- package/.claude/scripts/flow-workspace-init.sh +2 -2
- package/.claude/scripts/generate-clarification-report.sh +4 -4
- package/.claude/scripts/recover-workflow.sh +70 -73
- package/.claude/scripts/run-quality-gates.sh +1 -1
- package/.claude/scripts/setup-epic.sh +2 -2
- package/.claude/scripts/setup-ralph-loop.sh +2 -2
- package/.claude/scripts/validate-research.sh +1 -1
- package/.claude/scripts/verify-setup.sh +1 -1
- package/.claude/skills/cc-devflow-orchestrator/SKILL.md +88 -108
- package/.claude/skills/workflow/CLAUDE.md +24 -0
- package/.claude/skills/workflow/flow-dev/CLAUDE.md +14 -76
- package/.claude/skills/workflow/flow-dev/SKILL.md +29 -67
- package/.claude/skills/workflow/flow-dev/context.jsonl +4 -8
- package/.claude/skills/workflow/flow-init/SKILL.md +24 -151
- package/.claude/skills/workflow/flow-init/assets/RESEARCH_TEMPLATE.md +1 -1
- package/.claude/skills/workflow/flow-init/context.jsonl +3 -3
- package/.claude/skills/workflow/flow-init/scripts/check-prerequisites.sh +1 -1
- package/.claude/skills/workflow/flow-init/scripts/validate-research.sh +1 -1
- package/.claude/skills/workflow/flow-release/SKILL.md +23 -56
- package/.claude/skills/workflow/flow-release/context.jsonl +5 -7
- package/.claude/skills/workflow/flow-spec/CLAUDE.md +15 -101
- package/.claude/skills/workflow/flow-spec/SKILL.md +15 -518
- package/.claude/skills/workflow/flow-spec/context.jsonl +5 -7
- package/.claude/skills/workflow/flow-verify/CLAUDE.md +10 -0
- package/.claude/skills/workflow/flow-verify/SKILL.md +53 -0
- package/.claude/skills/workflow/flow-verify/context.jsonl +5 -0
- package/.claude/skills/workflow.yaml +72 -267
- package/CHANGELOG.md +31 -0
- package/README.md +91 -69
- package/README.zh-CN.md +90 -67
- package/bin/harness.js +22 -0
- package/docs/commands/README.md +34 -38
- package/docs/commands/README.zh-CN.md +34 -36
- package/docs/commands/core-roadmap.md +2 -2
- package/docs/commands/core-roadmap.zh-CN.md +2 -2
- package/docs/commands/core-style.md +29 -381
- package/docs/commands/core-style.zh-CN.md +29 -381
- package/docs/commands/flow-init.md +10 -10
- package/docs/commands/flow-init.zh-CN.md +11 -11
- package/docs/commands/flow-new.md +25 -260
- package/docs/commands/flow-new.zh-CN.md +26 -257
- package/docs/guides/getting-started.md +16 -15
- package/docs/guides/getting-started.zh-CN.md +10 -12
- package/lib/compiler/__tests__/manifest.test.js +156 -0
- package/lib/compiler/__tests__/parser.test.js +21 -0
- package/lib/compiler/index.js +17 -1
- package/lib/compiler/manifest.js +68 -6
- package/lib/compiler/parser.js +5 -0
- package/lib/harness/CLAUDE.md +21 -0
- package/lib/harness/cli.js +208 -0
- package/lib/harness/index.js +16 -0
- package/lib/harness/operations/dispatch.js +285 -0
- package/lib/harness/operations/init.js +48 -0
- package/lib/harness/operations/janitor.js +74 -0
- package/lib/harness/operations/pack.js +100 -0
- package/lib/harness/operations/plan.js +29 -0
- package/lib/harness/operations/release.js +83 -0
- package/lib/harness/operations/resume.js +44 -0
- package/lib/harness/operations/verify.js +163 -0
- package/lib/harness/planner.js +141 -0
- package/lib/harness/schemas.js +108 -0
- package/lib/harness/store.js +240 -0
- package/package.json +9 -1
|
@@ -42,37 +42,35 @@ python3 .claude/scripts/demo.py
|
|
|
42
42
|
### 1. 启动需求开发
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
/flow
|
|
45
|
+
/flow:init "REQ-001|用户认证功能|https://docs.example.com/auth"
|
|
46
|
+
/flow:spec "REQ-001"
|
|
47
|
+
/flow:dev "REQ-001"
|
|
48
|
+
/flow:verify "REQ-001" --strict
|
|
49
|
+
/flow:release "REQ-001"
|
|
46
50
|
```
|
|
47
51
|
|
|
48
52
|
### 2. 查看进度
|
|
49
53
|
|
|
50
54
|
```bash
|
|
51
|
-
/flow
|
|
55
|
+
/flow:status REQ-001
|
|
52
56
|
```
|
|
53
57
|
|
|
54
58
|
### 3. 如果中断,恢复开发
|
|
55
59
|
|
|
56
60
|
```bash
|
|
57
|
-
/flow
|
|
61
|
+
/flow:dev "REQ-001" --resume
|
|
58
62
|
```
|
|
59
63
|
|
|
60
64
|
### 4. 验证一致性
|
|
61
65
|
|
|
62
66
|
```bash
|
|
63
|
-
/flow
|
|
67
|
+
/flow:verify "REQ-001" --strict
|
|
64
68
|
```
|
|
65
69
|
|
|
66
|
-
### 5.
|
|
70
|
+
### 5. 创建发布
|
|
67
71
|
|
|
68
72
|
```bash
|
|
69
|
-
/flow
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### 6. 创建发布
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
/flow-release "REQ-001"
|
|
73
|
+
/flow:release "REQ-001"
|
|
76
74
|
```
|
|
77
75
|
|
|
78
76
|
## 📋 核心脚本
|
|
@@ -13,9 +13,13 @@
|
|
|
13
13
|
const {
|
|
14
14
|
MANIFEST_VERSION,
|
|
15
15
|
createManifest,
|
|
16
|
+
createEntry,
|
|
16
17
|
migrateToV2,
|
|
18
|
+
needsRecompile,
|
|
19
|
+
addEntry,
|
|
17
20
|
addSkillEntry,
|
|
18
21
|
addRulesEntry,
|
|
22
|
+
pruneCommandEntries,
|
|
19
23
|
needsSkillRecompile,
|
|
20
24
|
needsRulesRecompile,
|
|
21
25
|
hashContent
|
|
@@ -87,6 +91,158 @@ describe('Manifest v2.0', () => {
|
|
|
87
91
|
});
|
|
88
92
|
});
|
|
89
93
|
|
|
94
|
+
describe('createEntry()', () => {
|
|
95
|
+
test('should persist sourceHash for incremental compile and target hash for drift check', () => {
|
|
96
|
+
const sourceHash = 'a'.repeat(64);
|
|
97
|
+
const targetHash = 'b'.repeat(64);
|
|
98
|
+
const entry = createEntry({
|
|
99
|
+
source: '.claude/commands/flow/init.md',
|
|
100
|
+
target: '.codex/prompts/flow/init.md',
|
|
101
|
+
sourceHash,
|
|
102
|
+
targetHash,
|
|
103
|
+
platform: 'codex'
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(entry.sourceHash).toBe(sourceHash);
|
|
107
|
+
expect(entry.hash).toBe(targetHash);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('needsRecompile()', () => {
|
|
112
|
+
test('should return false when sourceHash is unchanged', () => {
|
|
113
|
+
const sourcePath = '.claude/commands/flow/init.md';
|
|
114
|
+
const sourceHash = hashContent('source content');
|
|
115
|
+
const manifest = createManifest();
|
|
116
|
+
|
|
117
|
+
manifest.entries.push({
|
|
118
|
+
source: sourcePath,
|
|
119
|
+
target: '.codex/prompts/flow/init.md',
|
|
120
|
+
hash: hashContent('target content'),
|
|
121
|
+
sourceHash,
|
|
122
|
+
platform: 'codex'
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(needsRecompile(sourcePath, sourceHash, manifest, 'codex')).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('should return true for legacy entries without sourceHash', () => {
|
|
129
|
+
const sourcePath = '.claude/commands/flow/init.md';
|
|
130
|
+
const sourceHash = hashContent('source content');
|
|
131
|
+
const manifest = createManifest();
|
|
132
|
+
|
|
133
|
+
manifest.entries.push({
|
|
134
|
+
source: sourcePath,
|
|
135
|
+
target: '.codex/prompts/flow/init.md',
|
|
136
|
+
hash: sourceHash,
|
|
137
|
+
platform: 'codex'
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(needsRecompile(sourcePath, sourceHash, manifest, 'codex')).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('should return false when all split outputs share same sourceHash', () => {
|
|
144
|
+
const sourcePath = '.claude/commands/flow/init.md';
|
|
145
|
+
const sourceHash = hashContent('source content');
|
|
146
|
+
const manifest = createManifest();
|
|
147
|
+
|
|
148
|
+
manifest.entries.push({
|
|
149
|
+
source: sourcePath,
|
|
150
|
+
target: '.agent/workflows/flow/init.md',
|
|
151
|
+
hash: hashContent('output a'),
|
|
152
|
+
sourceHash,
|
|
153
|
+
platform: 'antigravity'
|
|
154
|
+
});
|
|
155
|
+
manifest.entries.push({
|
|
156
|
+
source: sourcePath,
|
|
157
|
+
target: '.agent/workflows/flow/init.toml',
|
|
158
|
+
hash: hashContent('output b'),
|
|
159
|
+
sourceHash,
|
|
160
|
+
platform: 'antigravity'
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(needsRecompile(sourcePath, sourceHash, manifest, 'antigravity')).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('addEntry()', () => {
|
|
168
|
+
test('should keep multiple outputs for same source and platform', () => {
|
|
169
|
+
const manifest = createManifest();
|
|
170
|
+
addEntry(manifest, {
|
|
171
|
+
source: '.claude/commands/flow/init.md',
|
|
172
|
+
target: '.agent/workflows/flow/init.md',
|
|
173
|
+
hash: 'a',
|
|
174
|
+
sourceHash: 'sa',
|
|
175
|
+
platform: 'antigravity'
|
|
176
|
+
});
|
|
177
|
+
addEntry(manifest, {
|
|
178
|
+
source: '.claude/commands/flow/init.md',
|
|
179
|
+
target: '.agent/workflows/flow/init.toml',
|
|
180
|
+
hash: 'b',
|
|
181
|
+
sourceHash: 'sa',
|
|
182
|
+
platform: 'antigravity'
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
expect(manifest.entries).toHaveLength(2);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('should update only the matching output target', () => {
|
|
189
|
+
const manifest = createManifest();
|
|
190
|
+
addEntry(manifest, {
|
|
191
|
+
source: '.claude/commands/flow/init.md',
|
|
192
|
+
target: '.agent/workflows/flow/init.md',
|
|
193
|
+
hash: 'a',
|
|
194
|
+
sourceHash: 'sa',
|
|
195
|
+
platform: 'antigravity'
|
|
196
|
+
});
|
|
197
|
+
addEntry(manifest, {
|
|
198
|
+
source: '.claude/commands/flow/init.md',
|
|
199
|
+
target: '.agent/workflows/flow/init.toml',
|
|
200
|
+
hash: 'b',
|
|
201
|
+
sourceHash: 'sa',
|
|
202
|
+
platform: 'antigravity'
|
|
203
|
+
});
|
|
204
|
+
addEntry(manifest, {
|
|
205
|
+
source: '.claude/commands/flow/init.md',
|
|
206
|
+
target: '.agent/workflows/flow/init.md',
|
|
207
|
+
hash: 'c',
|
|
208
|
+
sourceHash: 'sb',
|
|
209
|
+
platform: 'antigravity'
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
expect(manifest.entries).toHaveLength(2);
|
|
213
|
+
expect(
|
|
214
|
+
manifest.entries.find(entry => entry.target === '.agent/workflows/flow/init.md')
|
|
215
|
+
).toMatchObject({ hash: 'c', sourceHash: 'sb' });
|
|
216
|
+
expect(
|
|
217
|
+
manifest.entries.find(entry => entry.target === '.agent/workflows/flow/init.toml')
|
|
218
|
+
).toMatchObject({ hash: 'b', sourceHash: 'sa' });
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('pruneCommandEntries()', () => {
|
|
223
|
+
test('should remove stale command entries for selected platforms only', () => {
|
|
224
|
+
const manifest = createManifest();
|
|
225
|
+
manifest.entries = [
|
|
226
|
+
{ source: '.claude/commands/flow/init.md', target: '.codex/prompts/flow/init.md', hash: '1', platform: 'codex' },
|
|
227
|
+
{ source: '.claude/commands/flow/old.md', target: '.codex/prompts/flow/old.md', hash: '2', platform: 'codex' },
|
|
228
|
+
{ source: '.claude/commands/flow/old.md', target: '.qwen/commands/flow/old.toml', hash: '3', platform: 'qwen' },
|
|
229
|
+
{ source: '.claude/rules/base.md', target: '.cursor/rules/base.mdc', hash: '4', platform: 'cursor' }
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
const removed = pruneCommandEntries(manifest, {
|
|
233
|
+
sourcePrefix: '.claude/commands',
|
|
234
|
+
activeSources: new Set(['.claude/commands/flow/init.md']),
|
|
235
|
+
platforms: ['codex', 'cursor']
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(removed).toBe(1);
|
|
239
|
+
expect(manifest.entries).toHaveLength(3);
|
|
240
|
+
expect(manifest.entries.find(e => e.target === '.codex/prompts/flow/old.md')).toBeUndefined();
|
|
241
|
+
expect(manifest.entries.find(e => e.target === '.qwen/commands/flow/old.toml')).toBeDefined();
|
|
242
|
+
expect(manifest.entries.find(e => e.target === '.cursor/rules/base.mdc')).toBeDefined();
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
90
246
|
describe('addSkillEntry()', () => {
|
|
91
247
|
test('should add new skill entry', () => {
|
|
92
248
|
const manifest = createManifest();
|
|
@@ -479,6 +479,27 @@ Body`);
|
|
|
479
479
|
}
|
|
480
480
|
});
|
|
481
481
|
|
|
482
|
+
it('should ignore CLAUDE.md documentation files', async () => {
|
|
483
|
+
const tmpDir = path.join(os.tmpdir(), `test-claude-doc-${Date.now()}`);
|
|
484
|
+
const flowDir = path.join(tmpDir, 'flow');
|
|
485
|
+
fs.mkdirSync(flowDir, { recursive: true });
|
|
486
|
+
|
|
487
|
+
fs.writeFileSync(path.join(flowDir, 'CLAUDE.md'), '# flow docs only');
|
|
488
|
+
fs.writeFileSync(path.join(flowDir, 'init.md'), `---
|
|
489
|
+
name: flow-init
|
|
490
|
+
description: Flow init command
|
|
491
|
+
---
|
|
492
|
+
Body`);
|
|
493
|
+
|
|
494
|
+
try {
|
|
495
|
+
const result = await parser.parseAllCommands(tmpDir);
|
|
496
|
+
expect(result).toHaveLength(1);
|
|
497
|
+
expect(result[0].source.filename).toBe('flow/init');
|
|
498
|
+
} finally {
|
|
499
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
|
|
482
503
|
it('should preserve relative subpath in source filename', async () => {
|
|
483
504
|
const tmpDir = path.join(os.tmpdir(), `test-relpath-${Date.now()}`);
|
|
484
505
|
const flowDir = path.join(tmpDir, 'flow');
|
package/lib/compiler/index.js
CHANGED
|
@@ -33,6 +33,7 @@ const {
|
|
|
33
33
|
migrateToV2,
|
|
34
34
|
addSkillEntry,
|
|
35
35
|
addRulesEntry,
|
|
36
|
+
pruneCommandEntries,
|
|
36
37
|
needsSkillRecompile,
|
|
37
38
|
needsRulesRecompile,
|
|
38
39
|
MANIFEST_PATH
|
|
@@ -371,6 +372,8 @@ async function compile(options = {}) {
|
|
|
371
372
|
skillsRegistered: 0,
|
|
372
373
|
errors: []
|
|
373
374
|
};
|
|
375
|
+
const absoluteOutputBaseDir = path.resolve(outputBaseDir);
|
|
376
|
+
const toManifestPath = (absolutePath) => path.relative(absoluteOutputBaseDir, absolutePath).replace(/\\/g, '/');
|
|
374
377
|
|
|
375
378
|
// 加载 manifest 并迁移到 v2.0
|
|
376
379
|
const manifestPath = path.join(outputBaseDir, MANIFEST_PATH);
|
|
@@ -445,6 +448,19 @@ async function compile(options = {}) {
|
|
|
445
448
|
console.log(`Found ${irs.length} command files`);
|
|
446
449
|
}
|
|
447
450
|
|
|
451
|
+
// 清理已删除命令文件的旧 manifest 条目,避免多平台漂移噪音
|
|
452
|
+
const sourcePrefix = toManifestPath(absoluteSourceDir).replace(/\/+$/, '');
|
|
453
|
+
const activeSources = new Set(irs.map(ir => toManifestPath(ir.source.path)));
|
|
454
|
+
const prunedEntries = pruneCommandEntries(manifest, {
|
|
455
|
+
sourcePrefix,
|
|
456
|
+
activeSources,
|
|
457
|
+
platforms
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
if (verbose && prunedEntries > 0) {
|
|
461
|
+
console.log(`Pruned ${prunedEntries} stale command entries from manifest`);
|
|
462
|
+
}
|
|
463
|
+
|
|
448
464
|
// 生成规则入口文件
|
|
449
465
|
if (rules && registry) {
|
|
450
466
|
try {
|
|
@@ -494,7 +510,7 @@ async function compile(options = {}) {
|
|
|
494
510
|
for (const ir of irs) {
|
|
495
511
|
for (const platform of platforms) {
|
|
496
512
|
// 检查是否需要重新编译
|
|
497
|
-
const sourceRelative =
|
|
513
|
+
const sourceRelative = toManifestPath(ir.source.path);
|
|
498
514
|
if (!needsRecompile(sourceRelative, ir.source.hash, manifest, platform)) {
|
|
499
515
|
result.filesSkipped++;
|
|
500
516
|
if (verbose) {
|
package/lib/compiler/manifest.js
CHANGED
|
@@ -31,13 +31,19 @@ function hashContent(content) {
|
|
|
31
31
|
// createEntry - 创建 manifest 条目
|
|
32
32
|
// ============================================================
|
|
33
33
|
function createEntry({ source, target, sourceHash, targetHash, platform }) {
|
|
34
|
-
|
|
34
|
+
const entry = {
|
|
35
35
|
source,
|
|
36
36
|
target,
|
|
37
|
-
hash: sourceHash,
|
|
37
|
+
hash: targetHash || sourceHash,
|
|
38
38
|
timestamp: new Date().toISOString(),
|
|
39
39
|
platform
|
|
40
40
|
};
|
|
41
|
+
|
|
42
|
+
if (sourceHash) {
|
|
43
|
+
entry.sourceHash = sourceHash;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return entry;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
// ============================================================
|
|
@@ -72,19 +78,29 @@ function needsRecompile(sourcePath, sourceHash, manifest, platform) {
|
|
|
72
78
|
return true;
|
|
73
79
|
}
|
|
74
80
|
|
|
75
|
-
const
|
|
76
|
-
|
|
81
|
+
const scopedEntries = manifest.entries.filter(
|
|
82
|
+
entry => entry.source === sourcePath && entry.platform === platform
|
|
83
|
+
);
|
|
84
|
+
if (scopedEntries.length === 0) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Legacy entries (v1/v2 early) stored source hash in `hash`.
|
|
89
|
+
// Force one-time recompile to migrate to explicit `sourceHash`.
|
|
90
|
+
if (scopedEntries.some(entry => !entry.sourceHash)) {
|
|
77
91
|
return true;
|
|
78
92
|
}
|
|
79
93
|
|
|
80
|
-
return entry.
|
|
94
|
+
return scopedEntries.some(entry => entry.sourceHash !== sourceHash);
|
|
81
95
|
}
|
|
82
96
|
|
|
83
97
|
// ============================================================
|
|
84
98
|
// addEntry - 添加或更新条目
|
|
85
99
|
// ============================================================
|
|
86
100
|
function addEntry(manifest, entry) {
|
|
87
|
-
const existingIndex = manifest.entries.findIndex(
|
|
101
|
+
const existingIndex = manifest.entries.findIndex(
|
|
102
|
+
e => e.source === entry.source && e.platform === entry.platform && e.target === entry.target
|
|
103
|
+
);
|
|
88
104
|
|
|
89
105
|
if (existingIndex >= 0) {
|
|
90
106
|
manifest.entries[existingIndex] = entry;
|
|
@@ -93,6 +109,51 @@ function addEntry(manifest, entry) {
|
|
|
93
109
|
}
|
|
94
110
|
}
|
|
95
111
|
|
|
112
|
+
// ============================================================
|
|
113
|
+
// pruneCommandEntries - 清理已删除命令文件对应的旧条目
|
|
114
|
+
// ============================================================
|
|
115
|
+
function pruneCommandEntries(manifest, options = {}) {
|
|
116
|
+
if (!manifest || !Array.isArray(manifest.entries) || manifest.entries.length === 0) {
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const {
|
|
121
|
+
sourcePrefix = '.claude/commands',
|
|
122
|
+
activeSources = new Set(),
|
|
123
|
+
platforms = null
|
|
124
|
+
} = options;
|
|
125
|
+
|
|
126
|
+
const normalizePath = (value) => String(value || '').replace(/\\/g, '/');
|
|
127
|
+
const normalizedPrefix = normalizePath(sourcePrefix).replace(/\/+$/, '');
|
|
128
|
+
const normalizedActiveSources = new Set(
|
|
129
|
+
Array.from(activeSources).map(source => normalizePath(source))
|
|
130
|
+
);
|
|
131
|
+
const platformSet = Array.isArray(platforms) ? new Set(platforms) : null;
|
|
132
|
+
|
|
133
|
+
let removed = 0;
|
|
134
|
+
manifest.entries = manifest.entries.filter(entry => {
|
|
135
|
+
if (platformSet && !platformSet.has(entry.platform)) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const source = normalizePath(entry.source);
|
|
140
|
+
const inScope = source === normalizedPrefix || source.startsWith(`${normalizedPrefix}/`);
|
|
141
|
+
|
|
142
|
+
if (!inScope) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (normalizedActiveSources.has(source)) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
removed += 1;
|
|
151
|
+
return false;
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return removed;
|
|
155
|
+
}
|
|
156
|
+
|
|
96
157
|
// ============================================================
|
|
97
158
|
// checkDrift - 检查漂移(目标文件被手动修改)
|
|
98
159
|
// ============================================================
|
|
@@ -237,6 +298,7 @@ module.exports = {
|
|
|
237
298
|
migrateToV2,
|
|
238
299
|
addSkillEntry,
|
|
239
300
|
addRulesEntry,
|
|
301
|
+
pruneCommandEntries,
|
|
240
302
|
needsSkillRecompile,
|
|
241
303
|
needsRulesRecompile
|
|
242
304
|
};
|
package/lib/compiler/parser.js
CHANGED
|
@@ -35,6 +35,7 @@ const TEMPLATE_PATTERN = /\{TEMPLATE:([^}]+)\}/g;
|
|
|
35
35
|
const GUIDE_PATTERN = /\{GUIDE:([^}]+)\}/g;
|
|
36
36
|
const AGENT_SCRIPT_PATTERN = /\{AGENT_SCRIPT\}/g;
|
|
37
37
|
const ARGUMENTS_PATTERN = /\$ARGUMENTS/g;
|
|
38
|
+
const IGNORED_MARKDOWN_FILES = new Set(['CLAUDE.MD']);
|
|
38
39
|
|
|
39
40
|
// ============================================================
|
|
40
41
|
// hashContent - 计算 SHA-256 哈希
|
|
@@ -270,6 +271,10 @@ function collectMarkdownFiles(rootDir, currentDir) {
|
|
|
270
271
|
continue;
|
|
271
272
|
}
|
|
272
273
|
|
|
274
|
+
if (IGNORED_MARKDOWN_FILES.has(entry.name.toUpperCase())) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
273
278
|
results.push(path.join(currentDir, entry.name));
|
|
274
279
|
}
|
|
275
280
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# harness/
|
|
2
|
+
> L2 | 父级: /Users/dimon/001Area/80-CodeWorld/002-devflow/cc-devflow/CLAUDE.md
|
|
3
|
+
|
|
4
|
+
成员清单
|
|
5
|
+
index.js: 聚合导出 harness 基础模块,供 CLI 与测试统一引用。
|
|
6
|
+
schemas.js: 定义 manifest/report/checkpoint 的 Zod 契约,阻断脏数据进入执行层。
|
|
7
|
+
store.js: 提供路径规范、JSON/文本读写、JSONL 事件记录与 shell 命令执行。
|
|
8
|
+
planner.js: 将 TASKS.md 解析为 dependency-aware 的 task-manifest.json。
|
|
9
|
+
cli.js: 解析命令参数并分发到 operations 子模块。
|
|
10
|
+
operations/init.js: 初始化 requirement 与 runtime 目录并写入 harness-state。
|
|
11
|
+
operations/pack.js: 采集 git 与脚本事实,生成 context-package.md。
|
|
12
|
+
operations/plan.js: 调用 planner 生成 task-manifest.json。
|
|
13
|
+
operations/dispatch.js: 依据依赖图与文件冲突并行执行任务,写 checkpoint 与 events。
|
|
14
|
+
operations/resume.js: 恢复中断任务并复用 dispatch 继续执行。
|
|
15
|
+
operations/verify.js: 执行 quick/strict 质量门禁并输出 report-card.json。
|
|
16
|
+
operations/release.js: 读取通过的 report-card,生成 RELEASE_NOTE 并标记 released。
|
|
17
|
+
operations/janitor.js: 清理过期 runtime 工件,保留运行中任务状态。
|
|
18
|
+
|
|
19
|
+
法则: 成员完整·一行一文件·父级链接·技术词前置
|
|
20
|
+
|
|
21
|
+
[PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: 依赖 operations 子模块与命令行参数。
|
|
3
|
+
* [OUTPUT]: 对外提供 runCli(argv) 统一命令分发入口。
|
|
4
|
+
* [POS]: harness 命令编排层,被 bin/harness.js 调用。
|
|
5
|
+
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
resolveRepoRoot,
|
|
10
|
+
ChangeIdSchema
|
|
11
|
+
} = require('./index');
|
|
12
|
+
const { runInit } = require('./operations/init');
|
|
13
|
+
const { runPack } = require('./operations/pack');
|
|
14
|
+
const { runPlan } = require('./operations/plan');
|
|
15
|
+
const { runDispatch } = require('./operations/dispatch');
|
|
16
|
+
const { runResume } = require('./operations/resume');
|
|
17
|
+
const { runVerify } = require('./operations/verify');
|
|
18
|
+
const { runRelease } = require('./operations/release');
|
|
19
|
+
const { runJanitor } = require('./operations/janitor');
|
|
20
|
+
|
|
21
|
+
function parseArgs(argv) {
|
|
22
|
+
const [command, ...rest] = argv;
|
|
23
|
+
const args = {
|
|
24
|
+
command,
|
|
25
|
+
changeId: null,
|
|
26
|
+
goal: null,
|
|
27
|
+
parallel: 3,
|
|
28
|
+
maxRetries: undefined,
|
|
29
|
+
strict: false,
|
|
30
|
+
skipReview: false,
|
|
31
|
+
overwrite: false,
|
|
32
|
+
hours: 72
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
36
|
+
const token = rest[i];
|
|
37
|
+
|
|
38
|
+
if (token === '--change-id') {
|
|
39
|
+
args.changeId = rest[i + 1];
|
|
40
|
+
i += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (token === '--goal') {
|
|
45
|
+
args.goal = rest[i + 1];
|
|
46
|
+
i += 1;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (token === '--parallel') {
|
|
51
|
+
args.parallel = Number.parseInt(rest[i + 1], 10);
|
|
52
|
+
i += 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (token === '--max-retries') {
|
|
57
|
+
args.maxRetries = Number.parseInt(rest[i + 1], 10);
|
|
58
|
+
i += 1;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (token === '--hours') {
|
|
63
|
+
args.hours = Number.parseInt(rest[i + 1], 10);
|
|
64
|
+
i += 1;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (token === '--strict') {
|
|
69
|
+
args.strict = true;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (token === '--skip-review') {
|
|
74
|
+
args.skipReview = true;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (token === '--overwrite') {
|
|
79
|
+
args.overwrite = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return args;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function showHelp() {
|
|
88
|
+
console.log(`
|
|
89
|
+
Usage: node bin/harness.js <command> [options]
|
|
90
|
+
|
|
91
|
+
Commands:
|
|
92
|
+
init --change-id <REQ-ID> [--goal "..."]
|
|
93
|
+
pack --change-id <REQ-ID> [--goal "..."]
|
|
94
|
+
plan --change-id <REQ-ID> [--goal "..."] [--overwrite]
|
|
95
|
+
dispatch --change-id <REQ-ID> [--parallel 3] [--max-retries 2]
|
|
96
|
+
resume --change-id <REQ-ID> [--parallel 3] [--max-retries 2]
|
|
97
|
+
verify --change-id <REQ-ID> [--strict] [--skip-review]
|
|
98
|
+
release --change-id <REQ-ID>
|
|
99
|
+
janitor [--hours 72]
|
|
100
|
+
`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function requireChangeId(changeId) {
|
|
104
|
+
const parsed = ChangeIdSchema.safeParse(changeId);
|
|
105
|
+
if (!parsed.success) {
|
|
106
|
+
throw new Error('Missing or invalid --change-id (expected REQ-123 or BUG-123)');
|
|
107
|
+
}
|
|
108
|
+
return parsed.data;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function printResult(result) {
|
|
112
|
+
console.log(JSON.stringify(result, null, 2));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function runCli(argv) {
|
|
116
|
+
const args = parseArgs(argv);
|
|
117
|
+
|
|
118
|
+
if (!args.command || args.command === 'help' || args.command === '--help' || args.command === '-h') {
|
|
119
|
+
showHelp();
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const repoRoot = resolveRepoRoot(process.cwd());
|
|
124
|
+
|
|
125
|
+
switch (args.command) {
|
|
126
|
+
case 'init': {
|
|
127
|
+
const changeId = requireChangeId(args.changeId);
|
|
128
|
+
const result = await runInit({ repoRoot, changeId, goal: args.goal });
|
|
129
|
+
printResult(result);
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
case 'pack': {
|
|
134
|
+
const changeId = requireChangeId(args.changeId);
|
|
135
|
+
const result = await runPack({ repoRoot, changeId, goal: args.goal });
|
|
136
|
+
printResult(result);
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
case 'plan': {
|
|
141
|
+
const changeId = requireChangeId(args.changeId);
|
|
142
|
+
const result = await runPlan({
|
|
143
|
+
repoRoot,
|
|
144
|
+
changeId,
|
|
145
|
+
goal: args.goal,
|
|
146
|
+
overwrite: args.overwrite
|
|
147
|
+
});
|
|
148
|
+
printResult(result);
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
case 'dispatch': {
|
|
153
|
+
const changeId = requireChangeId(args.changeId);
|
|
154
|
+
const result = await runDispatch({
|
|
155
|
+
repoRoot,
|
|
156
|
+
changeId,
|
|
157
|
+
parallel: args.parallel,
|
|
158
|
+
maxRetries: Number.isInteger(args.maxRetries) ? args.maxRetries : undefined
|
|
159
|
+
});
|
|
160
|
+
printResult(result);
|
|
161
|
+
return result.success ? 0 : 1;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
case 'resume': {
|
|
165
|
+
const changeId = requireChangeId(args.changeId);
|
|
166
|
+
const result = await runResume({
|
|
167
|
+
repoRoot,
|
|
168
|
+
changeId,
|
|
169
|
+
parallel: args.parallel,
|
|
170
|
+
maxRetries: Number.isInteger(args.maxRetries) ? args.maxRetries : undefined
|
|
171
|
+
});
|
|
172
|
+
printResult(result);
|
|
173
|
+
return result.success ? 0 : 1;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
case 'verify': {
|
|
177
|
+
const changeId = requireChangeId(args.changeId);
|
|
178
|
+
const result = await runVerify({
|
|
179
|
+
repoRoot,
|
|
180
|
+
changeId,
|
|
181
|
+
strict: args.strict,
|
|
182
|
+
skipReview: args.skipReview
|
|
183
|
+
});
|
|
184
|
+
printResult(result);
|
|
185
|
+
return result.overall === 'pass' ? 0 : 1;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
case 'release': {
|
|
189
|
+
const changeId = requireChangeId(args.changeId);
|
|
190
|
+
const result = await runRelease({ repoRoot, changeId });
|
|
191
|
+
printResult(result);
|
|
192
|
+
return 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
case 'janitor': {
|
|
196
|
+
const result = await runJanitor({ repoRoot, hours: args.hours });
|
|
197
|
+
printResult(result);
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
default:
|
|
202
|
+
throw new Error(`Unknown harness command: ${args.command}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
module.exports = {
|
|
207
|
+
runCli
|
|
208
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: 依赖 harness 各模块。
|
|
3
|
+
* [OUTPUT]: 统一导出 schema/store/planner 与 operations 入口。
|
|
4
|
+
* [POS]: harness 模块聚合出口,被 bin 与测试代码使用。
|
|
5
|
+
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const store = require('./store');
|
|
9
|
+
const schemas = require('./schemas');
|
|
10
|
+
const planner = require('./planner');
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
...store,
|
|
14
|
+
...schemas,
|
|
15
|
+
...planner
|
|
16
|
+
};
|