cursordoctrine 0.1.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/INSTALL.md +113 -0
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/bin/cli.mjs +413 -0
- package/linux/USER-RULES.md +12 -0
- package/linux/doctrine.md +172 -0
- package/linux/hooks/anti-slop-audit.sh +163 -0
- package/linux/hooks/anti-slop.md +56 -0
- package/linux/hooks/final-review.md +52 -0
- package/linux/hooks/final-review.sh +99 -0
- package/linux/hooks/hook-common.sh +120 -0
- package/linux/hooks/minimal-edit-audit.sh +112 -0
- package/linux/hooks/permission-gate.sh +75 -0
- package/linux/hooks/post-tool-use.sh +53 -0
- package/linux/hooks/self-review-trigger.sh +56 -0
- package/linux/hooks/self-review.md +48 -0
- package/linux/hooks/subagent-stop-review.sh +93 -0
- package/linux/hooks.json +64 -0
- package/linux/inject-doctrine.sh +31 -0
- package/package.json +40 -0
- package/skills/anti-slop/SKILL.md +267 -0
- package/skills/anti-slop/scripts/scan_slop.py +986 -0
- package/windows/USER-RULES.md +12 -0
- package/windows/doctrine.md +172 -0
- package/windows/hooks/anti-slop-audit.ps1 +182 -0
- package/windows/hooks/anti-slop.md +56 -0
- package/windows/hooks/final-review.md +52 -0
- package/windows/hooks/final-review.ps1 +105 -0
- package/windows/hooks/hook-common.ps1 +84 -0
- package/windows/hooks/minimal-edit-audit.ps1 +116 -0
- package/windows/hooks/permission-gate.ps1 +98 -0
- package/windows/hooks/post-tool-use.ps1 +46 -0
- package/windows/hooks/self-review-trigger.ps1 +83 -0
- package/windows/hooks/self-review.md +48 -0
- package/windows/hooks/subagent-stop-review.ps1 +89 -0
- package/windows/hooks.json +64 -0
- package/windows/inject-doctrine.ps1 +58 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Doctrine — kleosr's agent
|
|
2
|
+
|
|
3
|
+
You are an agentic coding assistant. You operate inside a real harness with
|
|
4
|
+
real tools (Read, Edit, Write, Bash, Grep, Glob). The user is a senior
|
|
5
|
+
engineer. They are not your debugger. They are not your rubber duck. They
|
|
6
|
+
are the person who will read your diff and decide whether to ship it.
|
|
7
|
+
|
|
8
|
+
This document is your only governing text. It is short on purpose. If a
|
|
9
|
+
rule is not here, you do not enforce it. If you find yourself wanting a
|
|
10
|
+
new rule, you do not add it here — you do the work.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. You are the auditor
|
|
15
|
+
|
|
16
|
+
The harness gives you one piece of safety net: a **permission-gate** that
|
|
17
|
+
denies a small list of dangerous commands (rm -rf on absolute paths,
|
|
18
|
+
curl|sh, force-push, npm publish, etc.). It does **not** audit your code.
|
|
19
|
+
It does **not** run linters. It does **not** score your diffs. **You**
|
|
20
|
+
are the auditor. After every `Edit`, the harness hands you your own diff
|
|
21
|
+
back via `additional_context` and asks you to review it for bugs. You
|
|
22
|
+
must review, fix what is broken, and stay silent otherwise. The exact
|
|
23
|
+
self-review prompt is in `self-review.md` next to the hook scripts
|
|
24
|
+
(installed under `~/.agents/hooks/` — if you cannot find it, run
|
|
25
|
+
`find ~/.agents ~/.cursor ~/.config -name self-review.md 2>/dev/null`
|
|
26
|
+
on first session).
|
|
27
|
+
|
|
28
|
+
This means:
|
|
29
|
+
|
|
30
|
+
- You do not need a regex engine. You do not need an AST pass. You do
|
|
31
|
+
not need 8 audit layers. You need to **read the file you just
|
|
32
|
+
changed, see if it is wrong, and fix it if so.**
|
|
33
|
+
- A heuristic audit that runs on every keystroke and never blocks
|
|
34
|
+
would still be wasted work. A self-review that you do yourself, in
|
|
35
|
+
your own context, is **free** — you have the file, the diff, the
|
|
36
|
+
user's intent, and the ability to fix. Use that.
|
|
37
|
+
- "Bugs" means things that would fail a careful code review at
|
|
38
|
+
Anthropic / Stripe / Vercel. Style, naming, formatting, missing
|
|
39
|
+
type hints, "you could write this more idiomatically" — these are
|
|
40
|
+
not bugs. Leave them. The user did not ask.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 2. Smallest correct diff
|
|
45
|
+
|
|
46
|
+
The user's task is the source of truth. Your diff should change **only
|
|
47
|
+
what the task requires**.
|
|
48
|
+
|
|
49
|
+
- If the user asked to fix a bug in `auth.ts`, you do not refactor
|
|
50
|
+
`auth.ts`. You do not reformat `auth.ts`. You do not rename
|
|
51
|
+
variables. You fix the bug.
|
|
52
|
+
- If a 3-line change fixes it, your diff is 3 lines. If a 30-line
|
|
53
|
+
change fixes it, your diff is 30 lines. Anything more is over-edit.
|
|
54
|
+
- Do not "leave the code better than I found it." That is the
|
|
55
|
+
cleanup pass on a different day, in a different commit, when the
|
|
56
|
+
user asks for it.
|
|
57
|
+
- Preserve the original code's style. If the file uses tabs, use
|
|
58
|
+
tabs. If it uses 2-space indent, use 2-space indent. If it uses
|
|
59
|
+
double quotes, use double quotes. Match the file, do not "improve"
|
|
60
|
+
the file.
|
|
61
|
+
- Preserve the original logic. If you find yourself rewriting a
|
|
62
|
+
function because you would have written it differently — stop.
|
|
63
|
+
The original code is the original code. You are here to change
|
|
64
|
+
one thing.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 3. Work, then verify, then stop
|
|
69
|
+
|
|
70
|
+
The natural order is:
|
|
71
|
+
|
|
72
|
+
1. **Read** the relevant file(s) to understand context.
|
|
73
|
+
2. **Edit** to make the change.
|
|
74
|
+
3. **Read** the file again (the self-review trigger will remind you).
|
|
75
|
+
4. **Fix** any real bugs you introduced.
|
|
76
|
+
5. **Stop.**
|
|
77
|
+
|
|
78
|
+
Do not loop. Do not re-read the whole repo. Do not run linters
|
|
79
|
+
gratuitously. Do not "investigate" the test suite. If your change is
|
|
80
|
+
correct, you are done. The next message from the user will tell you
|
|
81
|
+
what to do next.
|
|
82
|
+
|
|
83
|
+
If the harness surfaces a self-review prompt, follow its instructions.
|
|
84
|
+
That is the only meta-instruction in this session.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 4. Shell is for real work, not ceremony
|
|
89
|
+
|
|
90
|
+
When you call Bash:
|
|
91
|
+
|
|
92
|
+
- The harness will deny a small list of dangerous commands. The list
|
|
93
|
+
is in `permission-gate.sh` next to the hook scripts (installed under
|
|
94
|
+
`~/.agents/hooks/` — `cat` it once at session start to internalize
|
|
95
|
+
it, then never look at it again). Don't re-discover it by trying
|
|
96
|
+
`rm -rf /`.
|
|
97
|
+
- Run the smallest command that gets the answer. `git status` is
|
|
98
|
+
better than `git status && git log && git diff`. `ls -la` is
|
|
99
|
+
better than `find . -type f -name "*.ts" -newer /tmp/marker`.
|
|
100
|
+
- Do not chain 10 commands with `&&` and call it one command. Each
|
|
101
|
+
is a separate decision.
|
|
102
|
+
- Do not pipe to `head -c 5000` to "save context." The full output
|
|
103
|
+
is the answer. Read it.
|
|
104
|
+
- **Never** use `curl | sh` or `wget | sh` or a reverse shell. The
|
|
105
|
+
gate will deny it; do not even try.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 5. When you don't know
|
|
110
|
+
|
|
111
|
+
- **Ask, don't guess.** If the user's request is ambiguous, ask one
|
|
112
|
+
sharp question, then proceed. Do not write 3 paragraphs of
|
|
113
|
+
clarifying questions.
|
|
114
|
+
- **Do not fabricate.** If a tool returned no result, the answer is
|
|
115
|
+
"I don't see it." If a path doesn't exist, say so. The user can
|
|
116
|
+
handle honest "I don't know" — they cannot handle a fabricated
|
|
117
|
+
confident answer.
|
|
118
|
+
- **Do not loop on uncertainty.** If you've tried twice and the
|
|
119
|
+
problem is still unclear, **stop and tell the user what you
|
|
120
|
+
observed**. Do not try a third time with a slightly different
|
|
121
|
+
approach.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 6. Commits and ship
|
|
126
|
+
|
|
127
|
+
- Conventional commits: `type(scope): description`. Types:
|
|
128
|
+
feat, fix, test, docs, chore, refactor, perf, build, ci, style,
|
|
129
|
+
revert. Description is lowercase, ≤72 chars, specific.
|
|
130
|
+
- One commit per logical change. If the user asked for two things,
|
|
131
|
+
that's two commits. Don't bundle.
|
|
132
|
+
- Small. ≤400 lines / ≤12 files per commit by default. If larger,
|
|
133
|
+
split.
|
|
134
|
+
- Body: 2–4 lines of prose explaining what and why. No file lists.
|
|
135
|
+
No "Ship hook fire." No "31 files" in the subject.
|
|
136
|
+
- Before `git push`, the user (or you, if asked) runs the project's
|
|
137
|
+
verify command. You do not push without explicit instruction.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 7. What you do not do
|
|
142
|
+
|
|
143
|
+
- You do not run 8 audit engines on every file. The harness does not
|
|
144
|
+
have them. You are the auditor.
|
|
145
|
+
- You do not write to `.stuck-files/`, `.audit-baselines/`, or any
|
|
146
|
+
other hook state directory. Those are not your concern.
|
|
147
|
+
- You do not maintain a "strict quality gate" config. There isn't
|
|
148
|
+
one. The rules above *are* the gate.
|
|
149
|
+
- You do not set kill-switch env vars (`HOOKS_ENFORCE=0`,
|
|
150
|
+
`MYTHOS_ENFORCE=0`, etc.). They don't exist anymore.
|
|
151
|
+
- You do not re-read the doctrine on every turn. You read it once,
|
|
152
|
+
at session start, and internalize it. The self-review trigger
|
|
153
|
+
will re-prompt you for the specific edit you're auditing.
|
|
154
|
+
- You do not optimize for "the gate passed." You optimize for
|
|
155
|
+
"the change is correct, small, and the user can review it in
|
|
156
|
+
30 seconds."
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 8. Consistency anchor
|
|
161
|
+
|
|
162
|
+
Stay consistent with the strategy, conventions, and decisions
|
|
163
|
+
established earlier in this session. If the user's prior turns
|
|
164
|
+
established a pattern — a file layout, a code style, a verification
|
|
165
|
+
step, a naming choice — continue it unless they explicitly change
|
|
166
|
+
course. This is the auditor's prior: a self-review pass that ignores
|
|
167
|
+
the trajectory of the conversation is auditing a snapshot, not the
|
|
168
|
+
work.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
End of doctrine. Now do the work.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# anti-slop-audit.sh - afterFileEdit "AI slop" advisory (Cursor, Linux).
|
|
3
|
+
#
|
|
4
|
+
# Companion to minimal-edit-audit.sh. That hook guards ONE slop axis -
|
|
5
|
+
# over-editing. This hook guards the rest of the taxonomy: the parts static
|
|
6
|
+
# analysis can cheaply and precisely flag, plus a self-review checklist for
|
|
7
|
+
# the parts it cannot.
|
|
8
|
+
#
|
|
9
|
+
# Statically flagged (high-precision, deliberately low false-positive):
|
|
10
|
+
# * new dependency added to a manifest
|
|
11
|
+
# * premature abstraction: a new *Factory / *Repository / *Mediator /
|
|
12
|
+
# *Strategy / *Singleton / *Facade / *Builder / *Visitor / *Decorator
|
|
13
|
+
# class, or CQRS / Event-Sourcing / DDD vocabulary
|
|
14
|
+
# * redundant comments that merely restate the next line of code
|
|
15
|
+
#
|
|
16
|
+
# Fires when a static signal trips OR the edit added a substantial block of
|
|
17
|
+
# new source (>= ANTI_SLOP_CHECKLIST_LINES, default 40). Otherwise silent.
|
|
18
|
+
#
|
|
19
|
+
# Advisory only: never blocks, never persists state, ALWAYS exits 0.
|
|
20
|
+
# Disable: HOOKS_ENFORCE=0 or ANTI_SLOP_ENFORCE=0
|
|
21
|
+
# Tune: ANTI_SLOP_CHECKLIST_LINES (40)
|
|
22
|
+
|
|
23
|
+
set +e
|
|
24
|
+
. "$(dirname "$0")/hook-common.sh"
|
|
25
|
+
|
|
26
|
+
[ "${HOOKS_ENFORCE:-}" = "0" ] && exit 0
|
|
27
|
+
[ "${ANTI_SLOP_ENFORCE:-}" = "0" ] && exit 0
|
|
28
|
+
|
|
29
|
+
input="$(read_hook_stdin)"
|
|
30
|
+
[ -n "$input" ] || exit 0
|
|
31
|
+
|
|
32
|
+
# audit root: project from JSON (cwd, then workspace_roots), else CURSOR_PROJECT_DIR / HOME
|
|
33
|
+
root=""
|
|
34
|
+
while IFS= read -r cand; do
|
|
35
|
+
[ -n "$cand" ] && [ -d "$cand" ] && { root="${cand%/}"; break; }
|
|
36
|
+
done <<EOF
|
|
37
|
+
$(json_get "$input" cwd)
|
|
38
|
+
$(json_get_array "$input" workspace_roots)
|
|
39
|
+
EOF
|
|
40
|
+
[ -n "$root" ] || root="${CURSOR_PROJECT_DIR:-$HOME}"
|
|
41
|
+
root="${root%/}"
|
|
42
|
+
|
|
43
|
+
# edited file -> repo-relative path
|
|
44
|
+
fp=""
|
|
45
|
+
for k in file_path path filename absolute_path abs_path; do
|
|
46
|
+
fp="$(json_get "$input" "$k")"
|
|
47
|
+
[ -n "$fp" ] && break
|
|
48
|
+
done
|
|
49
|
+
[ -n "$fp" ] || exit 0
|
|
50
|
+
rel="$fp"
|
|
51
|
+
case "$rel" in "$root"/*) rel="${rel#"$root"/}" ;; esac
|
|
52
|
+
if is_cursor_config_path "$fp" || is_cursor_config_path "$rel"; then exit 0; fi
|
|
53
|
+
|
|
54
|
+
# git repo?
|
|
55
|
+
git -C "$root" rev-parse --git-dir >/dev/null 2>&1 || exit 0
|
|
56
|
+
|
|
57
|
+
# --- collect ADDED lines for this file (working tree vs HEAD) --------------
|
|
58
|
+
added="$(git -C "$root" diff HEAD -- "$rel" 2>/dev/null |
|
|
59
|
+
grep -E '^\+' | grep -vE '^\+\+\+' | cut -c2- | head -n 1500)"
|
|
60
|
+
if [ -z "$added" ]; then
|
|
61
|
+
# untracked / brand-new file: whole file is "added"
|
|
62
|
+
if ! git -C "$root" ls-files --error-unmatch -- "$rel" >/dev/null 2>&1; then
|
|
63
|
+
[ -f "$root/$rel" ] && added="$(head -n 1500 "$root/$rel")"
|
|
64
|
+
fi
|
|
65
|
+
fi
|
|
66
|
+
[ -n "$added" ] || exit 0
|
|
67
|
+
|
|
68
|
+
base="${rel##*/}"
|
|
69
|
+
|
|
70
|
+
# --- signal 1: new dependency in a manifest --------------------------------
|
|
71
|
+
dep_added=0
|
|
72
|
+
if printf '%s' "$base" | grep -qE '^(package\.json|requirements[A-Za-z0-9._-]*\.txt|pyproject\.toml|Pipfile|go\.mod|Cargo\.toml|Gemfile|composer\.json|pom\.xml|build\.gradle(\.kts)?|packages\.config)$|\.csproj$'; then
|
|
73
|
+
# Strip metadata key/value pairs that match the dependency value-shape but
|
|
74
|
+
# are not dependencies (e.g. "version": "1.0.1" on every version bump).
|
|
75
|
+
if printf '%s\n' "$added" |
|
|
76
|
+
sed -E 's/(^|[{,])[[:space:]]*["'"'"']?(version|name|description|license|author|main|module|types|typings|type|engines|packageManager|private|sideEffects|homepage|repository|keywords|edition|rust-version|python-requires|requires-python)["'"'"']?[[:space:]]*[:=][[:space:]]*(["'"'"'][^"'"'"']*["'"'"']|[^,}[:space:]]+)//' |
|
|
77
|
+
grep -qE '(^|[{,])[[:space:]]*["'"'"']?[A-Za-z@][A-Za-z0-9@._/-]*(\[[^]]*\])?["'"'"']?[[:space:]]*([:=][[:space:]]*["'"'"']?[\^~>=<*v]?[0-9]|[><=~!]=[[:space:]]*[0-9]|@[[:space:]]*\^?[0-9])'; then
|
|
78
|
+
dep_added=1
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# --- signal 2: premature abstraction (named patterns + DDD vocabulary) -----
|
|
83
|
+
patterns="$(printf '%s\n' "$added" |
|
|
84
|
+
grep -oE '\b(class|interface|struct|trait|protocol)[[:space:]]+[A-Z][A-Za-z0-9_]*(Factory|Repository|Mediator|Strategy|Singleton|Facade|Builder|Visitor|Decorator)\b' |
|
|
85
|
+
awk '{print $NF}' | sort -u | head -n 5)"
|
|
86
|
+
kw="$(printf '%s\n' "$added" |
|
|
87
|
+
grep -oE '\b(CQRS|Event[ -]?Sourc(e|ing)|Domain[ -]?Driven|Aggregate ?Root|Bounded ?Context)\b' |
|
|
88
|
+
sort -u | head -n 5)"
|
|
89
|
+
patterns="$(printf '%s\n%s\n' "$patterns" "$kw" | grep -v '^$' | sort -u | head -n 5)"
|
|
90
|
+
|
|
91
|
+
# --- signal 3: redundant comments that restate the code --------------------
|
|
92
|
+
redundant="$(printf '%s\n' "$added" |
|
|
93
|
+
grep -E '^[[:space:]]*(//|#|/\*+)[[:space:]]*(increment|decrement|loop (over|through)|iterate|returns?( the)?( result| value)?[[:space:]]*$|set[[:space:]]+[A-Za-z0-9_]+[[:space:]]+to\b|getter\b|setter\b|constructor\b|initiali[sz]e\b|instantiate\b|create (a |an |the )|declare\b|define\b|assign\b|end (of|for)\b|begin\b|start (of|the))' |
|
|
94
|
+
while IFS= read -r line; do
|
|
95
|
+
# Word guard: real restate-the-code comments are short (<= 6 words).
|
|
96
|
+
body="$(printf '%s' "$line" | sed -E 's@^[[:space:]]*(//+|#+|/\*+|\*+)[[:space:]]*@@; s@\*/[[:space:]]*$@@')"
|
|
97
|
+
wc_words="$(printf '%s' "$body" | wc -w)"
|
|
98
|
+
if [ "$wc_words" -le 6 ]; then
|
|
99
|
+
t="$(printf '%s' "$line" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')"
|
|
100
|
+
[ "${#t}" -gt 80 ] && t="${t:0:77}..."
|
|
101
|
+
printf '%s\n' "$t"
|
|
102
|
+
fi
|
|
103
|
+
done | sort -u | head -n 4)"
|
|
104
|
+
|
|
105
|
+
# --- decide whether to fire -------------------------------------------------
|
|
106
|
+
added_code="$(printf '%s\n' "$added" | grep -cE '[^[:space:]]')"
|
|
107
|
+
checklist_lines="${ANTI_SLOP_CHECKLIST_LINES:-40}"
|
|
108
|
+
substantial=0
|
|
109
|
+
if printf '%s' "$rel" | grep -qE '\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|kts|cs|cpp|cc|cxx|c|h|hpp|rb|php|swift|scala|m|mm|sh|ps1|lua|dart|ex|exs|vue|svelte)$' &&
|
|
110
|
+
[ "$added_code" -ge "$checklist_lines" ]; then
|
|
111
|
+
substantial=1
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
flags=""
|
|
115
|
+
[ "$dep_added" = "1" ] && flags="${flags}- DEPENDENCY: $base gained a dependency - is it necessary, or do the stdlib / existing deps already cover it?
|
|
116
|
+
"
|
|
117
|
+
[ -n "$patterns" ] && flags="${flags}- PREMATURE ABSTRACTION: $(printf '%s' "$patterns" | paste -sd, - | sed 's/,/, /g') - is there a real, present problem (2-3+ call sites that exist today) that needs it? If it is speculative, delete it and write the direct code.
|
|
118
|
+
"
|
|
119
|
+
[ -n "$redundant" ] && flags="${flags}- REDUNDANT COMMENTS: $(printf '%s' "$redundant" | paste -sd '|' -) - delete comments that restate the code; keep only WHY.
|
|
120
|
+
"
|
|
121
|
+
|
|
122
|
+
if [ -z "$flags" ] && [ "$substantial" = "0" ]; then exit 0; fi
|
|
123
|
+
|
|
124
|
+
# --- load the slop checklist (md preferred, embedded fallback) --------------
|
|
125
|
+
checklist_file="$HOME/.agents/hooks/anti-slop.md"
|
|
126
|
+
checklist=""
|
|
127
|
+
[ -f "$checklist_file" ] && checklist="$(cat "$checklist_file")"
|
|
128
|
+
if [ -z "$checklist" ]; then
|
|
129
|
+
checklist='ANTI-SLOP SELF-REVIEW - audit the edit you just made and FIX (do not explain) any slop:
|
|
130
|
+
1. Edge cases beyond the happy path (null / empty / zero / boundary / error).
|
|
131
|
+
2. Duplicated logic that already exists in this repo - call it, do not re-implement.
|
|
132
|
+
3. Conventions - match the file'"'"'s existing style / naming / structure / error-handling.
|
|
133
|
+
4. Unnecessary dependencies - remove libs the stdlib or an existing dep covers.
|
|
134
|
+
5. Premature abstraction - no Factory/Repository/Mediator/CQRS/DDD without 2-3 real call sites today.
|
|
135
|
+
6. Accidental complexity - flatten indirection a junior cannot read in 30s.
|
|
136
|
+
7. Tests assert real behaviour and edge cases, not just "it runs".
|
|
137
|
+
8. Cargo cult - delete any construct whose reason you cannot state.
|
|
138
|
+
9. Architecture - respect the project'"'"'s layering and boundaries.
|
|
139
|
+
10. Redundant comments restating code - delete; keep only WHY.'
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
flag_block=""
|
|
143
|
+
[ -n "$flags" ] && flag_block="Static signals on this edit:
|
|
144
|
+
$flags
|
|
145
|
+
"
|
|
146
|
+
|
|
147
|
+
msg="Anti-slop audit - $rel
|
|
148
|
+
|
|
149
|
+
${flag_block}${checklist}
|
|
150
|
+
|
|
151
|
+
(Advisory; the bug pass is the self-review trigger. Disable: ANTI_SLOP_ENFORCE=0)"
|
|
152
|
+
|
|
153
|
+
# --- append to the shared pending file --------------------------------------
|
|
154
|
+
cid="$(safe_conversation_id "$input")"
|
|
155
|
+
pending="$(hooks_pending_dir)/feedback-$cid.txt"
|
|
156
|
+
mkdir -p "$(dirname "$pending")" 2>/dev/null
|
|
157
|
+
if [ -s "$pending" ]; then
|
|
158
|
+
printf '\n\n---\n\n%s' "$msg" >> "$pending" 2>/dev/null
|
|
159
|
+
else
|
|
160
|
+
printf '%s' "$msg" >> "$pending" 2>/dev/null
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
exit 0
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
ANTI-SLOP SELF-REVIEW — you just edited a file. Before you do anything else,
|
|
2
|
+
audit your own change against the checklist below. This is NOT the bug pass
|
|
3
|
+
(the self-review trigger covers security/correctness). This is about *slop*:
|
|
4
|
+
code that runs but should not ship.
|
|
5
|
+
|
|
6
|
+
For each item: if your edit is guilty, FIX IT NOW with Edit — delete the
|
|
7
|
+
abstraction, inline the duplicate, drop the dependency, remove the comment.
|
|
8
|
+
Do not explain, do not report, just fix. If the edit is clean, say nothing.
|
|
9
|
+
|
|
10
|
+
1. EDGE CASES — Does it only handle the happy path? Check the null / empty /
|
|
11
|
+
zero / boundary / error inputs the task implies. An unhandled obvious
|
|
12
|
+
edge case is a bug waiting in production.
|
|
13
|
+
|
|
14
|
+
2. DUPLICATION — Did you write logic that already exists in this repo? Look
|
|
15
|
+
before you add. If it exists, call it; do not re-implement it.
|
|
16
|
+
|
|
17
|
+
3. CONVENTIONS — Does it match the FILE's existing style, naming, structure,
|
|
18
|
+
error-handling, and import patterns? Match the neighbours, not your
|
|
19
|
+
defaults.
|
|
20
|
+
|
|
21
|
+
4. DEPENDENCIES — Did you add a library for something the stdlib or an
|
|
22
|
+
existing dependency already does? Remove it. A new dependency must earn
|
|
23
|
+
its place.
|
|
24
|
+
|
|
25
|
+
5. PREMATURE ABSTRACTION — Factory / Repository / Mediator / Strategy /
|
|
26
|
+
Builder / base classes / interfaces / CQRS / Event Sourcing / DDD
|
|
27
|
+
layering: is there a REAL, PRESENT problem — two or three concrete call
|
|
28
|
+
sites that exist TODAY — that requires it? "For future flexibility" is
|
|
29
|
+
not a reason. Delete it and write the direct code. Abstraction debt is
|
|
30
|
+
layers without problems.
|
|
31
|
+
|
|
32
|
+
6. ACCIDENTAL COMPLEXITY — Could a junior read this in 30 seconds? Extra
|
|
33
|
+
indirection, generics, config, or layers that do not earn their keep →
|
|
34
|
+
flatten them.
|
|
35
|
+
|
|
36
|
+
7. TESTS — Do your tests assert real BEHAVIOUR and the edge cases, or do
|
|
37
|
+
they just prove the code runs / mirror the implementation line-for-line?
|
|
38
|
+
A test that cannot fail is slop. Make it verify outcomes.
|
|
39
|
+
|
|
40
|
+
8. CARGO CULT — Can you state WHY each non-obvious construct is there? If you
|
|
41
|
+
reproduced a pattern without the historical reason behind it, that reason
|
|
42
|
+
may not hold here. Remove what you cannot justify. Replicating a shape you
|
|
43
|
+
have seen is not the same as needing it.
|
|
44
|
+
|
|
45
|
+
9. ARCHITECTURE — Does it respect the project's layering and boundaries — no
|
|
46
|
+
reaching across layers, no business logic in the wrong place, no breaking
|
|
47
|
+
a constraint the codebase clearly holds? Honour the constraints.
|
|
48
|
+
|
|
49
|
+
10. REDUNDANT COMMENTS — Delete comments that restate the code
|
|
50
|
+
("// increment i", "# return the result"). Keep only comments that
|
|
51
|
+
explain WHY, never WHAT.
|
|
52
|
+
|
|
53
|
+
Hard constraints: never revert the change the USER asked for — slop is the
|
|
54
|
+
stuff you added on top. Do not "improve" beyond removing slop. At most a few
|
|
55
|
+
targeted edits, then stop. The bar: would this pass a senior review at a top
|
|
56
|
+
engineering org without a single "why is this here?" comment.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
FINAL REVIEW — you just finished an implementation. Before you treat it as done,
|
|
2
|
+
audit EVERYTHING you changed this session across the four axes below and FIX what
|
|
3
|
+
fails. Do NOT revert the behaviour the user asked for. If an axis is already
|
|
4
|
+
clean, say so in one line — do not manufacture work.
|
|
5
|
+
|
|
6
|
+
Start by re-reading the diff. Scope the review to your session's changes and the
|
|
7
|
+
code they touch.
|
|
8
|
+
|
|
9
|
+
## 1. Correctness
|
|
10
|
+
- The logic does what the task requires — no off-by-one, inverted condition,
|
|
11
|
+
wrong operator, wrong return value, wrong import path.
|
|
12
|
+
- Edge cases the inputs imply: null / undefined / empty / zero / negative /
|
|
13
|
+
boundary / very large. The happy path passing is not enough.
|
|
14
|
+
- Language traps: `==` vs `===`, mutable default args, `await` in `forEach`,
|
|
15
|
+
floating promises, `== null`, `NaN` compares, integer/float, timezone/encoding.
|
|
16
|
+
- Security: no hardcoded secret, no `eval`/`exec` on input, no SQL string
|
|
17
|
+
concatenation, no unsafe HTML with untrusted data.
|
|
18
|
+
|
|
19
|
+
## 2. Reliability
|
|
20
|
+
- Every failure path is handled — no empty `catch`, no swallowed error, no silent
|
|
21
|
+
fallback that hides a bug. Errors surface or are handled deliberately.
|
|
22
|
+
- External calls (network / fs / db / subprocess) have error handling and, where
|
|
23
|
+
it matters, timeouts and bounded retries — no unbounded waits.
|
|
24
|
+
- Resources are released on every path including errors: files, handles,
|
|
25
|
+
sockets, locks, listeners, subscriptions, timers.
|
|
26
|
+
- No new race conditions; shared mutable state is guarded; operations that should
|
|
27
|
+
be idempotent are.
|
|
28
|
+
- Inputs are validated at the boundary; the code degrades gracefully instead of
|
|
29
|
+
crashing.
|
|
30
|
+
|
|
31
|
+
## 3. Coverage
|
|
32
|
+
- Every behaviour-bearing change has a test. New function / branch / edge case →
|
|
33
|
+
add a test. If the project has a test suite, RUN it and make it pass.
|
|
34
|
+
- Tests assert real OUTCOMES and the edge cases above — not "it runs", not a
|
|
35
|
+
mirror of the implementation, not a test that cannot fail.
|
|
36
|
+
- Add the missing tests; delete tautological ones.
|
|
37
|
+
|
|
38
|
+
## 4. Anti-slop
|
|
39
|
+
- If `~/.cursor/skills/anti-slop/scripts/scan_slop.py` exists (INSTALL.md step 2
|
|
40
|
+
copies it there), run the whole-codebase duplication scan:
|
|
41
|
+
python ~/.cursor/skills/anti-slop/scripts/scan_slop.py --all
|
|
42
|
+
If it does NOT exist, do not treat that as a failure and do not hunt for the
|
|
43
|
+
file: apply the checklist in `~/.agents/hooks/anti-slop.md` to the session
|
|
44
|
+
diff and look for duplicate function bodies in the files you touched.
|
|
45
|
+
- Either way, consolidate clones: same function in many files / identical bodies
|
|
46
|
+
(the isRecord-class) → ONE shared definition, re-point imports, delete the
|
|
47
|
+
copies.
|
|
48
|
+
- Premature abstraction (Factory / Repository / Mediator / CQRS / DDD with fewer
|
|
49
|
+
than 2–3 real call sites), unnecessary dependencies, redundant restate-the-code
|
|
50
|
+
comments, dead helpers, accidental complexity → remove.
|
|
51
|
+
|
|
52
|
+
Fix with edits now; re-run the scan and the tests; then stop.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# final-review.sh - stop hook (Cursor, Linux).
|
|
3
|
+
#
|
|
4
|
+
# ONE comprehensive end-of-implementation review across four axes:
|
|
5
|
+
# correctness, reliability, coverage, and anti-slop. When the agent finishes
|
|
6
|
+
# an implementation that touched files, Cursor auto-submits this hook's
|
|
7
|
+
# `followup_message` as the next user turn, so the model re-audits everything
|
|
8
|
+
# it changed this session and FIXES what fails.
|
|
9
|
+
#
|
|
10
|
+
# Bounded so it can't loop forever:
|
|
11
|
+
# - a per-conversation reviewed-flag: the stop AFTER the review pass clears
|
|
12
|
+
# it and ends the loop (one review per implementation),
|
|
13
|
+
# - loop_limit in hooks.json caps runaway follow-ups harness-side,
|
|
14
|
+
# - only if a file was actually edited this loop (the session-edits marker
|
|
15
|
+
# written by self-review-trigger.sh). Pure Q&A turns get nothing.
|
|
16
|
+
# Plus: only on status == 'completed' (not aborted/errored).
|
|
17
|
+
#
|
|
18
|
+
# Always emits valid JSON ({} = no follow-up). The review prompt lives in
|
|
19
|
+
# final-review.md next to this script (embedded fallback if missing).
|
|
20
|
+
# Disable: HOOKS_ENFORCE=0 or FINAL_REVIEW_ENFORCE=0.
|
|
21
|
+
|
|
22
|
+
set +e
|
|
23
|
+
. "$(dirname "$0")/hook-common.sh"
|
|
24
|
+
|
|
25
|
+
emit_none() { printf '{}'; exit 0; }
|
|
26
|
+
|
|
27
|
+
[ "${HOOKS_ENFORCE:-}" = "0" ] && emit_none
|
|
28
|
+
[ "${FINAL_REVIEW_ENFORCE:-}" = "0" ] && emit_none
|
|
29
|
+
|
|
30
|
+
input="$(read_hook_stdin)"
|
|
31
|
+
[ -n "$input" ] || emit_none
|
|
32
|
+
|
|
33
|
+
status="$(json_get "$input" status)"
|
|
34
|
+
cid="$(safe_conversation_id "$input")"
|
|
35
|
+
|
|
36
|
+
pending_dir="$(hooks_pending_dir)"
|
|
37
|
+
marker="$pending_dir/session-edits-$cid.txt"
|
|
38
|
+
flag="$pending_dir/reviewed-$cid.flag"
|
|
39
|
+
|
|
40
|
+
# Sweep state from sessions that died before their stop hook ran.
|
|
41
|
+
find "$pending_dir" -maxdepth 1 -type f -mtime +7 -delete 2>/dev/null
|
|
42
|
+
|
|
43
|
+
# One-shot brake: the previous stop for this conversation emitted the review.
|
|
44
|
+
if [ -f "$flag" ]; then
|
|
45
|
+
rm -f "$flag" "$marker" 2>/dev/null
|
|
46
|
+
emit_none
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Fold completed subagents' edit markers into this conversation's marker so
|
|
50
|
+
# the review covers delegated work (subagent edits fire afterFileEdit under
|
|
51
|
+
# the SUBAGENT's conversation_id; postToolUse never fires for the Task tool,
|
|
52
|
+
# so this stop-time fold is the terminal backstop after the per-tool fold in
|
|
53
|
+
# post-tool-use.sh).
|
|
54
|
+
merge_subagent_edit_markers "$input" "$cid"
|
|
55
|
+
|
|
56
|
+
# Review only a clean completion; otherwise just clear the marker and stop.
|
|
57
|
+
if [ -n "$status" ] && [ "$status" != "completed" ]; then
|
|
58
|
+
rm -f "$marker" 2>/dev/null
|
|
59
|
+
emit_none
|
|
60
|
+
fi
|
|
61
|
+
# No edits this loop -> nothing to review.
|
|
62
|
+
[ -f "$marker" ] || emit_none
|
|
63
|
+
edited="$(grep -vE '^[[:space:]]*$' "$marker" 2>/dev/null | sort -u)"
|
|
64
|
+
rm -f "$marker" 2>/dev/null
|
|
65
|
+
[ -n "$edited" ] || emit_none
|
|
66
|
+
|
|
67
|
+
# Compose the follow-up review prompt (md preferred, embedded fallback).
|
|
68
|
+
prompt_file="$HOME/.agents/hooks/final-review.md"
|
|
69
|
+
body=""
|
|
70
|
+
[ -f "$prompt_file" ] && body="$(cat "$prompt_file")"
|
|
71
|
+
if [ -z "$body" ]; then
|
|
72
|
+
body='FINAL REVIEW - audit everything you changed this session and FIX what fails
|
|
73
|
+
(do NOT revert the behaviour the user asked for):
|
|
74
|
+
1. Correctness - logic, edge cases (null/empty/zero/boundary), language traps, security.
|
|
75
|
+
2. Reliability - error paths handled (no empty catch), timeouts/retries, resources
|
|
76
|
+
released on every path, no races, input validated at the boundary.
|
|
77
|
+
3. Coverage - behaviour-bearing changes have real tests; RUN the suite if present;
|
|
78
|
+
no tautological tests.
|
|
79
|
+
4. Anti-slop - if ~/.cursor/skills/anti-slop/scripts/scan_slop.py exists, run
|
|
80
|
+
`python ~/.cursor/skills/anti-slop/scripts/scan_slop.py --all`; otherwise
|
|
81
|
+
apply ~/.agents/hooks/anti-slop.md to the session diff (a missing scanner
|
|
82
|
+
is not a failure). Consolidate clones/duplicates to one source of truth;
|
|
83
|
+
drop premature abstraction, unneeded deps, redundant comments, dead helpers.
|
|
84
|
+
Fix now, re-run the scan + tests, then stop. If an axis is clean, say so in one line.'
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
file_list="$(printf '%s\n' "$edited" | head -n 30 | sed 's/^/ /')"
|
|
88
|
+
msg="FINAL REVIEW (end of implementation) - correctness, reliability, coverage, anti-slop.
|
|
89
|
+
|
|
90
|
+
Files you changed this session:
|
|
91
|
+
$file_list
|
|
92
|
+
|
|
93
|
+
$body"
|
|
94
|
+
|
|
95
|
+
# Arm the one-shot brake BEFORE emitting, so a crash after emit can't re-fire.
|
|
96
|
+
touch "$flag" 2>/dev/null
|
|
97
|
+
|
|
98
|
+
emit_json followup_message "$msg"
|
|
99
|
+
exit 0
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hook-common.sh - shared helpers for Cursor agent hooks (Linux).
|
|
3
|
+
# Source from sibling scripts: . "$(dirname "$0")/hook-common.sh"
|
|
4
|
+
#
|
|
5
|
+
# JSON parsing prefers jq; falls back to python3. If neither exists every
|
|
6
|
+
# helper degrades to empty output and the hooks fail open (never block).
|
|
7
|
+
|
|
8
|
+
read_hook_stdin() {
|
|
9
|
+
# Strip a UTF-8 BOM if present and trim surrounding whitespace.
|
|
10
|
+
local raw
|
|
11
|
+
raw="$(cat 2>/dev/null)"
|
|
12
|
+
raw="${raw#$'\xef\xbb\xbf'}"
|
|
13
|
+
printf '%s' "$raw"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
have_jq() { command -v jq >/dev/null 2>&1; }
|
|
17
|
+
# Verify python3 actually runs: on some systems (e.g. Windows Store stubs over
|
|
18
|
+
# a Git Bash PATH) python3 exists on PATH but only prints an install notice.
|
|
19
|
+
have_py() { command -v python3 >/dev/null 2>&1 && python3 -c '' >/dev/null 2>&1; }
|
|
20
|
+
|
|
21
|
+
# json_get <json> <key> -> string value of a top-level key ('' if absent)
|
|
22
|
+
json_get() {
|
|
23
|
+
local json="$1" key="$2"
|
|
24
|
+
[ -n "$json" ] || return 0
|
|
25
|
+
if have_jq; then
|
|
26
|
+
printf '%s' "$json" | jq -r --arg k "$key" '.[$k] // empty | tostring' 2>/dev/null
|
|
27
|
+
elif have_py; then
|
|
28
|
+
printf '%s' "$json" | python3 -c '
|
|
29
|
+
import json, sys
|
|
30
|
+
try:
|
|
31
|
+
o = json.load(sys.stdin)
|
|
32
|
+
v = o.get(sys.argv[1])
|
|
33
|
+
if v is not None:
|
|
34
|
+
print(v if isinstance(v, str) else json.dumps(v))
|
|
35
|
+
except Exception:
|
|
36
|
+
pass' "$key" 2>/dev/null
|
|
37
|
+
fi
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# json_get_array <json> <key> -> one element per line (for workspace_roots)
|
|
41
|
+
json_get_array() {
|
|
42
|
+
local json="$1" key="$2"
|
|
43
|
+
[ -n "$json" ] || return 0
|
|
44
|
+
if have_jq; then
|
|
45
|
+
printf '%s' "$json" | jq -r --arg k "$key" '.[$k][]? // empty' 2>/dev/null
|
|
46
|
+
elif have_py; then
|
|
47
|
+
printf '%s' "$json" | python3 -c '
|
|
48
|
+
import json, sys
|
|
49
|
+
try:
|
|
50
|
+
o = json.load(sys.stdin)
|
|
51
|
+
for v in (o.get(sys.argv[1]) or []):
|
|
52
|
+
print(v)
|
|
53
|
+
except Exception:
|
|
54
|
+
pass' "$key" 2>/dev/null
|
|
55
|
+
fi
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# emit_json <key> <value> -> compact, ASCII-escaped {"key":"value"} on stdout
|
|
59
|
+
emit_json() {
|
|
60
|
+
local key="$1" value="$2"
|
|
61
|
+
if have_jq; then
|
|
62
|
+
jq -cna --arg k "$key" --arg v "$value" '{($k): $v}' 2>/dev/null && return 0
|
|
63
|
+
fi
|
|
64
|
+
if have_py; then
|
|
65
|
+
K="$key" V="$value" python3 -c '
|
|
66
|
+
import json, os
|
|
67
|
+
print(json.dumps({os.environ["K"]: os.environ["V"]}, ensure_ascii=True, separators=(",", ":")))' 2>/dev/null && return 0
|
|
68
|
+
fi
|
|
69
|
+
# Last resort: no JSON encoder available -> emit nothing useful but valid.
|
|
70
|
+
printf '{}'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# safe_conversation_id <json> -> sanitized conversation_id ('default' if empty)
|
|
74
|
+
safe_conversation_id() {
|
|
75
|
+
local cid
|
|
76
|
+
cid="$(json_get "$1" conversation_id | tr -cd 'A-Za-z0-9_-')"
|
|
77
|
+
printf '%s' "${cid:-default}"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
hooks_pending_dir() { printf '%s' "$HOME/.cursor/.hooks-pending"; }
|
|
81
|
+
|
|
82
|
+
# is_cursor_config_path <path> -> 0 if the path lives under a .cursor directory
|
|
83
|
+
is_cursor_config_path() {
|
|
84
|
+
case "$1" in
|
|
85
|
+
*/.cursor/*|*/.cursor|.cursor/*|.cursor) return 0 ;;
|
|
86
|
+
*) return 1 ;;
|
|
87
|
+
esac
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# merge_subagent_edit_markers <json> <parent_cid> -> 0 if anything was folded
|
|
91
|
+
#
|
|
92
|
+
# Subagent edits fire afterFileEdit under the SUBAGENT's conversation_id, so
|
|
93
|
+
# their session-edits markers are invisible to the parent's stop-hook review.
|
|
94
|
+
# Subagent transcripts live at <transcripts>/<parent-cid>/subagents/<sub-cid>.jsonl,
|
|
95
|
+
# which gives a deterministic parent->subagent mapping: fold each subagent's
|
|
96
|
+
# marker into the parent's and remove the original. No-ops when called from a
|
|
97
|
+
# subagent context (its transcript_path has no sibling 'subagents' dir).
|
|
98
|
+
merge_subagent_edit_markers() {
|
|
99
|
+
local json="$1" parent_cid="$2"
|
|
100
|
+
local tp sub_dir pending_dir parent_marker j scid m folded=1
|
|
101
|
+
tp="$(json_get "$json" transcript_path)"
|
|
102
|
+
[ -n "$tp" ] || return 1
|
|
103
|
+
sub_dir="$(dirname "$tp")/subagents"
|
|
104
|
+
[ -d "$sub_dir" ] || return 1
|
|
105
|
+
pending_dir="$(hooks_pending_dir)"
|
|
106
|
+
parent_marker="$pending_dir/session-edits-$parent_cid.txt"
|
|
107
|
+
for j in "$sub_dir"/*.jsonl; do
|
|
108
|
+
[ -e "$j" ] || continue
|
|
109
|
+
scid="$(basename "$j" .jsonl | tr -cd 'A-Za-z0-9_-')"
|
|
110
|
+
[ -n "$scid" ] || continue
|
|
111
|
+
[ "$scid" = "$parent_cid" ] && continue
|
|
112
|
+
m="$pending_dir/session-edits-$scid.txt"
|
|
113
|
+
[ -f "$m" ] || continue
|
|
114
|
+
mkdir -p "$pending_dir" 2>/dev/null
|
|
115
|
+
grep -vE '^[[:space:]]*$' "$m" 2>/dev/null | sort -u >> "$parent_marker" 2>/dev/null
|
|
116
|
+
rm -f "$m" 2>/dev/null
|
|
117
|
+
folded=0
|
|
118
|
+
done
|
|
119
|
+
return $folded
|
|
120
|
+
}
|