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.
- package/README.md +2 -2
- package/bin/autoloop.sh +382 -0
- package/bin/ctl +327 -5
- package/bin/native-app.py +5 -2
- package/bin/watchdog.sh +357 -0
- package/cognitive/__init__.py +14 -0
- package/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
- package/cognitive/__pycache__/dispatcher.cpython-314.pyc +0 -0
- package/cognitive/__pycache__/evaluator.cpython-314.pyc +0 -0
- package/cognitive/__pycache__/goal_engine.cpython-314.pyc +0 -0
- package/cognitive/__pycache__/learning.cpython-314.pyc +0 -0
- package/cognitive/__pycache__/orchestrator.cpython-314.pyc +0 -0
- package/cognitive/__pycache__/planner.cpython-314.pyc +0 -0
- package/cognitive/dispatcher.py +192 -0
- package/cognitive/evaluator.py +289 -0
- package/cognitive/goal_engine.py +232 -0
- package/cognitive/learning.py +189 -0
- package/cognitive/orchestrator.py +303 -0
- package/cognitive/planner.py +207 -0
- package/cognitive/prompts/analyst.md +31 -0
- package/cognitive/prompts/coder.md +22 -0
- package/cognitive/prompts/reviewer.md +33 -0
- package/cognitive/prompts/tester.md +21 -0
- package/cognitive/prompts/writer.md +25 -0
- package/config.sh +6 -1
- package/dag/__init__.py +5 -0
- package/dag/__pycache__/__init__.cpython-314.pyc +0 -0
- package/dag/__pycache__/graph.cpython-314.pyc +0 -0
- package/dag/graph.py +222 -0
- package/lib/jobs.sh +12 -1
- package/package.json +5 -1
- package/postinstall.sh +1 -1
- package/service/controller.sh +43 -11
- package/web/audit.py +122 -0
- package/web/checkpoint.py +80 -0
- package/web/config.py +2 -5
- package/web/handler.py +464 -26
- package/web/handler_fs.py +15 -14
- package/web/handler_goals.py +203 -0
- package/web/handler_jobs.py +165 -42
- package/web/handler_memory.py +203 -0
- package/web/jobs.py +576 -12
- package/web/personas.py +419 -0
- package/web/pipeline.py +682 -50
- package/web/presets.py +506 -0
- package/web/projects.py +58 -4
- package/web/static/api.js +90 -3
- package/web/static/app.js +8 -0
- package/web/static/base.css +51 -12
- package/web/static/context.js +14 -4
- package/web/static/form.css +3 -2
- package/web/static/goals.css +363 -0
- package/web/static/goals.js +300 -0
- package/web/static/i18n.js +288 -0
- package/web/static/index.html +142 -6
- package/web/static/jobs.css +951 -4
- package/web/static/jobs.js +890 -54
- package/web/static/memoryview.js +117 -0
- package/web/static/personas.js +228 -0
- package/web/static/pipeline.css +308 -1
- package/web/static/pipelines.js +249 -14
- package/web/static/presets.js +244 -0
- package/web/static/send.js +26 -4
- package/web/static/settings-style.css +34 -3
- package/web/static/settings.js +37 -1
- package/web/static/stream.js +242 -19
- package/web/static/utils.js +54 -2
- 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})
|