cc-devflow 4.1.6 → 4.3.0

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/commands/core/architecture.md +30 -0
  2. package/.claude/commands/core/guidelines.md +25 -0
  3. package/.claude/commands/core/roadmap.md +29 -0
  4. package/.claude/commands/core/style.md +18 -0
  5. package/.claude/commands/flow/CLAUDE.md +0 -4
  6. package/.claude/docs/examples/design-inspiration-pool.md +59 -0
  7. package/.claude/docs/examples/ui-prototype-constitution-checklist.md +75 -0
  8. package/.claude/docs/implementation-summary-v7.md +449 -0
  9. package/.claude/docs/spec-format-guide.md +349 -0
  10. package/.claude/docs/state-consolidation-design.md +323 -0
  11. package/.claude/docs/templates/ARCHITECTURE_TEMPLATE.md +85 -386
  12. package/.claude/docs/templates/DESIGN_TEMPLATE.md +157 -0
  13. package/.claude/docs/templates/PROPOSAL_TEMPLATE.md +91 -0
  14. package/.claude/docs/templates/SPEC_TEMPLATE_DELTA.md +139 -0
  15. package/.claude/docs/templates/SPEC_TEMPLATE_PROJECT.md +93 -0
  16. package/.claude/docs/templates/STYLE_TEMPLATE.md +114 -901
  17. package/.claude/docs/templates/UI_PROTOTYPE_TEMPLATE.md +143 -1205
  18. package/.claude/hooks/inject-agent-context.ts +9 -9
  19. package/.claude/scripts/.claude/commands/flow/export-openspec.md +221 -0
  20. package/.claude/scripts/.claude/commands/flow/import-openspec.md +171 -0
  21. package/.claude/scripts/__tests__/openspec.test.js +212 -0
  22. package/.claude/scripts/delta-parser.ts +112 -2
  23. package/.claude/scripts/export-openspec.js +222 -0
  24. package/.claude/scripts/import-openspec.js +272 -0
  25. package/.claude/scripts/validate-scope.sh +200 -0
  26. package/.claude/skills/cc-devflow-orchestrator/SKILL.md +25 -0
  27. package/.claude/skills/flow-dev/SKILL.md +94 -0
  28. package/.claude/skills/flow-init/SKILL.md +105 -0
  29. package/.claude/skills/{workflow/flow-release → flow-release}/SKILL.md +14 -3
  30. package/.claude/skills/flow-spec/SKILL.md +102 -0
  31. package/.claude/skills/utility/npm-release/CLAUDE.md +55 -0
  32. package/.claude/skills/utility/npm-release/SKILL.md +111 -46
  33. package/.claude/skills/utility/npm-release/references/version-decision-guide.md +134 -0
  34. package/.claude/skills/utility/npm-release/scripts/atomic-version-bump.sh +95 -0
  35. package/.claude/skills/utility/npm-release/scripts/validate-version-sync.sh +82 -0
  36. package/.claude/skills/utility/npm-release/scripts/version-decision-tree.sh +44 -0
  37. package/.claude/tsc-cache/70d2fc6d-2936-429b-b529-429f1aae8c88/affected-repos.txt +1 -0
  38. package/.claude/tsc-cache/70d2fc6d-2936-429b-b529-429f1aae8c88/edited-files.log +2 -0
  39. package/CHANGELOG.md +81 -0
  40. package/README.md +7 -1
  41. package/README.zh-CN.md +7 -1
  42. package/bin/cc-devflow-cli.js +154 -0
  43. package/docs/v4.3.0-migration-guide.md +276 -0
  44. package/lib/harness/CLAUDE.md +5 -4
  45. package/lib/harness/__tests__/planner.tdd.test.js +125 -0
  46. package/lib/harness/index.js +4 -2
  47. package/lib/harness/operations/dispatch.js +13 -0
  48. package/lib/harness/operations/plan.js +55 -1
  49. package/lib/harness/operations/release.js +87 -0
  50. package/lib/harness/operations/verify.js +14 -0
  51. package/lib/harness/planner.js +131 -0
  52. package/lib/harness/query.js +126 -0
  53. package/lib/harness/schemas.js +22 -1
  54. package/package.json +1 -1
  55. package/.claude/commands/flow/checklist.md +0 -18
  56. package/.claude/commands/flow/clarify.md +0 -18
  57. package/.claude/commands/flow/new.md +0 -23
  58. package/.claude/commands/flow/quality.md +0 -21
  59. package/.claude/docs/templates/EPIC_TEMPLATE.md +0 -805
  60. package/.claude/docs/templates/PRD_TEMPLATE.md +0 -562
  61. package/.claude/docs/templates/TASKS_TEMPLATE.md +0 -523
  62. package/.claude/docs/templates/TECH_DESIGN_TEMPLATE.md +0 -1019
  63. package/.claude/skills/workflow/CLAUDE.md +0 -24
  64. package/.claude/skills/workflow/flow-dev/SKILL.md +0 -58
  65. package/.claude/skills/workflow/flow-init/SKILL.md +0 -55
  66. package/.claude/skills/workflow/flow-spec/SKILL.md +0 -42
  67. /package/.claude/skills/{domain/attention-refresh → attention-refresh}/SKILL.md +0 -0
  68. /package/.claude/skills/{domain/brainstorming → brainstorming}/SKILL.md +0 -0
  69. /package/.claude/skills/{guardrail/constitution-guardian → constitution-guardian}/SKILL.md +0 -0
  70. /package/.claude/skills/{utility/constitution-quick-ref → constitution-quick-ref}/SKILL.md +0 -0
  71. /package/.claude/skills/{domain/debugging → debugging}/SKILL.md +0 -0
  72. /package/.claude/skills/{utility/file-standards → file-standards}/SKILL.md +0 -0
  73. /package/.claude/skills/{domain/finishing-branch → finishing-branch}/SKILL.md +0 -0
  74. /package/.claude/skills/{workflow/flow-dev → flow-dev}/CLAUDE.md +0 -0
  75. /package/.claude/skills/{workflow/flow-dev → flow-dev}/assets/IMPLEMENTATION_PLAN_TEMPLATE.md +0 -0
  76. /package/.claude/skills/{workflow/flow-dev → flow-dev}/context.jsonl +0 -0
  77. /package/.claude/skills/{workflow/flow-dev → flow-dev}/dev-implementer.jsonl +0 -0
  78. /package/.claude/skills/{workflow/flow-dev → flow-dev}/scripts/entry-gate.sh +0 -0
  79. /package/.claude/skills/{workflow/flow-dev → flow-dev}/scripts/exit-gate.sh +0 -0
  80. /package/.claude/skills/{workflow/flow-dev → flow-dev}/scripts/task-orchestrator.sh +0 -0
  81. /package/.claude/skills/{workflow/flow-fix → flow-fix}/SKILL.md +0 -0
  82. /package/.claude/skills/{workflow/flow-fix → flow-fix}/context.jsonl +0 -0
  83. /package/.claude/skills/{workflow/flow-fix → flow-fix}/references/bug-analyzer.md +0 -0
  84. /package/.claude/skills/{workflow/flow-init → flow-init}/assets/BRAINSTORM_TEMPLATE.md +0 -0
  85. /package/.claude/skills/{workflow/flow-init → flow-init}/assets/INIT_FLOW_TEMPLATE.md +0 -0
  86. /package/.claude/skills/{workflow/flow-init → flow-init}/assets/RESEARCH_TEMPLATE.md +0 -0
  87. /package/.claude/skills/{workflow/flow-init → flow-init}/context.jsonl +0 -0
  88. /package/.claude/skills/{workflow/flow-init → flow-init}/references/flow-researcher.md +0 -0
  89. /package/.claude/skills/{workflow/flow-init → flow-init}/scripts/check-prerequisites.sh +0 -0
  90. /package/.claude/skills/{workflow/flow-init → flow-init}/scripts/consolidate-research.sh +0 -0
  91. /package/.claude/skills/{workflow/flow-init → flow-init}/scripts/create-requirement.sh +0 -0
  92. /package/.claude/skills/{workflow/flow-init → flow-init}/scripts/generate-research-tasks.sh +0 -0
  93. /package/.claude/skills/{workflow/flow-init → flow-init}/scripts/populate-research-tasks.sh +0 -0
  94. /package/.claude/skills/{workflow/flow-init → flow-init}/scripts/validate-research.sh +0 -0
  95. /package/.claude/skills/{workflow/flow-quality → flow-quality}/SKILL.md +0 -0
  96. /package/.claude/skills/{workflow/flow-quality → flow-quality}/context.jsonl +0 -0
  97. /package/.claude/skills/{workflow/flow-quality → flow-quality}/references/code-quality-reviewer.md +0 -0
  98. /package/.claude/skills/{workflow/flow-quality → flow-quality}/references/qa-tester.md +0 -0
  99. /package/.claude/skills/{workflow/flow-quality → flow-quality}/references/security-reviewer.md +0 -0
  100. /package/.claude/skills/{workflow/flow-quality → flow-quality}/references/spec-reviewer.md +0 -0
  101. /package/.claude/skills/{workflow/flow-release → flow-release}/context.jsonl +0 -0
  102. /package/.claude/skills/{workflow/flow-release → flow-release}/references/release-manager.md +0 -0
  103. /package/.claude/skills/{workflow/flow-spec → flow-spec}/CLAUDE.md +0 -0
  104. /package/.claude/skills/{workflow/flow-spec → flow-spec}/context.jsonl +0 -0
  105. /package/.claude/skills/{workflow/flow-spec → flow-spec}/scripts/entry-gate.sh +0 -0
  106. /package/.claude/skills/{workflow/flow-spec → flow-spec}/scripts/exit-gate.sh +0 -0
  107. /package/.claude/skills/{workflow/flow-spec → flow-spec}/scripts/parallel-orchestrator.sh +0 -0
  108. /package/.claude/skills/{workflow/flow-spec → flow-spec}/scripts/team-communication.sh +0 -0
  109. /package/.claude/skills/{workflow/flow-spec → flow-spec}/scripts/team-init.sh +0 -0
  110. /package/.claude/skills/{workflow/flow-spec → flow-spec}/scripts/test-team-mode.sh +0 -0
  111. /package/.claude/skills/{workflow/flow-spec → flow-spec}/team-config.json +0 -0
  112. /package/.claude/skills/{workflow/flow-verify → flow-verify}/CLAUDE.md +0 -0
  113. /package/.claude/skills/{workflow/flow-verify → flow-verify}/SKILL.md +0 -0
  114. /package/.claude/skills/{workflow/flow-verify → flow-verify}/context.jsonl +0 -0
  115. /package/.claude/skills/{utility/fractal-docs → fractal-docs}/SKILL.md +0 -0
  116. /package/.claude/skills/{utility/journey-checker → journey-checker}/SKILL.md +0 -0
  117. /package/.claude/skills/{utility/journey-checker → journey-checker}/pressure-scenarios.md +0 -0
  118. /package/.claude/skills/{domain/receiving-review → receiving-review}/SKILL.md +0 -0
  119. /package/.claude/skills/{utility/skill-creator → skill-creator}/LICENSE.txt +0 -0
  120. /package/.claude/skills/{utility/skill-creator → skill-creator}/SKILL.md +0 -0
  121. /package/.claude/skills/{utility/skill-creator → skill-creator}/references/output-patterns.md +0 -0
  122. /package/.claude/skills/{utility/skill-creator → skill-creator}/references/workflows.md +0 -0
  123. /package/.claude/skills/{utility/skill-creator → skill-creator}/scripts/init_skill.py +0 -0
  124. /package/.claude/skills/{utility/skill-creator → skill-creator}/scripts/package_skill.py +0 -0
  125. /package/.claude/skills/{utility/skill-creator → skill-creator}/scripts/quick_validate.py +0 -0
  126. /package/.claude/skills/{domain/tdd → tdd}/SKILL.md +0 -0
  127. /package/.claude/skills/{guardrail/tdd-enforcer → tdd-enforcer}/SKILL.md +0 -0
  128. /package/.claude/skills/{domain/verification → verification}/SKILL.md +0 -0
@@ -0,0 +1,125 @@
1
+ /**
2
+ * TDD Order Validation Tests
3
+ *
4
+ * Tests for Constitution Article VI enforcement in planner.js
5
+ */
6
+
7
+ const { parseTasksMarkdown } = require('../planner');
8
+
9
+ describe('TDD Order Validation', () => {
10
+ describe('Valid TDD sequences', () => {
11
+ test('should accept TEST before IMPL with correct dependency', () => {
12
+ const markdown = `
13
+ - [ ] T001 [TEST] 用户登录功能测试
14
+ - [ ] T002 [IMPL] 用户登录功能实现 (dependsOn:T001)
15
+ `.trim();
16
+
17
+ expect(() => parseTasksMarkdown(markdown)).not.toThrow();
18
+ });
19
+
20
+ test('should accept multiple TEST-IMPL pairs', () => {
21
+ const markdown = `
22
+ - [ ] T001 [TEST] 用户登录测试
23
+ - [ ] T002 [IMPL] 用户登录实现 (dependsOn:T001)
24
+ - [ ] T003 [TEST] 用户注册测试 [P]
25
+ - [ ] T004 [IMPL] 用户注册实现 (dependsOn:T003)
26
+ `.trim();
27
+
28
+ expect(() => parseTasksMarkdown(markdown)).not.toThrow();
29
+ });
30
+
31
+ test('should accept tasks without TEST/IMPL markers', () => {
32
+ const markdown = `
33
+ - [ ] T001 初始化项目结构
34
+ - [ ] T002 配置开发环境
35
+ `.trim();
36
+
37
+ expect(() => parseTasksMarkdown(markdown)).not.toThrow();
38
+ });
39
+ });
40
+
41
+ describe('TDD violations', () => {
42
+ test('should reject IMPL without corresponding TEST', () => {
43
+ const markdown = `
44
+ - [ ] T001 [IMPL] 用户登录功能实现
45
+ `.trim();
46
+
47
+ expect(() => parseTasksMarkdown(markdown)).toThrow(
48
+ /missing corresponding TEST task/
49
+ );
50
+ });
51
+
52
+ test('should reject IMPL not depending on TEST', () => {
53
+ const markdown = `
54
+ - [ ] T001 [TEST] 用户登录功能测试
55
+ - [ ] T002 [IMPL] 用户登录功能实现 [P]
56
+ `.trim();
57
+
58
+ expect(() => parseTasksMarkdown(markdown)).toThrow(
59
+ /must depend on T001/
60
+ );
61
+ });
62
+
63
+ test('should reject TEST depending on IMPL', () => {
64
+ const markdown = `
65
+ - [ ] T001 [IMPL] 用户登录功能实现
66
+ - [ ] T002 [TEST] 用户登录功能测试 (dependsOn:T001)
67
+ `.trim();
68
+
69
+ expect(() => parseTasksMarkdown(markdown)).toThrow(
70
+ /Tests must be written BEFORE implementation/
71
+ );
72
+ });
73
+
74
+ test('should reject IMPL depending on wrong TEST', () => {
75
+ const markdown = `
76
+ - [ ] T001 [TEST] 用户注册测试
77
+ - [ ] T002 [IMPL] 用户登录实现 (dependsOn:T001)
78
+ `.trim();
79
+
80
+ expect(() => parseTasksMarkdown(markdown)).toThrow(
81
+ /missing corresponding TEST task/
82
+ );
83
+ });
84
+ });
85
+
86
+ describe('Feature name extraction', () => {
87
+ test('should match TEST and IMPL with same feature name', () => {
88
+ const markdown = `
89
+ - [ ] T001 [TEST] 实现用户认证功能
90
+ - [ ] T002 [IMPL] 实现用户认证功能 (dependsOn:T001)
91
+ `.trim();
92
+
93
+ expect(() => parseTasksMarkdown(markdown)).not.toThrow();
94
+ });
95
+
96
+ test('should handle case-insensitive markers', () => {
97
+ const markdown = `
98
+ - [ ] T001 [test] 用户登录
99
+ - [ ] T002 [impl] 用户登录 (dependsOn:T001)
100
+ `.trim();
101
+
102
+ expect(() => parseTasksMarkdown(markdown)).not.toThrow();
103
+ });
104
+ });
105
+
106
+ describe('Error messages', () => {
107
+ test('should provide clear violation details', () => {
108
+ const markdown = `
109
+ - [ ] T001 [IMPL] 功能A
110
+ - [ ] T002 [TEST] 功能B
111
+ - [ ] T003 [IMPL] 功能B
112
+ `.trim();
113
+
114
+ try {
115
+ parseTasksMarkdown(markdown);
116
+ fail('Should have thrown TDD violation error');
117
+ } catch (error) {
118
+ expect(error.message).toContain('TDD Order Validation Failed');
119
+ expect(error.message).toContain('Constitution Article VI');
120
+ expect(error.message).toContain('T001');
121
+ expect(error.message).toContain('missing corresponding TEST task');
122
+ }
123
+ });
124
+ });
125
+ });
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * [INPUT]: 依赖 harness 各模块。
3
- * [OUTPUT]: 统一导出 schema/store/planner 与 operations 入口。
3
+ * [OUTPUT]: 统一导出 schema/store/planner/query 与 operations 入口。
4
4
  * [POS]: harness 模块聚合出口,被 bin 与测试代码使用。
5
5
  * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
6
  */
@@ -8,9 +8,11 @@
8
8
  const store = require('./store');
9
9
  const schemas = require('./schemas');
10
10
  const planner = require('./planner');
11
+ const query = require('./query');
11
12
 
12
13
  module.exports = {
13
14
  ...store,
14
15
  ...schemas,
15
- ...planner
16
+ ...planner,
17
+ ...query
16
18
  };
@@ -13,6 +13,7 @@ const {
13
13
  readJson,
14
14
  runCommand,
15
15
  getTaskManifestPath,
16
+ getHarnessStatePath,
16
17
  getRuntimeTaskDir,
17
18
  getEventsPath,
18
19
  getCheckpointPath
@@ -203,6 +204,18 @@ async function runDispatch({ repoRoot, changeId, parallel = 3, maxRetries, resum
203
204
  const manifestPath = getTaskManifestPath(repoRoot, changeId);
204
205
  const manifest = parseManifest(await readJson(manifestPath));
205
206
 
207
+ // Update harness-state.json to in_progress on first dispatch
208
+ const statePath = getHarnessStatePath(repoRoot, changeId);
209
+ const stateExists = require('fs').existsSync(statePath);
210
+ if (stateExists) {
211
+ const state = await readJson(statePath);
212
+ if (state.status === 'planned' || state.status === 'initialized') {
213
+ state.status = 'in_progress';
214
+ state.updatedAt = nowIso();
215
+ await writeJson(statePath, state);
216
+ }
217
+ }
218
+
206
219
  if (resume) {
207
220
  for (const task of manifest.tasks) {
208
221
  if (task.status === 'running') {
@@ -6,9 +6,53 @@
6
6
  */
7
7
 
8
8
  const { createTaskManifest } = require('../planner');
9
- const { getTaskManifestPath } = require('../store');
9
+ const { getTaskManifestPath, getHarnessStatePath, exists, readText, readJson, writeJson } = require('../store');
10
+ const path = require('path');
11
+ const { execSync } = require('child_process');
10
12
 
11
13
  async function runPlan({ repoRoot, changeId, goal, overwrite }) {
14
+ // v4.3: 检查是否存在 proposal.md(新架构)
15
+ const reqDir = path.join(repoRoot, 'devflow', 'requirements', changeId);
16
+ const proposalPath = path.join(reqDir, 'proposal.md');
17
+ const hasProposal = await exists(proposalPath);
18
+
19
+ if (hasProposal) {
20
+ console.log(`[v4.3] Detected proposal.md, generating design.md and Delta specs...`);
21
+
22
+ // 注意:实际的 design.md 和 Delta specs 生成由 Claude Agent 在 /flow:spec 中完成
23
+ // 这里只是检测并提示,不执行生成逻辑
24
+ // 生成逻辑在 .claude/skills/flow-spec/SKILL.md 的 Execution Steps 中定义
25
+
26
+ const designPath = path.join(reqDir, 'design.md');
27
+ const specsDir = path.join(reqDir, 'specs');
28
+ const scopeReportPath = path.join(reqDir, 'scope-creep-report.md');
29
+
30
+ // 检查必需产物是否存在
31
+ const hasDesign = await exists(designPath);
32
+ const hasSpecs = await exists(specsDir);
33
+ const hasScopeReport = await exists(scopeReportPath);
34
+
35
+ if (!hasDesign) {
36
+ console.warn(`[v4.3] WARNING: design.md not found. Expected at ${designPath}`);
37
+ }
38
+
39
+ if (!hasSpecs) {
40
+ console.warn(`[v4.3] WARNING: specs/ directory not found. Expected at ${specsDir}`);
41
+ }
42
+
43
+ if (!hasScopeReport) {
44
+ console.warn(`[v4.3] WARNING: scope-creep-report.md not found. Run validate-scope.sh`);
45
+ }
46
+
47
+ // 如果存在 scope-creep-report.md,检查是否有阻塞性警告
48
+ if (hasScopeReport) {
49
+ const reportContent = await readText(scopeReportPath);
50
+ if (reportContent.includes('⚠️') && reportContent.includes('Potential scope creep')) {
51
+ console.warn(`[v4.3] WARNING: Scope creep detected. Review ${scopeReportPath}`);
52
+ }
53
+ }
54
+ }
55
+
12
56
  const manifest = await createTaskManifest({
13
57
  repoRoot,
14
58
  changeId,
@@ -16,6 +60,16 @@ async function runPlan({ repoRoot, changeId, goal, overwrite }) {
16
60
  overwrite
17
61
  });
18
62
 
63
+ // Update harness-state.json with plannedAt timestamp
64
+ const statePath = getHarnessStatePath(repoRoot, changeId);
65
+ if (await exists(statePath)) {
66
+ const state = await readJson(statePath);
67
+ state.status = 'planned';
68
+ state.plannedAt = new Date().toISOString();
69
+ state.updatedAt = new Date().toISOString();
70
+ await writeJson(statePath, state);
71
+ }
72
+
19
73
  return {
20
74
  changeId,
21
75
  manifestPath: getTaskManifestPath(repoRoot, changeId),
@@ -10,12 +10,16 @@ const {
10
10
  readJson,
11
11
  writeText,
12
12
  writeJson,
13
+ exists,
13
14
  getReportCardPath,
14
15
  getTaskManifestPath,
15
16
  getReleaseNotePath,
16
17
  getHarnessStatePath
17
18
  } = require('../store');
18
19
  const { parseReportCard, parseManifest } = require('../schemas');
20
+ const path = require('path');
21
+ const { execSync } = require('child_process');
22
+ const fs = require('fs').promises;
19
23
 
20
24
  function formatReleaseNote({ changeId, manifest, report }) {
21
25
  const passedTasks = manifest.tasks.filter((task) => task.status === 'passed');
@@ -59,6 +63,89 @@ async function runRelease({ repoRoot, changeId }) {
59
63
  throw new Error('Release blocked: report-card overall is not pass');
60
64
  }
61
65
 
66
+ // v4.3: 合并 Delta specs 到项目级 specs/
67
+ const reqDir = path.join(repoRoot, 'devflow', 'requirements', changeId);
68
+ const deltaSpecsDir = path.join(reqDir, 'specs');
69
+ const projectSpecsDir = path.join(repoRoot, 'devflow', 'specs');
70
+ const hasDeltaSpecs = await exists(deltaSpecsDir);
71
+
72
+ if (hasDeltaSpecs) {
73
+ console.log(`[v4.3] Merging Delta specs to project-level specs/...`);
74
+
75
+ try {
76
+ // 遍历所有 Delta spec.md 文件
77
+ const modules = await fs.readdir(deltaSpecsDir);
78
+ const mergeResults = [];
79
+
80
+ for (const module of modules) {
81
+ const deltaSpecPath = path.join(deltaSpecsDir, module, 'spec.md');
82
+ const projectSpecPath = path.join(projectSpecsDir, module, 'spec.md');
83
+
84
+ if (!(await exists(deltaSpecPath))) {
85
+ console.warn(`[v4.3] WARNING: Delta spec not found: ${deltaSpecPath}`);
86
+ continue;
87
+ }
88
+
89
+ if (!(await exists(projectSpecPath))) {
90
+ console.warn(`[v4.3] WARNING: Project spec not found: ${projectSpecPath}`);
91
+ console.log(`[v4.3] Creating new project spec for module: ${module}`);
92
+
93
+ // 如果项目级 spec 不存在,创建目录并复制 Delta 作为初始版本
94
+ await fs.mkdir(path.dirname(projectSpecPath), { recursive: true });
95
+ await fs.copyFile(deltaSpecPath, projectSpecPath);
96
+
97
+ mergeResults.push({ module, status: 'created', newVersion: '1.0.0' });
98
+ continue;
99
+ }
100
+
101
+ // 调用 delta-parser.ts merge 命令
102
+ const deltaParserPath = path.join(repoRoot, '.claude', 'scripts', 'delta-parser.ts');
103
+
104
+ try {
105
+ const output = execSync(
106
+ `npx ts-node "${deltaParserPath}" merge "${projectSpecPath}" "${deltaSpecPath}"`,
107
+ { cwd: repoRoot, encoding: 'utf-8' }
108
+ );
109
+
110
+ // 解析输出获取新版本号
111
+ const versionMatch = output.match(/New version: ([\d.]+)/);
112
+ const newVersion = versionMatch ? versionMatch[1] : 'unknown';
113
+
114
+ mergeResults.push({ module, status: 'merged', newVersion });
115
+ console.log(`[v4.3] ✅ Merged ${module}: ${newVersion}`);
116
+ } catch (mergeError) {
117
+ console.error(`[v4.3] ❌ Failed to merge ${module}:`, mergeError.message);
118
+ mergeResults.push({ module, status: 'failed', error: mergeError.message });
119
+ }
120
+ }
121
+
122
+ // 记录合并结果到 RELEASE_NOTE
123
+ const mergeReport = mergeResults
124
+ .map(r => {
125
+ if (r.status === 'merged') {
126
+ return `- ✅ ${r.module}: merged to v${r.newVersion}`;
127
+ } else if (r.status === 'created') {
128
+ return `- 🆕 ${r.module}: created v${r.newVersion}`;
129
+ } else {
130
+ return `- ❌ ${r.module}: ${r.error}`;
131
+ }
132
+ })
133
+ .join('\n');
134
+
135
+ console.log(`[v4.3] Delta merge summary:\n${mergeReport}`);
136
+
137
+ // 将合并结果添加到 release note
138
+ manifest.metadata = manifest.metadata || {};
139
+ manifest.metadata.deltaMergeResults = mergeResults;
140
+
141
+ } catch (error) {
142
+ console.error(`[v4.3] ERROR during Delta merge:`, error);
143
+ throw new Error(`Delta merge failed: ${error.message}`);
144
+ }
145
+ } else {
146
+ console.log(`[v4.3] No Delta specs found, skipping merge.`);
147
+ }
148
+
62
149
  const note = formatReleaseNote({ changeId, manifest, report });
63
150
  const releaseNotePath = getReleaseNotePath(repoRoot, changeId);
64
151
 
@@ -12,6 +12,7 @@ const {
12
12
  writeJson,
13
13
  runCommand,
14
14
  getTaskManifestPath,
15
+ getHarnessStatePath,
15
16
  getReportCardPath
16
17
  } = require('../store');
17
18
  const { parseManifest, parseReportCard } = require('../schemas');
@@ -150,6 +151,19 @@ async function runVerify({ repoRoot, changeId, strict = false, skipReview = fals
150
151
  const outputPath = getReportCardPath(repoRoot, changeId);
151
152
  await writeJson(outputPath, report);
152
153
 
154
+ // Update harness-state.json with verifiedAt timestamp if passed
155
+ if (!hasFailures) {
156
+ const statePath = getHarnessStatePath(repoRoot, changeId);
157
+ const stateExists = require('fs').existsSync(statePath);
158
+ if (stateExists) {
159
+ const state = await readJson(statePath);
160
+ state.status = 'verified';
161
+ state.verifiedAt = nowIso();
162
+ state.updatedAt = nowIso();
163
+ await writeJson(statePath, state);
164
+ }
165
+ }
166
+
153
167
  return {
154
168
  changeId,
155
169
  outputPath,
@@ -72,9 +72,13 @@ function parseTasksMarkdown(content) {
72
72
  const dependsOn = parseDependsOn(tail, previousTaskId, isParallel);
73
73
  const title = normalizeTitle(tail).replace(TRAILING_PATHS, '').trim() || `Task ${taskId}`;
74
74
 
75
+ // 提取任务类型 [TEST] 或 [IMPL]
76
+ const taskType = extractTaskType(title);
77
+
75
78
  tasks.push({
76
79
  id: taskId,
77
80
  title,
81
+ type: taskType,
78
82
  dependsOn,
79
83
  touches,
80
84
  run: [`echo "[TASK ${taskId}] ${title}"`],
@@ -87,9 +91,136 @@ function parseTasksMarkdown(content) {
87
91
  previousTaskId = taskId;
88
92
  }
89
93
 
94
+ // TDD 顺序验证 (Constitution Article VI)
95
+ validateTDDOrder(tasks);
96
+
90
97
  return tasks;
91
98
  }
92
99
 
100
+ function extractTaskType(title) {
101
+ if (/\[TEST\]/i.test(title)) {
102
+ return 'TEST';
103
+ }
104
+ if (/\[IMPL\]/i.test(title)) {
105
+ return 'IMPL';
106
+ }
107
+ return 'OTHER';
108
+ }
109
+
110
+ /**
111
+ * TDD 顺序验证 (Constitution Article VI)
112
+ *
113
+ * 规则:
114
+ * 1. 每个 [IMPL] 任务必须有对应的 [TEST] 任务
115
+ * 2. [IMPL] 任务必须依赖对应的 [TEST] 任务 (通过 dependsOn)
116
+ * 3. [TEST] 任务不能依赖 [IMPL] 任务
117
+ *
118
+ * @param {Array} tasks - 任务列表
119
+ * @throws {Error} - TDD 顺序违规时抛出错误
120
+ */
121
+ function validateTDDOrder(tasks) {
122
+ const violations = [];
123
+ const testTasks = tasks.filter(t => t.type === 'TEST');
124
+ const implTasks = tasks.filter(t => t.type === 'IMPL');
125
+
126
+ // 为每个 IMPL 任务查找对应的 TEST 任务
127
+ for (const implTask of implTasks) {
128
+ // 提取功能名称 (去除 [IMPL] 标记后的主要描述)
129
+ const implFeature = extractFeatureName(implTask.title);
130
+
131
+ // 查找匹配的 TEST 任务 (使用模糊匹配)
132
+ const matchingTest = testTasks.find(testTask => {
133
+ const testFeature = extractFeatureName(testTask.title);
134
+ // 模糊匹配:检查是否包含相同的核心关键词
135
+ return isSimilarFeature(testFeature, implFeature);
136
+ });
137
+
138
+ if (!matchingTest) {
139
+ violations.push(
140
+ `Task ${implTask.id} (${implTask.title}) missing corresponding TEST task. ` +
141
+ `Constitution Article VI requires: NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST.`
142
+ );
143
+ continue;
144
+ }
145
+
146
+ // 检查依赖关系
147
+ if (!implTask.dependsOn.includes(matchingTest.id)) {
148
+ violations.push(
149
+ `Task ${implTask.id} (${implTask.title}) must depend on ${matchingTest.id} (${matchingTest.title}). ` +
150
+ `TDD violation: Implementation must come AFTER test.`
151
+ );
152
+ }
153
+ }
154
+
155
+ // 检查 TEST 任务不能依赖 IMPL 任务
156
+ for (const testTask of testTasks) {
157
+ const implDeps = testTask.dependsOn.filter(depId => {
158
+ const depTask = tasks.find(t => t.id === depId);
159
+ return depTask && depTask.type === 'IMPL';
160
+ });
161
+
162
+ if (implDeps.length > 0) {
163
+ violations.push(
164
+ `Task ${testTask.id} (${testTask.title}) depends on IMPL tasks: ${implDeps.join(', ')}. ` +
165
+ `TDD violation: Tests must be written BEFORE implementation.`
166
+ );
167
+ }
168
+ }
169
+
170
+ if (violations.length > 0) {
171
+ throw new Error(
172
+ `TDD Order Validation Failed (Constitution Article VI):\n\n` +
173
+ violations.map((v, i) => `${i + 1}. ${v}`).join('\n\n') +
174
+ `\n\nFix TASKS.md to follow TDD sequence: [TEST] tasks BEFORE [IMPL] tasks.`
175
+ );
176
+ }
177
+ }
178
+
179
+ /**
180
+ * 从任务标题中提取功能名称
181
+ * 例如: "[TEST] 用户登录功能" -> "用户登录功能"
182
+ * "[IMPL] 用户登录功能" -> "用户登录功能"
183
+ */
184
+ function extractFeatureName(title) {
185
+ return title
186
+ .replace(/\[TEST\]/gi, '')
187
+ .replace(/\[IMPL\]/gi, '')
188
+ .replace(/\[P\]/gi, '')
189
+ .replace(/\(dependsOn:[^)]*\)/gi, '')
190
+ .replace(/\([^)]*\)/g, '')
191
+ .trim();
192
+ }
193
+
194
+ /**
195
+ * 判断两个功能名称是否相似
196
+ * 使用核心关键词匹配策略
197
+ */
198
+ function isSimilarFeature(feature1, feature2) {
199
+ // 提取核心关键词 (去除常见后缀如"测试"、"实现"、"功能"等)
200
+ const normalize = (str) => str
201
+ .replace(/测试$/g, '')
202
+ .replace(/实现$/g, '')
203
+ .replace(/功能$/g, '')
204
+ .replace(/开发$/g, '')
205
+ .replace(/编写$/g, '')
206
+ .trim();
207
+
208
+ const core1 = normalize(feature1);
209
+ const core2 = normalize(feature2);
210
+
211
+ // 精确匹配
212
+ if (core1 === core2) {
213
+ return true;
214
+ }
215
+
216
+ // 包含匹配 (较长的包含较短的)
217
+ if (core1.length > core2.length) {
218
+ return core1.includes(core2);
219
+ } else {
220
+ return core2.includes(core1);
221
+ }
222
+ }
223
+
93
224
  function buildDefaultTasks(changeId) {
94
225
  return [
95
226
  {
@@ -0,0 +1,126 @@
1
+ /**
2
+ * [INPUT]: 依赖 store 读取 harness-state/task-manifest/report-card,接收 repoRoot 和 changeId。
3
+ * [OUTPUT]: 对外提供 getProgress/getNextTask/getFullState 查询函数,聚合分散的状态信息。
4
+ * [POS]: harness 查询工具层,被 CLI 与 skills 调用以获取聚合状态视图。
5
+ * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
+ */
7
+
8
+ const {
9
+ getHarnessStatePath,
10
+ getTaskManifestPath,
11
+ getReportCardPath,
12
+ readJson
13
+ } = require('./store');
14
+
15
+ /**
16
+ * 获取任务进度统计
17
+ * @param {string} repoRoot - 仓库根目录
18
+ * @param {string} changeId - 需求 ID
19
+ * @returns {Promise<Object>} 进度统计对象
20
+ */
21
+ async function getProgress(repoRoot, changeId) {
22
+ const manifestPath = getTaskManifestPath(repoRoot, changeId);
23
+
24
+ try {
25
+ const manifest = await readJson(manifestPath);
26
+
27
+ return {
28
+ totalTasks: manifest.tasks.length,
29
+ completedTasks: manifest.tasks.filter(t => t.status === 'passed').length,
30
+ failedTasks: manifest.tasks.filter(t => t.status === 'failed').length,
31
+ pendingTasks: manifest.tasks.filter(t => t.status === 'pending').length,
32
+ runningTasks: manifest.tasks.filter(t => t.status === 'running').length,
33
+ skippedTasks: manifest.tasks.filter(t => t.status === 'skipped').length
34
+ };
35
+ } catch (error) {
36
+ return {
37
+ totalTasks: 0,
38
+ completedTasks: 0,
39
+ failedTasks: 0,
40
+ pendingTasks: 0,
41
+ runningTasks: 0,
42
+ skippedTasks: 0,
43
+ error: error.message
44
+ };
45
+ }
46
+ }
47
+
48
+ /**
49
+ * 获取下一个待执行的任务
50
+ * @param {string} repoRoot - 仓库根目录
51
+ * @param {string} changeId - 需求 ID
52
+ * @returns {Promise<Object|null>} 下一个任务对象或 null
53
+ */
54
+ async function getNextTask(repoRoot, changeId) {
55
+ const manifestPath = getTaskManifestPath(repoRoot, changeId);
56
+
57
+ try {
58
+ const manifest = await readJson(manifestPath);
59
+
60
+ // 找到第一个 pending 状态的任务
61
+ const nextTask = manifest.tasks.find(t => t.status === 'pending');
62
+
63
+ return nextTask || null;
64
+ } catch (error) {
65
+ return null;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * 获取完整的聚合状态视图
71
+ * @param {string} repoRoot - 仓库根目录
72
+ * @param {string} changeId - 需求 ID
73
+ * @returns {Promise<Object>} 完整状态对象
74
+ */
75
+ async function getFullState(repoRoot, changeId) {
76
+ const statePath = getHarnessStatePath(repoRoot, changeId);
77
+ const reportPath = getReportCardPath(repoRoot, changeId);
78
+
79
+ try {
80
+ const state = await readJson(statePath);
81
+ const progress = await getProgress(repoRoot, changeId);
82
+ const nextTask = await getNextTask(repoRoot, changeId);
83
+
84
+ let report = null;
85
+ try {
86
+ report = await readJson(reportPath);
87
+ } catch {
88
+ // report-card.json 可能不存在(未执行 verify)
89
+ report = null;
90
+ }
91
+
92
+ return {
93
+ lifecycle: {
94
+ changeId: state.changeId,
95
+ goal: state.goal,
96
+ status: state.status,
97
+ initializedAt: state.initializedAt,
98
+ plannedAt: state.plannedAt,
99
+ verifiedAt: state.verifiedAt,
100
+ releasedAt: state.releasedAt,
101
+ updatedAt: state.updatedAt
102
+ },
103
+ progress,
104
+ nextTask,
105
+ quality: report ? {
106
+ overall: report.overall,
107
+ blockingFindings: report.blockingFindings,
108
+ timestamp: report.timestamp
109
+ } : null
110
+ };
111
+ } catch (error) {
112
+ return {
113
+ error: error.message,
114
+ lifecycle: null,
115
+ progress: null,
116
+ nextTask: null,
117
+ quality: null
118
+ };
119
+ }
120
+ }
121
+
122
+ module.exports = {
123
+ getProgress,
124
+ getNextTask,
125
+ getFullState
126
+ };