@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.
- package/README.md +10 -10
- package/build/auth/index.js +0 -2
- package/build/auth/public_key.js +6 -4
- package/build/bootstrap/doctor.js +113 -5
- package/build/bootstrap/installer.js +85 -15
- package/build/bootstrap/registry.js +11 -6
- package/build/bootstrap/skills-installer.js +365 -0
- package/build/dx/activity.js +26 -3
- package/build/engine.js +151 -0
- package/build/errors.js +107 -0
- package/build/generated/bridge_build_seed_input.js +2 -0
- package/build/generated/bridge_build_seed_output.js +2 -0
- package/build/generated/bridge_confirm_reference_input.js +2 -0
- package/build/generated/bridge_confirm_reference_output.js +2 -0
- package/build/generated/bridge_confirmed_reference_file.js +2 -0
- package/build/generated/bridge_generate_references_input.js +2 -0
- package/build/generated/bridge_generate_references_output.js +2 -0
- package/build/generated/bridge_references_file.js +2 -0
- package/build/generated/bridge_work_order_seed_file.js +2 -0
- package/build/generated/contracts_bundle_info.js +3 -3
- package/build/generated/index.js +14 -0
- package/build/generated/ingress_input.js +2 -0
- package/build/generated/ingress_output.js +2 -0
- package/build/generated/ingress_resolution_file.js +2 -0
- package/build/generated/ingress_summary_file.js +2 -0
- package/build/generated/message_template_id_mapping_file.js +2 -0
- package/build/generated/run_app_input.js +1 -1
- package/build/index.js +4 -3
- package/build/local-mode/paths.js +1 -0
- package/build/local-mode/setup.js +21 -1
- package/build/path-utils.js +68 -0
- package/build/runtime/cli_invoker.js +1 -1
- package/build/tools/vibe_pm/advisory_review.js +5 -3
- package/build/tools/vibe_pm/bridge_build_seed.js +164 -0
- package/build/tools/vibe_pm/bridge_confirm_reference.js +91 -0
- package/build/tools/vibe_pm/bridge_generate_references.js +258 -0
- package/build/tools/vibe_pm/briefing.js +27 -3
- package/build/tools/vibe_pm/context.js +79 -0
- package/build/tools/vibe_pm/create_work_order.js +200 -3
- package/build/tools/vibe_pm/doctor.js +95 -0
- package/build/tools/vibe_pm/entity_gate/preflight.js +8 -3
- package/build/tools/vibe_pm/export_output.js +14 -13
- package/build/tools/vibe_pm/finalize_work.js +78 -40
- package/build/tools/vibe_pm/get_decision.js +2 -2
- package/build/tools/vibe_pm/index.js +128 -42
- package/build/tools/vibe_pm/ingress.js +645 -0
- package/build/tools/vibe_pm/ingress_gate.js +116 -0
- package/build/tools/vibe_pm/inspect_code.js +90 -20
- package/build/tools/vibe_pm/kce/doc_usage.js +4 -9
- package/build/tools/vibe_pm/kce/on_finalize.js +2 -2
- package/build/tools/vibe_pm/kce/preflight.js +11 -7
- package/build/tools/vibe_pm/memory_status.js +11 -8
- package/build/tools/vibe_pm/memory_sync.js +11 -8
- package/build/tools/vibe_pm/pm_language.js +17 -16
- package/build/tools/vibe_pm/python_error.js +115 -0
- package/build/tools/vibe_pm/run_app.js +169 -43
- package/build/tools/vibe_pm/run_app_podman.js +64 -2
- package/build/tools/vibe_pm/search_oss.js +5 -3
- package/build/tools/vibe_pm/spec_rag.js +185 -0
- package/build/tools/vibe_pm/status.js +50 -3
- package/build/tools/vibe_pm/submit_decision.js +2 -2
- package/build/tools/vibe_pm/types.js +28 -0
- package/build/tools/vibe_pm/undo_last_task.js +9 -2
- package/build/tools/vibe_pm/waiter_mapping.js +155 -0
- package/build/tools/vibe_pm/zoekt_evidence.js +5 -3
- package/build/tools.js +13 -5
- package/build/vibe-cli.js +245 -7
- package/package.json +5 -4
- package/skills/VRIP_INSTALL_MANIFEST_DOCTOR.skill.md +288 -0
- 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 {
|
|
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
|
|
42
|
+
const { code, stdout, stderr } = await runPythonCli(["entity-gate"], { timeoutMs: 60_000 });
|
|
42
43
|
if (code !== 0 && !stdout.trim()) {
|
|
43
|
-
|
|
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 {
|
|
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
|
-
|
|
11
|
-
|
|
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 =
|
|
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
|
|
66
|
+
const planRes = await runPythonCli(planCmd, { timeoutMs: 120_000 });
|
|
68
67
|
if (planRes.code !== 0) {
|
|
69
|
-
|
|
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
|
|
97
|
+
const genRes = await runPythonCli(genCmd, { timeoutMs: 300_000 });
|
|
98
98
|
if (genRes.code !== 0) {
|
|
99
|
-
|
|
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(
|
|
111
|
+
createdPaths.push(normalizeRelativePosix(p));
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
|
-
artifactsPathRel =
|
|
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:
|
|
124
|
-
export_plan_path:
|
|
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 {
|
|
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:
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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 {
|
|
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
|
|
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);
|