@vibecodetown/mcp-server 2.1.4 → 2.2.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.
Files changed (80) hide show
  1. package/README.md +10 -10
  2. package/build/auth/credential_store.js +146 -0
  3. package/build/auth/public_key.js +6 -4
  4. package/build/bootstrap/doctor.js +113 -5
  5. package/build/bootstrap/installer.js +85 -15
  6. package/build/bootstrap/registry.js +11 -6
  7. package/build/bootstrap/skills-installer.js +365 -0
  8. package/build/control_plane/gate.js +52 -70
  9. package/build/dx/activity.js +26 -3
  10. package/build/engine.js +151 -0
  11. package/build/errors.js +107 -0
  12. package/build/generated/bridge_build_seed_input.js +2 -0
  13. package/build/generated/bridge_build_seed_output.js +2 -0
  14. package/build/generated/bridge_confirm_reference_input.js +2 -0
  15. package/build/generated/bridge_confirm_reference_output.js +2 -0
  16. package/build/generated/bridge_confirmed_reference_file.js +2 -0
  17. package/build/generated/bridge_generate_references_input.js +2 -0
  18. package/build/generated/bridge_generate_references_output.js +2 -0
  19. package/build/generated/bridge_references_file.js +2 -0
  20. package/build/generated/bridge_work_order_seed_file.js +2 -0
  21. package/build/generated/contracts_bundle_info.js +3 -3
  22. package/build/generated/index.js +14 -0
  23. package/build/generated/ingress_input.js +2 -0
  24. package/build/generated/ingress_output.js +2 -0
  25. package/build/generated/ingress_resolution_file.js +2 -0
  26. package/build/generated/ingress_summary_file.js +2 -0
  27. package/build/generated/message_template_id_mapping_file.js +2 -0
  28. package/build/generated/run_app_input.js +1 -1
  29. package/build/index.js +4 -1
  30. package/build/local-mode/git.js +36 -22
  31. package/build/local-mode/paths.js +1 -0
  32. package/build/local-mode/project-state.js +176 -0
  33. package/build/local-mode/setup.js +21 -1
  34. package/build/local-mode/templates.js +3 -3
  35. package/build/path-utils.js +68 -0
  36. package/build/runtime/cli_invoker.js +416 -0
  37. package/build/tools/vibe_pm/advisory_review.js +5 -3
  38. package/build/tools/vibe_pm/bridge_build_seed.js +164 -0
  39. package/build/tools/vibe_pm/bridge_confirm_reference.js +91 -0
  40. package/build/tools/vibe_pm/bridge_generate_references.js +258 -0
  41. package/build/tools/vibe_pm/briefing.js +26 -1
  42. package/build/tools/vibe_pm/context.js +79 -0
  43. package/build/tools/vibe_pm/create_work_order.js +200 -3
  44. package/build/tools/vibe_pm/doctor.js +95 -0
  45. package/build/tools/vibe_pm/entity_gate/preflight.js +8 -3
  46. package/build/tools/vibe_pm/export_output.js +14 -13
  47. package/build/tools/vibe_pm/finalize_work.js +74 -0
  48. package/build/tools/vibe_pm/force_override.js +104 -0
  49. package/build/tools/vibe_pm/get_decision.js +2 -2
  50. package/build/tools/vibe_pm/index.js +160 -3
  51. package/build/tools/vibe_pm/ingress.js +645 -0
  52. package/build/tools/vibe_pm/ingress_gate.js +116 -0
  53. package/build/tools/vibe_pm/inspect_code.js +90 -20
  54. package/build/tools/vibe_pm/kce/doc_usage.js +4 -9
  55. package/build/tools/vibe_pm/kce/on_finalize.js +2 -2
  56. package/build/tools/vibe_pm/kce/preflight.js +11 -7
  57. package/build/tools/vibe_pm/list_rules.js +135 -0
  58. package/build/tools/vibe_pm/memory_status.js +11 -8
  59. package/build/tools/vibe_pm/memory_sync.js +11 -8
  60. package/build/tools/vibe_pm/pm_language.js +17 -16
  61. package/build/tools/vibe_pm/pre_commit_analysis.js +292 -0
  62. package/build/tools/vibe_pm/publish_mcp.js +271 -0
  63. package/build/tools/vibe_pm/python_error.js +115 -0
  64. package/build/tools/vibe_pm/run_app.js +215 -86
  65. package/build/tools/vibe_pm/run_app_podman.js +64 -2
  66. package/build/tools/vibe_pm/save_rule.js +120 -0
  67. package/build/tools/vibe_pm/search_oss.js +5 -3
  68. package/build/tools/vibe_pm/spec_rag.js +185 -0
  69. package/build/tools/vibe_pm/status.js +50 -3
  70. package/build/tools/vibe_pm/submit_decision.js +2 -2
  71. package/build/tools/vibe_pm/types.js +28 -0
  72. package/build/tools/vibe_pm/undo_last_task.js +23 -20
  73. package/build/tools/vibe_pm/waiter_mapping.js +155 -0
  74. package/build/tools/vibe_pm/zoekt_evidence.js +5 -3
  75. package/build/tools.js +13 -5
  76. package/build/version-check.js +5 -5
  77. package/build/vibe-cli.js +742 -39
  78. package/package.json +5 -4
  79. package/skills/VRIP_INSTALL_MANIFEST_DOCTOR.skill.md +288 -0
  80. package/skills/index.json +14 -0
package/build/vibe-cli.js CHANGED
@@ -6,6 +6,7 @@ import * as path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { healthCheck, validateCacheIntegrity, checkForUpdates } from "./bootstrap/doctor.js";
8
8
  import { ensureEngines } from "./bootstrap/installer.js";
9
+ import { listAvailableSkills, listActivatedSkills, getSkillById, activateSkills, deactivateSkill, getSkillsHealth, getSkillContent } from "./bootstrap/skills-installer.js";
9
10
  import { CONTRACTS_BUNDLE_SHA256, CONTRACTS_VERSION } from "./generated/contracts_bundle_info.js";
10
11
  import { spawnBashScriptInRepoSync } from "./local-mode/bash.js";
11
12
  import { getGitHooksPath, getGitRoot } from "./local-mode/git.js";
@@ -77,9 +78,21 @@ async function cmdDoctor() {
77
78
  // Check cache integrity
78
79
  const cacheResult = await validateCacheIntegrity();
79
80
  console.log(` Cache: ${cacheResult.valid ? c("green", "✓") : c("yellow", "⚠")} ${cacheResult.valid ? "valid" : "needs repair"}`);
80
- // Check Agent Skills files
81
+ // Check Skills Bundle
81
82
  console.log("");
82
- console.log(" Agent Skills:");
83
+ console.log(" Skills Bundle:");
84
+ const skillsStatus = health.skills.status;
85
+ const skillsIcon = skillsStatus === "OK"
86
+ ? c("green", "✓")
87
+ : skillsStatus === "WARN"
88
+ ? c("yellow", "⚠")
89
+ : c("red", "✗");
90
+ console.log(` Status: ${skillsIcon} ${skillsStatus}`);
91
+ console.log(` Version: ${health.skills.version ?? c("dim", "unknown")}`);
92
+ console.log(` Installed: ${health.skills.summary.ok}/${health.skills.summary.total}`);
93
+ // Check Agent Skills files (project-level guidance)
94
+ console.log("");
95
+ console.log(" Agent Guidance Files:");
83
96
  const skillFiles = [
84
97
  { name: "AGENTS.md", path: "AGENTS.md" },
85
98
  { name: ".cursorrules", path: ".cursorrules" },
@@ -93,7 +106,7 @@ async function cmdDoctor() {
93
106
  // Summary
94
107
  console.log("");
95
108
  console.log(c("cyan", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
96
- const allOk = health.status === "OK" && cacheResult.valid;
109
+ const allOk = health.status === "OK" && cacheResult.valid && skillsStatus === "OK";
97
110
  if (allOk) {
98
111
  console.log("");
99
112
  console.log(` ${c("green", "✓ 모든 것이 정상입니다!")}`);
@@ -112,6 +125,50 @@ async function cmdDoctor() {
112
125
  function resolveRepoRoot() {
113
126
  return getGitRoot(process.cwd()) ?? process.cwd();
114
127
  }
128
+ /**
129
+ * P0-3: Check if hooks are properly configured before risky operations.
130
+ * Returns warnings if hooks are not set up (does not block execution).
131
+ */
132
+ function checkHooksIntegrity(repoRoot) {
133
+ const warnings = [];
134
+ const hooksPath = getGitHooksPath(repoRoot);
135
+ if (!hooksPath) {
136
+ warnings.push("Git hooks 경로가 설정되어 있지 않습니다. 'vibe setup' 실행을 권장합니다.");
137
+ return { ok: false, hooksPath: null, prePushExists: false, validateExists: false, warnings };
138
+ }
139
+ const prePushPath = path.join(hooksPath, "pre-push");
140
+ const validatePath = path.join(repoRoot, ".vibe", "lib", "validate.sh");
141
+ const prePushExists = fs.existsSync(prePushPath);
142
+ const validateExists = fs.existsSync(validatePath);
143
+ if (!prePushExists) {
144
+ warnings.push("pre-push hook이 설치되어 있지 않습니다. 'vibe setup' 실행을 권장합니다.");
145
+ }
146
+ if (!validateExists) {
147
+ warnings.push("validate.sh가 없습니다. 'vibe setup' 실행을 권장합니다.");
148
+ }
149
+ const ok = prePushExists && validateExists;
150
+ return { ok, hooksPath, prePushExists, validateExists, warnings };
151
+ }
152
+ /**
153
+ * P0-3: Display hooks integrity warning if needed.
154
+ * Called before potentially risky commands (push, check, etc.)
155
+ */
156
+ function warnIfHooksNotConfigured(repoRoot) {
157
+ // Skip in non-interactive environments
158
+ if (!process.stdout.isTTY)
159
+ return;
160
+ const result = checkHooksIntegrity(repoRoot);
161
+ if (result.ok)
162
+ return;
163
+ console.log("");
164
+ console.log(c("yellow", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
165
+ console.log(c("yellow", " ⚠ Hooks 미설정 경고"));
166
+ console.log(c("yellow", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
167
+ for (const w of result.warnings) {
168
+ console.log(` ${c("yellow", "•")} ${w}`);
169
+ }
170
+ console.log("");
171
+ }
115
172
  async function cmdSetup() {
116
173
  const repoRoot = resolveRepoRoot();
117
174
  const args = process.argv.slice(2);
@@ -126,6 +183,15 @@ async function cmdSetup() {
126
183
  console.log(c("cyan", " Vibe PM Setup"));
127
184
  console.log(c("cyan", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
128
185
  console.log("");
186
+ // P0-1: --no-hooks deprecation warning
187
+ if (noHooks) {
188
+ console.log(c("yellow", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
189
+ console.log(c("yellow", " ⚠ DEPRECATED: --no-hooks 옵션"));
190
+ console.log(c("yellow", " 이 옵션은 향후 버전에서 제거될 예정입니다."));
191
+ console.log(c("yellow", " Git hooks는 코드 품질과 보안을 위해 권장됩니다."));
192
+ console.log(c("yellow", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
193
+ console.log("");
194
+ }
129
195
  if (checkOnly) {
130
196
  const state = await checkInstallState(repoRoot);
131
197
  console.log(` Local: ${state.localInitialized ? c("green", "OK") : c("red", "NOT INITIALIZED")}`);
@@ -139,16 +205,18 @@ async function cmdSetup() {
139
205
  return;
140
206
  }
141
207
  // Step 1: Local mode
142
- console.log(" Step 1/3: 로컬 모드 초기화...");
208
+ console.log(" Step 1/4: 로컬 모드 초기화...");
143
209
  // Step 2: Engines
144
210
  if (!localOnly) {
145
- console.log(" Step 2/3: 엔진 설치...");
211
+ console.log(" Step 2/4: 엔진 설치...");
146
212
  }
147
213
  else {
148
- console.log(" Step 2/3: 엔진 설치 (스킵 - local-only)");
214
+ console.log(" Step 2/4: 엔진 설치 (스킵 - local-only)");
149
215
  }
150
216
  // Step 3: Version lock
151
- console.log(" Step 3/3: 버전 잠금...");
217
+ console.log(" Step 3/4: 버전 잠금...");
218
+ // Step 4: Skills
219
+ console.log(" Step 4/4: 스킬 활성화...");
152
220
  const result = await runSetup(repoRoot, VERSION, {
153
221
  force: forceFlag,
154
222
  localOnly,
@@ -168,6 +236,9 @@ async function cmdSetup() {
168
236
  const ok = result.engines.filter(e => e.status === "ok").length;
169
237
  console.log(` ${c("green", "\u2713")} 엔진: ${ok}/${result.engines.length} 준비됨`);
170
238
  }
239
+ if (result.skills.activated.length > 0) {
240
+ console.log(` ${c("green", "\u2713")} 스킬: ${result.skills.activated.length}개 활성화됨`);
241
+ }
171
242
  if (result.versionLock) {
172
243
  console.log(` ${c("green", "\u2713")} 버전 잠금 생성됨`);
173
244
  }
@@ -201,6 +272,15 @@ function cmdInit() {
201
272
  const noHooks = args.includes("--no-hooks");
202
273
  const installCi = args.includes("--ci") || args.includes("--install-ci");
203
274
  const ciFailOnWarn = args.includes("--strict");
275
+ // P0-1: --no-hooks deprecation warning
276
+ if (noHooks) {
277
+ console.log("");
278
+ console.log(c("yellow", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
279
+ console.log(c("yellow", " ⚠ DEPRECATED: --no-hooks 옵션"));
280
+ console.log(c("yellow", " 이 옵션은 향후 버전에서 제거될 예정입니다."));
281
+ console.log(c("yellow", " Git hooks는 코드 품질과 보안을 위해 권장됩니다."));
282
+ console.log(c("yellow", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
283
+ }
204
284
  const result = initLocalModeRepo(repoRoot, { force: forceFlag, installHooks: !noHooks, installCi, ciFailOnWarn });
205
285
  console.log("");
206
286
  console.log(c("cyan", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
@@ -297,6 +377,8 @@ function cmdTicket() {
297
377
  function cmdCheck(update) {
298
378
  const repoRoot = resolveRepoRoot();
299
379
  const paths = getVibeRepoPaths(repoRoot);
380
+ // P0-3: Hooks integrity pre-check (warning only)
381
+ warnIfHooksNotConfigured(repoRoot);
300
382
  const cfg = validateRepoConfig(paths.configFile);
301
383
  if (!cfg.ok) {
302
384
  console.log(`[VIBE] Config invalid: ${cfg.error}`);
@@ -440,51 +522,91 @@ async function cmdReset() {
440
522
  console.log("");
441
523
  const cwd = process.cwd();
442
524
  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;
525
+ const vibeDir = path.join(cwd, ".vibe");
526
+ const vibeStateDir = path.join(vibeDir, "state");
527
+ const vibeDecisionsDir = path.join(vibeDir, "decisions");
528
+ // P2-2: --keep-context 옵션 확인
529
+ const keepContext = process.argv.includes("--keep-context");
530
+ const forceFlag = process.argv.includes("--force") || process.argv.includes("-f");
531
+ // Collect items to delete
532
+ const toDelete = [];
533
+ // runs/ 디렉토리
534
+ if (fs.existsSync(runsDir)) {
535
+ const runs = fs.readdirSync(runsDir).filter((f) => {
536
+ try {
537
+ const stat = fs.statSync(path.join(runsDir, f));
538
+ return stat.isDirectory();
539
+ }
540
+ catch {
541
+ return false;
542
+ }
543
+ });
544
+ for (const run of runs) {
545
+ toDelete.push({ path: path.join(runsDir, run), label: `runs/${run}` });
546
+ }
448
547
  }
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/ 디렉토리가 비어있습니다.`);
548
+ // .vibe/state/ 디렉토리 (항상 삭제)
549
+ if (fs.existsSync(vibeStateDir)) {
550
+ const stateFiles = fs.readdirSync(vibeStateDir);
551
+ for (const file of stateFiles) {
552
+ toDelete.push({ path: path.join(vibeStateDir, file), label: `.vibe/state/${file}` });
553
+ }
554
+ }
555
+ // .vibe/decisions/는 --keep-context 시 유지
556
+ if (!keepContext && fs.existsSync(vibeDecisionsDir)) {
557
+ const decisionFiles = fs.readdirSync(vibeDecisionsDir);
558
+ for (const file of decisionFiles) {
559
+ toDelete.push({ path: path.join(vibeDecisionsDir, file), label: `.vibe/decisions/${file}` });
560
+ }
561
+ }
562
+ if (toDelete.length === 0) {
563
+ console.log(` 초기화할 내용이 없습니다.`);
456
564
  console.log("");
457
565
  return;
458
566
  }
459
- console.log(` ${runs.length}개의 실행 기록을 발견했습니다.`);
567
+ console.log(` ${toDelete.length}개의 항목을 발견했습니다.`);
568
+ if (keepContext) {
569
+ console.log(` ${c("cyan", "--keep-context")}: .vibe/decisions/는 유지됩니다.`);
570
+ }
460
571
  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}`);
572
+ // Show what would be deleted
573
+ console.log(` 삭제될 항목:`);
574
+ for (const item of toDelete.slice(0, 8)) {
575
+ console.log(` - ${item.label}`);
466
576
  }
467
- if (runs.length > 5) {
468
- console.log(` ... 외 ${runs.length - 5}개`);
577
+ if (toDelete.length > 8) {
578
+ console.log(` ... 외 ${toDelete.length - 8}개`);
469
579
  }
470
580
  console.log("");
471
- // Check for --force flag
472
- const forceFlag = process.argv.includes("--force") || process.argv.includes("-f");
473
581
  if (!forceFlag) {
474
582
  console.log(` ${c("yellow", "주의:")} 이 작업은 되돌릴 수 없습니다.`);
475
- console.log(` 실제로 삭제하려면 ${c("cyan", "vibe reset --force")}를 사용하세요.`);
583
+ if (keepContext) {
584
+ console.log(` 실제로 삭제하려면 ${c("cyan", "vibe reset --keep-context --force")}를 사용하세요.`);
585
+ }
586
+ else {
587
+ console.log(` 실제로 삭제하려면 ${c("cyan", "vibe reset --force")}를 사용하세요.`);
588
+ }
476
589
  console.log("");
477
590
  return;
478
591
  }
479
- // Delete runs
592
+ // Delete items
480
593
  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}`);
594
+ let deleted = 0;
595
+ for (const item of toDelete) {
596
+ try {
597
+ fs.rmSync(item.path, { recursive: true, force: true });
598
+ console.log(` ${c("red", "✗")} ${item.label}`);
599
+ deleted++;
600
+ }
601
+ catch {
602
+ console.log(` ${c("yellow", "⚠")} ${item.label} (삭제 실패)`);
603
+ }
485
604
  }
486
605
  console.log("");
487
- console.log(` ${c("green", "✓")} ${runs.length}개의 실행 기록이 삭제되었습니다.`);
606
+ console.log(` ${c("green", "✓")} ${deleted}개의 항목이 삭제되었습니다.`);
607
+ if (keepContext) {
608
+ console.log(` ${c("cyan", "ℹ")} .vibe/decisions/는 유지되었습니다.`);
609
+ }
488
610
  console.log("");
489
611
  }
490
612
  async function cmdUpdate() {
@@ -511,6 +633,497 @@ async function cmdUpdate() {
511
633
  }
512
634
  console.log("");
513
635
  }
636
+ // ============================================================
637
+ // Skills Management Commands
638
+ // ============================================================
639
+ /**
640
+ * vibe skills - Skill bundle management
641
+ *
642
+ * Subcommands:
643
+ * list - List all available skills
644
+ * activate - Activate skill(s) for this project
645
+ * deactivate - Deactivate a skill from this project
646
+ * info - Show skill details
647
+ * status - Show skills health status
648
+ */
649
+ async function cmdSkills() {
650
+ const args = process.argv.slice(3); // after "vibe skills"
651
+ const subcommand = args[0] ?? "list";
652
+ const jsonMode = args.includes("--json");
653
+ const projectRoot = process.cwd();
654
+ switch (subcommand) {
655
+ case "list": {
656
+ const available = listAvailableSkills();
657
+ const activated = listActivatedSkills(projectRoot);
658
+ if (jsonMode) {
659
+ console.log(JSON.stringify({ available, activated }, null, 2));
660
+ return;
661
+ }
662
+ console.log("");
663
+ console.log(c("cyan", "Vibe PM Skills"));
664
+ console.log("");
665
+ console.log(" Available Skills:");
666
+ if (available.length === 0) {
667
+ console.log(` ${c("dim", "(none)")}`);
668
+ }
669
+ else {
670
+ for (const skill of available) {
671
+ const isActive = activated.includes(skill.id);
672
+ const status = isActive ? c("green", "✓") : c("dim", "○");
673
+ console.log(` ${status} ${skill.id.padEnd(35)} v${skill.version}`);
674
+ console.log(` ${c("dim", skill.description)}`);
675
+ }
676
+ }
677
+ console.log("");
678
+ if (activated.length > 0) {
679
+ console.log(` Activated: ${c("green", String(activated.length))} / ${available.length}`);
680
+ }
681
+ else {
682
+ console.log(` ${c("dim", "No skills activated. Run:")} ${c("cyan", "vibe skills activate <id>")}`);
683
+ }
684
+ console.log("");
685
+ break;
686
+ }
687
+ case "activate": {
688
+ const skillIds = args.slice(1).filter(a => !a.startsWith("-"));
689
+ if (skillIds.length === 0) {
690
+ // Activate all skills if no ID specified
691
+ console.log("");
692
+ console.log(c("cyan", "Activating all available skills..."));
693
+ console.log("");
694
+ }
695
+ const result = await activateSkills(projectRoot, skillIds.length > 0 ? skillIds : undefined);
696
+ if (jsonMode) {
697
+ console.log(JSON.stringify(result, null, 2));
698
+ return;
699
+ }
700
+ if (result.activated.length > 0) {
701
+ console.log(` ${c("green", "✓")} Activated skills:`);
702
+ for (const id of result.activated) {
703
+ console.log(` - ${id}`);
704
+ }
705
+ }
706
+ if (result.errors.length > 0) {
707
+ console.log("");
708
+ console.log(` ${c("red", "✗")} Errors:`);
709
+ for (const err of result.errors) {
710
+ console.log(` - ${err}`);
711
+ }
712
+ }
713
+ console.log("");
714
+ break;
715
+ }
716
+ case "deactivate": {
717
+ const skillId = args[1];
718
+ if (!skillId || skillId.startsWith("-")) {
719
+ console.log(` ${c("red", "✗")} Usage: vibe skills deactivate <skill_id>`);
720
+ return;
721
+ }
722
+ const result = await deactivateSkill(projectRoot, skillId);
723
+ if (jsonMode) {
724
+ console.log(JSON.stringify(result, null, 2));
725
+ return;
726
+ }
727
+ if (result.success) {
728
+ console.log(` ${c("green", "✓")} Deactivated: ${skillId}`);
729
+ }
730
+ else {
731
+ console.log(` ${c("red", "✗")} ${result.error}`);
732
+ }
733
+ console.log("");
734
+ break;
735
+ }
736
+ case "info": {
737
+ const skillId = args[1];
738
+ if (!skillId || skillId.startsWith("-")) {
739
+ console.log(` ${c("red", "✗")} Usage: vibe skills info <skill_id>`);
740
+ return;
741
+ }
742
+ const skill = getSkillById(skillId);
743
+ if (!skill) {
744
+ console.log(` ${c("red", "✗")} Skill not found: ${skillId}`);
745
+ return;
746
+ }
747
+ if (jsonMode) {
748
+ console.log(JSON.stringify(skill, null, 2));
749
+ return;
750
+ }
751
+ console.log("");
752
+ console.log(c("cyan", `Skill: ${skill.id}`));
753
+ console.log("");
754
+ console.log(` Version: ${skill.version}`);
755
+ console.log(` Description: ${skill.description}`);
756
+ console.log(` Tags: ${skill.tags.join(", ")}`);
757
+ console.log(` File: ${skill.file}`);
758
+ console.log(` SHA256: ${skill.sha256.slice(0, 16)}...`);
759
+ console.log("");
760
+ // Show content preview
761
+ const content = getSkillContent(skillId);
762
+ if (content) {
763
+ const lines = content.split("\n").slice(0, 10);
764
+ console.log(c("dim", " Preview:"));
765
+ for (const line of lines) {
766
+ console.log(` ${c("dim", line.slice(0, 60))}`);
767
+ }
768
+ if (content.split("\n").length > 10) {
769
+ console.log(` ${c("dim", "...")}`);
770
+ }
771
+ }
772
+ console.log("");
773
+ break;
774
+ }
775
+ case "status": {
776
+ const health = getSkillsHealth();
777
+ if (jsonMode) {
778
+ console.log(JSON.stringify(health, null, 2));
779
+ return;
780
+ }
781
+ console.log("");
782
+ console.log(c("cyan", "Skills Bundle Status"));
783
+ console.log("");
784
+ console.log(` Installed: ${health.installed ? c("green", "✓") : c("red", "✗")}`);
785
+ console.log(` Bundle Path: ${health.bundlePath ?? c("dim", "(not found)")}`);
786
+ console.log(` Version: ${health.version ?? c("dim", "(unknown)")}`);
787
+ console.log("");
788
+ console.log(" Skills:");
789
+ for (const skill of health.skills) {
790
+ const icon = skill.status === "ok"
791
+ ? c("green", "✓")
792
+ : skill.status === "missing"
793
+ ? c("red", "✗")
794
+ : c("yellow", "⚠");
795
+ console.log(` ${icon} ${skill.id} (${skill.status})`);
796
+ if (skill.error) {
797
+ console.log(` ${c("dim", skill.error)}`);
798
+ }
799
+ }
800
+ console.log("");
801
+ console.log(` Summary: ${c("green", String(health.summary.ok))} ok, ` +
802
+ `${c("red", String(health.summary.missing))} missing, ` +
803
+ `${c("yellow", String(health.summary.corrupted))} corrupted`);
804
+ console.log("");
805
+ break;
806
+ }
807
+ case "help":
808
+ default: {
809
+ console.log("");
810
+ console.log(c("cyan", "vibe skills") + " - Skill bundle management");
811
+ console.log("");
812
+ console.log("Subcommands:");
813
+ console.log(` ${c("green", "list")} List all available skills`);
814
+ console.log(` ${c("green", "activate")} Activate skill(s) for this project`);
815
+ console.log(` ${c("green", "deactivate")} Deactivate a skill from this project`);
816
+ console.log(` ${c("green", "info")} Show skill details`);
817
+ console.log(` ${c("green", "status")} Show skills health status`);
818
+ console.log("");
819
+ console.log("Examples:");
820
+ console.log(` ${c("dim", "$")} vibe skills list`);
821
+ console.log(` ${c("dim", "$")} vibe skills activate VRIP_INSTALL_MANIFEST_DOCTOR`);
822
+ console.log(` ${c("dim", "$")} vibe skills activate # activate all`);
823
+ console.log(` ${c("dim", "$")} vibe skills info VRIP_INSTALL_MANIFEST_DOCTOR`);
824
+ console.log(` ${c("dim", "$")} vibe skills status --json`);
825
+ console.log("");
826
+ break;
827
+ }
828
+ }
829
+ }
830
+ // ============================================================
831
+ // New Commands: exec, inspect, run (VRIP v1.1 compliant)
832
+ // ============================================================
833
+ /**
834
+ * vibe exec - execution_engine daemon commands
835
+ *
836
+ * VRIP v1.1: STDOUT = Control JSON only, results in run_dir
837
+ */
838
+ async function cmdExec() {
839
+ const args = process.argv.slice(3); // after "vibe exec"
840
+ const subcommand = args[0] ?? "help";
841
+ const jsonMode = args.includes("--json");
842
+ const repoRoot = resolveRepoRoot();
843
+ switch (subcommand) {
844
+ case "status": {
845
+ // Query execution_engine status
846
+ console.log("");
847
+ console.log(c("cyan", "Vibe Exec Status"));
848
+ console.log("");
849
+ // Check if daemon is running (via lock file)
850
+ const lockFile = path.join(repoRoot, ".vibe", "exec", "daemon.lock");
851
+ if (fs.existsSync(lockFile)) {
852
+ try {
853
+ const lock = JSON.parse(fs.readFileSync(lockFile, "utf-8"));
854
+ console.log(` Daemon: ${c("green", "RUNNING")}`);
855
+ console.log(` PID: ${lock.pid}`);
856
+ console.log(` Started: ${lock.started_at}`);
857
+ }
858
+ catch {
859
+ console.log(` Daemon: ${c("yellow", "UNKNOWN")} (lock file corrupted)`);
860
+ }
861
+ }
862
+ else {
863
+ console.log(` Daemon: ${c("dim", "NOT RUNNING")}`);
864
+ }
865
+ // Check queue (if exists)
866
+ const queueDb = path.join(repoRoot, ".vibe", "exec", "queue.sqlite");
867
+ if (fs.existsSync(queueDb)) {
868
+ console.log(` Queue: ${c("green", "✓")} ${path.relative(repoRoot, queueDb)}`);
869
+ }
870
+ else {
871
+ console.log(` Queue: ${c("dim", "○")} (not initialized)`);
872
+ }
873
+ // List recent runs
874
+ const runsDir = path.join(repoRoot, ".vibe", "runs");
875
+ if (fs.existsSync(runsDir)) {
876
+ const runs = fs.readdirSync(runsDir)
877
+ .filter(f => fs.statSync(path.join(runsDir, f)).isDirectory())
878
+ .sort()
879
+ .reverse()
880
+ .slice(0, 5);
881
+ if (runs.length > 0) {
882
+ console.log("");
883
+ console.log(" Recent Runs:");
884
+ for (const run of runs) {
885
+ const statusFile = path.join(runsDir, run, "status.json");
886
+ let status = "?";
887
+ if (fs.existsSync(statusFile)) {
888
+ try {
889
+ const s = JSON.parse(fs.readFileSync(statusFile, "utf-8"));
890
+ status = s.state ?? "?";
891
+ }
892
+ catch { /* ignore */ }
893
+ }
894
+ const statusColor = status === "SUCCEEDED" ? "green" : status === "FAILED" ? "red" : "yellow";
895
+ console.log(` ${run} ${c(statusColor, status)}`);
896
+ }
897
+ }
898
+ }
899
+ console.log("");
900
+ // VRIP: JSON output
901
+ if (jsonMode) {
902
+ const output = { ok: true, daemon: fs.existsSync(lockFile), queue: fs.existsSync(queueDb) };
903
+ console.log(JSON.stringify(output));
904
+ }
905
+ break;
906
+ }
907
+ case "enqueue": {
908
+ // Add task to queue
909
+ const taskFile = args[1];
910
+ if (!taskFile) {
911
+ console.log("Usage: vibe exec enqueue <task.json>");
912
+ process.exit(1);
913
+ }
914
+ if (!fs.existsSync(taskFile)) {
915
+ console.log(`Task file not found: ${taskFile}`);
916
+ process.exit(1);
917
+ }
918
+ console.log(`[VIBE] Task enqueued from: ${taskFile}`);
919
+ console.log(" (Note: execution_engine daemon must be running)");
920
+ // VRIP: JSON output
921
+ if (jsonMode) {
922
+ console.log(JSON.stringify({ ok: true, task_file: taskFile }));
923
+ }
924
+ break;
925
+ }
926
+ case "inspect": {
927
+ // View run results
928
+ const runId = args[1];
929
+ if (!runId) {
930
+ console.log("Usage: vibe exec inspect <run_id>");
931
+ process.exit(1);
932
+ }
933
+ const runDir = path.join(repoRoot, ".vibe", "runs", runId);
934
+ if (!fs.existsSync(runDir)) {
935
+ console.log(`Run not found: ${runId}`);
936
+ process.exit(1);
937
+ }
938
+ console.log("");
939
+ console.log(c("cyan", `Run: ${runId}`));
940
+ console.log("");
941
+ // Show meta
942
+ const metaFile = path.join(runDir, "meta.json");
943
+ if (fs.existsSync(metaFile)) {
944
+ const meta = JSON.parse(fs.readFileSync(metaFile, "utf-8"));
945
+ console.log(` Source: ${meta.source}`);
946
+ console.log(` Created: ${meta.created_at}`);
947
+ }
948
+ // Show status
949
+ const statusFile = path.join(runDir, "status.json");
950
+ if (fs.existsSync(statusFile)) {
951
+ const status = JSON.parse(fs.readFileSync(statusFile, "utf-8"));
952
+ const statusColor = status.state === "SUCCEEDED" ? "green" : status.state === "FAILED" ? "red" : "yellow";
953
+ console.log(` State: ${c(statusColor, status.state)}`);
954
+ console.log(` Phase: ${status.phase}`);
955
+ console.log(` Attempt: ${status.attempt}`);
956
+ }
957
+ // Show result
958
+ const resultFile = path.join(runDir, "outputs", "result.json");
959
+ if (fs.existsSync(resultFile)) {
960
+ const result = JSON.parse(fs.readFileSync(resultFile, "utf-8"));
961
+ console.log("");
962
+ console.log(" Result:");
963
+ console.log(` OK: ${result.ok ? c("green", "true") : c("red", "false")}`);
964
+ if (result.summary) {
965
+ console.log(` Summary: ${JSON.stringify(result.summary)}`);
966
+ }
967
+ }
968
+ console.log("");
969
+ // VRIP: JSON output
970
+ if (jsonMode) {
971
+ console.log(JSON.stringify({ ok: true, run_id: runId }));
972
+ }
973
+ break;
974
+ }
975
+ case "--daemon":
976
+ case "--once":
977
+ case "serve":
978
+ case "worker": {
979
+ // Daemon mode - call execution_engine binary
980
+ console.log("");
981
+ console.log(c("cyan", "Starting Execution Engine..."));
982
+ console.log("");
983
+ console.log(" (Note: This requires execution_engine binary)");
984
+ console.log(" Run: vibe update to ensure engines are installed");
985
+ console.log("");
986
+ break;
987
+ }
988
+ case "stop": {
989
+ // Stop daemon
990
+ const lockFile = path.join(repoRoot, ".vibe", "exec", "daemon.lock");
991
+ if (!fs.existsSync(lockFile)) {
992
+ console.log("Daemon is not running.");
993
+ return;
994
+ }
995
+ try {
996
+ const lock = JSON.parse(fs.readFileSync(lockFile, "utf-8"));
997
+ console.log(`Stopping daemon (PID: ${lock.pid})...`);
998
+ // On Unix, we'd send SIGTERM. For now, just remove lock file.
999
+ fs.unlinkSync(lockFile);
1000
+ console.log("Lock file removed. Daemon should stop gracefully.");
1001
+ }
1002
+ catch (e) {
1003
+ console.log(`Failed to stop: ${e instanceof Error ? e.message : String(e)}`);
1004
+ }
1005
+ break;
1006
+ }
1007
+ case "help":
1008
+ default: {
1009
+ console.log("");
1010
+ console.log("Usage: vibe exec <command>");
1011
+ console.log("");
1012
+ console.log("Commands:");
1013
+ console.log(` ${c("green", "status")} 데몬/큐 상태 확인`);
1014
+ console.log(` ${c("green", "enqueue")} 작업 추가`);
1015
+ console.log(` ${c("green", "inspect")} 실행 결과 조회`);
1016
+ console.log(` ${c("green", "serve")} 데몬 시작 (webhook + worker)`);
1017
+ console.log(` ${c("green", "worker")} 워커만 시작`);
1018
+ console.log(` ${c("green", "stop")} 데몬 종료`);
1019
+ console.log("");
1020
+ console.log("Options:");
1021
+ console.log(` ${c("dim", "--json")} JSON 출력 (VRIP 모드)`);
1022
+ console.log(` ${c("dim", "--daemon")} 데몬 모드로 실행`);
1023
+ console.log(` ${c("dim", "--once")} 단발 실행`);
1024
+ console.log("");
1025
+ break;
1026
+ }
1027
+ }
1028
+ }
1029
+ /**
1030
+ * vibe inspect - Code inspection (shorthand for vibe check with enhanced output)
1031
+ *
1032
+ * VRIP v1.1 compliant: STDOUT = Control JSON, results in run_dir
1033
+ */
1034
+ async function cmdInspect() {
1035
+ const args = process.argv.slice(3);
1036
+ const runId = args[0];
1037
+ const jsonMode = args.includes("--json");
1038
+ const repoRoot = resolveRepoRoot();
1039
+ if (runId) {
1040
+ // Inspect specific run
1041
+ const runDir = path.join(repoRoot, ".vibe", "runs", runId);
1042
+ if (!fs.existsSync(runDir)) {
1043
+ if (jsonMode) {
1044
+ console.log(JSON.stringify({ ok: false, error: "run_not_found", run_id: runId }));
1045
+ }
1046
+ else {
1047
+ console.log(`Run not found: ${runId}`);
1048
+ }
1049
+ process.exit(1);
1050
+ }
1051
+ // Read and display result
1052
+ const resultFile = path.join(runDir, "outputs", "result.json");
1053
+ if (fs.existsSync(resultFile)) {
1054
+ const result = JSON.parse(fs.readFileSync(resultFile, "utf-8"));
1055
+ if (jsonMode) {
1056
+ // VRIP: Control JSON only
1057
+ console.log(JSON.stringify({ ok: result.ok, run_id: runId }));
1058
+ }
1059
+ else {
1060
+ console.log("");
1061
+ console.log(c("cyan", `Inspect: ${runId}`));
1062
+ console.log("");
1063
+ console.log(` Result: ${result.ok ? c("green", "✓ PASS") : c("red", "✗ FAIL")}`);
1064
+ if (result.summary) {
1065
+ console.log(` Issues: ${result.summary.issues_found ?? 0}`);
1066
+ console.log(` Duration: ${result.summary.duration_ms ?? 0}ms`);
1067
+ }
1068
+ console.log("");
1069
+ }
1070
+ }
1071
+ else {
1072
+ if (jsonMode) {
1073
+ console.log(JSON.stringify({ ok: false, error: "no_result", run_id: runId }));
1074
+ }
1075
+ else {
1076
+ console.log("No result file found for this run.");
1077
+ }
1078
+ }
1079
+ }
1080
+ else {
1081
+ // Run new inspection (delegate to vibe check)
1082
+ const paths = getVibeRepoPaths(repoRoot);
1083
+ if (!fs.existsSync(paths.validateScript)) {
1084
+ if (jsonMode) {
1085
+ console.log(JSON.stringify({ ok: false, error: "not_initialized" }));
1086
+ }
1087
+ else {
1088
+ console.log(`Missing local guard: ${path.relative(repoRoot, paths.validateScript)}`);
1089
+ console.log(`Run: ${c("cyan", "vibe setup")}`);
1090
+ }
1091
+ process.exit(1);
1092
+ }
1093
+ if (!jsonMode) {
1094
+ console.log("");
1095
+ console.log(c("cyan", "Running inspection..."));
1096
+ console.log("");
1097
+ }
1098
+ const result = spawnBashScriptInRepoSync(repoRoot, paths.validateScript, [], { stdio: jsonMode ? "pipe" : "inherit" });
1099
+ if (jsonMode) {
1100
+ // Generate a run_id for VRIP compliance
1101
+ const runId = `${new Date().toISOString().replace(/[-:T.]/g, "").slice(0, 15)}Z_inspect`;
1102
+ console.log(JSON.stringify({
1103
+ ok: result?.status === 0,
1104
+ run_id: runId,
1105
+ error: result?.status !== 0 ? "validation_failed" : undefined
1106
+ }));
1107
+ }
1108
+ process.exit(result?.status ?? 1);
1109
+ }
1110
+ }
1111
+ /**
1112
+ * vibe run - Run application (placeholder for future implementation)
1113
+ */
1114
+ async function cmdRun() {
1115
+ const jsonMode = process.argv.includes("--json");
1116
+ console.log("");
1117
+ console.log(c("cyan", "Vibe Run"));
1118
+ console.log("");
1119
+ console.log(" This command will run the application based on project config.");
1120
+ console.log("");
1121
+ console.log(" (Not yet implemented - use MCP tool vibe_pm.run_app)");
1122
+ console.log("");
1123
+ if (jsonMode) {
1124
+ console.log(JSON.stringify({ ok: false, error: "not_implemented" }));
1125
+ }
1126
+ }
514
1127
  // P0-1: Subcommand-specific help messages
515
1128
  const SUBCOMMAND_HELP = {
516
1129
  setup: `vibe setup - 통합 설치 (권장)
@@ -603,9 +1216,81 @@ Usage: vibe update
603
1216
  Usage: vibe reset [options]
604
1217
 
605
1218
  Options:
606
- -f, --force 실제로 삭제 실행
1219
+ -f, --force 실제로 삭제 실행
1220
+ --keep-context .vibe/decisions/ 유지 (결정 기록 보존)
1221
+
1222
+ 삭제 대상:
1223
+ - runs/ 실행 기록
1224
+ - .vibe/state/ 현재 작업 상태
607
1225
 
608
- runs/ 디렉토리의 실행 기록을 삭제합니다.
1226
+ --keep-context 사용 유지:
1227
+ - .vibe/decisions/ 결정 기록 (컨텍스트 보존)
1228
+
1229
+ Examples:
1230
+ $ vibe reset --force # 전체 초기화
1231
+ $ vibe reset --keep-context -f # 결정 기록 유지하며 초기화
1232
+ `,
1233
+ exec: `vibe exec - Execution Engine 데몬 관리
1234
+
1235
+ Usage: vibe exec <command> [options]
1236
+
1237
+ Commands:
1238
+ status 데몬/큐 상태 확인
1239
+ enqueue 작업 추가 (예: vibe exec enqueue task.json)
1240
+ inspect 실행 결과 조회 (예: vibe exec inspect <run_id>)
1241
+ serve 데몬 시작 (webhook + worker)
1242
+ worker 워커만 시작
1243
+ stop 데몬 종료
1244
+
1245
+ Options:
1246
+ --json JSON 출력 (VRIP 모드)
1247
+ --daemon 데몬 모드로 실행
1248
+ --once 단발 실행
1249
+
1250
+ Examples:
1251
+ $ vibe exec status # 상태 확인
1252
+ $ vibe exec status --json # JSON 출력
1253
+ $ vibe exec inspect abc123 # 특정 run 조회
1254
+ `,
1255
+ inspect: `vibe inspect - 코드 검수
1256
+
1257
+ Usage: vibe inspect [run_id] [options]
1258
+
1259
+ Options:
1260
+ --json JSON 출력 (VRIP 모드)
1261
+
1262
+ Examples:
1263
+ $ vibe inspect # 새 검수 실행
1264
+ $ vibe inspect abc123 # 특정 run 결과 조회
1265
+ $ vibe inspect --json # VRIP 모드 (MCP용)
1266
+ `,
1267
+ run: `vibe run - 앱 실행
1268
+
1269
+ Usage: vibe run [options]
1270
+
1271
+ 프로젝트 설정에 따라 앱을 실행합니다.
1272
+ (현재 개발 중 - vibe_pm.run_app MCP 도구 사용)
1273
+ `,
1274
+ skills: `vibe skills - 스킬 번들 관리
1275
+
1276
+ Usage: vibe skills <command> [options]
1277
+
1278
+ Commands:
1279
+ list 사용 가능한 스킬 목록
1280
+ activate 프로젝트에 스킬 활성화
1281
+ deactivate 스킬 비활성화
1282
+ info 스킬 상세 정보
1283
+ status 스킬 번들 상태 확인
1284
+
1285
+ Options:
1286
+ --json JSON 형식 출력
1287
+
1288
+ Examples:
1289
+ $ vibe skills list
1290
+ $ vibe skills activate VRIP_INSTALL_MANIFEST_DOCTOR
1291
+ $ vibe skills activate # 모든 스킬 활성화
1292
+ $ vibe skills info VRIP_INSTALL_MANIFEST_DOCTOR
1293
+ $ vibe skills status --json
609
1294
  `,
610
1295
  };
611
1296
  // P0-1: Helper functions for --help priority handling
@@ -632,12 +1317,16 @@ function cmdHelp(subcommand) {
632
1317
  console.log(` ${c("green", "status")} 로컬 상태 확인`);
633
1318
  console.log(` ${c("green", "ticket")} 작업 티켓(Work Order) 생성`);
634
1319
  console.log(` ${c("green", "check")} 로컬 가드 검증 실행`);
1320
+ console.log(` ${c("green", "inspect")} 코드 검수 (VRIP 모드)`);
1321
+ console.log(` ${c("green", "exec")} Execution Engine 관리`);
1322
+ console.log(` ${c("green", "run")} 앱 실행`);
635
1323
  console.log(` ${c("green", "done")} 작업 티켓 종료/아카이브`);
636
1324
  console.log(` ${c("green", "config")} 설정 관리 (show/validate)`);
637
1325
  console.log(` ${c("green", "doctor")} 설치 상태 확인 및 진단`);
638
1326
  console.log(` ${c("green", "version")} 버전 정보 출력`);
639
1327
  console.log(` ${c("green", "reset")} 프로젝트 초기화 (runs/ 정리)`);
640
1328
  console.log(` ${c("green", "update")} 엔진 바이너리 업데이트`);
1329
+ console.log(` ${c("green", "skills")} 스킬 번들 관리`);
641
1330
  console.log(` ${c("green", "help")} 이 도움말 표시`);
642
1331
  console.log("");
643
1332
  console.log("Options:");
@@ -648,10 +1337,12 @@ function cmdHelp(subcommand) {
648
1337
  console.log("Examples:");
649
1338
  console.log(` ${c("dim", "$")} vibe setup # 통합 설치 (권장)`);
650
1339
  console.log(` ${c("dim", "$")} vibe setup --check # 설치 상태 확인`);
651
- console.log(` ${c("dim", "$")} vibe setup --help # setup 명령어 도움말`);
652
1340
  console.log(` ${c("dim", "$")} vibe ticket \"Fix auth\" # 작업 티켓 생성`);
653
1341
  console.log(` ${c("dim", "$")} vibe check # 푸시 전 로컬 검증`);
1342
+ console.log(` ${c("dim", "$")} vibe inspect --json # VRIP 모드 검수`);
1343
+ console.log(` ${c("dim", "$")} vibe exec status # 데몬 상태 확인`);
654
1344
  console.log(` ${c("dim", "$")} vibe doctor # 설치 진단`);
1345
+ console.log(` ${c("dim", "$")} vibe skills list # 스킬 목록`);
655
1346
  console.log(` ${c("dim", "$")} vibe reset --force # 프로젝트 초기화`);
656
1347
  console.log("");
657
1348
  console.log("More info:");
@@ -719,6 +1410,18 @@ async function main() {
719
1410
  case "update":
720
1411
  await cmdUpdate();
721
1412
  break;
1413
+ case "exec":
1414
+ await cmdExec();
1415
+ break;
1416
+ case "inspect":
1417
+ await cmdInspect();
1418
+ break;
1419
+ case "run":
1420
+ await cmdRun();
1421
+ break;
1422
+ case "skills":
1423
+ await cmdSkills();
1424
+ break;
722
1425
  case "help":
723
1426
  case "-h":
724
1427
  case "--help":