viepilot 1.6.1 → 1.8.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/CHANGELOG.md +24 -1
- package/bin/viepilot.cjs +54 -22
- package/docs/dev/deployment.md +3 -4
- package/docs/troubleshooting.md +37 -0
- package/install.sh +23 -101
- package/lib/viepilot-install.cjs +427 -0
- package/package.json +1 -1
- package/skills/vp-audit/SKILL.md +7 -1
- package/skills/vp-crystallize/SKILL.md +8 -0
- package/skills/vp-debug/SKILL.md +12 -5
- package/templates/project/ARCHITECTURE.md +53 -0
- package/workflows/autonomous.md +7 -0
- package/workflows/brainstorm.md +18 -0
- package/workflows/crystallize.md +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Planned
|
|
11
|
+
|
|
12
|
+
- **M1.25 / Phase 29 (ENH-018)** — **Crystallize + ARCHITECTURE**: Mermaid diagrams **complexity-gated** from brainstorm; six diagram kinds with required/optional/N/A; vp-audit / vp-auto / vp-debug alignment; target **1.8.0** on complete.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
## [1.8.0] - 2026-04-01
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **M1.25 / Phase 29 (ENH-018) completed** — complexity-gated Mermaid architecture contract shipped: brainstorm inputs + crystallize matrix (`required|optional|N/A`) for six diagram types, architecture template sections with N/A rationale policy, and skill/workflow alignment across `vp-crystallize`, `vp-audit`, `vp-debug`, and `autonomous`.
|
|
21
|
+
|
|
22
|
+
## [1.7.0] - 2026-04-01
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **M1.24 / Phase 28 (ENH-017) completed** — Node-native installer flow shipped: `lib/viepilot-install.cjs` (`buildInstallPlan`/`applyInstallPlan`), `bin/viepilot.cjs install` no longer spawns `bash`, `install.sh` now thin wrapper to Node, and Jest coverage for dry-run/apply/wrapper paths.
|
|
27
|
+
|
|
28
|
+
### Documentation
|
|
29
|
+
|
|
30
|
+
- `docs/troubleshooting.md`, `docs/dev/deployment.md` — updated install engine behavior (`npx viepilot install` Node-native, `install.sh` wrapper), Windows guidance, and reinstall semantics with `dev-install.sh`.
|
|
31
|
+
|
|
10
32
|
## [1.6.1] - 2026-04-01
|
|
11
33
|
|
|
12
34
|
### Enhanced
|
|
@@ -252,7 +274,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
252
274
|
|
|
253
275
|
---
|
|
254
276
|
|
|
255
|
-
[Unreleased]: https://github.com/0-CODE/viepilot/compare/
|
|
277
|
+
[Unreleased]: https://github.com/0-CODE/viepilot/compare/v1.8.0...HEAD
|
|
278
|
+
[1.8.0]: https://github.com/0-CODE/viepilot/compare/v1.7.0...v1.8.0
|
|
256
279
|
[0.10.0]: https://github.com/0-CODE/viepilot/compare/v0.9.0...v0.10.0
|
|
257
280
|
[0.9.0]: https://github.com/0-CODE/viepilot/compare/v0.8.2...v0.9.0
|
|
258
281
|
[0.8.2]: https://github.com/0-CODE/viepilot/compare/v0.8.1...v0.8.2
|
package/bin/viepilot.cjs
CHANGED
|
@@ -5,9 +5,15 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const { spawnSync } = require('child_process');
|
|
9
8
|
const readline = require('readline');
|
|
10
9
|
const fs = require('fs');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const { buildInstallPlan, applyInstallPlan, resolveViepilotPackageRoot } = require(path.join(
|
|
12
|
+
__dirname,
|
|
13
|
+
'..',
|
|
14
|
+
'lib',
|
|
15
|
+
'viepilot-install.cjs',
|
|
16
|
+
));
|
|
11
17
|
|
|
12
18
|
const TARGETS = [
|
|
13
19
|
{ id: 'claude-code', label: 'Claude Code' },
|
|
@@ -28,7 +34,7 @@ Usage:
|
|
|
28
34
|
Install options:
|
|
29
35
|
--target <id|id,id|all> Target profile(s): claude-code,cursor-agent,cursor-ide
|
|
30
36
|
--yes Non-interactive mode (skip confirmations)
|
|
31
|
-
--dry-run Print actions only
|
|
37
|
+
--dry-run Print actions only (Node installer; no bash)
|
|
32
38
|
--list-targets Print supported targets and exit
|
|
33
39
|
--help Show help
|
|
34
40
|
|
|
@@ -239,28 +245,48 @@ async function interactiveTargetSelection() {
|
|
|
239
245
|
return runKeyboardSelector(TARGETS, 'multi', 'Select install targets');
|
|
240
246
|
}
|
|
241
247
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
/**
|
|
249
|
+
* One install applies the same file bundle for every target id (profiles are informational).
|
|
250
|
+
* @param {string[]} selectedTargets
|
|
251
|
+
* @param {boolean} dryRun
|
|
252
|
+
* @returns {{ ok: boolean, code: number }}
|
|
253
|
+
*/
|
|
254
|
+
function runInstallViaNode(selectedTargets, dryRun) {
|
|
255
|
+
const fallbackRoot = path.join(__dirname, '..');
|
|
256
|
+
const pkgRoot = resolveViepilotPackageRoot(fallbackRoot) || fallbackRoot;
|
|
257
|
+
const profile = selectedTargets[0] || 'cursor-ide';
|
|
258
|
+
const env = {
|
|
259
|
+
...process.env,
|
|
260
|
+
VIEPILOT_AUTO_YES: '1',
|
|
261
|
+
VIEPILOT_INSTALL_PROFILE: profile,
|
|
247
262
|
};
|
|
248
|
-
|
|
263
|
+
const wantPathShim = env.VIEPILOT_ADD_PATH === '1';
|
|
249
264
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
console.
|
|
255
|
-
return { ok:
|
|
265
|
+
let plan;
|
|
266
|
+
try {
|
|
267
|
+
plan = buildInstallPlan(pkgRoot, env, { wantPathShim });
|
|
268
|
+
} catch (err) {
|
|
269
|
+
console.error(err.message);
|
|
270
|
+
return { ok: false, code: 1 };
|
|
256
271
|
}
|
|
257
272
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
273
|
+
console.log('');
|
|
274
|
+
console.log('ViePilot install (Node — no bash required)');
|
|
275
|
+
console.log(` Package: ${pkgRoot}`);
|
|
276
|
+
console.log(` Targets (informational): ${selectedTargets.join(', ')}`);
|
|
277
|
+
|
|
278
|
+
const applied = applyInstallPlan(plan, { dryRun });
|
|
279
|
+
if (applied.logs.length > 0) {
|
|
280
|
+
console.log('');
|
|
281
|
+
console.log(applied.logs.join('\n'));
|
|
282
|
+
}
|
|
283
|
+
if (!applied.ok && applied.errors.length > 0) {
|
|
284
|
+
const first = applied.errors[0];
|
|
285
|
+
const msg = first.error && first.error.message ? first.error.message : String(first.error);
|
|
286
|
+
console.error(msg);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return { ok: applied.ok, code: applied.ok ? 0 : 1 };
|
|
264
290
|
}
|
|
265
291
|
|
|
266
292
|
async function installCommand(rawArgs) {
|
|
@@ -290,7 +316,13 @@ async function installCommand(rawArgs) {
|
|
|
290
316
|
}
|
|
291
317
|
|
|
292
318
|
console.log(`\nSelected targets: ${selectedTargets.join(', ')}`);
|
|
293
|
-
const
|
|
319
|
+
const run = runInstallViaNode(selectedTargets, options.dryRun);
|
|
320
|
+
const results = selectedTargets.map((target) => ({
|
|
321
|
+
ok: run.ok,
|
|
322
|
+
target,
|
|
323
|
+
dryRun: options.dryRun,
|
|
324
|
+
code: run.code,
|
|
325
|
+
}));
|
|
294
326
|
const failed = results.filter((r) => !r.ok);
|
|
295
327
|
|
|
296
328
|
console.log('\nInstall summary:');
|
|
@@ -307,7 +339,7 @@ async function installCommand(rawArgs) {
|
|
|
307
339
|
}
|
|
308
340
|
|
|
309
341
|
function computeUninstallPaths(targets) {
|
|
310
|
-
const home = process.env.HOME || '';
|
|
342
|
+
const home = process.env.HOME || os.homedir() || '';
|
|
311
343
|
const cursorSkills = path.join(home, '.cursor', 'skills');
|
|
312
344
|
const vpRoot = path.join(home, '.cursor', 'viepilot');
|
|
313
345
|
const paths = [];
|
package/docs/dev/deployment.md
CHANGED
|
@@ -24,11 +24,10 @@ cd viepilot
|
|
|
24
24
|
./install.sh
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
`install.sh`
|
|
27
|
+
`install.sh` is a thin **bash** wrapper: optional **cloc** / **PATH** prompts, then **`node bin/viepilot.cjs install`** (same **Node** engine as `npx viepilot install`). It installs to:
|
|
28
28
|
- `skills/vp-*/` → `~/.cursor/skills/`
|
|
29
|
-
- `workflows/` → `~/.cursor/viepilot
|
|
30
|
-
-
|
|
31
|
-
- `bin/vp-tools.cjs` → `~/.local/bin/vp-tools`
|
|
29
|
+
- `workflows/`, `templates/`, `bin/`, `lib/` → `~/.cursor/viepilot/…`
|
|
30
|
+
- Optional PATH symlinks (Unix) via `VIEPILOT_ADD_PATH=1`
|
|
32
31
|
|
|
33
32
|
### Method 3: Development Mode
|
|
34
33
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -68,6 +68,43 @@ chmod +x install.sh
|
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
71
|
+
### `npx viepilot install` vs dev clone — upgrade / cài lại có “conflict” không?
|
|
72
|
+
|
|
73
|
+
**Không có** giao diện merge từng file. Hành vi phụ thuộc **script nào** chạy:
|
|
74
|
+
|
|
75
|
+
| Cách cài | Script | Trước khi ghi file mới |
|
|
76
|
+
|----------|--------|-------------------------|
|
|
77
|
+
| **`npx viepilot install`** (mọi `--target`) | **`bin/viepilot.cjs`** + `lib/viepilot-install.cjs` (Node, **không** bash) | **Không** xóa `~/.cursor/skills/vp-*` hay `~/.cursor/viepilot`; copy đè tương đương **`cp -r`**. File cùng tên bị ghi đè; file **chỉ còn ở bản cũ** có thể **vẫn nằm lại** (orphan). |
|
|
78
|
+
| **`./install.sh`** từ clone / tarball | Thin **bash** wrapper → gọi `node bin/viepilot.cjs install` | Cùng engine Node như NPX; bash chỉ còn prompt (cloc, PATH) khi không `VIEPILOT_AUTO_YES=1`. |
|
|
79
|
+
| **`./dev-install.sh`** từ clone repo | `dev-install.sh` | **Có** xóa sạch `vp-*` skills + **`rm -rf ~/.cursor/viepilot`** rồi cài lại → gần như cài mới hoàn toàn. |
|
|
80
|
+
|
|
81
|
+
`npx viepilot install` chạy **trực tiếp** Node installer — **không** spawn `bash install.sh`. `install.sh` dùng khi bạn chạy tay từ repo và muốn prompt cloc/PATH giống trước.
|
|
82
|
+
|
|
83
|
+
**Muốn cài sạch sau bản cũ** (tránh sót file):
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx viepilot uninstall --yes
|
|
87
|
+
npx viepilot install --target cursor-agent --yes
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
(Chọn lại `--target` đúng profile bạn dùng.)
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### Windows / đa OS: `install.sh` và `npx viepilot install` chạy thế nào?
|
|
95
|
+
|
|
96
|
+
**`npx viepilot install`** dùng **Node** (`lib/viepilot-install.cjs`) — **không** cần Bash. **`./install.sh`** là wrapper Bash (prompt cloc/PATH) rồi gọi `node bin/viepilot.cjs install …`. **`dev-install.sh`** vẫn là Bash đầy đủ (xóa + copy).
|
|
97
|
+
|
|
98
|
+
| OS | `npx viepilot install` | `./install.sh` |
|
|
99
|
+
|----|------------------------|----------------|
|
|
100
|
+
| **macOS**, **Linux** | Chỉ cần Node trên PATH | Bash → Node (cùng engine với NPX) |
|
|
101
|
+
| **Windows (CMD/PowerShell)** | Chạy được với Node | Cần Git Bash / WSL cho file `.sh`, hoặc: `node bin\viepilot.cjs install --target cursor-agent --yes` |
|
|
102
|
+
| **PATH `/usr/local/bin`** | Tùy chọn trong installer (Unix); Windows bỏ qua / PATH thủ công | |
|
|
103
|
+
|
|
104
|
+
Với ViePilot **Node-native install**, lỗi **`bash: command not found`** khi chạy **`npx viepilot install`** không còn điển hình — nếu vẫn gặp trên bản cũ, hãy nâng ViePilot hoặc dùng `node bin/viepilot.cjs install …` từ source.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
71
108
|
### `vp-tools: command not found`
|
|
72
109
|
|
|
73
110
|
**Cause**: ViePilot bin not in PATH.
|
package/install.sh
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
-
# ViePilot
|
|
4
|
-
#
|
|
3
|
+
# ViePilot installation — thin wrapper around the Node installer (bin/viepilot.cjs).
|
|
4
|
+
# Implements prompts + optional cloc install here; file copy/symlink/chmod/path is in lib/viepilot-install.cjs.
|
|
5
5
|
#
|
|
6
|
-
# Optional: VIEPILOT_SYMLINK_SKILLS=1 — symlink skills
|
|
6
|
+
# Optional: VIEPILOT_SYMLINK_SKILLS=1 — passed through to Node (symlink skills into ~/.cursor/skills/)
|
|
7
|
+
# Optional: VIEPILOT_INSTALL_DRY_RUN=1 — runs `viepilot install --dry-run` (for CI/tests)
|
|
7
8
|
|
|
8
9
|
set -e
|
|
9
10
|
|
|
10
|
-
# Colors for output
|
|
11
11
|
RED='\033[0;31m'
|
|
12
12
|
GREEN='\033[0;32m'
|
|
13
13
|
YELLOW='\033[1;33m'
|
|
14
14
|
BLUE='\033[0;34m'
|
|
15
|
-
NC='\033[0m'
|
|
15
|
+
NC='\033[0m'
|
|
16
16
|
|
|
17
|
-
# Get script directory
|
|
18
17
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
cd "$SCRIPT_DIR" || exit 1
|
|
19
19
|
|
|
20
|
-
# Default installation paths
|
|
21
20
|
CURSOR_SKILLS_DIR="$HOME/.cursor/skills"
|
|
22
21
|
VIEPILOT_DIR="$HOME/.cursor/viepilot"
|
|
23
22
|
AUTO_YES="${VIEPILOT_AUTO_YES:-0}"
|
|
@@ -30,7 +29,6 @@ echo " VIEPILOT INSTALLER"
|
|
|
30
29
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
31
30
|
echo -e "${NC}"
|
|
32
31
|
|
|
33
|
-
# Check if running from viepilot directory
|
|
34
32
|
if [ ! -f "$SCRIPT_DIR/README.md" ] || [ ! -d "$SCRIPT_DIR/skills" ]; then
|
|
35
33
|
echo -e "${RED}Error: Please run this script from the viepilot directory${NC}"
|
|
36
34
|
exit 1
|
|
@@ -40,6 +38,7 @@ echo -e "${YELLOW}Installation paths:${NC}"
|
|
|
40
38
|
echo " Skills: $CURSOR_SKILLS_DIR"
|
|
41
39
|
echo " ViePilot: $VIEPILOT_DIR"
|
|
42
40
|
echo " Profile: $INSTALL_PROFILE"
|
|
41
|
+
echo " Engine: Node (bin/viepilot.cjs) — no bash copy logic"
|
|
43
42
|
echo ""
|
|
44
43
|
|
|
45
44
|
install_cloc_best_effort() {
|
|
@@ -86,7 +85,6 @@ install_cloc_best_effort() {
|
|
|
86
85
|
fi
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
# Confirm installation
|
|
90
88
|
if [ "$AUTO_YES" != "1" ]; then
|
|
91
89
|
read -p "Continue with installation? (y/n) " -n 1 -r
|
|
92
90
|
echo
|
|
@@ -99,105 +97,29 @@ else
|
|
|
99
97
|
fi
|
|
100
98
|
|
|
101
99
|
echo ""
|
|
102
|
-
echo -e "${BLUE}
|
|
103
|
-
|
|
104
|
-
# Create directories
|
|
105
|
-
echo " Creating directories..."
|
|
106
|
-
mkdir -p "$CURSOR_SKILLS_DIR"
|
|
107
|
-
mkdir -p "$VIEPILOT_DIR/workflows"
|
|
108
|
-
mkdir -p "$VIEPILOT_DIR/templates/project"
|
|
109
|
-
mkdir -p "$VIEPILOT_DIR/templates/phase"
|
|
110
|
-
mkdir -p "$VIEPILOT_DIR/bin"
|
|
111
|
-
mkdir -p "$VIEPILOT_DIR/lib"
|
|
112
|
-
mkdir -p "$VIEPILOT_DIR/ui-components"
|
|
113
|
-
|
|
114
|
-
# Install skills (copy default; VIEPILOT_SYMLINK_SKILLS=1 for dev-style live links)
|
|
115
|
-
echo " Installing skills..."
|
|
116
|
-
for skill_dir in "$SCRIPT_DIR/skills"/*; do
|
|
117
|
-
if [ -d "$skill_dir" ]; then
|
|
118
|
-
skill_name=$(basename "$skill_dir")
|
|
119
|
-
if [ "${VIEPILOT_SYMLINK_SKILLS:-0}" = "1" ]; then
|
|
120
|
-
if command -v realpath >/dev/null 2>&1; then
|
|
121
|
-
skill_abs=$(realpath "$skill_dir")
|
|
122
|
-
else
|
|
123
|
-
skill_abs=$(cd "$skill_dir" && pwd)
|
|
124
|
-
fi
|
|
125
|
-
ln -sfn "$skill_abs" "$CURSOR_SKILLS_DIR/$skill_name"
|
|
126
|
-
echo " ✓ $skill_name (symlink)"
|
|
127
|
-
else
|
|
128
|
-
cp -r "$skill_dir" "$CURSOR_SKILLS_DIR/"
|
|
129
|
-
echo " ✓ $skill_name"
|
|
130
|
-
fi
|
|
131
|
-
fi
|
|
132
|
-
done
|
|
133
|
-
|
|
134
|
-
# Install workflows
|
|
135
|
-
echo " Installing workflows..."
|
|
136
|
-
cp -r "$SCRIPT_DIR/workflows"/* "$VIEPILOT_DIR/workflows/"
|
|
137
|
-
echo " ✓ workflows"
|
|
138
|
-
|
|
139
|
-
# Install templates
|
|
140
|
-
echo " Installing templates..."
|
|
141
|
-
cp -r "$SCRIPT_DIR/templates/project"/* "$VIEPILOT_DIR/templates/project/"
|
|
142
|
-
cp -r "$SCRIPT_DIR/templates/phase"/* "$VIEPILOT_DIR/templates/phase/"
|
|
143
|
-
echo " ✓ templates"
|
|
144
|
-
|
|
145
|
-
# Install stock UI components
|
|
146
|
-
echo " Installing stock UI components..."
|
|
147
|
-
if [ -d "$SCRIPT_DIR/ui-components" ]; then
|
|
148
|
-
cp -r "$SCRIPT_DIR/ui-components"/* "$VIEPILOT_DIR/ui-components/"
|
|
149
|
-
echo " ✓ ui-components"
|
|
150
|
-
fi
|
|
151
|
-
|
|
152
|
-
# Install CLI tools
|
|
153
|
-
echo " Installing CLI tools..."
|
|
154
|
-
cp "$SCRIPT_DIR/bin/vp-tools.cjs" "$VIEPILOT_DIR/bin/"
|
|
155
|
-
cp "$SCRIPT_DIR/bin/viepilot.cjs" "$VIEPILOT_DIR/bin/"
|
|
156
|
-
cp "$SCRIPT_DIR/lib/cli-shared.cjs" "$VIEPILOT_DIR/lib/"
|
|
157
|
-
chmod +x "$VIEPILOT_DIR/bin/vp-tools.cjs"
|
|
158
|
-
chmod +x "$VIEPILOT_DIR/bin/viepilot.cjs"
|
|
159
|
-
echo " ✓ vp-tools.cjs + viepilot.cjs + lib/cli-shared.cjs"
|
|
160
|
-
|
|
161
|
-
echo " Checking optional dependency for README metric sync..."
|
|
100
|
+
echo -e "${BLUE}Preparing Node installer...${NC}"
|
|
162
101
|
install_cloc_best_effort
|
|
163
102
|
|
|
164
|
-
# Create symlink in PATH (optional)
|
|
165
103
|
echo ""
|
|
166
104
|
if [ "$AUTO_YES" != "1" ]; then
|
|
167
|
-
read -p "Add vp-tools + viepilot to PATH? (
|
|
105
|
+
read -p "Add vp-tools + viepilot to PATH? (symlinks via Node installer, often /usr/local/bin) (y/n) " -n 1 -r
|
|
168
106
|
echo
|
|
107
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
108
|
+
export VIEPILOT_ADD_PATH=1
|
|
109
|
+
fi
|
|
169
110
|
else
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
173
|
-
if [ -w "/usr/local/bin" ]; then
|
|
174
|
-
ln -sf "$VIEPILOT_DIR/bin/vp-tools.cjs" "/usr/local/bin/vp-tools"
|
|
175
|
-
ln -sf "$VIEPILOT_DIR/bin/viepilot.cjs" "/usr/local/bin/viepilot"
|
|
176
|
-
echo " ✓ vp-tools + viepilot added to PATH"
|
|
177
|
-
else
|
|
178
|
-
echo -e "${YELLOW} Note: Need sudo to create symlink${NC}"
|
|
179
|
-
sudo ln -sf "$VIEPILOT_DIR/bin/vp-tools.cjs" "/usr/local/bin/vp-tools"
|
|
180
|
-
sudo ln -sf "$VIEPILOT_DIR/bin/viepilot.cjs" "/usr/local/bin/viepilot"
|
|
181
|
-
echo " ✓ vp-tools + viepilot added to PATH"
|
|
111
|
+
if [ "$ADD_PATH_CHOICE" = "1" ]; then
|
|
112
|
+
export VIEPILOT_ADD_PATH=1
|
|
182
113
|
fi
|
|
183
114
|
fi
|
|
184
115
|
|
|
116
|
+
export VIEPILOT_AUTO_YES=1
|
|
117
|
+
|
|
118
|
+
NODE_ARGS=(install --target "$INSTALL_PROFILE" --yes)
|
|
119
|
+
if [ "${VIEPILOT_INSTALL_DRY_RUN:-0}" = "1" ]; then
|
|
120
|
+
NODE_ARGS+=(--dry-run)
|
|
121
|
+
fi
|
|
122
|
+
|
|
185
123
|
echo ""
|
|
186
|
-
echo -e "${
|
|
187
|
-
|
|
188
|
-
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
189
|
-
echo ""
|
|
190
|
-
echo "Installed:"
|
|
191
|
-
echo " - Skills: $CURSOR_SKILLS_DIR/vp-*"
|
|
192
|
-
echo " - Workflows: $VIEPILOT_DIR/workflows/"
|
|
193
|
-
echo " - Templates: $VIEPILOT_DIR/templates/"
|
|
194
|
-
echo " - CLI: $VIEPILOT_DIR/bin/vp-tools.cjs"
|
|
195
|
-
echo ""
|
|
196
|
-
echo "Quick Start:"
|
|
197
|
-
echo " 1. Open your project in Cursor"
|
|
198
|
-
echo " 2. Run: /vp-brainstorm"
|
|
199
|
-
echo " 3. After brainstorm: /vp-crystallize"
|
|
200
|
-
echo " 4. Start coding: /vp-auto"
|
|
201
|
-
echo ""
|
|
202
|
-
echo "Documentation: $SCRIPT_DIR/docs/getting-started.md"
|
|
203
|
-
echo ""
|
|
124
|
+
echo -e "${BLUE}Running: node bin/viepilot.cjs ${NODE_ARGS[*]}${NC}"
|
|
125
|
+
exec node "$SCRIPT_DIR/bin/viepilot.cjs" "${NODE_ARGS[@]}"
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ViePilot filesystem install plan — Node implementation (ENH-017 / Phase 28).
|
|
3
|
+
* Mirrors install.sh steps as a structured plan for dry-run and (later) execution.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const { resolveViepilotPackageRoot } = require('./viepilot-info.cjs');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {Record<string, string | undefined>} [envSource]
|
|
13
|
+
* @returns {{ autoYes: boolean, profile: string, addPath: boolean, symlinkSkills: boolean }}
|
|
14
|
+
*/
|
|
15
|
+
function normalizeInstallEnv(envSource = process.env) {
|
|
16
|
+
return {
|
|
17
|
+
autoYes: envSource.VIEPILOT_AUTO_YES === '1',
|
|
18
|
+
profile: envSource.VIEPILOT_INSTALL_PROFILE || 'cursor-ide',
|
|
19
|
+
addPath: envSource.VIEPILOT_ADD_PATH === '1',
|
|
20
|
+
symlinkSkills: envSource.VIEPILOT_SYMLINK_SKILLS === '1',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Same guard as install.sh: README.md + skills/ directory.
|
|
26
|
+
* @param {string} root - viepilot package root
|
|
27
|
+
*/
|
|
28
|
+
function validateViepilotPackageRoot(root) {
|
|
29
|
+
const readme = path.join(root, 'README.md');
|
|
30
|
+
const skillsDir = path.join(root, 'skills');
|
|
31
|
+
if (!fs.existsSync(readme)) {
|
|
32
|
+
const err = new Error('Please run this from the viepilot package root (README.md missing)');
|
|
33
|
+
err.code = 'VIEPILOT_LAYOUT';
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
if (!fs.existsSync(skillsDir) || !fs.statSync(skillsDir).isDirectory()) {
|
|
37
|
+
const err = new Error('Please run this from the viepilot package root (skills/ missing)');
|
|
38
|
+
err.code = 'VIEPILOT_LAYOUT';
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} packageRoot
|
|
45
|
+
* @returns {string[]}
|
|
46
|
+
*/
|
|
47
|
+
function listSkillDirNames(packageRoot) {
|
|
48
|
+
const skillsDir = path.join(packageRoot, 'skills');
|
|
49
|
+
return fs
|
|
50
|
+
.readdirSync(skillsDir, { withFileTypes: true })
|
|
51
|
+
.filter((d) => d.isDirectory())
|
|
52
|
+
.map((d) => d.name);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} packageRoot
|
|
57
|
+
* @param {string} subdir - relative to package root
|
|
58
|
+
*/
|
|
59
|
+
function listDirEntries(packageRoot, subdir) {
|
|
60
|
+
const full = path.join(packageRoot, subdir);
|
|
61
|
+
if (!fs.existsSync(full)) return [];
|
|
62
|
+
return fs.readdirSync(full, { withFileTypes: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Build an ordered install plan (no I/O besides reading source tree layout).
|
|
67
|
+
*
|
|
68
|
+
* @param {string} packageRoot - absolute viepilot package root
|
|
69
|
+
* @param {Record<string, string | undefined>} [envSource] - defaults to process.env
|
|
70
|
+
* @param {{ wantPathShim?: boolean, overrideHomedir?: string }} [opts] - `overrideHomedir`: absolute fake home for tests only. `wantPathShim`: append /usr/local/bin steps (skipped on Windows at apply time).
|
|
71
|
+
* @returns {{ version: number, packageRoot: string, env: ReturnType<typeof normalizeInstallEnv>, home: string, paths: object, steps: object[] }}
|
|
72
|
+
*/
|
|
73
|
+
function buildInstallPlan(packageRoot, envSource = process.env, opts = {}) {
|
|
74
|
+
const root = path.resolve(packageRoot);
|
|
75
|
+
validateViepilotPackageRoot(root);
|
|
76
|
+
const env = normalizeInstallEnv(envSource);
|
|
77
|
+
const home =
|
|
78
|
+
opts.overrideHomedir != null ? path.resolve(opts.overrideHomedir) : os.homedir();
|
|
79
|
+
const cursorSkillsDir = path.join(home, '.cursor', 'skills');
|
|
80
|
+
const viepilotDir = path.join(home, '.cursor', 'viepilot');
|
|
81
|
+
|
|
82
|
+
let wantPathShim = opts.wantPathShim;
|
|
83
|
+
if (wantPathShim === undefined) {
|
|
84
|
+
wantPathShim = env.autoYes && env.addPath;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** @type {object[]} */
|
|
88
|
+
const steps = [];
|
|
89
|
+
|
|
90
|
+
const mkdirTargets = [
|
|
91
|
+
cursorSkillsDir,
|
|
92
|
+
path.join(viepilotDir, 'workflows'),
|
|
93
|
+
path.join(viepilotDir, 'templates', 'project'),
|
|
94
|
+
path.join(viepilotDir, 'templates', 'phase'),
|
|
95
|
+
path.join(viepilotDir, 'bin'),
|
|
96
|
+
path.join(viepilotDir, 'lib'),
|
|
97
|
+
path.join(viepilotDir, 'ui-components'),
|
|
98
|
+
];
|
|
99
|
+
for (const dir of mkdirTargets) {
|
|
100
|
+
steps.push({ kind: 'mkdir', path: dir });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const name of listSkillDirNames(root)) {
|
|
104
|
+
const src = path.join(root, 'skills', name);
|
|
105
|
+
const dest = path.join(cursorSkillsDir, name);
|
|
106
|
+
if (env.symlinkSkills) {
|
|
107
|
+
steps.push({
|
|
108
|
+
kind: 'symlink_dir',
|
|
109
|
+
target: dest,
|
|
110
|
+
sourceAbsolute: path.resolve(src),
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
steps.push({ kind: 'copy_dir', from: src, to: dest });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const ent of listDirEntries(root, 'workflows')) {
|
|
118
|
+
const src = path.join(root, 'workflows', ent.name);
|
|
119
|
+
const dest = path.join(viepilotDir, 'workflows', ent.name);
|
|
120
|
+
if (ent.isDirectory()) {
|
|
121
|
+
steps.push({ kind: 'copy_dir', from: src, to: dest });
|
|
122
|
+
} else if (ent.isFile()) {
|
|
123
|
+
steps.push({ kind: 'copy_file', from: src, to: dest });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const ent of listDirEntries(root, path.join('templates', 'project'))) {
|
|
128
|
+
const src = path.join(root, 'templates', 'project', ent.name);
|
|
129
|
+
const dest = path.join(viepilotDir, 'templates', 'project', ent.name);
|
|
130
|
+
if (ent.isDirectory()) {
|
|
131
|
+
steps.push({ kind: 'copy_dir', from: src, to: dest });
|
|
132
|
+
} else if (ent.isFile()) {
|
|
133
|
+
steps.push({ kind: 'copy_file', from: src, to: dest });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (const ent of listDirEntries(root, path.join('templates', 'phase'))) {
|
|
138
|
+
const src = path.join(root, 'templates', 'phase', ent.name);
|
|
139
|
+
const dest = path.join(viepilotDir, 'templates', 'phase', ent.name);
|
|
140
|
+
if (ent.isDirectory()) {
|
|
141
|
+
steps.push({ kind: 'copy_dir', from: src, to: dest });
|
|
142
|
+
} else if (ent.isFile()) {
|
|
143
|
+
steps.push({ kind: 'copy_file', from: src, to: dest });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const uiRoot = path.join(root, 'ui-components');
|
|
148
|
+
if (fs.existsSync(uiRoot)) {
|
|
149
|
+
for (const ent of listDirEntries(root, 'ui-components')) {
|
|
150
|
+
const src = path.join(root, 'ui-components', ent.name);
|
|
151
|
+
const dest = path.join(viepilotDir, 'ui-components', ent.name);
|
|
152
|
+
if (ent.isDirectory()) {
|
|
153
|
+
steps.push({ kind: 'copy_dir', from: src, to: dest });
|
|
154
|
+
} else if (ent.isFile()) {
|
|
155
|
+
steps.push({ kind: 'copy_file', from: src, to: dest });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const binFiles = ['vp-tools.cjs', 'viepilot.cjs'];
|
|
161
|
+
for (const f of binFiles) {
|
|
162
|
+
steps.push({
|
|
163
|
+
kind: 'copy_file',
|
|
164
|
+
from: path.join(root, 'bin', f),
|
|
165
|
+
to: path.join(viepilotDir, 'bin', f),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
steps.push({
|
|
169
|
+
kind: 'copy_file',
|
|
170
|
+
from: path.join(root, 'lib', 'cli-shared.cjs'),
|
|
171
|
+
to: path.join(viepilotDir, 'lib', 'cli-shared.cjs'),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
for (const f of binFiles) {
|
|
175
|
+
steps.push({
|
|
176
|
+
kind: 'chmod',
|
|
177
|
+
path: path.join(viepilotDir, 'bin', f),
|
|
178
|
+
mode: 0o755,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
steps.push({
|
|
183
|
+
kind: 'note',
|
|
184
|
+
id: 'cloc_optional',
|
|
185
|
+
message:
|
|
186
|
+
'Check optional cloc for README metrics (brew/apt/dnf/choco); installation continues if missing.',
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (wantPathShim) {
|
|
190
|
+
steps.push({
|
|
191
|
+
kind: 'path_shim',
|
|
192
|
+
links: [
|
|
193
|
+
{ path: '/usr/local/bin/vp-tools', target: path.join(viepilotDir, 'bin', 'vp-tools.cjs') },
|
|
194
|
+
{ path: '/usr/local/bin/viepilot', target: path.join(viepilotDir, 'bin', 'viepilot.cjs') },
|
|
195
|
+
],
|
|
196
|
+
note: 'Unix typical; on Windows native, PATH shim may be skipped or manual.',
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
version: 1,
|
|
202
|
+
packageRoot: root,
|
|
203
|
+
env,
|
|
204
|
+
home,
|
|
205
|
+
paths: {
|
|
206
|
+
cursorSkillsDir,
|
|
207
|
+
viepilotDir,
|
|
208
|
+
},
|
|
209
|
+
steps,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Human-readable lines for dry-run output.
|
|
215
|
+
* @param {ReturnType<typeof buildInstallPlan>} plan
|
|
216
|
+
* @returns {string[]}
|
|
217
|
+
*/
|
|
218
|
+
function formatPlanLines(plan) {
|
|
219
|
+
const lines = [];
|
|
220
|
+
lines.push('ViePilot install plan (dry-run)');
|
|
221
|
+
lines.push(` packageRoot: ${plan.packageRoot}`);
|
|
222
|
+
lines.push(` profile: ${plan.env.profile} (informational; same file set as install.sh)`);
|
|
223
|
+
lines.push(` skills: ${plan.paths.cursorSkillsDir}`);
|
|
224
|
+
lines.push(` viepilot: ${plan.paths.viepilotDir}`);
|
|
225
|
+
lines.push('');
|
|
226
|
+
for (let i = 0; i < plan.steps.length; i++) {
|
|
227
|
+
const s = plan.steps[i];
|
|
228
|
+
const n = i + 1;
|
|
229
|
+
switch (s.kind) {
|
|
230
|
+
case 'mkdir':
|
|
231
|
+
lines.push(`${n}. mkdir -p ${s.path}`);
|
|
232
|
+
break;
|
|
233
|
+
case 'copy_file':
|
|
234
|
+
lines.push(`${n}. copy ${s.from} -> ${s.to}`);
|
|
235
|
+
break;
|
|
236
|
+
case 'copy_dir':
|
|
237
|
+
lines.push(`${n}. copyDir ${s.from} -> ${s.to}`);
|
|
238
|
+
break;
|
|
239
|
+
case 'symlink_dir':
|
|
240
|
+
lines.push(`${n}. symlink ${s.target} -> ${s.sourceAbsolute}`);
|
|
241
|
+
break;
|
|
242
|
+
case 'chmod':
|
|
243
|
+
lines.push(`${n}. chmod ${s.mode.toString(8)} ${s.path}`);
|
|
244
|
+
break;
|
|
245
|
+
case 'note':
|
|
246
|
+
lines.push(`${n}. note: ${s.message}`);
|
|
247
|
+
break;
|
|
248
|
+
case 'path_shim':
|
|
249
|
+
lines.push(`${n}. path shim: ${s.links.map((l) => `${l.path} -> ${l.target}`).join('; ')}`);
|
|
250
|
+
if (s.note) lines.push(` (${s.note})`);
|
|
251
|
+
break;
|
|
252
|
+
default:
|
|
253
|
+
lines.push(`${n}. ${JSON.stringify(s)}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return lines;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function removePathIfExists(p) {
|
|
260
|
+
if (fs.existsSync(p)) {
|
|
261
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function ensureDirForFile(filePath) {
|
|
266
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function copyDirRecursive(src, dest) {
|
|
270
|
+
removePathIfExists(dest);
|
|
271
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
272
|
+
if (typeof fs.cpSync === 'function') {
|
|
273
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
277
|
+
for (const ent of fs.readdirSync(src, { withFileTypes: true })) {
|
|
278
|
+
const s = path.join(src, ent.name);
|
|
279
|
+
const d = path.join(dest, ent.name);
|
|
280
|
+
if (ent.isDirectory()) {
|
|
281
|
+
copyDirRecursive(s, d);
|
|
282
|
+
} else {
|
|
283
|
+
fs.copyFileSync(s, d);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function symlinkSkillDir(sourceAbsolute, target) {
|
|
289
|
+
removePathIfExists(target);
|
|
290
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
291
|
+
const linkType = process.platform === 'win32' ? 'dir' : 'dir';
|
|
292
|
+
fs.symlinkSync(sourceAbsolute, target, linkType);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Same messaging as install.sh install_cloc_best_effort (non-blocking; no interactive install here).
|
|
297
|
+
* @param {ReturnType<typeof normalizeInstallEnv>} env
|
|
298
|
+
* @returns {string[]}
|
|
299
|
+
*/
|
|
300
|
+
function getClocGuidanceLines(env) {
|
|
301
|
+
const lines = [];
|
|
302
|
+
try {
|
|
303
|
+
const { execFileSync } = require('child_process');
|
|
304
|
+
execFileSync('cloc', ['--version'], { stdio: 'ignore' });
|
|
305
|
+
lines.push(' ✓ cloc detected');
|
|
306
|
+
return lines;
|
|
307
|
+
} catch (_e) {
|
|
308
|
+
/* continue */
|
|
309
|
+
}
|
|
310
|
+
lines.push(' cloc not found.');
|
|
311
|
+
lines.push(' README metric auto-sync can still run with fallback, but LOC refresh will be skipped.');
|
|
312
|
+
lines.push(' Suggested install:');
|
|
313
|
+
lines.push(' - macOS: brew install cloc');
|
|
314
|
+
lines.push(' - Ubuntu/Debian: sudo apt-get install -y cloc');
|
|
315
|
+
lines.push(' - Windows: choco install cloc');
|
|
316
|
+
if (!env.autoYes) {
|
|
317
|
+
lines.push(' (Interactive cloc install omitted in Node installer; set AUTO_YES and use OS package manager.)');
|
|
318
|
+
}
|
|
319
|
+
return lines;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Apply install plan to disk (or dry-run log only).
|
|
324
|
+
*
|
|
325
|
+
* @param {ReturnType<typeof buildInstallPlan>} plan
|
|
326
|
+
* @param {{ dryRun?: boolean }} [options]
|
|
327
|
+
* @returns {{ ok: boolean, logs: string[], errors: { step: object, error: Error }[] }}
|
|
328
|
+
*/
|
|
329
|
+
function applyInstallPlan(plan, options = {}) {
|
|
330
|
+
const dryRun = !!options.dryRun;
|
|
331
|
+
/** @type {string[]} */
|
|
332
|
+
const logs = [];
|
|
333
|
+
/** @type {{ step: object, error: Error }[]} */
|
|
334
|
+
const errors = [];
|
|
335
|
+
|
|
336
|
+
for (const step of plan.steps) {
|
|
337
|
+
try {
|
|
338
|
+
switch (step.kind) {
|
|
339
|
+
case 'mkdir':
|
|
340
|
+
if (dryRun) logs.push(`[dry-run] mkdir -p ${step.path}`);
|
|
341
|
+
else fs.mkdirSync(step.path, { recursive: true });
|
|
342
|
+
break;
|
|
343
|
+
case 'copy_file':
|
|
344
|
+
if (dryRun) logs.push(`[dry-run] copy ${step.from} -> ${step.to}`);
|
|
345
|
+
else {
|
|
346
|
+
if (!fs.existsSync(step.from)) {
|
|
347
|
+
throw new Error(`Missing source: ${step.from}`);
|
|
348
|
+
}
|
|
349
|
+
ensureDirForFile(step.to);
|
|
350
|
+
fs.copyFileSync(step.from, step.to);
|
|
351
|
+
}
|
|
352
|
+
break;
|
|
353
|
+
case 'copy_dir':
|
|
354
|
+
if (dryRun) logs.push(`[dry-run] copyDir ${step.from} -> ${step.to}`);
|
|
355
|
+
else {
|
|
356
|
+
if (!fs.existsSync(step.from)) {
|
|
357
|
+
throw new Error(`Missing source dir: ${step.from}`);
|
|
358
|
+
}
|
|
359
|
+
copyDirRecursive(step.from, step.to);
|
|
360
|
+
}
|
|
361
|
+
break;
|
|
362
|
+
case 'symlink_dir':
|
|
363
|
+
if (dryRun) {
|
|
364
|
+
logs.push(`[dry-run] symlink ${step.target} -> ${step.sourceAbsolute}`);
|
|
365
|
+
} else {
|
|
366
|
+
symlinkSkillDir(step.sourceAbsolute, step.target);
|
|
367
|
+
}
|
|
368
|
+
break;
|
|
369
|
+
case 'chmod':
|
|
370
|
+
if (dryRun) logs.push(`[dry-run] chmod ${step.mode.toString(8)} ${step.path}`);
|
|
371
|
+
else {
|
|
372
|
+
try {
|
|
373
|
+
fs.chmodSync(step.path, step.mode);
|
|
374
|
+
} catch (e) {
|
|
375
|
+
logs.push(`chmod skipped: ${step.path} (${e.message})`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
break;
|
|
379
|
+
case 'note':
|
|
380
|
+
if (step.id === 'cloc_optional') {
|
|
381
|
+
logs.push(...getClocGuidanceLines(plan.env));
|
|
382
|
+
} else {
|
|
383
|
+
logs.push(`note: ${step.message}`);
|
|
384
|
+
}
|
|
385
|
+
break;
|
|
386
|
+
case 'path_shim':
|
|
387
|
+
if (process.platform === 'win32') {
|
|
388
|
+
logs.push('path_shim: skipped on Windows (add ~/.cursor/viepilot/bin to PATH manually if needed).');
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
if (dryRun) {
|
|
392
|
+
logs.push(`[dry-run] path_shim: ${step.links.map((l) => `${l.path} -> ${l.target}`).join('; ')}`);
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
for (const L of step.links) {
|
|
396
|
+
try {
|
|
397
|
+
removePathIfExists(L.path);
|
|
398
|
+
fs.mkdirSync(path.dirname(L.path), { recursive: true });
|
|
399
|
+
fs.symlinkSync(L.target, L.path, 'file');
|
|
400
|
+
logs.push(`path_shim: ${L.path} -> ${L.target}`);
|
|
401
|
+
} catch (e) {
|
|
402
|
+
logs.push(`path_shim failed ${L.path}: ${e.message} (try sudo or adjust permissions)`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
break;
|
|
406
|
+
default:
|
|
407
|
+
logs.push(`unknown step kind: ${JSON.stringify(step)}`);
|
|
408
|
+
}
|
|
409
|
+
} catch (e) {
|
|
410
|
+
errors.push({ step, error: e });
|
|
411
|
+
return { ok: false, logs, errors };
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return { ok: true, logs, errors };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
module.exports = {
|
|
419
|
+
resolveViepilotPackageRoot,
|
|
420
|
+
normalizeInstallEnv,
|
|
421
|
+
validateViepilotPackageRoot,
|
|
422
|
+
listSkillDirNames,
|
|
423
|
+
buildInstallPlan,
|
|
424
|
+
formatPlanLines,
|
|
425
|
+
applyInstallPlan,
|
|
426
|
+
getClocGuidanceLines,
|
|
427
|
+
};
|
package/package.json
CHANGED
package/skills/vp-audit/SKILL.md
CHANGED
|
@@ -32,6 +32,10 @@ Auto-detect nếu đang chạy trong viepilot framework repo để thêm framewo
|
|
|
32
32
|
- `CHANGELOG.md` vs recent git commits
|
|
33
33
|
- Placeholder URLs trong `docs/` (`your-org`, `YOUR_USERNAME`, v.v.)
|
|
34
34
|
- Features mới (từ phases gần đây) chưa có documentation
|
|
35
|
+
- `ARCHITECTURE.md` diagram applicability matrix consistency:
|
|
36
|
+
- `required` diagrams must have Mermaid content
|
|
37
|
+
- `optional` diagrams may be omitted/merged with explicit note
|
|
38
|
+
- `N/A` diagrams must have rationale line
|
|
35
39
|
|
|
36
40
|
**Tier 3 — Stack Best Practices + Code Quality (mọi project, theo stack detect được):**
|
|
37
41
|
- Detect stacks liên quan từ context/project manifests
|
|
@@ -66,7 +70,8 @@ Optional flags:
|
|
|
66
70
|
- `--silent` : Only output if issues found
|
|
67
71
|
- `--tier1` : Run Tier 1 (state consistency) only
|
|
68
72
|
- `--tier2` : Run Tier 2 (docs drift) only
|
|
69
|
-
- `--tier3` : Run Tier 3 (
|
|
73
|
+
- `--tier3` : Run Tier 3 (stack best-practice) only
|
|
74
|
+
- `--tier4` : Run Tier 4 (framework integrity) only
|
|
70
75
|
</context>
|
|
71
76
|
|
|
72
77
|
<process>
|
|
@@ -90,6 +95,7 @@ Execute workflow from `@$HOME/.cursor/viepilot/workflows/audit.md`
|
|
|
90
95
|
# README.md version mention
|
|
91
96
|
# CHANGELOG.md vs recent commits
|
|
92
97
|
# Placeholder URLs in docs/
|
|
98
|
+
# ARCHITECTURE.md diagram matrix consistency (required|optional|N/A)
|
|
93
99
|
```
|
|
94
100
|
|
|
95
101
|
**Step 3 — Tier 3**: Stack Best Practices + Code Quality
|
|
@@ -116,6 +116,13 @@ Ask user for:
|
|
|
116
116
|
- Services definitions
|
|
117
117
|
- Data flow
|
|
118
118
|
- Technology decisions
|
|
119
|
+
- Build **diagram applicability matrix** for:
|
|
120
|
+
- `system-overview`, `data-flow`, `event-flows`, `module-dependencies`, `deployment`, `user-use-case`
|
|
121
|
+
- For each type assign status: `required` | `optional` | `N/A`
|
|
122
|
+
- Apply generation policy:
|
|
123
|
+
- `required` => include concrete Mermaid block
|
|
124
|
+
- `optional` => allow lightweight/merged representation
|
|
125
|
+
- `N/A` => keep section heading + one-line rationale
|
|
119
126
|
|
|
120
127
|
### Step 5: Generate PROJECT-CONTEXT.md
|
|
121
128
|
- Domain knowledge
|
|
@@ -177,4 +184,5 @@ Ask user for:
|
|
|
177
184
|
- [ ] TRACKER.md initialized
|
|
178
185
|
- [ ] Project files created
|
|
179
186
|
- [ ] Git committed
|
|
187
|
+
- [ ] ARCHITECTURE diagram matrix is present and consistent (`required|optional|N/A`)
|
|
180
188
|
</success_criteria>
|
package/skills/vp-debug/SKILL.md
CHANGED
|
@@ -28,6 +28,12 @@ Systematic debugging với persistent state tracking. Giúp track vấn đề qu
|
|
|
28
28
|
- `continue` - Continue current session
|
|
29
29
|
- `list` - List all sessions
|
|
30
30
|
- `close` - Close current session with resolution
|
|
31
|
+
|
|
32
|
+
**Architecture diagram intake (ENH-018):**
|
|
33
|
+
- If `.viepilot/ARCHITECTURE.md` has diagram applicability matrix, consume it before deep debugging.
|
|
34
|
+
- Prioritize investigation context from diagrams marked `required`.
|
|
35
|
+
- For `optional` diagrams: use when available; do not block debug flow if missing.
|
|
36
|
+
- For `N/A` diagrams: respect rationale and avoid forcing diagram creation during debug.
|
|
31
37
|
</objective>
|
|
32
38
|
|
|
33
39
|
<execution_context>
|
|
@@ -79,11 +85,12 @@ Execute workflow from `@$HOME/.cursor/viepilot/workflows/debug.md`
|
|
|
79
85
|
|
|
80
86
|
### Key Steps
|
|
81
87
|
1. **Start Session**: Gather problem description
|
|
82
|
-
2. **
|
|
83
|
-
3. **
|
|
84
|
-
4. **
|
|
85
|
-
5. **
|
|
86
|
-
6. **
|
|
88
|
+
2. **Load Architecture Context**: Read `.viepilot/ARCHITECTURE.md` matrix status (`required|optional|N/A`) and relevant Mermaid diagrams if present
|
|
89
|
+
3. **Hypothesize**: Generate possible causes
|
|
90
|
+
4. **Test**: Run tests to confirm/reject hypotheses
|
|
91
|
+
5. **Track**: Log all findings
|
|
92
|
+
6. **Resolve**: Document fix and root cause
|
|
93
|
+
7. **Close**: Mark session complete
|
|
87
94
|
</process>
|
|
88
95
|
|
|
89
96
|
<success_criteria>
|
|
@@ -8,6 +8,26 @@
|
|
|
8
8
|
{{SYSTEM_DIAGRAM}}
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Architecture Diagram Applicability
|
|
12
|
+
|
|
13
|
+
> Decide diagram depth from brainstorm complexity signals. Do not force all six as detailed by default.
|
|
14
|
+
|
|
15
|
+
- **Complexity**: {{ARCH_COMPLEXITY_LEVEL}} <!-- simple | moderate | complex -->
|
|
16
|
+
- **Services/Modules signal**: {{ARCH_SIGNAL_SERVICES}}
|
|
17
|
+
- **Event-driven signal**: {{ARCH_SIGNAL_EVENTS}}
|
|
18
|
+
- **Deployment signal**: {{ARCH_SIGNAL_DEPLOYMENT}}
|
|
19
|
+
- **User-flow signal**: {{ARCH_SIGNAL_USER_FLOWS}}
|
|
20
|
+
- **Integration signal**: {{ARCH_SIGNAL_INTEGRATIONS}}
|
|
21
|
+
|
|
22
|
+
| Diagram type | Status (`required|optional|N/A`) | Reason |
|
|
23
|
+
|--------------|----------------------------------|--------|
|
|
24
|
+
| system-overview | {{DIAGRAM_STATUS_SYSTEM_OVERVIEW}} | {{DIAGRAM_REASON_SYSTEM_OVERVIEW}} |
|
|
25
|
+
| data-flow | {{DIAGRAM_STATUS_DATA_FLOW}} | {{DIAGRAM_REASON_DATA_FLOW}} |
|
|
26
|
+
| event-flows | {{DIAGRAM_STATUS_EVENT_FLOWS}} | {{DIAGRAM_REASON_EVENT_FLOWS}} |
|
|
27
|
+
| module-dependencies | {{DIAGRAM_STATUS_MODULE_DEPENDENCIES}} | {{DIAGRAM_REASON_MODULE_DEPENDENCIES}} |
|
|
28
|
+
| deployment | {{DIAGRAM_STATUS_DEPLOYMENT}} | {{DIAGRAM_REASON_DEPLOYMENT}} |
|
|
29
|
+
| user-use-case | {{DIAGRAM_STATUS_USER_USE_CASE}} | {{DIAGRAM_REASON_USER_USE_CASE}} |
|
|
30
|
+
|
|
11
31
|
## Services
|
|
12
32
|
|
|
13
33
|
{{#SERVICES}}
|
|
@@ -26,6 +46,36 @@
|
|
|
26
46
|
{{DATA_FLOW_DIAGRAM}}
|
|
27
47
|
```
|
|
28
48
|
|
|
49
|
+
### Event Flows
|
|
50
|
+
|
|
51
|
+
- **Status**: {{DIAGRAM_STATUS_EVENT_FLOWS}}
|
|
52
|
+
- **Not applicable rationale**: {{DIAGRAM_NA_EVENT_FLOWS}}
|
|
53
|
+
|
|
54
|
+
```mermaid
|
|
55
|
+
flowchart LR
|
|
56
|
+
EventSource --> EventBus --> Consumer
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Module Dependencies
|
|
60
|
+
|
|
61
|
+
- **Status**: {{DIAGRAM_STATUS_MODULE_DEPENDENCIES}}
|
|
62
|
+
- **Not applicable rationale**: {{DIAGRAM_NA_MODULE_DEPENDENCIES}}
|
|
63
|
+
|
|
64
|
+
```mermaid
|
|
65
|
+
flowchart LR
|
|
66
|
+
UI --> Service --> Repository
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### User Use-Case Flows
|
|
70
|
+
|
|
71
|
+
- **Status**: {{DIAGRAM_STATUS_USER_USE_CASE}}
|
|
72
|
+
- **Not applicable rationale**: {{DIAGRAM_NA_USER_USE_CASE}}
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
flowchart TD
|
|
76
|
+
User --> Action --> Outcome
|
|
77
|
+
```
|
|
78
|
+
|
|
29
79
|
## Integration Points
|
|
30
80
|
|
|
31
81
|
| Service A | Service B | Protocol | Endpoint/Topic |
|
|
@@ -62,6 +112,9 @@
|
|
|
62
112
|
{{DEPLOYMENT_DIAGRAM}}
|
|
63
113
|
```
|
|
64
114
|
|
|
115
|
+
- **Status**: {{DIAGRAM_STATUS_DEPLOYMENT}}
|
|
116
|
+
- **Not applicable rationale**: {{DIAGRAM_NA_DEPLOYMENT}}
|
|
117
|
+
|
|
65
118
|
## Monitoring & Observability
|
|
66
119
|
|
|
67
120
|
- **Logging**: {{LOGGING_SOLUTION}}
|
package/workflows/autonomous.md
CHANGED
|
@@ -90,6 +90,13 @@ read:
|
|
|
90
90
|
- context_required files from task file
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
+
Architecture context rule (ENH-018):
|
|
94
|
+
- If `.viepilot/ARCHITECTURE.md` includes a diagram applicability matrix, load only diagrams relevant to current task:
|
|
95
|
+
- `required`: must be consulted before implementation/debug decisions.
|
|
96
|
+
- `optional`: consult when directly related; do not block task if absent.
|
|
97
|
+
- `N/A`: respect rationale; do not force diagram regeneration.
|
|
98
|
+
- If matrix is missing, continue with explicit assumption notes in task logs.
|
|
99
|
+
|
|
93
100
|
#### Validate Task Contract (required before code)
|
|
94
101
|
Task must include execution-grade details:
|
|
95
102
|
- Objective (specific outcome)
|
package/workflows/brainstorm.md
CHANGED
|
@@ -59,6 +59,12 @@ Gợi ý các topics để brainstorm:
|
|
|
59
59
|
- System components
|
|
60
60
|
- Data flow
|
|
61
61
|
- Technology stack
|
|
62
|
+
- Diagram applicability signals (for crystallize):
|
|
63
|
+
- Service/module count and boundaries
|
|
64
|
+
- Event-driven usage (queues, webhooks, async jobs)
|
|
65
|
+
- Deployment topology (single node vs multi-env / distributed)
|
|
66
|
+
- User journey complexity (single actor/simple flow vs multi-actor)
|
|
67
|
+
- External integration surface (few vs many protocols/services)
|
|
62
68
|
|
|
63
69
|
3. **Data Model**
|
|
64
70
|
- Core entities
|
|
@@ -196,6 +202,18 @@ Tạo/cập nhật file: `docs/brainstorm/session-{YYYY-MM-DD}.md`
|
|
|
196
202
|
|
|
197
203
|
**Scope note (optional):** Nếu không có post-MVP: `Single-release product — no separate horizon epics.`
|
|
198
204
|
|
|
205
|
+
## Architecture diagram applicability inputs
|
|
206
|
+
|
|
207
|
+
> Input contract for `/vp-crystallize` Step 4. Keep concise and explicit.
|
|
208
|
+
|
|
209
|
+
- **Project complexity**: simple | moderate | complex
|
|
210
|
+
- **Services/modules**: {single | multiple} + boundaries
|
|
211
|
+
- **Event-driven**: yes/no + channels (queue/webhook/cron)
|
|
212
|
+
- **Deployment shape**: local-only | single-env cloud | multi-env/distributed
|
|
213
|
+
- **User flow complexity**: simple | multi-step | multi-actor
|
|
214
|
+
- **Integration surface**: low | medium | high
|
|
215
|
+
- **Initial diagram hints** (optional): required / optional / N/A candidates
|
|
216
|
+
|
|
199
217
|
## Topics Discussed
|
|
200
218
|
|
|
201
219
|
### Topic 1: {Name}
|
package/workflows/crystallize.md
CHANGED
|
@@ -229,6 +229,23 @@ Extract from brainstorm:
|
|
|
229
229
|
- Data flow
|
|
230
230
|
- Technology decisions with rationale
|
|
231
231
|
- Integration points
|
|
232
|
+
|
|
233
|
+
Before writing diagrams, create a **diagram applicability matrix** from brainstorm signals (complexity, service boundaries, event usage, deployment shape, user-flow complexity, integration surface):
|
|
234
|
+
|
|
235
|
+
| Diagram type | Status | Rule |
|
|
236
|
+
|--------------|--------|------|
|
|
237
|
+
| `system-overview` | required/optional/N/A | Required if >1 major component or integration boundary |
|
|
238
|
+
| `data-flow` | required/optional/N/A | Required when request/response or data pipeline is central |
|
|
239
|
+
| `event-flows` | required/optional/N/A | Required when async events/webhooks/queues exist |
|
|
240
|
+
| `module-dependencies` | required/optional/N/A | Required when multi-module/layer boundaries matter |
|
|
241
|
+
| `deployment` | required/optional/N/A | Required for multi-env/distributed deployment concerns |
|
|
242
|
+
| `user-use-case` | required/optional/N/A | Required when user journey/actor interactions drive design |
|
|
243
|
+
|
|
244
|
+
Generation rules:
|
|
245
|
+
- `required`: include explicit ` ```mermaid ` block in `.viepilot/ARCHITECTURE.md`
|
|
246
|
+
- `optional`: may be simplified or merged with a nearby section, but keep section heading discoverable
|
|
247
|
+
- `N/A`: keep heading and add one-line rationale (`Not applicable: ...`) so `vp-audit` and `vp-auto` can interpret intent
|
|
248
|
+
- Never default to “all six detailed diagrams”; diagram depth must scale with project complexity from brainstorm.
|
|
232
249
|
</step>
|
|
233
250
|
|
|
234
251
|
<step name="generate_context">
|