viepilot 1.0.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 +230 -0
- package/LICENSE +23 -0
- package/README.md +550 -0
- package/bin/viepilot.cjs +222 -0
- package/bin/vp-tools.cjs +912 -0
- package/dev-install.sh +109 -0
- package/docs/README.md +125 -0
- package/docs/advanced-usage.md +366 -0
- package/docs/api/README.md +12 -0
- package/docs/api/graphql-schema.md +5 -0
- package/docs/api/kafka-events.md +5 -0
- package/docs/api/rest-api.md +19 -0
- package/docs/api/websocket-api.md +5 -0
- package/docs/dev/architecture.md +226 -0
- package/docs/dev/cli-reference.md +324 -0
- package/docs/dev/contributing.md +195 -0
- package/docs/dev/deployment.md +204 -0
- package/docs/dev/getting-started.md +16 -0
- package/docs/dev/testing.md +171 -0
- package/docs/dev/ui-components-library.md +36 -0
- package/docs/getting-started.md +163 -0
- package/docs/skills-reference.md +399 -0
- package/docs/troubleshooting.md +297 -0
- package/docs/user/faq.md +117 -0
- package/docs/user/features/autonomous-mode.md +111 -0
- package/docs/user/features/checkpoint-recovery.md +76 -0
- package/docs/user/features/debug-mode.md +77 -0
- package/docs/user/features/ui-direction.md +29 -0
- package/docs/user/quick-start.md +157 -0
- package/docs/videos/01-installation.md +113 -0
- package/docs/videos/02-first-project.md +132 -0
- package/docs/videos/03-autonomous-mode.md +147 -0
- package/install.sh +144 -0
- package/lib/cli-shared.cjs +108 -0
- package/package.json +78 -0
- package/skills/vp-audit/SKILL.md +140 -0
- package/skills/vp-auto/SKILL.md +204 -0
- package/skills/vp-brainstorm/SKILL.md +75 -0
- package/skills/vp-crystallize/SKILL.md +175 -0
- package/skills/vp-debug/SKILL.md +96 -0
- package/skills/vp-docs/SKILL.md +258 -0
- package/skills/vp-evolve/SKILL.md +165 -0
- package/skills/vp-pause/SKILL.md +150 -0
- package/skills/vp-request/SKILL.md +250 -0
- package/skills/vp-resume/SKILL.md +141 -0
- package/skills/vp-rollback/SKILL.md +116 -0
- package/skills/vp-status/SKILL.md +137 -0
- package/skills/vp-task/SKILL.md +139 -0
- package/skills/vp-ui-components/SKILL.md +64 -0
- package/templates/phase/PHASE-STATE.md +35 -0
- package/templates/phase/SPEC.md +40 -0
- package/templates/phase/SUMMARY.md +67 -0
- package/templates/phase/TASK.md +101 -0
- package/templates/phase/VERIFICATION.md +49 -0
- package/templates/project/AI-GUIDE.md +114 -0
- package/templates/project/ARCHITECTURE.md +70 -0
- package/templates/project/CHANGELOG.md +36 -0
- package/templates/project/CONTRIBUTING.md +154 -0
- package/templates/project/CONTRIBUTORS.md +41 -0
- package/templates/project/PROJECT-CONTEXT.md +74 -0
- package/templates/project/PROJECT-META.md +133 -0
- package/templates/project/README.md +197 -0
- package/templates/project/ROADMAP.md +56 -0
- package/templates/project/SYSTEM-RULES.md +368 -0
- package/templates/project/TRACKER.md +50 -0
- package/ui-components/INDEX.md +9 -0
- package/ui-components/base/button/README.md +8 -0
- package/ui-components/base/button/metadata.json +8 -0
- package/ui-components/base/card/README.md +8 -0
- package/ui-components/base/card/metadata.json +8 -0
- package/ui-components/base/input/README.md +8 -0
- package/ui-components/base/input/metadata.json +8 -0
- package/workflows/audit.md +549 -0
- package/workflows/autonomous.md +425 -0
- package/workflows/brainstorm.md +257 -0
- package/workflows/crystallize.md +418 -0
- package/workflows/debug.md +241 -0
- package/workflows/documentation.md +587 -0
- package/workflows/evolve.md +258 -0
- package/workflows/pause-work.md +255 -0
- package/workflows/request.md +534 -0
- package/workflows/resume-work.md +226 -0
- package/workflows/rollback.md +202 -0
- package/workflows/ui-components.md +109 -0
package/bin/vp-tools.cjs
ADDED
|
@@ -0,0 +1,912 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ViePilot CLI Tools
|
|
5
|
+
* Helper utilities for state management and workflow operations
|
|
6
|
+
*
|
|
7
|
+
* @package viepilot
|
|
8
|
+
* @author Trần Thành Nhân
|
|
9
|
+
* @license MIT
|
|
10
|
+
* @version 0.1.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const readline = require('readline');
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
validators,
|
|
19
|
+
levenshteinDistance,
|
|
20
|
+
findProjectRoot,
|
|
21
|
+
VIEPILOT_DIR,
|
|
22
|
+
} = require(path.join(__dirname, '../lib/cli-shared.cjs'));
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Output Formatting (TTY-aware)
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
const isTTY = process.stdout.isTTY;
|
|
29
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
30
|
+
|
|
31
|
+
const colors = {
|
|
32
|
+
reset: isTTY ? '\x1b[0m' : '',
|
|
33
|
+
red: isTTY ? '\x1b[31m' : '',
|
|
34
|
+
green: isTTY ? '\x1b[32m' : '',
|
|
35
|
+
yellow: isTTY ? '\x1b[33m' : '',
|
|
36
|
+
blue: isTTY ? '\x1b[34m' : '',
|
|
37
|
+
cyan: isTTY ? '\x1b[36m' : '',
|
|
38
|
+
gray: isTTY ? '\x1b[90m' : '',
|
|
39
|
+
bold: isTTY ? '\x1b[1m' : '',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function formatError(message, hint = null) {
|
|
43
|
+
let output = `${colors.red}✖ Error:${colors.reset} ${message}`;
|
|
44
|
+
if (hint) {
|
|
45
|
+
output += `\n${colors.gray} Hint: ${hint}${colors.reset}`;
|
|
46
|
+
}
|
|
47
|
+
return output;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatSuccess(message) {
|
|
51
|
+
return `${colors.green}✔${colors.reset} ${message}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function formatWarning(message) {
|
|
55
|
+
return `${colors.yellow}⚠${colors.reset} ${message}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function formatInfo(message) {
|
|
59
|
+
return `${colors.blue}ℹ${colors.reset} ${message}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function validateArgs(validations) {
|
|
63
|
+
for (const validation of validations) {
|
|
64
|
+
if (!validation.valid) {
|
|
65
|
+
console.error(formatError(validation.error, validation.hint));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Interactive Prompts
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
async function confirm(message, defaultYes = false) {
|
|
76
|
+
if (!isInteractive) {
|
|
77
|
+
console.log(formatWarning(`Non-interactive mode, assuming ${defaultYes ? 'yes' : 'no'}`));
|
|
78
|
+
return defaultYes;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const rl = readline.createInterface({
|
|
82
|
+
input: process.stdin,
|
|
83
|
+
output: process.stdout,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const defaultHint = defaultYes ? '[Y/n]' : '[y/N]';
|
|
87
|
+
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
rl.question(`${colors.yellow}?${colors.reset} ${message} ${colors.gray}${defaultHint}${colors.reset} `, (answer) => {
|
|
90
|
+
rl.close();
|
|
91
|
+
const normalized = answer.trim().toLowerCase();
|
|
92
|
+
if (normalized === '') {
|
|
93
|
+
resolve(defaultYes);
|
|
94
|
+
} else {
|
|
95
|
+
resolve(normalized === 'y' || normalized === 'yes');
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function select(message, options) {
|
|
102
|
+
if (!isInteractive) {
|
|
103
|
+
console.log(formatWarning('Non-interactive mode, using first option'));
|
|
104
|
+
return options[0].value;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const rl = readline.createInterface({
|
|
108
|
+
input: process.stdin,
|
|
109
|
+
output: process.stdout,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
console.log(`\n${colors.cyan}?${colors.reset} ${message}\n`);
|
|
113
|
+
options.forEach((opt, i) => {
|
|
114
|
+
console.log(` ${colors.bold}${i + 1}.${colors.reset} ${opt.label}`);
|
|
115
|
+
});
|
|
116
|
+
console.log();
|
|
117
|
+
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
rl.question(`${colors.gray}Enter choice (1-${options.length}):${colors.reset} `, (answer) => {
|
|
120
|
+
rl.close();
|
|
121
|
+
const index = parseInt(answer, 10) - 1;
|
|
122
|
+
if (index >= 0 && index < options.length) {
|
|
123
|
+
resolve(options[index].value);
|
|
124
|
+
} else {
|
|
125
|
+
console.log(formatWarning('Invalid choice, using first option'));
|
|
126
|
+
resolve(options[0].value);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ============================================================================
|
|
133
|
+
// Utility Functions
|
|
134
|
+
// ============================================================================
|
|
135
|
+
|
|
136
|
+
function readJson(filePath) {
|
|
137
|
+
try {
|
|
138
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
139
|
+
} catch (e) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function writeJson(filePath, data) {
|
|
145
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function readMarkdown(filePath) {
|
|
149
|
+
try {
|
|
150
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
151
|
+
} catch (e) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function currentTimestamp(format = 'iso') {
|
|
157
|
+
const now = new Date();
|
|
158
|
+
if (format === 'iso') {
|
|
159
|
+
return now.toISOString();
|
|
160
|
+
} else if (format === 'date') {
|
|
161
|
+
return now.toISOString().split('T')[0];
|
|
162
|
+
} else if (format === 'full') {
|
|
163
|
+
return now.toISOString().replace('T', ' ').split('.')[0];
|
|
164
|
+
}
|
|
165
|
+
return now.toISOString();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// Commands
|
|
170
|
+
// ============================================================================
|
|
171
|
+
|
|
172
|
+
const commands = {
|
|
173
|
+
/**
|
|
174
|
+
* Initialize or get project state
|
|
175
|
+
*/
|
|
176
|
+
init: (args) => {
|
|
177
|
+
const projectCheck = validators.requireProjectRoot();
|
|
178
|
+
validateArgs([projectCheck]);
|
|
179
|
+
const projectRoot = projectCheck.value;
|
|
180
|
+
|
|
181
|
+
const trackerPath = path.join(projectRoot, VIEPILOT_DIR, 'TRACKER.md');
|
|
182
|
+
const roadmapPath = path.join(projectRoot, VIEPILOT_DIR, 'ROADMAP.md');
|
|
183
|
+
const handoffPath = path.join(projectRoot, VIEPILOT_DIR, 'HANDOFF.json');
|
|
184
|
+
|
|
185
|
+
const result = {
|
|
186
|
+
project_root: projectRoot,
|
|
187
|
+
viepilot_dir: path.join(projectRoot, VIEPILOT_DIR),
|
|
188
|
+
tracker_exists: fs.existsSync(trackerPath),
|
|
189
|
+
roadmap_exists: fs.existsSync(roadmapPath),
|
|
190
|
+
handoff_exists: fs.existsSync(handoffPath),
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
if (result.handoff_exists) {
|
|
194
|
+
result.handoff = readJson(handoffPath);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log(formatSuccess('Project found'));
|
|
198
|
+
console.log(JSON.stringify(result, null, 2));
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get current timestamp
|
|
203
|
+
*/
|
|
204
|
+
'current-timestamp': (args) => {
|
|
205
|
+
const format = args[0] || 'iso';
|
|
206
|
+
const raw = args.includes('--raw');
|
|
207
|
+
|
|
208
|
+
if (args[0] && !args.includes('--raw')) {
|
|
209
|
+
const formatCheck = validators.isValidTimestampFormat(format);
|
|
210
|
+
validateArgs([formatCheck]);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const ts = currentTimestamp(format);
|
|
214
|
+
if (raw) {
|
|
215
|
+
console.log(ts);
|
|
216
|
+
} else {
|
|
217
|
+
console.log(JSON.stringify({ timestamp: ts }));
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get phase information
|
|
223
|
+
*/
|
|
224
|
+
'phase-info': (args) => {
|
|
225
|
+
const phaseNum = args[0];
|
|
226
|
+
|
|
227
|
+
// Validate phase number
|
|
228
|
+
const phaseCheck = validators.isPositiveInteger(phaseNum, 'Phase number');
|
|
229
|
+
const projectCheck = validators.requireProjectRoot();
|
|
230
|
+
validateArgs([
|
|
231
|
+
phaseNum ? phaseCheck : { valid: false, error: 'Phase number is required', hint: 'Usage: vp-tools phase-info <phase_number>' },
|
|
232
|
+
projectCheck
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
const projectRoot = projectCheck.value;
|
|
236
|
+
const phasesDir = path.join(projectRoot, VIEPILOT_DIR, 'phases');
|
|
237
|
+
|
|
238
|
+
if (!fs.existsSync(phasesDir)) {
|
|
239
|
+
console.error(formatError('No phases directory found', 'Run /vp-crystallize first to create phases'));
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const phaseDirs = fs.readdirSync(phasesDir).filter(d => d.startsWith(String(phaseCheck.value).padStart(2, '0')));
|
|
244
|
+
|
|
245
|
+
if (phaseDirs.length === 0) {
|
|
246
|
+
console.error(formatError(`Phase ${phaseNum} not found`, `Available phases in ${phasesDir}`));
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const phaseDir = path.join(phasesDir, phaseDirs[0]);
|
|
251
|
+
const specPath = path.join(phaseDir, 'SPEC.md');
|
|
252
|
+
const statePath = path.join(phaseDir, 'PHASE-STATE.md');
|
|
253
|
+
const tasksDir = path.join(phaseDir, 'tasks');
|
|
254
|
+
|
|
255
|
+
const result = {
|
|
256
|
+
phase_number: phaseCheck.value,
|
|
257
|
+
phase_dir: phaseDir,
|
|
258
|
+
phase_slug: phaseDirs[0],
|
|
259
|
+
has_spec: fs.existsSync(specPath),
|
|
260
|
+
has_state: fs.existsSync(statePath),
|
|
261
|
+
tasks: [],
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
if (fs.existsSync(tasksDir)) {
|
|
265
|
+
result.tasks = fs.readdirSync(tasksDir)
|
|
266
|
+
.filter(f => f.endsWith('.md'))
|
|
267
|
+
.map(f => ({
|
|
268
|
+
file: f,
|
|
269
|
+
path: path.join(tasksDir, f),
|
|
270
|
+
}));
|
|
271
|
+
result.task_count = result.tasks.length;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
console.log(formatSuccess(`Phase ${phaseCheck.value} found`));
|
|
275
|
+
console.log(JSON.stringify(result, null, 2));
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Update task status
|
|
280
|
+
*/
|
|
281
|
+
'task-status': (args) => {
|
|
282
|
+
const phaseNum = args[0];
|
|
283
|
+
const taskNum = args[1];
|
|
284
|
+
const status = args[2];
|
|
285
|
+
|
|
286
|
+
// Validate all inputs
|
|
287
|
+
const phaseCheck = validators.isPositiveInteger(phaseNum, 'Phase number');
|
|
288
|
+
const taskCheck = validators.isPositiveInteger(taskNum, 'Task number');
|
|
289
|
+
const statusCheck = status ? validators.isValidStatus(status) : { valid: false, error: 'Status is required', hint: 'Valid: not_started, in_progress, done, skipped, blocked' };
|
|
290
|
+
const projectCheck = validators.requireProjectRoot();
|
|
291
|
+
|
|
292
|
+
validateArgs([
|
|
293
|
+
phaseNum ? phaseCheck : { valid: false, error: 'Phase number is required', hint: 'Usage: vp-tools task-status <phase> <task> <status>' },
|
|
294
|
+
taskNum ? taskCheck : { valid: false, error: 'Task number is required', hint: 'Usage: vp-tools task-status <phase> <task> <status>' },
|
|
295
|
+
statusCheck,
|
|
296
|
+
projectCheck
|
|
297
|
+
]);
|
|
298
|
+
|
|
299
|
+
console.log(formatSuccess(`Task ${phaseCheck.value}.${taskCheck.value} → ${statusCheck.value}`));
|
|
300
|
+
console.log(JSON.stringify({
|
|
301
|
+
updated: true,
|
|
302
|
+
phase: phaseCheck.value,
|
|
303
|
+
task: taskCheck.value,
|
|
304
|
+
status: statusCheck.value,
|
|
305
|
+
timestamp: currentTimestamp(),
|
|
306
|
+
}, null, 2));
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Create git commit with standard format
|
|
311
|
+
*/
|
|
312
|
+
commit: (args) => {
|
|
313
|
+
const message = args[0];
|
|
314
|
+
const filesArg = args.indexOf('--files');
|
|
315
|
+
let files = [];
|
|
316
|
+
|
|
317
|
+
if (filesArg !== -1) {
|
|
318
|
+
files = args.slice(filesArg + 1);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Validate commit message
|
|
322
|
+
const msgCheck = validators.isNonEmptyString(message, 'Commit message');
|
|
323
|
+
validateArgs([msgCheck]);
|
|
324
|
+
|
|
325
|
+
console.log(formatInfo(`Commit: ${msgCheck.value.substring(0, 50)}${msgCheck.value.length > 50 ? '...' : ''}`));
|
|
326
|
+
console.log(JSON.stringify({
|
|
327
|
+
command: 'git',
|
|
328
|
+
args: ['commit', '-m', msgCheck.value],
|
|
329
|
+
files: files,
|
|
330
|
+
}, null, 2));
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Calculate progress
|
|
335
|
+
*/
|
|
336
|
+
progress: (args) => {
|
|
337
|
+
const projectCheck = validators.requireProjectRoot();
|
|
338
|
+
validateArgs([projectCheck]);
|
|
339
|
+
const projectRoot = projectCheck.value;
|
|
340
|
+
|
|
341
|
+
const phasesDir = path.join(projectRoot, VIEPILOT_DIR, 'phases');
|
|
342
|
+
if (!fs.existsSync(phasesDir)) {
|
|
343
|
+
console.log(formatWarning('No phases found'));
|
|
344
|
+
console.log(JSON.stringify({ phases: [], overall: 0 }));
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const phases = fs.readdirSync(phasesDir)
|
|
349
|
+
.filter(d => fs.statSync(path.join(phasesDir, d)).isDirectory())
|
|
350
|
+
.sort()
|
|
351
|
+
.map(d => {
|
|
352
|
+
const statePath = path.join(phasesDir, d, 'PHASE-STATE.md');
|
|
353
|
+
const specPath = path.join(phasesDir, d, 'SPEC.md');
|
|
354
|
+
const tasksDir = path.join(phasesDir, d, 'tasks');
|
|
355
|
+
|
|
356
|
+
let taskCount = 0;
|
|
357
|
+
let completedCount = 0;
|
|
358
|
+
|
|
359
|
+
// Count tasks from SPEC.md table (more reliable)
|
|
360
|
+
if (fs.existsSync(specPath)) {
|
|
361
|
+
const content = readMarkdown(specPath);
|
|
362
|
+
const taskMatches = content.match(/\|\s*\d+\.\d+\s*\|/g);
|
|
363
|
+
taskCount = taskMatches ? taskMatches.length : 0;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Fallback to tasks directory
|
|
367
|
+
if (taskCount === 0 && fs.existsSync(tasksDir)) {
|
|
368
|
+
taskCount = fs.readdirSync(tasksDir).filter(f => f.endsWith('.md')).length;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Parse PHASE-STATE.md for completed tasks
|
|
372
|
+
if (fs.existsSync(statePath)) {
|
|
373
|
+
const content = readMarkdown(statePath);
|
|
374
|
+
const doneMatches = content.match(/✅\s*(Done|done|DONE)/gi);
|
|
375
|
+
completedCount = doneMatches ? doneMatches.length : 0;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const progress = taskCount > 0 ? Math.round((completedCount / taskCount) * 100) : 0;
|
|
379
|
+
const progressBar = createProgressBar(progress);
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
name: d,
|
|
383
|
+
tasks: taskCount,
|
|
384
|
+
completed: completedCount,
|
|
385
|
+
progress,
|
|
386
|
+
progressBar,
|
|
387
|
+
};
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const totalTasks = phases.reduce((sum, p) => sum + p.tasks, 0);
|
|
391
|
+
const totalCompleted = phases.reduce((sum, p) => sum + p.completed, 0);
|
|
392
|
+
const overall = totalTasks > 0 ? Math.round((totalCompleted / totalTasks) * 100) : 0;
|
|
393
|
+
|
|
394
|
+
// Display formatted progress
|
|
395
|
+
console.log(`\n${colors.bold}Project Progress${colors.reset}\n`);
|
|
396
|
+
phases.forEach(p => {
|
|
397
|
+
const status = p.progress === 100 ? colors.green + '✅' : p.progress > 0 ? colors.yellow + '🔄' : colors.gray + '⏳';
|
|
398
|
+
console.log(` ${status}${colors.reset} ${p.name.padEnd(30)} ${p.progressBar} ${String(p.progress).padStart(3)}%`);
|
|
399
|
+
});
|
|
400
|
+
console.log(` ${'─'.repeat(55)}`);
|
|
401
|
+
console.log(` ${colors.bold}Overall${colors.reset}${' '.repeat(24)} ${createProgressBar(overall)} ${String(overall).padStart(3)}%\n`);
|
|
402
|
+
|
|
403
|
+
console.log(JSON.stringify({
|
|
404
|
+
phases: phases.map(p => ({ name: p.name, tasks: p.tasks, completed: p.completed, progress: p.progress })),
|
|
405
|
+
total_tasks: totalTasks,
|
|
406
|
+
total_completed: totalCompleted,
|
|
407
|
+
overall,
|
|
408
|
+
}, null, 2));
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Version management
|
|
413
|
+
*/
|
|
414
|
+
version: (args) => {
|
|
415
|
+
const action = args[0] || 'get';
|
|
416
|
+
const projectCheck = validators.requireProjectRoot();
|
|
417
|
+
validateArgs([projectCheck]);
|
|
418
|
+
const projectRoot = projectCheck.value;
|
|
419
|
+
|
|
420
|
+
const trackerPath = path.join(projectRoot, VIEPILOT_DIR, 'TRACKER.md');
|
|
421
|
+
const content = readMarkdown(trackerPath);
|
|
422
|
+
|
|
423
|
+
if (!content) {
|
|
424
|
+
console.error(formatError('Cannot read TRACKER.md', 'File may be corrupted or missing'));
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Extract current version
|
|
429
|
+
const versionMatch = content.match(/```\n([\d.]+[-\w.]*)\n```/);
|
|
430
|
+
const currentVersion = versionMatch ? versionMatch[1] : '0.0.0';
|
|
431
|
+
|
|
432
|
+
if (action === 'get') {
|
|
433
|
+
console.log(formatInfo(`Current version: ${colors.bold}${currentVersion}${colors.reset}`));
|
|
434
|
+
console.log(JSON.stringify({ version: currentVersion }));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (action === 'bump') {
|
|
439
|
+
const type = args[1] || 'patch';
|
|
440
|
+
const typeCheck = validators.isValidBumpType(type);
|
|
441
|
+
validateArgs([typeCheck]);
|
|
442
|
+
|
|
443
|
+
const parts = currentVersion.replace(/-.*/, '').split('.').map(Number);
|
|
444
|
+
|
|
445
|
+
switch (typeCheck.value) {
|
|
446
|
+
case 'major':
|
|
447
|
+
parts[0]++;
|
|
448
|
+
parts[1] = 0;
|
|
449
|
+
parts[2] = 0;
|
|
450
|
+
break;
|
|
451
|
+
case 'minor':
|
|
452
|
+
parts[1]++;
|
|
453
|
+
parts[2] = 0;
|
|
454
|
+
break;
|
|
455
|
+
case 'patch':
|
|
456
|
+
parts[2]++;
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const newVersion = parts.join('.');
|
|
461
|
+
console.log(formatSuccess(`Version bump: ${currentVersion} → ${colors.bold}${newVersion}${colors.reset} (${typeCheck.value})`));
|
|
462
|
+
console.log(JSON.stringify({
|
|
463
|
+
old_version: currentVersion,
|
|
464
|
+
new_version: newVersion,
|
|
465
|
+
bump_type: typeCheck.value,
|
|
466
|
+
}, null, 2));
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Unknown action
|
|
471
|
+
console.error(formatError(`Unknown action: "${action}"`, 'Valid actions: get, bump'));
|
|
472
|
+
process.exit(1);
|
|
473
|
+
},
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Reset task or phase state (destructive - requires confirmation)
|
|
477
|
+
*/
|
|
478
|
+
reset: async (args) => {
|
|
479
|
+
const target = args[0];
|
|
480
|
+
const force = args.includes('--force') || args.includes('-f');
|
|
481
|
+
|
|
482
|
+
if (!target) {
|
|
483
|
+
console.error(formatError('Reset target required', 'Usage: vp-tools reset <task|phase|all> [--force]'));
|
|
484
|
+
process.exit(1);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const validTargets = ['task', 'phase', 'all'];
|
|
488
|
+
if (!validTargets.includes(target)) {
|
|
489
|
+
console.error(formatError(`Invalid reset target: "${target}"`, `Valid targets: ${validTargets.join(', ')}`));
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const projectCheck = validators.requireProjectRoot();
|
|
494
|
+
validateArgs([projectCheck]);
|
|
495
|
+
|
|
496
|
+
const warnings = {
|
|
497
|
+
task: 'This will reset the current task status to not_started',
|
|
498
|
+
phase: 'This will reset ALL tasks in the current phase',
|
|
499
|
+
all: 'This will reset ALL phases and tasks to not_started',
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
console.log(formatWarning(warnings[target]));
|
|
503
|
+
|
|
504
|
+
if (!force) {
|
|
505
|
+
const confirmed = await confirm(`Are you sure you want to reset ${target}?`, false);
|
|
506
|
+
if (!confirmed) {
|
|
507
|
+
console.log(formatInfo('Reset cancelled'));
|
|
508
|
+
process.exit(0);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
console.log(formatSuccess(`Reset ${target} completed`));
|
|
513
|
+
console.log(JSON.stringify({ reset: target, timestamp: currentTimestamp() }, null, 2));
|
|
514
|
+
},
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Clean generated files (destructive - requires confirmation)
|
|
518
|
+
*/
|
|
519
|
+
clean: async (args) => {
|
|
520
|
+
const force = args.includes('--force') || args.includes('-f');
|
|
521
|
+
const dryRun = args.includes('--dry-run');
|
|
522
|
+
|
|
523
|
+
const projectCheck = validators.requireProjectRoot();
|
|
524
|
+
validateArgs([projectCheck]);
|
|
525
|
+
const projectRoot = projectCheck.value;
|
|
526
|
+
|
|
527
|
+
const filesToClean = [
|
|
528
|
+
path.join(projectRoot, VIEPILOT_DIR, 'HANDOFF.json'),
|
|
529
|
+
];
|
|
530
|
+
|
|
531
|
+
console.log(formatWarning('Files to be removed:'));
|
|
532
|
+
filesToClean.forEach(f => {
|
|
533
|
+
if (fs.existsSync(f)) {
|
|
534
|
+
console.log(` ${colors.red}✖${colors.reset} ${path.relative(projectRoot, f)}`);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
if (dryRun) {
|
|
539
|
+
console.log(formatInfo('Dry run - no files removed'));
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (!force) {
|
|
544
|
+
const confirmed = await confirm('Delete these files?', false);
|
|
545
|
+
if (!confirmed) {
|
|
546
|
+
console.log(formatInfo('Clean cancelled'));
|
|
547
|
+
process.exit(0);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
let removed = 0;
|
|
552
|
+
filesToClean.forEach(f => {
|
|
553
|
+
if (fs.existsSync(f)) {
|
|
554
|
+
fs.unlinkSync(f);
|
|
555
|
+
removed++;
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
console.log(formatSuccess(`Cleaned ${removed} file(s)`));
|
|
560
|
+
},
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* List and manage checkpoints (git tags)
|
|
564
|
+
*/
|
|
565
|
+
checkpoints: (args) => {
|
|
566
|
+
const projectCheck = validators.requireProjectRoot();
|
|
567
|
+
validateArgs([projectCheck]);
|
|
568
|
+
const projectRoot = projectCheck.value;
|
|
569
|
+
|
|
570
|
+
const { execSync } = require('child_process');
|
|
571
|
+
|
|
572
|
+
try {
|
|
573
|
+
const tags = execSync('git tag -l "vp-*" --sort=-creatordate', {
|
|
574
|
+
cwd: projectRoot,
|
|
575
|
+
encoding: 'utf8'
|
|
576
|
+
}).trim().split('\n').filter(t => t);
|
|
577
|
+
|
|
578
|
+
if (tags.length === 0) {
|
|
579
|
+
console.log(formatWarning('No checkpoints found'));
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
console.log(`\n${colors.bold}ViePilot Checkpoints${colors.reset}\n`);
|
|
584
|
+
console.log(` ${colors.gray}TAG${' '.repeat(25)}COMMIT DATE${colors.reset}`);
|
|
585
|
+
console.log(` ${'─'.repeat(55)}`);
|
|
586
|
+
|
|
587
|
+
tags.slice(0, 20).forEach(tag => {
|
|
588
|
+
try {
|
|
589
|
+
const info = execSync(`git log -1 --format="%h %ci" ${tag}`, {
|
|
590
|
+
cwd: projectRoot,
|
|
591
|
+
encoding: 'utf8'
|
|
592
|
+
}).trim();
|
|
593
|
+
const [hash, ...dateParts] = info.split(' ');
|
|
594
|
+
const date = dateParts.slice(0, 2).join(' ');
|
|
595
|
+
|
|
596
|
+
let icon = '📌';
|
|
597
|
+
if (tag.includes('-complete')) icon = '✅';
|
|
598
|
+
else if (tag.includes('-done')) icon = '✔️';
|
|
599
|
+
else if (tag.includes('-backup')) icon = '💾';
|
|
600
|
+
|
|
601
|
+
console.log(` ${icon} ${tag.padEnd(26)} ${hash} ${date}`);
|
|
602
|
+
} catch (e) {
|
|
603
|
+
console.log(` 📌 ${tag.padEnd(26)} ${colors.gray}(no info)${colors.reset}`);
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
if (tags.length > 20) {
|
|
608
|
+
console.log(`\n ${colors.gray}... and ${tags.length - 20} more${colors.reset}`);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
console.log(`\n Total: ${tags.length} checkpoints\n`);
|
|
612
|
+
|
|
613
|
+
} catch (error) {
|
|
614
|
+
console.error(formatError('Failed to list checkpoints', error.message));
|
|
615
|
+
process.exit(1);
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Check for potential conflicts
|
|
621
|
+
*/
|
|
622
|
+
conflicts: (args) => {
|
|
623
|
+
const projectCheck = validators.requireProjectRoot();
|
|
624
|
+
validateArgs([projectCheck]);
|
|
625
|
+
const projectRoot = projectCheck.value;
|
|
626
|
+
|
|
627
|
+
const { execSync } = require('child_process');
|
|
628
|
+
|
|
629
|
+
try {
|
|
630
|
+
// Check for uncommitted changes
|
|
631
|
+
const status = execSync('git status --porcelain', {
|
|
632
|
+
cwd: projectRoot,
|
|
633
|
+
encoding: 'utf8'
|
|
634
|
+
}).trim();
|
|
635
|
+
|
|
636
|
+
const changes = status.split('\n').filter(l => l);
|
|
637
|
+
|
|
638
|
+
if (changes.length === 0) {
|
|
639
|
+
console.log(formatSuccess('No conflicts detected - working directory clean'));
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
console.log(`\n${colors.bold}Potential Conflicts${colors.reset}\n`);
|
|
644
|
+
|
|
645
|
+
const conflicts = {
|
|
646
|
+
modified: [],
|
|
647
|
+
untracked: [],
|
|
648
|
+
deleted: [],
|
|
649
|
+
staged: [],
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
changes.forEach(line => {
|
|
653
|
+
const [status, ...fileParts] = line.trim().split(/\s+/);
|
|
654
|
+
const file = fileParts.join(' ');
|
|
655
|
+
|
|
656
|
+
if (status.includes('M')) conflicts.modified.push(file);
|
|
657
|
+
else if (status === '??') conflicts.untracked.push(file);
|
|
658
|
+
else if (status.includes('D')) conflicts.deleted.push(file);
|
|
659
|
+
else if (status.includes('A') || status.includes('R')) conflicts.staged.push(file);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
if (conflicts.modified.length > 0) {
|
|
663
|
+
console.log(` ${colors.yellow}Modified files:${colors.reset}`);
|
|
664
|
+
conflicts.modified.forEach(f => console.log(` ${colors.yellow}M${colors.reset} ${f}`));
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (conflicts.untracked.length > 0) {
|
|
668
|
+
console.log(` ${colors.blue}Untracked files:${colors.reset}`);
|
|
669
|
+
conflicts.untracked.forEach(f => console.log(` ${colors.blue}?${colors.reset} ${f}`));
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (conflicts.deleted.length > 0) {
|
|
673
|
+
console.log(` ${colors.red}Deleted files:${colors.reset}`);
|
|
674
|
+
conflicts.deleted.forEach(f => console.log(` ${colors.red}D${colors.reset} ${f}`));
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (conflicts.staged.length > 0) {
|
|
678
|
+
console.log(` ${colors.green}Staged files:${colors.reset}`);
|
|
679
|
+
conflicts.staged.forEach(f => console.log(` ${colors.green}A${colors.reset} ${f}`));
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
console.log(`\n ${colors.gray}Total: ${changes.length} file(s) with changes${colors.reset}\n`);
|
|
683
|
+
|
|
684
|
+
console.log(formatWarning('Resolve these before running /vp-auto or /vp-rollback'));
|
|
685
|
+
|
|
686
|
+
} catch (error) {
|
|
687
|
+
console.error(formatError('Failed to check conflicts', error.message));
|
|
688
|
+
process.exit(1);
|
|
689
|
+
}
|
|
690
|
+
},
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Save current state for precise resume
|
|
694
|
+
*/
|
|
695
|
+
'save-state': (args) => {
|
|
696
|
+
const projectCheck = validators.requireProjectRoot();
|
|
697
|
+
validateArgs([projectCheck]);
|
|
698
|
+
const projectRoot = projectCheck.value;
|
|
699
|
+
|
|
700
|
+
const handoffPath = path.join(projectRoot, VIEPILOT_DIR, 'HANDOFF.json');
|
|
701
|
+
const trackerPath = path.join(projectRoot, VIEPILOT_DIR, 'TRACKER.md');
|
|
702
|
+
|
|
703
|
+
// Read current state
|
|
704
|
+
let handoff = {};
|
|
705
|
+
if (fs.existsSync(handoffPath)) {
|
|
706
|
+
handoff = readJson(handoffPath);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Get current git info
|
|
710
|
+
const { execSync } = require('child_process');
|
|
711
|
+
let gitInfo = {};
|
|
712
|
+
try {
|
|
713
|
+
gitInfo.head = execSync('git rev-parse HEAD', { cwd: projectRoot, encoding: 'utf8' }).trim();
|
|
714
|
+
gitInfo.branch = execSync('git branch --show-current', { cwd: projectRoot, encoding: 'utf8' }).trim();
|
|
715
|
+
gitInfo.status = execSync('git status --porcelain', { cwd: projectRoot, encoding: 'utf8' }).trim();
|
|
716
|
+
} catch (e) {
|
|
717
|
+
gitInfo.error = e.message;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Update handoff with precise state
|
|
721
|
+
handoff.updated_at = currentTimestamp();
|
|
722
|
+
handoff.git = gitInfo;
|
|
723
|
+
handoff.resume_point = {
|
|
724
|
+
timestamp: currentTimestamp(),
|
|
725
|
+
cwd: process.cwd(),
|
|
726
|
+
node_version: process.version,
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
// Save
|
|
730
|
+
writeJson(handoffPath, handoff);
|
|
731
|
+
|
|
732
|
+
console.log(formatSuccess('State saved for resume'));
|
|
733
|
+
console.log(JSON.stringify({
|
|
734
|
+
saved_at: handoff.updated_at,
|
|
735
|
+
phase: handoff.phase,
|
|
736
|
+
task: handoff.task,
|
|
737
|
+
git_head: gitInfo.head?.substring(0, 7),
|
|
738
|
+
}, null, 2));
|
|
739
|
+
},
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Help
|
|
743
|
+
*/
|
|
744
|
+
help: (args) => {
|
|
745
|
+
const command = args[0];
|
|
746
|
+
|
|
747
|
+
const commandHelp = {
|
|
748
|
+
init: {
|
|
749
|
+
usage: 'vp-tools init',
|
|
750
|
+
description: 'Get project initialization state and verify .viepilot/ exists',
|
|
751
|
+
examples: ['vp-tools init'],
|
|
752
|
+
},
|
|
753
|
+
'current-timestamp': {
|
|
754
|
+
usage: 'vp-tools current-timestamp [format] [--raw]',
|
|
755
|
+
description: 'Get current timestamp in specified format',
|
|
756
|
+
options: [
|
|
757
|
+
'format: iso (default), date, full',
|
|
758
|
+
'--raw: Output only the timestamp string',
|
|
759
|
+
],
|
|
760
|
+
examples: [
|
|
761
|
+
'vp-tools current-timestamp',
|
|
762
|
+
'vp-tools current-timestamp full --raw',
|
|
763
|
+
],
|
|
764
|
+
},
|
|
765
|
+
'phase-info': {
|
|
766
|
+
usage: 'vp-tools phase-info <phase_number>',
|
|
767
|
+
description: 'Get information about a specific phase',
|
|
768
|
+
examples: [
|
|
769
|
+
'vp-tools phase-info 1',
|
|
770
|
+
'vp-tools phase-info 2',
|
|
771
|
+
],
|
|
772
|
+
},
|
|
773
|
+
'task-status': {
|
|
774
|
+
usage: 'vp-tools task-status <phase> <task> <status>',
|
|
775
|
+
description: 'Update the status of a task',
|
|
776
|
+
options: [
|
|
777
|
+
'status: not_started, in_progress, done, skipped, blocked',
|
|
778
|
+
],
|
|
779
|
+
examples: [
|
|
780
|
+
'vp-tools task-status 1 1 in_progress',
|
|
781
|
+
'vp-tools task-status 1 2 done',
|
|
782
|
+
],
|
|
783
|
+
},
|
|
784
|
+
commit: {
|
|
785
|
+
usage: 'vp-tools commit "<message>" [--files <file1> <file2>...]',
|
|
786
|
+
description: 'Generate git commit command with standard format',
|
|
787
|
+
examples: [
|
|
788
|
+
'vp-tools commit "feat(cli): add validation"',
|
|
789
|
+
'vp-tools commit "fix(core): resolve bug" --files src/index.js',
|
|
790
|
+
],
|
|
791
|
+
},
|
|
792
|
+
progress: {
|
|
793
|
+
usage: 'vp-tools progress',
|
|
794
|
+
description: 'Calculate and display overall project progress',
|
|
795
|
+
examples: ['vp-tools progress'],
|
|
796
|
+
},
|
|
797
|
+
version: {
|
|
798
|
+
usage: 'vp-tools version [get|bump] [major|minor|patch]',
|
|
799
|
+
description: 'Version management - get current or bump version',
|
|
800
|
+
examples: [
|
|
801
|
+
'vp-tools version',
|
|
802
|
+
'vp-tools version get',
|
|
803
|
+
'vp-tools version bump minor',
|
|
804
|
+
],
|
|
805
|
+
},
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
if (command && commandHelp[command]) {
|
|
809
|
+
const help = commandHelp[command];
|
|
810
|
+
console.log(`\n${colors.bold}${command}${colors.reset}\n`);
|
|
811
|
+
console.log(` ${help.description}\n`);
|
|
812
|
+
console.log(` ${colors.cyan}Usage:${colors.reset} ${help.usage}\n`);
|
|
813
|
+
if (help.options) {
|
|
814
|
+
console.log(` ${colors.cyan}Options:${colors.reset}`);
|
|
815
|
+
help.options.forEach(opt => console.log(` ${opt}`));
|
|
816
|
+
console.log();
|
|
817
|
+
}
|
|
818
|
+
console.log(` ${colors.cyan}Examples:${colors.reset}`);
|
|
819
|
+
help.examples.forEach(ex => console.log(` ${colors.gray}$${colors.reset} ${ex}`));
|
|
820
|
+
console.log();
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
console.log(`
|
|
825
|
+
${colors.bold}ViePilot CLI Tools${colors.reset}
|
|
826
|
+
${colors.gray}Helper utilities for state management and workflow operations${colors.reset}
|
|
827
|
+
|
|
828
|
+
${colors.cyan}Usage:${colors.reset}
|
|
829
|
+
vp-tools <command> [options]
|
|
830
|
+
|
|
831
|
+
${colors.cyan}Commands:${colors.reset}
|
|
832
|
+
${colors.bold}init${colors.reset} Get project initialization state
|
|
833
|
+
${colors.bold}current-timestamp${colors.reset} Get current timestamp (iso|date|full) [--raw]
|
|
834
|
+
${colors.bold}phase-info${colors.reset} <N> Get phase N information
|
|
835
|
+
${colors.bold}task-status${colors.reset} <P> <T> <S> Update task T in phase P to status S
|
|
836
|
+
${colors.bold}commit${colors.reset} <msg> [--files] Create git commit command
|
|
837
|
+
${colors.bold}progress${colors.reset} Calculate overall progress
|
|
838
|
+
${colors.bold}version${colors.reset} [get|bump] Version management
|
|
839
|
+
${colors.bold}reset${colors.reset} <target> [-f] Reset task/phase/all state (interactive)
|
|
840
|
+
${colors.bold}clean${colors.reset} [-f] [--dry-run] Clean generated files (interactive)
|
|
841
|
+
${colors.bold}checkpoints${colors.reset} List all ViePilot checkpoints (git tags)
|
|
842
|
+
${colors.bold}conflicts${colors.reset} Check for potential conflicts
|
|
843
|
+
${colors.bold}save-state${colors.reset} Save current state for precise resume
|
|
844
|
+
${colors.bold}help${colors.reset} [command] Show help (optionally for specific command)
|
|
845
|
+
|
|
846
|
+
${colors.cyan}Examples:${colors.reset}
|
|
847
|
+
${colors.gray}$${colors.reset} vp-tools init
|
|
848
|
+
${colors.gray}$${colors.reset} vp-tools phase-info 1
|
|
849
|
+
${colors.gray}$${colors.reset} vp-tools progress
|
|
850
|
+
${colors.gray}$${colors.reset} vp-tools version bump minor
|
|
851
|
+
${colors.gray}$${colors.reset} vp-tools help phase-info
|
|
852
|
+
|
|
853
|
+
${colors.gray}Run 'vp-tools help <command>' for detailed help on a specific command.${colors.reset}
|
|
854
|
+
`);
|
|
855
|
+
},
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
// Helper function for progress bars
|
|
859
|
+
function createProgressBar(percent, width = 10) {
|
|
860
|
+
const filled = Math.round(percent / (100 / width));
|
|
861
|
+
const empty = width - filled;
|
|
862
|
+
return `[${colors.green}${'█'.repeat(filled)}${colors.gray}${'░'.repeat(empty)}${colors.reset}]`;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// ============================================================================
|
|
866
|
+
// Main
|
|
867
|
+
// ============================================================================
|
|
868
|
+
|
|
869
|
+
async function main() {
|
|
870
|
+
const args = process.argv.slice(2);
|
|
871
|
+
const command = args[0];
|
|
872
|
+
|
|
873
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
874
|
+
commands.help(args.slice(1));
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (commands[command]) {
|
|
879
|
+
try {
|
|
880
|
+
const result = commands[command](args.slice(1));
|
|
881
|
+
if (result instanceof Promise) {
|
|
882
|
+
await result;
|
|
883
|
+
}
|
|
884
|
+
} catch (error) {
|
|
885
|
+
console.error(formatError(`Command failed: ${error.message}`));
|
|
886
|
+
process.exit(1);
|
|
887
|
+
}
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Suggest similar commands
|
|
892
|
+
const available = Object.keys(commands);
|
|
893
|
+
const similar = available.filter(cmd =>
|
|
894
|
+
cmd.includes(command) || command.includes(cmd) ||
|
|
895
|
+
levenshteinDistance(cmd, command) <= 2
|
|
896
|
+
);
|
|
897
|
+
|
|
898
|
+
let hint = `Available commands: ${available.join(', ')}`;
|
|
899
|
+
if (similar.length > 0 && similar.length < available.length) {
|
|
900
|
+
hint = `Did you mean: ${similar.join(', ')}?`;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
console.error(formatError(`Unknown command: "${command}"`, hint));
|
|
904
|
+
process.exit(1);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
if (require.main === module) {
|
|
908
|
+
main().catch((err) => {
|
|
909
|
+
console.error(formatError(`Unexpected error: ${err.message}`));
|
|
910
|
+
process.exit(1);
|
|
911
|
+
});
|
|
912
|
+
}
|