@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.
@@ -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.sh)',
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'), { mode: 0o600 });
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'), { mode: 0o600 });
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
- const uvRunner = join(vdsScriptsDir, 'scripts', 'worktree_uv.sh');
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 bash
192
- # Project-local vds-cli wrapper — created by vsaf install vds-skill
193
- # Delegates to worktree_uv.sh in the detected vds-scripts directory.
194
- set -euo pipefail
195
- exec "${uvRunner}" run --directory "${vdsScriptsDir}" --package vds-cli vds-cli "$@"
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, { mode: 0o755 });
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: command -v vds-cli && vds-cli --version');
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
- const uvRunner = join(vdsScriptsDir, 'scripts', 'worktree_uv.sh');
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(`"${uvRunner}" sync --directory "${vdsScriptsDir}" --all-packages`, {
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 (e) {
229
- // Fallback: try direct uv sync
230
- try {
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 shimPath = execSync('command -v vds-cli', { stdio: 'pipe', encoding: 'utf-8' }).trim();
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
- bash .claude/skills/vds-skill-pull/scripts/pull.sh "$@"
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
- source .claude/skills/_shared/vds-skill/credentials.sh 2>/dev/null
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 (`command -v vds-cli`)
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.sh ensure_env`.
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=$(echo "$SEARCH_RESULT" | python3 -c \
164
- 'import json,sys; d=json.load(sys.stdin); print(d["results"][0]["id"] if d.get("results") else "")')
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=$(echo "$SEARCH_RESULT" | python3 -c \
173
- 'import json,sys; d=json.load(sys.stdin); print(d["results"][0].get("version",{}).get("number",1))')
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
- | python3 -c 'import json,sys; print(json.load(sys.stdin)["version"]["number"])')
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
- | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["results"][0]["id"] if d.get("results") else "")')
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
- source .claude/skills/_shared/vds-skill/credentials.sh 2>/dev/null
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=$(echo "$PRD_PAGE" | python3 -c \
99
- 'import json,sys; d=json.load(sys.stdin); r=d.get("results"); print(r[0].get("url","") if r else "")')
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
- source .claude/skills/_shared/vds-skill/credentials.sh 2>/dev/null
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
- bash .claude/skills/vds-skill-search-confluence/scripts/search.sh "$@"
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
- .claude/vds-scripts/scripts/worktree_uv.sh run --directory .claude/vds-scripts --package vds-cli vds-cli --help
61
- .claude/vds-scripts/scripts/worktree_uv.sh run --directory .claude/vds-scripts --package audit_orchestrator vds-audit --help
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
- ~/.claude/vds-scripts/scripts/worktree_uv.sh run --directory ~/.claude/vds-scripts --package vds-cli vds-cli --help
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
- }