anvil-dev-framework 0.1.6
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/README.md +719 -0
- package/VERSION +1 -0
- package/docs/ANVIL-REPO-IMPLEMENTATION-PLAN.md +441 -0
- package/docs/FIRST-SKILL-TUTORIAL.md +408 -0
- package/docs/INSTALLATION-RETRO-NOTES.md +458 -0
- package/docs/INSTALLATION.md +984 -0
- package/docs/anvil-hud.md +469 -0
- package/docs/anvil-init.md +255 -0
- package/docs/anvil-state.md +210 -0
- package/docs/boris-cherny-ralph-wiggum-insights.md +608 -0
- package/docs/command-reference.md +2022 -0
- package/docs/hooks-tts.md +368 -0
- package/docs/implementation-guide.md +810 -0
- package/docs/linear-github-integration.md +247 -0
- package/docs/local-issues.md +677 -0
- package/docs/patterns/README.md +419 -0
- package/docs/planning-responsibilities.md +139 -0
- package/docs/session-workflow.md +573 -0
- package/docs/simplification-plan-template.md +297 -0
- package/docs/simplification-principles.md +129 -0
- package/docs/specifications/CCS-RALPH-INTEGRATION-DESIGN.md +633 -0
- package/docs/specifications/CCS-RESEARCH-REPORT.md +169 -0
- package/docs/specifications/PLAN-ANV-verification-ralph-wiggum.md +403 -0
- package/docs/specifications/PLAN-parallel-tracks-anvil-memory-ccs.md +494 -0
- package/docs/specifications/SPEC-ANV-VRW/component-01-verify.md +208 -0
- package/docs/specifications/SPEC-ANV-VRW/component-02-stop-gate.md +226 -0
- package/docs/specifications/SPEC-ANV-VRW/component-03-posttooluse.md +209 -0
- package/docs/specifications/SPEC-ANV-VRW/component-04-ralph-wiggum.md +604 -0
- package/docs/specifications/SPEC-ANV-VRW/component-05-atomic-actions.md +311 -0
- package/docs/specifications/SPEC-ANV-VRW/component-06-verify-subagent.md +264 -0
- package/docs/specifications/SPEC-ANV-VRW/component-07-claude-md.md +363 -0
- package/docs/specifications/SPEC-ANV-VRW/index.md +182 -0
- package/docs/specifications/SPEC-ANV-anvil-memory.md +573 -0
- package/docs/specifications/SPEC-ANV-context-checkpoints.md +781 -0
- package/docs/specifications/SPEC-ANV-verification-ralph-wiggum.md +789 -0
- package/docs/sync.md +122 -0
- package/global/CLAUDE.md +140 -0
- package/global/agents/verify-app.md +164 -0
- package/global/commands/anvil-settings.md +527 -0
- package/global/commands/anvil-sync.md +121 -0
- package/global/commands/change.md +197 -0
- package/global/commands/clarify.md +252 -0
- package/global/commands/cleanup.md +292 -0
- package/global/commands/commit-push-pr.md +207 -0
- package/global/commands/decay-review.md +127 -0
- package/global/commands/discover.md +158 -0
- package/global/commands/doc-coverage.md +122 -0
- package/global/commands/evidence.md +307 -0
- package/global/commands/explore.md +121 -0
- package/global/commands/force-exit.md +135 -0
- package/global/commands/handoff.md +191 -0
- package/global/commands/healthcheck.md +302 -0
- package/global/commands/hud.md +84 -0
- package/global/commands/insights.md +319 -0
- package/global/commands/linear-setup.md +184 -0
- package/global/commands/lint-fix.md +198 -0
- package/global/commands/orient.md +510 -0
- package/global/commands/plan.md +228 -0
- package/global/commands/ralph.md +346 -0
- package/global/commands/ready.md +182 -0
- package/global/commands/release.md +305 -0
- package/global/commands/retro.md +96 -0
- package/global/commands/shard.md +166 -0
- package/global/commands/spec.md +227 -0
- package/global/commands/sprint.md +184 -0
- package/global/commands/tasks.md +228 -0
- package/global/commands/test-and-commit.md +151 -0
- package/global/commands/validate.md +132 -0
- package/global/commands/verify.md +251 -0
- package/global/commands/weekly-review.md +156 -0
- package/global/hooks/__pycache__/ralph_context_monitor.cpython-314.pyc +0 -0
- package/global/hooks/__pycache__/statusline_agent_sync.cpython-314.pyc +0 -0
- package/global/hooks/anvil_memory_observe.ts +322 -0
- package/global/hooks/anvil_memory_session.ts +166 -0
- package/global/hooks/anvil_memory_stop.ts +187 -0
- package/global/hooks/parse_transcript.py +116 -0
- package/global/hooks/post_merge_cleanup.sh +132 -0
- package/global/hooks/post_tool_format.sh +215 -0
- package/global/hooks/ralph_context_monitor.py +240 -0
- package/global/hooks/ralph_stop.sh +502 -0
- package/global/hooks/statusline.sh +1110 -0
- package/global/hooks/statusline_agent_sync.py +224 -0
- package/global/hooks/stop_gate.sh +250 -0
- package/global/lib/.claude/anvil-state.json +21 -0
- package/global/lib/__pycache__/agent_registry.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/claim_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/coderabbit_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/config_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/coordination_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/doc_coverage_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/gate_logger.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/github_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/hygiene_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/issue_models.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/issue_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/linear_data_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/linear_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/local_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/quality_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/ralph_state.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/state_manager.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/transcript_parser.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verification_runner.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verify_iteration.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verify_subagent.cpython-314.pyc +0 -0
- package/global/lib/agent_registry.py +995 -0
- package/global/lib/anvil-state.sh +435 -0
- package/global/lib/claim_service.py +515 -0
- package/global/lib/coderabbit_service.py +314 -0
- package/global/lib/config_service.py +423 -0
- package/global/lib/coordination_service.py +331 -0
- package/global/lib/doc_coverage_service.py +1305 -0
- package/global/lib/gate_logger.py +316 -0
- package/global/lib/github_service.py +310 -0
- package/global/lib/handoff_generator.py +775 -0
- package/global/lib/hygiene_service.py +712 -0
- package/global/lib/issue_models.py +257 -0
- package/global/lib/issue_provider.py +339 -0
- package/global/lib/linear_data_service.py +210 -0
- package/global/lib/linear_provider.py +987 -0
- package/global/lib/linear_provider.py.backup +671 -0
- package/global/lib/local_provider.py +486 -0
- package/global/lib/orient_fast.py +457 -0
- package/global/lib/quality_service.py +470 -0
- package/global/lib/ralph_prompt_generator.py +563 -0
- package/global/lib/ralph_state.py +1202 -0
- package/global/lib/state_manager.py +417 -0
- package/global/lib/transcript_parser.py +597 -0
- package/global/lib/verification_runner.py +557 -0
- package/global/lib/verify_iteration.py +490 -0
- package/global/lib/verify_subagent.py +250 -0
- package/global/skills/README.md +155 -0
- package/global/skills/quality-gates/SKILL.md +252 -0
- package/global/skills/skill-template/SKILL.md +109 -0
- package/global/skills/testing-strategies/SKILL.md +337 -0
- package/global/templates/CHANGE-template.md +105 -0
- package/global/templates/HANDOFF-template.md +63 -0
- package/global/templates/PLAN-template.md +111 -0
- package/global/templates/SPEC-template.md +93 -0
- package/global/templates/ralph/PROMPT.md.template +89 -0
- package/global/templates/ralph/fix_plan.md.template +31 -0
- package/global/templates/ralph/progress.txt.template +23 -0
- package/global/tests/__pycache__/test_doc_coverage.cpython-314.pyc +0 -0
- package/global/tests/test_doc_coverage.py +520 -0
- package/global/tests/test_issue_models.py +299 -0
- package/global/tests/test_local_provider.py +323 -0
- package/global/tools/README.md +178 -0
- package/global/tools/__pycache__/anvil-hud.cpython-314.pyc +0 -0
- package/global/tools/anvil-hud.py +3622 -0
- package/global/tools/anvil-hud.py.bak +3318 -0
- package/global/tools/anvil-issue.py +432 -0
- package/global/tools/anvil-memory/CLAUDE.md +49 -0
- package/global/tools/anvil-memory/README.md +42 -0
- package/global/tools/anvil-memory/bun.lock +25 -0
- package/global/tools/anvil-memory/bunfig.toml +9 -0
- package/global/tools/anvil-memory/package.json +23 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/context-monitor.test.ts +535 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/edge-cases.test.ts +645 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/fixtures.ts +363 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/index.ts +8 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/integration.test.ts +417 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/prompt-generator.test.ts +571 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/ralph-stop.test.ts +440 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/test-utils.ts +252 -0
- package/global/tools/anvil-memory/src/__tests__/commands.test.ts +657 -0
- package/global/tools/anvil-memory/src/__tests__/db.test.ts +641 -0
- package/global/tools/anvil-memory/src/__tests__/hooks.test.ts +272 -0
- package/global/tools/anvil-memory/src/__tests__/performance.test.ts +427 -0
- package/global/tools/anvil-memory/src/__tests__/test-utils.ts +113 -0
- package/global/tools/anvil-memory/src/commands/checkpoint.ts +197 -0
- package/global/tools/anvil-memory/src/commands/get.ts +115 -0
- package/global/tools/anvil-memory/src/commands/init.ts +94 -0
- package/global/tools/anvil-memory/src/commands/observe.ts +163 -0
- package/global/tools/anvil-memory/src/commands/search.ts +112 -0
- package/global/tools/anvil-memory/src/db.ts +638 -0
- package/global/tools/anvil-memory/src/index.ts +205 -0
- package/global/tools/anvil-memory/src/types.ts +122 -0
- package/global/tools/anvil-memory/tsconfig.json +29 -0
- package/global/tools/ralph-loop.sh +359 -0
- package/package.json +45 -0
- package/scripts/anvil +822 -0
- package/scripts/extract_patterns.py +222 -0
- package/scripts/init-project.sh +541 -0
- package/scripts/install.sh +229 -0
- package/scripts/postinstall.js +41 -0
- package/scripts/rollback.sh +188 -0
- package/scripts/sync.sh +623 -0
- package/scripts/test-statusline.sh +248 -0
- package/scripts/update_claude_md.py +224 -0
- package/scripts/verify.sh +255 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test-statusline.sh - Test statusline with various configurations
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# ./scripts/test-statusline.sh # Run all tests
|
|
6
|
+
# ./scripts/test-statusline.sh --live # Watch live updates
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
12
|
+
STATUSLINE_SCRIPT="$PROJECT_ROOT/global/hooks/statusline.sh"
|
|
13
|
+
CONFIG_FILE="$PROJECT_ROOT/.claude/anvil.config.json"
|
|
14
|
+
|
|
15
|
+
# Colors for test output
|
|
16
|
+
RED='\033[0;31m'
|
|
17
|
+
GREEN='\033[0;32m'
|
|
18
|
+
YELLOW='\033[1;33m'
|
|
19
|
+
CYAN='\033[0;36m'
|
|
20
|
+
RESET='\033[0m'
|
|
21
|
+
|
|
22
|
+
echo_header() {
|
|
23
|
+
echo ""
|
|
24
|
+
echo -e "${CYAN}═══════════════════════════════════════════════════${RESET}"
|
|
25
|
+
echo -e "${CYAN}$1${RESET}"
|
|
26
|
+
echo -e "${CYAN}═══════════════════════════════════════════════════${RESET}"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
echo_test() {
|
|
30
|
+
echo -e "${YELLOW}▶ $1${RESET}"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
echo_result() {
|
|
34
|
+
echo -e " ${GREEN}→${RESET} $1"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Create test JSON input for statusline
|
|
38
|
+
# Uses the actual Claude Code input format
|
|
39
|
+
create_test_input() {
|
|
40
|
+
local context_pct="${1:-50}"
|
|
41
|
+
local context_size="${2:-200000}"
|
|
42
|
+
local model="${3:-Claude Opus 4}"
|
|
43
|
+
|
|
44
|
+
# Calculate tokens based on percentage
|
|
45
|
+
local current_tokens=$((context_size * context_pct / 100))
|
|
46
|
+
local input_tokens=$((current_tokens * 80 / 100))
|
|
47
|
+
local cache_write=$((current_tokens * 20 / 100))
|
|
48
|
+
local cache_read=$((current_tokens / 2))
|
|
49
|
+
|
|
50
|
+
cat <<EOF
|
|
51
|
+
{
|
|
52
|
+
"session_id": "test-session-123",
|
|
53
|
+
"model": {
|
|
54
|
+
"display_name": "$model"
|
|
55
|
+
},
|
|
56
|
+
"context_window": {
|
|
57
|
+
"context_window_size": $context_size,
|
|
58
|
+
"current_usage": {
|
|
59
|
+
"input_tokens": $input_tokens,
|
|
60
|
+
"cache_read_input_tokens": $cache_read,
|
|
61
|
+
"cache_creation_input_tokens": $cache_write
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"cost": {
|
|
65
|
+
"total_cost_usd": 2.45
|
|
66
|
+
},
|
|
67
|
+
"workspace": {
|
|
68
|
+
"current_dir": "$PROJECT_ROOT"
|
|
69
|
+
},
|
|
70
|
+
"transcript_path": "/tmp/test-transcript.jsonl"
|
|
71
|
+
}
|
|
72
|
+
EOF
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Run statusline with test input
|
|
76
|
+
run_statusline() {
|
|
77
|
+
create_test_input "$@" | bash "$STATUSLINE_SCRIPT"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Test different config scenarios
|
|
81
|
+
test_config_variants() {
|
|
82
|
+
echo_header "Testing Config Variants"
|
|
83
|
+
|
|
84
|
+
# Backup original config
|
|
85
|
+
local backup=""
|
|
86
|
+
if [[ -f "$CONFIG_FILE" ]]; then
|
|
87
|
+
backup=$(cat "$CONFIG_FILE")
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Test 1: Default config (agent cost, never show project)
|
|
91
|
+
echo_test "Default config (costDisplay=agent, showProject=never)"
|
|
92
|
+
cat > "$CONFIG_FILE" <<EOF
|
|
93
|
+
{
|
|
94
|
+
"version": "1.3",
|
|
95
|
+
"statusline": {
|
|
96
|
+
"enabled": true,
|
|
97
|
+
"variant": "full",
|
|
98
|
+
"costDisplay": "agent",
|
|
99
|
+
"showProject": "never"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
EOF
|
|
103
|
+
echo_result "$(run_statusline 50)"
|
|
104
|
+
|
|
105
|
+
# Test 2: Total cost display
|
|
106
|
+
echo_test "Total cost display (costDisplay=total)"
|
|
107
|
+
cat > "$CONFIG_FILE" <<EOF
|
|
108
|
+
{
|
|
109
|
+
"version": "1.3",
|
|
110
|
+
"statusline": {
|
|
111
|
+
"enabled": true,
|
|
112
|
+
"variant": "full",
|
|
113
|
+
"costDisplay": "total",
|
|
114
|
+
"showProject": "never"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
EOF
|
|
118
|
+
echo_result "$(run_statusline 50)"
|
|
119
|
+
|
|
120
|
+
# Test 3: Both costs display
|
|
121
|
+
echo_test "Both costs display (costDisplay=both)"
|
|
122
|
+
cat > "$CONFIG_FILE" <<EOF
|
|
123
|
+
{
|
|
124
|
+
"version": "1.3",
|
|
125
|
+
"statusline": {
|
|
126
|
+
"enabled": true,
|
|
127
|
+
"variant": "full",
|
|
128
|
+
"costDisplay": "both",
|
|
129
|
+
"showProject": "never"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
EOF
|
|
133
|
+
echo_result "$(run_statusline 50)"
|
|
134
|
+
|
|
135
|
+
# Test 4: No cost display
|
|
136
|
+
echo_test "No cost display (costDisplay=none)"
|
|
137
|
+
cat > "$CONFIG_FILE" <<EOF
|
|
138
|
+
{
|
|
139
|
+
"version": "1.3",
|
|
140
|
+
"statusline": {
|
|
141
|
+
"enabled": true,
|
|
142
|
+
"variant": "full",
|
|
143
|
+
"costDisplay": "none",
|
|
144
|
+
"showProject": "never"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
EOF
|
|
148
|
+
echo_result "$(run_statusline 50)"
|
|
149
|
+
|
|
150
|
+
# Test 5: Always show project
|
|
151
|
+
echo_test "Always show project (showProject=always)"
|
|
152
|
+
cat > "$CONFIG_FILE" <<EOF
|
|
153
|
+
{
|
|
154
|
+
"version": "1.3",
|
|
155
|
+
"statusline": {
|
|
156
|
+
"enabled": true,
|
|
157
|
+
"variant": "full",
|
|
158
|
+
"costDisplay": "agent",
|
|
159
|
+
"showProject": "always"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
EOF
|
|
163
|
+
echo_result "$(run_statusline 50)"
|
|
164
|
+
|
|
165
|
+
# Test 6: Minimal variant
|
|
166
|
+
echo_test "Minimal variant"
|
|
167
|
+
cat > "$CONFIG_FILE" <<EOF
|
|
168
|
+
{
|
|
169
|
+
"version": "1.3",
|
|
170
|
+
"statusline": {
|
|
171
|
+
"enabled": true,
|
|
172
|
+
"variant": "minimal",
|
|
173
|
+
"costDisplay": "agent",
|
|
174
|
+
"showProject": "never"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
EOF
|
|
178
|
+
echo_result "$(run_statusline 50)"
|
|
179
|
+
|
|
180
|
+
# Restore original config
|
|
181
|
+
if [[ -n "$backup" ]]; then
|
|
182
|
+
echo "$backup" > "$CONFIG_FILE"
|
|
183
|
+
fi
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Test context thresholds (ANV-192: CCS L1/L2/L3 indicators)
|
|
187
|
+
test_context_thresholds() {
|
|
188
|
+
echo_header "Testing Context Thresholds (CCS Levels)"
|
|
189
|
+
|
|
190
|
+
echo_test "L0 - Normal (50% context) - No level indicator"
|
|
191
|
+
echo_result "$(run_statusline 50)"
|
|
192
|
+
|
|
193
|
+
echo_test "L0 - Normal (65% context) - Still no indicator"
|
|
194
|
+
echo_result "$(run_statusline 65)"
|
|
195
|
+
|
|
196
|
+
echo_test "L1 - Warning (70% context) - ⚠️ L1 + [CKPT: Ready]"
|
|
197
|
+
echo_result "$(run_statusline 70)"
|
|
198
|
+
|
|
199
|
+
echo_test "L1 - Warning (75% context)"
|
|
200
|
+
echo_result "$(run_statusline 75)"
|
|
201
|
+
|
|
202
|
+
echo_test "L1 - Warning (84% context) - Upper L1 bound"
|
|
203
|
+
echo_result "$(run_statusline 84)"
|
|
204
|
+
|
|
205
|
+
echo_test "L2 - Critical (85% context) - 🔶 L2 + token breakdown"
|
|
206
|
+
echo_result "$(run_statusline 85)"
|
|
207
|
+
|
|
208
|
+
echo_test "L2 - Critical (90% context)"
|
|
209
|
+
echo_result "$(run_statusline 90)"
|
|
210
|
+
|
|
211
|
+
echo_test "L2 - Critical (94% context) - Upper L2 bound"
|
|
212
|
+
echo_result "$(run_statusline 94)"
|
|
213
|
+
|
|
214
|
+
echo_test "L3 - Emergency (95% context) - 🔴 L3 + [CKPT: URGENT]"
|
|
215
|
+
echo_result "$(run_statusline 95)"
|
|
216
|
+
|
|
217
|
+
echo_test "L3 - Emergency (98% context)"
|
|
218
|
+
echo_result "$(run_statusline 98)"
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
# Live mode - watch updates
|
|
222
|
+
live_mode() {
|
|
223
|
+
echo_header "Live Mode - Press Ctrl+C to exit"
|
|
224
|
+
echo "Statusline will refresh every 2 seconds..."
|
|
225
|
+
echo ""
|
|
226
|
+
|
|
227
|
+
while true; do
|
|
228
|
+
# Clear line and print statusline
|
|
229
|
+
printf "\r\033[K"
|
|
230
|
+
run_statusline 50
|
|
231
|
+
sleep 2
|
|
232
|
+
done
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# Main
|
|
236
|
+
main() {
|
|
237
|
+
if [[ "${1:-}" == "--live" ]]; then
|
|
238
|
+
live_mode
|
|
239
|
+
else
|
|
240
|
+
test_config_variants
|
|
241
|
+
test_context_thresholds
|
|
242
|
+
|
|
243
|
+
echo_header "All Tests Complete"
|
|
244
|
+
echo -e "${GREEN}✓ Statusline configuration tests passed${RESET}"
|
|
245
|
+
fi
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
main "$@"
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Update CLAUDE.md with extracted patterns.
|
|
4
|
+
|
|
5
|
+
Usage: python scripts/update_claude_md.py patterns.json [--claude-md PATH] [--dry-run]
|
|
6
|
+
|
|
7
|
+
This script:
|
|
8
|
+
1. Reads extracted patterns from JSON file
|
|
9
|
+
2. Parses existing Project-Learned Patterns table in CLAUDE.md
|
|
10
|
+
3. Merges new patterns (avoiding duplicates by name similarity)
|
|
11
|
+
4. Updates the table with dates
|
|
12
|
+
5. Writes the updated file (unless --dry-run)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
import json
|
|
17
|
+
import re
|
|
18
|
+
import argparse
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from difflib import SequenceMatcher
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def read_file(path: str) -> str:
|
|
25
|
+
"""Read file contents."""
|
|
26
|
+
return Path(path).read_text()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def write_file(path: str, content: str) -> None:
|
|
30
|
+
"""Write content to file."""
|
|
31
|
+
Path(path).write_text(content)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def parse_patterns_table(content: str) -> list[dict]:
|
|
35
|
+
"""Extract existing patterns from CLAUDE.md Project-Learned Patterns table."""
|
|
36
|
+
# Match the table after "### Project-Learned Patterns"
|
|
37
|
+
table_pattern = r'### Project-Learned Patterns.*?\n\| Pattern \| Evidence \| Added \|\n\|[-|]+\|\n((?:\|[^\n]+\|\n)*)'
|
|
38
|
+
match = re.search(table_pattern, content, re.DOTALL)
|
|
39
|
+
|
|
40
|
+
if not match:
|
|
41
|
+
return []
|
|
42
|
+
|
|
43
|
+
rows = match.group(1).strip().split('\n')
|
|
44
|
+
patterns = []
|
|
45
|
+
|
|
46
|
+
for row in rows:
|
|
47
|
+
if not row.strip():
|
|
48
|
+
continue
|
|
49
|
+
# Parse: | Pattern Name | Evidence | Date |
|
|
50
|
+
cols = [c.strip() for c in row.split('|')]
|
|
51
|
+
# Filter out empty strings from split
|
|
52
|
+
cols = [c for c in cols if c]
|
|
53
|
+
|
|
54
|
+
if len(cols) >= 3:
|
|
55
|
+
patterns.append({
|
|
56
|
+
'name': cols[0],
|
|
57
|
+
'evidence': cols[1],
|
|
58
|
+
'added': cols[2]
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
return patterns
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def similarity(a: str, b: str) -> float:
|
|
65
|
+
"""Calculate string similarity ratio (0-1)."""
|
|
66
|
+
return SequenceMatcher(None, a.lower(), b.lower()).ratio()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def is_duplicate(new_pattern: str, existing_patterns: list[dict], threshold: float = 0.7) -> bool:
|
|
70
|
+
"""Check if a pattern name is too similar to existing ones."""
|
|
71
|
+
for existing in existing_patterns:
|
|
72
|
+
if similarity(new_pattern, existing['name']) >= threshold:
|
|
73
|
+
return True
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def merge_patterns(existing: list[dict], new_patterns: list[dict]) -> tuple[list[dict], list[dict]]:
|
|
78
|
+
"""
|
|
79
|
+
Merge new patterns with existing, avoiding duplicates.
|
|
80
|
+
|
|
81
|
+
Returns: (merged_list, added_patterns)
|
|
82
|
+
"""
|
|
83
|
+
today = datetime.now().strftime('%Y-%m-%d')
|
|
84
|
+
merged = list(existing)
|
|
85
|
+
added = []
|
|
86
|
+
|
|
87
|
+
for pattern in new_patterns:
|
|
88
|
+
# Skip patterns that are too similar to existing ones
|
|
89
|
+
if is_duplicate(pattern['name'], existing):
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
# Also skip if too similar to already-added patterns
|
|
93
|
+
if is_duplicate(pattern['name'], added):
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
# Only include "learning" and "retro" type patterns (most valuable)
|
|
97
|
+
pattern_type = pattern.get('type', '')
|
|
98
|
+
if pattern_type not in ('learning', 'retro'):
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
new_entry = {
|
|
102
|
+
'name': clean_pattern_name(pattern['name']),
|
|
103
|
+
'evidence': pattern.get('evidence', ''),
|
|
104
|
+
'added': today
|
|
105
|
+
}
|
|
106
|
+
merged.append(new_entry)
|
|
107
|
+
added.append(new_entry)
|
|
108
|
+
|
|
109
|
+
return merged, added
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def clean_pattern_name(name: str) -> str:
|
|
113
|
+
"""Clean up pattern name for table display."""
|
|
114
|
+
# Remove markdown formatting
|
|
115
|
+
name = re.sub(r'\*\*([^*]+)\*\*', r'\1', name)
|
|
116
|
+
# Remove leading/trailing whitespace
|
|
117
|
+
name = name.strip()
|
|
118
|
+
# Truncate if too long
|
|
119
|
+
if len(name) > 50:
|
|
120
|
+
name = name[:47] + '...'
|
|
121
|
+
# Capitalize first letter
|
|
122
|
+
if name:
|
|
123
|
+
name = name[0].upper() + name[1:]
|
|
124
|
+
return name
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def format_table(patterns: list[dict]) -> str:
|
|
128
|
+
"""Format patterns as markdown table."""
|
|
129
|
+
lines = [
|
|
130
|
+
'| Pattern | Evidence | Added |',
|
|
131
|
+
'|---------|----------|-------|'
|
|
132
|
+
]
|
|
133
|
+
for p in patterns:
|
|
134
|
+
# Escape pipe characters in content
|
|
135
|
+
name = p['name'].replace('|', '\\|')
|
|
136
|
+
evidence = p['evidence'].replace('|', '\\|')
|
|
137
|
+
added = p['added']
|
|
138
|
+
lines.append(f'| {name} | {evidence} | {added} |')
|
|
139
|
+
|
|
140
|
+
return '\n'.join(lines)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def update_claude_md(content: str, patterns: list[dict]) -> str:
|
|
144
|
+
"""Replace the patterns table in CLAUDE.md content."""
|
|
145
|
+
format_table(patterns)
|
|
146
|
+
|
|
147
|
+
# Pattern to match the existing table
|
|
148
|
+
table_pattern = r'(### Project-Learned Patterns.*?\n\| Pattern \| Evidence \| Added \|\n\|[-|]+\|\n)((?:\|[^\n]+\|\n)*)'
|
|
149
|
+
|
|
150
|
+
def replacer(match):
|
|
151
|
+
header = match.group(1)
|
|
152
|
+
return header + '\n'.join(
|
|
153
|
+
f'| {p["name"]} | {p["evidence"]} | {p["added"]} |'
|
|
154
|
+
for p in patterns
|
|
155
|
+
) + '\n'
|
|
156
|
+
|
|
157
|
+
updated = re.sub(table_pattern, replacer, content, flags=re.DOTALL)
|
|
158
|
+
|
|
159
|
+
return updated
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def main():
|
|
163
|
+
parser = argparse.ArgumentParser(description='Update CLAUDE.md with extracted patterns')
|
|
164
|
+
parser.add_argument('patterns_file', help='JSON file with extracted patterns')
|
|
165
|
+
parser.add_argument('--claude-md', default='.claude/CLAUDE.md', help='Path to CLAUDE.md')
|
|
166
|
+
parser.add_argument('--dry-run', action='store_true', help='Show changes without writing')
|
|
167
|
+
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')
|
|
168
|
+
args = parser.parse_args()
|
|
169
|
+
|
|
170
|
+
# Read patterns JSON
|
|
171
|
+
try:
|
|
172
|
+
with open(args.patterns_file) as f:
|
|
173
|
+
data = json.load(f)
|
|
174
|
+
except FileNotFoundError:
|
|
175
|
+
print(f"Error: Pattern file not found: {args.patterns_file}", file=sys.stderr)
|
|
176
|
+
sys.exit(1)
|
|
177
|
+
except json.JSONDecodeError as e:
|
|
178
|
+
print(f"Error: Invalid JSON in {args.patterns_file}: {e}", file=sys.stderr)
|
|
179
|
+
sys.exit(1)
|
|
180
|
+
|
|
181
|
+
new_patterns = data.get('patterns', [])
|
|
182
|
+
|
|
183
|
+
# Read CLAUDE.md
|
|
184
|
+
claude_md_path = Path(args.claude_md)
|
|
185
|
+
if not claude_md_path.exists():
|
|
186
|
+
print(f"Error: CLAUDE.md not found: {args.claude_md}", file=sys.stderr)
|
|
187
|
+
sys.exit(1)
|
|
188
|
+
|
|
189
|
+
content = read_file(args.claude_md)
|
|
190
|
+
existing = parse_patterns_table(content)
|
|
191
|
+
|
|
192
|
+
if args.verbose:
|
|
193
|
+
print(f"Found {len(existing)} existing patterns", file=sys.stderr)
|
|
194
|
+
print(f"Processing {len(new_patterns)} new patterns", file=sys.stderr)
|
|
195
|
+
|
|
196
|
+
# Merge patterns
|
|
197
|
+
merged, added = merge_patterns(existing, new_patterns)
|
|
198
|
+
|
|
199
|
+
if not added:
|
|
200
|
+
print("No new patterns to add (all duplicates or non-learning types)")
|
|
201
|
+
sys.exit(0)
|
|
202
|
+
|
|
203
|
+
if args.verbose:
|
|
204
|
+
print(f"Adding {len(added)} new patterns:", file=sys.stderr)
|
|
205
|
+
for p in added:
|
|
206
|
+
print(f" - {p['name']}", file=sys.stderr)
|
|
207
|
+
|
|
208
|
+
# Update content
|
|
209
|
+
updated = update_claude_md(content, merged)
|
|
210
|
+
|
|
211
|
+
if args.dry_run:
|
|
212
|
+
print("=== DRY RUN - Changes that would be made ===")
|
|
213
|
+
print(f"\nNew patterns to add ({len(added)}):")
|
|
214
|
+
for p in added:
|
|
215
|
+
print(f" | {p['name']} | {p['evidence']} | {p['added']} |")
|
|
216
|
+
print(f"\nTotal patterns after merge: {len(merged)}")
|
|
217
|
+
else:
|
|
218
|
+
write_file(args.claude_md, updated)
|
|
219
|
+
print(f"Updated {args.claude_md} with {len(added)} new patterns")
|
|
220
|
+
print(f"Total patterns: {len(merged)}")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
if __name__ == '__main__':
|
|
224
|
+
main()
|