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.
Files changed (206) hide show
  1. vibelign/__init__.py +3 -0
  2. vibelign/__main__.py +6 -0
  3. vibelign/_bundled/.gitkeep +0 -0
  4. vibelign/_bundled/vibelign-engine.exe +0 -0
  5. vibelign/_bundled/vibelign-engine.exe.sha256 +1 -0
  6. vibelign/_vib_entry.py +7 -0
  7. vibelign/action_engine/__init__.py +3 -0
  8. vibelign/action_engine/action_planner.py +147 -0
  9. vibelign/action_engine/executors/__init__.py +3 -0
  10. vibelign/action_engine/executors/action_executor.py +191 -0
  11. vibelign/action_engine/executors/checkpoint_bridge.py +22 -0
  12. vibelign/action_engine/generators/__init__.py +3 -0
  13. vibelign/action_engine/generators/patch_generator.py +102 -0
  14. vibelign/action_engine/models/__init__.py +7 -0
  15. vibelign/action_engine/models/action.py +34 -0
  16. vibelign/action_engine/models/issue.py +31 -0
  17. vibelign/action_engine/models/plan.py +33 -0
  18. vibelign/cli/__init__.py +5 -0
  19. vibelign/cli/cli_base.py +158 -0
  20. vibelign/cli/cli_command_groups.py +911 -0
  21. vibelign/cli/cli_completion.py +409 -0
  22. vibelign/cli/cli_core_commands.py +357 -0
  23. vibelign/cli/cli_runtime.py +79 -0
  24. vibelign/cli/vib_cli.py +127 -0
  25. vibelign/commands/__init__.py +0 -0
  26. vibelign/commands/anchor_cmd.py +48 -0
  27. vibelign/commands/ask_cmd.py +615 -0
  28. vibelign/commands/bench_fixtures.py +110 -0
  29. vibelign/commands/checkpoint_cmd.py +18 -0
  30. vibelign/commands/config_cmd.py +492 -0
  31. vibelign/commands/doctor_cmd.py +31 -0
  32. vibelign/commands/explain_cmd.py +204 -0
  33. vibelign/commands/export_cmd.py +681 -0
  34. vibelign/commands/guard_cmd.py +156 -0
  35. vibelign/commands/init_cmd.py +368 -0
  36. vibelign/commands/install_guide_cmd.py +129 -0
  37. vibelign/commands/internal_post_commit_cmd.py +68 -0
  38. vibelign/commands/internal_record_commit_cmd.py +28 -0
  39. vibelign/commands/patch_cmd.py +75 -0
  40. vibelign/commands/protect_cmd.py +126 -0
  41. vibelign/commands/transfer_git_context.py +319 -0
  42. vibelign/commands/vib_anchor_cmd.py +569 -0
  43. vibelign/commands/vib_backup_cleanup_cmd.py +62 -0
  44. vibelign/commands/vib_backup_db_maintenance_cmd.py +53 -0
  45. vibelign/commands/vib_backup_db_viewer_cmd.py +50 -0
  46. vibelign/commands/vib_backup_graph_summary_cmd.py +50 -0
  47. vibelign/commands/vib_bench_cmd.py +919 -0
  48. vibelign/commands/vib_checkpoint_cmd.py +220 -0
  49. vibelign/commands/vib_claude_hook_cmd.py +46 -0
  50. vibelign/commands/vib_doc_sources_cmd.py +97 -0
  51. vibelign/commands/vib_docs_build_cmd.py +371 -0
  52. vibelign/commands/vib_doctor_cmd.py +362 -0
  53. vibelign/commands/vib_explain_cmd.py +401 -0
  54. vibelign/commands/vib_guard_cmd.py +982 -0
  55. vibelign/commands/vib_history_cmd.py +69 -0
  56. vibelign/commands/vib_init_cmd.py +40 -0
  57. vibelign/commands/vib_log_gui_error_cmd.py +33 -0
  58. vibelign/commands/vib_manual_cmd.py +1832 -0
  59. vibelign/commands/vib_mcp_cmd.py +69 -0
  60. vibelign/commands/vib_memory_cmd.py +390 -0
  61. vibelign/commands/vib_patch_cmd.py +252 -0
  62. vibelign/commands/vib_plan_close_cmd.py +37 -0
  63. vibelign/commands/vib_plan_override_cmd.py +64 -0
  64. vibelign/commands/vib_plan_structure_cmd.py +139 -0
  65. vibelign/commands/vib_precheck_cmd.py +210 -0
  66. vibelign/commands/vib_recover_cmd.py +249 -0
  67. vibelign/commands/vib_scan_cmd.py +135 -0
  68. vibelign/commands/vib_secrets_cmd.py +132 -0
  69. vibelign/commands/vib_show_cmd.py +81 -0
  70. vibelign/commands/vib_start_cmd.py +1168 -0
  71. vibelign/commands/vib_transfer_cmd.py +1673 -0
  72. vibelign/commands/vib_undo_cmd.py +173 -0
  73. vibelign/commands/watch_cmd.py +37 -0
  74. vibelign/core/__init__.py +80 -0
  75. vibelign/core/ai_codespeak.py +197 -0
  76. vibelign/core/ai_dev_system.py +449 -0
  77. vibelign/core/ai_explain.py +476 -0
  78. vibelign/core/analysis_cache.py +113 -0
  79. vibelign/core/anchor_tools.py +1212 -0
  80. vibelign/core/auto_install.py +261 -0
  81. vibelign/core/change_explainer.py +575 -0
  82. vibelign/core/checkpoint_engine/__init__.py +52 -0
  83. vibelign/core/checkpoint_engine/auto_backup.py +81 -0
  84. vibelign/core/checkpoint_engine/contracts.py +66 -0
  85. vibelign/core/checkpoint_engine/fallback_policy.py +82 -0
  86. vibelign/core/checkpoint_engine/python_engine.py +549 -0
  87. vibelign/core/checkpoint_engine/requests.py +117 -0
  88. vibelign/core/checkpoint_engine/responses.py +245 -0
  89. vibelign/core/checkpoint_engine/router.py +108 -0
  90. vibelign/core/checkpoint_engine/rust_checkpoint_engine.py +270 -0
  91. vibelign/core/checkpoint_engine/rust_engine/__init__.py +255 -0
  92. vibelign/core/checkpoint_engine/rust_engine/daemon_client.py +283 -0
  93. vibelign/core/checkpoint_engine/rust_engine/discovery.py +150 -0
  94. vibelign/core/checkpoint_engine/rust_engine/transport_oneshot.py +82 -0
  95. vibelign/core/checkpoint_engine/shadow_runner.py +193 -0
  96. vibelign/core/codespeak.py +788 -0
  97. vibelign/core/config_loader.py +48 -0
  98. vibelign/core/context_chunk.py +166 -0
  99. vibelign/core/doc_sources.py +273 -0
  100. vibelign/core/docs_access.py +47 -0
  101. vibelign/core/docs_ai_enhance.py +292 -0
  102. vibelign/core/docs_cache.py +537 -0
  103. vibelign/core/docs_html_visualizer.py +189 -0
  104. vibelign/core/docs_index_cache.py +163 -0
  105. vibelign/core/docs_scan.py +101 -0
  106. vibelign/core/docs_visualizer.py +1300 -0
  107. vibelign/core/doctor_v2.py +699 -0
  108. vibelign/core/error_log.py +247 -0
  109. vibelign/core/fast_tools.py +132 -0
  110. vibelign/core/feature_flags.py +26 -0
  111. vibelign/core/file_lock.py +70 -0
  112. vibelign/core/git_hooks.py +357 -0
  113. vibelign/core/guard_report.py +120 -0
  114. vibelign/core/hook_setup.py +322 -0
  115. vibelign/core/http_retry.py +186 -0
  116. vibelign/core/import_resolver.py +109 -0
  117. vibelign/core/intent_ir.py +36 -0
  118. vibelign/core/keys_store.py +138 -0
  119. vibelign/core/local_checkpoints.py +641 -0
  120. vibelign/core/memory/__init__.py +25 -0
  121. vibelign/core/memory/agent.py +567 -0
  122. vibelign/core/memory/aggregator.py +176 -0
  123. vibelign/core/memory/audit.py +260 -0
  124. vibelign/core/memory/capability_grants.py +140 -0
  125. vibelign/core/memory/capability_policy.py +88 -0
  126. vibelign/core/memory/freshness.py +237 -0
  127. vibelign/core/memory/handoff_review.py +33 -0
  128. vibelign/core/memory/memory_state.schema.json +82 -0
  129. vibelign/core/memory/models.py +73 -0
  130. vibelign/core/memory/redaction.py +148 -0
  131. vibelign/core/memory/retention.py +192 -0
  132. vibelign/core/memory/review.py +267 -0
  133. vibelign/core/memory/store.py +1052 -0
  134. vibelign/core/meta_paths.py +116 -0
  135. vibelign/core/patch_contract.py +128 -0
  136. vibelign/core/patch_plan.py +54 -0
  137. vibelign/core/patch_suggester.py +1765 -0
  138. vibelign/core/patch_validation.py +56 -0
  139. vibelign/core/project_map.py +172 -0
  140. vibelign/core/project_root.py +28 -0
  141. vibelign/core/project_scan.py +190 -0
  142. vibelign/core/protected_files.py +77 -0
  143. vibelign/core/recovery/__init__.py +68 -0
  144. vibelign/core/recovery/agent.py +827 -0
  145. vibelign/core/recovery/apply.py +513 -0
  146. vibelign/core/recovery/intent_zone.py +99 -0
  147. vibelign/core/recovery/locks.py +142 -0
  148. vibelign/core/recovery/models.py +199 -0
  149. vibelign/core/recovery/path.py +128 -0
  150. vibelign/core/recovery/planner.py +254 -0
  151. vibelign/core/recovery/recovery_plan.schema.json +130 -0
  152. vibelign/core/recovery/render.py +178 -0
  153. vibelign/core/recovery/sandwich.py +64 -0
  154. vibelign/core/recovery/signals.py +428 -0
  155. vibelign/core/recovery/trigger_baseline.py +155 -0
  156. vibelign/core/request_normalizer.py +273 -0
  157. vibelign/core/risk_analyzer.py +378 -0
  158. vibelign/core/scan_cache.py +128 -0
  159. vibelign/core/schema_contracts.py +95 -0
  160. vibelign/core/secret_scan.py +483 -0
  161. vibelign/core/strict_patch.py +401 -0
  162. vibelign/core/structure_planner.py +494 -0
  163. vibelign/core/structure_policy.py +408 -0
  164. vibelign/core/target_resolution.py +52 -0
  165. vibelign/core/ui_label_index.py +174 -0
  166. vibelign/core/watch_engine.py +751 -0
  167. vibelign/core/watch_reporter.py +41 -0
  168. vibelign/core/watch_rules.py +185 -0
  169. vibelign/core/watch_state.py +76 -0
  170. vibelign/core/work_memory.py +665 -0
  171. vibelign/mcp/__init__.py +0 -0
  172. vibelign/mcp/mcp_anchor_handlers.py +257 -0
  173. vibelign/mcp/mcp_checkpoint_handlers.py +218 -0
  174. vibelign/mcp/mcp_denied_handlers.py +116 -0
  175. vibelign/mcp/mcp_dispatch.py +119 -0
  176. vibelign/mcp/mcp_doctor_handlers.py +87 -0
  177. vibelign/mcp/mcp_handler_registry.py +515 -0
  178. vibelign/mcp/mcp_health_handlers.py +69 -0
  179. vibelign/mcp/mcp_memory_handlers.py +187 -0
  180. vibelign/mcp/mcp_misc_handlers.py +95 -0
  181. vibelign/mcp/mcp_patch_handlers.py +174 -0
  182. vibelign/mcp/mcp_protect_handlers.py +46 -0
  183. vibelign/mcp/mcp_recovery_handlers.py +250 -0
  184. vibelign/mcp/mcp_runtime.py +63 -0
  185. vibelign/mcp/mcp_server.py +124 -0
  186. vibelign/mcp/mcp_state_store.py +114 -0
  187. vibelign/mcp/mcp_tool_loader.py +39 -0
  188. vibelign/mcp/mcp_tool_specs.py +582 -0
  189. vibelign/mcp/mcp_transfer_handlers.py +211 -0
  190. vibelign/patch/__init__.py +0 -0
  191. vibelign/patch/patch_builder.py +556 -0
  192. vibelign/patch/patch_contract_helpers.py +498 -0
  193. vibelign/patch/patch_fanout.py +24 -0
  194. vibelign/patch/patch_handoff.py +207 -0
  195. vibelign/patch/patch_output.py +89 -0
  196. vibelign/patch/patch_preview.py +95 -0
  197. vibelign/patch/patch_render.py +116 -0
  198. vibelign/patch/patch_steps.py +212 -0
  199. vibelign/patch/patch_targeting.py +159 -0
  200. vibelign/terminal_render.py +928 -0
  201. vibelign-2.2.17.dist-info/METADATA +506 -0
  202. vibelign-2.2.17.dist-info/RECORD +206 -0
  203. vibelign-2.2.17.dist-info/WHEEL +5 -0
  204. vibelign-2.2.17.dist-info/entry_points.txt +4 -0
  205. vibelign-2.2.17.dist-info/licenses/LICENSE +21 -0
  206. vibelign-2.2.17.dist-info/top_level.txt +1 -0
vibelign/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ # === ANCHOR: __INIT___START ===
2
+ __version__ = "2.2.17"
3
+ # === ANCHOR: __INIT___END ===
vibelign/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ # === ANCHOR: __MAIN___START ===
2
+ from vibelign.cli import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
6
+ # === ANCHOR: __MAIN___END ===
File without changes
Binary file
@@ -0,0 +1 @@
1
+ d5e0635fd0e9525f73f59cc817e89ad7d0666a1252b023e1ddef1831f2e93493 vibelign-engine.exe
vibelign/_vib_entry.py ADDED
@@ -0,0 +1,7 @@
1
+ # === ANCHOR: _VIB_ENTRY_START ===
2
+ from vibelign.cli.vib_cli import main
3
+
4
+
5
+ if __name__ == "__main__":
6
+ main()
7
+ # === ANCHOR: _VIB_ENTRY_END ===
@@ -0,0 +1,3 @@
1
+ # === ANCHOR: ACTION_ENGINE_INIT_START ===
2
+ """VibeLign Action Engine — Analyze → Plan → Generate → Execute"""
3
+ # === ANCHOR: ACTION_ENGINE_INIT_END ===
@@ -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,3 @@
1
+ # === ANCHOR: ACTION_ENGINE_EXECUTORS_INIT_START ===
2
+ """Action Engine executors — 실제 파일 저장 레이어."""
3
+ # === ANCHOR: ACTION_ENGINE_EXECUTORS_INIT_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,3 @@
1
+ # === ANCHOR: ACTION_ENGINE_GENERATORS_INIT_START ===
2
+ """Action Engine generators — 수정안 생성 (저장 금지)."""
3
+ # === ANCHOR: ACTION_ENGINE_GENERATORS_INIT_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 ===
@@ -0,0 +1,5 @@
1
+ # === ANCHOR: CLI_START ===
2
+ from vibelign.cli.vib_cli import build_parser, main
3
+
4
+ __all__ = ["build_parser", "main"]
5
+ # === ANCHOR: CLI_END ===