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,117 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# shellcheck disable=SC2312
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
usage() {
|
|
7
|
+
cat <<'USAGE'
|
|
8
|
+
Usage: scripts/bash/export-contracts.sh <requirement-dir>
|
|
9
|
+
|
|
10
|
+
Extracts API contract definitions from TECH_DESIGN.md and writes them into
|
|
11
|
+
contracts/openapi.yaml (or schema.graphql if GraphQL). Creates placeholders when
|
|
12
|
+
structured data is unavailable.
|
|
13
|
+
USAGE
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
17
|
+
usage
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if [[ $# -lt 1 ]]; then
|
|
22
|
+
echo "Error: requirement directory is required." >&2
|
|
23
|
+
usage
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
REQ_DIR="$1"
|
|
28
|
+
if [[ ! -d "$REQ_DIR" ]]; then
|
|
29
|
+
echo "Error: requirement directory '$REQ_DIR' does not exist." >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
python3 - "$REQ_DIR" <<'PY'
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import re
|
|
37
|
+
import sys
|
|
38
|
+
from datetime import datetime, timezone
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
|
|
41
|
+
req_dir = Path(sys.argv[1]).resolve()
|
|
42
|
+
tech_design = req_dir / "TECH_DESIGN.md"
|
|
43
|
+
contracts_dir = req_dir / "contracts"
|
|
44
|
+
contracts_dir.mkdir(exist_ok=True, parents=True)
|
|
45
|
+
openapi_path = contracts_dir / "openapi.yaml"
|
|
46
|
+
graphql_path = contracts_dir / "schema.graphql"
|
|
47
|
+
|
|
48
|
+
if not tech_design.exists():
|
|
49
|
+
print(f"Error: {tech_design} not found.", file=sys.stderr)
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
|
|
52
|
+
content = tech_design.read_text(encoding="utf-8")
|
|
53
|
+
|
|
54
|
+
api_patterns = [
|
|
55
|
+
r"(^##\s+4\.\s*API.*?)(?=^##\s+\d+\.)",
|
|
56
|
+
r"(^##\s+API Design.*?)(?=^##\s+)",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
api_section = None
|
|
60
|
+
for pattern in api_patterns:
|
|
61
|
+
match = re.search(pattern, content, re.MULTILINE | re.DOTALL)
|
|
62
|
+
if match:
|
|
63
|
+
api_section = match.group(1).strip()
|
|
64
|
+
break
|
|
65
|
+
|
|
66
|
+
generated_at = datetime.now(timezone.utc).isoformat()
|
|
67
|
+
|
|
68
|
+
def extract_code_block(section: str, languages: tuple[str, ...]) -> str | None:
|
|
69
|
+
fence_regex = re.compile(
|
|
70
|
+
r"```(?P<lang>[^\n]*)\n(?P<body>.*?)```",
|
|
71
|
+
re.DOTALL | re.IGNORECASE,
|
|
72
|
+
)
|
|
73
|
+
for match in fence_regex.finditer(section):
|
|
74
|
+
lang = match.group("lang").strip().lower()
|
|
75
|
+
body = match.group("body")
|
|
76
|
+
if not languages or lang in languages:
|
|
77
|
+
return body.strip()
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
if api_section:
|
|
81
|
+
openapi_block = extract_code_block(api_section, ("yaml", "yml", "openapi", "json"))
|
|
82
|
+
graphql_block = extract_code_block(api_section, ("graphql",))
|
|
83
|
+
else:
|
|
84
|
+
openapi_block = graphql_block = None
|
|
85
|
+
|
|
86
|
+
if graphql_block:
|
|
87
|
+
graphql_path.write_text(graphql_block + "\n", encoding="utf-8")
|
|
88
|
+
print(f"Wrote GraphQL schema → {graphql_path}")
|
|
89
|
+
# If both exist, prefer OpenAPI as well; otherwise create stub below.
|
|
90
|
+
|
|
91
|
+
if openapi_block:
|
|
92
|
+
if not openapi_block.lstrip().startswith("openapi:"):
|
|
93
|
+
openapi_block = f"# Generated {generated_at}\n{openapi_block}"
|
|
94
|
+
openapi_path.write_text(openapi_block + "\n", encoding="utf-8")
|
|
95
|
+
print(f"Wrote OpenAPI contract → {openapi_path}")
|
|
96
|
+
else:
|
|
97
|
+
placeholder = f"""# openapi.yaml generated {generated_at}
|
|
98
|
+
openapi: 3.0.3
|
|
99
|
+
info:
|
|
100
|
+
title: {req_dir.name} API
|
|
101
|
+
version: 0.1.0
|
|
102
|
+
paths:
|
|
103
|
+
/example:
|
|
104
|
+
get:
|
|
105
|
+
summary: TODO - replace with real endpoint
|
|
106
|
+
responses:
|
|
107
|
+
'200':
|
|
108
|
+
description: OK
|
|
109
|
+
components:
|
|
110
|
+
schemas:
|
|
111
|
+
TODO:
|
|
112
|
+
type: object
|
|
113
|
+
description: Replace with actual schema
|
|
114
|
+
"""
|
|
115
|
+
openapi_path.write_text(placeholder, encoding="utf-8")
|
|
116
|
+
print(f"Warning: No structured OpenAPI block found; wrote placeholder → {openapi_path}", file=sys.stderr)
|
|
117
|
+
PY
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# shellcheck disable=SC2312
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
usage() {
|
|
7
|
+
cat <<'USAGE'
|
|
8
|
+
Usage: scripts/bash/extract-data-model.sh <requirement-dir>
|
|
9
|
+
|
|
10
|
+
Extracts the "Data Model" section from TECH_DESIGN.md into data-model.md.
|
|
11
|
+
USAGE
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
15
|
+
usage
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
if [[ $# -lt 1 ]]; then
|
|
20
|
+
echo "Error: requirement directory is required." >&2
|
|
21
|
+
usage
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
REQ_DIR="$1"
|
|
26
|
+
if [[ ! -d "$REQ_DIR" ]]; then
|
|
27
|
+
echo "Error: requirement directory '$REQ_DIR' does not exist." >&2
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
python3 - "$REQ_DIR" <<'PY'
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import re
|
|
35
|
+
import sys
|
|
36
|
+
from datetime import datetime, timezone
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
req_dir = Path(sys.argv[1]).resolve()
|
|
40
|
+
tech_design = req_dir / "TECH_DESIGN.md"
|
|
41
|
+
output_path = req_dir / "data-model.md"
|
|
42
|
+
|
|
43
|
+
if not tech_design.exists():
|
|
44
|
+
print(f"Error: {tech_design} not found.", file=sys.stderr)
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
|
|
47
|
+
content = tech_design.read_text(encoding="utf-8")
|
|
48
|
+
|
|
49
|
+
section_regexes = [
|
|
50
|
+
r"(^##\s+3\.\s*Data Model.*?)(?=^##\s+\d+\.)",
|
|
51
|
+
r"(^##\s+Data Model.*?)(?=^##\s+)",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
section = None
|
|
55
|
+
for pattern in section_regexes:
|
|
56
|
+
match = re.search(pattern, content, re.MULTILINE | re.DOTALL)
|
|
57
|
+
if match:
|
|
58
|
+
section = match.group(1).strip()
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
if section is None:
|
|
62
|
+
print("Warning: Data Model section not found; creating placeholder.", file=sys.stderr)
|
|
63
|
+
section = "## Data Model\n\n_TODO: populate data model details in TECH_DESIGN.md_"
|
|
64
|
+
|
|
65
|
+
generated_at = datetime.now(timezone.utc).isoformat()
|
|
66
|
+
|
|
67
|
+
lines = [
|
|
68
|
+
f"# Data Model — {req_dir.name}",
|
|
69
|
+
"",
|
|
70
|
+
f"_Generated from TECH_DESIGN.md on {generated_at}_",
|
|
71
|
+
"",
|
|
72
|
+
section,
|
|
73
|
+
"",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
output_path.write_text("\n".join(lines), encoding="utf-8")
|
|
77
|
+
print(f"Wrote data model extract → {output_path}")
|
|
78
|
+
PY
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# generate-clarification-questions.sh - 智能问题生成与优先级排序
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Purpose: 基于扫描结果生成优先级排序问题 (≤5)
|
|
6
|
+
# Usage: generate-clarification-questions.sh --input scan_result.json --max 5
|
|
7
|
+
# Output: JSON (stdout) - ClarificationQuestion[]
|
|
8
|
+
# Exit codes: 0=success, 1=no issues, 2=fatal
|
|
9
|
+
# =============================================================================
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
source "${SCRIPT_DIR}/common.sh"
|
|
15
|
+
|
|
16
|
+
# Import functions from run-clarify-scan.sh
|
|
17
|
+
source "${SCRIPT_DIR}/run-clarify-scan.sh" 2>/dev/null || true
|
|
18
|
+
|
|
19
|
+
# =============================================================================
|
|
20
|
+
# API Key 检测 (与 run-clarify-scan.sh 对齐)
|
|
21
|
+
# =============================================================================
|
|
22
|
+
if ! declare -F has_valid_api_key >/dev/null 2>&1; then
|
|
23
|
+
has_valid_api_key() {
|
|
24
|
+
[[ -n "${CLAUDE_API_KEY:-}" ]] && [[ "${CLAUDE_API_KEY}" =~ ^sk-ant- ]]
|
|
25
|
+
}
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Configuration
|
|
29
|
+
MAX_QUESTIONS=5
|
|
30
|
+
INPUT_FILE=""
|
|
31
|
+
|
|
32
|
+
# =============================================================================
|
|
33
|
+
# T026: 优先级计算
|
|
34
|
+
# =============================================================================
|
|
35
|
+
calculate_priority() {
|
|
36
|
+
local issue_json="$1"
|
|
37
|
+
local impact uncertainty
|
|
38
|
+
impact=$(echo "$issue_json" | jq -r '.impact // 5')
|
|
39
|
+
uncertainty=$(echo "$issue_json" | jq -r '.uncertainty // 5')
|
|
40
|
+
echo $((impact * uncertainty))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# =============================================================================
|
|
44
|
+
# T027: 问题合并 (同维度多个歧义合并为 1 个问题)
|
|
45
|
+
# =============================================================================
|
|
46
|
+
merge_similar_issues() {
|
|
47
|
+
local issues_json="$1"
|
|
48
|
+
|
|
49
|
+
# 按维度分组,每个维度取最高优先级的问题
|
|
50
|
+
echo "$issues_json" | jq '
|
|
51
|
+
group_by(.dimensionId) |
|
|
52
|
+
map(
|
|
53
|
+
sort_by(-.priority) |
|
|
54
|
+
.[0] + {
|
|
55
|
+
relatedIssues: (. | length),
|
|
56
|
+
mergedDescriptions: (map(.description) | join("; "))
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# =============================================================================
|
|
63
|
+
# T028: 问题模板生成
|
|
64
|
+
# =============================================================================
|
|
65
|
+
generate_heuristic_question() {
|
|
66
|
+
local dim_id="$1"
|
|
67
|
+
local description="$2"
|
|
68
|
+
|
|
69
|
+
case "$dim_id" in
|
|
70
|
+
1)
|
|
71
|
+
jq -n \
|
|
72
|
+
--arg desc "$description" \
|
|
73
|
+
'{
|
|
74
|
+
text: "REQ-004 的 MVP 适配范围应如何定义?",
|
|
75
|
+
options: [
|
|
76
|
+
{optionId: "A", text: "双平台可运行", description: "交付 claude-code + codex-cli 两个可运行 adapter,其它平台先留 stub"},
|
|
77
|
+
{optionId: "B", text: "单平台先抽象", description: "只交付 claude-code adapter + 接口/注册表,其它平台后续需求再做"},
|
|
78
|
+
{optionId: "C", text: "多平台一次到位", description: "至少 3 个可运行 adapter(Claude/Codex/Cursor 等)"},
|
|
79
|
+
{optionId: "D", text: "仅出设计不落地", description: "只输出 PRD/Tech Design,本需求不交付代码"}
|
|
80
|
+
],
|
|
81
|
+
recommendedOption: "A",
|
|
82
|
+
recommendedRationale: "至少落地 2 个可运行 adapter 才能验证架构正确性,同时把范围控制在可交付的最小集合"
|
|
83
|
+
}'
|
|
84
|
+
;;
|
|
85
|
+
4)
|
|
86
|
+
jq -n \
|
|
87
|
+
--arg desc "$description" \
|
|
88
|
+
'{
|
|
89
|
+
text: "对 adapter 的探测/执行链路,有哪些硬性非功能要求需要现在锁定?",
|
|
90
|
+
options: [
|
|
91
|
+
{optionId: "A", text: "轻量指标 + 结构化日志", description: "detect 总耗时 <50ms(缓存后 <5ms),记录 adapter/timing/result 的结构化日志"},
|
|
92
|
+
{optionId: "B", text: "严格指标 + 可观测套件", description: "detect <5ms,强制 metrics/tracing,所有执行都有 traceId"},
|
|
93
|
+
{optionId: "C", text: "不设指标", description: "先按 best-effort 做通功能,后续再补性能与观测"}
|
|
94
|
+
],
|
|
95
|
+
recommendedOption: "A",
|
|
96
|
+
recommendedRationale: "先把可观测性与基本性能边界写清,避免后续返工;同时不引入过度工程"
|
|
97
|
+
}'
|
|
98
|
+
;;
|
|
99
|
+
6)
|
|
100
|
+
jq -n \
|
|
101
|
+
--arg desc "$description" \
|
|
102
|
+
'{
|
|
103
|
+
text: "当多个 adapter 同时命中或没有命中时,选择/降级策略应是什么?",
|
|
104
|
+
options: [
|
|
105
|
+
{optionId: "A", text: "可覆盖 + 确定性优先级", description: "用户显式指定(ENV/CLI) > 配置 > detect 打分最高 > fallback 默认 adapter;冲突时输出告警"},
|
|
106
|
+
{optionId: "B", text: "冲突即失败", description: "多个命中或无命中直接报错,要求用户显式指定"},
|
|
107
|
+
{optionId: "C", text: "先到先得", description: "按注册顺序 first-match,不做打分与冲突处理"}
|
|
108
|
+
],
|
|
109
|
+
recommendedOption: "A",
|
|
110
|
+
recommendedRationale: "保证可预测性与可控性:默认自动化,但允许用户明确覆盖,并且对冲突有确定性规则"
|
|
111
|
+
}'
|
|
112
|
+
;;
|
|
113
|
+
9)
|
|
114
|
+
jq -n \
|
|
115
|
+
--arg desc "$description" \
|
|
116
|
+
'{
|
|
117
|
+
text: "REQ-004 的 Definition of Done 应以哪些可验证交付物为准?",
|
|
118
|
+
options: [
|
|
119
|
+
{optionId: "A", text: "可运行 + 可验证", description: "接口规范 + registry + 默认 adapter + 配置机制 + 测试 + 文档 + 至少 1 个非默认平台 adapter"},
|
|
120
|
+
{optionId: "B", text: "先抽象后验证", description: "接口规范 + registry + 默认 adapter + 基础测试;非默认 adapter 仅 stub"},
|
|
121
|
+
{optionId: "C", text: "文档优先", description: "只产出 PRD/Tech Design/TASKS,代码实现拆到后续需求"}
|
|
122
|
+
],
|
|
123
|
+
recommendedOption: "A",
|
|
124
|
+
recommendedRationale: "定义完成必须可验证;至少一个非默认 adapter 能证明架构不是纸上谈兵"
|
|
125
|
+
}'
|
|
126
|
+
;;
|
|
127
|
+
11)
|
|
128
|
+
jq -n \
|
|
129
|
+
--arg desc "$description" \
|
|
130
|
+
'{
|
|
131
|
+
text: "Adapter 的能力边界与默认安全策略应如何设计(shell/filesystem/network)?",
|
|
132
|
+
options: [
|
|
133
|
+
{optionId: "A", text: "Capability allow-list", description: "adapter 声明 capabilities;命令声明 required capabilities;默认 deny 危险能力(shell/network),启用需显式配置并写审计日志"},
|
|
134
|
+
{optionId: "B", text: "完全信任", description: "adapter 拥有全部能力,不做额外限制"},
|
|
135
|
+
{optionId: "C", text: "全局 Safe Mode", description: "默认关闭 shell/network,提供全局开关;不做细粒度 capability 控制"}
|
|
136
|
+
],
|
|
137
|
+
recommendedOption: "A",
|
|
138
|
+
recommendedRationale: "用能力模型把危险操作从设计上隔离出去,默认最小权限,审计可追踪"
|
|
139
|
+
}'
|
|
140
|
+
;;
|
|
141
|
+
*)
|
|
142
|
+
jq -n \
|
|
143
|
+
--arg desc "$description" \
|
|
144
|
+
'{
|
|
145
|
+
text: ("需要澄清:" + $desc),
|
|
146
|
+
options: [
|
|
147
|
+
{optionId: "A", text: "采用推荐方案", description: "采用行业最佳实践"},
|
|
148
|
+
{optionId: "B", text: "自定义方案", description: "需要进一步说明"},
|
|
149
|
+
{optionId: "C", text: "暂时跳过", description: "稍后决定"}
|
|
150
|
+
],
|
|
151
|
+
recommendedOption: "A",
|
|
152
|
+
recommendedRationale: "优先选可验证、低风险路径"
|
|
153
|
+
}'
|
|
154
|
+
;;
|
|
155
|
+
esac
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
generate_question_template() {
|
|
159
|
+
local issue_json="$1"
|
|
160
|
+
local question_id="$2"
|
|
161
|
+
|
|
162
|
+
local dim_id dim_name description
|
|
163
|
+
dim_id=$(echo "$issue_json" | jq -r '.dimensionId')
|
|
164
|
+
description=$(echo "$issue_json" | jq -r '.description // .mergedDescriptions // "需要澄清"')
|
|
165
|
+
|
|
166
|
+
# 维度名称映射
|
|
167
|
+
local dim_names=(
|
|
168
|
+
"" "Functional Scope" "Data Model" "UX Flow" "Non-Functional Quality"
|
|
169
|
+
"Integration & Dependencies" "Edge Cases" "Constraints & Tradeoffs"
|
|
170
|
+
"Terminology" "Completion Signals" "Misc & Placeholders" "Security & Privacy"
|
|
171
|
+
)
|
|
172
|
+
dim_name="${dim_names[$dim_id]:-Unknown}"
|
|
173
|
+
|
|
174
|
+
# 生成问题文本
|
|
175
|
+
local question_text="关于 ${dim_name}: ${description}"
|
|
176
|
+
|
|
177
|
+
local template_json options recommended recommended_rationale
|
|
178
|
+
template_json="{}"
|
|
179
|
+
options='[]'
|
|
180
|
+
recommended="A"
|
|
181
|
+
recommended_rationale="推荐使用行业最佳实践以降低风险"
|
|
182
|
+
|
|
183
|
+
if has_valid_api_key; then
|
|
184
|
+
template_json=$(generate_ai_question "$issue_json" "$dim_name") || true
|
|
185
|
+
else
|
|
186
|
+
template_json=$(generate_heuristic_question "$dim_id" "$description") || true
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
if [[ -n "$template_json" ]] && echo "$template_json" | jq -e '.text and (.options | length > 0)' >/dev/null 2>&1; then
|
|
190
|
+
question_text=$(echo "$template_json" | jq -r '.text')
|
|
191
|
+
options=$(echo "$template_json" | jq '.options')
|
|
192
|
+
recommended=$(echo "$template_json" | jq -r '.recommendedOption // "A"')
|
|
193
|
+
recommended_rationale=$(echo "$template_json" | jq -r '.recommendedRationale // "推荐使用行业最佳实践以降低风险"')
|
|
194
|
+
else
|
|
195
|
+
# 兜底:使用最通用模板
|
|
196
|
+
options='[
|
|
197
|
+
{"optionId": "A", "text": "使用推荐方案", "description": "采用行业最佳实践"},
|
|
198
|
+
{"optionId": "B", "text": "自定义方案", "description": "需要进一步说明"},
|
|
199
|
+
{"optionId": "C", "text": "暂时跳过", "description": "稍后决定"}
|
|
200
|
+
]'
|
|
201
|
+
recommended="A"
|
|
202
|
+
recommended_rationale="推荐使用行业最佳实践以降低风险"
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
# 构建问题 JSON
|
|
206
|
+
jq -n \
|
|
207
|
+
--arg qid "$question_id" \
|
|
208
|
+
--argjson dimId "$dim_id" \
|
|
209
|
+
--arg text "$question_text" \
|
|
210
|
+
--argjson options "$options" \
|
|
211
|
+
--arg recommended "$recommended" \
|
|
212
|
+
--arg rationale "$recommended_rationale" \
|
|
213
|
+
'{
|
|
214
|
+
questionId: $qid,
|
|
215
|
+
dimensionId: $dimId,
|
|
216
|
+
text: $text,
|
|
217
|
+
type: "multiple_choice",
|
|
218
|
+
options: $options,
|
|
219
|
+
recommendedOption: $recommended,
|
|
220
|
+
recommendedRationale: $rationale,
|
|
221
|
+
answer: null,
|
|
222
|
+
answeredAt: null,
|
|
223
|
+
rationale: null
|
|
224
|
+
}'
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# =============================================================================
|
|
228
|
+
# AI 问题生成 (使用 Claude Sonnet)
|
|
229
|
+
# =============================================================================
|
|
230
|
+
generate_ai_question() {
|
|
231
|
+
local issue_json="$1"
|
|
232
|
+
local dim_name="$2"
|
|
233
|
+
|
|
234
|
+
local description
|
|
235
|
+
description=$(echo "$issue_json" | jq -r '.description // .mergedDescriptions')
|
|
236
|
+
|
|
237
|
+
local system_prompt="You are a requirements clarification specialist.
|
|
238
|
+
Generate a clarification question for this ambiguity:
|
|
239
|
+
Dimension: ${dim_name}
|
|
240
|
+
Issue: ${description}
|
|
241
|
+
|
|
242
|
+
Output ONLY valid JSON:
|
|
243
|
+
{
|
|
244
|
+
\"text\": \"问题文本(中文,简洁明确)\",
|
|
245
|
+
\"options\": [
|
|
246
|
+
{\"optionId\": \"A\", \"text\": \"选项A\", \"description\": \"说明\"},
|
|
247
|
+
{\"optionId\": \"B\", \"text\": \"选项B\", \"description\": \"说明\"},
|
|
248
|
+
{\"optionId\": \"C\", \"text\": \"选项C\", \"description\": \"说明\"}
|
|
249
|
+
],
|
|
250
|
+
\"recommendedOption\": \"A\",
|
|
251
|
+
\"recommendedRationale\": \"推荐理由\"
|
|
252
|
+
}"
|
|
253
|
+
|
|
254
|
+
call_claude_api "claude-sonnet-4-5-20241022" "$system_prompt" "$description" 1000 30 2>/dev/null || echo '{}'
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# =============================================================================
|
|
258
|
+
# T029: 主入口
|
|
259
|
+
# =============================================================================
|
|
260
|
+
parse_args() {
|
|
261
|
+
local input_file=""
|
|
262
|
+
local max_questions="$MAX_QUESTIONS"
|
|
263
|
+
|
|
264
|
+
while [[ $# -gt 0 ]]; do
|
|
265
|
+
case "$1" in
|
|
266
|
+
--input)
|
|
267
|
+
input_file="$2"
|
|
268
|
+
shift 2
|
|
269
|
+
;;
|
|
270
|
+
--max)
|
|
271
|
+
max_questions="$2"
|
|
272
|
+
shift 2
|
|
273
|
+
;;
|
|
274
|
+
--help|-h)
|
|
275
|
+
echo "Usage: generate-clarification-questions.sh --input scan_result.json [--max 5]"
|
|
276
|
+
exit 0
|
|
277
|
+
;;
|
|
278
|
+
*)
|
|
279
|
+
shift
|
|
280
|
+
;;
|
|
281
|
+
esac
|
|
282
|
+
done
|
|
283
|
+
|
|
284
|
+
echo "$input_file|$max_questions"
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
extract_all_issues() {
|
|
288
|
+
local scan_result="$1"
|
|
289
|
+
|
|
290
|
+
# 从扫描结果中提取所有 issues
|
|
291
|
+
echo "$scan_result" | jq '
|
|
292
|
+
.dimensions // [] |
|
|
293
|
+
map(select(.status == "ambiguous")) |
|
|
294
|
+
map(.issues[] + {dimensionId: .dimensionId, dimensionName: .name}) |
|
|
295
|
+
sort_by(-.priority)
|
|
296
|
+
'
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
main() {
|
|
300
|
+
# 解析参数
|
|
301
|
+
local parsed
|
|
302
|
+
parsed=$(parse_args "$@")
|
|
303
|
+
local input_file max_questions
|
|
304
|
+
IFS='|' read -r input_file max_questions <<< "$parsed"
|
|
305
|
+
|
|
306
|
+
# 验证输入
|
|
307
|
+
if [[ -z "$input_file" ]]; then
|
|
308
|
+
echo '{"error": {"code": "MISSING_INPUT", "message": "--input is required"}}' >&2
|
|
309
|
+
exit 2
|
|
310
|
+
fi
|
|
311
|
+
|
|
312
|
+
local scan_result
|
|
313
|
+
if [[ "$input_file" == "-" ]]; then
|
|
314
|
+
scan_result=$(cat)
|
|
315
|
+
elif [[ -f "$input_file" ]]; then
|
|
316
|
+
scan_result=$(cat "$input_file")
|
|
317
|
+
else
|
|
318
|
+
echo '{"error": {"code": "FILE_NOT_FOUND", "message": "Input file not found"}}' >&2
|
|
319
|
+
exit 2
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
# 验证 JSON
|
|
323
|
+
if ! echo "$scan_result" | jq -e '.' >/dev/null 2>&1; then
|
|
324
|
+
echo '{"error": {"code": "INVALID_JSON", "message": "Input is not valid JSON"}}' >&2
|
|
325
|
+
exit 2
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
# 提取所有 issues
|
|
329
|
+
local all_issues
|
|
330
|
+
all_issues=$(extract_all_issues "$scan_result")
|
|
331
|
+
|
|
332
|
+
local issue_count
|
|
333
|
+
issue_count=$(echo "$all_issues" | jq 'length')
|
|
334
|
+
|
|
335
|
+
# T024: 如果没有 issues,返回空
|
|
336
|
+
if [[ "$issue_count" -eq 0 ]]; then
|
|
337
|
+
echo '{"questions": [], "message": "No ambiguities found - research.md is clear"}'
|
|
338
|
+
exit 1
|
|
339
|
+
fi
|
|
340
|
+
|
|
341
|
+
# 合并相似问题
|
|
342
|
+
local merged_issues
|
|
343
|
+
merged_issues=$(merge_similar_issues "$all_issues")
|
|
344
|
+
|
|
345
|
+
# 按优先级排序并限制数量
|
|
346
|
+
local top_issues
|
|
347
|
+
top_issues=$(echo "$merged_issues" | jq --argjson max "$max_questions" '
|
|
348
|
+
sort_by(-.priority) | .[:$max]
|
|
349
|
+
')
|
|
350
|
+
|
|
351
|
+
# 生成问题
|
|
352
|
+
local questions="[]"
|
|
353
|
+
local idx=0
|
|
354
|
+
while IFS= read -r issue; do
|
|
355
|
+
idx=$((idx + 1))
|
|
356
|
+
local question_id="Q${idx}"
|
|
357
|
+
local question
|
|
358
|
+
question=$(generate_question_template "$issue" "$question_id")
|
|
359
|
+
questions=$(echo "$questions" | jq --argjson q "$question" '. + [$q]')
|
|
360
|
+
done < <(echo "$top_issues" | jq -c '.[]')
|
|
361
|
+
|
|
362
|
+
# 输出结果
|
|
363
|
+
jq -n \
|
|
364
|
+
--argjson questions "$questions" \
|
|
365
|
+
--argjson totalIssues "$issue_count" \
|
|
366
|
+
--argjson generatedCount "$(echo "$questions" | jq 'length')" \
|
|
367
|
+
'{
|
|
368
|
+
questions: $questions,
|
|
369
|
+
totalIssuesFound: $totalIssues,
|
|
370
|
+
questionsGenerated: $generatedCount
|
|
371
|
+
}'
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
# 只在直接执行时运行 main
|
|
375
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
376
|
+
main "$@"
|
|
377
|
+
fi
|