@vibecodetown/mcp-server 1.4.0 → 1.5.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/build/mcp/profile-config.d.ts +2 -2
- package/build/mcp/profile-config.js +9 -1
- package/build/mcp/tool-registry.js +19 -0
- package/build/mcp/tools/vibe_pm/branch_pivot.d.ts +19 -0
- package/build/mcp/tools/vibe_pm/branch_pivot.js +85 -0
- package/build/mcp/tools/vibe_pm/check_dormant.d.ts +13 -0
- package/build/mcp/tools/vibe_pm/check_dormant.js +67 -0
- package/build/mcp/tools/vibe_pm/check_resources.d.ts +10 -0
- package/build/mcp/tools/vibe_pm/check_resources.js +30 -0
- package/build/mcp/tools/vibe_pm/finalize_work.d.ts +19 -0
- package/build/mcp/tools/vibe_pm/finalize_work.js +78 -0
- package/build/mcp/tools/vibe_pm/index.d.ts +124 -0
- package/build/mcp/tools/vibe_pm/index.js +96 -0
- package/build/mcp/tools/vibe_pm/inspect_code/core.js +11 -2
- package/build/mcp/tools/vibe_pm/repair_plan.d.ts +13 -0
- package/build/mcp/tools/vibe_pm/repair_plan.js +67 -0
- package/build/mcp/tools/vibe_pm/skills_tool.d.ts +13 -0
- package/build/mcp/tools/vibe_pm/skills_tool.js +57 -0
- package/build/mcp/tools/vibe_pm/suggest_roots.d.ts +21 -0
- package/build/mcp/tools/vibe_pm/suggest_roots.js +56 -0
- package/build/mcp/tools/vibe_pm/tool_descriptions.js +8 -0
- package/build/mcp/tools/vibe_pm/undo_last_task.d.ts +13 -0
- package/build/mcp/tools/vibe_pm/undo_last_task.js +87 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const TOOL_GROUPS: {
|
|
2
2
|
readonly mvp: readonly ["vibe_pm.briefing", "vibe_pm.get_decision", "vibe_pm.submit_decision", "vibe_pm.create_work_order", "vibe_pm.inspect_code", "vibe_pm.status", "vibe_pm.doctor", "vibe_pm.ingress"];
|
|
3
|
-
readonly core: readonly ["vibe_pm.briefing", "vibe_pm.get_decision", "vibe_pm.submit_decision", "vibe_pm.create_work_order", "vibe_pm.inspect_code", "vibe_pm.status", "vibe_pm.doctor", "vibe_pm.ingress"];
|
|
4
|
-
readonly ops: readonly ["vibe_pm.init_docs", "vibe_pm.ui_manifest", "vibe_pm.ui_resource", "vibe_pm.scaffold", "vibe_pm.workspace_recipe", "vibe_pm.setup", "vibe_pm.block_search"];
|
|
3
|
+
readonly core: readonly ["vibe_pm.briefing", "vibe_pm.get_decision", "vibe_pm.submit_decision", "vibe_pm.create_work_order", "vibe_pm.inspect_code", "vibe_pm.status", "vibe_pm.doctor", "vibe_pm.ingress", "vibe_pm.finalize_work", "vibe_pm.undo_last_task"];
|
|
4
|
+
readonly ops: readonly ["vibe_pm.init_docs", "vibe_pm.ui_manifest", "vibe_pm.ui_resource", "vibe_pm.scaffold", "vibe_pm.workspace_recipe", "vibe_pm.setup", "vibe_pm.block_search", "vibe_pm.skills", "vibe_pm.suggest_roots", "vibe_pm.check_resources", "vibe_pm.check_dormant", "vibe_pm.repair_plan", "vibe_pm.branch_pivot"];
|
|
5
5
|
readonly bridge: readonly [];
|
|
6
6
|
readonly memory: readonly ["vibe_pm.memory", "vibe_pm.memory_status", "vibe_pm.memory_sync", "vibe_pm.memory_retrieve"];
|
|
7
7
|
readonly research: readonly [];
|
|
@@ -26,8 +26,10 @@ export const TOOL_GROUPS = {
|
|
|
26
26
|
'vibe_pm.status',
|
|
27
27
|
'vibe_pm.doctor',
|
|
28
28
|
'vibe_pm.ingress',
|
|
29
|
+
'vibe_pm.finalize_work', // 작업 마무리 + 커밋
|
|
30
|
+
'vibe_pm.undo_last_task', // 마지막 작업 되돌리기
|
|
29
31
|
],
|
|
30
|
-
// Ops: 설치/운영 + UI 도구 + 블록 검색
|
|
32
|
+
// Ops: 설치/운영 + UI 도구 + 블록 검색 + 진단/관리 도구
|
|
31
33
|
ops: [
|
|
32
34
|
'vibe_pm.init_docs',
|
|
33
35
|
'vibe_pm.ui_manifest',
|
|
@@ -36,6 +38,12 @@ export const TOOL_GROUPS = {
|
|
|
36
38
|
'vibe_pm.workspace_recipe',
|
|
37
39
|
'vibe_pm.setup',
|
|
38
40
|
'vibe_pm.block_search', // P38: DB FTS 블록 검색
|
|
41
|
+
'vibe_pm.skills', // 스킬팩 조회
|
|
42
|
+
'vibe_pm.suggest_roots', // 프로젝트 루트 탐색
|
|
43
|
+
'vibe_pm.check_resources', // 시스템 리소스 점검
|
|
44
|
+
'vibe_pm.check_dormant', // 프로젝트 비활성 감지
|
|
45
|
+
'vibe_pm.repair_plan', // 복구 계획 생성
|
|
46
|
+
'vibe_pm.branch_pivot', // 브랜치 전환 + 스냅샷
|
|
39
47
|
],
|
|
40
48
|
// Bridge: 예약(AS-BUILT 미등록)
|
|
41
49
|
bridge: [],
|
|
@@ -199,6 +199,25 @@ export async function registerAllTools(server, opts = {}) {
|
|
|
199
199
|
if (registerIfEnabled(server, 'vibe_pm.block_snapshot', VIBE_PM_TOOL_DESCRIPTIONS['vibe_pm.block_snapshot'], pm.blockSnapshotInputSchema.shape, async (input) => pm.vibePmBlockSnapshot(pm.blockSnapshotInputSchema.parse(input))))
|
|
200
200
|
registeredCount++;
|
|
201
201
|
// ============================================================
|
|
202
|
+
// Sprint 048: Gap Closure tools (skills, suggest_roots, check_resources, check_dormant, repair_plan, finalize_work, undo_last_task, branch_pivot)
|
|
203
|
+
// ============================================================
|
|
204
|
+
if (registerIfEnabled(server, 'vibe_pm.skills', VIBE_PM_TOOL_DESCRIPTIONS['vibe_pm.skills'], pm.skillsInputSchema.shape, async (input) => pm.vibePmSkills(pm.skillsInputSchema.parse(input))))
|
|
205
|
+
registeredCount++;
|
|
206
|
+
if (registerIfEnabled(server, 'vibe_pm.suggest_roots', VIBE_PM_TOOL_DESCRIPTIONS['vibe_pm.suggest_roots'], pm.suggestRootsInputSchema.shape, async (input) => pm.vibePmSuggestRoots(pm.suggestRootsInputSchema.parse(input))))
|
|
207
|
+
registeredCount++;
|
|
208
|
+
if (registerIfEnabled(server, 'vibe_pm.check_resources', VIBE_PM_TOOL_DESCRIPTIONS['vibe_pm.check_resources'], pm.checkResourcesInputSchema.shape, async (input) => pm.vibePmCheckResources(pm.checkResourcesInputSchema.parse(input))))
|
|
209
|
+
registeredCount++;
|
|
210
|
+
if (registerIfEnabled(server, 'vibe_pm.check_dormant', VIBE_PM_TOOL_DESCRIPTIONS['vibe_pm.check_dormant'], pm.checkDormantInputSchema.shape, async (input) => pm.vibePmCheckDormant(pm.checkDormantInputSchema.parse(input))))
|
|
211
|
+
registeredCount++;
|
|
212
|
+
if (registerIfEnabled(server, 'vibe_pm.repair_plan', VIBE_PM_TOOL_DESCRIPTIONS['vibe_pm.repair_plan'], pm.repairPlanInputSchema.shape, async (input) => pm.vibePmRepairPlan(pm.repairPlanInputSchema.parse(input))))
|
|
213
|
+
registeredCount++;
|
|
214
|
+
if (registerIfEnabled(server, 'vibe_pm.finalize_work', VIBE_PM_TOOL_DESCRIPTIONS['vibe_pm.finalize_work'], pm.finalizeWorkInputSchema.shape, async (input) => pm.vibePmFinalizeWork(pm.finalizeWorkInputSchema.parse(input))))
|
|
215
|
+
registeredCount++;
|
|
216
|
+
if (registerIfEnabled(server, 'vibe_pm.undo_last_task', VIBE_PM_TOOL_DESCRIPTIONS['vibe_pm.undo_last_task'], pm.undoLastTaskInputSchema.shape, async (input) => pm.vibePmUndoLastTask(pm.undoLastTaskInputSchema.parse(input))))
|
|
217
|
+
registeredCount++;
|
|
218
|
+
if (registerIfEnabled(server, 'vibe_pm.branch_pivot', VIBE_PM_TOOL_DESCRIPTIONS['vibe_pm.branch_pivot'], pm.branchPivotInputSchema.shape, async (input) => pm.vibePmBranchPivot(pm.branchPivotInputSchema.parse(input))))
|
|
219
|
+
registeredCount++;
|
|
220
|
+
// ============================================================
|
|
202
221
|
// Human Surface meta-tools (gated by VIBE_PROFILE=human)
|
|
203
222
|
// ============================================================
|
|
204
223
|
let humanCount = 0;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const branchPivotInputSchema: z.ZodObject<{
|
|
3
|
+
target_branch: z.ZodString;
|
|
4
|
+
save_snapshot: z.ZodDefault<z.ZodBoolean>;
|
|
5
|
+
restore_snapshot: z.ZodDefault<z.ZodBoolean>;
|
|
6
|
+
workspace_root: z.ZodOptional<z.ZodString>;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
target_branch: string;
|
|
9
|
+
save_snapshot: boolean;
|
|
10
|
+
restore_snapshot: boolean;
|
|
11
|
+
workspace_root?: string | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
target_branch: string;
|
|
14
|
+
workspace_root?: string | undefined;
|
|
15
|
+
save_snapshot?: boolean | undefined;
|
|
16
|
+
restore_snapshot?: boolean | undefined;
|
|
17
|
+
}>;
|
|
18
|
+
export type BranchPivotInput = z.infer<typeof branchPivotInputSchema>;
|
|
19
|
+
export declare function branchPivot(input: BranchPivotInput): Promise<unknown>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// vibe_pm.branch_pivot — Switch branches with optional .vibe/ snapshot save/restore
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
export const branchPivotInputSchema = z.object({
|
|
7
|
+
target_branch: z.string(),
|
|
8
|
+
save_snapshot: z.boolean().default(false),
|
|
9
|
+
restore_snapshot: z.boolean().default(false),
|
|
10
|
+
workspace_root: z.string().optional(),
|
|
11
|
+
});
|
|
12
|
+
export async function branchPivot(input) {
|
|
13
|
+
const workspaceRoot = input.workspace_root ?? process.cwd();
|
|
14
|
+
const vibePath = path.join(workspaceRoot, '.vibe');
|
|
15
|
+
const snapshotDir = path.join(vibePath, 'snapshots');
|
|
16
|
+
// Get current branch
|
|
17
|
+
let currentBranch = '';
|
|
18
|
+
try {
|
|
19
|
+
currentBranch = execSync('git branch --show-current', { cwd: workspaceRoot, timeout: 5000, encoding: 'utf8' }).trim();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return { error: 'git 브랜치를 확인할 수 없습니다.' };
|
|
23
|
+
}
|
|
24
|
+
// Save .vibe/ snapshot before switching
|
|
25
|
+
if (input.save_snapshot) {
|
|
26
|
+
try {
|
|
27
|
+
const snapshotPath = path.join(snapshotDir, `branch_${currentBranch}`);
|
|
28
|
+
fs.mkdirSync(snapshotPath, { recursive: true });
|
|
29
|
+
// Copy key state files
|
|
30
|
+
const stateFiles = ['decision_log.v1.jsonl', 'fsm_state.json', 'config.json', 'dev_log.jsonl'];
|
|
31
|
+
for (const file of stateFiles) {
|
|
32
|
+
const src = path.join(vibePath, file);
|
|
33
|
+
if (fs.existsSync(src)) {
|
|
34
|
+
fs.copyFileSync(src, path.join(snapshotPath, file));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Save metadata
|
|
38
|
+
fs.writeFileSync(path.join(snapshotPath, '_meta.json'), JSON.stringify({ branch: currentBranch, saved_at: new Date().toISOString() }));
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
return { error: `스냅샷 저장 실패: ${err instanceof Error ? err.message : String(err)}` };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Switch branch
|
|
45
|
+
try {
|
|
46
|
+
execSync(`git checkout ${input.target_branch}`, { cwd: workspaceRoot, timeout: 10000 });
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
return { error: `브랜치 전환 실패: ${err instanceof Error ? err.message : String(err)}` };
|
|
50
|
+
}
|
|
51
|
+
// Restore .vibe/ snapshot for target branch
|
|
52
|
+
if (input.restore_snapshot) {
|
|
53
|
+
const snapshotPath = path.join(snapshotDir, `branch_${input.target_branch}`);
|
|
54
|
+
if (fs.existsSync(snapshotPath)) {
|
|
55
|
+
try {
|
|
56
|
+
const files = fs.readdirSync(snapshotPath).filter(f => !f.startsWith('_'));
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
fs.copyFileSync(path.join(snapshotPath, file), path.join(vibePath, file));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// fail-safe: continue without restore
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
status: 'switched_and_restored',
|
|
66
|
+
from_branch: currentBranch,
|
|
67
|
+
to_branch: input.target_branch,
|
|
68
|
+
snapshot_restored: true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
status: 'switched',
|
|
73
|
+
from_branch: currentBranch,
|
|
74
|
+
to_branch: input.target_branch,
|
|
75
|
+
snapshot_restored: false,
|
|
76
|
+
warning: `브랜치 '${input.target_branch}'에 대한 스냅샷이 없습니다.`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
status: 'switched',
|
|
81
|
+
from_branch: currentBranch,
|
|
82
|
+
to_branch: input.target_branch,
|
|
83
|
+
snapshot_saved: input.save_snapshot,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const checkDormantInputSchema: z.ZodObject<{
|
|
3
|
+
workspace_root: z.ZodOptional<z.ZodString>;
|
|
4
|
+
threshold_days: z.ZodDefault<z.ZodNumber>;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
threshold_days: number;
|
|
7
|
+
workspace_root?: string | undefined;
|
|
8
|
+
}, {
|
|
9
|
+
workspace_root?: string | undefined;
|
|
10
|
+
threshold_days?: number | undefined;
|
|
11
|
+
}>;
|
|
12
|
+
export type CheckDormantInput = z.infer<typeof checkDormantInputSchema>;
|
|
13
|
+
export declare function checkDormant(input: CheckDormantInput): Promise<unknown>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// vibe_pm.check_dormant — Detect dormant/inactive projects
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
export const checkDormantInputSchema = z.object({
|
|
7
|
+
workspace_root: z.string().optional(),
|
|
8
|
+
threshold_days: z.number().int().min(1).default(7),
|
|
9
|
+
});
|
|
10
|
+
export async function checkDormant(input) {
|
|
11
|
+
const workspaceRoot = input.workspace_root ?? process.cwd();
|
|
12
|
+
const thresholdDays = input.threshold_days ?? 7;
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
const msPerDay = 86400000;
|
|
15
|
+
let latestMs = 0;
|
|
16
|
+
let latestSource = 'none';
|
|
17
|
+
// Check .vibe/ directory mtime
|
|
18
|
+
try {
|
|
19
|
+
const vibePath = path.join(workspaceRoot, '.vibe');
|
|
20
|
+
const stat = fs.statSync(vibePath);
|
|
21
|
+
if (stat.mtimeMs > latestMs) {
|
|
22
|
+
latestMs = stat.mtimeMs;
|
|
23
|
+
latestSource = '.vibe directory';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch { /* no .vibe */ }
|
|
27
|
+
// Check decision_log.v1.jsonl last line timestamp
|
|
28
|
+
try {
|
|
29
|
+
const logPath = path.join(workspaceRoot, '.vibe', 'decision_log.v1.jsonl');
|
|
30
|
+
const content = fs.readFileSync(logPath, 'utf8');
|
|
31
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
32
|
+
if (lines.length > 0) {
|
|
33
|
+
const last = JSON.parse(lines[lines.length - 1]);
|
|
34
|
+
const ts = new Date(last.timestamp ?? last.ts ?? 0).getTime();
|
|
35
|
+
if (ts > latestMs) {
|
|
36
|
+
latestMs = ts;
|
|
37
|
+
latestSource = 'decision_log';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch { /* no log */ }
|
|
42
|
+
// Check git log
|
|
43
|
+
try {
|
|
44
|
+
const gitDate = execSync('git log -1 --format=%ci', { cwd: workspaceRoot, timeout: 5000, encoding: 'utf8' }).trim();
|
|
45
|
+
const gitMs = new Date(gitDate).getTime();
|
|
46
|
+
if (gitMs > latestMs) {
|
|
47
|
+
latestMs = gitMs;
|
|
48
|
+
latestSource = 'git commit';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch { /* no git */ }
|
|
52
|
+
const daysInactive = latestMs > 0 ? Math.floor((now - latestMs) / msPerDay) : -1;
|
|
53
|
+
const isDormant = daysInactive < 0 || daysInactive >= thresholdDays;
|
|
54
|
+
let suggestedAction = '정상 활동 중';
|
|
55
|
+
if (isDormant) {
|
|
56
|
+
suggestedAction = daysInactive >= 30
|
|
57
|
+
? '장기 미사용 프로젝트. 아카이브 또는 재활성화를 검토하세요.'
|
|
58
|
+
: `${daysInactive}일 비활성. vibe_pm.ingress로 컨텍스트를 재스캔하세요.`;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
is_dormant: isDormant,
|
|
62
|
+
days_inactive: daysInactive,
|
|
63
|
+
last_activity_source: latestSource,
|
|
64
|
+
suggested_action: suggestedAction,
|
|
65
|
+
threshold_days: thresholdDays,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const checkResourcesInputSchema: z.ZodObject<{
|
|
3
|
+
workspace_root: z.ZodOptional<z.ZodString>;
|
|
4
|
+
}, "strip", z.ZodTypeAny, {
|
|
5
|
+
workspace_root?: string | undefined;
|
|
6
|
+
}, {
|
|
7
|
+
workspace_root?: string | undefined;
|
|
8
|
+
}>;
|
|
9
|
+
export type CheckResourcesInput = z.infer<typeof checkResourcesInputSchema>;
|
|
10
|
+
export declare function checkResources(input: CheckResourcesInput): Promise<unknown>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// vibe_pm.check_resources — System resource check (CPU, memory, disk)
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
export const checkResourcesInputSchema = z.object({
|
|
6
|
+
workspace_root: z.string().optional(),
|
|
7
|
+
});
|
|
8
|
+
export async function checkResources(input) {
|
|
9
|
+
const workspaceRoot = input.workspace_root ?? process.cwd();
|
|
10
|
+
const GB = 1024 * 1024 * 1024;
|
|
11
|
+
let diskTotal = 0;
|
|
12
|
+
let diskFree = 0;
|
|
13
|
+
try {
|
|
14
|
+
const stat = fs.statfsSync(workspaceRoot);
|
|
15
|
+
diskTotal = (stat.blocks * stat.bsize) / GB;
|
|
16
|
+
diskFree = (stat.bavail * stat.bsize) / GB;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// statfs not available on all platforms
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
cpu_cores: os.cpus().length,
|
|
23
|
+
cpu_load_1m: os.loadavg()[0],
|
|
24
|
+
memory_total_gb: +(os.totalmem() / GB).toFixed(2),
|
|
25
|
+
memory_free_gb: +(os.freemem() / GB).toFixed(2),
|
|
26
|
+
disk_total_gb: +diskTotal.toFixed(2),
|
|
27
|
+
disk_free_gb: +diskFree.toFixed(2),
|
|
28
|
+
workspace_root: workspaceRoot,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const finalizeWorkInputSchema: z.ZodObject<{
|
|
3
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
4
|
+
commit_type: z.ZodDefault<z.ZodEnum<["feat", "fix", "refactor", "chore", "docs", "test"]>>;
|
|
5
|
+
confirm: z.ZodDefault<z.ZodBoolean>;
|
|
6
|
+
workspace_root: z.ZodOptional<z.ZodString>;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
commit_type: "fix" | "docs" | "test" | "feat" | "refactor" | "chore";
|
|
9
|
+
confirm: boolean;
|
|
10
|
+
workspace_root?: string | undefined;
|
|
11
|
+
summary?: string | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
workspace_root?: string | undefined;
|
|
14
|
+
summary?: string | undefined;
|
|
15
|
+
commit_type?: "fix" | "docs" | "test" | "feat" | "refactor" | "chore" | undefined;
|
|
16
|
+
confirm?: boolean | undefined;
|
|
17
|
+
}>;
|
|
18
|
+
export type FinalizeWorkInput = z.infer<typeof finalizeWorkInputSchema>;
|
|
19
|
+
export declare function finalizeWork(input: FinalizeWorkInput): Promise<unknown>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// vibe_pm.finalize_work — Finalize work session with dev log + commit message
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
export const finalizeWorkInputSchema = z.object({
|
|
7
|
+
summary: z.string().optional(),
|
|
8
|
+
commit_type: z.enum(['feat', 'fix', 'refactor', 'chore', 'docs', 'test']).default('feat'),
|
|
9
|
+
confirm: z.boolean().default(false),
|
|
10
|
+
workspace_root: z.string().optional(),
|
|
11
|
+
});
|
|
12
|
+
export async function finalizeWork(input) {
|
|
13
|
+
const workspaceRoot = input.workspace_root ?? process.cwd();
|
|
14
|
+
const commitType = input.commit_type ?? 'feat';
|
|
15
|
+
// Get changed files from git
|
|
16
|
+
let changedFiles = [];
|
|
17
|
+
let diffSummary = '';
|
|
18
|
+
try {
|
|
19
|
+
changedFiles = execSync('git diff --name-only HEAD', { cwd: workspaceRoot, timeout: 5000, encoding: 'utf8' })
|
|
20
|
+
.trim().split('\n').filter(Boolean);
|
|
21
|
+
const stagedFiles = execSync('git diff --cached --name-only', { cwd: workspaceRoot, timeout: 5000, encoding: 'utf8' })
|
|
22
|
+
.trim().split('\n').filter(Boolean);
|
|
23
|
+
changedFiles = [...new Set([...changedFiles, ...stagedFiles])];
|
|
24
|
+
diffSummary = execSync('git diff --stat HEAD', { cwd: workspaceRoot, timeout: 5000, encoding: 'utf8' }).trim();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// no git or no changes
|
|
28
|
+
}
|
|
29
|
+
if (changedFiles.length === 0) {
|
|
30
|
+
return { status: 'no_changes', message: '변경된 파일이 없습니다.' };
|
|
31
|
+
}
|
|
32
|
+
// Infer scope from changed files
|
|
33
|
+
const dirs = changedFiles.map(f => f.split('/')[0]);
|
|
34
|
+
const uniqueDirs = [...new Set(dirs)];
|
|
35
|
+
const scope = uniqueDirs.length === 1 ? uniqueDirs[0] : uniqueDirs.length <= 3 ? uniqueDirs.join(',') : 'multi';
|
|
36
|
+
const summary = input.summary ?? `${changedFiles.length}개 파일 변경`;
|
|
37
|
+
const commitMessage = `${commitType}(${scope}): ${summary}`;
|
|
38
|
+
// Append to dev_log
|
|
39
|
+
try {
|
|
40
|
+
const devLogPath = path.join(workspaceRoot, '.vibe', 'dev_log.jsonl');
|
|
41
|
+
const logEntry = {
|
|
42
|
+
timestamp: new Date().toISOString(),
|
|
43
|
+
action: 'finalize_work',
|
|
44
|
+
summary,
|
|
45
|
+
changed_files: changedFiles.length,
|
|
46
|
+
commit_type: commitType,
|
|
47
|
+
committed: input.confirm,
|
|
48
|
+
};
|
|
49
|
+
fs.mkdirSync(path.dirname(devLogPath), { recursive: true });
|
|
50
|
+
fs.appendFileSync(devLogPath, JSON.stringify(logEntry) + '\n');
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// fail-safe
|
|
54
|
+
}
|
|
55
|
+
// Execute commit if confirmed
|
|
56
|
+
if (input.confirm) {
|
|
57
|
+
try {
|
|
58
|
+
execSync('git add -A', { cwd: workspaceRoot, timeout: 10000 });
|
|
59
|
+
execSync(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, { cwd: workspaceRoot, timeout: 10000 });
|
|
60
|
+
return {
|
|
61
|
+
status: 'committed',
|
|
62
|
+
commit_message: commitMessage,
|
|
63
|
+
changed_files: changedFiles,
|
|
64
|
+
diff_summary: diffSummary,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
return { status: 'commit_failed', error: err instanceof Error ? err.message : String(err) };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
status: 'dry_run',
|
|
73
|
+
commit_message: commitMessage,
|
|
74
|
+
changed_files: changedFiles,
|
|
75
|
+
diff_summary: diffSummary,
|
|
76
|
+
hint: 'confirm: true를 전달하면 실제로 커밋합니다.',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -603,6 +603,95 @@ export declare function defineVibePmTools(opts?: {
|
|
|
603
603
|
until?: string | undefined;
|
|
604
604
|
retention_days?: number | undefined;
|
|
605
605
|
}>;
|
|
606
|
+
skillsInputSchema: import("zod").ZodObject<{
|
|
607
|
+
mode: import("zod").ZodDefault<import("zod").ZodEnum<["list", "detail"]>>;
|
|
608
|
+
skill_id: import("zod").ZodOptional<import("zod").ZodString>;
|
|
609
|
+
}, "strip", import("zod").ZodTypeAny, {
|
|
610
|
+
mode: "list" | "detail";
|
|
611
|
+
skill_id?: string | undefined;
|
|
612
|
+
}, {
|
|
613
|
+
mode?: "list" | "detail" | undefined;
|
|
614
|
+
skill_id?: string | undefined;
|
|
615
|
+
}>;
|
|
616
|
+
suggestRootsInputSchema: import("zod").ZodObject<{
|
|
617
|
+
scan_path: import("zod").ZodOptional<import("zod").ZodString>;
|
|
618
|
+
max_depth: import("zod").ZodDefault<import("zod").ZodNumber>;
|
|
619
|
+
}, "strip", import("zod").ZodTypeAny, {
|
|
620
|
+
max_depth: number;
|
|
621
|
+
scan_path?: string | undefined;
|
|
622
|
+
}, {
|
|
623
|
+
scan_path?: string | undefined;
|
|
624
|
+
max_depth?: number | undefined;
|
|
625
|
+
}>;
|
|
626
|
+
checkResourcesInputSchema: import("zod").ZodObject<{
|
|
627
|
+
workspace_root: import("zod").ZodOptional<import("zod").ZodString>;
|
|
628
|
+
}, "strip", import("zod").ZodTypeAny, {
|
|
629
|
+
workspace_root?: string | undefined;
|
|
630
|
+
}, {
|
|
631
|
+
workspace_root?: string | undefined;
|
|
632
|
+
}>;
|
|
633
|
+
checkDormantInputSchema: import("zod").ZodObject<{
|
|
634
|
+
workspace_root: import("zod").ZodOptional<import("zod").ZodString>;
|
|
635
|
+
threshold_days: import("zod").ZodDefault<import("zod").ZodNumber>;
|
|
636
|
+
}, "strip", import("zod").ZodTypeAny, {
|
|
637
|
+
threshold_days: number;
|
|
638
|
+
workspace_root?: string | undefined;
|
|
639
|
+
}, {
|
|
640
|
+
workspace_root?: string | undefined;
|
|
641
|
+
threshold_days?: number | undefined;
|
|
642
|
+
}>;
|
|
643
|
+
repairPlanInputSchema: import("zod").ZodObject<{
|
|
644
|
+
run_id: import("zod").ZodOptional<import("zod").ZodString>;
|
|
645
|
+
workspace_root: import("zod").ZodOptional<import("zod").ZodString>;
|
|
646
|
+
}, "strip", import("zod").ZodTypeAny, {
|
|
647
|
+
run_id?: string | undefined;
|
|
648
|
+
workspace_root?: string | undefined;
|
|
649
|
+
}, {
|
|
650
|
+
run_id?: string | undefined;
|
|
651
|
+
workspace_root?: string | undefined;
|
|
652
|
+
}>;
|
|
653
|
+
finalizeWorkInputSchema: import("zod").ZodObject<{
|
|
654
|
+
summary: import("zod").ZodOptional<import("zod").ZodString>;
|
|
655
|
+
commit_type: import("zod").ZodDefault<import("zod").ZodEnum<["feat", "fix", "refactor", "chore", "docs", "test"]>>;
|
|
656
|
+
confirm: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
657
|
+
workspace_root: import("zod").ZodOptional<import("zod").ZodString>;
|
|
658
|
+
}, "strip", import("zod").ZodTypeAny, {
|
|
659
|
+
commit_type: "fix" | "docs" | "test" | "feat" | "refactor" | "chore";
|
|
660
|
+
confirm: boolean;
|
|
661
|
+
workspace_root?: string | undefined;
|
|
662
|
+
summary?: string | undefined;
|
|
663
|
+
}, {
|
|
664
|
+
workspace_root?: string | undefined;
|
|
665
|
+
summary?: string | undefined;
|
|
666
|
+
commit_type?: "fix" | "docs" | "test" | "feat" | "refactor" | "chore" | undefined;
|
|
667
|
+
confirm?: boolean | undefined;
|
|
668
|
+
}>;
|
|
669
|
+
undoLastTaskInputSchema: import("zod").ZodObject<{
|
|
670
|
+
confirm: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
671
|
+
workspace_root: import("zod").ZodOptional<import("zod").ZodString>;
|
|
672
|
+
}, "strip", import("zod").ZodTypeAny, {
|
|
673
|
+
confirm: boolean;
|
|
674
|
+
workspace_root?: string | undefined;
|
|
675
|
+
}, {
|
|
676
|
+
workspace_root?: string | undefined;
|
|
677
|
+
confirm?: boolean | undefined;
|
|
678
|
+
}>;
|
|
679
|
+
branchPivotInputSchema: import("zod").ZodObject<{
|
|
680
|
+
target_branch: import("zod").ZodString;
|
|
681
|
+
save_snapshot: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
682
|
+
restore_snapshot: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
683
|
+
workspace_root: import("zod").ZodOptional<import("zod").ZodString>;
|
|
684
|
+
}, "strip", import("zod").ZodTypeAny, {
|
|
685
|
+
target_branch: string;
|
|
686
|
+
save_snapshot: boolean;
|
|
687
|
+
restore_snapshot: boolean;
|
|
688
|
+
workspace_root?: string | undefined;
|
|
689
|
+
}, {
|
|
690
|
+
target_branch: string;
|
|
691
|
+
workspace_root?: string | undefined;
|
|
692
|
+
save_snapshot?: boolean | undefined;
|
|
693
|
+
restore_snapshot?: boolean | undefined;
|
|
694
|
+
}>;
|
|
606
695
|
vibePmBriefing: (input: {
|
|
607
696
|
mode: "mvp_fast" | "balanced" | "quality_first";
|
|
608
697
|
project_brief: string;
|
|
@@ -813,6 +902,41 @@ export declare function defineVibePmTools(opts?: {
|
|
|
813
902
|
until?: string | undefined;
|
|
814
903
|
retention_days?: number | undefined;
|
|
815
904
|
}, extra?: unknown) => Promise<import("./tool_handler_factory.js").CallToolResult>;
|
|
905
|
+
vibePmSkills: (input: {
|
|
906
|
+
mode: "list" | "detail";
|
|
907
|
+
skill_id?: string | undefined;
|
|
908
|
+
}, extra?: unknown) => Promise<import("./tool_handler_factory.js").CallToolResult>;
|
|
909
|
+
vibePmSuggestRoots: (input: {
|
|
910
|
+
max_depth: number;
|
|
911
|
+
scan_path?: string | undefined;
|
|
912
|
+
}, extra?: unknown) => Promise<import("./tool_handler_factory.js").CallToolResult>;
|
|
913
|
+
vibePmCheckResources: (input: {
|
|
914
|
+
workspace_root?: string | undefined;
|
|
915
|
+
}, extra?: unknown) => Promise<import("./tool_handler_factory.js").CallToolResult>;
|
|
916
|
+
vibePmCheckDormant: (input: {
|
|
917
|
+
threshold_days: number;
|
|
918
|
+
workspace_root?: string | undefined;
|
|
919
|
+
}, extra?: unknown) => Promise<import("./tool_handler_factory.js").CallToolResult>;
|
|
920
|
+
vibePmRepairPlan: (input: {
|
|
921
|
+
run_id?: string | undefined;
|
|
922
|
+
workspace_root?: string | undefined;
|
|
923
|
+
}, extra?: unknown) => Promise<import("./tool_handler_factory.js").CallToolResult>;
|
|
924
|
+
vibePmFinalizeWork: (input: {
|
|
925
|
+
commit_type: "fix" | "docs" | "test" | "feat" | "refactor" | "chore";
|
|
926
|
+
confirm: boolean;
|
|
927
|
+
workspace_root?: string | undefined;
|
|
928
|
+
summary?: string | undefined;
|
|
929
|
+
}, extra?: unknown) => Promise<import("./tool_handler_factory.js").CallToolResult>;
|
|
930
|
+
vibePmUndoLastTask: (input: {
|
|
931
|
+
confirm: boolean;
|
|
932
|
+
workspace_root?: string | undefined;
|
|
933
|
+
}, extra?: unknown) => Promise<import("./tool_handler_factory.js").CallToolResult>;
|
|
934
|
+
vibePmBranchPivot: (input: {
|
|
935
|
+
target_branch: string;
|
|
936
|
+
save_snapshot: boolean;
|
|
937
|
+
restore_snapshot: boolean;
|
|
938
|
+
workspace_root?: string | undefined;
|
|
939
|
+
}, extra?: unknown) => Promise<import("./tool_handler_factory.js").CallToolResult>;
|
|
816
940
|
ensureDocsStructure: typeof ensureDocsStructure;
|
|
817
941
|
};
|
|
818
942
|
export { VIBE_PM_TOOL_DESCRIPTIONS } from './tool_descriptions.js';
|
|
@@ -39,6 +39,14 @@ import { dashboard, dashboardInputSchema, } from './dashboard.js';
|
|
|
39
39
|
import { blockSearch, blockSearchInputSchema, } from './block_search.js';
|
|
40
40
|
import { blockEvents, blockEventsInputSchema, } from './block_events.js';
|
|
41
41
|
import { blockSnapshot, blockSnapshotInputSchema, } from './block_snapshot.js';
|
|
42
|
+
import { skills, skillsInputSchema } from './skills_tool.js';
|
|
43
|
+
import { suggestRoots, suggestRootsInputSchema } from './suggest_roots.js';
|
|
44
|
+
import { checkResources, checkResourcesInputSchema } from './check_resources.js';
|
|
45
|
+
import { checkDormant, checkDormantInputSchema } from './check_dormant.js';
|
|
46
|
+
import { repairPlan, repairPlanInputSchema } from './repair_plan.js';
|
|
47
|
+
import { finalizeWork, finalizeWorkInputSchema } from './finalize_work.js';
|
|
48
|
+
import { undoLastTask, undoLastTaskInputSchema } from './undo_last_task.js';
|
|
49
|
+
import { branchPivot, branchPivotInputSchema } from './branch_pivot.js';
|
|
42
50
|
// Extracted modules
|
|
43
51
|
import { createToolHandler, setKernelDepsForTools, resolveRunDir, toolResult, toStructuredContent, formatStatusSummary, readNestedString, getProjectOrRunId, fsmTransition, } from './tool_handler_factory.js';
|
|
44
52
|
// ============================================================
|
|
@@ -375,6 +383,78 @@ const vibePmBlockSnapshot = createToolHandler({
|
|
|
375
383
|
error: { code: 'block_snapshot_failed', recovery: 'mode 또는 snapshot_id를 확인하세요.' },
|
|
376
384
|
});
|
|
377
385
|
// ============================================================
|
|
386
|
+
// Skills Tool
|
|
387
|
+
// ============================================================
|
|
388
|
+
const vibePmSkills = createToolHandler({
|
|
389
|
+
toolName: 'vibe_pm.skills',
|
|
390
|
+
inputSchema: skillsInputSchema,
|
|
391
|
+
handler: input => skills(input),
|
|
392
|
+
error: { code: 'skills_failed', recovery: 'mode를 확인하세요 (list/detail).' },
|
|
393
|
+
});
|
|
394
|
+
// ============================================================
|
|
395
|
+
// Suggest Roots Tool
|
|
396
|
+
// ============================================================
|
|
397
|
+
const vibePmSuggestRoots = createToolHandler({
|
|
398
|
+
toolName: 'vibe_pm.suggest_roots',
|
|
399
|
+
inputSchema: suggestRootsInputSchema,
|
|
400
|
+
handler: input => suggestRoots(input),
|
|
401
|
+
error: { code: 'suggest_roots_failed', recovery: 'scan_path를 확인하세요.' },
|
|
402
|
+
});
|
|
403
|
+
// ============================================================
|
|
404
|
+
// Check Resources Tool
|
|
405
|
+
// ============================================================
|
|
406
|
+
const vibePmCheckResources = createToolHandler({
|
|
407
|
+
toolName: 'vibe_pm.check_resources',
|
|
408
|
+
inputSchema: checkResourcesInputSchema,
|
|
409
|
+
handler: input => checkResources(input),
|
|
410
|
+
error: { code: 'check_resources_failed' },
|
|
411
|
+
});
|
|
412
|
+
// ============================================================
|
|
413
|
+
// Check Dormant Tool
|
|
414
|
+
// ============================================================
|
|
415
|
+
const vibePmCheckDormant = createToolHandler({
|
|
416
|
+
toolName: 'vibe_pm.check_dormant',
|
|
417
|
+
inputSchema: checkDormantInputSchema,
|
|
418
|
+
handler: input => checkDormant(input),
|
|
419
|
+
error: { code: 'check_dormant_failed', recovery: 'workspace_root를 확인하세요.' },
|
|
420
|
+
});
|
|
421
|
+
// ============================================================
|
|
422
|
+
// Repair Plan Tool
|
|
423
|
+
// ============================================================
|
|
424
|
+
const vibePmRepairPlan = createToolHandler({
|
|
425
|
+
toolName: 'vibe_pm.repair_plan',
|
|
426
|
+
inputSchema: repairPlanInputSchema,
|
|
427
|
+
handler: input => repairPlan(input),
|
|
428
|
+
error: { code: 'repair_plan_failed', recovery: 'inspect_code를 먼저 실행하세요.' },
|
|
429
|
+
});
|
|
430
|
+
// ============================================================
|
|
431
|
+
// Finalize Work Tool
|
|
432
|
+
// ============================================================
|
|
433
|
+
const vibePmFinalizeWork = createToolHandler({
|
|
434
|
+
toolName: 'vibe_pm.finalize_work',
|
|
435
|
+
inputSchema: finalizeWorkInputSchema,
|
|
436
|
+
handler: input => finalizeWork(input),
|
|
437
|
+
error: { code: 'finalize_work_failed', recovery: 'workspace_root를 확인하세요.' },
|
|
438
|
+
});
|
|
439
|
+
// ============================================================
|
|
440
|
+
// Undo Last Task Tool
|
|
441
|
+
// ============================================================
|
|
442
|
+
const vibePmUndoLastTask = createToolHandler({
|
|
443
|
+
toolName: 'vibe_pm.undo_last_task',
|
|
444
|
+
inputSchema: undoLastTaskInputSchema,
|
|
445
|
+
handler: input => undoLastTask(input),
|
|
446
|
+
error: { code: 'undo_last_task_failed', recovery: 'GO 판정 기록이 있는지 확인하세요.' },
|
|
447
|
+
});
|
|
448
|
+
// ============================================================
|
|
449
|
+
// Branch Pivot Tool
|
|
450
|
+
// ============================================================
|
|
451
|
+
const vibePmBranchPivot = createToolHandler({
|
|
452
|
+
toolName: 'vibe_pm.branch_pivot',
|
|
453
|
+
inputSchema: branchPivotInputSchema,
|
|
454
|
+
handler: input => branchPivot(input),
|
|
455
|
+
error: { code: 'branch_pivot_failed', recovery: 'target_branch를 확인하세요.' },
|
|
456
|
+
});
|
|
457
|
+
// ============================================================
|
|
378
458
|
// Export
|
|
379
459
|
// ============================================================
|
|
380
460
|
export function defineVibePmTools(opts) {
|
|
@@ -411,6 +491,14 @@ export function defineVibePmTools(opts) {
|
|
|
411
491
|
blockEventsInputSchema,
|
|
412
492
|
blockSnapshotInputSchema,
|
|
413
493
|
decisionHistoryInputSchema: DecisionHistoryInputSchema,
|
|
494
|
+
skillsInputSchema,
|
|
495
|
+
suggestRootsInputSchema,
|
|
496
|
+
checkResourcesInputSchema,
|
|
497
|
+
checkDormantInputSchema,
|
|
498
|
+
repairPlanInputSchema,
|
|
499
|
+
finalizeWorkInputSchema,
|
|
500
|
+
undoLastTaskInputSchema,
|
|
501
|
+
branchPivotInputSchema,
|
|
414
502
|
// Tool handlers
|
|
415
503
|
vibePmBriefing,
|
|
416
504
|
vibePmGetDecision,
|
|
@@ -440,6 +528,14 @@ export function defineVibePmTools(opts) {
|
|
|
440
528
|
vibePmBlockEvents,
|
|
441
529
|
vibePmBlockSnapshot,
|
|
442
530
|
vibePmDecisionHistory,
|
|
531
|
+
vibePmSkills,
|
|
532
|
+
vibePmSuggestRoots,
|
|
533
|
+
vibePmCheckResources,
|
|
534
|
+
vibePmCheckDormant,
|
|
535
|
+
vibePmRepairPlan,
|
|
536
|
+
vibePmFinalizeWork,
|
|
537
|
+
vibePmUndoLastTask,
|
|
538
|
+
vibePmBranchPivot,
|
|
443
539
|
// Docs structure helper
|
|
444
540
|
ensureDocsStructure,
|
|
445
541
|
};
|
|
@@ -16,7 +16,7 @@ import { appendDecisionLogEvent, readCurrentWorkOrderId, readCurrentDecisionId,
|
|
|
16
16
|
import { readLastCheckSync } from '../bitnet-cache.js';
|
|
17
17
|
import { computePhaseAndMissing } from '../phase.js';
|
|
18
18
|
import { computeAndPersistStage } from '../stage-computer.js';
|
|
19
|
-
import { attachNudge } from '../nudge.js';
|
|
19
|
+
import { attachNudge, buildNudgeContext } from '../nudge.js';
|
|
20
20
|
import { readScorecard } from '../scorecard/index.js';
|
|
21
21
|
import { runPreprocess } from '../../../preprocess.js';
|
|
22
22
|
import { isPolicyEnabled, loadPolicyConfig, analyzeForPolicy, } from '../policy/index.js';
|
|
@@ -288,7 +288,16 @@ export async function inspectCode(input, progress, log) {
|
|
|
288
288
|
}
|
|
289
289
|
// Determine review result from signal_level
|
|
290
290
|
const signalLevel = oneLoopResult.signal_level ?? oneLoopResult.signal ?? 'LEVEL_2';
|
|
291
|
-
|
|
291
|
+
let { result: reviewResult, verdict } = signalToReview(signalLevel);
|
|
292
|
+
// 3-Strike auto-BLOCK: escalate FIX → BLOCK after 3 consecutive FIX verdicts
|
|
293
|
+
{
|
|
294
|
+
const nudgeCtx = buildNudgeContext(basePath);
|
|
295
|
+
if (nudgeCtx.consecutive_fix_count >= 3 && reviewResult === 'FIX') {
|
|
296
|
+
reviewResult = 'BLOCK';
|
|
297
|
+
verdict = '동일 문제 3회 반복 — 접근 방식 재검토 필요';
|
|
298
|
+
logger.warn({ consecutive_fix: nudgeCtx.consecutive_fix_count }, '3-strike auto-BLOCK');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
292
301
|
// === INJECTION POINT C: Final Verdict ===
|
|
293
302
|
const finalBottleneck = reviewResult !== 'GO' ? inferBottleneck(oneLoopResult.issues) : undefined;
|
|
294
303
|
await appendScorecardSafe(basePath, run_id, {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const repairPlanInputSchema: z.ZodObject<{
|
|
3
|
+
run_id: z.ZodOptional<z.ZodString>;
|
|
4
|
+
workspace_root: z.ZodOptional<z.ZodString>;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
run_id?: string | undefined;
|
|
7
|
+
workspace_root?: string | undefined;
|
|
8
|
+
}, {
|
|
9
|
+
run_id?: string | undefined;
|
|
10
|
+
workspace_root?: string | undefined;
|
|
11
|
+
}>;
|
|
12
|
+
export type RepairPlanInput = z.infer<typeof repairPlanInputSchema>;
|
|
13
|
+
export declare function repairPlan(input: RepairPlanInput): Promise<unknown>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// vibe_pm.repair_plan — Generate structured repair plan from last inspect_code result
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { generateRepairPrompt, formatIssueType } from './pm_language.js';
|
|
6
|
+
export const repairPlanInputSchema = z.object({
|
|
7
|
+
run_id: z.string().optional(),
|
|
8
|
+
workspace_root: z.string().optional(),
|
|
9
|
+
});
|
|
10
|
+
export async function repairPlan(input) {
|
|
11
|
+
const workspaceRoot = input.workspace_root ?? process.cwd();
|
|
12
|
+
// Find the most recent inspect result from decision_log
|
|
13
|
+
const logPath = path.join(workspaceRoot, '.vibe', 'decision_log.v1.jsonl');
|
|
14
|
+
let lastInspect = null;
|
|
15
|
+
try {
|
|
16
|
+
const content = fs.readFileSync(logPath, 'utf8');
|
|
17
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
18
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
19
|
+
const entry = JSON.parse(lines[i]);
|
|
20
|
+
if (entry.event_type === 'inspect_verdict') {
|
|
21
|
+
if (!input.run_id || entry.run_id === input.run_id) {
|
|
22
|
+
lastInspect = entry;
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return { error: 'decision_log를 읽을 수 없습니다. inspect_code를 먼저 실행하세요.' };
|
|
30
|
+
}
|
|
31
|
+
if (!lastInspect) {
|
|
32
|
+
return { error: 'inspect_verdict 기록이 없습니다. inspect_code를 먼저 실행하세요.' };
|
|
33
|
+
}
|
|
34
|
+
const verdict = lastInspect.verdict;
|
|
35
|
+
if (verdict === 'GO') {
|
|
36
|
+
return { problem_summary: '이슈 없음', repair_steps: [], repair_prompt: null, next_tool: 'vibe_pm.status' };
|
|
37
|
+
}
|
|
38
|
+
const issuesSummary = lastInspect.issues_summary ?? '';
|
|
39
|
+
const bottleneck = lastInspect.bottleneck ?? 'unknown';
|
|
40
|
+
const runId = lastInspect.run_id ?? '';
|
|
41
|
+
// Parse issues from summary
|
|
42
|
+
const issueParts = issuesSummary.split(';').map(s => s.trim()).filter(Boolean);
|
|
43
|
+
const repairSteps = issueParts.map((part, i) => {
|
|
44
|
+
const [type, ...rest] = part.split(':');
|
|
45
|
+
return {
|
|
46
|
+
step: i + 1,
|
|
47
|
+
issue_type: type?.trim() ?? 'unknown',
|
|
48
|
+
description: rest.join(':').trim() || part,
|
|
49
|
+
action: `${formatIssueType((type?.trim() ?? 'unknown')).name} 수정`,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
const repairData = {
|
|
53
|
+
projectName: runId,
|
|
54
|
+
mode: 'balanced',
|
|
55
|
+
problem: issuesSummary || `${bottleneck} 관련 문제`,
|
|
56
|
+
cause: `주요 병목: ${bottleneck}`,
|
|
57
|
+
fixDirection: repairSteps.map(s => s.action),
|
|
58
|
+
verifyCriteria: ['빌드 성공', '테스트 통과', '범위 내 변경'],
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
problem_summary: `${verdict} — ${bottleneck} (${issueParts.length}건)`,
|
|
62
|
+
repair_steps: repairSteps,
|
|
63
|
+
repair_prompt: generateRepairPrompt(repairData),
|
|
64
|
+
next_tool: verdict === 'FIX' ? 'vibe_pm.inspect_code' : 'vibe_pm.get_decision',
|
|
65
|
+
run_id: runId,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const skillsInputSchema: z.ZodObject<{
|
|
3
|
+
mode: z.ZodDefault<z.ZodEnum<["list", "detail"]>>;
|
|
4
|
+
skill_id: z.ZodOptional<z.ZodString>;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
mode: "list" | "detail";
|
|
7
|
+
skill_id?: string | undefined;
|
|
8
|
+
}, {
|
|
9
|
+
mode?: "list" | "detail" | undefined;
|
|
10
|
+
skill_id?: string | undefined;
|
|
11
|
+
}>;
|
|
12
|
+
export type SkillsInput = z.infer<typeof skillsInputSchema>;
|
|
13
|
+
export declare function skills(input: SkillsInput): Promise<unknown>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// vibe_pm.skills — Skill pack listing and detail
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { listActivatedSkills } from '../../bootstrap/skills-installer.js';
|
|
6
|
+
export const skillsInputSchema = z.object({
|
|
7
|
+
mode: z.enum(['list', 'detail']).default('list'),
|
|
8
|
+
skill_id: z.string().optional(),
|
|
9
|
+
});
|
|
10
|
+
export async function skills(input) {
|
|
11
|
+
const skillsDir = path.resolve(__dirname, '../../../../skills');
|
|
12
|
+
const skillpackPath = path.join(skillsDir, 'skillpack.json');
|
|
13
|
+
let entries = [];
|
|
14
|
+
try {
|
|
15
|
+
const raw = fs.readFileSync(skillpackPath, 'utf8');
|
|
16
|
+
const parsed = JSON.parse(raw);
|
|
17
|
+
entries = Array.isArray(parsed.skills) ? parsed.skills : [];
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { skills: [], error: 'skillpack.json을 읽을 수 없습니다.' };
|
|
21
|
+
}
|
|
22
|
+
// Get activation state
|
|
23
|
+
let activated = [];
|
|
24
|
+
try {
|
|
25
|
+
activated = await listActivatedSkills();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// fail-safe
|
|
29
|
+
}
|
|
30
|
+
const activatedSet = new Set(activated);
|
|
31
|
+
if (input.mode === 'detail' && input.skill_id) {
|
|
32
|
+
const skill = entries.find(s => s.id === input.skill_id);
|
|
33
|
+
if (!skill) {
|
|
34
|
+
return { error: `스킬 '${input.skill_id}'을(를) 찾을 수 없습니다.` };
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
skill: {
|
|
38
|
+
id: skill.id,
|
|
39
|
+
name: skill.name,
|
|
40
|
+
description: skill.description ?? '',
|
|
41
|
+
required_tools: skill.required_tools ?? [],
|
|
42
|
+
version: skill.version ?? '1.0.0',
|
|
43
|
+
activated: activatedSet.has(skill.id),
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
skills: entries.map(s => ({
|
|
49
|
+
id: s.id,
|
|
50
|
+
name: s.name,
|
|
51
|
+
description: s.description ?? '',
|
|
52
|
+
activated: activatedSet.has(s.id),
|
|
53
|
+
})),
|
|
54
|
+
total: entries.length,
|
|
55
|
+
activated_count: entries.filter(s => activatedSet.has(s.id)).length,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const suggestRootsInputSchema: z.ZodObject<{
|
|
3
|
+
scan_path: z.ZodOptional<z.ZodString>;
|
|
4
|
+
max_depth: z.ZodDefault<z.ZodNumber>;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
max_depth: number;
|
|
7
|
+
scan_path?: string | undefined;
|
|
8
|
+
}, {
|
|
9
|
+
scan_path?: string | undefined;
|
|
10
|
+
max_depth?: number | undefined;
|
|
11
|
+
}>;
|
|
12
|
+
export type SuggestRootsInput = z.infer<typeof suggestRootsInputSchema>;
|
|
13
|
+
interface Root {
|
|
14
|
+
path: string;
|
|
15
|
+
type: string;
|
|
16
|
+
confidence: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function suggestRoots(input: SuggestRootsInput): Promise<{
|
|
19
|
+
roots: Root[];
|
|
20
|
+
}>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// vibe_pm.suggest_roots — Discover project roots by marker files
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
export const suggestRootsInputSchema = z.object({
|
|
6
|
+
scan_path: z.string().optional(),
|
|
7
|
+
max_depth: z.number().int().min(1).max(10).default(5),
|
|
8
|
+
});
|
|
9
|
+
const MARKERS = {
|
|
10
|
+
'package.json': 'node',
|
|
11
|
+
'Cargo.toml': 'rust',
|
|
12
|
+
'go.mod': 'go',
|
|
13
|
+
'pyproject.toml': 'python',
|
|
14
|
+
'pom.xml': 'java',
|
|
15
|
+
'build.gradle': 'java',
|
|
16
|
+
'CMakeLists.txt': 'cmake',
|
|
17
|
+
};
|
|
18
|
+
export async function suggestRoots(input) {
|
|
19
|
+
const scanPath = input.scan_path ?? process.cwd();
|
|
20
|
+
const maxDepth = input.max_depth ?? 5;
|
|
21
|
+
const roots = [];
|
|
22
|
+
function walk(dir, depth) {
|
|
23
|
+
if (depth > maxDepth)
|
|
24
|
+
return;
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if (!entry.isFile())
|
|
34
|
+
continue;
|
|
35
|
+
const markerType = MARKERS[entry.name];
|
|
36
|
+
if (markerType) {
|
|
37
|
+
roots.push({
|
|
38
|
+
path: dir,
|
|
39
|
+
type: markerType,
|
|
40
|
+
confidence: Math.max(0.1, 1 - depth * 0.15),
|
|
41
|
+
});
|
|
42
|
+
return; // don't recurse into found root's children for same marker
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
if (!entry.isDirectory())
|
|
47
|
+
continue;
|
|
48
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'target')
|
|
49
|
+
continue;
|
|
50
|
+
walk(path.join(dir, entry.name), depth + 1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
walk(scanPath, 0);
|
|
54
|
+
roots.sort((a, b) => b.confidence - a.confidence);
|
|
55
|
+
return { roots };
|
|
56
|
+
}
|
|
@@ -28,4 +28,12 @@ export const VIBE_PM_TOOL_DESCRIPTIONS = {
|
|
|
28
28
|
'vibe_pm.block_search': '블록 검색(FTS). mode: search(전문 검색), chain(체인 내 블록), recent(최근 블록). block_types로 필터링 가능.',
|
|
29
29
|
'vibe_pm.block_events': '블록 이벤트 조회/폴링. mode: recent(최근 이벤트), poll(cursor 기반 폴링). kinds로 이벤트 종류 필터링.',
|
|
30
30
|
'vibe_pm.block_snapshot': '블록 스토어 스냅샷. mode: create(생성), restore(복원), list(목록), get(상세), delete(삭제).',
|
|
31
|
+
'vibe_pm.skills': '스킬팩 조회. mode: list(전체 목록), detail(특정 스킬 상세). 활성화 상태 포함.',
|
|
32
|
+
'vibe_pm.suggest_roots': '프로젝트 루트 자동 탐색. package.json/Cargo.toml 등 마커 파일 기반 추천.',
|
|
33
|
+
'vibe_pm.check_resources': '시스템 리소스 점검. CPU, 메모리, 디스크 사용량 확인.',
|
|
34
|
+
'vibe_pm.check_dormant': '프로젝트 비활성 감지. .vibe/git 활동 기반 휴면 상태 판단.',
|
|
35
|
+
'vibe_pm.repair_plan': '복구 계획 생성. 최근 inspect_code 결과에서 구조화된 수정 프롬프트 생성.',
|
|
36
|
+
'vibe_pm.finalize_work': '작업 마무리. dev_log 기록 + conventional commit 메시지 생성. confirm: true로 실제 커밋.',
|
|
37
|
+
'vibe_pm.undo_last_task': '마지막 작업 되돌리기. GO 판정 기록의 관련 커밋을 git revert. confirm: true로 실행.',
|
|
38
|
+
'vibe_pm.branch_pivot': '브랜치 전환. .vibe/ 상태 스냅샷 저장/복원 지원.',
|
|
31
39
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const undoLastTaskInputSchema: z.ZodObject<{
|
|
3
|
+
confirm: z.ZodDefault<z.ZodBoolean>;
|
|
4
|
+
workspace_root: z.ZodOptional<z.ZodString>;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
confirm: boolean;
|
|
7
|
+
workspace_root?: string | undefined;
|
|
8
|
+
}, {
|
|
9
|
+
workspace_root?: string | undefined;
|
|
10
|
+
confirm?: boolean | undefined;
|
|
11
|
+
}>;
|
|
12
|
+
export type UndoLastTaskInput = z.infer<typeof undoLastTaskInputSchema>;
|
|
13
|
+
export declare function undoLastTask(input: UndoLastTaskInput): Promise<unknown>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// vibe_pm.undo_last_task — Revert the most recent task's git commits
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
export const undoLastTaskInputSchema = z.object({
|
|
7
|
+
confirm: z.boolean().default(false),
|
|
8
|
+
workspace_root: z.string().optional(),
|
|
9
|
+
});
|
|
10
|
+
export async function undoLastTask(input) {
|
|
11
|
+
const workspaceRoot = input.workspace_root ?? process.cwd();
|
|
12
|
+
// Find last task from decision_log
|
|
13
|
+
const logPath = path.join(workspaceRoot, '.vibe', 'decision_log.v1.jsonl');
|
|
14
|
+
let lastTask = null;
|
|
15
|
+
try {
|
|
16
|
+
const content = fs.readFileSync(logPath, 'utf8');
|
|
17
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
18
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
19
|
+
const entry = JSON.parse(lines[i]);
|
|
20
|
+
if (entry.event_type === 'inspect_verdict' && entry.verdict === 'GO') {
|
|
21
|
+
lastTask = entry;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { error: 'decision_log를 읽을 수 없습니다.' };
|
|
28
|
+
}
|
|
29
|
+
if (!lastTask) {
|
|
30
|
+
return { error: 'GO 판정 기록이 없습니다. 되돌릴 작업이 없습니다.' };
|
|
31
|
+
}
|
|
32
|
+
// Find commits associated with this run_id (between this GO and previous verdict)
|
|
33
|
+
const runId = lastTask.run_id;
|
|
34
|
+
const timestamp = lastTask.timestamp;
|
|
35
|
+
let commitsToRevert = [];
|
|
36
|
+
try {
|
|
37
|
+
const since = new Date(timestamp);
|
|
38
|
+
since.setMinutes(since.getMinutes() - 30); // look back 30 mins before GO
|
|
39
|
+
const commits = execSync(`git log --since="${since.toISOString()}" --until="${timestamp}" --format=%H`, { cwd: workspaceRoot, timeout: 5000, encoding: 'utf8' }).trim().split('\n').filter(Boolean);
|
|
40
|
+
commitsToRevert = commits;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// fallback: revert HEAD
|
|
44
|
+
try {
|
|
45
|
+
const head = execSync('git rev-parse HEAD', { cwd: workspaceRoot, timeout: 5000, encoding: 'utf8' }).trim();
|
|
46
|
+
commitsToRevert = [head];
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return { error: 'git 커밋을 찾을 수 없습니다.' };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (commitsToRevert.length === 0) {
|
|
53
|
+
return { error: '되돌릴 커밋이 없습니다.', run_id: runId };
|
|
54
|
+
}
|
|
55
|
+
// Preview diff
|
|
56
|
+
let previewDiff = '';
|
|
57
|
+
try {
|
|
58
|
+
const oldest = commitsToRevert[commitsToRevert.length - 1];
|
|
59
|
+
previewDiff = execSync(`git diff ${oldest}^..HEAD --stat`, { cwd: workspaceRoot, timeout: 5000, encoding: 'utf8' }).trim();
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
previewDiff = '(diff 미리보기 실패)';
|
|
63
|
+
}
|
|
64
|
+
if (input.confirm) {
|
|
65
|
+
try {
|
|
66
|
+
for (const hash of commitsToRevert) {
|
|
67
|
+
execSync(`git revert --no-commit ${hash}`, { cwd: workspaceRoot, timeout: 10000 });
|
|
68
|
+
}
|
|
69
|
+
execSync(`git commit -m "revert: undo task ${runId}"`, { cwd: workspaceRoot, timeout: 10000 });
|
|
70
|
+
return {
|
|
71
|
+
status: 'reverted',
|
|
72
|
+
commits_reverted: commitsToRevert,
|
|
73
|
+
run_id: runId,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
return { status: 'revert_failed', error: err instanceof Error ? err.message : String(err) };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
status: 'dry_run',
|
|
82
|
+
commits_to_revert: commitsToRevert,
|
|
83
|
+
preview_diff: previewDiff,
|
|
84
|
+
run_id: runId,
|
|
85
|
+
hint: 'confirm: true를 전달하면 실제로 되돌립니다.',
|
|
86
|
+
};
|
|
87
|
+
}
|