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.
- package/.claude/CLAUDE.md +83 -0
- package/.claude/agents/architecture-designer.md +443 -0
- package/.claude/agents/bug-analyzer.md +382 -0
- package/.claude/agents/checklist-agent.md +175 -0
- package/.claude/agents/clarify-analyst.md +50 -0
- package/.claude/agents/code-reviewer.md +71 -0
- package/.claude/agents/codex-analyzer.md +39 -0
- package/.claude/agents/compatibility-checker.md +580 -0
- package/.claude/agents/consistency-checker.md +532 -0
- package/.claude/agents/impact-analyzer.md +441 -0
- package/.claude/agents/planner.md +230 -0
- package/.claude/agents/prd-writer.md +320 -0
- package/.claude/agents/project-guidelines-generator.md +1329 -0
- package/.claude/agents/qa-tester.md +313 -0
- package/.claude/agents/release-manager.md +295 -0
- package/.claude/agents/security-reviewer.md +314 -0
- package/.claude/agents/style-guide-generator.md +458 -0
- package/.claude/agents/tech-architect.md +516 -0
- package/.claude/agents/ui-designer.md +485 -0
- package/.claude/commands/code-review-high.md +58 -0
- package/.claude/commands/core-architecture.md +429 -0
- package/.claude/commands/core-guidelines.md +486 -0
- package/.claude/commands/core-roadmap.md +439 -0
- package/.claude/commands/core-style.md +293 -0
- package/.claude/commands/flow-archive.md +245 -0
- package/.claude/commands/flow-checklist.md +260 -0
- package/.claude/commands/flow-clarify.md +136 -0
- package/.claude/commands/flow-constitution.md +82 -0
- package/.claude/commands/flow-dev.md +134 -0
- package/.claude/commands/flow-epic.md +150 -0
- package/.claude/commands/flow-fix.md +104 -0
- package/.claude/commands/flow-ideate.md +214 -0
- package/.claude/commands/flow-init.md +313 -0
- package/.claude/commands/flow-new.md +394 -0
- package/.claude/commands/flow-prd.md +131 -0
- package/.claude/commands/flow-qa.md +93 -0
- package/.claude/commands/flow-release.md +92 -0
- package/.claude/commands/flow-restart.md +98 -0
- package/.claude/commands/flow-status.md +64 -0
- package/.claude/commands/flow-tech.md +142 -0
- package/.claude/commands/flow-ui.md +189 -0
- package/.claude/commands/flow-update.md +111 -0
- package/.claude/commands/flow-upgrade.md +115 -0
- package/.claude/commands/flow-verify.md +96 -0
- package/.claude/commands/problem-analyzer.md +60 -0
- package/.claude/config/quality-rules.yml +161 -0
- package/.claude/docs/SPEC_KIT_CONSTITUTION_ANALYSIS.md +426 -0
- package/.claude/docs/design/consistency-conflict-detection-algorithms.md +658 -0
- package/.claude/docs/design/intent-driven-input-design.md +380 -0
- package/.claude/docs/design/prd-version-management-design.md +437 -0
- package/.claude/docs/guides/INIT_TROUBLESHOOTING.md +117 -0
- package/.claude/docs/guides/NEW_TROUBLESHOOTING.md +151 -0
- package/.claude/docs/guides/ROADMAP_TROUBLESHOOTING.md +188 -0
- package/.claude/docs/guides/TASK_COMPLETION_MARKING.md +338 -0
- package/.claude/docs/templates/ARCHITECTURE_TEMPLATE.md +633 -0
- package/.claude/docs/templates/BACKLOG_TEMPLATE.md +261 -0
- package/.claude/docs/templates/CHECKLIST_TEMPLATE.md +52 -0
- package/.claude/docs/templates/CLARIFICATION_REPORT_TEMPLATE.md +206 -0
- package/.claude/docs/templates/CODE_REVIEW_TEMPLATE.md +71 -0
- package/.claude/docs/templates/EPIC_TEMPLATE.md +805 -0
- package/.claude/docs/templates/INIT_FLOW_TEMPLATE.md +213 -0
- package/.claude/docs/templates/INTENT_CLARIFICATION_TEMPLATE.md +57 -0
- package/.claude/docs/templates/NEW_ORCHESTRATION_TEMPLATE.md +148 -0
- package/.claude/docs/templates/PRD_TEMPLATE.md +562 -0
- package/.claude/docs/templates/RESEARCH_TEMPLATE.md +276 -0
- package/.claude/docs/templates/REVIEW-HIGH.md +57 -0
- package/.claude/docs/templates/ROADMAP_DIALOGUE_TEMPLATE.md +198 -0
- package/.claude/docs/templates/ROADMAP_TEMPLATE.md +310 -0
- package/.claude/docs/templates/STYLE_TEMPLATE.md +1266 -0
- package/.claude/docs/templates/TASKS_TEMPLATE.md +523 -0
- package/.claude/docs/templates/TECH_DESIGN_TEMPLATE.md +1019 -0
- package/.claude/docs/templates/UI_PROTOTYPE_TEMPLATE.md +1436 -0
- package/.claude/guides/agent-guides/agent-coordination-guide.md +459 -0
- package/.claude/guides/project-guidelines-system.md +463 -0
- package/.claude/guides/technical-guides/datetime-handling-guide.md +563 -0
- package/.claude/guides/technical-guides/git-github-guide.md +642 -0
- package/.claude/guides/technical-guides/test-execution-guide.md +618 -0
- package/.claude/guides/workflow-guides/bug-fix-orchestrator.md +217 -0
- package/.claude/guides/workflow-guides/flow-orchestrator.md +282 -0
- package/.claude/hooks/checklist-gate.js +397 -0
- package/.claude/hooks/error-handling-reminder.sh +12 -0
- package/.claude/hooks/error-handling-reminder.ts +459 -0
- package/.claude/hooks/post-tool-use-tracker.sh +280 -0
- package/.claude/hooks/pre-tool-use-guardrail.sh +36 -0
- package/.claude/hooks/pre-tool-use-guardrail.ts +342 -0
- package/.claude/hooks/skill-activation-prompt.sh +36 -0
- package/.claude/hooks/skill-activation-prompt.ts +214 -0
- package/.claude/hooks/state/skills-used-test-guard.json +3 -0
- package/.claude/rules/devflow-conventions.md +305 -0
- package/.claude/rules/project-constitution.md +748 -0
- package/.claude/schemas/constitution.schema.json +43 -0
- package/.claude/scripts/analyze-upgrade-impact.sh +200 -0
- package/.claude/scripts/archive-requirement.sh +351 -0
- package/.claude/scripts/calculate-checklist-completion.sh +243 -0
- package/.claude/scripts/calculate-quarter.sh +206 -0
- package/.claude/scripts/check-dependencies.sh +409 -0
- package/.claude/scripts/check-prerequisites.sh +232 -0
- package/.claude/scripts/check-task-status.sh +264 -0
- package/.claude/scripts/checklist-errors.sh +131 -0
- package/.claude/scripts/common.sh +570 -0
- package/.claude/scripts/consolidate-research.sh +182 -0
- package/.claude/scripts/create-requirement.sh +426 -0
- package/.claude/scripts/export-contracts.sh +117 -0
- package/.claude/scripts/extract-data-model.sh +78 -0
- package/.claude/scripts/generate-clarification-questions.sh +377 -0
- package/.claude/scripts/generate-clarification-report.sh +463 -0
- package/.claude/scripts/generate-quickstart.sh +146 -0
- package/.claude/scripts/generate-research-tasks.sh +157 -0
- package/.claude/scripts/generate-status-report.sh +523 -0
- package/.claude/scripts/generate-tech-analysis.sh +46 -0
- package/.claude/scripts/locate-requirement-in-roadmap.sh +233 -0
- package/.claude/scripts/manage-constitution.sh +602 -0
- package/.claude/scripts/mark-task-complete.sh +198 -0
- package/.claude/scripts/populate-research-tasks.sh +259 -0
- package/.claude/scripts/recover-workflow.sh +460 -0
- package/.claude/scripts/run-clarify-scan.sh +601 -0
- package/.claude/scripts/run-high-review.sh +62 -0
- package/.claude/scripts/run-problem-analysis.sh +68 -0
- package/.claude/scripts/setup-epic.sh +173 -0
- package/.claude/scripts/sync-roadmap-progress.sh +300 -0
- package/.claude/scripts/sync-task-marks.sh +199 -0
- package/.claude/scripts/test-clarify-scan.sh +515 -0
- package/.claude/scripts/update-agent-context.sh +806 -0
- package/.claude/scripts/validate-constitution.sh +567 -0
- package/.claude/scripts/validate-hooks.sh +487 -0
- package/.claude/scripts/validate-research.sh +332 -0
- package/.claude/scripts/validate-scope-boundary.sh +493 -0
- package/.claude/scripts/verify-setup.sh +37 -0
- package/.claude/settings.json +76 -0
- package/.claude/skills/_reference-implementations/README.md +96 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/SKILL.md +302 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/architecture-overview.md +451 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/async-and-errors.md +307 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/complete-examples.md +638 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/configuration.md +275 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/database-patterns.md +224 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/middleware-guide.md +213 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/routing-and-controllers.md +756 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/sentry-and-monitoring.md +336 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/services-and-repositories.md +789 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/testing-guide.md +235 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/validation-patterns.md +754 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/SKILL.md +399 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/common-patterns.md +331 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/complete-examples.md +872 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/component-patterns.md +502 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/data-fetching.md +767 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/file-organization.md +502 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/loading-and-error-states.md +501 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/performance.md +406 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/routing-guide.md +364 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/styling-guide.md +428 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/typescript-standards.md +418 -0
- package/.claude/skills/cc-devflow-orchestrator/SKILL.md +229 -0
- package/.claude/skills/constitution-guardian/SKILL.md +306 -0
- package/.claude/skills/devflow-constitution-quick-ref/SKILL.md +374 -0
- package/.claude/skills/devflow-file-standards/SKILL.md +353 -0
- package/.claude/skills/devflow-tdd-enforcer/SKILL.md +192 -0
- package/.claude/skills/skill-developer/ADVANCED.md +197 -0
- package/.claude/skills/skill-developer/HOOK_MECHANISMS.md +306 -0
- package/.claude/skills/skill-developer/PATTERNS_LIBRARY.md +152 -0
- package/.claude/skills/skill-developer/SKILL.md +426 -0
- package/.claude/skills/skill-developer/SKILL_RULES_REFERENCE.md +315 -0
- package/.claude/skills/skill-developer/TRIGGER_TYPES.md +305 -0
- package/.claude/skills/skill-developer/TROUBLESHOOTING.md +514 -0
- package/.claude/skills/skill-rules.json +213 -0
- package/.claude/tests/README.md +300 -0
- package/.claude/tests/TODO.md +69 -0
- package/.claude/tests/__pycache__/test_analyze_upgrade_impact.cpython-311-pytest-7.2.2.pyc +0 -0
- package/.claude/tests/__pycache__/test_consolidate_research.cpython-311-pytest-7.2.2.pyc +0 -0
- package/.claude/tests/__pycache__/test_export_contracts.cpython-311-pytest-7.2.2.pyc +0 -0
- package/.claude/tests/__pycache__/test_extract_data_model.cpython-311-pytest-7.2.2.pyc +0 -0
- package/.claude/tests/__pycache__/test_generate_quickstart.cpython-311-pytest-7.2.2.pyc +0 -0
- package/.claude/tests/__pycache__/test_generate_research_tasks.cpython-311-pytest-7.2.2.pyc +0 -0
- package/.claude/tests/constitution/run_all_constitution_tests.sh +111 -0
- package/.claude/tests/constitution/test_agent_assignment.sh +207 -0
- package/.claude/tests/constitution/test_article_coverage.sh +201 -0
- package/.claude/tests/constitution/test_template_completeness.sh +150 -0
- package/.claude/tests/constitution/test_version_consistency.sh +120 -0
- package/.claude/tests/fixtures/spec_delta_full.md +16 -0
- package/.claude/tests/fixtures/tasks_progress_sample.md +5 -0
- package/.claude/tests/run-all-tests.sh +229 -0
- package/.claude/tests/scripts/run.sh +30 -0
- package/.claude/tests/scripts/test-framework.sh +128 -0
- package/.claude/tests/scripts/test_check_prerequisites.sh +511 -0
- package/.claude/tests/scripts/test_check_prerequisites.sh.bak +504 -0
- package/.claude/tests/scripts/test_check_prerequisites.sh.bak2 +505 -0
- package/.claude/tests/scripts/test_check_prerequisites.sh.bak3 +506 -0
- package/.claude/tests/scripts/test_check_prerequisites.sh.bak4 +507 -0
- package/.claude/tests/scripts/test_check_prerequisites.sh.bak5 +508 -0
- package/.claude/tests/scripts/test_check_task_status.sh +499 -0
- package/.claude/tests/scripts/test_common.sh +244 -0
- package/.claude/tests/scripts/test_generate_status_report.sh +71 -0
- package/.claude/tests/scripts/test_mark_task_complete.sh +441 -0
- package/.claude/tests/scripts/test_mark_task_complete.sh.backup +410 -0
- package/.claude/tests/scripts/test_recover_workflow.sh +304 -0
- package/.claude/tests/scripts/test_setup_epic.sh +437 -0
- package/.claude/tests/scripts/test_sync_task_marks.sh +196 -0
- package/.claude/tests/scripts/test_validate_constitution.sh +74 -0
- package/.claude/tests/scripts/test_validate_research.sh +462 -0
- package/.claude/tests/slugify.bats +82 -0
- package/.claude/tests/test-framework.sh +732 -0
- package/.claude/tests/test_analyze_upgrade_impact.py +34 -0
- package/.claude/tests/test_consolidate_research.py +48 -0
- package/.claude/tests/test_export_contracts.py +43 -0
- package/.claude/tests/test_extract_data_model.py +33 -0
- package/.claude/tests/test_generate_quickstart.py +50 -0
- package/.claude/tests/test_generate_research_tasks.py +52 -0
- package/.claude/tsc-cache/6e64f818-6398-49ca-8623-581a9af85c44/edited-files.log +1 -0
- package/.claude/tsc-cache/795ba6e3-b98a-423b-bab2-51aa62812569/affected-repos.txt +1 -0
- package/.claude/tsc-cache/795ba6e3-b98a-423b-bab2-51aa62812569/edited-files.log +1 -0
- package/.claude/tsc-cache/ae335694-be5a-4ba4-a1a0-b676c09a7906/affected-repos.txt +1 -0
- package/.claude/tsc-cache/ae335694-be5a-4ba4-a1a0-b676c09a7906/edited-files.log +1 -0
- package/CHANGELOG.md +507 -0
- package/LICENSE +21 -0
- package/README.md +534 -0
- package/README.zh-CN.md +530 -0
- package/bin/adapt.js +240 -0
- package/bin/cc-devflow-cli.js +185 -0
- package/bin/cc-devflow.js +78 -0
- package/config/adapters.yml +5 -0
- package/config/schema/adapters.schema.json +44 -0
- package/docs/CLAUDE.md +26 -0
- package/docs/commands/README.md +61 -0
- package/docs/commands/README.zh-CN.md +55 -0
- package/docs/commands/core-roadmap.md +106 -0
- package/docs/commands/core-roadmap.zh-CN.md +102 -0
- package/docs/commands/core-style.md +405 -0
- package/docs/commands/core-style.zh-CN.md +405 -0
- package/docs/commands/flow-init.md +134 -0
- package/docs/commands/flow-init.zh-CN.md +163 -0
- package/docs/commands/flow-new.md +274 -0
- package/docs/commands/flow-new.zh-CN.md +270 -0
- package/docs/guides/getting-started.md +204 -0
- package/docs/guides/getting-started.zh-CN.md +152 -0
- package/lib/adapters/adapter-interface.js +57 -0
- package/lib/adapters/claude-adapter.js +74 -0
- package/lib/adapters/codex-adapter.js +40 -0
- package/lib/adapters/config-validator.js +68 -0
- package/lib/adapters/logger.js +42 -0
- package/lib/adapters/registry.js +153 -0
- package/lib/compiler/CLAUDE.md +92 -0
- package/lib/compiler/__tests__/drift.test.js +215 -0
- package/lib/compiler/__tests__/errors.test.js +184 -0
- package/lib/compiler/__tests__/incremental.test.js +174 -0
- package/lib/compiler/__tests__/integration.test.js +174 -0
- package/lib/compiler/__tests__/manifest.test.js +233 -0
- package/lib/compiler/__tests__/parser.test.js +456 -0
- package/lib/compiler/__tests__/schemas.test.js +301 -0
- package/lib/compiler/__tests__/skills-registry.test.js +125 -0
- package/lib/compiler/__tests__/transformer.test.js +286 -0
- package/lib/compiler/emitters/antigravity-emitter.js +171 -0
- package/lib/compiler/emitters/base-emitter.js +73 -0
- package/lib/compiler/emitters/codex-emitter.js +52 -0
- package/lib/compiler/emitters/cursor-emitter.js +31 -0
- package/lib/compiler/emitters/index.js +50 -0
- package/lib/compiler/emitters/qwen-emitter.js +39 -0
- package/lib/compiler/errors.js +119 -0
- package/lib/compiler/index.js +256 -0
- package/lib/compiler/manifest.js +242 -0
- package/lib/compiler/parser.js +258 -0
- package/lib/compiler/platforms.js +113 -0
- package/lib/compiler/resource-copier.js +320 -0
- package/lib/compiler/rules-emitters/__tests__/antigravity-rules-emitter.test.js +191 -0
- package/lib/compiler/rules-emitters/__tests__/codex-rules-emitter.test.js +109 -0
- package/lib/compiler/rules-emitters/__tests__/cursor-rules-emitter.test.js +123 -0
- package/lib/compiler/rules-emitters/__tests__/qwen-rules-emitter.test.js +123 -0
- package/lib/compiler/rules-emitters/antigravity-rules-emitter.js +253 -0
- package/lib/compiler/rules-emitters/base-rules-emitter.js +83 -0
- package/lib/compiler/rules-emitters/codex-rules-emitter.js +116 -0
- package/lib/compiler/rules-emitters/cursor-rules-emitter.js +98 -0
- package/lib/compiler/rules-emitters/index.js +71 -0
- package/lib/compiler/rules-emitters/qwen-rules-emitter.js +70 -0
- package/lib/compiler/schemas.js +144 -0
- package/lib/compiler/skills-registry.js +225 -0
- package/lib/compiler/transformer.js +236 -0
- 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"; }
|