continuous-improvement 3.1.0 → 3.8.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/.claude-plugin/marketplace.json +78 -0
- package/CHANGELOG.md +191 -0
- package/LICENSE +21 -21
- package/QUICKSTART.md +101 -81
- package/README.md +207 -359
- package/SKILL.md +87 -9
- package/action.yml +33 -33
- package/bin/analyze.sh +161 -153
- package/bin/backfill.mjs +172 -0
- package/bin/check-docs-substrings.mjs +333 -0
- package/bin/check-everything-mirror.mjs +145 -0
- package/bin/check-routing-targets.mjs +151 -0
- package/bin/check-skill-law-tag.mjs +128 -0
- package/bin/check-skill-mirror.mjs +119 -0
- package/bin/check-skill-tiers.mjs +116 -0
- package/bin/check-third-party-shape.mjs +202 -0
- package/bin/generate-plugin-manifests.mjs +169 -0
- package/bin/harvest-friction.mjs +279 -0
- package/bin/hook-stats.mjs +258 -0
- package/bin/install.mjs +417 -516
- package/bin/lint-transcript.mjs +182 -210
- package/bin/mcp-server.mjs +840 -617
- package/bin/observe.mjs +148 -0
- package/bin/pre-commit-block-strays.sh +49 -0
- package/bin/refresh-third-party.mjs +416 -0
- package/bin/unified-cli.mjs +533 -0
- package/commands/continuous-improvement.md +115 -74
- package/commands/dashboard.md +56 -56
- package/commands/discipline.md +51 -37
- package/commands/harvest.md +76 -0
- package/commands/learn-eval.md +117 -0
- package/commands/planning-with-files.md +66 -0
- package/commands/proceed-with-the-recommendation.md +62 -0
- package/commands/ralph.md +103 -0
- package/commands/release-train.md +81 -0
- package/commands/seven-laws.md +16 -0
- package/commands/superpowers.md +153 -0
- package/commands/swarm.md +101 -0
- package/commands/workspace-surface-audit.md +77 -0
- package/hooks/observe.sh +172 -134
- package/hooks/session.sh +106 -106
- package/hooks/three-section-close.mjs +181 -0
- package/instinct-packs/go.json +58 -58
- package/instinct-packs/meta.json +16 -0
- package/instinct-packs/python.json +58 -58
- package/instinct-packs/react.json +58 -58
- package/lib/cli-anything.mjs +401 -0
- package/lib/compound-engineering.mjs +831 -0
- package/lib/observe-event.mjs +128 -0
- package/lib/plugin-metadata.mjs +432 -0
- package/lib/pm-marketplace.mjs +61 -0
- package/lib/pm-skills.mjs +1274 -0
- package/lib/resolve-home-dir.mjs +43 -0
- package/lib/skill-tiers.mjs +137 -0
- package/lib/unified-plugin.mjs +924 -0
- package/llms.txt +68 -43
- package/package.json +28 -19
- package/plugins/beginner.json +17 -6
- package/plugins/continuous-improvement/.claude-plugin/marketplace.json +20 -0
- package/plugins/continuous-improvement/.claude-plugin/plugin.json +26 -0
- package/plugins/continuous-improvement/LICENSE +21 -0
- package/plugins/continuous-improvement/README.md +56 -0
- package/plugins/continuous-improvement/bin/backfill.mjs +172 -0
- package/plugins/continuous-improvement/bin/mcp-server.mjs +886 -0
- package/plugins/continuous-improvement/bin/observe.mjs +148 -0
- package/plugins/continuous-improvement/commands/continuous-improvement.md +115 -0
- package/plugins/continuous-improvement/commands/dashboard.md +56 -0
- package/plugins/continuous-improvement/commands/discipline.md +51 -0
- package/plugins/continuous-improvement/commands/harvest.md +76 -0
- package/plugins/continuous-improvement/commands/learn-eval.md +117 -0
- package/plugins/continuous-improvement/commands/planning-with-files.md +66 -0
- package/plugins/continuous-improvement/commands/proceed-with-the-recommendation.md +62 -0
- package/plugins/continuous-improvement/commands/ralph.md +103 -0
- package/plugins/continuous-improvement/commands/release-train.md +81 -0
- package/plugins/continuous-improvement/commands/seven-laws.md +16 -0
- package/plugins/continuous-improvement/commands/superpowers.md +153 -0
- package/plugins/continuous-improvement/commands/swarm.md +101 -0
- package/plugins/continuous-improvement/commands/workspace-surface-audit.md +77 -0
- package/plugins/continuous-improvement/hooks/hooks.json +60 -0
- package/plugins/continuous-improvement/hooks/observe.sh +172 -0
- package/plugins/continuous-improvement/hooks/session.sh +106 -0
- package/plugins/continuous-improvement/hooks/three-section-close.mjs +181 -0
- package/plugins/continuous-improvement/instinct-packs/go.json +58 -0
- package/plugins/continuous-improvement/instinct-packs/meta.json +16 -0
- package/plugins/continuous-improvement/instinct-packs/python.json +58 -0
- package/plugins/continuous-improvement/instinct-packs/react.json +58 -0
- package/plugins/continuous-improvement/lib/observe-event.mjs +128 -0
- package/plugins/continuous-improvement/lib/plugin-metadata.mjs +432 -0
- package/plugins/continuous-improvement/lib/resolve-home-dir.mjs +43 -0
- package/plugins/continuous-improvement/skills/README.md +34 -0
- package/plugins/continuous-improvement/skills/continuous-improvement/SKILL.md +249 -0
- package/plugins/continuous-improvement/skills/deploy-receipt/SKILL.md +131 -0
- package/plugins/continuous-improvement/skills/gateguard/SKILL.md +155 -0
- package/plugins/continuous-improvement/skills/para-memory-files/SKILL.md +108 -0
- package/plugins/continuous-improvement/skills/proceed-with-the-recommendation/SKILL.md +454 -0
- package/plugins/continuous-improvement/skills/ralph/SKILL.md +221 -0
- package/plugins/continuous-improvement/skills/safety-guard/SKILL.md +76 -0
- package/plugins/continuous-improvement/skills/strategic-compact/SKILL.md +104 -0
- package/plugins/continuous-improvement/skills/superpowers/SKILL.md +212 -0
- package/plugins/continuous-improvement/skills/tdd-workflow/SKILL.md +411 -0
- package/plugins/continuous-improvement/skills/token-budget-advisor/SKILL.md +136 -0
- package/plugins/continuous-improvement/skills/verification-loop/SKILL.md +192 -0
- package/plugins/continuous-improvement/skills/wild-risa-balance/SKILL.md +191 -0
- package/plugins/continuous-improvement/skills/workspace-surface-audit/SKILL.md +147 -0
- package/plugins/continuous-improvement/templates/planning-with-files/findings.md +8 -0
- package/plugins/continuous-improvement/templates/planning-with-files/progress.md +7 -0
- package/plugins/continuous-improvement/templates/planning-with-files/task_plan.md +23 -0
- package/plugins/expert.json +26 -5
- package/skills/README.md +79 -0
- package/skills/deploy-receipt.md +131 -0
- package/skills/gateguard.md +155 -0
- package/skills/para-memory-files.md +108 -0
- package/skills/proceed-with-the-recommendation.md +454 -0
- package/skills/ralph.md +221 -0
- package/skills/safety-guard.md +76 -0
- package/skills/strategic-compact.md +104 -0
- package/skills/superpowers.md +212 -0
- package/skills/tdd-workflow.md +411 -0
- package/skills/token-budget-advisor.md +136 -0
- package/skills/verification-loop.md +192 -0
- package/skills/wild-risa-balance.md +191 -0
- package/skills/workspace-surface-audit.md +147 -0
- package/templates/planning-with-files/findings.md +8 -0
- package/templates/planning-with-files/progress.md +7 -0
- package/templates/planning-with-files/task_plan.md +23 -0
- package/templates/verify-ladder.example.json +47 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# observe.sh — Mulahazah PreToolUse/PostToolUse observation hook.
|
|
3
|
+
#
|
|
4
|
+
# Two-phase design:
|
|
5
|
+
# 1. PREFERRED: exec the Node observer (bin/observe.mjs) which captures the
|
|
6
|
+
# rich event schema (tool_input.command for Bash, file_path for Edit/
|
|
7
|
+
# Write/Read, tool_output) without depending on jq.
|
|
8
|
+
# 2. FALLBACK: if either node is missing OR the Node observer file is not
|
|
9
|
+
# installed alongside this script, fall through to the in-bash thin-
|
|
10
|
+
# schema path below. The fallback preserves prior behavior so an
|
|
11
|
+
# operator who never re-runs `npx continuous-improvement install` is
|
|
12
|
+
# no worse off than today.
|
|
13
|
+
#
|
|
14
|
+
# Always exits 0 — never blocks the Claude session.
|
|
15
|
+
# Usage: echo '<hook_json>' | observe.sh
|
|
16
|
+
|
|
17
|
+
trap 'exit 0' EXIT ERR INT TERM
|
|
18
|
+
|
|
19
|
+
# Phase 1 — try the Node observer. The shim copies bin/observe.mjs and
|
|
20
|
+
# lib/observe-event.mjs alongside this script under ${HOME}/.claude/instincts/
|
|
21
|
+
# preserving the bin/ + lib/ subdirectory layout so the relative import
|
|
22
|
+
# resolves: observe.mjs lives at .../instincts/bin/observe.mjs, the imported
|
|
23
|
+
# observe-event.mjs at .../instincts/lib/observe-event.mjs.
|
|
24
|
+
NODE_OBSERVER="$(dirname "${BASH_SOURCE[0]}")/bin/observe.mjs"
|
|
25
|
+
if [[ -f "$NODE_OBSERVER" ]] && command -v node &>/dev/null; then
|
|
26
|
+
exec node "$NODE_OBSERVER"
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Phase 2 — bash fallback (thin schema; preserved for back-compat).
|
|
30
|
+
INSTINCTS_DIR="${HOME}/.claude/instincts"
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Read stdin (hook payload) — single read for performance
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
INPUT="$(cat)"
|
|
36
|
+
[[ -z "$INPUT" ]] && exit 0
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Parse hook payload — use jq if available, otherwise basic extraction
|
|
40
|
+
#
|
|
41
|
+
# When jq is missing, observation rows lack tool_input/tool_output, which
|
|
42
|
+
# defeats the auto-instinct pipeline. Emit a one-shot stderr warning per
|
|
43
|
+
# project so the operator notices at the next session start instead of
|
|
44
|
+
# discovering the gap weeks later. The marker file makes it idempotent.
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
if command -v jq &>/dev/null; then
|
|
47
|
+
read -r TOOL_NAME SESSION_ID HAS_OUTPUT INPUT_JSON OUTPUT_JSON <<< "$(
|
|
48
|
+
printf '%s' "$INPUT" | jq -r '
|
|
49
|
+
(.tool_name // ""),
|
|
50
|
+
(.session_id // ""),
|
|
51
|
+
(if has("tool_output") then "yes" else "no" end),
|
|
52
|
+
((.tool_input // {} | tostring) | .[0:500]),
|
|
53
|
+
((.tool_output // {} | tostring) | .[0:200])
|
|
54
|
+
' | paste - - - - -
|
|
55
|
+
)"
|
|
56
|
+
else
|
|
57
|
+
# One-shot stderr warning per HOME so the operator learns the constraint.
|
|
58
|
+
# Marker lives next to instincts/ (not inside it) so it never collides with
|
|
59
|
+
# tests or analysis loops that iterate INSTINCTS_DIR for project-hash dirs.
|
|
60
|
+
JQ_WARN_MARKER="${HOME}/.claude/.continuous-improvement-jq-warned"
|
|
61
|
+
if [[ ! -f "$JQ_WARN_MARKER" ]]; then
|
|
62
|
+
mkdir -p "${HOME}/.claude" 2>/dev/null
|
|
63
|
+
printf '[continuous-improvement] jq not found on PATH — observe.sh is using thin-schema fallback. Install jq to enable auto-instinct detection (winget install jqlang.jq | brew install jq | apt install jq). This warning is one-shot per host.\n' >&2
|
|
64
|
+
: > "$JQ_WARN_MARKER"
|
|
65
|
+
fi
|
|
66
|
+
# Fallback: extract tool_name with basic pattern matching
|
|
67
|
+
TOOL_NAME="$(printf '%s' "$INPUT" | sed -n 's/.*"tool_name" *: *"\([^"]*\)".*/\1/p' | head -1)"
|
|
68
|
+
SESSION_ID="$(printf '%s' "$INPUT" | sed -n 's/.*"session_id" *: *"\([^"]*\)".*/\1/p' | head -1)"
|
|
69
|
+
HAS_OUTPUT="no"
|
|
70
|
+
printf '%s' "$INPUT" | grep -q '"tool_output"' && HAS_OUTPUT="yes"
|
|
71
|
+
INPUT_JSON="$(printf '%s' "$INPUT" | head -c 500)"
|
|
72
|
+
OUTPUT_JSON=""
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
[[ -z "$TOOL_NAME" ]] && exit 0
|
|
76
|
+
|
|
77
|
+
# Determine event type
|
|
78
|
+
if [[ "$HAS_OUTPUT" == "yes" ]]; then
|
|
79
|
+
EVENT="tool_complete"
|
|
80
|
+
else
|
|
81
|
+
EVENT="tool_start"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
# Project detection
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
PROJECT_ROOT=""
|
|
88
|
+
|
|
89
|
+
if [[ -n "${CLAUDE_PROJECT_DIR:-}" && -d "${CLAUDE_PROJECT_DIR}" ]]; then
|
|
90
|
+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR}"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
if [[ -z "$PROJECT_ROOT" ]]; then
|
|
94
|
+
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
if [[ -z "$PROJECT_ROOT" ]]; then
|
|
98
|
+
PROJECT_ROOT="global"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
# Compute project hash and name
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
PROJECT_HASH="$(printf '%s' "$PROJECT_ROOT" | sha256sum | cut -c1-12)"
|
|
105
|
+
PROJECT_NAME="$(basename "${PROJECT_ROOT%.git}")"
|
|
106
|
+
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
# Directory setup
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
PROJECT_DIR="${INSTINCTS_DIR}/${PROJECT_HASH}"
|
|
111
|
+
OBS_FILE="${PROJECT_DIR}/observations.jsonl"
|
|
112
|
+
|
|
113
|
+
[[ -d "$PROJECT_DIR" ]] || mkdir -p "$PROJECT_DIR"
|
|
114
|
+
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
# Rotate observations.jsonl at 10,000 lines
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
if [[ -f "$OBS_FILE" ]]; then
|
|
119
|
+
LINE_COUNT="$(wc -l < "$OBS_FILE")"
|
|
120
|
+
if (( LINE_COUNT >= 10000 )); then
|
|
121
|
+
mv "$OBS_FILE" "${PROJECT_DIR}/observations.$(date -u +"%Y-%m-%d-%H%M%S").jsonl"
|
|
122
|
+
|
|
123
|
+
# Clean up old archives - keep only 10 most recent
|
|
124
|
+
ls -t "${PROJECT_DIR}"/observations.*.jsonl 2>/dev/null | tail -n +11 | xargs -r rm -f
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
# Append JSONL observation line
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
TS="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
132
|
+
|
|
133
|
+
if command -v jq &>/dev/null; then
|
|
134
|
+
printf '%s\n' "$(jq -cn \
|
|
135
|
+
--arg ts "$TS" \
|
|
136
|
+
--arg event "$EVENT" \
|
|
137
|
+
--arg session "$SESSION_ID" \
|
|
138
|
+
--arg tool "$TOOL_NAME" \
|
|
139
|
+
--arg input_summary "$INPUT_JSON" \
|
|
140
|
+
--arg output_summary "$OUTPUT_JSON" \
|
|
141
|
+
--arg project_id "$PROJECT_HASH" \
|
|
142
|
+
--arg project_name "$PROJECT_NAME" \
|
|
143
|
+
'{ts:$ts,event:$event,session:$session,tool:$tool,input_summary:$input_summary,output_summary:$output_summary,project_id:$project_id,project_name:$project_name}')" \
|
|
144
|
+
>> "$OBS_FILE"
|
|
145
|
+
else
|
|
146
|
+
# Fallback: manual JSON construction
|
|
147
|
+
printf '{"ts":"%s","event":"%s","session":"%s","tool":"%s","project_id":"%s","project_name":"%s"}\n' \
|
|
148
|
+
"$TS" "$EVENT" "$SESSION_ID" "$TOOL_NAME" "$PROJECT_HASH" "$PROJECT_NAME" \
|
|
149
|
+
>> "$OBS_FILE"
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# Write project.json if new project
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
PROJECT_JSON="${PROJECT_DIR}/project.json"
|
|
156
|
+
if [[ ! -f "$PROJECT_JSON" ]]; then
|
|
157
|
+
if command -v jq &>/dev/null; then
|
|
158
|
+
jq -n \
|
|
159
|
+
--arg id "$PROJECT_HASH" \
|
|
160
|
+
--arg name "$PROJECT_NAME" \
|
|
161
|
+
--arg root "$PROJECT_ROOT" \
|
|
162
|
+
--arg created_at "$TS" \
|
|
163
|
+
'{id:$id,name:$name,root:$root,created_at:$created_at}' \
|
|
164
|
+
> "$PROJECT_JSON"
|
|
165
|
+
else
|
|
166
|
+
printf '{"id":"%s","name":"%s","root":"%s","created_at":"%s"}\n' \
|
|
167
|
+
"$PROJECT_HASH" "$PROJECT_NAME" "$PROJECT_ROOT" "$TS" \
|
|
168
|
+
> "$PROJECT_JSON"
|
|
169
|
+
fi
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
exit 0
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# session.sh — SessionStart/SessionEnd hook for continuous-improvement
|
|
3
|
+
# SessionStart: loads instincts and prints status
|
|
4
|
+
# SessionEnd: prompts reflection
|
|
5
|
+
# Always exits 0 — never blocks the session
|
|
6
|
+
trap 'exit 0' EXIT ERR INT TERM
|
|
7
|
+
|
|
8
|
+
INSTINCTS_DIR="${HOME}/.claude/instincts"
|
|
9
|
+
|
|
10
|
+
# Read stdin
|
|
11
|
+
INPUT="$(cat)"
|
|
12
|
+
[[ -z "$INPUT" ]] && exit 0
|
|
13
|
+
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
# Detect event type from hook context
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# SessionStart hooks receive no tool_name, SessionEnd hooks may vary.
|
|
18
|
+
# We detect based on the hook_type field or the calling context.
|
|
19
|
+
EVENT_TYPE=""
|
|
20
|
+
if command -v jq &>/dev/null; then
|
|
21
|
+
EVENT_TYPE="$(printf '%s' "$INPUT" | jq -r '.hook_type // .event_type // "unknown"' 2>/dev/null)"
|
|
22
|
+
else
|
|
23
|
+
if printf '%s' "$INPUT" | grep -q '"SessionStart"'; then
|
|
24
|
+
EVENT_TYPE="SessionStart"
|
|
25
|
+
elif printf '%s' "$INPUT" | grep -q '"SessionEnd"'; then
|
|
26
|
+
EVENT_TYPE="SessionEnd"
|
|
27
|
+
fi
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Project detection (same as observe.sh)
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
PROJECT_ROOT=""
|
|
34
|
+
if [[ -n "${CLAUDE_PROJECT_DIR:-}" && -d "${CLAUDE_PROJECT_DIR}" ]]; then
|
|
35
|
+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR}"
|
|
36
|
+
fi
|
|
37
|
+
if [[ -z "$PROJECT_ROOT" ]]; then
|
|
38
|
+
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
39
|
+
fi
|
|
40
|
+
if [[ -z "$PROJECT_ROOT" ]]; then
|
|
41
|
+
PROJECT_ROOT="global"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
PROJECT_HASH="$(printf '%s' "$PROJECT_ROOT" | sha256sum | cut -c1-12)"
|
|
45
|
+
PROJECT_DIR="${INSTINCTS_DIR}/${PROJECT_HASH}"
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# SessionStart: count observations and instincts, print brief status
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
if [[ "$EVENT_TYPE" == "SessionStart" || "$EVENT_TYPE" == "unknown" ]]; then
|
|
51
|
+
OBS_COUNT=0
|
|
52
|
+
INSTINCT_COUNT=0
|
|
53
|
+
|
|
54
|
+
OBS_FILE="${PROJECT_DIR}/observations.jsonl"
|
|
55
|
+
if [[ -f "$OBS_FILE" ]]; then
|
|
56
|
+
OBS_COUNT="$(wc -l < "$OBS_FILE" 2>/dev/null || echo 0)"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Count yaml files in project + global
|
|
60
|
+
for dir in "$PROJECT_DIR" "${INSTINCTS_DIR}/global"; do
|
|
61
|
+
if [[ -d "$dir" ]]; then
|
|
62
|
+
count="$(find "$dir" -maxdepth 1 -name '*.yaml' 2>/dev/null | wc -l)"
|
|
63
|
+
INSTINCT_COUNT=$((INSTINCT_COUNT + count))
|
|
64
|
+
fi
|
|
65
|
+
done
|
|
66
|
+
|
|
67
|
+
# Determine level
|
|
68
|
+
LEVEL="CAPTURE"
|
|
69
|
+
if (( OBS_COUNT >= 20 )) || (( INSTINCT_COUNT > 0 )); then
|
|
70
|
+
LEVEL="ANALYZE"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Check for high-confidence instincts
|
|
74
|
+
if (( INSTINCT_COUNT > 0 )); then
|
|
75
|
+
for dir in "$PROJECT_DIR" "${INSTINCTS_DIR}/global"; do
|
|
76
|
+
if [[ -d "$dir" ]]; then
|
|
77
|
+
for f in "$dir"/*.yaml; do
|
|
78
|
+
[[ -f "$f" ]] || continue
|
|
79
|
+
conf="$(grep '^confidence:' "$f" 2>/dev/null | head -1 | sed 's/confidence: *//')"
|
|
80
|
+
if [[ -n "$conf" ]]; then
|
|
81
|
+
# Compare as integer (multiply by 100) using awk for portability
|
|
82
|
+
int_conf="$(printf '%s' "$conf" | awk '{printf "%.0f", $1 * 100}')"
|
|
83
|
+
if [[ -n "$int_conf" ]] && (( int_conf >= 70 )); then
|
|
84
|
+
LEVEL="AUTO-APPLY"
|
|
85
|
+
break 2
|
|
86
|
+
elif (( int_conf >= 50 )); then
|
|
87
|
+
LEVEL="SUGGEST"
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
done
|
|
91
|
+
fi
|
|
92
|
+
done
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Write status to stderr (visible in hook output, not blocking)
|
|
96
|
+
echo "[continuous-improvement] Level: ${LEVEL} | Observations: ${OBS_COUNT} | Instincts: ${INSTINCT_COUNT}" >&2
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
# SessionEnd: remind to reflect
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
if [[ "$EVENT_TYPE" == "SessionEnd" ]]; then
|
|
103
|
+
echo "[continuous-improvement] Session ending. Run /continuous-improvement to reflect and capture learnings." >&2
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
exit 0
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// three-section-close.mjs — Stop hook that requires every substantive
|
|
3
|
+
// assistant reply to close with three named sections:
|
|
4
|
+
// "What has been done", "What is next", "Recommendation".
|
|
5
|
+
//
|
|
6
|
+
// Implements Law 4 + Law 5 in-band: verify before reporting, reflect at
|
|
7
|
+
// the end. See SKILL.md for the discipline rationale.
|
|
8
|
+
//
|
|
9
|
+
// Behavior:
|
|
10
|
+
// - exit 0 to allow the response to stand
|
|
11
|
+
// - emit {"decision":"block","reason":"..."} on stdout to re-prompt
|
|
12
|
+
// - never block on internal failure (parse error, missing transcript,
|
|
13
|
+
// unexpected message shape) — fail open
|
|
14
|
+
//
|
|
15
|
+
// Heuristic: skip messages shorter than 600 chars. Pure clarifying replies
|
|
16
|
+
// don't need the close; only substantive technical responses (work
|
|
17
|
+
// summaries, plan responses, post-execution reports) get gated.
|
|
18
|
+
//
|
|
19
|
+
// Telemetry: best-effort JSONL line per gated invocation written to
|
|
20
|
+
// ~/.claude/hook-telemetry/<project-hash>.jsonl. Never throws, never
|
|
21
|
+
// blocks the response. No network. No transcript content recorded.
|
|
22
|
+
|
|
23
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
24
|
+
import { createHash } from "node:crypto";
|
|
25
|
+
import { platform } from "node:os";
|
|
26
|
+
import { dirname, join } from "node:path";
|
|
27
|
+
import { performance } from "node:perf_hooks";
|
|
28
|
+
|
|
29
|
+
import { resolveHomeDir } from "../lib/resolve-home-dir.mjs";
|
|
30
|
+
|
|
31
|
+
const MIN_LENGTH_TO_GATE = 600;
|
|
32
|
+
|
|
33
|
+
const REQUIRED_SECTIONS = [
|
|
34
|
+
{
|
|
35
|
+
heading: "What has been done",
|
|
36
|
+
hint: "concrete summary of changes/findings from this turn",
|
|
37
|
+
pattern: /^#+ +What has been done(?:\s|$)/m,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
heading: "What is next",
|
|
41
|
+
hint: "minimum 5 specific, actionable bullets",
|
|
42
|
+
pattern: /^#+ +What is next(?:\s|$)/m,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
heading: "Recommendation",
|
|
46
|
+
hint: "ranked descending by impact, or write \"no\" if there is no recommendation",
|
|
47
|
+
pattern: /^#+ +Recommendation(?:\s|$)/m,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
function readStdinSync() {
|
|
52
|
+
try {
|
|
53
|
+
return readFileSync(0, "utf8");
|
|
54
|
+
} catch {
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function safeJsonParse(text) {
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(text);
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function extractLastAssistantText(transcriptPath) {
|
|
68
|
+
const raw = readFileSync(transcriptPath, "utf8");
|
|
69
|
+
const lines = raw.split(/\r?\n/);
|
|
70
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
71
|
+
const line = lines[i].trim();
|
|
72
|
+
if (!line) continue;
|
|
73
|
+
const obj = safeJsonParse(line);
|
|
74
|
+
if (!obj || obj.type !== "assistant") continue;
|
|
75
|
+
const blocks = obj.message?.content;
|
|
76
|
+
if (!Array.isArray(blocks)) continue;
|
|
77
|
+
const texts = blocks
|
|
78
|
+
.filter((b) => b && b.type === "text" && typeof b.text === "string")
|
|
79
|
+
.map((b) => b.text);
|
|
80
|
+
if (texts.length === 0) continue;
|
|
81
|
+
return texts.join("\n");
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function emitBlock(missing) {
|
|
87
|
+
const bullets = missing
|
|
88
|
+
.map(
|
|
89
|
+
(s) =>
|
|
90
|
+
`- "## ${s.heading}" — ${s.hint}`,
|
|
91
|
+
)
|
|
92
|
+
.join("\n");
|
|
93
|
+
const reason =
|
|
94
|
+
`Your reply is missing the required 3-section close. Add these sections at the end of the reply with these exact headings:\n${bullets}`;
|
|
95
|
+
process.stdout.write(JSON.stringify({ decision: "block", reason }) + "\n");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function projectHashFor(transcriptPath) {
|
|
99
|
+
const dir = dirname(transcriptPath);
|
|
100
|
+
let normalized = dir.split("\\").join("/");
|
|
101
|
+
if (platform() === "win32") normalized = normalized.toLowerCase();
|
|
102
|
+
return createHash("sha256").update(normalized).digest("hex").slice(0, 12);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function recordTelemetry(entry, transcriptPath) {
|
|
106
|
+
try {
|
|
107
|
+
const home = resolveHomeDir();
|
|
108
|
+
if (!home) return;
|
|
109
|
+
const telemetryDir = join(home, ".claude", "hook-telemetry");
|
|
110
|
+
try {
|
|
111
|
+
mkdirSync(telemetryDir, { recursive: true });
|
|
112
|
+
} catch {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const file = join(telemetryDir, `${projectHashFor(transcriptPath)}.jsonl`);
|
|
116
|
+
try {
|
|
117
|
+
appendFileSync(file, JSON.stringify(entry) + "\n", "utf8");
|
|
118
|
+
} catch {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// belt-and-suspenders: telemetry must never throw
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function main() {
|
|
127
|
+
// Per-operator opt-out. When CLAUDE_THREE_SECTION_CLOSE_DISABLED=1, the hook
|
|
128
|
+
// short-circuits entirely: no enforcement, no telemetry. Public default is
|
|
129
|
+
// unchanged (rule still fires for everyone else). See feedback memory
|
|
130
|
+
// 2026-05-04 for rationale: an audit found 22,876 observations produced
|
|
131
|
+
// only 2 graduated instincts, so visible end-of-turn reflection earns its
|
|
132
|
+
// keep poorly enough that operators may want to run it as internal thinking.
|
|
133
|
+
if (process.env.CLAUDE_THREE_SECTION_CLOSE_DISABLED === "1") return;
|
|
134
|
+
const startedAt = performance.now();
|
|
135
|
+
const stdin = readStdinSync();
|
|
136
|
+
if (!stdin) return;
|
|
137
|
+
|
|
138
|
+
const payload = safeJsonParse(stdin);
|
|
139
|
+
if (!payload || typeof payload.transcript_path !== "string") return;
|
|
140
|
+
if (!existsSync(payload.transcript_path)) return;
|
|
141
|
+
|
|
142
|
+
let lastText;
|
|
143
|
+
try {
|
|
144
|
+
lastText = extractLastAssistantText(payload.transcript_path);
|
|
145
|
+
} catch {
|
|
146
|
+
return; // fail open on read error
|
|
147
|
+
}
|
|
148
|
+
if (!lastText) return;
|
|
149
|
+
|
|
150
|
+
const textLength = lastText.length;
|
|
151
|
+
let action;
|
|
152
|
+
let missing = [];
|
|
153
|
+
|
|
154
|
+
if (textLength < MIN_LENGTH_TO_GATE) {
|
|
155
|
+
action = "skip-short";
|
|
156
|
+
} else {
|
|
157
|
+
missing = REQUIRED_SECTIONS.filter((s) => !s.pattern.test(lastText));
|
|
158
|
+
if (missing.length === 0) {
|
|
159
|
+
action = "pass";
|
|
160
|
+
} else {
|
|
161
|
+
action = "block";
|
|
162
|
+
emitBlock(missing);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const entry = {
|
|
167
|
+
ts: new Date().toISOString(),
|
|
168
|
+
hook: "three-section-close",
|
|
169
|
+
action,
|
|
170
|
+
textLength,
|
|
171
|
+
missing: missing.map((s) => s.heading),
|
|
172
|
+
durationMs: Math.round((performance.now() - startedAt) * 1000) / 1000,
|
|
173
|
+
};
|
|
174
|
+
recordTelemetry(entry, payload.transcript_path);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
main();
|
|
179
|
+
} catch {
|
|
180
|
+
// fail open — never block the session due to a hook bug
|
|
181
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "go-error-handling",
|
|
4
|
+
"trigger": "when calling functions that return errors",
|
|
5
|
+
"body": "Always check returned errors immediately. Never use _ to discard errors unless you've explicitly decided it's safe and documented why.",
|
|
6
|
+
"confidence": 0.7,
|
|
7
|
+
"domain": "patterns"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "go-defer-cleanup",
|
|
11
|
+
"trigger": "when opening files, connections, or acquiring locks",
|
|
12
|
+
"body": "Use defer immediately after acquiring a resource for cleanup. Place defer right after the error check for the acquisition.",
|
|
13
|
+
"confidence": 0.7,
|
|
14
|
+
"domain": "patterns"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "go-interface-consumer",
|
|
18
|
+
"trigger": "when defining Go interfaces",
|
|
19
|
+
"body": "Define interfaces at the consumer side, not the producer side. Keep interfaces small (1-3 methods). Accept interfaces, return structs.",
|
|
20
|
+
"confidence": 0.65,
|
|
21
|
+
"domain": "patterns"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "go-table-driven-tests",
|
|
25
|
+
"trigger": "when writing Go tests",
|
|
26
|
+
"body": "Use table-driven tests with subtests (t.Run) for functions with multiple input/output cases. Name test cases descriptively.",
|
|
27
|
+
"confidence": 0.7,
|
|
28
|
+
"domain": "testing"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "go-context-propagation",
|
|
32
|
+
"trigger": "when writing HTTP handlers or long-running operations",
|
|
33
|
+
"body": "Accept context.Context as the first parameter. Propagate it to all downstream calls. Use it for cancellation and timeouts.",
|
|
34
|
+
"confidence": 0.65,
|
|
35
|
+
"domain": "patterns"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "go-goroutine-lifecycle",
|
|
39
|
+
"trigger": "when spawning goroutines",
|
|
40
|
+
"body": "Always ensure goroutines have a way to exit (context cancellation, done channel, or WaitGroup). Never fire-and-forget goroutines without cleanup.",
|
|
41
|
+
"confidence": 0.7,
|
|
42
|
+
"domain": "patterns"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "go-struct-zero-values",
|
|
46
|
+
"trigger": "when designing Go structs",
|
|
47
|
+
"body": "Design structs so their zero value is useful. Use pointer fields only when nil is a meaningful distinct state from zero value.",
|
|
48
|
+
"confidence": 0.6,
|
|
49
|
+
"domain": "patterns"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "go-mod-tidy",
|
|
53
|
+
"trigger": "when adding or removing dependencies",
|
|
54
|
+
"body": "Run 'go mod tidy' after adding or removing imports to keep go.mod and go.sum clean.",
|
|
55
|
+
"confidence": 0.65,
|
|
56
|
+
"domain": "workflow"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "skip-thin-observation-schema",
|
|
4
|
+
"trigger": "when running /continuous-improvement and observations.jsonl entries contain only tool_start events with no inputs/outputs/errors",
|
|
5
|
+
"body": "Skip instinct fabrication and report the schema gap instead. With only ts/event/session/tool/project_id/project_name fields, the stream cannot distinguish a `git status` Bash call from a `rm -rf` Bash call. Tool-name bigrams (Bash→Bash, Edit→Edit, Read→Edit) reflect parallel-call artifacts and harness-enforced read-before-edit, not learned preferences. Recommend enhancing PostToolUse/PreToolUse hooks to log tool inputs (Bash command head, Skill name) and tool_end with success flag before retrying analysis.",
|
|
6
|
+
"confidence": 0.6,
|
|
7
|
+
"domain": "meta"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "parallelize-independent-tool-calls",
|
|
11
|
+
"trigger": "when issuing 2+ tool calls with no data dependency between them in the same turn",
|
|
12
|
+
"body": "Batch independent tool calls into a single message instead of issuing them sequentially. Independent reads, independent greps, independent git inspections, independent file edits in unrelated files belong in one message with multiple tool blocks. Dependent calls (output of A determines input of B) stay sequential. Exception: when a single check would short-circuit the whole task (e.g., `git status` returning empty), one call first is fine. Sequential issuing of independent calls wastes wall-clock time and burns context per round-trip.",
|
|
13
|
+
"confidence": 0.6,
|
|
14
|
+
"domain": "workflow"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "python-virtual-env",
|
|
4
|
+
"trigger": "when starting work on a Python project",
|
|
5
|
+
"body": "Check for virtual environment (venv, .venv, conda) before installing packages. Never install to system Python.",
|
|
6
|
+
"confidence": 0.7,
|
|
7
|
+
"domain": "workflow"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "python-type-hints",
|
|
11
|
+
"trigger": "when writing Python functions",
|
|
12
|
+
"body": "Add type hints to function parameters and return values. Use Optional[], list[], dict[] (Python 3.10+) or typing module for older versions.",
|
|
13
|
+
"confidence": 0.6,
|
|
14
|
+
"domain": "code-style"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "python-pathlib",
|
|
18
|
+
"trigger": "when working with file paths in Python",
|
|
19
|
+
"body": "Use pathlib.Path instead of os.path for file operations. It's more readable and cross-platform.",
|
|
20
|
+
"confidence": 0.65,
|
|
21
|
+
"domain": "patterns"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "python-context-managers",
|
|
25
|
+
"trigger": "when opening files or database connections",
|
|
26
|
+
"body": "Always use context managers (with statement) for files, database connections, and locks. Never rely on manual .close() calls.",
|
|
27
|
+
"confidence": 0.7,
|
|
28
|
+
"domain": "patterns"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "python-list-comprehension",
|
|
32
|
+
"trigger": "when writing simple for loops that build lists",
|
|
33
|
+
"body": "Prefer list comprehensions for simple transformations. Use regular for loops when the logic is complex or has side effects.",
|
|
34
|
+
"confidence": 0.6,
|
|
35
|
+
"domain": "code-style"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "python-requirements-check",
|
|
39
|
+
"trigger": "when adding a new import",
|
|
40
|
+
"body": "Check if the package is already in requirements.txt, pyproject.toml, or Pipfile before adding. Search for existing usage in the codebase.",
|
|
41
|
+
"confidence": 0.65,
|
|
42
|
+
"domain": "workflow"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "python-pytest-fixtures",
|
|
46
|
+
"trigger": "when writing Python tests",
|
|
47
|
+
"body": "Use pytest fixtures for test setup/teardown instead of unittest setUp/tearDown. Use conftest.py for shared fixtures.",
|
|
48
|
+
"confidence": 0.6,
|
|
49
|
+
"domain": "testing"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "python-dataclass",
|
|
53
|
+
"trigger": "when creating classes that primarily hold data",
|
|
54
|
+
"body": "Use @dataclass or Pydantic BaseModel instead of plain classes for data containers. They provide __init__, __repr__, and comparison for free.",
|
|
55
|
+
"confidence": 0.65,
|
|
56
|
+
"domain": "patterns"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "react-check-existing-components",
|
|
4
|
+
"trigger": "when creating a new React component",
|
|
5
|
+
"body": "Search the codebase for existing components that solve the same problem before creating new ones. Check shared/, components/, and ui/ directories.",
|
|
6
|
+
"confidence": 0.65,
|
|
7
|
+
"domain": "workflow"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "react-prefer-server-components",
|
|
11
|
+
"trigger": "when creating components in Next.js App Router",
|
|
12
|
+
"body": "Default to Server Components. Only add 'use client' when the component needs useState, useEffect, event handlers, or browser APIs.",
|
|
13
|
+
"confidence": 0.7,
|
|
14
|
+
"domain": "patterns"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "react-key-prop-lists",
|
|
18
|
+
"trigger": "when rendering lists with .map()",
|
|
19
|
+
"body": "Always use a stable, unique key prop. Never use array index as key unless the list is static and never reordered.",
|
|
20
|
+
"confidence": 0.7,
|
|
21
|
+
"domain": "patterns"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "react-effect-cleanup",
|
|
25
|
+
"trigger": "when writing useEffect with subscriptions or timers",
|
|
26
|
+
"body": "Always return a cleanup function from useEffect when setting up subscriptions, event listeners, or timers to prevent memory leaks.",
|
|
27
|
+
"confidence": 0.7,
|
|
28
|
+
"domain": "patterns"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "react-memo-expensive",
|
|
32
|
+
"trigger": "when a component re-renders with expensive calculations",
|
|
33
|
+
"body": "Use useMemo for expensive computations and React.memo for components that receive the same props frequently. Don't memo everything — only what's measurably slow.",
|
|
34
|
+
"confidence": 0.6,
|
|
35
|
+
"domain": "patterns"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "react-form-validation",
|
|
39
|
+
"trigger": "when building forms",
|
|
40
|
+
"body": "Check if react-hook-form or zod is already in the project before building custom form validation. Prefer library solutions over hand-rolled validation.",
|
|
41
|
+
"confidence": 0.65,
|
|
42
|
+
"domain": "tooling"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "react-error-boundary",
|
|
46
|
+
"trigger": "when adding a new page or route",
|
|
47
|
+
"body": "Ensure error boundaries exist around new pages/routes. In Next.js App Router, add error.tsx. In other React apps, wrap with ErrorBoundary component.",
|
|
48
|
+
"confidence": 0.6,
|
|
49
|
+
"domain": "patterns"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "react-test-user-behavior",
|
|
53
|
+
"trigger": "when writing React component tests",
|
|
54
|
+
"body": "Test user behavior, not implementation details. Use @testing-library/react. Query by role, label, or text — not by class name or test ID.",
|
|
55
|
+
"confidence": 0.65,
|
|
56
|
+
"domain": "testing"
|
|
57
|
+
}
|
|
58
|
+
]
|