cc-devflow 1.0.1

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.
Files changed (277) hide show
  1. package/.claude/CLAUDE.md +83 -0
  2. package/.claude/agents/architecture-designer.md +443 -0
  3. package/.claude/agents/bug-analyzer.md +382 -0
  4. package/.claude/agents/checklist-agent.md +175 -0
  5. package/.claude/agents/clarify-analyst.md +50 -0
  6. package/.claude/agents/code-reviewer.md +71 -0
  7. package/.claude/agents/codex-analyzer.md +39 -0
  8. package/.claude/agents/compatibility-checker.md +580 -0
  9. package/.claude/agents/consistency-checker.md +532 -0
  10. package/.claude/agents/impact-analyzer.md +441 -0
  11. package/.claude/agents/planner.md +230 -0
  12. package/.claude/agents/prd-writer.md +320 -0
  13. package/.claude/agents/project-guidelines-generator.md +1329 -0
  14. package/.claude/agents/qa-tester.md +313 -0
  15. package/.claude/agents/release-manager.md +295 -0
  16. package/.claude/agents/security-reviewer.md +314 -0
  17. package/.claude/agents/style-guide-generator.md +458 -0
  18. package/.claude/agents/tech-architect.md +516 -0
  19. package/.claude/agents/ui-designer.md +485 -0
  20. package/.claude/commands/code-review-high.md +58 -0
  21. package/.claude/commands/core-architecture.md +429 -0
  22. package/.claude/commands/core-guidelines.md +486 -0
  23. package/.claude/commands/core-roadmap.md +439 -0
  24. package/.claude/commands/core-style.md +293 -0
  25. package/.claude/commands/flow-archive.md +245 -0
  26. package/.claude/commands/flow-checklist.md +260 -0
  27. package/.claude/commands/flow-clarify.md +136 -0
  28. package/.claude/commands/flow-constitution.md +82 -0
  29. package/.claude/commands/flow-dev.md +134 -0
  30. package/.claude/commands/flow-epic.md +150 -0
  31. package/.claude/commands/flow-fix.md +104 -0
  32. package/.claude/commands/flow-ideate.md +214 -0
  33. package/.claude/commands/flow-init.md +313 -0
  34. package/.claude/commands/flow-new.md +394 -0
  35. package/.claude/commands/flow-prd.md +131 -0
  36. package/.claude/commands/flow-qa.md +93 -0
  37. package/.claude/commands/flow-release.md +92 -0
  38. package/.claude/commands/flow-restart.md +98 -0
  39. package/.claude/commands/flow-status.md +64 -0
  40. package/.claude/commands/flow-tech.md +142 -0
  41. package/.claude/commands/flow-ui.md +189 -0
  42. package/.claude/commands/flow-update.md +111 -0
  43. package/.claude/commands/flow-upgrade.md +115 -0
  44. package/.claude/commands/flow-verify.md +96 -0
  45. package/.claude/commands/problem-analyzer.md +60 -0
  46. package/.claude/config/quality-rules.yml +161 -0
  47. package/.claude/docs/SPEC_KIT_CONSTITUTION_ANALYSIS.md +426 -0
  48. package/.claude/docs/design/consistency-conflict-detection-algorithms.md +658 -0
  49. package/.claude/docs/design/intent-driven-input-design.md +380 -0
  50. package/.claude/docs/design/prd-version-management-design.md +437 -0
  51. package/.claude/docs/guides/INIT_TROUBLESHOOTING.md +117 -0
  52. package/.claude/docs/guides/NEW_TROUBLESHOOTING.md +151 -0
  53. package/.claude/docs/guides/ROADMAP_TROUBLESHOOTING.md +188 -0
  54. package/.claude/docs/guides/TASK_COMPLETION_MARKING.md +338 -0
  55. package/.claude/docs/templates/ARCHITECTURE_TEMPLATE.md +633 -0
  56. package/.claude/docs/templates/BACKLOG_TEMPLATE.md +261 -0
  57. package/.claude/docs/templates/CHECKLIST_TEMPLATE.md +52 -0
  58. package/.claude/docs/templates/CLARIFICATION_REPORT_TEMPLATE.md +206 -0
  59. package/.claude/docs/templates/CODE_REVIEW_TEMPLATE.md +71 -0
  60. package/.claude/docs/templates/EPIC_TEMPLATE.md +805 -0
  61. package/.claude/docs/templates/INIT_FLOW_TEMPLATE.md +213 -0
  62. package/.claude/docs/templates/INTENT_CLARIFICATION_TEMPLATE.md +57 -0
  63. package/.claude/docs/templates/NEW_ORCHESTRATION_TEMPLATE.md +148 -0
  64. package/.claude/docs/templates/PRD_TEMPLATE.md +562 -0
  65. package/.claude/docs/templates/RESEARCH_TEMPLATE.md +276 -0
  66. package/.claude/docs/templates/REVIEW-HIGH.md +57 -0
  67. package/.claude/docs/templates/ROADMAP_DIALOGUE_TEMPLATE.md +198 -0
  68. package/.claude/docs/templates/ROADMAP_TEMPLATE.md +310 -0
  69. package/.claude/docs/templates/STYLE_TEMPLATE.md +1266 -0
  70. package/.claude/docs/templates/TASKS_TEMPLATE.md +523 -0
  71. package/.claude/docs/templates/TECH_DESIGN_TEMPLATE.md +1019 -0
  72. package/.claude/docs/templates/UI_PROTOTYPE_TEMPLATE.md +1436 -0
  73. package/.claude/guides/agent-guides/agent-coordination-guide.md +459 -0
  74. package/.claude/guides/project-guidelines-system.md +463 -0
  75. package/.claude/guides/technical-guides/datetime-handling-guide.md +563 -0
  76. package/.claude/guides/technical-guides/git-github-guide.md +642 -0
  77. package/.claude/guides/technical-guides/test-execution-guide.md +618 -0
  78. package/.claude/guides/workflow-guides/bug-fix-orchestrator.md +217 -0
  79. package/.claude/guides/workflow-guides/flow-orchestrator.md +282 -0
  80. package/.claude/hooks/checklist-gate.js +397 -0
  81. package/.claude/hooks/error-handling-reminder.sh +12 -0
  82. package/.claude/hooks/error-handling-reminder.ts +459 -0
  83. package/.claude/hooks/post-tool-use-tracker.sh +280 -0
  84. package/.claude/hooks/pre-tool-use-guardrail.sh +36 -0
  85. package/.claude/hooks/pre-tool-use-guardrail.ts +342 -0
  86. package/.claude/hooks/skill-activation-prompt.sh +36 -0
  87. package/.claude/hooks/skill-activation-prompt.ts +214 -0
  88. package/.claude/hooks/state/skills-used-test-guard.json +3 -0
  89. package/.claude/rules/devflow-conventions.md +305 -0
  90. package/.claude/rules/project-constitution.md +748 -0
  91. package/.claude/schemas/constitution.schema.json +43 -0
  92. package/.claude/scripts/analyze-upgrade-impact.sh +200 -0
  93. package/.claude/scripts/archive-requirement.sh +351 -0
  94. package/.claude/scripts/calculate-checklist-completion.sh +243 -0
  95. package/.claude/scripts/calculate-quarter.sh +206 -0
  96. package/.claude/scripts/check-dependencies.sh +409 -0
  97. package/.claude/scripts/check-prerequisites.sh +232 -0
  98. package/.claude/scripts/check-task-status.sh +264 -0
  99. package/.claude/scripts/checklist-errors.sh +131 -0
  100. package/.claude/scripts/common.sh +570 -0
  101. package/.claude/scripts/consolidate-research.sh +182 -0
  102. package/.claude/scripts/create-requirement.sh +426 -0
  103. package/.claude/scripts/export-contracts.sh +117 -0
  104. package/.claude/scripts/extract-data-model.sh +78 -0
  105. package/.claude/scripts/generate-clarification-questions.sh +377 -0
  106. package/.claude/scripts/generate-clarification-report.sh +463 -0
  107. package/.claude/scripts/generate-quickstart.sh +146 -0
  108. package/.claude/scripts/generate-research-tasks.sh +157 -0
  109. package/.claude/scripts/generate-status-report.sh +523 -0
  110. package/.claude/scripts/generate-tech-analysis.sh +46 -0
  111. package/.claude/scripts/locate-requirement-in-roadmap.sh +233 -0
  112. package/.claude/scripts/manage-constitution.sh +602 -0
  113. package/.claude/scripts/mark-task-complete.sh +198 -0
  114. package/.claude/scripts/populate-research-tasks.sh +259 -0
  115. package/.claude/scripts/recover-workflow.sh +460 -0
  116. package/.claude/scripts/run-clarify-scan.sh +601 -0
  117. package/.claude/scripts/run-high-review.sh +62 -0
  118. package/.claude/scripts/run-problem-analysis.sh +68 -0
  119. package/.claude/scripts/setup-epic.sh +173 -0
  120. package/.claude/scripts/sync-roadmap-progress.sh +300 -0
  121. package/.claude/scripts/sync-task-marks.sh +199 -0
  122. package/.claude/scripts/test-clarify-scan.sh +515 -0
  123. package/.claude/scripts/update-agent-context.sh +806 -0
  124. package/.claude/scripts/validate-constitution.sh +567 -0
  125. package/.claude/scripts/validate-hooks.sh +487 -0
  126. package/.claude/scripts/validate-research.sh +332 -0
  127. package/.claude/scripts/validate-scope-boundary.sh +493 -0
  128. package/.claude/scripts/verify-setup.sh +37 -0
  129. package/.claude/settings.json +76 -0
  130. package/.claude/skills/_reference-implementations/README.md +96 -0
  131. package/.claude/skills/_reference-implementations/backend-express-prisma/SKILL.md +302 -0
  132. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/architecture-overview.md +451 -0
  133. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/async-and-errors.md +307 -0
  134. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/complete-examples.md +638 -0
  135. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/configuration.md +275 -0
  136. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/database-patterns.md +224 -0
  137. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/middleware-guide.md +213 -0
  138. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/routing-and-controllers.md +756 -0
  139. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/sentry-and-monitoring.md +336 -0
  140. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/services-and-repositories.md +789 -0
  141. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/testing-guide.md +235 -0
  142. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/validation-patterns.md +754 -0
  143. package/.claude/skills/_reference-implementations/frontend-react-mui/SKILL.md +399 -0
  144. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/common-patterns.md +331 -0
  145. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/complete-examples.md +872 -0
  146. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/component-patterns.md +502 -0
  147. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/data-fetching.md +767 -0
  148. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/file-organization.md +502 -0
  149. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/loading-and-error-states.md +501 -0
  150. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/performance.md +406 -0
  151. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/routing-guide.md +364 -0
  152. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/styling-guide.md +428 -0
  153. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/typescript-standards.md +418 -0
  154. package/.claude/skills/cc-devflow-orchestrator/SKILL.md +229 -0
  155. package/.claude/skills/constitution-guardian/SKILL.md +306 -0
  156. package/.claude/skills/devflow-constitution-quick-ref/SKILL.md +374 -0
  157. package/.claude/skills/devflow-file-standards/SKILL.md +353 -0
  158. package/.claude/skills/devflow-tdd-enforcer/SKILL.md +192 -0
  159. package/.claude/skills/skill-developer/ADVANCED.md +197 -0
  160. package/.claude/skills/skill-developer/HOOK_MECHANISMS.md +306 -0
  161. package/.claude/skills/skill-developer/PATTERNS_LIBRARY.md +152 -0
  162. package/.claude/skills/skill-developer/SKILL.md +426 -0
  163. package/.claude/skills/skill-developer/SKILL_RULES_REFERENCE.md +315 -0
  164. package/.claude/skills/skill-developer/TRIGGER_TYPES.md +305 -0
  165. package/.claude/skills/skill-developer/TROUBLESHOOTING.md +514 -0
  166. package/.claude/skills/skill-rules.json +213 -0
  167. package/.claude/tests/README.md +300 -0
  168. package/.claude/tests/TODO.md +69 -0
  169. package/.claude/tests/__pycache__/test_analyze_upgrade_impact.cpython-311-pytest-7.2.2.pyc +0 -0
  170. package/.claude/tests/__pycache__/test_consolidate_research.cpython-311-pytest-7.2.2.pyc +0 -0
  171. package/.claude/tests/__pycache__/test_export_contracts.cpython-311-pytest-7.2.2.pyc +0 -0
  172. package/.claude/tests/__pycache__/test_extract_data_model.cpython-311-pytest-7.2.2.pyc +0 -0
  173. package/.claude/tests/__pycache__/test_generate_quickstart.cpython-311-pytest-7.2.2.pyc +0 -0
  174. package/.claude/tests/__pycache__/test_generate_research_tasks.cpython-311-pytest-7.2.2.pyc +0 -0
  175. package/.claude/tests/constitution/run_all_constitution_tests.sh +111 -0
  176. package/.claude/tests/constitution/test_agent_assignment.sh +207 -0
  177. package/.claude/tests/constitution/test_article_coverage.sh +201 -0
  178. package/.claude/tests/constitution/test_template_completeness.sh +150 -0
  179. package/.claude/tests/constitution/test_version_consistency.sh +120 -0
  180. package/.claude/tests/fixtures/spec_delta_full.md +16 -0
  181. package/.claude/tests/fixtures/tasks_progress_sample.md +5 -0
  182. package/.claude/tests/run-all-tests.sh +229 -0
  183. package/.claude/tests/scripts/run.sh +30 -0
  184. package/.claude/tests/scripts/test-framework.sh +128 -0
  185. package/.claude/tests/scripts/test_check_prerequisites.sh +511 -0
  186. package/.claude/tests/scripts/test_check_prerequisites.sh.bak +504 -0
  187. package/.claude/tests/scripts/test_check_prerequisites.sh.bak2 +505 -0
  188. package/.claude/tests/scripts/test_check_prerequisites.sh.bak3 +506 -0
  189. package/.claude/tests/scripts/test_check_prerequisites.sh.bak4 +507 -0
  190. package/.claude/tests/scripts/test_check_prerequisites.sh.bak5 +508 -0
  191. package/.claude/tests/scripts/test_check_task_status.sh +499 -0
  192. package/.claude/tests/scripts/test_common.sh +244 -0
  193. package/.claude/tests/scripts/test_generate_status_report.sh +71 -0
  194. package/.claude/tests/scripts/test_mark_task_complete.sh +441 -0
  195. package/.claude/tests/scripts/test_mark_task_complete.sh.backup +410 -0
  196. package/.claude/tests/scripts/test_recover_workflow.sh +304 -0
  197. package/.claude/tests/scripts/test_setup_epic.sh +437 -0
  198. package/.claude/tests/scripts/test_sync_task_marks.sh +196 -0
  199. package/.claude/tests/scripts/test_validate_constitution.sh +74 -0
  200. package/.claude/tests/scripts/test_validate_research.sh +462 -0
  201. package/.claude/tests/slugify.bats +82 -0
  202. package/.claude/tests/test-framework.sh +732 -0
  203. package/.claude/tests/test_analyze_upgrade_impact.py +34 -0
  204. package/.claude/tests/test_consolidate_research.py +48 -0
  205. package/.claude/tests/test_export_contracts.py +43 -0
  206. package/.claude/tests/test_extract_data_model.py +33 -0
  207. package/.claude/tests/test_generate_quickstart.py +50 -0
  208. package/.claude/tests/test_generate_research_tasks.py +52 -0
  209. package/.claude/tsc-cache/6e64f818-6398-49ca-8623-581a9af85c44/edited-files.log +1 -0
  210. package/.claude/tsc-cache/795ba6e3-b98a-423b-bab2-51aa62812569/affected-repos.txt +1 -0
  211. package/.claude/tsc-cache/795ba6e3-b98a-423b-bab2-51aa62812569/edited-files.log +1 -0
  212. package/.claude/tsc-cache/ae335694-be5a-4ba4-a1a0-b676c09a7906/affected-repos.txt +1 -0
  213. package/.claude/tsc-cache/ae335694-be5a-4ba4-a1a0-b676c09a7906/edited-files.log +1 -0
  214. package/CHANGELOG.md +507 -0
  215. package/LICENSE +21 -0
  216. package/README.md +534 -0
  217. package/README.zh-CN.md +530 -0
  218. package/bin/adapt.js +240 -0
  219. package/bin/cc-devflow-cli.js +185 -0
  220. package/bin/cc-devflow.js +78 -0
  221. package/config/adapters.yml +5 -0
  222. package/config/schema/adapters.schema.json +44 -0
  223. package/docs/CLAUDE.md +26 -0
  224. package/docs/commands/README.md +61 -0
  225. package/docs/commands/README.zh-CN.md +55 -0
  226. package/docs/commands/core-roadmap.md +106 -0
  227. package/docs/commands/core-roadmap.zh-CN.md +102 -0
  228. package/docs/commands/core-style.md +405 -0
  229. package/docs/commands/core-style.zh-CN.md +405 -0
  230. package/docs/commands/flow-init.md +134 -0
  231. package/docs/commands/flow-init.zh-CN.md +163 -0
  232. package/docs/commands/flow-new.md +274 -0
  233. package/docs/commands/flow-new.zh-CN.md +270 -0
  234. package/docs/guides/getting-started.md +204 -0
  235. package/docs/guides/getting-started.zh-CN.md +152 -0
  236. package/lib/adapters/adapter-interface.js +57 -0
  237. package/lib/adapters/claude-adapter.js +74 -0
  238. package/lib/adapters/codex-adapter.js +40 -0
  239. package/lib/adapters/config-validator.js +68 -0
  240. package/lib/adapters/logger.js +42 -0
  241. package/lib/adapters/registry.js +153 -0
  242. package/lib/compiler/CLAUDE.md +92 -0
  243. package/lib/compiler/__tests__/drift.test.js +215 -0
  244. package/lib/compiler/__tests__/errors.test.js +184 -0
  245. package/lib/compiler/__tests__/incremental.test.js +174 -0
  246. package/lib/compiler/__tests__/integration.test.js +174 -0
  247. package/lib/compiler/__tests__/manifest.test.js +233 -0
  248. package/lib/compiler/__tests__/parser.test.js +456 -0
  249. package/lib/compiler/__tests__/schemas.test.js +301 -0
  250. package/lib/compiler/__tests__/skills-registry.test.js +125 -0
  251. package/lib/compiler/__tests__/transformer.test.js +286 -0
  252. package/lib/compiler/emitters/antigravity-emitter.js +171 -0
  253. package/lib/compiler/emitters/base-emitter.js +73 -0
  254. package/lib/compiler/emitters/codex-emitter.js +52 -0
  255. package/lib/compiler/emitters/cursor-emitter.js +31 -0
  256. package/lib/compiler/emitters/index.js +50 -0
  257. package/lib/compiler/emitters/qwen-emitter.js +39 -0
  258. package/lib/compiler/errors.js +119 -0
  259. package/lib/compiler/index.js +256 -0
  260. package/lib/compiler/manifest.js +242 -0
  261. package/lib/compiler/parser.js +258 -0
  262. package/lib/compiler/platforms.js +113 -0
  263. package/lib/compiler/resource-copier.js +320 -0
  264. package/lib/compiler/rules-emitters/__tests__/antigravity-rules-emitter.test.js +191 -0
  265. package/lib/compiler/rules-emitters/__tests__/codex-rules-emitter.test.js +109 -0
  266. package/lib/compiler/rules-emitters/__tests__/cursor-rules-emitter.test.js +123 -0
  267. package/lib/compiler/rules-emitters/__tests__/qwen-rules-emitter.test.js +123 -0
  268. package/lib/compiler/rules-emitters/antigravity-rules-emitter.js +253 -0
  269. package/lib/compiler/rules-emitters/base-rules-emitter.js +83 -0
  270. package/lib/compiler/rules-emitters/codex-rules-emitter.js +116 -0
  271. package/lib/compiler/rules-emitters/cursor-rules-emitter.js +98 -0
  272. package/lib/compiler/rules-emitters/index.js +71 -0
  273. package/lib/compiler/rules-emitters/qwen-rules-emitter.js +70 -0
  274. package/lib/compiler/schemas.js +144 -0
  275. package/lib/compiler/skills-registry.js +225 -0
  276. package/lib/compiler/transformer.js +236 -0
  277. package/package.json +50 -0
@@ -0,0 +1,280 @@
1
+ #!/bin/bash
2
+
3
+ # ═══════════════════════════════════════════════════════════════════════════════
4
+ # 文件变更追踪 Hook - PostToolUse
5
+ # ═══════════════════════════════════════════════════════════════════════════════
6
+ #
7
+ # 【核心功能】
8
+ # 在 Edit/MultiEdit/Write 工具执行后追踪被修改的文件
9
+ #
10
+ # 【工作原理】
11
+ # 1. 监听工具执行完成事件
12
+ # 2. 检测文件所属的仓库/服务
13
+ # 3. 记录受影响的文件和仓库
14
+ # 4. 生成构建和类型检查命令
15
+ # 5. 缓存信息供后续使用(如 tsc-check hook)
16
+ #
17
+ # 【设计哲学】
18
+ # "理解上下文是智能的基础"
19
+ # 通过追踪文件变更,Claude 能更好地理解项目的活跃区域
20
+ #
21
+ # 【应用场景】
22
+ # - 配合 tsc-check hook 进行增量类型检查
23
+ # - 为技能激活提供上下文(哪些服务被修改了)
24
+ # - 生成项目活动报告
25
+ #
26
+ # ═══════════════════════════════════════════════════════════════════════════════
27
+
28
+ # 错误时立即退出
29
+ set -e
30
+
31
+ # ───────────────────────────────────────────────────────────────────────────────
32
+ # 步骤 1: 读取工具信息
33
+ # ───────────────────────────────────────────────────────────────────────────────
34
+ # 从 stdin 读取工具执行信息(JSON 格式)
35
+ tool_info=$(cat)
36
+
37
+ # 提取关键数据
38
+ # - tool_name: 工具名称(Edit/MultiEdit/Write)
39
+ # - file_path: 被修改的文件路径
40
+ # - session_id: 会话 ID(用于隔离不同会话的缓存)
41
+ tool_name=$(echo "$tool_info" | jq -r '.tool_name // empty')
42
+ file_path=$(echo "$tool_info" | jq -r '.tool_input.file_path // empty')
43
+ session_id=$(echo "$tool_info" | jq -r '.session_id // empty')
44
+
45
+ # ───────────────────────────────────────────────────────────────────────────────
46
+ # 步骤 2: 过滤条件检查
47
+ # ───────────────────────────────────────────────────────────────────────────────
48
+
49
+ # 检查 1: 只处理编辑工具
50
+ if [[ ! "$tool_name" =~ ^(Edit|MultiEdit|Write)$ ]] || [[ -z "$file_path" ]]; then
51
+ exit 0 # 不是编辑工具或没有文件路径,跳过
52
+ fi
53
+
54
+ # 检查 2: 跳过 Markdown 文件(文档变更不需要类型检查)
55
+ if [[ "$file_path" =~ \.(md|markdown)$ ]]; then
56
+ exit 0 # Markdown 文件,跳过
57
+ fi
58
+
59
+ # ───────────────────────────────────────────────────────────────────────────────
60
+ # 步骤 3: 初始化缓存目录
61
+ # ───────────────────────────────────────────────────────────────────────────────
62
+ # 在项目目录下创建会话专用的缓存
63
+ # 路径:.claude/tsc-cache/{session_id}/
64
+ cache_dir="$CLAUDE_PROJECT_DIR/.claude/tsc-cache/${session_id:-default}"
65
+ mkdir -p "$cache_dir"
66
+
67
+ # ───────────────────────────────────────────────────────────────────────────────
68
+ # 函数:检测文件所属的仓库/服务
69
+ # ───────────────────────────────────────────────────────────────────────────────
70
+ #
71
+ # 【功能】
72
+ # 从文件路径推断文件属于哪个仓库/服务
73
+ #
74
+ # 【支持的结构】
75
+ # - 单体应用:frontend/、backend/、src/
76
+ # - Monorepo:packages/xxx/、services/xxx/
77
+ # - 特殊目录:database/、prisma/、examples/
78
+ #
79
+ # 【设计原则】
80
+ # - 自适应:自动检测项目结构
81
+ # - 模式匹配:支持常见的目录命名约定
82
+ # - 可扩展:易于添加新的目录模式
83
+ #
84
+ detect_repo() {
85
+ local file="$1"
86
+ local project_root="$CLAUDE_PROJECT_DIR"
87
+
88
+ # 计算相对路径(去除项目根目录前缀)
89
+ local relative_path="${file#$project_root/}"
90
+
91
+ # 提取第一级目录(通常是仓库/服务名)
92
+ local repo=$(echo "$relative_path" | cut -d'/' -f1)
93
+
94
+ # ┌───────────────────────────────────────────────────────────────┐
95
+ # │ 模式匹配:识别常见的项目结构 │
96
+ # └───────────────────────────────────────────────────────────────┘
97
+ case "$repo" in
98
+ # 前端变体
99
+ frontend|client|web|app|ui)
100
+ echo "$repo"
101
+ ;;
102
+ # 后端变体
103
+ backend|server|api|src|services)
104
+ echo "$repo"
105
+ ;;
106
+ # 数据库
107
+ database|prisma|migrations)
108
+ echo "$repo"
109
+ ;;
110
+ # Monorepo 结构:packages/xxx
111
+ packages)
112
+ # 提取包名(第二级目录)
113
+ local package=$(echo "$relative_path" | cut -d'/' -f2)
114
+ if [[ -n "$package" ]]; then
115
+ echo "packages/$package"
116
+ else
117
+ echo "$repo"
118
+ fi
119
+ ;;
120
+ # 示例目录:examples/xxx
121
+ examples)
122
+ local example=$(echo "$relative_path" | cut -d'/' -f2)
123
+ if [[ -n "$example" ]]; then
124
+ echo "examples/$example"
125
+ else
126
+ echo "$repo"
127
+ fi
128
+ ;;
129
+ *)
130
+ # 检查是否是根目录下的文件
131
+ if [[ ! "$relative_path" =~ / ]]; then
132
+ echo "root"
133
+ else
134
+ echo "unknown"
135
+ fi
136
+ ;;
137
+ esac
138
+ }
139
+
140
+ # ───────────────────────────────────────────────────────────────────────────────
141
+ # 函数:获取构建命令
142
+ # ───────────────────────────────────────────────────────────────────────────────
143
+ #
144
+ # 【功能】
145
+ # 为仓库生成合适的构建命令
146
+ #
147
+ # 【检测逻辑】
148
+ # 1. 检查 package.json 中是否有 build 脚本
149
+ # 2. 自动识别包管理器(pnpm > npm > yarn)
150
+ # 3. 特殊处理:Prisma 数据库使用 prisma generate
151
+ #
152
+ get_build_command() {
153
+ local repo="$1"
154
+ local project_root="$CLAUDE_PROJECT_DIR"
155
+ local repo_path="$project_root/$repo"
156
+
157
+ # ┌───────────────────────────────────────────────────────────────┐
158
+ # │ 检查 1: package.json 中的 build 脚本 │
159
+ # └───────────────────────────────────────────────────────────────┘
160
+ if [[ -f "$repo_path/package.json" ]]; then
161
+ if grep -q '"build"' "$repo_path/package.json" 2>/dev/null; then
162
+ # 根据 lock 文件检测包管理器
163
+ if [[ -f "$repo_path/pnpm-lock.yaml" ]]; then
164
+ echo "cd $repo_path && pnpm build"
165
+ elif [[ -f "$repo_path/package-lock.json" ]]; then
166
+ echo "cd $repo_path && npm run build"
167
+ elif [[ -f "$repo_path/yarn.lock" ]]; then
168
+ echo "cd $repo_path && yarn build"
169
+ else
170
+ echo "cd $repo_path && npm run build" # 默认使用 npm
171
+ fi
172
+ return
173
+ fi
174
+ fi
175
+
176
+ # ┌───────────────────────────────────────────────────────────────┐
177
+ # │ 检查 2: Prisma 数据库特殊处理 │
178
+ # └───────────────────────────────────────────────────────────────┘
179
+ if [[ "$repo" == "database" ]] || [[ "$repo" =~ prisma ]]; then
180
+ if [[ -f "$repo_path/schema.prisma" ]] || [[ -f "$repo_path/prisma/schema.prisma" ]]; then
181
+ echo "cd $repo_path && npx prisma generate"
182
+ return
183
+ fi
184
+ fi
185
+
186
+ # 没有找到构建命令
187
+ echo ""
188
+ }
189
+
190
+ # ───────────────────────────────────────────────────────────────────────────────
191
+ # 函数:获取 TypeScript 检查命令
192
+ # ───────────────────────────────────────────────────────────────────────────────
193
+ #
194
+ # 【功能】
195
+ # 为仓库生成 TypeScript 类型检查命令
196
+ #
197
+ # 【检测逻辑】
198
+ # 1. 检查是否存在 tsconfig.json
199
+ # 2. Vite/React 项目特殊处理(使用 tsconfig.app.json)
200
+ # 3. 使用 --noEmit 只检查类型,不生成文件
201
+ #
202
+ get_tsc_command() {
203
+ local repo="$1"
204
+ local project_root="$CLAUDE_PROJECT_DIR"
205
+ local repo_path="$project_root/$repo"
206
+
207
+ # 检查 TypeScript 配置文件
208
+ if [[ -f "$repo_path/tsconfig.json" ]]; then
209
+ # Vite/React 项目通常有独立的 tsconfig.app.json
210
+ if [[ -f "$repo_path/tsconfig.app.json" ]]; then
211
+ echo "cd $repo_path && npx tsc --project tsconfig.app.json --noEmit"
212
+ else
213
+ echo "cd $repo_path && npx tsc --noEmit"
214
+ fi
215
+ return
216
+ fi
217
+
218
+ # 没有找到 TypeScript 配置
219
+ echo ""
220
+ }
221
+
222
+ # ───────────────────────────────────────────────────────────────────────────────
223
+ # 步骤 4: 执行仓库检测
224
+ # ───────────────────────────────────────────────────────────────────────────────
225
+ repo=$(detect_repo "$file_path")
226
+
227
+ # 如果检测失败(unknown 或为空),跳过
228
+ if [[ "$repo" == "unknown" ]] || [[ -z "$repo" ]]; then
229
+ exit 0
230
+ fi
231
+
232
+ # ───────────────────────────────────────────────────────────────────────────────
233
+ # 步骤 5: 记录信息到缓存
234
+ # ───────────────────────────────────────────────────────────────────────────────
235
+
236
+ # ┌───────────────────────────────────────────────────────────────┐
237
+ # │ 5.1 记录编辑的文件(带时间戳) │
238
+ # └───────────────────────────────────────────────────────────────┘
239
+ # 格式:timestamp\ttool\tfile_path\trepo
240
+ # 使用 tab 分隔以便其他 hooks(如 error-handling-reminder)正确解析
241
+ echo -e "$(date +%s)\t$tool_name\t$file_path\t$repo" >> "$cache_dir/edited-files.log"
242
+
243
+ # ┌───────────────────────────────────────────────────────────────┐
244
+ # │ 5.2 更新受影响的仓库列表(去重) │
245
+ # └───────────────────────────────────────────────────────────────┘
246
+ if ! grep -q "^$repo$" "$cache_dir/affected-repos.txt" 2>/dev/null; then
247
+ echo "$repo" >> "$cache_dir/affected-repos.txt"
248
+ fi
249
+
250
+ # ┌───────────────────────────────────────────────────────────────┐
251
+ # │ 5.3 生成并存储构建/类型检查命令 │
252
+ # └───────────────────────────────────────────────────────────────┘
253
+ build_cmd=$(get_build_command "$repo")
254
+ tsc_cmd=$(get_tsc_command "$repo")
255
+
256
+ # 存储构建命令
257
+ if [[ -n "$build_cmd" ]]; then
258
+ echo "$repo:build:$build_cmd" >> "$cache_dir/commands.txt.tmp"
259
+ fi
260
+
261
+ # 存储类型检查命令
262
+ if [[ -n "$tsc_cmd" ]]; then
263
+ echo "$repo:tsc:$tsc_cmd" >> "$cache_dir/commands.txt.tmp"
264
+ fi
265
+
266
+ # ┌───────────────────────────────────────────────────────────────┐
267
+ # │ 5.4 去重并整理命令列表 │
268
+ # └───────────────────────────────────────────────────────────────┘
269
+ if [[ -f "$cache_dir/commands.txt.tmp" ]]; then
270
+ # sort -u: 排序并去重
271
+ sort -u "$cache_dir/commands.txt.tmp" > "$cache_dir/commands.txt"
272
+ rm -f "$cache_dir/commands.txt.tmp"
273
+ fi
274
+
275
+ # ───────────────────────────────────────────────────────────────────────────────
276
+ # 步骤 6: 正常退出
277
+ # ───────────────────────────────────────────────────────────────────────────────
278
+ # exit 0 表示 hook 成功执行
279
+ # Claude Code 会继续正常流程
280
+ exit 0
@@ -0,0 +1,36 @@
1
+ #!/bin/bash
2
+
3
+ # ═══════════════════════════════════════════════════════════════════════════════
4
+ # 工具使用前置守卫 Hook - Bash 包装器
5
+ # ═══════════════════════════════════════════════════════════════════════════════
6
+ #
7
+ # 【核心功能】
8
+ # Bash 脚本包装器,调用 TypeScript 实现的工具使用前置守卫逻辑
9
+ #
10
+ # 【工作原理】
11
+ # 1. 切换到 hooks 目录
12
+ # 2. 通过管道传递 stdin 到 TypeScript 脚本
13
+ # 3. 使用 npx tsx 运行 TypeScript(无需编译)
14
+ #
15
+ # 【为什么需要这个包装器?】
16
+ # - Claude Code hooks 必须是可执行的 shell 脚本
17
+ # - 此脚本负责设置环境并调用实际的 TypeScript 实现
18
+ #
19
+ # 【设计原则】
20
+ # KISS - Keep It Simple, Stupid
21
+ # 包装器只做一件事:调用 TypeScript 实现
22
+ #
23
+ # ═══════════════════════════════════════════════════════════════════════════════
24
+
25
+ # 错误时立即退出
26
+ set -e
27
+
28
+ # 切换到 hooks 目录(确保能找到 TypeScript 文件)
29
+ cd "$CLAUDE_PROJECT_DIR/.claude/hooks"
30
+
31
+ # 通过管道传递 stdin 到 TypeScript 脚本
32
+ # - cat: 读取 stdin
33
+ # - |: 管道传递
34
+ # - npx tsx: 直接运行 TypeScript(无需 tsc 编译)
35
+ # - pre-tool-use-guardrail.ts: TypeScript 实现
36
+ cat | npx tsx pre-tool-use-guardrail.ts
@@ -0,0 +1,342 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ==============================================
4
+ * PreToolUse Hook: Guardrail Enforcer
5
+ * ==============================================
6
+ *
7
+ * 功能: 在文件编辑前检查 Guardrail Skills 的触发条件
8
+ * 如果匹配到 guardrail 且 enforcement="block",返回 exit code 2 阻止操作
9
+ *
10
+ * 集成: devflow-tdd-enforcer, constitution-guardian
11
+ *
12
+ * Exit Codes:
13
+ * 0 - Allow (no violations)
14
+ * 2 - Block (guardrail violation detected)
15
+ * 1 - Error (unexpected failure)
16
+ */
17
+
18
+ import { readFileSync, existsSync } from 'fs';
19
+ import { join } from 'path';
20
+
21
+ // ============================================================================
22
+ // 📋 Type Definitions
23
+ // ============================================================================
24
+
25
+ interface HookInput {
26
+ session_id: string;
27
+ file_path: string;
28
+ content: string;
29
+ tool_name: string;
30
+ }
31
+
32
+ interface FileTriggers {
33
+ pathPatterns?: string[];
34
+ contentPatterns?: string[];
35
+ }
36
+
37
+ interface SkipConditions {
38
+ sessionSkillUsed?: boolean;
39
+ fileMarkers?: string[];
40
+ envOverride?: string;
41
+ }
42
+
43
+ interface GuardrailSkill {
44
+ type: 'guardrail';
45
+ enforcement: 'block' | 'suggest' | 'warn';
46
+ priority: 'critical' | 'high' | 'medium' | 'low';
47
+ fileTriggers?: FileTriggers;
48
+ blockMessage?: string;
49
+ skipConditions?: SkipConditions;
50
+ }
51
+
52
+ interface SkillRules {
53
+ version: string;
54
+ skills: Record<string, GuardrailSkill>;
55
+ }
56
+
57
+ interface Violation {
58
+ skillName: string;
59
+ matchType: 'pathPattern' | 'contentPattern';
60
+ matchedPattern: string;
61
+ line?: number;
62
+ }
63
+
64
+ // ============================================================================
65
+ // 📦 Helper Functions
66
+ // ============================================================================
67
+
68
+ /**
69
+ * 归一化文件路径为项目相对路径
70
+ *
71
+ * 【问题】
72
+ * hook payload 传递的是绝对路径,但 skill-rules.json 中的 glob 是相对路径
73
+ * 直接比较会导致永不匹配
74
+ *
75
+ * 【解决】
76
+ * 将绝对路径转为相对于项目根目录的路径
77
+ *
78
+ * @example
79
+ * normalizeFilePath('/Users/dimon/cc-devflow/devflow/requirements/REQ-001/TASKS.md', '/Users/dimon/cc-devflow')
80
+ * // → 'devflow/requirements/REQ-001/TASKS.md'
81
+ */
82
+ function normalizeFilePath(filePath: string, projectRoot: string): string {
83
+ // 如果是绝对路径且在项目内,转为相对路径
84
+ if (filePath.startsWith(projectRoot)) {
85
+ const relative = filePath.slice(projectRoot.length);
86
+ // 去除前导斜杠
87
+ return relative.startsWith('/') ? relative.slice(1) : relative;
88
+ }
89
+ // 已经是相对路径或不在项目内
90
+ return filePath;
91
+ }
92
+
93
+ /**
94
+ * 检查文件路径是否匹配 glob 模式
95
+ *
96
+ * 【修复】
97
+ * 1. 先转义正则元字符(防止 . + ^ $ 等被误解释)
98
+ * 2. 再替换 glob 通配符(两个星号、一个星号、问号)
99
+ *
100
+ * 【为什么顺序重要】
101
+ * 错误顺序: "foo.md".replace(星号→点星).escape() → "foo.md" (点匹配任意字符 ❌)
102
+ * 正确顺序: "foo.md".escape().replace(星号→点星) → "foo\.md" (点匹配字面点号 ✓)
103
+ *
104
+ * @example
105
+ * matchesPath('devflow/requirements/REQ-001/TASKS.md', 'devflow/requirements/双星/TASKS.md')
106
+ * // → true
107
+ */
108
+ function matchesPath(filePath: string, pattern: string): boolean {
109
+ // Step 1: 转义所有正则特殊字符(保留 glob 通配符)
110
+ const escapeRegex = (str: string) => {
111
+ // 转义: . + ^ $ { } ( ) | [ ] \
112
+ // 不转义: * ? (因为这些是 glob 语法)
113
+ return str.replace(/[.+^${}()|[\]\\]/g, '\\$&');
114
+ };
115
+
116
+ // Step 2: 先转义,再替换 glob 通配符
117
+ // 注意:escapeRegex 不转义 *,所以字符串中的 * 仍然是 *,不是 \*
118
+ const regexPattern = escapeRegex(pattern)
119
+ .replace(/\*\*/g, '.*') // ** → .* (任意字符,包括 /)
120
+ .replace(/\*/g, '[^/]*') // * → [^/]* (任意字符,不包括 /)
121
+ .replace(/\?/g, '.'); // ? → . (单个字符)
122
+
123
+ const regex = new RegExp(`^${regexPattern}$`);
124
+ return regex.test(filePath);
125
+ }
126
+
127
+ /**
128
+ * 检查内容是否匹配正则模式,并返回匹配的行号
129
+ */
130
+ function matchesContent(content: string, pattern: string): { matched: boolean; line?: number } {
131
+ const regex = new RegExp(pattern, 'im'); // i = case-insensitive, m = multiline
132
+ const match = regex.exec(content);
133
+
134
+ if (!match) {
135
+ return { matched: false };
136
+ }
137
+
138
+ // 计算匹配位置所在的行号
139
+ const textBeforeMatch = content.substring(0, match.index);
140
+ const lineNumber = textBeforeMatch.split('\n').length;
141
+
142
+ return { matched: true, line: lineNumber };
143
+ }
144
+
145
+ /**
146
+ * 检查是否应该跳过此 guardrail
147
+ */
148
+ function shouldSkip(
149
+ skillName: string,
150
+ content: string,
151
+ skipConditions?: SkipConditions,
152
+ sessionId?: string
153
+ ): boolean {
154
+ if (!skipConditions) {
155
+ return false;
156
+ }
157
+
158
+ // Check 1: Environment variable override
159
+ if (skipConditions.envOverride && process.env[skipConditions.envOverride] === '1') {
160
+ console.error(`ℹ️ Skipping ${skillName}: ${skipConditions.envOverride}=1`);
161
+ return true;
162
+ }
163
+
164
+ // Check 2: File markers (e.g., @skip-tdd-check)
165
+ if (skipConditions.fileMarkers) {
166
+ for (const marker of skipConditions.fileMarkers) {
167
+ if (content.includes(marker)) {
168
+ console.error(`ℹ️ Skipping ${skillName}: Found marker '${marker}'`);
169
+ return true;
170
+ }
171
+ }
172
+ }
173
+
174
+ // Check 3: Session skill used (已实现完整持久化)
175
+ if (skipConditions.sessionSkillUsed && sessionId) {
176
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
177
+ const stateDir = join(projectDir, '.claude', 'hooks', 'state');
178
+ const stateFile = join(stateDir, `skills-used-${sessionId}.json`);
179
+
180
+ try {
181
+ if (existsSync(stateFile)) {
182
+ const usedSkills: string[] = JSON.parse(readFileSync(stateFile, 'utf-8'));
183
+ if (usedSkills.includes(skillName)) {
184
+ console.error(`ℹ️ Skipping ${skillName}: Already used in this session`);
185
+ return true;
186
+ }
187
+ }
188
+ } catch (error) {
189
+ // 状态文件损坏或读取失败,忽略错误(fail open)
190
+ console.error(`⚠️ Warning: Failed to read session state for ${skillName}:`, error);
191
+ }
192
+ }
193
+
194
+ return false;
195
+ }
196
+
197
+ // ============================================================================
198
+ // 🎯 Main Logic
199
+ // ============================================================================
200
+
201
+ async function main() {
202
+ try {
203
+ // ===== 1. 读取输入 =====
204
+ const input = readFileSync(0, 'utf-8');
205
+ const data: HookInput = JSON.parse(input);
206
+
207
+ const { session_id, file_path, content, tool_name } = data;
208
+
209
+ // 只在文件编辑工具时触发 (Edit, Write)
210
+ if (!['Edit', 'Write'].includes(tool_name)) {
211
+ process.exit(0); // Allow non-edit operations
212
+ }
213
+
214
+ // ===== 2. 加载 Guardrail Rules =====
215
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
216
+
217
+ // ===== 归一化文件路径 =====
218
+ // hook payload 是绝对路径,需转为相对路径才能与 skill-rules.json 中的 glob 匹配
219
+ const normalizedPath = normalizeFilePath(file_path, projectDir);
220
+ const rulesPath = join(projectDir, '.claude', 'skills', 'skill-rules.json');
221
+
222
+ if (!existsSync(rulesPath)) {
223
+ // No skill rules configured, allow all
224
+ process.exit(0);
225
+ }
226
+
227
+ const rules: SkillRules = JSON.parse(readFileSync(rulesPath, 'utf-8'));
228
+
229
+ // ===== 3. 检查所有 Guardrail Skills =====
230
+ const violations: Violation[] = [];
231
+
232
+ for (const [skillName, config] of Object.entries(rules.skills)) {
233
+ // 只处理 type="guardrail" 且 enforcement="block"
234
+ if (config.type !== 'guardrail' || config.enforcement !== 'block') {
235
+ continue;
236
+ }
237
+
238
+ // 检查是否应该跳过
239
+ if (shouldSkip(skillName, content, config.skipConditions, session_id)) {
240
+ continue;
241
+ }
242
+
243
+ const triggers = config.fileTriggers;
244
+ if (!triggers) {
245
+ continue;
246
+ }
247
+
248
+ // Check 1: Path patterns
249
+ if (triggers.pathPatterns) {
250
+ for (const pattern of triggers.pathPatterns) {
251
+ // 使用归一化后的相对路径进行匹配
252
+ if (matchesPath(normalizedPath, pattern)) {
253
+ // Path matched, now check content patterns
254
+ if (triggers.contentPatterns) {
255
+ for (const contentPattern of triggers.contentPatterns) {
256
+ const result = matchesContent(content, contentPattern);
257
+ if (result.matched) {
258
+ violations.push({
259
+ skillName,
260
+ matchType: 'contentPattern',
261
+ matchedPattern: contentPattern,
262
+ line: result.line
263
+ });
264
+ }
265
+ }
266
+ }
267
+ break; // Path matched, no need to check other path patterns
268
+ }
269
+ }
270
+ }
271
+ }
272
+
273
+ // ===== 4. 处理违规 =====
274
+ if (violations.length > 0) {
275
+ // 按 priority 排序 (critical > high > medium > low)
276
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
277
+ violations.sort((a, b) => {
278
+ const priorityA = priorityOrder[rules.skills[a.skillName].priority] || 999;
279
+ const priorityB = priorityOrder[rules.skills[b.skillName].priority] || 999;
280
+ return priorityA - priorityB;
281
+ });
282
+
283
+ // 显示第一个违规的 blockMessage
284
+ const firstViolation = violations[0];
285
+ const skill = rules.skills[firstViolation.skillName];
286
+
287
+ let blockMessage = skill.blockMessage || `⚠️ BLOCKED by ${firstViolation.skillName}`;
288
+
289
+ // 替换占位符
290
+ blockMessage = blockMessage.replace('{file_path}', file_path);
291
+
292
+ // 如果有行号,添加到消息中
293
+ if (firstViolation.line) {
294
+ blockMessage += `\n\n📍 Violation at Line ${firstViolation.line}`;
295
+ }
296
+
297
+ // 输出到 stderr (用户可见)
298
+ console.error('\n' + blockMessage + '\n');
299
+
300
+ // ===== 记录 Session 状态(用于 sessionSkillUsed 跳过条件)=====
301
+ // 在阻塞前记录,以便下次同一 session 中跳过
302
+ if (skill.skipConditions?.sessionSkillUsed) {
303
+ try {
304
+ const stateDir = join(projectDir, '.claude', 'hooks', 'state');
305
+ const stateFile = join(stateDir, `skills-used-${session_id}.json`);
306
+
307
+ // 确保目录存在
308
+ if (!existsSync(stateDir)) {
309
+ require('fs').mkdirSync(stateDir, { recursive: true });
310
+ }
311
+
312
+ // 读取现有状态
313
+ let usedSkills: string[] = [];
314
+ if (existsSync(stateFile)) {
315
+ usedSkills = JSON.parse(readFileSync(stateFile, 'utf-8'));
316
+ }
317
+
318
+ // 添加当前 skill(去重)
319
+ if (!usedSkills.includes(firstViolation.skillName)) {
320
+ usedSkills.push(firstViolation.skillName);
321
+ require('fs').writeFileSync(stateFile, JSON.stringify(usedSkills, null, 2), 'utf-8');
322
+ }
323
+ } catch (error) {
324
+ // 状态写入失败不应影响阻塞(fail closed)
325
+ console.error(`⚠️ Warning: Failed to write session state:`, error);
326
+ }
327
+ }
328
+
329
+ // 返回 exit code 2 (阻塞)
330
+ process.exit(2);
331
+ }
332
+
333
+ // ===== 5. 无违规,允许操作 =====
334
+ process.exit(0);
335
+
336
+ } catch (error) {
337
+ console.error('❌ PreToolUse Hook Error:', error);
338
+ process.exit(1); // Error, but don't block (fail open)
339
+ }
340
+ }
341
+
342
+ main();