cc-devflow 4.1.4 → 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.
Files changed (128) hide show
  1. package/.claude/CLAUDE.md +87 -1183
  2. package/.claude/commands/core/architecture.md +2 -2
  3. package/.claude/commands/core/guidelines.md +2 -2
  4. package/.claude/commands/core/roadmap.md +4 -4
  5. package/.claude/commands/core/style.md +40 -268
  6. package/.claude/commands/flow/CLAUDE.md +28 -0
  7. package/.claude/commands/flow/archive.md +2 -2
  8. package/.claude/commands/flow/checklist.md +9 -251
  9. package/.claude/commands/flow/clarify.md +9 -127
  10. package/.claude/commands/flow/constitution.md +1 -1
  11. package/.claude/commands/flow/context.md +1 -1
  12. package/.claude/commands/flow/dev.md +19 -395
  13. package/.claude/commands/flow/fix.md +1 -6
  14. package/.claude/commands/flow/ideate.md +13 -13
  15. package/.claude/commands/flow/init.md +19 -41
  16. package/.claude/commands/flow/new.md +12 -268
  17. package/.claude/commands/flow/quality.md +10 -153
  18. package/.claude/commands/flow/release.md +18 -131
  19. package/.claude/commands/flow/restart.md +15 -16
  20. package/.claude/commands/flow/spec.md +14 -164
  21. package/.claude/commands/flow/status.md +12 -12
  22. package/.claude/commands/flow/update.md +4 -4
  23. package/.claude/commands/flow/upgrade.md +6 -6
  24. package/.claude/commands/flow/verify.md +19 -78
  25. package/.claude/commands/flow/workspace.md +3 -20
  26. package/.claude/docs/guides/INIT_TROUBLESHOOTING.md +7 -7
  27. package/.claude/docs/guides/NEW_TROUBLESHOOTING.md +44 -96
  28. package/.claude/docs/guides/ROADMAP_TROUBLESHOOTING.md +1 -1
  29. package/.claude/docs/guides/TASK_COMPLETION_MARKING.md +5 -5
  30. package/.claude/docs/guides/TEAM_MODE_GUIDE.md +0 -1
  31. package/.claude/docs/templates/ATTEMPT_TEMPLATE.md +1 -1
  32. package/.claude/docs/templates/BACKLOG_TEMPLATE.md +3 -3
  33. package/.claude/docs/templates/CLARIFICATION_REPORT_TEMPLATE.md +5 -5
  34. package/.claude/docs/templates/ERROR_LOG_TEMPLATE.md +2 -2
  35. package/.claude/docs/templates/INIT_FLOW_TEMPLATE.md +3 -3
  36. package/.claude/docs/templates/NEW_ORCHESTRATION_TEMPLATE.md +33 -64
  37. package/.claude/docs/templates/RESEARCH_TEMPLATE.md +3 -3
  38. package/.claude/docs/templates/ROADMAP_DIALOGUE_TEMPLATE.md +2 -2
  39. package/.claude/docs/templates/ROADMAP_TEMPLATE.md +2 -2
  40. package/.claude/docs/templates/STYLE_TEMPLATE.md +3 -3
  41. package/.claude/docs/templates/UI_PROTOTYPE_TEMPLATE.md +8 -9
  42. package/.claude/guides/workflow-guides/flow-orchestrator.md +31 -265
  43. package/.claude/hooks/CLAUDE.md +1 -1
  44. package/.claude/hooks/checklist-gate.js +4 -4
  45. package/.claude/hooks/inject-agent-context.ts +2 -2
  46. package/.claude/hooks/teammate-idle-hook.ts +1 -1
  47. package/.claude/rules/devflow-conventions.md +2 -93
  48. package/.claude/scripts/CLAUDE.md +1 -4
  49. package/.claude/scripts/calculate-checklist-completion.sh +2 -2
  50. package/.claude/scripts/check-prerequisites.sh +2 -2
  51. package/.claude/scripts/checklist-errors.sh +4 -4
  52. package/.claude/scripts/common.sh +12 -147
  53. package/.claude/scripts/flow-quality-full.sh +5 -5
  54. package/.claude/scripts/flow-quality-quick.sh +4 -4
  55. package/.claude/scripts/flow-workspace-init.sh +2 -2
  56. package/.claude/scripts/generate-clarification-report.sh +4 -4
  57. package/.claude/scripts/recover-workflow.sh +70 -73
  58. package/.claude/scripts/run-quality-gates.sh +1 -1
  59. package/.claude/scripts/setup-epic.sh +2 -2
  60. package/.claude/scripts/setup-ralph-loop.sh +2 -2
  61. package/.claude/scripts/validate-research.sh +1 -1
  62. package/.claude/scripts/verify-setup.sh +1 -1
  63. package/.claude/skills/cc-devflow-orchestrator/SKILL.md +88 -108
  64. package/.claude/skills/workflow/CLAUDE.md +24 -0
  65. package/.claude/skills/workflow/flow-dev/CLAUDE.md +14 -76
  66. package/.claude/skills/workflow/flow-dev/SKILL.md +29 -67
  67. package/.claude/skills/workflow/flow-dev/context.jsonl +4 -8
  68. package/.claude/skills/workflow/flow-init/SKILL.md +23 -186
  69. package/.claude/skills/workflow/flow-init/assets/RESEARCH_TEMPLATE.md +1 -1
  70. package/.claude/skills/workflow/flow-init/context.jsonl +3 -3
  71. package/.claude/skills/workflow/flow-init/scripts/check-prerequisites.sh +1 -1
  72. package/.claude/skills/workflow/flow-init/scripts/create-requirement.sh +15 -134
  73. package/.claude/skills/workflow/flow-init/scripts/validate-research.sh +1 -1
  74. package/.claude/skills/workflow/flow-release/SKILL.md +20 -110
  75. package/.claude/skills/workflow/flow-release/context.jsonl +5 -7
  76. package/.claude/skills/workflow/flow-spec/CLAUDE.md +15 -101
  77. package/.claude/skills/workflow/flow-spec/SKILL.md +15 -518
  78. package/.claude/skills/workflow/flow-spec/context.jsonl +5 -7
  79. package/.claude/skills/workflow/flow-verify/CLAUDE.md +10 -0
  80. package/.claude/skills/workflow/flow-verify/SKILL.md +53 -0
  81. package/.claude/skills/workflow/flow-verify/context.jsonl +5 -0
  82. package/.claude/skills/workflow.yaml +72 -270
  83. package/CHANGELOG.md +72 -0
  84. package/README.md +91 -69
  85. package/README.zh-CN.md +90 -67
  86. package/bin/harness.js +22 -0
  87. package/docs/commands/README.md +34 -38
  88. package/docs/commands/README.zh-CN.md +34 -36
  89. package/docs/commands/core-roadmap.md +2 -2
  90. package/docs/commands/core-roadmap.zh-CN.md +2 -2
  91. package/docs/commands/core-style.md +29 -381
  92. package/docs/commands/core-style.zh-CN.md +29 -381
  93. package/docs/commands/flow-init.md +10 -10
  94. package/docs/commands/flow-init.zh-CN.md +11 -11
  95. package/docs/commands/flow-new.md +25 -260
  96. package/docs/commands/flow-new.zh-CN.md +26 -257
  97. package/docs/guides/getting-started.md +16 -15
  98. package/docs/guides/getting-started.zh-CN.md +10 -12
  99. package/lib/compiler/__tests__/manifest.test.js +156 -0
  100. package/lib/compiler/__tests__/parser.test.js +21 -0
  101. package/lib/compiler/index.js +17 -1
  102. package/lib/compiler/manifest.js +68 -6
  103. package/lib/compiler/parser.js +5 -0
  104. package/lib/harness/CLAUDE.md +21 -0
  105. package/lib/harness/cli.js +208 -0
  106. package/lib/harness/index.js +16 -0
  107. package/lib/harness/operations/dispatch.js +285 -0
  108. package/lib/harness/operations/init.js +48 -0
  109. package/lib/harness/operations/janitor.js +74 -0
  110. package/lib/harness/operations/pack.js +100 -0
  111. package/lib/harness/operations/plan.js +29 -0
  112. package/lib/harness/operations/release.js +83 -0
  113. package/lib/harness/operations/resume.js +44 -0
  114. package/lib/harness/operations/verify.js +163 -0
  115. package/lib/harness/planner.js +141 -0
  116. package/lib/harness/schemas.js +108 -0
  117. package/lib/harness/store.js +240 -0
  118. package/package.json +9 -1
  119. package/.claude/scripts/flow-workspace-start.sh +0 -217
  120. package/.claude/scripts/flow-workspace-switch.sh +0 -234
  121. package/.claude/skills/domain/using-git-worktrees/SKILL.md +0 -252
  122. package/.claude/skills/domain/using-git-worktrees/assets/SHELL_ALIASES.md +0 -133
  123. package/.claude/skills/domain/using-git-worktrees/context.jsonl +0 -4
  124. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-cleanup.sh +0 -218
  125. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-create.sh +0 -232
  126. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-list.sh +0 -130
  127. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-status.sh +0 -140
  128. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-switch.sh +0 -70
@@ -42,37 +42,35 @@ python3 .claude/scripts/demo.py
42
42
  ### 1. 启动需求开发
43
43
 
44
44
  ```bash
45
- /flow-new "REQ-001|用户认证功能|https://docs.example.com/auth"
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-status REQ-001
55
+ /flow:status REQ-001
52
56
  ```
53
57
 
54
58
  ### 3. 如果中断,恢复开发
55
59
 
56
60
  ```bash
57
- /flow-restart "REQ-001"
61
+ /flow:dev "REQ-001" --resume
58
62
  ```
59
63
 
60
64
  ### 4. 验证一致性
61
65
 
62
66
  ```bash
63
- /flow-verify "REQ-001"
67
+ /flow:verify "REQ-001" --strict
64
68
  ```
65
69
 
66
- ### 5. 执行 QA
70
+ ### 5. 创建发布
67
71
 
68
72
  ```bash
69
- /flow-qa "REQ-001"
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');
@@ -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 = path.relative(outputBaseDir, ir.source.path);
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) {
@@ -31,13 +31,19 @@ function hashContent(content) {
31
31
  // createEntry - 创建 manifest 条目
32
32
  // ============================================================
33
33
  function createEntry({ source, target, sourceHash, targetHash, platform }) {
34
- return {
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 entry = manifest.entries.find(e => e.source === sourcePath && e.platform === platform);
76
- if (!entry) {
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.hash !== sourceHash;
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(e => e.source === entry.source && e.platform === entry.platform);
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
  };
@@ -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
+ };