@moneko/core 4.12.2 → 4.12.3

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.
@@ -1,66 +1,102 @@
1
1
  #!/usr/bin/env bash
2
+ #
3
+ # Shared AI Agent ESLint fix Stop hook.
4
+ # Used by both Claude Code (project .claude/settings.json) and Codex
5
+ # (project .codex/config.toml). Runs the project's own `npx mo eslint <file> --fix`
6
+ # over the files the Agent just created/modified/untracked, then blocks the
7
+ # Agent Stop until the remaining ESLint errors are fixed.
8
+ #
9
+ # Exit codes:
10
+ # 0 no target files, or all errors were auto-fixed
11
+ # 2 at least one file still has ESLint errors after --fix (blocks Stop)
2
12
  set -u
3
13
 
14
+ # ---------------------------------------------------------------------------
15
+ # Recursion guard. Both Claude Code and Codex pass a JSON payload on stdin for
16
+ # Stop hooks. When a Stop hook blocks (exit 2), the platform starts a follow-up
17
+ # turn and sets `stop_hook_active` to true in that payload. We must not block
18
+ # again on the follow-up turn, otherwise the Agent loops forever.
19
+ # ---------------------------------------------------------------------------
4
20
  if [ ! -t 0 ]; then
5
- hook_input=$(cat)
6
- if printf '%s' "$hook_input" | grep -Eq '"stop_hook_active"[[:space:]]*:[[:space:]]*true'; then
7
- exit 0
8
- fi
21
+ hook_input="$(cat 2>/dev/null || true)"
22
+ case "$hook_input" in
23
+ *'"stop_hook_active"':*'true'*) exit 0 ;;
24
+ esac
9
25
  fi
10
26
 
11
- repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || {
12
- printf 'AI ESLint hook: unable to locate the Git repository root.\n' >&2
13
- exit 2
14
- }
15
-
16
- cd "$repo_root" || exit 2
27
+ # Locate the repository root so this works from any subdirectory.
28
+ root="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
29
+ cd "$root" || exit 0
17
30
 
18
31
  if ! command -v npx >/dev/null 2>&1; then
19
- printf 'AI ESLint hook: npx is unavailable; install no dependency automatically and fix the environment before continuing.\n' >&2
32
+ printf '%s\n' 'AI ESLint stop hook: npx is unavailable; install the existing project dependencies before retrying. No dependency was installed.' >&2
20
33
  exit 2
21
34
  fi
22
35
 
23
- tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/ai-eslint-fix.XXXXXX") || exit 2
24
- trap 'rm -rf "$tmp_dir"' EXIT
25
-
26
- had_errors=0
27
-
28
- while IFS= read -r -d '' record; do
29
- xy=${record:0:2}
30
- path=${record:3}
36
+ if ! npx --no-install mo --version >/dev/null 2>&1; then
37
+ printf '%s\n' 'AI ESLint stop hook: project command "mo" is unavailable; install the existing project dependencies before retrying. No dependency was installed.' >&2
38
+ exit 2
39
+ fi
31
40
 
32
- case "$xy" in
33
- *R*|*C*)
34
- # Rename/copy records contain a second NUL-delimited path. Always consume it.
35
- IFS= read -r -d '' ignored_path || true
36
- ;;
37
- esac
41
+ # ---------------------------------------------------------------------------
42
+ # Collect target files from NUL-separated porcelain v1 output. This keeps
43
+ # spaces / CJK / other characters intact and correctly consumes the second
44
+ # NUL-terminated path of rename/copy records.
45
+ # ---------------------------------------------------------------------------
46
+ targets=()
47
+ while IFS= read -r -d '' entry; do
48
+ x="${entry:0:1}" # index status: ' ', M, A, D, R, C, U, ?
49
+ y="${entry:1:1}" # worktree status: ' ', M, D, U, ?
50
+ path="${entry:3}" # skip the "XY " prefix and preserve the full path
38
51
 
39
- case "$xy" in
40
- \?\?) should_lint=1 ;;
41
- *)
42
- case "$xy" in
43
- *A*|*M*) should_lint=1 ;;
44
- *) should_lint=0 ;;
45
- esac
46
- ;;
47
- esac
52
+ # Rename (R) / copy (C) records carry a second NUL-terminated source path.
53
+ # Consume it so the next status record is not misaligned.
54
+ if [ "$x" = R ] || [ "$x" = C ]; then
55
+ IFS= read -r -d '' _src || true
56
+ fi
48
57
 
49
- [ "$should_lint" -eq 1 ] || continue
50
- [ -e "$path" ] || [ -L "$path" ] || continue
58
+ # Deleted files are never processed.
59
+ if [ "$x" = D ] || [ "$y" = D ]; then
60
+ continue
61
+ fi
51
62
 
52
- output_file="$tmp_dir/output-${RANDOM}-${RANDOM}"
53
- if ! npx mo eslint "$path" --fix >"$output_file" 2>&1; then
54
- had_errors=1
63
+ # Pure rename/copy (no added or modified content) is not processed.
64
+ if { [ "$x" = R ] || [ "$x" = C ]; } && [ "$y" != M ]; then
65
+ continue
55
66
  fi
56
- if [ -s "$output_file" ]; then
57
- cat "$output_file" >&2
67
+
68
+ # Process: modified (staged or working-tree), added, or untracked.
69
+ if [ "$x" = M ] || [ "$y" = M ] || [ "$x" = A ] || { [ "$x" = '?' ] && [ "$y" = '?' ]; }; then
70
+ targets+=("$path")
58
71
  fi
59
72
  done < <(git status --porcelain=v1 -z --untracked-files=all)
60
73
 
61
- if [ "$had_errors" -ne 0 ]; then
62
- printf '\nAI ESLint hook: 请 Agent 继续修复剩余 ESLint 问题。\n' >&2
74
+ # ---------------------------------------------------------------------------
75
+ # Run the project's own linter one file at a time. A failed file must not stop
76
+ # the remaining files; collect all output and report it once at the end.
77
+ # ---------------------------------------------------------------------------
78
+ failed=0
79
+ collected=""
80
+ for file in "${targets[@]:-}"; do
81
+ # Skip paths that no longer exist on disk.
82
+ [ -e "$file" ] || continue
83
+
84
+ out="$(npx mo eslint "$file" --fix 2>&1)"
85
+ rc=$?
86
+ if [ "$rc" -ne 0 ]; then
87
+ failed=1
88
+ collected+="${out}"$'\n'
89
+ fi
90
+ done
91
+
92
+ if [ "$failed" -ne 0 ]; then
93
+ {
94
+ [ -n "$collected" ] && printf '%s\n' "$collected"
95
+ printf '%s\n' '============================================================'
96
+ printf '%s\n' 'AI ESLint stop hook: 以上文件在 --fix 后仍存在 ESLint error。'
97
+ printf '%s\n' '请继续修复剩余的 ESLint 问题后再结束当前任务。'
98
+ } >&2
63
99
  exit 2
64
100
  fi
65
101
 
66
- exit 0
102
+ exit 0
@@ -20,9 +20,10 @@ if ! command -v npx >/dev/null 2>&1; then
20
20
  exit 2
21
21
  fi
22
22
 
23
- if ! npx mo stylelint src; then
23
+ output=$(npx mo stylelint src 2>&1) || {
24
+ printf '%s\n' "$output" >&2
24
25
  printf '\nAI Stylelint hook: 请 Agent 继续修复剩余 Stylelint 问题。\n' >&2
25
26
  exit 2
26
- fi
27
+ }
27
28
 
28
29
  exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moneko/core",
3
- "version": "4.12.2",
3
+ "version": "4.12.3",
4
4
  "description": "@moneko/core",
5
5
  "main": "lib/index.mjs",
6
6
  "type": "module",