@ngocsangairvds/vsaf 4.0.9 → 4.0.11
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/package.json +2 -2
- package/skills/vds-skill/_shared/config-check.js +76 -0
- package/skills/vds-skill/_shared/credentials.js +139 -0
- package/skills/vds-skill/create-bitbucket-pr/SKILL.md +2 -18
- package/skills/vds-skill/create-bitbucket-pr/scripts/create-pr.js +125 -0
- package/skills/vds-skill/create-jira-epic/SKILL.md +2 -20
- package/skills/vds-skill/create-jira-epic/scripts/create-epic.js +120 -0
- package/skills/vds-skill/install-deps.mjs +34 -32
- package/skills/vds-skill/pull/SKILL.md +1 -1
- package/skills/vds-skill/pull/scripts/pull.js +59 -0
- package/skills/vds-skill/push-prd/SKILL.md +11 -30
- package/skills/vds-skill/push-srs/SKILL.md +4 -23
- package/skills/vds-skill/search-confluence/SKILL.md +2 -22
- package/skills/vds-skill/search-confluence/scripts/search.js +114 -0
- package/skills/vds-skill/vds-scripts-skill/SKILL.md +3 -3
- package/skills/vds-skill/_shared/credentials.sh +0 -79
- package/skills/vds-skill/create-bitbucket-pr/scripts/create-pr.sh +0 -105
- package/skills/vds-skill/create-jira-epic/scripts/create-epic.sh +0 -113
- package/skills/vds-skill/pull/scripts/pull.sh +0 -52
- package/skills/vds-skill/search-confluence/scripts/search.sh +0 -128
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* node install-deps.mjs [projectPath]
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, cpSync } from 'fs';
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, cpSync, chmodSync } from 'fs';
|
|
14
14
|
import { join, dirname } from 'path';
|
|
15
15
|
import { homedir } from 'os';
|
|
16
16
|
|
|
@@ -77,7 +77,7 @@ if (!existsSync(CONFIG_FILE)) {
|
|
|
77
77
|
const lines = [
|
|
78
78
|
'# VDS Skill Pack — credential config',
|
|
79
79
|
'# File: ~/.vds/sdlc-config.env',
|
|
80
|
-
'# Permissions: 600 (auto-set by credentials.
|
|
80
|
+
'# Permissions: 600 (auto-set by credentials.js)',
|
|
81
81
|
'#',
|
|
82
82
|
'# Fill in the values below. Skills will use these automatically.',
|
|
83
83
|
'# To rotate a token: update the value here, skills pick it up next run.',
|
|
@@ -91,7 +91,10 @@ if (!existsSync(CONFIG_FILE)) {
|
|
|
91
91
|
lines.push('');
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
writeFileSync(CONFIG_FILE, lines.join('\n')
|
|
94
|
+
writeFileSync(CONFIG_FILE, lines.join('\n'));
|
|
95
|
+
if (process.platform !== 'win32') {
|
|
96
|
+
chmodSync(CONFIG_FILE, 0o600);
|
|
97
|
+
}
|
|
95
98
|
log('✅', `Config created: ${CONFIG_FILE}`);
|
|
96
99
|
} else if (missing.length > 0) {
|
|
97
100
|
// Append missing vars to existing config
|
|
@@ -105,7 +108,10 @@ if (!existsSync(CONFIG_FILE)) {
|
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
110
|
if (appendLines.length > 1) {
|
|
108
|
-
writeFileSync(CONFIG_FILE, readFileSync(CONFIG_FILE, 'utf-8') + appendLines.join('\n')
|
|
111
|
+
writeFileSync(CONFIG_FILE, readFileSync(CONFIG_FILE, 'utf-8') + appendLines.join('\n'));
|
|
112
|
+
if (process.platform !== 'win32') {
|
|
113
|
+
chmodSync(CONFIG_FILE, 0o600);
|
|
114
|
+
}
|
|
109
115
|
log('✅', `Config updated: ${CONFIG_FILE}`);
|
|
110
116
|
}
|
|
111
117
|
} else {
|
|
@@ -184,17 +190,24 @@ try {
|
|
|
184
190
|
if (!vdsCliFound && vdsScriptsDir) {
|
|
185
191
|
const wrapperDir = join(projectPath, '.claude', 'bin');
|
|
186
192
|
const wrapperPath = join(wrapperDir, 'vds-cli');
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (existsSync(uvRunner)) {
|
|
193
|
+
// Check vds-scripts has valid structure (pyproject.toml = uv project)
|
|
194
|
+
if (existsSync(join(vdsScriptsDir, 'pyproject.toml'))) {
|
|
190
195
|
mkdirSync(wrapperDir, { recursive: true });
|
|
191
|
-
const wrapperContent = `#!/usr/bin/env
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
+
const wrapperContent = `#!/usr/bin/env node
|
|
197
|
+
// Project-local vds-cli wrapper — created by vsaf install vds-skill
|
|
198
|
+
const { execFileSync } = require('child_process');
|
|
199
|
+
try {
|
|
200
|
+
execFileSync('uv', ['run', '--directory', ${JSON.stringify(vdsScriptsDir)}, '--package', 'vds-cli', 'vds-cli', ...process.argv.slice(2)], { stdio: 'inherit' });
|
|
201
|
+
} catch (e) {
|
|
202
|
+
process.exit(e.status || 1);
|
|
203
|
+
}
|
|
196
204
|
`;
|
|
197
|
-
writeFileSync(wrapperPath, wrapperContent
|
|
205
|
+
writeFileSync(wrapperPath, wrapperContent);
|
|
206
|
+
if (process.platform !== 'win32') {
|
|
207
|
+
chmodSync(wrapperPath, 0o755);
|
|
208
|
+
} else {
|
|
209
|
+
writeFileSync(wrapperPath + '.cmd', `@node "%~dp0\\vds-cli" %*\r\n`);
|
|
210
|
+
}
|
|
198
211
|
log('✅', `vds-cli wrapper created: ${wrapperPath}`);
|
|
199
212
|
log(' ', `Points to: ${vdsScriptsDir}`);
|
|
200
213
|
log('💡', 'Add to PATH: export PATH=".claude/bin:$PATH"');
|
|
@@ -206,39 +219,27 @@ if (!vdsCliFound) {
|
|
|
206
219
|
log('⚠️', 'vds-cli not found — required for non-dry-run execution');
|
|
207
220
|
log(' ', 'Option 1: Clone vds-scripts into .claude/vds-scripts/ then re-run install');
|
|
208
221
|
log(' ', 'Option 2: Install vds-cli globally (pip install / Viettel internal)');
|
|
209
|
-
log(' ', 'Verify:
|
|
222
|
+
log(' ', 'Verify: vds-cli --version');
|
|
210
223
|
}
|
|
211
224
|
|
|
212
225
|
// ── Step 5: Sync all vds-scripts packages (ensures subcommand binaries exist) ──
|
|
213
226
|
|
|
214
227
|
if (vdsScriptsDir) {
|
|
215
228
|
log('📂', `vds-scripts: ${vdsScriptsDir}`);
|
|
216
|
-
|
|
217
|
-
if (existsSync(uvRunner)) {
|
|
229
|
+
if (existsSync(join(vdsScriptsDir, 'pyproject.toml'))) {
|
|
218
230
|
console.log('');
|
|
219
231
|
log('🔄', 'Syncing vds-scripts packages (uv sync --all-packages)...');
|
|
220
232
|
try {
|
|
221
|
-
execSync(
|
|
233
|
+
execSync('uv sync --all-packages', {
|
|
222
234
|
stdio: 'pipe',
|
|
223
235
|
encoding: 'utf-8',
|
|
224
236
|
timeout: 120000,
|
|
225
237
|
cwd: vdsScriptsDir,
|
|
226
238
|
});
|
|
227
239
|
log('✅', 'All vds-scripts packages synced');
|
|
228
|
-
} catch
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
execSync('uv sync --all-packages', {
|
|
232
|
-
stdio: 'pipe',
|
|
233
|
-
encoding: 'utf-8',
|
|
234
|
-
timeout: 120000,
|
|
235
|
-
cwd: vdsScriptsDir,
|
|
236
|
-
});
|
|
237
|
-
log('✅', 'All vds-scripts packages synced (direct uv)');
|
|
238
|
-
} catch {
|
|
239
|
-
log('⚠️', 'Failed to sync vds-scripts packages — subcommands like confluence/jira may not work');
|
|
240
|
-
log(' ', `Fix: cd ${vdsScriptsDir} && uv sync --all-packages`);
|
|
241
|
-
}
|
|
240
|
+
} catch {
|
|
241
|
+
log('⚠️', 'Failed to sync vds-scripts packages — subcommands like confluence/jira may not work');
|
|
242
|
+
log(' ', `Fix: cd ${vdsScriptsDir} && uv sync --all-packages`);
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
} else {
|
|
@@ -268,7 +269,8 @@ try {
|
|
|
268
269
|
} catch {
|
|
269
270
|
// Check if a shim exists but is broken
|
|
270
271
|
try {
|
|
271
|
-
const
|
|
272
|
+
const whichCmd = process.platform === 'win32' ? 'where' : 'which';
|
|
273
|
+
const shimPath = execSync(`${whichCmd} vds-cli`, { stdio: 'pipe', encoding: 'utf-8' }).trim().split('\n')[0];
|
|
272
274
|
log('❌', `vds-cli BROKEN — ${shimPath} exists but fails to run`);
|
|
273
275
|
log(' ', 'The shim likely points to a deleted venv or missing vds-scripts directory.');
|
|
274
276
|
log(' ', `Fix: rm ${shimPath} then re-run: vsaf install vds-skill`);
|
|
@@ -27,7 +27,7 @@ Wrapper for `git pull` that detects stale local knowledge index after pull and s
|
|
|
27
27
|
Invoke the bundled script:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
|
|
30
|
+
node .claude/skills/vds-skill-pull/scripts/pull.js "$@"
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
## Notes
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* vds-skill-pull: git pull + detect stale local knowledge index.
|
|
6
|
+
* Usage: node pull.js [...git pull args]
|
|
7
|
+
* Exit: forwards git pull exit code. Advisory stale warnings never block.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
const { existsSync, statSync } = require('fs');
|
|
12
|
+
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
|
|
15
|
+
// Forward git pull with all args, inherit I/O
|
|
16
|
+
let exitCode = 0;
|
|
17
|
+
try {
|
|
18
|
+
execSync(`git pull ${args.map(a => JSON.stringify(a)).join(' ')}`, { stdio: 'inherit' });
|
|
19
|
+
} catch (err) {
|
|
20
|
+
exitCode = err.status || 1;
|
|
21
|
+
process.exit(exitCode);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Skip stale check if FETCH_HEAD doesn't exist
|
|
25
|
+
if (!existsSync('.git/FETCH_HEAD')) {
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const fetchMtime = statSync('.git/FETCH_HEAD').mtimeMs;
|
|
30
|
+
const staleReasons = [];
|
|
31
|
+
|
|
32
|
+
// Check .gitnexus/
|
|
33
|
+
if (existsSync('.gitnexus')) {
|
|
34
|
+
const gnMtime = statSync('.gitnexus').mtimeMs;
|
|
35
|
+
if (gnMtime < fetchMtime) {
|
|
36
|
+
staleReasons.push('.gitnexus/ index older than fetched commits');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check graphify-out/graph.json
|
|
41
|
+
if (existsSync('graphify-out/graph.json')) {
|
|
42
|
+
const gMtime = statSync('graphify-out/graph.json').mtimeMs;
|
|
43
|
+
if (gMtime < fetchMtime) {
|
|
44
|
+
staleReasons.push('graphify-out/graph.json older than fetched commits');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (staleReasons.length > 0) {
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(' Knowledge index may be stale after pull:');
|
|
51
|
+
for (const reason of staleReasons) {
|
|
52
|
+
console.log(` - ${reason}`);
|
|
53
|
+
}
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(' Recommend: run /sdlc-onboard-code to rebuild GitNexus + Graphify locally.');
|
|
56
|
+
console.log('');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
process.exit(0);
|
|
@@ -17,32 +17,14 @@ Publish the current PRD to Viettel Confluence via `vds-cli` — create a new pag
|
|
|
17
17
|
Before doing anything, run this check via Bash tool:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
|
|
21
|
-
MISSING=""
|
|
22
|
-
[[ -x .claude/bin/vds-cli ]] && export PATH=".claude/bin:$PATH"
|
|
23
|
-
if ! vds-cli --version >/dev/null 2>&1; then
|
|
24
|
-
MISSING="$MISSING vds-cli"
|
|
25
|
-
# Show why: broken shim vs truly missing
|
|
26
|
-
VDS_PATH=$(command -v vds-cli 2>/dev/null)
|
|
27
|
-
if [[ -n "$VDS_PATH" ]]; then
|
|
28
|
-
echo "NOTE: $VDS_PATH exists but fails to run (broken shim or missing venv)"
|
|
29
|
-
fi
|
|
30
|
-
fi
|
|
31
|
-
[[ -z "${VDS_CONFLUENCE_TOKEN:-}" ]] && MISSING="$MISSING VDS_CONFLUENCE_TOKEN"
|
|
32
|
-
[[ -z "${VDS_CONFLUENCE_SPACE_DEFAULT:-}" ]] && MISSING="$MISSING VDS_CONFLUENCE_SPACE_DEFAULT"
|
|
33
|
-
if [[ -n "$MISSING" ]]; then
|
|
34
|
-
echo "BLOCKED — missing:$MISSING"
|
|
35
|
-
echo "Fix: edit ~/.vds/sdlc-config.env (or run: vsaf install vds-skill)"
|
|
36
|
-
else
|
|
37
|
-
echo "OK"
|
|
38
|
-
fi
|
|
20
|
+
node .claude/skills/_shared/vds-skill/config-check.js --cmd vds-cli --env VDS_CONFLUENCE_TOKEN,VDS_CONFLUENCE_SPACE_DEFAULT
|
|
39
21
|
```
|
|
40
22
|
|
|
41
23
|
If BLOCKED: tell the user exactly what's missing and how to fix, then STOP. Do NOT fabricate output.
|
|
42
24
|
|
|
43
25
|
## Prerequisites
|
|
44
26
|
|
|
45
|
-
- `vds-cli` installed
|
|
27
|
+
- `vds-cli` installed
|
|
46
28
|
- A PRD markdown file must exist
|
|
47
29
|
- `VDS_CONFLUENCE_TOKEN` + `VDS_CONFLUENCE_SPACE_DEFAULT` in `~/.vds/sdlc-config.env`
|
|
48
30
|
- Confluence parent page configured in `.vsaf/config.yaml` (or lazy-prompted)
|
|
@@ -63,7 +45,7 @@ Read `.vsaf/config.yaml` (or `.vsaf/_bmad/bmm/config.yaml`) and extract:
|
|
|
63
45
|
- `confluence_space_key`
|
|
64
46
|
- `confluence_parent_page`
|
|
65
47
|
|
|
66
|
-
If missing: lazy-prompt + persist via `_shared/credentials.
|
|
48
|
+
If missing: lazy-prompt + persist via `_shared/credentials.js ensureEnv`.
|
|
67
49
|
|
|
68
50
|
### Step 3 — Determine page title
|
|
69
51
|
|
|
@@ -150,9 +132,6 @@ Save converted XHTML to a temp file: `BODY_FILE=$(mktemp --suffix=.html)`.
|
|
|
150
132
|
Use Bash tool to run:
|
|
151
133
|
|
|
152
134
|
```bash
|
|
153
|
-
source .claude/skills/_shared/vds-skill/credentials.sh
|
|
154
|
-
ensure_env VDS_CONFLUENCE_TOKEN "Enter VDS Confluence personal access token"
|
|
155
|
-
|
|
156
135
|
SPACE_KEY="<from config>"
|
|
157
136
|
PAGE_TITLE="<title from Step 3>"
|
|
158
137
|
|
|
@@ -160,8 +139,9 @@ SEARCH_RESULT=$(vds-cli confluence search \
|
|
|
160
139
|
--cql "space=$SPACE_KEY AND title = \"$PAGE_TITLE\"" \
|
|
161
140
|
--limit 1 --json-only)
|
|
162
141
|
|
|
163
|
-
EXISTING_PAGE_ID=$(
|
|
164
|
-
|
|
142
|
+
EXISTING_PAGE_ID=$(node -e \
|
|
143
|
+
"const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); process.stdout.write(d.results?.[0]?.id ?? '');" \
|
|
144
|
+
<<< "$SEARCH_RESULT")
|
|
165
145
|
```
|
|
166
146
|
|
|
167
147
|
If `$EXISTING_PAGE_ID` non-empty → Step 6 (update). Otherwise → Step 7 (create).
|
|
@@ -169,8 +149,9 @@ If `$EXISTING_PAGE_ID` non-empty → Step 6 (update). Otherwise → Step 7 (crea
|
|
|
169
149
|
### Step 6 — Update existing page (via vds-cli)
|
|
170
150
|
|
|
171
151
|
```bash
|
|
172
|
-
EXISTING_VERSION=$(
|
|
173
|
-
|
|
152
|
+
EXISTING_VERSION=$(node -e \
|
|
153
|
+
"const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); process.stdout.write(String(d.results?.[0]?.version?.number ?? 1));" \
|
|
154
|
+
<<< "$SEARCH_RESULT")
|
|
174
155
|
|
|
175
156
|
VERSION_COMMENT="${COMMENT:-Updated via vds-skill-push-prd}"
|
|
176
157
|
|
|
@@ -188,7 +169,7 @@ rm -f "$BODY_FILE"
|
|
|
188
169
|
|
|
189
170
|
```bash
|
|
190
171
|
LATEST_VERSION=$(vds-cli confluence content page "$EXISTING_PAGE_ID" --json-only \
|
|
191
|
-
|
|
|
172
|
+
| node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); process.stdout.write(String(d.version.number));")
|
|
192
173
|
|
|
193
174
|
vds-cli confluence content update \
|
|
194
175
|
--page-id "$EXISTING_PAGE_ID" \
|
|
@@ -207,7 +188,7 @@ if [[ -n "$CONFLUENCE_PARENT_PAGE" ]]; then
|
|
|
207
188
|
PARENT_ID=$(vds-cli confluence search \
|
|
208
189
|
--cql "space=$SPACE_KEY AND title = \"$CONFLUENCE_PARENT_PAGE\"" \
|
|
209
190
|
--limit 1 --json-only \
|
|
210
|
-
|
|
|
191
|
+
| node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); process.stdout.write(d.results?.[0]?.id ?? '');")
|
|
211
192
|
fi
|
|
212
193
|
|
|
213
194
|
CREATE_ARGS=(vds-cli confluence content create-page
|
|
@@ -17,24 +17,7 @@ Publish the current SRS to Viettel Confluence via `vds-cli` — create or update
|
|
|
17
17
|
Before doing anything, run this check via Bash tool:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
|
|
21
|
-
MISSING=""
|
|
22
|
-
[[ -x .claude/bin/vds-cli ]] && export PATH=".claude/bin:$PATH"
|
|
23
|
-
if ! vds-cli --version >/dev/null 2>&1; then
|
|
24
|
-
MISSING="$MISSING vds-cli"
|
|
25
|
-
VDS_PATH=$(command -v vds-cli 2>/dev/null)
|
|
26
|
-
if [[ -n "$VDS_PATH" ]]; then
|
|
27
|
-
echo "NOTE: $VDS_PATH exists but fails to run (broken shim or missing venv)"
|
|
28
|
-
fi
|
|
29
|
-
fi
|
|
30
|
-
[[ -z "${VDS_CONFLUENCE_TOKEN:-}" ]] && MISSING="$MISSING VDS_CONFLUENCE_TOKEN"
|
|
31
|
-
[[ -z "${VDS_CONFLUENCE_SPACE_DEFAULT:-}" ]] && MISSING="$MISSING VDS_CONFLUENCE_SPACE_DEFAULT"
|
|
32
|
-
if [[ -n "$MISSING" ]]; then
|
|
33
|
-
echo "BLOCKED — missing:$MISSING"
|
|
34
|
-
echo "Fix: edit ~/.vds/sdlc-config.env (or run: vsaf install vds-skill)"
|
|
35
|
-
else
|
|
36
|
-
echo "OK"
|
|
37
|
-
fi
|
|
20
|
+
node .claude/skills/_shared/vds-skill/config-check.js --cmd vds-cli --env VDS_CONFLUENCE_TOKEN,VDS_CONFLUENCE_SPACE_DEFAULT
|
|
38
21
|
```
|
|
39
22
|
|
|
40
23
|
If BLOCKED: tell the user exactly what's missing and how to fix, then STOP. Do NOT fabricate output.
|
|
@@ -84,9 +67,6 @@ Same as `/vds-skill-push-prd` Step 7. Use `Initial SRS push via vds-skill-push-s
|
|
|
84
67
|
### Step 8 — Link SRS page to PRD page (if PRD exists)
|
|
85
68
|
|
|
86
69
|
```bash
|
|
87
|
-
source .claude/skills/_shared/vds-skill/credentials.sh
|
|
88
|
-
ensure_env VDS_CONFLUENCE_TOKEN "Enter VDS Confluence personal access token"
|
|
89
|
-
|
|
90
70
|
# Derive PRD page title from feature name
|
|
91
71
|
FEATURE_NAME="<from folder>"
|
|
92
72
|
PRD_TITLE="[PRD] $FEATURE_NAME"
|
|
@@ -95,8 +75,9 @@ PRD_PAGE=$(vds-cli confluence search \
|
|
|
95
75
|
--cql "space=$SPACE_KEY AND title = \"$PRD_TITLE\"" \
|
|
96
76
|
--limit 1 --json-only)
|
|
97
77
|
|
|
98
|
-
PRD_URL=$(
|
|
99
|
-
|
|
78
|
+
PRD_URL=$(node -e \
|
|
79
|
+
"const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); process.stdout.write(d.results?.[0]?.url ?? '');" \
|
|
80
|
+
<<< "$PRD_PAGE")
|
|
100
81
|
|
|
101
82
|
if [[ -n "$PRD_URL" ]]; then
|
|
102
83
|
# Prepend "Related Documents" macro to SRS body BEFORE push (modify $BODY_FILE)
|
|
@@ -12,26 +12,7 @@ Search Viettel Confluence + Jira for pages/tickets related to a feature, then wr
|
|
|
12
12
|
Before doing anything, run this check via Bash tool:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
|
|
16
|
-
MISSING=""
|
|
17
|
-
[[ -x .claude/bin/vds-cli ]] && export PATH=".claude/bin:$PATH"
|
|
18
|
-
if ! vds-cli --version >/dev/null 2>&1; then
|
|
19
|
-
MISSING="$MISSING vds-cli"
|
|
20
|
-
VDS_PATH=$(command -v vds-cli 2>/dev/null)
|
|
21
|
-
if [[ -n "$VDS_PATH" ]]; then
|
|
22
|
-
echo "NOTE: $VDS_PATH exists but fails to run (broken shim or missing venv)"
|
|
23
|
-
fi
|
|
24
|
-
fi
|
|
25
|
-
[[ -z "${VDS_CONFLUENCE_TOKEN:-}" ]] && MISSING="$MISSING VDS_CONFLUENCE_TOKEN"
|
|
26
|
-
[[ -z "${VDS_JIRA_TOKEN:-}" ]] && MISSING="$MISSING VDS_JIRA_TOKEN"
|
|
27
|
-
[[ -z "${VDS_CONFLUENCE_SPACE_DEFAULT:-}" ]] && MISSING="$MISSING VDS_CONFLUENCE_SPACE_DEFAULT"
|
|
28
|
-
[[ -z "${VDS_JIRA_PROJECT_DEFAULT:-}" ]] && MISSING="$MISSING VDS_JIRA_PROJECT_DEFAULT"
|
|
29
|
-
if [[ -n "$MISSING" ]]; then
|
|
30
|
-
echo "BLOCKED — missing:$MISSING"
|
|
31
|
-
echo "Fix: edit ~/.vds/sdlc-config.env (or run: vsaf install vds-skill)"
|
|
32
|
-
else
|
|
33
|
-
echo "OK"
|
|
34
|
-
fi
|
|
15
|
+
node .claude/skills/_shared/vds-skill/config-check.js --cmd vds-cli --env VDS_CONFLUENCE_TOKEN,VDS_JIRA_TOKEN,VDS_CONFLUENCE_SPACE_DEFAULT,VDS_JIRA_PROJECT_DEFAULT
|
|
35
16
|
```
|
|
36
17
|
|
|
37
18
|
If BLOCKED: tell the user exactly what's missing and how to fix, then STOP. Do NOT fabricate output. `--dry-run` mode skips vds-cli check.
|
|
@@ -39,7 +20,6 @@ If BLOCKED: tell the user exactly what's missing and how to fix, then STOP. Do N
|
|
|
39
20
|
## Prerequisites
|
|
40
21
|
|
|
41
22
|
- `vds-cli` installed
|
|
42
|
-
- `python3` installed
|
|
43
23
|
- Inside a project root with `.vsaf/docs/features/`
|
|
44
24
|
- `VDS_CONFLUENCE_TOKEN`, `VDS_JIRA_TOKEN`, `VDS_CONFLUENCE_SPACE_DEFAULT`, `VDS_JIRA_PROJECT_DEFAULT` in `~/.vds/sdlc-config.env`
|
|
45
25
|
|
|
@@ -62,7 +42,7 @@ If BLOCKED: tell the user exactly what's missing and how to fix, then STOP. Do N
|
|
|
62
42
|
## Implementation
|
|
63
43
|
|
|
64
44
|
```bash
|
|
65
|
-
|
|
45
|
+
node .claude/skills/vds-skill-search-confluence/scripts/search.js "$@"
|
|
66
46
|
```
|
|
67
47
|
|
|
68
48
|
## Notes
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { execFileSync } = require('child_process');
|
|
5
|
+
const { existsSync, mkdirSync, writeFileSync } = require('fs');
|
|
6
|
+
const { join } = require('path');
|
|
7
|
+
// NOTE: path matches DEPLOYED location (.claude/skills/_shared/vds-skill/), not source repo
|
|
8
|
+
const { loadCredentials, ensureEnv, requireCommand } = require('../../_shared/vds-skill/credentials.js');
|
|
9
|
+
|
|
10
|
+
let feature = '';
|
|
11
|
+
let query = '';
|
|
12
|
+
let days = 180;
|
|
13
|
+
let limit = 30;
|
|
14
|
+
let dryRun = false;
|
|
15
|
+
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
for (let i = 0; i < args.length; i++) {
|
|
18
|
+
switch (args[i]) {
|
|
19
|
+
case '--feature': feature = args[++i]; break;
|
|
20
|
+
case '--query': query = args[++i]; break;
|
|
21
|
+
case '--days': days = parseInt(args[++i], 10); break;
|
|
22
|
+
case '--limit': limit = parseInt(args[++i], 10); break;
|
|
23
|
+
case '--dry-run': dryRun = true; break;
|
|
24
|
+
default:
|
|
25
|
+
process.stderr.write(`Unknown arg: ${args[i]}\n`);
|
|
26
|
+
process.exit(2);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!feature) { process.stderr.write('ERROR: --feature is required\n'); process.exit(2); }
|
|
31
|
+
if (!query) { process.stderr.write('ERROR: --query (search keywords) is required\n'); process.exit(2); }
|
|
32
|
+
|
|
33
|
+
const outDir = join('.vsaf', 'docs', 'features', feature);
|
|
34
|
+
const outJson = join(outDir, '01-discovery-historical.json');
|
|
35
|
+
const outMd = join(outDir, '01-discovery-historical.md');
|
|
36
|
+
|
|
37
|
+
if (dryRun) {
|
|
38
|
+
loadCredentials();
|
|
39
|
+
const space = process.env.VDS_CONFLUENCE_SPACE_DEFAULT || '<VDS_CONFLUENCE_SPACE_DEFAULT>';
|
|
40
|
+
const project = process.env.VDS_JIRA_PROJECT_DEFAULT || '<VDS_JIRA_PROJECT_DEFAULT>';
|
|
41
|
+
const cql = `space=${space} AND text ~ "${query}" AND lastmodified > now("-${days}d")`;
|
|
42
|
+
const jql = `project = ${project} AND text ~ "${query}" AND created > -${days}d ORDER BY created DESC`;
|
|
43
|
+
console.log('DRY-RUN — would execute:');
|
|
44
|
+
console.log(` vds-cli confluence search --cql "${cql}" --limit ${limit} --json-only`);
|
|
45
|
+
console.log(` vds-cli jira search "${jql}" --limit ${limit} --json-only`);
|
|
46
|
+
console.log(` Write to: ${outJson} + ${outMd}`);
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
loadCredentials();
|
|
51
|
+
|
|
52
|
+
const cmdCheck = requireCommand('vds-cli');
|
|
53
|
+
if (!cmdCheck.found) {
|
|
54
|
+
process.stderr.write('ERROR: vds-cli not found in PATH\n');
|
|
55
|
+
if (cmdCheck.broken) process.stderr.write(`NOTE: ${cmdCheck.path} exists but fails to run (broken shim or missing venv)\n`);
|
|
56
|
+
process.exit(127);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
(async () => {
|
|
60
|
+
if (!await ensureEnv('VDS_CONFLUENCE_TOKEN', 'Enter VDS Confluence personal access token')) process.exit(1);
|
|
61
|
+
if (!await ensureEnv('VDS_JIRA_TOKEN', 'Enter VDS Jira personal access token')) process.exit(1);
|
|
62
|
+
if (!await ensureEnv('VDS_CONFLUENCE_SPACE_DEFAULT', 'Enter default Confluence space key (e.g. ENG)', false)) process.exit(1);
|
|
63
|
+
if (!await ensureEnv('VDS_JIRA_PROJECT_DEFAULT', 'Enter default Jira project key (e.g. NTTC)', false)) process.exit(1);
|
|
64
|
+
|
|
65
|
+
const space = process.env.VDS_CONFLUENCE_SPACE_DEFAULT;
|
|
66
|
+
const project = process.env.VDS_JIRA_PROJECT_DEFAULT;
|
|
67
|
+
const cql = `space=${space} AND text ~ "${query}" AND lastmodified > now("-${days}d")`;
|
|
68
|
+
const jql = `project = ${project} AND text ~ "${query}" AND created > -${days}d ORDER BY created DESC`;
|
|
69
|
+
|
|
70
|
+
mkdirSync(outDir, { recursive: true });
|
|
71
|
+
|
|
72
|
+
console.log(`Searching Confluence (limit ${limit}, last ${days}d)...`);
|
|
73
|
+
let confResults;
|
|
74
|
+
try {
|
|
75
|
+
const stdout = execFileSync('vds-cli', ['confluence', 'search', '--cql', cql, '--limit', String(limit), '--json-only'], {
|
|
76
|
+
encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
77
|
+
});
|
|
78
|
+
confResults = JSON.parse(stdout);
|
|
79
|
+
} catch { confResults = { results: [] }; }
|
|
80
|
+
|
|
81
|
+
console.log(`Searching Jira (limit ${limit}, last ${days}d)...`);
|
|
82
|
+
let jiraResults;
|
|
83
|
+
try {
|
|
84
|
+
const stdout = execFileSync('vds-cli', ['jira', 'search', jql, '--limit', String(limit), '--json-only'], {
|
|
85
|
+
encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
86
|
+
});
|
|
87
|
+
jiraResults = JSON.parse(stdout);
|
|
88
|
+
} catch { jiraResults = { issues: [] }; }
|
|
89
|
+
|
|
90
|
+
const combined = { query, days_back: days, confluence: confResults.results || [], jira: jiraResults.issues || [] };
|
|
91
|
+
writeFileSync(outJson, JSON.stringify(combined, null, 2) + '\n');
|
|
92
|
+
|
|
93
|
+
const lines = [
|
|
94
|
+
`# Discovery Historical Context — ${query}`, '',
|
|
95
|
+
`> Generated by /vds-skill-search-confluence — searched last ${days} days`, '',
|
|
96
|
+
`## Confluence pages (${combined.confluence.length} results)`, '',
|
|
97
|
+
];
|
|
98
|
+
for (const page of combined.confluence.slice(0, 10)) {
|
|
99
|
+
const title = page.title || '<no title>';
|
|
100
|
+
const url = page.url || (page._links && page._links.webui) || '';
|
|
101
|
+
const modified = page.lastModified || (page.version && page.version.when) || '?';
|
|
102
|
+
lines.push(`- [${title}](${url}) — modified ${modified}`);
|
|
103
|
+
}
|
|
104
|
+
lines.push('', `## Jira tickets (${combined.jira.length} results)`, '');
|
|
105
|
+
for (const issue of combined.jira.slice(0, 15)) {
|
|
106
|
+
const key = issue.key || '?';
|
|
107
|
+
const summary = (issue.fields && issue.fields.summary) || '<no summary>';
|
|
108
|
+
const status = (issue.fields && issue.fields.status && issue.fields.status.name) || '?';
|
|
109
|
+
lines.push(`- **${key}** — ${summary} (${status})`);
|
|
110
|
+
}
|
|
111
|
+
writeFileSync(outMd, lines.join('\n') + '\n');
|
|
112
|
+
console.log(`Wrote: ${outJson}`);
|
|
113
|
+
console.log(`Wrote: ${outMd}`);
|
|
114
|
+
})();
|
|
@@ -57,11 +57,11 @@ vds-cli bitbucket --help
|
|
|
57
57
|
vds-cli git --help
|
|
58
58
|
|
|
59
59
|
# Direct uv invocation — project-local
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
uv run --directory .claude/vds-scripts --package vds-cli vds-cli --help
|
|
61
|
+
uv run --directory .claude/vds-scripts --package audit_orchestrator vds-audit --help
|
|
62
62
|
|
|
63
63
|
# Direct uv invocation — global fallback
|
|
64
|
-
|
|
64
|
+
uv run --directory ~/.claude/vds-scripts --package vds-cli vds-cli --help
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
## Platform Command Families
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Shared credential helper for vds-skill-* skills.
|
|
3
|
-
# Sourced by skill scripts to lazy-prompt + persist credentials.
|
|
4
|
-
# Config file: ~/.vds/sdlc-config.env (chmod 600)
|
|
5
|
-
#
|
|
6
|
-
# Portable across bash and zsh (uses eval for indirect expansion).
|
|
7
|
-
|
|
8
|
-
VSAF_CONFIG_FILE="${VSAF_CONFIG_FILE:-$HOME/.vds/sdlc-config.env}"
|
|
9
|
-
readonly VSAF_CONFIG_FILE
|
|
10
|
-
|
|
11
|
-
# Load existing config if present
|
|
12
|
-
if [[ -f "$VSAF_CONFIG_FILE" ]]; then
|
|
13
|
-
# shellcheck source=/dev/null
|
|
14
|
-
source "$VSAF_CONFIG_FILE"
|
|
15
|
-
fi
|
|
16
|
-
|
|
17
|
-
# Also load vds-cli's own env if present (for token reuse)
|
|
18
|
-
if [[ -f "$HOME/.vds/.env" ]]; then
|
|
19
|
-
set -a
|
|
20
|
-
# shellcheck source=/dev/null
|
|
21
|
-
source "$HOME/.vds/.env"
|
|
22
|
-
set +a
|
|
23
|
-
fi
|
|
24
|
-
|
|
25
|
-
# ensure_env VAR_NAME "Prompt message" [is_secret]
|
|
26
|
-
# Prompts user if VAR is empty, persists to config file.
|
|
27
|
-
# Re-persists if VAR is pre-set (supports token rotation).
|
|
28
|
-
ensure_env() {
|
|
29
|
-
local var="$1"
|
|
30
|
-
local prompt="${2:-Enter $var}"
|
|
31
|
-
local is_secret="${3:-true}"
|
|
32
|
-
|
|
33
|
-
# Portable indirect expansion (works in both bash and zsh)
|
|
34
|
-
local value
|
|
35
|
-
eval "value=\${$var:-}"
|
|
36
|
-
|
|
37
|
-
if [[ -z "$value" ]]; then
|
|
38
|
-
# Prompt user
|
|
39
|
-
if [[ "$is_secret" == "true" ]]; then
|
|
40
|
-
read -rsp "$prompt: " value
|
|
41
|
-
echo "" >&2
|
|
42
|
-
else
|
|
43
|
-
read -rp "$prompt: " value
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
if [[ -z "$value" ]]; then
|
|
47
|
-
echo "ERROR: $var is required" >&2
|
|
48
|
-
return 1
|
|
49
|
-
fi
|
|
50
|
-
|
|
51
|
-
export "$var=$value"
|
|
52
|
-
fi
|
|
53
|
-
|
|
54
|
-
# Persist to config file (idempotent — replace existing entry or append)
|
|
55
|
-
mkdir -p "$(dirname "$VSAF_CONFIG_FILE")" || { echo "ERROR: Cannot create config dir $(dirname "$VSAF_CONFIG_FILE")" >&2; return 1; }
|
|
56
|
-
|
|
57
|
-
if grep -q "^${var}=" "$VSAF_CONFIG_FILE" 2>/dev/null; then
|
|
58
|
-
# Update in place (BSD/GNU sed compatible via tmp file)
|
|
59
|
-
local tmp
|
|
60
|
-
tmp=$(mktemp) || { echo "ERROR: mktemp failed" >&2; return 1; }
|
|
61
|
-
trap 'rm -f "$tmp"' EXIT INT TERM
|
|
62
|
-
grep -v "^${var}=" "$VSAF_CONFIG_FILE" > "$tmp"
|
|
63
|
-
printf '%s=%q\n' "$var" "$value" >> "$tmp"
|
|
64
|
-
mv "$tmp" "$VSAF_CONFIG_FILE"
|
|
65
|
-
trap - EXIT INT TERM
|
|
66
|
-
else
|
|
67
|
-
printf '%s=%q\n' "$var" "$value" >> "$VSAF_CONFIG_FILE"
|
|
68
|
-
fi
|
|
69
|
-
chmod 600 "$VSAF_CONFIG_FILE" || echo "WARN: Could not set chmod 600 on $VSAF_CONFIG_FILE" >&2
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
# require_command COMMAND_NAME — exits 127 if missing
|
|
73
|
-
require_command() {
|
|
74
|
-
if ! command -v "$1" >/dev/null 2>&1; then
|
|
75
|
-
echo "ERROR: Required command '$1' not found in PATH" >&2
|
|
76
|
-
echo " Install it before running this skill." >&2
|
|
77
|
-
return 127
|
|
78
|
-
fi
|
|
79
|
-
}
|