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 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/v0.10.0...HEAD
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, do not execute installers
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
- function handlerForTarget(target) {
243
- const root = path.join(__dirname, '..');
244
- return {
245
- script: path.join(root, 'install.sh'),
246
- env: { VIEPILOT_AUTO_YES: '1', VIEPILOT_INSTALL_PROFILE: target },
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
- function runInstaller(target, dryRun) {
251
- const config = handlerForTarget(target);
252
- const commandLabel = `${config.script} [profile=${target}]`;
253
- if (dryRun) {
254
- console.log(`[dry-run] ${commandLabel}`);
255
- return { ok: true, target, dryRun: true };
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
- const result = spawnSync('bash', [config.script], {
259
- stdio: 'inherit',
260
- env: { ...process.env, ...config.env },
261
- cwd: path.join(__dirname, '..'),
262
- });
263
- return { ok: result.status === 0, target, code: result.status ?? 1 };
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 results = selectedTargets.map((target) => runInstaller(target, options.dryRun));
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 = [];
@@ -24,11 +24,10 @@ cd viepilot
24
24
  ./install.sh
25
25
  ```
26
26
 
27
- `install.sh` copies:
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/workflows/`
30
- - `templates/` `~/.cursor/viepilot/templates/`
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
 
@@ -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 Installation Script
4
- # Installs ViePilot skills and tools to Cursor/Claude environment
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/* into ~/.cursor/skills/ (absolute paths)
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' # No Color
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}Installing...${NC}"
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? (creates symlink in /usr/local/bin) (y/n) " -n 1 -r
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
- REPLY=$([ "$ADD_PATH_CHOICE" = "1" ] && echo "y" || echo "n")
171
- fi
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 "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
187
- echo -e "${GREEN} INSTALLATION COMPLETE ✓${NC}"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viepilot",
3
- "version": "1.6.1",
3
+ "version": "1.8.0",
4
4
  "description": "**Autonomous Vibe Coding Framework / Bộ khung phát triển tự động có kiểm soát**",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -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 (framework integrity) only
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>
@@ -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. **Hypothesize**: Generate possible causes
83
- 3. **Test**: Run tests to confirm/reject hypotheses
84
- 4. **Track**: Log all findings
85
- 5. **Resolve**: Document fix and root cause
86
- 6. **Close**: Mark session complete
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}}
@@ -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)
@@ -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}
@@ -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">