claude-controller 0.2.0 → 0.3.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.
Files changed (68) hide show
  1. package/README.md +2 -2
  2. package/bin/autoloop.sh +382 -0
  3. package/bin/ctl +327 -5
  4. package/bin/native-app.py +5 -2
  5. package/bin/watchdog.sh +357 -0
  6. package/cognitive/__init__.py +14 -0
  7. package/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  8. package/cognitive/__pycache__/dispatcher.cpython-314.pyc +0 -0
  9. package/cognitive/__pycache__/evaluator.cpython-314.pyc +0 -0
  10. package/cognitive/__pycache__/goal_engine.cpython-314.pyc +0 -0
  11. package/cognitive/__pycache__/learning.cpython-314.pyc +0 -0
  12. package/cognitive/__pycache__/orchestrator.cpython-314.pyc +0 -0
  13. package/cognitive/__pycache__/planner.cpython-314.pyc +0 -0
  14. package/cognitive/dispatcher.py +192 -0
  15. package/cognitive/evaluator.py +289 -0
  16. package/cognitive/goal_engine.py +232 -0
  17. package/cognitive/learning.py +189 -0
  18. package/cognitive/orchestrator.py +303 -0
  19. package/cognitive/planner.py +207 -0
  20. package/cognitive/prompts/analyst.md +31 -0
  21. package/cognitive/prompts/coder.md +22 -0
  22. package/cognitive/prompts/reviewer.md +33 -0
  23. package/cognitive/prompts/tester.md +21 -0
  24. package/cognitive/prompts/writer.md +25 -0
  25. package/config.sh +6 -1
  26. package/dag/__init__.py +5 -0
  27. package/dag/__pycache__/__init__.cpython-314.pyc +0 -0
  28. package/dag/__pycache__/graph.cpython-314.pyc +0 -0
  29. package/dag/graph.py +222 -0
  30. package/lib/jobs.sh +12 -1
  31. package/package.json +5 -1
  32. package/postinstall.sh +1 -1
  33. package/service/controller.sh +43 -11
  34. package/web/audit.py +122 -0
  35. package/web/checkpoint.py +80 -0
  36. package/web/config.py +2 -5
  37. package/web/handler.py +464 -26
  38. package/web/handler_fs.py +15 -14
  39. package/web/handler_goals.py +203 -0
  40. package/web/handler_jobs.py +165 -42
  41. package/web/handler_memory.py +203 -0
  42. package/web/jobs.py +576 -12
  43. package/web/personas.py +419 -0
  44. package/web/pipeline.py +682 -50
  45. package/web/presets.py +506 -0
  46. package/web/projects.py +58 -4
  47. package/web/static/api.js +90 -3
  48. package/web/static/app.js +8 -0
  49. package/web/static/base.css +51 -12
  50. package/web/static/context.js +14 -4
  51. package/web/static/form.css +3 -2
  52. package/web/static/goals.css +363 -0
  53. package/web/static/goals.js +300 -0
  54. package/web/static/i18n.js +288 -0
  55. package/web/static/index.html +142 -6
  56. package/web/static/jobs.css +951 -4
  57. package/web/static/jobs.js +890 -54
  58. package/web/static/memoryview.js +117 -0
  59. package/web/static/personas.js +228 -0
  60. package/web/static/pipeline.css +308 -1
  61. package/web/static/pipelines.js +249 -14
  62. package/web/static/presets.js +244 -0
  63. package/web/static/send.js +26 -4
  64. package/web/static/settings-style.css +34 -3
  65. package/web/static/settings.js +37 -1
  66. package/web/static/stream.js +242 -19
  67. package/web/static/utils.js +54 -2
  68. package/web/webhook.py +210 -0
package/web/checkpoint.py CHANGED
@@ -13,6 +13,14 @@ from config import LOGS_DIR
13
13
  from utils import parse_meta_file
14
14
  from jobs import send_to_fifo
15
15
 
16
+ _GIT_HASH_RE = re.compile(r'^[0-9a-fA-F]{4,40}$')
17
+
18
+
19
+ def _validate_git_hash(value):
20
+ """git commit hash 형식을 검증한다. 유효하지 않으면 ValueError를 발생시킨다."""
21
+ if not value or not _GIT_HASH_RE.match(value):
22
+ raise ValueError(f"유효하지 않은 git hash입니다: {value!r}")
23
+
16
24
 
17
25
  def get_job_checkpoints(job_id):
18
26
  """worktree의 git log에서 해당 job의 checkpoint 커밋 목록을 반환한다."""
@@ -108,8 +116,80 @@ def extract_conversation_context(out_file, max_chars=4000):
108
116
  return full[:max_chars]
109
117
 
110
118
 
119
+ def diff_checkpoints(job_id, from_hash, to_hash=None):
120
+ """두 체크포인트 간 diff를 반환한다. to_hash 생략 시 from_hash의 부모와 비교."""
121
+ try:
122
+ _validate_git_hash(from_hash)
123
+ if to_hash:
124
+ _validate_git_hash(to_hash)
125
+ except ValueError as e:
126
+ return None, str(e)
127
+
128
+ meta_file = LOGS_DIR / f"job_{job_id}.meta"
129
+ if not meta_file.exists():
130
+ return None, "작업을 찾을 수 없습니다"
131
+
132
+ meta = parse_meta_file(meta_file)
133
+ wt_path = meta.get("WORKTREE", "")
134
+ if not wt_path or not os.path.isdir(wt_path):
135
+ return None, "워크트리를 찾을 수 없습니다"
136
+
137
+ # to_hash가 없으면 from_hash 단독 커밋의 변경사항 (부모 대비)
138
+ if not to_hash:
139
+ diff_spec = [f"{from_hash}^", from_hash]
140
+ else:
141
+ diff_spec = [from_hash, to_hash]
142
+
143
+ try:
144
+ result = subprocess.run(
145
+ ["git", "diff", "--no-color", "-U3"] + diff_spec,
146
+ cwd=wt_path, capture_output=True, text=True, timeout=15
147
+ )
148
+ raw_diff = result.stdout
149
+
150
+ # 파일별로 파싱
151
+ files = []
152
+ current = None
153
+ for line in raw_diff.split("\n"):
154
+ if line.startswith("diff --git"):
155
+ if current:
156
+ files.append(current)
157
+ # "diff --git a/path b/path" → path 추출
158
+ parts = line.split(" b/", 1)
159
+ fname = parts[1] if len(parts) > 1 else "unknown"
160
+ current = {"file": fname, "chunks": [], "additions": 0, "deletions": 0}
161
+ elif current is not None:
162
+ current["chunks"].append(line)
163
+ if line.startswith("+") and not line.startswith("+++"):
164
+ current["additions"] += 1
165
+ elif line.startswith("-") and not line.startswith("---"):
166
+ current["deletions"] += 1
167
+
168
+ if current:
169
+ files.append(current)
170
+
171
+ return {
172
+ "from": diff_spec[0],
173
+ "to": diff_spec[1],
174
+ "files": files,
175
+ "total_files": len(files),
176
+ "total_additions": sum(f["additions"] for f in files),
177
+ "total_deletions": sum(f["deletions"] for f in files),
178
+ }, None
179
+
180
+ except subprocess.TimeoutExpired:
181
+ return None, "diff 생성 시간 초과"
182
+ except OSError as e:
183
+ return None, f"diff 실패: {e}"
184
+
185
+
111
186
  def rewind_job(job_id, checkpoint_hash, new_prompt):
112
187
  """job을 특정 checkpoint로 되돌리고 새 job을 디스패치한다."""
188
+ try:
189
+ _validate_git_hash(checkpoint_hash)
190
+ except ValueError as e:
191
+ return None, str(e)
192
+
113
193
  meta_file = LOGS_DIR / f"job_{job_id}.meta"
114
194
  out_file = LOGS_DIR / f"job_{job_id}.out"
115
195
 
package/web/config.py CHANGED
@@ -21,7 +21,6 @@ RECENT_DIRS_FILE = DATA_DIR / "recent_dirs.json"
21
21
  SETTINGS_FILE = DATA_DIR / "settings.json"
22
22
  SERVICE_SCRIPT = CONTROLLER_DIR / "service" / "controller.sh"
23
23
  SESSIONS_DIR = CONTROLLER_DIR / "sessions"
24
- QUEUE_DIR = CONTROLLER_DIR / "queue"
25
24
  CLAUDE_PROJECTS_DIR = Path.home() / ".claude" / "projects"
26
25
 
27
26
  PORT = int(os.environ.get("PORT", 8420))
@@ -33,8 +32,6 @@ PORT = int(os.environ.get("PORT", 8420))
33
32
  # 허용된 Origin 목록 (CORS)
34
33
  # 환경변수 ALLOWED_ORIGINS로 오버라이드 가능 (쉼표 구분)
35
34
  _DEFAULT_ORIGINS = [
36
- "http://claude.won-space.com",
37
- "https://claude.won-space.com",
38
35
  "http://localhost:8420",
39
36
  "https://localhost:8420",
40
37
  ]
@@ -54,11 +51,11 @@ AUTH_REQUIRED = os.environ.get("AUTH_REQUIRED", "false").lower() == "true"
54
51
 
55
52
  # 인증 면제 경로 (AUTH_REQUIRED=true일 때만 적용)
56
53
  AUTH_EXEMPT_PREFIXES = ("/static/", "/uploads/", "/api/auth/")
57
- AUTH_EXEMPT_PATHS = {"/", "/index.html", "/styles.css", "/app.js"}
54
+ AUTH_EXEMPT_PATHS = {"/", "/index.html", "/styles.css", "/app.js", "/api/health"}
58
55
 
59
56
  # 앱 실행 시 브라우저에서 열 공개 URL
60
57
  # 환경변수 PUBLIC_URL로 오버라이드 가능
61
- PUBLIC_URL = os.environ.get("PUBLIC_URL", "https://claude.won-space.com")
58
+ PUBLIC_URL = os.environ.get("PUBLIC_URL", "https://localhost:8420")
62
59
 
63
60
  # SSL 인증서 경로 (mkcert 생성 파일)
64
61
  SSL_CERT = os.environ.get("SSL_CERT", str(CONTROLLER_DIR / "certs" / "localhost+1.pem"))