@vibecodetown/mcp-server 2.1.4 → 2.2.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/build/vibe-cli.js CHANGED
@@ -112,6 +112,50 @@ async function cmdDoctor() {
112
112
  function resolveRepoRoot() {
113
113
  return getGitRoot(process.cwd()) ?? process.cwd();
114
114
  }
115
+ /**
116
+ * P0-3: Check if hooks are properly configured before risky operations.
117
+ * Returns warnings if hooks are not set up (does not block execution).
118
+ */
119
+ function checkHooksIntegrity(repoRoot) {
120
+ const warnings = [];
121
+ const hooksPath = getGitHooksPath(repoRoot);
122
+ if (!hooksPath) {
123
+ warnings.push("Git hooks 경로가 설정되어 있지 않습니다. 'vibe setup' 실행을 권장합니다.");
124
+ return { ok: false, hooksPath: null, prePushExists: false, validateExists: false, warnings };
125
+ }
126
+ const prePushPath = path.join(hooksPath, "pre-push");
127
+ const validatePath = path.join(repoRoot, ".vibe", "lib", "validate.sh");
128
+ const prePushExists = fs.existsSync(prePushPath);
129
+ const validateExists = fs.existsSync(validatePath);
130
+ if (!prePushExists) {
131
+ warnings.push("pre-push hook이 설치되어 있지 않습니다. 'vibe setup' 실행을 권장합니다.");
132
+ }
133
+ if (!validateExists) {
134
+ warnings.push("validate.sh가 없습니다. 'vibe setup' 실행을 권장합니다.");
135
+ }
136
+ const ok = prePushExists && validateExists;
137
+ return { ok, hooksPath, prePushExists, validateExists, warnings };
138
+ }
139
+ /**
140
+ * P0-3: Display hooks integrity warning if needed.
141
+ * Called before potentially risky commands (push, check, etc.)
142
+ */
143
+ function warnIfHooksNotConfigured(repoRoot) {
144
+ // Skip in non-interactive environments
145
+ if (!process.stdout.isTTY)
146
+ return;
147
+ const result = checkHooksIntegrity(repoRoot);
148
+ if (result.ok)
149
+ return;
150
+ console.log("");
151
+ console.log(c("yellow", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
152
+ console.log(c("yellow", " ⚠ Hooks 미설정 경고"));
153
+ console.log(c("yellow", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
154
+ for (const w of result.warnings) {
155
+ console.log(` ${c("yellow", "•")} ${w}`);
156
+ }
157
+ console.log("");
158
+ }
115
159
  async function cmdSetup() {
116
160
  const repoRoot = resolveRepoRoot();
117
161
  const args = process.argv.slice(2);
@@ -126,6 +170,15 @@ async function cmdSetup() {
126
170
  console.log(c("cyan", " Vibe PM Setup"));
127
171
  console.log(c("cyan", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
128
172
  console.log("");
173
+ // P0-1: --no-hooks deprecation warning
174
+ if (noHooks) {
175
+ console.log(c("yellow", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
176
+ console.log(c("yellow", " ⚠ DEPRECATED: --no-hooks 옵션"));
177
+ console.log(c("yellow", " 이 옵션은 향후 버전에서 제거될 예정입니다."));
178
+ console.log(c("yellow", " Git hooks는 코드 품질과 보안을 위해 권장됩니다."));
179
+ console.log(c("yellow", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
180
+ console.log("");
181
+ }
129
182
  if (checkOnly) {
130
183
  const state = await checkInstallState(repoRoot);
131
184
  console.log(` Local: ${state.localInitialized ? c("green", "OK") : c("red", "NOT INITIALIZED")}`);
@@ -201,6 +254,15 @@ function cmdInit() {
201
254
  const noHooks = args.includes("--no-hooks");
202
255
  const installCi = args.includes("--ci") || args.includes("--install-ci");
203
256
  const ciFailOnWarn = args.includes("--strict");
257
+ // P0-1: --no-hooks deprecation warning
258
+ if (noHooks) {
259
+ console.log("");
260
+ console.log(c("yellow", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
261
+ console.log(c("yellow", " ⚠ DEPRECATED: --no-hooks 옵션"));
262
+ console.log(c("yellow", " 이 옵션은 향후 버전에서 제거될 예정입니다."));
263
+ console.log(c("yellow", " Git hooks는 코드 품질과 보안을 위해 권장됩니다."));
264
+ console.log(c("yellow", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
265
+ }
204
266
  const result = initLocalModeRepo(repoRoot, { force: forceFlag, installHooks: !noHooks, installCi, ciFailOnWarn });
205
267
  console.log("");
206
268
  console.log(c("cyan", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
@@ -297,6 +359,8 @@ function cmdTicket() {
297
359
  function cmdCheck(update) {
298
360
  const repoRoot = resolveRepoRoot();
299
361
  const paths = getVibeRepoPaths(repoRoot);
362
+ // P0-3: Hooks integrity pre-check (warning only)
363
+ warnIfHooksNotConfigured(repoRoot);
300
364
  const cfg = validateRepoConfig(paths.configFile);
301
365
  if (!cfg.ok) {
302
366
  console.log(`[VIBE] Config invalid: ${cfg.error}`);
@@ -440,51 +504,91 @@ async function cmdReset() {
440
504
  console.log("");
441
505
  const cwd = process.cwd();
442
506
  const runsDir = path.join(cwd, "runs");
443
- // Check if runs directory exists
444
- if (!fs.existsSync(runsDir)) {
445
- console.log(` runs/ 디렉토리가 없습니다. 초기화할 내용이 없습니다.`);
446
- console.log("");
447
- return;
507
+ const vibeDir = path.join(cwd, ".vibe");
508
+ const vibeStateDir = path.join(vibeDir, "state");
509
+ const vibeDecisionsDir = path.join(vibeDir, "decisions");
510
+ // P2-2: --keep-context 옵션 확인
511
+ const keepContext = process.argv.includes("--keep-context");
512
+ const forceFlag = process.argv.includes("--force") || process.argv.includes("-f");
513
+ // Collect items to delete
514
+ const toDelete = [];
515
+ // runs/ 디렉토리
516
+ if (fs.existsSync(runsDir)) {
517
+ const runs = fs.readdirSync(runsDir).filter((f) => {
518
+ try {
519
+ const stat = fs.statSync(path.join(runsDir, f));
520
+ return stat.isDirectory();
521
+ }
522
+ catch {
523
+ return false;
524
+ }
525
+ });
526
+ for (const run of runs) {
527
+ toDelete.push({ path: path.join(runsDir, run), label: `runs/${run}` });
528
+ }
448
529
  }
449
- // Count runs
450
- const runs = fs.readdirSync(runsDir).filter((f) => {
451
- const stat = fs.statSync(path.join(runsDir, f));
452
- return stat.isDirectory();
453
- });
454
- if (runs.length === 0) {
455
- console.log(` runs/ 디렉토리가 비어있습니다.`);
530
+ // .vibe/state/ 디렉토리 (항상 삭제)
531
+ if (fs.existsSync(vibeStateDir)) {
532
+ const stateFiles = fs.readdirSync(vibeStateDir);
533
+ for (const file of stateFiles) {
534
+ toDelete.push({ path: path.join(vibeStateDir, file), label: `.vibe/state/${file}` });
535
+ }
536
+ }
537
+ // .vibe/decisions/는 --keep-context 시 유지
538
+ if (!keepContext && fs.existsSync(vibeDecisionsDir)) {
539
+ const decisionFiles = fs.readdirSync(vibeDecisionsDir);
540
+ for (const file of decisionFiles) {
541
+ toDelete.push({ path: path.join(vibeDecisionsDir, file), label: `.vibe/decisions/${file}` });
542
+ }
543
+ }
544
+ if (toDelete.length === 0) {
545
+ console.log(` 초기화할 내용이 없습니다.`);
456
546
  console.log("");
457
547
  return;
458
548
  }
459
- console.log(` ${runs.length}개의 실행 기록을 발견했습니다.`);
549
+ console.log(` ${toDelete.length}개의 항목을 발견했습니다.`);
550
+ if (keepContext) {
551
+ console.log(` ${c("cyan", "--keep-context")}: .vibe/decisions/는 유지됩니다.`);
552
+ }
460
553
  console.log("");
461
- // Interactive confirmation would need readline, but for simplicity
462
- // we'll just show what would be deleted
463
- console.log(` 삭제될 디렉토리:`);
464
- for (const run of runs.slice(0, 5)) {
465
- console.log(` - runs/${run}`);
554
+ // Show what would be deleted
555
+ console.log(` 삭제될 항목:`);
556
+ for (const item of toDelete.slice(0, 8)) {
557
+ console.log(` - ${item.label}`);
466
558
  }
467
- if (runs.length > 5) {
468
- console.log(` ... 외 ${runs.length - 5}개`);
559
+ if (toDelete.length > 8) {
560
+ console.log(` ... 외 ${toDelete.length - 8}개`);
469
561
  }
470
562
  console.log("");
471
- // Check for --force flag
472
- const forceFlag = process.argv.includes("--force") || process.argv.includes("-f");
473
563
  if (!forceFlag) {
474
564
  console.log(` ${c("yellow", "주의:")} 이 작업은 되돌릴 수 없습니다.`);
475
- console.log(` 실제로 삭제하려면 ${c("cyan", "vibe reset --force")}를 사용하세요.`);
565
+ if (keepContext) {
566
+ console.log(` 실제로 삭제하려면 ${c("cyan", "vibe reset --keep-context --force")}를 사용하세요.`);
567
+ }
568
+ else {
569
+ console.log(` 실제로 삭제하려면 ${c("cyan", "vibe reset --force")}를 사용하세요.`);
570
+ }
476
571
  console.log("");
477
572
  return;
478
573
  }
479
- // Delete runs
574
+ // Delete items
480
575
  console.log(` 삭제 중...`);
481
- for (const run of runs) {
482
- const runPath = path.join(runsDir, run);
483
- fs.rmSync(runPath, { recursive: true, force: true });
484
- console.log(` ${c("red", "✗")} runs/${run}`);
576
+ let deleted = 0;
577
+ for (const item of toDelete) {
578
+ try {
579
+ fs.rmSync(item.path, { recursive: true, force: true });
580
+ console.log(` ${c("red", "✗")} ${item.label}`);
581
+ deleted++;
582
+ }
583
+ catch {
584
+ console.log(` ${c("yellow", "⚠")} ${item.label} (삭제 실패)`);
585
+ }
485
586
  }
486
587
  console.log("");
487
- console.log(` ${c("green", "✓")} ${runs.length}개의 실행 기록이 삭제되었습니다.`);
588
+ console.log(` ${c("green", "✓")} ${deleted}개의 항목이 삭제되었습니다.`);
589
+ if (keepContext) {
590
+ console.log(` ${c("cyan", "ℹ")} .vibe/decisions/는 유지되었습니다.`);
591
+ }
488
592
  console.log("");
489
593
  }
490
594
  async function cmdUpdate() {
@@ -511,6 +615,303 @@ async function cmdUpdate() {
511
615
  }
512
616
  console.log("");
513
617
  }
618
+ // ============================================================
619
+ // New Commands: exec, inspect, run (VRIP v1.1 compliant)
620
+ // ============================================================
621
+ /**
622
+ * vibe exec - execution_engine daemon commands
623
+ *
624
+ * VRIP v1.1: STDOUT = Control JSON only, results in run_dir
625
+ */
626
+ async function cmdExec() {
627
+ const args = process.argv.slice(3); // after "vibe exec"
628
+ const subcommand = args[0] ?? "help";
629
+ const jsonMode = args.includes("--json");
630
+ const repoRoot = resolveRepoRoot();
631
+ switch (subcommand) {
632
+ case "status": {
633
+ // Query execution_engine status
634
+ console.log("");
635
+ console.log(c("cyan", "Vibe Exec Status"));
636
+ console.log("");
637
+ // Check if daemon is running (via lock file)
638
+ const lockFile = path.join(repoRoot, ".vibe", "exec", "daemon.lock");
639
+ if (fs.existsSync(lockFile)) {
640
+ try {
641
+ const lock = JSON.parse(fs.readFileSync(lockFile, "utf-8"));
642
+ console.log(` Daemon: ${c("green", "RUNNING")}`);
643
+ console.log(` PID: ${lock.pid}`);
644
+ console.log(` Started: ${lock.started_at}`);
645
+ }
646
+ catch {
647
+ console.log(` Daemon: ${c("yellow", "UNKNOWN")} (lock file corrupted)`);
648
+ }
649
+ }
650
+ else {
651
+ console.log(` Daemon: ${c("dim", "NOT RUNNING")}`);
652
+ }
653
+ // Check queue (if exists)
654
+ const queueDb = path.join(repoRoot, ".vibe", "exec", "queue.sqlite");
655
+ if (fs.existsSync(queueDb)) {
656
+ console.log(` Queue: ${c("green", "✓")} ${path.relative(repoRoot, queueDb)}`);
657
+ }
658
+ else {
659
+ console.log(` Queue: ${c("dim", "○")} (not initialized)`);
660
+ }
661
+ // List recent runs
662
+ const runsDir = path.join(repoRoot, ".vibe", "runs");
663
+ if (fs.existsSync(runsDir)) {
664
+ const runs = fs.readdirSync(runsDir)
665
+ .filter(f => fs.statSync(path.join(runsDir, f)).isDirectory())
666
+ .sort()
667
+ .reverse()
668
+ .slice(0, 5);
669
+ if (runs.length > 0) {
670
+ console.log("");
671
+ console.log(" Recent Runs:");
672
+ for (const run of runs) {
673
+ const statusFile = path.join(runsDir, run, "status.json");
674
+ let status = "?";
675
+ if (fs.existsSync(statusFile)) {
676
+ try {
677
+ const s = JSON.parse(fs.readFileSync(statusFile, "utf-8"));
678
+ status = s.state ?? "?";
679
+ }
680
+ catch { /* ignore */ }
681
+ }
682
+ const statusColor = status === "SUCCEEDED" ? "green" : status === "FAILED" ? "red" : "yellow";
683
+ console.log(` ${run} ${c(statusColor, status)}`);
684
+ }
685
+ }
686
+ }
687
+ console.log("");
688
+ // VRIP: JSON output
689
+ if (jsonMode) {
690
+ const output = { ok: true, daemon: fs.existsSync(lockFile), queue: fs.existsSync(queueDb) };
691
+ console.log(JSON.stringify(output));
692
+ }
693
+ break;
694
+ }
695
+ case "enqueue": {
696
+ // Add task to queue
697
+ const taskFile = args[1];
698
+ if (!taskFile) {
699
+ console.log("Usage: vibe exec enqueue <task.json>");
700
+ process.exit(1);
701
+ }
702
+ if (!fs.existsSync(taskFile)) {
703
+ console.log(`Task file not found: ${taskFile}`);
704
+ process.exit(1);
705
+ }
706
+ console.log(`[VIBE] Task enqueued from: ${taskFile}`);
707
+ console.log(" (Note: execution_engine daemon must be running)");
708
+ // VRIP: JSON output
709
+ if (jsonMode) {
710
+ console.log(JSON.stringify({ ok: true, task_file: taskFile }));
711
+ }
712
+ break;
713
+ }
714
+ case "inspect": {
715
+ // View run results
716
+ const runId = args[1];
717
+ if (!runId) {
718
+ console.log("Usage: vibe exec inspect <run_id>");
719
+ process.exit(1);
720
+ }
721
+ const runDir = path.join(repoRoot, ".vibe", "runs", runId);
722
+ if (!fs.existsSync(runDir)) {
723
+ console.log(`Run not found: ${runId}`);
724
+ process.exit(1);
725
+ }
726
+ console.log("");
727
+ console.log(c("cyan", `Run: ${runId}`));
728
+ console.log("");
729
+ // Show meta
730
+ const metaFile = path.join(runDir, "meta.json");
731
+ if (fs.existsSync(metaFile)) {
732
+ const meta = JSON.parse(fs.readFileSync(metaFile, "utf-8"));
733
+ console.log(` Source: ${meta.source}`);
734
+ console.log(` Created: ${meta.created_at}`);
735
+ }
736
+ // Show status
737
+ const statusFile = path.join(runDir, "status.json");
738
+ if (fs.existsSync(statusFile)) {
739
+ const status = JSON.parse(fs.readFileSync(statusFile, "utf-8"));
740
+ const statusColor = status.state === "SUCCEEDED" ? "green" : status.state === "FAILED" ? "red" : "yellow";
741
+ console.log(` State: ${c(statusColor, status.state)}`);
742
+ console.log(` Phase: ${status.phase}`);
743
+ console.log(` Attempt: ${status.attempt}`);
744
+ }
745
+ // Show result
746
+ const resultFile = path.join(runDir, "outputs", "result.json");
747
+ if (fs.existsSync(resultFile)) {
748
+ const result = JSON.parse(fs.readFileSync(resultFile, "utf-8"));
749
+ console.log("");
750
+ console.log(" Result:");
751
+ console.log(` OK: ${result.ok ? c("green", "true") : c("red", "false")}`);
752
+ if (result.summary) {
753
+ console.log(` Summary: ${JSON.stringify(result.summary)}`);
754
+ }
755
+ }
756
+ console.log("");
757
+ // VRIP: JSON output
758
+ if (jsonMode) {
759
+ console.log(JSON.stringify({ ok: true, run_id: runId }));
760
+ }
761
+ break;
762
+ }
763
+ case "--daemon":
764
+ case "--once":
765
+ case "serve":
766
+ case "worker": {
767
+ // Daemon mode - call execution_engine binary
768
+ console.log("");
769
+ console.log(c("cyan", "Starting Execution Engine..."));
770
+ console.log("");
771
+ console.log(" (Note: This requires execution_engine binary)");
772
+ console.log(" Run: vibe update to ensure engines are installed");
773
+ console.log("");
774
+ break;
775
+ }
776
+ case "stop": {
777
+ // Stop daemon
778
+ const lockFile = path.join(repoRoot, ".vibe", "exec", "daemon.lock");
779
+ if (!fs.existsSync(lockFile)) {
780
+ console.log("Daemon is not running.");
781
+ return;
782
+ }
783
+ try {
784
+ const lock = JSON.parse(fs.readFileSync(lockFile, "utf-8"));
785
+ console.log(`Stopping daemon (PID: ${lock.pid})...`);
786
+ // On Unix, we'd send SIGTERM. For now, just remove lock file.
787
+ fs.unlinkSync(lockFile);
788
+ console.log("Lock file removed. Daemon should stop gracefully.");
789
+ }
790
+ catch (e) {
791
+ console.log(`Failed to stop: ${e instanceof Error ? e.message : String(e)}`);
792
+ }
793
+ break;
794
+ }
795
+ case "help":
796
+ default: {
797
+ console.log("");
798
+ console.log("Usage: vibe exec <command>");
799
+ console.log("");
800
+ console.log("Commands:");
801
+ console.log(` ${c("green", "status")} 데몬/큐 상태 확인`);
802
+ console.log(` ${c("green", "enqueue")} 작업 추가`);
803
+ console.log(` ${c("green", "inspect")} 실행 결과 조회`);
804
+ console.log(` ${c("green", "serve")} 데몬 시작 (webhook + worker)`);
805
+ console.log(` ${c("green", "worker")} 워커만 시작`);
806
+ console.log(` ${c("green", "stop")} 데몬 종료`);
807
+ console.log("");
808
+ console.log("Options:");
809
+ console.log(` ${c("dim", "--json")} JSON 출력 (VRIP 모드)`);
810
+ console.log(` ${c("dim", "--daemon")} 데몬 모드로 실행`);
811
+ console.log(` ${c("dim", "--once")} 단발 실행`);
812
+ console.log("");
813
+ break;
814
+ }
815
+ }
816
+ }
817
+ /**
818
+ * vibe inspect - Code inspection (shorthand for vibe check with enhanced output)
819
+ *
820
+ * VRIP v1.1 compliant: STDOUT = Control JSON, results in run_dir
821
+ */
822
+ async function cmdInspect() {
823
+ const args = process.argv.slice(3);
824
+ const runId = args[0];
825
+ const jsonMode = args.includes("--json");
826
+ const repoRoot = resolveRepoRoot();
827
+ if (runId) {
828
+ // Inspect specific run
829
+ const runDir = path.join(repoRoot, ".vibe", "runs", runId);
830
+ if (!fs.existsSync(runDir)) {
831
+ if (jsonMode) {
832
+ console.log(JSON.stringify({ ok: false, error: "run_not_found", run_id: runId }));
833
+ }
834
+ else {
835
+ console.log(`Run not found: ${runId}`);
836
+ }
837
+ process.exit(1);
838
+ }
839
+ // Read and display result
840
+ const resultFile = path.join(runDir, "outputs", "result.json");
841
+ if (fs.existsSync(resultFile)) {
842
+ const result = JSON.parse(fs.readFileSync(resultFile, "utf-8"));
843
+ if (jsonMode) {
844
+ // VRIP: Control JSON only
845
+ console.log(JSON.stringify({ ok: result.ok, run_id: runId }));
846
+ }
847
+ else {
848
+ console.log("");
849
+ console.log(c("cyan", `Inspect: ${runId}`));
850
+ console.log("");
851
+ console.log(` Result: ${result.ok ? c("green", "✓ PASS") : c("red", "✗ FAIL")}`);
852
+ if (result.summary) {
853
+ console.log(` Issues: ${result.summary.issues_found ?? 0}`);
854
+ console.log(` Duration: ${result.summary.duration_ms ?? 0}ms`);
855
+ }
856
+ console.log("");
857
+ }
858
+ }
859
+ else {
860
+ if (jsonMode) {
861
+ console.log(JSON.stringify({ ok: false, error: "no_result", run_id: runId }));
862
+ }
863
+ else {
864
+ console.log("No result file found for this run.");
865
+ }
866
+ }
867
+ }
868
+ else {
869
+ // Run new inspection (delegate to vibe check)
870
+ const paths = getVibeRepoPaths(repoRoot);
871
+ if (!fs.existsSync(paths.validateScript)) {
872
+ if (jsonMode) {
873
+ console.log(JSON.stringify({ ok: false, error: "not_initialized" }));
874
+ }
875
+ else {
876
+ console.log(`Missing local guard: ${path.relative(repoRoot, paths.validateScript)}`);
877
+ console.log(`Run: ${c("cyan", "vibe setup")}`);
878
+ }
879
+ process.exit(1);
880
+ }
881
+ if (!jsonMode) {
882
+ console.log("");
883
+ console.log(c("cyan", "Running inspection..."));
884
+ console.log("");
885
+ }
886
+ const result = spawnBashScriptInRepoSync(repoRoot, paths.validateScript, [], { stdio: jsonMode ? "pipe" : "inherit" });
887
+ if (jsonMode) {
888
+ // Generate a run_id for VRIP compliance
889
+ const runId = `${new Date().toISOString().replace(/[-:T.]/g, "").slice(0, 15)}Z_inspect`;
890
+ console.log(JSON.stringify({
891
+ ok: result?.status === 0,
892
+ run_id: runId,
893
+ error: result?.status !== 0 ? "validation_failed" : undefined
894
+ }));
895
+ }
896
+ process.exit(result?.status ?? 1);
897
+ }
898
+ }
899
+ /**
900
+ * vibe run - Run application (placeholder for future implementation)
901
+ */
902
+ async function cmdRun() {
903
+ const jsonMode = process.argv.includes("--json");
904
+ console.log("");
905
+ console.log(c("cyan", "Vibe Run"));
906
+ console.log("");
907
+ console.log(" This command will run the application based on project config.");
908
+ console.log("");
909
+ console.log(" (Not yet implemented - use MCP tool vibe_pm.run_app)");
910
+ console.log("");
911
+ if (jsonMode) {
912
+ console.log(JSON.stringify({ ok: false, error: "not_implemented" }));
913
+ }
914
+ }
514
915
  // P0-1: Subcommand-specific help messages
515
916
  const SUBCOMMAND_HELP = {
516
917
  setup: `vibe setup - 통합 설치 (권장)
@@ -603,9 +1004,60 @@ Usage: vibe update
603
1004
  Usage: vibe reset [options]
604
1005
 
605
1006
  Options:
606
- -f, --force 실제로 삭제 실행
1007
+ -f, --force 실제로 삭제 실행
1008
+ --keep-context .vibe/decisions/ 유지 (결정 기록 보존)
1009
+
1010
+ 삭제 대상:
1011
+ - runs/ 실행 기록
1012
+ - .vibe/state/ 현재 작업 상태
1013
+
1014
+ --keep-context 사용 시 유지:
1015
+ - .vibe/decisions/ 결정 기록 (컨텍스트 보존)
607
1016
 
608
- runs/ 디렉토리의 실행 기록을 삭제합니다.
1017
+ Examples:
1018
+ $ vibe reset --force # 전체 초기화
1019
+ $ vibe reset --keep-context -f # 결정 기록 유지하며 초기화
1020
+ `,
1021
+ exec: `vibe exec - Execution Engine 데몬 관리
1022
+
1023
+ Usage: vibe exec <command> [options]
1024
+
1025
+ Commands:
1026
+ status 데몬/큐 상태 확인
1027
+ enqueue 작업 추가 (예: vibe exec enqueue task.json)
1028
+ inspect 실행 결과 조회 (예: vibe exec inspect <run_id>)
1029
+ serve 데몬 시작 (webhook + worker)
1030
+ worker 워커만 시작
1031
+ stop 데몬 종료
1032
+
1033
+ Options:
1034
+ --json JSON 출력 (VRIP 모드)
1035
+ --daemon 데몬 모드로 실행
1036
+ --once 단발 실행
1037
+
1038
+ Examples:
1039
+ $ vibe exec status # 상태 확인
1040
+ $ vibe exec status --json # JSON 출력
1041
+ $ vibe exec inspect abc123 # 특정 run 조회
1042
+ `,
1043
+ inspect: `vibe inspect - 코드 검수
1044
+
1045
+ Usage: vibe inspect [run_id] [options]
1046
+
1047
+ Options:
1048
+ --json JSON 출력 (VRIP 모드)
1049
+
1050
+ Examples:
1051
+ $ vibe inspect # 새 검수 실행
1052
+ $ vibe inspect abc123 # 특정 run 결과 조회
1053
+ $ vibe inspect --json # VRIP 모드 (MCP용)
1054
+ `,
1055
+ run: `vibe run - 앱 실행
1056
+
1057
+ Usage: vibe run [options]
1058
+
1059
+ 프로젝트 설정에 따라 앱을 실행합니다.
1060
+ (현재 개발 중 - vibe_pm.run_app MCP 도구 사용)
609
1061
  `,
610
1062
  };
611
1063
  // P0-1: Helper functions for --help priority handling
@@ -632,6 +1084,9 @@ function cmdHelp(subcommand) {
632
1084
  console.log(` ${c("green", "status")} 로컬 상태 확인`);
633
1085
  console.log(` ${c("green", "ticket")} 작업 티켓(Work Order) 생성`);
634
1086
  console.log(` ${c("green", "check")} 로컬 가드 검증 실행`);
1087
+ console.log(` ${c("green", "inspect")} 코드 검수 (VRIP 모드)`);
1088
+ console.log(` ${c("green", "exec")} Execution Engine 관리`);
1089
+ console.log(` ${c("green", "run")} 앱 실행`);
635
1090
  console.log(` ${c("green", "done")} 작업 티켓 종료/아카이브`);
636
1091
  console.log(` ${c("green", "config")} 설정 관리 (show/validate)`);
637
1092
  console.log(` ${c("green", "doctor")} 설치 상태 확인 및 진단`);
@@ -648,9 +1103,10 @@ function cmdHelp(subcommand) {
648
1103
  console.log("Examples:");
649
1104
  console.log(` ${c("dim", "$")} vibe setup # 통합 설치 (권장)`);
650
1105
  console.log(` ${c("dim", "$")} vibe setup --check # 설치 상태 확인`);
651
- console.log(` ${c("dim", "$")} vibe setup --help # setup 명령어 도움말`);
652
1106
  console.log(` ${c("dim", "$")} vibe ticket \"Fix auth\" # 작업 티켓 생성`);
653
1107
  console.log(` ${c("dim", "$")} vibe check # 푸시 전 로컬 검증`);
1108
+ console.log(` ${c("dim", "$")} vibe inspect --json # VRIP 모드 검수`);
1109
+ console.log(` ${c("dim", "$")} vibe exec status # 데몬 상태 확인`);
654
1110
  console.log(` ${c("dim", "$")} vibe doctor # 설치 진단`);
655
1111
  console.log(` ${c("dim", "$")} vibe reset --force # 프로젝트 초기화`);
656
1112
  console.log("");
@@ -719,6 +1175,15 @@ async function main() {
719
1175
  case "update":
720
1176
  await cmdUpdate();
721
1177
  break;
1178
+ case "exec":
1179
+ await cmdExec();
1180
+ break;
1181
+ case "inspect":
1182
+ await cmdInspect();
1183
+ break;
1184
+ case "run":
1185
+ await cmdRun();
1186
+ break;
722
1187
  case "help":
723
1188
  case "-h":
724
1189
  case "--help":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecodetown/mcp-server",
3
- "version": "2.1.4",
3
+ "version": "2.2.0",
4
4
  "type": "module",
5
5
  "description": "Vibe PM - AI Project Manager MCP Server for non-technical founders",
6
6
  "keywords": [