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.
- package/.claude/CLAUDE.md +1065 -48
- package/.claude/agents/dev-implementer.md +195 -0
- package/.claude/commands/{flow-archive.md → flow/archive.md} +46 -11
- package/.claude/commands/flow/context.md +150 -0
- package/.claude/commands/flow/delta.md +245 -0
- package/.claude/commands/{flow-dev.md → flow/dev.md} +112 -11
- package/.claude/commands/flow/init.md +45 -0
- package/.claude/commands/flow/quality.md +159 -0
- package/.claude/commands/flow/spec.md +186 -0
- package/.claude/commands/flow/workspace.md +146 -0
- package/.claude/commands/{cancel-ralph.md → util/cancel-ralph.md} +1 -0
- package/.claude/config/quality-gates.yml +305 -0
- package/.claude/docs/guides/TEAM_MODE_GUIDE.md +313 -0
- package/.claude/docs/templates/DELTA_SPEC_TEMPLATE.md +91 -0
- package/.claude/docs/templates/DESIGN_DECISIONS_TEMPLATE.md +151 -0
- package/.claude/docs/templates/JOURNAL_TEMPLATE.md +75 -0
- package/.claude/docs/templates/_shared/CLAUDE.md +36 -0
- package/.claude/docs/templates/_shared/CONSTITUTION_CHECK.md +125 -0
- package/.claude/docs/templates/_shared/VALIDATION_CHECKLIST.md +187 -0
- package/.claude/docs/templates/_shared/YAML_FRONTMATTER.md +164 -0
- package/.claude/docs/templates/context/dev.jsonl.template +6 -0
- package/.claude/docs/templates/context/epic.jsonl.template +5 -0
- package/.claude/docs/templates/context/prd.jsonl.template +4 -0
- package/.claude/docs/templates/context/research.jsonl.template +4 -0
- package/.claude/docs/templates/context/review.jsonl.template +5 -0
- package/.claude/docs/templates/context/tech.jsonl.template +5 -0
- package/.claude/hooks/CLAUDE.md +342 -0
- package/.claude/hooks/inject-agent-context.ts +480 -0
- package/.claude/hooks/inject-skill-context.ts +359 -0
- package/.claude/hooks/ralph-loop.ts +931 -0
- package/.claude/hooks/task-completed-hook.ts +593 -0
- package/.claude/hooks/teammate-idle-hook.ts +690 -0
- package/.claude/hooks/types/team-types.d.ts +238 -0
- package/.claude/rules/devflow-conventions.md +82 -9
- package/.claude/scripts/archive-requirement.sh +44 -1
- package/.claude/scripts/common.sh +670 -3
- package/.claude/scripts/delta-parser.ts +527 -0
- package/.claude/scripts/detect-file-conflicts.sh +151 -0
- package/.claude/scripts/flow-context-add.sh +134 -0
- package/.claude/scripts/flow-context-init.sh +133 -0
- package/.claude/scripts/flow-context-validate.sh +144 -0
- package/.claude/scripts/flow-delta-apply.sh +297 -0
- package/.claude/scripts/flow-delta-archive.sh +71 -0
- package/.claude/scripts/flow-delta-create.sh +202 -0
- package/.claude/scripts/flow-delta-list.sh +142 -0
- package/.claude/scripts/flow-delta-status.sh +235 -0
- package/.claude/scripts/flow-quality-full.sh +184 -0
- package/.claude/scripts/flow-quality-quick.sh +64 -0
- package/.claude/scripts/flow-workspace-init.sh +117 -0
- package/.claude/scripts/flow-workspace-record.sh +164 -0
- package/.claude/scripts/flow-workspace-start.sh +88 -0
- package/.claude/scripts/get-workflow-status.sh +415 -0
- package/.claude/scripts/parse-task-dependencies.js +334 -0
- package/.claude/scripts/record-quality-error.sh +165 -0
- package/.claude/scripts/run-quality-gates.sh +242 -0
- package/.claude/scripts/team-dev-init.sh +319 -0
- package/.claude/scripts/team-state-recovery.sh +229 -0
- package/.claude/scripts/workflow-status.ts +433 -0
- package/.claude/settings.json +19 -0
- package/.claude/skills/cc-devflow-orchestrator/SKILL.md +85 -200
- package/.claude/skills/domain/using-git-worktrees/SKILL.md +252 -0
- package/.claude/skills/domain/using-git-worktrees/assets/SHELL_ALIASES.md +133 -0
- package/.claude/skills/domain/using-git-worktrees/context.jsonl +4 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-cleanup.sh +218 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-create.sh +232 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-list.sh +130 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-status.sh +140 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-switch.sh +70 -0
- package/.claude/skills/skill-rules.json +72 -1
- package/.claude/skills/utility/journey-checker/SKILL.md +199 -0
- package/.claude/skills/utility/journey-checker/pressure-scenarios.md +164 -0
- package/.claude/skills/utility/skill-creator/LICENSE.txt +202 -0
- package/.claude/skills/utility/skill-creator/SKILL.md +356 -0
- package/.claude/skills/utility/skill-creator/references/output-patterns.md +82 -0
- package/.claude/skills/utility/skill-creator/references/workflows.md +28 -0
- package/.claude/skills/utility/skill-creator/scripts/init_skill.py +303 -0
- package/.claude/skills/utility/skill-creator/scripts/package_skill.py +110 -0
- package/.claude/skills/utility/skill-creator/scripts/quick_validate.py +95 -0
- package/.claude/skills/workflow/flow-dev/CLAUDE.md +78 -0
- package/.claude/skills/workflow/flow-dev/SKILL.md +96 -0
- package/.claude/skills/workflow/flow-dev/assets/IMPLEMENTATION_PLAN_TEMPLATE.md +71 -0
- package/.claude/skills/workflow/flow-dev/context.jsonl +8 -0
- package/.claude/skills/workflow/flow-dev/dev-implementer.jsonl +8 -0
- package/.claude/skills/workflow/flow-dev/scripts/entry-gate.sh +116 -0
- package/.claude/skills/workflow/flow-dev/scripts/exit-gate.sh +101 -0
- package/.claude/skills/workflow/flow-dev/scripts/task-orchestrator.sh +106 -0
- package/.claude/skills/workflow/flow-fix/SKILL.md +105 -0
- package/.claude/skills/workflow/flow-fix/context.jsonl +6 -0
- package/.claude/skills/workflow/flow-fix/references/bug-analyzer.md +381 -0
- package/.claude/skills/workflow/flow-init/SKILL.md +211 -0
- package/.claude/skills/workflow/flow-init/assets/BRAINSTORM_TEMPLATE.md +148 -0
- package/.claude/skills/workflow/flow-init/assets/INIT_FLOW_TEMPLATE.md +198 -0
- package/.claude/skills/workflow/flow-init/assets/RESEARCH_TEMPLATE.md +276 -0
- package/.claude/skills/workflow/flow-init/context.jsonl +5 -0
- package/.claude/skills/workflow/flow-init/references/flow-researcher.md +132 -0
- package/.claude/skills/workflow/flow-init/scripts/check-prerequisites.sh +232 -0
- package/.claude/skills/workflow/flow-init/scripts/consolidate-research.sh +182 -0
- package/.claude/skills/workflow/flow-init/scripts/create-requirement.sh +515 -0
- package/.claude/skills/workflow/flow-init/scripts/generate-research-tasks.sh +157 -0
- package/.claude/skills/workflow/flow-init/scripts/populate-research-tasks.sh +284 -0
- package/.claude/skills/workflow/flow-init/scripts/validate-research.sh +332 -0
- package/.claude/skills/workflow/flow-quality/SKILL.md +94 -0
- package/.claude/skills/workflow/flow-quality/context.jsonl +6 -0
- package/.claude/skills/workflow/flow-quality/references/code-quality-reviewer.md +205 -0
- package/.claude/skills/workflow/flow-quality/references/qa-tester.md +313 -0
- package/.claude/skills/workflow/flow-quality/references/security-reviewer.md +314 -0
- package/.claude/skills/workflow/flow-quality/references/spec-reviewer.md +221 -0
- package/.claude/skills/workflow/flow-release/SKILL.md +126 -0
- package/.claude/skills/workflow/flow-release/context.jsonl +7 -0
- package/.claude/skills/workflow/flow-release/references/release-manager.md +295 -0
- package/.claude/skills/workflow/flow-spec/CLAUDE.md +103 -0
- package/.claude/skills/workflow/flow-spec/SKILL.md +545 -0
- package/.claude/skills/workflow/flow-spec/context.jsonl +7 -0
- package/.claude/skills/workflow/flow-spec/scripts/entry-gate.sh +194 -0
- package/.claude/skills/workflow/flow-spec/scripts/exit-gate.sh +244 -0
- package/.claude/skills/workflow/flow-spec/scripts/parallel-orchestrator.sh +205 -0
- package/.claude/skills/workflow/flow-spec/scripts/team-communication.sh +353 -0
- package/.claude/skills/workflow/flow-spec/scripts/team-init.sh +195 -0
- package/.claude/skills/workflow/flow-spec/scripts/test-team-mode.sh +496 -0
- package/.claude/skills/workflow/flow-spec/team-config.json +165 -0
- package/.claude/skills/workflow.yaml +417 -0
- package/CHANGELOG.md +254 -0
- package/README.md +193 -33
- package/README.zh-CN.md +206 -46
- package/lib/compiler/CLAUDE.md +77 -46
- package/lib/compiler/__tests__/multi-module-emitters.test.js +508 -0
- package/lib/compiler/context-expander.js +179 -0
- package/lib/compiler/emitters/antigravity-emitter.js +195 -5
- package/lib/compiler/emitters/base-emitter.js +217 -2
- package/lib/compiler/emitters/codex-emitter.js +200 -4
- package/lib/compiler/emitters/cursor-emitter.js +307 -3
- package/lib/compiler/emitters/qwen-emitter.js +196 -4
- package/lib/compiler/index.js +197 -2
- package/lib/compiler/platforms.js +270 -21
- package/package.json +1 -1
- package/.claude/commands/flow-epic.md +0 -183
- package/.claude/commands/flow-init.md +0 -370
- package/.claude/commands/flow-prd.md +0 -144
- package/.claude/commands/flow-qa.md +0 -93
- package/.claude/commands/flow-review.md +0 -257
- package/.claude/commands/flow-tech.md +0 -142
- package/.claude/commands/flow-ui.md +0 -189
- package/.claude/skills/file-header-guardian/SKILL.md +0 -56
- package/.claude/skills/skill-developer/ADVANCED.md +0 -197
- package/.claude/skills/skill-developer/HOOK_MECHANISMS.md +0 -306
- package/.claude/skills/skill-developer/PATTERNS_LIBRARY.md +0 -152
- package/.claude/skills/skill-developer/SKILL.md +0 -426
- package/.claude/skills/skill-developer/SKILL_RULES_REFERENCE.md +0 -315
- package/.claude/skills/skill-developer/TRIGGER_TYPES.md +0 -305
- package/.claude/skills/skill-developer/TROUBLESHOOTING.md +0 -514
- package/.claude/skills/writing-skills/SKILL.md +0 -655
- package/.claude/skills/writing-skills/anthropic-best-practices.md +0 -1150
- package/.claude/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +0 -189
- package/.claude/skills/writing-skills/graphviz-conventions.dot +0 -172
- package/.claude/skills/writing-skills/persuasion-principles.md +0 -187
- package/.claude/skills/writing-skills/render-graphs.js +0 -168
- package/.claude/skills/writing-skills/testing-skills-with-subagents.md +0 -384
- package/.claude/tsc-cache/795ba6e3-b98a-423b-bab2-51aa62812569/affected-repos.txt +0 -1
- package/.claude/tsc-cache/ae335694-be5a-4ba4-a1a0-b676c09a7906/affected-repos.txt +0 -1
- /package/.claude/commands/{core-architecture.md → core/architecture.md} +0 -0
- /package/.claude/commands/{core-guidelines.md → core/guidelines.md} +0 -0
- /package/.claude/commands/{core-roadmap.md → core/roadmap.md} +0 -0
- /package/.claude/commands/{core-style.md → core/style.md} +0 -0
- /package/.claude/commands/{flow-checklist.md → flow/checklist.md} +0 -0
- /package/.claude/commands/{flow-clarify.md → flow/clarify.md} +0 -0
- /package/.claude/commands/{flow-constitution.md → flow/constitution.md} +0 -0
- /package/.claude/commands/{flow-fix.md → flow/fix.md} +0 -0
- /package/.claude/commands/{flow-ideate.md → flow/ideate.md} +0 -0
- /package/.claude/commands/{flow-new.md → flow/new.md} +0 -0
- /package/.claude/commands/{flow-release.md → flow/release.md} +0 -0
- /package/.claude/commands/{flow-restart.md → flow/restart.md} +0 -0
- /package/.claude/commands/{flow-status.md → flow/status.md} +0 -0
- /package/.claude/commands/{flow-update.md → flow/update.md} +0 -0
- /package/.claude/commands/{flow-upgrade.md → flow/upgrade.md} +0 -0
- /package/.claude/commands/{flow-verify.md → flow/verify.md} +0 -0
- /package/.claude/commands/{code-review-high.md → util/code-review.md} +0 -0
- /package/.claude/commands/{git-commit.md → util/git-commit.md} +0 -0
- /package/.claude/commands/{problem-analyzer.md → util/problem-analyzer.md} +0 -0
- /package/.claude/skills/{flow-attention-refresh → domain/attention-refresh}/SKILL.md +0 -0
- /package/.claude/skills/{flow-brainstorming → domain/brainstorming}/SKILL.md +0 -0
- /package/.claude/skills/{flow-debugging → domain/debugging}/SKILL.md +0 -0
- /package/.claude/skills/{flow-finishing-branch → domain/finishing-branch}/SKILL.md +0 -0
- /package/.claude/skills/{flow-receiving-review → domain/receiving-review}/SKILL.md +0 -0
- /package/.claude/skills/{flow-tdd → domain/tdd}/SKILL.md +0 -0
- /package/.claude/skills/{verification-before-completion → domain/verification}/SKILL.md +0 -0
- /package/.claude/skills/{constitution-guardian → guardrail/constitution-guardian}/SKILL.md +0 -0
- /package/.claude/skills/{devflow-tdd-enforcer → guardrail/tdd-enforcer}/SKILL.md +0 -0
- /package/.claude/skills/{devflow-constitution-quick-ref → utility/constitution-quick-ref}/SKILL.md +0 -0
- /package/.claude/skills/{devflow-file-standards → utility/file-standards}/SKILL.md +0 -0
- /package/.claude/skills/{fractal-docs-generator → utility/fractal-docs}/SKILL.md +0 -0
- /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 "$@"
|