ironweave 1.0.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-plugin/plugin.json +16 -0
- package/.clinerules +7 -0
- package/.codex/INSTALL.md +45 -0
- package/.cursor-plugin/plugin.json +19 -0
- package/.cursorrules +7 -0
- package/.github/copilot-instructions.md +7 -0
- package/.opencode/INSTALL.md +42 -0
- package/.windsurfrules +7 -0
- package/AGENTS.md +1 -0
- package/CLAUDE.md +22 -0
- package/CONTRIBUTING.md +81 -0
- package/GEMINI.md +1 -0
- package/LICENSE +21 -0
- package/README.md +250 -0
- package/README_CN.md +248 -0
- package/package.json +48 -0
- package/skills/api-contract-design/SKILL.md +227 -0
- package/skills/api-contract-design/references/api-design-rules.md +106 -0
- package/skills/brainstorm/SKILL.md +271 -0
- package/skills/brainstorm/agents/architect.md +34 -0
- package/skills/brainstorm/agents/challenger.md +34 -0
- package/skills/brainstorm/agents/domain-expert.md +34 -0
- package/skills/brainstorm/agents/pragmatist.md +34 -0
- package/skills/brainstorm/agents/product-manager.md +34 -0
- package/skills/brainstorm/agents/ux-designer.md +34 -0
- package/skills/brainstorm/references/synthesis-rules.md +51 -0
- package/skills/code-scaffold/SKILL.md +313 -0
- package/skills/code-scaffold/references/scaffold-rules.md +131 -0
- package/skills/docs-output/SKILL.md +149 -0
- package/skills/docs-output/references/naming-rules.md +52 -0
- package/skills/docs-output/scripts/docs_manager.py +353 -0
- package/skills/engineering-principles/SKILL.md +133 -0
- package/skills/engineering-principles/references/anti-patterns.md +144 -0
- package/skills/engineering-principles/references/ddd-patterns.md +66 -0
- package/skills/engineering-principles/references/design-patterns.md +34 -0
- package/skills/engineering-principles/references/patterns-architecture.md +301 -0
- package/skills/engineering-principles/references/patterns-backend.md +77 -0
- package/skills/engineering-principles/references/patterns-classic.md +200 -0
- package/skills/engineering-principles/references/patterns-crosscut.md +67 -0
- package/skills/engineering-principles/references/patterns-frontend.md +27 -0
- package/skills/engineering-principles/references/patterns-module.md +95 -0
- package/skills/engineering-principles/references/patterns-small-scale.md +79 -0
- package/skills/engineering-principles/references/quality-checklist.md +76 -0
- package/skills/engineering-principles/references/solid-principles.md +46 -0
- package/skills/engineering-principles/references/tdd-workflow.md +60 -0
- package/skills/engineering-principles/scripts/principles_matcher.py +433 -0
- package/skills/error-handling-strategy/SKILL.md +347 -0
- package/skills/error-handling-strategy/references/error-handling-rules.md +91 -0
- package/skills/implementation-complexity-analysis/SKILL.md +193 -0
- package/skills/implementation-complexity-analysis/references/complexity-rules.md +126 -0
- package/skills/integration-test-design/SKILL.md +296 -0
- package/skills/integration-test-design/references/test-strategy-rules.md +90 -0
- package/skills/observability-design/SKILL.md +327 -0
- package/skills/observability-design/references/observability-rules.md +129 -0
- package/skills/orchestrator/SKILL.md +260 -0
- package/skills/orchestrator/references/deliver.md +112 -0
- package/skills/orchestrator/references/execute.md +313 -0
- package/skills/orchestrator/references/gates.md +252 -0
- package/skills/orchestrator/references/parallel.md +70 -0
- package/skills/orchestrator/references/route-a.md +135 -0
- package/skills/orchestrator/references/route-b.md +91 -0
- package/skills/orchestrator/references/route-c.md +65 -0
- package/skills/orchestrator/references/route-d.md +75 -0
- package/skills/orchestrator/references/scope-sizer.md +219 -0
- package/skills/performance-arch-design/SKILL.md +208 -0
- package/skills/performance-arch-design/references/performance-rules.md +95 -0
- package/skills/project-context/SKILL.md +104 -0
- package/skills/project-context/references/schema.md +97 -0
- package/skills/project-context/scripts/context_db.py +358 -0
- package/skills/requirement-qa/SKILL.md +287 -0
- package/skills/requirement-qa/references/completion-signals.md +42 -0
- package/skills/requirement-qa/references/option-rules.md +57 -0
- package/skills/requirement-qa/scripts/qa_session.py +223 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/SKILL.md +485 -0
- package/skills/skill-creator/agents/analyzer.md +274 -0
- package/skills/skill-creator/agents/comparator.md +202 -0
- package/skills/skill-creator/agents/grader.md +223 -0
- package/skills/skill-creator/assets/eval_review.html +146 -0
- package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/skill-creator/references/schemas.md +430 -0
- package/skills/skill-creator/scripts/__init__.py +0 -0
- package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/skill-creator/scripts/generate_report.py +326 -0
- package/skills/skill-creator/scripts/improve_description.py +247 -0
- package/skills/skill-creator/scripts/package_skill.py +136 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/run_eval.py +310 -0
- package/skills/skill-creator/scripts/run_loop.py +328 -0
- package/skills/skill-creator/scripts/utils.py +47 -0
- package/skills/spec-writing/SKILL.md +96 -0
- package/skills/spec-writing/references/mermaid-guide.md +66 -0
- package/skills/spec-writing/references/test-matrix.md +73 -0
- package/skills/task-difficulty/SKILL.md +162 -0
- package/skills/task-difficulty/references/scoring-guide.md +123 -0
- package/skills/task-difficulty/scripts/difficulty_scorer.py +328 -0
- package/skills/tech-stack/SKILL.md +67 -0
- package/skills/tech-stack/references/tech-reference-tables.md +130 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
docs-output: 文档产出管理脚本(模块文档 + 进度记录)
|
|
4
|
+
|
|
5
|
+
用法:
|
|
6
|
+
python docs_manager.py create --root <project_root> --module <模块名> --name <文档名> [--title <标题>]
|
|
7
|
+
python docs_manager.py progress --root <project_root> --topic <主题> --type <类型> --summary <摘要> [--files <JSON>] [--decisions <决策>] [--todos <遗留>]
|
|
8
|
+
python docs_manager.py list --root <project_root>
|
|
9
|
+
python docs_manager.py validate --root <project_root>
|
|
10
|
+
python docs_manager.py archive --root <project_root> [--older-than <天数>]
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import secrets
|
|
18
|
+
import shutil
|
|
19
|
+
import subprocess
|
|
20
|
+
from datetime import datetime, timedelta, timezone
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
DOCS_DIR = "docs"
|
|
24
|
+
PROGRESS_DIR = "progress"
|
|
25
|
+
ARCHIVE_DIR = "archive"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_docs_dir(root: str) -> Path:
|
|
29
|
+
return Path(root).resolve() / DOCS_DIR
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_progress_dir(root: str) -> Path:
|
|
33
|
+
return Path(root).resolve() / DOCS_DIR / PROGRESS_DIR
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def extract_title(filepath: Path) -> str:
|
|
37
|
+
try:
|
|
38
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
39
|
+
for line in f:
|
|
40
|
+
line = line.strip()
|
|
41
|
+
if line.startswith("# "):
|
|
42
|
+
return line[2:].strip()
|
|
43
|
+
return ""
|
|
44
|
+
except (OSError, UnicodeDecodeError):
|
|
45
|
+
return ""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_git_user(root: str) -> dict:
|
|
49
|
+
"""获取 Git 用户信息。"""
|
|
50
|
+
info = {"name": "unknown", "email": ""}
|
|
51
|
+
try:
|
|
52
|
+
name = subprocess.run(
|
|
53
|
+
["git", "config", "user.name"], capture_output=True, text=True, cwd=root
|
|
54
|
+
).stdout.strip()
|
|
55
|
+
email = subprocess.run(
|
|
56
|
+
["git", "config", "user.email"], capture_output=True, text=True, cwd=root
|
|
57
|
+
).stdout.strip()
|
|
58
|
+
if name:
|
|
59
|
+
info["name"] = name
|
|
60
|
+
if email:
|
|
61
|
+
info["email"] = email
|
|
62
|
+
except (OSError, FileNotFoundError):
|
|
63
|
+
pass
|
|
64
|
+
return info
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def session_hash() -> str:
|
|
68
|
+
"""生成 6 位会话 hash。"""
|
|
69
|
+
return secrets.token_hex(3)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def now_date() -> str:
|
|
73
|
+
return datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def scan_modules(docs_dir: Path) -> dict[str, list[dict]]:
|
|
77
|
+
"""扫描模块文档(排除 progress/)。"""
|
|
78
|
+
modules: dict[str, list[dict]] = {}
|
|
79
|
+
if not docs_dir.exists():
|
|
80
|
+
return modules
|
|
81
|
+
|
|
82
|
+
for item in sorted(docs_dir.iterdir()):
|
|
83
|
+
if item.is_dir() and item.name != PROGRESS_DIR:
|
|
84
|
+
docs = []
|
|
85
|
+
for md in sorted(item.rglob("*.md")):
|
|
86
|
+
title = extract_title(md)
|
|
87
|
+
rel = str(md.relative_to(docs_dir))
|
|
88
|
+
docs.append({"name": md.stem, "file": rel, "title": title})
|
|
89
|
+
if docs:
|
|
90
|
+
modules[item.name] = docs
|
|
91
|
+
|
|
92
|
+
return modules
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def scan_progress(progress_dir: Path, days: int | None = None) -> list[dict]:
|
|
96
|
+
"""扫描进度记录。"""
|
|
97
|
+
results = []
|
|
98
|
+
if not progress_dir.exists():
|
|
99
|
+
return results
|
|
100
|
+
|
|
101
|
+
cutoff = None
|
|
102
|
+
if days is not None:
|
|
103
|
+
cutoff = (datetime.now(timezone.utc) - timedelta(days=days)).strftime("%Y-%m-%d")
|
|
104
|
+
|
|
105
|
+
for date_dir in sorted(progress_dir.iterdir(), reverse=True):
|
|
106
|
+
if not date_dir.is_dir() or date_dir.name == ARCHIVE_DIR:
|
|
107
|
+
continue
|
|
108
|
+
if cutoff and date_dir.name < cutoff:
|
|
109
|
+
continue
|
|
110
|
+
for md in sorted(date_dir.iterdir()):
|
|
111
|
+
if md.suffix == ".md":
|
|
112
|
+
title = extract_title(md)
|
|
113
|
+
results.append({
|
|
114
|
+
"date": date_dir.name,
|
|
115
|
+
"hash": md.stem,
|
|
116
|
+
"title": title,
|
|
117
|
+
"file": f"{PROGRESS_DIR}/{date_dir.name}/{md.name}",
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
return results
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ── 命令实现 ──────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
def cmd_create(root: str, module: str, name: str, title: str | None = None):
|
|
126
|
+
docs_dir = get_docs_dir(root)
|
|
127
|
+
module_dir = docs_dir / module
|
|
128
|
+
module_dir.mkdir(parents=True, exist_ok=True)
|
|
129
|
+
|
|
130
|
+
filename = f"{name}.md"
|
|
131
|
+
filepath = module_dir / filename
|
|
132
|
+
|
|
133
|
+
if filepath.exists():
|
|
134
|
+
print(json.dumps({"status": "error", "message": f"File already exists: {module}/{filename}"}, ensure_ascii=False))
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
doc_title = title or name
|
|
138
|
+
content = f"# {doc_title}\n\n"
|
|
139
|
+
filepath.write_text(content, encoding="utf-8")
|
|
140
|
+
|
|
141
|
+
print(json.dumps({
|
|
142
|
+
"status": "ok",
|
|
143
|
+
"file": f"{module}/{filename}",
|
|
144
|
+
"module": module,
|
|
145
|
+
"path": str(filepath),
|
|
146
|
+
}, ensure_ascii=False))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
PROGRESS_TEMPLATE = """# {topic}
|
|
150
|
+
|
|
151
|
+
- **日期**: {date}
|
|
152
|
+
- **开发者**: {developer}
|
|
153
|
+
- **类型**: {record_type}
|
|
154
|
+
|
|
155
|
+
## 任务摘要
|
|
156
|
+
|
|
157
|
+
{summary}
|
|
158
|
+
|
|
159
|
+
## 变更文件
|
|
160
|
+
|
|
161
|
+
{files}
|
|
162
|
+
|
|
163
|
+
## 决策记录
|
|
164
|
+
|
|
165
|
+
{decisions}
|
|
166
|
+
|
|
167
|
+
## 遗留问题
|
|
168
|
+
|
|
169
|
+
{todos}
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def cmd_progress(root: str, topic: str, record_type: str, summary: str,
|
|
174
|
+
files: str | None = None, decisions: str | None = None, todos: str | None = None):
|
|
175
|
+
progress_dir = get_progress_dir(root)
|
|
176
|
+
date = now_date()
|
|
177
|
+
date_dir = progress_dir / date
|
|
178
|
+
date_dir.mkdir(parents=True, exist_ok=True)
|
|
179
|
+
|
|
180
|
+
h = session_hash()
|
|
181
|
+
filepath = date_dir / f"{h}.md"
|
|
182
|
+
# 极小概率碰撞
|
|
183
|
+
while filepath.exists():
|
|
184
|
+
h = session_hash()
|
|
185
|
+
filepath = date_dir / f"{h}.md"
|
|
186
|
+
|
|
187
|
+
# 获取 Git 用户
|
|
188
|
+
git_user = get_git_user(root)
|
|
189
|
+
developer = f"{git_user['name']} <{git_user['email']}>" if git_user["email"] else git_user["name"]
|
|
190
|
+
|
|
191
|
+
# 解析变更文件
|
|
192
|
+
files_text = "(无)"
|
|
193
|
+
if files:
|
|
194
|
+
try:
|
|
195
|
+
file_list = json.loads(files)
|
|
196
|
+
files_text = "\n".join(f"- `{f['path']}` — {f.get('reason', '')}" for f in file_list)
|
|
197
|
+
except (json.JSONDecodeError, KeyError, TypeError):
|
|
198
|
+
files_text = files
|
|
199
|
+
|
|
200
|
+
content = PROGRESS_TEMPLATE.format(
|
|
201
|
+
topic=topic,
|
|
202
|
+
date=date,
|
|
203
|
+
developer=developer,
|
|
204
|
+
record_type=record_type,
|
|
205
|
+
summary=summary,
|
|
206
|
+
files=files_text,
|
|
207
|
+
decisions=decisions or "(无)",
|
|
208
|
+
todos=todos or "(无)",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
filepath.write_text(content, encoding="utf-8")
|
|
212
|
+
|
|
213
|
+
print(json.dumps({
|
|
214
|
+
"status": "ok",
|
|
215
|
+
"file": f"{PROGRESS_DIR}/{date}/{h}.md",
|
|
216
|
+
"developer": developer,
|
|
217
|
+
"path": str(filepath),
|
|
218
|
+
}, ensure_ascii=False))
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def cmd_list(root: str):
|
|
222
|
+
docs_dir = get_docs_dir(root)
|
|
223
|
+
modules = scan_modules(docs_dir)
|
|
224
|
+
progress = scan_progress(get_progress_dir(root), days=30)
|
|
225
|
+
|
|
226
|
+
total_modules = sum(len(docs) for docs in modules.values())
|
|
227
|
+
print(json.dumps({
|
|
228
|
+
"status": "ok",
|
|
229
|
+
"modules": modules,
|
|
230
|
+
"module_docs_total": total_modules,
|
|
231
|
+
"recent_progress": progress,
|
|
232
|
+
"progress_count": len(progress),
|
|
233
|
+
}, ensure_ascii=False))
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def cmd_validate(root: str):
|
|
237
|
+
docs_dir = get_docs_dir(root)
|
|
238
|
+
if not docs_dir.exists():
|
|
239
|
+
print(json.dumps({"status": "error", "message": "docs/ directory does not exist"}))
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
issues = []
|
|
243
|
+
kebab = re.compile(r"^[a-z0-9\u4e00-\u9fff]+(-[a-z0-9\u4e00-\u9fff]+)*$")
|
|
244
|
+
|
|
245
|
+
# 根目录散落文件
|
|
246
|
+
for item in docs_dir.iterdir():
|
|
247
|
+
if item.is_file() and item.suffix == ".md":
|
|
248
|
+
issues.append({
|
|
249
|
+
"type": "root_file",
|
|
250
|
+
"file": item.name,
|
|
251
|
+
"message": "文件应放入模块子目录",
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
# 空模块目录
|
|
255
|
+
modules = scan_modules(docs_dir)
|
|
256
|
+
for item in docs_dir.iterdir():
|
|
257
|
+
if item.is_dir() and item.name not in (PROGRESS_DIR,) and item.name not in modules:
|
|
258
|
+
issues.append({"type": "empty_module", "module": item.name})
|
|
259
|
+
|
|
260
|
+
# 命名检查
|
|
261
|
+
for module_name, docs in modules.items():
|
|
262
|
+
if not kebab.match(module_name):
|
|
263
|
+
issues.append({"type": "naming", "module": module_name, "message": "模块名应使用 kebab-case"})
|
|
264
|
+
for doc in docs:
|
|
265
|
+
stem = Path(doc["file"]).stem
|
|
266
|
+
if not kebab.match(stem):
|
|
267
|
+
issues.append({"type": "naming", "file": doc["file"], "message": "文件名应使用 kebab-case"})
|
|
268
|
+
|
|
269
|
+
total = sum(len(docs) for docs in modules.values())
|
|
270
|
+
print(json.dumps({
|
|
271
|
+
"status": "ok" if not issues else "warning",
|
|
272
|
+
"total_docs": total,
|
|
273
|
+
"issues": issues,
|
|
274
|
+
}, ensure_ascii=False))
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def cmd_archive(root: str, older_than: int = 30):
|
|
278
|
+
progress_dir = get_progress_dir(root)
|
|
279
|
+
if not progress_dir.exists():
|
|
280
|
+
print(json.dumps({"status": "error", "message": "progress/ directory does not exist"}))
|
|
281
|
+
return
|
|
282
|
+
|
|
283
|
+
cutoff = (datetime.now(timezone.utc) - timedelta(days=older_than)).strftime("%Y-%m-%d")
|
|
284
|
+
archived = 0
|
|
285
|
+
|
|
286
|
+
for date_dir in sorted(progress_dir.iterdir()):
|
|
287
|
+
if not date_dir.is_dir() or date_dir.name == ARCHIVE_DIR:
|
|
288
|
+
continue
|
|
289
|
+
if date_dir.name >= cutoff:
|
|
290
|
+
continue
|
|
291
|
+
|
|
292
|
+
month = date_dir.name[:7]
|
|
293
|
+
archive_month = progress_dir / ARCHIVE_DIR / month
|
|
294
|
+
archive_month.mkdir(parents=True, exist_ok=True)
|
|
295
|
+
|
|
296
|
+
dest = archive_month / date_dir.name
|
|
297
|
+
shutil.move(str(date_dir), str(dest))
|
|
298
|
+
archived += 1
|
|
299
|
+
|
|
300
|
+
print(json.dumps({"status": "ok", "archived_days": archived, "cutoff": cutoff}))
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# ── CLI 入口 ──────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
def main():
|
|
306
|
+
parser = argparse.ArgumentParser(description="docs-output: 文档产出管理")
|
|
307
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
308
|
+
|
|
309
|
+
p_create = sub.add_parser("create", help="创建模块文档")
|
|
310
|
+
p_create.add_argument("--root", required=True, help="项目根目录路径")
|
|
311
|
+
p_create.add_argument("--module", required=True, help="模块名称")
|
|
312
|
+
p_create.add_argument("--name", required=True, help="文档名称(不含扩展名)")
|
|
313
|
+
p_create.add_argument("--title", default=None, help="文档一级标题(默认使用 name)")
|
|
314
|
+
|
|
315
|
+
p_progress = sub.add_parser("progress", help="记录会话进度")
|
|
316
|
+
p_progress.add_argument("--root", required=True)
|
|
317
|
+
p_progress.add_argument("--topic", required=True, help="主题")
|
|
318
|
+
p_progress.add_argument("--type", required=True, dest="record_type",
|
|
319
|
+
choices=["需求开发", "Bug修复", "技术方案", "重构", "其他"])
|
|
320
|
+
p_progress.add_argument("--summary", required=True, help="任务摘要")
|
|
321
|
+
p_progress.add_argument("--files", default=None, help="变更文件JSON数组")
|
|
322
|
+
p_progress.add_argument("--decisions", default=None, help="决策记录")
|
|
323
|
+
p_progress.add_argument("--todos", default=None, help="遗留问题")
|
|
324
|
+
|
|
325
|
+
p_list = sub.add_parser("list", help="列出文档和进度")
|
|
326
|
+
p_list.add_argument("--root", required=True)
|
|
327
|
+
|
|
328
|
+
p_val = sub.add_parser("validate", help="校验目录健康状态")
|
|
329
|
+
p_val.add_argument("--root", required=True)
|
|
330
|
+
|
|
331
|
+
p_archive = sub.add_parser("archive", help="归档旧进度记录")
|
|
332
|
+
p_archive.add_argument("--root", required=True)
|
|
333
|
+
p_archive.add_argument("--older-than", type=int, default=30, dest="older_than",
|
|
334
|
+
help="归档N天前的记录(默认30)")
|
|
335
|
+
|
|
336
|
+
args = parser.parse_args()
|
|
337
|
+
|
|
338
|
+
if args.command == "create":
|
|
339
|
+
cmd_create(args.root, args.module, args.name, getattr(args, "title", None))
|
|
340
|
+
elif args.command == "progress":
|
|
341
|
+
cmd_progress(args.root, args.topic, args.record_type, args.summary,
|
|
342
|
+
getattr(args, "files", None), getattr(args, "decisions", None),
|
|
343
|
+
getattr(args, "todos", None))
|
|
344
|
+
elif args.command == "list":
|
|
345
|
+
cmd_list(args.root)
|
|
346
|
+
elif args.command == "validate":
|
|
347
|
+
cmd_validate(args.root)
|
|
348
|
+
elif args.command == "archive":
|
|
349
|
+
cmd_archive(args.root, args.older_than)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
if __name__ == "__main__":
|
|
353
|
+
main()
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: engineering-principles
|
|
3
|
+
description: >-
|
|
4
|
+
工程原则匹配器——根据当前任务和项目上下文,从原则库中筛选出适用的开发原则和约束,输出可执行的指导清单。不强推不适用的原则(如无测试依赖的旧项目不推TDD),只输出经上下文验证后确实适用的原则。被动调用,不会主动触发。
|
|
5
|
+
务必在以下场景使用本 skill:编码规范、设计原则、架构指导、代码质量、可维护性、可扩展性、TDD、DDD、SOLID、设计模式、工程实践、代码审查、engineering principles、code quality、best practices、ddd、tdd、bdd、solid、clean code、重构建议。
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# 工程原则匹配器
|
|
9
|
+
|
|
10
|
+
本 Skill 解决一个核心问题:**模型写代码时缺乏工程纪律**。不是所有原则都适用于所有项目——旧项目没有测试框架就不该强推 TDD,小项目不需要 DDD 分层。
|
|
11
|
+
|
|
12
|
+
本 Skill 的核心逻辑:**先感知项目上下文 → 再匹配适用原则 → 输出可执行约束**。
|
|
13
|
+
|
|
14
|
+
## 核心机制
|
|
15
|
+
|
|
16
|
+
```mermaid
|
|
17
|
+
graph LR
|
|
18
|
+
TASK["当前任务"]
|
|
19
|
+
CTX["项目上下文扫描<br/>技术栈·测试框架·项目规模·目录结构"]
|
|
20
|
+
MATCH["原则匹配<br/>上下文条件 → 适用原则"]
|
|
21
|
+
OUT["输出<br/>适用原则清单 + DO/DON'T"]
|
|
22
|
+
|
|
23
|
+
TASK --> CTX --> MATCH --> OUT
|
|
24
|
+
|
|
25
|
+
style TASK fill:#e3f2fd,stroke:#1976d2
|
|
26
|
+
style CTX fill:#fff3e0,stroke:#f57c00
|
|
27
|
+
style MATCH fill:#f3e5f5,stroke:#7b1fa2
|
|
28
|
+
style OUT fill:#e8f5e9,stroke:#388e3c
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 原则分类与适用条件
|
|
32
|
+
|
|
33
|
+
每个原则类别有明确的**适用前置条件**——条件不满足则跳过。
|
|
34
|
+
|
|
35
|
+
| 分类 | 包含原则 | 适用条件 |
|
|
36
|
+
|------|---------|---------|
|
|
37
|
+
| **SOLID** | SRP, OCP, LSP, ISP, DIP | 项目使用 OOP 语言(TS/Java/Python class) |
|
|
38
|
+
| **DDD 分层** | 聚合、限界上下文、领域服务、值对象 | 项目有明确的业务领域且模块 ≥ 3 个 |
|
|
39
|
+
| **TDD** | 红绿重构、测试先行、测试金字塔 | 项目已配置测试框架(jest/vitest/junit/pytest) |
|
|
40
|
+
| **BDD** | Gherkin 验收、场景驱动 | 项目有 E2E 测试工具(cypress/playwright) |
|
|
41
|
+
| **设计模式** | 小尺度(Guard Clause/表驱动/Pipe)、经典GoF(工厂/建造者/装饰器/策略/观察者/状态机/命令/责任链)、中尺度(管道/断路器/工作池/仓库)、前端/后端/跨域模式 | 代码中存在可识别的模式适用场景 |
|
|
42
|
+
| **架构模式** | 分层架构、六边形/Clean Architecture、微内核/插件、模块化单体、微服务、事件驱动、Serverless | 涉及系统架构决策或项目初始化 |
|
|
43
|
+
| **反模式** | 上帝对象、循环依赖、散弹式修改、过度抽象、贫血模型等 | 代码审查或重构任务 |
|
|
44
|
+
| **Clean Code** | 命名、函数长度、注释、可读性 | **始终适用**(无条件) |
|
|
45
|
+
| **错误处理** | 异常分级、边界校验、防御式编程 | **始终适用**(无条件) |
|
|
46
|
+
| **可测试性** | 依赖注入、纯函数、接口隔离 | 项目有测试框架 或 新建项目 |
|
|
47
|
+
| **性能意识** | 避免 N+1、懒加载、缓存策略 | 涉及数据库查询或 API 调用的任务 |
|
|
48
|
+
| **安全实践** | 输入校验、SQL 注入防护、XSS、CSRF | 涉及用户输入、API 端点、认证 |
|
|
49
|
+
|
|
50
|
+
> 每个分类的完整原则和 DO/DON'T 见 → `references/` 下对应文件
|
|
51
|
+
>
|
|
52
|
+
> 设计模式按尺度分文件:`design-patterns.md`(总表索引)→ `patterns-small-scale.md` / `patterns-classic.md` / `patterns-module.md` / `patterns-frontend.md` / `patterns-backend.md` / `patterns-crosscut.md` / `patterns-architecture.md` / `anti-patterns.md`(按需加载)
|
|
53
|
+
|
|
54
|
+
## 上下文扫描
|
|
55
|
+
|
|
56
|
+
匹配原则前,先扫描项目上下文确定以下信息:
|
|
57
|
+
|
|
58
|
+
### 自动检测项
|
|
59
|
+
|
|
60
|
+
| 检测项 | 检测方式 | 影响选择 |
|
|
61
|
+
|-------|---------|---------|
|
|
62
|
+
| 语言/框架 | package.json, pom.xml, pyproject.toml, tsconfig | SOLID/DDD/设计模式的适用方式 |
|
|
63
|
+
| 测试框架 | jest.config, vitest.config, pytest.ini, build.gradle | TDD/BDD 是否适用 |
|
|
64
|
+
| 项目规模 | 文件数量、模块数量 | DDD 分层是否值得引入 |
|
|
65
|
+
| 目录结构 | 是否已有分层(controller/service/repository) | 分层实践是否延续 |
|
|
66
|
+
| 已有测试 | __tests__/, *.test.*, *.spec.* | 测试覆盖现状 |
|
|
67
|
+
| E2E 工具 | cypress/, playwright.config | BDD 是否适用 |
|
|
68
|
+
| ORM/数据库 | prisma/, typeorm, mybatis, sequelize | 性能意识、数据建模原则 |
|
|
69
|
+
|
|
70
|
+
### 上下文分级
|
|
71
|
+
|
|
72
|
+
| 项目类型 | 特征 | 默认适用原则 |
|
|
73
|
+
|---------|------|------------|
|
|
74
|
+
| **新项目** | 无 src/, 无 package.json 或空项目 | Clean Code + SOLID + 可测试性(推荐 TDD) |
|
|
75
|
+
| **小项目** | < 50 文件, 单模块 | Clean Code + SOLID + 错误处理 |
|
|
76
|
+
| **中型项目** | 50-500 文件, 2-5 模块, 有测试 | 全部可选(按检测结果) |
|
|
77
|
+
| **大型项目** | > 500 文件, 多模块 | DDD + SOLID + 性能 + 安全(按检测结果) |
|
|
78
|
+
| **遗留项目** | 无测试框架, 无明确分层 | Clean Code + 错误处理 + 渐进式改善 |
|
|
79
|
+
|
|
80
|
+
## 输出格式
|
|
81
|
+
|
|
82
|
+
### 原则清单
|
|
83
|
+
|
|
84
|
+
```markdown
|
|
85
|
+
# 工程原则匹配报告
|
|
86
|
+
|
|
87
|
+
## 项目上下文
|
|
88
|
+
- 语言/框架: TypeScript + NestJS
|
|
89
|
+
- 测试框架: vitest(已配置)
|
|
90
|
+
- 项目规模: 中型(~200 文件)
|
|
91
|
+
- 目录结构: DDD 分层(controller/service/repository)
|
|
92
|
+
|
|
93
|
+
## 适用原则
|
|
94
|
+
|
|
95
|
+
### ✅ 始终适用
|
|
96
|
+
- **Clean Code**: 函数 ≤ 30 行, 有意义的变量名, 避免 magic number
|
|
97
|
+
- **错误处理**: 业务异常 vs 系统异常分级, 不吞异常
|
|
98
|
+
|
|
99
|
+
### ✅ 经检测适用
|
|
100
|
+
- **SOLID (OOP)**: 检测到 class 使用 → SRP, OCP, DIP 适用
|
|
101
|
+
- **TDD**: 检测到 vitest → 新功能应先写测试
|
|
102
|
+
- **DDD 分层**: 检测到已有 controller/service/repository 结构 → 延续
|
|
103
|
+
- **可测试性**: 依赖注入已使用(NestJS @Injectable) → 继续遵循
|
|
104
|
+
- **性能**: 涉及数据库 → 注意 N+1, 添加必要索引
|
|
105
|
+
|
|
106
|
+
### ⏭️ 不适用(跳过理由)
|
|
107
|
+
- **BDD**: 未检测到 E2E 框架(无 cypress/playwright)
|
|
108
|
+
- **设计模式-工厂/策略**: 当前任务不涉及多态/策略选择场景
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 单原则约束(嵌入编码)
|
|
112
|
+
|
|
113
|
+
当模型执行编码任务时,适用的原则以简短约束形式嵌入:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
[原则约束]
|
|
117
|
+
- SRP: 每个类/函数只做一件事
|
|
118
|
+
- OCP: 通过接口扩展,不修改已有类
|
|
119
|
+
- TDD: 先写失败测试 → 通过 → 重构
|
|
120
|
+
- 错误处理: 用自定义异常类,不用字符串
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Python 脚本
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
python scripts/principles_matcher.py match --root <project_root> [--task <任务描述>] [--format json|markdown]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- `--root`:项目路径,用于扫描上下文
|
|
130
|
+
- `--task`:任务描述(可选),辅助匹配更精确的原则
|
|
131
|
+
- 无 `--root` 时仅基于任务描述做通用匹配
|
|
132
|
+
|
|
133
|
+
> 脚本实现见 → `scripts/principles_matcher.py`
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# 反模式识别与改进
|
|
2
|
+
|
|
3
|
+
> 代码审查和重构时的常见问题。识别信号 → 判断严重度 → 选择改进方向。
|
|
4
|
+
|
|
5
|
+
## 快速识别表
|
|
6
|
+
|
|
7
|
+
| 反模式 | 识别信号 | 严重度 | 改进方向 |
|
|
8
|
+
|--------|---------|--------|---------|
|
|
9
|
+
| **上帝对象** | 文件 > 500 行,职责 > 3 项 | 高 | 按职责拆分为独立模块 |
|
|
10
|
+
| **循环依赖** | A → B → A | 高 | 提取公共模块 / 依赖反转 / 事件解耦 |
|
|
11
|
+
| **散弹式修改** | 一个需求改 > 5 个文件 | 高 | 识别变化原因,聚合受同因驱动的代码 |
|
|
12
|
+
| **隐式时序耦合** | 必须按特定顺序调用 | 高 | Builder / FSM 强制顺序 |
|
|
13
|
+
| **平台型 if-else** | 条件分支 > 3 层 | 高 | 策略模式 / 多态 / 表驱动 |
|
|
14
|
+
| **过度抽象** | 抽象层 > 实现层 | 中 | 删除不必要中间层 |
|
|
15
|
+
| **过长参数列表** | 参数 > 5 个 | 中 | 参数对象化 / Builder |
|
|
16
|
+
| **数据泥团** | 同组数据总一起传递 | 中 | 提取为独立类/接口 |
|
|
17
|
+
| **贫血模型** | 数据和操作完全分离 | 中 | 将相关行为内聚到实体 |
|
|
18
|
+
| **僵尸代码** | 无人调用但不敢删 | 低 | 确认无引用后删除 |
|
|
19
|
+
|
|
20
|
+
## 重构优先级
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
P0: 阻碍当前任务 / 导致 bug → 立即修复
|
|
24
|
+
P1: 当前路过且改动成本低 → 顺手修复
|
|
25
|
+
P2: 影响范围大但不紧急 → 记录技术债
|
|
26
|
+
P3: 不美观但不影响功能 → 不修
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 详细改进策略
|
|
30
|
+
|
|
31
|
+
### 上帝对象
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
步骤:
|
|
35
|
+
1. 列出当前对象的所有职责
|
|
36
|
+
2. 按"谁关心这数据"分组
|
|
37
|
+
3. 每组提取为独立模块/类
|
|
38
|
+
4. 原对象降级为协调者(Facade)或删除
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// BEFORE: UserService 做了太多事
|
|
43
|
+
class UserService {
|
|
44
|
+
register() { ... }
|
|
45
|
+
login() { ... }
|
|
46
|
+
sendEmail() { ... }
|
|
47
|
+
generateReport() { ... }
|
|
48
|
+
managePermissions() { ... }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// AFTER: 按职责拆分
|
|
52
|
+
class AuthService { register() { ... }; login() { ... } }
|
|
53
|
+
class EmailService { send() { ... } }
|
|
54
|
+
class ReportService { generate() { ... } }
|
|
55
|
+
class PermissionService { manage() { ... } }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 循环依赖
|
|
59
|
+
|
|
60
|
+
四种解法,按优先级排序:
|
|
61
|
+
|
|
62
|
+
| 方案 | 适用场景 |
|
|
63
|
+
|------|---------|
|
|
64
|
+
| **提取公共模块** | 共享类型/接口导致循环 |
|
|
65
|
+
| **依赖反转** | A 依赖 B 的具体实现 → A 定义接口,B 实现 |
|
|
66
|
+
| **事件解耦** | 双方不需要同步返回值 |
|
|
67
|
+
| **合并模块** | 两个模块本就是一个概念 |
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// BEFORE: 循环依赖
|
|
71
|
+
// order.service.ts
|
|
72
|
+
import { PaymentService } from './payment.service' // A → B
|
|
73
|
+
|
|
74
|
+
// payment.service.ts
|
|
75
|
+
import { OrderService } from './order.service' // B → A
|
|
76
|
+
|
|
77
|
+
// AFTER: 事件解耦
|
|
78
|
+
// order.service.ts
|
|
79
|
+
this.eventBus.emit('orderCreated', order)
|
|
80
|
+
|
|
81
|
+
// payment.service.ts
|
|
82
|
+
this.eventBus.on('orderCreated', (order) => this.process(order))
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 散弹式修改
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
步骤:
|
|
89
|
+
1. 识别"变化原因"——是需求驱动还是技术驱动
|
|
90
|
+
2. 聚合受同一原因驱动的代码到一个模块
|
|
91
|
+
3. 考虑引入注册机制(策略注册/插件注册)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 隐式时序耦合
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// BEFORE: 必须按顺序调用,但编译器不强制
|
|
98
|
+
service.init()
|
|
99
|
+
service.configure()
|
|
100
|
+
service.start() // 如果没调 configure 会崩
|
|
101
|
+
|
|
102
|
+
// AFTER: Builder 强制顺序
|
|
103
|
+
const service = ServiceBuilder.create()
|
|
104
|
+
.withConfig(config) // 必须先配置
|
|
105
|
+
.build() // build 时校验
|
|
106
|
+
.start() // 配置完才能 start
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 过度抽象
|
|
110
|
+
|
|
111
|
+
识别信号:
|
|
112
|
+
- 接口只有一个实现,且没有可预见的替换场景
|
|
113
|
+
- 中间层只做转发,不加任何逻辑
|
|
114
|
+
- 3 行代码被包了 2 个 class
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
原则: 抽象的收益 > 阅读理解成本时才保留
|
|
118
|
+
行动: 删除只有 1 个实现且不需要 mock 的接口
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 贫血模型 vs 充血模型
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// BEFORE: 贫血模型——数据和操作分离
|
|
125
|
+
class Order { status: string; items: Item[]; total: number }
|
|
126
|
+
class OrderService {
|
|
127
|
+
cancel(order: Order) {
|
|
128
|
+
if (order.status !== 'pending') throw new Error('...')
|
|
129
|
+
order.status = 'cancelled'
|
|
130
|
+
order.total = 0
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// AFTER: 充血模型——行为内聚到实体
|
|
135
|
+
class Order {
|
|
136
|
+
cancel() {
|
|
137
|
+
if (this.status !== 'pending') throw new Error('...')
|
|
138
|
+
this.status = 'cancelled'
|
|
139
|
+
this.total = 0
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
> 注意:贫血模型在 CRUD 简单场景是合理的,只有在业务规则复杂时才需要转向充血模型。
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# DDD 模式指南
|
|
2
|
+
|
|
3
|
+
## 适用条件
|
|
4
|
+
|
|
5
|
+
- 项目有明确的业务领域(不是纯工具/脚本)
|
|
6
|
+
- 模块 ≥ 3 个,存在模块间协作
|
|
7
|
+
- 项目规模中等以上(> 50 文件)
|
|
8
|
+
|
|
9
|
+
## 核心概念
|
|
10
|
+
|
|
11
|
+
### 限界上下文 (Bounded Context)
|
|
12
|
+
|
|
13
|
+
每个业务子域有独立的模型定义,同一个词在不同上下文中含义不同。
|
|
14
|
+
|
|
15
|
+
- **DO**: 订单上下文的 `Product` 只包含 `id, name, price`;商品上下文的 `Product` 包含完整属性
|
|
16
|
+
- **DON'T**: 全局共用一个 `Product` 实体,所有模块往里加字段
|
|
17
|
+
|
|
18
|
+
### 聚合 (Aggregate)
|
|
19
|
+
|
|
20
|
+
一组紧密关联的实体和值对象,通过聚合根统一访问和修改,保证业务一致性。
|
|
21
|
+
|
|
22
|
+
- **DO**: `Order`(聚合根)包含 `OrderItem`(实体),通过 `Order.addItem()` 修改
|
|
23
|
+
- **DON'T**: 直接操作 `OrderItem` 绕过 `Order`
|
|
24
|
+
|
|
25
|
+
### 领域服务 (Domain Service)
|
|
26
|
+
|
|
27
|
+
不属于任何实体的业务逻辑,放在领域服务中。
|
|
28
|
+
|
|
29
|
+
- **DO**: `PricingService.calculateDiscount(order, user)` — 涉及多个聚合
|
|
30
|
+
- **DON'T**: 把折扣逻辑放在 `Order` 实体中(它不仅依赖订单,还依赖用户等级)
|
|
31
|
+
|
|
32
|
+
### 值对象 (Value Object)
|
|
33
|
+
|
|
34
|
+
无标识的不可变对象,通过属性值判等。
|
|
35
|
+
|
|
36
|
+
- **DO**: `Money(amount: 100, currency: 'CNY')` — 不可变,值相等即相同
|
|
37
|
+
- **DON'T**: 用 `number` 直接表示金额(丢失币种信息,可比较性差)
|
|
38
|
+
|
|
39
|
+
### 仓储 (Repository)
|
|
40
|
+
|
|
41
|
+
对聚合的持久化抽象,领域层定义接口,基础设施层实现。
|
|
42
|
+
|
|
43
|
+
- **DO**: 领域层 `interface OrderRepository { findById(id): Order }`
|
|
44
|
+
- **DON'T**: 领域层直接依赖 `TypeORM.getRepository(Order)`
|
|
45
|
+
|
|
46
|
+
## 分层架构
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
表现层 (Presentation) → Controller / Handler / GraphQL Resolver
|
|
50
|
+
↓ 调用
|
|
51
|
+
应用层 (Application) → ApplicationService / UseCase(编排,不含业务逻辑)
|
|
52
|
+
↓ 调用
|
|
53
|
+
领域层 (Domain) → Entity, ValueObject, DomainService, Repository接口
|
|
54
|
+
↑ 实现
|
|
55
|
+
基础设施层 (Infrastructure) → RepositoryImpl, 外部API适配, ORM配置
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**依赖方向**:上层依赖下层,领域层不依赖任何外层。
|
|
59
|
+
|
|
60
|
+
## 渐进式采用
|
|
61
|
+
|
|
62
|
+
不必一步到位全面 DDD:
|
|
63
|
+
|
|
64
|
+
1. **第一步**:至少做到分层(Controller vs Service vs Repository)
|
|
65
|
+
2. **第二步**:识别核心聚合,用实体方法替代贫血模型
|
|
66
|
+
3. **第三步**:对复杂模块引入限界上下文和领域事件
|