create-dwy 0.15.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/bin/index.js +5 -3
  3. package/package.json +3 -2
  4. package/src/searchable-select.js +419 -0
  5. package/src/skills-install.js +48 -7
  6. package/src/sync-all.js +17 -25
  7. package/src/sync.js +38 -32
  8. package/templates/ai-tools/CHANGELOG.md +76 -0
  9. package/templates/ai-tools/CLAUDE.md +27 -0
  10. package/templates/ai-tools/VERSION +1 -0
  11. package/templates/ai-tools/hook-manifests/hooks.json +7 -0
  12. package/templates/ai-tools/hooks/Git/pre-git-commit-ai-signature-check.sh +57 -0
  13. package/templates/ai-tools/rules/Android/dwy-android-core.md +215 -4
  14. package/templates/ai-tools/rules/Android/dwy-android-pitfalls.md +52 -0
  15. package/templates/ai-tools/rules/Docker/dwy-docker.md +52 -1
  16. package/templates/ai-tools/rules/HarmonyOS/dwy-arkts-ui-build.md +50 -1
  17. package/templates/ai-tools/rules/HarmonyOS/dwy-arkts-ui-design.md +139 -0
  18. package/templates/ai-tools/rules/HarmonyOS/dwy-harmony-pitfalls.md +72 -0
  19. package/templates/ai-tools/rules/Python/dwy-python-backend.md +16 -63
  20. package/templates/ai-tools/rules/Python/dwy-python-base-lib.md +89 -0
  21. package/templates/ai-tools/rules/Python/dwy-python-pitfalls.md +49 -0
  22. package/templates/ai-tools/rules/Vue/dwy-vue-base-lib.md +127 -0
  23. package/templates/ai-tools/rules/Vue/dwy-vue-core.md +22 -86
  24. package/templates/ai-tools/rules/Vue/dwy-vue-pitfalls.md +66 -0
  25. package/templates/ai-tools/rules/iOS/dwy-ios-pitfalls.md +7 -0
  26. package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-dependency-freshness.md +59 -0
  27. package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-git-commit.md +9 -6
  28. package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-tdd-development.md +47 -2
  29. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/SKILL.md +511 -0
  30. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/checks-data.md +126 -0
  31. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/checks-network.md +88 -0
  32. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/checks-runtime.md +195 -0
  33. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/security-doc-search.md +179 -0
  34. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_base.sh +39 -0
  35. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_capacity.sh +278 -0
  36. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_db.sh +147 -0
  37. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_docker.sh +230 -0
  38. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_env.sh +64 -0
  39. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_https.sh +60 -0
  40. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_logs.sh +209 -0
  41. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_nginx.sh +100 -0
  42. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_resilience.sh +157 -0
  43. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_secrets.sh +265 -0
  44. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_services.sh +94 -0
  45. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_ssh.sh +56 -0
  46. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/run_all.sh +105 -0
  47. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-acceptance/SKILL.md +3 -2
  48. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-architecture/SKILL.md +51 -9
  49. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-commercial/SKILL.md +58 -0
  50. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-competitor/SKILL.md +2 -1
  51. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-explore/SKILL.md +83 -0
  52. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-mvp/SKILL.md +2 -1
  53. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-prd/SKILL.md +1 -0
  54. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-product-launcher/SKILL.md +14 -9
  55. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-product-launcher/references/state-and-contract.md +65 -13
  56. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-prototype/SKILL.md +30 -11
  57. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-release/SKILL.md +3 -2
  58. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-design/SKILL.md +17 -10
  59. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-dev/SKILL.md +42 -10
  60. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-launch/SKILL.md +22 -10
  61. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-prd/SKILL.md +13 -4
  62. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-ship/SKILL.md +13 -4
  63. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-tasks/SKILL.md +12 -2
  64. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-tdd-dev/SKILL.md +15 -6
  65. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-validate/SKILL.md +2 -1
  66. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-version/SKILL.md +1 -0
  67. package/templates/ai-tools/skills//345/205/203/345/267/245/345/205/267/dwy-shared/SKILL.md +22 -1
  68. package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/SKILL.md +1 -1
  69. package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/references/checks-runtime.md +2 -2
  70. package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_docker.sh +3 -3
  71. package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_secrets.sh +1 -1
  72. package/templates/ai-tools/skills//350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-first/SKILL.md +176 -0
  73. package/templates/ai-tools/skills//350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-first/references/chapter-01-mirrors.md +112 -0
  74. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-ideate/SKILL.md +0 -21
  75. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-0to1/SKILL.md +0 -113
  76. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-0to1/templates/STATE.md +0 -56
  77. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-dev-qa/SKILL.md +0 -117
  78. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-market-analysis/SKILL.md +0 -92
  79. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-prd-coarse/SKILL.md +0 -72
  80. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-prd-coarse/templates/prd-coarse.md +0 -61
  81. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-prd-detailed/SKILL.md +0 -76
  82. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-prd-detailed/templates/prd-detailed.md +0 -119
  83. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-prototype/SKILL.md +0 -104
  84. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-spike/SKILL.md +0 -81
  85. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-tdd-dev/SKILL.md +0 -212
  86. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-tdd-dev/templates/impl-plan.md +0 -115
  87. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-tech-feasibility/SKILL.md +0 -105
  88. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-version-plan/SKILL.md +0 -64
  89. package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-version-plan/templates/version-plan.md +0 -72
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env bash
2
+ # 凭证强度审计 (只读, 全脱敏)
3
+ #
4
+ # 检查范围 (5 类):
5
+ # 1. .env 文件: PASSWORD/SECRET/TOKEN/KEY/AK/SK 字段
6
+ # 2. docker 容器内联 env (Config.Env)
7
+ # 3. Redis 容器 --requirepass 启动参数
8
+ # 4. Postgres 容器 POSTGRES_PASSWORD
9
+ # 5. SSH 私钥 类型 + 位数 (~/.ssh/id_*)
10
+ #
11
+ # 强制脱敏: 只输出派生指标 (len/classes/strength), 永不输出明文
12
+ # 字典词: 内置 ~30 个最常见弱口令
13
+
14
+ TARGET="$1"; shift
15
+ SSH_OPTS=("$@")
16
+
17
+ ssh "${SSH_OPTS[@]}" "${TARGET}" bash -s <<'REMOTE'
18
+ # ---------- 强度评估函数 ----------
19
+ # 输入: 一个秘密字符串(从 stdin)
20
+ # 输出: "len=N classes=K strength=STRONG/MEDIUM/WEAK reason=xxx"
21
+ # 不打印明文, 不写文件
22
+ strength_of() {
23
+ local val
24
+ IFS= read -r val
25
+ local len=${#val}
26
+
27
+ # 字典词内置(精简, 30 个最常见弱口令; 大小写不敏感子串匹配)
28
+ local weak_dict='password|passwd|123456|123123|qwerty|abc123|admin|administrator|root|test|demo|guest|user|welcome|letmein|changeme|secret|default|temp|public|private|master|sample|example|prod|dev|staging|backup|hello|monkey'
29
+ local is_dict=0
30
+ echo "$val" | grep -qiE "($weak_dict)" && is_dict=1
31
+
32
+ # 字符类
33
+ local has_upper=0 has_lower=0 has_digit=0 has_symbol=0
34
+ [[ "$val" =~ [A-Z] ]] && has_upper=1
35
+ [[ "$val" =~ [a-z] ]] && has_lower=1
36
+ [[ "$val" =~ [0-9] ]] && has_digit=1
37
+ [[ "$val" =~ [^a-zA-Z0-9] ]] && has_symbol=1
38
+ local classes=$(( has_upper + has_lower + has_digit + has_symbol ))
39
+
40
+ # 评级
41
+ local strength reason
42
+ if [[ "$len" -lt 8 ]]; then strength="WEAK"; reason="len<8"
43
+ elif [[ "$is_dict" == "1" ]]; then strength="WEAK"; reason="dict_match"
44
+ elif [[ "$len" -lt 16 ]]; then strength="MEDIUM"; reason="len<16"
45
+ elif [[ "$classes" -lt 3 ]]; then strength="MEDIUM"; reason="classes<3"
46
+ else strength="STRONG"; reason="ok"
47
+ fi
48
+ printf "len=%d classes=%d strength=%s reason=%s" "$len" "$classes" "$strength" "$reason"
49
+ }
50
+
51
+ # 排除明显是"数值/枚举配置参数"而非凭证的 key 名
52
+ # (子串匹配 TOKEN/KEY 会误命中 ACCESS_TOKEN_EXPIRE_MINUTES / KEY_TIMEOUT 这种)
53
+ is_config_not_secret() {
54
+ local key="$1"
55
+ echo "$key" | grep -qiE '(EXPIRE|EXPIRY|TTL|TIMEOUT|SECONDS|MINUTES|HOURS|DAYS|COUNT|LIMIT|RETRY|MAX|MIN|SIZE|LENGTH|INTERVAL|DELAY|ENABLED|DISABLED|MODE|TYPE|VERSION|PORT|HOST|URL|PATH|FILE|DIR)$|_(EXPIRE|TTL|TIMEOUT|SECONDS|MINUTES|HOURS|DAYS|COUNT|LIMIT|MAX|MIN|SIZE|MODE|TYPE)_'
56
+ }
57
+
58
+ # 包装: 给定 key+value, 加上"高敏字段长度门槛 32"判定
59
+ evaluate_secret() {
60
+ local key="$1"
61
+ local val="$2"
62
+ [[ -z "$val" ]] && { echo " (空值)"; return; }
63
+
64
+ # 判定是否真凭证字段 (key 名含 PASSWORD/SECRET/ACCESS_KEY_SECRET 等明确含义的)
65
+ # 这类即使值是纯数字短串(如某些服务默认密码 123456) 也必须评估为 WEAK 而非 SKIP
66
+ local is_true_credential=0
67
+ echo "$key" | grep -qiE '(PASSWORD|PASSWD|SECRET|PRIVATE[_-]?KEY|ACCESS[_-]?KEY[_-]?SECRET|_SK$|^SK_)' && is_true_credential=1
68
+
69
+ # Bug fix 1: 跳过明显是配置参数的 key (含 EXPIRE/TTL/TIMEOUT/MAX/MIN 等)
70
+ # 但真凭证字段不跳过
71
+ if [[ "$is_true_credential" == "0" ]] && is_config_not_secret "$key"; then
72
+ printf " %-30s [i] SKIP: 看起来是配置参数(非凭证), key 名含 EXPIRE/TTL/TIMEOUT/MAX 等\n" "$key"
73
+ return
74
+ fi
75
+ # Bug fix 1.b: 纯数字短值(像 30 / 86400 这种 TTL/expire) 跳过
76
+ # 但真凭证字段不跳过 (服务默认密码 123456 / Redis 默认 123 这种弱密码必须报)
77
+ if [[ "$is_true_credential" == "0" ]] && [[ "$val" =~ ^[0-9]+$ ]] && [[ ${#val} -lt 10 ]]; then
78
+ printf " %-30s [i] SKIP: 纯数字短值(看起来是配置参数, len=%d)\n" "$key" "${#val}"
79
+ return
80
+ fi
81
+
82
+ # 是否高敏字段
83
+ local high_sens=0
84
+ echo "$key" | grep -qiE 'SECRET|JWT|TOKEN|API[_-]?KEY|ACCESS[_-]?KEY[_-]?SECRET|PRIVATE[_-]?KEY|_AK$|_SK$|^AK_|^SK_' && high_sens=1
85
+
86
+ local result
87
+ result=$(printf '%s\n' "$val" | strength_of)
88
+ local len=$(echo "$result" | grep -oE 'len=[0-9]+' | cut -d= -f2)
89
+ local classes=$(echo "$result" | grep -oE 'classes=[0-9]+' | cut -d= -f2)
90
+ local strength=$(echo "$result" | grep -oE 'strength=[A-Z]+' | cut -d= -f2)
91
+
92
+ # Bug fix 2: 长度 >= 24 即视为 STRONG (即使 classes 少, 熵足够)
93
+ # 例如 48 字符 base64 / 64 字符 hex 即使只 2 类, 熵 >= 192 bits 远超 128 bits 安全门槛
94
+ if [[ "$strength" != "WEAK" && "$len" -ge 24 ]]; then
95
+ strength="STRONG"
96
+ result="${result/strength=MEDIUM/strength=STRONG}"
97
+ result="${result/reason=classes<3/reason=long_enough}"
98
+ result="${result/reason=len<16/reason=long_enough}"
99
+ fi
100
+
101
+ printf " %-30s %s" "$key" "$result"
102
+ # 严重级标记
103
+ if [[ "$strength" == "WEAK" ]]; then
104
+ printf " [!!!] CRITICAL: 弱凭证, 必须立即轮换\n"
105
+ elif [[ "$high_sens" == "1" && "$len" -lt 32 ]]; then
106
+ printf " [!!] HIGH: 高敏字段建议 >= 32 字符随机串\n"
107
+ elif [[ "$strength" == "MEDIUM" ]]; then
108
+ printf " [!] MEDIUM: 建议加长至 16+ 且 3 类字符\n"
109
+ else
110
+ printf " [OK]\n"
111
+ fi
112
+ }
113
+
114
+ # ---------- 1. .env 文件扫描 ----------
115
+ echo "########## 1. .env 文件凭证强度 ##########"
116
+ ENV_FILES=$(sudo -n find /home /opt /srv /root -maxdepth 6 -type f \
117
+ \( -name ".env" -o -name ".env.*" -o -name "*.env" \) 2>/dev/null \
118
+ | grep -vE '\.example|\.template|\.sample|\.bak|\.old|node_modules' \
119
+ | sort -u | head -20)
120
+ [[ -z "$ENV_FILES" ]] && ENV_FILES=$(find /home /opt /srv /root -maxdepth 6 -type f \
121
+ \( -name ".env" -o -name ".env.*" -o -name "*.env" \) 2>/dev/null \
122
+ | grep -vE '\.example|\.template|\.sample|\.bak|\.old|node_modules' \
123
+ | sort -u | head -20)
124
+
125
+ if [[ -z "$ENV_FILES" ]]; then
126
+ echo "[i] 未找到 .env 文件"
127
+ else
128
+ for f in $ENV_FILES; do
129
+ echo ""
130
+ echo "[$f]"
131
+ # 只挑密码类字段, 解析 KEY=VALUE
132
+ if ! sudo -n cat "$f" 2>/dev/null | head -1 >/dev/null && ! cat "$f" 2>/dev/null | head -1 >/dev/null; then
133
+ echo " [i] 不可读"
134
+ continue
135
+ fi
136
+ (sudo -n cat "$f" 2>/dev/null || cat "$f" 2>/dev/null) \
137
+ | grep -E '^[[:space:]]*[A-Z][A-Z0-9_]*[[:space:]]*=' \
138
+ | grep -iE '(PASSWORD|PASSWD|SECRET|TOKEN|API[_-]?KEY|ACCESS[_-]?KEY|PRIVATE[_-]?KEY|_AK|_SK|^AK_|^SK_)' \
139
+ | while IFS='=' read -r key val; do
140
+ key=$(echo "$key" | tr -d ' \t')
141
+ # 去引号 + 行尾注释
142
+ val=$(echo "$val" | sed -E 's/^[[:space:]]*//; s/[[:space:]]*#.*$//; s/^"//; s/"$//; s/^'\''//; s/'\''$//')
143
+ evaluate_secret "$key" "$val"
144
+ done
145
+ done
146
+ fi
147
+
148
+ # ---------- 2. docker 容器内联 env ----------
149
+ echo ""
150
+ echo "########## 2. Docker 容器内联 env 凭证强度 ##########"
151
+ if command -v docker >/dev/null 2>&1; then
152
+ for c in $(docker ps -q 2>/dev/null); do
153
+ name=$(docker inspect -f '{{.Name}}' "$c" 2>/dev/null | sed 's|^/||')
154
+ echo ""
155
+ echo "[$name]"
156
+ docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$c" 2>/dev/null \
157
+ | grep -iE '(PASSWORD|PASSWD|SECRET|TOKEN|API[_-]?KEY|ACCESS[_-]?KEY|PRIVATE[_-]?KEY|_AK=|_SK=|^AK_|^SK_)' \
158
+ | while IFS='=' read -r key val; do
159
+ key=$(echo "$key" | tr -d ' \t')
160
+ evaluate_secret "$key" "$val"
161
+ done
162
+ done
163
+ else
164
+ echo "[i] Docker 未安装"
165
+ fi
166
+
167
+ # ---------- 3. Redis requirepass (从启动参数提取) ----------
168
+ echo ""
169
+ echo "########## 3. Redis --requirepass 强度 ##########"
170
+ if command -v docker >/dev/null 2>&1; then
171
+ REDIS_FOUND=0
172
+ for c in $(docker ps -q 2>/dev/null); do
173
+ image=$(docker inspect -f '{{.Config.Image}}' "$c" 2>/dev/null)
174
+ if echo "$image" | grep -qi "redis"; then
175
+ REDIS_FOUND=1
176
+ name=$(docker inspect -f '{{.Name}}' "$c" 2>/dev/null | sed 's|^/||')
177
+ echo ""
178
+ echo "[$name]"
179
+ args=$(docker inspect -f '{{range .Args}}{{.}} {{end}}' "$c" 2>/dev/null)
180
+ pass=$(echo "$args" | grep -oE -- '--requirepass [^ ]+' | head -1 | awk '{print $2}')
181
+ if [[ -n "$pass" ]]; then
182
+ evaluate_secret "REDIS_PASSWORD(--requirepass)" "$pass"
183
+ else
184
+ echo " [!!!] CRITICAL: Redis 未设置 --requirepass"
185
+ fi
186
+ fi
187
+ done
188
+ [[ "$REDIS_FOUND" == "0" ]] && echo "[i] 未发现 Redis 容器"
189
+ fi
190
+
191
+ # ---------- 4. Postgres POSTGRES_PASSWORD (容器 env, 已在 2 覆盖, 此处单独 highlight) ----------
192
+ echo ""
193
+ echo "########## 4. Postgres POSTGRES_PASSWORD 强度 ##########"
194
+ if command -v docker >/dev/null 2>&1; then
195
+ PG_FOUND=0
196
+ for c in $(docker ps -q 2>/dev/null); do
197
+ image=$(docker inspect -f '{{.Config.Image}}' "$c" 2>/dev/null)
198
+ if echo "$image" | grep -qi "postgres"; then
199
+ PG_FOUND=1
200
+ name=$(docker inspect -f '{{.Name}}' "$c" 2>/dev/null | sed 's|^/||')
201
+ echo ""
202
+ echo "[$name]"
203
+ pass=$(docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$c" 2>/dev/null \
204
+ | grep '^POSTGRES_PASSWORD=' | head -1 | cut -d= -f2-)
205
+ if [[ -n "$pass" ]]; then
206
+ evaluate_secret "POSTGRES_PASSWORD" "$pass"
207
+ else
208
+ echo " [!!!] CRITICAL: 未取得 POSTGRES_PASSWORD (是否 secrets 挂载或 trust 认证?)"
209
+ fi
210
+ fi
211
+ done
212
+ [[ "$PG_FOUND" == "0" ]] && echo "[i] 未发现 Postgres 容器"
213
+ fi
214
+
215
+ # ---------- 5. SSH 私钥强度 ----------
216
+ echo ""
217
+ echo "########## 5. SSH 私钥类型 / 位数 ##########"
218
+ # 当前用户 + root 的 ~/.ssh/ (Bug fix 3: 去重, root 用户登录时 $HOME=/root 会重复)
219
+ SSH_DIRS=$(printf '%s\n' "$HOME/.ssh" /root/.ssh /home/*/.ssh 2>/dev/null | sort -u)
220
+ for ssh_dir in $SSH_DIRS; do
221
+ [[ -d "$ssh_dir" ]] || continue
222
+ for kf in "$ssh_dir"/id_rsa "$ssh_dir"/id_ed25519 "$ssh_dir"/id_ecdsa "$ssh_dir"/id_dsa; do
223
+ [[ -f "$kf" ]] || continue
224
+ info=$(ssh-keygen -l -f "$kf" 2>/dev/null || sudo -n ssh-keygen -l -f "$kf" 2>/dev/null)
225
+ if [[ -z "$info" ]]; then
226
+ echo " $kf [i] 无法读取(权限?)"
227
+ continue
228
+ fi
229
+ bits=$(echo "$info" | awk '{print $1}')
230
+ type=$(echo "$info" | grep -oE '\([A-Z0-9]+\)$' | tr -d '()')
231
+ perm=$(stat -c '%a' "$kf" 2>/dev/null)
232
+
233
+ case "$type" in
234
+ ED25519)
235
+ printf " %-40s type=%-8s bits=%-4s perm=%s [OK]\n" "$kf" "$type" "$bits" "$perm" ;;
236
+ RSA)
237
+ if [[ "$bits" -ge 4096 ]]; then mark="[OK]"
238
+ elif [[ "$bits" -ge 3072 ]]; then mark="[OK] (建议升 ed25519)"
239
+ elif [[ "$bits" -ge 2048 ]]; then mark="[!] MEDIUM: RSA-${bits} 可接受但建议升 ed25519 或 RSA-4096"
240
+ else mark="[!!!] CRITICAL: RSA-${bits} 已不安全, 立即轮换"
241
+ fi
242
+ printf " %-40s type=%-8s bits=%-4s perm=%s %s\n" "$kf" "$type" "$bits" "$perm" "$mark" ;;
243
+ ECDSA)
244
+ printf " %-40s type=%-8s bits=%-4s perm=%s [!] MEDIUM: ECDSA 存疑(NIST 曲线), 建议 ed25519\n" "$kf" "$type" "$bits" "$perm" ;;
245
+ DSA)
246
+ printf " %-40s type=%-8s bits=%-4s perm=%s [!!!] CRITICAL: DSA 已废弃, 立即轮换\n" "$kf" "$type" "$bits" "$perm" ;;
247
+ *)
248
+ printf " %-40s type=%-8s bits=%-4s perm=%s\n" "$kf" "$type" "$bits" "$perm" ;;
249
+ esac
250
+
251
+ # 权限检查
252
+ if [[ -n "$perm" && "$perm" != "600" && "$perm" != "400" ]]; then
253
+ echo " [!!] HIGH: 私钥权限 $perm 过宽, 应 600"
254
+ fi
255
+ done
256
+ done
257
+
258
+ echo ""
259
+ echo "########## 凭证强度汇总 ##########"
260
+ echo "[i] 修复指引:"
261
+ echo " - WEAK 密码: 立即轮换 (建议 >= 16 字符 + 三类字符的强密码)"
262
+ echo " - 高敏字段 (JWT/SECRET/AK/SK): >= 32 字符随机串"
263
+ echo " - 生成命令: openssl rand -base64 24 | tr -d '=+/' | cut -c1-20"
264
+ echo " - 高敏: openssl rand -hex 32 (生成 64 字符 hex token)"
265
+ REMOTE
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env bash
2
+ # 依赖服务连通性 / 防火墙
3
+ TARGET="$1"; shift
4
+ SSH_OPTS=("$@")
5
+
6
+ ssh "${SSH_OPTS[@]}" "${TARGET}" bash -s <<'REMOTE'
7
+ echo "--- 防火墙状态 ---"
8
+ if command -v ufw >/dev/null 2>&1; then
9
+ echo "[ufw]"
10
+ sudo -n ufw status verbose 2>/dev/null || echo " [!] 需 sudo 查看 ufw"
11
+ fi
12
+ if command -v firewall-cmd >/dev/null 2>&1; then
13
+ echo "[firewalld]"
14
+ sudo -n firewall-cmd --state 2>/dev/null
15
+ sudo -n firewall-cmd --list-all 2>/dev/null
16
+ fi
17
+ echo "[iptables]"
18
+ sudo -n iptables -L INPUT -n --line-numbers 2>/dev/null | head -20 || echo " [i] 无法读取 iptables"
19
+
20
+ echo ""
21
+ echo "--- 监听端口 (按地址分组) ---"
22
+ (ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null) | awk 'NR>1 {print $4}' | awk -F: '{
23
+ port=$NF;
24
+ ip=substr($0,1,length($0)-length(port)-1);
25
+ if (ip=="0.0.0.0" || ip=="*" || ip=="[::]") public[port]++;
26
+ else if (ip=="127.0.0.1" || ip=="[::1]") loop[port]++;
27
+ else internal[port]++;
28
+ }
29
+ END {
30
+ print "公网监听 (0.0.0.0 / *):"
31
+ for (p in public) print " " p
32
+ print "内网监听 (具体 IP):"
33
+ for (p in internal) print " " p
34
+ print "回环监听 (127.0.0.1):"
35
+ for (p in loop) print " " p
36
+ }'
37
+
38
+ echo ""
39
+ echo "--- 系统负载与内存 ---"
40
+ uptime
41
+ free -h 2>/dev/null | head -3
42
+
43
+ echo ""
44
+ echo "--- 磁盘 ---"
45
+ df -h | grep -vE "tmpfs|udev" | head -10
46
+
47
+ echo ""
48
+ echo "--- 已安装的 Web 应用进程 (常见) ---"
49
+ ps -eo user,pid,%cpu,%mem,cmd --sort=-%mem 2>/dev/null \
50
+ | grep -E "(nginx|node|python|uvicorn|gunicorn|java|php-fpm|redis-server|postgres|mysql|mongod)" \
51
+ | grep -v grep | head -20
52
+
53
+ echo ""
54
+ echo "--- 应用健康检查端点 (经本地 nginx 回环 + --resolve, 避免 hairpin NAT) ---"
55
+ NGINX_DUMP=$(sudo -n nginx -T 2>/dev/null || nginx -T 2>/dev/null || true)
56
+ # 只取真实 server_name 指令行(行首仅空白 + 关键字), 去行内注释, 拆多域名, 过滤 _ / 通配 / 空 / 非法 token
57
+ # 旧实现用 awk '/server_name/' 会误匹配注释行里的 server_name 文字, 产出 _) 1. :80 等垃圾域名
58
+ DOMAINS=$(echo "$NGINX_DUMP" \
59
+ | grep -E '^[[:space:]]*server_name[[:space:]]' \
60
+ | sed -E 's/#.*$//; s/^[[:space:]]*server_name[[:space:]]+//; s/;.*$//' \
61
+ | tr ' ' '\n' \
62
+ | grep -E '^[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?$' \
63
+ | grep -v '^_$' \
64
+ | sort -u | head -5)
65
+ [[ -z "$DOMAINS" ]] && DOMAINS="localhost"
66
+ # 是否启用 https(443 在监听); 公网域名从本机直连会触发 hairpin NAT 返回 000,
67
+ # 用 --resolve 把域名钉到回环, 既走本地 nginx 又匹配 SNI 证书, 才是白盒正解
68
+ HAS_HTTPS=$( (ss -tln 2>/dev/null || netstat -tln 2>/dev/null) | grep -qE ':443[[:space:]]' && echo yes || echo no )
69
+ for D in $DOMAINS; do
70
+ for path in "/api/health" "/health" "/healthz" "/_health" "/status"; do
71
+ if [[ "$HAS_HTTPS" == "yes" ]]; then
72
+ code=$(curl -k -sS -o /dev/null -w "%{http_code}" --max-time 3 --resolve "$D:443:127.0.0.1" "https://$D$path" 2>/dev/null || echo "000")
73
+ scheme="https"
74
+ else
75
+ code=$(curl -sS -o /dev/null -w "%{http_code}" --max-time 3 --resolve "$D:80:127.0.0.1" "http://$D$path" 2>/dev/null || echo "000")
76
+ scheme="http"
77
+ fi
78
+ [[ "$code" != "404" && "$code" != "000" ]] && printf " %s://%s%s -> %s\n" "$scheme" "$D" "$path" "$code"
79
+ done
80
+ done
81
+
82
+ echo ""
83
+ echo "--- 系统更新提示 ---"
84
+ if command -v apt >/dev/null 2>&1; then
85
+ sudo -n apt list --upgradable 2>/dev/null | head -10 || echo " [i] 无法检查 apt 更新"
86
+ elif command -v yum >/dev/null 2>&1; then
87
+ sudo -n yum check-update 2>/dev/null | head -10 || echo " [i] 无法检查 yum 更新"
88
+ fi
89
+
90
+ echo ""
91
+ echo "--- 内核/系统启动时间 ---"
92
+ who -b 2>/dev/null
93
+ echo "kernel: $(uname -r)"
94
+ REMOTE
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bash
2
+ # SSH 安全检查 (只读)
3
+ TARGET="$1"; shift
4
+ SSH_OPTS=("$@")
5
+
6
+ ssh "${SSH_OPTS[@]}" "${TARGET}" bash -s <<'REMOTE'
7
+ echo "--- /etc/ssh/sshd_config (filtered key directives) ---"
8
+ SSHD_CONF="/etc/ssh/sshd_config"
9
+ SSHD_DIR="/etc/ssh/sshd_config.d"
10
+ if [[ -r "$SSHD_CONF" ]]; then
11
+ grep -Ei "^[[:space:]]*(PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|PermitEmptyPasswords|Port|Protocol|MaxAuthTries|LoginGraceTime|AllowUsers|AllowGroups|DenyUsers|DenyGroups|ChallengeResponseAuthentication|UsePAM|X11Forwarding|ClientAliveInterval|ClientAliveCountMax|LogLevel|Banner)" "$SSHD_CONF" 2>/dev/null
12
+ if [[ -d "$SSHD_DIR" ]]; then
13
+ echo "--- $SSHD_DIR/*.conf ---"
14
+ grep -EiH "^[[:space:]]*(PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|PermitEmptyPasswords|Port|Protocol|MaxAuthTries)" "$SSHD_DIR"/*.conf 2>/dev/null
15
+ fi
16
+ else
17
+ echo "[!] $SSHD_CONF 不可读 (需要 sudo)"
18
+ fi
19
+
20
+ echo ""
21
+ echo "--- effective sshd config (sshd -T, 需 sudo) ---"
22
+ sudo -n sshd -T 2>/dev/null | grep -Ei "^(permitrootlogin|passwordauthentication|pubkeyauthentication|permitemptypasswords|port|protocol|maxauthtries|logingracetime|allowusers|allowgroups|x11forwarding|loglevel)" || echo "[i] 无法读取 effective config (需要 sudo 或 sshd -T 不可用)"
23
+
24
+ echo ""
25
+ echo "--- ssh process & version ---"
26
+ sshd -V 2>&1 | head -2 || ssh -V 2>&1
27
+
28
+ echo ""
29
+ echo "--- recent failed logins (last 20) ---"
30
+ sudo -n journalctl _COMM=sshd --since "7 days ago" 2>/dev/null | grep -Ei "failed|invalid" | tail -20 \
31
+ || sudo -n grep -Ei "failed|invalid" /var/log/auth.log 2>/dev/null | tail -20 \
32
+ || echo "[i] 无法读取认证日志 (需要 sudo)"
33
+
34
+ echo ""
35
+ echo "--- fail2ban status ---"
36
+ if command -v fail2ban-client >/dev/null 2>&1; then
37
+ sudo -n fail2ban-client status 2>/dev/null || echo "[i] fail2ban 已安装,但需要 sudo 查看状态"
38
+ else
39
+ echo "[!] fail2ban 未安装"
40
+ fi
41
+
42
+ echo ""
43
+ echo "--- authorized_keys 概览 (当前用户) ---"
44
+ if [[ -r ~/.ssh/authorized_keys ]]; then
45
+ awk '{print " key: " $1 " " substr($2,1,16) "... " $3}' ~/.ssh/authorized_keys
46
+ echo " total: $(wc -l < ~/.ssh/authorized_keys) keys"
47
+ else
48
+ echo "[i] 当前用户无 authorized_keys"
49
+ fi
50
+
51
+ echo ""
52
+ echo "--- /root/.ssh/authorized_keys (需 sudo) ---"
53
+ sudo -n test -f /root/.ssh/authorized_keys 2>/dev/null && \
54
+ echo " [!] root 用户存在 authorized_keys, 共 $(sudo -n wc -l < /root/.ssh/authorized_keys 2>/dev/null) 行" \
55
+ || echo "[i] 无 root authorized_keys 或无 sudo 权限"
56
+ REMOTE
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env bash
2
+ # uc-deploy-audit: 一键全量巡检(subagent 不可用时的串行/并行退化方案)
3
+ #
4
+ # 优先做法是让 Claude 用 subagent 并行派遣多个 Agent 跑各类 check_xxx.sh
5
+ # (见 SKILL.md Step 4)。本脚本仅作为本地一键回退使用。
6
+ #
7
+ # 用法:
8
+ # ./run_all.sh <user>@<host> [-p <port>] [-i <key>] [--serial]
9
+ #
10
+ # 默认 8 路并行。--serial 强制串行。
11
+
12
+ set -uo pipefail
13
+
14
+ if [[ $# -lt 1 ]]; then
15
+ echo "Usage: $0 <user>@<host|alias> [-p <port>] [-i <key>] [--serial]" >&2
16
+ exit 1
17
+ fi
18
+
19
+ TARGET="$1"; shift
20
+ SSH_OPTS=(-o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=accept-new)
21
+ PARALLEL=true
22
+
23
+ while [[ $# -gt 0 ]]; do
24
+ case "$1" in
25
+ -p) SSH_OPTS+=(-p "$2"); shift 2;;
26
+ -i) SSH_OPTS+=(-i "$2"); shift 2;;
27
+ --serial) PARALLEL=false; shift;;
28
+ *) echo "Unknown arg: $1" >&2; exit 1;;
29
+ esac
30
+ done
31
+
32
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
33
+ TMP_DIR=$(mktemp -d -t uc-audit-XXXXXX)
34
+ trap 'rm -rf "$TMP_DIR"' EXIT
35
+
36
+ # 检查项: <section_name>:<script_name>
37
+ SECTIONS=(
38
+ "BASE_INFO:check_base.sh"
39
+ "SSH:check_ssh.sh"
40
+ "NGINX:check_nginx.sh"
41
+ "DB:check_db.sh"
42
+ "HTTPS:check_https.sh"
43
+ "ENV:check_env.sh"
44
+ "DOCKER:check_docker.sh"
45
+ "SERVICES:check_services.sh"
46
+ "RESILIENCE:check_resilience.sh"
47
+ "LOGS:check_logs.sh"
48
+ "CAPACITY:check_capacity.sh"
49
+ "SECRETS:check_secrets.sh"
50
+ )
51
+
52
+ echo "========================================"
53
+ echo " uc-deploy-audit | target: ${TARGET}"
54
+ echo " timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
55
+ echo " mode: $([ "$PARALLEL" = true ] && echo "parallel (${#SECTIONS[@]} workers)" || echo 'serial')"
56
+ echo "========================================"
57
+
58
+ run_section() {
59
+ local name="$1"
60
+ local script="$2"
61
+ local out_file="${TMP_DIR}/${name}.out"
62
+ {
63
+ echo "########## SECTION: ${name} ##########"
64
+ bash "${SCRIPT_DIR}/${script}" "${TARGET}" "${SSH_OPTS[@]}" 2>&1 \
65
+ || echo "[!] ${name} 部分失败"
66
+ echo "########## END: ${name} ##########"
67
+ } > "$out_file" 2>&1
68
+ }
69
+
70
+ if [[ "$PARALLEL" == "true" ]]; then
71
+ # 并行:所有 section 后台启动
72
+ pids=()
73
+ for entry in "${SECTIONS[@]}"; do
74
+ name="${entry%%:*}"
75
+ script="${entry##*:}"
76
+ run_section "$name" "$script" &
77
+ pids+=($!)
78
+ done
79
+
80
+ # 等所有完成
81
+ for pid in "${pids[@]}"; do
82
+ wait "$pid"
83
+ done
84
+
85
+ # 按顺序输出结果(保证报告分类有序)
86
+ for entry in "${SECTIONS[@]}"; do
87
+ name="${entry%%:*}"
88
+ echo ""
89
+ cat "${TMP_DIR}/${name}.out"
90
+ done
91
+ else
92
+ # 串行
93
+ for entry in "${SECTIONS[@]}"; do
94
+ name="${entry%%:*}"
95
+ script="${entry##*:}"
96
+ echo ""
97
+ run_section "$name" "$script"
98
+ cat "${TMP_DIR}/${name}.out"
99
+ done
100
+ fi
101
+
102
+ echo ""
103
+ echo "========================================"
104
+ echo " 巡检数据采集完成,请由 Claude 分析生成报告"
105
+ echo "========================================"
@@ -9,15 +9,16 @@ description: "【dwy·验收测试】产品 0 到 1 上架迭代阶段。触发
9
9
  ## 输入(从哪读)
10
10
  - state.json 的 `confirmed.dev_progress`、`confirmed.prd`、`confirmed.architecture`
11
11
  - 项目源码 + `.dwy/prod/[项目]/02-需求规划/PRD.md`
12
- - `.dwy/prod/[项目]/03-设计与架构/接口契约.md`
12
+ - `.dwy/prod/[项目]/04-架构设计/接口契约.md`
13
13
 
14
14
  ## 实现
15
15
  [包装型] 用全局本地 skill(位于 `~/.dwy/skills/<外部skill名>/`):
16
16
  读其 `SKILL.md`(及同目录 scripts/、配套 .md),按它的方式与用户交互/提问,产出按下方「产出契约」落地。
17
+ - **提问形式(Claude Code 下强制)**:借用外部 skill 的「问什么、怎么一步步收敛」方法论,但**每个要用户选择/拍板的问题用 `AskUserQuestion` 工具弹点选卡片**,不在正文列 A/B/C/D 让用户打字;保留「一次一问、答完再问下一个」的节奏,开放式补充靠卡片的「Other」兜底。其他工具(Codex 等无此组件)退回外部 skill 原生的正文问答。
17
18
  - 本 skill 依赖的外部 skill:`test-scenarios`、`intended-vs-implemented`(各对应 `~/.dwy/skills/<名>/`)
18
19
  - 若 `~/.dwy/skills/<名>/` 不存在:提示用户先跑 `dwy skills install`(或 `dwy claude sync` 会自动装),不要自己用内置能力顶替。
19
20
 
20
21
  ## 产出契约(硬约束)
21
- - 落到:`.dwy/prod/[项目]/04-上线交付/验收报告.md`
22
+ - 落到:`.dwy/prod/[项目]/05-上线交付/验收报告.md`
22
23
  - 固定章节:集成/E2E 场景结果 / 兼容性 / 合规与沙盒 / 文档vs代码差距 / 验收结论
23
24
  - 回写 state.json:`confirmed.acceptance`(验收结论 / 是否达上线标准)
@@ -1,28 +1,70 @@
1
1
  ---
2
2
  name: dwy-architecture
3
- description: "【dwy·技术架构】产品 0 到 1 设计与架构阶段。触发场景:用户说『设计架构 / 出技术方案 / 设计库表 / 定接口契约 / 技术架构 / architecture』,要在开发前定下技术架构、数据库设计和接口契约时。"
3
+ description: "【dwy·技术架构】产品 0 到 1 设计与架构阶段。触发场景:用户说『设计架构 / 出技术方案 / 设计库表 / 定接口契约 / 技术架构 / architecture』,要在开发前定下技术架构、数据库设计和接口契约时。按四层架构(Assembly/Features/BizFoundation/Foundation)+ 统一选型(Python+FastAPI、Vue+Vite)产出。"
4
4
  ---
5
5
 
6
6
  ## 职责(单一)
7
- 按项目 rules 产出技术架构 + 数据库库表设计 + 接口契约。
7
+ 按项目 rules + 下方架构约束,产出技术架构 + 数据库库表设计 + 接口契约。
8
8
 
9
9
  ## 输入(从哪读)
10
10
  - state.json 的 `confirmed.prd`、`confirmed.tasks`、`confirmed.prototype`
11
11
  - `.dwy/prod/[项目]/02-需求规划/PRD.md`、`开发任务拆解.md`
12
12
  - 项目 `.claude/rules/` 下的技术栈与编码规则
13
13
 
14
+ ## 架构约束(硬)
15
+
16
+ 项目已有 `.claude/rules/` 时**以项目 rules 为准**;项目无明确规定时按本节统一缺省。
17
+
18
+ ### 技术选型(缺省基线,写在架构选项之上)
19
+
20
+ 以下为默认选型基线,**先按此基线落地**;结合上下文(PRD / 选型 / 项目 rules)判断**基线不合适时,必须用 `AskUserQuestion` 跟用户确认后才换**,禁止默默偏离基线、也禁止默默换栈。
21
+
22
+ - **后端统一 Python + FastAPI**(异步、Pydantic v2、SQLAlchemy 2.0 风格,与 `dwyeapi` 生态对齐)
23
+ - **Python 虚拟环境用 `uv` 管理**(venv + 依赖 + 运行)
24
+ - **DB 迁移用 Alembic**(与 SQLAlchemy 2.0 配套)
25
+ - **配置 / settings 用 pydantic-settings**(与 `dwyeapi` config 对齐)
26
+ - **认证用 JWT**(与 `dwyeapi` security 的 create_token/decode_token + bcrypt 对齐)
27
+ - **后端有数据库时**:
28
+ - **关系型数据库用 PostgreSQL**(连接串如 `postgresql+asyncpg://user:pass@localhost:5432/mydb`)
29
+ - **内存数据库 / 缓存用 Redis**(`redis://localhost:6379/0`)
30
+ - **用 Docker 部署**(后端 + Postgres + Redis 编排到 `docker-compose.yml`)
31
+ - **反向代理 / 入口网关用 Nginx**
32
+ - **前端 Vue 用 Vite + TailwindCSS + TypeScript**(`@dwydev/eui` + `@dwydev/ekit` 可选用,不强制)
33
+ - **包管理器用 pnpm**(与本 monorepo 一致)
34
+ - **状态管理用 Pinia**
35
+ - **HTTP 客户端用 axios**(或 `@dwydev/ekit` 的 request)
36
+ - **E2E 测试用 Playwright**(单测 Vitest 为项目默认)
37
+ - **CI/CD 走 GitHub Actions + OIDC**(发布鉴权用 OIDC,禁长期静态凭据)
38
+ - **基线不适用就先确认**:任一项基线与上下文冲突(如纯前端无需 Docker/DB、要换其他 DB/MQ/中间件、不能用某项)→ 先 `AskUserQuestion` 跟用户确认,确认后在架构文档写明偏离基线的理由
39
+
40
+ ### 四层架构(依赖严格单向向下:Assembly → Features → BizFoundation → Foundation)
41
+
42
+ | 层 | 职责 | 后端示例 | 前端示例 |
43
+ |---|---|---|---|
44
+ | **装配层 (Assembly)** | 实例化 / 依赖注入 / 路由挂载 / 生命周期,**零业务逻辑** | `src/app/`(main.py 装 Provider/Hook、factories) | `apps/web`(路由、stores、main.ts) |
45
+ | **Features (业务功能层)** | 各业务域领域逻辑,域内 router+service+schema+model **聚在同一目录** | `<域>/`(如 `factor/`、`plan/`、`quota/`) | `features/*`(如 auth、settings) |
46
+ | **业务基础层 (BizFoundation)** | 业务相关但跨业务域复用:鉴权、领域协议/Provider、领域设施 | `auth`、各包 `protocols.py`/`providers` | 跨域复用的业务 composable/store |
47
+ | **业务无关层 (Foundation)** | 与业务完全无关的 kernel | `shared`(DB/Redis/签名/中间件) | `core`(httpClient/格式化/常量) |
48
+
49
+ ### 三条铁律
50
+ - **依赖单向向下,绝不反向**:下层禁止 import 上层;Foundation 禁止 import 任何业务包;跨业务域**不直接互 import**,靠装配层用 Protocol/Hook 桥接。
51
+ - **业务内聚**:一个业务域 = 一个目录,域内 router/service/schema/model 聚在一起。✅ `factor/`(含 router+service+validators);❌ 顶层 `routers/`+`services/`+`models/` 三个大锅各塞所有域。
52
+ - **装配层是唯一能 import 具体实现的地方**:业务代码只通过 `get_xxx_provider()` 取实例,换实现不改业务代码。
53
+
14
54
  ## 实现
15
55
  [自写型]
16
- 1. 读项目 `.claude/rules/`,按项目既定技术栈与规范产出架构
17
- 2. 技术架构:分层、模块边界、技术选型理由
18
- 3. 数据库设计:库表、字段、索引、关系
56
+ 1. 读项目 `.claude/rules/`,结合上方架构约束产出架构(项目 rules 优先,本约束兜底)
57
+ 2. 技术架构:**先按四层归位模块、按业务域聚目录**,再写分层/模块边界/选型理由;选型不确定处走 `AskUserQuestion`
58
+ 3. **判有无后端**(读选型/PRD:有 API / 持久化 / 账户体系 → 有后端):
59
+ - **有后端** → 必出数据库设计,且**建模到位**:库表清单 / 字段(类型·约束·默认·可空) / 主外键关系 / 索引 / 必要的范式或反范式说明,与接口契约 schema 对齐
60
+ - **纯前端**(无后端)→ 跳过数据库设计,在 `技术架构.md` 注明「无后端,不涉及库表」
19
61
  4. 接口契约:路由、请求/响应 schema(遵循项目响应格式约定)
20
- 5. **走 rules 土建**,不引入项目规范外的技术栈
62
+ 5. **走 rules + 约束土建**,不引入约束外的技术栈
21
63
 
22
64
  ## 产出契约(硬约束)
23
- - 落到:`.dwy/prod/[项目]/03-设计与架构/技术架构.md`、`数据库设计.md`、`接口契约.md`
65
+ - 落到:`.dwy/prod/[项目]/04-架构设计/技术架构.md`、`接口契约.md`,**有后端时**加 `数据库设计.md`
24
66
  - 固定章节:
25
- - 技术架构.md:分层架构 / 模块划分 / 技术选型
26
- - 数据库设计.md:库表清单 / 字段定义 / 关系与索引
67
+ - 技术架构.md:**四层划分 + 各业务域目录结构** / 模块边界 / 技术选型(按缺省基线写明 Python+FastAPI+uv+Alembic+pydantic-settings+JWT、前端 Vue+Vite+TS+Tailwind+pnpm+Pinia+axios+Playwright、CI/CD GitHub Actions OIDC;有 DB 写明 PostgreSQL+Redis+Docker+Nginx;偏离基线处写明原因)/ 选型不明或偏离基线处的 AskUserQuestion 确认记录 / 有无后端结论
68
+ - 数据库设计.md(**有后端必出**):库表清单 / 字段定义(类型·约束·默认·可空) / 主外键关系与索引 / 与接口契约对齐
27
69
  - 接口契约.md:接口清单 / 请求响应 schema / 错误码
28
70
  - 回写 state.json:`confirmed.architecture`