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
@@ -0,0 +1,203 @@
1
+ """
2
+ Memory 관련 HTTP 핸들러 Mixin
3
+
4
+ 포함 엔드포인트:
5
+ - GET /api/memory # 메모리 검색 (query, type, tags, project 파라미터)
6
+ - GET /api/memory/:id # 메모리 상세
7
+ - POST /api/memory # 메모리 추가
8
+ - PUT /api/memory/:id/update # 메모리 수정 (POST로 처리)
9
+ - DELETE /api/memory/:id # 메모리 삭제
10
+ """
11
+
12
+ import sys
13
+ from urllib.parse import parse_qs
14
+
15
+ from config import CONTROLLER_DIR, DATA_DIR
16
+
17
+ # memory 패키지를 import 경로에 추가
18
+ if str(CONTROLLER_DIR) not in sys.path:
19
+ sys.path.insert(0, str(CONTROLLER_DIR))
20
+
21
+ from memory.store import MemoryStore, MemoryType
22
+
23
+ # 모듈 수준 싱글턴
24
+ _memory_store = None
25
+
26
+ # 유효한 MemoryType 값 목록
27
+ _VALID_TYPES = [t.value for t in MemoryType]
28
+
29
+
30
+ def _get_store():
31
+ global _memory_store
32
+ if _memory_store is None:
33
+ _memory_store = MemoryStore(str(DATA_DIR / "memory"))
34
+ return _memory_store
35
+
36
+
37
+ class MemoryHandlerMixin:
38
+
39
+ def _handle_list_memory(self, parsed):
40
+ """GET /api/memory — 메모리 검색/목록
41
+
42
+ 쿼리 파라미터:
43
+ - query: 키워드 검색어
44
+ - type: 메모리 유형 필터 (decision, pattern, failure, context)
45
+ - tags: 태그 필터 (쉼표 구분)
46
+ - project: 프로젝트 스코프 필터
47
+ - limit: 최대 반환 수 (기본 20)
48
+ """
49
+ qs = parse_qs(parsed.query)
50
+ query = qs.get("query", [None])[0]
51
+ type_str = qs.get("type", [None])[0]
52
+ tags_str = qs.get("tags", [None])[0]
53
+ project = qs.get("project", [None])[0]
54
+
55
+ try:
56
+ limit = int(qs.get("limit", [20])[0])
57
+ if limit < 1:
58
+ limit = 20
59
+ except (ValueError, TypeError):
60
+ limit = 20
61
+
62
+ # type 유효성 검사
63
+ mem_type = None
64
+ if type_str:
65
+ if type_str not in _VALID_TYPES:
66
+ return self._error_response(
67
+ f"유효하지 않은 type: {type_str}. 가능한 값: {_VALID_TYPES}",
68
+ 400, code="INVALID_PARAM")
69
+ mem_type = MemoryType(type_str)
70
+
71
+ tags = [t.strip() for t in tags_str.split(",") if t.strip()] if tags_str else None
72
+
73
+ store = _get_store()
74
+ if query:
75
+ results = store.search(
76
+ query=query, memory_type=mem_type,
77
+ tags=tags, project=project, limit=limit)
78
+ else:
79
+ results = store.list_all(memory_type=mem_type, limit=limit)
80
+ # list_all은 project 필터가 없으므로 수동 필터
81
+ if project:
82
+ results = [m for m in results if not m.get("project") or m["project"] == project]
83
+ if tags:
84
+ tag_set = set(tags)
85
+ results = [m for m in results if tag_set & set(m.get("tags", []))]
86
+
87
+ self._json_response({"memories": results, "count": len(results)})
88
+
89
+ def _handle_get_memory(self, mem_id):
90
+ """GET /api/memory/:id — 메모리 상세"""
91
+ mem = _get_store().get(mem_id)
92
+ if mem is None:
93
+ return self._error_response(
94
+ "메모리를 찾을 수 없습니다", 404, code="MEMORY_NOT_FOUND")
95
+ self._json_response(mem)
96
+
97
+ def _handle_create_memory(self):
98
+ """POST /api/memory — 메모리 추가
99
+
100
+ 요청 body:
101
+ - type: string (필수) — decision, pattern, failure, context
102
+ - title: string (필수)
103
+ - content: string (필수)
104
+ - tags: string[] (선택, 기본 [])
105
+ - project: string (선택)
106
+ - goal_id: string (선택)
107
+ """
108
+ body = self._read_body()
109
+
110
+ # 필수 필드 검증
111
+ type_str = body.get("type", "").strip()
112
+ if not type_str:
113
+ return self._error_response(
114
+ "type 필드가 필요합니다", 400, code="MISSING_FIELD")
115
+ if type_str not in _VALID_TYPES:
116
+ return self._error_response(
117
+ f"유효하지 않은 type: {type_str}. 가능한 값: {_VALID_TYPES}",
118
+ 400, code="INVALID_PARAM")
119
+
120
+ title = body.get("title", "").strip()
121
+ if not title:
122
+ return self._error_response(
123
+ "title 필드가 필요합니다", 400, code="MISSING_FIELD")
124
+
125
+ content = body.get("content", "").strip()
126
+ if not content:
127
+ return self._error_response(
128
+ "content 필드가 필요합니다", 400, code="MISSING_FIELD")
129
+
130
+ tags = body.get("tags", [])
131
+ if not isinstance(tags, list):
132
+ return self._error_response(
133
+ "tags는 문자열 배열이어야 합니다", 400, code="INVALID_PARAM")
134
+
135
+ project = body.get("project")
136
+ goal_id = body.get("goal_id")
137
+
138
+ mem = _get_store().add(
139
+ memory_type=MemoryType(type_str),
140
+ title=title,
141
+ content=content,
142
+ tags=tags,
143
+ project=project,
144
+ goal_id=goal_id,
145
+ )
146
+ self._json_response(mem, 201)
147
+
148
+ def _handle_update_memory(self, mem_id):
149
+ """POST /api/memory/:id/update — 메모리 수정
150
+
151
+ 요청 body (모두 선택):
152
+ - title: string
153
+ - content: string
154
+ - tags: string[]
155
+ - project: string
156
+ """
157
+ store = _get_store()
158
+ existing = store.get(mem_id)
159
+ if existing is None:
160
+ return self._error_response(
161
+ "메모리를 찾을 수 없습니다", 404, code="MEMORY_NOT_FOUND")
162
+
163
+ body = self._read_body()
164
+ kwargs = {}
165
+
166
+ if "title" in body:
167
+ title = body["title"].strip() if isinstance(body["title"], str) else ""
168
+ if not title:
169
+ return self._error_response(
170
+ "title은 빈 문자열일 수 없습니다", 400, code="INVALID_PARAM")
171
+ kwargs["title"] = title
172
+
173
+ if "content" in body:
174
+ content = body["content"].strip() if isinstance(body["content"], str) else ""
175
+ if not content:
176
+ return self._error_response(
177
+ "content는 빈 문자열일 수 없습니다", 400, code="INVALID_PARAM")
178
+ kwargs["content"] = content
179
+
180
+ if "tags" in body:
181
+ if not isinstance(body["tags"], list):
182
+ return self._error_response(
183
+ "tags는 문자열 배열이어야 합니다", 400, code="INVALID_PARAM")
184
+ kwargs["tags"] = body["tags"]
185
+
186
+ if "project" in body:
187
+ kwargs["project"] = body["project"]
188
+
189
+ if not kwargs:
190
+ return self._error_response(
191
+ "변경할 필드가 없습니다. title, content, tags, project 중 하나를 지정하세요.",
192
+ 400, code="NO_CHANGES")
193
+
194
+ updated = store.update(mem_id, **kwargs)
195
+ self._json_response(updated)
196
+
197
+ def _handle_delete_memory(self, mem_id):
198
+ """DELETE /api/memory/:id — 메모리 삭제"""
199
+ deleted = _get_store().delete(mem_id)
200
+ if not deleted:
201
+ return self._error_response(
202
+ "메모리를 찾을 수 없습니다", 404, code="MEMORY_NOT_FOUND")
203
+ self._json_response({"deleted": True, "id": mem_id})