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,433 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
engineering-principles: 工程原则匹配脚本
|
|
4
|
+
|
|
5
|
+
用法:
|
|
6
|
+
python principles_matcher.py match --root <project_root> [--task <任务描述>] [--format json|markdown]
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# ── 原则分类定义 ──────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
PRINCIPLES = [
|
|
17
|
+
{
|
|
18
|
+
"id": "clean-code",
|
|
19
|
+
"name": "Clean Code",
|
|
20
|
+
"always": True,
|
|
21
|
+
"condition": None,
|
|
22
|
+
"ref": "quality-checklist.md",
|
|
23
|
+
"summary": "命名规范、函数 ≤ 30 行、避免 magic number、注释解释 why 不解释 what",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "error-handling",
|
|
27
|
+
"name": "错误处理",
|
|
28
|
+
"always": True,
|
|
29
|
+
"condition": None,
|
|
30
|
+
"ref": "quality-checklist.md",
|
|
31
|
+
"summary": "业务/系统异常分级、不吞异常、边界处统一捕获、入口校验",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "solid",
|
|
35
|
+
"name": "SOLID 原则",
|
|
36
|
+
"always": False,
|
|
37
|
+
"condition": "oop",
|
|
38
|
+
"ref": "solid-principles.md",
|
|
39
|
+
"summary": "SRP 单一职责, OCP 开闭, LSP 里氏替换, ISP 接口隔离, DIP 依赖倒置",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "ddd",
|
|
43
|
+
"name": "DDD 分层",
|
|
44
|
+
"always": False,
|
|
45
|
+
"condition": "ddd",
|
|
46
|
+
"ref": "ddd-patterns.md",
|
|
47
|
+
"summary": "聚合、限界上下文、领域服务、值对象、仓储模式",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "tdd",
|
|
51
|
+
"name": "TDD 测试驱动",
|
|
52
|
+
"always": False,
|
|
53
|
+
"condition": "test-framework",
|
|
54
|
+
"ref": "tdd-workflow.md",
|
|
55
|
+
"summary": "红绿重构循环、测试金字塔、Bug 先写复现测试",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "bdd",
|
|
59
|
+
"name": "BDD 行为驱动",
|
|
60
|
+
"always": False,
|
|
61
|
+
"condition": "e2e-framework",
|
|
62
|
+
"ref": "tdd-workflow.md",
|
|
63
|
+
"summary": "Gherkin 验收场景、用户行为驱动测试",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "design-patterns",
|
|
67
|
+
"name": "设计模式",
|
|
68
|
+
"always": False,
|
|
69
|
+
"condition": "oop",
|
|
70
|
+
"ref": "design-patterns.md",
|
|
71
|
+
"summary": "策略、工厂、观察者、适配器——仅在识别到适用信号时引入",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": "testability",
|
|
75
|
+
"name": "可测试性",
|
|
76
|
+
"always": False,
|
|
77
|
+
"condition": "test-framework-or-new",
|
|
78
|
+
"ref": "quality-checklist.md",
|
|
79
|
+
"summary": "依赖注入、纯函数优先、外部依赖接口隔离",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": "performance",
|
|
83
|
+
"name": "性能意识",
|
|
84
|
+
"always": False,
|
|
85
|
+
"condition": "database",
|
|
86
|
+
"ref": "quality-checklist.md",
|
|
87
|
+
"summary": "避免 N+1、检查索引、按需 select、缓存重复计算",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "security",
|
|
91
|
+
"name": "安全实践",
|
|
92
|
+
"always": False,
|
|
93
|
+
"condition": "user-input",
|
|
94
|
+
"ref": "quality-checklist.md",
|
|
95
|
+
"summary": "参数化查询、XSS 防护、CSRF token、权限校验、敏感数据脱敏",
|
|
96
|
+
},
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
# ── 上下文扫描 ──────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
IGNORE_DIRS = {
|
|
102
|
+
"node_modules", "dist", "build", ".git", "__pycache__",
|
|
103
|
+
".next", ".nuxt", ".output", "coverage", ".cache", "target",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
TEST_FRAMEWORK_SIGNALS = [
|
|
107
|
+
"jest.config", "vitest.config", "pytest.ini", "pyproject.toml",
|
|
108
|
+
"conftest.py", "setup.cfg", "__tests__",
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
E2E_SIGNALS = ["cypress", "cypress.config", "playwright.config", "playwright"]
|
|
112
|
+
|
|
113
|
+
ORM_SIGNALS = ["prisma", "typeorm", "sequelize", "drizzle", "mybatis", "hibernate"]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def scan_context(root: str) -> dict:
|
|
117
|
+
"""扫描项目上下文。"""
|
|
118
|
+
ctx = {
|
|
119
|
+
"has_project": False,
|
|
120
|
+
"total_files": 0,
|
|
121
|
+
"modules": [],
|
|
122
|
+
"language": "unknown",
|
|
123
|
+
"framework": "unknown",
|
|
124
|
+
"has_test_framework": False,
|
|
125
|
+
"has_e2e": False,
|
|
126
|
+
"has_orm": False,
|
|
127
|
+
"has_layered_structure": False,
|
|
128
|
+
"is_new_project": False,
|
|
129
|
+
"project_size": "unknown",
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
root_path = Path(root).resolve()
|
|
133
|
+
if not root_path.exists():
|
|
134
|
+
return ctx
|
|
135
|
+
ctx["has_project"] = True
|
|
136
|
+
|
|
137
|
+
# 计算文件数量
|
|
138
|
+
file_count = 0
|
|
139
|
+
has_src = False
|
|
140
|
+
has_tests = False
|
|
141
|
+
dir_names = set()
|
|
142
|
+
|
|
143
|
+
for dirpath, dirnames, filenames in os.walk(root_path):
|
|
144
|
+
dirnames[:] = [d for d in dirnames if d not in IGNORE_DIRS]
|
|
145
|
+
rel = Path(dirpath).relative_to(root_path)
|
|
146
|
+
dir_names.add(str(rel))
|
|
147
|
+
|
|
148
|
+
for fname in filenames:
|
|
149
|
+
file_count += 1
|
|
150
|
+
|
|
151
|
+
ctx["total_files"] = file_count
|
|
152
|
+
|
|
153
|
+
# 项目规模
|
|
154
|
+
if file_count == 0:
|
|
155
|
+
ctx["is_new_project"] = True
|
|
156
|
+
ctx["project_size"] = "new"
|
|
157
|
+
elif file_count < 50:
|
|
158
|
+
ctx["project_size"] = "small"
|
|
159
|
+
elif file_count < 500:
|
|
160
|
+
ctx["project_size"] = "medium"
|
|
161
|
+
else:
|
|
162
|
+
ctx["project_size"] = "large"
|
|
163
|
+
|
|
164
|
+
# 语言/框架检测
|
|
165
|
+
root_files = [f.name for f in root_path.iterdir() if f.is_file()]
|
|
166
|
+
|
|
167
|
+
if "package.json" in root_files:
|
|
168
|
+
ctx["language"] = "typescript/javascript"
|
|
169
|
+
pkg_path = root_path / "package.json"
|
|
170
|
+
try:
|
|
171
|
+
pkg = json.loads(pkg_path.read_text(encoding="utf-8"))
|
|
172
|
+
deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
|
|
173
|
+
|
|
174
|
+
if "next" in deps:
|
|
175
|
+
ctx["framework"] = "Next.js"
|
|
176
|
+
elif "nuxt" in deps or "@nuxt/kit" in deps:
|
|
177
|
+
ctx["framework"] = "Nuxt"
|
|
178
|
+
elif "@nestjs/core" in deps:
|
|
179
|
+
ctx["framework"] = "NestJS"
|
|
180
|
+
elif "vue" in deps:
|
|
181
|
+
ctx["framework"] = "Vue"
|
|
182
|
+
elif "react" in deps:
|
|
183
|
+
ctx["framework"] = "React"
|
|
184
|
+
elif "express" in deps:
|
|
185
|
+
ctx["framework"] = "Express"
|
|
186
|
+
|
|
187
|
+
# 测试框架
|
|
188
|
+
for test_dep in ["jest", "vitest", "@jest/core", "mocha", "ava"]:
|
|
189
|
+
if test_dep in deps:
|
|
190
|
+
ctx["has_test_framework"] = True
|
|
191
|
+
break
|
|
192
|
+
|
|
193
|
+
# E2E
|
|
194
|
+
for e2e_dep in ["cypress", "@playwright/test", "playwright"]:
|
|
195
|
+
if e2e_dep in deps:
|
|
196
|
+
ctx["has_e2e"] = True
|
|
197
|
+
break
|
|
198
|
+
|
|
199
|
+
# ORM
|
|
200
|
+
for orm_dep in ["typeorm", "prisma", "@prisma/client", "sequelize", "drizzle-orm"]:
|
|
201
|
+
if orm_dep in deps:
|
|
202
|
+
ctx["has_orm"] = True
|
|
203
|
+
break
|
|
204
|
+
|
|
205
|
+
except (json.JSONDecodeError, OSError):
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
elif "pom.xml" in root_files or "build.gradle" in root_files:
|
|
209
|
+
ctx["language"] = "java"
|
|
210
|
+
ctx["framework"] = "Spring Boot"
|
|
211
|
+
ctx["has_test_framework"] = True # Java 项目通常自带 JUnit
|
|
212
|
+
ctx["has_orm"] = True
|
|
213
|
+
|
|
214
|
+
elif "pyproject.toml" in root_files or "setup.py" in root_files:
|
|
215
|
+
ctx["language"] = "python"
|
|
216
|
+
|
|
217
|
+
# 分层结构检测
|
|
218
|
+
layer_indicators = {"controller", "service", "repository", "domain", "infrastructure", "application"}
|
|
219
|
+
for d in dir_names:
|
|
220
|
+
parts = d.lower().split("/")
|
|
221
|
+
if layer_indicators.intersection(parts):
|
|
222
|
+
ctx["has_layered_structure"] = True
|
|
223
|
+
break
|
|
224
|
+
|
|
225
|
+
# 测试目录检测(补充)
|
|
226
|
+
for test_dir in ["__tests__", "test", "tests", "spec"]:
|
|
227
|
+
if (root_path / test_dir).exists() or (root_path / "src" / test_dir).exists():
|
|
228
|
+
ctx["has_test_framework"] = True
|
|
229
|
+
break
|
|
230
|
+
|
|
231
|
+
# 模块识别
|
|
232
|
+
src = root_path / "src"
|
|
233
|
+
if src.exists():
|
|
234
|
+
ctx["modules"] = sorted([
|
|
235
|
+
d.name for d in src.iterdir()
|
|
236
|
+
if d.is_dir() and d.name not in IGNORE_DIRS
|
|
237
|
+
])
|
|
238
|
+
|
|
239
|
+
return ctx
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ── 原则匹配 ──────────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
TASK_SECURITY_KEYWORDS = [
|
|
245
|
+
"登录", "注册", "认证", "auth", "密码", "password",
|
|
246
|
+
"api", "接口", "表单", "输入", "input",
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
TASK_DB_KEYWORDS = [
|
|
250
|
+
"数据库", "database", "查询", "query", "表", "table",
|
|
251
|
+
"sql", "orm", "crud", "列表", "分页",
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def match_principles(ctx: dict, task: str | None = None) -> dict:
|
|
256
|
+
"""根据上下文匹配适用原则。"""
|
|
257
|
+
always_applicable = []
|
|
258
|
+
detected_applicable = []
|
|
259
|
+
skipped = []
|
|
260
|
+
|
|
261
|
+
task_lower = (task or "").lower()
|
|
262
|
+
|
|
263
|
+
for p in PRINCIPLES:
|
|
264
|
+
if p["always"]:
|
|
265
|
+
always_applicable.append({
|
|
266
|
+
"id": p["id"],
|
|
267
|
+
"name": p["name"],
|
|
268
|
+
"summary": p["summary"],
|
|
269
|
+
"ref": p["ref"],
|
|
270
|
+
})
|
|
271
|
+
continue
|
|
272
|
+
|
|
273
|
+
cond = p["condition"]
|
|
274
|
+
applicable = False
|
|
275
|
+
reason = ""
|
|
276
|
+
|
|
277
|
+
if cond == "oop":
|
|
278
|
+
if ctx.get("language") in ("typescript/javascript", "java", "python"):
|
|
279
|
+
applicable = True
|
|
280
|
+
reason = f"检测到 OOP 语言: {ctx['language']}"
|
|
281
|
+
else:
|
|
282
|
+
reason = "未检测到 OOP 语言"
|
|
283
|
+
|
|
284
|
+
elif cond == "ddd":
|
|
285
|
+
if ctx.get("has_layered_structure") and ctx.get("project_size") in ("medium", "large"):
|
|
286
|
+
applicable = True
|
|
287
|
+
reason = "检测到分层结构且项目规模 ≥ 中型"
|
|
288
|
+
elif ctx.get("project_size") == "large" and len(ctx.get("modules", [])) >= 3:
|
|
289
|
+
applicable = True
|
|
290
|
+
reason = f"大型项目,{len(ctx['modules'])} 个模块"
|
|
291
|
+
else:
|
|
292
|
+
reason = "项目规模不足或无分层结构"
|
|
293
|
+
|
|
294
|
+
elif cond == "test-framework":
|
|
295
|
+
if ctx.get("has_test_framework"):
|
|
296
|
+
applicable = True
|
|
297
|
+
reason = "检测到测试框架"
|
|
298
|
+
else:
|
|
299
|
+
reason = "未检测到测试框架"
|
|
300
|
+
|
|
301
|
+
elif cond == "e2e-framework":
|
|
302
|
+
if ctx.get("has_e2e"):
|
|
303
|
+
applicable = True
|
|
304
|
+
reason = "检测到 E2E 框架"
|
|
305
|
+
else:
|
|
306
|
+
reason = "未检测到 E2E 框架(cypress/playwright)"
|
|
307
|
+
|
|
308
|
+
elif cond == "test-framework-or-new":
|
|
309
|
+
if ctx.get("has_test_framework") or ctx.get("is_new_project"):
|
|
310
|
+
applicable = True
|
|
311
|
+
reason = "有测试框架" if ctx.get("has_test_framework") else "新项目(推荐)"
|
|
312
|
+
else:
|
|
313
|
+
reason = "无测试框架且非新项目"
|
|
314
|
+
|
|
315
|
+
elif cond == "database":
|
|
316
|
+
if ctx.get("has_orm"):
|
|
317
|
+
applicable = True
|
|
318
|
+
reason = "检测到 ORM/数据库依赖"
|
|
319
|
+
elif any(kw in task_lower for kw in TASK_DB_KEYWORDS):
|
|
320
|
+
applicable = True
|
|
321
|
+
reason = "任务描述涉及数据库操作"
|
|
322
|
+
else:
|
|
323
|
+
reason = "未检测到数据库相关内容"
|
|
324
|
+
|
|
325
|
+
elif cond == "user-input":
|
|
326
|
+
if any(kw in task_lower for kw in TASK_SECURITY_KEYWORDS):
|
|
327
|
+
applicable = True
|
|
328
|
+
reason = "任务描述涉及用户输入/认证"
|
|
329
|
+
else:
|
|
330
|
+
reason = "任务描述未涉及用户输入/安全相关"
|
|
331
|
+
|
|
332
|
+
if applicable:
|
|
333
|
+
detected_applicable.append({
|
|
334
|
+
"id": p["id"],
|
|
335
|
+
"name": p["name"],
|
|
336
|
+
"summary": p["summary"],
|
|
337
|
+
"ref": p["ref"],
|
|
338
|
+
"reason": reason,
|
|
339
|
+
})
|
|
340
|
+
else:
|
|
341
|
+
skipped.append({
|
|
342
|
+
"id": p["id"],
|
|
343
|
+
"name": p["name"],
|
|
344
|
+
"reason": reason,
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
"always": always_applicable,
|
|
349
|
+
"detected": detected_applicable,
|
|
350
|
+
"skipped": skipped,
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
# ── 输出格式化 ──────────────────────────────────────────────
|
|
355
|
+
|
|
356
|
+
def format_markdown(ctx: dict, matched: dict, task: str | None) -> str:
|
|
357
|
+
lines = [
|
|
358
|
+
"# 工程原则匹配报告",
|
|
359
|
+
"",
|
|
360
|
+
"## 项目上下文",
|
|
361
|
+
f"- 语言/框架: {ctx.get('language', 'unknown')} + {ctx.get('framework', 'unknown')}",
|
|
362
|
+
f"- 测试框架: {'已配置' if ctx.get('has_test_framework') else '未检测到'}",
|
|
363
|
+
f"- E2E 框架: {'已配置' if ctx.get('has_e2e') else '未检测到'}",
|
|
364
|
+
f"- 项目规模: {ctx.get('project_size', 'unknown')}({ctx.get('total_files', 0)} 文件)",
|
|
365
|
+
f"- 分层结构: {'是' if ctx.get('has_layered_structure') else '否'}",
|
|
366
|
+
f"- ORM/数据库: {'是' if ctx.get('has_orm') else '否'}",
|
|
367
|
+
]
|
|
368
|
+
|
|
369
|
+
if task:
|
|
370
|
+
lines.append(f"- 当前任务: {task}")
|
|
371
|
+
lines.append("")
|
|
372
|
+
|
|
373
|
+
lines.append("## 适用原则")
|
|
374
|
+
lines.append("")
|
|
375
|
+
lines.append("### ✅ 始终适用")
|
|
376
|
+
for p in matched["always"]:
|
|
377
|
+
lines.append(f"- **{p['name']}**: {p['summary']}")
|
|
378
|
+
lines.append("")
|
|
379
|
+
|
|
380
|
+
if matched["detected"]:
|
|
381
|
+
lines.append("### ✅ 经检测适用")
|
|
382
|
+
for p in matched["detected"]:
|
|
383
|
+
lines.append(f"- **{p['name']}**: {p['summary']}({p['reason']})")
|
|
384
|
+
lines.append("")
|
|
385
|
+
|
|
386
|
+
if matched["skipped"]:
|
|
387
|
+
lines.append("### ⏭️ 不适用")
|
|
388
|
+
for p in matched["skipped"]:
|
|
389
|
+
lines.append(f"- **{p['name']}**: {p['reason']}")
|
|
390
|
+
lines.append("")
|
|
391
|
+
|
|
392
|
+
# 简短约束清单
|
|
393
|
+
lines.append("## 约束清单(编码时参考)")
|
|
394
|
+
lines.append("")
|
|
395
|
+
lines.append("```")
|
|
396
|
+
for p in matched["always"]:
|
|
397
|
+
lines.append(f"- {p['name']}: {p['summary'].split('、')[0]}")
|
|
398
|
+
for p in matched["detected"]:
|
|
399
|
+
lines.append(f"- {p['name']}: {p['summary'].split('、')[0]}")
|
|
400
|
+
lines.append("```")
|
|
401
|
+
|
|
402
|
+
return "\n".join(lines)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def format_json(ctx: dict, matched: dict) -> str:
|
|
406
|
+
return json.dumps({"context": ctx, "principles": matched}, ensure_ascii=False, indent=2)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
# ── CLI ──────────────────────────────────────────────
|
|
410
|
+
|
|
411
|
+
def main():
|
|
412
|
+
parser = argparse.ArgumentParser(description="engineering-principles: 工程原则匹配")
|
|
413
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
414
|
+
|
|
415
|
+
p_match = sub.add_parser("match", help="匹配适用原则")
|
|
416
|
+
p_match.add_argument("--root", required=True, help="项目根目录")
|
|
417
|
+
p_match.add_argument("--task", default=None, help="当前任务描述")
|
|
418
|
+
p_match.add_argument("--format", default="markdown", choices=["json", "markdown"])
|
|
419
|
+
|
|
420
|
+
args = parser.parse_args()
|
|
421
|
+
|
|
422
|
+
if args.command == "match":
|
|
423
|
+
ctx = scan_context(args.root)
|
|
424
|
+
matched = match_principles(ctx, args.task)
|
|
425
|
+
|
|
426
|
+
if args.format == "json":
|
|
427
|
+
print(format_json(ctx, matched))
|
|
428
|
+
else:
|
|
429
|
+
print(format_markdown(ctx, matched, args.task))
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
if __name__ == "__main__":
|
|
433
|
+
main()
|