cc-devflow 4.2.0 → 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.
- package/.claude/commands/flow/CLAUDE.md +0 -4
- package/.claude/docs/examples/design-inspiration-pool.md +59 -0
- package/.claude/docs/examples/ui-prototype-constitution-checklist.md +75 -0
- package/.claude/docs/implementation-summary-v7.md +449 -0
- package/.claude/docs/spec-format-guide.md +349 -0
- package/.claude/docs/state-consolidation-design.md +323 -0
- package/.claude/docs/templates/ARCHITECTURE_TEMPLATE.md +85 -386
- package/.claude/docs/templates/DESIGN_TEMPLATE.md +157 -0
- package/.claude/docs/templates/PROPOSAL_TEMPLATE.md +91 -0
- package/.claude/docs/templates/SPEC_TEMPLATE_DELTA.md +139 -0
- package/.claude/docs/templates/SPEC_TEMPLATE_PROJECT.md +93 -0
- package/.claude/docs/templates/STYLE_TEMPLATE.md +114 -901
- package/.claude/docs/templates/UI_PROTOTYPE_TEMPLATE.md +143 -1205
- package/.claude/hooks/inject-agent-context.ts +9 -9
- package/.claude/scripts/.claude/commands/flow/export-openspec.md +221 -0
- package/.claude/scripts/.claude/commands/flow/import-openspec.md +171 -0
- package/.claude/scripts/__tests__/openspec.test.js +212 -0
- package/.claude/scripts/delta-parser.ts +112 -2
- package/.claude/scripts/export-openspec.js +222 -0
- package/.claude/scripts/import-openspec.js +272 -0
- package/.claude/scripts/validate-scope.sh +200 -0
- package/.claude/skills/{workflow/flow-init → flow-init}/SKILL.md +25 -4
- package/.claude/skills/{workflow/flow-release → flow-release}/SKILL.md +14 -3
- package/.claude/skills/{workflow/flow-spec → flow-spec}/SKILL.md +30 -2
- package/.claude/skills/utility/npm-release/CLAUDE.md +55 -0
- package/.claude/skills/utility/npm-release/SKILL.md +111 -46
- package/.claude/skills/utility/npm-release/references/version-decision-guide.md +134 -0
- package/.claude/skills/utility/npm-release/scripts/atomic-version-bump.sh +95 -0
- package/.claude/skills/utility/npm-release/scripts/validate-version-sync.sh +82 -0
- package/.claude/skills/utility/npm-release/scripts/version-decision-tree.sh +44 -0
- package/.claude/tsc-cache/70d2fc6d-2936-429b-b529-429f1aae8c88/affected-repos.txt +1 -0
- package/.claude/tsc-cache/70d2fc6d-2936-429b-b529-429f1aae8c88/edited-files.log +2 -0
- package/CHANGELOG.md +40 -0
- package/README.md +2 -1
- package/README.zh-CN.md +2 -1
- package/docs/v4.3.0-migration-guide.md +276 -0
- package/lib/harness/CLAUDE.md +5 -4
- package/lib/harness/__tests__/planner.tdd.test.js +125 -0
- package/lib/harness/index.js +4 -2
- package/lib/harness/operations/dispatch.js +13 -0
- package/lib/harness/operations/plan.js +55 -1
- package/lib/harness/operations/release.js +87 -0
- package/lib/harness/operations/verify.js +14 -0
- package/lib/harness/planner.js +131 -0
- package/lib/harness/query.js +126 -0
- package/lib/harness/schemas.js +22 -1
- package/package.json +1 -1
- package/.claude/commands/flow/checklist.md +0 -18
- package/.claude/commands/flow/clarify.md +0 -18
- package/.claude/commands/flow/new.md +0 -23
- package/.claude/commands/flow/quality.md +0 -21
- package/.claude/docs/templates/EPIC_TEMPLATE.md +0 -805
- package/.claude/docs/templates/PRD_TEMPLATE.md +0 -562
- package/.claude/docs/templates/TASKS_TEMPLATE.md +0 -523
- package/.claude/docs/templates/TECH_DESIGN_TEMPLATE.md +0 -1019
- package/.claude/skills/workflow/CLAUDE.md +0 -24
- /package/.claude/skills/{domain/attention-refresh → attention-refresh}/SKILL.md +0 -0
- /package/.claude/skills/{domain/brainstorming → brainstorming}/SKILL.md +0 -0
- /package/.claude/skills/{guardrail/constitution-guardian → constitution-guardian}/SKILL.md +0 -0
- /package/.claude/skills/{utility/constitution-quick-ref → constitution-quick-ref}/SKILL.md +0 -0
- /package/.claude/skills/{domain/debugging → debugging}/SKILL.md +0 -0
- /package/.claude/skills/{utility/file-standards → file-standards}/SKILL.md +0 -0
- /package/.claude/skills/{domain/finishing-branch → finishing-branch}/SKILL.md +0 -0
- /package/.claude/skills/{workflow/flow-dev → flow-dev}/CLAUDE.md +0 -0
- /package/.claude/skills/{workflow/flow-dev → flow-dev}/SKILL.md +0 -0
- /package/.claude/skills/{workflow/flow-dev → flow-dev}/assets/IMPLEMENTATION_PLAN_TEMPLATE.md +0 -0
- /package/.claude/skills/{workflow/flow-dev → flow-dev}/context.jsonl +0 -0
- /package/.claude/skills/{workflow/flow-dev → flow-dev}/dev-implementer.jsonl +0 -0
- /package/.claude/skills/{workflow/flow-dev → flow-dev}/scripts/entry-gate.sh +0 -0
- /package/.claude/skills/{workflow/flow-dev → flow-dev}/scripts/exit-gate.sh +0 -0
- /package/.claude/skills/{workflow/flow-dev → flow-dev}/scripts/task-orchestrator.sh +0 -0
- /package/.claude/skills/{workflow/flow-fix → flow-fix}/SKILL.md +0 -0
- /package/.claude/skills/{workflow/flow-fix → flow-fix}/context.jsonl +0 -0
- /package/.claude/skills/{workflow/flow-fix → flow-fix}/references/bug-analyzer.md +0 -0
- /package/.claude/skills/{workflow/flow-init → flow-init}/assets/BRAINSTORM_TEMPLATE.md +0 -0
- /package/.claude/skills/{workflow/flow-init → flow-init}/assets/INIT_FLOW_TEMPLATE.md +0 -0
- /package/.claude/skills/{workflow/flow-init → flow-init}/assets/RESEARCH_TEMPLATE.md +0 -0
- /package/.claude/skills/{workflow/flow-init → flow-init}/context.jsonl +0 -0
- /package/.claude/skills/{workflow/flow-init → flow-init}/references/flow-researcher.md +0 -0
- /package/.claude/skills/{workflow/flow-init → flow-init}/scripts/check-prerequisites.sh +0 -0
- /package/.claude/skills/{workflow/flow-init → flow-init}/scripts/consolidate-research.sh +0 -0
- /package/.claude/skills/{workflow/flow-init → flow-init}/scripts/create-requirement.sh +0 -0
- /package/.claude/skills/{workflow/flow-init → flow-init}/scripts/generate-research-tasks.sh +0 -0
- /package/.claude/skills/{workflow/flow-init → flow-init}/scripts/populate-research-tasks.sh +0 -0
- /package/.claude/skills/{workflow/flow-init → flow-init}/scripts/validate-research.sh +0 -0
- /package/.claude/skills/{workflow/flow-quality → flow-quality}/SKILL.md +0 -0
- /package/.claude/skills/{workflow/flow-quality → flow-quality}/context.jsonl +0 -0
- /package/.claude/skills/{workflow/flow-quality → flow-quality}/references/code-quality-reviewer.md +0 -0
- /package/.claude/skills/{workflow/flow-quality → flow-quality}/references/qa-tester.md +0 -0
- /package/.claude/skills/{workflow/flow-quality → flow-quality}/references/security-reviewer.md +0 -0
- /package/.claude/skills/{workflow/flow-quality → flow-quality}/references/spec-reviewer.md +0 -0
- /package/.claude/skills/{workflow/flow-release → flow-release}/context.jsonl +0 -0
- /package/.claude/skills/{workflow/flow-release → flow-release}/references/release-manager.md +0 -0
- /package/.claude/skills/{workflow/flow-spec → flow-spec}/CLAUDE.md +0 -0
- /package/.claude/skills/{workflow/flow-spec → flow-spec}/context.jsonl +0 -0
- /package/.claude/skills/{workflow/flow-spec → flow-spec}/scripts/entry-gate.sh +0 -0
- /package/.claude/skills/{workflow/flow-spec → flow-spec}/scripts/exit-gate.sh +0 -0
- /package/.claude/skills/{workflow/flow-spec → flow-spec}/scripts/parallel-orchestrator.sh +0 -0
- /package/.claude/skills/{workflow/flow-spec → flow-spec}/scripts/team-communication.sh +0 -0
- /package/.claude/skills/{workflow/flow-spec → flow-spec}/scripts/team-init.sh +0 -0
- /package/.claude/skills/{workflow/flow-spec → flow-spec}/scripts/test-team-mode.sh +0 -0
- /package/.claude/skills/{workflow/flow-spec → flow-spec}/team-config.json +0 -0
- /package/.claude/skills/{workflow/flow-verify → flow-verify}/CLAUDE.md +0 -0
- /package/.claude/skills/{workflow/flow-verify → flow-verify}/SKILL.md +0 -0
- /package/.claude/skills/{workflow/flow-verify → flow-verify}/context.jsonl +0 -0
- /package/.claude/skills/{utility/fractal-docs → fractal-docs}/SKILL.md +0 -0
- /package/.claude/skills/{utility/journey-checker → journey-checker}/SKILL.md +0 -0
- /package/.claude/skills/{utility/journey-checker → journey-checker}/pressure-scenarios.md +0 -0
- /package/.claude/skills/{domain/receiving-review → receiving-review}/SKILL.md +0 -0
- /package/.claude/skills/{utility/skill-creator → skill-creator}/LICENSE.txt +0 -0
- /package/.claude/skills/{utility/skill-creator → skill-creator}/SKILL.md +0 -0
- /package/.claude/skills/{utility/skill-creator → skill-creator}/references/output-patterns.md +0 -0
- /package/.claude/skills/{utility/skill-creator → skill-creator}/references/workflows.md +0 -0
- /package/.claude/skills/{utility/skill-creator → skill-creator}/scripts/init_skill.py +0 -0
- /package/.claude/skills/{utility/skill-creator → skill-creator}/scripts/package_skill.py +0 -0
- /package/.claude/skills/{utility/skill-creator → skill-creator}/scripts/quick_validate.py +0 -0
- /package/.claude/skills/{domain/tdd → tdd}/SKILL.md +0 -0
- /package/.claude/skills/{guardrail/tdd-enforcer → tdd-enforcer}/SKILL.md +0 -0
- /package/.claude/skills/{domain/verification → verification}/SKILL.md +0 -0
|
@@ -474,6 +474,95 @@ function escapeRegex(str: string): string {
|
|
|
474
474
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
475
475
|
}
|
|
476
476
|
|
|
477
|
+
// ============================================================================
|
|
478
|
+
// Version Bump
|
|
479
|
+
// ============================================================================
|
|
480
|
+
|
|
481
|
+
function bumpVersion(currentVersion: string, deltaBlocks: DeltaBlock[]): string {
|
|
482
|
+
const [major, minor, patch] = currentVersion.split('.').map(Number);
|
|
483
|
+
|
|
484
|
+
// REMOVED → MAJOR +1 (破坏性变更)
|
|
485
|
+
if (deltaBlocks.some(b => b.type === 'REMOVED')) {
|
|
486
|
+
return `${major + 1}.0.0`;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// ADDED → MINOR +1 (新功能)
|
|
490
|
+
if (deltaBlocks.some(b => b.type === 'ADDED')) {
|
|
491
|
+
return `${major}.${minor + 1}.0`;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// MODIFIED/RENAMED → PATCH +1 (修复/改进)
|
|
495
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ============================================================================
|
|
499
|
+
// Merge to Project-Level spec.md
|
|
500
|
+
// ============================================================================
|
|
501
|
+
|
|
502
|
+
export function mergeDeltaToMainSpec(
|
|
503
|
+
mainSpecPath: string,
|
|
504
|
+
deltaSpecPath: string
|
|
505
|
+
): { success: boolean; newVersion: string; error?: string } {
|
|
506
|
+
const fs = require('fs');
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
// 读取文件
|
|
510
|
+
const mainSpecContent = fs.readFileSync(mainSpecPath, 'utf-8');
|
|
511
|
+
const deltaSpecContent = fs.readFileSync(deltaSpecPath, 'utf-8');
|
|
512
|
+
|
|
513
|
+
// 解析 frontmatter
|
|
514
|
+
const { metadata: mainMeta, body: mainBody } = extractYamlFrontmatter(mainSpecContent);
|
|
515
|
+
const { metadata: deltaMeta } = extractYamlFrontmatter(deltaSpecContent);
|
|
516
|
+
|
|
517
|
+
// 验证模块匹配
|
|
518
|
+
if (mainMeta.module !== deltaMeta.module) {
|
|
519
|
+
return {
|
|
520
|
+
success: false,
|
|
521
|
+
newVersion: mainMeta.version || '1.0.0',
|
|
522
|
+
error: `Module mismatch: main=${mainMeta.module}, delta=${deltaMeta.module}`
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// 解析 Delta blocks
|
|
527
|
+
const deltaBlocks = parseDelta(deltaSpecContent);
|
|
528
|
+
|
|
529
|
+
if (deltaBlocks.length === 0) {
|
|
530
|
+
return {
|
|
531
|
+
success: false,
|
|
532
|
+
newVersion: mainMeta.version || '1.0.0',
|
|
533
|
+
error: 'No delta blocks found'
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// 应用 Delta
|
|
538
|
+
const result = applyDelta(mainSpecContent, deltaBlocks);
|
|
539
|
+
|
|
540
|
+
// 更新版本号和时间戳
|
|
541
|
+
const newVersion = bumpVersion(mainMeta.version || '1.0.0', deltaBlocks);
|
|
542
|
+
const updatedMeta = {
|
|
543
|
+
...mainMeta,
|
|
544
|
+
version: newVersion,
|
|
545
|
+
updated_at: new Date().toISOString()
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
// 重新组装 frontmatter
|
|
549
|
+
const yamlLines = Object.entries(updatedMeta).map(([k, v]) => `${k}: "${v}"`);
|
|
550
|
+
const newContent = `---\n${yamlLines.join('\n')}\n---\n${result.split('---\n')[2] || result}`;
|
|
551
|
+
|
|
552
|
+
// 写回文件
|
|
553
|
+
fs.writeFileSync(mainSpecPath, newContent, 'utf-8');
|
|
554
|
+
|
|
555
|
+
return { success: true, newVersion };
|
|
556
|
+
|
|
557
|
+
} catch (error) {
|
|
558
|
+
return {
|
|
559
|
+
success: false,
|
|
560
|
+
newVersion: '',
|
|
561
|
+
error: error instanceof Error ? error.message : String(error)
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
477
566
|
// ============================================================================
|
|
478
567
|
// CLI Interface
|
|
479
568
|
// ============================================================================
|
|
@@ -485,8 +574,9 @@ if (require.main === module) {
|
|
|
485
574
|
if (args.length < 1) {
|
|
486
575
|
console.error('Usage: delta-parser.ts <command> [args]');
|
|
487
576
|
console.error('Commands:');
|
|
488
|
-
console.error(' parse <delta-file>
|
|
489
|
-
console.error(' apply <prd-file> <delta-file>
|
|
577
|
+
console.error(' parse <delta-file> Parse delta file and output JSON');
|
|
578
|
+
console.error(' apply <prd-file> <delta-file> Apply delta to PRD and output result');
|
|
579
|
+
console.error(' merge <main-spec> <delta-spec> Merge delta to project-level spec.md');
|
|
490
580
|
process.exit(1);
|
|
491
581
|
}
|
|
492
582
|
|
|
@@ -520,6 +610,26 @@ if (require.main === module) {
|
|
|
520
610
|
break;
|
|
521
611
|
}
|
|
522
612
|
|
|
613
|
+
case 'merge': {
|
|
614
|
+
const mainSpecPath = args[1];
|
|
615
|
+
const deltaSpecPath = args[2];
|
|
616
|
+
if (!mainSpecPath || !deltaSpecPath) {
|
|
617
|
+
console.error('Error: main-spec and delta-spec required');
|
|
618
|
+
process.exit(1);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const result = mergeDeltaToMainSpec(mainSpecPath, deltaSpecPath);
|
|
622
|
+
|
|
623
|
+
if (result.success) {
|
|
624
|
+
console.log(`✅ Delta merged successfully`);
|
|
625
|
+
console.log(`📦 New version: ${result.newVersion}`);
|
|
626
|
+
} else {
|
|
627
|
+
console.error(`❌ Merge failed: ${result.error}`);
|
|
628
|
+
process.exit(1);
|
|
629
|
+
}
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
|
|
523
633
|
default:
|
|
524
634
|
console.error(`Unknown command: ${command}`);
|
|
525
635
|
process.exit(1);
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* [INPUT]: 依赖 CC-DevFlow spec.md 文件路径
|
|
4
|
+
* [OUTPUT]: 生成 OpenSpec 格式的 spec.md (纯 Requirements,无 CC-DevFlow 元数据)
|
|
5
|
+
* [POS]: OpenSpec 互操作层,被 /flow:export-openspec 命令调用
|
|
6
|
+
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 解析 CC-DevFlow spec.md 格式
|
|
14
|
+
*/
|
|
15
|
+
function parseDevFlowSpec(content) {
|
|
16
|
+
const lines = content.split('\n');
|
|
17
|
+
const result = {
|
|
18
|
+
moduleName: '',
|
|
19
|
+
purpose: '',
|
|
20
|
+
requirements: []
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
let inFrontmatter = false;
|
|
24
|
+
let currentSection = null;
|
|
25
|
+
let currentRequirement = null;
|
|
26
|
+
let currentScenario = null;
|
|
27
|
+
let buffer = [];
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30
|
+
const line = lines[i];
|
|
31
|
+
|
|
32
|
+
// 跳过 YAML frontmatter
|
|
33
|
+
if (line === '---') {
|
|
34
|
+
inFrontmatter = !inFrontmatter;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (inFrontmatter) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// # Module Name
|
|
42
|
+
if (line.startsWith('# ') && !result.moduleName) {
|
|
43
|
+
result.moduleName = line.substring(2).trim();
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ## Purpose
|
|
48
|
+
if (line.startsWith('## Purpose')) {
|
|
49
|
+
currentSection = 'purpose';
|
|
50
|
+
buffer = [];
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ## Requirements
|
|
55
|
+
if (line.startsWith('## Requirements')) {
|
|
56
|
+
if (currentSection === 'purpose') {
|
|
57
|
+
result.purpose = buffer.join('\n').trim();
|
|
58
|
+
}
|
|
59
|
+
currentSection = 'requirements';
|
|
60
|
+
buffer = [];
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ## Design (停止解析,OpenSpec 不需要)
|
|
65
|
+
if (line.startsWith('## Design') || line.startsWith('## Tasks') || line.startsWith('## Verification')) {
|
|
66
|
+
if (currentRequirement) {
|
|
67
|
+
result.requirements.push(currentRequirement);
|
|
68
|
+
currentRequirement = null;
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ### Requirement: Name
|
|
74
|
+
if (line.startsWith('### Requirement:')) {
|
|
75
|
+
if (currentRequirement) {
|
|
76
|
+
result.requirements.push(currentRequirement);
|
|
77
|
+
}
|
|
78
|
+
currentRequirement = {
|
|
79
|
+
name: line.substring(16).trim(),
|
|
80
|
+
description: '',
|
|
81
|
+
scenarios: []
|
|
82
|
+
};
|
|
83
|
+
currentScenario = null;
|
|
84
|
+
buffer = [];
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// #### Scenario: Case
|
|
89
|
+
if (line.startsWith('#### Scenario:')) {
|
|
90
|
+
if (currentRequirement && buffer.length > 0) {
|
|
91
|
+
currentRequirement.description = buffer.join('\n').trim();
|
|
92
|
+
buffer = [];
|
|
93
|
+
}
|
|
94
|
+
currentScenario = {
|
|
95
|
+
name: line.substring(14).trim(),
|
|
96
|
+
steps: []
|
|
97
|
+
};
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// BDD steps
|
|
102
|
+
if (currentScenario && (line.startsWith('- GIVEN') || line.startsWith('- WHEN') || line.startsWith('- THEN') || line.startsWith('- AND'))) {
|
|
103
|
+
currentScenario.steps.push(line.substring(2).trim());
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 收集内容
|
|
108
|
+
if (currentSection === 'purpose' && line.trim()) {
|
|
109
|
+
buffer.push(line);
|
|
110
|
+
} else if (currentRequirement && !currentScenario && line.trim() && !line.startsWith('[NEEDS CLARIFICATION')) {
|
|
111
|
+
buffer.push(line);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 场景结束
|
|
115
|
+
if (currentScenario && line.trim() === '' && currentScenario.steps.length > 0) {
|
|
116
|
+
currentRequirement.scenarios.push(currentScenario);
|
|
117
|
+
currentScenario = null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 处理最后的 requirement
|
|
122
|
+
if (currentRequirement) {
|
|
123
|
+
if (currentScenario && currentScenario.steps.length > 0) {
|
|
124
|
+
currentRequirement.scenarios.push(currentScenario);
|
|
125
|
+
}
|
|
126
|
+
if (buffer.length > 0) {
|
|
127
|
+
currentRequirement.description = buffer.join('\n').trim();
|
|
128
|
+
}
|
|
129
|
+
result.requirements.push(currentRequirement);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 转换为 OpenSpec 格式
|
|
137
|
+
*/
|
|
138
|
+
function convertToOpenSpec(devflowData) {
|
|
139
|
+
let output = `# ${devflowData.moduleName}
|
|
140
|
+
|
|
141
|
+
## Purpose
|
|
142
|
+
|
|
143
|
+
${devflowData.purpose}
|
|
144
|
+
|
|
145
|
+
## Requirements
|
|
146
|
+
|
|
147
|
+
`;
|
|
148
|
+
|
|
149
|
+
// 转换 Requirements
|
|
150
|
+
for (const req of devflowData.requirements) {
|
|
151
|
+
output += `### Requirement: ${req.name}\n`;
|
|
152
|
+
|
|
153
|
+
if (req.description) {
|
|
154
|
+
output += `${req.description}\n\n`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 转换 Scenarios
|
|
158
|
+
for (const scenario of req.scenarios) {
|
|
159
|
+
output += `#### Scenario: ${scenario.name}\n`;
|
|
160
|
+
for (const step of scenario.steps) {
|
|
161
|
+
output += `- ${step}\n`;
|
|
162
|
+
}
|
|
163
|
+
output += '\n';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return output;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 主函数
|
|
172
|
+
*/
|
|
173
|
+
function exportOpenSpec(devflowSpecPath, outputPath) {
|
|
174
|
+
// 读取 CC-DevFlow spec.md
|
|
175
|
+
if (!fs.existsSync(devflowSpecPath)) {
|
|
176
|
+
throw new Error(`DevFlow spec.md not found: ${devflowSpecPath}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const content = fs.readFileSync(devflowSpecPath, 'utf-8');
|
|
180
|
+
|
|
181
|
+
// 解析 CC-DevFlow spec
|
|
182
|
+
const devflowData = parseDevFlowSpec(content);
|
|
183
|
+
|
|
184
|
+
// 转换为 OpenSpec 格式
|
|
185
|
+
const openspecContent = convertToOpenSpec(devflowData);
|
|
186
|
+
|
|
187
|
+
// 写入输出文件
|
|
188
|
+
const outputDir = path.dirname(outputPath);
|
|
189
|
+
if (!fs.existsSync(outputDir)) {
|
|
190
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
fs.writeFileSync(outputPath, openspecContent, 'utf-8');
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
success: true,
|
|
197
|
+
outputPath,
|
|
198
|
+
requirementsCount: devflowData.requirements.length
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// CLI 接口
|
|
203
|
+
if (require.main === module) {
|
|
204
|
+
const args = process.argv.slice(2);
|
|
205
|
+
|
|
206
|
+
if (args.length < 2) {
|
|
207
|
+
console.error('Usage: export-openspec.js <devflow-spec-path> <output-path>');
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const [devflowSpecPath, outputPath] = args;
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const result = exportOpenSpec(devflowSpecPath, outputPath);
|
|
215
|
+
console.log(JSON.stringify(result, null, 2));
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('Error:', error.message);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
module.exports = { exportOpenSpec, parseDevFlowSpec, convertToOpenSpec };
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* [INPUT]: 依赖 OpenSpec spec.md 文件路径,依赖 REQ-ID
|
|
4
|
+
* [OUTPUT]: 生成 CC-DevFlow 格式的 spec.md,自动补充 TDD 任务
|
|
5
|
+
* [POS]: OpenSpec 互操作层,被 /flow:import-openspec 命令调用
|
|
6
|
+
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 解析 OpenSpec spec.md 格式
|
|
14
|
+
*
|
|
15
|
+
* OpenSpec 格式:
|
|
16
|
+
* # Module Name
|
|
17
|
+
* ## Purpose
|
|
18
|
+
* ## Requirements
|
|
19
|
+
* ### Requirement: Name
|
|
20
|
+
* #### Scenario: Case
|
|
21
|
+
* - GIVEN ...
|
|
22
|
+
* - WHEN ...
|
|
23
|
+
* - THEN ...
|
|
24
|
+
*/
|
|
25
|
+
function parseOpenSpecMarkdown(content) {
|
|
26
|
+
const lines = content.split('\n');
|
|
27
|
+
const result = {
|
|
28
|
+
moduleName: '',
|
|
29
|
+
purpose: '',
|
|
30
|
+
requirements: []
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let currentSection = null;
|
|
34
|
+
let currentRequirement = null;
|
|
35
|
+
let currentScenario = null;
|
|
36
|
+
let buffer = [];
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < lines.length; i++) {
|
|
39
|
+
const line = lines[i];
|
|
40
|
+
|
|
41
|
+
// # Module Name
|
|
42
|
+
if (line.startsWith('# ') && !result.moduleName) {
|
|
43
|
+
result.moduleName = line.substring(2).trim();
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ## Purpose
|
|
48
|
+
if (line.startsWith('## Purpose')) {
|
|
49
|
+
currentSection = 'purpose';
|
|
50
|
+
buffer = [];
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ## Requirements
|
|
55
|
+
if (line.startsWith('## Requirements')) {
|
|
56
|
+
if (currentSection === 'purpose') {
|
|
57
|
+
result.purpose = buffer.join('\n').trim();
|
|
58
|
+
}
|
|
59
|
+
currentSection = 'requirements';
|
|
60
|
+
buffer = [];
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ### Requirement: Name
|
|
65
|
+
if (line.startsWith('### Requirement:')) {
|
|
66
|
+
if (currentRequirement) {
|
|
67
|
+
result.requirements.push(currentRequirement);
|
|
68
|
+
}
|
|
69
|
+
currentRequirement = {
|
|
70
|
+
name: line.substring(16).trim(),
|
|
71
|
+
description: '',
|
|
72
|
+
scenarios: []
|
|
73
|
+
};
|
|
74
|
+
currentScenario = null;
|
|
75
|
+
buffer = [];
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// #### Scenario: Case
|
|
80
|
+
if (line.startsWith('#### Scenario:')) {
|
|
81
|
+
if (currentRequirement && buffer.length > 0) {
|
|
82
|
+
currentRequirement.description = buffer.join('\n').trim();
|
|
83
|
+
buffer = [];
|
|
84
|
+
}
|
|
85
|
+
currentScenario = {
|
|
86
|
+
name: line.substring(14).trim(),
|
|
87
|
+
steps: []
|
|
88
|
+
};
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// BDD steps (GIVEN/WHEN/THEN)
|
|
93
|
+
if (currentScenario && (line.startsWith('- GIVEN') || line.startsWith('- WHEN') || line.startsWith('- THEN') || line.startsWith('- AND'))) {
|
|
94
|
+
currentScenario.steps.push(line.substring(2).trim());
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 收集内容
|
|
99
|
+
if (currentSection === 'purpose' && line.trim()) {
|
|
100
|
+
buffer.push(line);
|
|
101
|
+
} else if (currentRequirement && !currentScenario && line.trim()) {
|
|
102
|
+
buffer.push(line);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 场景结束
|
|
106
|
+
if (currentScenario && line.trim() === '' && currentScenario.steps.length > 0) {
|
|
107
|
+
currentRequirement.scenarios.push(currentScenario);
|
|
108
|
+
currentScenario = null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 处理最后的 requirement
|
|
113
|
+
if (currentRequirement) {
|
|
114
|
+
if (currentScenario && currentScenario.steps.length > 0) {
|
|
115
|
+
currentRequirement.scenarios.push(currentScenario);
|
|
116
|
+
}
|
|
117
|
+
result.requirements.push(currentRequirement);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 转换为 CC-DevFlow spec.md 格式
|
|
125
|
+
*/
|
|
126
|
+
function convertToDevFlowSpec(openspecData, reqId, title) {
|
|
127
|
+
const now = new Date().toISOString();
|
|
128
|
+
|
|
129
|
+
let output = `---
|
|
130
|
+
req_id: "${reqId}"
|
|
131
|
+
title: "${title}"
|
|
132
|
+
created_at: "${now}"
|
|
133
|
+
updated_at: "${now}"
|
|
134
|
+
version: "1.0.0"
|
|
135
|
+
status: "draft"
|
|
136
|
+
source: "openspec"
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
# ${openspecData.moduleName}
|
|
140
|
+
|
|
141
|
+
## Purpose
|
|
142
|
+
|
|
143
|
+
${openspecData.purpose}
|
|
144
|
+
|
|
145
|
+
## Requirements
|
|
146
|
+
|
|
147
|
+
`;
|
|
148
|
+
|
|
149
|
+
// 转换 Requirements
|
|
150
|
+
for (const req of openspecData.requirements) {
|
|
151
|
+
output += `### Requirement: ${req.name}\n\n`;
|
|
152
|
+
|
|
153
|
+
if (req.description) {
|
|
154
|
+
output += `${req.description}\n\n`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 转换 Scenarios (BDD 格式)
|
|
158
|
+
for (const scenario of req.scenarios) {
|
|
159
|
+
output += `#### Scenario: ${scenario.name}\n\n`;
|
|
160
|
+
for (const step of scenario.steps) {
|
|
161
|
+
output += `- ${step}\n`;
|
|
162
|
+
}
|
|
163
|
+
output += '\n';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 添加 Design 部分 (空白,待填充)
|
|
168
|
+
output += `## Design
|
|
169
|
+
|
|
170
|
+
[NEEDS CLARIFICATION: 技术实现方案]
|
|
171
|
+
|
|
172
|
+
### Architecture
|
|
173
|
+
|
|
174
|
+
[NEEDS CLARIFICATION: 架构设计]
|
|
175
|
+
|
|
176
|
+
### Data Model
|
|
177
|
+
|
|
178
|
+
[NEEDS CLARIFICATION: 数据模型]
|
|
179
|
+
|
|
180
|
+
### API Design
|
|
181
|
+
|
|
182
|
+
[NEEDS CLARIFICATION: API 设计]
|
|
183
|
+
|
|
184
|
+
## Tasks
|
|
185
|
+
|
|
186
|
+
`;
|
|
187
|
+
|
|
188
|
+
// 自动生成 TDD 任务
|
|
189
|
+
let taskId = 1;
|
|
190
|
+
for (const req of openspecData.requirements) {
|
|
191
|
+
const featureName = req.name;
|
|
192
|
+
|
|
193
|
+
// TEST 任务
|
|
194
|
+
output += `- [ ] T${String(taskId).padStart(3, '0')} [TEST] ${featureName} - 测试\n`;
|
|
195
|
+
const testTaskId = taskId;
|
|
196
|
+
taskId++;
|
|
197
|
+
|
|
198
|
+
// IMPL 任务
|
|
199
|
+
output += `- [ ] T${String(taskId).padStart(3, '0')} [IMPL] ${featureName} - 实现 (dependsOn:T${String(testTaskId).padStart(3, '0')})\n`;
|
|
200
|
+
taskId++;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
output += `
|
|
204
|
+
## Verification
|
|
205
|
+
|
|
206
|
+
`;
|
|
207
|
+
|
|
208
|
+
// 生成验收标准
|
|
209
|
+
for (const req of openspecData.requirements) {
|
|
210
|
+
output += `- [ ] ${req.name}\n`;
|
|
211
|
+
for (const scenario of req.scenarios) {
|
|
212
|
+
output += ` - [ ] ${scenario.name}\n`;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return output;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 主函数
|
|
221
|
+
*/
|
|
222
|
+
function importOpenSpec(openspecPath, reqId, title, outputPath) {
|
|
223
|
+
// 读取 OpenSpec 文件
|
|
224
|
+
if (!fs.existsSync(openspecPath)) {
|
|
225
|
+
throw new Error(`OpenSpec file not found: ${openspecPath}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const content = fs.readFileSync(openspecPath, 'utf-8');
|
|
229
|
+
|
|
230
|
+
// 解析 OpenSpec
|
|
231
|
+
const openspecData = parseOpenSpecMarkdown(content);
|
|
232
|
+
|
|
233
|
+
// 转换为 CC-DevFlow 格式
|
|
234
|
+
const devflowSpec = convertToDevFlowSpec(openspecData, reqId, title);
|
|
235
|
+
|
|
236
|
+
// 写入输出文件
|
|
237
|
+
const outputDir = path.dirname(outputPath);
|
|
238
|
+
if (!fs.existsSync(outputDir)) {
|
|
239
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fs.writeFileSync(outputPath, devflowSpec, 'utf-8');
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
success: true,
|
|
246
|
+
outputPath,
|
|
247
|
+
requirementsCount: openspecData.requirements.length,
|
|
248
|
+
tasksCount: openspecData.requirements.length * 2 // TEST + IMPL
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// CLI 接口
|
|
253
|
+
if (require.main === module) {
|
|
254
|
+
const args = process.argv.slice(2);
|
|
255
|
+
|
|
256
|
+
if (args.length < 4) {
|
|
257
|
+
console.error('Usage: import-openspec.js <openspec-path> <req-id> <title> <output-path>');
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const [openspecPath, reqId, title, outputPath] = args;
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const result = importOpenSpec(openspecPath, reqId, title, outputPath);
|
|
265
|
+
console.log(JSON.stringify(result, null, 2));
|
|
266
|
+
} catch (error) {
|
|
267
|
+
console.error('Error:', error.message);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
module.exports = { importOpenSpec, parseOpenSpecMarkdown, convertToDevFlowSpec };
|