ai-dev-flow 3.10.0__py3-none-any.whl
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.
- ai_dev_flow/__init__.py +5 -0
- ai_dev_flow/__main__.py +6 -0
- ai_dev_flow/checkpoint.py +65 -0
- ai_dev_flow/cli.py +598 -0
- ai_dev_flow/config.py +404 -0
- ai_dev_flow/data/pipeline.yaml +193 -0
- ai_dev_flow/data/profiles/java-spring.yml +140 -0
- ai_dev_flow/data/recipes/steps/00-init-project.yaml +168 -0
- ai_dev_flow/data/recipes/steps/01-refine-requirement.yaml +117 -0
- ai_dev_flow/data/recipes/steps/02-analysis.yaml +97 -0
- ai_dev_flow/data/recipes/steps/03-design.yaml +91 -0
- ai_dev_flow/data/recipes/steps/03.5-decompose.yaml +120 -0
- ai_dev_flow/data/recipes/steps/05-build.yaml +72 -0
- ai_dev_flow/data/recipes/steps/06-review.yaml +115 -0
- ai_dev_flow/data/recipes/steps/07-delivery.yaml +76 -0
- ai_dev_flow/data/recipes/steps/task-template.yaml +74 -0
- ai_dev_flow/error_handler.py +122 -0
- ai_dev_flow/executor.py +406 -0
- ai_dev_flow/git_ops.py +187 -0
- ai_dev_flow/knowledge_accumulator.py +121 -0
- ai_dev_flow/logger.py +84 -0
- ai_dev_flow/sandbox.py +291 -0
- ai_dev_flow/semantic_summarizer.py +351 -0
- ai_dev_flow/snapshot.py +210 -0
- ai_dev_flow/state.py +87 -0
- ai_dev_flow/task_context.py +134 -0
- ai_dev_flow/task_graph.py +841 -0
- ai_dev_flow/task_state.py +151 -0
- ai_dev_flow/watchdog.py +120 -0
- ai_dev_flow-3.10.0.dist-info/METADATA +171 -0
- ai_dev_flow-3.10.0.dist-info/RECORD +35 -0
- ai_dev_flow-3.10.0.dist-info/WHEEL +5 -0
- ai_dev_flow-3.10.0.dist-info/entry_points.txt +2 -0
- ai_dev_flow-3.10.0.dist-info/licenses/LICENSE +21 -0
- ai_dev_flow-3.10.0.dist-info/top_level.txt +1 -0
ai_dev_flow/config.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"""配置加载 - pipeline.yaml 和 profile.yml 解析。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
import re
|
|
7
|
+
import logging
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
|
|
11
|
+
from ai_dev_flow.executor import _needs_yaml_quoting
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("ai-dev-flow")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class StageConfig:
|
|
18
|
+
id: str
|
|
19
|
+
name: str
|
|
20
|
+
recipe: str = ""
|
|
21
|
+
max_turns: int = 0
|
|
22
|
+
params: dict[str, str] = field(default_factory=dict)
|
|
23
|
+
output_file: str | None = None
|
|
24
|
+
is_checkpoint: bool = False
|
|
25
|
+
checkpoint_prompt: str = ""
|
|
26
|
+
state_value: str = ""
|
|
27
|
+
is_task_graph: bool = False
|
|
28
|
+
prerequisite: str = "" # 前置阶段 id 或条件名(如 "task_graph_success")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class TaskConfig:
|
|
33
|
+
"""单个原子任务定义。"""
|
|
34
|
+
id: str
|
|
35
|
+
name: str
|
|
36
|
+
description: str
|
|
37
|
+
category: str = ""
|
|
38
|
+
estimated_turns: int = 40
|
|
39
|
+
priority: str = "P1"
|
|
40
|
+
depends_on: list[str] = field(default_factory=list)
|
|
41
|
+
input_files: list[str] = field(default_factory=list)
|
|
42
|
+
output_files: list[str] = field(default_factory=list)
|
|
43
|
+
context_notes: str = ""
|
|
44
|
+
reference_docs: list[str] = field(default_factory=list)
|
|
45
|
+
module: str = "" # 所属模块/子系统
|
|
46
|
+
parallel_group: str | None = None
|
|
47
|
+
sub_pipeline: bool = False # 大模块内走 mini-pipeline(方案→编码→测试→审查)
|
|
48
|
+
retry_limit: int = 2
|
|
49
|
+
timeout_minutes: int = 15
|
|
50
|
+
sandbox_enabled: bool = False # 任务是否在 git worktree 沙箱中执行
|
|
51
|
+
verification: str = "auto" # "auto"|"backend"|"frontend"|"none" — 按任务类型选验证策略
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class TaskGraphConfig:
|
|
56
|
+
"""任务图定义。"""
|
|
57
|
+
version: str = "1.0"
|
|
58
|
+
project: str = ""
|
|
59
|
+
created_at: str = ""
|
|
60
|
+
total_estimated_turns: int = 0
|
|
61
|
+
max_workers: int = 3
|
|
62
|
+
tasks: list[TaskConfig] = field(default_factory=list)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class PipelineConfig:
|
|
67
|
+
stages: list[StageConfig]
|
|
68
|
+
|
|
69
|
+
def find_resume_index(self, current_state: str) -> int:
|
|
70
|
+
"""根据当前状态找到应恢复的阶段索引。返回 -1 表示从头开始。"""
|
|
71
|
+
if not current_state:
|
|
72
|
+
return 0
|
|
73
|
+
for i, stage in enumerate(self.stages):
|
|
74
|
+
if stage.state_value == current_state:
|
|
75
|
+
return i + 1
|
|
76
|
+
logger.warning(f"unknown state '{current_state}', starting from beginning")
|
|
77
|
+
return 0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def load_pipeline(path: Path) -> PipelineConfig:
|
|
81
|
+
"""从 pipeline.yaml 加载阶段定义。"""
|
|
82
|
+
if not path.exists():
|
|
83
|
+
raise FileNotFoundError(f"pipeline config not found: {path}")
|
|
84
|
+
|
|
85
|
+
with open(path, encoding="utf-8") as f:
|
|
86
|
+
data = yaml.safe_load(f)
|
|
87
|
+
|
|
88
|
+
stages = []
|
|
89
|
+
for item in data.get("stages", []):
|
|
90
|
+
stages.append(StageConfig(
|
|
91
|
+
id=item["id"],
|
|
92
|
+
name=item["name"],
|
|
93
|
+
recipe=item.get("recipe", ""),
|
|
94
|
+
max_turns=item.get("max_turns", 0),
|
|
95
|
+
params=item.get("params", {}),
|
|
96
|
+
output_file=item.get("output_file"),
|
|
97
|
+
is_checkpoint=item.get("is_checkpoint", False),
|
|
98
|
+
checkpoint_prompt=item.get("checkpoint_prompt", ""),
|
|
99
|
+
state_value=item["state_value"],
|
|
100
|
+
is_task_graph=item.get("is_task_graph", False),
|
|
101
|
+
prerequisite=item.get("prerequisite", ""),
|
|
102
|
+
))
|
|
103
|
+
|
|
104
|
+
logger.debug(f"loaded {len(stages)} stages from {path}")
|
|
105
|
+
return PipelineConfig(stages=stages)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class EmptyTaskGraphError(Exception):
|
|
109
|
+
"""tasks.yaml 解析后无有效任务——可能因 YAML 语法错误或缺少 id 字段。"""
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def load_task_graph(path: Path) -> TaskGraphConfig:
|
|
113
|
+
"""从 tasks.yaml 加载任务图定义。自动跳过无效任务并校验依赖引用。
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
FileNotFoundError: tasks.yaml 不存在
|
|
117
|
+
EmptyTaskGraphError: 文件存在但无法提取任何有效任务(YAML 语法错误或缺失 id 字段)
|
|
118
|
+
"""
|
|
119
|
+
if not path.exists():
|
|
120
|
+
raise FileNotFoundError(f"task graph config not found: {path}")
|
|
121
|
+
|
|
122
|
+
with open(path, encoding="utf-8") as f:
|
|
123
|
+
text = f.read()
|
|
124
|
+
|
|
125
|
+
repair_attempted = False
|
|
126
|
+
try:
|
|
127
|
+
data = yaml.safe_load(text)
|
|
128
|
+
except yaml.YAMLError:
|
|
129
|
+
logger.warning("tasks.yaml has YAML syntax errors, attempting auto-repair")
|
|
130
|
+
repaired = _repair_yaml(text)
|
|
131
|
+
repair_attempted = True
|
|
132
|
+
try:
|
|
133
|
+
data = yaml.safe_load(repaired)
|
|
134
|
+
path.write_text(repaired, encoding="utf-8")
|
|
135
|
+
logger.info("tasks.yaml auto-repaired and saved")
|
|
136
|
+
except yaml.YAMLError as e:
|
|
137
|
+
raise EmptyTaskGraphError(
|
|
138
|
+
f"tasks.yaml YAML 语法错误无法自动修复: {e}\n"
|
|
139
|
+
f" 文件: {path}\n"
|
|
140
|
+
f" 建议: 检查 YAML 缩进、特殊字符引号、块标量语法"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
tasks = []
|
|
144
|
+
task_ids: set[str] = set()
|
|
145
|
+
skipped = 0
|
|
146
|
+
for item in data.get("tasks", []):
|
|
147
|
+
if not isinstance(item, dict):
|
|
148
|
+
# YAML 序列包含非映射项(如 bare string),报告并跳过
|
|
149
|
+
logger.warning(f"Skipping non-mapping task entry: {str(item)[:100]}")
|
|
150
|
+
skipped += 1
|
|
151
|
+
continue
|
|
152
|
+
tid = item.get("id")
|
|
153
|
+
if not tid:
|
|
154
|
+
logger.warning(f"Skipping task without 'id': {item.get('name', str(item)[:80])}")
|
|
155
|
+
skipped += 1
|
|
156
|
+
continue
|
|
157
|
+
if tid in task_ids:
|
|
158
|
+
logger.warning(f"Skipping duplicate task id: {tid}")
|
|
159
|
+
skipped += 1
|
|
160
|
+
continue
|
|
161
|
+
task_ids.add(tid)
|
|
162
|
+
|
|
163
|
+
# 校验 estimated_turns 合理范围
|
|
164
|
+
turns = item.get("estimated_turns", 40)
|
|
165
|
+
if not isinstance(turns, (int, float)) or turns < 1:
|
|
166
|
+
logger.warning(f"Task {tid}: invalid estimated_turns={turns}, using 40")
|
|
167
|
+
turns = 40
|
|
168
|
+
if isinstance(turns, float):
|
|
169
|
+
turns = int(turns)
|
|
170
|
+
|
|
171
|
+
# 校验 priority
|
|
172
|
+
priority = item.get("priority", "P1")
|
|
173
|
+
if priority not in ("P0", "P1", "P2"):
|
|
174
|
+
logger.warning(f"Task {tid}: invalid priority={priority}, using P1")
|
|
175
|
+
priority = "P1"
|
|
176
|
+
|
|
177
|
+
# 校验 output_files — 过滤 AI 幻觉的非文件路径(纯中文描述等)
|
|
178
|
+
raw_outputs = item.get("output_files", [])
|
|
179
|
+
valid_outputs = []
|
|
180
|
+
for f in raw_outputs:
|
|
181
|
+
if not isinstance(f, str) or not f.strip():
|
|
182
|
+
continue
|
|
183
|
+
if _looks_like_file_path(f):
|
|
184
|
+
valid_outputs.append(f)
|
|
185
|
+
else:
|
|
186
|
+
logger.warning(
|
|
187
|
+
f"Task {tid}: output_file '{f}' does not look like a file path — "
|
|
188
|
+
f"likely an AI hallucination, skipping"
|
|
189
|
+
)
|
|
190
|
+
if len(valid_outputs) < len(raw_outputs):
|
|
191
|
+
logger.warning(
|
|
192
|
+
f"Task {tid}: filtered {len(raw_outputs) - len(valid_outputs)}/{len(raw_outputs)} "
|
|
193
|
+
f"invalid output_files"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
tasks.append(TaskConfig(
|
|
197
|
+
id=tid,
|
|
198
|
+
name=item.get("name", tid),
|
|
199
|
+
description=item.get("description", ""),
|
|
200
|
+
category=item.get("category", ""),
|
|
201
|
+
estimated_turns=turns,
|
|
202
|
+
priority=priority,
|
|
203
|
+
depends_on=item.get("depends_on", []),
|
|
204
|
+
input_files=item.get("input_files", []),
|
|
205
|
+
output_files=valid_outputs,
|
|
206
|
+
context_notes=item.get("context_notes", ""),
|
|
207
|
+
reference_docs=item.get("reference_docs", []),
|
|
208
|
+
module=item.get("module", ""),
|
|
209
|
+
parallel_group=item.get("parallel_group"),
|
|
210
|
+
sub_pipeline=item.get("sub_pipeline", False),
|
|
211
|
+
retry_limit=item.get("retry_limit", 2),
|
|
212
|
+
timeout_minutes=item.get("timeout_minutes", 15),
|
|
213
|
+
sandbox_enabled=item.get("sandbox_enabled", False),
|
|
214
|
+
verification=item.get("verification", "auto"),
|
|
215
|
+
))
|
|
216
|
+
|
|
217
|
+
if not tasks:
|
|
218
|
+
raw_items = data.get("tasks", [])
|
|
219
|
+
if raw_items:
|
|
220
|
+
raise EmptyTaskGraphError(
|
|
221
|
+
f"tasks.yaml 中 {len(raw_items)} 个条目均无法解析为有效任务\n"
|
|
222
|
+
f" 文件: {path}\n"
|
|
223
|
+
f" 已跳过: {skipped} 个\n"
|
|
224
|
+
f" 常见原因: 任务条目缺少 'id:' 字段前缀\n"
|
|
225
|
+
f" 示例: '- task-name' 应改为 '- id: task-name'"
|
|
226
|
+
)
|
|
227
|
+
raise EmptyTaskGraphError(
|
|
228
|
+
f"tasks.yaml 中未定义任何任务\n"
|
|
229
|
+
f" 文件: {path}\n"
|
|
230
|
+
f" 修复后: {'已' if repair_attempted else '无'}自动修复"
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# 后校验:检查 depends_on 引用的 task_id 是否存在
|
|
234
|
+
for t in tasks:
|
|
235
|
+
for dep in t.depends_on:
|
|
236
|
+
if dep not in task_ids:
|
|
237
|
+
logger.warning(f"Task {t.id}: depends_on '{dep}' does not exist — treating as external")
|
|
238
|
+
|
|
239
|
+
if skipped:
|
|
240
|
+
logger.warning(f"Skipped {skipped} invalid task(s) in {path}")
|
|
241
|
+
|
|
242
|
+
return TaskGraphConfig(
|
|
243
|
+
version=data.get("version", "1.0"),
|
|
244
|
+
project=data.get("project", ""),
|
|
245
|
+
created_at=data.get("created_at", ""),
|
|
246
|
+
total_estimated_turns=data.get("total_estimated_turns", 0),
|
|
247
|
+
max_workers=data.get("max_workers", 3),
|
|
248
|
+
tasks=tasks,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def load_profile(profile_path: Path) -> dict | None:
|
|
253
|
+
"""加载项目画像文件。YAML 语法错误时自动修复重试一次;仍失败则回退到最小可用模板。"""
|
|
254
|
+
if not profile_path.exists():
|
|
255
|
+
return None
|
|
256
|
+
with open(profile_path, encoding="utf-8") as f:
|
|
257
|
+
text = f.read()
|
|
258
|
+
try:
|
|
259
|
+
return yaml.safe_load(text)
|
|
260
|
+
except yaml.YAMLError:
|
|
261
|
+
logger.warning("profile.yml has YAML syntax errors, attempting auto-repair")
|
|
262
|
+
repaired = _repair_yaml(text)
|
|
263
|
+
try:
|
|
264
|
+
yaml.safe_load(repaired)
|
|
265
|
+
profile_path.write_text(repaired, encoding="utf-8")
|
|
266
|
+
logger.info("profile.yml auto-repaired and saved")
|
|
267
|
+
return yaml.safe_load(repaired)
|
|
268
|
+
except yaml.YAMLError as e:
|
|
269
|
+
logger.warning(f"profile.yml repair failed, falling back to minimal template: {e}")
|
|
270
|
+
fallback = _make_minimal_profile(text)
|
|
271
|
+
profile_path.write_text(yaml.dump(fallback, allow_unicode=True), encoding="utf-8")
|
|
272
|
+
logger.info("profile.yml replaced with minimal template")
|
|
273
|
+
return fallback
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _repair_yaml(text: str) -> str:
|
|
277
|
+
"""修复 goose 生成 YAML 时的常见语法错误。
|
|
278
|
+
|
|
279
|
+
修复策略(按优先级):
|
|
280
|
+
1. bare task ID → - id: <bare-id>(缺 id: 前缀)
|
|
281
|
+
2. 块标量 | > → 引号字符串
|
|
282
|
+
3. 序列内联映射 - key: @value → 加引号
|
|
283
|
+
4. 普通映射 key: @value → 加引号
|
|
284
|
+
"""
|
|
285
|
+
lines = text.splitlines()
|
|
286
|
+
# Pass 1: 修复缺少 id: 前缀的 bare task ID
|
|
287
|
+
lines = _fix_bare_task_ids(lines)
|
|
288
|
+
# Pass 2: 块标量 + 特殊字符引号修复
|
|
289
|
+
fixed = []
|
|
290
|
+
i = 0
|
|
291
|
+
while i < len(lines):
|
|
292
|
+
line = lines[i]
|
|
293
|
+
# 检测块标量:key: | 或 key: >
|
|
294
|
+
m_block = re.match(r'^(\s+)([\w-]+)\s*:\s*[|>]\s*$', line)
|
|
295
|
+
if m_block:
|
|
296
|
+
indent = len(m_block.group(1))
|
|
297
|
+
key_indent = indent
|
|
298
|
+
value_lines = []
|
|
299
|
+
i += 1
|
|
300
|
+
while i < len(lines):
|
|
301
|
+
stripped = lines[i].rstrip()
|
|
302
|
+
if not stripped or stripped.lstrip().startswith('#'):
|
|
303
|
+
i += 1
|
|
304
|
+
continue
|
|
305
|
+
leading_spaces = len(lines[i]) - len(lines[i].lstrip())
|
|
306
|
+
if leading_spaces <= key_indent:
|
|
307
|
+
break
|
|
308
|
+
value_lines.append(stripped)
|
|
309
|
+
i += 1
|
|
310
|
+
escaped = '\\n'.join(value_lines).replace('"', '\\"')
|
|
311
|
+
fixed.append(f'{m_block.group(1)}{m_block.group(2)}: "{escaped}"')
|
|
312
|
+
continue
|
|
313
|
+
|
|
314
|
+
# 修复 YAML 序列内联映射: - key: value(含 @ 等特殊字符)
|
|
315
|
+
m_seq = re.match(r'^(\s+)-\s+([\w-]+):\s+(.+)', line)
|
|
316
|
+
if m_seq and '\"' not in m_seq.group(3).lstrip() and '\'' not in m_seq.group(3).lstrip():
|
|
317
|
+
val = m_seq.group(3)
|
|
318
|
+
if _needs_yaml_quoting(val):
|
|
319
|
+
val = val.replace('\"', '\\"')
|
|
320
|
+
line = f'{m_seq.group(1)}- {m_seq.group(2)}: "{val}"'
|
|
321
|
+
|
|
322
|
+
# 修复普通映射 key: value 中未加引号的值
|
|
323
|
+
m = re.match(r'^(\s+)([\w-]+):\s+(.+)', line)
|
|
324
|
+
if m and '\"' not in m.group(3).lstrip() and '\'' not in m.group(3).lstrip():
|
|
325
|
+
val = m.group(3)
|
|
326
|
+
if _needs_yaml_quoting(val):
|
|
327
|
+
val = val.replace('\"', '\\"')
|
|
328
|
+
line = f'{m.group(1)}{m.group(2)}: "{val}"'
|
|
329
|
+
fixed.append(line)
|
|
330
|
+
i += 1
|
|
331
|
+
return '\n'.join(fixed)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _looks_like_file_path(s: str) -> bool:
|
|
335
|
+
"""判断字符串是否像文件路径(而非 AI 幻觉的中文描述)。"""
|
|
336
|
+
s = s.strip()
|
|
337
|
+
if not s:
|
|
338
|
+
return False
|
|
339
|
+
if "/" in s or "\\" in s:
|
|
340
|
+
return True
|
|
341
|
+
if re.search(r'\.[a-zA-Z0-9]{1,10}$', s):
|
|
342
|
+
return True
|
|
343
|
+
if re.match(r'^[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef()(),。、《》??!!::;;]+$', s):
|
|
344
|
+
return False
|
|
345
|
+
if re.search(r'[a-zA-Z0-9]', s):
|
|
346
|
+
return True
|
|
347
|
+
return False
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _fix_bare_task_ids(lines: list[str]) -> list[str]:
|
|
351
|
+
"""修复 YAML 序列中缺少 id: 前缀的 bare task ID。
|
|
352
|
+
|
|
353
|
+
将:
|
|
354
|
+
- task-something
|
|
355
|
+
name: "..."
|
|
356
|
+
转换为:
|
|
357
|
+
- id: task-something
|
|
358
|
+
name: "..."
|
|
359
|
+
"""
|
|
360
|
+
result = []
|
|
361
|
+
i = 0
|
|
362
|
+
while i < len(lines):
|
|
363
|
+
line = lines[i]
|
|
364
|
+
m = re.match(r'^(\s+)-\s+([\w-]+)$', line)
|
|
365
|
+
if m:
|
|
366
|
+
current_indent = len(m.group(1))
|
|
367
|
+
bare_value = m.group(2)
|
|
368
|
+
# 检查下一行是否是更深的缩进 + key: value 映射
|
|
369
|
+
if i + 1 < len(lines):
|
|
370
|
+
next_line = lines[i + 1]
|
|
371
|
+
next_indent = len(next_line) - len(next_line.lstrip())
|
|
372
|
+
if next_indent > current_indent and re.match(r'\s+[\w-]+\s*:', next_line):
|
|
373
|
+
result.append(f'{m.group(1)}- id: {bare_value}')
|
|
374
|
+
i += 1
|
|
375
|
+
continue
|
|
376
|
+
result.append(line)
|
|
377
|
+
i += 1
|
|
378
|
+
return result
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _make_minimal_profile(text: str) -> dict:
|
|
382
|
+
"""从破损 YAML 中提取可用的 JDK/Framework 信息,构造最小可用 profile。"""
|
|
383
|
+
profile: dict = {
|
|
384
|
+
"profile": "java-spring",
|
|
385
|
+
"description": "Auto-generated minimal profile (original YAML was unrepairable)",
|
|
386
|
+
"projectRules": {},
|
|
387
|
+
"backend": {"language": "Java", "jdk": {"compileRelease": 17}, "buildTool": "Maven"},
|
|
388
|
+
"commands": {
|
|
389
|
+
"compileOnly": ["mvn -DskipTests compile"],
|
|
390
|
+
"test": ["mvn test"],
|
|
391
|
+
},
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
# 尝试从 text 中提取 java.version
|
|
395
|
+
m = re.search(r'<java\.version>(\d+)</java\.version>', text)
|
|
396
|
+
if m:
|
|
397
|
+
profile["backend"]["jdk"]["compileRelease"] = int(m.group(1))
|
|
398
|
+
|
|
399
|
+
# 尝试从 text 中提取项目名
|
|
400
|
+
m = re.search(r'profile:\s*(\S+)', text)
|
|
401
|
+
if m:
|
|
402
|
+
profile["profile"] = m.group(1)
|
|
403
|
+
|
|
404
|
+
return profile
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# AI 全流程开发管线 — 阶段定义
|
|
2
|
+
# 添加新阶段只需在此文件中新增条目,无需修改任何 Python 代码。
|
|
3
|
+
|
|
4
|
+
stages:
|
|
5
|
+
# ============================================================
|
|
6
|
+
# Phase 0: 项目初始化(自动扫描生成 profile)
|
|
7
|
+
# ============================================================
|
|
8
|
+
- id: phase0
|
|
9
|
+
name: "项目初始化"
|
|
10
|
+
recipe: recipes/steps/00-init-project.yaml
|
|
11
|
+
max_turns: 60
|
|
12
|
+
params:
|
|
13
|
+
project_root: "{P}"
|
|
14
|
+
output_profile: "{AD}/profile.yml"
|
|
15
|
+
output_project_map: "{AD}/project-map.md"
|
|
16
|
+
output_file: "{AD}/profile.yml"
|
|
17
|
+
state_value: init_done
|
|
18
|
+
|
|
19
|
+
# ============================================================
|
|
20
|
+
# Phase 1: 需求精炼
|
|
21
|
+
# ============================================================
|
|
22
|
+
- id: phase1
|
|
23
|
+
name: "需求精炼"
|
|
24
|
+
recipe: recipes/steps/01-refine-requirement.yaml
|
|
25
|
+
max_turns: 80
|
|
26
|
+
params:
|
|
27
|
+
project_root: "{P}"
|
|
28
|
+
profile: "{AD}/profile.yml"
|
|
29
|
+
project_map: "{AD}/project-map.md"
|
|
30
|
+
requirement_raw: "{REQ}"
|
|
31
|
+
requirement: "{AD}/requirement.md"
|
|
32
|
+
output_file: "{AD}/requirement.md"
|
|
33
|
+
state_value: refine_done
|
|
34
|
+
|
|
35
|
+
# ============================================================
|
|
36
|
+
# Checkpoint A: 确认需求
|
|
37
|
+
# ============================================================
|
|
38
|
+
- id: checkpoint_a
|
|
39
|
+
name: "确认需求"
|
|
40
|
+
is_checkpoint: true
|
|
41
|
+
checkpoint_prompt: "请查看 {AD}/requirement.md,确认需求文档无误后按 Enter 继续"
|
|
42
|
+
state_value: checkpoint_a_done
|
|
43
|
+
|
|
44
|
+
# ============================================================
|
|
45
|
+
# Phase 2: 需求分析
|
|
46
|
+
# ============================================================
|
|
47
|
+
- id: phase2
|
|
48
|
+
name: "需求分析"
|
|
49
|
+
recipe: recipes/steps/02-analysis.yaml
|
|
50
|
+
max_turns: 100
|
|
51
|
+
params:
|
|
52
|
+
project_root: "{P}"
|
|
53
|
+
requirement: "{AD}/requirement.md"
|
|
54
|
+
profile: "{AD}/profile.yml"
|
|
55
|
+
project_map: "{AD}/project-map.md"
|
|
56
|
+
output_analysis: "{OUT}/02-analysis.md"
|
|
57
|
+
output_file: "{OUT}/02-analysis.md"
|
|
58
|
+
state_value: analysis_done
|
|
59
|
+
|
|
60
|
+
# ============================================================
|
|
61
|
+
# Checkpoint B: 闭环不确定项
|
|
62
|
+
# ============================================================
|
|
63
|
+
- id: checkpoint_b
|
|
64
|
+
name: "闭环不确定项"
|
|
65
|
+
is_checkpoint: true
|
|
66
|
+
checkpoint_prompt: "请查看 {OUT}/02-analysis.md,逐条确认不确定项后按 Enter 继续"
|
|
67
|
+
state_value: checkpoint_b_done
|
|
68
|
+
|
|
69
|
+
# ============================================================
|
|
70
|
+
# Phase 3: 工程方案
|
|
71
|
+
# ============================================================
|
|
72
|
+
- id: phase3
|
|
73
|
+
name: "工程方案"
|
|
74
|
+
recipe: recipes/steps/03-design.yaml
|
|
75
|
+
max_turns: 120
|
|
76
|
+
params:
|
|
77
|
+
project_root: "{P}"
|
|
78
|
+
requirement: "{AD}/requirement.md"
|
|
79
|
+
input_analysis: "{OUT}/02-analysis.md"
|
|
80
|
+
profile: "{AD}/profile.yml"
|
|
81
|
+
output_plan: "{OUT}/03-plan.md"
|
|
82
|
+
output_file: "{OUT}/03-plan.md"
|
|
83
|
+
state_value: design_done
|
|
84
|
+
|
|
85
|
+
# ============================================================
|
|
86
|
+
# Checkpoint C: 确认方案
|
|
87
|
+
# ============================================================
|
|
88
|
+
- id: checkpoint_c
|
|
89
|
+
name: "确认方案"
|
|
90
|
+
is_checkpoint: true
|
|
91
|
+
checkpoint_prompt: "请查看 {OUT}/03-plan.md,确认方案可行后按 Enter 继续"
|
|
92
|
+
state_value: checkpoint_c_done
|
|
93
|
+
|
|
94
|
+
# ============================================================
|
|
95
|
+
# Phase 3.5: 任务拆分
|
|
96
|
+
# ============================================================
|
|
97
|
+
- id: phase3_5
|
|
98
|
+
name: "任务拆分"
|
|
99
|
+
recipe: recipes/steps/03.5-decompose.yaml
|
|
100
|
+
max_turns: 80
|
|
101
|
+
params:
|
|
102
|
+
requirement: "{AD}/requirement.md"
|
|
103
|
+
input_analysis: "{OUT}/02-analysis.md"
|
|
104
|
+
input_plan: "{OUT}/03-plan.md"
|
|
105
|
+
profile: "{AD}/profile.yml"
|
|
106
|
+
project_root: "{P}"
|
|
107
|
+
output_tasks: "{AD}/tasks.yaml"
|
|
108
|
+
output_file: "{AD}/tasks.yaml"
|
|
109
|
+
state_value: decompose_done
|
|
110
|
+
|
|
111
|
+
# ============================================================
|
|
112
|
+
# Checkpoint C2: 确认任务拆分
|
|
113
|
+
# ============================================================
|
|
114
|
+
- id: checkpoint_c2
|
|
115
|
+
name: "确认任务拆分"
|
|
116
|
+
is_checkpoint: true
|
|
117
|
+
checkpoint_prompt: "请查看 {AD}/tasks.yaml,逐条确认任务拆分合理后按 Enter 继续。可按需调整任务顺序、增删任务。"
|
|
118
|
+
state_value: checkpoint_c2_done
|
|
119
|
+
|
|
120
|
+
# ============================================================
|
|
121
|
+
# Task Graph: 任务图执行(逐任务 fresh session)
|
|
122
|
+
# ============================================================
|
|
123
|
+
- id: task_graph
|
|
124
|
+
name: "任务图执行"
|
|
125
|
+
is_task_graph: true
|
|
126
|
+
recipe: ""
|
|
127
|
+
max_turns: 0
|
|
128
|
+
params:
|
|
129
|
+
tasks_file: "{AD}/tasks.yaml"
|
|
130
|
+
project_root: "{P}"
|
|
131
|
+
ai_dev_dir: "{AD}"
|
|
132
|
+
task_recipe: "recipes/steps/task-template.yaml"
|
|
133
|
+
output_file: ""
|
|
134
|
+
state_value: tasks_done
|
|
135
|
+
|
|
136
|
+
# ============================================================
|
|
137
|
+
# Phase 5: 编译 + 测试
|
|
138
|
+
# ============================================================
|
|
139
|
+
- id: phase5
|
|
140
|
+
name: "编译与测试"
|
|
141
|
+
prerequisite: task_graph # 前置:需要任务图执行产出代码
|
|
142
|
+
recipe: recipes/steps/05-build.yaml
|
|
143
|
+
max_turns: 60
|
|
144
|
+
params:
|
|
145
|
+
project_root: "{P}"
|
|
146
|
+
profile: "{AD}/profile.yml"
|
|
147
|
+
output_result: "{OUT}/05-build.md"
|
|
148
|
+
output_file: "{OUT}/05-build.md"
|
|
149
|
+
state_value: build_done
|
|
150
|
+
|
|
151
|
+
# ============================================================
|
|
152
|
+
# Phase 6: 代码 + 安全审查
|
|
153
|
+
# ============================================================
|
|
154
|
+
- id: phase6
|
|
155
|
+
name: "代码与安全审查"
|
|
156
|
+
recipe: recipes/steps/06-review.yaml
|
|
157
|
+
max_turns: 80
|
|
158
|
+
params:
|
|
159
|
+
project_root: "{P}"
|
|
160
|
+
requirement: "{AD}/requirement.md"
|
|
161
|
+
input_analysis: "{OUT}/02-analysis.md"
|
|
162
|
+
input_engineering_plan: "{OUT}/03-plan.md"
|
|
163
|
+
input_change_list: "{AD}/tasks.yaml"
|
|
164
|
+
input_build_result: "{OUT}/05-build.md"
|
|
165
|
+
profile: "{AD}/profile.yml"
|
|
166
|
+
output_review: "{OUT}/06-review.md"
|
|
167
|
+
output_file: "{OUT}/06-review.md"
|
|
168
|
+
state_value: review_done
|
|
169
|
+
|
|
170
|
+
# ============================================================
|
|
171
|
+
# Checkpoint D: 安全确认
|
|
172
|
+
# ============================================================
|
|
173
|
+
- id: checkpoint_d
|
|
174
|
+
name: "安全确认"
|
|
175
|
+
is_checkpoint: true
|
|
176
|
+
checkpoint_prompt: "请查看 {OUT}/06-review.md,确认无高危风险后按 Enter 继续"
|
|
177
|
+
state_value: checkpoint_d_done
|
|
178
|
+
|
|
179
|
+
# ============================================================
|
|
180
|
+
# Phase 7: 交付
|
|
181
|
+
# ============================================================
|
|
182
|
+
- id: phase7
|
|
183
|
+
name: "交付"
|
|
184
|
+
recipe: recipes/steps/07-delivery.yaml
|
|
185
|
+
max_turns: 40
|
|
186
|
+
params:
|
|
187
|
+
requirement: "{AD}/requirement.md"
|
|
188
|
+
input_change_list: "{AD}/tasks.yaml"
|
|
189
|
+
input_build_result: "{OUT}/05-build.md"
|
|
190
|
+
input_review: "{OUT}/06-review.md"
|
|
191
|
+
output_delivery: "{OUT}/07-delivery.md"
|
|
192
|
+
output_file: "{OUT}/07-delivery.md"
|
|
193
|
+
state_value: done
|