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.
Files changed (46) hide show
  1. package/README.md +103 -0
  2. package/agents/qa-autonomous.md +131 -0
  3. package/agents/requirement-analyst.md +98 -0
  4. package/agents/security-auditor.md +120 -0
  5. package/agents/ui-tester.md +186 -0
  6. package/bin/openmoneta.js +11 -0
  7. package/hooks/check-plan-exists.sh +154 -0
  8. package/hooks/enforce-docs-first.sh +169 -0
  9. package/hooks/inject-process-context.sh +117 -0
  10. package/hooks/track-changes.sh +46 -0
  11. package/hooks/verify-completion.sh +165 -0
  12. package/hooks.json +30 -0
  13. package/opencode/AGENTS.md.tpl +38 -0
  14. package/opencode/agents/qa-autonomous.md +42 -0
  15. package/opencode/agents/requirement-analyst.md +51 -0
  16. package/opencode/agents/security-auditor.md +46 -0
  17. package/opencode/agents/ui-tester.md +43 -0
  18. package/opencode/plugins/openmoneta-guard.ts +389 -0
  19. package/package.json +41 -0
  20. package/scripts/debug-hooks.sh +54 -0
  21. package/scripts/init-project.sh +438 -0
  22. package/scripts/list-affected-modules.sh +74 -0
  23. package/skills/auth-bypass-testing/SKILL.md +236 -0
  24. package/skills/automated-testing/SKILL.md +162 -0
  25. package/skills/automated-testing/scripts/install-playwright.sh +134 -0
  26. package/skills/module-architect/SKILL.md +256 -0
  27. package/skills/plan-writer/SKILL.md +229 -0
  28. package/skills/requirement-analysis/SKILL.md +163 -0
  29. package/skills/safe-push/SKILL.md +182 -0
  30. package/skills/security-checklist/SKILL.md +116 -0
  31. package/skills/test-strategy/SKILL.md +135 -0
  32. package/skills/ui-test-loop/SKILL.md +161 -0
  33. package/src/cli.js +63 -0
  34. package/src/commands/check.js +30 -0
  35. package/src/commands/init.js +43 -0
  36. package/src/commands/install.js +50 -0
  37. package/src/commands/uninstall.js +74 -0
  38. package/src/commands/update.js +81 -0
  39. package/src/lib/paths.js +46 -0
  40. package/src/lib/version.js +45 -0
  41. package/templates/AGENTS.md.tpl +106 -0
  42. package/templates/docs-INDEX.md.tpl +62 -0
  43. package/templates/env.test.tpl +16 -0
  44. package/templates/karpathy-reference.md +49 -0
  45. package/templates/plans-INDEX.md.tpl +38 -0
  46. 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
+ }'