specweave 1.0.300 → 1.0.301
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/dist/src/cli/commands/auto.js +1 -0
- package/dist/src/cli/commands/auto.js.map +1 -1
- package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts +8 -27
- package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts.map +1 -1
- package/dist/src/core/lazy-loading/llm-plugin-detector.js +12 -90
- package/dist/src/core/lazy-loading/llm-plugin-detector.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/lib/score-increment.sh +87 -0
- package/plugins/specweave/hooks/stop-auto-v5.sh +55 -9
- package/plugins/specweave/hooks/tests/test-auto-context-integration.sh +126 -0
- package/plugins/specweave/hooks/tests/test-stop-auto-enriched.sh +128 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +89 -150
- package/plugins/specweave/scripts/setup-auto.sh +58 -4
- package/plugins/specweave/scripts/tests/test-setup-auto-selection.sh +74 -0
- package/plugins/specweave/scripts/tests/test-setup-auto-usergoal.sh +83 -0
- package/plugins/specweave/skills/auto/SKILL.md +3 -1
- package/plugins/specweave/skills/do/SKILL.md +11 -0
- package/plugins/specweave-jira/skills/jira-mapper/SKILL.md +13 -14
- package/plugins/specweave-jira/skills/jira-resource-validator/SKILL.md +74 -4
- package/plugins/specweave-jira/skills/jira-sync/SKILL.md +18 -27
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Integration test: scored selection + enriched feedback (T-012, AC-US1-01, AC-US2-01, AC-US3-01)
|
|
3
|
+
# Creates a mock .specweave/ project, scores increments, and verifies enrichment helpers.
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
SCORE_SCRIPT="$SCRIPT_DIR/../lib/score-increment.sh"
|
|
9
|
+
STOP_HOOK="$SCRIPT_DIR/../stop-auto-v5.sh"
|
|
10
|
+
TMPDIR_ROOT=$(mktemp -d)
|
|
11
|
+
trap 'rm -rf "$TMPDIR_ROOT"' EXIT
|
|
12
|
+
|
|
13
|
+
PASS=0; FAIL=0
|
|
14
|
+
|
|
15
|
+
assert_eq() {
|
|
16
|
+
if [ "$1" = "$2" ]; then
|
|
17
|
+
echo " ✓ $3"; PASS=$((PASS+1))
|
|
18
|
+
else
|
|
19
|
+
echo " ✗ $3 (expected '$2', got '$1')"; FAIL=$((FAIL+1))
|
|
20
|
+
fi
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
assert_gt() {
|
|
24
|
+
if [ "$1" -gt "$2" ]; then
|
|
25
|
+
echo " ✓ $3 ($1 > $2)"; PASS=$((PASS+1))
|
|
26
|
+
else
|
|
27
|
+
echo " ✗ $3 (expected $1 > $2)"; FAIL=$((FAIL+1))
|
|
28
|
+
fi
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
assert_contains() {
|
|
32
|
+
if echo "$1" | grep -q "$2"; then
|
|
33
|
+
echo " ✓ $3"; PASS=$((PASS+1))
|
|
34
|
+
else
|
|
35
|
+
echo " ✗ $3 (expected '$1' to contain '$2')"; FAIL=$((FAIL+1))
|
|
36
|
+
fi
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
echo "Integration: scored selection + enriched feedback"
|
|
40
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
41
|
+
|
|
42
|
+
# Setup mock .specweave/ structure
|
|
43
|
+
SW="$TMPDIR_ROOT/.specweave"
|
|
44
|
+
INC_DIR="$SW/increments"
|
|
45
|
+
STATE_DIR="$SW/state"
|
|
46
|
+
LOGS_DIR="$SW/logs"
|
|
47
|
+
mkdir -p "$INC_DIR/0100-user-auth" "$INC_DIR/0101-deploy-pipeline" "$STATE_DIR" "$LOGS_DIR"
|
|
48
|
+
|
|
49
|
+
# Create increment 0100: auth
|
|
50
|
+
echo '{"title":"User Authentication Feature","status":"active","lastActivity":"2026-02-18T10:00:00Z"}' \
|
|
51
|
+
> "$INC_DIR/0100-user-auth/metadata.json"
|
|
52
|
+
cat > "$INC_DIR/0100-user-auth/spec.md" << 'EOF'
|
|
53
|
+
# User Authentication Feature
|
|
54
|
+
|
|
55
|
+
Implement login, signup, and OAuth for user authentication.
|
|
56
|
+
EOF
|
|
57
|
+
cat > "$INC_DIR/0100-user-auth/tasks.md" << 'EOF'
|
|
58
|
+
### T-001: Add login endpoint
|
|
59
|
+
**Status**: [x] completed
|
|
60
|
+
### T-002: Add signup endpoint
|
|
61
|
+
**Status**: [x] completed
|
|
62
|
+
### T-003: OAuth integration
|
|
63
|
+
**Status**: [ ] pending
|
|
64
|
+
### T-004: Session management
|
|
65
|
+
**Status**: [ ] pending
|
|
66
|
+
EOF
|
|
67
|
+
|
|
68
|
+
# Create increment 0101: deploy
|
|
69
|
+
echo '{"title":"CI/CD Deploy Pipeline","status":"active","lastActivity":"2026-02-17T10:00:00Z"}' \
|
|
70
|
+
> "$INC_DIR/0101-deploy-pipeline/metadata.json"
|
|
71
|
+
cat > "$INC_DIR/0101-deploy-pipeline/spec.md" << 'EOF'
|
|
72
|
+
# CI/CD Deploy Pipeline
|
|
73
|
+
|
|
74
|
+
Setup continuous integration and deployment pipeline with Docker and GitHub Actions.
|
|
75
|
+
EOF
|
|
76
|
+
cat > "$INC_DIR/0101-deploy-pipeline/tasks.md" << 'EOF'
|
|
77
|
+
### T-001: Create Dockerfile
|
|
78
|
+
**Status**: [x] completed
|
|
79
|
+
### T-002: Configure GitHub Actions
|
|
80
|
+
**Status**: [ ] pending
|
|
81
|
+
### T-003: Set up staging environment
|
|
82
|
+
**Status**: [ ] pending
|
|
83
|
+
EOF
|
|
84
|
+
|
|
85
|
+
# TC-015: Scored selection — auth scores higher for "fix authentication"
|
|
86
|
+
s_auth=$(bash "$SCORE_SCRIPT" "$INC_DIR/0100-user-auth" "fix authentication" 2>/dev/null)
|
|
87
|
+
s_deploy=$(bash "$SCORE_SCRIPT" "$INC_DIR/0101-deploy-pipeline" "fix authentication" 2>/dev/null)
|
|
88
|
+
assert_gt "$s_auth" "$s_deploy" "TC-015a: auth scores higher than deploy for auth prompt"
|
|
89
|
+
|
|
90
|
+
# TC-015b: Deploy scores higher for "deploy pipeline docker"
|
|
91
|
+
s_auth2=$(bash "$SCORE_SCRIPT" "$INC_DIR/0100-user-auth" "deploy pipeline docker" 2>/dev/null)
|
|
92
|
+
s_deploy2=$(bash "$SCORE_SCRIPT" "$INC_DIR/0101-deploy-pipeline" "deploy pipeline docker" 2>/dev/null)
|
|
93
|
+
assert_gt "$s_deploy2" "$s_auth2" "TC-015b: deploy scores higher for deploy prompt"
|
|
94
|
+
|
|
95
|
+
# TC-015c: userGoal wiring — verify logic matches setup-auto.sh
|
|
96
|
+
AUTO_MODE_FILE="$STATE_DIR/auto-mode.json"
|
|
97
|
+
PROMPT="fix authentication"
|
|
98
|
+
if [ -f "$AUTO_MODE_FILE" ]; then
|
|
99
|
+
if [ -n "$PROMPT" ]; then
|
|
100
|
+
_UPDATED=$(jq --arg g "$PROMPT" '.userGoal = $g' "$AUTO_MODE_FILE" 2>/dev/null)
|
|
101
|
+
else
|
|
102
|
+
_UPDATED=$(jq '.userGoal = null' "$AUTO_MODE_FILE" 2>/dev/null)
|
|
103
|
+
fi
|
|
104
|
+
[ -n "$_UPDATED" ] && echo "$_UPDATED" > "$AUTO_MODE_FILE"
|
|
105
|
+
elif [ -n "$PROMPT" ]; then
|
|
106
|
+
jq -n --arg g "$PROMPT" '{"active":false,"userGoal":$g}' > "$AUTO_MODE_FILE"
|
|
107
|
+
fi
|
|
108
|
+
goal=$(jq -r '.userGoal' "$AUTO_MODE_FILE")
|
|
109
|
+
assert_eq "$goal" "fix authentication" "TC-015c: userGoal written to auto-mode.json"
|
|
110
|
+
|
|
111
|
+
# TC-015d: Stop hook enrichment — source hook, test helpers
|
|
112
|
+
export PROJECT_ROOT="$TMPDIR_ROOT"
|
|
113
|
+
export __STOP_AUTO_V5_SOURCED=1
|
|
114
|
+
source "$STOP_HOOK" 2>/dev/null
|
|
115
|
+
|
|
116
|
+
next_auth=$(get_next_task_title "$INC_DIR/0100-user-auth/tasks.md")
|
|
117
|
+
assert_eq "$next_auth" "OAuth integration" "TC-015d: enriched next task from auth increment"
|
|
118
|
+
|
|
119
|
+
done_auth=$(count_completed_tasks "$INC_DIR/0100-user-auth/tasks.md")
|
|
120
|
+
pending_auth=$(count_pending_tasks "$INC_DIR/0100-user-auth/tasks.md")
|
|
121
|
+
assert_eq "$done_auth" "2" "TC-015e: auth increment done count = 2"
|
|
122
|
+
assert_eq "$pending_auth" "2" "TC-015f: auth increment pending count = 2"
|
|
123
|
+
|
|
124
|
+
echo ""
|
|
125
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
126
|
+
[ "$FAIL" -eq 0 ] || exit 1
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tests for enriched stop hook feedback (AC-US3-01, AC-US3-02, AC-US3-03, AC-US4-01, AC-US4-03)
|
|
3
|
+
# Sources stop-auto-v5.sh to load helper functions only.
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
STOP_HOOK="$SCRIPT_DIR/../stop-auto-v5.sh"
|
|
9
|
+
TMPDIR_ROOT=$(mktemp -d)
|
|
10
|
+
trap 'rm -rf "$TMPDIR_ROOT"' EXIT
|
|
11
|
+
|
|
12
|
+
PASS=0; FAIL=0
|
|
13
|
+
|
|
14
|
+
assert_eq() {
|
|
15
|
+
if [ "$1" = "$2" ]; then
|
|
16
|
+
echo " ✓ $3"; PASS=$((PASS+1))
|
|
17
|
+
else
|
|
18
|
+
echo " ✗ $3 (expected '$2', got '$1')"; FAIL=$((FAIL+1))
|
|
19
|
+
fi
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
assert_ge() {
|
|
23
|
+
if [ "$1" -ge "$2" ]; then
|
|
24
|
+
echo " ✓ $3 ($1 >= $2)"; PASS=$((PASS+1))
|
|
25
|
+
else
|
|
26
|
+
echo " ✗ $3 (expected >= $2, got $1)"; FAIL=$((FAIL+1))
|
|
27
|
+
fi
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Set up minimal .specweave structure so sourcing succeeds
|
|
31
|
+
export PROJECT_ROOT="$TMPDIR_ROOT"
|
|
32
|
+
mkdir -p "$TMPDIR_ROOT/.specweave/logs" "$TMPDIR_ROOT/.specweave/state" "$TMPDIR_ROOT/.specweave/increments"
|
|
33
|
+
|
|
34
|
+
# Source only (loads helper functions, skips main execution)
|
|
35
|
+
export __STOP_AUTO_V5_SOURCED=1
|
|
36
|
+
source "$STOP_HOOK" 2>/dev/null
|
|
37
|
+
|
|
38
|
+
echo "stop-auto-v5.sh enrichment function tests"
|
|
39
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
40
|
+
|
|
41
|
+
# Create a tasks.md with known counts
|
|
42
|
+
TASKS_FILE="$TMPDIR_ROOT/tasks.md"
|
|
43
|
+
cat > "$TASKS_FILE" << 'EOF'
|
|
44
|
+
### T-001: Setup database
|
|
45
|
+
**User Story**: US-001 | **Status**: [x] completed
|
|
46
|
+
|
|
47
|
+
### T-002: Implement login
|
|
48
|
+
**User Story**: US-001 | **Status**: [x] completed
|
|
49
|
+
|
|
50
|
+
### T-003: Add unit tests
|
|
51
|
+
**User Story**: US-001 | **Status**: [x] completed
|
|
52
|
+
|
|
53
|
+
### T-004: Write integration tests
|
|
54
|
+
**User Story**: US-001 | **Status**: [x] completed
|
|
55
|
+
|
|
56
|
+
### T-005: Deploy to staging
|
|
57
|
+
**User Story**: US-001 | **Status**: [x] completed
|
|
58
|
+
|
|
59
|
+
### T-006: Configure auth middleware
|
|
60
|
+
**User Story**: US-001 | **Status**: [ ] pending
|
|
61
|
+
|
|
62
|
+
### T-007: Add error handling
|
|
63
|
+
**User Story**: US-001 | **Status**: [ ] pending
|
|
64
|
+
|
|
65
|
+
### T-008: Final review and cleanup
|
|
66
|
+
**User Story**: US-001 | **Status**: [ ] pending
|
|
67
|
+
EOF
|
|
68
|
+
|
|
69
|
+
# TC-010: Block message includes next task title (AC-US3-01)
|
|
70
|
+
result=$(get_next_task_title "$TASKS_FILE")
|
|
71
|
+
assert_eq "$result" "Configure auth middleware" "TC-010: get_next_task_title returns first pending task"
|
|
72
|
+
|
|
73
|
+
# TC-011: count_completed_tasks counts [x] entries
|
|
74
|
+
done_count=$(count_completed_tasks "$TASKS_FILE")
|
|
75
|
+
assert_eq "$done_count" "5" "TC-011: count_completed_tasks = 5"
|
|
76
|
+
|
|
77
|
+
# TC-012: count_pending_tasks counts [ ] entries (AC-US3-03)
|
|
78
|
+
pending_count=$(count_pending_tasks "$TASKS_FILE")
|
|
79
|
+
assert_eq "$pending_count" "3" "TC-012: count_pending_tasks = 3"
|
|
80
|
+
|
|
81
|
+
# TC-012b: Progress fraction calculation
|
|
82
|
+
total=$((done_count + pending_count))
|
|
83
|
+
assert_eq "$total" "8" "TC-012b: total = done + pending = 8"
|
|
84
|
+
|
|
85
|
+
# TC-013: Empty tasks file → zeros
|
|
86
|
+
EMPTY_FILE="$TMPDIR_ROOT/empty.md"
|
|
87
|
+
touch "$EMPTY_FILE"
|
|
88
|
+
done_empty=$(count_completed_tasks "$EMPTY_FILE")
|
|
89
|
+
pending_empty=$(count_pending_tasks "$EMPTY_FILE")
|
|
90
|
+
assert_eq "$done_empty" "0" "TC-013: empty file → 0 completed"
|
|
91
|
+
assert_eq "$pending_empty" "0" "TC-013b: empty file → 0 pending"
|
|
92
|
+
|
|
93
|
+
# TC-013c: get_next_task_title on empty file → empty string
|
|
94
|
+
next_empty=$(get_next_task_title "$EMPTY_FILE")
|
|
95
|
+
assert_eq "$next_empty" "" "TC-013c: empty file → no next title"
|
|
96
|
+
|
|
97
|
+
# TC-014: Missing file → zeros
|
|
98
|
+
done_missing=$(count_completed_tasks "/nonexistent/tasks.md")
|
|
99
|
+
assert_eq "$done_missing" "0" "TC-014: missing file → 0 completed"
|
|
100
|
+
|
|
101
|
+
# TC-015: get_next_task_title with all completed → empty
|
|
102
|
+
ALL_DONE_FILE="$TMPDIR_ROOT/all-done.md"
|
|
103
|
+
cat > "$ALL_DONE_FILE" << 'EOF'
|
|
104
|
+
### T-001: Done task one
|
|
105
|
+
**Status**: [x] completed
|
|
106
|
+
|
|
107
|
+
### T-002: Done task two
|
|
108
|
+
**Status**: [x] completed
|
|
109
|
+
EOF
|
|
110
|
+
next_done=$(get_next_task_title "$ALL_DONE_FILE")
|
|
111
|
+
assert_eq "$next_done" "" "TC-015: all done → no next title"
|
|
112
|
+
|
|
113
|
+
# TC-016: get_next_task_title with task without title pattern
|
|
114
|
+
MIXED_FILE="$TMPDIR_ROOT/mixed.md"
|
|
115
|
+
cat > "$MIXED_FILE" << 'EOF'
|
|
116
|
+
### T-001: First Task Title
|
|
117
|
+
**Status**: [x] completed
|
|
118
|
+
### T-002: Second Task Title
|
|
119
|
+
**Status**: [ ] pending
|
|
120
|
+
### T-003: Third Task Title
|
|
121
|
+
**Status**: [ ] pending
|
|
122
|
+
EOF
|
|
123
|
+
next_mixed=$(get_next_task_title "$MIXED_FILE")
|
|
124
|
+
assert_eq "$next_mixed" "Second Task Title" "TC-016: returns first pending task title"
|
|
125
|
+
|
|
126
|
+
echo ""
|
|
127
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
128
|
+
[ "$FAIL" -eq 0 ] || exit 1
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
# Purpose: Auto-load plugins, discipline validation, context injection, instant command execution
|
|
6
6
|
#
|
|
7
7
|
# FEATURES:
|
|
8
|
-
# - v1.0.
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
8
|
+
# - v1.0.279: VSKILL INSTALL - sw-* plugins installed via vskill at project scope.
|
|
9
|
+
# Uses install_plugin_via_vskill(): node <vskill-cli> install <specweave-dir>
|
|
10
|
+
# --plugin <name> --plugin-dir <dir> --force, run from SW_PROJECT_ROOT.
|
|
11
|
+
# Installs to .claude/commands/<name>/ (project scope). Never ~/.claude/commands/.
|
|
12
12
|
# - v1.0.201: LSP CLI FALLBACK INSTRUCTIONS - When LSP requested, instruct Claude to use
|
|
13
13
|
# `specweave lsp` commands instead of Grep. These use TsServerClient for REAL semantic
|
|
14
14
|
# analysis. Key fix: "find references" now gets semantic refs, not text matches!
|
|
@@ -266,61 +266,72 @@ fi
|
|
|
266
266
|
|
|
267
267
|
if [[ "$SCOPE_GUARD_RUN" == "true" ]] && command -v jq >/dev/null 2>&1 && command -v claude >/dev/null 2>&1; then
|
|
268
268
|
USER_SETTINGS="$HOME/.claude/settings.json"
|
|
269
|
+
PROJECT_SETTINGS="${SW_PROJECT_ROOT}/.claude/settings.json"
|
|
269
270
|
|
|
271
|
+
# ---- 1. Clean user-level settings ----
|
|
272
|
+
# Remove: sw-* domain plugins at user scope (should be project-scoped via vskill)
|
|
273
|
+
# any *@claude-plugins-official (never allowed)
|
|
274
|
+
# Exempt: sw@specweave (core plugin, user-scoped by design)
|
|
270
275
|
if [[ -f "$USER_SETTINGS" ]]; then
|
|
271
|
-
|
|
272
|
-
# Exempt: sw@specweave (core plugin, intentionally user-scoped)
|
|
273
|
-
POLLUTED_PLUGINS=$(jq -r '
|
|
276
|
+
POLLUTED_USER=$(jq -r '
|
|
274
277
|
.enabledPlugins // {} | to_entries[]
|
|
275
278
|
| select(
|
|
276
279
|
(.key | test("^sw-.*@specweave$")) or
|
|
277
|
-
(.key | test("-
|
|
280
|
+
(.key | test("@claude-plugins-official$"))
|
|
278
281
|
)
|
|
279
282
|
| .key
|
|
280
283
|
' "$USER_SETTINGS" 2>/dev/null)
|
|
281
284
|
|
|
282
|
-
if [[ -n "$
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
285
|
+
if [[ -n "$POLLUTED_USER" ]]; then
|
|
286
|
+
REMOVED=""
|
|
287
|
+
REINSTALLED=""
|
|
288
|
+
for plugin_key in $POLLUTED_USER; do
|
|
286
289
|
if timeout 5 claude plugin uninstall "$plugin_key" >/dev/null 2>&1; then
|
|
287
|
-
# v1.0.278: sw-* plugins reinstall via direct copy, LSP plugins via claude CLI
|
|
288
290
|
if [[ "$plugin_key" == sw-*@specweave ]]; then
|
|
289
|
-
#
|
|
291
|
+
# Reinstall sw-* at project scope via vskill
|
|
290
292
|
_sw_name="${plugin_key%%@*}"
|
|
291
|
-
if
|
|
292
|
-
[[ -n "$
|
|
293
|
-
|
|
294
|
-
fi
|
|
295
|
-
else
|
|
296
|
-
# LSP and other plugins: reinstall via claude CLI at project scope
|
|
297
|
-
if timeout 10 claude plugin install "$plugin_key" --scope project >/dev/null 2>&1; then
|
|
298
|
-
[[ -n "$MIGRATED" ]] && MIGRATED="$MIGRATED, "
|
|
299
|
-
MIGRATED="${MIGRATED}${plugin_key}"
|
|
293
|
+
if install_plugin_via_vskill "$_sw_name"; then
|
|
294
|
+
[[ -n "$REINSTALLED" ]] && REINSTALLED="$REINSTALLED, "
|
|
295
|
+
REINSTALLED="${REINSTALLED}${plugin_key}"
|
|
300
296
|
fi
|
|
301
297
|
fi
|
|
298
|
+
# *@claude-plugins-official: uninstall only — never reinstall
|
|
299
|
+
[[ -n "$REMOVED" ]] && REMOVED="$REMOVED, "
|
|
300
|
+
REMOVED="${REMOVED}${plugin_key}"
|
|
302
301
|
fi
|
|
303
302
|
done
|
|
304
303
|
|
|
305
|
-
if [[ -n "$
|
|
306
|
-
echo "[$(date -Iseconds)] scope-guard |
|
|
304
|
+
if [[ -n "$REMOVED" ]]; then
|
|
305
|
+
echo "[$(date -Iseconds)] scope-guard | removed user-level: $REMOVED | reinstalled@project: ${REINSTALLED:-none}" >> "$SW_PROJECT_ROOT/.specweave/state/hook.log" 2>/dev/null || true
|
|
307
306
|
fi
|
|
308
307
|
|
|
309
|
-
#
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
# Re-enable core plugin (preserves all other settings)
|
|
316
|
-
jq '.enabledPlugins."sw@specweave" = true' "$USER_SETTINGS" > "${USER_SETTINGS}.tmp" 2>/dev/null && \
|
|
317
|
-
mv "${USER_SETTINGS}.tmp" "$USER_SETTINGS" 2>/dev/null || true
|
|
318
|
-
echo "[$(date -Iseconds)] scope-guard | restored sw@specweave enabled state" >> "$SW_PROJECT_ROOT/.specweave/state/hook.log" 2>/dev/null || true
|
|
319
|
-
fi
|
|
308
|
+
# Restore sw@specweave core plugin enabled state (uninstall may collateral-damage it)
|
|
309
|
+
SW_ENABLED=$(jq -r '.enabledPlugins."sw@specweave" // "not_set"' "$USER_SETTINGS" 2>/dev/null)
|
|
310
|
+
if [[ "$SW_ENABLED" != "true" ]]; then
|
|
311
|
+
jq '.enabledPlugins."sw@specweave" = true' "$USER_SETTINGS" > "${USER_SETTINGS}.tmp" 2>/dev/null && \
|
|
312
|
+
mv "${USER_SETTINGS}.tmp" "$USER_SETTINGS" 2>/dev/null || true
|
|
313
|
+
echo "[$(date -Iseconds)] scope-guard | restored sw@specweave" >> "$SW_PROJECT_ROOT/.specweave/state/hook.log" 2>/dev/null || true
|
|
320
314
|
fi
|
|
321
315
|
fi
|
|
322
316
|
fi
|
|
323
317
|
|
|
318
|
+
# ---- 2. Clean project-level settings ----
|
|
319
|
+
# Remove any *@claude-plugins-official from project settings — never allowed
|
|
320
|
+
if [[ -f "$PROJECT_SETTINGS" ]]; then
|
|
321
|
+
POLLUTED_PROJECT=$(jq -r '
|
|
322
|
+
.enabledPlugins // {} | to_entries[]
|
|
323
|
+
| select(.key | test("@claude-plugins-official$"))
|
|
324
|
+
| .key
|
|
325
|
+
' "$PROJECT_SETTINGS" 2>/dev/null)
|
|
326
|
+
|
|
327
|
+
if [[ -n "$POLLUTED_PROJECT" ]]; then
|
|
328
|
+
for plugin_key in $POLLUTED_PROJECT; do
|
|
329
|
+
timeout 5 claude plugin uninstall "$plugin_key" >/dev/null 2>&1 || true
|
|
330
|
+
done
|
|
331
|
+
echo "[$(date -Iseconds)] scope-guard | removed project official plugins: $POLLUTED_PROJECT" >> "$SW_PROJECT_ROOT/.specweave/state/hook.log" 2>/dev/null || true
|
|
332
|
+
fi
|
|
333
|
+
fi
|
|
334
|
+
|
|
324
335
|
# Write today's marker
|
|
325
336
|
mkdir -p "$(dirname "$SCOPE_GUARD_MARKER")" 2>/dev/null
|
|
326
337
|
date +%Y-%m-%d > "$SCOPE_GUARD_MARKER" 2>/dev/null || true
|
|
@@ -472,85 +483,59 @@ check_plugin_in_vskill_lock() {
|
|
|
472
483
|
fi
|
|
473
484
|
}
|
|
474
485
|
|
|
475
|
-
# Helper: Install sw-* plugin via
|
|
476
|
-
#
|
|
477
|
-
#
|
|
486
|
+
# Helper: Install sw-* plugin via vskill (v1.0.279)
|
|
487
|
+
# Uses: node <vskill-cli> install <specweave-dir> --plugin <name> --plugin-dir <specweave-dir> --force
|
|
488
|
+
# Installs to project scope: ${SW_PROJECT_ROOT}/.claude/commands/<name>/
|
|
478
489
|
# Args: $1=plugin name (e.g., "sw-frontend")
|
|
479
490
|
# Returns: 0 if installed successfully, 1 if failed
|
|
480
491
|
# Sets VSKILL_INSTALL_OUTPUT with status message
|
|
481
|
-
|
|
492
|
+
install_plugin_via_vskill() {
|
|
482
493
|
local plugin="$1"
|
|
483
|
-
local
|
|
484
|
-
local
|
|
485
|
-
|
|
486
|
-
#
|
|
487
|
-
|
|
488
|
-
|
|
494
|
+
local specweave_dir="${HOME}/.claude/plugins/marketplaces/specweave"
|
|
495
|
+
local project_dir="${SW_PROJECT_ROOT:-$PWD}"
|
|
496
|
+
|
|
497
|
+
# Find node binary
|
|
498
|
+
local node_bin
|
|
499
|
+
node_bin=$(command -v node 2>/dev/null)
|
|
500
|
+
if [[ -z "$node_bin" ]]; then
|
|
501
|
+
VSKILL_INSTALL_OUTPUT="node not found in PATH"
|
|
489
502
|
return 1
|
|
490
503
|
fi
|
|
491
504
|
|
|
492
|
-
#
|
|
493
|
-
local
|
|
494
|
-
if command -v
|
|
495
|
-
|
|
505
|
+
# Find vskill CLI — check global first, then bundled with specweave
|
|
506
|
+
local vskill_cli=""
|
|
507
|
+
if command -v vskill >/dev/null 2>&1; then
|
|
508
|
+
vskill_cli=$(command -v vskill)
|
|
496
509
|
else
|
|
497
|
-
#
|
|
498
|
-
|
|
510
|
+
# Look for vskill bundled alongside specweave in node_modules
|
|
511
|
+
local candidate="${specweave_dir}/../../../node_modules/.bin/vskill"
|
|
512
|
+
if [[ -f "$candidate" ]]; then
|
|
513
|
+
vskill_cli="$candidate"
|
|
514
|
+
fi
|
|
499
515
|
fi
|
|
500
516
|
|
|
501
|
-
if [[ -z "$
|
|
502
|
-
VSKILL_INSTALL_OUTPUT="
|
|
517
|
+
if [[ -z "$vskill_cli" ]]; then
|
|
518
|
+
VSKILL_INSTALL_OUTPUT="vskill not found — install with: npm install -g vskill"
|
|
503
519
|
return 1
|
|
504
520
|
fi
|
|
505
521
|
|
|
506
|
-
#
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
VSKILL_INSTALL_OUTPUT="source dir not found: $source_dir"
|
|
522
|
+
# Verify specweave marketplace dir exists
|
|
523
|
+
if [[ ! -d "$specweave_dir" ]]; then
|
|
524
|
+
VSKILL_INSTALL_OUTPUT="specweave marketplace dir not found at $specweave_dir"
|
|
510
525
|
return 1
|
|
511
526
|
fi
|
|
512
527
|
|
|
513
|
-
#
|
|
514
|
-
local
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
find "$target_dir" -name "*.sh" -exec chmod 755 {} \; 2>/dev/null || true
|
|
519
|
-
|
|
520
|
-
# Write lockfile entry so subsequent prompts skip re-install
|
|
521
|
-
local lock_dir="${SW_PROJECT_ROOT:-$PWD}"
|
|
522
|
-
local lockfile="$lock_dir/vskill.lock"
|
|
523
|
-
local now
|
|
524
|
-
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -Iseconds 2>/dev/null)
|
|
525
|
-
|
|
526
|
-
if command -v jq >/dev/null 2>&1 && [[ -n "$lock_dir" ]]; then
|
|
527
|
-
local skill_entry
|
|
528
|
-
skill_entry=$(jq -n \
|
|
529
|
-
--arg ver "0.0.0" \
|
|
530
|
-
--arg now "$now" \
|
|
531
|
-
'{version: $ver, sha: "", tier: "BUNDLED", installedAt: $now, source: "local:specweave"}')
|
|
532
|
-
|
|
533
|
-
if [[ -f "$lockfile" ]]; then
|
|
534
|
-
# Merge into existing lockfile
|
|
535
|
-
jq --arg key "$plugin" --argjson entry "$skill_entry" --arg now "$now" \
|
|
536
|
-
'.skills[$key] = $entry | .updatedAt = $now' \
|
|
537
|
-
"$lockfile" > "${lockfile}.tmp" 2>/dev/null && \
|
|
538
|
-
mv "${lockfile}.tmp" "$lockfile" 2>/dev/null || true
|
|
539
|
-
else
|
|
540
|
-
# Create new lockfile
|
|
541
|
-
jq -n \
|
|
542
|
-
--arg now "$now" \
|
|
543
|
-
--arg key "$plugin" \
|
|
544
|
-
--argjson entry "$skill_entry" \
|
|
545
|
-
'{version: 1, agents: ["claude-code"], skills: {($key): $entry}, createdAt: $now, updatedAt: $now}' \
|
|
546
|
-
> "$lockfile" 2>/dev/null || true
|
|
547
|
-
fi
|
|
548
|
-
fi
|
|
528
|
+
# Run vskill install at project scope (cwd = project root)
|
|
529
|
+
local output
|
|
530
|
+
output=$(cd "$project_dir" && "$node_bin" "$vskill_cli" install "$specweave_dir" \
|
|
531
|
+
--plugin "$plugin" --plugin-dir "$specweave_dir" --force 2>&1)
|
|
532
|
+
local exit_code=$?
|
|
549
533
|
|
|
550
|
-
|
|
534
|
+
if [[ $exit_code -eq 0 ]]; then
|
|
535
|
+
VSKILL_INSTALL_OUTPUT="installed $plugin via vskill (project scope)"
|
|
551
536
|
return 0
|
|
552
537
|
else
|
|
553
|
-
VSKILL_INSTALL_OUTPUT="
|
|
538
|
+
VSKILL_INSTALL_OUTPUT="vskill install failed for $plugin: $output"
|
|
554
539
|
return 1
|
|
555
540
|
fi
|
|
556
541
|
}
|
|
@@ -1270,13 +1255,10 @@ if [[ "${SPECWEAVE_DISABLE_AUTO_LOAD:-0}" != "1" ]] && [[ "${SPECWEAVE_DISABLE_H
|
|
|
1270
1255
|
for plugin in $DETECTED_PLUGINS; do
|
|
1271
1256
|
[[ -z "$plugin" ]] && continue
|
|
1272
1257
|
|
|
1273
|
-
# v1.0.
|
|
1274
|
-
#
|
|
1275
|
-
# v1.0.240 (0198): context7/playwright removed from auto-install
|
|
1276
|
-
# v1.0.278 (0241): sw-* plugins now installed via direct copy (no vskill dependency)
|
|
1258
|
+
# v1.0.279: Only @specweave plugins allowed — no claude-plugins-official
|
|
1259
|
+
# Install via vskill (project scope: .claude/commands/<name>/)
|
|
1277
1260
|
if [[ "$plugin" == sw-* ]] || [[ "$plugin" == "sw" ]]; then
|
|
1278
|
-
#
|
|
1279
|
-
# Fast-path: check vskill.lock first (no CLI invocation needed)
|
|
1261
|
+
# Fast-path: check vskill.lock first
|
|
1280
1262
|
if check_plugin_in_vskill_lock "$plugin"; then
|
|
1281
1263
|
[[ -n "$PLUGINS_ALREADY" ]] && PLUGINS_ALREADY="$PLUGINS_ALREADY, "
|
|
1282
1264
|
PLUGINS_ALREADY="${PLUGINS_ALREADY}${plugin}"
|
|
@@ -1285,61 +1267,18 @@ if [[ "${SPECWEAVE_DISABLE_AUTO_LOAD:-0}" != "1" ]] && [[ "${SPECWEAVE_DISABLE_H
|
|
|
1285
1267
|
[[ -n "$PLUGINS_ALREADY" ]] && PLUGINS_ALREADY="$PLUGINS_ALREADY, "
|
|
1286
1268
|
PLUGINS_ALREADY="${PLUGINS_ALREADY}${plugin}"
|
|
1287
1269
|
else
|
|
1288
|
-
#
|
|
1289
|
-
if
|
|
1270
|
+
# Install via vskill at project scope
|
|
1271
|
+
if install_plugin_via_vskill "$plugin"; then
|
|
1290
1272
|
[[ -n "$PLUGINS_INSTALLED" ]] && PLUGINS_INSTALLED="$PLUGINS_INSTALLED, "
|
|
1291
1273
|
PLUGINS_INSTALLED="${PLUGINS_INSTALLED}${plugin}"
|
|
1292
|
-
|
|
1293
|
-
# Display scan result if available
|
|
1294
|
-
if [[ -n "$VSKILL_INSTALL_OUTPUT" ]]; then
|
|
1295
|
-
SCAN_RESULT=$(echo "$VSKILL_INSTALL_OUTPUT" | grep -oE "Score:[[:space:]]*[0-9]+/100[[:space:]]*Verdict:[[:space:]]*[A-Z]+" || true)
|
|
1296
|
-
[[ -n "$SCAN_RESULT" ]] && echo "[$(date -Iseconds)] vskill | ${plugin} | ${SCAN_RESULT}" >> "$LAZY_LOAD_LOG"
|
|
1297
|
-
fi
|
|
1274
|
+
echo "[$(date -Iseconds)] vskill | ${plugin} | ${VSKILL_INSTALL_OUTPUT:-ok}" >> "$LAZY_LOAD_LOG"
|
|
1298
1275
|
else
|
|
1299
1276
|
echo "[$(date -Iseconds)] vskill | ${plugin} | FAILED: ${VSKILL_INSTALL_OUTPUT:-unknown}" >> "$LAZY_LOAD_LOG"
|
|
1300
1277
|
fi
|
|
1301
1278
|
fi
|
|
1302
1279
|
else
|
|
1303
|
-
#
|
|
1304
|
-
|
|
1305
|
-
PLUGIN_SCOPE="$DEFAULT_PLUGIN_SCOPE"
|
|
1306
|
-
FULL_PLUGIN_NAME="${plugin}@${MARKETPLACE}"
|
|
1307
|
-
ALREADY_INSTALLED=false
|
|
1308
|
-
|
|
1309
|
-
if check_plugin_installed_from_json "$plugin" "$MARKETPLACE"; then
|
|
1310
|
-
ALREADY_INSTALLED=true
|
|
1311
|
-
else
|
|
1312
|
-
CURRENT_PLUGINS=""
|
|
1313
|
-
if command -v timeout >/dev/null 2>&1; then
|
|
1314
|
-
CURRENT_PLUGINS=$(timeout 10 claude plugin list 2>/dev/null | grep -E "^ ❯ " | sed 's/^ ❯ //' || true)
|
|
1315
|
-
else
|
|
1316
|
-
CURRENT_PLUGINS=$(claude plugin list 2>/dev/null | grep -E "^ ❯ " | sed 's/^ ❯ //' || true)
|
|
1317
|
-
fi
|
|
1318
|
-
if echo "$CURRENT_PLUGINS" | grep -q "^${FULL_PLUGIN_NAME}$"; then
|
|
1319
|
-
ALREADY_INSTALLED=true
|
|
1320
|
-
fi
|
|
1321
|
-
fi
|
|
1322
|
-
|
|
1323
|
-
if [[ "$ALREADY_INSTALLED" == "true" ]]; then
|
|
1324
|
-
[[ -n "$PLUGINS_ALREADY" ]] && PLUGINS_ALREADY="$PLUGINS_ALREADY, "
|
|
1325
|
-
PLUGINS_ALREADY="${PLUGINS_ALREADY}${plugin}"
|
|
1326
|
-
else
|
|
1327
|
-
if command -v timeout >/dev/null 2>&1; then
|
|
1328
|
-
OUT=$(timeout 10 claude plugin install "${FULL_PLUGIN_NAME}" --scope "$PLUGIN_SCOPE" 2>&1) || true
|
|
1329
|
-
else
|
|
1330
|
-
OUT=$(claude plugin install "${FULL_PLUGIN_NAME}" --scope "$PLUGIN_SCOPE" 2>&1) || true
|
|
1331
|
-
fi
|
|
1332
|
-
if echo "$OUT" | grep -qiE "(success|installed)"; then
|
|
1333
|
-
sleep 0.5
|
|
1334
|
-
if check_plugin_installed_from_json "$plugin" "$MARKETPLACE"; then
|
|
1335
|
-
[[ -n "$PLUGINS_INSTALLED" ]] && PLUGINS_INSTALLED="$PLUGINS_INSTALLED, "
|
|
1336
|
-
PLUGINS_INSTALLED="${PLUGINS_INSTALLED}${plugin}"
|
|
1337
|
-
else
|
|
1338
|
-
[[ -n "$PLUGINS_ALREADY" ]] && PLUGINS_ALREADY="$PLUGINS_ALREADY, "
|
|
1339
|
-
PLUGINS_ALREADY="${PLUGINS_ALREADY}${plugin}"
|
|
1340
|
-
fi
|
|
1341
|
-
fi
|
|
1342
|
-
fi
|
|
1280
|
+
# Non-sw-* plugin detected — skip (only @specweave plugins allowed)
|
|
1281
|
+
echo "[$(date -Iseconds)] plugins | SKIPPED non-specweave plugin: $plugin" >> "$LAZY_LOAD_LOG"
|
|
1343
1282
|
fi
|
|
1344
1283
|
done
|
|
1345
1284
|
|