add-coder 0.3.25 → 0.3.27
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/README.md +36 -10
- package/dist/index.js +481 -209
- package/package.json +14 -4
- package/templates/.add-coder-src-hash.json +129 -106
- package/templates/adapters/claude/hooks/doc-format-guard.sh +114 -33
- package/templates/adapters/claude/hooks/lib/common.sh +46 -87
- package/templates/adapters/claude/hooks/lib/context-inject.sh +2 -2
- package/templates/adapters/claude/hooks/lib/notify.sh +6 -4
- package/templates/adapters/claude/hooks/lib/preload-templates.sh +27 -2
- package/templates/adapters/claude/hooks/lib/state-detect.sh +8 -112
- package/templates/adapters/claude/hooks/lib/vocabulary.sh +13 -7
- package/templates/adapters/claude/hooks/prompt-submit.sh +1 -1
- package/templates/adapters/claude/hooks/stop-check.sh +9 -0
- package/templates/adapters/codex/config.toml.example +12 -0
- package/templates/adapters/codex/hooks/doc-format-guard.sh +123 -41
- package/templates/adapters/codex/hooks/lib/common.sh +47 -99
- package/templates/adapters/codex/hooks/lib/context-inject.sh +4 -4
- package/templates/adapters/codex/hooks/lib/notify.sh +6 -4
- package/templates/adapters/codex/hooks/lib/preload-templates.sh +27 -6
- package/templates/adapters/codex/hooks/lib/state-detect.sh +5 -114
- package/templates/adapters/codex/hooks/lib/vocabulary.sh +3 -8
- package/templates/adapters/codex/hooks/notification.sh +3 -18
- package/templates/adapters/codex/hooks/post-tool-use.sh +24 -119
- package/templates/adapters/codex/hooks/pre-tool-use.sh +97 -184
- package/templates/adapters/codex/hooks/prompt-submit.sh +8 -3
- package/templates/adapters/codex/hooks/session-start.sh +19 -9
- package/templates/adapters/codex/hooks/stop-check.sh +44 -18
- package/templates/adapters/codex/hooks.json +67 -1
- package/templates/adapters/qoder/hooks/doc-format-guard.sh +121 -40
- package/templates/adapters/qoder/hooks/lib/common.sh +49 -87
- package/templates/adapters/qoder/hooks/lib/context-inject.sh +2 -2
- package/templates/adapters/qoder/hooks/lib/notify.sh +6 -4
- package/templates/adapters/qoder/hooks/lib/preload-templates.sh +27 -2
- package/templates/adapters/qoder/hooks/lib/state-detect.sh +8 -112
- package/templates/adapters/qoder/hooks/lib/vocabulary.sh +13 -7
- package/templates/adapters/qoder/hooks/prompt-submit.sh +1 -1
- package/templates/adapters/qoder/hooks/session-start.sh +1 -1
- package/templates/adapters/qoder/hooks/stop-check.sh +11 -0
- package/templates/adapters/trae/hooks/doc-format-guard.sh +122 -40
- package/templates/adapters/trae/hooks/lib/common.sh +46 -87
- package/templates/adapters/trae/hooks/lib/context-inject.sh +2 -2
- package/templates/adapters/trae/hooks/lib/notify.sh +5 -3
- package/templates/adapters/trae/hooks/lib/preload-templates.sh +27 -2
- package/templates/adapters/trae/hooks/lib/state-detect.sh +8 -115
- package/templates/adapters/trae/hooks/lib/vocabulary.sh +13 -7
- package/templates/adapters/trae/hooks/pre-tool-use.sh +14 -8
- package/templates/adapters/trae/hooks/stop-check.sh +9 -0
- package/templates/adapters/vscode/hooks/doc-format-guard.sh +114 -33
- package/templates/adapters/vscode/hooks/lib/common.sh +46 -87
- package/templates/adapters/vscode/hooks/lib/context-inject.sh +2 -2
- package/templates/adapters/vscode/hooks/lib/notify.sh +6 -4
- package/templates/adapters/vscode/hooks/lib/preload-templates.sh +27 -2
- package/templates/adapters/vscode/hooks/lib/state-detect.sh +8 -112
- package/templates/adapters/vscode/hooks/lib/vocabulary.sh +13 -7
- package/templates/adapters/vscode/hooks/notification.sh +3 -5
- package/templates/adapters/vscode/hooks/prompt-submit.sh +1 -1
- package/templates/adapters/vscode/hooks/stop-check.sh +9 -0
- package/templates/core/docs/ADD-governance-claude-code.md +1 -1
- package/templates/core/docs/ADD-governance-codex.md +1 -1
- package/templates/core/docs/ADD-governance-qoder-cn.md +1 -1
- package/templates/core/docs/ADD-governance-trae.md +1 -1
- package/templates/core/docs/ADD-governance-vscode-copilot.md +1 -1
- package/templates/core/hooks/doc-format-guard.sh +122 -40
- package/templates/core/hooks/lib/common.sh +46 -87
- package/templates/core/hooks/lib/context-inject.sh +2 -2
- package/templates/core/hooks/lib/notify.sh +5 -3
- package/templates/core/hooks/lib/preload-templates.sh +27 -2
- package/templates/core/hooks/lib/state-detect.sh +8 -115
- package/templates/core/hooks/lib/vocabulary.sh +13 -7
- package/templates/core/hooks/pre-tool-use.sh +14 -8
- package/templates/core/hooks/stop-check.sh +9 -0
- package/templates/core/prisma/add.prisma +140 -82
- package/templates/core/rules/project_rules.md +27 -24
- package/templates/core/scripts/db-ensure.sh +8 -0
- package/templates/core/scripts/gen-plan-index.sh +4 -15
- package/templates/core/scripts/mcp-server/notifications/hitl.ts +9 -1
- package/templates/core/scripts/mcp-server/notifications/hook.ts +25 -9
- package/templates/core/scripts/mcp-server/resources/hitl-approval-widget.ts +34 -0
- package/templates/core/scripts/mcp-server/resources/hook-events-report.ts +14 -2
- package/templates/core/scripts/mcp-server/resources/index.ts +2 -0
- package/templates/core/scripts/mcp-server/sampling/review.ts +25 -10
- package/templates/core/scripts/mcp-server/shared/db-types.ts +33 -8
- package/templates/core/scripts/mcp-server/shared/dps-scoring.strategy.ts +1 -1
- package/templates/core/scripts/mcp-server/shared/env.ts +28 -8
- package/templates/core/scripts/mcp-server/shared/fs.ts +3 -0
- package/templates/core/scripts/mcp-server/shared/hitl-interaction.strategy.ts +2 -3
- package/templates/core/scripts/mcp-server/shared/hitl-lifecycle-mutation.ts +129 -0
- package/templates/core/scripts/mcp-server/shared/hitl-proposal-mutation.ts +98 -0
- package/templates/core/scripts/mcp-server/shared/hitl-ui.ts +3 -0
- package/templates/core/scripts/mcp-server/shared/plan-lifecycle-events.ts +62 -0
- package/templates/core/scripts/mcp-server/shared/plan-lifecycle-mutation.ts +78 -0
- package/templates/core/scripts/mcp-server/shared/plan-lifecycle-subscriber.ts +145 -0
- package/templates/core/scripts/mcp-server/shared/plan-lifecycle.ts +153 -0
- package/templates/core/scripts/mcp-server/shared/plan-round-events.ts +56 -0
- package/templates/core/scripts/mcp-server/shared/plan-round-mutation.ts +143 -0
- package/templates/core/scripts/mcp-server/shared/plan-round-store.ts +47 -0
- package/templates/core/scripts/mcp-server/shared/plan-round-subscriber.ts +99 -0
- package/templates/core/scripts/mcp-server/shared/plan-status-store.ts +37 -0
- package/templates/core/scripts/mcp-server/shared/plan-tracking-mutation.ts +71 -0
- package/templates/core/scripts/mcp-server/shared/prisma.ts +12 -6
- package/templates/core/scripts/mcp-server/shared/project-root-strategy.ts +1 -2
- package/templates/core/scripts/mcp-server/shared/quant-recheck.strategy.ts +16 -0
- package/templates/core/scripts/mcp-server/shared/redact.ts +28 -0
- package/templates/core/scripts/mcp-server/shared/response.ts +3 -2
- package/templates/core/scripts/mcp-server/shared/runtime-context.ts +77 -0
- package/templates/core/scripts/mcp-server/shared/schema-topology.ts +159 -0
- package/templates/core/scripts/mcp-server/tools/audit.ts +49 -7
- package/templates/core/scripts/mcp-server/tools/context.ts +117 -121
- package/templates/core/scripts/mcp-server/tools/contract.ts +34 -9
- package/templates/core/scripts/mcp-server/tools/gateway/check_add_route_status.ts +50 -3
- package/templates/core/scripts/mcp-server/tools/gateway/check_doc_similarity.ts +218 -0
- package/templates/core/scripts/mcp-server/tools/gateway/check_dps.ts +22 -7
- package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +75 -4
- package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +19 -6
- package/templates/core/scripts/mcp-server/tools/gateway/index.ts +2 -0
- package/templates/core/scripts/mcp-server/tools/hitl.ts +160 -75
- package/templates/core/scripts/mcp-server/tools/hook-event-report.ts +9 -2
- package/templates/core/scripts/mcp-server/tools/index.ts +69 -3
- package/templates/core/scripts/mcp-server/tools/plan.ts +140 -15
- package/templates/core/scripts/mcp-server/tools/review.ts +44 -15
- package/templates/core/scripts/mcp-server.ts +60 -1
- package/templates/core/scripts/plan-status-bridge.ts +26 -0
- package/templates/core/skills/add-paradigm/SKILL.md +2 -2
- package/templates/core/templates/add-route-template.schema.json +24 -56
- package/templates/core/templates/collab-contract-template.md +1 -1
- package/templates/core/templates/handoff-multi-round-template.schema.json +48 -0
- package/templates/core/templates/handoff-single-round-template.schema.json +92 -0
- package/templates/core/templates/handoff-single-round.schema.json +1 -1
- package/templates/core/templates/hitl-approval-widget.html +222 -37
- package/templates/core/templates/spec-template.schema.json +9 -46
- package/templates/core/templates/standard-plan-template.schema.json +1 -1
- package/templates/core/templates/tasks-template.schema.json +13 -54
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# vocabulary.sh — 从 vocabulary markdown 表格加载触发词
|
|
3
|
-
# 单一数据源:
|
|
3
|
+
# 单一数据源: {MAGIC_DIR}/vocabulary/add-governance-vocabulary.md §类别 A-F 表格
|
|
4
|
+
# 协议层契约(Review P1 #5): 仅当前 magicDir scope;入口注入 MAGIC_DIR 或物理位置推导;
|
|
5
|
+
# 禁止跨端扫描与 .add fallback,禁止 adapter 名称默认值。
|
|
4
6
|
|
|
5
|
-
#
|
|
7
|
+
# MAGIC_DIR 解析: 入口注入优先;未注入时从脚本物理位置(<magicDir>/hooks/lib/)反推
|
|
6
8
|
if [ -z "${MAGIC_DIR:-}" ]; then
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
if [ -n "${HOOK_DIR:-}" ]; then
|
|
10
|
+
MAGIC_DIR="$(basename "$(dirname "$HOOK_DIR")")"
|
|
11
|
+
else
|
|
12
|
+
# 无注入且无物理位置 → 无法判定 → 返回空(调用方 fail-closed,禁止猜测 adapter)
|
|
13
|
+
VOCABULARY_FILE=""
|
|
14
|
+
fi
|
|
15
|
+
fi
|
|
16
|
+
if [ -n "${MAGIC_DIR:-}" ]; then
|
|
17
|
+
VOCABULARY_FILE="${PROJECT_DIR:-$PWD}/$MAGIC_DIR/vocabulary/add-governance-vocabulary.md"
|
|
11
18
|
fi
|
|
12
|
-
VOCABULARY_FILE="${PROJECT_DIR:-$PWD}/$MAGIC_DIR/vocabulary/add-governance-vocabulary.md"
|
|
13
19
|
|
|
14
20
|
# 输出格式: 优先级::触发词正则::响应文本(:: 避免与触发词内的 | 冲突)
|
|
15
21
|
load_triggers() {
|
|
@@ -62,8 +62,9 @@ EOF
|
|
|
62
62
|
exit 2
|
|
63
63
|
fi
|
|
64
64
|
|
|
65
|
-
# 检测 2: sed -i
|
|
66
|
-
|
|
65
|
+
# 检测 2: sed -i 原地编辑(精确判定:`-i` 必须是 sed 独立 option;不把路径子串如 session-init 误判)
|
|
66
|
+
# 协议层契约(Review P0 #3/命令分类): 与 codex 已实现版本同契约语义
|
|
67
|
+
if echo "$command" | grep -qE '(^|[;&|][[:space:]]*)sed[[:space:]]+([^;&|]*[[:space:]])?(-[[:alpha:]]*i[^[:space:];&|]*|--in-place(=[^[:space:];&|]*)?)([[:space:];&|]|$)'; then
|
|
67
68
|
_reason="禁止通过 sed -i 直接编辑文件。请使用 SearchReplace 工具。"
|
|
68
69
|
cat >&2 <<'EOF'
|
|
69
70
|
⛔ [ADD PreToolUse §A] 阻断: 禁止通过 sed -i 原地编辑文件。
|
|
@@ -136,7 +137,8 @@ if [ "$tool_name" = "Write" ] || [ "$tool_name" = "Edit" ] || [ "$tool_name" = "
|
|
|
136
137
|
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
|
|
137
138
|
[ -z "$file_path" ] && exit 0
|
|
138
139
|
|
|
139
|
-
|
|
140
|
+
# §B: 写入守卫按当前 magicDir 限域(Review P1 #5: 禁止多端路径正则)
|
|
141
|
+
if echo "$file_path" | grep -qE "${MAGIC_DIR}/(plans|specs|reviews)/"; then
|
|
140
142
|
if type detect_active_add >/dev/null 2>&1; then
|
|
141
143
|
state=$(detect_active_add 2>/dev/null || true)
|
|
142
144
|
if [ -z "$state" ]; then
|
|
@@ -154,8 +156,8 @@ if [ "$tool_name" = "Write" ] || [ "$tool_name" = "Edit" ] || [ "$tool_name" = "
|
|
|
154
156
|
exit 2
|
|
155
157
|
fi
|
|
156
158
|
|
|
157
|
-
# §C: 模板类型前置注入 — plans/ 写入前提示应选模板
|
|
158
|
-
if echo "$file_path" | grep -qE
|
|
159
|
+
# §C: 模板类型前置注入 — 仅当前 magicDir plans/ 写入前提示应选模板
|
|
160
|
+
if echo "$file_path" | grep -qE "${MAGIC_DIR}/(plans)/"; then
|
|
159
161
|
fname=$(basename "$file_path")
|
|
160
162
|
if echo "$fname" | grep -qE 'plan-v[0-9]'; then
|
|
161
163
|
echo "💡 [ADD PreToolUse] 写入 Plan → 模板: standard-plan-template.md(标准)或 simple-plan-template.md(≤3文件)" >&2
|
|
@@ -177,13 +179,13 @@ if [ "$tool_name" = "Write" ] || [ "$tool_name" = "Edit" ] || [ "$tool_name" = "
|
|
|
177
179
|
# implementation/runtime review 不需要 HITL,走 §B 活跃 Plan 检查
|
|
178
180
|
# handoff 是 Step 8 收敛产物(多轮交接手册),不经 HITL 审批,同样豁免
|
|
179
181
|
# TODO 应该caijuehub管理
|
|
180
|
-
if echo "$file_path" | grep -qE
|
|
182
|
+
if echo "$file_path" | grep -qE "${MAGIC_DIR}/(plans)/"; then
|
|
181
183
|
if echo "$file_path" | grep -qE '\-handoff'; then
|
|
182
184
|
_do_hitl=false # handoff 不被 HITL 拦截(Step 8 产物,无独立审批)
|
|
183
185
|
else
|
|
184
186
|
_do_hitl=true
|
|
185
187
|
fi
|
|
186
|
-
elif echo "$file_path" | grep -qE
|
|
188
|
+
elif echo "$file_path" | grep -qE "${MAGIC_DIR}/(reviews)/"; then
|
|
187
189
|
if echo "$file_path" | grep -qE '-(implementation|runtime)'; then
|
|
188
190
|
_do_hitl=false # implementation/runtime review 不被 HITL 拦截
|
|
189
191
|
else
|
|
@@ -194,7 +196,11 @@ if [ "$tool_name" = "Write" ] || [ "$tool_name" = "Edit" ] || [ "$tool_name" = "
|
|
|
194
196
|
fi
|
|
195
197
|
|
|
196
198
|
if [ "$_do_hitl" = true ]; then
|
|
197
|
-
_relative=$(echo "$file_path" | sed
|
|
199
|
+
_relative=$(echo "$file_path" | sed "s|.*/\.\(qoder\|claude\|add\|vscode\|trae\)/\(plans\|reviews\)/||")
|
|
200
|
+
# 限域: 仅当前 magicDir 下的 plans/reviews 触发 HITL 门禁
|
|
201
|
+
if ! echo "$file_path" | grep -qE "${MAGIC_DIR}/(plans|reviews)/"; then
|
|
202
|
+
_do_hitl=false
|
|
203
|
+
fi
|
|
198
204
|
_planName=$(basename "$_relative" .md | sed 's/\.hitl$//;s/-plan-v[0-9]*$//;s/-add-route-v[0-9]*$//;s/-review-v[0-9]*$//;s/-review-implementation$//;s/-review-runtime$//')
|
|
199
205
|
if [ -n "$_planName" ]; then
|
|
200
206
|
_tongyi_marker="${PROJECT_DIR}/${MAGIC_DIR}/hitl/.tongyi-${_planName}"
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# stop-check.sh — Claude Code Stop:四象限分流 + 验收检查(few-shot 上下文注入)
|
|
3
3
|
# 治理卡位 #7: 验收检查 + devlog + 阻断
|
|
4
|
+
# DB lifecycle 真相源(协议层):DB 不可用时 fail-closed,禁止回退 Handoff/add-route 猜测
|
|
4
5
|
set -euo pipefail
|
|
5
6
|
|
|
6
7
|
HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
8
|
export CURRENT_MAGIC=$(basename "$(dirname "$HOOK_DIR")")
|
|
9
|
+
export MAGIC_DIR=$(basename "$(dirname "$HOOK_DIR")")
|
|
8
10
|
COMMON_LIB="$HOOK_DIR/lib/common.sh"
|
|
9
11
|
[ -f "$COMMON_LIB" ] && source "$COMMON_LIB"
|
|
10
12
|
|
|
@@ -13,6 +15,13 @@ export PROJECT_DIR="$PWD"
|
|
|
13
15
|
state=$(detect_active_add 2>/dev/null || true)
|
|
14
16
|
has_dev=$(has_dev_action 2>/dev/null && echo "true" || echo "false")
|
|
15
17
|
|
|
18
|
+
# ═══════ Q0: DB 不可用 → fail closed(禁止当“无 Plan”) ═══════
|
|
19
|
+
if [ "${state%%::*}" = "__STATUS_UNAVAILABLE__" ]; then
|
|
20
|
+
reason=$(echo "$state" | awk -F'::' '{print $2}')
|
|
21
|
+
echo "[ADD Stop] ⛔ Plan status 暂不可用(${reason})。未回退 Handoff/add-route 猜测,请恢复数据库或 MCP resolver 后重试。" >&2
|
|
22
|
+
exit "${EXIT_BLOCK:-2}"
|
|
23
|
+
fi
|
|
24
|
+
|
|
16
25
|
# ═══════ Q1: 无 ADD + 无 dev → 正常停 ═══════
|
|
17
26
|
if [ -z "$state" ] && [ "$has_dev" != "true" ]; then
|
|
18
27
|
exit 0
|
|
@@ -1,43 +1,54 @@
|
|
|
1
1
|
// add.prisma — ADD 治理模型,独立于用户业务表
|
|
2
2
|
|
|
3
3
|
model AddUser {
|
|
4
|
-
id
|
|
5
|
-
username
|
|
6
|
-
email
|
|
7
|
-
devOperations
|
|
8
|
-
auditLogs
|
|
4
|
+
id String @id @default(cuid())
|
|
5
|
+
username String @unique
|
|
6
|
+
email String?
|
|
7
|
+
devOperations DevOperation[]
|
|
8
|
+
auditLogs AuditLog[]
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
model DevOperation {
|
|
12
|
-
id
|
|
13
|
-
userId
|
|
14
|
-
user
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
id String @id @default(cuid())
|
|
13
|
+
userId String
|
|
14
|
+
user AddUser @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
15
|
+
projectKey String @default("legacy-unknown")
|
|
16
|
+
producerAdapterKey String @default("legacy-unknown")
|
|
17
|
+
contextId String @default("legacy-unknown")
|
|
18
|
+
toolName String @default("record_dev_operation")
|
|
19
|
+
operationKey String @default(cuid())
|
|
20
|
+
planKeyword String
|
|
21
|
+
action String
|
|
22
|
+
targetType String
|
|
23
|
+
targetId String
|
|
24
|
+
beforeState Json?
|
|
25
|
+
afterState Json?
|
|
26
|
+
reason String?
|
|
27
|
+
createdAt DateTime @default(now())
|
|
23
28
|
|
|
29
|
+
@@unique([projectKey, producerAdapterKey, toolName, operationKey])
|
|
24
30
|
@@index([planKeyword])
|
|
31
|
+
@@index([projectKey, producerAdapterKey, planKeyword])
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
model AuditLog {
|
|
28
|
-
id
|
|
29
|
-
userId
|
|
30
|
-
user
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
id String @id @default(cuid())
|
|
36
|
+
userId String
|
|
37
|
+
user AddUser @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
38
|
+
projectKey String @default("legacy-unknown")
|
|
39
|
+
producerAdapterKey String @default("legacy-unknown")
|
|
40
|
+
contextId String @default("legacy-unknown")
|
|
41
|
+
action String
|
|
42
|
+
targetType String
|
|
43
|
+
targetId String
|
|
44
|
+
traceId String?
|
|
45
|
+
beforeState Json?
|
|
46
|
+
afterState Json?
|
|
47
|
+
reason String?
|
|
48
|
+
createdAt DateTime @default(now())
|
|
39
49
|
|
|
40
50
|
@@index([traceId])
|
|
51
|
+
@@index([projectKey, producerAdapterKey, contextId])
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
// ===== HITL 人机审核架构 =====
|
|
@@ -61,65 +72,85 @@ enum ReviewType {
|
|
|
61
72
|
RUNTIME
|
|
62
73
|
}
|
|
63
74
|
|
|
75
|
+
enum PlanLifecycleStatus {
|
|
76
|
+
DRAFT
|
|
77
|
+
ACTIVE
|
|
78
|
+
BLOCKED
|
|
79
|
+
REJECTED
|
|
80
|
+
CLOSED
|
|
81
|
+
ABANDONED
|
|
82
|
+
}
|
|
83
|
+
|
|
64
84
|
model HitlRecord {
|
|
65
|
-
id String
|
|
85
|
+
id String @id @default(cuid())
|
|
86
|
+
projectKey String @default("legacy-unknown")
|
|
87
|
+
adapterKey String @default("legacy-unknown")
|
|
66
88
|
planName String
|
|
67
|
-
plan PlanRecord
|
|
68
|
-
round Int
|
|
69
|
-
type HitlType
|
|
70
|
-
status HitlStatus
|
|
89
|
+
plan PlanRecord @relation(fields: [projectKey, adapterKey, planName], references: [projectKey, adapterKey, planName], onDelete: Cascade)
|
|
90
|
+
round Int @default(1)
|
|
91
|
+
type HitlType @default(PLAN)
|
|
92
|
+
status HitlStatus @default(DRAFT)
|
|
71
93
|
proposalPath String?
|
|
72
94
|
approvedAt DateTime?
|
|
73
95
|
rejectedAt DateTime?
|
|
74
96
|
rejectReason String?
|
|
75
|
-
createdBy String
|
|
76
|
-
createdAt DateTime
|
|
77
|
-
updatedAt DateTime
|
|
97
|
+
createdBy String @default("ai-assistant")
|
|
98
|
+
createdAt DateTime @default(now())
|
|
99
|
+
updatedAt DateTime @updatedAt
|
|
78
100
|
|
|
79
|
-
@@unique([planName, round])
|
|
80
|
-
@@index([planName])
|
|
101
|
+
@@unique([projectKey, adapterKey, planName, type, round])
|
|
102
|
+
@@index([projectKey, adapterKey, planName])
|
|
81
103
|
@@index([status])
|
|
82
104
|
}
|
|
83
105
|
|
|
84
106
|
model PlanRecord {
|
|
85
|
-
id
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
id String @id @default(cuid())
|
|
108
|
+
projectKey String @default("legacy-unknown")
|
|
109
|
+
adapterKey String @default("legacy-unknown")
|
|
110
|
+
planName String
|
|
111
|
+
lifecycle PlanLifecycleStatus @default(DRAFT)
|
|
112
|
+
revision Int @default(0)
|
|
113
|
+
planPath String
|
|
114
|
+
specPath String?
|
|
115
|
+
tasksPath String?
|
|
116
|
+
checklistPath String?
|
|
117
|
+
addRoutePath String?
|
|
118
|
+
totalTasks Int @default(0)
|
|
119
|
+
doneTasks Int @default(0)
|
|
120
|
+
checklistT Int @default(0)
|
|
121
|
+
checklistTDone Int @default(0)
|
|
122
|
+
checklistR Int @default(0)
|
|
123
|
+
planKeyword String?
|
|
98
124
|
// 并发协作契约:该 Plan 在契约中的角色(MASTER=总控 / SUB=子 Plan)
|
|
99
|
-
contractRole
|
|
125
|
+
contractRole ContractRole?
|
|
100
126
|
// 所属契约名(与 CollabContract.contractName 对应,反查用)
|
|
101
|
-
contractName
|
|
127
|
+
contractName String?
|
|
102
128
|
// 作为总控 Plan 持有的契约(masterPlanName 外键方向)
|
|
103
|
-
masterContract
|
|
129
|
+
masterContract CollabContract? @relation("MasterPlan")
|
|
130
|
+
contractBindings CollaborationPlanBinding[]
|
|
104
131
|
// DPS 四维评分历史(check_dps 调用后回写,供 FFT 自适应权重消费)
|
|
105
|
-
dpsSemScore
|
|
106
|
-
dpsEntropyScore
|
|
107
|
-
dpsCpmScore
|
|
108
|
-
dpsStructScore
|
|
109
|
-
dpsComposite
|
|
110
|
-
hitls
|
|
111
|
-
reviews
|
|
112
|
-
createdAt
|
|
113
|
-
updatedAt
|
|
114
|
-
|
|
132
|
+
dpsSemScore Int? // 语义相关性 0-100
|
|
133
|
+
dpsEntropyScore Int? // 信息熵匹配 0-100
|
|
134
|
+
dpsCpmScore Int? // CPM 关键路径 0-100
|
|
135
|
+
dpsStructScore Int? // 结构完整度 0-100
|
|
136
|
+
dpsComposite Int? // 加权 DPS 总分 0-100
|
|
137
|
+
hitls HitlRecord[]
|
|
138
|
+
reviews ReviewRecord[]
|
|
139
|
+
createdAt DateTime @default(now())
|
|
140
|
+
updatedAt DateTime @updatedAt
|
|
141
|
+
|
|
142
|
+
@@unique([projectKey, adapterKey, planName])
|
|
115
143
|
@@index([planKeyword])
|
|
116
144
|
@@index([contractName])
|
|
145
|
+
@@index([projectKey, adapterKey, lifecycle])
|
|
117
146
|
}
|
|
118
147
|
|
|
119
148
|
model ReviewRecord {
|
|
120
149
|
id String @id @default(cuid())
|
|
150
|
+
projectKey String @default("legacy-unknown")
|
|
151
|
+
adapterKey String @default("legacy-unknown")
|
|
121
152
|
planName String
|
|
122
|
-
plan PlanRecord @relation(fields: [planName], references: [planName], onDelete: Cascade)
|
|
153
|
+
plan PlanRecord @relation(fields: [projectKey, adapterKey, planName], references: [projectKey, adapterKey, planName], onDelete: Cascade)
|
|
123
154
|
type ReviewType @default(PLAN_REVIEW)
|
|
124
155
|
reviewPath String?
|
|
125
156
|
p0Count Int @default(0)
|
|
@@ -128,33 +159,60 @@ model ReviewRecord {
|
|
|
128
159
|
createdAt DateTime @default(now())
|
|
129
160
|
updatedAt DateTime @updatedAt
|
|
130
161
|
|
|
131
|
-
@@index([planName])
|
|
162
|
+
@@index([projectKey, adapterKey, planName])
|
|
132
163
|
@@index([type])
|
|
133
164
|
}
|
|
134
165
|
|
|
135
166
|
enum ContractRole {
|
|
136
|
-
MASTER
|
|
137
|
-
SUB
|
|
167
|
+
MASTER // 总控 Plan(契约持有者)
|
|
168
|
+
SUB // 子 Plan(契约实施单元)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
enum CollaborationBindingRole {
|
|
172
|
+
MASTER
|
|
173
|
+
MEMBER
|
|
138
174
|
}
|
|
139
175
|
|
|
140
176
|
// 并发协作契约(多智能体并行实施规则集,对应 collab-contract 文档)
|
|
141
177
|
model CollabContract {
|
|
142
|
-
id String
|
|
143
|
-
|
|
178
|
+
id String @id @default(cuid())
|
|
179
|
+
projectKey String @default("legacy-unknown")
|
|
180
|
+
ownerAdapterKey String @default("legacy-unknown")
|
|
181
|
+
contractName String
|
|
144
182
|
contractPath String
|
|
145
183
|
// 外键:总控 Plan(契约持有者,ContractRole=MASTER,一对一)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
184
|
+
masterProjectKey String @default("legacy-unknown")
|
|
185
|
+
masterAdapterKey String @default("legacy-unknown")
|
|
186
|
+
masterPlanName String
|
|
187
|
+
masterPlan PlanRecord @relation("MasterPlan", fields: [masterProjectKey, masterAdapterKey, masterPlanName], references: [projectKey, adapterKey, planName], onDelete: Restrict)
|
|
188
|
+
bindings CollaborationPlanBinding[]
|
|
189
|
+
participants Json // [{role, platformEntity, boundPlan, planKeyword, description}]
|
|
190
|
+
abilityMatrix Json? // {能力: {专家: boolean}}
|
|
191
|
+
stages Json // [{stage, expert, trigger, parallelism}]
|
|
192
|
+
dependencyGraph String? // ASCII 图 + 机器可读声明
|
|
193
|
+
fileBoundaries Json // [{expert, exclusiveDomain, forbidden, isolationMode}] isolationMode: file/worktree
|
|
194
|
+
completionCriteria Json? // {专家: 完成标志}
|
|
195
|
+
status String @default("ACTIVE") // ACTIVE / SUPERSEDED
|
|
196
|
+
version Int @default(1)
|
|
197
|
+
createdAt DateTime @default(now())
|
|
198
|
+
updatedAt DateTime @updatedAt
|
|
199
|
+
|
|
200
|
+
@@unique([projectKey, contractName])
|
|
201
|
+
@@unique([masterProjectKey, masterAdapterKey, masterPlanName])
|
|
202
|
+
@@index([projectKey, ownerAdapterKey])
|
|
203
|
+
}
|
|
158
204
|
|
|
159
|
-
|
|
205
|
+
model CollaborationPlanBinding {
|
|
206
|
+
id String @id @default(cuid())
|
|
207
|
+
contractId String
|
|
208
|
+
contract CollabContract @relation(fields: [contractId], references: [id], onDelete: Cascade)
|
|
209
|
+
projectKey String
|
|
210
|
+
adapterKey String
|
|
211
|
+
planName String
|
|
212
|
+
plan PlanRecord @relation(fields: [projectKey, adapterKey, planName], references: [projectKey, adapterKey, planName], onDelete: Cascade)
|
|
213
|
+
role CollaborationBindingRole @default(MEMBER)
|
|
214
|
+
createdAt DateTime @default(now())
|
|
215
|
+
|
|
216
|
+
@@unique([contractId, projectKey, adapterKey, planName])
|
|
217
|
+
@@index([projectKey, adapterKey, planName])
|
|
160
218
|
}
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
| P2 | ADD-13 DPS 上游文档质量闸门 | Plan 概括度 → Review 注意力稀释 → Specs 遗漏 → 实现偏差。`check_dps`(DPS ≥ {{dpsPass}})在 Step 0 末尾量化阻断 | ✅ 已实现 | 2026-06-11 引入 |
|
|
27
27
|
| P2 | ADD-14 RAHS 下游执行健康度闸门 | 范围保真度 + 类型安全 + 审计完整度 + Spec 合规 + 阶段对称性。`check_rahs`(RAHS ≥ 90)在 Step 4/8 量化阻断 | ✅ 已实现 | 2026-06-11 引入 |
|
|
28
28
|
| P2 | ADD-15 add-route 闭环自检 | Step 3 代码完成后必须调用 `check_add_route_completeness` 扫描 add-route Step 完成度,防止执行遗漏。返回 complete 方可进入 Step 3.5 | ✅ 已实现 | 2026-06-11 引入 |
|
|
29
|
-
| P2 | ADD-17 HITL
|
|
30
|
-
| P2 | ADD-18 HITL 人机审核强制规则 | Plan
|
|
29
|
+
| P2 | ADD-17 HITL 提案载体与状态机 | `create_hitl` 接收 `dimensions`,创建 `HitlRecord(DRAFT)` 与 `*.hitl.md` 提案;人工拍板后由 `update_hitl` 写入终态,正式文档仅在 `status_hitl=TONGYI` 后生成 | ✅ 已实现 | 2026-07-23 引入,2026-08-12 对齐 MCP 实现 |
|
|
30
|
+
| P2 | ADD-18 HITL 人机审核强制规则 | Plan、方案 Review、实现 Review 必须经过 `create_hitl` → 人工 tongyi/bohui → `update_hitl`;`TONGYI` 同步写 DB、提案状态和 `.tongyi-{planName}` 哨兵 | ✅ 已实现 | 2026-07-27 引入 |
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
### P0 规则:不可跳过
|
|
@@ -68,7 +68,7 @@ AI 助手必须先恢复到基线、找到对应的 SKILL 文件并按步骤执
|
|
|
68
68
|
| ADD-14 | `add-paradigm` Step 4 末端 + Step 8 收敛 + MCP `check_rahs` | Step 4 合规检查 / Step 8 收敛判定 |
|
|
69
69
|
| ADD-15 | `add-paradigm` Step 3.6 + MCP `check_add_route_completeness` | Step 3 代码完成后自检 |
|
|
70
70
|
| ADD-16 | `add-paradigm` Step 9 | runtime-fix plan 收敛后,关闭 gateway.md 运行时发现 |
|
|
71
|
-
| ADD-17 | `add-paradigm` 独立能力:生成 Plan / Step 0 | Plan/Review HITL
|
|
71
|
+
| ADD-17 | `add-paradigm` 独立能力:生成 Plan / Step 0 / Step 3.5 | Plan/Review HITL:整理 dimensions → create_hitl 生成提案 → update_hitl 拍板 → status_hitl 校验终态 |
|
|
72
72
|
| ADD-18 | `add-paradigm` 独立能力:生成 Plan + Step 0 + pre-tool-use §C | HITL 审批强制:create_hitl → 人工 tongyi/bohui → update_hitl 确认;hook 哨兵检查 |
|
|
73
73
|
|
|
74
74
|
---
|
|
@@ -783,45 +783,47 @@ ADD-0.1 要求"文档先行"(先改文档再改代码)。ADD-16 是对 ADD-0
|
|
|
783
783
|
|
|
784
784
|
---
|
|
785
785
|
|
|
786
|
-
## ADD-17:HITL
|
|
786
|
+
## ADD-17:HITL 提案载体与状态机
|
|
787
787
|
|
|
788
|
-
**Plan
|
|
788
|
+
**Plan、方案 Review 和实现 Review 在写入正式文件前,必须通过 `create_hitl` 建立结构化审批提案。`*.hitl.md` 与 `HitlRecord` 是同一轮审批的文件/数据库双通道载体;正式文档不得替代审批提案。**
|
|
789
789
|
|
|
790
790
|
### 流程
|
|
791
791
|
|
|
792
792
|
```
|
|
793
|
-
① AI
|
|
794
|
-
②
|
|
795
|
-
③
|
|
796
|
-
④
|
|
793
|
+
① AI 读取对应模板并将待拍板发现整理为 dimensions
|
|
794
|
+
② create_hitl({ planName, type, dimensions }) → HitlRecord(DRAFT) + plans/{YYYY-MM}/{DD}/{planName}.hitl.md
|
|
795
|
+
③ 人类审阅提案并逐项拍板 → update_hitl({ planName, type, status })
|
|
796
|
+
④ status_hitl 确认 TONGYI → 生成完整 Plan/Review 并写入正式路径
|
|
797
|
+
⑤ BOHUI → 修订 dimensions 后调用 create_hitl 创建 round+1
|
|
797
798
|
```
|
|
798
799
|
|
|
799
800
|
### 适用范围
|
|
800
801
|
|
|
801
|
-
| 文档类型 |
|
|
802
|
+
| 文档类型 | HITL type | 正式写入路径 |
|
|
802
803
|
|---------|:---:|------|
|
|
803
|
-
| Plan(标准版/精简版) |
|
|
804
|
-
| Review(方案/实现) |
|
|
805
|
-
|
|
|
806
|
-
|
|
|
804
|
+
| Plan(标准版/精简版) | `PLAN` | `{{magicDir}}/plans/{YYYY-MM}/{DD}/` |
|
|
805
|
+
| Review(方案/实现) | `PLAN_REVIEW` | `{{magicDir}}/reviews/` |
|
|
806
|
+
| Runtime Review | 不需要 | `[T]` 全部通过后直接写入 `{{magicDir}}/reviews/` |
|
|
807
|
+
| Spec / Tasks / Checklist | 不需要 | 直接写入 `{{magicDir}}/specs/` |
|
|
808
|
+
| Handoff | 不需要 | 直接写入 `{{magicDir}}/plans/` |
|
|
807
809
|
|
|
808
810
|
### 与 ADD-0.1 的关系
|
|
809
811
|
|
|
810
|
-
ADD-0.1
|
|
812
|
+
ADD-0.1 要求“文档先行”。`create_hitl` 先持久化决策维度与审批状态,再展开正式文档,使“先拍板、后展开”具备可查询、可追溯的事实源。
|
|
811
813
|
|
|
812
814
|
---
|
|
813
815
|
|
|
814
816
|
## ADD-18:HITL 人机审核强制规则
|
|
815
817
|
|
|
816
|
-
**Plan
|
|
818
|
+
**Plan、方案 Review 和实现 Review 在写入正式文件前,必须经过 HITL 审批。** 审批通过后,`update_hitl` 更新数据库与 `*.hitl.md` 状态,并在 `{{magicDir}}/hitl/` 生成 `.tongyi-{planName}` 哨兵。pre-tool-use hook 对 Plan/方案 Review 执行哨兵门禁;实现 Review 由 `add-paradigm` Step 3.5 与 `status_hitl` 门禁约束。
|
|
817
819
|
|
|
818
820
|
### 强制流程
|
|
819
821
|
|
|
820
822
|
```
|
|
821
|
-
① create_hitl({ planName, type }) → HitlRecord DRAFT,生成 hitl.md 提案文件
|
|
822
|
-
② 人类审阅 hitl.md → 逐行拍板
|
|
823
|
-
③ update_hitl({ planName, status: "TONGYI" }) →
|
|
824
|
-
或 update_hitl({ planName, status: "BOHUI", reason }) →
|
|
823
|
+
① create_hitl({ planName, type, dimensions }) → HitlRecord DRAFT,生成 *.hitl.md 提案文件
|
|
824
|
+
② 人类审阅 *.hitl.md → 逐行拍板
|
|
825
|
+
③ update_hitl({ planName, type, status: "TONGYI" }) → 更新 DB/提案并写 .tongyi-{planName} 哨兵
|
|
826
|
+
或 update_hitl({ planName, type, status: "BOHUI", reason }) → 更新 DB/提案并写 .bohui-{planName} 哨兵
|
|
825
827
|
④ TONGYI 后:正式写入 Plan/Review 文件(hook §C 放行)
|
|
826
828
|
⑤ BOHUI 后:create_hitl 新建 round+1,重新审批
|
|
827
829
|
```
|
|
@@ -830,15 +832,16 @@ ADD-0.1 要求"文档先行"——HITL 磋商本身就是文档先行的一种
|
|
|
830
832
|
|
|
831
833
|
| 文档类型 | HITL 审批 | 依据 |
|
|
832
834
|
|---------|:--------:|------|
|
|
833
|
-
| Plan | ✅ 必须 | pre-tool-use §C
|
|
834
|
-
| Review
|
|
835
|
-
| Review
|
|
835
|
+
| Plan | ✅ 必须 | pre-tool-use §C 哨兵门禁 |
|
|
836
|
+
| 方案 Review | ✅ 必须 | pre-tool-use §C 哨兵门禁 |
|
|
837
|
+
| Implementation Review | ✅ 必须 | `add-paradigm` Step 3.5 + `status_hitl` |
|
|
838
|
+
| Runtime Review | ❌ 不需要 | `[T]` 全部通过后直接生成 |
|
|
836
839
|
| Spec / Tasks / Checklist | ❌ 不需要 | 直接写 |
|
|
837
840
|
| Handoff | ❌ 不需要 | 直接写 |
|
|
838
841
|
|
|
839
842
|
### 与 ADD-17 的关系
|
|
840
843
|
|
|
841
|
-
ADD-17
|
|
844
|
+
ADD-17 定义审批提案的载体、维度和状态流转;ADD-18 定义哪些正式文档必须先取得 `TONGYI`。两者共同以 `create_hitl → update_hitl → status_hitl` 为事实链,文件、数据库和哨兵相互校验。
|
|
842
845
|
|
|
843
846
|
---
|
|
844
847
|
|
|
@@ -156,6 +156,14 @@ apply_atlas_diff() {
|
|
|
156
156
|
# ⑦ Atlas 同步主流程(探测 → dev-url → 目标 → baseline → diff → apply)
|
|
157
157
|
atlas_sync() {
|
|
158
158
|
[ "$ENGINE" = "sqlite" ] && return 0
|
|
159
|
+
# 迁移锁(进程层契约 v2 §6):多 IDE 并发 init 时仅一个进程执行迁移
|
|
160
|
+
# 非阻塞拿锁(pg_try_advisory_lock),会话级锁——脚本退出连接断开自动释放
|
|
161
|
+
LOCK_KEY="0xADD001"
|
|
162
|
+
LOCKED="$(podman exec "${PROJECT_NAME:-add-project}-postgres" psql -U "${DATABASE_USER:-admin}" -d "${PROJECT_NAME:-add-project}" -tAc "SELECT pg_try_advisory_lock(${LOCK_KEY});" 2>/dev/null || true)"
|
|
163
|
+
if [ "$LOCKED" != "t" ]; then
|
|
164
|
+
echo "!!! 另一个进程正在迁移,请稍后重试"
|
|
165
|
+
return 1
|
|
166
|
+
fi
|
|
159
167
|
if ! atlas_cmd version > /dev/null 2>&1; then
|
|
160
168
|
echo "!!! Atlas 不可用。add-coder sync --patch 可自动安装 @ariga/atlas;或手动: pnpm add -D @ariga/atlas"
|
|
161
169
|
echo " 降级路径: prisma-diff(免 shadow)→ db-push + 强制备份;文档: README「Atlas 数据库同步能力」"
|
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# 生成 {MAGIC_DIR}/plans/index.md 总览清单 — 按日期分层,每日自动更新
|
|
3
|
-
#
|
|
3
|
+
# MAGIC_DIR 由脚本自身的物理目录确定,各 adapter 自治。
|
|
4
4
|
set -e
|
|
5
5
|
|
|
6
|
-
# 探测 MAGIC_DIR:在当前目录向上查找包含 plans/ 的隐藏目录
|
|
7
6
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
-
|
|
9
|
-
MAGIC_DIR=""
|
|
10
|
-
while [ "$CURRENT" != "/" ]; do
|
|
11
|
-
for d in "$CURRENT"/.qoder "$CURRENT"/.claude "$CURRENT"/.vscode "$CURRENT"/.trae "$CURRENT"/.codex "$CURRENT"/.add; do
|
|
12
|
-
if [ -d "$d/plans" ]; then
|
|
13
|
-
MAGIC_DIR="$d"
|
|
14
|
-
break 2
|
|
15
|
-
fi
|
|
16
|
-
done
|
|
17
|
-
CURRENT="$(dirname "$CURRENT")"
|
|
18
|
-
done
|
|
7
|
+
MAGIC_DIR="$(dirname "$SCRIPT_DIR")"
|
|
19
8
|
|
|
20
|
-
if [ -
|
|
21
|
-
echo "❌
|
|
9
|
+
if [ ! -d "$MAGIC_DIR/plans" ]; then
|
|
10
|
+
echo "❌ 当前 adapter 不存在 plans/: $MAGIC_DIR" >&2
|
|
22
11
|
exit 1
|
|
23
12
|
fi
|
|
24
13
|
|
|
@@ -3,8 +3,10 @@ import { join } from "path"
|
|
|
3
3
|
import { existsSync } from "fs"
|
|
4
4
|
import { readFileSafe, readdirRecursive, PROJECT_ROOT, MAGIC_DIR } from "../shared/fs.js"
|
|
5
5
|
import { prisma } from "../shared/prisma.js"
|
|
6
|
+
import { getRuntimeContext } from "../shared/env.js"
|
|
6
7
|
|
|
7
8
|
export function registerHitlNotifications(server: McpServer) {
|
|
9
|
+
const runtimeContext = getRuntimeContext()
|
|
8
10
|
// ── HITL 表扫描 ──
|
|
9
11
|
const scanHitl = async () => {
|
|
10
12
|
const plansDir = join(PROJECT_ROOT, MAGIC_DIR, "plans")
|
|
@@ -29,7 +31,13 @@ export function registerHitlNotifications(server: McpServer) {
|
|
|
29
31
|
const ops = prisma.devOperation as Record<string, (...a: unknown[]) => unknown>
|
|
30
32
|
const since = new Date(Date.now() - 24 * 60 * 60 * 1000)
|
|
31
33
|
const count = await ops.count({
|
|
32
|
-
where: {
|
|
34
|
+
where: {
|
|
35
|
+
action: "HOOK_INTERCEPT",
|
|
36
|
+
planKeyword: "no-active-plan",
|
|
37
|
+
projectKey: runtimeContext.projectKey,
|
|
38
|
+
producerAdapterKey: runtimeContext.adapterKey,
|
|
39
|
+
createdAt: { gte: since },
|
|
40
|
+
},
|
|
33
41
|
}) as number
|
|
34
42
|
if (count >= 10 && count !== lastWarnedCount) {
|
|
35
43
|
lastWarnedCount = count
|