claude-coding-flow 1.0.0 → 1.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.
@@ -0,0 +1,253 @@
1
+ import argparse
2
+ import os
3
+ from pathlib import Path
4
+
5
+ from fastapi import FastAPI, HTTPException
6
+ from fastapi.staticfiles import StaticFiles
7
+ from fastapi.responses import FileResponse
8
+
9
+ try:
10
+ import db
11
+ import logger
12
+ from models import ModuleCreate, TaskCreate, PhaseUpdate, TaskStatusUpdate
13
+ except ImportError:
14
+ from dashboard import db, logger
15
+ from dashboard.models import ModuleCreate, TaskCreate, PhaseUpdate, TaskStatusUpdate
16
+
17
+ app = FastAPI()
18
+
19
+ STATIC_DIR = Path(__file__).parent / "static"
20
+ app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
21
+
22
+
23
+ @app.get("/")
24
+ def index():
25
+ return FileResponse(STATIC_DIR / "index.html")
26
+
27
+
28
+ # ── Module API ──
29
+
30
+ @app.get("/api/modules")
31
+ def list_modules():
32
+ modules = db.get_modules()
33
+ for m in modules:
34
+ tasks = db.get_module_tasks(m["id"])
35
+ m["task_count"] = len(tasks)
36
+ m["latest_task"] = tasks[-1]["title"] if tasks else None
37
+ m.pop("_path", None)
38
+ return modules
39
+
40
+
41
+ @app.get("/api/modules/{module_id}")
42
+ def get_module(module_id: str):
43
+ module = db.get_module(module_id)
44
+ if not module:
45
+ raise HTTPException(status_code=404, detail="Module not found")
46
+ module.pop("_path", None)
47
+ module["tasks"] = db.get_module_tasks(module_id)
48
+ return module
49
+
50
+
51
+ @app.post("/api/modules")
52
+ def create_module(body: ModuleCreate):
53
+ return {"message": "模块由 code-gen 创建,Dashboard 仅读取"}
54
+
55
+
56
+ # ── Task API ──
57
+
58
+ @app.get("/api/tasks")
59
+ def list_tasks():
60
+ return db.get_tasks()
61
+
62
+
63
+ @app.get("/api/tasks/{task_id}")
64
+ def get_task(task_id: str):
65
+ task = db.get_task(task_id)
66
+ if not task:
67
+ raise HTTPException(status_code=404, detail="Task not found")
68
+ return task
69
+
70
+
71
+ @app.post("/api/tasks")
72
+ def create_task(body: TaskCreate):
73
+ return {"message": "任务由 code-gen 创建,Dashboard 仅读取"}
74
+
75
+
76
+ @app.delete("/api/tasks/{task_id}")
77
+ def delete_task(task_id: str):
78
+ import shutil
79
+ task_dir = db.get_task_dir(task_id)
80
+ if not task_dir:
81
+ raise HTTPException(status_code=404, detail="Task not found")
82
+ shutil.rmtree(task_dir, ignore_errors=True)
83
+ return {"ok": True}
84
+
85
+
86
+ # ── Log & Snapshot API ──
87
+
88
+ @app.get("/api/tasks/{task_id}/logs")
89
+ def get_logs(task_id: str):
90
+ task_dir = db.get_task_dir(task_id)
91
+ if not task_dir:
92
+ raise HTTPException(status_code=404, detail="Task not found")
93
+ return {
94
+ "logs": logger.get_logs(task_dir),
95
+ "token_summary": logger.get_token_summary(task_dir),
96
+ "text_log": logger.get_text_log(task_dir),
97
+ }
98
+
99
+
100
+ @app.get("/api/tasks/{task_id}/snapshots")
101
+ def get_snapshots(task_id: str):
102
+ task_dir = db.get_task_dir(task_id)
103
+ if not task_dir:
104
+ raise HTTPException(status_code=404, detail="Task not found")
105
+ return logger.list_snapshots(task_dir)
106
+
107
+
108
+ @app.get("/api/tasks/{task_id}/snapshots/{snapshot_name}/{filepath:path}")
109
+ def get_snapshot_file(task_id: str, snapshot_name: str, filepath: str):
110
+ task_dir = db.get_task_dir(task_id)
111
+ if not task_dir:
112
+ raise HTTPException(status_code=404, detail="Task not found")
113
+ content = logger.get_snapshot_file(task_dir, snapshot_name, filepath)
114
+ if content is None:
115
+ raise HTTPException(status_code=404, detail="Snapshot file not found")
116
+ return {"file": filepath, "content": content}
117
+
118
+
119
+ @app.get("/api/tasks/{task_id}/plan")
120
+ def get_plan(task_id: str):
121
+ task_dir = db.get_task_dir(task_id)
122
+ if not task_dir:
123
+ raise HTTPException(status_code=404, detail="Task not found")
124
+ plan = logger.get_plan(task_dir)
125
+ if plan is None:
126
+ raise HTTPException(status_code=404, detail="Plan not found")
127
+ return {"plan": plan}
128
+
129
+
130
+ # ── 按类型过滤模块 ──
131
+
132
+ @app.get("/api/modules/type/{module_type}")
133
+ def list_modules_by_type(module_type: str):
134
+ if module_type not in ("code-gen", "doc-gen", "bug-fix"):
135
+ raise HTTPException(status_code=400, detail="Invalid module type")
136
+ modules = db.get_modules_by_type(module_type)
137
+ for m in modules:
138
+ tasks = db.get_module_tasks(m["id"])
139
+ m["task_count"] = len(tasks)
140
+ m["latest_task"] = tasks[-1]["title"] if tasks else None
141
+ m.pop("_path", None)
142
+ return modules
143
+
144
+
145
+ # ── doc-gen 产物 API ──
146
+
147
+ @app.get("/api/tasks/{task_id}/doc-artifacts")
148
+ def get_doc_artifacts(task_id: str):
149
+ task_dir = db.get_task_dir(task_id)
150
+ if not task_dir:
151
+ raise HTTPException(status_code=404, detail="Task not found")
152
+ return db.get_doc_gen_artifacts(task_dir)
153
+
154
+
155
+ @app.get("/api/tasks/{task_id}/doc-artifacts/{artifact_type}/{filepath:path}")
156
+ def get_doc_artifact_file(task_id: str, artifact_type: str, filepath: str):
157
+ task_dir = db.get_task_dir(task_id)
158
+ if not task_dir:
159
+ raise HTTPException(status_code=404, detail="Task not found")
160
+
161
+ if artifact_type == "develop":
162
+ path = os.path.join(task_dir, filepath)
163
+ elif artifact_type in ("requirement", "images"):
164
+ path = os.path.join(task_dir, artifact_type, filepath)
165
+ else:
166
+ raise HTTPException(status_code=400, detail="Invalid artifact type")
167
+
168
+ if not os.path.exists(path):
169
+ raise HTTPException(status_code=404, detail="File not found")
170
+
171
+ if filepath.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp')):
172
+ return FileResponse(path)
173
+
174
+ try:
175
+ with open(path, "r", encoding="utf-8") as f:
176
+ return {"content": f.read()}
177
+ except UnicodeDecodeError:
178
+ return FileResponse(path)
179
+
180
+
181
+ # ── bug-fix 信息 API ──
182
+
183
+ @app.get("/api/tasks/{task_id}/bug-info")
184
+ def get_bug_info(task_id: str):
185
+ task_dir = db.get_task_dir(task_id)
186
+ if not task_dir:
187
+ raise HTTPException(status_code=404, detail="Task not found")
188
+ return db.get_bug_fix_info(task_dir)
189
+
190
+
191
+ # ── 任务链 API ──
192
+
193
+ @app.get("/api/modules/{module_id}/task-chain")
194
+ def get_task_chain(module_id: str):
195
+ module = db.get_module(module_id)
196
+ if not module:
197
+ raise HTTPException(status_code=404, detail="Module not found")
198
+ return db.get_task_chain(module_id)
199
+
200
+
201
+ @app.get("/api/tasks/{task_id}/cross-relations")
202
+ def get_cross_relations(task_id: str):
203
+ task = db.get_task(task_id)
204
+ if not task:
205
+ raise HTTPException(status_code=404, detail="Task not found")
206
+ return db.get_cross_relations(task_id)
207
+
208
+
209
+ # ── CLI ──
210
+
211
+ def cli():
212
+ parser = argparse.ArgumentParser(description="Code-Gen Dashboard CLI")
213
+ sub = parser.add_subparsers(dest="command")
214
+
215
+ # serve
216
+ sub.add_parser("serve", help="启动 Dashboard 服务")
217
+
218
+ # list modules
219
+ sub.add_parser("modules", help="列出所有模块")
220
+
221
+ # scan
222
+ sub.add_parser("scan", help="扫描 worktree 目录并显示信息")
223
+
224
+ args = parser.parse_args()
225
+
226
+ if args.command == "modules":
227
+ for m in db.get_modules():
228
+ tasks = db.get_module_tasks(m["id"])
229
+ print(f"{m['id']} {m['name']} ({len(tasks)} tasks)")
230
+
231
+ elif args.command == "scan":
232
+ modules = db.get_modules()
233
+ if not modules:
234
+ print("worktree 为空,暂无模块")
235
+ for m in modules:
236
+ tasks = db.get_module_tasks(m["id"])
237
+ print(f"\n模块: {m['name']} ({m['id']})")
238
+ for t in tasks:
239
+ phase_status = " ".join(
240
+ f"P{p['phase']}:{p['status']}" for p in t.get("phases", [])
241
+ )
242
+ print(f" 任务: {t['title']} ({t['id']}) [{t['status']}] {phase_status}")
243
+
244
+ elif args.command == "serve":
245
+ import uvicorn
246
+ uvicorn.run(app, host="0.0.0.0", port=8000)
247
+
248
+ else:
249
+ parser.print_help()
250
+
251
+
252
+ if __name__ == "__main__":
253
+ cli()
@@ -0,0 +1,26 @@
1
+ from typing import Optional, List
2
+ from pydantic import BaseModel
3
+
4
+
5
+ class ModuleCreate(BaseModel):
6
+ name: str
7
+ type: str = "code-gen"
8
+ id: Optional[str] = None
9
+
10
+
11
+ class TaskCreate(BaseModel):
12
+ id: str
13
+ module_id: str
14
+ title: str
15
+ requirements_doc: str = ""
16
+
17
+
18
+ class PhaseUpdate(BaseModel):
19
+ phase: int
20
+ status: str # running / completed / failed
21
+ detail: Optional[str] = None
22
+ error: Optional[str] = None
23
+
24
+
25
+ class TaskStatusUpdate(BaseModel):
26
+ status: str # running / completed / failed
@@ -0,0 +1,2 @@
1
+ fastapi
2
+ uvicorn