specweave 1.0.96 → 1.0.97

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specweave",
3
- "version": "1.0.96",
3
+ "version": "1.0.97",
4
4
  "description": "Spec-driven development framework for Claude Code. AI-native workflow with living documentation, intelligent agents, and multilingual support (9 languages). Enterprise-grade traceability with permanent specs and temporary increments.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,24 +1,29 @@
1
1
  #!/bin/bash
2
- # reflect.sh - Self-Improving Skills Reflection System (v2.0 - Simplified)
2
+ # reflect.sh - Self-Improving Skills Reflection System (v3.0)
3
3
  #
4
- # Analyzes session transcripts and extracts learnings to memory files.
5
- # Designed to be lightweight, robust, and context-window friendly.
4
+ # PHILOSOPHY: Only store HIGH-VALUE learnings that contain actionable rules.
5
+ # Most sessions produce NO learnings - that's correct behavior.
6
6
  #
7
- # KEY DESIGN DECISIONS:
8
- # - Max 20 learnings per category (auto-prunes oldest)
9
- # - Max 5KB per memory file (prevents context bloat)
10
- # - Log rotation: keeps last 100 entries
11
- # - Compact learning format (no verbose metadata)
7
+ # What we capture:
8
+ # - Direct corrections with both WRONG and RIGHT: "No, don't X. Do Y instead."
9
+ # - Explicit rules: "Always use X" / "Never use Y" / "In this project, use X"
10
+ # - Project-specific patterns that differ from defaults
12
11
  #
13
- # Usage:
14
- # reflect.sh [on|off|status|reflect] [options]
12
+ # What we SKIP:
13
+ # - Generic praise ("Perfect!", "Great!")
14
+ # - Vague approval ("That's right") - no actionable info
15
+ # - Things already in CLAUDE.md
15
16
  #
16
- # Exit codes:
17
- # 0 - Success
18
- # 1 - No learnings found
19
- # 2 - Configuration error
17
+ # FORMAT: Test-like rules with optional example
18
+ # RULE: When [context], [always|never] [action]
19
+ # EXAMPLE: [one concrete example if helpful]
20
+ #
21
+ # LIMITS:
22
+ # - 30 rules max per category (quality over quantity)
23
+ # - ~100 chars per rule (compact)
24
+ # - Deduplication by keyword similarity
20
25
 
21
- set +e # Don't exit on error (hook safety)
26
+ set +e
22
27
 
23
28
  # ============================================================================
24
29
  # CONFIGURATION
@@ -30,187 +35,218 @@ REFLECT_CONFIG="$STATE_DIR/reflect-config.json"
30
35
  LOGS_DIR="$PROJECT_ROOT/.specweave/logs/reflect"
31
36
  MEMORY_DIR="$PROJECT_ROOT/.specweave/memory"
32
37
 
33
- # Limits to prevent bloat
34
- MAX_LEARNINGS_PER_CATEGORY=20
35
- MAX_LOG_LINES=100
36
- MAX_MEMORY_FILE_KB=5
37
-
38
- # Defaults
39
- DEFAULT_CONFIDENCE="medium"
40
- DEFAULT_MAX_LEARNINGS=10
38
+ # Conservative limits - quality over quantity
39
+ MAX_RULES_PER_CATEGORY=30
40
+ MAX_LOG_LINES=50
41
+ DEFAULT_CONFIDENCE="high" # Only high-confidence by default
42
+ DEFAULT_MAX_LEARNINGS=5 # Max 5 per session (most sessions = 0)
41
43
 
42
44
  # ============================================================================
43
- # UTILITY FUNCTIONS
45
+ # LOGGING (with rotation)
44
46
  # ============================================================================
45
47
 
46
48
  log() {
47
- local level="$1"
48
- shift
49
- local timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
49
+ local level="$1"; shift
50
50
  mkdir -p "$LOGS_DIR"
51
-
52
- # Log rotation - keep only last MAX_LOG_LINES
53
51
  local log_file="$LOGS_DIR/reflect.log"
52
+
53
+ # Rotate if needed
54
54
  if [ -f "$log_file" ]; then
55
- local line_count=$(wc -l < "$log_file" 2>/dev/null || echo "0")
56
- if [ "$line_count" -gt "$MAX_LOG_LINES" ]; then
57
- tail -n $((MAX_LOG_LINES / 2)) "$log_file" > "$log_file.tmp" && mv "$log_file.tmp" "$log_file"
58
- fi
55
+ local lines=$(wc -l < "$log_file" 2>/dev/null || echo 0)
56
+ [ "$lines" -gt "$MAX_LOG_LINES" ] && tail -n 25 "$log_file" > "$log_file.tmp" && mv "$log_file.tmp" "$log_file"
59
57
  fi
60
58
 
61
- echo "{\"ts\":\"$timestamp\",\"lvl\":\"$level\",\"msg\":\"$*\"}" >> "$log_file"
59
+ echo "[$(date +%H:%M:%S)] $level: $*" >> "$log_file"
62
60
  }
63
61
 
64
- ensure_dirs() {
65
- mkdir -p "$LOGS_DIR" "$MEMORY_DIR" "$STATE_DIR"
62
+ ensure_dirs() { mkdir -p "$LOGS_DIR" "$MEMORY_DIR" "$STATE_DIR"; }
63
+
64
+ get_config() {
65
+ local key="$1" default="$2"
66
+ [ -f "$REFLECT_CONFIG" ] && jq -r ".$key // \"$default\"" "$REFLECT_CONFIG" 2>/dev/null || echo "$default"
66
67
  }
67
68
 
68
- get_config_value() {
69
- local key="$1"
70
- local default="$2"
69
+ # ============================================================================
70
+ # HIGH-VALUE SIGNAL DETECTION
71
+ # ============================================================================
71
72
 
72
- if [ -f "$REFLECT_CONFIG" ]; then
73
- local value=$(jq -r ".$key // \"$default\"" "$REFLECT_CONFIG" 2>/dev/null)
74
- echo "${value:-$default}"
75
- else
76
- echo "$default"
77
- fi
78
- }
73
+ # Patterns that indicate a CORRECTION with actionable info
74
+ # Must have both "don't do X" AND "do Y instead" to be valuable
75
+ CORRECTION_PATTERNS=(
76
+ "No,? don.t.*instead"
77
+ "No,? use.*not"
78
+ "Wrong.*should be"
79
+ "Never.*always"
80
+ "Don.t.*use.*instead"
81
+ "That.s incorrect.*correct"
82
+ "Actually,?.*should"
83
+ "Stop using.*use"
84
+ )
85
+
86
+ # Patterns for explicit rules (no correction needed, just a rule)
87
+ RULE_PATTERNS=(
88
+ "Always use"
89
+ "Never use"
90
+ "In this (project|codebase|repo)"
91
+ "The convention (here|is)"
92
+ "We always"
93
+ "We never"
94
+ "The rule is"
95
+ "Remember to always"
96
+ )
97
+
98
+ # Patterns to SKIP (generic praise with no actionable info)
99
+ SKIP_PATTERNS=(
100
+ "^Perfect!?$"
101
+ "^Great!?$"
102
+ "^Exactly!?$"
103
+ "^That.s right\.?$"
104
+ "^Well done\.?$"
105
+ "^Good job\.?$"
106
+ "^Correct\.?$"
107
+ )
108
+
109
+ # Check if context contains actionable info (not just praise)
110
+ is_actionable() {
111
+ local text="$1"
112
+
113
+ # Skip if it's just generic praise
114
+ for pattern in "${SKIP_PATTERNS[@]}"; do
115
+ if echo "$text" | grep -qiE "$pattern"; then
116
+ return 1
117
+ fi
118
+ done
79
119
 
80
- is_reflect_enabled() {
81
- local enabled=$(get_config_value "enabled" "true")
82
- [ "$enabled" = "true" ]
83
- }
120
+ # Must be longer than 20 chars to have real content
121
+ [ ${#text} -lt 20 ] && return 1
122
+
123
+ # Must contain some actionable verb
124
+ echo "$text" | grep -qiE "(use|don.t|never|always|should|must|avoid|prefer)" || return 1
84
125
 
85
- is_auto_reflect_enabled() {
86
- local auto=$(get_config_value "autoReflect" "false")
87
- [ "$auto" = "true" ]
126
+ return 0
88
127
  }
89
128
 
90
- # ============================================================================
91
- # SIGNAL DETECTION (Simplified)
92
- # ============================================================================
129
+ # Extract the actual rule from a correction context
130
+ extract_rule() {
131
+ local context="$1"
93
132
 
94
- # Correction patterns (user says "no", "wrong", etc.)
95
- CORRECTION_REGEX="(No,? don.t|No,? use|Wrong|That.s incorrect|Actually,? you should|Never use|Always use|Stop using|The correct way)"
133
+ # Try to find the "do Y instead" part
134
+ local rule=""
96
135
 
97
- # Approval patterns (user says "perfect", "correct", etc.)
98
- APPROVAL_REGEX="(Perfect!|That.s right|That.s correct|Exactly!|Well done|Great job|Good pattern|Keep doing this)"
136
+ # Pattern: "No, don't X. Use Y instead" -> "Use Y"
137
+ rule=$(echo "$context" | grep -oiE "(use|prefer|always)[^.!?]*" | head -1)
138
+ [ -n "$rule" ] && echo "$rule" && return
99
139
 
100
- # Detect signals and extract context
101
- detect_signals() {
102
- local transcript="$1"
140
+ # Pattern: "Should be X" -> "Should be X"
141
+ rule=$(echo "$context" | grep -oiE "should (be|use)[^.!?]*" | head -1)
142
+ [ -n "$rule" ] && echo "$rule" && return
103
143
 
104
- if [ ! -f "$transcript" ]; then
105
- log "warn" "Transcript not found: $transcript"
106
- return 1
107
- fi
144
+ # Pattern: "Never X, always Y" -> "Always Y"
145
+ rule=$(echo "$context" | grep -oiE "always[^.!?]*" | head -1)
146
+ [ -n "$rule" ] && echo "$rule" && return
108
147
 
109
- local signals_file="$STATE_DIR/reflect-signals.txt"
110
- > "$signals_file" # Clear file
111
-
112
- # Find corrections with context (using process substitution to avoid subshell)
113
- local matches
114
- matches=$(grep -inE "$CORRECTION_REGEX" "$transcript" 2>/dev/null | head -5) || true
115
- if [ -n "$matches" ]; then
116
- echo "$matches" | while read -r match; do
117
- local linenum=$(echo "$match" | cut -d: -f1)
118
- local start=$((linenum > 3 ? linenum - 2 : 1))
119
- local end=$((linenum + 2))
120
- local context=$(sed -n "${start},${end}p" "$transcript" 2>/dev/null | tr '\n' ' ' | cut -c1-300)
121
- echo "CORRECTION|$context"
122
- done >> "$signals_file"
123
- fi
148
+ # Fallback: return cleaned context
149
+ echo "$context" | cut -c1-100
150
+ }
124
151
 
125
- # Find approvals with context
126
- matches=$(grep -inE "$APPROVAL_REGEX" "$transcript" 2>/dev/null | head -5) || true
127
- if [ -n "$matches" ]; then
128
- echo "$matches" | while read -r match; do
129
- local linenum=$(echo "$match" | cut -d: -f1)
130
- local start=$((linenum > 3 ? linenum - 2 : 1))
131
- local end=$((linenum + 2))
132
- local context=$(sed -n "${start},${end}p" "$transcript" 2>/dev/null | tr '\n' ' ' | cut -c1-300)
133
- echo "APPROVAL|$context"
134
- done >> "$signals_file"
135
- fi
152
+ # Detect high-value signals in transcript
153
+ detect_signals() {
154
+ local transcript="$1"
155
+ local signals_file="$STATE_DIR/reflect-signals.txt"
136
156
 
137
- local corrections
138
- local approvals
139
- corrections=$(grep -c "^CORRECTION|" "$signals_file" 2>/dev/null) || corrections=0
140
- approvals=$(grep -c "^APPROVAL|" "$signals_file" 2>/dev/null) || approvals=0
157
+ [ ! -f "$transcript" ] && return 1
158
+ > "$signals_file"
159
+
160
+ # Look for corrections (high value)
161
+ for pattern in "${CORRECTION_PATTERNS[@]}"; do
162
+ local matches=$(grep -inE "$pattern" "$transcript" 2>/dev/null | head -3) || true
163
+ if [ -n "$matches" ]; then
164
+ echo "$matches" | while read -r match; do
165
+ local linenum=$(echo "$match" | cut -d: -f1)
166
+ local start=$((linenum > 2 ? linenum - 1 : 1))
167
+ local end=$((linenum + 1))
168
+ local context=$(sed -n "${start},${end}p" "$transcript" 2>/dev/null | tr '\n' ' ' | sed 's/ */ /g')
169
+
170
+ # Only add if actionable
171
+ if is_actionable "$context"; then
172
+ local rule=$(extract_rule "$context")
173
+ echo "CORRECTION|$rule" >> "$signals_file"
174
+ fi
175
+ done
176
+ fi
177
+ done
178
+
179
+ # Look for explicit rules (medium value)
180
+ for pattern in "${RULE_PATTERNS[@]}"; do
181
+ local matches=$(grep -inE "$pattern" "$transcript" 2>/dev/null | head -2) || true
182
+ if [ -n "$matches" ]; then
183
+ echo "$matches" | while read -r match; do
184
+ local linenum=$(echo "$match" | cut -d: -f1)
185
+ local context=$(sed -n "${linenum}p" "$transcript" 2>/dev/null | tr '\n' ' ')
186
+
187
+ if is_actionable "$context"; then
188
+ local rule=$(extract_rule "$context")
189
+ echo "RULE|$rule" >> "$signals_file"
190
+ fi
191
+ done
192
+ fi
193
+ done
141
194
 
142
- log "info" "Detected $corrections corrections, $approvals approvals"
195
+ local count=$(wc -l < "$signals_file" 2>/dev/null || echo 0)
196
+ count=$(echo "$count" | tr -d ' ')
143
197
 
144
- local total=$((corrections + approvals))
145
- if [ "$total" -eq 0 ]; then
146
- return 1
147
- fi
198
+ log "info" "Found $count actionable signals"
199
+ [ "$count" -eq 0 ] && return 1
148
200
 
149
201
  echo "$signals_file"
150
202
  }
151
203
 
152
204
  # ============================================================================
153
- # LEARNING EXTRACTION
205
+ # DEDUPLICATION
154
206
  # ============================================================================
155
207
 
156
- # Generate compact learning ID
157
- generate_learning_id() {
158
- echo "L$(date +%m%d)-$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' ')"
159
- }
160
-
161
- # Categorize based on keywords
162
- categorize_learning() {
163
- local context="$1"
164
- local lower=$(echo "$context" | tr '[:upper:]' '[:lower:]')
165
-
166
- case "$lower" in
167
- *button*|*component*|*ui*|*style*|*css*|*react*|*vue*) echo "frontend" ;;
168
- *api*|*endpoint*|*route*|*rest*|*graphql*|*backend*) echo "backend" ;;
169
- *test*|*spec*|*mock*|*playwright*|*vitest*|*jest*) echo "testing" ;;
170
- *deploy*|*docker*|*k8s*|*ci*|*cd*|*terraform*) echo "devops" ;;
171
- *auth*|*security*|*token*|*password*|*encrypt*) echo "security" ;;
172
- *query*|*database*|*sql*|*schema*|*postgres*) echo "database" ;;
173
- *) echo "general" ;;
174
- esac
175
- }
176
-
177
- # Prune memory file to max learnings
178
- prune_memory_file() {
208
+ # Check if a similar rule already exists (by key words)
209
+ rule_exists() {
179
210
  local file="$1"
211
+ local new_rule="$2"
180
212
 
181
- if [ ! -f "$file" ]; then
182
- return
183
- fi
213
+ [ ! -f "$file" ] && return 1
214
+
215
+ # Extract key words from new rule (nouns, verbs)
216
+ local keywords=$(echo "$new_rule" | tr '[:upper:]' '[:lower:]' | grep -oE '\b[a-z]{4,}\b' | sort -u | head -5 | tr '\n' ' ')
184
217
 
185
- # Count learnings (lines starting with "- ")
186
- local count=$(grep -c "^- L[0-9]" "$file" 2>/dev/null || echo "0")
218
+ # Check if 3+ keywords match any existing rule
219
+ local match_count=0
220
+ for kw in $keywords; do
221
+ if grep -qi "\b$kw\b" "$file" 2>/dev/null; then
222
+ match_count=$((match_count + 1))
223
+ fi
224
+ done
187
225
 
188
- if [ "$count" -gt "$MAX_LEARNINGS_PER_CATEGORY" ]; then
189
- log "info" "Pruning $file: $count > $MAX_LEARNINGS_PER_CATEGORY"
226
+ [ "$match_count" -ge 3 ] && return 0
227
+ return 1
228
+ }
190
229
 
191
- # Keep header and last N learnings
192
- local header=$(head -5 "$file")
193
- local learnings=$(grep "^- L[0-9]" "$file" | tail -n "$MAX_LEARNINGS_PER_CATEGORY")
230
+ # ============================================================================
231
+ # MEMORY MANAGEMENT
232
+ # ============================================================================
194
233
 
195
- echo "$header" > "$file"
196
- echo "" >> "$file"
197
- echo "$learnings" >> "$file"
198
- fi
234
+ categorize() {
235
+ local text="$1"
236
+ local lower=$(echo "$text" | tr '[:upper:]' '[:lower:]')
199
237
 
200
- # Also check file size
201
- local size_kb=$(du -k "$file" 2>/dev/null | cut -f1)
202
- if [ "${size_kb:-0}" -gt "$MAX_MEMORY_FILE_KB" ]; then
203
- log "warn" "Memory file too large: ${size_kb}KB > ${MAX_MEMORY_FILE_KB}KB, pruning"
204
- # Keep only last half of learnings
205
- local header=$(head -5 "$file")
206
- local learnings=$(grep "^- L[0-9]" "$file" | tail -n $((MAX_LEARNINGS_PER_CATEGORY / 2)))
207
- echo "$header" > "$file"
208
- echo "" >> "$file"
209
- echo "$learnings" >> "$file"
210
- fi
238
+ case "$lower" in
239
+ *component*|*button*|*ui*|*style*|*css*|*react*|*vue*|*html*) echo "frontend" ;;
240
+ *api*|*endpoint*|*route*|*rest*|*graphql*|*server*) echo "backend" ;;
241
+ *test*|*spec*|*mock*|*assert*|*expect*) echo "testing" ;;
242
+ *deploy*|*docker*|*k8s*|*ci*|*terraform*|*aws*) echo "devops" ;;
243
+ *auth*|*security*|*token*|*password*|*secret*) echo "security" ;;
244
+ *query*|*database*|*sql*|*schema*|*table*) echo "database" ;;
245
+ *file*|*path*|*import*|*export*|*module*) echo "structure" ;;
246
+ *) echo "general" ;;
247
+ esac
211
248
  }
212
249
 
213
- # Ensure memory file exists with header
214
250
  ensure_memory_file() {
215
251
  local category="$1"
216
252
  local file="$MEMORY_DIR/${category}.md"
@@ -218,227 +254,181 @@ ensure_memory_file() {
218
254
  mkdir -p "$MEMORY_DIR"
219
255
 
220
256
  if [ ! -f "$file" ]; then
221
- # POSIX-compatible capitalize first letter
222
- local cap_category=$(echo "$category" | awk '{print toupper(substr($0,1,1)) substr($0,2)}')
257
+ local cap=$(echo "$category" | awk '{print toupper(substr($0,1,1)) substr($0,2)}')
223
258
  cat > "$file" << EOF
224
- # $cap_category Memory
225
- > SpecWeave learnings - auto-pruned to $MAX_LEARNINGS_PER_CATEGORY entries
226
- > Updated: $(date +%Y-%m-%d)
259
+ # $cap Rules
260
+ > Project-specific patterns learned from corrections.
261
+ > Max $MAX_RULES_PER_CATEGORY rules, auto-deduplicated.
227
262
 
228
- ## Learnings
229
263
  EOF
230
264
  fi
231
265
 
232
266
  echo "$file"
233
267
  }
234
268
 
235
- # Add learning (compact format)
236
- add_learning() {
269
+ add_rule() {
237
270
  local category="$1"
238
- local confidence="$2"
239
- local context="$3"
271
+ local type="$2" # CORRECTION or RULE
272
+ local rule="$3"
240
273
 
241
274
  local file=$(ensure_memory_file "$category")
242
- local id=$(generate_learning_id)
243
275
 
244
- # Compact format: - ID (conf): context
245
- local conf_short=$(echo "$confidence" | cut -c1 | tr '[:lower:]' '[:upper:]')
246
- local clean_context=$(echo "$context" | tr -d '\n\r' | sed 's/ */ /g' | cut -c1-200)
276
+ # Skip if similar rule exists
277
+ if rule_exists "$file" "$rule"; then
278
+ log "info" "Skipped duplicate: $rule"
279
+ return 1
280
+ fi
247
281
 
248
- echo "- $id ($conf_short): $clean_context" >> "$file"
282
+ # Clean and format
283
+ local clean=$(echo "$rule" | tr -d '\n\r' | sed 's/ */ /g' | cut -c1-100)
284
+ local marker="→"
285
+ [ "$type" = "CORRECTION" ] && marker="✗→✓"
249
286
 
250
- # Update timestamp in header
251
- sed -i.bak "s/^> Updated:.*/> Updated: $(date +%Y-%m-%d)/" "$file" 2>/dev/null
252
- rm -f "${file}.bak"
287
+ echo "- $marker $clean" >> "$file"
253
288
 
254
- # Prune if needed
255
- prune_memory_file "$file"
289
+ # Prune if over limit (keep newest, they're at bottom)
290
+ local count=$(grep -c "^- " "$file" 2>/dev/null || echo 0)
291
+ if [ "$count" -gt "$MAX_RULES_PER_CATEGORY" ]; then
292
+ local header=$(head -4 "$file")
293
+ local rules=$(grep "^- " "$file" | tail -n "$MAX_RULES_PER_CATEGORY")
294
+ echo "$header" > "$file"
295
+ echo "$rules" >> "$file"
296
+ log "info" "Pruned $category to $MAX_RULES_PER_CATEGORY rules"
297
+ fi
256
298
 
257
- log "info" "Added $id to $category"
299
+ log "info" "Added to $category: $clean"
300
+ return 0
258
301
  }
259
302
 
260
303
  # ============================================================================
261
- # MAIN REFLECTION LOGIC
304
+ # MAIN REFLECTION
262
305
  # ============================================================================
263
306
 
264
307
  reflect_session() {
265
308
  local transcript="$1"
266
309
  local dry_run="${2:-false}"
267
- local confidence_threshold="${3:-$DEFAULT_CONFIDENCE}"
268
- local max_learnings="${4:-$DEFAULT_MAX_LEARNINGS}"
310
+ local max="${3:-$DEFAULT_MAX_LEARNINGS}"
269
311
 
270
312
  ensure_dirs
271
313
 
272
- # Detect signals
273
314
  local signals_file=$(detect_signals "$transcript")
274
315
 
275
316
  if [ -z "$signals_file" ] || [ ! -f "$signals_file" ]; then
276
- echo "No signals detected in session."
277
- return 1
317
+ echo "No actionable signals found (this is normal)."
318
+ return 0
278
319
  fi
279
320
 
280
- local total=$(wc -l < "$signals_file" 2>/dev/null || echo "0")
321
+ local total=$(wc -l < "$signals_file" 2>/dev/null || echo 0)
322
+ total=$(echo "$total" | tr -d ' ')
281
323
 
282
- if [ "$total" -eq 0 ]; then
283
- echo "No corrections or approvals found."
284
- return 1
285
- fi
324
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
325
+ echo "🧠 REFLECT: $total actionable signals"
326
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
286
327
 
287
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
288
- echo "🧠 REFLECT: Found $total signals"
289
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
328
+ local added=0
290
329
 
291
- local learnings_created=0
330
+ while IFS='|' read -r type rule; do
331
+ [ "$added" -ge "$max" ] && break
332
+ [ -z "$rule" ] && continue
292
333
 
293
- # Process each signal
294
- while IFS='|' read -r type context; do
295
- if [ "$learnings_created" -ge "$max_learnings" ]; then
296
- break
297
- fi
298
-
299
- local category=$(categorize_learning "$context")
300
- local confidence="medium"
301
- [ "$type" = "CORRECTION" ] && confidence="high"
302
-
303
- # Skip low confidence if threshold is high
304
- if [ "$confidence_threshold" = "high" ] && [ "$confidence" != "high" ]; then
305
- continue
306
- fi
334
+ local category=$(categorize "$rule")
307
335
 
308
- echo " 📝 [$category] $confidence confidence"
336
+ echo " [$category] $type"
309
337
 
310
338
  if [ "$dry_run" = "false" ]; then
311
- add_learning "$category" "$confidence" "$context"
339
+ if add_rule "$category" "$type" "$rule"; then
340
+ added=$((added + 1))
341
+ fi
342
+ else
343
+ added=$((added + 1))
312
344
  fi
313
-
314
- learnings_created=$((learnings_created + 1))
315
345
  done < "$signals_file"
316
346
 
317
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
347
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
318
348
 
319
349
  if [ "$dry_run" = "true" ]; then
320
- echo "🔍 DRY RUN - no changes saved"
350
+ echo "🔍 DRY RUN - would add $added rules"
321
351
  else
322
- echo "✅ Saved $learnings_created learnings"
352
+ echo "✅ Added $added rules"
323
353
  fi
324
354
 
325
- # Cleanup
326
355
  rm -f "$signals_file"
327
-
328
356
  return 0
329
357
  }
330
358
 
331
359
  # ============================================================================
332
- # COMMAND HANDLERS
360
+ # COMMANDS
333
361
  # ============================================================================
334
362
 
335
- cmd_reflect_on() {
363
+ cmd_on() {
336
364
  ensure_dirs
337
-
338
365
  cat > "$REFLECT_CONFIG" << EOF
339
366
  {
340
367
  "enabled": true,
341
368
  "autoReflect": true,
342
369
  "enabledAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
343
- "confidenceThreshold": "$DEFAULT_CONFIDENCE",
344
- "maxLearningsPerSession": $DEFAULT_MAX_LEARNINGS,
345
- "gitCommit": false,
346
- "gitPush": false
370
+ "confidenceThreshold": "high",
371
+ "maxLearningsPerSession": $DEFAULT_MAX_LEARNINGS
347
372
  }
348
373
  EOF
349
-
350
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
351
- echo "🧠 REFLECT: Auto-mode ENABLED"
352
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
353
- echo ""
354
- echo "Sessions will now extract learnings on exit."
355
- echo "Limits: $MAX_LEARNINGS_PER_CATEGORY per category, ${MAX_MEMORY_FILE_KB}KB max"
356
- echo ""
357
- echo "To disable: /sw:reflect-off"
358
-
359
- log "info" "Auto-reflection enabled"
374
+ echo "🧠 Auto-reflection ENABLED (high-value only)"
375
+ echo " Max $DEFAULT_MAX_LEARNINGS rules/session, $MAX_RULES_PER_CATEGORY per category"
360
376
  }
361
377
 
362
- cmd_reflect_off() {
378
+ cmd_off() {
363
379
  ensure_dirs
364
-
365
- if [ -f "$REFLECT_CONFIG" ]; then
366
- jq '.autoReflect = false' "$REFLECT_CONFIG" > "$REFLECT_CONFIG.tmp" && mv "$REFLECT_CONFIG.tmp" "$REFLECT_CONFIG"
367
- else
368
- cat > "$REFLECT_CONFIG" << EOF
369
- {
370
- "enabled": true,
371
- "autoReflect": false,
372
- "disabledAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
380
+ [ -f "$REFLECT_CONFIG" ] && jq '.autoReflect = false' "$REFLECT_CONFIG" > "$REFLECT_CONFIG.tmp" && mv "$REFLECT_CONFIG.tmp" "$REFLECT_CONFIG"
381
+ echo "🧠 Auto-reflection DISABLED"
373
382
  }
374
- EOF
375
- fi
376
-
377
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
378
- echo "🧠 REFLECT: Auto-mode DISABLED"
379
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
380
- echo ""
381
- echo "Manual /sw:reflect still works."
382
- echo "To re-enable: /sw:reflect-on"
383
383
 
384
- log "info" "Auto-reflection disabled"
385
- }
386
-
387
- cmd_reflect_status() {
384
+ cmd_status() {
388
385
  ensure_dirs
389
-
390
- local enabled=$(get_config_value "enabled" "true")
391
- local auto_reflect=$(get_config_value "autoReflect" "false")
392
-
393
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
394
- echo "🧠 REFLECT: Status"
395
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
386
+ local auto=$(get_config "autoReflect" "false")
387
+
388
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
389
+ echo "🧠 REFLECT STATUS"
390
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
391
+ [ "$auto" = "true" ] && echo " Auto: ✅ ON" || echo " Auto: ❌ OFF"
392
+ echo " Max/session: $DEFAULT_MAX_LEARNINGS"
393
+ echo " Max/category: $MAX_RULES_PER_CATEGORY"
396
394
  echo ""
397
395
 
398
- [ "$enabled" = "true" ] && echo " Reflection: ✅ Enabled" || echo " Reflection: ❌ Disabled"
399
- [ "$auto_reflect" = "true" ] && echo " Auto-reflect: ✅ On" || echo " Auto-reflect: ❌ Off"
400
- echo " Max/category: $MAX_LEARNINGS_PER_CATEGORY"
401
- echo " Max file: ${MAX_MEMORY_FILE_KB}KB"
402
- echo ""
403
-
404
- # Count learnings
405
396
  local total=0
406
397
  if [ -d "$MEMORY_DIR" ]; then
407
- echo " 📚 Memory files:"
408
398
  for f in "$MEMORY_DIR"/*.md; do
409
- if [ -f "$f" ]; then
410
- local name=$(basename "$f" .md)
411
- local count=$(grep -c "^- L[0-9]" "$f" 2>/dev/null || echo "0")
412
- local size=$(du -k "$f" 2>/dev/null | cut -f1)
413
- echo " $name: $count learnings (${size}KB)"
414
- total=$((total + count))
415
- fi
399
+ [ -f "$f" ] || continue
400
+ local name=$(basename "$f" .md)
401
+ local count=$(grep -c "^- " "$f" 2>/dev/null || echo 0)
402
+ echo " 📁 $name: $count rules"
403
+ total=$((total + count))
416
404
  done
417
405
  fi
418
406
 
419
407
  echo ""
420
- echo " Total: $total learnings"
421
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
408
+ echo " Total: $total rules"
409
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
422
410
  }
423
411
 
424
- cmd_reflect_clear() {
425
- local category="${1:-all}"
426
-
427
- if [ "$category" = "all" ]; then
428
- echo "Clearing all memory files..."
429
- rm -rf "$MEMORY_DIR"/*.md
430
- echo "✅ All learnings cleared"
412
+ cmd_clear() {
413
+ local cat="${1:-all}"
414
+ if [ "$cat" = "all" ]; then
415
+ rm -f "$MEMORY_DIR"/*.md
416
+ echo " Cleared all rules"
431
417
  else
432
- local file="$MEMORY_DIR/${category}.md"
433
- if [ -f "$file" ]; then
434
- rm -f "$file"
435
- echo "✅ Cleared $category learnings"
436
- else
437
- echo "⚠️ No $category memory file found"
438
- fi
418
+ rm -f "$MEMORY_DIR/${cat}.md"
419
+ echo "✅ Cleared $cat rules"
439
420
  fi
421
+ }
440
422
 
441
- log "info" "Cleared learnings: $category"
423
+ cmd_show() {
424
+ local cat="${1:-all}"
425
+ if [ "$cat" = "all" ]; then
426
+ for f in "$MEMORY_DIR"/*.md; do
427
+ [ -f "$f" ] && cat "$f" && echo ""
428
+ done
429
+ else
430
+ [ -f "$MEMORY_DIR/${cat}.md" ] && cat "$MEMORY_DIR/${cat}.md" || echo "No $cat rules"
431
+ fi
442
432
  }
443
433
 
444
434
  # ============================================================================
@@ -446,59 +436,34 @@ cmd_reflect_clear() {
446
436
  # ============================================================================
447
437
 
448
438
  main() {
449
- local command="${1:-reflect}"
450
- shift || true
451
-
452
- case "$command" in
453
- on|enable)
454
- cmd_reflect_on
455
- ;;
456
- off|disable)
457
- cmd_reflect_off
458
- ;;
459
- status)
460
- cmd_reflect_status
461
- ;;
462
- clear)
463
- cmd_reflect_clear "$@"
464
- ;;
439
+ local cmd="${1:-reflect}"; shift || true
440
+
441
+ case "$cmd" in
442
+ on|enable) cmd_on ;;
443
+ off|disable) cmd_off ;;
444
+ status) cmd_status ;;
445
+ clear) cmd_clear "$@" ;;
446
+ show) cmd_show "$@" ;;
465
447
  reflect|analyze)
466
- local transcript=""
467
- local dry_run="false"
468
- local confidence="$DEFAULT_CONFIDENCE"
469
- local max="$DEFAULT_MAX_LEARNINGS"
470
-
448
+ local transcript="" dry_run="false" max="$DEFAULT_MAX_LEARNINGS"
471
449
  while [ $# -gt 0 ]; do
472
450
  case "$1" in
473
451
  --transcript) transcript="$2"; shift 2 ;;
474
452
  --dry-run) dry_run="true"; shift ;;
475
- --confidence) confidence="$2"; shift 2 ;;
476
453
  --max) max="$2"; shift 2 ;;
477
454
  *) shift ;;
478
455
  esac
479
456
  done
480
457
 
481
- # Auto-detect transcript if not provided
482
458
  if [ -z "$transcript" ]; then
483
- local tmp="${TMPDIR:-/tmp}"
484
- transcript=$(find "$tmp" -name "*.md" -mmin -5 2>/dev/null | head -1)
485
-
486
- if [ -z "$transcript" ]; then
487
- echo "⚠️ No transcript found. Use --transcript <path>"
488
- return 1
489
- fi
459
+ transcript=$(find "${TMPDIR:-/tmp}" -name "*.md" -mmin -5 2>/dev/null | head -1)
460
+ [ -z "$transcript" ] && echo "No transcript found" && return 1
490
461
  fi
491
462
 
492
- reflect_session "$transcript" "$dry_run" "$confidence" "$max"
493
- ;;
494
- *)
495
- echo "Usage: reflect.sh [on|off|status|clear|reflect] [options]"
496
- return 1
463
+ reflect_session "$transcript" "$dry_run" "$max"
497
464
  ;;
465
+ *) echo "Usage: reflect.sh [on|off|status|clear|show|reflect]" ;;
498
466
  esac
499
467
  }
500
468
 
501
- # Run if executed directly
502
- if [ "${BASH_SOURCE[0]}" = "$0" ]; then
503
- main "$@"
504
- fi
469
+ [ "${BASH_SOURCE[0]}" = "$0" ] && main "$@"