@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
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const BridgeBuildSeedOutputSchema = z.object({ "success": z.boolean(), "project_id": z.string().min(1), "run_id": z.string().min(1), "seed_path": z.string().min(1), "next_action": z.object({ "tool": z.literal("vibe_pm.create_work_order"), "reason": z.string().min(1) }).strict() }).strict().describe("SSOT schema for vibe_pm.bridge_build_seed MCP tool output");
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const BridgeConfirmReferenceInputSchema = z.object({ "project_id": z.string().min(1), "run_id": z.string().min(1).optional(), "selected_key": z.enum(["A", "B", "C"]), "raw_answer": z.string().min(1).max(1000), "notes": z.string().max(2000).optional(), "must_have_overrides": z.array(z.string().min(1).max(120)).max(12).optional(), "must_not_overrides": z.array(z.string().min(1).max(120)).max(12).optional(), "write_files": z.boolean().default(true) }).strict().describe("SSOT schema for vibe_pm.bridge_confirm_reference MCP tool input");
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const BridgeConfirmReferenceOutputSchema = z.object({ "success": z.boolean(), "project_id": z.string().min(1), "run_id": z.string().min(1), "confirmed_reference_path": z.string().min(1), "selected": z.object({ "key": z.enum(["A", "B", "C"]), "title": z.string().min(1).max(120), "primary_link": z.string().min(4).max(2000) }).strict(), "next_action": z.object({ "tool": z.literal("vibe_pm.bridge_build_seed"), "reason": z.string().min(1) }).strict() }).strict().describe("SSOT schema for vibe_pm.bridge_confirm_reference MCP tool output");
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const BridgeConfirmedReferenceFileSchema = z.object({ "schema_version": z.literal("bridge.confirmed_reference.v1"), "run_id": z.string().min(6).max(128), "project_id": z.string().min(1).max(128).optional(), "created_at": z.string().datetime({ offset: true }), "selected": z.object({ "key": z.enum(["A", "B", "C"]), "title": z.string().min(1).max(120), "kind": z.enum(["oss_repo", "library", "product", "pattern", "doc"]), "primary_link": z.string().min(4).max(2000) }).strict(), "user_confirmation": z.object({ "raw_answer": z.string().min(1).max(1000), "notes": z.string().max(2000).optional(), "must_have_overrides": z.array(z.string().min(1).max(120)).max(12).optional(), "must_not_overrides": z.array(z.string().min(1).max(120)).max(12).optional() }).strict() }).strict();
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const BridgeGenerateReferencesInputSchema = z.object({ "project_id": z.string().min(1), "run_id": z.string().min(1).optional(), "raw_user_intent": z.string().min(1).max(4000), "keywords": z.array(z.string().min(1).max(80)).min(0).max(12).describe("선택. 비어 있으면 context_scan 산출물 기반으로 자동 보강합니다.").optional(), "constraints": z.array(z.string().min(1).max(120)).max(12).optional(), "must_have": z.array(z.string().min(1).max(120)).max(12).optional(), "must_not": z.array(z.string().min(1).max(120)).max(12).optional(), "candidates_seed": z.array(z.object({ "title": z.string().min(1).max(120), "kind": z.enum(["oss_repo", "library", "product", "pattern", "doc"]), "primary_link": z.string().min(4).max(2000), "why_similar": z.string().max(1200).optional(), "fit_summary": z.string().max(1200).optional() }).strict()).max(10).describe("Optional. If provided, the tool will use these candidates as the primary source instead of running external search.").optional(), "max_candidates": z.number().int().gte(2).lte(3).default(3), "write_files": z.boolean().default(true) }).strict().describe("SSOT schema for vibe_pm.bridge_generate_references MCP tool input");
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const BridgeGenerateReferencesOutputSchema = z.object({ "success": z.boolean(), "project_id": z.string().min(1), "run_id": z.string().min(1), "references_path": z.string().min(1), "candidates": z.array(z.object({ "key": z.enum(["A", "B", "C"]), "title": z.string().min(1).max(120), "primary_link": z.string().min(4).max(2000) }).strict()).min(2).max(3), "warnings": z.array(z.string()), "next_action": z.object({ "tool": z.literal("vibe_pm.bridge_confirm_reference"), "reason": z.string().min(1) }).strict() }).strict().describe("SSOT schema for vibe_pm.bridge_generate_references MCP tool output");
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const BridgeReferencesFileSchema = z.object({ "schema_version": z.literal("bridge.references.v1"), "run_id": z.string().min(6).max(128), "project_id": z.string().min(1).max(128).optional(), "created_at": z.string().datetime({ offset: true }), "query": z.object({ "raw_user_intent": z.string().min(1).max(4000), "keywords": z.array(z.string().min(1).max(80)).min(1).max(12), "constraints": z.array(z.string().min(1).max(120)).max(12).optional(), "must_have": z.array(z.string().min(1).max(120)).max(12).optional(), "must_not": z.array(z.string().min(1).max(120)).max(12).optional() }).strict(), "candidates": z.array(z.object({ "key": z.enum(["A", "B", "C"]), "title": z.string().min(1).max(120), "kind": z.enum(["oss_repo", "library", "product", "pattern", "doc"]), "why_similar": z.string().min(1).max(1200), "fit_summary": z.string().max(1200).optional(), "confidence": z.number().int().gte(1).lte(5), "links": z.object({ "primary": z.string().min(4).max(2000), "secondary": z.array(z.string().min(4).max(2000)).max(5).optional() }).strict(), "evidence": z.object({ "source": z.enum(["github", "local_zoekt", "manual"]).optional(), "highlights": z.array(z.string().min(1).max(300)).max(8).optional() }).strict().optional() }).strict()).min(2).max(3) }).strict();
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const BridgeWorkOrderSeedFileSchema = z.object({ "schema_version": z.literal("bridge.work_order_seed.v1"), "run_id": z.string().min(6).max(128), "project_id": z.string().min(1).max(128).optional(), "created_at": z.string().datetime({ offset: true }), "intent": z.object({ "goal": z.string().min(1).max(2000), "user_story": z.string().min(1).max(2000), "acceptance": z.array(z.string().min(1).max(300)).min(3).max(12), "constraints": z.array(z.string().min(1).max(200)).max(12).optional(), "non_goals": z.array(z.string().min(1).max(200)).max(8).optional() }).strict(), "scope_seed": z.object({ "include": z.array(z.string().min(1).max(300)).min(1).max(20), "exclude": z.array(z.string().min(1).max(300)).max(20), "do_not_touch": z.array(z.string().min(1).max(300)).max(20).optional() }).strict(), "reference": z.object({ "key": z.enum(["A", "B", "C"]), "title": z.string().min(1).max(120), "primary_link": z.string().min(4).max(2000), "takeaways": z.array(z.string().min(1).max(200)).max(10).optional() }).strict(), "handoff": z.object({ "preferred_stack_hint": z.string().max(200).optional(), "risk_notes": z.array(z.string().min(1).max(200)).max(8).optional() }).strict().optional() }).strict();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Auto-generated from schemas/contracts.version.json + schemas/contracts.lock.json
|
|
2
2
|
// DO NOT EDIT MANUALLY - run scripts/generate-contracts.sh
|
|
3
|
-
export const CONTRACTS_VERSION = "1.8.
|
|
4
|
-
export const CONTRACTS_BUNDLE_SHA256 = "
|
|
5
|
-
export const CONTRACTS_SCHEMA_COUNT =
|
|
3
|
+
export const CONTRACTS_VERSION = "1.8.6";
|
|
4
|
+
export const CONTRACTS_BUNDLE_SHA256 = "117ae96448d5d0bd988281ae7170facabc90c20da074d1d3e845c63cc734ba06";
|
|
5
|
+
export const CONTRACTS_SCHEMA_COUNT = 103;
|
package/build/generated/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
export * from "./selection_validation_result.js";
|
|
4
4
|
export * from "./finalize_work_input.js";
|
|
5
5
|
export * from "./briefing_input.js";
|
|
6
|
+
export * from "./ingress_input.js";
|
|
6
7
|
export * from "./get_decision_input.js";
|
|
7
8
|
export * from "./submit_decision_input.js";
|
|
8
9
|
export * from "./create_work_order_input.js";
|
|
@@ -18,6 +19,9 @@ export * from "./run_app_input.js";
|
|
|
18
19
|
export * from "./export_output_input.js";
|
|
19
20
|
export * from "./search_oss_input.js";
|
|
20
21
|
export * from "./zoekt_evidence_input.js";
|
|
22
|
+
export * from "./bridge_generate_references_input.js";
|
|
23
|
+
export * from "./bridge_confirm_reference_input.js";
|
|
24
|
+
export * from "./bridge_build_seed_input.js";
|
|
21
25
|
export * from "./doctor_input.js";
|
|
22
26
|
export * from "./update_input.js";
|
|
23
27
|
export * from "./activate_input.js";
|
|
@@ -31,6 +35,7 @@ export * from "./vibe_pm_verdict.js";
|
|
|
31
35
|
export * from "./create_work_order_output.js";
|
|
32
36
|
export * from "./repair_plan_output.js";
|
|
33
37
|
export * from "./briefing_output.js";
|
|
38
|
+
export * from "./ingress_output.js";
|
|
34
39
|
export * from "./get_decision_output.js";
|
|
35
40
|
export * from "./submit_decision_output.js";
|
|
36
41
|
export * from "./status_output.js";
|
|
@@ -40,6 +45,12 @@ export * from "./memory_retrieve_output.js";
|
|
|
40
45
|
export * from "./memory_state_file.js";
|
|
41
46
|
export * from "./vibe_repo_config.js";
|
|
42
47
|
export * from "./current_work_order_file.js";
|
|
48
|
+
export * from "./ingress_resolution_file.js";
|
|
49
|
+
export * from "./ingress_summary_file.js";
|
|
50
|
+
export * from "./bridge_references_file.js";
|
|
51
|
+
export * from "./bridge_confirmed_reference_file.js";
|
|
52
|
+
export * from "./bridge_work_order_seed_file.js";
|
|
53
|
+
export * from "./message_template_id_mapping_file.js";
|
|
43
54
|
export * from "./advisory_review_output.js";
|
|
44
55
|
export * from "./scaffold_output.js";
|
|
45
56
|
export * from "./finalize_work_output.js";
|
|
@@ -48,6 +59,9 @@ export * from "./run_app_output.js";
|
|
|
48
59
|
export * from "./export_output_output.js";
|
|
49
60
|
export * from "./search_oss_output.js";
|
|
50
61
|
export * from "./zoekt_evidence_output.js";
|
|
62
|
+
export * from "./bridge_generate_references_output.js";
|
|
63
|
+
export * from "./bridge_confirm_reference_output.js";
|
|
64
|
+
export * from "./bridge_build_seed_output.js";
|
|
51
65
|
export * from "./doctor_output.js";
|
|
52
66
|
export * from "./update_output.js";
|
|
53
67
|
export * from "./activate_output.js";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const IngressInputSchema = z.object({ "project_id": z.string().min(1).describe("프로젝트 ID 또는 run_id (생략 시 최근 프로젝트 자동)").optional(), "run_id": z.string().min(1).describe("명시 run_id (project_id보다 우선)").optional(), "workspace_path": z.string().min(1).describe("업무 폴더(생략 시 현재 작업 폴더)").optional(), "resolution": z.enum(["INGRESS_RESOLVED_RESUME", "INGRESS_RESOLVED_NEW_TASK", "INGRESS_RESOLVED_ADOPT", "INGRESS_ABORTED"]).describe("Ingress 종료 상태"), "context_scan": z.boolean().describe("Context Scan 수행 여부(권장: true)").default(true), "write_files": z.boolean().describe("runs/<run_id>/ingress 산출물 파일 생성 여부(테스트용)").default(true) }).strict().describe("SSOT schema for vibe_pm.ingress MCP tool input");
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const IngressOutputSchema = z.object({ "success": z.boolean(), "project_id": z.string().min(1), "run_id": z.string().min(1), "workspace_path": z.string().min(1), "has_vibe_state": z.boolean(), "resolution": z.enum(["INGRESS_RESOLVED_RESUME", "INGRESS_RESOLVED_NEW_TASK", "INGRESS_RESOLVED_ADOPT", "INGRESS_ABORTED"]), "context_scan_required": z.boolean(), "context_scan_completed": z.boolean(), "ingress_resolution_path": z.string().min(1), "artifacts": z.object({ "project_fingerprint": z.string().min(1).optional(), "repo_map": z.string().min(1).optional(), "entrypoints": z.string().min(1).optional(), "docs_index": z.string().min(1).optional(), "risk_flags": z.string().min(1).optional(), "ingress_summary": z.string().min(1).optional() }).strict().optional(), "warnings": z.array(z.string().min(1)).describe("Best-effort 경고 목록").optional(), "message": z.string().min(1), "next_action": z.union([z.object({ "tool": z.enum(["vibe_pm.get_decision", "vibe_pm.briefing", "vibe_pm.ingress"]), "reason": z.string().min(1) }).strict(), z.null()]) }).strict().describe("SSOT schema for vibe_pm.ingress MCP tool output");
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const IngressResolutionFileSchema = z.object({ "schema_version": z.literal("ingress.resolution.v2"), "run_id": z.string().min(3).max(128), "project_id": z.string().min(1).max(128).optional(), "workspace_path": z.string().min(1).max(4096), "resolution": z.enum(["INGRESS_RESOLVED_RESUME", "INGRESS_RESOLVED_NEW_TASK", "INGRESS_RESOLVED_ADOPT", "INGRESS_ABORTED"]), "has_vibe_state": z.boolean(), "context_scan_required": z.boolean(), "context_scan_completed": z.boolean(), "created_at": z.string().datetime({ offset: true }) }).strict();
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const IngressSummaryFileSchema = z.object({ "format_version": z.literal("ingress.summary.v1"), "run_id": z.string().min(3).max(128), "project_id": z.string().min(1).max(128).optional(), "created_at": z.string().datetime({ offset: true }), "producer": z.object({ "name": z.string().min(1).max(120), "version": z.string().min(1).max(64), "build": z.string().min(1).max(128).optional() }).strict(), "pointers": z.object({ "project_fingerprint": z.string().min(1).max(512), "repo_map": z.string().min(1).max(512), "entrypoints": z.string().min(1).max(512), "docs_index": z.string().min(1).max(512), "risk_flags": z.string().min(1).max(512) }).strict(), "high_level": z.object({ "repo_kind_hint": z.string().max(200).optional(), "entrypoint_status": z.enum(["OK", "AMBIGUOUS", "NONE"]), "clarification_recommended": z.boolean(), "clarification_reasons": z.array(z.string().min(1).max(120)).max(12).optional(), "docs_index_count": z.number().int().gte(0) }).strict(), "consumption_policy": z.object({ "front_reads": z.array(z.string().min(1).max(120)).min(1).max(20), "bridge_reads": z.array(z.string().min(1).max(120)).min(1).max(20), "back_reads": z.array(z.string().min(1).max(120)).min(1).max(20) }).strict() }).strict();
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const MessageTemplateIdMappingFileSchema = z.object({ "version": z.literal("message_template_id_mapping.v1"), "description": z.string().min(0).max(2000).optional(), "default_locale": z.any(), "supported_locales": z.array(z.any()).min(1).refine((xs) => new Set(xs).size === xs.length, { message: "items must be unique" }), "human_language_policy": z.object({ "response_language_strategy": z.enum(["user_last_input", "fixed", "ask_once_on_conflict"]), "fallback_locale": z.any(), "max_confirmations_per_project": z.number().int().gte(0).lte(3) }).strict().optional(), "templates": z.object({ "GO-01": z.any(), "GO-02": z.any(), "FIX-01": z.any(), "FIX-02": z.any(), "BLOCK-01": z.any(), "BLOCK-02": z.any(), "TRIAGE-SKIP-01": z.any(), "TRIAGE-RUN-01": z.any(), "TRIAGE-RUN-02": z.any(), "ADV-01": z.any(), "ADV-02": z.any(), "WARN-01": z.any(), "WARN-02": z.any(), "SOFTBLOCK-01": z.any() }).strict(), "term_dictionary": z.object({ "entries": z.array(z.any()).min(1).max(200) }).strict() }).strict();
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export const RunAppInputSchema = z.object({ "mode": z.enum(["dev", "prod", "test"]).describe("실행 모드").default("dev"), "port": z.number().int().gte(1).lte(65535).describe("포트 번호 (선택사항)").optional(), "container": z.enum(["none", "podman"]).describe("실행 런타임 선택. 기본값은 기존 호스트 실행을 유지합니다.").default("none"), "container_image": z.string().regex(new RegExp("^[a-z0-9][a-z0-9./:_-]{0,199}$")).min(1).max(200).describe("컨테이너 이미지. 재현성을 위해 ':latest' 사용은 경고/차단 대상입니다(정책은 코드에서 강제).").optional(), "container_ports": z.array(z.number().int().gte(1).lte(65535)).max(32).refine((xs) => new Set(xs).size === xs.length, { message: "items must be unique" }).describe("컨테이너 포트 목록(선택). 생략 시 port(또는 프로젝트 디폴트 포트) 기반으로 자동 선택합니다.").optional(), "container_env": z.record(z.string().max(4096)).describe("컨테이너 환경변수(선택). allowlist 정책은 코드에서 강제합니다.").optional(), "container_mounts": z.array(z.object({ "host": z.string().min(1).max(512).describe("호스트 경로(상대/절대). repoRoot 밖 경로는 코드에서 차단합니다."), "container": z.string().regex(new RegExp("^/.*")).min(1).max(256).describe("컨테이너 내부 경로(절대 경로)"), "mode": z.enum(["ro", "rw"]).describe("마운트 모드") }).strict()).max(16).describe("컨테이너 마운트 목록(선택). 생략 시 repoRoot -> /work:rw 를 기본으로 사용합니다.").optional() }).strict().describe("SSOT schema for vibe_pm.run_app MCP tool input");
|
|
2
|
+
export const RunAppInputSchema = z.object({ "mode": z.enum(["dev", "prod", "test"]).describe("실행 모드").default("dev"), "port": z.number().int().gte(1).lte(65535).describe("포트 번호 (선택사항)").optional(), "container": z.enum(["none", "podman"]).describe("실행 런타임 선택. 기본값은 기존 호스트 실행을 유지합니다.").default("none"), "container_image": z.string().regex(new RegExp("^[a-z0-9][a-z0-9./:_-]{0,199}$")).min(1).max(200).describe("컨테이너 이미지. 재현성을 위해 ':latest' 사용은 경고/차단 대상입니다(정책은 코드에서 강제).").optional(), "container_ports": z.array(z.number().int().gte(1).lte(65535)).max(32).refine((xs) => new Set(xs).size === xs.length, { message: "items must be unique" }).describe("컨테이너 포트 목록(선택). 생략 시 port(또는 프로젝트 디폴트 포트) 기반으로 자동 선택합니다.").optional(), "container_env": z.record(z.string().max(4096)).describe("컨테이너 환경변수(선택). allowlist 정책은 코드에서 강제합니다.").optional(), "container_mounts": z.array(z.object({ "host": z.string().min(1).max(512).describe("호스트 경로(상대/절대). repoRoot 밖 경로는 코드에서 차단합니다."), "container": z.string().regex(new RegExp("^/.*")).min(1).max(256).describe("컨테이너 내부 경로(절대 경로)"), "mode": z.enum(["ro", "rw"]).describe("마운트 모드") }).strict()).max(16).describe("컨테이너 마운트 목록(선택). 생략 시 repoRoot -> /work:rw 를 기본으로 사용합니다.").optional(), "option_key": z.enum(["A", "B", "C"]).describe("실행 후보가 여러 개일 때 선택 키(A/B/C). 후보가 1개면 생략 가능합니다.").optional() }).strict().describe("SSOT schema for vibe_pm.run_app MCP tool input");
|
package/build/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
// adapters/mcp-ts/src/index.ts
|
|
3
2
|
// VibeCoding MCP Server (One-click Bootstrap)
|
|
4
3
|
import * as fs from "node:fs";
|
|
@@ -51,6 +50,7 @@ const rp = defineReactPerfTools();
|
|
|
51
50
|
// NEW: vibe_pm.* Tools (Recommended)
|
|
52
51
|
// ============================================================
|
|
53
52
|
server.tool("vibe_pm.briefing", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.briefing"], pm.briefingInputSchema.shape, async (input) => pm.vibePmBriefing(pm.briefingInputSchema.parse(input)));
|
|
53
|
+
server.tool("vibe_pm.ingress", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.ingress"], pm.ingressInputSchema.shape, async (input) => pm.vibePmIngress(pm.ingressInputSchema.parse(input)));
|
|
54
54
|
server.tool("vibe_pm.get_decision", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.get_decision"], pm.getDecisionInputSchema.shape, async (input) => pm.vibePmGetDecision(pm.getDecisionInputSchema.parse(input)));
|
|
55
55
|
server.tool("vibe_pm.submit_decision", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.submit_decision"], pm.submitDecisionInputSchema.shape, async (input) => pm.vibePmSubmitDecision(pm.submitDecisionInputSchema.parse(input)));
|
|
56
56
|
server.tool("vibe_pm.create_work_order", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.create_work_order"], pm.createWorkOrderInputSchema.shape, async (input) => pm.vibePmCreateWorkOrder(pm.createWorkOrderInputSchema.parse(input)));
|
|
@@ -70,9 +70,10 @@ server.tool("vibe_pm.run_app", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.run_app"], pm.
|
|
|
70
70
|
server.tool("vibe_pm.export_output", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.export_output"], pm.exportOutputInputSchema.shape, async (input) => pm.vibePmExportOutput(pm.exportOutputInputSchema.parse(input)));
|
|
71
71
|
server.tool("vibe_pm.search_oss", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.search_oss"], pm.searchOssInputSchema.shape, async (input) => pm.vibePmSearchOss(pm.searchOssInputSchema.parse(input)));
|
|
72
72
|
server.tool("vibe_pm.zoekt_evidence", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.zoekt_evidence"], pm.zoektEvidenceInputSchema.shape, async (input) => pm.vibePmZoektEvidence(pm.zoektEvidenceInputSchema.parse(input)));
|
|
73
|
+
server.tool("vibe_pm.bridge_generate_references", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.bridge_generate_references"], pm.bridgeGenerateReferencesInputSchema.shape, async (input) => pm.vibePmBridgeGenerateReferences(pm.bridgeGenerateReferencesInputSchema.parse(input)));
|
|
74
|
+
server.tool("vibe_pm.bridge_confirm_reference", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.bridge_confirm_reference"], pm.bridgeConfirmReferenceInputSchema.shape, async (input) => pm.vibePmBridgeConfirmReference(pm.bridgeConfirmReferenceInputSchema.parse(input)));
|
|
75
|
+
server.tool("vibe_pm.bridge_build_seed", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.bridge_build_seed"], pm.bridgeBuildSeedInputSchema.shape, async (input) => pm.vibePmBridgeBuildSeed(pm.bridgeBuildSeedInputSchema.parse(input)));
|
|
73
76
|
server.tool("vibe_pm.gate", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.gate"], pm.gateInputSchema.shape, async (input) => pm.vibePmGate(pm.gateInputSchema.parse(input)));
|
|
74
|
-
server.tool("vibe_pm.save_rule", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.save_rule"], pm.saveRuleInputSchema.shape, async (input) => pm.vibePmSaveRule(pm.saveRuleInputSchema.parse(input)));
|
|
75
|
-
server.tool("vibe_pm.list_rules", VIBE_PM_TOOL_DESCRIPTIONS["vibe_pm.list_rules"], pm.listRulesInputSchema.shape, async (input) => pm.vibePmListRules(pm.listRulesInputSchema.parse(input)));
|
|
76
77
|
// ============================================================
|
|
77
78
|
// OPTIONAL: react_perf.* Tools (Performance Analysis)
|
|
78
79
|
// ============================================================
|
|
@@ -15,6 +15,7 @@ export function getVibeRepoPaths(repoRoot) {
|
|
|
15
15
|
archiveDir: path.join(vibeDir, "archive"),
|
|
16
16
|
hooksDir,
|
|
17
17
|
libDir,
|
|
18
|
+
skillsDir: path.join(vibeDir, "skills"),
|
|
18
19
|
validateScript: path.join(libDir, "validate.sh"),
|
|
19
20
|
prePushHook: path.join(hooksDir, "pre-push"),
|
|
20
21
|
ciWorkflowFile,
|
|
@@ -4,6 +4,7 @@ import { initLocalModeRepo } from "./init.js";
|
|
|
4
4
|
import { writeVersionLock } from "./version-lock.js";
|
|
5
5
|
import { ensureEngines } from "../bootstrap/installer.js";
|
|
6
6
|
import { healthCheck } from "../bootstrap/doctor.js";
|
|
7
|
+
import { activateSkills, getSkillsHealth } from "../bootstrap/skills-installer.js";
|
|
7
8
|
/**
|
|
8
9
|
* Run unified setup process
|
|
9
10
|
*/
|
|
@@ -42,5 +43,24 @@ export async function runSetup(repoRoot, cliVersion, opts) {
|
|
|
42
43
|
catch (e) {
|
|
43
44
|
warnings.push(`버전 잠금 파일 생성 실패: ${e instanceof Error ? e.message : String(e)}`);
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
+
// Step 4: Skills activation
|
|
47
|
+
let skills = { activated: [], errors: [] };
|
|
48
|
+
if (!opts.skipSkills) {
|
|
49
|
+
try {
|
|
50
|
+
const skillsHealth = getSkillsHealth();
|
|
51
|
+
if (skillsHealth.installed && skillsHealth.skills.length > 0) {
|
|
52
|
+
// Activate all available skills by default
|
|
53
|
+
skills = await activateSkills(repoRoot);
|
|
54
|
+
if (skills.errors.length > 0) {
|
|
55
|
+
for (const err of skills.errors) {
|
|
56
|
+
warnings.push(`스킬 활성화 오류: ${err}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
warnings.push(`스킬 활성화 실패: ${e instanceof Error ? e.message : String(e)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { local, engines, skills, versionLock, warnings };
|
|
46
66
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/path-utils.ts
|
|
2
|
+
// P1-5: Centralized path normalization utilities (DRY)
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Convert Windows backslashes to POSIX forward slashes.
|
|
6
|
+
* This is the most basic normalization used across the codebase.
|
|
7
|
+
*/
|
|
8
|
+
export function toPosixPath(value) {
|
|
9
|
+
return value.replace(/\\/g, "/");
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Normalize a relative path for POSIX style:
|
|
13
|
+
* - Convert backslashes to forward slashes
|
|
14
|
+
* - Trim whitespace
|
|
15
|
+
* - Remove leading slashes (makes it relative)
|
|
16
|
+
*/
|
|
17
|
+
export function normalizeRelativePosix(value) {
|
|
18
|
+
return (value ?? "").trim().replace(/\\/g, "/").replace(/^\/+/, "");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Normalize a path for comparison/matching:
|
|
22
|
+
* - Convert backslashes to forward slashes
|
|
23
|
+
* - Trim whitespace
|
|
24
|
+
* - Remove leading "./" prefix
|
|
25
|
+
*/
|
|
26
|
+
export function normalizeForMatch(value) {
|
|
27
|
+
let v = (value ?? "").trim().replace(/\\/g, "/");
|
|
28
|
+
if (v.startsWith("./"))
|
|
29
|
+
v = v.slice(2);
|
|
30
|
+
return v;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve a path relative to a base path and normalize to POSIX.
|
|
34
|
+
* Returns empty string for empty input, "." for base path itself.
|
|
35
|
+
*/
|
|
36
|
+
export function resolveRelativePosix(basePath, value) {
|
|
37
|
+
const trimmed = (value ?? "").trim();
|
|
38
|
+
if (!trimmed)
|
|
39
|
+
return "";
|
|
40
|
+
const absolute = path.isAbsolute(trimmed) ? trimmed : path.resolve(basePath, trimmed);
|
|
41
|
+
const relative = path.relative(basePath, absolute);
|
|
42
|
+
if (!relative)
|
|
43
|
+
return ".";
|
|
44
|
+
return toPosixPath(relative);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Process and deduplicate an array of paths, normalizing each to POSIX relative format.
|
|
48
|
+
* Empty and duplicate paths are filtered out.
|
|
49
|
+
*/
|
|
50
|
+
export function normalizePathArray(basePath, values) {
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
const out = [];
|
|
53
|
+
for (const raw of values) {
|
|
54
|
+
const normalized = resolveRelativePosix(basePath, raw);
|
|
55
|
+
if (!normalized || seen.has(normalized))
|
|
56
|
+
continue;
|
|
57
|
+
seen.add(normalized);
|
|
58
|
+
out.push(normalized);
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Deduplicate and normalize an array of strings for set operations.
|
|
64
|
+
* Uses basic POSIX normalization (backslash to forward slash).
|
|
65
|
+
*/
|
|
66
|
+
export function uniquePosixPaths(items) {
|
|
67
|
+
return Array.from(new Set(items.map((p) => normalizeRelativePosix(p)).filter(Boolean))).sort();
|
|
68
|
+
}
|
|
@@ -66,7 +66,7 @@ const SYSTEM_BINS = new Set([
|
|
|
66
66
|
]);
|
|
67
67
|
/** Engine binary names (for type checking) */
|
|
68
68
|
const ENGINE_BINS = new Set([
|
|
69
|
-
"spec-high", "
|
|
69
|
+
"spec-high", "vibe-execution-engine", "clinic",
|
|
70
70
|
]);
|
|
71
71
|
/**
|
|
72
72
|
* Resolve binary path for a given CLI bin
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// adapters/mcp-ts/src/tools/vibe_pm/advisory_review.ts
|
|
2
2
|
// vibe_pm.advisory_review - Advisory review (triage/quick/thorough) with routing + cache
|
|
3
|
-
import {
|
|
3
|
+
import { runPythonCli } from "../../engine.js";
|
|
4
4
|
import { safeJsonParse } from "../../cli.js";
|
|
5
5
|
import { validateToolInput } from "../../security/input-validator.js";
|
|
6
6
|
import { resolveProjectId } from "./context.js";
|
|
7
7
|
import { AdvisoryReviewOutputSchema } from "../../generated/advisory_review_output.js";
|
|
8
|
+
import { classifyPythonError } from "./python_error.js";
|
|
8
9
|
function clipArg(value, maxChars) {
|
|
9
10
|
const v = value.trim();
|
|
10
11
|
if (v.length <= maxChars)
|
|
@@ -65,9 +66,10 @@ export async function advisoryReview(input) {
|
|
|
65
66
|
args.push("--product-intent-refs", input.product_intent_refs.join(","));
|
|
66
67
|
}
|
|
67
68
|
const timeoutMs = mode === "triage" ? 30_000 : 180_000;
|
|
68
|
-
const { code, stdout, stderr } = await
|
|
69
|
+
const { code, stdout, stderr } = await runPythonCli(args, { timeoutMs });
|
|
69
70
|
if (code !== 0) {
|
|
70
|
-
|
|
71
|
+
const classified = classifyPythonError(stderr ?? "", code, "Advisory Review");
|
|
72
|
+
throw new Error(`${classified.issueCode}: ${classified.summary}`);
|
|
71
73
|
}
|
|
72
74
|
const parsed = safeJsonParse(stdout);
|
|
73
75
|
if (!parsed.ok) {
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/vibe_pm/bridge_build_seed.ts
|
|
2
|
+
// vibe_pm.bridge_build_seed - Create runs/<run_id>/bridge/work_order_seed.json
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { validateToolInput } from "../../security/input-validator.js";
|
|
6
|
+
import { resolveProjectId, resolveRunId, getRunContext } from "./context.js";
|
|
7
|
+
import { BridgeConfirmedReferenceFileSchema } from "../../generated/bridge_confirmed_reference_file.js";
|
|
8
|
+
import { BridgeReferencesFileSchema } from "../../generated/bridge_references_file.js";
|
|
9
|
+
import { BridgeWorkOrderSeedFileSchema } from "../../generated/bridge_work_order_seed_file.js";
|
|
10
|
+
function sanitizeStrings(values, maxItems) {
|
|
11
|
+
if (!Array.isArray(values))
|
|
12
|
+
return [];
|
|
13
|
+
return values
|
|
14
|
+
.map((v) => (typeof v === "string" ? v.trim() : ""))
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.slice(0, maxItems);
|
|
17
|
+
}
|
|
18
|
+
function toRelPath(basePath, absPath) {
|
|
19
|
+
const rel = path.relative(basePath, absPath);
|
|
20
|
+
return rel.replace(/\\\\/g, "/");
|
|
21
|
+
}
|
|
22
|
+
function looksKorean(text) {
|
|
23
|
+
return /[가-힣]/.test(text);
|
|
24
|
+
}
|
|
25
|
+
export async function bridgeBuildSeed(input, basePath = process.cwd()) {
|
|
26
|
+
validateToolInput({ project_id: input.project_id, run_id: input.run_id });
|
|
27
|
+
const resolvedRun = resolveRunId(input.run_id ?? input.project_id, basePath);
|
|
28
|
+
const run_id = resolvedRun.run_id;
|
|
29
|
+
const project_id = resolveProjectId(run_id, basePath);
|
|
30
|
+
const context = getRunContext(run_id, basePath);
|
|
31
|
+
const bridgeDir = path.join(context.runs_path, "bridge");
|
|
32
|
+
const confirmedPathAbs = path.join(bridgeDir, "confirmed_reference.json");
|
|
33
|
+
const referencesPathAbs = path.join(bridgeDir, "references.json");
|
|
34
|
+
const seedPathAbs = path.join(bridgeDir, "work_order_seed.json");
|
|
35
|
+
// Rule C: confirmed_reference 없이는 seed 생성 불가
|
|
36
|
+
let confirmedRaw;
|
|
37
|
+
try {
|
|
38
|
+
confirmedRaw = JSON.parse(await fs.readFile(confirmedPathAbs, "utf-8"));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
throw new Error("confirmed_reference.json이 없어 seed를 만들 수 없습니다. 먼저 레퍼런스를 확정하세요.");
|
|
42
|
+
}
|
|
43
|
+
const confirmed = BridgeConfirmedReferenceFileSchema.parse(confirmedRaw);
|
|
44
|
+
const overwrite = input?.overwrite !== false;
|
|
45
|
+
if (!overwrite) {
|
|
46
|
+
try {
|
|
47
|
+
await fs.access(seedPathAbs);
|
|
48
|
+
throw new Error("work_order_seed.json이 이미 존재합니다. overwrite=true로 다시 시도하세요.");
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// ok (does not exist)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
let rawIntent = typeof input?.raw_user_intent === "string" ? input.raw_user_intent.trim() : "";
|
|
55
|
+
if (!rawIntent) {
|
|
56
|
+
try {
|
|
57
|
+
const refsRaw = JSON.parse(await fs.readFile(referencesPathAbs, "utf-8"));
|
|
58
|
+
const refs = BridgeReferencesFileSchema.parse(refsRaw);
|
|
59
|
+
rawIntent = typeof refs.query?.raw_user_intent === "string" ? refs.query.raw_user_intent.trim() : "";
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// ignore
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!rawIntent)
|
|
66
|
+
rawIntent = "Clarify requirements using the selected reference.";
|
|
67
|
+
const isKo = looksKorean(rawIntent);
|
|
68
|
+
const goal = typeof input?.goal === "string" && input.goal.trim()
|
|
69
|
+
? input.goal.trim()
|
|
70
|
+
: isKo
|
|
71
|
+
? `레퍼런스(${confirmed.selected.title}) 기준으로 요구사항을 정규화한다: ${rawIntent}`
|
|
72
|
+
: `Normalize requirements using reference (${confirmed.selected.title}): ${rawIntent}`;
|
|
73
|
+
const userStory = typeof input?.user_story === "string" && input.user_story.trim()
|
|
74
|
+
? input.user_story.trim()
|
|
75
|
+
: isKo
|
|
76
|
+
? `사용자로서, 나는 ${rawIntent}을(를) 원한다. 그래서 원하는 결과를 빠르게 얻을 수 있다.`
|
|
77
|
+
: `As a user, I want ${rawIntent} so I can get the intended result quickly.`;
|
|
78
|
+
const acceptance = sanitizeStrings(input?.acceptance, 12);
|
|
79
|
+
const acceptanceFinal = acceptance.length >= 3
|
|
80
|
+
? acceptance
|
|
81
|
+
: isKo
|
|
82
|
+
? [
|
|
83
|
+
"레퍼런스 확정(confirmed_reference.json)이 기록된다",
|
|
84
|
+
"seed(work_order_seed.json)이 생성된다",
|
|
85
|
+
"create_work_order가 seed를 소비한다"
|
|
86
|
+
]
|
|
87
|
+
: [
|
|
88
|
+
"Confirmed reference is written (confirmed_reference.json)",
|
|
89
|
+
"Seed is written (work_order_seed.json)",
|
|
90
|
+
"create_work_order consumes the seed when it exists"
|
|
91
|
+
];
|
|
92
|
+
const constraints = mergeOverrides(sanitizeStrings(input?.constraints, 12), sanitizeStrings(confirmed?.user_confirmation?.must_have_overrides, 12), 12);
|
|
93
|
+
const nonGoals = mergeOverrides(sanitizeStrings(input?.non_goals, 8), sanitizeStrings(confirmed?.user_confirmation?.must_not_overrides, 8), 8);
|
|
94
|
+
const scopeSeedRaw = input?.scope_seed;
|
|
95
|
+
const scope_seed = scopeSeedRaw && typeof scopeSeedRaw === "object"
|
|
96
|
+
? {
|
|
97
|
+
include: sanitizeStrings(scopeSeedRaw?.include, 20),
|
|
98
|
+
exclude: sanitizeStrings(scopeSeedRaw?.exclude, 20),
|
|
99
|
+
do_not_touch: sanitizeStrings(scopeSeedRaw?.do_not_touch, 20)
|
|
100
|
+
}
|
|
101
|
+
: {
|
|
102
|
+
include: ["src/**", "tests/**"],
|
|
103
|
+
exclude: ["**/.vibe/**", "config/**"],
|
|
104
|
+
do_not_touch: ["config/**", ".env*", "*.pem", "*.key", ".vibe/**"]
|
|
105
|
+
};
|
|
106
|
+
if (scope_seed.include.length === 0)
|
|
107
|
+
scope_seed.include = ["src/**"];
|
|
108
|
+
const preferredStackHint = typeof input?.preferred_stack_hint === "string" ? input.preferred_stack_hint : "";
|
|
109
|
+
const riskNotes = sanitizeStrings(input?.risk_notes, 8);
|
|
110
|
+
const nowIso = new Date().toISOString();
|
|
111
|
+
const seedDoc = BridgeWorkOrderSeedFileSchema.parse({
|
|
112
|
+
schema_version: "bridge.work_order_seed.v1",
|
|
113
|
+
run_id,
|
|
114
|
+
project_id,
|
|
115
|
+
created_at: nowIso,
|
|
116
|
+
intent: {
|
|
117
|
+
goal,
|
|
118
|
+
user_story: userStory,
|
|
119
|
+
acceptance: acceptanceFinal,
|
|
120
|
+
constraints,
|
|
121
|
+
non_goals: nonGoals
|
|
122
|
+
},
|
|
123
|
+
scope_seed,
|
|
124
|
+
reference: {
|
|
125
|
+
key: confirmed.selected.key,
|
|
126
|
+
title: confirmed.selected.title,
|
|
127
|
+
primary_link: confirmed.selected.primary_link,
|
|
128
|
+
takeaways: []
|
|
129
|
+
},
|
|
130
|
+
handoff: {
|
|
131
|
+
preferred_stack_hint: preferredStackHint,
|
|
132
|
+
risk_notes: riskNotes
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
if (input?.write_files !== false) {
|
|
136
|
+
await fs.mkdir(bridgeDir, { recursive: true });
|
|
137
|
+
await fs.writeFile(seedPathAbs, JSON.stringify(seedDoc, null, 2) + "\n", "utf-8");
|
|
138
|
+
}
|
|
139
|
+
const out = {
|
|
140
|
+
success: true,
|
|
141
|
+
project_id,
|
|
142
|
+
run_id,
|
|
143
|
+
seed_path: toRelPath(basePath, seedPathAbs),
|
|
144
|
+
next_action: {
|
|
145
|
+
tool: "vibe_pm.create_work_order",
|
|
146
|
+
reason: "seed를 기반으로 작업 지시서를 발행하세요."
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
function mergeOverrides(base, overrides, maxItems) {
|
|
152
|
+
const seen = new Set();
|
|
153
|
+
const out = [];
|
|
154
|
+
for (const x of [...base, ...overrides]) {
|
|
155
|
+
const v = typeof x === "string" ? x.trim() : "";
|
|
156
|
+
if (!v || seen.has(v))
|
|
157
|
+
continue;
|
|
158
|
+
seen.add(v);
|
|
159
|
+
out.push(v);
|
|
160
|
+
if (out.length >= maxItems)
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/vibe_pm/bridge_confirm_reference.ts
|
|
2
|
+
// vibe_pm.bridge_confirm_reference - Create runs/<run_id>/bridge/confirmed_reference.json
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { validateToolInput } from "../../security/input-validator.js";
|
|
6
|
+
import { resolveProjectId, resolveRunId, getRunContext } from "./context.js";
|
|
7
|
+
import { BridgeReferencesFileSchema } from "../../generated/bridge_references_file.js";
|
|
8
|
+
import { BridgeConfirmedReferenceFileSchema } from "../../generated/bridge_confirmed_reference_file.js";
|
|
9
|
+
function sanitizeStrings(values, maxItems) {
|
|
10
|
+
if (!Array.isArray(values))
|
|
11
|
+
return [];
|
|
12
|
+
return values
|
|
13
|
+
.map((v) => (typeof v === "string" ? v.trim() : ""))
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.slice(0, maxItems);
|
|
16
|
+
}
|
|
17
|
+
function toRelPath(basePath, absPath) {
|
|
18
|
+
const rel = path.relative(basePath, absPath);
|
|
19
|
+
return rel.replace(/\\\\/g, "/");
|
|
20
|
+
}
|
|
21
|
+
export async function bridgeConfirmReference(input, basePath = process.cwd()) {
|
|
22
|
+
validateToolInput({ project_id: input.project_id, run_id: input.run_id });
|
|
23
|
+
const resolvedRun = resolveRunId(input.run_id ?? input.project_id, basePath);
|
|
24
|
+
const run_id = resolvedRun.run_id;
|
|
25
|
+
const project_id = resolveProjectId(run_id, basePath);
|
|
26
|
+
const context = getRunContext(run_id, basePath);
|
|
27
|
+
const bridgeDir = path.join(context.runs_path, "bridge");
|
|
28
|
+
const referencesPathAbs = path.join(bridgeDir, "references.json");
|
|
29
|
+
const confirmedPathAbs = path.join(bridgeDir, "confirmed_reference.json");
|
|
30
|
+
let referencesRaw;
|
|
31
|
+
try {
|
|
32
|
+
referencesRaw = JSON.parse(await fs.readFile(referencesPathAbs, "utf-8"));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new Error("references.json이 없어 레퍼런스를 확정할 수 없습니다. 먼저 레퍼런스 후보를 생성하세요.");
|
|
36
|
+
}
|
|
37
|
+
const references = BridgeReferencesFileSchema.parse(referencesRaw);
|
|
38
|
+
const selectedKey = String(input?.selected_key ?? "").trim();
|
|
39
|
+
if (!["A", "B", "C"].includes(selectedKey)) {
|
|
40
|
+
throw new Error("selected_key는 A/B/C 중 하나여야 합니다.");
|
|
41
|
+
}
|
|
42
|
+
const selected = (references.candidates ?? []).find((c) => c?.key === selectedKey);
|
|
43
|
+
if (!selected) {
|
|
44
|
+
throw new Error("선택한 레퍼런스를 찾을 수 없습니다. A/B/C 중 다시 선택해주세요.");
|
|
45
|
+
}
|
|
46
|
+
const rawAnswer = String(input?.raw_answer ?? "").trim();
|
|
47
|
+
if (!rawAnswer)
|
|
48
|
+
throw new Error("raw_answer is required");
|
|
49
|
+
const notes = typeof input?.notes === "string" ? input.notes : "";
|
|
50
|
+
const mustHave = sanitizeStrings(input?.must_have_overrides, 12);
|
|
51
|
+
const mustNot = sanitizeStrings(input?.must_not_overrides, 12);
|
|
52
|
+
const nowIso = new Date().toISOString();
|
|
53
|
+
const confirmedDoc = BridgeConfirmedReferenceFileSchema.parse({
|
|
54
|
+
schema_version: "bridge.confirmed_reference.v1",
|
|
55
|
+
run_id,
|
|
56
|
+
project_id,
|
|
57
|
+
created_at: nowIso,
|
|
58
|
+
selected: {
|
|
59
|
+
key: selectedKey,
|
|
60
|
+
title: selected.title,
|
|
61
|
+
kind: selected.kind,
|
|
62
|
+
primary_link: selected.links.primary
|
|
63
|
+
},
|
|
64
|
+
user_confirmation: {
|
|
65
|
+
raw_answer: rawAnswer,
|
|
66
|
+
notes,
|
|
67
|
+
must_have_overrides: mustHave,
|
|
68
|
+
must_not_overrides: mustNot
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
if (input?.write_files !== false) {
|
|
72
|
+
await fs.mkdir(bridgeDir, { recursive: true });
|
|
73
|
+
await fs.writeFile(confirmedPathAbs, JSON.stringify(confirmedDoc, null, 2) + "\n", "utf-8");
|
|
74
|
+
}
|
|
75
|
+
const out = {
|
|
76
|
+
success: true,
|
|
77
|
+
project_id,
|
|
78
|
+
run_id,
|
|
79
|
+
confirmed_reference_path: toRelPath(basePath, confirmedPathAbs),
|
|
80
|
+
selected: {
|
|
81
|
+
key: confirmedDoc.selected.key,
|
|
82
|
+
title: confirmedDoc.selected.title,
|
|
83
|
+
primary_link: confirmedDoc.selected.primary_link
|
|
84
|
+
},
|
|
85
|
+
next_action: {
|
|
86
|
+
tool: "vibe_pm.bridge_build_seed",
|
|
87
|
+
reason: "확정된 레퍼런스를 기반으로 seed를 생성하세요."
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
return out;
|
|
91
|
+
}
|