viepilot 1.3.1 → 1.6.1
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 +27 -0
- package/README.md +23 -19
- package/bin/vp-tools.cjs +152 -0
- package/dev-install.sh +29 -8
- package/docs/README.md +4 -2
- package/docs/dev/cli-reference.md +70 -1
- package/docs/dev/contributing.md +4 -0
- package/docs/skills-reference.md +44 -0
- package/docs/user/features/brainstorm.md +28 -0
- package/docs/user/features/product-horizon.md +18 -0
- package/docs/user/features/ui-direction.md +63 -12
- package/docs/user/quick-start.md +22 -0
- package/install.sh +15 -3
- package/lib/viepilot-info.cjs +196 -0
- package/lib/viepilot-update.cjs +156 -0
- package/package.json +2 -1
- package/skills/vp-brainstorm/SKILL.md +9 -5
- package/skills/vp-crystallize/SKILL.md +17 -12
- package/skills/vp-info/SKILL.md +62 -0
- package/skills/vp-update/SKILL.md +59 -0
- package/templates/project/AI-GUIDE.md +23 -11
- package/templates/project/PROJECT-CONTEXT.md +22 -0
- package/templates/project/ROADMAP.md +27 -0
- package/workflows/brainstorm.md +62 -11
- package/workflows/crystallize.md +36 -9
|
@@ -5,25 +5,76 @@
|
|
|
5
5
|
## Mục tiêu
|
|
6
6
|
- Chốt hướng UI/UX sớm bằng prototype định hướng
|
|
7
7
|
- Ghi quyết định thiết kế cùng ngữ cảnh nghiệp vụ
|
|
8
|
-
- Tạo đầu vào rõ ràng cho `/vp-crystallize
|
|
8
|
+
- Tạo đầu vào rõ ràng cho `/vp-crystallize`, kể cả khi sản phẩm có **nhiều page** (FEAT-007)
|
|
9
9
|
|
|
10
|
-
##
|
|
11
|
-
|
|
10
|
+
## Hai layout được hỗ trợ
|
|
11
|
+
|
|
12
|
+
### A) Legacy — một file chính (FEAT-002)
|
|
13
|
+
Dùng khi chỉ có một màn hình / một luồng prototype đơn.
|
|
12
14
|
|
|
13
15
|
```text
|
|
14
16
|
.viepilot/ui-direction/{session-id}/
|
|
15
|
-
index.html
|
|
17
|
+
index.html # toàn bộ direction
|
|
16
18
|
style.css
|
|
17
|
-
notes.md
|
|
19
|
+
notes.md # rationale, assumptions, references
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### B) Multi-page — mỗi page một HTML (khuyến nghị khi ≥2 màn)
|
|
23
|
+
Dễ diff, dễ review, dễ map sang routing thật sau này.
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
.viepilot/ui-direction/{session-id}/
|
|
27
|
+
index.html # hub: liên kết tới mọi page (nav / danh sách)
|
|
28
|
+
style.css # shared styles (tránh copy lớn giữa các file)
|
|
29
|
+
pages/
|
|
30
|
+
landing.html
|
|
31
|
+
dashboard.html
|
|
32
|
+
...
|
|
33
|
+
notes.md # rationale + bảng inventory (bắt buộc khi có pages/)
|
|
18
34
|
```
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
36
|
+
- **`index.html`**: không bắt buộc trùng nội dung với một page cụ thể; nên là **mục lục + link** tới `pages/*.html` để mở nhanh từng màn.
|
|
37
|
+
- **`pages/{slug}.html`**: một file cho một page/screen; đặt `slug` ổn định (trùng tên route dự kiến nếu đã biết).
|
|
38
|
+
|
|
39
|
+
## `notes.md` — nguồn sự thật
|
|
40
|
+
|
|
41
|
+
Luôn ghi:
|
|
42
|
+
- rationale, assumptions, references (21st.dev, v.v.)
|
|
43
|
+
|
|
44
|
+
### Hook bắt buộc khi thư mục `pages/` tồn tại
|
|
45
|
+
|
|
46
|
+
Sau **mỗi** lần thêm / đổi tên / xóa file trong `pages/`, assistant **phải** cập nhật:
|
|
47
|
+
|
|
48
|
+
1. Liên kết trong `index.html` (hub).
|
|
49
|
+
2. Section **`## Pages inventory`** trong `notes.md` (bảng đầy đủ mọi page hiện có).
|
|
50
|
+
|
|
51
|
+
Mẫu bảng (copy-paste và điền):
|
|
52
|
+
|
|
53
|
+
```markdown
|
|
54
|
+
## Pages inventory
|
|
55
|
+
|
|
56
|
+
| Slug | File | Title | Purpose | Key sections | Nav to |
|
|
57
|
+
|------|------|-------|---------|--------------|--------|
|
|
58
|
+
| landing | pages/landing.html | Marketing home | Acquire signups | hero, features, CTA | signup, login |
|
|
59
|
+
| dashboard | pages/dashboard.html | App shell | Overview metrics | sidebar, charts, alerts | settings |
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- **Slug**: định danh ngắn, ổn định.
|
|
63
|
+
- **File**: đường dẫn tương đối từ thư mục session.
|
|
64
|
+
- **Nav to**: slug hoặc tên page đích (comma-separated).
|
|
65
|
+
|
|
66
|
+
Nếu **không** có thư mục `pages/`, không cần section `## Pages inventory` (layout legacy).
|
|
24
67
|
|
|
25
68
|
## Flow khuyến nghị
|
|
26
69
|
1. `/vp-brainstorm --ui`
|
|
27
|
-
2.
|
|
28
|
-
3.
|
|
29
|
-
4. `/vp-crystallize` đọc
|
|
70
|
+
2. Chọn legacy hoặc multi-page theo số màn hình.
|
|
71
|
+
3. Mỗi thay đổi page → cập nhật HTML + **hub + `## Pages inventory`** trong cùng một lượt.
|
|
72
|
+
4. `/vp-crystallize` đọc `notes.md` trước, sau đó `index.html`, `style.css`, và **từng** `pages/*.html` (nếu có) để lên kiến trúc UI đủ page.
|
|
73
|
+
|
|
74
|
+
## Kiểm tra nhanh (optional)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
node scripts/verify-ui-direction-pages.cjs
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Script báo lỗi nếu có session có `pages/*.html` nhưng thiếu `## Pages inventory` hoặc thiếu tên file page trong `notes.md`.
|
package/docs/user/quick-start.md
CHANGED
|
@@ -54,6 +54,25 @@ npm run readme:sync
|
|
|
54
54
|
# refresh README Total LOC metrics via cloc
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
### Version check & update
|
|
58
|
+
|
|
59
|
+
Sau khi có `vp-tools` trên PATH (ví dụ `npm i -g viepilot` hoặc cài qua `npx viepilot install` copy bundle):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
vp-tools info
|
|
63
|
+
vp-tools info --json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Cập nhật ViePilot qua npm — nên xem trước bằng dry-run; trong script/CI thêm `--yes` khi apply:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
vp-tools update --dry-run
|
|
70
|
+
vp-tools update --yes
|
|
71
|
+
vp-tools update --global --yes
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Trong project có dependency `viepilot`, có thể chạy: `node node_modules/viepilot/bin/vp-tools.cjs info`.
|
|
75
|
+
|
|
57
76
|
---
|
|
58
77
|
|
|
59
78
|
## Step 2: Create a New Project
|
|
@@ -103,6 +122,9 @@ ViePilot tạo `.viepilot/` directory với:
|
|
|
103
122
|
- `TRACKER.md` — Progress tracking
|
|
104
123
|
- `ARCHITECTURE.md` — System design
|
|
105
124
|
- `SYSTEM-RULES.md` — Coding standards
|
|
125
|
+
- `AI-GUIDE.md` — Thứ tự đọc context (kể cả **vision / horizon** trước khi khóa kiến trúc sâu)
|
|
126
|
+
|
|
127
|
+
**Post-MVP không chỉ nằm trong brainstorm:** sau crystallize, horizon phải vào `ROADMAP.md` + vision theo pha trong `PROJECT-CONTEXT.md`. Tóm tắt cho user: [Product horizon end-to-end](features/product-horizon.md).
|
|
106
128
|
|
|
107
129
|
---
|
|
108
130
|
|
package/install.sh
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# ViePilot Installation Script
|
|
4
4
|
# Installs ViePilot skills and tools to Cursor/Claude environment
|
|
5
|
+
#
|
|
6
|
+
# Optional: VIEPILOT_SYMLINK_SKILLS=1 — symlink skills/* into ~/.cursor/skills/ (absolute paths)
|
|
5
7
|
|
|
6
8
|
set -e
|
|
7
9
|
|
|
@@ -109,13 +111,23 @@ mkdir -p "$VIEPILOT_DIR/bin"
|
|
|
109
111
|
mkdir -p "$VIEPILOT_DIR/lib"
|
|
110
112
|
mkdir -p "$VIEPILOT_DIR/ui-components"
|
|
111
113
|
|
|
112
|
-
# Install skills
|
|
114
|
+
# Install skills (copy default; VIEPILOT_SYMLINK_SKILLS=1 for dev-style live links)
|
|
113
115
|
echo " Installing skills..."
|
|
114
116
|
for skill_dir in "$SCRIPT_DIR/skills"/*; do
|
|
115
117
|
if [ -d "$skill_dir" ]; then
|
|
116
118
|
skill_name=$(basename "$skill_dir")
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
119
131
|
fi
|
|
120
132
|
done
|
|
121
133
|
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ViePilot bundle metadata for `vp-tools info` (FEAT-008).
|
|
3
|
+
* Resolves the viepilot package root from the CLI location — no `.viepilot/` project required.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { execFileSync } = require('child_process');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Walk upward from startDir (and optionally cwd) for package.json with name "viepilot".
|
|
12
|
+
* @param {string} startDir - e.g. path.join(__dirname, '..') from bin/vp-tools.cjs
|
|
13
|
+
* @returns {string|null} absolute package root or null
|
|
14
|
+
*/
|
|
15
|
+
function resolveViepilotPackageRoot(startDir) {
|
|
16
|
+
const tryRoots = [path.resolve(startDir), path.resolve(process.cwd())];
|
|
17
|
+
const seen = new Set();
|
|
18
|
+
for (const base of tryRoots) {
|
|
19
|
+
if (seen.has(base)) continue;
|
|
20
|
+
seen.add(base);
|
|
21
|
+
let dir = base;
|
|
22
|
+
while (dir && dir !== path.dirname(dir)) {
|
|
23
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
24
|
+
if (fs.existsSync(pkgPath)) {
|
|
25
|
+
try {
|
|
26
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
27
|
+
if (pkg && pkg.name === 'viepilot') {
|
|
28
|
+
return dir;
|
|
29
|
+
}
|
|
30
|
+
} catch (_e) {
|
|
31
|
+
/* ignore */
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
dir = path.dirname(dir);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {string} root - viepilot package root
|
|
42
|
+
* @returns {string|null}
|
|
43
|
+
*/
|
|
44
|
+
function readInstalledVersion(root) {
|
|
45
|
+
const pkgPath = path.join(root, 'package.json');
|
|
46
|
+
if (!fs.existsSync(pkgPath)) return null;
|
|
47
|
+
try {
|
|
48
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
49
|
+
return pkg.version || null;
|
|
50
|
+
} catch (_e) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Query npm registry for latest published version (requires network + npm on PATH).
|
|
57
|
+
* @returns {{ ok: true, version: string } | { ok: false, error: string }}
|
|
58
|
+
*/
|
|
59
|
+
function fetchLatestNpmVersion() {
|
|
60
|
+
try {
|
|
61
|
+
const out = execFileSync('npm', ['view', 'viepilot', 'version', '--json'], {
|
|
62
|
+
encoding: 'utf8',
|
|
63
|
+
timeout: 15000,
|
|
64
|
+
windowsHide: true,
|
|
65
|
+
}).trim();
|
|
66
|
+
const parsed = JSON.parse(out);
|
|
67
|
+
const version = typeof parsed === 'string' ? parsed : String(parsed);
|
|
68
|
+
return { ok: true, version };
|
|
69
|
+
} catch (e) {
|
|
70
|
+
return { ok: false, error: e.message || String(e) };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Parse `version:` from YAML-like frontmatter (first --- block).
|
|
76
|
+
* @param {string} content
|
|
77
|
+
* @returns {string}
|
|
78
|
+
*/
|
|
79
|
+
function parseSkillFileVersion(content) {
|
|
80
|
+
if (typeof content !== 'string' || !content.startsWith('---')) {
|
|
81
|
+
return 'unspecified';
|
|
82
|
+
}
|
|
83
|
+
const end = content.indexOf('\n---', 3);
|
|
84
|
+
if (end === -1) {
|
|
85
|
+
return 'unspecified';
|
|
86
|
+
}
|
|
87
|
+
const block = content.slice(3, end);
|
|
88
|
+
const m = block.match(/^version:\s*["']?([^"'\r\n]+)["']?/m);
|
|
89
|
+
return m ? m[1].trim() : 'unspecified';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {string} root - viepilot package root
|
|
94
|
+
* @returns {Array<{ id: string, version: string, relativePath: string }>}
|
|
95
|
+
*/
|
|
96
|
+
function listSkillsWithVersions(root) {
|
|
97
|
+
const skillsDir = path.join(root, 'skills');
|
|
98
|
+
if (!fs.existsSync(skillsDir)) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
102
|
+
const out = [];
|
|
103
|
+
for (const ent of entries) {
|
|
104
|
+
if (!ent.isDirectory()) continue;
|
|
105
|
+
const skillFile = path.join(skillsDir, ent.name, 'SKILL.md');
|
|
106
|
+
if (!fs.existsSync(skillFile)) continue;
|
|
107
|
+
const content = fs.readFileSync(skillFile, 'utf8');
|
|
108
|
+
out.push({
|
|
109
|
+
id: ent.name,
|
|
110
|
+
version: parseSkillFileVersion(content),
|
|
111
|
+
relativePath: path.join('skills', ent.name, 'SKILL.md').replace(/\\/g, '/'),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
out.sort((a, b) => a.id.localeCompare(b.id));
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const WORKFLOW_SEMVER_NOTE = 'no semver in workflow markdown';
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {string} root
|
|
122
|
+
* @returns {Array<{ id: string, relativePath: string, semverInFile: null, note: string }>}
|
|
123
|
+
*/
|
|
124
|
+
function listWorkflows(root) {
|
|
125
|
+
const wfDir = path.join(root, 'workflows');
|
|
126
|
+
if (!fs.existsSync(wfDir)) {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
const files = fs
|
|
130
|
+
.readdirSync(wfDir)
|
|
131
|
+
.filter((f) => f.endsWith('.md'))
|
|
132
|
+
.sort((a, b) => a.localeCompare(b));
|
|
133
|
+
return files.map((f) => ({
|
|
134
|
+
id: f.replace(/\.md$/i, ''),
|
|
135
|
+
relativePath: path.join('workflows', f).replace(/\\/g, '/'),
|
|
136
|
+
semverInFile: null,
|
|
137
|
+
note: WORKFLOW_SEMVER_NOTE,
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {string} root
|
|
143
|
+
* @returns {string|null}
|
|
144
|
+
*/
|
|
145
|
+
function tryGitHead(root) {
|
|
146
|
+
try {
|
|
147
|
+
return execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
148
|
+
cwd: root,
|
|
149
|
+
encoding: 'utf8',
|
|
150
|
+
timeout: 8000,
|
|
151
|
+
windowsHide: true,
|
|
152
|
+
}).trim();
|
|
153
|
+
} catch (_e) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @param {string} root
|
|
160
|
+
* @param {{ includeLatestNpm?: boolean }} [options]
|
|
161
|
+
*/
|
|
162
|
+
function buildInfoReport(root, options = {}) {
|
|
163
|
+
const includeLatestNpm = options.includeLatestNpm !== false;
|
|
164
|
+
const installedVersion = readInstalledVersion(root);
|
|
165
|
+
let pkgName = 'viepilot';
|
|
166
|
+
try {
|
|
167
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
168
|
+
if (pkg.name) pkgName = pkg.name;
|
|
169
|
+
} catch (_e) {
|
|
170
|
+
/* keep default */
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const report = {
|
|
174
|
+
packageRoot: root,
|
|
175
|
+
packageName: pkgName,
|
|
176
|
+
installedVersion: installedVersion || 'unknown',
|
|
177
|
+
latestNpm: includeLatestNpm ? fetchLatestNpmVersion() : { ok: false, error: 'skipped' },
|
|
178
|
+
gitHead: tryGitHead(root),
|
|
179
|
+
skills: listSkillsWithVersions(root),
|
|
180
|
+
workflows: listWorkflows(root),
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
return report;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
resolveViepilotPackageRoot,
|
|
188
|
+
readInstalledVersion,
|
|
189
|
+
fetchLatestNpmVersion,
|
|
190
|
+
parseSkillFileVersion,
|
|
191
|
+
listSkillsWithVersions,
|
|
192
|
+
listWorkflows,
|
|
193
|
+
tryGitHead,
|
|
194
|
+
buildInfoReport,
|
|
195
|
+
WORKFLOW_SEMVER_NOTE,
|
|
196
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan and run `npm` upgrade for the viepilot package (FEAT-008 / vp-tools update).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { spawnSync, execFileSync } = require('child_process');
|
|
7
|
+
const viepilotInfo = require('./viepilot-info.cjs');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @returns {string|null} absolute path to global .../node_modules/viepilot
|
|
11
|
+
*/
|
|
12
|
+
function tryGetNpmGlobalViepilotPath() {
|
|
13
|
+
try {
|
|
14
|
+
const root = execFileSync('npm', ['root', '-g'], {
|
|
15
|
+
encoding: 'utf8',
|
|
16
|
+
timeout: 10000,
|
|
17
|
+
windowsHide: true,
|
|
18
|
+
}).trim();
|
|
19
|
+
if (!root) return null;
|
|
20
|
+
return path.resolve(root, 'viepilot');
|
|
21
|
+
} catch (_e) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} viepilotPackageRoot
|
|
28
|
+
* @param {boolean} forceGlobal
|
|
29
|
+
* @param {string|null} globalViepilotPath
|
|
30
|
+
*/
|
|
31
|
+
function classifyInstall(viepilotPackageRoot, forceGlobal, globalViepilotPath) {
|
|
32
|
+
if (forceGlobal) {
|
|
33
|
+
return {
|
|
34
|
+
mode: 'global',
|
|
35
|
+
cwd: undefined,
|
|
36
|
+
npmArgs: ['install', '-g', 'viepilot@latest'],
|
|
37
|
+
ambiguous: false,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const r = path.resolve(viepilotPackageRoot);
|
|
41
|
+
if (globalViepilotPath) {
|
|
42
|
+
const g = path.resolve(globalViepilotPath);
|
|
43
|
+
if (r === g) {
|
|
44
|
+
return {
|
|
45
|
+
mode: 'global',
|
|
46
|
+
cwd: undefined,
|
|
47
|
+
npmArgs: ['install', '-g', 'viepilot@latest'],
|
|
48
|
+
ambiguous: false,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const localSuffix = path.join('node_modules', 'viepilot');
|
|
53
|
+
if (r.endsWith(localSuffix)) {
|
|
54
|
+
const projectRoot = path.resolve(viepilotPackageRoot, '..', '..');
|
|
55
|
+
return {
|
|
56
|
+
mode: 'local',
|
|
57
|
+
cwd: projectRoot,
|
|
58
|
+
npmArgs: ['install', 'viepilot@latest'],
|
|
59
|
+
ambiguous: false,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
mode: 'global',
|
|
64
|
+
cwd: undefined,
|
|
65
|
+
npmArgs: ['install', '-g', 'viepilot@latest'],
|
|
66
|
+
ambiguous: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Rough semver compare for x.y.z (numeric segments only).
|
|
72
|
+
* @returns {-1|0|1|null} null if either side empty
|
|
73
|
+
*/
|
|
74
|
+
function compareSemver(a, b) {
|
|
75
|
+
if (a == null || b == null || a === '' || b === '') return null;
|
|
76
|
+
const pa = String(a).split(/[.+]/).map((x) => parseInt(x, 10) || 0);
|
|
77
|
+
const pb = String(b).split(/[.+]/).map((x) => parseInt(x, 10) || 0);
|
|
78
|
+
const len = Math.max(pa.length, pb.length);
|
|
79
|
+
for (let i = 0; i < len; i++) {
|
|
80
|
+
const da = pa[i] || 0;
|
|
81
|
+
const db = pb[i] || 0;
|
|
82
|
+
if (da < db) return -1;
|
|
83
|
+
if (da > db) return 1;
|
|
84
|
+
}
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {{ startDir: string, forceGlobal?: boolean }} opts
|
|
90
|
+
*/
|
|
91
|
+
function buildUpdatePlan(opts) {
|
|
92
|
+
const startDir = opts.startDir;
|
|
93
|
+
const forceGlobal = Boolean(opts.forceGlobal);
|
|
94
|
+
const root = viepilotInfo.resolveViepilotPackageRoot(startDir);
|
|
95
|
+
if (!root) {
|
|
96
|
+
return { ok: false, error: 'Could not locate viepilot package root' };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const installed = viepilotInfo.readInstalledVersion(root);
|
|
100
|
+
const latestResult = viepilotInfo.fetchLatestNpmVersion();
|
|
101
|
+
|
|
102
|
+
if (latestResult.ok && installed && compareSemver(installed, latestResult.version) >= 0) {
|
|
103
|
+
return {
|
|
104
|
+
ok: true,
|
|
105
|
+
alreadyLatest: true,
|
|
106
|
+
installedVersion: installed,
|
|
107
|
+
latestVersion: latestResult.version,
|
|
108
|
+
viepilotRoot: root,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const globalViepilotPath = tryGetNpmGlobalViepilotPath();
|
|
113
|
+
const layout = classifyInstall(root, forceGlobal, globalViepilotPath);
|
|
114
|
+
const displayCommand = layout.cwd
|
|
115
|
+
? `(cwd ${layout.cwd}) npm ${layout.npmArgs.join(' ')}`
|
|
116
|
+
: `npm ${layout.npmArgs.join(' ')}`;
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
ok: true,
|
|
120
|
+
alreadyLatest: false,
|
|
121
|
+
installedVersion: installed,
|
|
122
|
+
latestVersion: latestResult.ok ? latestResult.version : null,
|
|
123
|
+
latestNpmError: latestResult.ok ? null : latestResult.error,
|
|
124
|
+
viepilotRoot: root,
|
|
125
|
+
mode: layout.mode,
|
|
126
|
+
cwd: layout.cwd,
|
|
127
|
+
npmArgs: layout.npmArgs,
|
|
128
|
+
ambiguous: layout.ambiguous,
|
|
129
|
+
displayCommand,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {object} plan - from buildUpdatePlan (not alreadyLatest)
|
|
135
|
+
* @returns {{ ok: boolean, code?: number }}
|
|
136
|
+
*/
|
|
137
|
+
function runNpmUpdate(plan) {
|
|
138
|
+
const r = spawnSync('npm', plan.npmArgs, {
|
|
139
|
+
cwd: plan.cwd,
|
|
140
|
+
stdio: 'inherit',
|
|
141
|
+
shell: false,
|
|
142
|
+
windowsHide: true,
|
|
143
|
+
});
|
|
144
|
+
if (r.status === 0) {
|
|
145
|
+
return { ok: true };
|
|
146
|
+
}
|
|
147
|
+
return { ok: false, code: r.status == null ? 1 : r.status };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
tryGetNpmGlobalViepilotPath,
|
|
152
|
+
classifyInstall,
|
|
153
|
+
compareSemver,
|
|
154
|
+
buildUpdatePlan,
|
|
155
|
+
runNpmUpdate,
|
|
156
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viepilot",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
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": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"test:watch": "jest --watch",
|
|
17
17
|
"readme:sync": "node scripts/sync-readme-metrics.cjs",
|
|
18
18
|
"lint:cli": "node --check bin/vp-tools.cjs && node --check bin/viepilot.cjs",
|
|
19
|
+
"verify:ui-direction": "node scripts/verify-ui-direction-pages.cjs",
|
|
19
20
|
"verify:release": "npm run lint:cli && npm test && npm pack --dry-run",
|
|
20
21
|
"prepublishOnly": "npm run verify:release",
|
|
21
22
|
"smoke:published": "node scripts/smoke-published.cjs",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: vp-brainstorm
|
|
3
3
|
description: "Brainstorm session để thu thập ý tưởng, quyết định cho dự án"
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<cursor_skill_adapter>
|
|
@@ -25,7 +25,8 @@ Hỗ trợ:
|
|
|
25
25
|
- Xem lại session trước đó
|
|
26
26
|
- Landing page layout discovery (hỏi thêm để chốt bố cục)
|
|
27
27
|
- In-session research (research ngay trong phiên brainstorm theo yêu cầu)
|
|
28
|
-
- UI Direction mode: tạo/cập nhật HTML prototype + notes trong `.viepilot/ui-direction/{session-id}/`
|
|
28
|
+
- UI Direction mode: tạo/cập nhật HTML prototype + notes trong `.viepilot/ui-direction/{session-id}/` — hỗ trợ **multi-page** (`pages/{slug}.html` + hub `index.html`) và hook **`## Pages inventory`** trong `notes.md` khi có `pages/` (FEAT-007)
|
|
29
|
+
- **Product horizon (ENH-014):** mọi session phải duy trì **`## Product horizon`** khi thảo luận capability/milestone — tier tags `(MVP)` / `(Post-MVP)` / `(Future)`, non-goals, deferred capabilities; hoặc ghi rõ **single-release / no deferred epics** (contract: `workflows/brainstorm.md`)
|
|
29
30
|
|
|
30
31
|
**Creates/Updates:**
|
|
31
32
|
- `docs/brainstorm/session-{YYYY-MM-DD}.md`
|
|
@@ -56,10 +57,11 @@ Key steps:
|
|
|
56
57
|
3. Load context if continuing
|
|
57
58
|
4. Run interactive Q&A với topic-based structure
|
|
58
59
|
5. Nếu topic là landing page: hỏi thêm bố cục + tham khảo `21st.dev` để đề xuất section/components
|
|
59
|
-
6. Nếu topic cần UI/UX: tạo/cập nhật UI Direction artifacts
|
|
60
|
+
6. Nếu topic cần UI/UX: tạo/cập nhật UI Direction artifacts trong `.viepilot/ui-direction/{session-id}/` — legacy: `index.html` + `style.css` + `notes.md`; multi-page: thêm `pages/*.html`, `index.html` làm hub, và sau mỗi thay đổi page cập nhật **`## Pages inventory`** trong `notes.md` (xem `docs/user/features/ui-direction.md`)
|
|
60
61
|
7. Nếu user yêu cầu research hoặc cần làm rõ quyết định: research ngay trong session và quay lại topic
|
|
61
|
-
8.
|
|
62
|
-
9.
|
|
62
|
+
8. Khi topic thêm/sửa capability hoặc release scope: cập nhật **`## Product horizon`** trong session (merge, không xóa tier tags im lặng) theo `workflows/brainstorm.md`
|
|
63
|
+
9. Save session with structured format (bao gồm research notes + UI direction references + **Product horizon** khi có)
|
|
64
|
+
10. Suggest next action: `/vp-crystallize`
|
|
63
65
|
</process>
|
|
64
66
|
|
|
65
67
|
<success_criteria>
|
|
@@ -71,5 +73,7 @@ Key steps:
|
|
|
71
73
|
- [ ] 21st.dev references included when relevant
|
|
72
74
|
- [ ] Research can be executed inside the same brainstorm session
|
|
73
75
|
- [ ] UI Direction artifacts created/updated when UI mode is active
|
|
76
|
+
- [ ] Multi-page sessions: hub links + `## Pages inventory` stay in sync with `pages/*.html`
|
|
77
|
+
- [ ] `## Product horizon` present với MVP / Post-MVP / Future (hoặc explicit single-release statement) khi scope được thảo luận
|
|
74
78
|
- [ ] Next steps suggested
|
|
75
79
|
</success_criteria>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: vp-crystallize
|
|
3
3
|
description: "Chuyển đổi brainstorm thành executable artifacts"
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<cursor_skill_adapter>
|
|
@@ -25,9 +25,9 @@ Chuyển đổi brainstorm sessions thành structured artifacts để AI có th
|
|
|
25
25
|
├── AI-GUIDE.md # Navigation cho AI
|
|
26
26
|
├── PROJECT-META.md # Metadata dự án
|
|
27
27
|
├── ARCHITECTURE.md # System design
|
|
28
|
-
├── PROJECT-CONTEXT.md # Domain knowledge
|
|
28
|
+
├── PROJECT-CONTEXT.md # Domain knowledge + `<product_vision>` (phased scope)
|
|
29
29
|
├── SYSTEM-RULES.md # Coding rules & standards
|
|
30
|
-
├── ROADMAP.md #
|
|
30
|
+
├── ROADMAP.md # MVP phases & tasks + **Post-MVP / Product horizon** block (mandatory)
|
|
31
31
|
├── TRACKER.md # Progress tracking
|
|
32
32
|
├── HANDOFF.json # Machine-readable state
|
|
33
33
|
└── schemas/ # Database, API, Kafka schemas
|
|
@@ -75,12 +75,15 @@ Ask user for:
|
|
|
75
75
|
- Load all brainstorm sessions
|
|
76
76
|
- Extract: decisions, architecture, schemas, features
|
|
77
77
|
- Extract selected tech stacks
|
|
78
|
-
-
|
|
78
|
+
- **Product horizon (ENH-014):** parse each session **`## Product horizon`**; build consolidated **horizon inventory**; record **single-release** mode when stated; run **validation gate** (missing section vs multi-release discussion → stop and ask; tier conflicts → stop) — full contract: `workflows/crystallize.md` Step 1
|
|
79
|
+
- Validate completeness (tech stack, features, schema/API clarity, **horizon gate**)
|
|
79
80
|
|
|
80
81
|
### Step 1A: Consume UI direction (if present)
|
|
81
|
-
- Read `.viepilot/ui-direction/{session-id}/notes.md
|
|
82
|
-
-
|
|
83
|
-
-
|
|
82
|
+
- Read `.viepilot/ui-direction/{session-id}/notes.md` first, then `style.css`, then HTML:
|
|
83
|
+
- **Multi-page:** if `pages/*.html` exists → require `## Pages inventory` in `notes.md`, validate it lists every page file, read each `pages/*.html` plus hub `index.html` for navigation.
|
|
84
|
+
- **Legacy:** no `pages/` → read `index.html` + `style.css` as before.
|
|
85
|
+
- Carry approved layout/component decisions into architecture + roadmap artifacts; **architecture must reference all pages** from inventory when multi-page.
|
|
86
|
+
- Mark assumptions explicitly if direction artifacts are missing or inventory/files mismatch.
|
|
84
87
|
|
|
85
88
|
### Step 1B: Official stack research (mandatory)
|
|
86
89
|
- For every selected stack, research official docs and authoritative sources
|
|
@@ -119,6 +122,7 @@ Ask user for:
|
|
|
119
122
|
- Business rules
|
|
120
123
|
- Conventions
|
|
121
124
|
- Constraints
|
|
125
|
+
- Fill **`<product_vision>`** from template (`templates/project/PROJECT-CONTEXT.md`): MVP boundary, Post-MVP / Future themes, anti-goals — aligned with brainstorm horizon + Step 1 inventory
|
|
122
126
|
|
|
123
127
|
### Step 6: Generate SYSTEM-RULES.md
|
|
124
128
|
- Architecture rules
|
|
@@ -131,10 +135,9 @@ Ask user for:
|
|
|
131
135
|
- Stack-specific rules from cache
|
|
132
136
|
|
|
133
137
|
### Step 7: Generate ROADMAP.md
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
- Add verification checkpoints
|
|
138
|
+
- Use `templates/project/ROADMAP.md` — **executable MVP phases** first (tasks, criteria, verification)
|
|
139
|
+
- **Mandatory `## Post-MVP / Product horizon`:** epic-level deferred work from horizon inventory **or** explicit single-release statement (no silent omission)
|
|
140
|
+
- **Self-check before finalize:** non-empty horizon inventory must appear in ROADMAP; if mismatch → stop and ask user — see `workflows/crystallize.md` Step 7
|
|
138
141
|
|
|
139
142
|
### Step 8: Generate schemas/
|
|
140
143
|
- database-schema.sql
|
|
@@ -168,7 +171,9 @@ Ask user for:
|
|
|
168
171
|
- [ ] All artifacts created in .viepilot/
|
|
169
172
|
- [ ] PROJECT-META.md has complete metadata
|
|
170
173
|
- [ ] SYSTEM-RULES.md has all standards
|
|
171
|
-
- [ ]
|
|
174
|
+
- [ ] Step 1 horizon extracted or explicit single-release recorded; validation gate satisfied
|
|
175
|
+
- [ ] PROJECT-CONTEXT.md includes populated **`<product_vision>`** (template placeholders filled from brainstorm)
|
|
176
|
+
- [ ] ROADMAP.md has MVP phases with tasks **and** mandatory Post-MVP / horizon block (or explicit single-release statement)
|
|
172
177
|
- [ ] TRACKER.md initialized
|
|
173
178
|
- [ ] Project files created
|
|
174
179
|
- [ ] Git committed
|