@ngocsangairvds/vsaf 5.5.1 → 5.6.0
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 +3 -0
- package/package.json +1 -1
- package/scripts/bun-bootstrap.js +127 -0
- package/scripts/postinstall.js +39 -8
- package/skills/sdlc/onboard-code/SKILL.md +4 -1
- package/skills/sdlc/onboard-docs/SKILL.md +4 -1
- package/workflows/sdlc/onboard-code.yaml +5 -1
- package/workflows/sdlc/onboard-docs.yaml +10 -1
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ Every release is verified by the [Install Matrix CI](.github/workflows/install-m
|
|
|
62
62
|
| Requirement | Version | Check |
|
|
63
63
|
|---|---|---|
|
|
64
64
|
| Node.js | >= 20.0.0 | `node -v` |
|
|
65
|
+
| Bun | >= 1.3.0 | `bun --version` |
|
|
65
66
|
| npm | >= 9.0.0 | `npm -v` |
|
|
66
67
|
| Python | >= 3.10 | `python --version` |
|
|
67
68
|
| git | any | `git --version` |
|
|
@@ -70,6 +71,8 @@ Every release is verified by the [Install Matrix CI](.github/workflows/install-m
|
|
|
70
71
|
|
|
71
72
|
**Claude Code CLI** is required for prompt and command nodes. **GitHub CLI** is required only for workflows that interact with GitHub (e.g., `fix-issue` uses `gh issue view`); on Windows install it with `winget install GitHub.cli` or the MSI from [github.com/cli/cli/releases](https://github.com/cli/cli/releases). **Python ≥ 3.10** is required by `vsaf install sdlc` (pipx installs graphify + markitdown). On Windows the Visual C++ Redistributable is auto-installed by `vsaf install sdlc` when missing; run `vsaf doctor` any time to see which prerequisites are present.
|
|
72
73
|
|
|
74
|
+
> **Bun is required to run `vsaf`** (the CLI executes under Bun). `npm install -g @ngocsangairvds/vsaf` auto-installs Bun via the official [bun.sh](https://bun.sh) installer when it is missing — the install is announced, best-effort (never fails your `npm install`), and skipped in CI. Opt out with `VSAF_SKIP_BUN_INSTALL=1` and install Bun yourself. After an auto-install, open a **new terminal** so `~/.bun/bin` is on your `PATH`.
|
|
75
|
+
|
|
73
76
|
### Option 1: Global Install (Recommended)
|
|
74
77
|
|
|
75
78
|
```bash
|
package/package.json
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Bun bootstrap decision logic (pure) + orchestrator.
|
|
4
|
+
// Runs under plain `node` during `npm install` (postinstall) → CommonJS on purpose.
|
|
5
|
+
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const BUN_INSTALL_SH = 'https://bun.sh/install';
|
|
10
|
+
const BUN_INSTALL_PS1 = 'https://bun.sh/install.ps1';
|
|
11
|
+
|
|
12
|
+
/** Default location the bun.sh installer drops the binary. */
|
|
13
|
+
function wellKnownBunPath(platform, env) {
|
|
14
|
+
if (platform === 'win32') {
|
|
15
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
16
|
+
return path.join(home, '.bun', 'bin', 'bun.exe');
|
|
17
|
+
}
|
|
18
|
+
const home = env.HOME || os.homedir();
|
|
19
|
+
return path.join(home, '.bun', 'bin', 'bun');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Is Bun already available? Checks PATH first, then the well-known install dir,
|
|
24
|
+
* so a prior install whose PATH has not been reloaded this session is still detected.
|
|
25
|
+
*/
|
|
26
|
+
function detectBun({ platform, env, exists, probe }) {
|
|
27
|
+
if (probe('bun')) return { present: true, onPath: true };
|
|
28
|
+
const p = wellKnownBunPath(platform, env);
|
|
29
|
+
if (exists(p)) return { present: true, onPath: false, path: p };
|
|
30
|
+
return { present: false, onPath: false };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function manualInstructions(platform) {
|
|
34
|
+
if (platform === 'win32') {
|
|
35
|
+
return 'VSAF needs Bun. Install it, then open a new terminal:\n'
|
|
36
|
+
+ ' powershell -c "irm https://bun.sh/install.ps1 | iex"';
|
|
37
|
+
}
|
|
38
|
+
return 'VSAF needs Bun. Install it, then open a new terminal:\n'
|
|
39
|
+
+ ' curl -fsSL https://bun.sh/install | bash\n'
|
|
40
|
+
+ ' (or: wget -qO- https://bun.sh/install | bash)';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Decide what to do about Bun. Pure. */
|
|
44
|
+
function bunInstallPlan({ platform, hasBun, isCI, optOut, hasCurl, hasWget }) {
|
|
45
|
+
if (hasBun) return { action: 'skip', reason: 'bun already installed' };
|
|
46
|
+
if (optOut) return { action: 'skip', reason: 'VSAF_SKIP_BUN_INSTALL=1' };
|
|
47
|
+
if (isCI) return { action: 'skip', reason: 'CI environment' };
|
|
48
|
+
|
|
49
|
+
if (platform === 'win32') {
|
|
50
|
+
return { action: 'install', shell: 'powershell', command: `powershell -c "irm ${BUN_INSTALL_PS1} | iex"` };
|
|
51
|
+
}
|
|
52
|
+
if (hasCurl) {
|
|
53
|
+
return { action: 'install', shell: 'bash', command: `curl -fsSL ${BUN_INSTALL_SH} | bash` };
|
|
54
|
+
}
|
|
55
|
+
if (hasWget) {
|
|
56
|
+
return { action: 'install', shell: 'bash', command: `wget -qO- ${BUN_INSTALL_SH} | bash` };
|
|
57
|
+
}
|
|
58
|
+
return { action: 'manual', instructions: manualInstructions(platform) };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Reminder that the current shell session has not picked up ~/.bun/bin yet. */
|
|
62
|
+
function postInstallPathNote(platform, env) {
|
|
63
|
+
if (platform === 'win32') {
|
|
64
|
+
return '✓ Bun installed. Open a NEW terminal before using `vsaf`.';
|
|
65
|
+
}
|
|
66
|
+
const rc = String(env.SHELL || '').includes('zsh') ? '~/.zshrc' : '~/.bashrc';
|
|
67
|
+
return '✓ Bun installed to ~/.bun/bin\n'
|
|
68
|
+
+ ` ⚠ Open a NEW terminal (or run: source ${rc}) before using \`vsaf\`.`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Device that reaches the user's controlling terminal directly. npm suppresses
|
|
73
|
+
* postinstall stdout/stderr by default (even with a TTY), so writing to the
|
|
74
|
+
* terminal device is the only way the user reliably sees these messages.
|
|
75
|
+
*/
|
|
76
|
+
function userTtyDevice(platform) {
|
|
77
|
+
return platform === 'win32' ? '\\\\.\\CONOUT$' : '/dev/tty';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Build a notifier that writes to the controlling TTY (bypassing npm's output
|
|
82
|
+
* capture), falling back to stderr when there is no TTY (CI / piped output).
|
|
83
|
+
*/
|
|
84
|
+
function makeUserNotifier({ platform, openSync, writeSync, closeSync, stderrWrite }) {
|
|
85
|
+
return function notify(msg) {
|
|
86
|
+
try {
|
|
87
|
+
const fd = openSync(userTtyDevice(platform), 'w');
|
|
88
|
+
try { writeSync(fd, msg + '\n'); } finally { closeSync(fd); }
|
|
89
|
+
return;
|
|
90
|
+
} catch { /* no controlling TTY (CI / piped) */ }
|
|
91
|
+
try { stderrWrite(msg + '\n'); } catch { /* nothing else we can do */ }
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Orchestrator: probe real state via injected deps, then act. Never throws. */
|
|
96
|
+
function maybeInstallBun({ platform, env, exists, probe, exec, log }) {
|
|
97
|
+
const optOut = env.VSAF_SKIP_BUN_INSTALL === '1';
|
|
98
|
+
const isCI = !!env.CI;
|
|
99
|
+
const { present: hasBun } = detectBun({ platform, env, exists, probe });
|
|
100
|
+
const hasCurl = platform !== 'win32' && probe('curl');
|
|
101
|
+
const hasWget = platform !== 'win32' && probe('wget');
|
|
102
|
+
const plan = bunInstallPlan({ platform, hasBun, isCI, optOut, hasCurl, hasWget });
|
|
103
|
+
|
|
104
|
+
if (plan.action === 'skip') return plan;
|
|
105
|
+
if (plan.action === 'manual') { log(plan.instructions); return plan; }
|
|
106
|
+
|
|
107
|
+
log('[vsaf] Bun not found — installing via the official bun.sh installer…');
|
|
108
|
+
try {
|
|
109
|
+
exec(plan.command);
|
|
110
|
+
log(postInstallPathNote(platform, env));
|
|
111
|
+
} catch (e) {
|
|
112
|
+
log('[vsaf] Bun auto-install failed — install it manually:\n' + manualInstructions(platform));
|
|
113
|
+
log('[vsaf] (reason: ' + (e && e.message ? e.message : String(e)) + ')');
|
|
114
|
+
}
|
|
115
|
+
return plan;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
wellKnownBunPath,
|
|
120
|
+
detectBun,
|
|
121
|
+
bunInstallPlan,
|
|
122
|
+
manualInstructions,
|
|
123
|
+
postInstallPathNote,
|
|
124
|
+
userTtyDevice,
|
|
125
|
+
makeUserNotifier,
|
|
126
|
+
maybeInstallBun,
|
|
127
|
+
};
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,21 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Postinstall script — create @vsaf/core workspace link
|
|
4
|
+
* Postinstall script — bootstrap Bun (if missing) + create @vsaf/core workspace link
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
6
|
+
* 1. Bun bootstrap: every `vsaf` command runs under bun (bin shebang + bun:sqlite),
|
|
7
|
+
* so bun must exist before vsaf can run. This postinstall runs under `node`
|
|
8
|
+
* during `npm install` — the only place that can install bun automatically.
|
|
9
|
+
* Best-effort and never fatal (see scripts/bun-bootstrap.js).
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* 2. Workspace link: monorepo workspace links are stripped during `npm pack`
|
|
12
|
+
* (prepack removes "workspaces" from package.json). This recreates the link so
|
|
13
|
+
* `require('@vsaf/core')` works after `npm install -g`.
|
|
14
|
+
* Strategy (in order):
|
|
15
|
+
* a. symlink (macOS/Linux) or junction (Windows — no admin needed)
|
|
16
|
+
* b. cpSync fallback (copy dist/ contents)
|
|
17
|
+
* c. Fail with clear instructions
|
|
14
18
|
*/
|
|
15
19
|
|
|
16
20
|
const fs = require('fs');
|
|
17
21
|
const path = require('path');
|
|
22
|
+
const { execSync } = require('child_process');
|
|
23
|
+
const { maybeInstallBun, makeUserNotifier } = require('./bun-bootstrap.js');
|
|
18
24
|
|
|
25
|
+
// --- Step 1: bootstrap Bun before anything else. Must never break `npm install`.
|
|
26
|
+
function probeCmd(name) {
|
|
27
|
+
try { execSync(`${name} --version`, { stdio: 'ignore' }); return true; }
|
|
28
|
+
catch { return false; }
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
maybeInstallBun({
|
|
32
|
+
platform: process.platform,
|
|
33
|
+
env: process.env,
|
|
34
|
+
exists: fs.existsSync,
|
|
35
|
+
probe: probeCmd,
|
|
36
|
+
exec: (command) => execSync(command, { stdio: 'inherit', timeout: 300000 }),
|
|
37
|
+
// npm hides postinstall output by default → write to the controlling TTY so
|
|
38
|
+
// the user actually sees the announce + "open a new terminal" note.
|
|
39
|
+
log: makeUserNotifier({
|
|
40
|
+
platform: process.platform,
|
|
41
|
+
openSync: fs.openSync,
|
|
42
|
+
writeSync: fs.writeSync,
|
|
43
|
+
closeSync: fs.closeSync,
|
|
44
|
+
stderrWrite: (s) => process.stderr.write(s),
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
} catch { /* bootstrap must never break `npm install` */ }
|
|
48
|
+
|
|
49
|
+
// --- Step 2: link @vsaf/core (existing behavior).
|
|
19
50
|
const cwd = process.cwd();
|
|
20
51
|
const nsDir = path.resolve(cwd, 'node_modules', '@vsaf');
|
|
21
52
|
const target = path.resolve(nsDir, 'core');
|
|
@@ -142,7 +142,10 @@ Icon: `⏳` running, `✅` done, `❌` error.
|
|
|
142
142
|
- Code module (from GitNexus) → is there a corresponding business concept? (check graph.json)
|
|
143
143
|
- YES → record mapping
|
|
144
144
|
- NO → technical module (infra, utils) — OK, record note
|
|
145
|
-
17. Update `CONTEXT.md` with technical terms from code
|
|
145
|
+
17. Update `CONTEXT.md` with technical terms from code — IDEMPOTENTLY (never overwrite the whole file, never
|
|
146
|
+
blind-append; re-runs must not bloat it): PRESERVE the `## Shared Language` section written by onboard-docs;
|
|
147
|
+
own `## Cross-Domain Verification` + `## Technical Terms` — if a section exists REPLACE its body in place,
|
|
148
|
+
else append it; one row per mapping/term (dedupe, update rather than duplicate):
|
|
146
149
|
|
|
147
150
|
```markdown
|
|
148
151
|
## Technical Terms
|
|
@@ -155,7 +155,10 @@ Print conversion summary:
|
|
|
155
155
|
[ONBOARD-DOCS] [4/4] Extract domain terms → CONTEXT.md... ⏳
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
15. From the completed graph, extract domain terms → `CONTEXT.md`
|
|
158
|
+
15. From the completed graph, extract domain terms → `CONTEXT.md` — update IDEMPOTENTLY (never overwrite the
|
|
159
|
+
whole file, never blind-append; re-runs must not bloat it): ensure one top `# Project Context: {name}`
|
|
160
|
+
heading, then own the `## Shared Language` section — if it exists REPLACE its body in place, else append it;
|
|
161
|
+
leave other sections (`## Technical Terms`, `## Cross-Domain Verification`) untouched; one row per Term (dedupe).
|
|
159
162
|
16. Format Shared Language table:
|
|
160
163
|
|
|
161
164
|
```markdown
|
|
@@ -87,7 +87,11 @@ nodes:
|
|
|
87
87
|
Read `graphify-out/graph.json` (the docs graph from onboard-docs — READ ONLY) and query the code via GitNexus (`query`, `context`). Cross-reference:
|
|
88
88
|
- business concept (graph.json) → is there a matching code module? YES → record `{concept} ↔ {module/class}`; NO → "{concept} not yet implemented".
|
|
89
89
|
- code module (GitNexus) → matching business concept? YES → mapping; NO → technical module (infra/utils), record as such.
|
|
90
|
-
|
|
90
|
+
Update `CONTEXT.md` IDEMPOTENTLY — never overwrite the whole file, and never blind-append (re-runs would bloat it). Manage BY SECTION:
|
|
91
|
+
- PRESERVE the `## Shared Language` section written by onboard-docs — do NOT delete or rewrite it.
|
|
92
|
+
- You OWN `## Cross-Domain Verification` (the concept↔module mappings above) and `## Technical Terms`
|
|
93
|
+
(table: | Term | Definition | Maps to Business Concept |). If a section already exists, REPLACE its body in place; otherwise append it.
|
|
94
|
+
- Keep ONE row per mapping/term (dedupe by key) — update rather than duplicate.
|
|
91
95
|
Flag any cross-database access. If `graphify-out/graph.json` is absent, note that onboard-docs must run first.
|
|
92
96
|
|
|
93
97
|
- id: summary
|
|
@@ -83,7 +83,16 @@ nodes:
|
|
|
83
83
|
Print progress `[ONBOARD-DOCS] [4/4] Extract domain terms → CONTEXT.md... ⏳/✅`.
|
|
84
84
|
|
|
85
85
|
From the completed `graph.json`, extract DOMAIN terms (business vocabulary, not technical/code terms —
|
|
86
|
-
those are added later by onboard-code).
|
|
86
|
+
those are added later by onboard-code).
|
|
87
|
+
|
|
88
|
+
Update `CONTEXT.md` at the project root IDEMPOTENTLY — never overwrite the whole file, and never blind-append
|
|
89
|
+
(re-running onboarding would bloat it). Manage the file BY SECTION:
|
|
90
|
+
- Ensure a single top `# Project Context: {project-name}` heading (create once if missing).
|
|
91
|
+
- You OWN the `## Shared Language` section: if it already exists, REPLACE its body in place; otherwise append it.
|
|
92
|
+
Leave every other section (e.g. `## Technical Terms`, `## Cross-Domain Verification`) untouched.
|
|
93
|
+
- Keep ONE row per Term (dedupe by term name) — update an existing term's row instead of adding a duplicate.
|
|
94
|
+
|
|
95
|
+
## Shared Language
|
|
87
96
|
|
|
88
97
|
| Term | Definition | Example |
|
|
89
98
|
|------|-----------|---------|
|