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.
- package/README.md +2 -0
- package/bin/specweave.js +22 -0
- package/dist/dashboard/assets/index-DMjF8l_s.css +1 -0
- package/dist/dashboard/assets/index-Dfd-aHLI.js +11 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/src/core/config/types.d.ts +1 -0
- package/dist/src/core/config/types.d.ts.map +1 -1
- package/dist/src/core/config/types.js +1 -0
- package/dist/src/core/config/types.js.map +1 -1
- package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts +10 -4
- package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts.map +1 -1
- package/dist/src/core/lazy-loading/llm-plugin-detector.js +51 -49
- package/dist/src/core/lazy-loading/llm-plugin-detector.js.map +1 -1
- package/dist/src/dashboard/server/dashboard-server.d.ts +6 -0
- package/dist/src/dashboard/server/dashboard-server.d.ts.map +1 -1
- package/dist/src/dashboard/server/dashboard-server.js +114 -15
- package/dist/src/dashboard/server/dashboard-server.js.map +1 -1
- package/dist/src/dashboard/server/hooks/handlers/passthrough.d.ts +8 -0
- package/dist/src/dashboard/server/hooks/handlers/passthrough.d.ts.map +1 -0
- package/dist/src/dashboard/server/hooks/handlers/passthrough.js +9 -0
- package/dist/src/dashboard/server/hooks/handlers/passthrough.js.map +1 -0
- package/dist/src/dashboard/server/hooks/handlers/pre-tool-use.d.ts +7 -0
- package/dist/src/dashboard/server/hooks/handlers/pre-tool-use.d.ts.map +1 -0
- package/dist/src/dashboard/server/hooks/handlers/pre-tool-use.js +42 -0
- package/dist/src/dashboard/server/hooks/handlers/pre-tool-use.js.map +1 -0
- package/dist/src/dashboard/server/hooks/handlers/subagent-lifecycle.d.ts +5 -0
- package/dist/src/dashboard/server/hooks/handlers/subagent-lifecycle.d.ts.map +1 -0
- package/dist/src/dashboard/server/hooks/handlers/subagent-lifecycle.js +31 -0
- package/dist/src/dashboard/server/hooks/handlers/subagent-lifecycle.js.map +1 -0
- package/dist/src/dashboard/server/hooks/hook-event-router.d.ts +24 -0
- package/dist/src/dashboard/server/hooks/hook-event-router.d.ts.map +1 -0
- package/dist/src/dashboard/server/hooks/hook-event-router.js +77 -0
- package/dist/src/dashboard/server/hooks/hook-event-router.js.map +1 -0
- package/dist/src/dashboard/server/hooks/hook-event-store.d.ts +46 -0
- package/dist/src/dashboard/server/hooks/hook-event-store.d.ts.map +1 -0
- package/dist/src/dashboard/server/hooks/hook-event-store.js +203 -0
- package/dist/src/dashboard/server/hooks/hook-event-store.js.map +1 -0
- package/dist/src/dashboard/types.d.ts +22 -1
- package/dist/src/dashboard/types.d.ts.map +1 -1
- package/dist/src/hooks/generate-settings.d.ts +8 -0
- package/dist/src/hooks/generate-settings.d.ts.map +1 -0
- package/dist/src/hooks/generate-settings.js +70 -0
- package/dist/src/hooks/generate-settings.js.map +1 -0
- package/dist/src/hooks/hooks-status.d.ts +12 -0
- package/dist/src/hooks/hooks-status.d.ts.map +1 -0
- package/dist/src/hooks/hooks-status.js +101 -0
- package/dist/src/hooks/hooks-status.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/hooks/.specweave/logs/auto-iterations.log +1 -0
- package/plugins/specweave/hooks/.specweave/logs/auto-stop-reasons.log +1 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +80 -50
- package/plugins/specweave/skills/.specweave/logs/reflect/auto-reflect.log +15 -0
- package/plugins/specweave/skills/.specweave/logs/reflect/reflect.log +3 -0
- package/plugins/specweave/skills/.specweave/logs/stop-auto.log +1 -0
- package/plugins/specweave-ado/lib/ado-multi-project-sync.js +0 -1
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +180 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +1266 -0
- package/plugins/specweave-github/lib/enhanced-github-sync.js +249 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +150 -0
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +1260 -0
- package/dist/dashboard/assets/index-Bg3O0ISk.js +0 -11
- 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.
|
|
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=
|
|
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 //
|
|
376
|
-
[[ "$SUGGEST_VALUE" == "
|
|
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:]]*
|
|
398
|
-
PLUGIN_SUGGEST_ONLY=
|
|
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:
|
|
514
|
-
# Skills are invoked as plugin:skill (e.g.,
|
|
515
|
-
# v1.0.
|
|
516
|
-
VSKILL_REPO_PLUGINS="
|
|
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
|
-
#
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
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
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
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
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
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
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
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
|
-
|
|
1033
|
-
|
|
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 -
|
|
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
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
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
|
-
|
|
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 @@
|
|
|
1
|
+
[2026-02-03T22:03:06Z] APPROVE: No increments directory
|
|
@@ -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
|
+
};
|