@vibecodetown/mcp-server 2.2.0 → 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 (70) hide show
  1. package/README.md +10 -10
  2. package/build/auth/index.js +0 -2
  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/dx/activity.js +26 -3
  9. package/build/engine.js +151 -0
  10. package/build/errors.js +107 -0
  11. package/build/generated/bridge_build_seed_input.js +2 -0
  12. package/build/generated/bridge_build_seed_output.js +2 -0
  13. package/build/generated/bridge_confirm_reference_input.js +2 -0
  14. package/build/generated/bridge_confirm_reference_output.js +2 -0
  15. package/build/generated/bridge_confirmed_reference_file.js +2 -0
  16. package/build/generated/bridge_generate_references_input.js +2 -0
  17. package/build/generated/bridge_generate_references_output.js +2 -0
  18. package/build/generated/bridge_references_file.js +2 -0
  19. package/build/generated/bridge_work_order_seed_file.js +2 -0
  20. package/build/generated/contracts_bundle_info.js +3 -3
  21. package/build/generated/index.js +14 -0
  22. package/build/generated/ingress_input.js +2 -0
  23. package/build/generated/ingress_output.js +2 -0
  24. package/build/generated/ingress_resolution_file.js +2 -0
  25. package/build/generated/ingress_summary_file.js +2 -0
  26. package/build/generated/message_template_id_mapping_file.js +2 -0
  27. package/build/generated/run_app_input.js +1 -1
  28. package/build/index.js +4 -3
  29. package/build/local-mode/paths.js +1 -0
  30. package/build/local-mode/setup.js +21 -1
  31. package/build/path-utils.js +68 -0
  32. package/build/runtime/cli_invoker.js +1 -1
  33. package/build/tools/vibe_pm/advisory_review.js +5 -3
  34. package/build/tools/vibe_pm/bridge_build_seed.js +164 -0
  35. package/build/tools/vibe_pm/bridge_confirm_reference.js +91 -0
  36. package/build/tools/vibe_pm/bridge_generate_references.js +258 -0
  37. package/build/tools/vibe_pm/briefing.js +27 -3
  38. package/build/tools/vibe_pm/context.js +79 -0
  39. package/build/tools/vibe_pm/create_work_order.js +200 -3
  40. package/build/tools/vibe_pm/doctor.js +95 -0
  41. package/build/tools/vibe_pm/entity_gate/preflight.js +8 -3
  42. package/build/tools/vibe_pm/export_output.js +14 -13
  43. package/build/tools/vibe_pm/finalize_work.js +78 -40
  44. package/build/tools/vibe_pm/get_decision.js +2 -2
  45. package/build/tools/vibe_pm/index.js +128 -42
  46. package/build/tools/vibe_pm/ingress.js +645 -0
  47. package/build/tools/vibe_pm/ingress_gate.js +116 -0
  48. package/build/tools/vibe_pm/inspect_code.js +90 -20
  49. package/build/tools/vibe_pm/kce/doc_usage.js +4 -9
  50. package/build/tools/vibe_pm/kce/on_finalize.js +2 -2
  51. package/build/tools/vibe_pm/kce/preflight.js +11 -7
  52. package/build/tools/vibe_pm/memory_status.js +11 -8
  53. package/build/tools/vibe_pm/memory_sync.js +11 -8
  54. package/build/tools/vibe_pm/pm_language.js +17 -16
  55. package/build/tools/vibe_pm/python_error.js +115 -0
  56. package/build/tools/vibe_pm/run_app.js +169 -43
  57. package/build/tools/vibe_pm/run_app_podman.js +64 -2
  58. package/build/tools/vibe_pm/search_oss.js +5 -3
  59. package/build/tools/vibe_pm/spec_rag.js +185 -0
  60. package/build/tools/vibe_pm/status.js +50 -3
  61. package/build/tools/vibe_pm/submit_decision.js +2 -2
  62. package/build/tools/vibe_pm/types.js +28 -0
  63. package/build/tools/vibe_pm/undo_last_task.js +9 -2
  64. package/build/tools/vibe_pm/waiter_mapping.js +155 -0
  65. package/build/tools/vibe_pm/zoekt_evidence.js +5 -3
  66. package/build/tools.js +13 -5
  67. package/build/vibe-cli.js +245 -7
  68. package/package.json +5 -4
  69. package/skills/VRIP_INSTALL_MANIFEST_DOCTOR.skill.md +288 -0
  70. package/skills/index.json +14 -0
@@ -2,11 +2,13 @@
2
2
  // vibe_pm.doctor - Installation health check and auto-repair
3
3
  import { DoctorInputSchema } from "../../generated/doctor_input.js";
4
4
  import { doctor as runDoctor, healthCheck, checkForUpdates } from "../../bootstrap/doctor.js";
5
+ import { getSkillsHealth } from "../../bootstrap/skills-installer.js";
5
6
  import { CONTRACTS_BUNDLE_SHA256, CONTRACTS_SCHEMA_COUNT, CONTRACTS_VERSION } from "../../generated/contracts_bundle_info.js";
6
7
  import { getAuthGate } from "../../auth/index.js";
7
8
  import { resolveProjectId } from "./context.js";
8
9
  import { memoryStatus } from "./memory_status.js";
9
10
  import { memorySync } from "./memory_sync.js";
11
+ import { runPythonCli, findPythonRepoRoot } from "../../engine.js";
10
12
  // ============================================================
11
13
  // Input/Output Types
12
14
  // ============================================================
@@ -26,6 +28,36 @@ function mapEngineStatus(status) {
26
28
  return "손상됨";
27
29
  }
28
30
  }
31
+ /**
32
+ * Check Python CLI health
33
+ * Verifies that `python -m vibecoding_helper` can be executed
34
+ */
35
+ async function checkPythonCli() {
36
+ const cwd = process.cwd();
37
+ const detectedRoot = findPythonRepoRoot(cwd);
38
+ try {
39
+ const result = await runPythonCli(["--help"], { timeoutMs: 10_000 });
40
+ if (result.code === 0) {
41
+ return {
42
+ status: "정상",
43
+ message: "Python CLI 정상 동작",
44
+ pythonpath_detected: detectedRoot
45
+ };
46
+ }
47
+ return {
48
+ status: "오류",
49
+ message: `Python CLI 오류: ${result.stderr?.slice(0, 100) ?? "알 수 없는 오류"}`,
50
+ pythonpath_detected: detectedRoot
51
+ };
52
+ }
53
+ catch (e) {
54
+ return {
55
+ status: "오류",
56
+ message: e instanceof Error ? e.message : "알 수 없는 오류",
57
+ pythonpath_detected: detectedRoot
58
+ };
59
+ }
60
+ }
29
61
  // ============================================================
30
62
  // Doctor Implementation
31
63
  // ============================================================
@@ -57,6 +89,23 @@ export async function doctor(input) {
57
89
  return null;
58
90
  }
59
91
  }
92
+ function getSkillBundleStatus() {
93
+ const skillsHealth = getSkillsHealth();
94
+ let status = "OK";
95
+ if (!skillsHealth.installed || skillsHealth.summary.missing > 0 || skillsHealth.summary.corrupted > 0) {
96
+ status = "ERROR";
97
+ }
98
+ else if (skillsHealth.version !== skillsHealth.version) {
99
+ // Note: In a real scenario, compare with expected version from registry
100
+ status = "WARN";
101
+ }
102
+ return {
103
+ status,
104
+ version: skillsHealth.version,
105
+ installed: skillsHealth.summary.ok,
106
+ total: skillsHealth.summary.total
107
+ };
108
+ }
60
109
  try {
61
110
  // Step 1: Quick health check
62
111
  const quickCheck = await healthCheck();
@@ -68,6 +117,8 @@ export async function doctor(input) {
68
117
  version: e.version,
69
118
  current_version: e.currentVersion
70
119
  }));
120
+ // Python CLI check
121
+ const pythonCliStatus = await checkPythonCli();
71
122
  // Local Memory check (best-effort)
72
123
  let localMemory = await getLocalMemoryStatus();
73
124
  if (autoFix && localMemory?.status === "NEEDS_SYNC") {
@@ -91,6 +142,23 @@ export async function doctor(input) {
91
142
  // ignore
92
143
  }
93
144
  }
145
+ // Check if Python CLI has issues
146
+ if (pythonCliStatus.status === "오류") {
147
+ return {
148
+ status: "NEEDS_ATTENTION",
149
+ contracts,
150
+ summary: "Python CLI에 문제가 있습니다.",
151
+ engines,
152
+ python_cli: pythonCliStatus,
153
+ skills: getSkillBundleStatus(),
154
+ local_memory: localMemory,
155
+ actions_taken: actionsTaken.length > 0 ? actionsTaken : undefined,
156
+ next_action: {
157
+ type: "MANUAL_FIX",
158
+ message: `Python CLI 오류: ${pythonCliStatus.message}. PYTHONPATH 설정을 확인하세요.`
159
+ }
160
+ };
161
+ }
94
162
  if (localMemory && localMemory.status !== "READY") {
95
163
  const msg = localMemory.status === "NEEDS_SYNC"
96
164
  ? "Local Memory 갱신이 필요합니다. vibe_pm.memory_sync를 실행해 보세요."
@@ -102,6 +170,8 @@ export async function doctor(input) {
102
170
  contracts,
103
171
  summary: "엔진은 정상입니다. 다만 Local Memory 준비가 필요합니다.",
104
172
  engines,
173
+ python_cli: pythonCliStatus,
174
+ skills: getSkillBundleStatus(),
105
175
  local_memory: localMemory,
106
176
  actions_taken: actionsTaken.length > 0 ? actionsTaken : undefined,
107
177
  next_action: {
@@ -115,6 +185,8 @@ export async function doctor(input) {
115
185
  contracts,
116
186
  summary: "모든 엔진이 정상적으로 설치되어 있습니다.",
117
187
  engines,
188
+ python_cli: pythonCliStatus,
189
+ skills: getSkillBundleStatus(),
118
190
  local_memory: localMemory,
119
191
  actions_taken: actionsTaken.length > 0 ? actionsTaken : undefined,
120
192
  next_action: {
@@ -136,6 +208,8 @@ export async function doctor(input) {
136
208
  version: info.version,
137
209
  current_version: info.currentVersion
138
210
  }));
211
+ // Python CLI check (after auto-fix)
212
+ const pythonCliStatus = await checkPythonCli();
139
213
  if (doctorResult.status === "OK") {
140
214
  actionsTaken.push("문제 자동 수정 완료");
141
215
  // Local Memory check (best-effort)
@@ -172,6 +246,8 @@ export async function doctor(input) {
172
246
  contracts,
173
247
  summary: "엔진은 정상입니다. 다만 Local Memory 준비가 필요합니다.",
174
248
  engines,
249
+ python_cli: pythonCliStatus,
250
+ skills: getSkillBundleStatus(),
175
251
  actions_taken: actionsTaken,
176
252
  local_memory: localMemory,
177
253
  next_action: {
@@ -185,6 +261,8 @@ export async function doctor(input) {
185
261
  contracts,
186
262
  summary: "문제를 발견하고 자동으로 수정했습니다.",
187
263
  engines,
264
+ python_cli: pythonCliStatus,
265
+ skills: getSkillBundleStatus(),
188
266
  actions_taken: actionsTaken,
189
267
  local_memory: localMemory,
190
268
  next_action: {
@@ -201,6 +279,8 @@ export async function doctor(input) {
201
279
  contracts,
202
280
  summary: "일부 엔진 업데이트가 필요합니다.",
203
281
  engines,
282
+ python_cli: pythonCliStatus,
283
+ skills: getSkillBundleStatus(),
204
284
  actions_taken: actionsTaken,
205
285
  local_memory: localMemory,
206
286
  next_action: {
@@ -216,6 +296,8 @@ export async function doctor(input) {
216
296
  contracts,
217
297
  summary: doctorResult.error ?? "알 수 없는 오류가 발생했습니다.",
218
298
  engines,
299
+ python_cli: pythonCliStatus,
300
+ skills: getSkillBundleStatus(),
219
301
  actions_taken: actionsTaken,
220
302
  local_memory: localMemory,
221
303
  next_action: {
@@ -231,6 +313,7 @@ export async function doctor(input) {
231
313
  version: e.version,
232
314
  current_version: e.currentVersion
233
315
  }));
316
+ const pythonCliStatus = await checkPythonCli();
234
317
  const issueCount = engines.filter((e) => e.status !== "정상").length;
235
318
  const localMemory = await getLocalMemoryStatus();
236
319
  return {
@@ -238,6 +321,8 @@ export async function doctor(input) {
238
321
  contracts,
239
322
  summary: `${issueCount}개의 엔진에 문제가 있습니다.`,
240
323
  engines,
324
+ python_cli: pythonCliStatus,
325
+ skills: getSkillBundleStatus(),
241
326
  local_memory: localMemory,
242
327
  next_action: {
243
328
  type: "MANUAL_FIX",
@@ -247,11 +332,21 @@ export async function doctor(input) {
247
332
  }
248
333
  catch (e) {
249
334
  const localMemory = await getLocalMemoryStatus();
335
+ // Try to get Python CLI status even in error state
336
+ let pythonCliStatus;
337
+ try {
338
+ pythonCliStatus = await checkPythonCli();
339
+ }
340
+ catch {
341
+ // ignore
342
+ }
250
343
  return {
251
344
  status: "ERROR",
252
345
  contracts,
253
346
  summary: e instanceof Error ? e.message : "알 수 없는 오류",
254
347
  engines: [],
348
+ python_cli: pythonCliStatus,
349
+ skills: getSkillBundleStatus(),
255
350
  local_memory: localMemory,
256
351
  next_action: {
257
352
  type: "CONTACT_SUPPORT",
@@ -3,9 +3,10 @@
3
3
  import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
5
  import { z } from "zod";
6
- import { runEngine } from "../../../engine.js";
6
+ import { runPythonCli } from "../../../engine.js";
7
7
  import { safeJsonParse } from "../../../cli.js";
8
8
  import { resolveRunDir } from "../context.js";
9
+ import { classifyPythonError } from "../python_error.js";
9
10
  const EntityGateReportSchema = z.object({
10
11
  status: z.enum(["OK", "FAIL"]),
11
12
  errors: z.array(z.string()),
@@ -38,9 +39,13 @@ function kceEventsPath(run_id, basePath) {
38
39
  export async function runEntityGate(args) {
39
40
  const eventsPath = args.writeEvidence && args.run_id ? kceEventsPath(args.run_id, args.basePath) : null;
40
41
  try {
41
- const { code, stdout, stderr } = await runEngine("vibecoding-helper", ["entity-gate"], { timeoutMs: 60_000 });
42
+ const { code, stdout, stderr } = await runPythonCli(["entity-gate"], { timeoutMs: 60_000 });
42
43
  if (code !== 0 && !stdout.trim()) {
43
- throw new Error(`entity-gate failed: ${stderr || `exit_code=${code}`}`);
44
+ const classified = classifyPythonError(stderr ?? "", code, "Entity Gate");
45
+ const hint = classified.issueCode === "python_module_not_found"
46
+ ? " (PYTHONPATH 문제 - vibe_pm.doctor 실행 권장)"
47
+ : "";
48
+ throw new Error(`${classified.issueCode}: ${classified.summary}${hint}`);
44
49
  }
45
50
  const parsed = safeJsonParse(stdout);
46
51
  if (!parsed.ok)
@@ -2,14 +2,13 @@
2
2
  // vibe_pm.export_output - Export a feature into a chosen output format via Output Adapters
3
3
  import * as path from "node:path";
4
4
  import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
5
- import { runEngine } from "../../engine.js";
5
+ import { runPythonCli } from "../../engine.js";
6
6
  import { safeJsonParse } from "../../cli.js";
7
7
  import { validateToolInput } from "../../security/input-validator.js";
8
8
  import { isPathForbidden, validatePath } from "../../security/path-policy.js";
9
9
  import { resolveProjectId, resolveRunId, resolveRunDir } from "./context.js";
10
- function normalizeRelPosix(p) {
11
- return p.replace(/\\/g, "/").replace(/^\/+/, "");
12
- }
10
+ import { classifyPythonError } from "./python_error.js";
11
+ import { normalizeRelativePosix } from "../../path-utils.js";
13
12
  function resolveDefaultOutputDir(target) {
14
13
  const t = (target ?? "").trim();
15
14
  return t ? `export/${t}` : "export/out";
@@ -41,7 +40,7 @@ export async function exportOutput(input) {
41
40
  }
42
41
  const outputDirInput = (input.output_dir ?? "").trim() || resolveDefaultOutputDir(target);
43
42
  const outputDirAbs = validatePath(outputDirInput, basePath);
44
- const outputDirRel = normalizeRelPosix(path.relative(basePath, outputDirAbs));
43
+ const outputDirRel = normalizeRelativePosix(path.relative(basePath, outputDirAbs));
45
44
  const forbid = isPathForbidden(outputDirRel);
46
45
  if (forbid.forbidden) {
47
46
  throw new McpError(ErrorCode.InvalidParams, "Forbidden export output_dir.", {
@@ -64,9 +63,10 @@ export async function exportOutput(input) {
64
63
  }
65
64
  if (dryRun)
66
65
  planCmd.push("--dry-run");
67
- const planRes = await runEngine("vibecoding-helper", planCmd, { timeoutMs: 120_000 });
66
+ const planRes = await runPythonCli(planCmd, { timeoutMs: 120_000 });
68
67
  if (planRes.code !== 0) {
69
- throw new Error(`export-plan failed: ${planRes.stderr || `exit_code=${planRes.code}`}`);
68
+ const classified = classifyPythonError(planRes.stderr ?? "", planRes.code, "export-plan");
69
+ throw new Error(`${classified.issueCode}: ${classified.summary}`);
70
70
  }
71
71
  const planParsed = safeJsonParse(planRes.stdout);
72
72
  if (!planParsed.ok) {
@@ -94,9 +94,10 @@ export async function exportOutput(input) {
94
94
  ];
95
95
  if (needsTemplatePin)
96
96
  genCmd.push("--template-repo", templateRepo, "--template-ref", templateRef);
97
- const genRes = await runEngine("vibecoding-helper", genCmd, { timeoutMs: 300_000 });
97
+ const genRes = await runPythonCli(genCmd, { timeoutMs: 300_000 });
98
98
  if (genRes.code !== 0) {
99
- throw new Error(`export-generate failed: ${genRes.stderr || `exit_code=${genRes.code}`}`);
99
+ const classified = classifyPythonError(genRes.stderr ?? "", genRes.code, "export-generate");
100
+ throw new Error(`${classified.issueCode}: ${classified.summary}`);
100
101
  }
101
102
  const genParsed = safeJsonParse(genRes.stdout);
102
103
  if (!genParsed.ok) {
@@ -107,10 +108,10 @@ export async function exportOutput(input) {
107
108
  for (const f of files) {
108
109
  const p = typeof f?.path === "string" ? f.path : "";
109
110
  if (p)
110
- createdPaths.push(normalizeRelPosix(p));
111
+ createdPaths.push(normalizeRelativePosix(p));
111
112
  }
112
113
  }
113
- artifactsPathRel = normalizeRelPosix(path.relative(basePath, exportArtifactsPath(runDirAbs)));
114
+ artifactsPathRel = normalizeRelativePosix(path.relative(basePath, exportArtifactsPath(runDirAbs)));
114
115
  }
115
116
  const out = {
116
117
  success: true,
@@ -120,8 +121,8 @@ export async function exportOutput(input) {
120
121
  output_dir: outputDirRel,
121
122
  dry_run: dryRun,
122
123
  paths: {
123
- feature_output_spec_path: normalizeRelPosix(path.relative(basePath, specAbs)),
124
- export_plan_path: normalizeRelPosix(path.relative(basePath, exportPlanPath(runDirAbs))),
124
+ feature_output_spec_path: normalizeRelativePosix(path.relative(basePath, specAbs)),
125
+ export_plan_path: normalizeRelativePosix(path.relative(basePath, exportPlanPath(runDirAbs))),
125
126
  export_artifacts_path: artifactsPathRel
126
127
  },
127
128
  created_paths: createdPaths,
@@ -10,7 +10,60 @@ import { runDocStatusTriage } from "./doc_status_gate.js";
10
10
  import { kickoffKceSyncBestEffort } from "./kce/on_finalize.js";
11
11
  import { updateVersionFile } from "../../version-check.js";
12
12
  import { CONTRACTS_VERSION, CONTRACTS_BUNDLE_SHA256 } from "../../generated/contracts_bundle_info.js";
13
- import { updateLastCommit, checkAndAddMcpBuildAction, formatReminders, } from "../../local-mode/project-state.js";
13
+ import { resolveRunId } from "./context.js";
14
+ /**
15
+ * Detect if a file is a spec/doc file
16
+ */
17
+ function isSpecFile(filePath) {
18
+ const normalized = filePath.replace(/\\/g, "/");
19
+ return (normalized === "docs/SSOT.md" ||
20
+ normalized === "docs/CURRENT_SPEC.md" ||
21
+ normalized.startsWith("docs/ssot/") ||
22
+ normalized.startsWith("docs/specs/") ||
23
+ normalized.startsWith("docs/dev_logs/") ||
24
+ normalized.startsWith("docs/planning/"));
25
+ }
26
+ /**
27
+ * Detect if a file is a code file
28
+ */
29
+ function isCodeFile(filePath) {
30
+ const normalized = filePath.replace(/\\/g, "/");
31
+ const codePatterns = [
32
+ /^src\//,
33
+ /^lib\//,
34
+ /^tests?\//,
35
+ /^scripts\//,
36
+ /^adapters\//,
37
+ /^engines\//,
38
+ /^vibecoding_helper\//,
39
+ /\.ts$/,
40
+ /\.tsx$/,
41
+ /\.js$/,
42
+ /\.jsx$/,
43
+ /\.py$/,
44
+ /\.rs$/,
45
+ /\.go$/
46
+ ];
47
+ return codePatterns.some((p) => p.test(normalized));
48
+ }
49
+ /**
50
+ * Write spec update evidence file for audit trail
51
+ */
52
+ function writeSpecUpdateEvidence(basePath, runId, evidence) {
53
+ try {
54
+ const specDir = path.join(basePath, "engines", "spec_high", "runs", runId, "spec");
55
+ fs.mkdirSync(specDir, { recursive: true });
56
+ const evidencePath = path.join(specDir, "spec_update.json");
57
+ const fullEvidence = {
58
+ version: "spec_update_evidence.v1",
59
+ ...evidence
60
+ };
61
+ fs.writeFileSync(evidencePath, JSON.stringify(fullEvidence, null, 2) + "\n", "utf-8");
62
+ }
63
+ catch {
64
+ // Best-effort: do not block finalize_work
65
+ }
66
+ }
14
67
  /**
15
68
  * vibe_pm.finalize_work - Complete work with documentation and Git
16
69
  *
@@ -162,13 +215,6 @@ export async function finalizeWork(input, basePath = process.cwd()) {
162
215
  await git.commit(commitMessage);
163
216
  commitHash = (await git.revparse(["HEAD"])).trim() || "unknown";
164
217
  commitCreated = true;
165
- // Update project state with commit info
166
- updateLastCommit({
167
- hash: commitHash,
168
- message: commitMessage.split("\n")[0],
169
- timestamp: new Date().toISOString(),
170
- pushed: false, // Will be updated after push
171
- }, basePath);
172
218
  }
173
219
  catch (err) {
174
220
  warning = appendWarning(warning, `Git commit 실패: ${err instanceof Error ? err.message : String(err)}`);
@@ -230,13 +276,6 @@ export async function finalizeWork(input, basePath = process.cwd()) {
230
276
  try {
231
277
  await git.push("origin", branchName);
232
278
  pushedToRemote = true;
233
- // Update project state - mark as pushed
234
- updateLastCommit({
235
- hash: commitHash,
236
- message: commitMessage.split("\n")[0],
237
- timestamp: new Date().toISOString(),
238
- pushed: true,
239
- }, basePath);
240
279
  }
241
280
  catch (err) {
242
281
  warning = appendWarning(warning, `Git push 실패: ${err instanceof Error ? err.message : String(err)}`);
@@ -249,6 +288,26 @@ export async function finalizeWork(input, basePath = process.cwd()) {
249
288
  // Git not available / not a repo / unexpected error: keep docs work, but surface warning
250
289
  warning = appendWarning(warning, `Git 처리 실패: ${err instanceof Error ? err.message : String(err)}`);
251
290
  }
291
+ // SRWP: Write spec update evidence (best-effort audit trail)
292
+ try {
293
+ const { run_id } = resolveRunId(undefined, basePath);
294
+ const specFiles = updatedFiles.filter((f) => isSpecFile(f));
295
+ const codeFiles = updatedFiles.filter((f) => isCodeFile(f));
296
+ if (specFiles.length > 0 || codeFiles.length > 0) {
297
+ writeSpecUpdateEvidence(basePath, run_id, {
298
+ run_id,
299
+ timestamp: new Date().toISOString(),
300
+ updated_spec_files: specFiles,
301
+ updated_code_files: codeFiles,
302
+ reason: "FinalizeWork",
303
+ commit_subject: input.git_commit.subject,
304
+ diff_summary: input.task_summary
305
+ });
306
+ }
307
+ }
308
+ catch {
309
+ // Best-effort: do not block finalize_work
310
+ }
252
311
  // Best-effort: KCE sync (do not block finalize_work)
253
312
  if (shouldKickoffAutoMemorySync(basePath)) {
254
313
  void kickoffKceSyncBestEffort();
@@ -272,22 +331,6 @@ export async function finalizeWork(input, basePath = process.cwd()) {
272
331
  catch {
273
332
  // Best-effort: do not block finalize_work
274
333
  }
275
- // Best-effort: Check if MCP package needs rebuild and update project state
276
- let mcpBuildNeeded = false;
277
- try {
278
- mcpBuildNeeded = await checkAndAddMcpBuildAction(basePath);
279
- if (mcpBuildNeeded) {
280
- warning = appendWarning(warning, "MCP_BUILD_NEEDED: adapters/mcp-ts 소스 변경됨. vibe_pm.publish_mcp로 빌드 필요");
281
- }
282
- // Add reminder summary to warning if there are pending actions
283
- const reminders = formatReminders(basePath);
284
- if (reminders) {
285
- warning = appendWarning(warning, `\n${reminders}`);
286
- }
287
- }
288
- catch {
289
- // ignore (best-effort)
290
- }
291
334
  return {
292
335
  success: commitHash !== "no-commit" || updatedFiles.length > 0,
293
336
  updated_files: updatedFiles,
@@ -295,15 +338,10 @@ export async function finalizeWork(input, basePath = process.cwd()) {
295
338
  pushed_to_remote: pushedToRemote,
296
339
  remote_url: remoteUrl,
297
340
  warning,
298
- next_action: mcpBuildNeeded
299
- ? {
300
- tool: "vibe_pm.publish_mcp",
301
- reason: "MCP 패키지 빌드 필요 (소스 변경 감지)"
302
- }
303
- : {
304
- tool: "vibe_pm.status",
305
- reason: "작업 완료 상태를 확인하세요"
306
- }
341
+ next_action: {
342
+ tool: "vibe_pm.status",
343
+ reason: "작업 완료 상태를 확인하세요"
344
+ }
307
345
  };
308
346
  }
309
347
  /**
@@ -2,7 +2,7 @@
2
2
  // vibe_pm.get_decision - Fetch approval items
3
3
  import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
- import { runEngineWithCache } from "../../engine.js";
5
+ import { runPythonCliWithCache } from "../../engine.js";
6
6
  import { safeJsonParse } from "../../cli.js";
7
7
  import { getRunsDir, resolveRunId, resolveProjectId, resolveSpecHighRunDir } from "./context.js";
8
8
  import { transformAskQueueText } from "./pm_language.js";
@@ -42,7 +42,7 @@ export async function getDecision(input, basePath = process.cwd()) {
42
42
  }
43
43
  else {
44
44
  // Fallback: run show-ask-queue command (with caching)
45
- const { code, stdout } = await runEngineWithCache("vibecoding-helper", ["show-ask-queue", run_id], { timeoutMs: 60_000 });
45
+ const { code, stdout } = await runPythonCliWithCache(["show-ask-queue", run_id], { timeoutMs: 60_000 });
46
46
  if (code === 0) {
47
47
  // Parse response
48
48
  const parsed = safeJsonParse(stdout);