@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.
- package/AGENTS.md +58 -0
- package/GO.md +245 -0
- package/bin/add-command.py +144 -0
- package/bin/atdc.js +460 -0
- package/bin/cls.js +98 -0
- package/central/link.sh +60 -0
- package/central/ticket_center_server/.handoffs/TASK-238389_QYinVipLimit.md +128 -0
- package/central/ticket_center_server/.test_plans/TASK-227254-SupportMembership-self-test-plan.md +566 -0
- package/central/ticket_center_server/.test_plans/task-238389-qyin-vip-limit-self-test-plan.md +210 -0
- package/commands/claude/atd.md +3 -0
- package/commands/claude/atdc/1-spec.md +5 -0
- package/commands/claude/atdc/1-update.md +5 -0
- package/commands/claude/atdc/2-qa.md +3 -0
- package/commands/claude/atdc/3-impl-skip-spec.md +5 -0
- package/commands/claude/atdc/3-impl.md +3 -0
- package/commands/claude/atdc/4-verify.md +3 -0
- package/commands/claude/atdc/5-done.md +3 -0
- package/commands/claude/atdc/add-command.md +4 -0
- package/commands/claude/atdc/add-to-hc.md +3 -0
- package/commands/claude/atdc/analysis-cls-log.md +4 -0
- package/commands/claude/atdc/article.md +6 -0
- package/commands/claude/atdc/bugfix.md +4 -0
- package/commands/claude/atdc/code-simplifier.md +8 -0
- package/commands/claude/atdc/commit.md +4 -0
- package/commands/claude/atdc/compound.md +5 -0
- package/commands/claude/atdc/context.md +3 -0
- package/commands/claude/atdc/diagnose.md +8 -0
- package/commands/claude/atdc/git-guardrails.md +18 -0
- package/commands/claude/atdc/grill-with-docs.md +8 -0
- package/commands/claude/atdc/handoff.md +8 -0
- package/commands/claude/atdc/health.md +3 -0
- package/commands/claude/atdc/learn.md +4 -0
- package/commands/claude/atdc/review.md +10 -0
- package/commands/claude/atdc/search.md +4 -0
- package/commands/claude/atdc/strategy.md +3 -0
- package/commands/claude/atdc/table2xlsx.md +4 -0
- package/commands/claude/atdc/test-plan.md +5 -0
- package/commands/claude/atdc/travel-planner.md +4 -0
- package/commands/claude/atdc/xlsx2sql-insert.md +4 -0
- package/commands/claude/atdc/xlsx2sql-update.md +4 -0
- package/commands/claude/atdc/zoom-out.md +8 -0
- package/commands/codex/atdc/SKILL.md +8 -0
- package/commands/codex/atdc-1-spec/SKILL.md +8 -0
- package/commands/codex/atdc-1-update/SKILL.md +8 -0
- package/commands/codex/atdc-2-qa/SKILL.md +8 -0
- package/commands/codex/atdc-3-impl/SKILL.md +8 -0
- package/commands/codex/atdc-3-impl-skip-spec/SKILL.md +8 -0
- package/commands/codex/atdc-4-verify/SKILL.md +8 -0
- package/commands/codex/atdc-5-done/SKILL.md +8 -0
- package/commands/codex/atdc-add-command/SKILL.md +7 -0
- package/commands/codex/atdc-add-to-hc/SKILL.md +8 -0
- package/commands/codex/atdc-analysis-cls-log/SKILL.md +5 -0
- package/commands/codex/atdc-article/SKILL.md +8 -0
- package/commands/codex/atdc-bugfix/SKILL.md +5 -0
- package/commands/codex/atdc-code-simplifier/SKILL.md +7 -0
- package/commands/codex/atdc-commit/SKILL.md +7 -0
- package/commands/codex/atdc-compound/SKILL.md +8 -0
- package/commands/codex/atdc-context/SKILL.md +8 -0
- package/commands/codex/atdc-diagnose/SKILL.md +9 -0
- package/commands/codex/atdc-git-guardrails/SKILL.md +19 -0
- package/commands/codex/atdc-grill-with-docs/SKILL.md +9 -0
- package/commands/codex/atdc-handoff/SKILL.md +9 -0
- package/commands/codex/atdc-health/SKILL.md +8 -0
- package/commands/codex/atdc-learn/SKILL.md +7 -0
- package/commands/codex/atdc-review/SKILL.md +49 -0
- package/commands/codex/atdc-search/SKILL.md +5 -0
- package/commands/codex/atdc-strategy/SKILL.md +8 -0
- package/commands/codex/atdc-table2xlsx/SKILL.md +8 -0
- package/commands/codex/atdc-test-plan/SKILL.md +13 -0
- package/commands/codex/atdc-travel-planner/SKILL.md +7 -0
- package/commands/codex/atdc-xlsx2sql-insert/SKILL.md +8 -0
- package/commands/codex/atdc-xlsx2sql-update/SKILL.md +8 -0
- package/commands/codex/atdc-zoom-out/SKILL.md +9 -0
- package/commands/cursor/atdc/SKILL.md +8 -0
- package/commands/cursor/atdc-1-spec/SKILL.md +8 -0
- package/commands/cursor/atdc-1-update/SKILL.md +8 -0
- package/commands/cursor/atdc-2-qa/SKILL.md +8 -0
- package/commands/cursor/atdc-3-impl/SKILL.md +8 -0
- package/commands/cursor/atdc-3-impl-skip-spec/SKILL.md +8 -0
- package/commands/cursor/atdc-4-verify/SKILL.md +8 -0
- package/commands/cursor/atdc-5-done/SKILL.md +8 -0
- package/commands/cursor/atdc-add-command/SKILL.md +7 -0
- package/commands/cursor/atdc-add-to-hc/SKILL.md +8 -0
- package/commands/cursor/atdc-analysis-cls-log/SKILL.md +5 -0
- package/commands/cursor/atdc-article/SKILL.md +8 -0
- package/commands/cursor/atdc-bugfix/SKILL.md +5 -0
- package/commands/cursor/atdc-code-simplifier/SKILL.md +7 -0
- package/commands/cursor/atdc-commit/SKILL.md +7 -0
- package/commands/cursor/atdc-compound/SKILL.md +8 -0
- package/commands/cursor/atdc-context/SKILL.md +8 -0
- package/commands/cursor/atdc-diagnose/SKILL.md +8 -0
- package/commands/cursor/atdc-git-guardrails/SKILL.md +18 -0
- package/commands/cursor/atdc-grill-with-docs/SKILL.md +8 -0
- package/commands/cursor/atdc-handoff/SKILL.md +8 -0
- package/commands/cursor/atdc-health/SKILL.md +8 -0
- package/commands/cursor/atdc-learn/SKILL.md +7 -0
- package/commands/cursor/atdc-review/SKILL.md +49 -0
- package/commands/cursor/atdc-search/SKILL.md +5 -0
- package/commands/cursor/atdc-strategy/SKILL.md +8 -0
- package/commands/cursor/atdc-table2xlsx/SKILL.md +8 -0
- package/commands/cursor/atdc-test-plan/SKILL.md +7 -0
- package/commands/cursor/atdc-travel-planner/SKILL.md +7 -0
- package/commands/cursor/atdc-xlsx2sql-insert/SKILL.md +8 -0
- package/commands/cursor/atdc-xlsx2sql-update/SKILL.md +8 -0
- package/commands/cursor/atdc-zoom-out/SKILL.md +8 -0
- package/commands/opencode/atd.md +6 -0
- package/commands/opencode/atdc/1-spec.md +8 -0
- package/commands/opencode/atdc/1-update.md +8 -0
- package/commands/opencode/atdc/2-qa.md +6 -0
- package/commands/opencode/atdc/3-impl-skip-spec.md +8 -0
- package/commands/opencode/atdc/3-impl.md +6 -0
- package/commands/opencode/atdc/4-verify.md +6 -0
- package/commands/opencode/atdc/5-done.md +6 -0
- package/commands/opencode/atdc/add-command.md +6 -0
- package/commands/opencode/atdc/add-to-hc.md +6 -0
- package/commands/opencode/atdc/analysis-cls-log.md +6 -0
- package/commands/opencode/atdc/article.md +6 -0
- package/commands/opencode/atdc/bugfix.md +6 -0
- package/commands/opencode/atdc/code-simplifier.md +8 -0
- package/commands/opencode/atdc/commit.md +6 -0
- package/commands/opencode/atdc/compound.md +8 -0
- package/commands/opencode/atdc/context.md +6 -0
- package/commands/opencode/atdc/diagnose.md +8 -0
- package/commands/opencode/atdc/git-guardrails.md +18 -0
- package/commands/opencode/atdc/grill-with-docs.md +8 -0
- package/commands/opencode/atdc/handoff.md +8 -0
- package/commands/opencode/atdc/health.md +6 -0
- package/commands/opencode/atdc/learn.md +6 -0
- package/commands/opencode/atdc/review.md +7 -0
- package/commands/opencode/atdc/search.md +6 -0
- package/commands/opencode/atdc/strategy.md +6 -0
- package/commands/opencode/atdc/table2xlsx.md +6 -0
- package/commands/opencode/atdc/test-plan.md +6 -0
- package/commands/opencode/atdc/travel-planner.md +6 -0
- package/commands/opencode/atdc/xlsx2sql-insert.md +6 -0
- package/commands/opencode/atdc/xlsx2sql-update.md +6 -0
- package/commands/opencode/atdc/zoom-out.md +8 -0
- package/config/cls-defaults.json +19 -0
- package/hooks/atd-archive.sh +86 -0
- package/hooks/atd-state.sh +165 -0
- package/hooks/evidence-hook.sh +88 -0
- package/hooks/evidence-log.sh +54 -0
- package/hooks/guardrails.sh +65 -0
- package/hooks/handoff-compress.sh +104 -0
- package/hooks/phase-guard.sh +134 -0
- package/hooks/phase-write-guard.sh +96 -0
- package/hooks/ponytail-fix.sh +179 -0
- package/hooks/ponytail-guard.sh +66 -0
- package/hooks/ponytail-scan.sh +62 -0
- package/install.sh +276 -0
- package/package.json +20 -0
- package/packages/cls/bin/cls.js +146 -0
- package/packages/cls/config/defaults.json +17 -0
- package/packages/cls/mcp/server.js +142 -0
- package/packages/cls/package.json +29 -0
- package/packages/cls/scripts/postinstall.js +19 -0
- package/packages/cls/src/client.js +262 -0
- package/prompts/1-spec.md +271 -0
- package/prompts/1-update.md +198 -0
- package/prompts/2-qa.md +164 -0
- package/prompts/3-impl-bugfix.md +271 -0
- package/prompts/3-impl.md +419 -0
- package/prompts/4-verify.md +160 -0
- package/prompts/5-done.md +227 -0
- package/prompts/6-article.md +571 -0
- package/prompts/X-add-command.md +60 -0
- package/prompts/X-add-to-hc.md +178 -0
- package/prompts/X-analysis-cls-log.md +68 -0
- package/prompts/X-bugfix.md +10 -0
- package/prompts/X-code-simplifier.md +38 -0
- package/prompts/X-commit.md +116 -0
- package/prompts/X-compound.md +188 -0
- package/prompts/X-context-scan.md +101 -0
- package/prompts/X-diagnose.md +116 -0
- package/prompts/X-grill-with-docs.md +120 -0
- package/prompts/X-handoff.md +80 -0
- package/prompts/X-health.md +78 -0
- package/prompts/X-learn.md +151 -0
- package/prompts/X-review.md +417 -0
- package/prompts/X-search.md +74 -0
- package/prompts/X-strategy.md +95 -0
- package/prompts/X-table2xlsx.md +46 -0
- package/prompts/X-test-plan.md +436 -0
- package/prompts/X-travel-planner.md +22 -0
- package/prompts/X-xlsx2sql-insert.md +34 -0
- package/prompts/X-xlsx2sql-update.md +40 -0
- package/prompts/X-zoom-out.md +119 -0
- package/reference/diagnose-loop.md +66 -0
- package/reference/gates.md +90 -0
- package/reference/git-guardrails.md +71 -0
- package/reference/guards.md +275 -0
- package/reference/learn.md +100 -0
- package/reference/memory.md +190 -0
- package/reference/risk.md +74 -0
- package/reference/tdd.md +40 -0
- package/scripts/svg2png.js +46 -0
- package/src/cls/client.js +244 -0
- package/src/constants.js +49 -0
- package/src/evidence/ai-draft-metrics.js +157 -0
- package/src/evidence/canvas.js +184 -0
- package/src/evidence/index.js +98 -0
- package/src/evidence/sink-events.js +165 -0
- package/src/evidence/stage-timing.js +112 -0
- package/src/installers/claude.js +79 -0
- package/src/installers/codex.js +62 -0
- package/src/installers/opencode.js +72 -0
- package/src/manifest/ponytail-patterns.json +185 -0
- package/src/mcp/cls-mcp-server.js +142 -0
- package/src/utils.js +79 -0
- package/src/validators/state-validate.js +123 -0
- package/templates/.phase-state.json +13 -0
- package/templates/adr.md +62 -0
- package/templates/changelog.md +21 -0
- package/templates/concepts.md +35 -0
- package/templates/design.md +47 -0
- package/templates/handoff.md +64 -0
- package/templates/learn-skill.md +43 -0
- package/templates/progress.json +17 -0
- package/templates/proposal.md +29 -0
- package/templates/solution.md +36 -0
- package/templates/strategy.md +39 -0
- 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
|