specweave 1.0.98 → 1.0.99
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.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
<!-- SW:META template="claude" version="1.0.
|
|
1
|
+
<!-- SW:META template="claude" version="1.0.98" sections="header,start,autodetect,metarule,rules,workflow,reflect,context,lsp,structure,taskformat,secrets,syncing,mapping,testing,api,limits,troubleshooting,principles,linking,mcp,autoexecute,auto,docs" -->
|
|
2
2
|
|
|
3
|
-
<!-- SW:SECTION:header version="1.0.
|
|
3
|
+
<!-- SW:SECTION:header version="1.0.98" -->
|
|
4
4
|
**Framework**: SpecWeave | **Truth**: `spec.md` + `tasks.md`
|
|
5
5
|
<!-- SW:END:header -->
|
|
6
6
|
|
|
7
|
-
<!-- SW:SECTION:start version="1.0.
|
|
7
|
+
<!-- SW:SECTION:start version="1.0.98" -->
|
|
8
8
|
## Getting Started
|
|
9
9
|
|
|
10
10
|
**Initial increment**: `0001-project-setup` (auto-created by `specweave init`)
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
2. **Customize**: Edit spec.md and use for setup tasks
|
|
15
15
|
<!-- SW:END:start -->
|
|
16
16
|
|
|
17
|
-
<!-- SW:SECTION:autodetect version="1.0.
|
|
17
|
+
<!-- SW:SECTION:autodetect version="1.0.98" -->
|
|
18
18
|
## Auto-Detection
|
|
19
19
|
|
|
20
20
|
SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
|
|
@@ -24,7 +24,7 @@ SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
|
|
|
24
24
|
**Opt-out phrases**: "Just brainstorm first" | "Don't plan yet" | "Quick discussion" | "Let's explore ideas"
|
|
25
25
|
<!-- SW:END:autodetect -->
|
|
26
26
|
|
|
27
|
-
<!-- SW:SECTION:metarule version="1.0.
|
|
27
|
+
<!-- SW:SECTION:metarule version="1.0.98" -->
|
|
28
28
|
## Meta-Rule: Think-Before-Act
|
|
29
29
|
|
|
30
30
|
**Satisfy dependencies BEFORE dependent operations.**
|
|
@@ -35,7 +35,7 @@ SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
|
|
|
35
35
|
```
|
|
36
36
|
<!-- SW:END:metarule -->
|
|
37
37
|
|
|
38
|
-
<!-- SW:SECTION:rules version="1.0.
|
|
38
|
+
<!-- SW:SECTION:rules version="1.0.98" -->
|
|
39
39
|
## Rules
|
|
40
40
|
|
|
41
41
|
1. **Files** → `.specweave/increments/####-name/` (spec.md, plan.md, tasks.md at root; reports/, scripts/, logs/ subfolders)
|
|
@@ -47,7 +47,7 @@ SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
|
|
|
47
47
|
7. **⛔ Initialization guard**: `.specweave/` folders MUST ONLY exist where `specweave init` was run. NEVER create `.specweave/` in parent, nested, or unrelated directories. Check `config.json` exists before creating ANY `.specweave/` subfolders.
|
|
48
48
|
<!-- SW:END:rules -->
|
|
49
49
|
|
|
50
|
-
<!-- SW:SECTION:workflow version="1.0.
|
|
50
|
+
<!-- SW:SECTION:workflow version="1.0.98" -->
|
|
51
51
|
## Workflow
|
|
52
52
|
|
|
53
53
|
`/sw:increment "X"` → `/sw:do` → `/sw:progress` → `/sw:done 0001`
|
|
@@ -67,7 +67,7 @@ SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
|
|
|
67
67
|
**Natural language**: "Let's build X" → `/sw:increment` | "What's status?" → `/sw:progress` | "We're done" → `/sw:done` | "Ship while sleeping" → `/sw:auto`
|
|
68
68
|
<!-- SW:END:workflow -->
|
|
69
69
|
|
|
70
|
-
<!-- SW:SECTION:reflect version="1.0.
|
|
70
|
+
<!-- SW:SECTION:reflect version="1.0.98" -->
|
|
71
71
|
## Self-Improving Skills (Reflect)
|
|
72
72
|
|
|
73
73
|
**Learn once, never repeat.** Claude learns from corrections and patterns across sessions.
|
|
@@ -111,7 +111,7 @@ ls ~/.specweave/memory/*.md 2>/dev/null
|
|
|
111
111
|
**Enable auto-learning**: `/sw:reflect-on` → Stop hook analyzes sessions automatically
|
|
112
112
|
<!-- SW:END:reflect -->
|
|
113
113
|
|
|
114
|
-
<!-- SW:SECTION:context version="1.0.
|
|
114
|
+
<!-- SW:SECTION:context version="1.0.98" -->
|
|
115
115
|
## Living Docs Context
|
|
116
116
|
|
|
117
117
|
**Before implementing features**: Check existing docs for patterns and decisions.
|
|
@@ -131,7 +131,7 @@ grep -ril "keyword" .specweave/docs/internal/
|
|
|
131
131
|
**Use `/sw:context <topic>`** to load relevant living docs into conversation.
|
|
132
132
|
<!-- SW:END:context -->
|
|
133
133
|
|
|
134
|
-
<!-- SW:SECTION:lsp version="1.0.
|
|
134
|
+
<!-- SW:SECTION:lsp version="1.0.98" -->
|
|
135
135
|
## LSP-Enhanced Exploration
|
|
136
136
|
|
|
137
137
|
**USE LSP ACTIVELY** for semantic code understanding (100x faster than grep).
|
|
@@ -148,7 +148,7 @@ go install golang.org/x/tools/gopls@latest # Go
|
|
|
148
148
|
**Best Practices**: ALWAYS use `findReferences` before refactoring | Use `goToDefinition` instead of grep | Combine with Explore agent
|
|
149
149
|
<!-- SW:END:lsp -->
|
|
150
150
|
|
|
151
|
-
<!-- SW:SECTION:structure version="1.0.
|
|
151
|
+
<!-- SW:SECTION:structure version="1.0.98" -->
|
|
152
152
|
## Structure
|
|
153
153
|
|
|
154
154
|
```
|
|
@@ -218,7 +218,7 @@ my-project/
|
|
|
218
218
|
```
|
|
219
219
|
<!-- SW:END:structure -->
|
|
220
220
|
|
|
221
|
-
<!-- SW:SECTION:taskformat version="1.0.
|
|
221
|
+
<!-- SW:SECTION:taskformat version="1.0.98" -->
|
|
222
222
|
## Task Format
|
|
223
223
|
|
|
224
224
|
```markdown
|
|
@@ -228,7 +228,7 @@ my-project/
|
|
|
228
228
|
```
|
|
229
229
|
<!-- SW:END:taskformat -->
|
|
230
230
|
|
|
231
|
-
<!-- SW:SECTION:secrets version="1.0.
|
|
231
|
+
<!-- SW:SECTION:secrets version="1.0.98" -->
|
|
232
232
|
## Secrets Check
|
|
233
233
|
|
|
234
234
|
**BEFORE CLI tools**: Check existing config first!
|
|
@@ -239,7 +239,7 @@ gh auth status
|
|
|
239
239
|
```
|
|
240
240
|
<!-- SW:END:secrets -->
|
|
241
241
|
|
|
242
|
-
<!-- SW:SECTION:syncing version="1.0.
|
|
242
|
+
<!-- SW:SECTION:syncing version="1.0.98" -->
|
|
243
243
|
## External Sync (GitHub/JIRA/ADO)
|
|
244
244
|
|
|
245
245
|
**After increment creation**: Run `/sw-github:sync {id}` to create issues!
|
|
@@ -267,7 +267,7 @@ Living docs sync ≠ External sync. They are separate:
|
|
|
267
267
|
**Verify tokens**: `grep GITHUB_TOKEN .env` | `gh auth status`
|
|
268
268
|
<!-- SW:END:syncing -->
|
|
269
269
|
|
|
270
|
-
<!-- SW:SECTION:mapping version="1.0.
|
|
270
|
+
<!-- SW:SECTION:mapping version="1.0.98" -->
|
|
271
271
|
## GitHub Mapping
|
|
272
272
|
|
|
273
273
|
| SpecWeave | GitHub |
|
|
@@ -277,7 +277,7 @@ Living docs sync ≠ External sync. They are separate:
|
|
|
277
277
|
| Task T-XXX | Checkbox |
|
|
278
278
|
<!-- SW:END:mapping -->
|
|
279
279
|
|
|
280
|
-
<!-- SW:SECTION:testing version="1.0.
|
|
280
|
+
<!-- SW:SECTION:testing version="1.0.98" -->
|
|
281
281
|
## Testing
|
|
282
282
|
|
|
283
283
|
BDD in tasks.md | Unit >80% | `.test.ts` (Vitest)
|
|
@@ -289,7 +289,7 @@ vi.mock('fs', () => ({ readFile: vi.fn() }));
|
|
|
289
289
|
```
|
|
290
290
|
<!-- SW:END:testing -->
|
|
291
291
|
|
|
292
|
-
<!-- SW:SECTION:api version="1.0.
|
|
292
|
+
<!-- SW:SECTION:api version="1.0.98" -->
|
|
293
293
|
## API Development (OpenAPI-First)
|
|
294
294
|
|
|
295
295
|
**For API projects only.** OpenAPI = source of truth → Postman derived from it.
|
|
@@ -308,13 +308,13 @@ vi.mock('fs', () => ({ readFile: vi.fn() }));
|
|
|
308
308
|
**Import**: Postman → Import collection + env → Fill secrets → Select env
|
|
309
309
|
<!-- SW:END:api -->
|
|
310
310
|
|
|
311
|
-
<!-- SW:SECTION:limits version="1.0.
|
|
311
|
+
<!-- SW:SECTION:limits version="1.0.98" -->
|
|
312
312
|
## Limits
|
|
313
313
|
|
|
314
314
|
**Max 1500 lines/file** — extract before adding
|
|
315
315
|
<!-- SW:END:limits -->
|
|
316
316
|
|
|
317
|
-
<!-- SW:SECTION:troubleshooting version="1.0.
|
|
317
|
+
<!-- SW:SECTION:troubleshooting version="1.0.98" -->
|
|
318
318
|
## Troubleshooting
|
|
319
319
|
|
|
320
320
|
| Issue | Fix |
|
|
@@ -333,7 +333,7 @@ vi.mock('fs', () => ({ readFile: vi.fn() }));
|
|
|
333
333
|
| Path patterns not working | `//path` = absolute, `/path` = relative to settings file, `additionalDirectories` for explicit working dirs |
|
|
334
334
|
<!-- SW:END:troubleshooting -->
|
|
335
335
|
|
|
336
|
-
<!-- SW:SECTION:principles version="1.0.
|
|
336
|
+
<!-- SW:SECTION:principles version="1.0.98" -->
|
|
337
337
|
## Principles
|
|
338
338
|
|
|
339
339
|
1. **Spec-first**: `/sw:increment` before coding
|
|
@@ -343,7 +343,7 @@ vi.mock('fs', () => ({ readFile: vi.fn() }));
|
|
|
343
343
|
5. **Clean**: All files in increment folders
|
|
344
344
|
<!-- SW:END:principles -->
|
|
345
345
|
|
|
346
|
-
<!-- SW:SECTION:linking version="1.0.
|
|
346
|
+
<!-- SW:SECTION:linking version="1.0.98" -->
|
|
347
347
|
## Bidirectional Linking
|
|
348
348
|
|
|
349
349
|
Tasks ↔ User Stories auto-linked via AC-IDs: `AC-US1-01` → `US-001`
|
|
@@ -351,7 +351,7 @@ Tasks ↔ User Stories auto-linked via AC-IDs: `AC-US1-01` → `US-001`
|
|
|
351
351
|
Task format: `**AC**: AC-US1-01, AC-US1-02` (CRITICAL for linking)
|
|
352
352
|
<!-- SW:END:linking -->
|
|
353
353
|
|
|
354
|
-
<!-- SW:SECTION:mcp version="1.0.
|
|
354
|
+
<!-- SW:SECTION:mcp version="1.0.98" -->
|
|
355
355
|
## External Service Connection
|
|
356
356
|
|
|
357
357
|
**Priority**: MCP Server → REST API → CLI → Direct Connection
|
|
@@ -375,7 +375,7 @@ wrangler whoami 2>/dev/null
|
|
|
375
375
|
```
|
|
376
376
|
<!-- SW:END:mcp -->
|
|
377
377
|
|
|
378
|
-
<!-- SW:SECTION:autoexecute version="1.0.
|
|
378
|
+
<!-- SW:SECTION:autoexecute version="1.0.98" -->
|
|
379
379
|
## Auto-Execute Rule
|
|
380
380
|
|
|
381
381
|
**NEVER** output "Manual Step Required" when credentials exist. **EXECUTE DIRECTLY.**
|
|
@@ -389,7 +389,7 @@ wrangler whoami 2>/dev/null && gh auth status 2>/dev/null
|
|
|
389
389
|
```
|
|
390
390
|
<!-- SW:END:autoexecute -->
|
|
391
391
|
|
|
392
|
-
<!-- SW:SECTION:auto version="1.0.
|
|
392
|
+
<!-- SW:SECTION:auto version="1.0.98" -->
|
|
393
393
|
## Auto Mode (Autonomous Execution)
|
|
394
394
|
|
|
395
395
|
**Continuous execution until all tasks complete.**
|
|
@@ -466,7 +466,7 @@ wrangler whoami 2>/dev/null && gh auth status 2>/dev/null
|
|
|
466
466
|
**Circuit Breaker**: External API fails 3x? Queue & continue
|
|
467
467
|
<!-- SW:END:auto -->
|
|
468
468
|
|
|
469
|
-
<!-- SW:SECTION:docs version="1.0.
|
|
469
|
+
<!-- SW:SECTION:docs version="1.0.98" -->
|
|
470
470
|
## Docs
|
|
471
471
|
|
|
472
472
|
[spec-weave.com](https://spec-weave.com) | `.specweave/docs/internal/`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specweave",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.99",
|
|
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",
|
|
@@ -123,30 +123,84 @@ is_actionable() {
|
|
|
123
123
|
# Must contain some actionable verb
|
|
124
124
|
echo "$text" | grep -qiE "(use|don.t|never|always|should|must|avoid|prefer)" || return 1
|
|
125
125
|
|
|
126
|
+
# QUALITY GATE: Skip documentation examples (markdown formatting, line numbers, JSON)
|
|
127
|
+
# These patterns indicate the text came from docs/examples, not actual user corrections
|
|
128
|
+
if echo "$text" | grep -qE '^\s*[0-9]+→|^\s*[0-9]+\s*\||```|\*\*|\\n|\\"|":[[:space:]]|"sessionId"|"userType"|"cwd"'; then
|
|
129
|
+
return 1
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# QUALITY GATE: Skip content that looks like code examples with backticks
|
|
133
|
+
if echo "$text" | grep -qE '`[^`]+`.*`[^`]+`'; then
|
|
134
|
+
return 1
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# QUALITY GATE: Skip content with escaped quotes (JSON/code artifacts)
|
|
138
|
+
if echo "$text" | grep -qE '\\"|\\".*\\"'; then
|
|
139
|
+
return 1
|
|
140
|
+
fi
|
|
141
|
+
|
|
126
142
|
return 0
|
|
127
143
|
}
|
|
128
144
|
|
|
129
145
|
# Extract the actual rule from a correction context
|
|
146
|
+
# PRIORITY: Extract the "do Y instead" part, not the "don't X" part
|
|
130
147
|
extract_rule() {
|
|
131
148
|
local context="$1"
|
|
132
|
-
|
|
133
|
-
# Try to find the "do Y instead" part
|
|
134
149
|
local rule=""
|
|
135
150
|
|
|
136
|
-
#
|
|
137
|
-
|
|
138
|
-
|
|
151
|
+
# PRIORITY 1: Look for "Use X instead" pattern
|
|
152
|
+
# Captures "Use logger.info() instead" - allows alphanumeric, dots, parens before "instead"
|
|
153
|
+
rule=$(echo "$context" | grep -oiE "[Uu]se [a-zA-Z0-9_.()/]+(\s+[a-zA-Z0-9_.()/]+)*\s+instead" | head -1)
|
|
154
|
+
if [ -n "$rule" ] && [ ${#rule} -gt 15 ]; then
|
|
155
|
+
echo "$rule" | sed 's/^[[:space:]]*//' | cut -c1-100
|
|
156
|
+
return
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
# PRIORITY 2: Look for "should use X" (correction pattern)
|
|
160
|
+
rule=$(echo "$context" | grep -oiE "should (use|be) [^.!?]+" | head -1)
|
|
161
|
+
if [ -n "$rule" ] && [ ${#rule} -gt 15 ]; then
|
|
162
|
+
echo "$rule" | sed 's/^[[:space:]]*//' | cut -c1-100
|
|
163
|
+
return
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
# PRIORITY 3: Look for sentence after "Wrong!" or "No,"
|
|
167
|
+
# Extract the sentence that comes after the negation marker
|
|
168
|
+
rule=$(echo "$context" | grep -oiE "(Wrong!?|No,) [^.!?]+" | sed 's/^[Ww]rong!*[[:space:]]*//;s/^[Nn]o,[[:space:]]*//' | head -1)
|
|
169
|
+
if [ -n "$rule" ] && [ ${#rule} -gt 15 ]; then
|
|
170
|
+
echo "$rule" | sed 's/^[[:space:]]*//' | cut -c1-100
|
|
171
|
+
return
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# PRIORITY 4: "Always X" (explicit rule)
|
|
175
|
+
rule=$(echo "$context" | grep -oiE "[Aa]lways [^.!?]+" | head -1)
|
|
176
|
+
if [ -n "$rule" ] && [ ${#rule} -gt 15 ]; then
|
|
177
|
+
echo "$rule" | sed 's/^[[:space:]]*//' | cut -c1-100
|
|
178
|
+
return
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
# PRIORITY 5: "Never X" (prohibition rule)
|
|
182
|
+
rule=$(echo "$context" | grep -oiE "[Nn]ever [^.!?]+" | head -1)
|
|
183
|
+
if [ -n "$rule" ] && [ ${#rule} -gt 15 ]; then
|
|
184
|
+
echo "$rule" | sed 's/^[[:space:]]*//' | cut -c1-100
|
|
185
|
+
return
|
|
186
|
+
fi
|
|
139
187
|
|
|
140
|
-
#
|
|
141
|
-
rule=$(echo "$context" | grep -oiE "
|
|
142
|
-
[ -n "$rule" ] &&
|
|
188
|
+
# PRIORITY 6: "In this project/codebase, use X"
|
|
189
|
+
rule=$(echo "$context" | grep -oiE "[Ii]n this (project|codebase)[^.!?]+" | head -1)
|
|
190
|
+
if [ -n "$rule" ] && [ ${#rule} -gt 15 ]; then
|
|
191
|
+
echo "$rule" | sed 's/^[[:space:]]*//' | cut -c1-100
|
|
192
|
+
return
|
|
193
|
+
fi
|
|
143
194
|
|
|
144
|
-
#
|
|
145
|
-
rule=$(echo "$context" | grep -oiE "
|
|
146
|
-
[ -n "$rule" ] &&
|
|
195
|
+
# PRIORITY 7: "prefer X" or "use X not Y"
|
|
196
|
+
rule=$(echo "$context" | grep -oiE "(prefer|use) [^.!?]+ (not|instead|over) [^.!?]+" | head -1)
|
|
197
|
+
if [ -n "$rule" ] && [ ${#rule} -gt 15 ]; then
|
|
198
|
+
echo "$rule" | sed 's/^[[:space:]]*//' | cut -c1-100
|
|
199
|
+
return
|
|
200
|
+
fi
|
|
147
201
|
|
|
148
|
-
# Fallback: return cleaned context
|
|
149
|
-
echo "$context" | cut -c1-100
|
|
202
|
+
# Fallback: return cleaned context (last resort, but apply quality filter in add_rule)
|
|
203
|
+
echo "$context" | sed 's/^[[:space:]]*//;s/^[Uu]ser:[[:space:]]*//' | cut -c1-100
|
|
150
204
|
}
|
|
151
205
|
|
|
152
206
|
# Detect high-value signals in transcript
|
|
@@ -205,25 +259,51 @@ detect_signals() {
|
|
|
205
259
|
# DEDUPLICATION
|
|
206
260
|
# ============================================================================
|
|
207
261
|
|
|
208
|
-
# Check if a similar rule already exists
|
|
262
|
+
# Check if a similar rule already exists
|
|
263
|
+
# STRICT DEDUPLICATION: Exact substring match OR high keyword overlap
|
|
209
264
|
rule_exists() {
|
|
210
265
|
local file="$1"
|
|
211
266
|
local new_rule="$2"
|
|
212
267
|
|
|
213
268
|
[ ! -f "$file" ] && return 1
|
|
214
269
|
|
|
215
|
-
#
|
|
216
|
-
local
|
|
270
|
+
# Normalize rule for comparison (lowercase, trim, collapse spaces)
|
|
271
|
+
local normalized=$(echo "$new_rule" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/[[:space:]]\+/ /g')
|
|
217
272
|
|
|
218
|
-
#
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
273
|
+
# STRICT CHECK 1: Exact substring match (catches same rule with different prefix)
|
|
274
|
+
if grep -qi "$normalized" "$file" 2>/dev/null; then
|
|
275
|
+
return 0
|
|
276
|
+
fi
|
|
277
|
+
|
|
278
|
+
# STRICT CHECK 2: Rule already contains the key phrase
|
|
279
|
+
# Extract core action phrase (verb + object)
|
|
280
|
+
local core_phrase=$(echo "$normalized" | grep -oE "(use|prefer|always|never) [a-z]+( [a-z]+)?" | head -1)
|
|
281
|
+
if [ -n "$core_phrase" ] && grep -qi "$core_phrase" "$file" 2>/dev/null; then
|
|
282
|
+
return 0
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
# STRICT CHECK 3: High keyword overlap (at least 50% of words must match a single existing rule)
|
|
286
|
+
local keywords=$(echo "$normalized" | grep -oE '\b[a-z]{4,}\b' | sort -u | tr '\n' ' ')
|
|
287
|
+
local keyword_count=$(echo "$keywords" | wc -w | tr -d ' ')
|
|
288
|
+
|
|
289
|
+
# For each existing rule, check overlap
|
|
290
|
+
while IFS= read -r existing_rule; do
|
|
291
|
+
[ -z "$existing_rule" ] && continue
|
|
292
|
+
local existing_lower=$(echo "$existing_rule" | tr '[:upper:]' '[:lower:]')
|
|
293
|
+
local match_count=0
|
|
294
|
+
|
|
295
|
+
for kw in $keywords; do
|
|
296
|
+
if echo "$existing_lower" | grep -q "\b$kw\b" 2>/dev/null; then
|
|
297
|
+
match_count=$((match_count + 1))
|
|
298
|
+
fi
|
|
299
|
+
done
|
|
300
|
+
|
|
301
|
+
# If more than 50% of keywords match ONE existing rule, it's a duplicate
|
|
302
|
+
local threshold=$((keyword_count / 2))
|
|
303
|
+
[ "$threshold" -lt 2 ] && threshold=2
|
|
304
|
+
[ "$match_count" -ge "$threshold" ] && return 0
|
|
305
|
+
done < <(grep "^- " "$file" 2>/dev/null)
|
|
225
306
|
|
|
226
|
-
[ "$match_count" -ge 3 ] && return 0
|
|
227
307
|
return 1
|
|
228
308
|
}
|
|
229
309
|
|
|
@@ -236,13 +316,16 @@ categorize() {
|
|
|
236
316
|
local lower=$(echo "$text" | tr '[:upper:]' '[:lower:]')
|
|
237
317
|
|
|
238
318
|
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" ;;
|
|
319
|
+
*component*|*button*|*ui*|*style*|*css*|*react*|*vue*|*html*|*tailwind*) echo "frontend" ;;
|
|
320
|
+
*api*|*endpoint*|*route*|*rest*|*graphql*|*server*|*backend*) echo "backend" ;;
|
|
321
|
+
*test*|*spec*|*mock*|*assert*|*expect*|*vitest*|*jest*|*playwright*) echo "testing" ;;
|
|
322
|
+
*deploy*|*docker*|*k8s*|*ci*|*terraform*|*aws*|*gcp*|*azure*) echo "devops" ;;
|
|
323
|
+
*auth*|*security*|*token*|*password*|*secret*|*encryption*) echo "security" ;;
|
|
324
|
+
*query*|*database*|*sql*|*schema*|*table*|*prisma*|*drizzle*) echo "database" ;;
|
|
325
|
+
*file*|*path*|*import*|*export*|*module*|*require*) echo "structure" ;;
|
|
326
|
+
*logger*|*log*|*console*|*debug*|*error*|*warn*) echo "logging" ;;
|
|
327
|
+
*type*|*interface*|*typescript*|*generic*|*enum*) echo "types" ;;
|
|
328
|
+
*git*|*commit*|*branch*|*merge*|*rebase*) echo "git" ;;
|
|
246
329
|
*) echo "general" ;;
|
|
247
330
|
esac
|
|
248
331
|
}
|
|
@@ -273,14 +356,42 @@ add_rule() {
|
|
|
273
356
|
|
|
274
357
|
local file=$(ensure_memory_file "$category")
|
|
275
358
|
|
|
359
|
+
# Clean and format FIRST
|
|
360
|
+
# Remove "User:" prefix, extra whitespace, and limit length
|
|
361
|
+
local clean=$(echo "$rule" | tr -d '\n\r' | sed 's/ */ /g' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sed 's/^[Uu]ser:[[:space:]]*//' | cut -c1-100)
|
|
362
|
+
|
|
363
|
+
# QUALITY GATE 1: Minimum length (too short = useless)
|
|
364
|
+
if [ ${#clean} -lt 15 ]; then
|
|
365
|
+
log "info" "Skipped too short: $clean"
|
|
366
|
+
return 1
|
|
367
|
+
fi
|
|
368
|
+
|
|
369
|
+
# QUALITY GATE 2: Must contain actionable verb
|
|
370
|
+
if ! echo "$clean" | grep -qiE "(use|prefer|always|never|should|avoid|don't|must|do not)"; then
|
|
371
|
+
log "info" "Skipped non-actionable: $clean"
|
|
372
|
+
return 1
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
# QUALITY GATE 3: Must have specific subject (not just a verb phrase)
|
|
376
|
+
local word_count=$(echo "$clean" | wc -w | tr -d ' ')
|
|
377
|
+
if [ "$word_count" -lt 3 ]; then
|
|
378
|
+
log "info" "Skipped too vague: $clean"
|
|
379
|
+
return 1
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
# QUALITY GATE 4: Skip documentation/JSON artifacts
|
|
383
|
+
# These patterns indicate corrupted data from parsing docs or code
|
|
384
|
+
if echo "$clean" | grep -qE '\\n|\\"|":|→|```|\*\*|sessionId|userType|"cwd"'; then
|
|
385
|
+
log "info" "Skipped doc/JSON artifact: $clean"
|
|
386
|
+
return 1
|
|
387
|
+
fi
|
|
388
|
+
|
|
276
389
|
# Skip if similar rule exists
|
|
277
|
-
if rule_exists "$file" "$
|
|
278
|
-
log "info" "Skipped duplicate: $
|
|
390
|
+
if rule_exists "$file" "$clean"; then
|
|
391
|
+
log "info" "Skipped duplicate: $clean"
|
|
279
392
|
return 1
|
|
280
393
|
fi
|
|
281
394
|
|
|
282
|
-
# Clean and format
|
|
283
|
-
local clean=$(echo "$rule" | tr -d '\n\r' | sed 's/ */ /g' | cut -c1-100)
|
|
284
395
|
local marker="→"
|
|
285
396
|
[ "$type" = "CORRECTION" ] && marker="✗→✓"
|
|
286
397
|
|
|
@@ -17,8 +17,8 @@ Smart merge for CLAUDE.md and AGENTS.md instruction files.
|
|
|
17
17
|
|
|
18
18
|
## When to Use
|
|
19
19
|
|
|
20
|
-
- After running `
|
|
21
|
-
- After upgrading SpecWeave version (`npm update specweave`)
|
|
20
|
+
- After running `specweave refresh-marketplace`
|
|
21
|
+
- After upgrading SpecWeave version (`npm update -g specweave`)
|
|
22
22
|
- When CLAUDE.md or AGENTS.md seem outdated
|
|
23
23
|
- To sync instruction files with latest framework features
|
|
24
24
|
|