vibelign 2.2.17__py3-none-win_amd64.whl
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.
- vibelign/__init__.py +3 -0
- vibelign/__main__.py +6 -0
- vibelign/_bundled/.gitkeep +0 -0
- vibelign/_bundled/vibelign-engine.exe +0 -0
- vibelign/_bundled/vibelign-engine.exe.sha256 +1 -0
- vibelign/_vib_entry.py +7 -0
- vibelign/action_engine/__init__.py +3 -0
- vibelign/action_engine/action_planner.py +147 -0
- vibelign/action_engine/executors/__init__.py +3 -0
- vibelign/action_engine/executors/action_executor.py +191 -0
- vibelign/action_engine/executors/checkpoint_bridge.py +22 -0
- vibelign/action_engine/generators/__init__.py +3 -0
- vibelign/action_engine/generators/patch_generator.py +102 -0
- vibelign/action_engine/models/__init__.py +7 -0
- vibelign/action_engine/models/action.py +34 -0
- vibelign/action_engine/models/issue.py +31 -0
- vibelign/action_engine/models/plan.py +33 -0
- vibelign/cli/__init__.py +5 -0
- vibelign/cli/cli_base.py +158 -0
- vibelign/cli/cli_command_groups.py +911 -0
- vibelign/cli/cli_completion.py +409 -0
- vibelign/cli/cli_core_commands.py +357 -0
- vibelign/cli/cli_runtime.py +79 -0
- vibelign/cli/vib_cli.py +127 -0
- vibelign/commands/__init__.py +0 -0
- vibelign/commands/anchor_cmd.py +48 -0
- vibelign/commands/ask_cmd.py +615 -0
- vibelign/commands/bench_fixtures.py +110 -0
- vibelign/commands/checkpoint_cmd.py +18 -0
- vibelign/commands/config_cmd.py +492 -0
- vibelign/commands/doctor_cmd.py +31 -0
- vibelign/commands/explain_cmd.py +204 -0
- vibelign/commands/export_cmd.py +681 -0
- vibelign/commands/guard_cmd.py +156 -0
- vibelign/commands/init_cmd.py +368 -0
- vibelign/commands/install_guide_cmd.py +129 -0
- vibelign/commands/internal_post_commit_cmd.py +68 -0
- vibelign/commands/internal_record_commit_cmd.py +28 -0
- vibelign/commands/patch_cmd.py +75 -0
- vibelign/commands/protect_cmd.py +126 -0
- vibelign/commands/transfer_git_context.py +319 -0
- vibelign/commands/vib_anchor_cmd.py +569 -0
- vibelign/commands/vib_backup_cleanup_cmd.py +62 -0
- vibelign/commands/vib_backup_db_maintenance_cmd.py +53 -0
- vibelign/commands/vib_backup_db_viewer_cmd.py +50 -0
- vibelign/commands/vib_backup_graph_summary_cmd.py +50 -0
- vibelign/commands/vib_bench_cmd.py +919 -0
- vibelign/commands/vib_checkpoint_cmd.py +220 -0
- vibelign/commands/vib_claude_hook_cmd.py +46 -0
- vibelign/commands/vib_doc_sources_cmd.py +97 -0
- vibelign/commands/vib_docs_build_cmd.py +371 -0
- vibelign/commands/vib_doctor_cmd.py +362 -0
- vibelign/commands/vib_explain_cmd.py +401 -0
- vibelign/commands/vib_guard_cmd.py +982 -0
- vibelign/commands/vib_history_cmd.py +69 -0
- vibelign/commands/vib_init_cmd.py +40 -0
- vibelign/commands/vib_log_gui_error_cmd.py +33 -0
- vibelign/commands/vib_manual_cmd.py +1832 -0
- vibelign/commands/vib_mcp_cmd.py +69 -0
- vibelign/commands/vib_memory_cmd.py +390 -0
- vibelign/commands/vib_patch_cmd.py +252 -0
- vibelign/commands/vib_plan_close_cmd.py +37 -0
- vibelign/commands/vib_plan_override_cmd.py +64 -0
- vibelign/commands/vib_plan_structure_cmd.py +139 -0
- vibelign/commands/vib_precheck_cmd.py +210 -0
- vibelign/commands/vib_recover_cmd.py +249 -0
- vibelign/commands/vib_scan_cmd.py +135 -0
- vibelign/commands/vib_secrets_cmd.py +132 -0
- vibelign/commands/vib_show_cmd.py +81 -0
- vibelign/commands/vib_start_cmd.py +1168 -0
- vibelign/commands/vib_transfer_cmd.py +1673 -0
- vibelign/commands/vib_undo_cmd.py +173 -0
- vibelign/commands/watch_cmd.py +37 -0
- vibelign/core/__init__.py +80 -0
- vibelign/core/ai_codespeak.py +197 -0
- vibelign/core/ai_dev_system.py +449 -0
- vibelign/core/ai_explain.py +476 -0
- vibelign/core/analysis_cache.py +113 -0
- vibelign/core/anchor_tools.py +1212 -0
- vibelign/core/auto_install.py +261 -0
- vibelign/core/change_explainer.py +575 -0
- vibelign/core/checkpoint_engine/__init__.py +52 -0
- vibelign/core/checkpoint_engine/auto_backup.py +81 -0
- vibelign/core/checkpoint_engine/contracts.py +66 -0
- vibelign/core/checkpoint_engine/fallback_policy.py +82 -0
- vibelign/core/checkpoint_engine/python_engine.py +549 -0
- vibelign/core/checkpoint_engine/requests.py +117 -0
- vibelign/core/checkpoint_engine/responses.py +245 -0
- vibelign/core/checkpoint_engine/router.py +108 -0
- vibelign/core/checkpoint_engine/rust_checkpoint_engine.py +270 -0
- vibelign/core/checkpoint_engine/rust_engine/__init__.py +255 -0
- vibelign/core/checkpoint_engine/rust_engine/daemon_client.py +283 -0
- vibelign/core/checkpoint_engine/rust_engine/discovery.py +150 -0
- vibelign/core/checkpoint_engine/rust_engine/transport_oneshot.py +82 -0
- vibelign/core/checkpoint_engine/shadow_runner.py +193 -0
- vibelign/core/codespeak.py +788 -0
- vibelign/core/config_loader.py +48 -0
- vibelign/core/context_chunk.py +166 -0
- vibelign/core/doc_sources.py +273 -0
- vibelign/core/docs_access.py +47 -0
- vibelign/core/docs_ai_enhance.py +292 -0
- vibelign/core/docs_cache.py +537 -0
- vibelign/core/docs_html_visualizer.py +189 -0
- vibelign/core/docs_index_cache.py +163 -0
- vibelign/core/docs_scan.py +101 -0
- vibelign/core/docs_visualizer.py +1300 -0
- vibelign/core/doctor_v2.py +699 -0
- vibelign/core/error_log.py +247 -0
- vibelign/core/fast_tools.py +132 -0
- vibelign/core/feature_flags.py +26 -0
- vibelign/core/file_lock.py +70 -0
- vibelign/core/git_hooks.py +357 -0
- vibelign/core/guard_report.py +120 -0
- vibelign/core/hook_setup.py +322 -0
- vibelign/core/http_retry.py +186 -0
- vibelign/core/import_resolver.py +109 -0
- vibelign/core/intent_ir.py +36 -0
- vibelign/core/keys_store.py +138 -0
- vibelign/core/local_checkpoints.py +641 -0
- vibelign/core/memory/__init__.py +25 -0
- vibelign/core/memory/agent.py +567 -0
- vibelign/core/memory/aggregator.py +176 -0
- vibelign/core/memory/audit.py +260 -0
- vibelign/core/memory/capability_grants.py +140 -0
- vibelign/core/memory/capability_policy.py +88 -0
- vibelign/core/memory/freshness.py +237 -0
- vibelign/core/memory/handoff_review.py +33 -0
- vibelign/core/memory/memory_state.schema.json +82 -0
- vibelign/core/memory/models.py +73 -0
- vibelign/core/memory/redaction.py +148 -0
- vibelign/core/memory/retention.py +192 -0
- vibelign/core/memory/review.py +267 -0
- vibelign/core/memory/store.py +1052 -0
- vibelign/core/meta_paths.py +116 -0
- vibelign/core/patch_contract.py +128 -0
- vibelign/core/patch_plan.py +54 -0
- vibelign/core/patch_suggester.py +1765 -0
- vibelign/core/patch_validation.py +56 -0
- vibelign/core/project_map.py +172 -0
- vibelign/core/project_root.py +28 -0
- vibelign/core/project_scan.py +190 -0
- vibelign/core/protected_files.py +77 -0
- vibelign/core/recovery/__init__.py +68 -0
- vibelign/core/recovery/agent.py +827 -0
- vibelign/core/recovery/apply.py +513 -0
- vibelign/core/recovery/intent_zone.py +99 -0
- vibelign/core/recovery/locks.py +142 -0
- vibelign/core/recovery/models.py +199 -0
- vibelign/core/recovery/path.py +128 -0
- vibelign/core/recovery/planner.py +254 -0
- vibelign/core/recovery/recovery_plan.schema.json +130 -0
- vibelign/core/recovery/render.py +178 -0
- vibelign/core/recovery/sandwich.py +64 -0
- vibelign/core/recovery/signals.py +428 -0
- vibelign/core/recovery/trigger_baseline.py +155 -0
- vibelign/core/request_normalizer.py +273 -0
- vibelign/core/risk_analyzer.py +378 -0
- vibelign/core/scan_cache.py +128 -0
- vibelign/core/schema_contracts.py +95 -0
- vibelign/core/secret_scan.py +483 -0
- vibelign/core/strict_patch.py +401 -0
- vibelign/core/structure_planner.py +494 -0
- vibelign/core/structure_policy.py +408 -0
- vibelign/core/target_resolution.py +52 -0
- vibelign/core/ui_label_index.py +174 -0
- vibelign/core/watch_engine.py +751 -0
- vibelign/core/watch_reporter.py +41 -0
- vibelign/core/watch_rules.py +185 -0
- vibelign/core/watch_state.py +76 -0
- vibelign/core/work_memory.py +665 -0
- vibelign/mcp/__init__.py +0 -0
- vibelign/mcp/mcp_anchor_handlers.py +257 -0
- vibelign/mcp/mcp_checkpoint_handlers.py +218 -0
- vibelign/mcp/mcp_denied_handlers.py +116 -0
- vibelign/mcp/mcp_dispatch.py +119 -0
- vibelign/mcp/mcp_doctor_handlers.py +87 -0
- vibelign/mcp/mcp_handler_registry.py +515 -0
- vibelign/mcp/mcp_health_handlers.py +69 -0
- vibelign/mcp/mcp_memory_handlers.py +187 -0
- vibelign/mcp/mcp_misc_handlers.py +95 -0
- vibelign/mcp/mcp_patch_handlers.py +174 -0
- vibelign/mcp/mcp_protect_handlers.py +46 -0
- vibelign/mcp/mcp_recovery_handlers.py +250 -0
- vibelign/mcp/mcp_runtime.py +63 -0
- vibelign/mcp/mcp_server.py +124 -0
- vibelign/mcp/mcp_state_store.py +114 -0
- vibelign/mcp/mcp_tool_loader.py +39 -0
- vibelign/mcp/mcp_tool_specs.py +582 -0
- vibelign/mcp/mcp_transfer_handlers.py +211 -0
- vibelign/patch/__init__.py +0 -0
- vibelign/patch/patch_builder.py +556 -0
- vibelign/patch/patch_contract_helpers.py +498 -0
- vibelign/patch/patch_fanout.py +24 -0
- vibelign/patch/patch_handoff.py +207 -0
- vibelign/patch/patch_output.py +89 -0
- vibelign/patch/patch_preview.py +95 -0
- vibelign/patch/patch_render.py +116 -0
- vibelign/patch/patch_steps.py +212 -0
- vibelign/patch/patch_targeting.py +159 -0
- vibelign/terminal_render.py +928 -0
- vibelign-2.2.17.dist-info/METADATA +506 -0
- vibelign-2.2.17.dist-info/RECORD +206 -0
- vibelign-2.2.17.dist-info/WHEEL +5 -0
- vibelign-2.2.17.dist-info/entry_points.txt +4 -0
- vibelign-2.2.17.dist-info/licenses/LICENSE +21 -0
- vibelign-2.2.17.dist-info/top_level.txt +1 -0
vibelign/__init__.py
ADDED
vibelign/__main__.py
ADDED
|
File without changes
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
d5e0635fd0e9525f73f59cc817e89ad7d0666a1252b023e1ddef1831f2e93493 vibelign-engine.exe
|
vibelign/_vib_entry.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# === ANCHOR: ACTION_PLANNER_START ===
|
|
2
|
+
"""Action Planner — DoctorV2Report를 실행 가능한 Plan으로 변환한다.
|
|
3
|
+
|
|
4
|
+
흐름:
|
|
5
|
+
1. issue 목록 수집
|
|
6
|
+
2. 각 issue → 후보 Action 생성
|
|
7
|
+
3. 의존 순서 정렬 (add_anchor → split_file)
|
|
8
|
+
4. 순서 확정된 Plan 반환
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from typing import Any, Dict, List
|
|
15
|
+
|
|
16
|
+
from vibelign.action_engine.models.action import Action
|
|
17
|
+
from vibelign.action_engine.models.plan import Plan
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# action_type 실행 우선순위 (낮을수록 먼저)
|
|
21
|
+
_ACTION_PRIORITY: Dict[str, int] = {
|
|
22
|
+
"fix_project_map": 0,
|
|
23
|
+
"fix_mcp": 1,
|
|
24
|
+
"add_anchor": 2,
|
|
25
|
+
"split_file": 3, # add_anchor 이후에만 안전
|
|
26
|
+
"review": 9,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# action_type → 선행 필요 action_type 목록
|
|
30
|
+
_ACTION_DEPENDENCY: Dict[str, List[str]] = {
|
|
31
|
+
"split_file": ["add_anchor"],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _classify_issue(issue: Dict[str, Any]) -> str:
|
|
36
|
+
"""issue dict에서 action_type을 결정한다."""
|
|
37
|
+
category = str(issue.get("category", "")).lower()
|
|
38
|
+
found: str = issue.get("found", "")
|
|
39
|
+
next_step: str = issue.get("next_step", "")
|
|
40
|
+
recommended_command: str = str(issue.get("recommended_command", ""))
|
|
41
|
+
text = found + " " + next_step
|
|
42
|
+
|
|
43
|
+
if category == "anchor":
|
|
44
|
+
return "add_anchor"
|
|
45
|
+
if category == "mcp":
|
|
46
|
+
return "fix_mcp"
|
|
47
|
+
check_type = str(issue.get("check_type", "")).lower()
|
|
48
|
+
path = str(issue.get("path", "")).lower()
|
|
49
|
+
if (
|
|
50
|
+
check_type in {"unsupported_project_map_schema", "invalid_project_map"}
|
|
51
|
+
or "project_map" in text.lower()
|
|
52
|
+
or path.endswith(".vibelign/project_map.json")
|
|
53
|
+
):
|
|
54
|
+
return "fix_project_map"
|
|
55
|
+
if "앵커" in text or "anchor" in text.lower():
|
|
56
|
+
return "add_anchor"
|
|
57
|
+
if "mcp.json" in text.lower():
|
|
58
|
+
return "fix_mcp"
|
|
59
|
+
if "분리" in text or "나눠" in text or "split" in text.lower():
|
|
60
|
+
return "split_file"
|
|
61
|
+
return "review"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _issue_to_action(issue: Dict[str, Any]) -> Action:
|
|
65
|
+
"""issue dict → Action 변환. path가 None이어도 안전하게 처리."""
|
|
66
|
+
action_type = _classify_issue(issue)
|
|
67
|
+
target_path = issue.get("path") # Optional[str] — None 가능
|
|
68
|
+
|
|
69
|
+
# 실행 명령어 결정
|
|
70
|
+
command: str | None = None
|
|
71
|
+
recommended_command = issue.get("recommended_command")
|
|
72
|
+
if isinstance(recommended_command, str) and recommended_command.strip():
|
|
73
|
+
command = recommended_command.strip()
|
|
74
|
+
else:
|
|
75
|
+
next_step: str = issue.get("next_step", "")
|
|
76
|
+
if "`" in next_step:
|
|
77
|
+
start = next_step.find("`") + 1
|
|
78
|
+
end = next_step.find("`", start)
|
|
79
|
+
if end > start:
|
|
80
|
+
command = next_step[start:end]
|
|
81
|
+
elif next_step.startswith("vib "):
|
|
82
|
+
command = next_step.strip()
|
|
83
|
+
|
|
84
|
+
depends_on = _ACTION_DEPENDENCY.get(action_type, [])
|
|
85
|
+
|
|
86
|
+
return Action(
|
|
87
|
+
action_type=action_type,
|
|
88
|
+
description=issue.get("found", ""),
|
|
89
|
+
target_path=target_path,
|
|
90
|
+
command=command,
|
|
91
|
+
depends_on=depends_on,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _topological_sort(actions: List[Action]) -> tuple[List[Action], List[str]]:
|
|
96
|
+
"""의존 순서를 보장하는 정렬.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
(sorted_actions, warnings)
|
|
100
|
+
"""
|
|
101
|
+
warnings: List[str] = []
|
|
102
|
+
present_types = {a.action_type for a in actions}
|
|
103
|
+
|
|
104
|
+
for action in actions:
|
|
105
|
+
for dep in action.depends_on:
|
|
106
|
+
if dep not in present_types:
|
|
107
|
+
dep_action = Action(
|
|
108
|
+
action_type=dep,
|
|
109
|
+
description=f"'{action.action_type}' 실행을 위해 자동 추가",
|
|
110
|
+
target_path=action.target_path,
|
|
111
|
+
command="vib anchor --suggest" if dep == "add_anchor" else None,
|
|
112
|
+
depends_on=[],
|
|
113
|
+
)
|
|
114
|
+
actions.append(dep_action)
|
|
115
|
+
present_types.add(dep)
|
|
116
|
+
warnings.append(
|
|
117
|
+
f"'{action.action_type}'이 '{dep}'에 의존해서 자동으로 추가했어요."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
sorted_actions = sorted(
|
|
121
|
+
actions,
|
|
122
|
+
key=lambda a: _ACTION_PRIORITY.get(a.action_type, 9),
|
|
123
|
+
)
|
|
124
|
+
return sorted_actions, warnings
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def generate_plan(doctor_report: Any) -> Plan:
|
|
128
|
+
"""DoctorV2Report 또는 그 dict로부터 Plan을 생성한다."""
|
|
129
|
+
if hasattr(doctor_report, "issues"):
|
|
130
|
+
issues: List[Dict[str, Any]] = doctor_report.issues
|
|
131
|
+
source_score: int = doctor_report.project_score
|
|
132
|
+
else:
|
|
133
|
+
issues = doctor_report.get("issues", [])
|
|
134
|
+
source_score = doctor_report.get("project_score", 0)
|
|
135
|
+
|
|
136
|
+
actions = [_issue_to_action(issue) for issue in issues]
|
|
137
|
+
sorted_actions, warnings = _topological_sort(actions)
|
|
138
|
+
|
|
139
|
+
return Plan(
|
|
140
|
+
actions=sorted_actions,
|
|
141
|
+
source_score=source_score,
|
|
142
|
+
generated_at=datetime.now(timezone.utc).isoformat(),
|
|
143
|
+
warnings=warnings,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# === ANCHOR: ACTION_PLANNER_END ===
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# === ANCHOR: ACTION_EXECUTOR_START ===
|
|
2
|
+
"""Action Executor — Plan의 Action을 실제로 실행한다.
|
|
3
|
+
|
|
4
|
+
원칙:
|
|
5
|
+
- add_anchor: 자동 실행 (anchor_tools 사용)
|
|
6
|
+
- fix_mcp / fix_project_map: 명령어 안내만 (자동 실행 안전하지 않음)
|
|
7
|
+
- split_file / review: 건너뜀 (수동 작업 필요)
|
|
8
|
+
- checkpoint는 사용자가 직접 관리 (자동 생성 안 함)
|
|
9
|
+
- 파일 변경 감지 시 경고 출력
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import List, Optional
|
|
17
|
+
|
|
18
|
+
from vibelign.action_engine.models.action import Action
|
|
19
|
+
from vibelign.action_engine.models.plan import Plan
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ExecutionResult:
|
|
24
|
+
action: Action
|
|
25
|
+
status: str # "done" | "skipped" | "manual" | "failed"
|
|
26
|
+
detail: str = ""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ApplyResult:
|
|
31
|
+
checkpoint_id: Optional[str]
|
|
32
|
+
results: List[ExecutionResult] = field(default_factory=list)
|
|
33
|
+
aborted: bool = False
|
|
34
|
+
needs_ai_aliases: bool = False
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def done_count(self) -> int:
|
|
38
|
+
return sum(1 for r in self.results if r.status == "done")
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def manual_count(self) -> int:
|
|
42
|
+
return sum(1 for r in self.results if r.status == "manual")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _check_plan_staleness(plan: Plan, root: Path) -> bool:
|
|
46
|
+
"""plan 생성 후 파일이 변경됐으면 True 반환."""
|
|
47
|
+
from vibelign.core.analysis_cache import _project_mtime_hash
|
|
48
|
+
from vibelign.core.meta_paths import MetaPaths
|
|
49
|
+
import json
|
|
50
|
+
|
|
51
|
+
meta = MetaPaths(root)
|
|
52
|
+
cache_path = meta.analysis_cache_path
|
|
53
|
+
if not cache_path.exists():
|
|
54
|
+
return False
|
|
55
|
+
try:
|
|
56
|
+
payload = json.loads(cache_path.read_text(encoding="utf-8"))
|
|
57
|
+
cached_hash = payload.get("project_mtime_hash", "")
|
|
58
|
+
current_hash = _project_mtime_hash(root)
|
|
59
|
+
return cached_hash != current_hash
|
|
60
|
+
except Exception:
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _execute_add_anchor(action: Action, root: Path) -> ExecutionResult:
|
|
65
|
+
"""앵커 없는 파일에 앵커 삽입."""
|
|
66
|
+
if not action.target_path:
|
|
67
|
+
return ExecutionResult(action, "skipped", "파일 경로 없음")
|
|
68
|
+
path = root / action.target_path
|
|
69
|
+
if not path.exists():
|
|
70
|
+
return ExecutionResult(action, "skipped", f"파일 없음: {action.target_path}")
|
|
71
|
+
try:
|
|
72
|
+
from vibelign.core.anchor_tools import extract_anchors, insert_module_anchors
|
|
73
|
+
if extract_anchors(path):
|
|
74
|
+
return ExecutionResult(action, "skipped", "이미 앵커 있음")
|
|
75
|
+
if insert_module_anchors(path):
|
|
76
|
+
try:
|
|
77
|
+
from vibelign.core.anchor_tools import generate_code_based_intents
|
|
78
|
+
generate_code_based_intents(root, [path])
|
|
79
|
+
except Exception as exc:
|
|
80
|
+
print(
|
|
81
|
+
f"[WARN] code-based intent generation failed for {action.target_path}: {exc}",
|
|
82
|
+
file=sys.stderr,
|
|
83
|
+
)
|
|
84
|
+
return ExecutionResult(
|
|
85
|
+
action,
|
|
86
|
+
"done",
|
|
87
|
+
f"앵커 추가: {action.target_path} (code-based intent 생성 실패: {exc})",
|
|
88
|
+
)
|
|
89
|
+
# AI 보강은 vib anchor --auto-intent 로 별도 실행 (APPLY 블로킹 방지)
|
|
90
|
+
return ExecutionResult(action, "done", f"앵커 추가: {action.target_path}")
|
|
91
|
+
return ExecutionResult(action, "failed", "앵커 삽입 실패")
|
|
92
|
+
except Exception as e:
|
|
93
|
+
return ExecutionResult(action, "failed", str(e))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _execute_action(action: Action, root: Path) -> ExecutionResult:
|
|
97
|
+
if action.action_type == "add_anchor":
|
|
98
|
+
return _execute_add_anchor(action, root)
|
|
99
|
+
elif action.action_type in ("fix_mcp", "fix_project_map"):
|
|
100
|
+
cmd = action.command or "(명령어 없음)"
|
|
101
|
+
return ExecutionResult(action, "manual", f"수동 실행 필요: {cmd}")
|
|
102
|
+
else:
|
|
103
|
+
return ExecutionResult(action, "skipped", f"{action.action_type} — 수동 작업 필요")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def execute_plan(plan: Plan, root: Path, force: bool = False, quiet: bool = False) -> ApplyResult:
|
|
107
|
+
"""Plan을 실행한다.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
plan: generate_plan()이 반환한 Plan
|
|
111
|
+
root: 프로젝트 루트
|
|
112
|
+
force: True면 확인 프롬프트 생략
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
ApplyResult — needs_ai_aliases가 True면 GUI에서
|
|
116
|
+
``vib anchor --auto-intent`` 를 비동기로 실행해야 한다.
|
|
117
|
+
"""
|
|
118
|
+
from vibelign.terminal_render import clack_step, clack_info, clack_warn
|
|
119
|
+
|
|
120
|
+
def _q_step(msg: str) -> None:
|
|
121
|
+
if not quiet:
|
|
122
|
+
clack_step(msg)
|
|
123
|
+
|
|
124
|
+
def _q_info(msg: str) -> None:
|
|
125
|
+
if not quiet:
|
|
126
|
+
clack_info(msg)
|
|
127
|
+
|
|
128
|
+
def _q_warn(msg: str) -> None:
|
|
129
|
+
if not quiet:
|
|
130
|
+
clack_warn(msg)
|
|
131
|
+
|
|
132
|
+
# plan 이후 파일 변경 확인
|
|
133
|
+
if _check_plan_staleness(plan, root):
|
|
134
|
+
_q_warn(
|
|
135
|
+
"⚠️ 분석 이후 파일이 변경됐습니다. "
|
|
136
|
+
"vib doctor --plan 을 다시 실행해 최신 계획을 확인하세요."
|
|
137
|
+
)
|
|
138
|
+
if not force:
|
|
139
|
+
try:
|
|
140
|
+
ans = input(" 그래도 계속할까요? [y/N]: ").strip().lower()
|
|
141
|
+
except (EOFError, KeyboardInterrupt):
|
|
142
|
+
return ApplyResult(checkpoint_id=None, aborted=True)
|
|
143
|
+
if ans not in ("y", "yes"):
|
|
144
|
+
return ApplyResult(checkpoint_id=None, aborted=True)
|
|
145
|
+
|
|
146
|
+
# 확인 프롬프트 (--force 없으면)
|
|
147
|
+
if not force:
|
|
148
|
+
print(f"\n 자동 적용 대상: {len(plan.actions)}개 항목")
|
|
149
|
+
try:
|
|
150
|
+
ans = input("\n 실행할까요? [y/N]: ").strip().lower()
|
|
151
|
+
except (EOFError, KeyboardInterrupt):
|
|
152
|
+
return ApplyResult(checkpoint_id=None, aborted=True)
|
|
153
|
+
if ans not in ("y", "yes"):
|
|
154
|
+
return ApplyResult(checkpoint_id=None, aborted=True)
|
|
155
|
+
|
|
156
|
+
# 액션 실행
|
|
157
|
+
results: List[ExecutionResult] = []
|
|
158
|
+
for action in plan.actions:
|
|
159
|
+
result = _execute_action(action, root)
|
|
160
|
+
results.append(result)
|
|
161
|
+
|
|
162
|
+
# 코드 기반 aliases 항상 실행 (즉시, 비용 0) + AI 보강 필요 플래그
|
|
163
|
+
needs_ai = False
|
|
164
|
+
try:
|
|
165
|
+
from vibelign.core.anchor_tools import extract_anchors
|
|
166
|
+
from vibelign.core.project_scan import iter_source_files
|
|
167
|
+
anchored = [p for p in iter_source_files(root) if extract_anchors(p)]
|
|
168
|
+
if anchored:
|
|
169
|
+
from vibelign.core.anchor_tools import generate_code_based_intents
|
|
170
|
+
generate_code_based_intents(root, anchored)
|
|
171
|
+
needs_ai = True
|
|
172
|
+
except Exception as exc:
|
|
173
|
+
print(
|
|
174
|
+
f"[WARN] bulk code-based intent generation failed: {exc}",
|
|
175
|
+
file=sys.stderr,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# 분석 캐시 무효화 (파일이 변경됐으므로)
|
|
179
|
+
try:
|
|
180
|
+
from vibelign.core.meta_paths import MetaPaths
|
|
181
|
+
cache_path = MetaPaths(root).analysis_cache_path
|
|
182
|
+
if cache_path.exists():
|
|
183
|
+
cache_path.unlink()
|
|
184
|
+
except Exception as exc:
|
|
185
|
+
print(
|
|
186
|
+
f"[WARN] analysis cache cleanup failed: {exc}",
|
|
187
|
+
file=sys.stderr,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
return ApplyResult(checkpoint_id=None, results=results, needs_ai_aliases=needs_ai)
|
|
191
|
+
# === ANCHOR: ACTION_EXECUTOR_END ===
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# === ANCHOR: CHECKPOINT_BRIDGE_START ===
|
|
2
|
+
"""Checkpoint Bridge — apply 실행 전 자동 checkpoint 생성."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from vibelign.core.checkpoint_engine.contracts import CheckpointSummary
|
|
10
|
+
from vibelign.core.checkpoint_engine.router import create_checkpoint
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def create_pre_apply_checkpoint(root: Path) -> Optional[CheckpointSummary]:
|
|
14
|
+
"""--apply 실행 직전 자동으로 checkpoint를 생성한다.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
CheckpointSummary if created, None if no changes to snapshot.
|
|
18
|
+
"""
|
|
19
|
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
20
|
+
message = f"vibelign: checkpoint - apply 전 자동 저장 ({timestamp})"
|
|
21
|
+
return create_checkpoint(root, message)
|
|
22
|
+
# === ANCHOR: CHECKPOINT_BRIDGE_END ===
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# === ANCHOR: PATCH_GENERATOR_START ===
|
|
2
|
+
"""Patch Generator — Plan의 각 Action에 대해 변경 예정 미리보기를 생성한다.
|
|
3
|
+
|
|
4
|
+
patch_suggester.py를 재사용해 파일/앵커 위치를 찾는다.
|
|
5
|
+
실제 파일 수정은 하지 않는다 — 미리보기만 생성.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List
|
|
11
|
+
|
|
12
|
+
from vibelign.action_engine.models.action import Action
|
|
13
|
+
from vibelign.action_engine.models.plan import Plan
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _preview_add_anchor(action: Action, root: Path) -> str:
|
|
17
|
+
"""앵커 추가 예정 미리보기."""
|
|
18
|
+
if not action.target_path:
|
|
19
|
+
return " 파일 경로 미정 — vib anchor --suggest 실행 후 확인하세요."
|
|
20
|
+
path = root / action.target_path
|
|
21
|
+
if not path.exists():
|
|
22
|
+
return f" {action.target_path} (파일 없음 — 경로 확인 필요)"
|
|
23
|
+
from vibelign.core.anchor_tools import extract_anchors
|
|
24
|
+
anchors = extract_anchors(path)
|
|
25
|
+
if anchors:
|
|
26
|
+
return f" {action.target_path}\n → 이미 앵커 있음: {', '.join(anchors[:3])}"
|
|
27
|
+
stem = path.stem.upper().replace("-", "_").replace(".", "_")
|
|
28
|
+
return (
|
|
29
|
+
f" {action.target_path}\n"
|
|
30
|
+
f" + # === ANCHOR: {stem}_START ===\n"
|
|
31
|
+
f" + # === ANCHOR: {stem}_END ==="
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _preview_fix_mcp(action: Action) -> str:
|
|
36
|
+
"""MCP 설정 변경 예정 미리보기."""
|
|
37
|
+
cmd = action.command or "vib start --tools <tool>"
|
|
38
|
+
target = action.target_path or "설정 파일"
|
|
39
|
+
return (
|
|
40
|
+
f" {target}\n"
|
|
41
|
+
f" → 실행: {cmd}\n"
|
|
42
|
+
f" → vibelign MCP 서버 항목이 추가됩니다."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _preview_fix_project_map(action: Action) -> str:
|
|
47
|
+
cmd = action.command or "vib start"
|
|
48
|
+
return (
|
|
49
|
+
f" .vibelign/project_map.json 재생성\n"
|
|
50
|
+
f" → 실행: {cmd}"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _preview_split_file(action: Action) -> str:
|
|
55
|
+
if not action.target_path:
|
|
56
|
+
return " 파일 경로 미정 — 앵커 추가 후 확인하세요."
|
|
57
|
+
return (
|
|
58
|
+
f" {action.target_path}\n"
|
|
59
|
+
f" → 기능별로 분리 예정 (앵커 확인 후 수동 또는 --apply로 실행)"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _preview_review(action: Action) -> str:
|
|
64
|
+
target = action.target_path or "(경로 미정)"
|
|
65
|
+
return f" {target}\n → 수동 검토 권장"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def generate_patch_preview(plan: Plan, root: Path) -> str:
|
|
69
|
+
"""Plan의 모든 Action에 대해 변경 예정 텍스트를 반환한다."""
|
|
70
|
+
if not plan.actions:
|
|
71
|
+
return "변경 예정 항목이 없습니다. 프로젝트 상태가 좋아요!\n"
|
|
72
|
+
|
|
73
|
+
lines = [
|
|
74
|
+
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
|
75
|
+
"VibeLign 변경 예정 미리보기 (--patch)",
|
|
76
|
+
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
|
77
|
+
"",
|
|
78
|
+
f"현재 점수: {plan.source_score} / 100 | 변경 예정: {len(plan.actions)}개",
|
|
79
|
+
"",
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
for i, action in enumerate(plan.actions, 1):
|
|
83
|
+
lines.append(f"[{i}/{len(plan.actions)}] {action.action_type.upper()}")
|
|
84
|
+
lines.append(f" 이유: {action.description[:80]}")
|
|
85
|
+
|
|
86
|
+
if action.action_type == "add_anchor":
|
|
87
|
+
lines.append(_preview_add_anchor(action, root))
|
|
88
|
+
elif action.action_type == "fix_mcp":
|
|
89
|
+
lines.append(_preview_fix_mcp(action))
|
|
90
|
+
elif action.action_type == "fix_project_map":
|
|
91
|
+
lines.append(_preview_fix_project_map(action))
|
|
92
|
+
elif action.action_type == "split_file":
|
|
93
|
+
lines.append(_preview_split_file(action))
|
|
94
|
+
else:
|
|
95
|
+
lines.append(_preview_review(action))
|
|
96
|
+
|
|
97
|
+
lines.append("")
|
|
98
|
+
|
|
99
|
+
lines.append("※ 이 미리보기는 파일을 수정하지 않아요.")
|
|
100
|
+
lines.append(" 실제 적용: vib doctor --apply")
|
|
101
|
+
return "\n".join(lines) + "\n"
|
|
102
|
+
# === ANCHOR: PATCH_GENERATOR_END ===
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# === ANCHOR: ACTION_ENGINE_MODELS_INIT_START ===
|
|
2
|
+
from vibelign.action_engine.models.issue import Issue
|
|
3
|
+
from vibelign.action_engine.models.action import Action
|
|
4
|
+
from vibelign.action_engine.models.plan import Plan
|
|
5
|
+
|
|
6
|
+
__all__ = ["Issue", "Action", "Plan"]
|
|
7
|
+
# === ANCHOR: ACTION_ENGINE_MODELS_INIT_END ===
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# === ANCHOR: ACTION_ENGINE_ACTION_START ===
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Action:
|
|
10
|
+
action_type: str # "add_anchor", "split_file", "fix_mcp", 등
|
|
11
|
+
description: str # 사람이 읽을 수 있는 설명
|
|
12
|
+
target_path: Optional[str] = None # 대상 파일 경로 (없을 수 있음)
|
|
13
|
+
command: Optional[str] = None # 실행 가능한 CLI 명령어 (있을 경우)
|
|
14
|
+
depends_on: List[str] = field(default_factory=list) # 먼저 실행되어야 할 action_type 목록
|
|
15
|
+
|
|
16
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
17
|
+
return {
|
|
18
|
+
"action_type": self.action_type,
|
|
19
|
+
"description": self.description,
|
|
20
|
+
"target_path": self.target_path,
|
|
21
|
+
"command": self.command,
|
|
22
|
+
"depends_on": self.depends_on,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Action":
|
|
27
|
+
return cls(
|
|
28
|
+
action_type=data["action_type"],
|
|
29
|
+
description=data["description"],
|
|
30
|
+
target_path=data.get("target_path"),
|
|
31
|
+
command=data.get("command"),
|
|
32
|
+
depends_on=data.get("depends_on", []),
|
|
33
|
+
)
|
|
34
|
+
# === ANCHOR: ACTION_ENGINE_ACTION_END ===
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# === ANCHOR: ACTION_ENGINE_ISSUE_START ===
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Issue:
|
|
10
|
+
found: str
|
|
11
|
+
why_it_matters: str
|
|
12
|
+
next_step: str
|
|
13
|
+
path: Optional[str] = None
|
|
14
|
+
|
|
15
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
16
|
+
return {
|
|
17
|
+
"found": self.found,
|
|
18
|
+
"why_it_matters": self.why_it_matters,
|
|
19
|
+
"next_step": self.next_step,
|
|
20
|
+
"path": self.path,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Issue":
|
|
25
|
+
return cls(
|
|
26
|
+
found=data["found"],
|
|
27
|
+
why_it_matters=data["why_it_matters"],
|
|
28
|
+
next_step=data["next_step"],
|
|
29
|
+
path=data.get("path"),
|
|
30
|
+
)
|
|
31
|
+
# === ANCHOR: ACTION_ENGINE_ISSUE_END ===
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# === ANCHOR: ACTION_ENGINE_PLAN_START ===
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any, Dict, List
|
|
6
|
+
|
|
7
|
+
from vibelign.action_engine.models.action import Action
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Plan:
|
|
12
|
+
actions: List[Action]
|
|
13
|
+
source_score: int # 분석 당시 project_score
|
|
14
|
+
generated_at: str # ISO 8601 타임스탬프
|
|
15
|
+
warnings: List[str] = field(default_factory=list) # 순환 의존 등 경고
|
|
16
|
+
|
|
17
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
18
|
+
return {
|
|
19
|
+
"actions": [a.to_dict() for a in self.actions],
|
|
20
|
+
"source_score": self.source_score,
|
|
21
|
+
"generated_at": self.generated_at,
|
|
22
|
+
"warnings": self.warnings,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Plan":
|
|
27
|
+
return cls(
|
|
28
|
+
actions=[Action.from_dict(a) for a in data.get("actions", [])],
|
|
29
|
+
source_score=data["source_score"],
|
|
30
|
+
generated_at=data["generated_at"],
|
|
31
|
+
warnings=data.get("warnings", []),
|
|
32
|
+
)
|
|
33
|
+
# === ANCHOR: ACTION_ENGINE_PLAN_END ===
|