@vibecodetown/mcp-server 2.1.4 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/build/auth/credential_store.js +146 -0
- 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/control_plane/gate.js +52 -70
- 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 -1
- package/build/local-mode/git.js +36 -22
- package/build/local-mode/paths.js +1 -0
- package/build/local-mode/project-state.js +176 -0
- package/build/local-mode/setup.js +21 -1
- package/build/local-mode/templates.js +3 -3
- package/build/path-utils.js +68 -0
- package/build/runtime/cli_invoker.js +416 -0
- 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 +26 -1
- 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 +74 -0
- package/build/tools/vibe_pm/force_override.js +104 -0
- package/build/tools/vibe_pm/get_decision.js +2 -2
- package/build/tools/vibe_pm/index.js +160 -3
- 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/list_rules.js +135 -0
- 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/pre_commit_analysis.js +292 -0
- package/build/tools/vibe_pm/publish_mcp.js +271 -0
- package/build/tools/vibe_pm/python_error.js +115 -0
- package/build/tools/vibe_pm/run_app.js +215 -86
- package/build/tools/vibe_pm/run_app_podman.js +64 -2
- package/build/tools/vibe_pm/save_rule.js +120 -0
- 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 +23 -20
- 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/version-check.js +5 -5
- package/build/vibe-cli.js +742 -39
- package/package.json +5 -4
- package/skills/VRIP_INSTALL_MANIFEST_DOCTOR.skill.md +288 -0
- package/skills/index.json +14 -0
package/build/engine.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// adapters/mcp-ts/src/engine.ts
|
|
2
2
|
// Engine execution using cached binaries (no PATH dependency)
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
3
6
|
import { ensureEngines } from "./bootstrap/installer.js";
|
|
4
7
|
import { runCmd } from "./cli.js";
|
|
5
8
|
import { TieredCache, createCacheKey } from "./cache/index.js";
|
|
@@ -104,3 +107,151 @@ export function invalidateEngineCache(pattern) {
|
|
|
104
107
|
export function getEngineCacheStats() {
|
|
105
108
|
return engineResultCache.stats();
|
|
106
109
|
}
|
|
110
|
+
// ============================================================
|
|
111
|
+
// Python CLI Support (vibecoding_helper package)
|
|
112
|
+
// ============================================================
|
|
113
|
+
/**
|
|
114
|
+
* Python CLI commands that are safe to cache (read-only operations)
|
|
115
|
+
*/
|
|
116
|
+
const PYTHON_CACHEABLE_COMMANDS = new Set([
|
|
117
|
+
"memory-status",
|
|
118
|
+
"kce-status",
|
|
119
|
+
"entity-gate",
|
|
120
|
+
"show-ask-queue",
|
|
121
|
+
]);
|
|
122
|
+
function isDebugMode() {
|
|
123
|
+
const v = (process.env.VIBECODE_DEBUG ?? "").trim().toLowerCase();
|
|
124
|
+
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Find repo root for Python import.
|
|
128
|
+
*
|
|
129
|
+
* Priority order:
|
|
130
|
+
* 1. VIBECODE_PYTHONPATH environment variable (explicit override)
|
|
131
|
+
* 2. MCP server directory (fallback for when MCP runs from user project)
|
|
132
|
+
* 3. startDir upwards search (heuristic: nearest ancestor with pyproject.toml or vibecoding_helper/)
|
|
133
|
+
*
|
|
134
|
+
* @param startDir - Directory to start searching from
|
|
135
|
+
* @returns Repo root path or null if not found
|
|
136
|
+
*/
|
|
137
|
+
export function findPythonRepoRoot(startDir) {
|
|
138
|
+
// Priority 1: VIBECODE_PYTHONPATH explicit override
|
|
139
|
+
const override = process.env.VIBECODE_PYTHONPATH?.trim();
|
|
140
|
+
if (override && existsSync(override)) {
|
|
141
|
+
return override;
|
|
142
|
+
}
|
|
143
|
+
// Priority 2: MCP server directory fallback
|
|
144
|
+
// When MCP runs from user project, we need to find the package from MCP installation
|
|
145
|
+
try {
|
|
146
|
+
const mcpDir = dirname(fileURLToPath(import.meta.url));
|
|
147
|
+
// mcpDir is typically: .../adapters/mcp-ts/build/
|
|
148
|
+
// repo root is: .../adapters/mcp-ts/build/../../../.. = repo root
|
|
149
|
+
const repoFromMcp = resolve(mcpDir, "..", "..", "..", "..");
|
|
150
|
+
const pkgFromMcp = join(repoFromMcp, "vibecoding_helper");
|
|
151
|
+
if (existsSync(pkgFromMcp)) {
|
|
152
|
+
return repoFromMcp;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// import.meta.url failed, continue to next priority
|
|
157
|
+
}
|
|
158
|
+
// Priority 3: startDir upwards search (original heuristic)
|
|
159
|
+
let current = resolve(startDir);
|
|
160
|
+
// Walk up until filesystem root
|
|
161
|
+
while (true) {
|
|
162
|
+
const pyproject = join(current, "pyproject.toml");
|
|
163
|
+
const pkgDir = join(current, "vibecoding_helper");
|
|
164
|
+
// Check for pyproject.toml or vibecoding_helper/ directory
|
|
165
|
+
if (existsSync(pyproject) || existsSync(pkgDir)) {
|
|
166
|
+
return current;
|
|
167
|
+
}
|
|
168
|
+
const parent = dirname(current);
|
|
169
|
+
if (parent === current)
|
|
170
|
+
break; // Reached filesystem root
|
|
171
|
+
current = parent;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Build env for Python CLI so that 'python -m vibecoding_helper ...' can import
|
|
177
|
+
* even when MCP client ignores .mcp.json env (e.g., Claude Code).
|
|
178
|
+
*
|
|
179
|
+
* Priority:
|
|
180
|
+
* 1. VIBECODE_PYTHONPATH (explicit override)
|
|
181
|
+
* 2. Auto-detected repo root
|
|
182
|
+
* 3. Existing PYTHONPATH
|
|
183
|
+
*
|
|
184
|
+
* @param baseEnv - Base environment variables
|
|
185
|
+
* @param cwd - Current working directory for repo root detection
|
|
186
|
+
* @returns Environment with PYTHONPATH properly set
|
|
187
|
+
*/
|
|
188
|
+
export function buildPythonCliEnv(baseEnv, cwd) {
|
|
189
|
+
const env = { ...baseEnv };
|
|
190
|
+
const delim = process.platform === "win32" ? ";" : ":";
|
|
191
|
+
// Priority 1: Explicit override via VIBECODE_PYTHONPATH
|
|
192
|
+
const override = env.VIBECODE_PYTHONPATH?.trim();
|
|
193
|
+
// Priority 2: Auto-detect repo root
|
|
194
|
+
const repoRoot = override || findPythonRepoRoot(cwd);
|
|
195
|
+
if (!repoRoot)
|
|
196
|
+
return env;
|
|
197
|
+
// Parse existing PYTHONPATH
|
|
198
|
+
const existing = env.PYTHONPATH ?? "";
|
|
199
|
+
const parts = existing.split(delim).filter(Boolean);
|
|
200
|
+
// Avoid duplicate insertion
|
|
201
|
+
if (!parts.includes(repoRoot)) {
|
|
202
|
+
env.PYTHONPATH = [repoRoot, ...parts].join(delim);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
env.PYTHONPATH = parts.join(delim);
|
|
206
|
+
}
|
|
207
|
+
return env;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Run Python vibecoding_helper CLI command
|
|
211
|
+
* Uses `python -m vibecoding_helper` for cross-platform compatibility
|
|
212
|
+
*
|
|
213
|
+
* Automatically injects PYTHONPATH by detecting the package root,
|
|
214
|
+
* making it work without .mcp.json env configuration.
|
|
215
|
+
*/
|
|
216
|
+
export async function runPythonCli(args, opts) {
|
|
217
|
+
const pythonCmd = process.platform === "win32" ? "python" : "python3";
|
|
218
|
+
const fullArgs = ["-m", "vibecoding_helper", ...args];
|
|
219
|
+
const cwd = process.cwd();
|
|
220
|
+
// Auto-inject PYTHONPATH using buildPythonCliEnv
|
|
221
|
+
const env = buildPythonCliEnv(process.env, cwd);
|
|
222
|
+
if (isDebugMode()) {
|
|
223
|
+
const repoRoot = findPythonRepoRoot(cwd);
|
|
224
|
+
console.error(`[DEBUG runPythonCli] cmd=${pythonCmd} args=${JSON.stringify(fullArgs)}`);
|
|
225
|
+
console.error(`[DEBUG runPythonCli] PYTHONPATH=${env.PYTHONPATH ?? "(not set)"}`);
|
|
226
|
+
console.error(`[DEBUG runPythonCli] detected_root=${repoRoot ?? "(not found)"}`);
|
|
227
|
+
console.error(`[DEBUG runPythonCli] cwd=${cwd}`);
|
|
228
|
+
}
|
|
229
|
+
const result = await runCmd(pythonCmd, fullArgs, {
|
|
230
|
+
timeoutMs: opts?.timeoutMs ?? 120_000,
|
|
231
|
+
env,
|
|
232
|
+
});
|
|
233
|
+
if (isDebugMode()) {
|
|
234
|
+
console.error(`[DEBUG runPythonCli] code=${result.code} stdout=${result.stdout.slice(0, 200)} stderr=${result.stderr.slice(0, 200)}`);
|
|
235
|
+
}
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Run Python CLI command with optional caching support
|
|
240
|
+
*/
|
|
241
|
+
export async function runPythonCliWithCache(args, opts) {
|
|
242
|
+
const subCommand = extractSubcommand(args);
|
|
243
|
+
const isCacheable = PYTHON_CACHEABLE_COMMANDS.has(subCommand) && !opts?.skipCache;
|
|
244
|
+
if (isCacheable) {
|
|
245
|
+
const cacheKey = createCacheKey("python-cli", ...args);
|
|
246
|
+
const cachedResult = engineResultCache.get(cacheKey);
|
|
247
|
+
if (cachedResult) {
|
|
248
|
+
return cachedResult;
|
|
249
|
+
}
|
|
250
|
+
const result = await runPythonCli(args, opts);
|
|
251
|
+
if (result.code === 0) {
|
|
252
|
+
engineResultCache.set(cacheKey, result);
|
|
253
|
+
}
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
return await runPythonCli(args, opts);
|
|
257
|
+
}
|
package/build/errors.js
CHANGED
|
@@ -169,3 +169,110 @@ export function doNotTouchError(path, pattern) {
|
|
|
169
169
|
recovery: "This path is protected and cannot be modified. Choose a different target.",
|
|
170
170
|
});
|
|
171
171
|
}
|
|
172
|
+
// ============================================================
|
|
173
|
+
// Error Detection Patterns (P1-4: Normalized string-based error detection)
|
|
174
|
+
// ============================================================
|
|
175
|
+
/**
|
|
176
|
+
* Centralized error string patterns for consistent detection across the codebase.
|
|
177
|
+
* Use these constants instead of hardcoded strings for maintainability.
|
|
178
|
+
*/
|
|
179
|
+
export const ErrorPatterns = {
|
|
180
|
+
// Bootstrap/download errors
|
|
181
|
+
DOWNLOAD_TIMEOUT: ["download_timeout", "AbortError"],
|
|
182
|
+
DOWNLOAD_404: ["404", "no_matching_asset", "download_failed:404"],
|
|
183
|
+
DOWNLOAD_FAILED: ["download_failed", "fetch"],
|
|
184
|
+
SHA_MISMATCH: ["sha_mismatch"],
|
|
185
|
+
SHA_MISSING: ["sha_missing_for_asset"],
|
|
186
|
+
BIN_NOT_FOUND: ["bin_not_found_after_extract"],
|
|
187
|
+
// Git errors
|
|
188
|
+
GIT_CONFLICT: ["conflict", "CONFLICT"],
|
|
189
|
+
// OPA/Policy errors
|
|
190
|
+
OPA_EVAL_FAILED: ["eval_failed", "opa_eval_failed", "opa exception", "opa_exception", "eval failed"],
|
|
191
|
+
OPA_MISSING: ["missing"],
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Check if a message matches any of the given patterns.
|
|
195
|
+
* Case-insensitive matching for OPA patterns, case-sensitive for others.
|
|
196
|
+
*/
|
|
197
|
+
export function matchesErrorPattern(message, patterns, options = {}) {
|
|
198
|
+
const normalizedMessage = options.caseInsensitive ? message.toLowerCase() : message;
|
|
199
|
+
return patterns.some((pattern) => {
|
|
200
|
+
const normalizedPattern = options.caseInsensitive ? pattern.toLowerCase() : pattern;
|
|
201
|
+
return normalizedMessage.includes(normalizedPattern);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Check if message indicates a download timeout error.
|
|
206
|
+
*/
|
|
207
|
+
export function isDownloadTimeoutError(message) {
|
|
208
|
+
return matchesErrorPattern(message, ErrorPatterns.DOWNLOAD_TIMEOUT);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Check if message indicates a 404/not found error.
|
|
212
|
+
*/
|
|
213
|
+
export function isDownload404Error(message) {
|
|
214
|
+
return matchesErrorPattern(message, ErrorPatterns.DOWNLOAD_404);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Check if message indicates a general download failure.
|
|
218
|
+
*/
|
|
219
|
+
export function isDownloadFailedError(message) {
|
|
220
|
+
return matchesErrorPattern(message, ErrorPatterns.DOWNLOAD_FAILED);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Check if message indicates a git conflict.
|
|
224
|
+
*/
|
|
225
|
+
export function isGitConflictError(message) {
|
|
226
|
+
return matchesErrorPattern(message, ErrorPatterns.GIT_CONFLICT);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Check if message indicates an OPA evaluation failure.
|
|
230
|
+
* Always case-insensitive for OPA patterns.
|
|
231
|
+
*/
|
|
232
|
+
export function isOpaEvalFailedError(message) {
|
|
233
|
+
return matchesErrorPattern(message, ErrorPatterns.OPA_EVAL_FAILED, { caseInsensitive: true });
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Check if message indicates OPA is missing.
|
|
237
|
+
*/
|
|
238
|
+
export function isOpaMissingError(message) {
|
|
239
|
+
return matchesErrorPattern(message, ErrorPatterns.OPA_MISSING, { caseInsensitive: true });
|
|
240
|
+
}
|
|
241
|
+
// ============================================================
|
|
242
|
+
// P2-23: Error Message Formatting Utilities
|
|
243
|
+
// ============================================================
|
|
244
|
+
/**
|
|
245
|
+
* Format CLI command failure error message.
|
|
246
|
+
* @param command - The command that failed (e.g., "kce-status", "zoekt-evidence")
|
|
247
|
+
* @param exitCode - Exit code from the process
|
|
248
|
+
* @param stderr - Standard error output
|
|
249
|
+
* @param stdout - Standard output (used as fallback)
|
|
250
|
+
*/
|
|
251
|
+
export function formatCliError(command, exitCode, stderr, stdout) {
|
|
252
|
+
const detail = stderr || stdout || `exit_code=${exitCode ?? "unknown"}`;
|
|
253
|
+
return `${command} failed: ${detail}`;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Format JSON parsing error message.
|
|
257
|
+
* @param context - Context where parsing failed (e.g., "kce-status", "config")
|
|
258
|
+
* @param error - The parsing error
|
|
259
|
+
*/
|
|
260
|
+
export function formatJsonParseError(context, error) {
|
|
261
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
262
|
+
return `${context} invalid_json: ${msg}`;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Format schema validation error message.
|
|
266
|
+
* @param context - Context where validation failed
|
|
267
|
+
*/
|
|
268
|
+
export function formatSchemaError(context) {
|
|
269
|
+
return `${context} schema mismatch`;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Format user-facing Korean error message.
|
|
273
|
+
* @param action - What action failed (e.g., "프로젝트 초기화", "작업 지시서 생성")
|
|
274
|
+
* @param detail - Additional detail (optional)
|
|
275
|
+
*/
|
|
276
|
+
export function formatKoreanError(action, detail) {
|
|
277
|
+
return `${action} 실패${detail ? `: ${detail}` : ""}`;
|
|
278
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const BridgeBuildSeedInputSchema = z.object({ "project_id": z.string().min(1), "run_id": z.string().min(1).optional(), "raw_user_intent": z.string().min(1).max(4000).optional(), "goal": z.string().min(1).max(2000).optional(), "user_story": z.string().min(1).max(2000).optional(), "acceptance": z.array(z.string().min(1).max(300)).min(3).max(12).optional(), "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(), "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().optional(), "preferred_stack_hint": z.string().max(200).optional(), "risk_notes": z.array(z.string().min(1).max(200)).max(8).optional(), "overwrite": z.boolean().default(true), "write_files": z.boolean().default(true) }).strict().describe("SSOT schema for vibe_pm.bridge_build_seed MCP tool input");
|
|
@@ -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,6 +70,9 @@ 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
77
|
// ============================================================
|
|
75
78
|
// OPTIONAL: react_perf.* Tools (Performance Analysis)
|
package/build/local-mode/git.js
CHANGED
|
@@ -1,33 +1,47 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Git utilities for local mode
|
|
3
|
+
*
|
|
4
|
+
* All git commands go through cli_invoker for centralized subprocess management.
|
|
5
|
+
*
|
|
6
|
+
* @module local-mode/git
|
|
7
|
+
*/
|
|
8
|
+
import { invokeGitSync } from "../runtime/cli_invoker.js";
|
|
9
|
+
/**
|
|
10
|
+
* Get the git repository root directory
|
|
11
|
+
*
|
|
12
|
+
* @param cwd - Current working directory
|
|
13
|
+
* @returns Git root path or null if not in a git repository
|
|
14
|
+
*/
|
|
2
15
|
export function getGitRoot(cwd) {
|
|
3
|
-
const result =
|
|
4
|
-
|
|
5
|
-
encoding: "utf-8",
|
|
6
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
7
|
-
});
|
|
8
|
-
if (result.status !== 0)
|
|
16
|
+
const result = invokeGitSync(["rev-parse", "--show-toplevel"], cwd);
|
|
17
|
+
if (result.exitCode !== 0)
|
|
9
18
|
return null;
|
|
10
|
-
const out =
|
|
19
|
+
const out = result.stdout.trim();
|
|
11
20
|
return out.length > 0 ? out : null;
|
|
12
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Get the configured git hooks path
|
|
24
|
+
*
|
|
25
|
+
* @param cwd - Current working directory (should be git root)
|
|
26
|
+
* @returns Configured hooks path or null if not set
|
|
27
|
+
*/
|
|
13
28
|
export function getGitHooksPath(cwd) {
|
|
14
|
-
const result =
|
|
15
|
-
|
|
16
|
-
encoding: "utf-8",
|
|
17
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
18
|
-
});
|
|
19
|
-
if (result.status !== 0)
|
|
29
|
+
const result = invokeGitSync(["config", "--local", "--get", "core.hooksPath"], cwd);
|
|
30
|
+
if (result.exitCode !== 0)
|
|
20
31
|
return null;
|
|
21
|
-
const out =
|
|
32
|
+
const out = result.stdout.trim();
|
|
22
33
|
return out.length > 0 ? out : null;
|
|
23
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Set the git hooks path
|
|
37
|
+
*
|
|
38
|
+
* @param cwd - Current working directory (should be git root)
|
|
39
|
+
* @param hooksPath - Path to hooks directory
|
|
40
|
+
* @returns Success or error
|
|
41
|
+
*/
|
|
24
42
|
export function setGitHooksPath(cwd, hooksPath) {
|
|
25
|
-
const result =
|
|
26
|
-
|
|
27
|
-
encoding: "utf-8",
|
|
28
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
29
|
-
});
|
|
30
|
-
if (result.status === 0)
|
|
43
|
+
const result = invokeGitSync(["config", "--local", "core.hooksPath", hooksPath], cwd);
|
|
44
|
+
if (result.exitCode === 0)
|
|
31
45
|
return { ok: true };
|
|
32
|
-
return { ok: false, error: (result.stderr
|
|
46
|
+
return { ok: false, error: (result.stderr || "git config failed").trim() };
|
|
33
47
|
}
|
|
@@ -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,
|