cc-discipline 2.11.0 → 2.12.1
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/bin/cli.js +147 -147
- package/init.sh +22 -2
- package/package.json +1 -1
- package/templates/.claude/hooks/action-counter.sh +27 -10
- package/templates/.claude/hooks/git-guard.sh +33 -4
- package/templates/.claude/hooks/post-error-remind.sh +22 -8
- package/templates/.claude/hooks/pre-edit-guard.sh +40 -20
- package/templates/.claude/hooks/session-start.sh +14 -8
- package/templates/.claude/hooks/streak-breaker.sh +14 -7
- package/templates/.claude/rules/00-core-principles.md +1 -1
- package/templates/.claude/rules/02-before-edit.md +0 -11
- package/templates/.claude/rules/03-context-mgmt.md +6 -7
- package/templates/.claude/rules/07-integrity.md +0 -13
- package/templates/.claude/skills/retro/SKILL.md +16 -7
- package/templates/.claude/skills/think/SKILL.md +1 -1
- package/templates/docs/progress.md +22 -0
package/bin/cli.js
CHANGED
|
@@ -1,147 +1,147 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// cc-discipline CLI entry point (cross-platform)
|
|
3
|
-
// Detects platform and spawns bash with correct paths
|
|
4
|
-
|
|
5
|
-
const { execSync, spawnSync } = require('child_process');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
|
|
9
|
-
const PKG_DIR = path.resolve(__dirname, '..');
|
|
10
|
-
const args = process.argv.slice(2);
|
|
11
|
-
|
|
12
|
-
// Find bash executable
|
|
13
|
-
function findBash() {
|
|
14
|
-
// Unix: bash is always available
|
|
15
|
-
if (process.platform !== 'win32') return 'bash';
|
|
16
|
-
|
|
17
|
-
// Windows: try common Git Bash locations
|
|
18
|
-
const candidates = [
|
|
19
|
-
'C:\\Program Files\\Git\\bin\\bash.exe',
|
|
20
|
-
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
|
|
21
|
-
process.env.PROGRAMFILES + '\\Git\\bin\\bash.exe',
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
for (const candidate of candidates) {
|
|
25
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Try PATH
|
|
29
|
-
try {
|
|
30
|
-
execSync('bash --version', { stdio: 'ignore' });
|
|
31
|
-
return 'bash';
|
|
32
|
-
} catch (e) {
|
|
33
|
-
console.error('Error: bash not found. Please install Git for Windows (https://git-scm.com/download/win)');
|
|
34
|
-
console.error('Git Bash is required to run cc-discipline on Windows.');
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const bash = findBash();
|
|
40
|
-
|
|
41
|
-
// Convert Windows path to Unix-style for bash
|
|
42
|
-
function toUnixPath(p) {
|
|
43
|
-
if (process.platform !== 'win32') return p;
|
|
44
|
-
// C:\Users\foo → /c/Users/foo
|
|
45
|
-
return p.replace(/\\/g, '/').replace(/^([A-Za-z]):/, (_, drive) => '/' + drive.toLowerCase());
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Route subcommands
|
|
49
|
-
const command = args[0] || 'init';
|
|
50
|
-
const restArgs = args.slice(1);
|
|
51
|
-
|
|
52
|
-
let script;
|
|
53
|
-
let scriptArgs;
|
|
54
|
-
|
|
55
|
-
switch (command) {
|
|
56
|
-
case 'init':
|
|
57
|
-
script = path.join(PKG_DIR, 'init.sh');
|
|
58
|
-
scriptArgs = restArgs;
|
|
59
|
-
break;
|
|
60
|
-
case 'upgrade':
|
|
61
|
-
script = path.join(PKG_DIR, 'init.sh');
|
|
62
|
-
scriptArgs = ['--auto', ...restArgs];
|
|
63
|
-
break;
|
|
64
|
-
case 'status':
|
|
65
|
-
script = path.join(PKG_DIR, 'lib', 'status.sh');
|
|
66
|
-
scriptArgs = [];
|
|
67
|
-
break;
|
|
68
|
-
case 'doctor':
|
|
69
|
-
script = path.join(PKG_DIR, 'lib', 'doctor.sh');
|
|
70
|
-
scriptArgs = [];
|
|
71
|
-
break;
|
|
72
|
-
case 'add-stack':
|
|
73
|
-
if (restArgs.length === 0) {
|
|
74
|
-
console.log('Usage: cc-discipline add-stack <numbers>');
|
|
75
|
-
console.log(' e.g.: cc-discipline add-stack 3 4');
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}
|
|
78
|
-
script = path.join(PKG_DIR, 'init.sh');
|
|
79
|
-
scriptArgs = ['--stack', restArgs.join(' '), '--no-global'];
|
|
80
|
-
break;
|
|
81
|
-
case 'remove-stack':
|
|
82
|
-
script = path.join(PKG_DIR, 'lib', 'stack-remove.sh');
|
|
83
|
-
scriptArgs = restArgs;
|
|
84
|
-
break;
|
|
85
|
-
case '-v':
|
|
86
|
-
case '--version':
|
|
87
|
-
case 'version':
|
|
88
|
-
const pkg = require(path.join(PKG_DIR, 'package.json'));
|
|
89
|
-
console.log(`cc-discipline v${pkg.version}`);
|
|
90
|
-
process.exit(0);
|
|
91
|
-
case '-h':
|
|
92
|
-
case '--help':
|
|
93
|
-
case 'help':
|
|
94
|
-
const ver = require(path.join(PKG_DIR, 'package.json')).version;
|
|
95
|
-
console.log(`cc-discipline v${ver} — Discipline framework for Claude Code
|
|
96
|
-
|
|
97
|
-
Usage: cc-discipline <command> [options]
|
|
98
|
-
|
|
99
|
-
Commands:
|
|
100
|
-
init [options] Install discipline into current project (default)
|
|
101
|
-
upgrade Upgrade rules/hooks (shortcut for init --auto)
|
|
102
|
-
add-stack <numbers> Add stack rules (e.g., add-stack 3 4)
|
|
103
|
-
remove-stack <numbers> Remove stack rules
|
|
104
|
-
status Show installed version, stacks, and hooks
|
|
105
|
-
doctor Check installation integrity
|
|
106
|
-
version Show version
|
|
107
|
-
|
|
108
|
-
Init options:
|
|
109
|
-
--auto Non-interactive with defaults
|
|
110
|
-
--stack <choices> Stack selection: 1-7, space-separated
|
|
111
|
-
--name <name> Project name (default: directory name)
|
|
112
|
-
--global Install global rules to ~/.claude/CLAUDE.md
|
|
113
|
-
--no-global Skip global rules install
|
|
114
|
-
|
|
115
|
-
Stacks:
|
|
116
|
-
1=RTL 2=Embedded 3=Python 4=JS/TS 5=Mobile 6=Fullstack 7=General
|
|
117
|
-
|
|
118
|
-
Examples:
|
|
119
|
-
npx cc-discipline # Interactive setup
|
|
120
|
-
npx cc-discipline init --auto # Non-interactive defaults
|
|
121
|
-
npx cc-discipline init --auto --stack "3 4" # Python + JS/TS
|
|
122
|
-
npx cc-discipline upgrade # Upgrade to latest
|
|
123
|
-
npx cc-discipline status # Check what's installed
|
|
124
|
-
npx cc-discipline doctor # Diagnose issues`);
|
|
125
|
-
process.exit(0);
|
|
126
|
-
default:
|
|
127
|
-
console.error(`Unknown command: ${command}`);
|
|
128
|
-
console.error("Run 'cc-discipline --help' for usage");
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Run the bash script
|
|
133
|
-
const unixScript = toUnixPath(script);
|
|
134
|
-
const pkgVersion = require(path.join(PKG_DIR, 'package.json')).version;
|
|
135
|
-
const env = {
|
|
136
|
-
...process.env,
|
|
137
|
-
CC_DISCIPLINE_PKG_DIR: process.platform === 'win32' ? toUnixPath(PKG_DIR) : PKG_DIR,
|
|
138
|
-
CC_DISCIPLINE_VERSION: pkgVersion,
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const result = spawnSync(bash, [unixScript, ...scriptArgs], {
|
|
142
|
-
stdio: 'inherit',
|
|
143
|
-
env,
|
|
144
|
-
cwd: process.cwd(),
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
process.exit(result.status || 0);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cc-discipline CLI entry point (cross-platform)
|
|
3
|
+
// Detects platform and spawns bash with correct paths
|
|
4
|
+
|
|
5
|
+
const { execSync, spawnSync } = require('child_process');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
|
|
9
|
+
const PKG_DIR = path.resolve(__dirname, '..');
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
|
|
12
|
+
// Find bash executable
|
|
13
|
+
function findBash() {
|
|
14
|
+
// Unix: bash is always available
|
|
15
|
+
if (process.platform !== 'win32') return 'bash';
|
|
16
|
+
|
|
17
|
+
// Windows: try common Git Bash locations
|
|
18
|
+
const candidates = [
|
|
19
|
+
'C:\\Program Files\\Git\\bin\\bash.exe',
|
|
20
|
+
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
|
|
21
|
+
process.env.PROGRAMFILES + '\\Git\\bin\\bash.exe',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
for (const candidate of candidates) {
|
|
25
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Try PATH
|
|
29
|
+
try {
|
|
30
|
+
execSync('bash --version', { stdio: 'ignore' });
|
|
31
|
+
return 'bash';
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.error('Error: bash not found. Please install Git for Windows (https://git-scm.com/download/win)');
|
|
34
|
+
console.error('Git Bash is required to run cc-discipline on Windows.');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const bash = findBash();
|
|
40
|
+
|
|
41
|
+
// Convert Windows path to Unix-style for bash
|
|
42
|
+
function toUnixPath(p) {
|
|
43
|
+
if (process.platform !== 'win32') return p;
|
|
44
|
+
// C:\Users\foo → /c/Users/foo
|
|
45
|
+
return p.replace(/\\/g, '/').replace(/^([A-Za-z]):/, (_, drive) => '/' + drive.toLowerCase());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Route subcommands
|
|
49
|
+
const command = args[0] || 'init';
|
|
50
|
+
const restArgs = args.slice(1);
|
|
51
|
+
|
|
52
|
+
let script;
|
|
53
|
+
let scriptArgs;
|
|
54
|
+
|
|
55
|
+
switch (command) {
|
|
56
|
+
case 'init':
|
|
57
|
+
script = path.join(PKG_DIR, 'init.sh');
|
|
58
|
+
scriptArgs = restArgs;
|
|
59
|
+
break;
|
|
60
|
+
case 'upgrade':
|
|
61
|
+
script = path.join(PKG_DIR, 'init.sh');
|
|
62
|
+
scriptArgs = ['--auto', ...restArgs];
|
|
63
|
+
break;
|
|
64
|
+
case 'status':
|
|
65
|
+
script = path.join(PKG_DIR, 'lib', 'status.sh');
|
|
66
|
+
scriptArgs = [];
|
|
67
|
+
break;
|
|
68
|
+
case 'doctor':
|
|
69
|
+
script = path.join(PKG_DIR, 'lib', 'doctor.sh');
|
|
70
|
+
scriptArgs = [];
|
|
71
|
+
break;
|
|
72
|
+
case 'add-stack':
|
|
73
|
+
if (restArgs.length === 0) {
|
|
74
|
+
console.log('Usage: cc-discipline add-stack <numbers>');
|
|
75
|
+
console.log(' e.g.: cc-discipline add-stack 3 4');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
script = path.join(PKG_DIR, 'init.sh');
|
|
79
|
+
scriptArgs = ['--stack', restArgs.join(' '), '--no-global'];
|
|
80
|
+
break;
|
|
81
|
+
case 'remove-stack':
|
|
82
|
+
script = path.join(PKG_DIR, 'lib', 'stack-remove.sh');
|
|
83
|
+
scriptArgs = restArgs;
|
|
84
|
+
break;
|
|
85
|
+
case '-v':
|
|
86
|
+
case '--version':
|
|
87
|
+
case 'version':
|
|
88
|
+
const pkg = require(path.join(PKG_DIR, 'package.json'));
|
|
89
|
+
console.log(`cc-discipline v${pkg.version}`);
|
|
90
|
+
process.exit(0);
|
|
91
|
+
case '-h':
|
|
92
|
+
case '--help':
|
|
93
|
+
case 'help':
|
|
94
|
+
const ver = require(path.join(PKG_DIR, 'package.json')).version;
|
|
95
|
+
console.log(`cc-discipline v${ver} — Discipline framework for Claude Code
|
|
96
|
+
|
|
97
|
+
Usage: cc-discipline <command> [options]
|
|
98
|
+
|
|
99
|
+
Commands:
|
|
100
|
+
init [options] Install discipline into current project (default)
|
|
101
|
+
upgrade Upgrade rules/hooks (shortcut for init --auto)
|
|
102
|
+
add-stack <numbers> Add stack rules (e.g., add-stack 3 4)
|
|
103
|
+
remove-stack <numbers> Remove stack rules
|
|
104
|
+
status Show installed version, stacks, and hooks
|
|
105
|
+
doctor Check installation integrity
|
|
106
|
+
version Show version
|
|
107
|
+
|
|
108
|
+
Init options:
|
|
109
|
+
--auto Non-interactive with defaults
|
|
110
|
+
--stack <choices> Stack selection: 1-7, space-separated
|
|
111
|
+
--name <name> Project name (default: directory name)
|
|
112
|
+
--global Install global rules to ~/.claude/CLAUDE.md
|
|
113
|
+
--no-global Skip global rules install
|
|
114
|
+
|
|
115
|
+
Stacks:
|
|
116
|
+
1=RTL 2=Embedded 3=Python 4=JS/TS 5=Mobile 6=Fullstack 7=General
|
|
117
|
+
|
|
118
|
+
Examples:
|
|
119
|
+
npx cc-discipline # Interactive setup
|
|
120
|
+
npx cc-discipline init --auto # Non-interactive defaults
|
|
121
|
+
npx cc-discipline init --auto --stack "3 4" # Python + JS/TS
|
|
122
|
+
npx cc-discipline upgrade # Upgrade to latest
|
|
123
|
+
npx cc-discipline status # Check what's installed
|
|
124
|
+
npx cc-discipline doctor # Diagnose issues`);
|
|
125
|
+
process.exit(0);
|
|
126
|
+
default:
|
|
127
|
+
console.error(`Unknown command: ${command}`);
|
|
128
|
+
console.error("Run 'cc-discipline --help' for usage");
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Run the bash script
|
|
133
|
+
const unixScript = toUnixPath(script);
|
|
134
|
+
const pkgVersion = require(path.join(PKG_DIR, 'package.json')).version;
|
|
135
|
+
const env = {
|
|
136
|
+
...process.env,
|
|
137
|
+
CC_DISCIPLINE_PKG_DIR: process.platform === 'win32' ? toUnixPath(PKG_DIR) : PKG_DIR,
|
|
138
|
+
CC_DISCIPLINE_VERSION: pkgVersion,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const result = spawnSync(bash, [unixScript, ...scriptArgs], {
|
|
142
|
+
stdio: 'inherit',
|
|
143
|
+
env,
|
|
144
|
+
cwd: process.cwd(),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
process.exit(result.status || 0);
|
package/init.sh
CHANGED
|
@@ -367,12 +367,32 @@ cp "$SCRIPT_DIR/templates/.claude/agents/investigator.md" .claude/agents/
|
|
|
367
367
|
echo -e "${GREEN}Installing skills...${NC}"
|
|
368
368
|
# Install every skill directory under templates/ — no per-skill enumeration,
|
|
369
369
|
# so adding a new skill needs zero changes here.
|
|
370
|
+
#
|
|
371
|
+
# Copy into an EXPLICIT destination directory. Do not use `cp -r "$skill_dir"
|
|
372
|
+
# .claude/skills/` here: the `*/` glob gives $skill_dir a trailing slash, and
|
|
373
|
+
# GNU and BSD cp disagree about what that means. GNU copies the directory;
|
|
374
|
+
# BSD (macOS) copies its *contents*, so every skill's SKILL.md landed on top of
|
|
375
|
+
# the previous one at .claude/skills/SKILL.md and no skill was ever updated.
|
|
376
|
+
# Shipped broken in v2.11.0 (the "directory-driven install" change) and fixed in
|
|
377
|
+
# v2.12.1. Symptom on an affected machine: a stray .claude/skills/SKILL.md plus
|
|
378
|
+
# skill dirs frozen at their first-install date. status/doctor could not catch
|
|
379
|
+
# it because they glob `skills/*/` and a bare file is not a directory.
|
|
370
380
|
for skill_dir in "$SCRIPT_DIR"/templates/.claude/skills/*/; do
|
|
371
381
|
[ -d "$skill_dir" ] || continue
|
|
372
|
-
|
|
373
|
-
|
|
382
|
+
skill_name=$(basename "$skill_dir")
|
|
383
|
+
mkdir -p ".claude/skills/$skill_name"
|
|
384
|
+
cp -R "$skill_dir"* ".claude/skills/$skill_name/"
|
|
385
|
+
echo " ✓ /$skill_name"
|
|
374
386
|
done
|
|
375
387
|
|
|
388
|
+
# Clean up the stray file left behind by the v2.11.0–v2.12.0 bug above. A bare
|
|
389
|
+
# SKILL.md directly under skills/ is never a valid skill (skills live in
|
|
390
|
+
# subdirectories), so this only ever removes that debris.
|
|
391
|
+
if [ -f ".claude/skills/SKILL.md" ]; then
|
|
392
|
+
rm -f ".claude/skills/SKILL.md"
|
|
393
|
+
echo -e " ${YELLOW}✓ removed stray .claude/skills/SKILL.md (v2.11.0 macOS install bug)${NC}"
|
|
394
|
+
fi
|
|
395
|
+
|
|
376
396
|
# ─── Handle CLAUDE.md ───
|
|
377
397
|
if [ ! -f "CLAUDE.md" ]; then
|
|
378
398
|
# No CLAUDE.md exists — generate from template
|
package/package.json
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
# Counts action-type tool calls per session, injects self-check every N actions.
|
|
4
4
|
# PreToolUse on Edit|Write|MultiEdit|Bash|Agent — additionalContext injection.
|
|
5
5
|
|
|
6
|
-
THRESHOLD=25
|
|
7
|
-
|
|
8
6
|
INPUT=$(cat)
|
|
9
7
|
# Extract session_id with a grep fallback for when jq is unavailable (e.g.
|
|
10
8
|
# Windows Git Bash). Without this fallback, every session collapsed to the
|
|
@@ -36,14 +34,29 @@ fi
|
|
|
36
34
|
|
|
37
35
|
# Progress.md staleness check: every 50 actions, check if progress.md was updated recently
|
|
38
36
|
if [ $((COUNT % 50)) -eq 0 ]; then
|
|
39
|
-
|
|
37
|
+
if command -v jq &>/dev/null; then
|
|
38
|
+
CWD=$(echo "$INPUT" | jq -r '.cwd // ""' 2>/dev/null)
|
|
39
|
+
else
|
|
40
|
+
CWD=$(echo "$INPUT" | sed -n -E 's/.*"cwd"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p' | head -1 | sed 's/[\][\]/\//g')
|
|
41
|
+
fi
|
|
40
42
|
PROGRESS_FILE=""
|
|
41
43
|
[ -f "$CWD/docs/progress.md" ] && PROGRESS_FILE="$CWD/docs/progress.md"
|
|
42
44
|
[ -z "$PROGRESS_FILE" ] && [ -f "docs/progress.md" ] && PROGRESS_FILE="docs/progress.md"
|
|
43
45
|
if [ -n "$PROGRESS_FILE" ]; then
|
|
44
|
-
# Check if file was modified in the last 30 minutes
|
|
46
|
+
# Check if file was modified in the last 30 minutes.
|
|
47
|
+
# GNU stat (Linux, Windows Git Bash) FIRST, BSD stat (macOS) second —
|
|
48
|
+
# this order matters and the reverse is broken: in GNU stat, `-f` is not
|
|
49
|
+
# BSD's "format" flag, it means "show filesystem status". It therefore
|
|
50
|
+
# SUCCEEDS on Git Bash and prints a multi-line filesystem dump, so the
|
|
51
|
+
# `||` fallback never ran and $((NOW - FILE_MOD)) died with "syntax error
|
|
52
|
+
# in expression". `stat -c` is simply an invalid option on macOS, so it
|
|
53
|
+
# fails cleanly and falls through. The numeric guard below makes the
|
|
54
|
+
# failure mode silent either way. (fixed 2026-07-30)
|
|
45
55
|
if command -v stat &>/dev/null; then
|
|
46
|
-
FILE_MOD=$(stat -
|
|
56
|
+
FILE_MOD=$(stat -c %Y "$PROGRESS_FILE" 2>/dev/null || stat -f %m "$PROGRESS_FILE" 2>/dev/null)
|
|
57
|
+
case "$FILE_MOD" in
|
|
58
|
+
''|*[!0-9]*) FILE_MOD="" ;;
|
|
59
|
+
esac
|
|
47
60
|
NOW=$(date +%s)
|
|
48
61
|
if [ -n "$FILE_MOD" ] && [ $((NOW - FILE_MOD)) -gt 1800 ]; then
|
|
49
62
|
STALE_MIN=$(( (NOW - FILE_MOD) / 60 ))
|
|
@@ -56,10 +69,14 @@ JSONEOF
|
|
|
56
69
|
fi
|
|
57
70
|
fi
|
|
58
71
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
# The 25-action, 10-question "Periodic reflection" block was removed 2026-07-30.
|
|
73
|
+
# Anthropic's Opus 5 guidance: the model verifies its own work without being told
|
|
74
|
+
# to, and "legacy harness scaffolding that adds separate verification steps"
|
|
75
|
+
# causes over-verification — it compounds with the model's own behavior and adds
|
|
76
|
+
# cost with no quality gain. The block also duplicated rules already injected
|
|
77
|
+
# every session (00-core, 03, 06, 07), at ~299 tokens per firing. The remaining
|
|
78
|
+
# checks above (early-action phase check, progress.md staleness) are kept: both
|
|
79
|
+
# came out of the 112-session insights analysis in v2.4.0 and target failure
|
|
80
|
+
# modes we actually measured, not generic self-review.
|
|
64
81
|
|
|
65
82
|
exit 0
|
|
@@ -3,17 +3,46 @@
|
|
|
3
3
|
# PreToolUse on Bash — blocks git checkout/restore/reset --hard/clean -f without confirmation.
|
|
4
4
|
|
|
5
5
|
INPUT=$(cat)
|
|
6
|
-
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
# Extract tool_name and command. MUST have a jq-or-grep fallback: jq is absent
|
|
8
|
+
# on Windows Git Bash, and the previous jq-only version left TOOL_NAME empty
|
|
9
|
+
# there, so the `!= "Bash"` check below exited 0 immediately and EVERY guard in
|
|
10
|
+
# this file was dead code. Destructive-git protection never ran on Windows.
|
|
11
|
+
# (fixed 2026-07-30 — verified with jq absent, see docs/progress.md)
|
|
12
|
+
if command -v jq &>/dev/null; then
|
|
13
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
|
|
14
|
+
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null)
|
|
15
|
+
else
|
|
16
|
+
TOOL_NAME=$(echo "$INPUT" | sed -n -E 's/.*"tool_name"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p' | head -1)
|
|
17
|
+
# The [{,] anchor makes this pick the real "command" field rather than an
|
|
18
|
+
# escaped \"command\" nested inside it (happens when piping JSON to a hook).
|
|
19
|
+
# Unescape \n and \" so line-spanning commands still match the patterns below.
|
|
20
|
+
CMD=$(echo "$INPUT" | sed -n -E 's/.*[{,][[:space:]]*"command"[[:space:]]*:[[:space:]]*"(.*)".*/\1/p' \
|
|
21
|
+
| sed 's/[\]n/ /g; s/[\]"/"/g')
|
|
22
|
+
# If the command could not be isolated, scan the whole payload instead of
|
|
23
|
+
# giving up. For a destructive-git guard the failure directions are not
|
|
24
|
+
# symmetric: a spurious confirmation prompt costs one turn, a miss costs the
|
|
25
|
+
# user's uncommitted work. Fail loud.
|
|
26
|
+
[ -z "$CMD" ] && CMD="$INPUT"
|
|
10
27
|
fi
|
|
11
28
|
|
|
12
|
-
|
|
29
|
+
# Only gate on tool_name when it actually resolved — an unresolvable tool_name
|
|
30
|
+
# must not silently disable the guard (that was the original bug).
|
|
31
|
+
if [ -n "$TOOL_NAME" ] && [ "$TOOL_NAME" != "Bash" ]; then
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
13
34
|
|
|
14
35
|
# Normalize: collapse whitespace, trim
|
|
15
36
|
CMD_NORM=$(echo "$CMD" | tr '\n' ' ' | sed 's/ */ /g')
|
|
16
37
|
|
|
38
|
+
# This guard matches command TEXT, so a command that merely quotes a destructive
|
|
39
|
+
# command trips it. The one recurring legitimate case is feeding JSON into one of
|
|
40
|
+
# these hooks to test them (the workflow documented in CLAUDE.md). Exempt it —
|
|
41
|
+
# piping a payload to a hook script never touches the working tree.
|
|
42
|
+
if echo "$CMD_NORM" | grep -qE 'hooks[/\\][a-z-]+\.sh'; then
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
17
46
|
BLOCKED=""
|
|
18
47
|
SUGGESTION=""
|
|
19
48
|
|
|
@@ -29,11 +29,25 @@ if command -v jq &>/dev/null; then
|
|
|
29
29
|
if [ -z "$OUTPUT" ]; then
|
|
30
30
|
OUTPUT=$(echo "$RAW_INPUT" | jq -r '.tool_response.output // empty' 2>/dev/null)
|
|
31
31
|
fi
|
|
32
|
+
else
|
|
33
|
+
# jq is absent on Windows Git Bash. Extract the same two fields with sed.
|
|
34
|
+
# Unescape \n so the line-anchored patterns below (^ERROR, ^Traceback, ...)
|
|
35
|
+
# still work.
|
|
36
|
+
TOOL_NAME=$(echo "$RAW_INPUT" | sed -n -E 's/.*"tool_name"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p' | head -1)
|
|
37
|
+
OUTPUT=$(echo "$RAW_INPUT" | sed -n -E 's/.*[{,][[:space:]]*"(error|output)"[[:space:]]*:[[:space:]]*"(.*)".*/\2/p' | head -1 \
|
|
38
|
+
| sed 's/[\]n/\
|
|
39
|
+
/g; s/[\]"/"/g')
|
|
32
40
|
fi
|
|
33
41
|
|
|
34
|
-
#
|
|
42
|
+
# NEVER match against the raw JSON envelope. The command text lives in there
|
|
43
|
+
# too, so `grep -rn "permission denied" logs/` used to flag itself — and with
|
|
44
|
+
# tool_name unresolved the non-Bash early-exit below never fired either, so this
|
|
45
|
+
# hook lectured on clean Read calls. Both were the old `OUTPUT="$RAW_INPUT"`
|
|
46
|
+
# fallback. If the tool's own output can't be isolated, stay silent: a false
|
|
47
|
+
# positive here costs a turn and misleads, so silence is the safe direction.
|
|
48
|
+
# (fixed 2026-07-30 — verified with jq absent, see docs/progress.md)
|
|
35
49
|
if [ -z "$OUTPUT" ]; then
|
|
36
|
-
|
|
50
|
+
exit 0
|
|
37
51
|
fi
|
|
38
52
|
|
|
39
53
|
# --- Early exits for known non-error situations ---
|
|
@@ -97,14 +111,14 @@ fi
|
|
|
97
111
|
# --- Emit reminder if error detected ---
|
|
98
112
|
|
|
99
113
|
if [ "$HAS_ERROR" = true ]; then
|
|
100
|
-
|
|
114
|
+
# Keep this to ONE line. The hypothesis discipline itself lives in the
|
|
115
|
+
# always-injected rules (00-core §6, 01-debugging) — re-injecting the full
|
|
116
|
+
# ">=3 causes / record in debug-log / eliminate >=2" ritual on every error
|
|
117
|
+
# duplicated them and pushed toward over-verification. Trimmed 2026-07-30.
|
|
118
|
+
REMINDER="Error detected: the first explanation that comes to mind is a hypothesis, not the root cause. Read the actual error before changing code."
|
|
101
119
|
|
|
102
120
|
if [ "$ERROR_TYPE" = "test" ]; then
|
|
103
|
-
REMINDER="$REMINDER
|
|
104
|
-
fi
|
|
105
|
-
|
|
106
|
-
if [ "$ERROR_TYPE" = "crash" ]; then
|
|
107
|
-
REMINDER="$REMINDER Note: Crash/segfault — may involve memory issues."
|
|
121
|
+
REMINDER="$REMINDER Test failure — decide whether the code is wrong or the test is outdated before touching either."
|
|
108
122
|
fi
|
|
109
123
|
|
|
110
124
|
echo "$REMINDER" >&2
|
|
@@ -51,34 +51,54 @@ if [ -n "$DEBUG_LOG" ]; then
|
|
|
51
51
|
CONFIRMED=$(echo "$CLEAN" | grep -c '| confirmed |' 2>/dev/null) || CONFIRMED=0
|
|
52
52
|
|
|
53
53
|
if [ "$PENDING" -gt "$CONFIRMED" ] 2>/dev/null; then
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
# Downgraded from a hard block (exit 2) to a note, 2026-07-30.
|
|
55
|
+
# As a hard block this combined with 01-debugging Phase 2 to form a trap:
|
|
56
|
+
# follow the rule, write 3 hypotheses into debug-log.md, and you were then
|
|
57
|
+
# forbidden from editing source until 3 were marked confirmed — including
|
|
58
|
+
# for unrelated planned work. It is the "legacy harness scaffolding that
|
|
59
|
+
# adds separate verification steps" Anthropic's Opus 5 guidance calls out.
|
|
60
|
+
# The hypothesis discipline itself stays in rules 00-core §6 / 01-debugging
|
|
61
|
+
# (56 measured wrong-approach incidents); only the enforcement is relaxed.
|
|
62
|
+
cat <<JSONEOF
|
|
63
|
+
{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"DEBUG-LOG NOTE: docs/debug-log.md has $((PENDING - CONFIRMED)) hypotheses still marked pending. If this edit is the fix for one of them, update its status first so the log stays truthful. If this edit is unrelated work, carry on."}}
|
|
64
|
+
JSONEOF
|
|
65
|
+
exit 0
|
|
60
66
|
fi
|
|
61
67
|
fi
|
|
62
68
|
|
|
63
69
|
# ─── Large diff warning ───
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
# Counts `content` too, not just new_string/old_string: Write sends `content`, so
|
|
71
|
+
# the previous version silently never fired on a large Write. Has a no-jq path so
|
|
72
|
+
# the "every jq read has an else" invariant holds repo-wide (see CLAUDE.md).
|
|
73
|
+
if command -v jq &>/dev/null; then
|
|
74
|
+
NEW_LINES=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // ""' | wc -l | tr -d ' ')
|
|
75
|
+
OLD_LINES=$(echo "$INPUT" | jq -r '.tool_input.old_string // ""' | wc -l | tr -d ' ')
|
|
76
|
+
else
|
|
77
|
+
# Newlines arrive as the two-character sequence \n inside the JSON string.
|
|
78
|
+
count_lines() {
|
|
79
|
+
c=$(echo "$INPUT" | sed -n -E "s/.*[{,][[:space:]]*\"$1\"[[:space:]]*:[[:space:]]*\"(.*)\".*/\1/p" \
|
|
80
|
+
| grep -o '[\]n' | wc -l | tr -d ' ')
|
|
81
|
+
echo $((c + 1))
|
|
82
|
+
}
|
|
83
|
+
NEW_LINES=$(count_lines new_string)
|
|
84
|
+
[ "$NEW_LINES" -le 1 ] && NEW_LINES=$(count_lines content)
|
|
85
|
+
OLD_LINES=$(count_lines old_string)
|
|
86
|
+
fi
|
|
87
|
+
DIFF_LINES=$((NEW_LINES > OLD_LINES ? NEW_LINES : OLD_LINES))
|
|
88
|
+
if [ "$DIFF_LINES" -gt 200 ]; then
|
|
89
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"additionalContext\":\"LARGE EDIT NOTE: This edit involves ${DIFF_LINES} lines. Consider: is this the minimal change needed? Could it be broken into smaller, more focused edits?\"}}"
|
|
90
|
+
exit 0
|
|
77
91
|
fi
|
|
78
92
|
|
|
79
93
|
# ─── New tool/script detection ───
|
|
80
94
|
# When creating a script file via Write, remind to register in CLAUDE.md
|
|
81
|
-
|
|
95
|
+
# jq-or-sed: the previous jq-only line left TOOL_NAME empty on Windows Git Bash,
|
|
96
|
+
# so this detection never fired there. (fixed 2026-07-30)
|
|
97
|
+
if command -v jq &>/dev/null; then
|
|
98
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
99
|
+
else
|
|
100
|
+
TOOL_NAME=$(echo "$INPUT" | sed -n -E 's/.*"tool_name"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p' | head -1)
|
|
101
|
+
fi
|
|
82
102
|
if [ "$TOOL_NAME" = "Write" ] && echo "$BASENAME" | grep -qiE "\.(sh|py|js|ts|rb|pl)$"; then
|
|
83
103
|
# Check if file already exists (new file = tool creation)
|
|
84
104
|
FULL_PATH="$FILE_PATH"
|
|
@@ -4,9 +4,15 @@
|
|
|
4
4
|
# stdout → context (Claude can see and act on it)
|
|
5
5
|
# Fires on: startup, resume, clear, compact
|
|
6
6
|
|
|
7
|
-
# Reset action counter for this session
|
|
7
|
+
# Reset action counter for this session.
|
|
8
|
+
# jq-or-sed: jq is absent on Windows Git Bash, and the previous jq-only version
|
|
9
|
+
# left SESSION_ID empty there, so the reset below never ran. (fixed 2026-07-30)
|
|
8
10
|
INPUT=$(cat)
|
|
9
|
-
|
|
11
|
+
if command -v jq &>/dev/null; then
|
|
12
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"' 2>/dev/null)
|
|
13
|
+
else
|
|
14
|
+
SESSION_ID=$(echo "$INPUT" | sed -n -E 's/.*"session_id"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p' | head -1)
|
|
15
|
+
fi
|
|
10
16
|
if [ -n "$SESSION_ID" ] && [ "$SESSION_ID" != "unknown" ]; then
|
|
11
17
|
rm -f "/tmp/cc-discipline-${SESSION_ID}/action-count"
|
|
12
18
|
fi
|
|
@@ -31,14 +37,14 @@ Verify project status by reading files or asking — don't assume beyond what is
|
|
|
31
37
|
EOF
|
|
32
38
|
fi
|
|
33
39
|
|
|
40
|
+
# Only the skill pointer is injected here. The four rule restatements that used
|
|
41
|
+
# to follow it (pre-edit checks, 3-failure rule, confirm-before-implementing,
|
|
42
|
+
# verify project state) were removed 2026-07-30: all four are already in the
|
|
43
|
+
# rules injected every session (02, 00-core §4, 05, 07 §4a), so repeating them
|
|
44
|
+
# here bought nothing. Skills are the one thing rules don't announce.
|
|
34
45
|
cat <<'EOF'
|
|
35
46
|
|
|
36
|
-
|
|
37
|
-
- /self-check available for periodic monitoring. For complex tasks: /loop 10m /self-check
|
|
38
|
-
- Before editing: root cause identified? scope respected? change recorded?
|
|
39
|
-
- 3 consecutive failures → pause and regroup with the user
|
|
40
|
-
- Confirm the approach with the user before starting implementation
|
|
41
|
-
- Verify project state (phase, status, dependencies) by reading files or asking
|
|
47
|
+
/self-check is available for periodic monitoring — for long tasks: /loop 10m /self-check
|
|
42
48
|
EOF
|
|
43
49
|
|
|
44
50
|
exit 0
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
# Pauses for reflection when the pattern suggests circling
|
|
5
5
|
#
|
|
6
6
|
# Design:
|
|
7
|
-
# - Source code files (.java/.ts/.py/.go etc): warn at
|
|
8
|
-
# - Config/doc files (.md/.json/.yaml/.xml etc): warn at
|
|
7
|
+
# - Source code files (.java/.ts/.py/.go etc): warn at 6, stop at 10
|
|
8
|
+
# - Config/doc files (.md/.json/.yaml/.xml etc): warn at 10, stop at 16
|
|
9
9
|
# - docs/ directory: exempt (always allow)
|
|
10
10
|
#
|
|
11
11
|
# Exit 0 + no output = silent allow
|
|
@@ -41,13 +41,20 @@ fi
|
|
|
41
41
|
BASENAME=$(basename "$FILE_PATH")
|
|
42
42
|
|
|
43
43
|
if echo "$BASENAME" | grep -qiE "\.(md|json|yaml|yml|toml|xml|cfg|ini|properties|gitignore)$"; then
|
|
44
|
-
# Config/doc files: higher thresholds (many independent sections to fill)
|
|
44
|
+
# Config/doc files: higher thresholds (many independent sections to fill).
|
|
45
|
+
# Raised 6/10 -> 10/16 on 2026-07-30 to keep this tier above the source tier
|
|
46
|
+
# after source was raised to 6/10.
|
|
47
|
+
WARN_THRESHOLD=10
|
|
48
|
+
STOP_THRESHOLD=16
|
|
49
|
+
else
|
|
50
|
+
# Source code files: repeated edits may indicate circling.
|
|
51
|
+
# Raised from 3/5 to 6/10 on 2026-07-30. Opus 5 completes multi-file features
|
|
52
|
+
# and larger refactors end-to-end rather than in one pass per file, so 5 edits
|
|
53
|
+
# to one source file is now common in legitimate feature work — the old
|
|
54
|
+
# thresholds fired on progress, not on circling. Circling that is actually
|
|
55
|
+
# worth interrupting still shows up well before 10.
|
|
45
56
|
WARN_THRESHOLD=6
|
|
46
57
|
STOP_THRESHOLD=10
|
|
47
|
-
else
|
|
48
|
-
# Source code files: strict thresholds (repeated edits may indicate circling)
|
|
49
|
-
WARN_THRESHOLD=3
|
|
50
|
-
STOP_THRESHOLD=5
|
|
51
58
|
fi
|
|
52
59
|
|
|
53
60
|
# Counter logic
|
|
@@ -5,7 +5,7 @@ description: "Core working principles — auto-injected before all operations"
|
|
|
5
5
|
|
|
6
6
|
## Core Principles
|
|
7
7
|
|
|
8
|
-
1. **Understand before acting** —
|
|
8
|
+
1. **Understand before acting** — Know what you're changing, why, and what it affects before you edit. State the reasoning when it isn't evident from the change itself; don't narrate routine edits
|
|
9
9
|
2. **Don't lock onto the first explanation** — After finding a suspected cause, list >=2 alternative hypotheses before acting
|
|
10
10
|
3. **Minimal change, minimal complexity** — No large-scale refactors unless explicitly requested. When proposing solutions, prefer the simplest approach that meets requirements. If a lightweight solution exists, choose it over a heavyweight one unless the user asks for more.
|
|
11
11
|
4. **3 consecutive failures → pause and regroup** — Report current state, attempted solutions, and points of confusion. Fresh perspective from the user often unblocks what repetition cannot.
|
|
@@ -9,14 +9,3 @@ Before modifying this file, confirm each of the following:
|
|
|
9
9
|
- [ ] **I know how to verify after the change** — Per 07-integrity: run it, paste output, or mark unverified
|
|
10
10
|
|
|
11
11
|
If any item is uncertain, resolving it first will make the edit smoother and avoid rework.
|
|
12
|
-
|
|
13
|
-
## Post-Edit Checklist
|
|
14
|
-
|
|
15
|
-
After writing or modifying code, before running it:
|
|
16
|
-
|
|
17
|
-
- [ ] **Syntax check** — Does the code compile/parse without errors? (e.g., `python -m py_compile`, `tsc --noEmit`, `go vet`)
|
|
18
|
-
- [ ] **Obvious errors** — No undefined variables, no wrong function signatures, no missing imports?
|
|
19
|
-
- [ ] **API correctness** — Are function/method calls using the right arguments, types, and return values? If unsure, read the API docs or source first.
|
|
20
|
-
- [ ] **Edge cases in your changes** — Did you handle empty inputs, None/null, off-by-one, etc.?
|
|
21
|
-
|
|
22
|
-
A 10-second syntax check catches errors that would otherwise cost 10-minute debug cycles. Always worth it.
|
|
@@ -5,13 +5,12 @@
|
|
|
5
5
|
- During debugging → update `docs/debug-log.md` (hypotheses, evidence, elimination results)
|
|
6
6
|
- When making architectural decisions → record the decision and reasoning in progress.md
|
|
7
7
|
|
|
8
|
-
###
|
|
9
|
-
- **
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- **Keep the main conversation for decisions
|
|
14
|
-
- **When to parallelize:** if two subtasks don't depend on each other's output, they should run concurrently, not sequentially.
|
|
8
|
+
### Delegation
|
|
9
|
+
- **Delegate for isolation and genuine parallelism — not by default.** A subagent earns its cost when the work is sizeable, genuinely independent, and would otherwise flood the main conversation: a wide multi-file investigation, one agent per area of a broad survey.
|
|
10
|
+
- **Work directly** on single-file edits, short sequences of tool calls, and anything where you need to carry context across steps. If you can finish it in a handful of tool calls, don't delegate it.
|
|
11
|
+
- **Never delegate verification.** Don't spawn agents to double-check or re-verify your own work.
|
|
12
|
+
- **Keep spawn counts low.** If one subagent can do the job, use one rather than several.
|
|
13
|
+
- **Keep the main conversation for decisions.** When you do delegate research, the subagent reads and reports; the main conversation synthesizes and decides.
|
|
15
14
|
|
|
16
15
|
### Compact Strategy
|
|
17
16
|
- Avoid proactively suggesting compacting or warning about "context running low." The system auto-compacts when context hits 0% — there is no advance warning, and you cannot see the percentage. With 200K-1M context, most sessions never hit the limit. The urge to say "this session is getting long" is understandable in a long session, but it's not based on information you have access to — the system will handle it. Focus on the work.
|
|
@@ -77,16 +77,3 @@ When you discover wrong information in memory, docs, or prior output:
|
|
|
77
77
|
1. Correct it now, not "next time"
|
|
78
78
|
2. Note the correction and why, to prevent recurrence
|
|
79
79
|
3. If wrong information was already sent externally, alert the user
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## Pre-action Checklist
|
|
84
|
-
|
|
85
|
-
Before any significant action, verify:
|
|
86
|
-
|
|
87
|
-
- [ ] Are my assumptions verified, or inferred from names/context?
|
|
88
|
-
- [ ] Are my "verified" claims actually backed by execution output?
|
|
89
|
-
- [ ] Am I quoting tool output verbatim, or have I altered it?
|
|
90
|
-
- [ ] Is the external information I'm referencing current?
|
|
91
|
-
- [ ] Does this content go external? Has the user reviewed it?
|
|
92
|
-
- [ ] Is there anything I wrote confidently but am actually unsure about?
|
|
@@ -3,11 +3,11 @@ name: retro
|
|
|
3
3
|
description: Find friction, remove friction. Quick post-task review that makes this project's workflow smoother and feeds improvements back to cc-discipline.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Find friction. Remove friction.
|
|
6
|
+
Find friction. Remove friction. Record what the rules actually caught.
|
|
7
7
|
|
|
8
8
|
## What to do
|
|
9
9
|
|
|
10
|
-
Quickly scan what just happened — `git log --oneline -10` and any hook triggers you remember. Then output
|
|
10
|
+
Quickly scan what just happened — `git log --oneline -10` and any hook triggers you remember. Then output in this format:
|
|
11
11
|
|
|
12
12
|
```
|
|
13
13
|
RETRO — [date]
|
|
@@ -15,6 +15,9 @@ RETRO — [date]
|
|
|
15
15
|
Friction:
|
|
16
16
|
- [what got in the way] → fix: [specific actionable change]
|
|
17
17
|
|
|
18
|
+
Saves:
|
|
19
|
+
- [rule/hook] caught [the specific real problem] — without it: [what would have shipped]
|
|
20
|
+
|
|
18
21
|
Insights:
|
|
19
22
|
- [something learned that should survive this session]
|
|
20
23
|
|
|
@@ -23,18 +26,24 @@ Framework:
|
|
|
23
26
|
```
|
|
24
27
|
|
|
25
28
|
Rules:
|
|
26
|
-
- **Only friction** —
|
|
27
|
-
- **Only actionable** — Every friction item must have a "→ fix:" with a concrete change (adjust a threshold, add to CLAUDE.md, update memory, exempt a path).
|
|
29
|
+
- **Only actionable friction** — Every friction item must have a "→ fix:" with a concrete change (adjust a threshold, add to CLAUDE.md, update memory, exempt a path).
|
|
28
30
|
- **Only new** — Don't repeat friction that's already been addressed or recorded in memory.
|
|
29
31
|
- **Be specific** — "streak-breaker was annoying" is not useful. "streak-breaker triggered 3x on config.yaml during template fill → fix: add config.yaml to docs/ exempt path, or raise config threshold to 10" is useful.
|
|
30
32
|
- **Framework items are rare** — Most friction is project-specific. Only flag framework issues if the same problem would hit other projects too.
|
|
31
|
-
- **Keep it short** — 3-5 items max. If you can't find friction, say "no friction found" and move on. An empty
|
|
33
|
+
- **Keep it short** — 3-5 items per section max. If you can't find friction, say "no friction found" and move on. An empty friction list is a good sign.
|
|
34
|
+
|
|
35
|
+
Saves — read this before filling that section in:
|
|
36
|
+
- **A save is not "what went well."** Log a save only when a rule or hook *changed the outcome*: it caught a real mistake, blocked a real loss, or stopped a wrong turn that was already in motion. "The rules kept me disciplined" is not a save. "git-guard blocked `git reset --hard` while 40 min of uncommitted work was in the tree" is.
|
|
37
|
+
- **"no saves" is a real answer, and it is data.** Write it plainly rather than manufacturing one.
|
|
38
|
+
- **Why this section exists:** friction is visible and saves are invisible — a rule that works silently suppresses the very failure it was written for, so the only naturally measurable signal is its cost. With cost-only data every rule eventually looks like pure overhead and gets cut, including the ones that are still load-bearing. The save log is what makes it possible to judge, later and with evidence, whether a given rule still earns its place.
|
|
32
39
|
|
|
33
40
|
## After output
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
Append both Friction and Saves to a `## Rule Ledger` section in `docs/progress.md` (create the section at the end of the file if it isn't there yet), one dated line each. This accumulates the record across sessions — a single retro proves nothing, twenty of them decide which rules stay.
|
|
43
|
+
|
|
44
|
+
Then present the items. User decides:
|
|
36
45
|
- "fix it" → apply the changes
|
|
37
46
|
- "remember it" → write to memory via /commit
|
|
38
47
|
- "skip" → move on
|
|
39
48
|
|
|
40
|
-
Do not auto-apply. Do not pad. Do not turn this into a report.
|
|
49
|
+
Do not auto-apply fixes. Do not pad. Do not turn this into a report. (Appending to the ledger is not a fix — do that without asking.)
|
|
@@ -73,7 +73,7 @@ Rules:
|
|
|
73
73
|
- Flag risks or unknowns you've spotted
|
|
74
74
|
- **Every step must have acceptance criteria** — "done when" must be observable and verifiable, not vague. Bad: "done when refactored". Good: "done when 3 methods extracted, each ≤20 lines, all tests pass"
|
|
75
75
|
- **Simplicity bias** — If a simple approach meets all requirements, list it first and recommend it. Do NOT lead with complex/elegant solutions unless the user signals they want that. "Simple but sufficient" beats "powerful but overkill".
|
|
76
|
-
- **
|
|
76
|
+
- **Note real dependencies** — Mark which steps depend on which. Don't annotate steps for parallel delegation by default; see 03-context-mgmt "Delegation" for when a subagent is actually warranted.
|
|
77
77
|
|
|
78
78
|
## Step 4: Self-review
|
|
79
79
|
|
|
@@ -70,3 +70,25 @@
|
|
|
70
70
|
| # | Decision | Reason | Impact Scope | Date |
|
|
71
71
|
|---|----------|--------|-------------|------|
|
|
72
72
|
| | | | | |
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Rule Ledger
|
|
77
|
+
|
|
78
|
+
<!--
|
|
79
|
+
Appended by /retro. Two columns of evidence about the discipline rules themselves:
|
|
80
|
+
|
|
81
|
+
- SAVE — a rule or hook changed the outcome: caught a real mistake, blocked a
|
|
82
|
+
real loss, stopped a wrong turn already in motion.
|
|
83
|
+
- FRICTION — a rule or hook got in the way and cost time for no benefit.
|
|
84
|
+
|
|
85
|
+
Why keep this: friction is visible, saves are invisible. A rule that works
|
|
86
|
+
silently suppresses the very failure it was written for, so cost is the only
|
|
87
|
+
signal that shows up on its own. Judging rules on cost alone eventually cuts the
|
|
88
|
+
load-bearing ones. This ledger is the counterweight — with enough dated entries,
|
|
89
|
+
"should this rule stay?" becomes a lookup instead of a guess.
|
|
90
|
+
-->
|
|
91
|
+
|
|
92
|
+
| Date | Kind | Rule / Hook | What happened |
|
|
93
|
+
|------|------|-------------|---------------|
|
|
94
|
+
| | | | |
|