openmoneta-dev-kit 1.9.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/README.md +103 -0
- package/agents/qa-autonomous.md +131 -0
- package/agents/requirement-analyst.md +98 -0
- package/agents/security-auditor.md +120 -0
- package/agents/ui-tester.md +186 -0
- package/bin/openmoneta.js +11 -0
- package/hooks/check-plan-exists.sh +154 -0
- package/hooks/enforce-docs-first.sh +169 -0
- package/hooks/inject-process-context.sh +117 -0
- package/hooks/track-changes.sh +46 -0
- package/hooks/verify-completion.sh +165 -0
- package/hooks.json +30 -0
- package/opencode/AGENTS.md.tpl +38 -0
- package/opencode/agents/qa-autonomous.md +42 -0
- package/opencode/agents/requirement-analyst.md +51 -0
- package/opencode/agents/security-auditor.md +46 -0
- package/opencode/agents/ui-tester.md +43 -0
- package/opencode/plugins/openmoneta-guard.ts +389 -0
- package/package.json +41 -0
- package/scripts/debug-hooks.sh +54 -0
- package/scripts/init-project.sh +438 -0
- package/scripts/list-affected-modules.sh +74 -0
- package/skills/auth-bypass-testing/SKILL.md +236 -0
- package/skills/automated-testing/SKILL.md +162 -0
- package/skills/automated-testing/scripts/install-playwright.sh +134 -0
- package/skills/module-architect/SKILL.md +256 -0
- package/skills/plan-writer/SKILL.md +229 -0
- package/skills/requirement-analysis/SKILL.md +163 -0
- package/skills/safe-push/SKILL.md +182 -0
- package/skills/security-checklist/SKILL.md +116 -0
- package/skills/test-strategy/SKILL.md +135 -0
- package/skills/ui-test-loop/SKILL.md +161 -0
- package/src/cli.js +63 -0
- package/src/commands/check.js +30 -0
- package/src/commands/init.js +43 -0
- package/src/commands/install.js +50 -0
- package/src/commands/uninstall.js +74 -0
- package/src/commands/update.js +81 -0
- package/src/lib/paths.js +46 -0
- package/src/lib/version.js +45 -0
- package/templates/AGENTS.md.tpl +106 -0
- package/templates/docs-INDEX.md.tpl +62 -0
- package/templates/env.test.tpl +16 -0
- package/templates/karpathy-reference.md +49 -0
- package/templates/plans-INDEX.md.tpl +38 -0
- package/templates/playwright.config.ts.tpl +44 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# preToolUse hook (Write|StrReplace|EditNotebook|Delete):
|
|
3
|
+
# v1.7.0 Adaptive Planning Gate:
|
|
4
|
+
# - Không có plan active → allow cho task thường.
|
|
5
|
+
# - Không có plan active + file nhạy cảm → block, yêu cầu tạo plan Draft + user review.
|
|
6
|
+
# - Có plan Draft → block code edit, yêu cầu user approve rồi đổi Status: In Progress.
|
|
7
|
+
# - Có plan In Progress → enforce file scope theo "Files ảnh hưởng"/"Files thay đổi".
|
|
8
|
+
# Trả permission: deny + agent_message để buộc AI tự correct.
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
INPUT_JSON=$(cat 2>/dev/null || echo '{}')
|
|
13
|
+
|
|
14
|
+
allow() {
|
|
15
|
+
echo '{"permission": "allow"}'
|
|
16
|
+
exit 0
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
deny() {
|
|
20
|
+
local msg="$1"
|
|
21
|
+
if command -v jq >/dev/null 2>&1; then
|
|
22
|
+
jq -n --arg msg "$msg" '{"permission": "deny", "agent_message": $msg}'
|
|
23
|
+
else
|
|
24
|
+
ESCAPED=$(printf '%s' "$msg" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))')
|
|
25
|
+
printf '{"permission": "deny", "agent_message": %s}\n' "$ESCAPED"
|
|
26
|
+
fi
|
|
27
|
+
exit 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
FILE_PATH=""
|
|
31
|
+
WORKSPACE="."
|
|
32
|
+
if command -v jq >/dev/null 2>&1; then
|
|
33
|
+
FILE_PATH=$(echo "$INPUT_JSON" | jq -r '.tool_input.path // .tool_input.file_path // .tool_input.target_notebook // ""' 2>/dev/null || echo "")
|
|
34
|
+
WORKSPACE=$(echo "$INPUT_JSON" | jq -r '.workspace_roots[0] // "."' 2>/dev/null || echo ".")
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
[[ -z "$FILE_PATH" ]] && allow
|
|
38
|
+
|
|
39
|
+
# Convert relative -> absolute
|
|
40
|
+
if [[ "$FILE_PATH" == /* ]]; then
|
|
41
|
+
ABS_PATH="$FILE_PATH"
|
|
42
|
+
else
|
|
43
|
+
ABS_PATH="$WORKSPACE/$FILE_PATH"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# === Skip nếu file nằm ngoài workspace (vd: ~/.cursor/, /tmp/...) ===
|
|
47
|
+
case "$ABS_PATH" in
|
|
48
|
+
"$WORKSPACE"/*) ;;
|
|
49
|
+
*) allow ;;
|
|
50
|
+
esac
|
|
51
|
+
|
|
52
|
+
REL_PATH="${ABS_PATH#$WORKSPACE/}"
|
|
53
|
+
|
|
54
|
+
# === Whitelist: file không cần plan ===
|
|
55
|
+
case "$REL_PATH" in
|
|
56
|
+
README.md|README|CHANGELOG.md|.gitignore|.env.example|AGENTS.md|LICENSE|LICENSE.md|LICENSE.txt)
|
|
57
|
+
allow
|
|
58
|
+
;;
|
|
59
|
+
docs/*|plans/*|.cursor/*|.vscode/*)
|
|
60
|
+
allow
|
|
61
|
+
;;
|
|
62
|
+
.github/*)
|
|
63
|
+
case "$REL_PATH" in
|
|
64
|
+
.github/workflows/*) ;;
|
|
65
|
+
*) allow ;;
|
|
66
|
+
esac
|
|
67
|
+
;;
|
|
68
|
+
*.md)
|
|
69
|
+
case "$REL_PATH" in
|
|
70
|
+
src/*|lib/*|app/*|pages/*|components/*) ;;
|
|
71
|
+
*) allow ;;
|
|
72
|
+
esac
|
|
73
|
+
;;
|
|
74
|
+
esac
|
|
75
|
+
|
|
76
|
+
# === Tìm plan active ===
|
|
77
|
+
PLANS_DIR="$WORKSPACE/plans"
|
|
78
|
+
if [[ ! -d "$PLANS_DIR" ]]; then
|
|
79
|
+
allow
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Tìm plan theo trạng thái. Draft = đang chờ user review, In Progress = đã được approve để triển khai.
|
|
83
|
+
DRAFT_PLANS=$(grep -liE "^\*{0,2}status\*{0,2}:?[[:space:]]*draft" "$PLANS_DIR"/*.md 2>/dev/null || true)
|
|
84
|
+
IN_PROGRESS_PLANS=$(grep -liE "^\*{0,2}status\*{0,2}:?[[:space:]]*in progress" "$PLANS_DIR"/*.md 2>/dev/null || true)
|
|
85
|
+
|
|
86
|
+
is_sensitive_path() {
|
|
87
|
+
local path_lc
|
|
88
|
+
path_lc=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')
|
|
89
|
+
case "$path_lc" in
|
|
90
|
+
.env|.env.*|*auth*|*oauth*|*session*|*token*|*secret*|*credential*|*security*|*permission*|*rbac*|*role*|*admin*|*payment*|*billing*|*invoice*|*subscription*|*checkout*|*db*|*database*|*schema*|*migration*|*prisma*|*drizzle*|*sql*|*deploy*|*infra*|.github/workflows/*)
|
|
91
|
+
return 0
|
|
92
|
+
;;
|
|
93
|
+
*)
|
|
94
|
+
return 1
|
|
95
|
+
;;
|
|
96
|
+
esac
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if [[ -z "$IN_PROGRESS_PLANS" ]]; then
|
|
100
|
+
if [[ -n "$DRAFT_PLANS" ]]; then
|
|
101
|
+
PLAN_LIST=$(echo "$DRAFT_PLANS" | sed "s|$WORKSPACE/||g")
|
|
102
|
+
deny "Có plan Draft nhưng chưa được user approve, nên chưa được sửa code '$REL_PATH':
|
|
103
|
+
$PLAN_LIST
|
|
104
|
+
|
|
105
|
+
Quy trình đúng:
|
|
106
|
+
1. Trình bày tóm tắt plan cho user review.
|
|
107
|
+
2. Hỏi user approve / sửa plan / hủy plan.
|
|
108
|
+
3. Chỉ khi user approve: đổi Status từ Draft → In Progress.
|
|
109
|
+
4. Retry edit này.
|
|
110
|
+
|
|
111
|
+
Lý do: Agent mode được phép tự tạo plan cho task lớn/rủi ro, nhưng KHÔNG được tự approve rồi triển khai."
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
if is_sensitive_path "$REL_PATH"; then
|
|
115
|
+
deny "File '$REL_PATH' thuộc nhóm nhạy cảm/rủi ro cao nhưng chưa có plan được user approve.
|
|
116
|
+
|
|
117
|
+
Nhóm cần plan trước khi code: auth/oauth/session/token/secret/security/permission/admin/payment/billing/db/schema/migration/deploy/infra.
|
|
118
|
+
|
|
119
|
+
Quy trình:
|
|
120
|
+
1. Tạo repo plan: plans/$(date +%Y-%m-%d)-<slug>.md với Status: Draft.
|
|
121
|
+
2. Liệt kê '$REL_PATH' trong '## Files ảnh hưởng' hoặc '## Files thay đổi'.
|
|
122
|
+
3. Trình bày plan cho user review.
|
|
123
|
+
4. Khi user approve: đổi Status: In Progress.
|
|
124
|
+
5. Retry edit này."
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# Adaptive mode: không có plan active và file không nhạy cảm → allow.
|
|
128
|
+
# Agent vẫn phải tự quyết định tạo plan nếu task lớn/rủi ro theo AGENTS.md + plan-writer.
|
|
129
|
+
allow
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# === Kiểm tra file có trong "Files ảnh hưởng" của ít nhất 1 plan active ===
|
|
133
|
+
FOUND=0
|
|
134
|
+
for plan in $IN_PROGRESS_PLANS; do
|
|
135
|
+
if awk '/^##[[:space:]]+(Files ảnh hưởng|Files thay đổi|Files changed|Files)[[:space:]]*$/{flag=1; next} /^##[[:space:]]/{flag=0} flag' "$plan" | grep -qF "$REL_PATH"; then
|
|
136
|
+
FOUND=1
|
|
137
|
+
break
|
|
138
|
+
fi
|
|
139
|
+
done
|
|
140
|
+
|
|
141
|
+
if [[ $FOUND -eq 0 ]]; then
|
|
142
|
+
PLAN_LIST=$(echo "$IN_PROGRESS_PLANS" | sed "s|$WORKSPACE/||g")
|
|
143
|
+
deny "File '$REL_PATH' KHÔNG nằm trong section '## Files ảnh hưởng' của plan(s) active:
|
|
144
|
+
$PLAN_LIST
|
|
145
|
+
|
|
146
|
+
Lựa chọn:
|
|
147
|
+
A) Update plan hiện tại - thêm '$REL_PATH' vào section '## Files ảnh hưởng' hoặc '## Files thay đổi', rồi retry.
|
|
148
|
+
B) Nếu đây là task khác, tạo plan mới với Status: Draft và xin user review trước khi code.
|
|
149
|
+
C) Nếu file này thuộc whitelist (docs/plans/README/CHANGELOG/...), kiểm tra path có đúng không.
|
|
150
|
+
|
|
151
|
+
KHÔNG được tự ý mở rộng scope mà không update plan."
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
allow
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# enforce-docs-first.sh — preToolUse hook (Read|Glob|Grep).
|
|
3
|
+
# Block AI search/read source code nếu CHƯA đọc docs/INDEX.md trong session này.
|
|
4
|
+
# Mục tiêu: enforce token-aware reading workflow của skill requirement-analysis.
|
|
5
|
+
#
|
|
6
|
+
# Marker: <workspace>/.cursor/.docs-index-read (created khi AI Read docs/INDEX.md).
|
|
7
|
+
# Marker được clean ở sessionStart hook → mỗi session phải đọc lại 1 lần.
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
INPUT_JSON=$(cat 2>/dev/null || echo '{}')
|
|
12
|
+
|
|
13
|
+
allow() {
|
|
14
|
+
echo '{"permission": "allow"}'
|
|
15
|
+
exit 0
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
deny() {
|
|
19
|
+
local msg="$1"
|
|
20
|
+
if command -v jq >/dev/null 2>&1; then
|
|
21
|
+
jq -n --arg msg "$msg" '{"permission": "deny", "agent_message": $msg}'
|
|
22
|
+
else
|
|
23
|
+
ESCAPED=$(printf '%s' "$msg" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))')
|
|
24
|
+
printf '{"permission": "deny", "agent_message": %s}\n' "$ESCAPED"
|
|
25
|
+
fi
|
|
26
|
+
exit 0
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
WORKSPACE="."
|
|
30
|
+
TOOL_NAME=""
|
|
31
|
+
PATHS=""
|
|
32
|
+
|
|
33
|
+
if command -v jq >/dev/null 2>&1; then
|
|
34
|
+
WORKSPACE=$(echo "$INPUT_JSON" | jq -r '.workspace_roots[0] // "."' 2>/dev/null || echo ".")
|
|
35
|
+
TOOL_NAME=$(echo "$INPUT_JSON" | jq -r '.tool_name // ""' 2>/dev/null || echo "")
|
|
36
|
+
PATHS=$(echo "$INPUT_JSON" | jq -r '
|
|
37
|
+
.tool_input // {} |
|
|
38
|
+
[.path, .file_path, .target_directory, .pattern, .glob_pattern, .glob] |
|
|
39
|
+
map(select(. != null and . != "")) |
|
|
40
|
+
join(" ")
|
|
41
|
+
' 2>/dev/null || echo "")
|
|
42
|
+
else
|
|
43
|
+
allow
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
[[ -z "$TOOL_NAME" ]] && allow
|
|
47
|
+
|
|
48
|
+
case "$TOOL_NAME" in
|
|
49
|
+
Read|Glob|Grep) ;;
|
|
50
|
+
*) allow ;;
|
|
51
|
+
esac
|
|
52
|
+
|
|
53
|
+
normalize_workspace() {
|
|
54
|
+
local ws="$1"
|
|
55
|
+
if [[ -d "$ws" ]]; then
|
|
56
|
+
(cd "$ws" && pwd -P) 2>/dev/null || printf '%s\n' "$ws"
|
|
57
|
+
else
|
|
58
|
+
printf '%s\n' "$ws"
|
|
59
|
+
fi
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
infer_workspace_from_paths() {
|
|
63
|
+
local raw p dir
|
|
64
|
+
for raw in $PATHS; do
|
|
65
|
+
case "$raw" in
|
|
66
|
+
*/docs/INDEX.md)
|
|
67
|
+
printf '%s\n' "${raw%/docs/INDEX.md}"
|
|
68
|
+
return 0
|
|
69
|
+
;;
|
|
70
|
+
esac
|
|
71
|
+
|
|
72
|
+
case "$raw" in
|
|
73
|
+
/*)
|
|
74
|
+
if [[ -d "$raw" ]]; then
|
|
75
|
+
dir="$raw"
|
|
76
|
+
else
|
|
77
|
+
dir=$(dirname "$raw")
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
while [[ -n "$dir" && "$dir" != "/" && "$dir" != "." ]]; do
|
|
81
|
+
if [[ -f "$dir/docs/INDEX.md" ]]; then
|
|
82
|
+
printf '%s\n' "$dir"
|
|
83
|
+
return 0
|
|
84
|
+
fi
|
|
85
|
+
p=$(dirname "$dir")
|
|
86
|
+
[[ "$p" == "$dir" ]] && break
|
|
87
|
+
dir="$p"
|
|
88
|
+
done
|
|
89
|
+
;;
|
|
90
|
+
esac
|
|
91
|
+
done
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
WORKSPACE=$(normalize_workspace "$WORKSPACE")
|
|
95
|
+
INFERRED_WORKSPACE=$(infer_workspace_from_paths || true)
|
|
96
|
+
if [[ -n "$INFERRED_WORKSPACE" && -f "$INFERRED_WORKSPACE/docs/INDEX.md" ]]; then
|
|
97
|
+
WORKSPACE=$(normalize_workspace "$INFERRED_WORKSPACE")
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
INDEX_FILE="$WORKSPACE/docs/INDEX.md"
|
|
101
|
+
[[ -f "$INDEX_FILE" ]] || allow
|
|
102
|
+
|
|
103
|
+
MARKER="$WORKSPACE/.cursor/.docs-index-read"
|
|
104
|
+
|
|
105
|
+
case "$PATHS" in
|
|
106
|
+
*"docs/INDEX.md"*)
|
|
107
|
+
mkdir -p "$WORKSPACE/.cursor"
|
|
108
|
+
touch "$MARKER"
|
|
109
|
+
allow
|
|
110
|
+
;;
|
|
111
|
+
esac
|
|
112
|
+
|
|
113
|
+
case "$PATHS" in
|
|
114
|
+
*"docs/"*|*"plans/"*|*".cursor/"*|*".github/"*|*".vscode/"*) allow ;;
|
|
115
|
+
*"AGENTS.md"*|*"README"*|*"CHANGELOG"*|*"LICENSE"*) allow ;;
|
|
116
|
+
*"package.json"*|*"package-lock.json"*|*"pnpm-lock.yaml"*|*"yarn.lock"*) allow ;;
|
|
117
|
+
*"tsconfig"*|*"vite.config"*|*"playwright.config"*|*"biome.config"*|*"vitest.config"*|*"astro.config"*) allow ;;
|
|
118
|
+
*".env"*|*".gitignore"*|*".gitattributes"*|*"Dockerfile"*|*"docker-compose"*) allow ;;
|
|
119
|
+
tests/*|test/*|e2e/*|*/tests/*|*/test/*|*/e2e/*|*"__tests__"*) allow ;;
|
|
120
|
+
*"terraform/"*|*"infra/"*|*"k8s/"*|*"kubernetes/"*) allow ;;
|
|
121
|
+
esac
|
|
122
|
+
|
|
123
|
+
SHOULD_CHECK=0
|
|
124
|
+
case "$PATHS" in
|
|
125
|
+
*"apps/"*|*"packages/"*|*"src/"*|*"lib/"*|*"components/"*|*"server/"*|*"modules/"*|*"pages/"*|" app/"*|*"/app/"*)
|
|
126
|
+
SHOULD_CHECK=1
|
|
127
|
+
;;
|
|
128
|
+
esac
|
|
129
|
+
|
|
130
|
+
if [[ "$SHOULD_CHECK" -eq 0 ]]; then
|
|
131
|
+
case "$TOOL_NAME" in
|
|
132
|
+
Glob|Grep) SHOULD_CHECK=1 ;;
|
|
133
|
+
esac
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
if [[ "$SHOULD_CHECK" -eq 1 ]]; then
|
|
137
|
+
if [[ ! -f "$MARKER" ]]; then
|
|
138
|
+
deny "Bạn đang $TOOL_NAME source code TRƯỚC khi đọc docs/INDEX.md. Vi phạm token-aware reading (skill requirement-analysis Bước 1).
|
|
139
|
+
|
|
140
|
+
DEBUG:
|
|
141
|
+
- Tool: $TOOL_NAME
|
|
142
|
+
- Workspace hook đang dùng: $WORKSPACE
|
|
143
|
+
- docs/INDEX.md expected: $INDEX_FILE
|
|
144
|
+
- Marker expected: $MARKER
|
|
145
|
+
- Tool input paths/patterns: $PATHS
|
|
146
|
+
|
|
147
|
+
QUY TRÌNH ĐÚNG (BẮT BUỘC, theo thứ tự):
|
|
148
|
+
1. Read $WORKSPACE/docs/INDEX.md TRƯỚC TIÊN.
|
|
149
|
+
2. Tìm keyword task trong table 'Feature/Keyword → Module' để biết module liên quan.
|
|
150
|
+
3. Read docs/modules/<matched-module>/README.md cho mỗi module liên quan (1-3 module thường đủ).
|
|
151
|
+
4. CHỈ rồi mới Read/Glob/Grep source code của module đó.
|
|
152
|
+
|
|
153
|
+
LÝ DO ENFORCE:
|
|
154
|
+
- Skip docs/INDEX.md = lãng phí 30-50% token (đọc thừa file không liên quan).
|
|
155
|
+
- Bỏ qua context module-level (Public API, dependencies, design rationale, anti-patterns).
|
|
156
|
+
- Có thể vô tình vi phạm architecture rules.
|
|
157
|
+
|
|
158
|
+
HÀNH ĐỘNG NGAY:
|
|
159
|
+
→ Read $WORKSPACE/docs/INDEX.md, sau đó retry $TOOL_NAME này.
|
|
160
|
+
|
|
161
|
+
LƯU Ý:
|
|
162
|
+
- Nếu bạn đã đọc docs/INDEX.md bằng subagent thì parent agent vẫn phải Read docs/INDEX.md một lần để tạo marker trong session hiện tại.
|
|
163
|
+
- Không delegate subagent chỉ để né hook. Subagent chỉ dùng khi task thật sự cần exploration/read-only isolation.
|
|
164
|
+
|
|
165
|
+
(Hook này chỉ chạy 1 lần / session. Sau khi đọc docs/INDEX.md, marker .cursor/.docs-index-read được set và hook sẽ allow mọi tool call source-code khác trong session.)"
|
|
166
|
+
fi
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
allow
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# sessionStart hook: inject SUMMARY ngắn (~45 dòng) của quy trình 6 bước vào context.
|
|
3
|
+
#
|
|
4
|
+
# v1.7.0 Lean Mode: KHÔNG inject full AGENTS.md.tpl nữa (giảm ~1500 tokens/session).
|
|
5
|
+
# AGENTS.md ở project root vẫn có đầy đủ chi tiết — AI đọc khi cần.
|
|
6
|
+
#
|
|
7
|
+
# Cũng check update từ remote git repo (cache 24h).
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
INPUT_JSON=$(cat 2>/dev/null || echo '{}')
|
|
12
|
+
|
|
13
|
+
# Reset docs-index marker mỗi session (force re-read INDEX.md)
|
|
14
|
+
if command -v jq >/dev/null 2>&1; then
|
|
15
|
+
WORKSPACE_FROM_INPUT=$(echo "$INPUT_JSON" | jq -r '.workspace_roots[0] // ""' 2>/dev/null || echo "")
|
|
16
|
+
if [[ -n "$WORKSPACE_FROM_INPUT" && -d "$WORKSPACE_FROM_INPUT/.cursor" ]]; then
|
|
17
|
+
rm -f "$WORKSPACE_FROM_INPUT/.cursor/.docs-index-read" 2>/dev/null || true
|
|
18
|
+
fi
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# === Summary 6 bước (~45 dòng, ~650 tokens) ===
|
|
22
|
+
SUMMARY='# OpenMoneta Dev Kit v1.7.0 — Quy trình 6 bước (Adaptive Planning)
|
|
23
|
+
|
|
24
|
+
> Đây là context inject mỗi session. Chi tiết đầy đủ: `<workspace>/AGENTS.md`. Skills chi tiết: `~/.cursor/skills/<name>/SKILL.md`.
|
|
25
|
+
|
|
26
|
+
## Nguyên tắc vàng
|
|
27
|
+
1. Trả lời tiếng Việt.
|
|
28
|
+
2. Đọc `docs/INDEX.md` ĐẦU TIÊN (hook `enforce-docs-first` chặn Read source nếu chưa đọc).
|
|
29
|
+
3. Hỏi clarify TRƯỚC khi plan/code khi yêu cầu chưa rõ; nếu có plan thì ghi vào "## Hiểu yêu cầu".
|
|
30
|
+
4. Surgical changes: mỗi dòng đổi trace về 1 task plan.
|
|
31
|
+
5. Simplicity First: không abstraction/config dư thừa.
|
|
32
|
+
6. Safe Push: khi user yêu cầu push, PHẢI sync remote ngay trước push; KHÔNG force-push shared branch.
|
|
33
|
+
|
|
34
|
+
## 6 bước
|
|
35
|
+
- **B1 Phân tích** (skill `requirement-analysis`): Read INDEX → match Token Routing → đọc README module candidate → đọc source → đặt câu hỏi critical để làm rõ yêu cầu, phân tích mọi trường hợp, phản biện và đề xuất phương án tối ưu nếu có (hoặc khai báo skip lý do).
|
|
36
|
+
- **B2 Thiết kế** (skill `module-architect`): SRP 1 module 1 trách nhiệm. Feature mới có concept riêng → module mới (KHÔNG nhét utils/common/shared). Existing project: backfill `docs/modules/<slug>/README.md` 3 sections nếu thiếu. Module mới → thêm keyword vào Token Routing.
|
|
37
|
+
- **B3 Adaptive Plan** (skill `plan-writer`): task nhỏ/rõ → không cần plan. Task lớn/rủi ro/mơ hồ → tạo repo plan `Status: Draft`, trình user review, chỉ code sau khi user approve và đổi `Status: In Progress`.
|
|
38
|
+
- **B4 Triển khai**: nếu có plan active → chỉ edit file trong scope plan. Không plan → chỉ sửa đúng yêu cầu; nếu vượt ngưỡng/rủi ro → dừng tạo plan Draft.
|
|
39
|
+
- **B5 Update doc + close** (hook `verify-completion` Check 6 enforce): sync module README (Public API + Dependencies). Plan → `Status: Done` + auto-archive `git mv plans/<f>.md plans/archive/`.
|
|
40
|
+
- **B6 Pre-push Sync + Safe Push** (skill `safe-push`, CONDITIONAL): chỉ khi user yêu cầu push/commit+push. `git fetch` + rebase trước push, conflict code logic → hỏi user, verify lại, `git push` thường, retry ≤3, KHÔNG `--force` shared branch.
|
|
41
|
+
|
|
42
|
+
## Sub-agents (delegate khi cần)
|
|
43
|
+
- `requirement-analyst` (core, yêu cầu lớn).
|
|
44
|
+
- `security-auditor`, `qa-autonomous`, `ui-tester` — **ON-DEMAND ONLY** (chỉ khi user yêu cầu explicit).
|
|
45
|
+
|
|
46
|
+
## Skills on-demand (KHÔNG tự trigger)
|
|
47
|
+
`security-checklist`, `test-strategy`, `automated-testing`, `auth-bypass-testing`, `ui-test-loop` — chỉ kích hoạt khi user yêu cầu rõ ("audit security X", "viết test cho Y", "Playwright UI test Z").
|
|
48
|
+
|
|
49
|
+
## Skill core conditional
|
|
50
|
+
`safe-push` — bắt buộc khi user yêu cầu push/đẩy code để tránh đè code người khác trong repo nhiều contributor.
|
|
51
|
+
'
|
|
52
|
+
|
|
53
|
+
# === Auto-check update (cache 24h) ===
|
|
54
|
+
UPDATE_NOTICE=""
|
|
55
|
+
VERSION_FILE="$HOME/.cursor/.openmoneta-version"
|
|
56
|
+
REPO_FILE="$HOME/.cursor/.openmoneta-repo"
|
|
57
|
+
CHECK_CACHE="$HOME/.cursor/.openmoneta-update-check"
|
|
58
|
+
|
|
59
|
+
[[ -f "$VERSION_FILE" ]] || VERSION_FILE="$HOME/.cursor/.cursor-team-dev-version"
|
|
60
|
+
[[ -f "$REPO_FILE" ]] || REPO_FILE="$HOME/.cursor/.cursor-team-dev-repo"
|
|
61
|
+
|
|
62
|
+
check_update() {
|
|
63
|
+
[[ -f "$VERSION_FILE" && -f "$REPO_FILE" ]] || return 0
|
|
64
|
+
|
|
65
|
+
local current_version repo_dir
|
|
66
|
+
current_version=$(cat "$VERSION_FILE" 2>/dev/null || echo "")
|
|
67
|
+
repo_dir=$(cat "$REPO_FILE" 2>/dev/null || echo "")
|
|
68
|
+
|
|
69
|
+
[[ -n "$current_version" && -d "$repo_dir/.git" ]] || return 0
|
|
70
|
+
|
|
71
|
+
if [[ -f "$CHECK_CACHE" ]]; then
|
|
72
|
+
local last_check now diff
|
|
73
|
+
last_check=$(stat -f %m "$CHECK_CACHE" 2>/dev/null || stat -c %Y "$CHECK_CACHE" 2>/dev/null || echo 0)
|
|
74
|
+
now=$(date +%s)
|
|
75
|
+
diff=$((now - last_check))
|
|
76
|
+
if [[ $diff -lt 86400 ]]; then
|
|
77
|
+
if [[ -s "$CHECK_CACHE" ]]; then
|
|
78
|
+
UPDATE_NOTICE=$(cat "$CHECK_CACHE")
|
|
79
|
+
fi
|
|
80
|
+
return 0
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
> "$CHECK_CACHE"
|
|
85
|
+
|
|
86
|
+
command -v git >/dev/null 2>&1 || return 0
|
|
87
|
+
local remote_version branch
|
|
88
|
+
branch=$(git -C "$repo_dir" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
|
|
89
|
+
|
|
90
|
+
remote_version=$(timeout 5 git -C "$repo_dir" fetch origin "$branch" --quiet 2>/dev/null && \
|
|
91
|
+
git -C "$repo_dir" show "origin/$branch:VERSION" 2>/dev/null | tr -d '[:space:]' || echo "")
|
|
92
|
+
|
|
93
|
+
if [[ -n "$remote_version" && "$remote_version" != "$current_version" ]]; then
|
|
94
|
+
UPDATE_NOTICE="
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
> [!] **OPENMONETA DEV KIT UPDATE AVAILABLE**: v$current_version → v$remote_version
|
|
98
|
+
> Chạy: \`bash $repo_dir/update.sh\` (macOS/Linux) hoặc \`pwsh $repo_dir/update.ps1\` (Windows). Restart Cursor sau update.
|
|
99
|
+
---
|
|
100
|
+
"
|
|
101
|
+
echo "$UPDATE_NOTICE" > "$CHECK_CACHE"
|
|
102
|
+
fi
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
check_update 2>/dev/null || true
|
|
106
|
+
|
|
107
|
+
FULL_CONTEXT="${SUMMARY}${UPDATE_NOTICE}"
|
|
108
|
+
|
|
109
|
+
if command -v jq >/dev/null 2>&1; then
|
|
110
|
+
jq -n --arg ctx "$FULL_CONTEXT" '{
|
|
111
|
+
"continue": true,
|
|
112
|
+
"additional_context": $ctx
|
|
113
|
+
}'
|
|
114
|
+
else
|
|
115
|
+
ESCAPED=$(printf '%s' "$FULL_CONTEXT" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))')
|
|
116
|
+
printf '{"continue": true, "additional_context": %s}\n' "$ESCAPED"
|
|
117
|
+
fi
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# afterFileEdit hook: silent track file đã bị sửa trong session.
|
|
3
|
+
# Append vào <workspace>/.cursor/.session-changes.json
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
INPUT_JSON=$(cat 2>/dev/null || echo '{}')
|
|
8
|
+
|
|
9
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
10
|
+
# Không có jq -> skip silent (không block agent)
|
|
11
|
+
echo '{"continue": true}'
|
|
12
|
+
exit 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
WORKSPACE=$(echo "$INPUT_JSON" | jq -r '.workspace_roots[0] // "."' 2>/dev/null || echo ".")
|
|
16
|
+
FILE_PATH=$(echo "$INPUT_JSON" | jq -r '.tool_input.path // .tool_input.file_path // .tool_input.target_notebook // ""' 2>/dev/null || echo "")
|
|
17
|
+
|
|
18
|
+
[[ -z "$FILE_PATH" ]] && { echo '{"continue": true}'; exit 0; }
|
|
19
|
+
|
|
20
|
+
# Convert path
|
|
21
|
+
if [[ "$FILE_PATH" == /* ]]; then
|
|
22
|
+
REL_PATH="${FILE_PATH#$WORKSPACE/}"
|
|
23
|
+
else
|
|
24
|
+
REL_PATH="$FILE_PATH"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Tạo .cursor/ nếu chưa có
|
|
28
|
+
mkdir -p "$WORKSPACE/.cursor"
|
|
29
|
+
TRACK_FILE="$WORKSPACE/.cursor/.session-changes.json"
|
|
30
|
+
|
|
31
|
+
# Khởi tạo file nếu chưa có
|
|
32
|
+
if [[ ! -f "$TRACK_FILE" ]]; then
|
|
33
|
+
echo "{\"started_at\": \"$(date -Iseconds)\", \"changes\": []}" > "$TRACK_FILE"
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Append change entry
|
|
37
|
+
TS=$(date -Iseconds)
|
|
38
|
+
TOOL=$(echo "$INPUT_JSON" | jq -r '.tool_name // "unknown"' 2>/dev/null || echo "unknown")
|
|
39
|
+
|
|
40
|
+
TMP=$(mktemp)
|
|
41
|
+
jq --arg path "$REL_PATH" --arg ts "$TS" --arg tool "$TOOL" \
|
|
42
|
+
'.changes += [{"path": $path, "ts": $ts, "tool": $tool}]' \
|
|
43
|
+
"$TRACK_FILE" > "$TMP" && mv "$TMP" "$TRACK_FILE"
|
|
44
|
+
|
|
45
|
+
# Silent - không block, không message
|
|
46
|
+
echo '{"continue": true}'
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# stop hook: verify completion conditions trước khi cho session kết thúc.
|
|
3
|
+
#
|
|
4
|
+
# v1.7.0 Adaptive Planning — checks:
|
|
5
|
+
# 5. Nếu có plan: Plan đánh dấu Status: Done + tất cả checkbox tick
|
|
6
|
+
# 6. Module README synced (chỉ check README tồn tại, KHÔNG check changelog)
|
|
7
|
+
# 7. Cross-module sprawl >3 module → WARN
|
|
8
|
+
# 8. Token-aware reading marker tồn tại → WARN nếu thiếu
|
|
9
|
+
# 9. Nếu có plan: section "## Hiểu yêu cầu" có nội dung không trống → BLOCK (audit clarify)
|
|
10
|
+
#
|
|
11
|
+
# Đã bỏ (so với v1.4.x):
|
|
12
|
+
# - Check 2 (test result file) — test giờ on-demand
|
|
13
|
+
# - Check 3 (UI screenshot) — test UI giờ on-demand
|
|
14
|
+
# - Check 4 (root CHANGELOG updated) — không enforce changelog dự án
|
|
15
|
+
#
|
|
16
|
+
# Graceful escape: nếu loop_count >= 4 -> exit, để user can thiệp.
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
INPUT_JSON=$(cat 2>/dev/null || echo '{}')
|
|
21
|
+
|
|
22
|
+
ok() { echo '{"continue": true}'; exit 0; }
|
|
23
|
+
|
|
24
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
25
|
+
ok
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
LOOP_COUNT=$(echo "$INPUT_JSON" | jq -r '.loop_count // 0' 2>/dev/null || echo 0)
|
|
29
|
+
WORKSPACE=$(echo "$INPUT_JSON" | jq -r '.workspace_roots[0] // "."' 2>/dev/null || echo ".")
|
|
30
|
+
|
|
31
|
+
LOOP_LIMIT=5
|
|
32
|
+
if [[ "$LOOP_COUNT" -ge $((LOOP_LIMIT - 1)) ]]; then
|
|
33
|
+
echo "[verify-completion] loop_count=$LOOP_COUNT, graceful exit. Hãy review thủ công." >&2
|
|
34
|
+
ok
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
PLANS_DIR="$WORKSPACE/plans"
|
|
38
|
+
[[ -d "$PLANS_DIR" ]] || ok
|
|
39
|
+
|
|
40
|
+
CHANGES_FILE="$WORKSPACE/.cursor/.session-changes.json"
|
|
41
|
+
[[ -f "$CHANGES_FILE" ]] || ok
|
|
42
|
+
|
|
43
|
+
NUM_CHANGES=$(jq '.changes | length' "$CHANGES_FILE" 2>/dev/null || echo 0)
|
|
44
|
+
[[ "$NUM_CHANGES" -gt 0 ]] || ok
|
|
45
|
+
|
|
46
|
+
# === Check 1 (gating): Có code change (file ngoài docs/plans/...) ===
|
|
47
|
+
CODE_CHANGED=$(jq -r '.changes[] | .path' "$CHANGES_FILE" 2>/dev/null | grep -vE '^(docs/|plans/|README|\.gitignore|\.env\.example|AGENTS\.md|\.cursor/)' | head -1 || true)
|
|
48
|
+
|
|
49
|
+
if [[ -z "$CODE_CHANGED" ]]; then
|
|
50
|
+
ok
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
ISSUES=()
|
|
54
|
+
WARNINGS=()
|
|
55
|
+
|
|
56
|
+
# === Check 5: Nếu có plan active thì phải Done + checkbox ticked ===
|
|
57
|
+
ACTIVE_PLANS=$(grep -liE "^\*{0,2}status\*{0,2}:?[[:space:]]*(in progress|draft)" "$PLANS_DIR"/*.md 2>/dev/null | grep -v INDEX || true)
|
|
58
|
+
if [[ -n "$ACTIVE_PLANS" ]]; then
|
|
59
|
+
for plan in $ACTIVE_PLANS; do
|
|
60
|
+
PNAME=$(basename "$plan")
|
|
61
|
+
UNTICKED=$(grep -cE "^- \[ \]" "$plan" 2>/dev/null || true)
|
|
62
|
+
UNTICKED=${UNTICKED:-0}
|
|
63
|
+
if [[ "$UNTICKED" -gt 0 ]]; then
|
|
64
|
+
ISSUES+=("❌ Plan '$PNAME' còn $UNTICKED checkbox chưa tick. Hoàn thành các task hoặc chuyển sang plan khác.")
|
|
65
|
+
fi
|
|
66
|
+
if grep -qiE "^\*{0,2}status\*{0,2}:?[[:space:]]*draft" "$plan"; then
|
|
67
|
+
ISSUES+=("❌ Plan '$PNAME' vẫn ở trạng thái Draft. Plan Draft nghĩa là chưa được user approve; không nên có code change gắn với plan này.")
|
|
68
|
+
elif grep -qiE "^\*{0,2}status\*{0,2}:?[[:space:]]*in progress" "$plan"; then
|
|
69
|
+
ISSUES+=("❌ Plan '$PNAME' vẫn ở trạng thái In Progress. Đổi Status: Done sau khi hoàn tất Bước 5 + auto-archive sang plans/archive/.")
|
|
70
|
+
fi
|
|
71
|
+
done
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# === Check 9: Nếu có plan trong session thì phải có section "## Hiểu yêu cầu" ===
|
|
75
|
+
# Audit trail clarify — đảm bảo AI đã hỏi user hoặc khai báo lý do skip.
|
|
76
|
+
# Check áp dụng cho plan active hoặc plan đã sửa/tạo trong session (kể cả đã Done).
|
|
77
|
+
CHANGED_PLANS=$(jq -r '.changes[] | .path' "$CHANGES_FILE" 2>/dev/null | grep -E '^plans/.*\.md$' | grep -v 'plans/INDEX.md' || true)
|
|
78
|
+
ALL_RECENT_PLANS="$ACTIVE_PLANS"
|
|
79
|
+
if [[ -n "$CHANGED_PLANS" ]]; then
|
|
80
|
+
while IFS= read -r rel_plan; do
|
|
81
|
+
[[ -z "$rel_plan" ]] && continue
|
|
82
|
+
ALL_RECENT_PLANS="$ALL_RECENT_PLANS"$'\n'"$WORKSPACE/$rel_plan"
|
|
83
|
+
done <<< "$CHANGED_PLANS"
|
|
84
|
+
fi
|
|
85
|
+
# Cũng check plan ở plans/archive/ nếu vừa archive
|
|
86
|
+
if [[ -d "$PLANS_DIR/archive" ]]; then
|
|
87
|
+
ARCHIVED_RECENT=$(find "$PLANS_DIR/archive" -maxdepth 1 -name "*.md" -newer "$CHANGES_FILE" 2>/dev/null || true)
|
|
88
|
+
ALL_RECENT_PLANS="$ALL_RECENT_PLANS"$'\n'"$ARCHIVED_RECENT"
|
|
89
|
+
fi
|
|
90
|
+
ALL_RECENT_PLANS=$(printf '%s\n' "$ALL_RECENT_PLANS" | awk 'NF && !seen[$0]++')
|
|
91
|
+
|
|
92
|
+
for plan in $ALL_RECENT_PLANS; do
|
|
93
|
+
[[ -z "$plan" || ! -f "$plan" ]] && continue
|
|
94
|
+
PNAME=$(basename "$plan")
|
|
95
|
+
# Phải có heading "## Hiểu yêu cầu"
|
|
96
|
+
if ! grep -qE "^##[[:space:]]+Hiểu yêu cầu" "$plan" 2>/dev/null; then
|
|
97
|
+
ISSUES+=("❌ Plan '$PNAME' THIẾU section '## Hiểu yêu cầu'. Đây là audit trail bắt buộc cho Bước 1 (clarify). Thêm câu hỏi đã hỏi user HOẶC 'Lý do skip clarify: ...'. Xem skill plan-writer template.")
|
|
98
|
+
continue
|
|
99
|
+
fi
|
|
100
|
+
# Phải có nội dung không trống dưới heading (≥1 dòng non-blank, non-comment trong 30 dòng tiếp theo)
|
|
101
|
+
CONTENT=$(awk '/^##[[:space:]]+Hiểu yêu cầu/{flag=1; next} flag && /^##[[:space:]]/{exit} flag {print}' "$plan" | grep -vE '^[[:space:]]*(>.*)?$' | head -5 || true)
|
|
102
|
+
if [[ -z "$(echo "$CONTENT" | tr -d '[:space:]')" ]]; then
|
|
103
|
+
ISSUES+=("❌ Plan '$PNAME' có section '## Hiểu yêu cầu' nhưng TRỐNG. Phải có 'Q: ... → A: ...' (đã hỏi user) HOẶC '**Lý do skip clarify**: ...' (task trivial).")
|
|
104
|
+
fi
|
|
105
|
+
done
|
|
106
|
+
|
|
107
|
+
# === Check 6: Module README synced (BLOCK strict) ===
|
|
108
|
+
LIST_MODULES_SCRIPT="$HOME/.cursor/scripts/list-affected-modules.sh"
|
|
109
|
+
AFFECTED=""
|
|
110
|
+
if [[ -x "$LIST_MODULES_SCRIPT" || -f "$LIST_MODULES_SCRIPT" ]]; then
|
|
111
|
+
AFFECTED=$(bash "$LIST_MODULES_SCRIPT" "$CHANGES_FILE" 2>/dev/null || true)
|
|
112
|
+
|
|
113
|
+
if [[ -n "$AFFECTED" ]]; then
|
|
114
|
+
while IFS=$'\t' read -r slug docs_path source_path; do
|
|
115
|
+
[[ -z "$slug" ]] && continue
|
|
116
|
+
MODULE_README="$WORKSPACE/$docs_path/README.md"
|
|
117
|
+
|
|
118
|
+
if [[ ! -f "$MODULE_README" ]]; then
|
|
119
|
+
ISSUES+=("❌ Module '$slug' (source: $source_path) bị sửa nhưng chưa có '$docs_path/README.md'. Tạo README 3 sections (Trách nhiệm + Public API + Dependencies) theo skill module-architect Bước 2.2 + cập nhật bảng 'Modules hiện có' và 'Token Routing' trong docs/INDEX.md.")
|
|
120
|
+
fi
|
|
121
|
+
done <<< "$AFFECTED"
|
|
122
|
+
|
|
123
|
+
# === Check 7: Cross-module sprawl (WARN) ===
|
|
124
|
+
NUM_MODULES=$(echo "$AFFECTED" | awk -F'\t' 'NF >= 1 { print $1 }' | sort -u | wc -l | tr -d ' ')
|
|
125
|
+
if [[ "$NUM_MODULES" -gt 3 ]]; then
|
|
126
|
+
MODULE_LIST=$(echo "$AFFECTED" | awk -F'\t' 'NF >= 1 { print $1 }' | sort -u | tr '\n' ',' | sed 's/,$//')
|
|
127
|
+
WARNINGS+=("⚠️ Session đụng $NUM_MODULES module ($MODULE_LIST). Cross-module sprawl >3 module = red flag. Plan có giải thích 'Lý do cross-module' chưa? Cân nhắc tách thành nhiều plan nhỏ.")
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# === Check 8: Token-aware reading marker (WARN) ===
|
|
133
|
+
if [[ -f "$WORKSPACE/docs/INDEX.md" && ! -f "$WORKSPACE/.cursor/.docs-index-read" ]]; then
|
|
134
|
+
WARNINGS+=("⚠️ Session có code change nhưng marker .cursor/.docs-index-read không tồn tại — AI có thể đã skip Bước 1 (Read docs/INDEX.md). Verify hook 'enforce-docs-first' đang chạy: cat ~/.cursor/hooks.json | jq '.hooks.preToolUse'. Nếu hook không chạy → restart Cursor sau khi cài v1.5.0.")
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# === Output ===
|
|
138
|
+
WARN_STR=""
|
|
139
|
+
if [[ ${#WARNINGS[@]} -gt 0 ]]; then
|
|
140
|
+
WARN_STR=$(printf '\n%s' "${WARNINGS[@]}")
|
|
141
|
+
WARN_STR="
|
|
142
|
+
Cảnh báo (không block):$WARN_STR
|
|
143
|
+
"
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
if [[ ${#ISSUES[@]} -eq 0 ]]; then
|
|
147
|
+
rm -f "$CHANGES_FILE" 2>/dev/null || true
|
|
148
|
+
if [[ -n "$WARN_STR" ]]; then
|
|
149
|
+
echo "[Verify Completion] PASS với warnings:$WARN_STR" >&2
|
|
150
|
+
fi
|
|
151
|
+
ok
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
ISSUES_STR=$(printf '%s\n' "${ISSUES[@]}")
|
|
155
|
+
|
|
156
|
+
MSG="[Verify Completion v1.7.0] Session chưa thể kết thúc. Còn $((${#ISSUES[@]})) vấn đề:
|
|
157
|
+
|
|
158
|
+
$ISSUES_STR
|
|
159
|
+
$WARN_STR
|
|
160
|
+
Hoàn tất các bước trên rồi thử kết thúc lại. Sau $((LOOP_LIMIT - 1 - LOOP_COUNT)) lần nhắc nữa, hệ thống sẽ ngừng nhắc."
|
|
161
|
+
|
|
162
|
+
jq -n --arg msg "$MSG" '{
|
|
163
|
+
"continue": false,
|
|
164
|
+
"followup_message": $msg
|
|
165
|
+
}'
|