specweave 1.0.395 → 1.0.397

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.
Files changed (62) hide show
  1. package/README.md +2 -0
  2. package/bin/specweave.js +22 -0
  3. package/dist/dashboard/assets/index-DMjF8l_s.css +1 -0
  4. package/dist/dashboard/assets/index-Dfd-aHLI.js +11 -0
  5. package/dist/dashboard/index.html +2 -2
  6. package/dist/src/core/config/types.d.ts +1 -0
  7. package/dist/src/core/config/types.d.ts.map +1 -1
  8. package/dist/src/core/config/types.js +1 -0
  9. package/dist/src/core/config/types.js.map +1 -1
  10. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts +10 -4
  11. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts.map +1 -1
  12. package/dist/src/core/lazy-loading/llm-plugin-detector.js +51 -49
  13. package/dist/src/core/lazy-loading/llm-plugin-detector.js.map +1 -1
  14. package/dist/src/dashboard/server/dashboard-server.d.ts +6 -0
  15. package/dist/src/dashboard/server/dashboard-server.d.ts.map +1 -1
  16. package/dist/src/dashboard/server/dashboard-server.js +114 -15
  17. package/dist/src/dashboard/server/dashboard-server.js.map +1 -1
  18. package/dist/src/dashboard/server/hooks/handlers/passthrough.d.ts +8 -0
  19. package/dist/src/dashboard/server/hooks/handlers/passthrough.d.ts.map +1 -0
  20. package/dist/src/dashboard/server/hooks/handlers/passthrough.js +9 -0
  21. package/dist/src/dashboard/server/hooks/handlers/passthrough.js.map +1 -0
  22. package/dist/src/dashboard/server/hooks/handlers/pre-tool-use.d.ts +7 -0
  23. package/dist/src/dashboard/server/hooks/handlers/pre-tool-use.d.ts.map +1 -0
  24. package/dist/src/dashboard/server/hooks/handlers/pre-tool-use.js +42 -0
  25. package/dist/src/dashboard/server/hooks/handlers/pre-tool-use.js.map +1 -0
  26. package/dist/src/dashboard/server/hooks/handlers/subagent-lifecycle.d.ts +5 -0
  27. package/dist/src/dashboard/server/hooks/handlers/subagent-lifecycle.d.ts.map +1 -0
  28. package/dist/src/dashboard/server/hooks/handlers/subagent-lifecycle.js +31 -0
  29. package/dist/src/dashboard/server/hooks/handlers/subagent-lifecycle.js.map +1 -0
  30. package/dist/src/dashboard/server/hooks/hook-event-router.d.ts +24 -0
  31. package/dist/src/dashboard/server/hooks/hook-event-router.d.ts.map +1 -0
  32. package/dist/src/dashboard/server/hooks/hook-event-router.js +77 -0
  33. package/dist/src/dashboard/server/hooks/hook-event-router.js.map +1 -0
  34. package/dist/src/dashboard/server/hooks/hook-event-store.d.ts +46 -0
  35. package/dist/src/dashboard/server/hooks/hook-event-store.d.ts.map +1 -0
  36. package/dist/src/dashboard/server/hooks/hook-event-store.js +203 -0
  37. package/dist/src/dashboard/server/hooks/hook-event-store.js.map +1 -0
  38. package/dist/src/dashboard/types.d.ts +22 -1
  39. package/dist/src/dashboard/types.d.ts.map +1 -1
  40. package/dist/src/hooks/generate-settings.d.ts +8 -0
  41. package/dist/src/hooks/generate-settings.d.ts.map +1 -0
  42. package/dist/src/hooks/generate-settings.js +70 -0
  43. package/dist/src/hooks/generate-settings.js.map +1 -0
  44. package/dist/src/hooks/hooks-status.d.ts +12 -0
  45. package/dist/src/hooks/hooks-status.d.ts.map +1 -0
  46. package/dist/src/hooks/hooks-status.js +101 -0
  47. package/dist/src/hooks/hooks-status.js.map +1 -0
  48. package/package.json +1 -1
  49. package/plugins/specweave/hooks/.specweave/logs/auto-iterations.log +1 -0
  50. package/plugins/specweave/hooks/.specweave/logs/auto-stop-reasons.log +1 -0
  51. package/plugins/specweave/hooks/user-prompt-submit.sh +80 -50
  52. package/plugins/specweave/skills/.specweave/logs/reflect/auto-reflect.log +15 -0
  53. package/plugins/specweave/skills/.specweave/logs/reflect/reflect.log +3 -0
  54. package/plugins/specweave/skills/.specweave/logs/stop-auto.log +1 -0
  55. package/plugins/specweave-ado/lib/ado-multi-project-sync.js +0 -1
  56. package/plugins/specweave-ado/lib/enhanced-ado-sync.js +180 -0
  57. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +1266 -0
  58. package/plugins/specweave-github/lib/enhanced-github-sync.js +249 -0
  59. package/plugins/specweave-jira/lib/enhanced-jira-sync.js +150 -0
  60. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +1260 -0
  61. package/dist/dashboard/assets/index-Bg3O0ISk.js +0 -11
  62. package/dist/dashboard/assets/index-SBBktW9a.css +0 -1
@@ -0,0 +1,101 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as net from 'net';
4
+ export function isPortReachable(port, timeoutMs = 500) {
5
+ return new Promise((resolve) => {
6
+ const socket = new net.Socket();
7
+ socket.setTimeout(timeoutMs);
8
+ socket.once('connect', () => {
9
+ socket.destroy();
10
+ resolve(true);
11
+ });
12
+ socket.once('timeout', () => {
13
+ socket.destroy();
14
+ resolve(false);
15
+ });
16
+ socket.once('error', () => {
17
+ socket.destroy();
18
+ resolve(false);
19
+ });
20
+ socket.connect(port, '127.0.0.1');
21
+ });
22
+ }
23
+ async function fetchStatus(port) {
24
+ try {
25
+ const http = await import('http');
26
+ return new Promise((resolve) => {
27
+ const req = http.request({ hostname: '127.0.0.1', port, path: '/api/hooks/status', method: 'GET', timeout: 1000 }, (res) => {
28
+ let data = '';
29
+ res.on('data', (chunk) => { data += chunk.toString(); });
30
+ res.on('end', () => {
31
+ try {
32
+ resolve(JSON.parse(data));
33
+ }
34
+ catch {
35
+ resolve(null);
36
+ }
37
+ });
38
+ });
39
+ req.on('error', () => resolve(null));
40
+ req.on('timeout', () => { req.destroy(); resolve(null); });
41
+ req.end();
42
+ });
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ export async function getHooksStatus(projectRoot) {
49
+ const pidFilePath = path.join(projectRoot, '.specweave', 'state', 'hooks', 'server.pid');
50
+ if (!fs.existsSync(pidFilePath)) {
51
+ return { serverRunning: false, stalePidFile: false };
52
+ }
53
+ let pidData;
54
+ try {
55
+ pidData = JSON.parse(fs.readFileSync(pidFilePath, 'utf-8'));
56
+ }
57
+ catch {
58
+ return { serverRunning: false, stalePidFile: true };
59
+ }
60
+ const reachable = await isPortReachable(pidData.port);
61
+ if (!reachable) {
62
+ return {
63
+ serverRunning: false,
64
+ port: pidData.port,
65
+ pid: pidData.pid,
66
+ startedAt: pidData.startedAt,
67
+ stalePidFile: true,
68
+ };
69
+ }
70
+ // Server is reachable — try to get event count
71
+ const stats = await fetchStatus(pidData.port);
72
+ return {
73
+ serverRunning: true,
74
+ port: pidData.port,
75
+ pid: pidData.pid,
76
+ startedAt: pidData.startedAt,
77
+ stalePidFile: false,
78
+ eventCount: stats?.totalEvents,
79
+ };
80
+ }
81
+ export function formatHooksStatus(result) {
82
+ const lines = [];
83
+ if (!result.serverRunning) {
84
+ lines.push('Dashboard server: not running');
85
+ if (result.stalePidFile) {
86
+ lines.push(` Stale PID file detected (port ${result.port}, pid ${result.pid})`);
87
+ lines.push(' The server may have crashed. PID file can be safely removed.');
88
+ }
89
+ }
90
+ else {
91
+ lines.push(`Dashboard server: running on port ${result.port} (pid ${result.pid})`);
92
+ if (result.startedAt) {
93
+ lines.push(` Started: ${result.startedAt}`);
94
+ }
95
+ if (result.eventCount != null) {
96
+ lines.push(` Events received: ${result.eventCount}`);
97
+ }
98
+ }
99
+ return lines.join('\n');
100
+ }
101
+ //# sourceMappingURL=hooks-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks-status.js","sourceRoot":"","sources":["../../../src/hooks/hooks-status.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specweave",
3
- "version": "1.0.395",
3
+ "version": "1.0.397",
4
4
  "description": "Spec-driven development framework for AI coding agents. Works with Claude Code, Codex, Antigravity, Cursor, Copilot & more. 100+ skills, 49 CLI commands, verified skill certification, autonomous execution, and living documentation.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1 @@
1
+ {"timestamp":"2026-01-04T16:11:43Z","event":"session_stop","reason":"No auto session active","details":"approve_called:main","success":false,"iteration":0,"increment":"none"}
@@ -0,0 +1 @@
1
+ {"timestamp":"2026-01-04T16:11:43Z","sessionId":"unknown","reason":"No auto session active","details":"approve_called:main","success":false,"iteration":0,"increment":"none","testsRun":false,"testsPassed":0,"testsFailed":0}
@@ -356,7 +356,7 @@ fi
356
356
 
357
357
  # Check config for pluginAutoLoad.enabled, suggestOnly and incrementAssist.enabled settings
358
358
  PLUGIN_AUTOLOAD_ENABLED=true
359
- PLUGIN_SUGGEST_ONLY=false
359
+ PLUGIN_SUGGEST_ONLY=true
360
360
  INCREMENT_ASSIST_ENABLED=true
361
361
  INCREMENT_CONFIDENCE_THRESHOLD=0.7
362
362
  INCREMENT_MANDATORY_CONFIG=true
@@ -371,9 +371,9 @@ if [[ -f "$CONFIG_PATH" ]]; then
371
371
  AUTOLOAD_VALUE=$(jq -r '.pluginAutoLoad.enabled // true' "$CONFIG_PATH" 2>/dev/null)
372
372
  [[ "$AUTOLOAD_VALUE" == "false" ]] && PLUGIN_AUTOLOAD_ENABLED=false
373
373
 
374
- # Check suggestOnly mode (v1.0.158)
375
- SUGGEST_VALUE=$(jq -r '.pluginAutoLoad.suggestOnly // false' "$CONFIG_PATH" 2>/dev/null)
376
- [[ "$SUGGEST_VALUE" == "true" ]] && PLUGIN_SUGGEST_ONLY=true
374
+ # Check suggestOnly mode (v1.0.158, default flipped to true in v1.0.397 — consent-first)
375
+ SUGGEST_VALUE=$(jq -r '.pluginAutoLoad.suggestOnly // true' "$CONFIG_PATH" 2>/dev/null)
376
+ [[ "$SUGGEST_VALUE" == "false" ]] && PLUGIN_SUGGEST_ONLY=false
377
377
 
378
378
  INCREMENT_VALUE=$(jq -r '.incrementAssist.enabled // true' "$CONFIG_PATH" 2>/dev/null)
379
379
  [[ "$INCREMENT_VALUE" == "false" ]] && INCREMENT_ASSIST_ENABLED=false
@@ -393,9 +393,9 @@ if [[ -f "$CONFIG_PATH" ]]; then
393
393
  if grep -q '"pluginAutoLoad"' "$CONFIG_PATH" 2>/dev/null && grep -q '"enabled"[[:space:]]*:[[:space:]]*false' "$CONFIG_PATH" 2>/dev/null; then
394
394
  PLUGIN_AUTOLOAD_ENABLED=false
395
395
  fi
396
- # Fallback: grep for suggestOnly
397
- if grep -q '"pluginAutoLoad"' "$CONFIG_PATH" 2>/dev/null && grep -A5 '"pluginAutoLoad"' "$CONFIG_PATH" 2>/dev/null | grep -q '"suggestOnly"[[:space:]]*:[[:space:]]*true'; then
398
- PLUGIN_SUGGEST_ONLY=true
396
+ # Fallback: grep for explicit suggestOnly=false (opt-in to auto-install)
397
+ if grep -q '"pluginAutoLoad"' "$CONFIG_PATH" 2>/dev/null && grep -A5 '"pluginAutoLoad"' "$CONFIG_PATH" 2>/dev/null | grep -q '"suggestOnly"[[:space:]]*:[[:space:]]*false'; then
398
+ PLUGIN_SUGGEST_ONLY=false
399
399
  fi
400
400
  if grep -q '"incrementAssist"' "$CONFIG_PATH" 2>/dev/null && grep -A5 '"incrementAssist"' "$CONFIG_PATH" 2>/dev/null | grep -q '"enabled"[[:space:]]*:[[:space:]]*false'; then
401
401
  INCREMENT_ASSIST_ENABLED=false
@@ -510,10 +510,10 @@ install_plugin_via_vskill() {
510
510
  }
511
511
 
512
512
  # Domain skill plugins in vskill marketplace (per-category plugins).
513
- # Each is a standalone plugin: frontend@vskill, backend@vskill, etc.
514
- # Skills are invoked as plugin:skill (e.g., frontend:nextjs, backend:dotnet).
515
- # v1.0.344 (0394): Removed k8s (use infra), cost (not in marketplace), docs (in specweave repo)
516
- VSKILL_REPO_PLUGINS="frontend backend testing mobile infra payments ml kafka confluent security skills blockchain"
513
+ # Each is a standalone plugin: mobile@vskill, skills@vskill.
514
+ # Skills are invoked as plugin:skill (e.g., mobile:react-native).
515
+ # v1.0.397: Reduced to plugins with actual directories on disk. Phantom entries removed.
516
+ VSKILL_REPO_PLUGINS="mobile skills"
517
517
 
518
518
  # Check if plugin name is a vskill marketplace plugin
519
519
  is_vskill_repo_plugin() {
@@ -983,57 +983,78 @@ if [[ "$LSP_NEEDS_INSTALL" == "true" ]] && [[ "$LSP_AUTO_INSTALL" == "true" ]];
983
983
  fi
984
984
 
985
985
  if [[ "$LSP_NEEDS_INSTALL" == "true" ]] && [[ "$LSP_AUTO_INSTALL" == "true" ]]; then
986
- # Check if marketplace is already installed
987
- MARKETPLACE_DIR="$HOME/.claude/plugins/marketplaces/claude-code-lsps"
988
- if [[ ! -d "$MARKETPLACE_DIR" ]] && command -v claude >/dev/null 2>&1; then
989
- # Install the marketplace
990
- if timeout 15 claude plugin marketplace add "$LSP_MARKETPLACE_URL" >/dev/null 2>&1; then
991
- LSP_INSTALL_MSG=" **LSP marketplace installed**: \`$LSP_MARKETPLACE\`
986
+ # v1.0.397: Respect global suggest-only mode for LSP plugins too (consent-first)
987
+ if [[ "$PLUGIN_SUGGEST_ONLY" == "true" ]]; then
988
+ LSP_SUGGEST_CMDS=""
989
+ if [[ "$LSP_PROJECT_NEEDS_TS" == "true" ]] || [[ "$LSP_PROMPT_NEEDS_TS" == "true" ]]; then
990
+ VTSLS_INSTALLED=$(jq -r '."vtsls@claude-code-lsps" // false' "$HOME/.claude/plugins/installed_plugins.json" 2>/dev/null)
991
+ [[ "$VTSLS_INSTALLED" != "true" ]] && LSP_SUGGEST_CMDS="${LSP_SUGGEST_CMDS} - **TypeScript**: \`claude plugin install vtsls@claude-code-lsps --scope ${LSP_PLUGIN_SCOPE}\`\n"
992
+ fi
993
+ if [[ "$LSP_PROJECT_NEEDS_PY" == "true" ]] || [[ "$LSP_PROMPT_NEEDS_PY" == "true" ]]; then
994
+ PYRIGHT_INSTALLED=$(jq -r '."pyright@claude-code-lsps" // false' "$HOME/.claude/plugins/installed_plugins.json" 2>/dev/null)
995
+ [[ "$PYRIGHT_INSTALLED" != "true" ]] && LSP_SUGGEST_CMDS="${LSP_SUGGEST_CMDS} - **Python**: \`claude plugin install pyright@claude-code-lsps --scope ${LSP_PLUGIN_SCOPE}\`\n"
996
+ fi
997
+ if [[ "$LSP_PROJECT_NEEDS_RUST" == "true" ]] || [[ "$LSP_PROMPT_NEEDS_RUST" == "true" ]]; then
998
+ RUST_ANALYZER_INSTALLED=$(jq -r '."rust-analyzer@claude-code-lsps" // false' "$HOME/.claude/plugins/installed_plugins.json" 2>/dev/null)
999
+ [[ "$RUST_ANALYZER_INSTALLED" != "true" ]] && LSP_SUGGEST_CMDS="${LSP_SUGGEST_CMDS} - **Rust**: \`claude plugin install rust-analyzer@claude-code-lsps --scope ${LSP_PLUGIN_SCOPE}\`\n"
1000
+ fi
1001
+ if [[ -n "$LSP_SUGGEST_CMDS" ]]; then
1002
+ LSP_INSTALL_MSG="**Suggested LSP plugins**:\n${LSP_SUGGEST_CMDS}After installing, **restart Claude Code** to use LSP features.\n\n---\n\n"
1003
+ fi
1004
+ else
1005
+ # NORMAL MODE (user opted in with suggestOnly: false) - Actually install LSP plugins
1006
+ # Check if marketplace is already installed
1007
+ MARKETPLACE_DIR="$HOME/.claude/plugins/marketplaces/claude-code-lsps"
1008
+ if [[ ! -d "$MARKETPLACE_DIR" ]] && command -v claude >/dev/null 2>&1; then
1009
+ # Install the marketplace
1010
+ if timeout 15 claude plugin marketplace add "$LSP_MARKETPLACE_URL" >/dev/null 2>&1; then
1011
+ LSP_INSTALL_MSG="✅ **LSP marketplace installed**: \`$LSP_MARKETPLACE\`
992
1012
  "
1013
+ fi
993
1014
  fi
994
- fi
995
1015
 
996
- # Auto-install TypeScript LSP plugin (vtsls) when TypeScript project/prompt detected
997
- # v1.0.196: Uses --scope $LSP_PLUGIN_SCOPE (default: project)
998
- if [[ "$LSP_PROJECT_NEEDS_TS" == "true" ]] || [[ "$LSP_PROMPT_NEEDS_TS" == "true" ]]; then
999
- VTSLS_INSTALLED=$(jq -r '."vtsls@claude-code-lsps" // false' "$HOME/.claude/plugins/installed_plugins.json" 2>/dev/null)
1000
- if [[ "$VTSLS_INSTALLED" != "true" ]] && command -v claude >/dev/null 2>&1; then
1001
- if timeout 15 claude plugin install vtsls@claude-code-lsps --scope $LSP_PLUGIN_SCOPE >/dev/null 2>&1; then
1002
- LSP_INSTALL_MSG="${LSP_INSTALL_MSG}✅ **TypeScript LSP installed**: \`vtsls@claude-code-lsps\` (scope: $LSP_PLUGIN_SCOPE)
1016
+ # Auto-install TypeScript LSP plugin (vtsls) when TypeScript project/prompt detected
1017
+ # v1.0.196: Uses --scope $LSP_PLUGIN_SCOPE (default: project)
1018
+ if [[ "$LSP_PROJECT_NEEDS_TS" == "true" ]] || [[ "$LSP_PROMPT_NEEDS_TS" == "true" ]]; then
1019
+ VTSLS_INSTALLED=$(jq -r '."vtsls@claude-code-lsps" // false' "$HOME/.claude/plugins/installed_plugins.json" 2>/dev/null)
1020
+ if [[ "$VTSLS_INSTALLED" != "true" ]] && command -v claude >/dev/null 2>&1; then
1021
+ if timeout 15 claude plugin install vtsls@claude-code-lsps --scope $LSP_PLUGIN_SCOPE >/dev/null 2>&1; then
1022
+ LSP_INSTALL_MSG="${LSP_INSTALL_MSG}✅ **TypeScript LSP installed**: \`vtsls@claude-code-lsps\` (scope: $LSP_PLUGIN_SCOPE)
1003
1023
  "
1024
+ fi
1004
1025
  fi
1005
1026
  fi
1006
- fi
1007
1027
 
1008
- # Auto-install Python LSP plugin (pyright) when Python project/prompt detected
1009
- # v1.0.196: Uses --scope $LSP_PLUGIN_SCOPE (default: project)
1010
- if [[ "$LSP_PROJECT_NEEDS_PY" == "true" ]] || [[ "$LSP_PROMPT_NEEDS_PY" == "true" ]]; then
1011
- PYRIGHT_INSTALLED=$(jq -r '."pyright@claude-code-lsps" // false' "$HOME/.claude/plugins/installed_plugins.json" 2>/dev/null)
1012
- if [[ "$PYRIGHT_INSTALLED" != "true" ]] && command -v claude >/dev/null 2>&1; then
1013
- if timeout 15 claude plugin install pyright@claude-code-lsps --scope $LSP_PLUGIN_SCOPE >/dev/null 2>&1; then
1014
- LSP_INSTALL_MSG="${LSP_INSTALL_MSG}✅ **Python LSP installed**: \`pyright@claude-code-lsps\` (scope: $LSP_PLUGIN_SCOPE)
1028
+ # Auto-install Python LSP plugin (pyright) when Python project/prompt detected
1029
+ # v1.0.196: Uses --scope $LSP_PLUGIN_SCOPE (default: project)
1030
+ if [[ "$LSP_PROJECT_NEEDS_PY" == "true" ]] || [[ "$LSP_PROMPT_NEEDS_PY" == "true" ]]; then
1031
+ PYRIGHT_INSTALLED=$(jq -r '."pyright@claude-code-lsps" // false' "$HOME/.claude/plugins/installed_plugins.json" 2>/dev/null)
1032
+ if [[ "$PYRIGHT_INSTALLED" != "true" ]] && command -v claude >/dev/null 2>&1; then
1033
+ if timeout 15 claude plugin install pyright@claude-code-lsps --scope $LSP_PLUGIN_SCOPE >/dev/null 2>&1; then
1034
+ LSP_INSTALL_MSG="${LSP_INSTALL_MSG}✅ **Python LSP installed**: \`pyright@claude-code-lsps\` (scope: $LSP_PLUGIN_SCOPE)
1015
1035
  "
1036
+ fi
1016
1037
  fi
1017
1038
  fi
1018
- fi
1019
1039
 
1020
- # Auto-install Rust LSP plugin (rust-analyzer) when Rust project/prompt detected
1021
- # v1.0.196: Uses --scope $LSP_PLUGIN_SCOPE (default: project)
1022
- if [[ "$LSP_PROJECT_NEEDS_RUST" == "true" ]] || [[ "$LSP_PROMPT_NEEDS_RUST" == "true" ]]; then
1023
- RUST_ANALYZER_INSTALLED=$(jq -r '."rust-analyzer@claude-code-lsps" // false' "$HOME/.claude/plugins/installed_plugins.json" 2>/dev/null)
1024
- if [[ "$RUST_ANALYZER_INSTALLED" != "true" ]] && command -v claude >/dev/null 2>&1; then
1025
- if timeout 15 claude plugin install rust-analyzer@claude-code-lsps --scope $LSP_PLUGIN_SCOPE >/dev/null 2>&1; then
1026
- LSP_INSTALL_MSG="${LSP_INSTALL_MSG}✅ **Rust LSP installed**: \`rust-analyzer@claude-code-lsps\` (scope: $LSP_PLUGIN_SCOPE)
1040
+ # Auto-install Rust LSP plugin (rust-analyzer) when Rust project/prompt detected
1041
+ # v1.0.196: Uses --scope $LSP_PLUGIN_SCOPE (default: project)
1042
+ if [[ "$LSP_PROJECT_NEEDS_RUST" == "true" ]] || [[ "$LSP_PROMPT_NEEDS_RUST" == "true" ]]; then
1043
+ RUST_ANALYZER_INSTALLED=$(jq -r '."rust-analyzer@claude-code-lsps" // false' "$HOME/.claude/plugins/installed_plugins.json" 2>/dev/null)
1044
+ if [[ "$RUST_ANALYZER_INSTALLED" != "true" ]] && command -v claude >/dev/null 2>&1; then
1045
+ if timeout 15 claude plugin install rust-analyzer@claude-code-lsps --scope $LSP_PLUGIN_SCOPE >/dev/null 2>&1; then
1046
+ LSP_INSTALL_MSG="${LSP_INSTALL_MSG}✅ **Rust LSP installed**: \`rust-analyzer@claude-code-lsps\` (scope: $LSP_PLUGIN_SCOPE)
1027
1047
  "
1048
+ fi
1028
1049
  fi
1029
1050
  fi
1030
- fi
1031
1051
 
1032
- if [[ -n "$LSP_INSTALL_MSG" ]]; then
1033
- LSP_INSTALL_MSG="${LSP_INSTALL_MSG}
1052
+ if [[ -n "$LSP_INSTALL_MSG" ]]; then
1053
+ LSP_INSTALL_MSG="${LSP_INSTALL_MSG}
1034
1054
  ---
1035
1055
 
1036
1056
  "
1057
+ fi
1037
1058
  fi
1038
1059
  fi
1039
1060
 
@@ -1248,14 +1269,23 @@ if [[ "${SPECWEAVE_DISABLE_AUTO_LOAD:-0}" != "1" ]] && [[ "${SPECWEAVE_DISABLE_H
1248
1269
  DETECTED_PLUGINS=$(echo "$JSON_OUTPUT" | jq -r '.plugins[]?' 2>/dev/null | tr '\n' ' ')
1249
1270
 
1250
1271
  if [[ -n "$DETECTED_PLUGINS" ]]; then
1251
- # v1.0.158: SUGGEST-ONLY MODE - Don't install, just inform user
1272
+ # v1.0.158/v1.0.397: SUGGEST-ONLY MODE (now the default) - suggest with install commands, don't auto-install
1252
1273
  if [[ "$PLUGIN_SUGGEST_ONLY" == "true" ]]; then
1253
- PLUGIN_LIST=$(echo "$DETECTED_PLUGINS" | tr ' ' ', ' | sed 's/,$//')
1254
- AUTOLOAD_PLUGINS_MSG="💡 **Suggested plugins**: ${PLUGIN_LIST}\\n"
1255
- AUTOLOAD_PLUGINS_MSG="${AUTOLOAD_PLUGINS_MSG}To install: \`npx vskill install --repo anton-abyzov/specweave --plugin <plugin> --force\` (sw-*) or \`npx vskill install --repo anton-abyzov/vskill --plugin <plugin> --force\` (domain)\\n"
1256
- AUTOLOAD_PLUGINS_MSG="${AUTOLOAD_PLUGINS_MSG}After installing, restart Claude Code session to use new plugins.\\n"
1274
+ SUGGEST_CMDS=""
1275
+ for plugin in $DETECTED_PLUGINS; do
1276
+ [[ -z "$plugin" ]] && continue
1277
+ if [[ "$plugin" == sw-* ]] || [[ "$plugin" == "sw" ]] || [[ "$plugin" == "docs" ]]; then
1278
+ SUGGEST_CMDS="${SUGGEST_CMDS} - **${plugin}**: \`npx vskill install --repo anton-abyzov/specweave --plugin ${plugin} --agent claude-code\`\\n"
1279
+ elif is_vskill_repo_plugin "$plugin"; then
1280
+ SUGGEST_CMDS="${SUGGEST_CMDS} - **${plugin}**: \`npx vskill install --repo anton-abyzov/vskill --plugin ${plugin} --agent claude-code\`\\n"
1281
+ fi
1282
+ done
1257
1283
  LLM_REASON=$(echo "$JSON_OUTPUT" | jq -r '.reasoning // empty' 2>/dev/null)
1258
- [[ -n "$LLM_REASON" ]] && AUTOLOAD_PLUGINS_MSG="${AUTOLOAD_PLUGINS_MSG}*${LLM_REASON}*\\n\\n---\\n"
1284
+ AUTOLOAD_PLUGINS_MSG="**Suggested plugins for this task**:\\n${SUGGEST_CMDS}"
1285
+ [[ -n "$LLM_REASON" ]] && AUTOLOAD_PLUGINS_MSG="${AUTOLOAD_PLUGINS_MSG}*Why*: ${LLM_REASON}\\n"
1286
+ AUTOLOAD_PLUGINS_MSG="${AUTOLOAD_PLUGINS_MSG}\\nTo enable auto-install: set \`\"pluginAutoLoad\": { \"suggestOnly\": false }\` in \`.specweave/config.json\`\\n"
1287
+ AUTOLOAD_PLUGINS_MSG="${AUTOLOAD_PLUGINS_MSG}After installing, **restart Claude Code** to use new plugins.\\n\\n---\\n"
1288
+ PLUGIN_LIST=$(echo "$DETECTED_PLUGINS" | tr ' ' ', ' | sed 's/,$//')
1259
1289
  echo "[$(date -Iseconds)] plugins | suggested=${PLUGIN_LIST} | mode=suggestOnly" >> "$LAZY_LOAD_LOG"
1260
1290
  elif command -v claude >/dev/null 2>&1; then
1261
1291
  # NORMAL MODE - Actually install plugins
@@ -0,0 +1,15 @@
1
+ {
2
+ "ran": false,
3
+ "inputSummary": {
4
+ "transcriptLines": 266
5
+ },
6
+ "extracted": {
7
+ "skillLearnings": []
8
+ },
9
+ "written": {
10
+ "learningsAdded": 0,
11
+ "learningsSkippedDuplicate": 0
12
+ },
13
+ "durationMs": 1.4448749999999997,
14
+ "reason": "CLAUDE.md not found in project"
15
+ }
@@ -0,0 +1,3 @@
1
+ [2026-02-03T22:03:06Z] [info] Starting reflection (265 lines)
2
+ [2026-02-03T22:03:06Z] [info] Reflection started in background
3
+ [2026-02-03T22:03:06Z] [info] Reflection completed - no learnings
@@ -0,0 +1 @@
1
+ [2026-02-03T22:03:06Z] APPROVE: No increments directory
@@ -373,7 +373,6 @@ ${userStory.technicalContext}
373
373
  return mapping.task || "Task";
374
374
  case "Subtask":
375
375
  return mapping.task || "Task";
376
- // ADO doesn't have subtasks, use Task
377
376
  default:
378
377
  return "User Story";
379
378
  }
@@ -0,0 +1,180 @@
1
+ import { AdoClientV2 } from "./ado-client-v2.js";
2
+ import { EnhancedContentBuilder } from "../../../src/core/sync/enhanced-content-builder.js";
3
+ import { SpecIncrementMapper } from "../../../src/core/sync/spec-increment-mapper.js";
4
+ import { parseSpecContent } from "../../../src/core/spec-content-sync.js";
5
+ import path from "path";
6
+ import fs from "fs/promises";
7
+ async function syncSpecToAdoWithEnhancedContent(options) {
8
+ const { specPath, organization, project, dryRun = false, verbose = false } = options;
9
+ try {
10
+ const baseSpec = await parseSpecContent(specPath);
11
+ if (!baseSpec) {
12
+ return {
13
+ success: false,
14
+ action: "error",
15
+ error: "Failed to parse spec content"
16
+ };
17
+ }
18
+ if (verbose) {
19
+ console.log(`\u{1F4C4} Parsed spec: ${baseSpec.identifier.compact}`);
20
+ }
21
+ const specId = baseSpec.identifier.full || baseSpec.identifier.compact;
22
+ const rootDir = await findSpecWeaveRoot(specPath);
23
+ const mapper = new SpecIncrementMapper(rootDir);
24
+ const mapping = await mapper.mapSpecToIncrements(specId);
25
+ if (verbose) {
26
+ console.log(`\u{1F517} Found ${mapping.increments.length} related increments`);
27
+ }
28
+ const taskMapping = buildTaskMapping(mapping.increments, organization, project);
29
+ const architectureDocs = await findArchitectureDocs(rootDir, specId);
30
+ const enhancedSpec = {
31
+ ...baseSpec,
32
+ summary: baseSpec.description,
33
+ taskMapping,
34
+ architectureDocs
35
+ };
36
+ const builder = new EnhancedContentBuilder();
37
+ const description = builder.buildExternalDescription(enhancedSpec);
38
+ if (verbose) {
39
+ console.log(`\u{1F4DD} Generated description: ${description.length} characters`);
40
+ }
41
+ if (dryRun) {
42
+ console.log("\u{1F50D} DRY RUN - Would create/update feature with:");
43
+ console.log(` Title: ${baseSpec.title}`);
44
+ console.log(` Description length: ${description.length}`);
45
+ return {
46
+ success: true,
47
+ action: "no-change",
48
+ tasksLinked: taskMapping?.tasks.length || 0
49
+ };
50
+ }
51
+ if (!organization || !project) {
52
+ return {
53
+ success: false,
54
+ action: "error",
55
+ error: "Azure DevOps organization/project not specified"
56
+ };
57
+ }
58
+ const profile = {
59
+ provider: "ado",
60
+ displayName: `${organization}/${project}`,
61
+ config: {
62
+ organization,
63
+ project
64
+ },
65
+ timeRange: { default: "1M", max: "6M" }
66
+ };
67
+ const pat = process.env.AZURE_DEVOPS_PAT || "";
68
+ const client = new AdoClientV2(profile, pat);
69
+ const existingFeature = await findExistingFeature(client, baseSpec.identifier.compact);
70
+ let result;
71
+ if (existingFeature) {
72
+ await client.updateWorkItem(existingFeature.id, {
73
+ title: `[${baseSpec.identifier.compact}] ${baseSpec.title}`,
74
+ description
75
+ });
76
+ result = {
77
+ success: true,
78
+ action: "updated",
79
+ featureId: existingFeature.id,
80
+ featureUrl: `https://dev.azure.com/${organization}/${project}/_workitems/edit/${existingFeature.id}`,
81
+ tasksLinked: taskMapping?.tasks.length || 0
82
+ };
83
+ } else {
84
+ const feature = await client.createEpic({
85
+ title: `[${baseSpec.identifier.compact}] ${baseSpec.title}`,
86
+ description,
87
+ tags: ["spec", "external-tool-sync"]
88
+ });
89
+ result = {
90
+ success: true,
91
+ action: "created",
92
+ featureId: feature.id,
93
+ featureUrl: `https://dev.azure.com/${organization}/${project}/_workitems/edit/${feature.id}`,
94
+ tasksLinked: taskMapping?.tasks.length || 0
95
+ };
96
+ }
97
+ if (verbose) {
98
+ console.log(`\u2705 ${result.action === "created" ? "Created" : "Updated"} feature #${result.featureId}`);
99
+ }
100
+ return result;
101
+ } catch (error) {
102
+ return {
103
+ success: false,
104
+ action: "error",
105
+ error: error.message
106
+ };
107
+ }
108
+ }
109
+ async function findSpecWeaveRoot(specPath) {
110
+ let currentDir = path.dirname(specPath);
111
+ while (true) {
112
+ const specweaveDir = path.join(currentDir, ".specweave");
113
+ try {
114
+ await fs.access(specweaveDir);
115
+ return currentDir;
116
+ } catch {
117
+ const parentDir = path.dirname(currentDir);
118
+ if (parentDir === currentDir) {
119
+ throw new Error(".specweave directory not found");
120
+ }
121
+ currentDir = parentDir;
122
+ }
123
+ }
124
+ }
125
+ function buildTaskMapping(increments, organization, project) {
126
+ if (increments.length === 0) return void 0;
127
+ const firstIncrement = increments[0];
128
+ const tasks = firstIncrement.tasks.map((task) => ({
129
+ id: task.id,
130
+ title: task.title,
131
+ userStories: task.userStories
132
+ }));
133
+ // Derive repository name from git remote or fall back to project name
134
+ let repoName = project;
135
+ try {
136
+ const { execSync } = require("child_process");
137
+ const remoteUrl = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
138
+ const match = remoteUrl.match(/\/([^/]+?)(?:\.git)?$/);
139
+ if (match) repoName = match[1];
140
+ } catch {
141
+ // Fallback to project name if git is unavailable
142
+ }
143
+ return {
144
+ incrementId: firstIncrement.id,
145
+ tasks,
146
+ tasksUrl: `https://dev.azure.com/${organization}/${project}/_git/${repoName}?path=/.specweave/increments/${firstIncrement.id}/tasks.md`
147
+ };
148
+ }
149
+ async function findArchitectureDocs(rootDir, specId) {
150
+ const docs = [];
151
+ const archDir = path.join(rootDir, ".specweave/docs/internal/architecture");
152
+ try {
153
+ const adrDir = path.join(archDir, "adr");
154
+ try {
155
+ const adrs = await fs.readdir(adrDir);
156
+ const relatedAdrs = adrs.filter((file) => file.includes(specId.replace("spec-", "")));
157
+ for (const adr of relatedAdrs) {
158
+ docs.push({
159
+ type: "adr",
160
+ path: path.join(adrDir, adr),
161
+ title: adr.replace(".md", "").replace(/-/g, " ")
162
+ });
163
+ }
164
+ } catch {
165
+ }
166
+ } catch {
167
+ }
168
+ return docs;
169
+ }
170
+ async function findExistingFeature(client, specId) {
171
+ try {
172
+ const features = await client.queryWorkItems(`[System.Title] Contains '[${specId}]' AND [System.WorkItemType] = 'Feature'`);
173
+ return features[0] || null;
174
+ } catch {
175
+ return null;
176
+ }
177
+ }
178
+ export {
179
+ syncSpecToAdoWithEnhancedContent
180
+ };