@vibecodetown/mcp-server 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +269 -0
- package/build/auth/gate.js +225 -0
- package/build/auth/index.js +55 -0
- package/build/auth/public_key.js +27 -0
- package/build/auth/token_cache.js +122 -0
- package/build/auth/token_verifier.js +103 -0
- package/build/bootstrap/doctor.js +115 -0
- package/build/bootstrap/installer.js +673 -0
- package/build/bootstrap/lock.js +37 -0
- package/build/bootstrap/platform.js +26 -0
- package/build/bootstrap/registry.js +37 -0
- package/build/cache/index.js +147 -0
- package/build/cli.js +101 -0
- package/build/contracts.js +22 -0
- package/build/control_plane/gate.js +161 -0
- package/build/control_plane/index.js +6 -0
- package/build/dx/activity.js +139 -0
- package/build/engine.js +106 -0
- package/build/errors.js +171 -0
- package/build/generated/activate_input.js +2 -0
- package/build/generated/activate_output.js +57 -0
- package/build/generated/advisory_review_input.js +2 -0
- package/build/generated/advisory_review_output.js +35 -0
- package/build/generated/auth_token_file.js +2 -0
- package/build/generated/briefing_input.js +2 -0
- package/build/generated/briefing_output.js +2 -0
- package/build/generated/clinic_bridge_file.js +13 -0
- package/build/generated/contracts_bundle_info.js +5 -0
- package/build/generated/create_work_order_input.js +2 -0
- package/build/generated/create_work_order_output.js +2 -0
- package/build/generated/current_work_order_file.js +2 -0
- package/build/generated/doctor_input.js +2 -0
- package/build/generated/doctor_output.js +24 -0
- package/build/generated/execution_result.js +2 -0
- package/build/generated/execution_task.js +2 -0
- package/build/generated/export_output_input.js +2 -0
- package/build/generated/export_output_output.js +2 -0
- package/build/generated/finalize_work_input.js +2 -0
- package/build/generated/finalize_work_output.js +2 -0
- package/build/generated/gate_input.js +2 -0
- package/build/generated/gate_output.js +2 -0
- package/build/generated/gate_result_v1.js +2 -0
- package/build/generated/get_decision_input.js +2 -0
- package/build/generated/get_decision_output.js +13 -0
- package/build/generated/handoff_to_clinic.js +2 -0
- package/build/generated/index.js +75 -0
- package/build/generated/inspect_code_input.js +2 -0
- package/build/generated/inspect_code_output.js +13 -0
- package/build/generated/memory_retrieve_output.js +2 -0
- package/build/generated/memory_state_file.js +2 -0
- package/build/generated/memory_status_input.js +2 -0
- package/build/generated/memory_status_output.js +13 -0
- package/build/generated/memory_sync_input.js +2 -0
- package/build/generated/memory_sync_output.js +13 -0
- package/build/generated/plugin_result.js +2 -0
- package/build/generated/react_perf_check_patterns_input.js +2 -0
- package/build/generated/react_perf_check_patterns_output.js +2 -0
- package/build/generated/react_perf_generate_report_input.js +2 -0
- package/build/generated/react_perf_generate_report_output.js +2 -0
- package/build/generated/repair_plan_input.js +2 -0
- package/build/generated/repair_plan_output.js +2 -0
- package/build/generated/run_app_input.js +2 -0
- package/build/generated/run_app_output.js +2 -0
- package/build/generated/run_state_file.js +13 -0
- package/build/generated/scaffold_input.js +2 -0
- package/build/generated/scaffold_output.js +2 -0
- package/build/generated/search_oss_input.js +2 -0
- package/build/generated/search_oss_output.js +2 -0
- package/build/generated/selection_validation_result.js +2 -0
- package/build/generated/signal_agent_input.js +2 -0
- package/build/generated/spec_high_ask_queue_items_file.js +2 -0
- package/build/generated/spec_high_clinic_bridge_output.js +2 -0
- package/build/generated/spec_high_decision_draft_output.js +2 -0
- package/build/generated/spec_high_validate_output.js +2 -0
- package/build/generated/status_input.js +2 -0
- package/build/generated/status_output.js +2 -0
- package/build/generated/submit_decision_input.js +2 -0
- package/build/generated/submit_decision_output.js +2 -0
- package/build/generated/tool_error_output.js +2 -0
- package/build/generated/undo_last_task_input.js +2 -0
- package/build/generated/undo_last_task_output.js +2 -0
- package/build/generated/update_input.js +2 -0
- package/build/generated/update_output.js +2 -0
- package/build/generated/vibe_pm_inspection_result.js +2 -0
- package/build/generated/vibe_pm_report_markdown.js +2 -0
- package/build/generated/vibe_pm_verdict.js +2 -0
- package/build/generated/vibe_repo_config.js +2 -0
- package/build/generated/vibecoding_helper_answer_output.js +2 -0
- package/build/generated/vibecoding_helper_one_loop_selection_output.js +2 -0
- package/build/generated/vibecoding_helper_show_ask_queue_output.js +2 -0
- package/build/generated/work_order_v1.js +2 -0
- package/build/generated/zoekt_evidence_input.js +2 -0
- package/build/generated/zoekt_evidence_output.js +2 -0
- package/build/index.js +111 -0
- package/build/legacy_alias.js +65 -0
- package/build/local-mode/bash.js +61 -0
- package/build/local-mode/config.js +171 -0
- package/build/local-mode/git.js +33 -0
- package/build/local-mode/init.js +110 -0
- package/build/local-mode/paths.js +24 -0
- package/build/local-mode/templates.js +856 -0
- package/build/local-mode/work-order.js +41 -0
- package/build/resources/index.js +246 -0
- package/build/security/input-validator.js +119 -0
- package/build/security/path-policy.js +289 -0
- package/build/security/sandbox.js +228 -0
- package/build/tools/react_perf/check_patterns.js +172 -0
- package/build/tools/react_perf/generate_report.js +337 -0
- package/build/tools/react_perf/index.js +119 -0
- package/build/tools/react_perf/rules/advanced.js +325 -0
- package/build/tools/react_perf/rules/async.js +104 -0
- package/build/tools/react_perf/rules/bundle.js +101 -0
- package/build/tools/react_perf/rules/client.js +186 -0
- package/build/tools/react_perf/rules/index.js +74 -0
- package/build/tools/react_perf/rules/js.js +148 -0
- package/build/tools/react_perf/rules/rendering.js +166 -0
- package/build/tools/react_perf/rules/rerender.js +161 -0
- package/build/tools/react_perf/rules/server.js +141 -0
- package/build/tools/react_perf/types.js +127 -0
- package/build/tools/vibe_pm/activate.js +102 -0
- package/build/tools/vibe_pm/advisory_review.js +77 -0
- package/build/tools/vibe_pm/briefing.js +178 -0
- package/build/tools/vibe_pm/context.js +439 -0
- package/build/tools/vibe_pm/create_work_order.js +271 -0
- package/build/tools/vibe_pm/doc_status_gate.js +370 -0
- package/build/tools/vibe_pm/doctor.js +262 -0
- package/build/tools/vibe_pm/entity_gate/preflight.js +78 -0
- package/build/tools/vibe_pm/export_output.js +135 -0
- package/build/tools/vibe_pm/finalize_work.js +393 -0
- package/build/tools/vibe_pm/gate.js +33 -0
- package/build/tools/vibe_pm/get_decision.js +281 -0
- package/build/tools/vibe_pm/index.js +593 -0
- package/build/tools/vibe_pm/inspect_code.js +828 -0
- package/build/tools/vibe_pm/intent/generator.js +294 -0
- package/build/tools/vibe_pm/intent/index.js +5 -0
- package/build/tools/vibe_pm/intent/prompt_density.js +227 -0
- package/build/tools/vibe_pm/intent/types.js +70 -0
- package/build/tools/vibe_pm/intent/verifier.js +237 -0
- package/build/tools/vibe_pm/kce/doc_usage.js +51 -0
- package/build/tools/vibe_pm/kce/on_finalize.js +11 -0
- package/build/tools/vibe_pm/kce/preflight.js +232 -0
- package/build/tools/vibe_pm/local_memory.js +26 -0
- package/build/tools/vibe_pm/memory_status.js +82 -0
- package/build/tools/vibe_pm/memory_sync.js +134 -0
- package/build/tools/vibe_pm/modules/decision_snapshot.js +29 -0
- package/build/tools/vibe_pm/modules/ensure.js +100 -0
- package/build/tools/vibe_pm/modules/fingerprint.js +30 -0
- package/build/tools/vibe_pm/modules/fix_dependencies.js +394 -0
- package/build/tools/vibe_pm/modules/planning_v1.js +110 -0
- package/build/tools/vibe_pm/modules/repo_context.js +56 -0
- package/build/tools/vibe_pm/modules/research_v1.js +114 -0
- package/build/tools/vibe_pm/modules/skills_v1.js +100 -0
- package/build/tools/vibe_pm/pm_language.js +222 -0
- package/build/tools/vibe_pm/repair_plan.js +199 -0
- package/build/tools/vibe_pm/run_app.js +597 -0
- package/build/tools/vibe_pm/run_app_podman.js +64 -0
- package/build/tools/vibe_pm/scaffold.js +550 -0
- package/build/tools/vibe_pm/search_oss.js +124 -0
- package/build/tools/vibe_pm/status.js +153 -0
- package/build/tools/vibe_pm/submit_decision.js +87 -0
- package/build/tools/vibe_pm/system_design/issue_mapping.js +47 -0
- package/build/tools/vibe_pm/system_design/rulebook.js +112 -0
- package/build/tools/vibe_pm/system_design/semgrep.js +132 -0
- package/build/tools/vibe_pm/types.js +229 -0
- package/build/tools/vibe_pm/undo_last_task.js +163 -0
- package/build/tools/vibe_pm/update.js +146 -0
- package/build/tools/vibe_pm/zoekt_evidence.js +96 -0
- package/build/tools.js +269 -0
- package/build/version-check.js +239 -0
- package/build/vibe-cli.js +631 -0
- package/package.json +76 -0
package/build/tools.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools.ts
|
|
2
|
+
// MCP tool definitions using bootstrap-based engine execution
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { safeJsonParse } from "./cli.js";
|
|
5
|
+
import { parseSelectionValidationResult } from "./contracts.js";
|
|
6
|
+
import { runEngine } from "./engine.js";
|
|
7
|
+
import { doctor as doctorImpl } from "./bootstrap/doctor.js";
|
|
8
|
+
import { ToolErrorOutputSchema } from "./generated/tool_error_output.js";
|
|
9
|
+
function toolText(obj) {
|
|
10
|
+
return { content: [{ type: "text", text: JSON.stringify(obj, null, 2) }] };
|
|
11
|
+
}
|
|
12
|
+
function err(reason, extra) {
|
|
13
|
+
const out = ToolErrorOutputSchema.parse({
|
|
14
|
+
status: "ERROR",
|
|
15
|
+
reason,
|
|
16
|
+
message: extra?.message ?? "요청을 처리하는 중 문제가 생겼습니다.",
|
|
17
|
+
recovery: extra?.recovery,
|
|
18
|
+
is_retryable: extra?.is_retryable,
|
|
19
|
+
details: extra?.details,
|
|
20
|
+
debug: extra?.debug
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
content: [{ type: "text", text: JSON.stringify(out, null, 2) }],
|
|
24
|
+
structuredContent: out,
|
|
25
|
+
isError: true
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function defineTools() {
|
|
29
|
+
// ---------- vibecode.doctor ----------
|
|
30
|
+
const doctorInput = z.object({});
|
|
31
|
+
async function vibecodeDoctor(_input) {
|
|
32
|
+
try {
|
|
33
|
+
return toolText(await doctorImpl());
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
37
|
+
return err("unknown", { message: msg });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// ---------- vibecode.one_loop (PRIMARY) ----------
|
|
41
|
+
const oneLoopInput = z.object({
|
|
42
|
+
run_id: z.string().default("default"),
|
|
43
|
+
output: z.enum(["selection"]).default("selection"),
|
|
44
|
+
timeout_ms: z.number().int().positive().optional()
|
|
45
|
+
});
|
|
46
|
+
async function vibecodeOneLoop(input) {
|
|
47
|
+
try {
|
|
48
|
+
const { code, stdout, stderr } = await runEngine("vibecoding-helper", ["one-loop", input.run_id, "--output", input.output], { timeoutMs: input.timeout_ms ?? 120_000 });
|
|
49
|
+
const parsed = safeJsonParse(stdout);
|
|
50
|
+
if (!parsed.ok) {
|
|
51
|
+
return err("invalid_json", {
|
|
52
|
+
message: parsed.error,
|
|
53
|
+
recovery: "도구를 업데이트한 뒤 다시 시도해 주세요.",
|
|
54
|
+
is_retryable: true,
|
|
55
|
+
details: { exit_code: code },
|
|
56
|
+
debug: { stdout, stderr }
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const validated = parseSelectionValidationResult(parsed.value);
|
|
61
|
+
return toolText(validated);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
65
|
+
return err("schema_mismatch", {
|
|
66
|
+
message: msg,
|
|
67
|
+
recovery: "도구를 업데이트한 뒤 다시 시도해 주세요.",
|
|
68
|
+
is_retryable: true,
|
|
69
|
+
details: { exit_code: code },
|
|
70
|
+
debug: { stdout, stderr }
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
76
|
+
return err("unknown", { message: msg, recovery: "잠시 후 다시 시도해 주세요.", is_retryable: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// ---------- spec_high.validate ----------
|
|
80
|
+
const validateInput = z.object({
|
|
81
|
+
run_id: z.string().default("default")
|
|
82
|
+
});
|
|
83
|
+
async function specHighValidate(input) {
|
|
84
|
+
try {
|
|
85
|
+
const { code, stdout, stderr } = await runEngine("spec-high", ["--root", "engines/spec_high", "validate", input.run_id], { timeoutMs: 120_000 });
|
|
86
|
+
return toolText({
|
|
87
|
+
status: code === 0 ? "OK" : "FAIL",
|
|
88
|
+
exit_code: code,
|
|
89
|
+
stdout,
|
|
90
|
+
stderr
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
95
|
+
return err("unknown", { message: msg });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ---------- spec_high.clinic_bridge ----------
|
|
99
|
+
const bridgeInput = z.object({
|
|
100
|
+
run_id: z.string().default("default"),
|
|
101
|
+
format: z.enum(["yaml", "json"]).default("yaml"),
|
|
102
|
+
timeout_ms: z.number().int().positive().optional()
|
|
103
|
+
});
|
|
104
|
+
async function specHighClinicBridge(input) {
|
|
105
|
+
try {
|
|
106
|
+
const { code, stdout, stderr } = await runEngine("spec-high", ["--root", "engines/spec_high", "clinic-bridge", input.run_id, "--write", "--format", input.format], { timeoutMs: input.timeout_ms ?? 120_000 });
|
|
107
|
+
const bridge_path = input.format === "json"
|
|
108
|
+
? `engines/spec_high/runs/${input.run_id}/handoff/clinic_bridge.json`
|
|
109
|
+
: `engines/spec_high/runs/${input.run_id}/handoff/clinic_bridge.yaml`;
|
|
110
|
+
return toolText({
|
|
111
|
+
status: code === 0 ? "OK" : "FAIL",
|
|
112
|
+
exit_code: code,
|
|
113
|
+
bridge_path,
|
|
114
|
+
stdout,
|
|
115
|
+
stderr
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
120
|
+
return err("unknown", { message: msg });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// ---------- spec_high.quality_eval ----------
|
|
124
|
+
const qualityInput = z.object({
|
|
125
|
+
run_id: z.string().default("default"),
|
|
126
|
+
timeout_ms: z.number().int().positive().optional()
|
|
127
|
+
});
|
|
128
|
+
async function specHighQualityEval(input) {
|
|
129
|
+
try {
|
|
130
|
+
const { code, stdout, stderr } = await runEngine("spec-high", ["--root", "engines/spec_high", "quality-eval", input.run_id, "--write"], { timeoutMs: input.timeout_ms ?? 120_000 });
|
|
131
|
+
return toolText({
|
|
132
|
+
status: code === 0 ? "OK" : "FAIL",
|
|
133
|
+
exit_code: code,
|
|
134
|
+
quality_report_path: `engines/spec_high/runs/${input.run_id}/quality/quality_report.json`,
|
|
135
|
+
stdout,
|
|
136
|
+
stderr
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
141
|
+
return err("unknown", { message: msg });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// ---------- vibecode.show_ask_queue ----------
|
|
145
|
+
const askQueueInput = z.object({
|
|
146
|
+
run_id: z.string().default("default"),
|
|
147
|
+
timeout_ms: z.number().int().positive().optional()
|
|
148
|
+
});
|
|
149
|
+
async function vibecodeShowAskQueue(input) {
|
|
150
|
+
try {
|
|
151
|
+
const { code, stdout, stderr } = await runEngine("vibecoding-helper", ["show-ask-queue", input.run_id], { timeoutMs: input.timeout_ms ?? 60_000 });
|
|
152
|
+
return toolText({
|
|
153
|
+
status: code === 0 ? "OK" : "FAIL",
|
|
154
|
+
exit_code: code,
|
|
155
|
+
text: stdout.trim(),
|
|
156
|
+
stderr
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
161
|
+
return err("unknown", { message: msg });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// ---------- vibecode.show_decisions ----------
|
|
165
|
+
const showDecisionsInput = z.object({
|
|
166
|
+
run_id: z.string().default("default"),
|
|
167
|
+
timeout_ms: z.number().int().positive().optional()
|
|
168
|
+
});
|
|
169
|
+
async function vibecodeShowDecisions(input) {
|
|
170
|
+
try {
|
|
171
|
+
const { code, stdout, stderr } = await runEngine("vibecoding-helper", ["show-decisions", input.run_id], { timeoutMs: input.timeout_ms ?? 60_000 });
|
|
172
|
+
return toolText({
|
|
173
|
+
status: code === 0 ? "OK" : "FAIL",
|
|
174
|
+
exit_code: code,
|
|
175
|
+
summary_markdown: stdout.trim(),
|
|
176
|
+
stderr
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
181
|
+
return err("unknown", { message: msg });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// ---------- vibecode.answer ----------
|
|
185
|
+
const answerInput = z.object({
|
|
186
|
+
run_id: z.string().default("default"),
|
|
187
|
+
text: z.string(),
|
|
188
|
+
timeout_ms: z.number().int().positive().optional()
|
|
189
|
+
});
|
|
190
|
+
async function vibecodeAnswer(input) {
|
|
191
|
+
try {
|
|
192
|
+
const { code, stdout, stderr } = await runEngine("vibecoding-helper", ["answer", input.run_id, "--text", input.text], { timeoutMs: input.timeout_ms ?? 120_000 });
|
|
193
|
+
return toolText({
|
|
194
|
+
status: code === 0 ? "OK" : "FAIL",
|
|
195
|
+
exit_code: code,
|
|
196
|
+
stdout: stdout.trim(),
|
|
197
|
+
stderr
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
catch (e) {
|
|
201
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
202
|
+
return err("unknown", { message: msg });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// ---------- clinic.verify ----------
|
|
206
|
+
const clinicVerifyInput = z.object({
|
|
207
|
+
run_id: z.string().default("default"),
|
|
208
|
+
bridge_path: z.string(),
|
|
209
|
+
timeout_ms: z.number().int().positive().optional()
|
|
210
|
+
});
|
|
211
|
+
async function clinicVerify(input) {
|
|
212
|
+
try {
|
|
213
|
+
const resultPath = `runs/${input.run_id}/clinic/result.json`;
|
|
214
|
+
const { code, stdout, stderr } = await runEngine("clinic", ["selection-validate", "--bridge", input.bridge_path], { timeoutMs: input.timeout_ms ?? 180_000 });
|
|
215
|
+
return toolText({
|
|
216
|
+
status: code === 0 ? "OK" : "FAIL",
|
|
217
|
+
exit_code: code,
|
|
218
|
+
result_path: resultPath,
|
|
219
|
+
stdout: stdout.trim(),
|
|
220
|
+
stderr
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
225
|
+
return err("unknown", { message: msg });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// ---------- clinic.report_latest ----------
|
|
229
|
+
const clinicReportInput = z.object({
|
|
230
|
+
timeout_ms: z.number().int().positive().optional()
|
|
231
|
+
});
|
|
232
|
+
async function clinicReportLatest(input) {
|
|
233
|
+
try {
|
|
234
|
+
const { code, stdout, stderr } = await runEngine("clinic", ["report", "--latest"], { timeoutMs: input.timeout_ms ?? 60_000 });
|
|
235
|
+
return toolText({
|
|
236
|
+
status: code === 0 ? "OK" : "FAIL",
|
|
237
|
+
exit_code: code,
|
|
238
|
+
text: stdout.trim(),
|
|
239
|
+
stderr
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
244
|
+
return err("unknown", { message: msg });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
doctorInput,
|
|
249
|
+
vibecodeDoctor,
|
|
250
|
+
oneLoopInput,
|
|
251
|
+
vibecodeOneLoop,
|
|
252
|
+
validateInput,
|
|
253
|
+
specHighValidate,
|
|
254
|
+
bridgeInput,
|
|
255
|
+
specHighClinicBridge,
|
|
256
|
+
qualityInput,
|
|
257
|
+
specHighQualityEval,
|
|
258
|
+
askQueueInput,
|
|
259
|
+
vibecodeShowAskQueue,
|
|
260
|
+
showDecisionsInput,
|
|
261
|
+
vibecodeShowDecisions,
|
|
262
|
+
answerInput,
|
|
263
|
+
vibecodeAnswer,
|
|
264
|
+
clinicVerifyInput,
|
|
265
|
+
clinicVerify,
|
|
266
|
+
clinicReportInput,
|
|
267
|
+
clinicReportLatest
|
|
268
|
+
};
|
|
269
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/version-check.ts
|
|
2
|
+
// CLI 진입 훅: 자동 업데이트 체크
|
|
3
|
+
// 정책: 절대 실행을 막지 않는다 (모든 실패는 GO)
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import readline from "node:readline";
|
|
8
|
+
function execGit(args, cwd) {
|
|
9
|
+
const r = spawnSync("git", args, { cwd, encoding: "utf-8", timeout: 10000 });
|
|
10
|
+
return {
|
|
11
|
+
status: r.status,
|
|
12
|
+
stdout: r.stdout ?? "",
|
|
13
|
+
stderr: r.stderr ?? "",
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function repoRootOrNull(cwd) {
|
|
17
|
+
const r = execGit(["rev-parse", "--show-toplevel"], cwd);
|
|
18
|
+
if (r.status !== 0)
|
|
19
|
+
return null;
|
|
20
|
+
return r.stdout.trim() || null;
|
|
21
|
+
}
|
|
22
|
+
function readLocalVersion(repoRoot) {
|
|
23
|
+
const p = path.join(repoRoot, ".vibe", "version.json");
|
|
24
|
+
if (!fs.existsSync(p))
|
|
25
|
+
return null;
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function readAutoUpdatePolicy(repoRoot) {
|
|
34
|
+
const p = path.join(repoRoot, ".vibe", "config.json");
|
|
35
|
+
if (!fs.existsSync(p))
|
|
36
|
+
return "prompt";
|
|
37
|
+
try {
|
|
38
|
+
const raw = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
39
|
+
const v = raw?.policy?.auto_update_on_start;
|
|
40
|
+
if (v === "off" || v === "prompt" || v === "silent")
|
|
41
|
+
return v;
|
|
42
|
+
return "prompt";
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return "prompt";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function isDirtyTree(repoRoot) {
|
|
49
|
+
const r = execGit(["status", "--porcelain"], repoRoot);
|
|
50
|
+
if (r.status !== 0)
|
|
51
|
+
return null;
|
|
52
|
+
return r.stdout.trim().length > 0;
|
|
53
|
+
}
|
|
54
|
+
function getDefaultBranch(repoRoot) {
|
|
55
|
+
// origin/HEAD → origin/main 심볼릭 참조
|
|
56
|
+
const r = execGit(["symbolic-ref", "refs/remotes/origin/HEAD"], repoRoot);
|
|
57
|
+
if (r.status !== 0)
|
|
58
|
+
return "main";
|
|
59
|
+
const full = r.stdout.trim();
|
|
60
|
+
const parts = full.split("/");
|
|
61
|
+
return parts[parts.length - 1] || "main";
|
|
62
|
+
}
|
|
63
|
+
function readRemoteVersion(repoRoot, branch) {
|
|
64
|
+
// 네트워크 최소화: fetch quiet + git show
|
|
65
|
+
execGit(["fetch", "--quiet", "origin", branch], repoRoot);
|
|
66
|
+
const r = execGit(["show", `origin/${branch}:.vibe/version.json`], repoRoot);
|
|
67
|
+
if (r.status !== 0)
|
|
68
|
+
return null;
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(r.stdout);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function compareSemver(a, b) {
|
|
77
|
+
const pa = a.split(".").map((x) => parseInt(x, 10) || 0);
|
|
78
|
+
const pb = b.split(".").map((x) => parseInt(x, 10) || 0);
|
|
79
|
+
for (let i = 0; i < 3; i++) {
|
|
80
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0))
|
|
81
|
+
return -1;
|
|
82
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0))
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
async function promptYesNo(question) {
|
|
88
|
+
// Non-TTY 환경에서는 자동 스킵
|
|
89
|
+
if (!process.stdin.isTTY)
|
|
90
|
+
return false;
|
|
91
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
92
|
+
const answer = await new Promise((res) => rl.question(question, res));
|
|
93
|
+
rl.close();
|
|
94
|
+
const a = answer.trim().toLowerCase();
|
|
95
|
+
return a === "" || a === "y" || a === "yes";
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* CLI 진입 훅: 자동 업데이트 체크
|
|
99
|
+
*
|
|
100
|
+
* 정책:
|
|
101
|
+
* - dirty tree → GO (경고만)
|
|
102
|
+
* - ff-only 실패 → GO (경고만)
|
|
103
|
+
* - 네트워크 실패 → GO (조용히 스킵)
|
|
104
|
+
* - 절대 throw 하지 않음
|
|
105
|
+
*/
|
|
106
|
+
export async function autoUpdateOnStart(opts) {
|
|
107
|
+
const result = {
|
|
108
|
+
checked: false,
|
|
109
|
+
updateAvailable: false,
|
|
110
|
+
localVersion: null,
|
|
111
|
+
remoteVersion: null,
|
|
112
|
+
updated: false,
|
|
113
|
+
skipped: false,
|
|
114
|
+
};
|
|
115
|
+
try {
|
|
116
|
+
// --no-update 플래그
|
|
117
|
+
if (opts.noUpdate) {
|
|
118
|
+
result.skipped = true;
|
|
119
|
+
result.reason = "noUpdate flag";
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
// Git 레포 확인
|
|
123
|
+
const repoRoot = repoRootOrNull(opts.cwd);
|
|
124
|
+
if (!repoRoot) {
|
|
125
|
+
result.reason = "not a git repo";
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
// Config 정책: auto_update_on_start
|
|
129
|
+
const policy = readAutoUpdatePolicy(repoRoot);
|
|
130
|
+
const effectiveSilent = opts.silent === true || policy === "silent";
|
|
131
|
+
if (policy === "off") {
|
|
132
|
+
result.skipped = true;
|
|
133
|
+
result.reason = "policy.auto_update_on_start=off";
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
// 로컬 버전 읽기
|
|
137
|
+
const local = readLocalVersion(repoRoot);
|
|
138
|
+
if (!local) {
|
|
139
|
+
result.reason = "no local version file";
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
result.localVersion = local.version;
|
|
143
|
+
// dirty tree 확인
|
|
144
|
+
const dirty = isDirtyTree(repoRoot);
|
|
145
|
+
const isDirty = dirty === true;
|
|
146
|
+
// 원격 버전 조회
|
|
147
|
+
const branch = getDefaultBranch(repoRoot);
|
|
148
|
+
const remote = readRemoteVersion(repoRoot, branch);
|
|
149
|
+
if (!remote) {
|
|
150
|
+
result.reason = "failed to fetch remote version";
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
result.remoteVersion = remote.version;
|
|
154
|
+
result.checked = true;
|
|
155
|
+
// 버전 비교
|
|
156
|
+
const versionNewer = compareSemver(remote.version, local.version) > 0;
|
|
157
|
+
const contractsDiff = remote.contracts.bundle_sha256 !== local.contracts.bundle_sha256;
|
|
158
|
+
result.updateAvailable = versionNewer || contractsDiff;
|
|
159
|
+
if (!result.updateAvailable) {
|
|
160
|
+
result.reason = "already up to date";
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
// dirty tree면 업데이트 스킵 (경고만)
|
|
164
|
+
if (isDirty) {
|
|
165
|
+
if (!effectiveSilent) {
|
|
166
|
+
console.log(`\u26A0\uFE0F 새 버전 감지됨 (${local.version} → ${remote.version})`);
|
|
167
|
+
console.log(`\u26A0\uFE0F 로컬 변경사항이 있어 자동 업데이트를 건너뜀 (dirty working tree)`);
|
|
168
|
+
}
|
|
169
|
+
result.skipped = true;
|
|
170
|
+
result.reason = "dirty working tree";
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
if (policy === "prompt") {
|
|
174
|
+
// 사용자 확인
|
|
175
|
+
const ok = await promptYesNo(`\uD83D\uDD04 새 버전 감지됨 (${local.version} → ${remote.version}) 지금 업데이트할까? [Y/n] `);
|
|
176
|
+
if (!ok) {
|
|
177
|
+
result.skipped = true;
|
|
178
|
+
result.reason = "user declined";
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// git pull --ff-only
|
|
183
|
+
const pull = execGit(["pull", "--ff-only", "origin", branch], repoRoot);
|
|
184
|
+
if (pull.status === 0) {
|
|
185
|
+
if (!effectiveSilent) {
|
|
186
|
+
console.log("\u2705 업데이트 완료");
|
|
187
|
+
}
|
|
188
|
+
result.updated = true;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
if (!effectiveSilent) {
|
|
192
|
+
console.log("\u26A0\uFE0F 자동 업데이트 실패 (충돌/fast-forward 불가)");
|
|
193
|
+
}
|
|
194
|
+
result.skipped = true;
|
|
195
|
+
result.reason = "ff-only failed";
|
|
196
|
+
}
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// 모든 예외는 GO (조용히 진행)
|
|
201
|
+
result.reason = "unexpected error";
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* 버전 파일 갱신 (finalize_work / release 시점)
|
|
207
|
+
*/
|
|
208
|
+
export function updateVersionFile(opts) {
|
|
209
|
+
try {
|
|
210
|
+
const gitBranch = execGit(["rev-parse", "--abbrev-ref", "HEAD"], opts.repoRoot);
|
|
211
|
+
const gitCommit = execGit(["rev-parse", "--short", "HEAD"], opts.repoRoot);
|
|
212
|
+
const dirty = isDirtyTree(opts.repoRoot);
|
|
213
|
+
const versionFile = {
|
|
214
|
+
app: "vibe-pm",
|
|
215
|
+
version: opts.version,
|
|
216
|
+
channel: opts.channel ?? "stable",
|
|
217
|
+
git: {
|
|
218
|
+
branch: gitBranch.stdout.trim() || "unknown",
|
|
219
|
+
commit: gitCommit.stdout.trim() || "unknown",
|
|
220
|
+
dirty: dirty === true,
|
|
221
|
+
},
|
|
222
|
+
contracts: {
|
|
223
|
+
bundle_semver: opts.contractsSemver,
|
|
224
|
+
bundle_sha256: opts.contractsSha256,
|
|
225
|
+
},
|
|
226
|
+
updated_at: new Date().toISOString(),
|
|
227
|
+
};
|
|
228
|
+
const vibeDir = path.join(opts.repoRoot, ".vibe");
|
|
229
|
+
if (!fs.existsSync(vibeDir)) {
|
|
230
|
+
fs.mkdirSync(vibeDir, { recursive: true });
|
|
231
|
+
}
|
|
232
|
+
const filePath = path.join(vibeDir, "version.json");
|
|
233
|
+
fs.writeFileSync(filePath, JSON.stringify(versionFile, null, 2) + "\n", "utf-8");
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|