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,570 @@
1
+ #!/usr/bin/env bash
2
+ # Common functions and variables for cc-devflow scripts
3
+ # Based on spec-kit principles with cc-devflow enhancements
4
+
5
+ # =============================================================================
6
+ # Beijing Time Functions (统一北京时间格式)
7
+ # =============================================================================
8
+
9
+ # Get current Beijing time in standard format (YYYY-MM-DD HH:MM:SS)
10
+ # Returns: 2025-01-10 14:30:25 (周五)
11
+ get_beijing_time() {
12
+ # Convert UTC to Beijing time (UTC+8)
13
+ TZ='Asia/Shanghai' date '+%Y-%m-%d %H:%M:%S (%a)'
14
+ }
15
+
16
+ # Get Beijing time in ISO format (for JSON/log files)
17
+ # Returns: 2025-01-10T14:30:25+08:00
18
+ get_beijing_time_iso() {
19
+ TZ='Asia/Shanghai' date '+%Y-%m-%dT%H:%M:%S+08:00'
20
+ }
21
+
22
+ # Get Beijing time with Chinese day of week
23
+ # Returns: 2025年1月10日 星期五 14:30:25
24
+ get_beijing_time_full() {
25
+ TZ='Asia/Shanghai' date '+%Y年%m月%d日 星期%u %H:%M:%S' | sed 's/星期1/星期一/' | sed 's/星期2/星期二/' | sed 's/星期3/星期三/' | sed 's/星期4/星期四/' | sed 's/星期5/星期五/' | sed 's/星期6/星期六/' | sed 's/星期7/星期日/'
26
+ }
27
+
28
+ # Get repository root, with fallback for non-git repositories
29
+ get_repo_root() {
30
+ if git rev-parse --show-toplevel >/dev/null 2>&1; then
31
+ git rev-parse --show-toplevel
32
+ else
33
+ # Fall back to script location for non-git repos
34
+ local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
35
+ (cd "$script_dir/../.." && pwd)
36
+ fi
37
+ }
38
+
39
+ # Get current requirement ID from branch or environment
40
+ # Returns: REQ-XXX or BUG-XXX format
41
+ get_current_req_id() {
42
+ # First check if DEVFLOW_REQ_ID environment variable is set
43
+ if [[ -n "${DEVFLOW_REQ_ID:-}" ]]; then
44
+ echo "$DEVFLOW_REQ_ID"
45
+ return
46
+ fi
47
+
48
+ # Then check git branch if available
49
+ if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then
50
+ local branch=$(git rev-parse --abbrev-ref HEAD)
51
+ # Extract REQ-XXX or BUG-XXX from branch name like feature/REQ-123-title or feature/REQ-20251006-001-title
52
+ # Support formats: REQ-123, REQ-20251006-001, BUG-456, etc.
53
+ if [[ "$branch" =~ (REQ-[0-9]+-[0-9]+|REQ-[0-9]+|BUG-[0-9]+-[0-9]+|BUG-[0-9]+) ]]; then
54
+ echo "${BASH_REMATCH[1]}"
55
+ return
56
+ fi
57
+ fi
58
+
59
+ # For non-git repos, try to find the latest requirement directory
60
+ local repo_root=$(get_repo_root)
61
+ local req_dir="$repo_root/devflow/requirements"
62
+
63
+ if [[ -d "$req_dir" ]]; then
64
+ local latest_req=""
65
+ local highest=0
66
+
67
+ for dir in "$req_dir"/REQ-* "$req_dir"/BUG-*; do
68
+ if [[ -d "$dir" ]]; then
69
+ local dirname=$(basename "$dir")
70
+ # Match full requirement ID including extended formats like REQ-20251006-001
71
+ if [[ "$dirname" =~ ^(REQ-[0-9]+-[0-9]+|REQ-[0-9]+|BUG-[0-9]+-[0-9]+|BUG-[0-9]+) ]]; then
72
+ local full_id="${BASH_REMATCH[1]}"
73
+ # Extract the first number for comparison
74
+ if [[ "$full_id" =~ (REQ|BUG)-([0-9]+) ]]; then
75
+ local prefix="${BASH_REMATCH[1]}"
76
+ local number="${BASH_REMATCH[2]}"
77
+ number=$((10#$number))
78
+ if [[ "$number" -gt "$highest" ]]; then
79
+ highest=$number
80
+ latest_req="$full_id"
81
+ fi
82
+ fi
83
+ fi
84
+ fi
85
+ done
86
+
87
+ if [[ -n "$latest_req" ]]; then
88
+ echo "$latest_req"
89
+ return
90
+ fi
91
+ fi
92
+
93
+ echo "" # No requirement ID found
94
+ }
95
+
96
+ next_available_req_id() {
97
+ local repo_root="${1:-$(get_repo_root)}"
98
+ ensure_devflow_dir "$repo_root"
99
+ local requirements_dir="$repo_root/devflow/requirements"
100
+ local used_numbers="" width=3 candidate=1
101
+ local nullglob_state
102
+ nullglob_state=$(shopt -p nullglob)
103
+ shopt -s nullglob
104
+ for path in "$requirements_dir"/REQ-*; do
105
+ [[ -d "$path" ]] || continue
106
+ local name=$(basename "$path")
107
+ [[ "$name" =~ ^REQ-([0-9]+)$ ]] || continue
108
+ local value=$((10#${BASH_REMATCH[1]}))
109
+ used_numbers+=" $value"
110
+ done
111
+ eval "$nullglob_state"
112
+ while [[ " $used_numbers " == *" $candidate "* ]]; do
113
+ candidate=$((candidate + 1))
114
+ done
115
+ printf "REQ-%0*d\n" "$width" "$candidate"
116
+ }
117
+
118
+ req_id_in_use() {
119
+ local repo_root="$1"
120
+ local req_id="$2"
121
+ local req_dir
122
+ req_dir=$(get_req_dir "$repo_root" "$req_id")
123
+ [[ -d "$req_dir" ]]
124
+ }
125
+
126
+ # Check if we have git available
127
+ has_git() {
128
+ git rev-parse --show-toplevel >/dev/null 2>&1
129
+ }
130
+
131
+ # Validate requirement ID format
132
+ # Args: $1 - requirement ID to validate
133
+ # Returns: 0 if valid, 1 if invalid
134
+ validate_req_id() {
135
+ local req_id="$1"
136
+
137
+ if [[ -z "$req_id" ]]; then
138
+ echo "ERROR: Requirement ID cannot be empty" >&2
139
+ return 1
140
+ fi
141
+
142
+ # Support both simple (REQ-123) and extended (REQ-20251006-001) formats
143
+ if [[ ! "$req_id" =~ ^(REQ|BUG)-[0-9]+(-[0-9]+)?$ ]]; then
144
+ echo "ERROR: Invalid requirement ID format: $req_id" >&2
145
+ echo "Expected format: REQ-XXX or REQ-YYYYMMDD-XXX (e.g., REQ-123 or REQ-20251006-001)" >&2
146
+ return 1
147
+ fi
148
+
149
+ return 0
150
+ }
151
+
152
+ # Get requirement type from ID
153
+ # Args: $1 - requirement ID
154
+ # Returns: "requirement" or "bug"
155
+ get_req_type() {
156
+ local req_id="$1"
157
+ if [[ "$req_id" =~ ^REQ- ]]; then
158
+ echo "requirement"
159
+ elif [[ "$req_id" =~ ^BUG- ]]; then
160
+ echo "bug"
161
+ else
162
+ echo "unknown"
163
+ fi
164
+ }
165
+
166
+ # Ensure devflow directory structure exists
167
+ # Args: $1 - repo root
168
+ ensure_devflow_dir() {
169
+ local repo_root="$1"
170
+ local devflow_dir="$repo_root/devflow"
171
+
172
+ mkdir -p "$devflow_dir/requirements"
173
+ mkdir -p "$devflow_dir/bugs"
174
+ }
175
+
176
+ # =============================================================================
177
+ # Chinese to Pinyin Conversion (REQ-003)
178
+ # =============================================================================
179
+
180
+ # Internal helper: Convert Chinese characters to pinyin using pypinyin
181
+ # Args: $1 - input string containing Chinese characters
182
+ # Returns: string with Chinese replaced by pinyin, other chars unchanged
183
+ # Side effects: Outputs warning to stderr if pypinyin not installed
184
+ _chinese_to_pinyin() {
185
+ local input="$1"
186
+
187
+ # Check pypinyin availability
188
+ if ! python3 -c "import pypinyin" 2>/dev/null; then
189
+ echo "Warning: pypinyin not installed. Chinese characters cannot be converted." >&2
190
+ echo "Install: pip install pypinyin" >&2
191
+ echo "$input"
192
+ return
193
+ fi
194
+
195
+ # Convert Chinese to pinyin, preserve other characters
196
+ # Use lazy_pinyin on full text for better polyphone handling (phrase-aware)
197
+ python3 -c "
198
+ from pypinyin import lazy_pinyin
199
+ import sys
200
+ import re
201
+
202
+ text = sys.argv[1]
203
+
204
+ # Extract consecutive Chinese chunks and convert them together
205
+ # This allows pypinyin to use phrase dictionary for polyphones
206
+ result = []
207
+ chinese_buffer = ''
208
+ non_chinese_buffer = ''
209
+
210
+ for char in text:
211
+ if re.match(r'[\u4e00-\u9fff]', char):
212
+ if non_chinese_buffer:
213
+ result.append(non_chinese_buffer)
214
+ non_chinese_buffer = ''
215
+ chinese_buffer += char
216
+ else:
217
+ if chinese_buffer:
218
+ # Convert accumulated Chinese as a phrase, space-separated
219
+ pinyin_list = lazy_pinyin(chinese_buffer)
220
+ result.append(' '.join(pinyin_list))
221
+ chinese_buffer = ''
222
+ non_chinese_buffer += char
223
+
224
+ # Flush remaining buffers
225
+ if chinese_buffer:
226
+ pinyin_list = lazy_pinyin(chinese_buffer)
227
+ result.append(' '.join(pinyin_list))
228
+ if non_chinese_buffer:
229
+ result.append(non_chinese_buffer)
230
+
231
+ # Join with space only between Chinese pinyin blocks and other content
232
+ print(' '.join(result))
233
+ " "$input"
234
+ }
235
+
236
+ # Convert arbitrary text to a lowercase slug (branch/task naming helper)
237
+ # Examples:
238
+ # slugify "Add User Login" => "add-user-login"
239
+ # slugify "SAML/OIDC" => "saml-oidc"
240
+ # slugify "用户登录" => "yong-hu-deng-lu" (REQ-003: Chinese to pinyin)
241
+ slugify() {
242
+ local input="$1"
243
+ if [[ -z "$input" ]]; then
244
+ echo ""
245
+ return
246
+ fi
247
+
248
+ local result="$input"
249
+
250
+ # REQ-003: Check for Chinese characters (Unicode range \u4e00-\u9fff)
251
+ # Use Python for cross-platform compatibility (macOS grep lacks -P)
252
+ if python3 -c "import sys,re; sys.exit(0 if re.search(r'[\u4e00-\u9fff]', sys.argv[1]) else 1)" "$input" 2>/dev/null; then
253
+ result=$(_chinese_to_pinyin "$input")
254
+ fi
255
+
256
+ # Convert to lowercase and replace non-alphanumeric characters with hyphen
257
+ local slug
258
+ slug=$(printf '%s' "$result" | tr '[:upper:]' '[:lower:]')
259
+ slug=$(printf '%s' "$slug" | sed 's/[^a-z0-9]/-/g')
260
+ # Collapse repeated hyphens and trim edges
261
+ slug=$(echo "$slug" | sed 's/-\{2,\}/-/g; s/^-//; s/-$//')
262
+
263
+ echo "$slug"
264
+ }
265
+
266
+ # =============================================================================
267
+ # JSON Schema 校验辅助 (轻量实现 draft-07 子集)
268
+ # =============================================================================
269
+ validate_json_schema() {
270
+ local json_path="$1"
271
+ local schema_path="$2"
272
+
273
+ python3 - "$json_path" "$schema_path" <<'PY'
274
+ import json
275
+ import sys
276
+ from pathlib import Path
277
+
278
+ def fail(message: str) -> None:
279
+ print(f"ERROR: {message}", file=sys.stderr)
280
+ sys.exit(1)
281
+
282
+ def ensure_type(target, expected: str, path: str) -> None:
283
+ mapping = {"object": dict, "array": list, "string": str}
284
+ python_type = mapping.get(expected)
285
+ if python_type is None:
286
+ return
287
+ if not isinstance(target, python_type):
288
+ fail(f"{path} must be {expected}")
289
+
290
+ def validate_array(node, schema, path):
291
+ if "minItems" in schema and len(node) < schema["minItems"]:
292
+ fail(f"{path} must contain at least {schema['minItems']} items")
293
+ if schema.get("uniqueItems") and len(set(map(json.dumps, node))) != len(node):
294
+ fail(f"{path} must contain unique items")
295
+ item_schema = schema.get("items")
296
+ if item_schema:
297
+ for idx, item in enumerate(node):
298
+ validate_node(item, item_schema, f"{path}[{idx}]")
299
+
300
+ def validate_object(node, schema, path):
301
+ properties = schema.get("properties", {})
302
+ required = schema.get("required", [])
303
+ additional = schema.get("additionalProperties", True)
304
+ for key in required:
305
+ if key not in node:
306
+ fail(f"{path}.{key} is required")
307
+ for key, value in node.items():
308
+ if key in properties:
309
+ validate_node(value, properties[key], f"{path}.{key}")
310
+ elif additional is False:
311
+ fail(f"{path}.{key} is not allowed")
312
+
313
+ def validate_node(node, schema, path: str) -> None:
314
+ node_type = schema.get("type")
315
+ if node_type:
316
+ ensure_type(node, node_type, path)
317
+ if "enum" in schema and node not in schema["enum"]:
318
+ fail(f"{path} must be one of {schema['enum']}")
319
+ if isinstance(node, str) and "minLength" in schema and len(node) < schema["minLength"]:
320
+ fail(f"{path} length must be >= {schema['minLength']}")
321
+ if isinstance(node, list):
322
+ validate_array(node, schema, path)
323
+ if isinstance(node, dict):
324
+ validate_object(node, schema, path)
325
+
326
+ json_path = Path(sys.argv[1])
327
+ schema_path = Path(sys.argv[2])
328
+
329
+ try:
330
+ data = json.loads(json_path.read_text(encoding="utf-8"))
331
+ except Exception as exc:
332
+ fail(f"Failed to read {json_path}: {exc}")
333
+
334
+ try:
335
+ schema = json.loads(schema_path.read_text(encoding="utf-8"))
336
+ except Exception as exc:
337
+ fail(f"Failed to read schema {schema_path}: {exc}")
338
+
339
+ validate_node(data, schema, "$")
340
+ PY
341
+ }
342
+
343
+ # Get requirement directory path
344
+ # Args: $1 - repo root, $2 - requirement ID
345
+ get_req_dir() {
346
+ local repo_root="$1"
347
+ local req_id="$2"
348
+ local req_type=$(get_req_type "$req_id")
349
+
350
+ # Ensure devflow directory exists
351
+ ensure_devflow_dir "$repo_root"
352
+
353
+ if [[ "$req_type" == "bug" ]]; then
354
+ echo "$repo_root/devflow/bugs/$req_id"
355
+ else
356
+ echo "$repo_root/devflow/requirements/$req_id"
357
+ fi
358
+ }
359
+
360
+ # Get all requirement paths for current context
361
+ # Outputs shell variables for sourcing with eval
362
+ get_requirement_paths() {
363
+ local repo_root=$(get_repo_root)
364
+ local req_id=$(get_current_req_id)
365
+ local has_git_repo="false"
366
+
367
+ if has_git; then
368
+ has_git_repo="true"
369
+ fi
370
+
371
+ local req_dir=$(get_req_dir "$repo_root" "$req_id")
372
+ local req_type=$(get_req_type "$req_id")
373
+
374
+ cat <<EOF
375
+ REPO_ROOT='$repo_root'
376
+ REQ_ID='$req_id'
377
+ REQ_TYPE='$req_type'
378
+ HAS_GIT='$has_git_repo'
379
+ REQ_DIR='$req_dir'
380
+ PRD_FILE='$req_dir/PRD.md'
381
+ EPIC_FILE='$req_dir/EPIC.md'
382
+ TASKS_DIR='$req_dir/tasks'
383
+ TASKS_FILE='$req_dir/TASKS.md'
384
+ RESEARCH_DIR='$req_dir/research'
385
+ TEST_PLAN_FILE='$req_dir/TEST_PLAN.md'
386
+ SECURITY_PLAN_FILE='$req_dir/SECURITY_PLAN.md'
387
+ TEST_REPORT_FILE='$req_dir/TEST_REPORT.md'
388
+ SECURITY_REPORT_FILE='$req_dir/SECURITY_REPORT.md'
389
+ RELEASE_PLAN_FILE='$req_dir/RELEASE_PLAN.md'
390
+ LOG_FILE='$req_dir/EXECUTION_LOG.md'
391
+ STATUS_FILE='$req_dir/orchestration_status.json'
392
+ EOF
393
+
394
+ # Add BUG-specific paths if it's a bug
395
+ if [[ "$req_type" == "bug" ]]; then
396
+ cat <<EOF
397
+ ANALYSIS_FILE='$req_dir/ANALYSIS.md'
398
+ PLAN_FILE='$req_dir/PLAN.md'
399
+ BUG_STATUS_FILE='$req_dir/status.json'
400
+ EOF
401
+ fi
402
+ }
403
+
404
+ # Check if file exists and display status
405
+ # Args: $1 - file path, $2 - display name
406
+ check_file() {
407
+ [[ -f "$1" ]] && echo " ✓ $2" || echo " ✗ $2"
408
+ }
409
+
410
+ # Check if directory exists and is not empty
411
+ # Args: $1 - directory path, $2 - display name
412
+ check_dir() {
413
+ [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"
414
+ }
415
+
416
+ # Parse JSON value from key
417
+ # Args: $1 - json string, $2 - key
418
+ # Returns: value for the key
419
+ json_get() {
420
+ local json="$1"
421
+ local key="$2"
422
+ echo "$json" | grep -o "\"$key\":\"[^\"]*\"" | cut -d'"' -f4
423
+ }
424
+
425
+ # Create requirement directory structure
426
+ # Args: $1 - requirement ID
427
+ create_req_structure() {
428
+ local req_id="$1"
429
+ local repo_root=$(get_repo_root)
430
+ local req_dir=$(get_req_dir "$repo_root" "$req_id")
431
+
432
+ mkdir -p "$req_dir"/{research,tasks}
433
+
434
+ # Initialize empty log file
435
+ if [[ ! -f "$req_dir/EXECUTION_LOG.md" ]]; then
436
+ cat > "$req_dir/EXECUTION_LOG.md" <<EOF
437
+ # Execution Log: $req_id
438
+
439
+ ## Created
440
+ $(get_beijing_time_full)
441
+
442
+ ## Events
443
+ EOF
444
+ fi
445
+
446
+ # Initialize status file
447
+ if [[ ! -f "$req_dir/orchestration_status.json" ]]; then
448
+ cat > "$req_dir/orchestration_status.json" <<EOF
449
+ {
450
+ "reqId": "$req_id",
451
+ "status": "initialized",
452
+ "phase": "planning",
453
+ "createdAt": "$(get_beijing_time_iso)",
454
+ "updatedAt": "$(get_beijing_time_iso)"
455
+ }
456
+ EOF
457
+ fi
458
+
459
+ echo "$req_dir"
460
+ }
461
+
462
+ # Log event to execution log
463
+ # Args: $1 - requirement ID, $2 - event message
464
+ log_event() {
465
+ local req_id="$1"
466
+ local message="$2"
467
+ local repo_root=$(get_repo_root)
468
+ local req_dir=$(get_req_dir "$repo_root" "$req_id")
469
+ local log_file="$req_dir/EXECUTION_LOG.md"
470
+
471
+ if [[ -f "$log_file" ]]; then
472
+ echo "" >> "$log_file"
473
+ echo "### $(get_beijing_time)" >> "$log_file"
474
+ echo "$message" >> "$log_file"
475
+ fi
476
+ }
477
+
478
+ # =============================================================================
479
+ # Archive Functions (归档功能)
480
+ # =============================================================================
481
+
482
+ # Get archive directory path (按年月组织)
483
+ # Args: $1 - repo root, $2 - year-month (YYYY-MM, optional, defaults to current)
484
+ # Returns: archive directory path
485
+ get_archive_dir() {
486
+ local repo_root="$1"
487
+ local year_month="${2:-$(TZ='Asia/Shanghai' date '+%Y-%m')}"
488
+ echo "$repo_root/devflow/archive/$year_month"
489
+ }
490
+
491
+ # Check if requirement is archived
492
+ # Args: $1 - repo root, $2 - requirement ID
493
+ # Returns: 0 if archived, 1 if not
494
+ is_archived() {
495
+ local repo_root="$1"
496
+ local req_id="$2"
497
+ local archive_base="$repo_root/devflow/archive"
498
+
499
+ # 搜索所有归档月份目录
500
+ if [[ -d "$archive_base" ]]; then
501
+ for month_dir in "$archive_base"/*/; do
502
+ if [[ -d "${month_dir}${req_id}" ]]; then
503
+ return 0
504
+ fi
505
+ done
506
+ fi
507
+ return 1
508
+ }
509
+
510
+ # Find archived requirement location
511
+ # Args: $1 - repo root, $2 - requirement ID
512
+ # Returns: full path to archived requirement, or empty if not found
513
+ find_archived_req() {
514
+ local repo_root="$1"
515
+ local req_id="$2"
516
+ local archive_base="$repo_root/devflow/archive"
517
+
518
+ if [[ -d "$archive_base" ]]; then
519
+ for month_dir in "$archive_base"/*/; do
520
+ if [[ -d "${month_dir}${req_id}" ]]; then
521
+ echo "${month_dir}${req_id}"
522
+ return 0
523
+ fi
524
+ done
525
+ fi
526
+ echo ""
527
+ return 1
528
+ }
529
+
530
+ # List all archived requirements
531
+ # Args: $1 - repo root
532
+ # Returns: list of archived requirement IDs with their archive dates
533
+ list_archived_reqs() {
534
+ local repo_root="$1"
535
+ local archive_base="$repo_root/devflow/archive"
536
+
537
+ if [[ ! -d "$archive_base" ]]; then
538
+ echo "No archived requirements found."
539
+ return
540
+ fi
541
+
542
+ local found=false
543
+ for month_dir in "$archive_base"/*/; do
544
+ [[ -d "$month_dir" ]] || continue
545
+ local month=$(basename "$month_dir")
546
+ for req_dir in "$month_dir"*/; do
547
+ [[ -d "$req_dir" ]] || continue
548
+ local req_id=$(basename "$req_dir")
549
+ # 读取归档原因
550
+ local status_file="$req_dir/orchestration_status.json"
551
+ local reason="unknown"
552
+ if [[ -f "$status_file" ]]; then
553
+ reason=$(jq -r '.archivedReason // "completed"' "$status_file" 2>/dev/null)
554
+ fi
555
+ echo "$month | $req_id | $reason"
556
+ found=true
557
+ done
558
+ done
559
+
560
+ if [[ "$found" == "false" ]]; then
561
+ echo "No archived requirements found."
562
+ fi
563
+ }
564
+
565
+ # Color output helpers
566
+ color_red() { echo -e "\033[0;31m$1\033[0m"; }
567
+ color_green() { echo -e "\033[0;32m$1\033[0m"; }
568
+ color_yellow() { echo -e "\033[0;33m$1\033[0m"; }
569
+ color_blue() { echo -e "\033[0;34m$1\033[0m"; }
570
+ color_bold() { echo -e "\033[1m$1\033[0m"; }