cc-devflow 2.4.6 → 4.1.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 (191) hide show
  1. package/.claude/CLAUDE.md +1065 -48
  2. package/.claude/agents/dev-implementer.md +195 -0
  3. package/.claude/commands/{flow-archive.md → flow/archive.md} +46 -11
  4. package/.claude/commands/flow/context.md +150 -0
  5. package/.claude/commands/flow/delta.md +245 -0
  6. package/.claude/commands/{flow-dev.md → flow/dev.md} +112 -11
  7. package/.claude/commands/flow/init.md +45 -0
  8. package/.claude/commands/flow/quality.md +159 -0
  9. package/.claude/commands/flow/spec.md +186 -0
  10. package/.claude/commands/flow/workspace.md +146 -0
  11. package/.claude/commands/{cancel-ralph.md → util/cancel-ralph.md} +1 -0
  12. package/.claude/config/quality-gates.yml +305 -0
  13. package/.claude/docs/guides/TEAM_MODE_GUIDE.md +313 -0
  14. package/.claude/docs/templates/DELTA_SPEC_TEMPLATE.md +91 -0
  15. package/.claude/docs/templates/DESIGN_DECISIONS_TEMPLATE.md +151 -0
  16. package/.claude/docs/templates/JOURNAL_TEMPLATE.md +75 -0
  17. package/.claude/docs/templates/_shared/CLAUDE.md +36 -0
  18. package/.claude/docs/templates/_shared/CONSTITUTION_CHECK.md +125 -0
  19. package/.claude/docs/templates/_shared/VALIDATION_CHECKLIST.md +187 -0
  20. package/.claude/docs/templates/_shared/YAML_FRONTMATTER.md +164 -0
  21. package/.claude/docs/templates/context/dev.jsonl.template +6 -0
  22. package/.claude/docs/templates/context/epic.jsonl.template +5 -0
  23. package/.claude/docs/templates/context/prd.jsonl.template +4 -0
  24. package/.claude/docs/templates/context/research.jsonl.template +4 -0
  25. package/.claude/docs/templates/context/review.jsonl.template +5 -0
  26. package/.claude/docs/templates/context/tech.jsonl.template +5 -0
  27. package/.claude/hooks/CLAUDE.md +342 -0
  28. package/.claude/hooks/inject-agent-context.ts +480 -0
  29. package/.claude/hooks/inject-skill-context.ts +359 -0
  30. package/.claude/hooks/ralph-loop.ts +931 -0
  31. package/.claude/hooks/task-completed-hook.ts +593 -0
  32. package/.claude/hooks/teammate-idle-hook.ts +690 -0
  33. package/.claude/hooks/types/team-types.d.ts +238 -0
  34. package/.claude/rules/devflow-conventions.md +82 -9
  35. package/.claude/scripts/archive-requirement.sh +44 -1
  36. package/.claude/scripts/common.sh +670 -3
  37. package/.claude/scripts/delta-parser.ts +527 -0
  38. package/.claude/scripts/detect-file-conflicts.sh +151 -0
  39. package/.claude/scripts/flow-context-add.sh +134 -0
  40. package/.claude/scripts/flow-context-init.sh +133 -0
  41. package/.claude/scripts/flow-context-validate.sh +144 -0
  42. package/.claude/scripts/flow-delta-apply.sh +297 -0
  43. package/.claude/scripts/flow-delta-archive.sh +71 -0
  44. package/.claude/scripts/flow-delta-create.sh +202 -0
  45. package/.claude/scripts/flow-delta-list.sh +142 -0
  46. package/.claude/scripts/flow-delta-status.sh +235 -0
  47. package/.claude/scripts/flow-quality-full.sh +184 -0
  48. package/.claude/scripts/flow-quality-quick.sh +64 -0
  49. package/.claude/scripts/flow-workspace-init.sh +117 -0
  50. package/.claude/scripts/flow-workspace-record.sh +164 -0
  51. package/.claude/scripts/flow-workspace-start.sh +88 -0
  52. package/.claude/scripts/get-workflow-status.sh +415 -0
  53. package/.claude/scripts/parse-task-dependencies.js +334 -0
  54. package/.claude/scripts/record-quality-error.sh +165 -0
  55. package/.claude/scripts/run-quality-gates.sh +242 -0
  56. package/.claude/scripts/team-dev-init.sh +319 -0
  57. package/.claude/scripts/team-state-recovery.sh +229 -0
  58. package/.claude/scripts/workflow-status.ts +433 -0
  59. package/.claude/settings.json +19 -0
  60. package/.claude/skills/cc-devflow-orchestrator/SKILL.md +85 -200
  61. package/.claude/skills/domain/using-git-worktrees/SKILL.md +252 -0
  62. package/.claude/skills/domain/using-git-worktrees/assets/SHELL_ALIASES.md +133 -0
  63. package/.claude/skills/domain/using-git-worktrees/context.jsonl +4 -0
  64. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-cleanup.sh +218 -0
  65. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-create.sh +232 -0
  66. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-list.sh +130 -0
  67. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-status.sh +140 -0
  68. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-switch.sh +70 -0
  69. package/.claude/skills/skill-rules.json +72 -1
  70. package/.claude/skills/utility/journey-checker/SKILL.md +199 -0
  71. package/.claude/skills/utility/journey-checker/pressure-scenarios.md +164 -0
  72. package/.claude/skills/utility/skill-creator/LICENSE.txt +202 -0
  73. package/.claude/skills/utility/skill-creator/SKILL.md +356 -0
  74. package/.claude/skills/utility/skill-creator/references/output-patterns.md +82 -0
  75. package/.claude/skills/utility/skill-creator/references/workflows.md +28 -0
  76. package/.claude/skills/utility/skill-creator/scripts/init_skill.py +303 -0
  77. package/.claude/skills/utility/skill-creator/scripts/package_skill.py +110 -0
  78. package/.claude/skills/utility/skill-creator/scripts/quick_validate.py +95 -0
  79. package/.claude/skills/workflow/flow-dev/CLAUDE.md +78 -0
  80. package/.claude/skills/workflow/flow-dev/SKILL.md +96 -0
  81. package/.claude/skills/workflow/flow-dev/assets/IMPLEMENTATION_PLAN_TEMPLATE.md +71 -0
  82. package/.claude/skills/workflow/flow-dev/context.jsonl +8 -0
  83. package/.claude/skills/workflow/flow-dev/dev-implementer.jsonl +8 -0
  84. package/.claude/skills/workflow/flow-dev/scripts/entry-gate.sh +116 -0
  85. package/.claude/skills/workflow/flow-dev/scripts/exit-gate.sh +101 -0
  86. package/.claude/skills/workflow/flow-dev/scripts/task-orchestrator.sh +106 -0
  87. package/.claude/skills/workflow/flow-fix/SKILL.md +105 -0
  88. package/.claude/skills/workflow/flow-fix/context.jsonl +6 -0
  89. package/.claude/skills/workflow/flow-fix/references/bug-analyzer.md +381 -0
  90. package/.claude/skills/workflow/flow-init/SKILL.md +211 -0
  91. package/.claude/skills/workflow/flow-init/assets/BRAINSTORM_TEMPLATE.md +148 -0
  92. package/.claude/skills/workflow/flow-init/assets/INIT_FLOW_TEMPLATE.md +198 -0
  93. package/.claude/skills/workflow/flow-init/assets/RESEARCH_TEMPLATE.md +276 -0
  94. package/.claude/skills/workflow/flow-init/context.jsonl +5 -0
  95. package/.claude/skills/workflow/flow-init/references/flow-researcher.md +132 -0
  96. package/.claude/skills/workflow/flow-init/scripts/check-prerequisites.sh +232 -0
  97. package/.claude/skills/workflow/flow-init/scripts/consolidate-research.sh +182 -0
  98. package/.claude/skills/workflow/flow-init/scripts/create-requirement.sh +515 -0
  99. package/.claude/skills/workflow/flow-init/scripts/generate-research-tasks.sh +157 -0
  100. package/.claude/skills/workflow/flow-init/scripts/populate-research-tasks.sh +284 -0
  101. package/.claude/skills/workflow/flow-init/scripts/validate-research.sh +332 -0
  102. package/.claude/skills/workflow/flow-quality/SKILL.md +94 -0
  103. package/.claude/skills/workflow/flow-quality/context.jsonl +6 -0
  104. package/.claude/skills/workflow/flow-quality/references/code-quality-reviewer.md +205 -0
  105. package/.claude/skills/workflow/flow-quality/references/qa-tester.md +313 -0
  106. package/.claude/skills/workflow/flow-quality/references/security-reviewer.md +314 -0
  107. package/.claude/skills/workflow/flow-quality/references/spec-reviewer.md +221 -0
  108. package/.claude/skills/workflow/flow-release/SKILL.md +126 -0
  109. package/.claude/skills/workflow/flow-release/context.jsonl +7 -0
  110. package/.claude/skills/workflow/flow-release/references/release-manager.md +295 -0
  111. package/.claude/skills/workflow/flow-spec/CLAUDE.md +103 -0
  112. package/.claude/skills/workflow/flow-spec/SKILL.md +545 -0
  113. package/.claude/skills/workflow/flow-spec/context.jsonl +7 -0
  114. package/.claude/skills/workflow/flow-spec/scripts/entry-gate.sh +194 -0
  115. package/.claude/skills/workflow/flow-spec/scripts/exit-gate.sh +244 -0
  116. package/.claude/skills/workflow/flow-spec/scripts/parallel-orchestrator.sh +205 -0
  117. package/.claude/skills/workflow/flow-spec/scripts/team-communication.sh +353 -0
  118. package/.claude/skills/workflow/flow-spec/scripts/team-init.sh +195 -0
  119. package/.claude/skills/workflow/flow-spec/scripts/test-team-mode.sh +496 -0
  120. package/.claude/skills/workflow/flow-spec/team-config.json +165 -0
  121. package/.claude/skills/workflow.yaml +417 -0
  122. package/CHANGELOG.md +254 -0
  123. package/README.md +193 -33
  124. package/README.zh-CN.md +206 -46
  125. package/lib/compiler/CLAUDE.md +77 -46
  126. package/lib/compiler/__tests__/multi-module-emitters.test.js +508 -0
  127. package/lib/compiler/context-expander.js +179 -0
  128. package/lib/compiler/emitters/antigravity-emitter.js +195 -5
  129. package/lib/compiler/emitters/base-emitter.js +217 -2
  130. package/lib/compiler/emitters/codex-emitter.js +200 -4
  131. package/lib/compiler/emitters/cursor-emitter.js +307 -3
  132. package/lib/compiler/emitters/qwen-emitter.js +196 -4
  133. package/lib/compiler/index.js +197 -2
  134. package/lib/compiler/platforms.js +270 -21
  135. package/package.json +1 -1
  136. package/.claude/commands/flow-epic.md +0 -183
  137. package/.claude/commands/flow-init.md +0 -370
  138. package/.claude/commands/flow-prd.md +0 -144
  139. package/.claude/commands/flow-qa.md +0 -93
  140. package/.claude/commands/flow-review.md +0 -257
  141. package/.claude/commands/flow-tech.md +0 -142
  142. package/.claude/commands/flow-ui.md +0 -189
  143. package/.claude/skills/file-header-guardian/SKILL.md +0 -56
  144. package/.claude/skills/skill-developer/ADVANCED.md +0 -197
  145. package/.claude/skills/skill-developer/HOOK_MECHANISMS.md +0 -306
  146. package/.claude/skills/skill-developer/PATTERNS_LIBRARY.md +0 -152
  147. package/.claude/skills/skill-developer/SKILL.md +0 -426
  148. package/.claude/skills/skill-developer/SKILL_RULES_REFERENCE.md +0 -315
  149. package/.claude/skills/skill-developer/TRIGGER_TYPES.md +0 -305
  150. package/.claude/skills/skill-developer/TROUBLESHOOTING.md +0 -514
  151. package/.claude/skills/writing-skills/SKILL.md +0 -655
  152. package/.claude/skills/writing-skills/anthropic-best-practices.md +0 -1150
  153. package/.claude/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +0 -189
  154. package/.claude/skills/writing-skills/graphviz-conventions.dot +0 -172
  155. package/.claude/skills/writing-skills/persuasion-principles.md +0 -187
  156. package/.claude/skills/writing-skills/render-graphs.js +0 -168
  157. package/.claude/skills/writing-skills/testing-skills-with-subagents.md +0 -384
  158. package/.claude/tsc-cache/795ba6e3-b98a-423b-bab2-51aa62812569/affected-repos.txt +0 -1
  159. package/.claude/tsc-cache/ae335694-be5a-4ba4-a1a0-b676c09a7906/affected-repos.txt +0 -1
  160. /package/.claude/commands/{core-architecture.md → core/architecture.md} +0 -0
  161. /package/.claude/commands/{core-guidelines.md → core/guidelines.md} +0 -0
  162. /package/.claude/commands/{core-roadmap.md → core/roadmap.md} +0 -0
  163. /package/.claude/commands/{core-style.md → core/style.md} +0 -0
  164. /package/.claude/commands/{flow-checklist.md → flow/checklist.md} +0 -0
  165. /package/.claude/commands/{flow-clarify.md → flow/clarify.md} +0 -0
  166. /package/.claude/commands/{flow-constitution.md → flow/constitution.md} +0 -0
  167. /package/.claude/commands/{flow-fix.md → flow/fix.md} +0 -0
  168. /package/.claude/commands/{flow-ideate.md → flow/ideate.md} +0 -0
  169. /package/.claude/commands/{flow-new.md → flow/new.md} +0 -0
  170. /package/.claude/commands/{flow-release.md → flow/release.md} +0 -0
  171. /package/.claude/commands/{flow-restart.md → flow/restart.md} +0 -0
  172. /package/.claude/commands/{flow-status.md → flow/status.md} +0 -0
  173. /package/.claude/commands/{flow-update.md → flow/update.md} +0 -0
  174. /package/.claude/commands/{flow-upgrade.md → flow/upgrade.md} +0 -0
  175. /package/.claude/commands/{flow-verify.md → flow/verify.md} +0 -0
  176. /package/.claude/commands/{code-review-high.md → util/code-review.md} +0 -0
  177. /package/.claude/commands/{git-commit.md → util/git-commit.md} +0 -0
  178. /package/.claude/commands/{problem-analyzer.md → util/problem-analyzer.md} +0 -0
  179. /package/.claude/skills/{flow-attention-refresh → domain/attention-refresh}/SKILL.md +0 -0
  180. /package/.claude/skills/{flow-brainstorming → domain/brainstorming}/SKILL.md +0 -0
  181. /package/.claude/skills/{flow-debugging → domain/debugging}/SKILL.md +0 -0
  182. /package/.claude/skills/{flow-finishing-branch → domain/finishing-branch}/SKILL.md +0 -0
  183. /package/.claude/skills/{flow-receiving-review → domain/receiving-review}/SKILL.md +0 -0
  184. /package/.claude/skills/{flow-tdd → domain/tdd}/SKILL.md +0 -0
  185. /package/.claude/skills/{verification-before-completion → domain/verification}/SKILL.md +0 -0
  186. /package/.claude/skills/{constitution-guardian → guardrail/constitution-guardian}/SKILL.md +0 -0
  187. /package/.claude/skills/{devflow-tdd-enforcer → guardrail/tdd-enforcer}/SKILL.md +0 -0
  188. /package/.claude/skills/{devflow-constitution-quick-ref → utility/constitution-quick-ref}/SKILL.md +0 -0
  189. /package/.claude/skills/{devflow-file-standards → utility/file-standards}/SKILL.md +0 -0
  190. /package/.claude/skills/{fractal-docs-generator → utility/fractal-docs}/SKILL.md +0 -0
  191. /package/.claude/skills/{npm-release → utility/npm-release}/SKILL.md +0 -0
@@ -0,0 +1,527 @@
1
+ /**
2
+ * [INPUT]: 依赖 DELTA_SPEC_TEMPLATE.md 格式的 delta 文件
3
+ * [OUTPUT]: 对外提供 DeltaBlock[], parseDelta(), applyDelta()
4
+ * [POS]: scripts 的 Delta 解析器,被 flow-delta-apply.sh 调用
5
+ * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
+ *
7
+ * 借鉴 OpenSpec 的 requirement-blocks.ts 实现
8
+ */
9
+
10
+ // ============================================================================
11
+ // Types
12
+ // ============================================================================
13
+
14
+ export type DeltaType = 'ADDED' | 'MODIFIED' | 'REMOVED' | 'RENAMED';
15
+
16
+ export interface DeltaBlock {
17
+ type: DeltaType;
18
+ name: string;
19
+ content: string;
20
+ previousContent?: string; // for MODIFIED
21
+ reason?: string; // for REMOVED
22
+ newName?: string; // for RENAMED
23
+ }
24
+
25
+ export interface DeltaMetadata {
26
+ delta_id: string;
27
+ req_id: string;
28
+ title: string;
29
+ created_at: string;
30
+ status: 'draft' | 'review' | 'approved' | 'applied';
31
+ }
32
+
33
+ export interface ParsedDelta {
34
+ metadata: DeltaMetadata;
35
+ summary: string;
36
+ blocks: DeltaBlock[];
37
+ }
38
+
39
+ // ============================================================================
40
+ // Utilities
41
+ // ============================================================================
42
+
43
+ function normalizeLineEndings(content: string): string {
44
+ return content.replace(/\r\n?/g, '\n');
45
+ }
46
+
47
+ function extractYamlFrontmatter(content: string): { metadata: Record<string, string>; body: string } {
48
+ const normalized = normalizeLineEndings(content);
49
+ const match = normalized.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
50
+
51
+ if (!match) {
52
+ return { metadata: {}, body: normalized };
53
+ }
54
+
55
+ const yamlContent = match[1];
56
+ const body = match[2];
57
+ const metadata: Record<string, string> = {};
58
+
59
+ for (const line of yamlContent.split('\n')) {
60
+ const colonIndex = line.indexOf(':');
61
+ if (colonIndex > 0) {
62
+ const key = line.slice(0, colonIndex).trim();
63
+ let value = line.slice(colonIndex + 1).trim();
64
+ // Remove quotes
65
+ if ((value.startsWith('"') && value.endsWith('"')) ||
66
+ (value.startsWith("'") && value.endsWith("'"))) {
67
+ value = value.slice(1, -1);
68
+ }
69
+ metadata[key] = value;
70
+ }
71
+ }
72
+
73
+ return { metadata, body };
74
+ }
75
+
76
+ // ============================================================================
77
+ // Section Parsing
78
+ // ============================================================================
79
+
80
+ interface SectionContent {
81
+ title: string;
82
+ body: string;
83
+ }
84
+
85
+ function splitSections(content: string): SectionContent[] {
86
+ const lines = normalizeLineEndings(content).split('\n');
87
+ const sections: SectionContent[] = [];
88
+ let currentSection: SectionContent | null = null;
89
+ let bodyLines: string[] = [];
90
+
91
+ for (const line of lines) {
92
+ // Match ## level headers
93
+ const headerMatch = line.match(/^##\s+(.+)$/);
94
+ if (headerMatch) {
95
+ // Save previous section
96
+ if (currentSection) {
97
+ currentSection.body = bodyLines.join('\n').trim();
98
+ sections.push(currentSection);
99
+ }
100
+ currentSection = { title: headerMatch[1].trim(), body: '' };
101
+ bodyLines = [];
102
+ } else if (currentSection) {
103
+ bodyLines.push(line);
104
+ }
105
+ }
106
+
107
+ // Save last section
108
+ if (currentSection) {
109
+ currentSection.body = bodyLines.join('\n').trim();
110
+ sections.push(currentSection);
111
+ }
112
+
113
+ return sections;
114
+ }
115
+
116
+ function findSection(sections: SectionContent[], titlePattern: string): SectionContent | undefined {
117
+ const pattern = titlePattern.toLowerCase();
118
+ return sections.find(s => s.title.toLowerCase().includes(pattern));
119
+ }
120
+
121
+ // ============================================================================
122
+ // Requirement Block Parsing
123
+ // ============================================================================
124
+
125
+ const REQUIREMENT_HEADER_REGEX = /^###\s*Requirement:\s*(.+)\s*$/;
126
+
127
+ interface RequirementBlock {
128
+ name: string;
129
+ content: string;
130
+ }
131
+
132
+ function parseRequirementBlocks(sectionBody: string): RequirementBlock[] {
133
+ if (!sectionBody) return [];
134
+
135
+ const lines = normalizeLineEndings(sectionBody).split('\n');
136
+ const blocks: RequirementBlock[] = [];
137
+ let i = 0;
138
+
139
+ while (i < lines.length) {
140
+ // Seek next requirement header
141
+ while (i < lines.length && !REQUIREMENT_HEADER_REGEX.test(lines[i])) {
142
+ i++;
143
+ }
144
+ if (i >= lines.length) break;
145
+
146
+ const headerLine = lines[i];
147
+ const match = headerLine.match(REQUIREMENT_HEADER_REGEX);
148
+ if (!match) {
149
+ i++;
150
+ continue;
151
+ }
152
+
153
+ const name = match[1].trim();
154
+ const contentLines: string[] = [headerLine];
155
+ i++;
156
+
157
+ // Gather lines until next requirement header or section header
158
+ while (i < lines.length &&
159
+ !REQUIREMENT_HEADER_REGEX.test(lines[i]) &&
160
+ !/^##\s+/.test(lines[i])) {
161
+ contentLines.push(lines[i]);
162
+ i++;
163
+ }
164
+
165
+ blocks.push({
166
+ name,
167
+ content: contentLines.join('\n').trim()
168
+ });
169
+ }
170
+
171
+ return blocks;
172
+ }
173
+
174
+ // ============================================================================
175
+ // RENAMED Section Parsing
176
+ // ============================================================================
177
+
178
+ interface RenamedPair {
179
+ from: string;
180
+ to: string;
181
+ }
182
+
183
+ function parseRenamedPairs(sectionBody: string): RenamedPair[] {
184
+ if (!sectionBody) return [];
185
+
186
+ const pairs: RenamedPair[] = [];
187
+ const lines = normalizeLineEndings(sectionBody).split('\n');
188
+ let currentFrom: string | null = null;
189
+
190
+ for (const line of lines) {
191
+ const fromMatch = line.match(/^\s*-?\s*FROM:\s*(.+)\s*$/i);
192
+ const toMatch = line.match(/^\s*-?\s*TO:\s*(.+)\s*$/i);
193
+
194
+ if (fromMatch) {
195
+ currentFrom = fromMatch[1].trim();
196
+ } else if (toMatch && currentFrom) {
197
+ pairs.push({ from: currentFrom, to: toMatch[1].trim() });
198
+ currentFrom = null;
199
+ }
200
+ }
201
+
202
+ return pairs;
203
+ }
204
+
205
+ // ============================================================================
206
+ // REMOVED Section Parsing
207
+ // ============================================================================
208
+
209
+ interface RemovedBlock {
210
+ name: string;
211
+ reason?: string;
212
+ migration?: string;
213
+ }
214
+
215
+ function parseRemovedBlocks(sectionBody: string): RemovedBlock[] {
216
+ if (!sectionBody) return [];
217
+
218
+ const blocks: RemovedBlock[] = [];
219
+ const reqBlocks = parseRequirementBlocks(sectionBody);
220
+
221
+ for (const block of reqBlocks) {
222
+ const lines = block.content.split('\n');
223
+ let reason: string | undefined;
224
+ let migration: string | undefined;
225
+
226
+ for (const line of lines) {
227
+ const reasonMatch = line.match(/^\*\*Reason\*\*:\s*(.+)$/i);
228
+ const migrationMatch = line.match(/^\*\*Migration\*\*:\s*(.+)$/i);
229
+
230
+ if (reasonMatch) {
231
+ reason = reasonMatch[1].trim();
232
+ } else if (migrationMatch) {
233
+ migration = migrationMatch[1].trim();
234
+ }
235
+ }
236
+
237
+ blocks.push({ name: block.name, reason, migration });
238
+ }
239
+
240
+ return blocks;
241
+ }
242
+
243
+ // ============================================================================
244
+ // MODIFIED Section Parsing
245
+ // ============================================================================
246
+
247
+ interface ModifiedBlock {
248
+ name: string;
249
+ content: string;
250
+ previousContent?: string;
251
+ }
252
+
253
+ function parseModifiedBlocks(sectionBody: string): ModifiedBlock[] {
254
+ if (!sectionBody) return [];
255
+
256
+ const blocks: ModifiedBlock[] = [];
257
+ const reqBlocks = parseRequirementBlocks(sectionBody);
258
+
259
+ for (const block of reqBlocks) {
260
+ const lines = block.content.split('\n');
261
+ let previousContent: string | undefined;
262
+
263
+ for (const line of lines) {
264
+ const prevMatch = line.match(/^\(Previously:\s*(.+)\)$/i);
265
+ if (prevMatch) {
266
+ previousContent = prevMatch[1].trim();
267
+ break;
268
+ }
269
+ }
270
+
271
+ blocks.push({
272
+ name: block.name,
273
+ content: block.content,
274
+ previousContent
275
+ });
276
+ }
277
+
278
+ return blocks;
279
+ }
280
+
281
+ // ============================================================================
282
+ // Main Parser
283
+ // ============================================================================
284
+
285
+ export function parseDelta(content: string): DeltaBlock[] {
286
+ const { body } = extractYamlFrontmatter(content);
287
+ const sections = splitSections(body);
288
+ const blocks: DeltaBlock[] = [];
289
+
290
+ // Parse ADDED Requirements
291
+ const addedSection = findSection(sections, 'ADDED Requirements');
292
+ if (addedSection) {
293
+ const reqBlocks = parseRequirementBlocks(addedSection.body);
294
+ for (const block of reqBlocks) {
295
+ blocks.push({
296
+ type: 'ADDED',
297
+ name: block.name,
298
+ content: block.content
299
+ });
300
+ }
301
+ }
302
+
303
+ // Parse MODIFIED Requirements
304
+ const modifiedSection = findSection(sections, 'MODIFIED Requirements');
305
+ if (modifiedSection) {
306
+ const modBlocks = parseModifiedBlocks(modifiedSection.body);
307
+ for (const block of modBlocks) {
308
+ blocks.push({
309
+ type: 'MODIFIED',
310
+ name: block.name,
311
+ content: block.content,
312
+ previousContent: block.previousContent
313
+ });
314
+ }
315
+ }
316
+
317
+ // Parse REMOVED Requirements
318
+ const removedSection = findSection(sections, 'REMOVED Requirements');
319
+ if (removedSection) {
320
+ const remBlocks = parseRemovedBlocks(removedSection.body);
321
+ for (const block of remBlocks) {
322
+ blocks.push({
323
+ type: 'REMOVED',
324
+ name: block.name,
325
+ content: '',
326
+ reason: block.reason
327
+ });
328
+ }
329
+ }
330
+
331
+ // Parse RENAMED Requirements
332
+ const renamedSection = findSection(sections, 'RENAMED Requirements');
333
+ if (renamedSection) {
334
+ const pairs = parseRenamedPairs(renamedSection.body);
335
+ for (const pair of pairs) {
336
+ blocks.push({
337
+ type: 'RENAMED',
338
+ name: pair.from,
339
+ content: '',
340
+ newName: pair.to
341
+ });
342
+ }
343
+ }
344
+
345
+ return blocks;
346
+ }
347
+
348
+ export function parseFullDelta(content: string): ParsedDelta {
349
+ const { metadata, body } = extractYamlFrontmatter(content);
350
+ const sections = splitSections(body);
351
+ const blocks = parseDelta(content);
352
+
353
+ // Extract summary
354
+ const summarySection = findSection(sections, 'Summary');
355
+ const summary = summarySection?.body || '';
356
+
357
+ return {
358
+ metadata: {
359
+ delta_id: metadata.delta_id || '',
360
+ req_id: metadata.req_id || '',
361
+ title: metadata.title || '',
362
+ created_at: metadata.created_at || '',
363
+ status: (metadata.status as DeltaMetadata['status']) || 'draft'
364
+ },
365
+ summary,
366
+ blocks
367
+ };
368
+ }
369
+
370
+ // ============================================================================
371
+ // Delta Application
372
+ // ============================================================================
373
+
374
+ export function applyDelta(prdContent: string, delta: DeltaBlock[]): string {
375
+ let result = normalizeLineEndings(prdContent);
376
+
377
+ // Build a map of existing requirements
378
+ const { body } = extractYamlFrontmatter(result);
379
+ const existingBlocks = parseRequirementBlocks(body);
380
+ const nameToContent = new Map<string, string>();
381
+
382
+ for (const block of existingBlocks) {
383
+ nameToContent.set(block.name.toLowerCase(), block.content);
384
+ }
385
+
386
+ // Apply operations in order: RENAMED → REMOVED → MODIFIED → ADDED
387
+
388
+ // 1. RENAMED
389
+ for (const block of delta.filter(b => b.type === 'RENAMED')) {
390
+ const oldName = block.name.toLowerCase();
391
+ const newName = block.newName!;
392
+
393
+ if (nameToContent.has(oldName)) {
394
+ const content = nameToContent.get(oldName)!;
395
+ // Replace header in content
396
+ const updatedContent = content.replace(
397
+ /^###\s*Requirement:\s*.+$/m,
398
+ `### Requirement: ${newName}`
399
+ );
400
+ nameToContent.delete(oldName);
401
+ nameToContent.set(newName.toLowerCase(), updatedContent);
402
+
403
+ // Update in result
404
+ const oldHeaderRegex = new RegExp(`###\\s*Requirement:\\s*${escapeRegex(block.name)}`, 'i');
405
+ result = result.replace(oldHeaderRegex, `### Requirement: ${newName}`);
406
+ }
407
+ }
408
+
409
+ // 2. REMOVED
410
+ for (const block of delta.filter(b => b.type === 'REMOVED')) {
411
+ const name = block.name.toLowerCase();
412
+ nameToContent.delete(name);
413
+
414
+ // Remove from result - find the requirement block and remove it
415
+ const headerRegex = new RegExp(
416
+ `###\\s*Requirement:\\s*${escapeRegex(block.name)}[\\s\\S]*?(?=###\\s*Requirement:|##\\s+|$)`,
417
+ 'i'
418
+ );
419
+ result = result.replace(headerRegex, '');
420
+ }
421
+
422
+ // 3. MODIFIED
423
+ for (const block of delta.filter(b => b.type === 'MODIFIED')) {
424
+ const name = block.name.toLowerCase();
425
+
426
+ if (nameToContent.has(name)) {
427
+ nameToContent.set(name, block.content);
428
+
429
+ // Replace in result
430
+ const headerRegex = new RegExp(
431
+ `###\\s*Requirement:\\s*${escapeRegex(block.name)}[\\s\\S]*?(?=###\\s*Requirement:|##\\s+|$)`,
432
+ 'i'
433
+ );
434
+ result = result.replace(headerRegex, block.content + '\n\n');
435
+ }
436
+ }
437
+
438
+ // 4. ADDED
439
+ for (const block of delta.filter(b => b.type === 'ADDED')) {
440
+ const name = block.name.toLowerCase();
441
+
442
+ if (!nameToContent.has(name)) {
443
+ nameToContent.set(name, block.content);
444
+
445
+ // Find Requirements section and append
446
+ const reqSectionMatch = result.match(/^##\s+Requirements\s*$/m);
447
+ if (reqSectionMatch) {
448
+ // Find end of Requirements section
449
+ const reqSectionIndex = result.indexOf(reqSectionMatch[0]);
450
+ const afterReqSection = result.slice(reqSectionIndex + reqSectionMatch[0].length);
451
+ const nextSectionMatch = afterReqSection.match(/^##\s+/m);
452
+
453
+ if (nextSectionMatch) {
454
+ const insertIndex = reqSectionIndex + reqSectionMatch[0].length + nextSectionMatch.index!;
455
+ result = result.slice(0, insertIndex) + '\n\n' + block.content + '\n' + result.slice(insertIndex);
456
+ } else {
457
+ // Append at end
458
+ result = result.trimEnd() + '\n\n' + block.content + '\n';
459
+ }
460
+ } else {
461
+ // No Requirements section, create one
462
+ result = result.trimEnd() + '\n\n## Requirements\n\n' + block.content + '\n';
463
+ }
464
+ }
465
+ }
466
+
467
+ // Clean up multiple newlines
468
+ result = result.replace(/\n{3,}/g, '\n\n');
469
+
470
+ return result;
471
+ }
472
+
473
+ function escapeRegex(str: string): string {
474
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
475
+ }
476
+
477
+ // ============================================================================
478
+ // CLI Interface
479
+ // ============================================================================
480
+
481
+ if (require.main === module) {
482
+ const fs = require('fs');
483
+ const args = process.argv.slice(2);
484
+
485
+ if (args.length < 1) {
486
+ console.error('Usage: delta-parser.ts <command> [args]');
487
+ console.error('Commands:');
488
+ console.error(' parse <delta-file> Parse delta file and output JSON');
489
+ console.error(' apply <prd-file> <delta-file> Apply delta to PRD and output result');
490
+ process.exit(1);
491
+ }
492
+
493
+ const command = args[0];
494
+
495
+ switch (command) {
496
+ case 'parse': {
497
+ const deltaFile = args[1];
498
+ if (!deltaFile) {
499
+ console.error('Error: delta-file required');
500
+ process.exit(1);
501
+ }
502
+ const content = fs.readFileSync(deltaFile, 'utf-8');
503
+ const parsed = parseFullDelta(content);
504
+ console.log(JSON.stringify(parsed, null, 2));
505
+ break;
506
+ }
507
+
508
+ case 'apply': {
509
+ const prdFile = args[1];
510
+ const deltaFile = args[2];
511
+ if (!prdFile || !deltaFile) {
512
+ console.error('Error: prd-file and delta-file required');
513
+ process.exit(1);
514
+ }
515
+ const prdContent = fs.readFileSync(prdFile, 'utf-8');
516
+ const deltaContent = fs.readFileSync(deltaFile, 'utf-8');
517
+ const blocks = parseDelta(deltaContent);
518
+ const result = applyDelta(prdContent, blocks);
519
+ console.log(result);
520
+ break;
521
+ }
522
+
523
+ default:
524
+ console.error(`Unknown command: ${command}`);
525
+ process.exit(1);
526
+ }
527
+ }
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # =============================================================================
4
+ # [INPUT]: 依赖 stdin 的 JSON 任务列表 (来自 parse-task-dependencies.ts)
5
+ # [OUTPUT]: 对外提供文件冲突检测结果 JSON
6
+ # [POS]: scripts/ 的并行任务冲突检测器,被 Team 调度系统消费
7
+ # [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
8
+ # =============================================================================
9
+ #
10
+ # Detect file conflicts in parallel task execution
11
+ #
12
+ # This script analyzes task file paths to detect potential conflicts
13
+ # when multiple tasks modify the same file in parallel.
14
+ #
15
+ # Usage:
16
+ # echo '{"tasks": [...]}' | ./detect-file-conflicts.sh
17
+ # ./detect-file-conflicts.sh < tasks.json
18
+ #
19
+ # INPUT FORMAT:
20
+ # {
21
+ # "tasks": [
22
+ # {"id": "T001", "filePath": "src/user.ts", "parallel": true},
23
+ # {"id": "T002", "filePath": "src/user.ts", "parallel": true}
24
+ # ]
25
+ # }
26
+ #
27
+ # OUTPUT FORMAT:
28
+ # {
29
+ # "hasConflicts": true,
30
+ # "conflicts": [
31
+ # {
32
+ # "file": "src/user.ts",
33
+ # "tasks": ["T001", "T002"],
34
+ # "recommendation": "Run sequentially or assign to same agent"
35
+ # }
36
+ # ],
37
+ # "safeGroups": [
38
+ # {"tasks": ["T003", "T004"], "reason": "Different files"}
39
+ # ]
40
+ # }
41
+ #
42
+ # EXIT CODES:
43
+ # 0 - Success (no conflicts or conflicts detected and reported)
44
+ # 1 - Invalid input or processing error
45
+
46
+ set -e
47
+
48
+ # =============================================================================
49
+ # Main Logic
50
+ # =============================================================================
51
+
52
+ main() {
53
+ local input
54
+ input=$(cat)
55
+
56
+ # Validate input is valid JSON
57
+ if ! echo "$input" | jq empty 2>/dev/null; then
58
+ echo '{"error": "Invalid JSON input", "hasConflicts": false, "conflicts": [], "safeGroups": []}' >&2
59
+ exit 1
60
+ fi
61
+
62
+ # Check if tasks array exists
63
+ local tasks_count
64
+ tasks_count=$(echo "$input" | jq '.tasks | length // 0')
65
+
66
+ if [[ "$tasks_count" -eq 0 ]]; then
67
+ echo '{"hasConflicts": false, "conflicts": [], "safeGroups": [], "message": "No tasks provided"}'
68
+ exit 0
69
+ fi
70
+
71
+ # Extract parallel tasks with file paths
72
+ # Build a map of file -> [task IDs]
73
+ local result
74
+ result=$(echo "$input" | jq '
75
+ # Filter to parallel tasks with file paths
76
+ .tasks
77
+ | map(select(.parallel == true and .filePath != null and .filePath != ""))
78
+
79
+ # Group by file path
80
+ | group_by(.filePath)
81
+
82
+ # Analyze each group
83
+ | map({
84
+ file: .[0].filePath,
85
+ tasks: [.[].id],
86
+ count: length
87
+ })
88
+
89
+ # Separate conflicts (count > 1) from safe groups
90
+ | {
91
+ conflicts: map(select(.count > 1) | {
92
+ file: .file,
93
+ tasks: .tasks,
94
+ recommendation: "Run sequentially or assign to same agent"
95
+ }),
96
+ safeFiles: map(select(.count == 1) | .tasks[0])
97
+ }
98
+
99
+ # Build final output
100
+ | {
101
+ hasConflicts: (.conflicts | length > 0),
102
+ conflicts: .conflicts,
103
+ safeGroups: (
104
+ if (.safeFiles | length > 0) then
105
+ [{tasks: .safeFiles, reason: "Different files"}]
106
+ else
107
+ []
108
+ end
109
+ )
110
+ }
111
+ ')
112
+
113
+ # Add additional analysis for directory-level conflicts
114
+ local dir_conflicts
115
+ dir_conflicts=$(echo "$input" | jq '
116
+ # Extract parallel tasks
117
+ .tasks
118
+ | map(select(.parallel == true and .filePath != null and .filePath != ""))
119
+
120
+ # Extract directory from file path
121
+ | map(. + {dir: (.filePath | split("/") | .[:-1] | join("/"))})
122
+
123
+ # Group by directory
124
+ | group_by(.dir)
125
+
126
+ # Find directories with multiple tasks
127
+ | map(select(length > 1))
128
+
129
+ # Format as warnings
130
+ | map({
131
+ directory: .[0].dir,
132
+ tasks: [.[].id],
133
+ files: [.[].filePath],
134
+ warning: "Multiple tasks in same directory - review for potential conflicts"
135
+ })
136
+ ')
137
+
138
+ # Merge directory warnings into result
139
+ local final_result
140
+ final_result=$(echo "$result" | jq --argjson dirWarnings "$dir_conflicts" '
141
+ . + {directoryWarnings: $dirWarnings}
142
+ ')
143
+
144
+ echo "$final_result"
145
+ }
146
+
147
+ # =============================================================================
148
+ # Entry Point
149
+ # =============================================================================
150
+
151
+ main "$@"