continuous-improvement 1.1.0 → 2.1.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/hooks/observe.sh CHANGED
@@ -6,12 +6,7 @@
6
6
  # Always exit 0 — never block the Claude session
7
7
  trap 'exit 0' EXIT ERR INT TERM
8
8
 
9
- # Require jq — if unavailable, silently exit
10
- command -v jq &>/dev/null || exit 0
11
-
12
- MULAHAZAH_DIR="${HOME}/.claude/mulahazah"
13
- PROJECTS_DIR="${MULAHAZAH_DIR}/projects"
14
- GLOBAL_REGISTRY="${MULAHAZAH_DIR}/projects.json"
9
+ INSTINCTS_DIR="${HOME}/.claude/instincts"
15
10
 
16
11
  # ---------------------------------------------------------------------------
17
12
  # Read stdin (hook payload) — single read for performance
@@ -20,17 +15,29 @@ INPUT="$(cat)"
20
15
  [[ -z "$INPUT" ]] && exit 0
21
16
 
22
17
  # ---------------------------------------------------------------------------
23
- # Parse hook payload in one jq call
18
+ # Parse hook payload use jq if available, otherwise basic extraction
24
19
  # ---------------------------------------------------------------------------
25
- read -r TOOL_NAME SESSION_ID HAS_OUTPUT INPUT_JSON OUTPUT_JSON <<< "$(
26
- printf '%s' "$INPUT" | jq -r '
27
- (.tool_name // ""),
28
- (.session_id // ""),
29
- (if has("tool_output") then "yes" else "no" end),
30
- ((.tool_input // {} | tostring) | .[0:500]),
31
- ((.tool_output // {} | tostring) | .[0:200])
32
- ' | paste - - - - -
33
- )"
20
+ if command -v jq &>/dev/null; then
21
+ read -r TOOL_NAME SESSION_ID HAS_OUTPUT INPUT_JSON OUTPUT_JSON <<< "$(
22
+ printf '%s' "$INPUT" | jq -r '
23
+ (.tool_name // ""),
24
+ (.session_id // ""),
25
+ (if has("tool_output") then "yes" else "no" end),
26
+ ((.tool_input // {} | tostring) | .[0:500]),
27
+ ((.tool_output // {} | tostring) | .[0:200])
28
+ ' | paste - - - - -
29
+ )"
30
+ else
31
+ # Fallback: extract tool_name with basic pattern matching
32
+ TOOL_NAME="$(printf '%s' "$INPUT" | sed -n 's/.*"tool_name" *: *"\([^"]*\)".*/\1/p' | head -1)"
33
+ SESSION_ID="$(printf '%s' "$INPUT" | sed -n 's/.*"session_id" *: *"\([^"]*\)".*/\1/p' | head -1)"
34
+ HAS_OUTPUT="no"
35
+ printf '%s' "$INPUT" | grep -q '"tool_output"' && HAS_OUTPUT="yes"
36
+ INPUT_JSON="$(printf '%s' "$INPUT" | head -c 500)"
37
+ OUTPUT_JSON=""
38
+ fi
39
+
40
+ [[ -z "$TOOL_NAME" ]] && exit 0
34
41
 
35
42
  # Determine event type
36
43
  if [[ "$HAS_OUTPUT" == "yes" ]]; then
@@ -40,27 +47,24 @@ else
40
47
  fi
41
48
 
42
49
  # ---------------------------------------------------------------------------
43
- # Project detection (4 priority levels)
50
+ # Project detection
44
51
  # ---------------------------------------------------------------------------
45
52
  PROJECT_ROOT=""
46
53
 
47
- # Priority 1: $CLAUDE_PROJECT_DIR env var
48
54
  if [[ -n "${CLAUDE_PROJECT_DIR:-}" && -d "${CLAUDE_PROJECT_DIR}" ]]; then
49
55
  PROJECT_ROOT="${CLAUDE_PROJECT_DIR}"
50
56
  fi
51
57
 
52
- # Priority 2+3: git repo root (covers both remote-url and root-hash priorities)
53
58
  if [[ -z "$PROJECT_ROOT" ]]; then
54
59
  PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
55
60
  fi
56
61
 
57
- # Priority 4: global fallback
58
62
  if [[ -z "$PROJECT_ROOT" ]]; then
59
63
  PROJECT_ROOT="global"
60
64
  fi
61
65
 
62
66
  # ---------------------------------------------------------------------------
63
- # Compute project hash and name (SHA-256 first 12 chars)
67
+ # Compute project hash and name
64
68
  # ---------------------------------------------------------------------------
65
69
  PROJECT_HASH="$(printf '%s' "$PROJECT_ROOT" | sha256sum | cut -c1-12)"
66
70
  PROJECT_NAME="$(basename "${PROJECT_ROOT%.git}")"
@@ -68,20 +72,18 @@ PROJECT_NAME="$(basename "${PROJECT_ROOT%.git}")"
68
72
  # ---------------------------------------------------------------------------
69
73
  # Directory setup
70
74
  # ---------------------------------------------------------------------------
71
- PROJECT_OBS_DIR="${PROJECTS_DIR}/${PROJECT_HASH}"
72
- OBS_FILE="${PROJECT_OBS_DIR}/observations.jsonl"
75
+ PROJECT_DIR="${INSTINCTS_DIR}/${PROJECT_HASH}"
76
+ OBS_FILE="${PROJECT_DIR}/observations.jsonl"
73
77
 
74
- # Create dirs only if needed (fast no-op if already exists)
75
- [[ -d "$PROJECT_OBS_DIR" ]] || mkdir -p "${PROJECT_OBS_DIR}/observations.archive"
78
+ [[ -d "$PROJECT_DIR" ]] || mkdir -p "$PROJECT_DIR"
76
79
 
77
80
  # ---------------------------------------------------------------------------
78
- # Rotate observations.jsonl if it exceeds 10,000 lines
81
+ # Rotate observations.jsonl at 10,000 lines
79
82
  # ---------------------------------------------------------------------------
80
83
  if [[ -f "$OBS_FILE" ]]; then
81
84
  LINE_COUNT="$(wc -l < "$OBS_FILE")"
82
85
  if (( LINE_COUNT >= 10000 )); then
83
- ARCHIVE_DATE="$(date -u +"%Y-%m-%d")"
84
- mv "$OBS_FILE" "${PROJECT_OBS_DIR}/observations.archive/${ARCHIVE_DATE}.jsonl"
86
+ mv "$OBS_FILE" "${PROJECT_DIR}/observations.$(date -u +"%Y-%m-%d").jsonl"
85
87
  fi
86
88
  fi
87
89
 
@@ -90,44 +92,43 @@ fi
90
92
  # ---------------------------------------------------------------------------
91
93
  TS="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
92
94
 
93
- printf '%s\n' "$(jq -cn \
94
- --arg ts "$TS" \
95
- --arg event "$EVENT" \
96
- --arg session "$SESSION_ID" \
97
- --arg tool "$TOOL_NAME" \
98
- --arg input_summary "$INPUT_JSON" \
99
- --arg output_summary "$OUTPUT_JSON" \
100
- --arg project_id "$PROJECT_HASH" \
101
- --arg project_name "$PROJECT_NAME" \
102
- '{ts:$ts,event:$event,session:$session,tool:$tool,input_summary:$input_summary,output_summary:$output_summary,project_id:$project_id,project_name:$project_name}')" \
103
- >> "$OBS_FILE"
95
+ if command -v jq &>/dev/null; then
96
+ printf '%s\n' "$(jq -cn \
97
+ --arg ts "$TS" \
98
+ --arg event "$EVENT" \
99
+ --arg session "$SESSION_ID" \
100
+ --arg tool "$TOOL_NAME" \
101
+ --arg input_summary "$INPUT_JSON" \
102
+ --arg output_summary "$OUTPUT_JSON" \
103
+ --arg project_id "$PROJECT_HASH" \
104
+ --arg project_name "$PROJECT_NAME" \
105
+ '{ts:$ts,event:$event,session:$session,tool:$tool,input_summary:$input_summary,output_summary:$output_summary,project_id:$project_id,project_name:$project_name}')" \
106
+ >> "$OBS_FILE"
107
+ else
108
+ # Fallback: manual JSON construction
109
+ printf '{"ts":"%s","event":"%s","session":"%s","tool":"%s","project_id":"%s","project_name":"%s"}\n' \
110
+ "$TS" "$EVENT" "$SESSION_ID" "$TOOL_NAME" "$PROJECT_HASH" "$PROJECT_NAME" \
111
+ >> "$OBS_FILE"
112
+ fi
104
113
 
105
114
  # ---------------------------------------------------------------------------
106
- # Write project.json and update registry (only if new — deferred to avoid
107
- # adding latency to every invocation)
115
+ # Write project.json if new project
108
116
  # ---------------------------------------------------------------------------
109
- PROJECT_JSON="${PROJECT_OBS_DIR}/project.json"
117
+ PROJECT_JSON="${PROJECT_DIR}/project.json"
110
118
  if [[ ! -f "$PROJECT_JSON" ]]; then
111
- CREATED_AT="$TS"
112
- jq -n \
113
- --arg id "$PROJECT_HASH" \
114
- --arg name "$PROJECT_NAME" \
115
- --arg root "$PROJECT_ROOT" \
116
- --arg created_at "$CREATED_AT" \
117
- '{id:$id,name:$name,root:$root,created_at:$created_at}' \
118
- > "$PROJECT_JSON"
119
-
120
- # Update global projects.json registry
121
- mkdir -p "$MULAHAZAH_DIR"
122
- [[ -f "$GLOBAL_REGISTRY" ]] || printf '{}' > "$GLOBAL_REGISTRY"
123
-
124
- TMP_REGISTRY="$(mktemp)"
125
- jq --arg id "$PROJECT_HASH" \
126
- --arg name "$PROJECT_NAME" \
127
- --arg root "$PROJECT_ROOT" \
128
- --arg created_at "$CREATED_AT" \
129
- '.[$id] = {name:$name,root:$root,created_at:$created_at}' \
130
- "$GLOBAL_REGISTRY" > "$TMP_REGISTRY" && mv "$TMP_REGISTRY" "$GLOBAL_REGISTRY"
119
+ if command -v jq &>/dev/null; then
120
+ jq -n \
121
+ --arg id "$PROJECT_HASH" \
122
+ --arg name "$PROJECT_NAME" \
123
+ --arg root "$PROJECT_ROOT" \
124
+ --arg created_at "$TS" \
125
+ '{id:$id,name:$name,root:$root,created_at:$created_at}' \
126
+ > "$PROJECT_JSON"
127
+ else
128
+ printf '{"id":"%s","name":"%s","root":"%s","created_at":"%s"}\n' \
129
+ "$PROJECT_HASH" "$PROJECT_NAME" "$PROJECT_ROOT" "$TS" \
130
+ > "$PROJECT_JSON"
131
+ fi
131
132
  fi
132
133
 
133
134
  exit 0
package/package.json CHANGED
@@ -1,23 +1,40 @@
1
1
  {
2
2
  "name": "continuous-improvement",
3
- "version": "1.1.0",
4
- "description": "Install structured self-improvement loops with instinct-based learning into Claude Code — research, plan, execute, verify, reflect, learn, iterate.",
3
+ "version": "2.1.1",
4
+ "description": "7-law discipline framework with auto-leveling instinct learning for AI agents — research, plan, execute, verify, reflect, learn, iterate",
5
+ "keywords": [
6
+ "ai-agent",
7
+ "claude-code",
8
+ "codex",
9
+ "openclaw",
10
+ "cursor",
11
+ "skill",
12
+ "continuous-improvement",
13
+ "workflow",
14
+ "productivity",
15
+ "mulahazah",
16
+ "instinct",
17
+ "learning",
18
+ "hooks"
19
+ ],
20
+ "author": "naimkatiman",
5
21
  "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/naimkatiman/continuous-improvement"
25
+ },
26
+ "homepage": "https://github.com/naimkatiman/continuous-improvement#readme",
6
27
  "bin": {
7
- "continuous-improvement": "scripts/install.js"
28
+ "continuous-improvement": "./bin/install.mjs"
8
29
  },
9
30
  "files": [
10
- "scripts/",
11
- "prompts/",
12
- "skills/",
13
- "hooks/",
14
- "agents/",
15
- "config.json",
31
+ "SKILL.md",
32
+ "QUICKSTART.md",
33
+ "CHANGELOG.md",
16
34
  "README.md",
17
- "LICENSE",
18
- "docs/"
35
+ "bin/",
36
+ "hooks/",
37
+ "commands/"
19
38
  ],
20
- "engines": {
21
- "node": ">=18"
22
- }
39
+ "type": "module"
23
40
  }
@@ -1,282 +0,0 @@
1
- #!/usr/bin/env bash
2
- # observer-loop.sh — Mulahazah background observer loop
3
- # Periodically analyzes observation logs and generates instincts via Haiku.
4
- # Started by start-observer.sh. Should not be invoked directly.
5
-
6
- set -euo pipefail
7
-
8
- MULAHAZAH_DIR="${HOME}/.claude/mulahazah"
9
- CONFIG_FILE="${MULAHAZAH_DIR}/config.json"
10
- PROJECTS_DIR="${MULAHAZAH_DIR}/projects"
11
- INSTINCTS_DIR="${MULAHAZAH_DIR}/instincts"
12
- OBSERVER_PROMPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/observer.md"
13
- LOG_FILE="${MULAHAZAH_DIR}/observer.log"
14
-
15
- # ---------------------------------------------------------------------------
16
- # Logging
17
- # ---------------------------------------------------------------------------
18
- log() {
19
- local level="$1"; shift
20
- printf '[%s] [%s] %s\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" "$level" "$*" >> "$LOG_FILE"
21
- }
22
-
23
- # ---------------------------------------------------------------------------
24
- # Read config values (with defaults)
25
- # ---------------------------------------------------------------------------
26
- read_config() {
27
- local key="$1"
28
- local default="$2"
29
- if [[ -f "$CONFIG_FILE" ]]; then
30
- local val
31
- val="$(jq -r "${key} // empty" "$CONFIG_FILE" 2>/dev/null || true)"
32
- if [[ -n "$val" && "$val" != "null" ]]; then
33
- printf '%s' "$val"
34
- return
35
- fi
36
- fi
37
- printf '%s' "$default"
38
- }
39
-
40
- # ---------------------------------------------------------------------------
41
- # Signal handling
42
- # ---------------------------------------------------------------------------
43
- FORCE_RUN=false
44
- SHUTDOWN=false
45
-
46
- handle_sigterm() {
47
- log INFO "Received SIGTERM — shutting down gracefully"
48
- SHUTDOWN=true
49
- }
50
-
51
- handle_sigusr1() {
52
- log INFO "Received SIGUSR1 — forcing immediate analysis run"
53
- FORCE_RUN=true
54
- }
55
-
56
- trap handle_sigterm SIGTERM
57
- trap handle_sigusr1 SIGUSR1
58
-
59
- # ---------------------------------------------------------------------------
60
- # Analyze a single project directory
61
- # ---------------------------------------------------------------------------
62
- analyze_project() {
63
- local project_dir="$1"
64
- local obs_file="${project_dir}/observations.jsonl"
65
- local project_json="${project_dir}/project.json"
66
-
67
- [[ -f "$obs_file" ]] || return 0
68
-
69
- local obs_count
70
- obs_count="$(wc -l < "$obs_file" 2>/dev/null || echo 0)"
71
-
72
- local min_obs
73
- min_obs="$(read_config '.observer.min_observations_to_analyze' '20')"
74
-
75
- if (( obs_count < min_obs )); then
76
- log DEBUG "Skipping ${project_dir} — only ${obs_count} observations (min: ${min_obs})"
77
- return 0
78
- fi
79
-
80
- local project_id project_name
81
- project_id="$(basename "$project_dir")"
82
- project_name="$(jq -r '.name // "unknown"' "$project_json" 2>/dev/null || echo "unknown")"
83
-
84
- log INFO "Analyzing project '${project_name}' (${project_id}) — ${obs_count} observations"
85
-
86
- # Build the prompt payload for claude
87
- local prompt
88
- prompt="$(cat <<PROMPT
89
- You are the Mulahazah observer agent. Analyze the following observation data and existing instincts.
90
-
91
- ## Project
92
- ID: ${project_id}
93
- Name: ${project_name}
94
-
95
- ## Observations (last 500 lines of observations.jsonl)
96
- $(tail -500 "$obs_file" 2>/dev/null || true)
97
-
98
- ## Existing Instincts
99
- $(ls "${INSTINCTS_DIR}/${project_id}/"*.yaml 2>/dev/null | xargs -I{} cat {} 2>/dev/null || echo "(none)")
100
-
101
- ## Global Instincts
102
- $(ls "${INSTINCTS_DIR}/global/"*.yaml 2>/dev/null | xargs -I{} cat {} 2>/dev/null || echo "(none)")
103
-
104
- Follow the instructions in your system prompt. Output only YAML instinct blocks.
105
- PROMPT
106
- )"
107
-
108
- # Run claude with observer.md as the system prompt
109
- local output
110
- output="$(printf '%s' "$prompt" | \
111
- claude --model haiku --print --system-prompt "$OBSERVER_PROMPT" 2>>"$LOG_FILE" || true)"
112
-
113
- if [[ -z "$output" ]]; then
114
- log WARN "No output from observer for project '${project_name}'"
115
- return 0
116
- fi
117
-
118
- # Write instincts to disk
119
- write_instincts "$output" "$project_id"
120
- }
121
-
122
- # ---------------------------------------------------------------------------
123
- # Analyze global observations
124
- # ---------------------------------------------------------------------------
125
- analyze_global() {
126
- local global_dir="${PROJECTS_DIR}/global"
127
- local obs_file="${global_dir}/observations.jsonl"
128
-
129
- [[ -f "$obs_file" ]] || return 0
130
-
131
- local obs_count
132
- obs_count="$(wc -l < "$obs_file" 2>/dev/null || echo 0)"
133
-
134
- local min_obs
135
- min_obs="$(read_config '.observer.min_observations_to_analyze' '20')"
136
-
137
- if (( obs_count < min_obs )); then
138
- log DEBUG "Skipping global observations — only ${obs_count} lines (min: ${min_obs})"
139
- return 0
140
- fi
141
-
142
- log INFO "Analyzing global observations — ${obs_count} lines"
143
-
144
- local prompt
145
- prompt="$(cat <<PROMPT
146
- You are the Mulahazah observer agent. Analyze the following global observation data.
147
-
148
- ## Global Observations (last 500 lines)
149
- $(tail -500 "$obs_file" 2>/dev/null || true)
150
-
151
- ## Existing Global Instincts
152
- $(ls "${INSTINCTS_DIR}/global/"*.yaml 2>/dev/null | xargs -I{} cat {} 2>/dev/null || echo "(none)")
153
-
154
- Follow the instructions in your system prompt. Output only YAML instinct blocks with scope: global.
155
- PROMPT
156
- )"
157
-
158
- local output
159
- output="$(printf '%s' "$prompt" | \
160
- claude --model haiku --print --system-prompt "$OBSERVER_PROMPT" 2>>"$LOG_FILE" || true)"
161
-
162
- if [[ -z "$output" ]]; then
163
- log WARN "No output from observer for global observations"
164
- return 0
165
- fi
166
-
167
- write_instincts "$output" "global"
168
- }
169
-
170
- # ---------------------------------------------------------------------------
171
- # Parse and write instinct YAML blocks to disk
172
- # ---------------------------------------------------------------------------
173
- write_instincts() {
174
- local yaml_output="$1"
175
- local project_id="$2"
176
-
177
- # Split on --- separators and process each block
178
- local instinct_dir="${INSTINCTS_DIR}/${project_id}"
179
- mkdir -p "$instinct_dir"
180
-
181
- # Write raw output to a temp file, then split by ---
182
- local tmpfile
183
- tmpfile="$(mktemp)"
184
- printf '%s' "$yaml_output" > "$tmpfile"
185
-
186
- # Use awk to split YAML documents on '---' separator
187
- awk 'BEGIN{n=0; block=""} /^---$/{if(block!=""){print block > "/tmp/mulahazah_instinct_"n".yaml"; n++; block=""}} !/^---$/{block=block"\n"$0} END{if(block!=""){print block > "/tmp/mulahazah_instinct_"n".yaml"}}' "$tmpfile"
188
-
189
- local written=0
190
- for instinct_file in /tmp/mulahazah_instinct_*.yaml; do
191
- [[ -f "$instinct_file" ]] || continue
192
-
193
- # Extract the instinct id
194
- local instinct_id
195
- instinct_id="$(grep -m1 '^id:' "$instinct_file" | sed 's/^id: *//' | tr -d '"' | tr -d "'" | xargs 2>/dev/null || true)"
196
-
197
- if [[ -z "$instinct_id" ]]; then
198
- log WARN "Skipping instinct block with no id"
199
- rm -f "$instinct_file"
200
- continue
201
- fi
202
-
203
- local dest="${instinct_dir}/${instinct_id}.yaml"
204
- mv "$instinct_file" "$dest"
205
- log INFO "Wrote instinct '${instinct_id}' to ${dest}"
206
- (( written++ )) || true
207
- done
208
-
209
- # Clean up any leftover temp files
210
- rm -f /tmp/mulahazah_instinct_*.yaml "$tmpfile"
211
-
212
- log INFO "Wrote ${written} instincts for project '${project_id}'"
213
- }
214
-
215
- # ---------------------------------------------------------------------------
216
- # Main loop
217
- # ---------------------------------------------------------------------------
218
- main() {
219
- log INFO "Mulahazah observer loop started (PID $$)"
220
-
221
- # Validate dependencies
222
- command -v jq &>/dev/null || { log ERROR "jq not found — observer cannot run"; exit 1; }
223
- command -v claude &>/dev/null || { log ERROR "claude CLI not found — observer cannot run"; exit 1; }
224
- [[ -f "$OBSERVER_PROMPT" ]] || { log ERROR "observer.md not found at ${OBSERVER_PROMPT}"; exit 1; }
225
-
226
- # Ensure instincts directory exists
227
- mkdir -p "${INSTINCTS_DIR}/global"
228
-
229
- while true; do
230
- # Check if observer is enabled
231
- local enabled
232
- enabled="$(read_config '.observer.enabled' 'true')"
233
- if [[ "$enabled" != "true" ]]; then
234
- log INFO "Observer is disabled in config — sleeping"
235
- sleep 60
236
- [[ "$SHUTDOWN" == "true" ]] && break
237
- continue
238
- fi
239
-
240
- if [[ "$FORCE_RUN" == "true" || "$SHUTDOWN" == "false" ]]; then
241
- FORCE_RUN=false
242
- log INFO "Starting analysis run"
243
-
244
- # Analyze each project directory
245
- if [[ -d "$PROJECTS_DIR" ]]; then
246
- for project_dir in "${PROJECTS_DIR}"/*/; do
247
- [[ -d "$project_dir" ]] || continue
248
- [[ "$(basename "$project_dir")" == "global" ]] && continue
249
- analyze_project "$project_dir" || log WARN "Analysis failed for ${project_dir}"
250
- [[ "$SHUTDOWN" == "true" ]] && break
251
- done
252
- fi
253
-
254
- # Analyze global observations
255
- analyze_global || log WARN "Global analysis failed"
256
-
257
- log INFO "Analysis run complete"
258
- fi
259
-
260
- [[ "$SHUTDOWN" == "true" ]] && break
261
-
262
- # Sleep for the configured interval
263
- local interval_minutes
264
- interval_minutes="$(read_config '.observer.run_interval_minutes' '5')"
265
- local interval_seconds=$(( interval_minutes * 60 ))
266
-
267
- log DEBUG "Sleeping for ${interval_minutes} minutes"
268
-
269
- # Sleep in 1-second chunks to remain responsive to signals
270
- local elapsed=0
271
- while (( elapsed < interval_seconds )); do
272
- sleep 1
273
- (( elapsed++ )) || true
274
- [[ "$SHUTDOWN" == "true" ]] && break
275
- [[ "$FORCE_RUN" == "true" ]] && break
276
- done
277
- done
278
-
279
- log INFO "Mulahazah observer loop exiting"
280
- }
281
-
282
- main "$@"
@@ -1,145 +0,0 @@
1
- ---
2
- name: mulahazah-observer
3
- description: Background analysis agent that reads raw tool observations and distills them into actionable instincts. Runs on a Haiku model for cost efficiency. Identifies patterns, user preferences, and recurring workflows from JSONL observation logs.
4
- model: haiku
5
- ---
6
-
7
- # Mulahazah Observer — Background Analysis Agent
8
-
9
- You are the Mulahazah observer. Your job is to analyze raw tool-use observations recorded by the `observe.sh` hook and extract reusable instincts that improve future Claude Code sessions.
10
-
11
- You run in the background, periodically. You are cost-sensitive (Haiku model). Be concise and conservative.
12
-
13
- ---
14
-
15
- ## Inputs
16
-
17
- You will be given:
18
- 1. The path to `observations.jsonl` for a project (or global observations)
19
- 2. The path to existing instincts (YAML files in `~/.claude/mulahazah/instincts/`)
20
- 3. The project metadata from `project.json` (if available)
21
-
22
- Read these files and analyze the patterns within.
23
-
24
- ---
25
-
26
- ## Pattern Detection Rules
27
-
28
- Scan observations for these signal types, in priority order:
29
-
30
- ### 1. User Corrections (highest signal)
31
- - A tool call was made, then immediately followed by an Edit or Write that undoes or modifies what was just produced
32
- - The same tool is called with a different argument within the same session after an error
33
- - A `Bash` command fails (non-zero exit in output) and is retried with a modified form
34
-
35
- ### 2. Error Resolutions
36
- - A tool produces an error, followed by a sequence of tools that resolves it
37
- - The resolution sequence is compact (3–7 tool calls) and clearly purposeful
38
- - Extract the resolution pattern as a workflow instinct
39
-
40
- ### 3. Repeated Workflows
41
- - The same sequence of 3+ tool calls appears in 3+ sessions
42
- - Order matters — a repeated sequence is only a pattern if the tools appear in the same relative order
43
- - Common examples: `Bash(git status)` → `Bash(git diff)` → `Bash(git commit)`, or `Read` → `Edit` → `Bash(npm run build)`
44
-
45
- ### 4. Tool Preferences
46
- - User consistently uses one tool over a functionally equivalent alternative
47
- - Example: always uses `Bash(rg ...)` via the Grep tool rather than raw `Bash(grep ...)`
48
- - Example: always uses `Edit` for single-file changes, never `Write` on existing files
49
- - Capture these as style or workflow instincts
50
-
51
- ### 5. Rejected Suggestions
52
- - A tool call produces output, session ends shortly after without using the output
53
- - Or an Edit is immediately reverted in the next tool call
54
- - These indicate something to avoid — create a negative instinct (what not to do)
55
-
56
- ---
57
-
58
- ## Scope Decision Guide
59
-
60
- Assign scope based on these rules:
61
-
62
- | Condition | Scope |
63
- |-----------|-------|
64
- | Pattern appears in only one project's observations | `project` |
65
- | Pattern appears in 3+ different projects | `global` |
66
- | Pattern involves language/framework-specific behavior | `project` (unless 3+ projects use same stack) |
67
- | Pattern involves user meta-habits (git, file editing, tool choice) | `global` |
68
- | Pattern involves project naming, directory structure, specific paths | `project` |
69
- | Uncertain | default to `project` |
70
-
71
- ---
72
-
73
- ## Instinct YAML Format
74
-
75
- Output each new instinct as a YAML block. Do not wrap in markdown code fences — output raw YAML only, one instinct per file.
76
-
77
- ```yaml
78
- id: <kebab-case-id>
79
- title: <short human-readable title, max 60 chars>
80
- scope: project | global
81
- project_id: <12-char hash if scope=project, omit if global>
82
- domain: <one of: code-style, testing, git, debugging, workflow, security, architecture>
83
- confidence: <float 0.0–0.85>
84
- observation_count: <number of observations supporting this instinct>
85
- last_seen: <ISO 8601 date>
86
- content: |
87
- <The instinct text. Written as a direct instruction to Claude.
88
- Max 5 sentences. No raw code snippets. No file paths unless abstract.
89
- Use imperative voice. Example: "When editing TypeScript files, always
90
- check for existing type aliases before creating new ones.">
91
- tags:
92
- - <tag1>
93
- - <tag2>
94
- ```
95
-
96
- ---
97
-
98
- ## Confidence Rules
99
-
100
- - **Never set confidence above 0.85** from observation data alone. Human review is required to reach 0.9.
101
- - **Confidence cap is 0.9** — no instinct may ever exceed this value.
102
- - Start new instincts at confidence 0.4–0.6 based on evidence strength:
103
- - 3–5 supporting observations: 0.4
104
- - 6–10 supporting observations: 0.55
105
- - 11–20 supporting observations: 0.65
106
- - 21+ supporting observations: 0.75
107
- - Strong signal (user correction or error resolution): add 0.1 bonus, capped at 0.85
108
- - **Decay rules** — reduce confidence by 0.05 if:
109
- - The instinct was not observed in the last 30 days
110
- - The instinct was observed but then contradicted (a counter-example appeared)
111
- - The session count for the project drops to zero for 60+ days
112
-
113
- ---
114
-
115
- ## Domain Tags
116
-
117
- Use exactly one domain per instinct:
118
-
119
- | Domain | Covers |
120
- |--------|--------|
121
- | `code-style` | Formatting, naming, language idioms, linting preferences |
122
- | `testing` | Test frameworks, coverage, test file conventions |
123
- | `git` | Commit messages, branch naming, staging habits |
124
- | `debugging` | Error resolution sequences, diagnostic tool preferences |
125
- | `workflow` | Multi-step task sequences, tool ordering preferences |
126
- | `security` | Auth patterns, secret handling, input validation |
127
- | `architecture` | File structure, module boundaries, design patterns |
128
-
129
- ---
130
-
131
- ## Observer Rules
132
-
133
- 1. **Be conservative.** It is better to produce no instinct than a wrong instinct. Only emit an instinct if you have clear, repeated evidence.
134
-
135
- 2. **Merge similar instincts.** If a new pattern is substantially similar to an existing instinct (same domain, same behavior), increase `observation_count` and update `confidence` on the existing instinct rather than creating a duplicate.
136
-
137
- 3. **Default to project scope.** When in doubt about scope, use `project`. Promotion to global scope should only happen when the same pattern is confirmed across multiple distinct projects.
138
-
139
- 4. **No raw code in instinct content.** Instinct content must be natural language instructions. Never embed shell commands, code snippets, or file paths in the `content` field. Use abstract descriptions instead.
140
-
141
- 5. **No hallucinated patterns.** Only describe patterns you can trace to specific observation lines. If you cannot point to concrete evidence, do not emit the instinct.
142
-
143
- 6. **Respect existing instincts.** Before creating a new instinct, check the existing instinct files. Do not duplicate, do not contradict without strong evidence.
144
-
145
- 7. **Output only YAML.** Your output must be valid YAML instinct blocks (one per instinct) separated by `---`. Do not include explanatory text, markdown, or commentary in your output — only the YAML instincts ready to be written to disk.