@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
@@ -0,0 +1,120 @@
1
+ // adapters/mcp-ts/src/tools/vibe_pm/save_rule.ts
2
+ // vibe_pm.save_rule - 프로젝트 워크플로우 규칙 저장
3
+ import { z } from "zod";
4
+ import * as fs from "fs/promises";
5
+ import * as path from "path";
6
+ import { getVibeRepoPaths } from "../../local-mode/paths.js";
7
+ // ============================================================
8
+ // Schema
9
+ // ============================================================
10
+ export const saveRuleInputSchema = z.object({
11
+ base_path: z.string().describe("프로젝트 루트 경로"),
12
+ rule_title: z.string().min(1).describe("규칙 제목 (예: 'API 변경 시 문서 갱신')"),
13
+ trigger: z.string().min(1).describe("언제 적용되는지 (예: 'API 엔드포인트를 수정할 때마다')"),
14
+ action: z.string().min(1).describe("무엇을 해야 하는지 (예: 'docs/api.md를 함께 갱신한다')"),
15
+ example: z.string().optional().describe("구체적인 예시 (선택)")
16
+ }).strict();
17
+ export const saveRuleOutputSchema = z.object({
18
+ status: z.enum(["saved", "error"]),
19
+ message: z.string(),
20
+ rules_file: z.string().describe(".vibe/project_rules.md 경로"),
21
+ rule_count: z.number().describe("현재 저장된 규칙 수")
22
+ }).strict();
23
+ // ============================================================
24
+ // Implementation
25
+ // ============================================================
26
+ const RULES_FILENAME = "project_rules.md";
27
+ /**
28
+ * 규칙 파일 경로 반환
29
+ */
30
+ function getRulesFilePath(basePath) {
31
+ const { vibeDir } = getVibeRepoPaths(basePath);
32
+ return path.join(vibeDir, RULES_FILENAME);
33
+ }
34
+ /**
35
+ * 규칙 파일 읽기 (없으면 기본 템플릿 반환)
36
+ */
37
+ async function readRulesFile(filePath) {
38
+ try {
39
+ return await fs.readFile(filePath, "utf-8");
40
+ }
41
+ catch {
42
+ // 파일이 없으면 기본 템플릿 반환
43
+ return `# Project Workflow Rules
44
+
45
+ > 이 파일은 Vibe PM이 자동으로 관리합니다.
46
+ > 프로젝트별 워크플로우 규칙이 여기에 저장됩니다.
47
+
48
+ ---
49
+
50
+ `;
51
+ }
52
+ }
53
+ /**
54
+ * 규칙 수 계산
55
+ */
56
+ function countRules(content) {
57
+ const lines = content.split("\n");
58
+ let count = 0;
59
+ for (const line of lines) {
60
+ if (line.startsWith("## ") && !line.includes("Project Workflow Rules")) {
61
+ count++;
62
+ }
63
+ }
64
+ return count;
65
+ }
66
+ /**
67
+ * 새 규칙을 마크다운 형식으로 포맷
68
+ */
69
+ function formatRule(input) {
70
+ const timestamp = new Date().toISOString().split("T")[0];
71
+ let rule = `## ${input.rule_title}
72
+
73
+ - **언제**: ${input.trigger}
74
+ - **무엇을**: ${input.action}
75
+ `;
76
+ if (input.example) {
77
+ rule += `- **예시**: ${input.example}
78
+ `;
79
+ }
80
+ rule += `- **등록일**: ${timestamp}
81
+
82
+ ---
83
+
84
+ `;
85
+ return rule;
86
+ }
87
+ /**
88
+ * 규칙 저장
89
+ */
90
+ export async function saveRule(input) {
91
+ const rulesFile = getRulesFilePath(input.base_path);
92
+ try {
93
+ // .vibe 디렉토리 확인/생성
94
+ const vibeDir = path.dirname(rulesFile);
95
+ await fs.mkdir(vibeDir, { recursive: true });
96
+ // 기존 규칙 읽기
97
+ let content = await readRulesFile(rulesFile);
98
+ // 새 규칙 추가
99
+ const newRule = formatRule(input);
100
+ content += newRule;
101
+ // 파일 저장
102
+ await fs.writeFile(rulesFile, content, "utf-8");
103
+ const ruleCount = countRules(content);
104
+ return {
105
+ status: "saved",
106
+ message: `규칙 '${input.rule_title}'이(가) 저장되었습니다.`,
107
+ rules_file: rulesFile,
108
+ rule_count: ruleCount
109
+ };
110
+ }
111
+ catch (e) {
112
+ const msg = e instanceof Error ? e.message : String(e);
113
+ return {
114
+ status: "error",
115
+ message: `규칙 저장 실패: ${msg}`,
116
+ rules_file: rulesFile,
117
+ rule_count: 0
118
+ };
119
+ }
120
+ }
@@ -1,10 +1,11 @@
1
1
  // adapters/mcp-ts/src/tools/vibe_pm/search_oss.ts
2
2
  // vibe_pm.search_oss - Search OSS repos (gh) and write evidence under runs/<run_id>/export/
3
3
  import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
4
- import { runEngine } from "../../engine.js";
4
+ import { runPythonCli } from "../../engine.js";
5
5
  import { safeJsonParse } from "../../cli.js";
6
6
  import { validateToolInput } from "../../security/input-validator.js";
7
7
  import { resolveProjectId, resolveRunId } from "./context.js";
8
+ import { classifyPythonError } from "./python_error.js";
8
9
  function toCsv(values) {
9
10
  return values
10
11
  .map((v) => (typeof v === "string" ? v.trim() : ""))
@@ -64,9 +65,10 @@ export async function searchOss(input) {
64
65
  const writeEvidence = input?.write_evidence !== false;
65
66
  if (!writeEvidence)
66
67
  cmd.push("--no-write-evidence");
67
- const res = await runEngine("vibecoding-helper", cmd, { timeoutMs: 180_000 });
68
+ const res = await runPythonCli(cmd, { timeoutMs: 180_000 });
68
69
  if (res.code !== 0) {
69
- throw new Error(`search-oss failed: ${res.stderr || res.stdout || `exit_code=${res.code}`}`);
70
+ const classified = classifyPythonError(res.stderr ?? "", res.code, "OSS 검색");
71
+ throw new Error(`${classified.issueCode}: ${classified.summary}`);
70
72
  }
71
73
  const parsed = safeJsonParse(res.stdout);
72
74
  if (!parsed.ok) {
@@ -0,0 +1,185 @@
1
+ // adapters/mcp-ts/src/tools/vibe_pm/spec_rag.ts
2
+ // Spec RAG: Auto-inject SSOT context into work orders
3
+ // Reference: docs/DEV_SPEC/0124/SPEC_RAG_WORK_PROTOCOL
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { runKceRetrieve, runKceStatus } from "./kce/preflight.js";
7
+ /**
8
+ * Priority-ordered list of SSOT documents to inject
9
+ */
10
+ const SPEC_DOCS = [
11
+ "docs/ssot/DECISION_SSOT.md",
12
+ "docs/ssot/QUALITY_SSOT.md",
13
+ "docs/ssot/MCP_SSOT.md",
14
+ "docs/SSOT.md"
15
+ ];
16
+ /**
17
+ * Get spec context for injection into work orders.
18
+ *
19
+ * Strategy:
20
+ * 1. Try KCE retrieval (if status=READY)
21
+ * 2. Fallback to direct file reading
22
+ * 3. Return empty if both fail
23
+ */
24
+ export async function getSpecContext(opts) {
25
+ const maxChars = opts.maxChars ?? 2500;
26
+ const maxItems = opts.maxItems ?? 4;
27
+ const basePath = opts.basePath ?? process.cwd();
28
+ // Strategy 1: Try KCE retrieval
29
+ try {
30
+ const status = await runKceStatus();
31
+ if (status.readiness_state === "READY") {
32
+ const result = await runKceRetrieve({
33
+ query: opts.query,
34
+ maxItems,
35
+ limit: 10,
36
+ maxTotalChars: maxChars,
37
+ maxChunkChars: Math.min(800, Math.floor(maxChars / maxItems)),
38
+ pathPrefix: "docs/ssot"
39
+ });
40
+ if (result.context_block.trim()) {
41
+ const referencedDocs = (result.hits ?? [])
42
+ .map((h) => h.path)
43
+ .filter((p) => p.endsWith(".md"));
44
+ return {
45
+ context_block: buildSpecBlock(result.context_block, referencedDocs),
46
+ source: "kce",
47
+ referenced_docs: referencedDocs
48
+ };
49
+ }
50
+ }
51
+ }
52
+ catch {
53
+ // KCE unavailable, fall through to fallback
54
+ }
55
+ // Strategy 2: Fallback to direct file reading
56
+ try {
57
+ const { content, docs } = readSpecDocsFallback(basePath, maxChars);
58
+ if (content.trim()) {
59
+ return {
60
+ context_block: buildSpecBlock(content, docs),
61
+ source: "fallback",
62
+ referenced_docs: docs
63
+ };
64
+ }
65
+ }
66
+ catch {
67
+ // Fallback also failed
68
+ }
69
+ // Strategy 3: Return empty
70
+ return {
71
+ context_block: "",
72
+ source: "empty",
73
+ referenced_docs: []
74
+ };
75
+ }
76
+ /**
77
+ * Build the [SPEC_CONTEXT] block for injection
78
+ */
79
+ export function buildSpecBlock(content, docs) {
80
+ if (!content.trim())
81
+ return "";
82
+ const lines = [];
83
+ lines.push("[SPEC_CONTEXT]");
84
+ lines.push(content.trim());
85
+ if (docs.length > 0) {
86
+ lines.push("");
87
+ lines.push("[참조 문서]");
88
+ for (const doc of docs.slice(0, 6)) {
89
+ const basename = path.basename(doc);
90
+ lines.push(`- ${basename}`);
91
+ }
92
+ }
93
+ return lines.join("\n");
94
+ }
95
+ /**
96
+ * Fallback: Read spec docs directly from filesystem
97
+ */
98
+ function readSpecDocsFallback(basePath, maxChars) {
99
+ const chunks = [];
100
+ const docs = [];
101
+ let totalChars = 0;
102
+ const perDocLimit = Math.floor(maxChars / SPEC_DOCS.length);
103
+ for (const docPath of SPEC_DOCS) {
104
+ if (totalChars >= maxChars)
105
+ break;
106
+ const fullPath = path.join(basePath, docPath);
107
+ if (!fs.existsSync(fullPath))
108
+ continue;
109
+ try {
110
+ const content = fs.readFileSync(fullPath, "utf-8");
111
+ const extracted = extractRelevantSections(content, perDocLimit);
112
+ if (extracted.trim()) {
113
+ const basename = path.basename(docPath);
114
+ chunks.push(`### ${basename}\n${extracted.trim()}`);
115
+ docs.push(docPath);
116
+ totalChars += extracted.length;
117
+ }
118
+ }
119
+ catch {
120
+ // Skip unreadable files
121
+ }
122
+ }
123
+ return {
124
+ content: chunks.join("\n\n"),
125
+ docs
126
+ };
127
+ }
128
+ /**
129
+ * Extract relevant sections from a markdown document
130
+ * Focus on rules, constraints, and key definitions
131
+ */
132
+ function extractRelevantSections(content, maxChars) {
133
+ // Priority headers to extract
134
+ const priorityPatterns = [
135
+ /^#+\s*(?:규칙|Rules|제약|Constraints|필수|Required|금지|Prohibited)/im,
136
+ /^#+\s*(?:개요|Overview|핵심|Core|원칙|Principles)/im,
137
+ /^#+\s*(?:정의|Definitions|용어|Terms)/im
138
+ ];
139
+ const lines = content.split("\n");
140
+ const sections = [];
141
+ let currentSection = [];
142
+ let inPrioritySection = false;
143
+ let sectionHeader = "";
144
+ for (const line of lines) {
145
+ // Check if this is a header
146
+ const isHeader = /^#+\s+/.test(line);
147
+ if (isHeader) {
148
+ // Save previous section if it was a priority section
149
+ if (inPrioritySection && currentSection.length > 0) {
150
+ sections.push([sectionHeader, ...currentSection].join("\n"));
151
+ }
152
+ // Check if this header is a priority section
153
+ inPrioritySection = priorityPatterns.some((p) => p.test(line));
154
+ sectionHeader = line;
155
+ currentSection = [];
156
+ }
157
+ else if (inPrioritySection) {
158
+ currentSection.push(line);
159
+ }
160
+ }
161
+ // Don't forget the last section
162
+ if (inPrioritySection && currentSection.length > 0) {
163
+ sections.push([sectionHeader, ...currentSection].join("\n"));
164
+ }
165
+ // If no priority sections found, extract the first N characters as overview
166
+ if (sections.length === 0) {
167
+ const overview = content.slice(0, Math.min(maxChars, 500)).trim();
168
+ if (overview) {
169
+ // Try to end at a sentence boundary
170
+ const lastPeriod = overview.lastIndexOf(".");
171
+ const lastNewline = overview.lastIndexOf("\n");
172
+ const cutoff = Math.max(lastPeriod, lastNewline);
173
+ return cutoff > 100 ? overview.slice(0, cutoff + 1) : overview;
174
+ }
175
+ return "";
176
+ }
177
+ // Combine sections up to maxChars
178
+ let result = "";
179
+ for (const section of sections) {
180
+ if (result.length + section.length > maxChars)
181
+ break;
182
+ result += (result ? "\n\n" : "") + section;
183
+ }
184
+ return result;
185
+ }
@@ -1,11 +1,14 @@
1
1
  // adapters/mcp-ts/src/tools/vibe_pm/status.ts
2
2
  // vibe_pm.status - Project status query
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
3
5
  import { runEngineWithCache } from "../../engine.js";
4
6
  import { safeJsonParse } from "../../cli.js";
5
7
  import { resolveRunId, resolveProjectId, getRunContext, readRunState } from "./context.js";
6
8
  import { formatMode, formatPhase, signalToReview } from "./pm_language.js";
7
9
  import { validateToolInput } from "../../security/input-validator.js";
8
10
  import { SpecHighValidateOutputSchema } from "../../generated/spec_high_validate_output.js";
11
+ import { IngressSummaryFileSchema } from "../../generated/ingress_summary_file.js";
9
12
  /**
10
13
  * vibe_pm.status - Query current project status
11
14
  *
@@ -13,8 +16,7 @@ import { SpecHighValidateOutputSchema } from "../../generated/spec_high_validate
13
16
  * → spec-high validate <run_id>
14
17
  * → status summary generation
15
18
  */
16
- export async function status(input) {
17
- const basePath = process.cwd();
19
+ export async function status(input, basePath = process.cwd()) {
18
20
  validateToolInput({ project_id: input.project_id });
19
21
  // Resolve run_id
20
22
  const { run_id, is_new } = resolveRunId(input.project_id, basePath);
@@ -77,7 +79,17 @@ export async function status(input) {
77
79
  // Collect risks
78
80
  const risks = validateResult?.risks ?? [];
79
81
  // Determine next action based on state
80
- const nextAction = determineNextAction(phase, decisionsPending, lastReviewStatus);
82
+ let nextAction = determineNextAction(phase, decisionsPending, lastReviewStatus);
83
+ // P27: deterministic auto-routing for "pre-work clarification" (based on ingress signals)
84
+ const routed = maybeAutoRouteForClarification({
85
+ runDirAbs: context.runs_path,
86
+ phase,
87
+ decisionsPending
88
+ });
89
+ if (routed) {
90
+ risks.push(routed.risk_note);
91
+ nextAction = routed.next_action;
92
+ }
81
93
  return {
82
94
  project_id,
83
95
  project: {
@@ -95,6 +107,41 @@ export async function status(input) {
95
107
  next_action: nextAction
96
108
  };
97
109
  }
110
+ function maybeAutoRouteForClarification(args) {
111
+ // Never override when user still has pending approvals.
112
+ if (args.decisionsPending > 0)
113
+ return null;
114
+ // Only relevant before work order is issued.
115
+ if (!(args.phase === "decision" || args.phase === "briefing" || args.phase === "init"))
116
+ return null;
117
+ const summaryAbs = path.join(args.runDirAbs, "ingress", "ingress_summary.json");
118
+ if (!fs.existsSync(summaryAbs))
119
+ return null;
120
+ let raw;
121
+ try {
122
+ raw = JSON.parse(fs.readFileSync(summaryAbs, "utf-8"));
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ const parsed = IngressSummaryFileSchema.safeParse(raw);
128
+ if (!parsed.success)
129
+ return null;
130
+ const recommended = parsed.data.high_level.clarification_recommended === true;
131
+ if (!recommended)
132
+ return null;
133
+ // Treat as completed if seed exists (protocol done).
134
+ const seedAbs = path.join(args.runDirAbs, "bridge", "work_order_seed.json");
135
+ if (fs.existsSync(seedAbs))
136
+ return null;
137
+ return {
138
+ next_action: {
139
+ tool: "vibe_pm.bridge_generate_references",
140
+ label: "다음 할 일: 예시를 고르고 작업 방향을 고정하기"
141
+ },
142
+ risk_note: "작업 방향이 아직 충분히 고정되지 않았습니다. 예시를 한 번 선택하면 다음 단계가 빨라집니다."
143
+ };
144
+ }
98
145
  function determineNextAction(phase, decisionsPending, lastReviewStatus) {
99
146
  // If there are pending decisions
100
147
  if (decisionsPending > 0) {
@@ -20,8 +20,8 @@ export async function submitDecision(input) {
20
20
  const project_id = resolveProjectId(run_id, basePath);
21
21
  // Format answer text (combine choice with note if provided)
22
22
  const answerText = input.note ? `${input.choice}: ${input.note}` : input.choice;
23
- // Step 1: Submit answer
24
- const answerResult = await runEngine("vibecoding-helper", ["answer", run_id, "--text", answerText], { timeoutMs: 120_000 });
23
+ // Step 1: Submit answer via spec-high
24
+ const answerResult = await runEngine("spec-high", ["--root", "engines/spec_high", "answer", run_id, "--text", answerText], { timeoutMs: 120_000 });
25
25
  if (answerResult.code !== 0) {
26
26
  throw new Error(`결재 제출 실패: ${answerResult.stderr || "알 수 없는 오류"}`);
27
27
  }
@@ -183,6 +183,14 @@ import { SearchOssInputSchema } from "../../generated/search_oss_input.js";
183
183
  import { SearchOssOutputSchema } from "../../generated/search_oss_output.js";
184
184
  import { ZoektEvidenceInputSchema } from "../../generated/zoekt_evidence_input.js";
185
185
  import { ZoektEvidenceOutputSchema } from "../../generated/zoekt_evidence_output.js";
186
+ import { IngressInputSchema } from "../../generated/ingress_input.js";
187
+ import { IngressOutputSchema } from "../../generated/ingress_output.js";
188
+ import { BridgeGenerateReferencesInputSchema } from "../../generated/bridge_generate_references_input.js";
189
+ import { BridgeGenerateReferencesOutputSchema } from "../../generated/bridge_generate_references_output.js";
190
+ import { BridgeConfirmReferenceInputSchema } from "../../generated/bridge_confirm_reference_input.js";
191
+ import { BridgeConfirmReferenceOutputSchema } from "../../generated/bridge_confirm_reference_output.js";
192
+ import { BridgeBuildSeedInputSchema } from "../../generated/bridge_build_seed_input.js";
193
+ import { BridgeBuildSeedOutputSchema } from "../../generated/bridge_build_seed_output.js";
186
194
  export const finalizeWorkInputSchema = FinalizeWorkInputSchema;
187
195
  // Output schema is generated from JSON Schema SSOT.
188
196
  export const finalizeWorkOutputSchema = FinalizeWorkOutputSchema;
@@ -222,6 +230,26 @@ export const zoektEvidenceInputSchema = ZoektEvidenceInputSchema;
222
230
  // Output schema is generated from JSON Schema SSOT.
223
231
  export const zoektEvidenceOutputSchema = ZoektEvidenceOutputSchema;
224
232
  // ============================================================
233
+ // vibe_pm.ingress
234
+ // ============================================================
235
+ export const ingressInputSchema = IngressInputSchema;
236
+ export const ingressOutputSchema = IngressOutputSchema;
237
+ // ============================================================
238
+ // vibe_pm.bridge_generate_references
239
+ // ============================================================
240
+ export const bridgeGenerateReferencesInputSchema = BridgeGenerateReferencesInputSchema;
241
+ export const bridgeGenerateReferencesOutputSchema = BridgeGenerateReferencesOutputSchema;
242
+ // ============================================================
243
+ // vibe_pm.bridge_confirm_reference
244
+ // ============================================================
245
+ export const bridgeConfirmReferenceInputSchema = BridgeConfirmReferenceInputSchema;
246
+ export const bridgeConfirmReferenceOutputSchema = BridgeConfirmReferenceOutputSchema;
247
+ // ============================================================
248
+ // vibe_pm.bridge_build_seed
249
+ // ============================================================
250
+ export const bridgeBuildSeedInputSchema = BridgeBuildSeedInputSchema;
251
+ export const bridgeBuildSeedOutputSchema = BridgeBuildSeedOutputSchema;
252
+ // ============================================================
225
253
  // vibe_pm.doctor / vibe_pm.update (Output SSOT only)
226
254
  // ============================================================
227
255
  export const doctorOutputSchema = DoctorOutputSchema;
@@ -1,10 +1,11 @@
1
1
  // adapters/mcp-ts/src/tools/vibe_pm/undo_last_task.ts
2
2
  // vibe_pm.undo_last_task - Time machine rollback via git revert
3
+ //
4
+ // All subprocess invocations go through cli_invoker for centralized management.
3
5
  import * as fs from "node:fs";
4
6
  import * as path from "node:path";
5
- import { exec } from "node:child_process";
6
- import { promisify } from "node:util";
7
- const execAsync = promisify(exec);
7
+ import { invokeGit } from "../../runtime/cli_invoker.js";
8
+ import { isGitConflictError } from "../../errors.js";
8
9
  /**
9
10
  * vibe_pm.undo_last_task - Rollback recent commits via git revert
10
11
  *
@@ -43,24 +44,25 @@ export async function undoLastTask(input) {
43
44
  const revertedTasks = [];
44
45
  const revertErrors = [];
45
46
  for (const commit of commits) {
46
- try {
47
- await execAsync(`git revert --no-edit ${commit.hash}`, { cwd: basePath });
47
+ const result = await invokeGit(["revert", "--no-edit", commit.hash], basePath);
48
+ if (result.exitCode === 0) {
48
49
  revertedTasks.push({
49
50
  commit_hash: commit.shortHash,
50
51
  task_summary: commit.subject,
51
52
  timestamp: commit.timestamp
52
53
  });
53
54
  }
54
- catch (err) {
55
- const errMsg = err instanceof Error ? err.message : String(err);
55
+ else {
56
+ const errMsg = result.stderr || result.stdout;
56
57
  revertErrors.push(`${commit.shortHash}: ${errMsg}`);
57
58
  // If revert fails due to conflicts, abort and stop
58
- if (errMsg.includes("conflict") || errMsg.includes("CONFLICT")) {
59
- try {
60
- await execAsync("git revert --abort", { cwd: basePath });
61
- }
62
- catch {
63
- // Ignore abort errors
59
+ // Check for conflict indicators (exit code 1 with conflict message)
60
+ // P1-4: Use centralized error pattern
61
+ if (isGitConflictError(errMsg) || result.exitCode === 1) {
62
+ const abortResult = await invokeGit(["revert", "--abort"], basePath);
63
+ if (abortResult.exitCode !== 0) {
64
+ // Abort failed - critical state inconsistency
65
+ revertErrors.push(`[CRITICAL] git revert --abort 실패. 수동 복구 필요: git status 확인 후 git revert --abort 또는 git revert --continue 실행`);
64
66
  }
65
67
  break;
66
68
  }
@@ -99,9 +101,12 @@ export async function undoLastTask(input) {
99
101
  * Get recent commits from git log
100
102
  */
101
103
  async function getRecentCommits(basePath, count) {
102
- const { stdout } = await execAsync(`git log --oneline -n ${count} --format="%H|%h|%s|%ci"`, { cwd: basePath });
104
+ const result = await invokeGit(["log", "--oneline", "-n", String(count), "--format=%H|%h|%s|%ci"], basePath);
105
+ if (result.exitCode !== 0) {
106
+ throw new Error(result.stderr || "git log failed");
107
+ }
103
108
  const commits = [];
104
- const lines = stdout.trim().split("\n").filter(Boolean);
109
+ const lines = result.stdout.trim().split("\n").filter(Boolean);
105
110
  for (const line of lines) {
106
111
  const [hash, shortHash, subject, timestamp] = line.split("|");
107
112
  if (hash && shortHash && subject) {
@@ -119,13 +124,11 @@ async function getRecentCommits(basePath, count) {
119
124
  * Get current HEAD commit hash
120
125
  */
121
126
  async function getCurrentCommit(basePath) {
122
- try {
123
- const { stdout } = await execAsync("git rev-parse --short HEAD", { cwd: basePath });
124
- return stdout.trim();
125
- }
126
- catch {
127
+ const result = await invokeGit(["rev-parse", "--short", "HEAD"], basePath);
128
+ if (result.exitCode !== 0) {
127
129
  return "unknown";
128
130
  }
131
+ return result.stdout.trim();
129
132
  }
130
133
  /**
131
134
  * Add rollback entry to DEV_LOG