@ngocsangairvds/vsaf 4.0.9 → 4.0.10
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 +1 -1
- 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
|
@@ -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
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# vds-skill-create-bitbucket-pr: create Bitbucket PR via vds-cli
|
|
3
|
-
# Usage: create-pr.sh [--dry-run] [--target BRANCH] [--description-file FILE] [--title TITLE]
|
|
4
|
-
# Auto-extracts project/repo from `git remote get-url origin`.
|
|
5
|
-
|
|
6
|
-
# NOTE: No `set -e` — we want full control of the exit-code path.
|
|
7
|
-
|
|
8
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
-
# shellcheck source=/dev/null
|
|
10
|
-
source "$SCRIPT_DIR/../../_shared/vds-skill/credentials.sh"
|
|
11
|
-
|
|
12
|
-
require_command git
|
|
13
|
-
# Note: require_command vds-cli moved AFTER dry-run check.
|
|
14
|
-
|
|
15
|
-
DRY_RUN=false
|
|
16
|
-
TARGET_BRANCH="master"
|
|
17
|
-
DESC_FILE=""
|
|
18
|
-
TITLE=""
|
|
19
|
-
|
|
20
|
-
while [[ $# -gt 0 ]]; do
|
|
21
|
-
case "$1" in
|
|
22
|
-
--dry-run) DRY_RUN=true; shift ;;
|
|
23
|
-
--target) TARGET_BRANCH="$2"; shift 2 ;;
|
|
24
|
-
--description-file) DESC_FILE="$2"; shift 2 ;;
|
|
25
|
-
--title) TITLE="$2"; shift 2 ;;
|
|
26
|
-
*) echo "Unknown arg: $1" >&2; exit 2 ;;
|
|
27
|
-
esac
|
|
28
|
-
done
|
|
29
|
-
|
|
30
|
-
REMOTE_URL=$(git remote get-url origin 2>/dev/null || true)
|
|
31
|
-
if [[ -z "$REMOTE_URL" ]]; then
|
|
32
|
-
echo "ERROR: No git remote 'origin' configured" >&2
|
|
33
|
-
exit 1
|
|
34
|
-
fi
|
|
35
|
-
|
|
36
|
-
if [[ ! "$REMOTE_URL" =~ bitbucket\.digital\.vn ]]; then
|
|
37
|
-
echo "WARNING: Remote URL does not match bitbucket.digital.vn pattern:" >&2
|
|
38
|
-
echo " $REMOTE_URL" >&2
|
|
39
|
-
echo " This skill is for Viettel Bitbucket. For GitHub, use 'gh pr create'." >&2
|
|
40
|
-
exit 1
|
|
41
|
-
fi
|
|
42
|
-
|
|
43
|
-
# Extract PROJECT/REPO from URL
|
|
44
|
-
# Handles:
|
|
45
|
-
# ssh://git@bitbucket.digital.vn/PROJECT/repo.git
|
|
46
|
-
# ssh://git@bitbucket.digital.vn:7999/PROJECT/repo.git
|
|
47
|
-
# https://bitbucket.digital.vn/scm/PROJECT/repo.git
|
|
48
|
-
if [[ "$REMOTE_URL" =~ /scm/([^/]+)/([^/]+)\.git$ ]]; then
|
|
49
|
-
PROJECT="${BASH_REMATCH[1]}"
|
|
50
|
-
REPO="${BASH_REMATCH[2]}"
|
|
51
|
-
elif [[ "$REMOTE_URL" =~ /([^/]+)/([^/]+)\.git$ ]]; then
|
|
52
|
-
PROJECT="${BASH_REMATCH[1]}"
|
|
53
|
-
REPO="${BASH_REMATCH[2]}"
|
|
54
|
-
else
|
|
55
|
-
echo "ERROR: Cannot parse PROJECT/REPO from remote URL: $REMOTE_URL" >&2
|
|
56
|
-
exit 1
|
|
57
|
-
fi
|
|
58
|
-
|
|
59
|
-
SOURCE_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
60
|
-
if [[ -z "$SOURCE_BRANCH" ]] || [[ "$SOURCE_BRANCH" == "HEAD" ]]; then
|
|
61
|
-
echo "ERROR: Not on a named branch" >&2
|
|
62
|
-
exit 1
|
|
63
|
-
fi
|
|
64
|
-
|
|
65
|
-
if [[ -z "$TITLE" ]]; then
|
|
66
|
-
TITLE=$(git log -1 --pretty=%s)
|
|
67
|
-
fi
|
|
68
|
-
|
|
69
|
-
if [[ -z "$DESC_FILE" ]]; then
|
|
70
|
-
DESC_FILE=$(find .vsaf/docs/features -name '09-ship.md' 2>/dev/null | head -1)
|
|
71
|
-
[[ -z "$DESC_FILE" ]] && DESC_FILE=$(find .vsaf/docs/hotfixes -name '03-ship.md' 2>/dev/null | head -1)
|
|
72
|
-
fi
|
|
73
|
-
|
|
74
|
-
CMD=(vds-cli bitbucket pr create "$PROJECT/$REPO"
|
|
75
|
-
--source "$SOURCE_BRANCH"
|
|
76
|
-
--target "$TARGET_BRANCH"
|
|
77
|
-
--title "$TITLE")
|
|
78
|
-
[[ -n "$DESC_FILE" ]] && [[ -f "$DESC_FILE" ]] && CMD+=(--description-file "$DESC_FILE")
|
|
79
|
-
|
|
80
|
-
if [[ "$DRY_RUN" == "true" ]]; then
|
|
81
|
-
echo "DRY-RUN — would execute:"
|
|
82
|
-
printf ' %q' "${CMD[@]}"
|
|
83
|
-
echo ""
|
|
84
|
-
echo " + --yes --json-only"
|
|
85
|
-
exit 0
|
|
86
|
-
fi
|
|
87
|
-
|
|
88
|
-
require_command vds-cli
|
|
89
|
-
|
|
90
|
-
ensure_env VDS_BITBUCKET_TOKEN "Enter VDS Bitbucket personal access token"
|
|
91
|
-
|
|
92
|
-
echo "About to create PR:"
|
|
93
|
-
echo " Project/Repo: $PROJECT/$REPO"
|
|
94
|
-
echo " Source: $SOURCE_BRANCH"
|
|
95
|
-
echo " Target: $TARGET_BRANCH"
|
|
96
|
-
echo " Title: $TITLE"
|
|
97
|
-
echo " Description: ${DESC_FILE:-<empty>}"
|
|
98
|
-
echo ""
|
|
99
|
-
read -rp "Proceed? [y/N]: " confirm
|
|
100
|
-
if [[ "$confirm" != "y" ]] && [[ "$confirm" != "Y" ]]; then
|
|
101
|
-
echo "Aborted"
|
|
102
|
-
exit 1
|
|
103
|
-
fi
|
|
104
|
-
|
|
105
|
-
"${CMD[@]}" --yes --json-only
|