@kunlv/atdc 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/AGENTS.md +58 -0
  2. package/GO.md +245 -0
  3. package/bin/add-command.py +144 -0
  4. package/bin/atdc.js +460 -0
  5. package/bin/cls.js +98 -0
  6. package/central/link.sh +60 -0
  7. package/central/ticket_center_server/.handoffs/TASK-238389_QYinVipLimit.md +128 -0
  8. package/central/ticket_center_server/.test_plans/TASK-227254-SupportMembership-self-test-plan.md +566 -0
  9. package/central/ticket_center_server/.test_plans/task-238389-qyin-vip-limit-self-test-plan.md +210 -0
  10. package/commands/claude/atd.md +3 -0
  11. package/commands/claude/atdc/1-spec.md +5 -0
  12. package/commands/claude/atdc/1-update.md +5 -0
  13. package/commands/claude/atdc/2-qa.md +3 -0
  14. package/commands/claude/atdc/3-impl-skip-spec.md +5 -0
  15. package/commands/claude/atdc/3-impl.md +3 -0
  16. package/commands/claude/atdc/4-verify.md +3 -0
  17. package/commands/claude/atdc/5-done.md +3 -0
  18. package/commands/claude/atdc/add-command.md +4 -0
  19. package/commands/claude/atdc/add-to-hc.md +3 -0
  20. package/commands/claude/atdc/analysis-cls-log.md +4 -0
  21. package/commands/claude/atdc/article.md +6 -0
  22. package/commands/claude/atdc/bugfix.md +4 -0
  23. package/commands/claude/atdc/code-simplifier.md +8 -0
  24. package/commands/claude/atdc/commit.md +4 -0
  25. package/commands/claude/atdc/compound.md +5 -0
  26. package/commands/claude/atdc/context.md +3 -0
  27. package/commands/claude/atdc/diagnose.md +8 -0
  28. package/commands/claude/atdc/git-guardrails.md +18 -0
  29. package/commands/claude/atdc/grill-with-docs.md +8 -0
  30. package/commands/claude/atdc/handoff.md +8 -0
  31. package/commands/claude/atdc/health.md +3 -0
  32. package/commands/claude/atdc/learn.md +4 -0
  33. package/commands/claude/atdc/review.md +10 -0
  34. package/commands/claude/atdc/search.md +4 -0
  35. package/commands/claude/atdc/strategy.md +3 -0
  36. package/commands/claude/atdc/table2xlsx.md +4 -0
  37. package/commands/claude/atdc/test-plan.md +5 -0
  38. package/commands/claude/atdc/travel-planner.md +4 -0
  39. package/commands/claude/atdc/xlsx2sql-insert.md +4 -0
  40. package/commands/claude/atdc/xlsx2sql-update.md +4 -0
  41. package/commands/claude/atdc/zoom-out.md +8 -0
  42. package/commands/codex/atdc/SKILL.md +8 -0
  43. package/commands/codex/atdc-1-spec/SKILL.md +8 -0
  44. package/commands/codex/atdc-1-update/SKILL.md +8 -0
  45. package/commands/codex/atdc-2-qa/SKILL.md +8 -0
  46. package/commands/codex/atdc-3-impl/SKILL.md +8 -0
  47. package/commands/codex/atdc-3-impl-skip-spec/SKILL.md +8 -0
  48. package/commands/codex/atdc-4-verify/SKILL.md +8 -0
  49. package/commands/codex/atdc-5-done/SKILL.md +8 -0
  50. package/commands/codex/atdc-add-command/SKILL.md +7 -0
  51. package/commands/codex/atdc-add-to-hc/SKILL.md +8 -0
  52. package/commands/codex/atdc-analysis-cls-log/SKILL.md +5 -0
  53. package/commands/codex/atdc-article/SKILL.md +8 -0
  54. package/commands/codex/atdc-bugfix/SKILL.md +5 -0
  55. package/commands/codex/atdc-code-simplifier/SKILL.md +7 -0
  56. package/commands/codex/atdc-commit/SKILL.md +7 -0
  57. package/commands/codex/atdc-compound/SKILL.md +8 -0
  58. package/commands/codex/atdc-context/SKILL.md +8 -0
  59. package/commands/codex/atdc-diagnose/SKILL.md +9 -0
  60. package/commands/codex/atdc-git-guardrails/SKILL.md +19 -0
  61. package/commands/codex/atdc-grill-with-docs/SKILL.md +9 -0
  62. package/commands/codex/atdc-handoff/SKILL.md +9 -0
  63. package/commands/codex/atdc-health/SKILL.md +8 -0
  64. package/commands/codex/atdc-learn/SKILL.md +7 -0
  65. package/commands/codex/atdc-review/SKILL.md +49 -0
  66. package/commands/codex/atdc-search/SKILL.md +5 -0
  67. package/commands/codex/atdc-strategy/SKILL.md +8 -0
  68. package/commands/codex/atdc-table2xlsx/SKILL.md +8 -0
  69. package/commands/codex/atdc-test-plan/SKILL.md +13 -0
  70. package/commands/codex/atdc-travel-planner/SKILL.md +7 -0
  71. package/commands/codex/atdc-xlsx2sql-insert/SKILL.md +8 -0
  72. package/commands/codex/atdc-xlsx2sql-update/SKILL.md +8 -0
  73. package/commands/codex/atdc-zoom-out/SKILL.md +9 -0
  74. package/commands/cursor/atdc/SKILL.md +8 -0
  75. package/commands/cursor/atdc-1-spec/SKILL.md +8 -0
  76. package/commands/cursor/atdc-1-update/SKILL.md +8 -0
  77. package/commands/cursor/atdc-2-qa/SKILL.md +8 -0
  78. package/commands/cursor/atdc-3-impl/SKILL.md +8 -0
  79. package/commands/cursor/atdc-3-impl-skip-spec/SKILL.md +8 -0
  80. package/commands/cursor/atdc-4-verify/SKILL.md +8 -0
  81. package/commands/cursor/atdc-5-done/SKILL.md +8 -0
  82. package/commands/cursor/atdc-add-command/SKILL.md +7 -0
  83. package/commands/cursor/atdc-add-to-hc/SKILL.md +8 -0
  84. package/commands/cursor/atdc-analysis-cls-log/SKILL.md +5 -0
  85. package/commands/cursor/atdc-article/SKILL.md +8 -0
  86. package/commands/cursor/atdc-bugfix/SKILL.md +5 -0
  87. package/commands/cursor/atdc-code-simplifier/SKILL.md +7 -0
  88. package/commands/cursor/atdc-commit/SKILL.md +7 -0
  89. package/commands/cursor/atdc-compound/SKILL.md +8 -0
  90. package/commands/cursor/atdc-context/SKILL.md +8 -0
  91. package/commands/cursor/atdc-diagnose/SKILL.md +8 -0
  92. package/commands/cursor/atdc-git-guardrails/SKILL.md +18 -0
  93. package/commands/cursor/atdc-grill-with-docs/SKILL.md +8 -0
  94. package/commands/cursor/atdc-handoff/SKILL.md +8 -0
  95. package/commands/cursor/atdc-health/SKILL.md +8 -0
  96. package/commands/cursor/atdc-learn/SKILL.md +7 -0
  97. package/commands/cursor/atdc-review/SKILL.md +49 -0
  98. package/commands/cursor/atdc-search/SKILL.md +5 -0
  99. package/commands/cursor/atdc-strategy/SKILL.md +8 -0
  100. package/commands/cursor/atdc-table2xlsx/SKILL.md +8 -0
  101. package/commands/cursor/atdc-test-plan/SKILL.md +7 -0
  102. package/commands/cursor/atdc-travel-planner/SKILL.md +7 -0
  103. package/commands/cursor/atdc-xlsx2sql-insert/SKILL.md +8 -0
  104. package/commands/cursor/atdc-xlsx2sql-update/SKILL.md +8 -0
  105. package/commands/cursor/atdc-zoom-out/SKILL.md +8 -0
  106. package/commands/opencode/atd.md +6 -0
  107. package/commands/opencode/atdc/1-spec.md +8 -0
  108. package/commands/opencode/atdc/1-update.md +8 -0
  109. package/commands/opencode/atdc/2-qa.md +6 -0
  110. package/commands/opencode/atdc/3-impl-skip-spec.md +8 -0
  111. package/commands/opencode/atdc/3-impl.md +6 -0
  112. package/commands/opencode/atdc/4-verify.md +6 -0
  113. package/commands/opencode/atdc/5-done.md +6 -0
  114. package/commands/opencode/atdc/add-command.md +6 -0
  115. package/commands/opencode/atdc/add-to-hc.md +6 -0
  116. package/commands/opencode/atdc/analysis-cls-log.md +6 -0
  117. package/commands/opencode/atdc/article.md +6 -0
  118. package/commands/opencode/atdc/bugfix.md +6 -0
  119. package/commands/opencode/atdc/code-simplifier.md +8 -0
  120. package/commands/opencode/atdc/commit.md +6 -0
  121. package/commands/opencode/atdc/compound.md +8 -0
  122. package/commands/opencode/atdc/context.md +6 -0
  123. package/commands/opencode/atdc/diagnose.md +8 -0
  124. package/commands/opencode/atdc/git-guardrails.md +18 -0
  125. package/commands/opencode/atdc/grill-with-docs.md +8 -0
  126. package/commands/opencode/atdc/handoff.md +8 -0
  127. package/commands/opencode/atdc/health.md +6 -0
  128. package/commands/opencode/atdc/learn.md +6 -0
  129. package/commands/opencode/atdc/review.md +7 -0
  130. package/commands/opencode/atdc/search.md +6 -0
  131. package/commands/opencode/atdc/strategy.md +6 -0
  132. package/commands/opencode/atdc/table2xlsx.md +6 -0
  133. package/commands/opencode/atdc/test-plan.md +6 -0
  134. package/commands/opencode/atdc/travel-planner.md +6 -0
  135. package/commands/opencode/atdc/xlsx2sql-insert.md +6 -0
  136. package/commands/opencode/atdc/xlsx2sql-update.md +6 -0
  137. package/commands/opencode/atdc/zoom-out.md +8 -0
  138. package/config/cls-defaults.json +19 -0
  139. package/hooks/atd-archive.sh +86 -0
  140. package/hooks/atd-state.sh +165 -0
  141. package/hooks/evidence-hook.sh +88 -0
  142. package/hooks/evidence-log.sh +54 -0
  143. package/hooks/guardrails.sh +65 -0
  144. package/hooks/handoff-compress.sh +104 -0
  145. package/hooks/phase-guard.sh +134 -0
  146. package/hooks/phase-write-guard.sh +96 -0
  147. package/hooks/ponytail-fix.sh +179 -0
  148. package/hooks/ponytail-guard.sh +66 -0
  149. package/hooks/ponytail-scan.sh +62 -0
  150. package/install.sh +276 -0
  151. package/package.json +20 -0
  152. package/packages/cls/bin/cls.js +146 -0
  153. package/packages/cls/config/defaults.json +17 -0
  154. package/packages/cls/mcp/server.js +142 -0
  155. package/packages/cls/package.json +29 -0
  156. package/packages/cls/scripts/postinstall.js +19 -0
  157. package/packages/cls/src/client.js +262 -0
  158. package/prompts/1-spec.md +271 -0
  159. package/prompts/1-update.md +198 -0
  160. package/prompts/2-qa.md +164 -0
  161. package/prompts/3-impl-bugfix.md +271 -0
  162. package/prompts/3-impl.md +419 -0
  163. package/prompts/4-verify.md +160 -0
  164. package/prompts/5-done.md +227 -0
  165. package/prompts/6-article.md +571 -0
  166. package/prompts/X-add-command.md +60 -0
  167. package/prompts/X-add-to-hc.md +178 -0
  168. package/prompts/X-analysis-cls-log.md +68 -0
  169. package/prompts/X-bugfix.md +10 -0
  170. package/prompts/X-code-simplifier.md +38 -0
  171. package/prompts/X-commit.md +116 -0
  172. package/prompts/X-compound.md +188 -0
  173. package/prompts/X-context-scan.md +101 -0
  174. package/prompts/X-diagnose.md +116 -0
  175. package/prompts/X-grill-with-docs.md +120 -0
  176. package/prompts/X-handoff.md +80 -0
  177. package/prompts/X-health.md +78 -0
  178. package/prompts/X-learn.md +151 -0
  179. package/prompts/X-review.md +417 -0
  180. package/prompts/X-search.md +74 -0
  181. package/prompts/X-strategy.md +95 -0
  182. package/prompts/X-table2xlsx.md +46 -0
  183. package/prompts/X-test-plan.md +436 -0
  184. package/prompts/X-travel-planner.md +22 -0
  185. package/prompts/X-xlsx2sql-insert.md +34 -0
  186. package/prompts/X-xlsx2sql-update.md +40 -0
  187. package/prompts/X-zoom-out.md +119 -0
  188. package/reference/diagnose-loop.md +66 -0
  189. package/reference/gates.md +90 -0
  190. package/reference/git-guardrails.md +71 -0
  191. package/reference/guards.md +275 -0
  192. package/reference/learn.md +100 -0
  193. package/reference/memory.md +190 -0
  194. package/reference/risk.md +74 -0
  195. package/reference/tdd.md +40 -0
  196. package/scripts/svg2png.js +46 -0
  197. package/src/cls/client.js +244 -0
  198. package/src/constants.js +49 -0
  199. package/src/evidence/ai-draft-metrics.js +157 -0
  200. package/src/evidence/canvas.js +184 -0
  201. package/src/evidence/index.js +98 -0
  202. package/src/evidence/sink-events.js +165 -0
  203. package/src/evidence/stage-timing.js +112 -0
  204. package/src/installers/claude.js +79 -0
  205. package/src/installers/codex.js +62 -0
  206. package/src/installers/opencode.js +72 -0
  207. package/src/manifest/ponytail-patterns.json +185 -0
  208. package/src/mcp/cls-mcp-server.js +142 -0
  209. package/src/utils.js +79 -0
  210. package/src/validators/state-validate.js +123 -0
  211. package/templates/.phase-state.json +13 -0
  212. package/templates/adr.md +62 -0
  213. package/templates/changelog.md +21 -0
  214. package/templates/concepts.md +35 -0
  215. package/templates/design.md +47 -0
  216. package/templates/handoff.md +64 -0
  217. package/templates/learn-skill.md +43 -0
  218. package/templates/progress.json +17 -0
  219. package/templates/proposal.md +29 -0
  220. package/templates/solution.md +36 -0
  221. package/templates/strategy.md +39 -0
  222. package/templates/tasks.md +41 -0
@@ -0,0 +1,104 @@
1
+ #!/bin/sh
2
+ # handoff-compress.sh — design→build context compression
3
+ # Generates compact handoff package with SHA256 hashes instead of full content.
4
+ # Output: openspec/changes/<name>/.comet/handoff/context.json
5
+ #
6
+ # Usage: handoff-compress.sh <change-name>
7
+
8
+ DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
9
+ STATE_SH="$DIR/atd-state.sh"
10
+
11
+ die() { echo "$@" >&2; exit 1; }
12
+
13
+ _name="${1:-}"
14
+ [ -z "$_name" ] && die "Usage: handoff-compress.sh <change-name>"
15
+
16
+ _d="openspec/changes/$_name"
17
+ [ -d "$_d" ] || die "change directory not found: $_d"
18
+
19
+ _handoff_dir="$_d/.comet/handoff"
20
+ mkdir -p "$_handoff_dir"
21
+
22
+ # SHA256 via shasum (macOS) or sha256sum (Linux)
23
+ _sha256() {
24
+ if command -v shasum >/dev/null 2>&1; then
25
+ shasum -a 256 "$1" | awk '{print $1}'
26
+ elif command -v sha256sum >/dev/null 2>&1; then
27
+ sha256sum "$1" | awk '{print $1}'
28
+ else
29
+ node -e "console.log(require('crypto').createHash('sha256').update(require('fs').readFileSync('$1')).digest('hex'))" 2>/dev/null || echo "unknown"
30
+ fi
31
+ }
32
+
33
+ # Collect source file info
34
+ _specs="["
35
+ _first=true
36
+ for f in "$_d"/specs/*/spec.md; do
37
+ [ -f "$f" ] || continue
38
+ _hash=$(_sha256 "$f")
39
+ _rel=$(echo "$f" | sed "s|$_d/||")
40
+ if $_first; then _first=false; else _specs="$_specs,"; fi
41
+ _specs="$_specs{\"file\":\"$_rel\",\"sha256\":\"$_hash\"}"
42
+ done
43
+ _specs="$_specs]"
44
+
45
+ # Design doc
46
+ _design="null"
47
+ [ -f "$_d/design.md" ] && _design="{\"file\":\"design.md\",\"sha256\":\"$(_sha256 "$_d/design.md")\"}"
48
+
49
+ # Tasks summary
50
+ _tasks_summary() {
51
+ local total completed remaining
52
+ [ -f "$_d/tasks.md" ] || { echo '{"total":0,"completed":0,"remaining":0,"titles":[]}'; return 0; }
53
+ total=$(grep -c '^\- \[' "$_d/tasks.md" 2>/dev/null | tr -d ' [:space:]' || echo 0)
54
+ [ -z "$total" ] && total=0
55
+ completed=$(grep -c '^\- \[x\]' "$_d/tasks.md" 2>/dev/null | tr -d ' [:space:]' || echo 0)
56
+ [ -z "$completed" ] && completed=0
57
+ remaining=$((total - completed))
58
+ # Extract task titles
59
+ local titles="["
60
+ local first_t=true
61
+ while IFS= read -r line; do
62
+ case "$line" in
63
+ "### "*)
64
+ local t=${line#"### "}
65
+ if $first_t; then first_t=false; else titles="$titles,"; fi
66
+ titles="$titles\"$t\""
67
+ ;;
68
+ esac
69
+ done < "$_d/tasks.md"
70
+ titles="$titles]"
71
+ echo "{\"total\":$total,\"completed\":$completed,\"remaining\":$remaining,\"titles\":$titles}"
72
+ }
73
+
74
+ _tasks=$(_tasks_summary)
75
+
76
+ # Proposal summary (first 3 lines after ##)
77
+ _proposal="null"
78
+ if [ -f "$_d/proposal.md" ]; then
79
+ _summary=$(grep -A2 "^## 背景与目标" "$_d/proposal.md" 2>/dev/null | tail -n +2 | tr '\n' ' ' || echo "")
80
+ [ -n "$_summary" ] && _proposal="\"$_summary\""
81
+ fi
82
+
83
+ cat > "$_handoff_dir/context.json" << JSONEOF
84
+ {
85
+ "change": "$_name",
86
+ "generatedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")",
87
+ "specs": $_specs,
88
+ "design": $_design,
89
+ "proposalSummary": $_proposal,
90
+ "tasks": $_tasks
91
+ }
92
+ JSONEOF
93
+
94
+ echo "handoff context generated: $_handoff_dir/context.json"
95
+
96
+ # Size report
97
+ _src_size=$(cat "$_d"/specs/*/spec.md "$_d"/design.md 2>/dev/null | wc -c | tr -d ' ')
98
+ _hf_size=$(wc -c < "$_handoff_dir/context.json" | tr -d ' ')
99
+ echo " source total: $_src_size bytes"
100
+ echo " handoff size: $_hf_size bytes"
101
+ if [ "$_src_size" -gt 0 ]; then
102
+ _ratio=$((_hf_size * 100 / _src_size))
103
+ echo " ratio: $_ratio%"
104
+ fi
@@ -0,0 +1,134 @@
1
+ #!/bin/sh
2
+ # phase-guard.sh — phase transition validation gate
3
+ # Usage:
4
+ # phase-guard.sh <change-name> <target-phase> check if can enter target phase
5
+ # phase-guard.sh --report <change-name> show full status summary
6
+
7
+ set -e
8
+
9
+ DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
10
+ STATE_SH="$DIR/atd-state.sh"
11
+
12
+ die() { echo "[HARD STOP] $*" >&2; exit 1; }
13
+ ok() { echo " ✓ $*"; }
14
+ warn_line() { echo " ⚠ $*"; }
15
+
16
+ _get_state() { sh "$STATE_SH" get "$1" "$2"; }
17
+
18
+ # ── guard: build ──
19
+ guard_build() {
20
+ local d="$1"
21
+ local mode
22
+ mode=$(_get_state "$name" "mode")
23
+ if [ "$mode" = "bugfix" ]; then
24
+ if [ -f "$d/bug-report.md" ]; then
25
+ ok "bug-report.md exists (bugfix mode)"
26
+ echo "build guard: passed (bugfix)"
27
+ return
28
+ else
29
+ die "bugfix requires bug-report.md"
30
+ fi
31
+ fi
32
+ local ok_count=0 fail_count=0
33
+ for f in proposal.md design.md tasks.md; do
34
+ if [ -f "$d/$f" ]; then
35
+ ok "$f exists"; ok_count=$((ok_count + 1))
36
+ else
37
+ warn_line "$f missing"; fail_count=$((fail_count + 1))
38
+ fi
39
+ done
40
+ # check specs
41
+ if ls "$d/specs"/*/spec.md >/dev/null 2>&1; then
42
+ ok "specs/ exists"; ok_count=$((ok_count + 1))
43
+ else
44
+ warn_line "specs/*/spec.md missing"; fail_count=$((fail_count + 1))
45
+ fi
46
+ [ "$fail_count" -gt 0 ] && die "build guard failed: $fail_count missing files"
47
+ echo "build guard: $ok_count checks passed"
48
+ }
49
+
50
+ # ── guard: verify ──
51
+ guard_verify() {
52
+ local d="$1"
53
+ # check incomplete tasks
54
+ local incomplete
55
+ incomplete=$(grep -c '^\- \[ \]' "$d/tasks.md" 2>/dev/null | tr -d ' [:space:]' || echo 0)
56
+ [ -z "$incomplete" ] && incomplete=0
57
+ if [ "$incomplete" -gt 0 ]; then
58
+ die "verify guard failed: $incomplete incomplete tasks in tasks.md"
59
+ fi
60
+ ok "all tasks complete"
61
+ # check verification report
62
+ local vr
63
+ vr=$(_get_state "$name" "verificationReport")
64
+ if [ -z "$vr" ] || [ "$vr" = "null" ]; then
65
+ die "verify guard failed: verificationReport not set"
66
+ fi
67
+ [ -f "$d/$vr" ] && ok "verificationReport $vr exists" || die "verify guard failed: $vr not found"
68
+ echo "verify guard: passed"
69
+ }
70
+
71
+ # ── guard: archive ──
72
+ guard_archive() {
73
+ local phase
74
+ phase=$(_get_state "$name" "phase")
75
+ [ "$phase" = "verify" ] || die "archive guard failed: phase must be verify, got $phase"
76
+ ok "phase is verify"
77
+ local bs
78
+ bs=$(_get_state "$name" "branchStatus")
79
+ [ "$bs" = "handled" ] || die "archive guard failed: branchStatus must be handled, got $bs"
80
+ ok "branchStatus is handled"
81
+ local vr
82
+ vr=$(_get_state "$name" "verificationReport")
83
+ [ -n "$vr" ] && [ "$vr" != "null" ] || die "archive guard failed: verificationReport not set"
84
+ ok "verificationReport set"
85
+ echo "archive guard: passed"
86
+ }
87
+
88
+ # ── report ──
89
+ cmd_report() {
90
+ local name="$1" d
91
+ d="openspec/changes/$name"
92
+ [ -d "$d" ] || die "change directory not found: $d"
93
+ local phase mode arch tasks_completed tasks_total vr bs
94
+ phase=$(_get_state "$name" "phase")
95
+ mode=$(_get_state "$name" "mode")
96
+ arch=$(_get_state "$name" "archived")
97
+ tasks_completed=$(_get_state "$name" "tasks.completed")
98
+ tasks_total=$(_get_state "$name" "tasks.total")
99
+ vr=$(_get_state "$name" "verificationReport")
100
+ bs=$(_get_state "$name" "branchStatus")
101
+
102
+ echo "📋 Phase Guard Report — $name"
103
+ echo " phase: $phase"
104
+ echo " mode: $mode"
105
+ echo " tasks: ${tasks_completed:-0}/${tasks_total:-0}"
106
+ echo " archived: $arch"
107
+ echo " branch: ${bs:-pending}"
108
+ echo " verify rpt: ${vr:-none}"
109
+ echo " directory: $d"
110
+ }
111
+
112
+ # ── main ──
113
+ case "${1:-}" in
114
+ --report)
115
+ [ -z "$2" ] && die "Usage: phase-guard.sh --report <change-name>"
116
+ shift
117
+ cmd_report "$1"
118
+ ;;
119
+ *)
120
+ [ -z "$2" ] && die "Usage: phase-guard.sh <change-name> <target-phase>"
121
+ name="$1"
122
+ target="$2"
123
+ d="openspec/changes/$name"
124
+ [ -d "$d" ] || die "change directory not found: $d"
125
+ echo "⚙ phase-guard: checking $name → $target"
126
+ case "$target" in
127
+ build) guard_build "$d";;
128
+ verify) guard_verify "$d";;
129
+ archive) guard_archive "$d";;
130
+ *) die "unknown target phase: $target (valid: build, verify, archive)";;
131
+ esac
132
+ echo "✅ phase guard passed: $target"
133
+ ;;
134
+ esac
@@ -0,0 +1,96 @@
1
+ #!/bin/sh
2
+ # phase-write-guard.sh — PreToolUse hook for write protection
3
+ # Blocks file writes in restricted phases:
4
+ # open/design: blocks paths listed in $ATDC_DIR/config.yaml write_guard.paths
5
+ # archive: blocks openspec/changes/
6
+ # build: all paths allowed
7
+ #
8
+ # Global config at $ATDC_DIR/config.yaml (no project-level files):
9
+ # write_guard:
10
+ # paths: [src, lib, pkg, internal, cmd] # default if not set
11
+ #
12
+ # Usage:
13
+ # phase-write-guard.sh <file-path>
14
+ # PHASE=<phase> phase-write-guard.sh <file-path> # override for testing
15
+ #
16
+ # Exit: 0 = allowed, 1 = blocked
17
+
18
+ DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
19
+ STATE_SH="$DIR/atd-state.sh"
20
+
21
+ # Default protected paths (covers JS/TS + Go projects)
22
+ _DEFAULT_PATHS="src/ lib/ pkg/ internal/ cmd/"
23
+
24
+ _load_config() {
25
+ if [ -n "$ATDC_DIR" ] && [ -f "$ATDC_DIR/config.yaml" ]; then
26
+ _config_file="$ATDC_DIR/config.yaml"
27
+ elif [ -f "$DIR/../config.yaml" ]; then
28
+ # fallback: config.yaml next to hooks/ directory
29
+ _config_file="$DIR/../config.yaml"
30
+ else
31
+ echo "$_DEFAULT_PATHS"
32
+ return
33
+ fi
34
+ if command -v node >/dev/null 2>&1; then
35
+ node -e "
36
+ var fs=require('fs'),yaml=fs.readFileSync('$_config_file','utf8');
37
+ var m=yaml.match(/write_guard:\s*\n(\s+paths:\s*\[([^\]]+)\])?/);
38
+ if(m&&m[2]) console.log(m[2].split(',').map(s=>s.trim().replace(/['\"]/g,'')+'/').join(' '));
39
+ else console.log('$_DEFAULT_PATHS');
40
+ " 2>/dev/null || echo "$_DEFAULT_PATHS"
41
+ else
42
+ echo "$_DEFAULT_PATHS"
43
+ fi
44
+ }
45
+
46
+ _detect_phase() {
47
+ for d in openspec/changes/*/; do
48
+ [ -d "$d" ] || continue
49
+ local name
50
+ name=$(basename "$d")
51
+ if [ -f "$d.phase-state.json" ]; then
52
+ sh "$STATE_SH" get "$name" phase 2>/dev/null && return 0
53
+ fi
54
+ done
55
+ echo "build"
56
+ }
57
+
58
+ _protected="$(_load_config)"
59
+ _phase="${PHASE:-$(_detect_phase)}"
60
+ _file="${1:-}"
61
+
62
+ [ -z "$_file" ] && { echo "Usage: phase-write-guard.sh <file-path>" >&2; exit 1; }
63
+
64
+ # Normalize path
65
+ _file=$(echo "$_file" | sed 's|^\./||')
66
+
67
+ case "$_phase" in
68
+ open|design)
69
+ for p in $_protected; do
70
+ case "$_file" in
71
+ $p*)
72
+ echo "[WRITE GUARD] BLOCKED: phase=$_phase prohibits writing to $_file (protected: $p)" >&2
73
+ exit 1
74
+ ;;
75
+ esac
76
+ done
77
+ exit 0
78
+ ;;
79
+ archive)
80
+ case "$_file" in
81
+ openspec/changes/*)
82
+ echo "[WRITE GUARD] BLOCKED: phase=$_phase prohibits writing to $_file" >&2
83
+ exit 1
84
+ ;;
85
+ *)
86
+ exit 0
87
+ ;;
88
+ esac
89
+ ;;
90
+ build|verify)
91
+ exit 0
92
+ ;;
93
+ *)
94
+ exit 0
95
+ ;;
96
+ esac
@@ -0,0 +1,179 @@
1
+ #!/bin/sh
2
+ # ponytail-fix.sh — apply generic refactors automatically
3
+ # Usage: ponytail-fix.sh [--dry-run] --pattern=<id>
4
+ # --dry-run preview only
5
+ # --pattern=<id> one of: crud | rpc | di
6
+
7
+ set -e
8
+
9
+ DRY=false
10
+ PATTERN=""
11
+
12
+ for arg in "$@"; do
13
+ case "$arg" in
14
+ --dry-run) DRY=true ;;
15
+ --pattern=*) PATTERN="${arg#*=}" ;;
16
+ esac
17
+ done
18
+
19
+ TMP="/tmp/ponytail-fix"
20
+ mkdir -p "$TMP"
21
+
22
+ fix_crud() {
23
+ echo "📦 CRUD → BaseModel[T]"
24
+ count=$(grep -rn "func.*Find.*ctx\.Context\|func.*Find.*context\.Context" models/ --include="*.go" 2>/dev/null | wc -l | tr -d ' ')
25
+ if $DRY; then
26
+ echo " DRY: 将为 $count model 提取 BaseModel[T],预估减少 ~$((count * 40)) 行"
27
+ return
28
+ fi
29
+ cat > "$TMP/base_model.go" << 'GOEOF'
30
+ package models
31
+
32
+ import (
33
+ "context"
34
+ "fmt"
35
+ "git.kugou.net/opensource/kugo/model"
36
+ )
37
+
38
+ // BaseModel provides generic CRUD for GORM models with sharding.
39
+ // Embed in your struct to inherit Create/Find/Update/Delete.
40
+ type BaseModel[T any] struct {
41
+ ShardingVal *int64
42
+ ShardingBiz string
43
+ TableBase string
44
+ }
45
+
46
+ func (m *BaseModel[T]) db(ctx context.Context) *gorm.DB {
47
+ ms := models.CreateModSharding(m.ShardingBiz, models.ModShardingParam{
48
+ ShardingVal: m.ShardingVal, ShardingBiz: m.ShardingBiz, ShardingSubBiz: m.ShardingBiz,
49
+ })
50
+ dbName := fmt.Sprintf("%s_%s", ms.GetDbRoute(), ms.GetDbSuffix())
51
+ tableName := fmt.Sprintf("%s_%s", m.TableBase, ms.GetTableSuffix())
52
+ return model.Db(dbName).Table(tableName).WithContext(ctx)
53
+ }
54
+
55
+ func (m *BaseModel[T]) Find(ctx context.Context, where any, args ...any) ([]*T, error) {
56
+ var data []*T
57
+ err := m.db(ctx).Where(where, args...).Find(&data).Error
58
+ return data, err
59
+ }
60
+
61
+ func (m *BaseModel[T]) FindOne(ctx context.Context, where any, args ...any) error {
62
+ return m.db(ctx).Where(where, args...).Limit(1).Find(new(T)).Error
63
+ }
64
+
65
+ func (m *BaseModel[T]) Create(ctx context.Context, val *T) error {
66
+ return m.db(ctx).Create(val).Error
67
+ }
68
+
69
+ func (m *BaseModel[T]) Updates(ctx context.Context, where, data map[string]any) error {
70
+ tx := m.db(ctx)
71
+ for q, arg := range where { tx = tx.Where(q, arg) }
72
+ return tx.Model(new(T)).Updates(data).Error
73
+ }
74
+
75
+ func (m *BaseModel[T]) UpdateOne(ctx context.Context, where, data map[string]any) (int64, error) {
76
+ tx := m.db(ctx)
77
+ for q, arg := range where { tx = tx.Where(q, arg) }
78
+ res := tx.Model(new(T)).Limit(1).Updates(data)
79
+ return res.RowsAffected, res.Error
80
+ }
81
+ GOEOF
82
+ echo " 已生成: $TMP/base_model.go"
83
+ }
84
+
85
+ fix_rpc() {
86
+ echo "📦 RPC → callLocalRPC[T]"
87
+ count=$(grep -rn "\.Post(" service/rpc/ --include="*.go" 2>/dev/null | wc -l | tr -d ' ')
88
+ if $DRY; then
89
+ echo " DRY: 将为 $count RPC 函数提取 callLocalRPC[T],预估减少 ~$((count * 25)) 行"
90
+ return
91
+ fi
92
+ cat > "$TMP/call_rpc.go" << 'GOEOF'
93
+ package rpc
94
+
95
+ import (
96
+ "context" "encoding/json" "strconv"
97
+ "ticket_center_server/internal/cerror" "ticket_center_server/utils"
98
+ "git.kugou.net/opensource/kugo/httpclient" "git.kugou.net/opensource/kugo/gerror"
99
+ )
100
+
101
+ type LocalResponse[T any] struct {
102
+ ErrCode int `json:"err_code"`
103
+ Status int `json:"status"`
104
+ ErrMsg string `json:"err_msg"`
105
+ Data T `json:"data"`
106
+ }
107
+
108
+ func callLocalRPC[T any](ctx context.Context, path string, reqBody any) (*T, error) {
109
+ rpcConfig := utils.Rpc("local")
110
+ url := rpcConfig.String("url", "http://ticketcenter.kgidc.cn") + path
111
+ host := rpcConfig.String("host", "")
112
+ req := httpclient.Post(url)
113
+ if len(host) > 0 { req = req.SetHost(host) }
114
+ jsonBytes, _ := json.Marshal(reqBody)
115
+ req.Body(jsonBytes)
116
+ req.FillingDefaultServerCommonParams()
117
+ respStr, err := req.String()
118
+ if err != nil {
119
+ utils.LoggerWithCtx(ctx).Errorf("RPC net: curl=%s err=%v", utils.GenCURL(req.GetRequest(), string(jsonBytes)), err)
120
+ return nil, cerror.RpcNetErr
121
+ }
122
+ var resp LocalResponse[T]
123
+ if err := json.Unmarshal([]byte(respStr), &resp); err != nil {
124
+ return nil, gerror.NewCode(cerror.RpcServerErr)
125
+ }
126
+ if resp.ErrCode != 0 || resp.Status != 1 {
127
+ return nil, gerror.NewCode(cerror.RpcServerErrCodeErr.SetMsg(strconv.Itoa(resp.ErrCode)+":"+resp.ErrMsg))
128
+ }
129
+ return &resp.Data, nil
130
+ }
131
+ GOEOF
132
+ echo " 已生成: $TMP/call_rpc.go"
133
+ }
134
+
135
+ fix_di() {
136
+ echo "📦 DI → 泛型容器"
137
+ reg=$(grep -rn "func Register" service/biz/ --include="*.go" 2>/dev/null | wc -l | tr -d ' ')
138
+ initc=$(find service/biz/ -name "*_init.go" 2>/dev/null | wc -l | tr -d ' ')
139
+ if $DRY; then
140
+ echo " DRY: $reg Register + $initc init 文件 → ~$((initc)) 文件删除"
141
+ return
142
+ fi
143
+ cat > "$TMP/di_container.go" << 'GOEOF'
144
+ package di
145
+
146
+ import "reflect"
147
+
148
+ var services = map[reflect.Type]any{}
149
+
150
+ func Get[T any]() T {
151
+ return services[reflect.TypeOf((*T)(nil)).Elem()].(T)
152
+ }
153
+
154
+ func Register[T any](impl T) {
155
+ services[reflect.TypeOf((*T)(nil)).Elem()] = impl
156
+ }
157
+
158
+ // Register all implementations here:
159
+ // func init() {
160
+ // Register[IOrder](order.NewOrderImpl())
161
+ // Register[ITicket](ticket.NewTicketImpl())
162
+ // }
163
+ GOEOF
164
+ echo " 已生成: $TMP/di_container.go"
165
+ echo " 手动: 将各 service 的 init() 内容移至此 init(),删除 *_init.go 文件"
166
+ }
167
+
168
+ case "$PATTERN" in
169
+ crud) fix_crud ;;
170
+ rpc) fix_rpc ;;
171
+ di) fix_di ;;
172
+ "")
173
+ echo "Usage: ponytail-fix.sh [--dry-run] --pattern=<crud|rpc|di>"
174
+ exit 1
175
+ ;;
176
+ *) echo "Unknown pattern: $PATTERN"; exit 1 ;;
177
+ esac
178
+ echo ""
179
+ echo "📝 将 $TMP/ 下生成的文件复制到项目目录即可"
@@ -0,0 +1,66 @@
1
+ #!/bin/sh
2
+ # ponytail-guard.sh — enforce ponytail rules before build phase
3
+ # Usage: ponytail-guard.sh [--strict|--warn]
4
+
5
+ set -e
6
+
7
+ MODE="${1:---warn}"
8
+ PROJECT="${2:-.}"
9
+ HARD=0
10
+
11
+ _c() { command grep -rn "$1" "$PROJECT/$2" --include="*.go" 2>/dev/null | wc -l | tr -d ' '; }
12
+
13
+ echo "⚙ Ponytail Guard ($MODE)"
14
+ echo ""
15
+
16
+ # CRUD
17
+ n=$(_c "func.*Find.*ctx\.Context\|func.*Find.*context\.Context" "models/")
18
+ if [ "$n" -gt 10 ]; then
19
+ if [ "$MODE" = "--strict" ]; then
20
+ echo "[HARD STOP] CRUD: $n models 重复 → 必须先提取 BaseModel[T]"; HARD=1
21
+ else
22
+ echo "⚠️ CRUD: $n models 重复 (建议 BaseModel[T])"
23
+ fi
24
+ fi
25
+
26
+ # RPC
27
+ n=$(_c "httpclient\.Post\|\\.Post(" "service/rpc/")
28
+ if [ "$n" -gt 3 ]; then
29
+ if [ "$MODE" = "--strict" ]; then
30
+ echo "[HARD STOP] RPC: $n funcs 同构 → 必须先提取 callLocalRPC[T]"; HARD=1
31
+ else
32
+ echo "⚠️ RPC: $n funcs 同构 (建议 callLocalRPC[T])"
33
+ fi
34
+ fi
35
+
36
+ # DI
37
+ reg=$(_c "func Register" "service/biz/")
38
+ init=$(find "$PROJECT/service/biz/" -name "*_init.go" 2>/dev/null | wc -l | tr -d ' ')
39
+ if [ "$reg" -gt 5 ] || [ "$init" -gt 5 ]; then
40
+ if [ "$MODE" = "--strict" ]; then
41
+ echo "[HARD STOP] DI: $reg Register + $init init → 必须先提取 DI 容器"; HARD=1
42
+ else
43
+ echo "⚠️ DI: $reg Register + $init init (建议 DI 容器)"
44
+ fi
45
+ fi
46
+
47
+ # Error framework
48
+ gerr=$(ls "$PROJECT/internal/errors/gerror/" 2>/dev/null | wc -l | tr -d ' ')
49
+ gcode=$(ls "$PROJECT/internal/errors/gcode/" 2>/dev/null | wc -l | tr -d ' ')
50
+ total=$((gerr + gcode))
51
+ if [ "$total" -gt 3 ]; then
52
+ if [ "$MODE" = "--strict" ]; then
53
+ echo "[HARD STOP] Error: $total files 冗余 → stdlib errors"; HARD=1
54
+ else
55
+ echo "⚠️ Error: $total files 冗余 (建议 stdlib errors)"
56
+ fi
57
+ fi
58
+
59
+ echo ""
60
+ if [ "$HARD" -eq 1 ]; then
61
+ echo "🔒 发现阻塞项"
62
+ echo " 修复: atdc ponytail fix --dry-run (预览)"
63
+ echo " 修复: atdc ponytail fix (执行)"
64
+ exit 1
65
+ fi
66
+ echo "✅ Ponytail guard: passed"
@@ -0,0 +1,62 @@
1
+ #!/bin/sh
2
+ # ponytail-scan.sh — scan project for backend over-engineering patterns
3
+ # Usage: ponytail-scan.sh [--diff]
4
+
5
+ set -e
6
+
7
+ MODE="${1:---full}"
8
+ PROJECT="${2:-.}"
9
+ FAIL=0
10
+
11
+ _c() { command grep -rn "$1" "$PROJECT/$2" --include="*.go" 2>/dev/null | wc -l | tr -d ' '; }
12
+ _d() { find "$PROJECT/$1" -name "*.go" 2>/dev/null | wc -l | tr -d ' '; }
13
+
14
+ count_crud() { _c "func.*Find.*ctx\.Context\|func.*Find.*context\.Context" "models/"; }
15
+ count_rpc() { _c "httpclient\.Post\|\\.Post(" "service/rpc/"; }
16
+ count_di() { _c "func Register" "service/biz/"; }
17
+ count_init() { find "$PROJECT/service/biz/" -name "*_init.go" 2>/dev/null | wc -l | tr -d ' '; }
18
+ count_gerror() { ls "$PROJECT/internal/errors/gerror/" 2>/dev/null | wc -l | tr -d ' '; }
19
+ count_gcode() { ls "$PROJECT/internal/errors/gcode/" 2>/dev/null | wc -l | tr -d ' '; }
20
+ count_whitelist() { _c "BuildWhitelistExcludeSet" "service/"; }
21
+ count_contains() { _c "func.*Contains\[" "utils/"; }
22
+ count_time() { _c "TimeToString\|GetLevelTimeFormat" "utils/"; }
23
+ count_phone() { _c "FormatPhoneNumber" "utils/"; }
24
+ count_gencurl() { _c "func.*GenCURL" "utils/"; }
25
+ count_serverip() { _c "getServerIP" "utils/"; }
26
+ count_config() { _c "type Apollo.*Config \[" "service/rtconf/"; }
27
+ count_retry() { _c "retry" "utils/"; }
28
+
29
+ echo "⚙ atdc Ponytail Scanner"
30
+ echo " project: $PROJECT"
31
+ echo ""
32
+
33
+ # P0
34
+ n=$(count_crud)
35
+ if [ "$n" -gt 10 ]; then echo "🔴 [P0] CRUD 模板: $n models → BaseModel[T]"; FAIL=1; fi
36
+
37
+ n=$(count_rpc)
38
+ if [ "$n" -gt 3 ]; then echo "🔴 [P0] RPC 模板: $n funcs → callLocalRPC[T]"; FAIL=1; fi
39
+
40
+ # P1
41
+ n=$(count_di); m=$(count_init)
42
+ if [ "$n" -gt 5 ] || [ "$m" -gt 5 ]; then echo "🟡 [P1] DI 样板: $n Register + $m init → DI 容器"; FAIL=1; fi
43
+
44
+ n=$(( $(count_gerror) + $(count_gcode) ))
45
+ if [ "$n" -gt 3 ]; then echo "🟡 [P1] 错误框架: $n files → stdlib errors"; FAIL=1; fi
46
+
47
+ n=$(count_whitelist)
48
+ [ "$n" -gt 2 ] && echo "🟡 [P1] 白名单 filter: $n dup → 泛型"
49
+
50
+ # P2
51
+ n=$(count_contains); [ "$n" -gt 0 ] && echo "🟢 [P2] 自定义 Contains($n) → slices.Contains"
52
+ n=$(count_time); [ "$n" -gt 0 ] && echo "🟢 [P2] Time 工具($n) → time.Format"
53
+ n=$(count_phone); [ "$n" -gt 0 ] && echo "🟢 [P2] Phone wrapper → 直调 nyaruka/phonenumbers"
54
+ n=$(count_gencurl); [ "$n" -gt 0 ] && echo "🟢 [P2] GenCURL → HTTP 库内建"
55
+ n=$(count_serverip); [ "$n" -gt 0 ] && echo "🟢 [P2] getServerIP → net.InterfaceAddrs"
56
+ n=$(count_config); [ "$n" -gt 5 ] && echo "🟢 [P2] Config type($n) → 合并"
57
+ n=$(count_retry); [ "$n" -gt 0 ] && echo "🟢 [P2] Custom retry → cenkalti/backoff"
58
+
59
+ echo ""
60
+ echo "──"
61
+ [ "$FAIL" -eq 0 ] && echo "✅ 无 P0/P1 阻塞项" || echo "🔒 发现阻塞项 — atdc ponytail fix"
62
+ [ "$FAIL" -ne 0 ] && exit 1