@mindfoldhq/trellis 0.1.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/LICENSE +110 -0
- package/README.md +149 -0
- package/bin/trellis.js +3 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +42 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +236 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/configurators/claude.d.ts +35 -0
- package/dist/configurators/claude.d.ts.map +1 -0
- package/dist/configurators/claude.js +83 -0
- package/dist/configurators/claude.js.map +1 -0
- package/dist/configurators/cursor.d.ts +8 -0
- package/dist/configurators/cursor.d.ts.map +1 -0
- package/dist/configurators/cursor.js +22 -0
- package/dist/configurators/cursor.js.map +1 -0
- package/dist/configurators/templates.d.ts +40 -0
- package/dist/configurators/templates.d.ts.map +1 -0
- package/dist/configurators/templates.js +67 -0
- package/dist/configurators/templates.js.map +1 -0
- package/dist/configurators/workflow.d.ts +16 -0
- package/dist/configurators/workflow.d.ts.map +1 -0
- package/dist/configurators/workflow.js +169 -0
- package/dist/configurators/workflow.js.map +1 -0
- package/dist/constants/paths.d.ts +69 -0
- package/dist/constants/paths.d.ts.map +1 -0
- package/dist/constants/paths.js +80 -0
- package/dist/constants/paths.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/templates/agents/check.txt +120 -0
- package/dist/templates/agents/debug.txt +121 -0
- package/dist/templates/agents/dispatch.txt +201 -0
- package/dist/templates/agents/implement.txt +114 -0
- package/dist/templates/agents/index.d.ts +35 -0
- package/dist/templates/agents/index.d.ts.map +1 -0
- package/dist/templates/agents/index.js +71 -0
- package/dist/templates/agents/index.js.map +1 -0
- package/dist/templates/agents/research.txt +258 -0
- package/dist/templates/commands/claude/start.md.txt +127 -0
- package/dist/templates/commands/common/before-backend-dev.txt +13 -0
- package/dist/templates/commands/common/before-frontend-dev.txt +13 -0
- package/dist/templates/commands/common/break-loop.txt +107 -0
- package/dist/templates/commands/common/check-backend.txt +13 -0
- package/dist/templates/commands/common/check-cross-layer.txt +153 -0
- package/dist/templates/commands/common/check-frontend.txt +13 -0
- package/dist/templates/commands/common/create-command.txt +154 -0
- package/dist/templates/commands/common/finish-work.txt +129 -0
- package/dist/templates/commands/common/integrate-skill.txt +219 -0
- package/dist/templates/commands/common/onboard-developer.txt +355 -0
- package/dist/templates/commands/common/record-agent-flow.txt +62 -0
- package/dist/templates/commands/cursor/start.md.txt +94 -0
- package/dist/templates/commands/index.d.ts +46 -0
- package/dist/templates/commands/index.d.ts.map +1 -0
- package/dist/templates/commands/index.js +151 -0
- package/dist/templates/commands/index.js.map +1 -0
- package/dist/templates/extract.d.ts +22 -0
- package/dist/templates/extract.d.ts.map +1 -0
- package/dist/templates/extract.js +34 -0
- package/dist/templates/extract.js.map +1 -0
- package/dist/templates/hooks/index.d.ts +33 -0
- package/dist/templates/hooks/index.d.ts.map +1 -0
- package/dist/templates/hooks/index.js +53 -0
- package/dist/templates/hooks/index.js.map +1 -0
- package/dist/templates/hooks/inject-subagent-context.py +620 -0
- package/dist/templates/hooks/settings.json +16 -0
- package/dist/templates/markdown/agent-traces-index.md.txt +124 -0
- package/dist/templates/markdown/agents.md.txt +18 -0
- package/dist/templates/markdown/gitignore.txt +3 -0
- package/dist/templates/markdown/index.d.ts +26 -0
- package/dist/templates/markdown/index.d.ts.map +1 -0
- package/dist/templates/markdown/index.js +33 -0
- package/dist/templates/markdown/index.js.map +1 -0
- package/dist/templates/markdown/init-agent.md.txt +315 -0
- package/dist/templates/markdown/structure/backend/database-guidelines.md.txt +51 -0
- package/dist/templates/markdown/structure/backend/directory-structure.md.txt +54 -0
- package/dist/templates/markdown/structure/backend/error-handling.md.txt +51 -0
- package/dist/templates/markdown/structure/backend/index.md.txt +38 -0
- package/dist/templates/markdown/structure/backend/logging-guidelines.md.txt +51 -0
- package/dist/templates/markdown/structure/backend/quality-guidelines.md.txt +51 -0
- package/dist/templates/markdown/structure/frontend/component-guidelines.md.txt +59 -0
- package/dist/templates/markdown/structure/frontend/directory-structure.md.txt +54 -0
- package/dist/templates/markdown/structure/frontend/hook-guidelines.md.txt +51 -0
- package/dist/templates/markdown/structure/frontend/index.md.txt +39 -0
- package/dist/templates/markdown/structure/frontend/quality-guidelines.md.txt +51 -0
- package/dist/templates/markdown/structure/frontend/state-management.md.txt +51 -0
- package/dist/templates/markdown/structure/frontend/type-safety.md.txt +51 -0
- package/dist/templates/markdown/structure/guides/code-reuse-thinking-guide.md.txt +92 -0
- package/dist/templates/markdown/structure/guides/cross-layer-thinking-guide.md.txt +94 -0
- package/dist/templates/markdown/structure/guides/index.md.txt +79 -0
- package/dist/templates/markdown/workflow.md.txt +335 -0
- package/dist/templates/scripts/add-session.sh.txt +384 -0
- package/dist/templates/scripts/common/developer.sh.txt +130 -0
- package/dist/templates/scripts/common/git-context.sh.txt +237 -0
- package/dist/templates/scripts/common/paths.sh.txt +201 -0
- package/dist/templates/scripts/create-bootstrap.sh.txt +298 -0
- package/dist/templates/scripts/feature.sh.txt +700 -0
- package/dist/templates/scripts/get-context.sh.txt +7 -0
- package/dist/templates/scripts/get-developer.sh.txt +15 -0
- package/dist/templates/scripts/index.d.ts +25 -0
- package/dist/templates/scripts/index.d.ts.map +1 -0
- package/dist/templates/scripts/index.js +28 -0
- package/dist/templates/scripts/index.js.map +1 -0
- package/dist/templates/scripts/init-developer.sh.txt +34 -0
- package/dist/types/ai-tools.d.ts +35 -0
- package/dist/types/ai-tools.d.ts.map +1 -0
- package/dist/types/ai-tools.js +31 -0
- package/dist/types/ai-tools.js.map +1 -0
- package/dist/utils/file-writer.d.ts +23 -0
- package/dist/utils/file-writer.d.ts.map +1 -0
- package/dist/utils/file-writer.js +140 -0
- package/dist/utils/file-writer.js.map +1 -0
- package/dist/utils/project-detector.d.ts +16 -0
- package/dist/utils/project-detector.d.ts.map +1 -0
- package/dist/utils/project-detector.js +186 -0
- package/dist/utils/project-detector.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Multi-Agent Pipeline Context Injection Hook
|
|
4
|
+
|
|
5
|
+
Core Design Philosophy:
|
|
6
|
+
- Dispatch becomes a pure dispatcher, only responsible for "calling subagents"
|
|
7
|
+
- Hook is responsible for injecting all context, subagent works autonomously with complete info
|
|
8
|
+
- Each agent has a dedicated jsonl file defining its context
|
|
9
|
+
- No resume needed, no segmentation, behavior controlled by code not prompt
|
|
10
|
+
|
|
11
|
+
Trigger: PreToolUse (before Task tool call)
|
|
12
|
+
|
|
13
|
+
Context Source: .trellis/.current-feature points to feature directory
|
|
14
|
+
- implement.jsonl - Implement agent dedicated context
|
|
15
|
+
- check.jsonl - Check agent dedicated context
|
|
16
|
+
- debug.jsonl - Debug agent dedicated context
|
|
17
|
+
- research.jsonl - Research agent dedicated context (optional, usually not needed)
|
|
18
|
+
- cr.jsonl - Code review dedicated context
|
|
19
|
+
- prd.md - Requirements document
|
|
20
|
+
- info.md - Technical design
|
|
21
|
+
- codex-review-output.txt - Code Review results
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import sys
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
# =============================================================================
|
|
30
|
+
# Path Constants (change here to rename directories)
|
|
31
|
+
# =============================================================================
|
|
32
|
+
|
|
33
|
+
DIR_WORKFLOW = ".trellis"
|
|
34
|
+
DIR_PROGRESS = "agent-traces"
|
|
35
|
+
DIR_FEATURES = "features"
|
|
36
|
+
DIR_STRUCTURE = "structure"
|
|
37
|
+
FILE_CURRENT_FEATURE = ".current-feature"
|
|
38
|
+
|
|
39
|
+
# =============================================================================
|
|
40
|
+
# Subagent Constants (change here to rename subagent types)
|
|
41
|
+
# =============================================================================
|
|
42
|
+
|
|
43
|
+
AGENT_IMPLEMENT = "implement"
|
|
44
|
+
AGENT_CHECK = "check"
|
|
45
|
+
AGENT_DEBUG = "debug"
|
|
46
|
+
AGENT_RESEARCH = "research"
|
|
47
|
+
|
|
48
|
+
# Agents that require a feature directory
|
|
49
|
+
AGENTS_REQUIRE_FEATURE = (AGENT_IMPLEMENT, AGENT_CHECK, AGENT_DEBUG)
|
|
50
|
+
# All supported agents
|
|
51
|
+
AGENTS_ALL = (AGENT_IMPLEMENT, AGENT_CHECK, AGENT_DEBUG, AGENT_RESEARCH)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def find_repo_root(start_path: str) -> str | None:
|
|
55
|
+
"""
|
|
56
|
+
Find git repo root from start_path upwards
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Repo root path, or None if not found
|
|
60
|
+
"""
|
|
61
|
+
current = Path(start_path).resolve()
|
|
62
|
+
while current != current.parent:
|
|
63
|
+
if (current / ".git").exists():
|
|
64
|
+
return str(current)
|
|
65
|
+
current = current.parent
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_current_feature(repo_root: str) -> str | None:
|
|
70
|
+
"""
|
|
71
|
+
Read current feature directory path from .trellis/.current-feature
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Feature directory relative path (relative to repo_root)
|
|
75
|
+
None if not set
|
|
76
|
+
"""
|
|
77
|
+
current_feature_file = os.path.join(repo_root, DIR_WORKFLOW, FILE_CURRENT_FEATURE)
|
|
78
|
+
if not os.path.exists(current_feature_file):
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
with open(current_feature_file, "r", encoding="utf-8") as f:
|
|
83
|
+
content = f.read().strip()
|
|
84
|
+
return content if content else None
|
|
85
|
+
except Exception:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def read_file_content(base_path: str, file_path: str) -> str | None:
|
|
90
|
+
"""Read file content, return None if file doesn't exist"""
|
|
91
|
+
full_path = os.path.join(base_path, file_path)
|
|
92
|
+
if os.path.exists(full_path) and os.path.isfile(full_path):
|
|
93
|
+
try:
|
|
94
|
+
with open(full_path, "r", encoding="utf-8") as f:
|
|
95
|
+
return f.read()
|
|
96
|
+
except Exception:
|
|
97
|
+
return None
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def read_directory_contents(
|
|
102
|
+
base_path: str, dir_path: str, max_files: int = 20
|
|
103
|
+
) -> list[tuple[str, str]]:
|
|
104
|
+
"""
|
|
105
|
+
Read all .md files in a directory
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
base_path: Base path (usually repo_root)
|
|
109
|
+
dir_path: Directory relative path
|
|
110
|
+
max_files: Max files to read (prevent huge directories)
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
[(file_path, content), ...]
|
|
114
|
+
"""
|
|
115
|
+
full_path = os.path.join(base_path, dir_path)
|
|
116
|
+
if not os.path.exists(full_path) or not os.path.isdir(full_path):
|
|
117
|
+
return []
|
|
118
|
+
|
|
119
|
+
results = []
|
|
120
|
+
try:
|
|
121
|
+
# Only read .md files, sorted by filename
|
|
122
|
+
md_files = sorted(
|
|
123
|
+
[
|
|
124
|
+
f
|
|
125
|
+
for f in os.listdir(full_path)
|
|
126
|
+
if f.endswith(".md") and os.path.isfile(os.path.join(full_path, f))
|
|
127
|
+
]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
for filename in md_files[:max_files]:
|
|
131
|
+
file_full_path = os.path.join(full_path, filename)
|
|
132
|
+
relative_path = os.path.join(dir_path, filename)
|
|
133
|
+
try:
|
|
134
|
+
with open(file_full_path, "r", encoding="utf-8") as f:
|
|
135
|
+
content = f.read()
|
|
136
|
+
results.append((relative_path, content))
|
|
137
|
+
except Exception:
|
|
138
|
+
continue
|
|
139
|
+
except Exception:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
return results
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def read_jsonl_entries(base_path: str, jsonl_path: str) -> list[tuple[str, str]]:
|
|
146
|
+
"""
|
|
147
|
+
Read all file/directory contents referenced in jsonl file
|
|
148
|
+
|
|
149
|
+
Schema:
|
|
150
|
+
{"file": "path/to/file.md", "reason": "..."}
|
|
151
|
+
{"file": "path/to/dir/", "type": "directory", "reason": "..."}
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
[(path, content), ...]
|
|
155
|
+
"""
|
|
156
|
+
full_path = os.path.join(base_path, jsonl_path)
|
|
157
|
+
if not os.path.exists(full_path):
|
|
158
|
+
return []
|
|
159
|
+
|
|
160
|
+
results = []
|
|
161
|
+
try:
|
|
162
|
+
with open(full_path, "r", encoding="utf-8") as f:
|
|
163
|
+
for line in f:
|
|
164
|
+
line = line.strip()
|
|
165
|
+
if not line:
|
|
166
|
+
continue
|
|
167
|
+
try:
|
|
168
|
+
item = json.loads(line)
|
|
169
|
+
file_path = item.get("file") or item.get("path")
|
|
170
|
+
entry_type = item.get("type", "file")
|
|
171
|
+
|
|
172
|
+
if not file_path:
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
if entry_type == "directory":
|
|
176
|
+
# Read all .md files in directory
|
|
177
|
+
dir_contents = read_directory_contents(base_path, file_path)
|
|
178
|
+
results.extend(dir_contents)
|
|
179
|
+
else:
|
|
180
|
+
# Read single file
|
|
181
|
+
content = read_file_content(base_path, file_path)
|
|
182
|
+
if content:
|
|
183
|
+
results.append((file_path, content))
|
|
184
|
+
except json.JSONDecodeError:
|
|
185
|
+
continue
|
|
186
|
+
except Exception:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
return results
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def get_agent_context(repo_root: str, feature_dir: str, agent_type: str) -> str:
|
|
193
|
+
"""
|
|
194
|
+
Get complete context for specified agent
|
|
195
|
+
|
|
196
|
+
Prioritize agent-specific jsonl, fallback to spec.jsonl if not exists
|
|
197
|
+
"""
|
|
198
|
+
context_parts = []
|
|
199
|
+
|
|
200
|
+
# 1. Try agent-specific jsonl
|
|
201
|
+
agent_jsonl = f"{feature_dir}/{agent_type}.jsonl"
|
|
202
|
+
agent_entries = read_jsonl_entries(repo_root, agent_jsonl)
|
|
203
|
+
|
|
204
|
+
# 2. If agent-specific jsonl doesn't exist or empty, fallback to spec.jsonl
|
|
205
|
+
if not agent_entries:
|
|
206
|
+
agent_entries = read_jsonl_entries(repo_root, f"{feature_dir}/spec.jsonl")
|
|
207
|
+
|
|
208
|
+
# 3. Add all files from jsonl
|
|
209
|
+
for file_path, content in agent_entries:
|
|
210
|
+
context_parts.append(f"=== {file_path} ===\n{content}")
|
|
211
|
+
|
|
212
|
+
return "\n\n".join(context_parts)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def get_implement_context(repo_root: str, feature_dir: str) -> str:
|
|
216
|
+
"""
|
|
217
|
+
Complete context for Implement Agent
|
|
218
|
+
|
|
219
|
+
Read order:
|
|
220
|
+
1. All files in implement.jsonl (dev specs)
|
|
221
|
+
2. prd.md (requirements)
|
|
222
|
+
3. info.md (technical design)
|
|
223
|
+
"""
|
|
224
|
+
context_parts = []
|
|
225
|
+
|
|
226
|
+
# 1. Read implement.jsonl (or fallback to spec.jsonl)
|
|
227
|
+
base_context = get_agent_context(repo_root, feature_dir, "implement")
|
|
228
|
+
if base_context:
|
|
229
|
+
context_parts.append(base_context)
|
|
230
|
+
|
|
231
|
+
# 2. Requirements document
|
|
232
|
+
prd_content = read_file_content(repo_root, f"{feature_dir}/prd.md")
|
|
233
|
+
if prd_content:
|
|
234
|
+
context_parts.append(
|
|
235
|
+
f"=== {feature_dir}/prd.md (Requirements) ===\n{prd_content}"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# 3. Technical design
|
|
239
|
+
info_content = read_file_content(repo_root, f"{feature_dir}/info.md")
|
|
240
|
+
if info_content:
|
|
241
|
+
context_parts.append(
|
|
242
|
+
f"=== {feature_dir}/info.md (Technical Design) ===\n{info_content}"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
return "\n\n".join(context_parts)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def get_check_context(repo_root: str, feature_dir: str) -> str:
|
|
249
|
+
"""
|
|
250
|
+
Complete context for Check Agent
|
|
251
|
+
|
|
252
|
+
Read order:
|
|
253
|
+
1. All files in check.jsonl (check specs + dev specs)
|
|
254
|
+
2. prd.md (for understanding feature intent)
|
|
255
|
+
"""
|
|
256
|
+
context_parts = []
|
|
257
|
+
|
|
258
|
+
# 1. Read check.jsonl (or fallback to spec.jsonl + hardcoded check files)
|
|
259
|
+
check_entries = read_jsonl_entries(repo_root, f"{feature_dir}/check.jsonl")
|
|
260
|
+
|
|
261
|
+
if check_entries:
|
|
262
|
+
for file_path, content in check_entries:
|
|
263
|
+
context_parts.append(f"=== {file_path} ===\n{content}")
|
|
264
|
+
else:
|
|
265
|
+
# Fallback: use hardcoded check files + spec.jsonl
|
|
266
|
+
check_files = [
|
|
267
|
+
(".claude/commands/finish-work.md", "Finish work checklist"),
|
|
268
|
+
(".claude/commands/check-cross-layer.md", "Cross-layer check spec"),
|
|
269
|
+
(".claude/commands/check-backend.md", "Backend check spec"),
|
|
270
|
+
(".claude/commands/check-frontend.md", "Frontend check spec"),
|
|
271
|
+
]
|
|
272
|
+
for file_path, description in check_files:
|
|
273
|
+
content = read_file_content(repo_root, file_path)
|
|
274
|
+
if content:
|
|
275
|
+
context_parts.append(f"=== {file_path} ({description}) ===\n{content}")
|
|
276
|
+
|
|
277
|
+
# Add spec.jsonl
|
|
278
|
+
spec_entries = read_jsonl_entries(repo_root, f"{feature_dir}/spec.jsonl")
|
|
279
|
+
for file_path, content in spec_entries:
|
|
280
|
+
context_parts.append(f"=== {file_path} (Dev spec) ===\n{content}")
|
|
281
|
+
|
|
282
|
+
# 2. Requirements document (for understanding feature intent)
|
|
283
|
+
prd_content = read_file_content(repo_root, f"{feature_dir}/prd.md")
|
|
284
|
+
if prd_content:
|
|
285
|
+
context_parts.append(
|
|
286
|
+
f"=== {feature_dir}/prd.md (Requirements - for understanding intent) ===\n{prd_content}"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
return "\n\n".join(context_parts)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def get_debug_context(repo_root: str, feature_dir: str) -> str:
|
|
293
|
+
"""
|
|
294
|
+
Complete context for Debug Agent
|
|
295
|
+
|
|
296
|
+
Read order:
|
|
297
|
+
1. All files in debug.jsonl (specs needed for fixing)
|
|
298
|
+
2. codex-review-output.txt (Codex Review results)
|
|
299
|
+
"""
|
|
300
|
+
context_parts = []
|
|
301
|
+
|
|
302
|
+
# 1. Read debug.jsonl (or fallback to spec.jsonl + hardcoded check files)
|
|
303
|
+
debug_entries = read_jsonl_entries(repo_root, f"{feature_dir}/debug.jsonl")
|
|
304
|
+
|
|
305
|
+
if debug_entries:
|
|
306
|
+
for file_path, content in debug_entries:
|
|
307
|
+
context_parts.append(f"=== {file_path} ===\n{content}")
|
|
308
|
+
else:
|
|
309
|
+
# Fallback: use spec.jsonl + hardcoded check files
|
|
310
|
+
spec_entries = read_jsonl_entries(repo_root, f"{feature_dir}/spec.jsonl")
|
|
311
|
+
for file_path, content in spec_entries:
|
|
312
|
+
context_parts.append(f"=== {file_path} (Dev spec) ===\n{content}")
|
|
313
|
+
|
|
314
|
+
check_files = [
|
|
315
|
+
(".claude/commands/check-backend.md", "Backend check spec"),
|
|
316
|
+
(".claude/commands/check-frontend.md", "Frontend check spec"),
|
|
317
|
+
(".claude/commands/check-cross-layer.md", "Cross-layer check spec"),
|
|
318
|
+
]
|
|
319
|
+
for file_path, description in check_files:
|
|
320
|
+
content = read_file_content(repo_root, file_path)
|
|
321
|
+
if content:
|
|
322
|
+
context_parts.append(f"=== {file_path} ({description}) ===\n{content}")
|
|
323
|
+
|
|
324
|
+
# 2. Codex review output (if exists)
|
|
325
|
+
codex_output = read_file_content(
|
|
326
|
+
repo_root, f"{feature_dir}/codex-review-output.txt"
|
|
327
|
+
)
|
|
328
|
+
if codex_output:
|
|
329
|
+
context_parts.append(
|
|
330
|
+
f"=== {feature_dir}/codex-review-output.txt (Codex Review Results) ===\n{codex_output}"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
return "\n\n".join(context_parts)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def build_implement_prompt(original_prompt: str, context: str) -> str:
|
|
337
|
+
"""Build complete prompt for Implement"""
|
|
338
|
+
return f"""# Implement Agent Task
|
|
339
|
+
|
|
340
|
+
You are the Implement Agent in the Multi-Agent Pipeline.
|
|
341
|
+
|
|
342
|
+
## Your Context
|
|
343
|
+
|
|
344
|
+
All the information you need has been prepared for you:
|
|
345
|
+
|
|
346
|
+
{context}
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Your Task
|
|
351
|
+
|
|
352
|
+
{original_prompt}
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Workflow
|
|
357
|
+
|
|
358
|
+
1. **Understand specs** - All dev specs are injected above, understand them
|
|
359
|
+
2. **Understand requirements** - Read requirements document and technical design
|
|
360
|
+
3. **Implement feature** - Implement following specs and design
|
|
361
|
+
4. **Self-check** - Ensure code quality against check specs
|
|
362
|
+
|
|
363
|
+
## Important Constraints
|
|
364
|
+
|
|
365
|
+
- Do NOT execute git commit, only code modifications
|
|
366
|
+
- Follow all dev specs injected above
|
|
367
|
+
- Report list of modified/created files when done"""
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def build_check_prompt(original_prompt: str, context: str) -> str:
|
|
371
|
+
"""Build complete prompt for Check"""
|
|
372
|
+
return f"""# Check Agent Task
|
|
373
|
+
|
|
374
|
+
You are the Check Agent in the Multi-Agent Pipeline (code and cross-layer checker).
|
|
375
|
+
|
|
376
|
+
## Your Context
|
|
377
|
+
|
|
378
|
+
All check specs and dev specs you need:
|
|
379
|
+
|
|
380
|
+
{context}
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Your Task
|
|
385
|
+
|
|
386
|
+
{original_prompt}
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Workflow
|
|
391
|
+
|
|
392
|
+
1. **Get changes** - Run `git diff --name-only` and `git diff` to get code changes
|
|
393
|
+
2. **Check against specs** - Check item by item against specs above
|
|
394
|
+
3. **Self-fix** - Fix issues directly, don't just report
|
|
395
|
+
4. **Run verification** - Reference .husky/pre-commit for typecheck and lint
|
|
396
|
+
|
|
397
|
+
## Important Constraints
|
|
398
|
+
|
|
399
|
+
- Fix issues yourself, don't just report
|
|
400
|
+
- Must execute complete checklist in finish-work.md
|
|
401
|
+
- Pay special attention to impact radius analysis (L1-L5)"""
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def build_debug_prompt(original_prompt: str, context: str) -> str:
|
|
405
|
+
"""Build complete prompt for Debug"""
|
|
406
|
+
return f"""# Debug Agent Task
|
|
407
|
+
|
|
408
|
+
You are the Debug Agent in the Multi-Agent Pipeline (issue fixer).
|
|
409
|
+
|
|
410
|
+
## Your Context
|
|
411
|
+
|
|
412
|
+
Dev specs and Codex Review results:
|
|
413
|
+
|
|
414
|
+
{context}
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Your Task
|
|
419
|
+
|
|
420
|
+
{original_prompt}
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Workflow
|
|
425
|
+
|
|
426
|
+
1. **Understand issues** - Analyze issues pointed out in Codex Review
|
|
427
|
+
2. **Locate code** - Find positions that need fixing
|
|
428
|
+
3. **Fix against specs** - Fix issues following dev specs
|
|
429
|
+
4. **Verify fixes** - Run typecheck to ensure no new issues
|
|
430
|
+
|
|
431
|
+
## Important Constraints
|
|
432
|
+
|
|
433
|
+
- Do NOT execute git commit, only code modifications
|
|
434
|
+
- Run typecheck after each fix to verify
|
|
435
|
+
- Report which issues were fixed and which files were modified"""
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def get_research_context(repo_root: str, feature_dir: str | None) -> str:
|
|
439
|
+
"""
|
|
440
|
+
Context for Research Agent
|
|
441
|
+
|
|
442
|
+
Research doesn't need much preset context, only needs:
|
|
443
|
+
1. Project structure overview (where spec directories are)
|
|
444
|
+
2. Optional research.jsonl (if there are specific search needs)
|
|
445
|
+
"""
|
|
446
|
+
context_parts = []
|
|
447
|
+
|
|
448
|
+
# 1. Project structure overview (uses constants for paths)
|
|
449
|
+
structure_path = f"{DIR_WORKFLOW}/{DIR_STRUCTURE}"
|
|
450
|
+
project_structure = f"""## Project Spec Directory Structure
|
|
451
|
+
|
|
452
|
+
```
|
|
453
|
+
{structure_path}/
|
|
454
|
+
├── shared/ # Cross-project common specs (TypeScript, code quality, git)
|
|
455
|
+
├── frontend/ # Frontend standards
|
|
456
|
+
├── backend/ # Backend standards
|
|
457
|
+
└── guides/ # Thinking guides (cross-layer, code reuse, etc.)
|
|
458
|
+
|
|
459
|
+
{DIR_WORKFLOW}/big-question/ # Known issues and pitfalls
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
## Search Tips
|
|
463
|
+
|
|
464
|
+
- Spec files: `{structure_path}/**/*.md`
|
|
465
|
+
- Known issues: `{DIR_WORKFLOW}/big-question/`
|
|
466
|
+
- Code search: Use Glob and Grep tools
|
|
467
|
+
- Tech solutions: Use mcp__exa__web_search_exa or mcp__exa__get_code_context_exa"""
|
|
468
|
+
|
|
469
|
+
context_parts.append(project_structure)
|
|
470
|
+
|
|
471
|
+
# 2. If feature directory exists, try reading research.jsonl (optional)
|
|
472
|
+
if feature_dir:
|
|
473
|
+
research_entries = read_jsonl_entries(
|
|
474
|
+
repo_root, f"{feature_dir}/research.jsonl"
|
|
475
|
+
)
|
|
476
|
+
if research_entries:
|
|
477
|
+
context_parts.append(
|
|
478
|
+
"\n## Additional Search Context (from research.jsonl)\n"
|
|
479
|
+
)
|
|
480
|
+
for file_path, content in research_entries:
|
|
481
|
+
context_parts.append(f"=== {file_path} ===\n{content}")
|
|
482
|
+
|
|
483
|
+
return "\n\n".join(context_parts)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def build_research_prompt(original_prompt: str, context: str) -> str:
|
|
487
|
+
"""Build complete prompt for Research"""
|
|
488
|
+
return f"""# Research Agent Task
|
|
489
|
+
|
|
490
|
+
You are the Research Agent in the Multi-Agent Pipeline (search researcher).
|
|
491
|
+
|
|
492
|
+
## Core Principle
|
|
493
|
+
|
|
494
|
+
**You do one thing: find and explain information.**
|
|
495
|
+
|
|
496
|
+
You are a documenter, not a reviewer.
|
|
497
|
+
|
|
498
|
+
## Project Info
|
|
499
|
+
|
|
500
|
+
{context}
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## Your Task
|
|
505
|
+
|
|
506
|
+
{original_prompt}
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## Workflow
|
|
511
|
+
|
|
512
|
+
1. **Understand query** - Determine search type (internal/external) and scope
|
|
513
|
+
2. **Plan search** - List search steps for complex queries
|
|
514
|
+
3. **Execute search** - Execute multiple independent searches in parallel
|
|
515
|
+
4. **Organize results** - Output structured report
|
|
516
|
+
|
|
517
|
+
## Search Tools
|
|
518
|
+
|
|
519
|
+
| Tool | Purpose |
|
|
520
|
+
|------|---------|
|
|
521
|
+
| Glob | Search by filename pattern |
|
|
522
|
+
| Grep | Search by content |
|
|
523
|
+
| Read | Read file content |
|
|
524
|
+
| mcp__exa__web_search_exa | External web search |
|
|
525
|
+
| mcp__exa__get_code_context_exa | External code/doc search |
|
|
526
|
+
|
|
527
|
+
## Strict Boundaries
|
|
528
|
+
|
|
529
|
+
**Only allowed**: Describe what exists, where it is, how it works
|
|
530
|
+
|
|
531
|
+
**Forbidden** (unless explicitly asked):
|
|
532
|
+
- Suggest improvements
|
|
533
|
+
- Criticize implementation
|
|
534
|
+
- Recommend refactoring
|
|
535
|
+
- Modify any files
|
|
536
|
+
|
|
537
|
+
## Report Format
|
|
538
|
+
|
|
539
|
+
Provide structured search results including:
|
|
540
|
+
- List of files found (with paths)
|
|
541
|
+
- Code pattern analysis (if applicable)
|
|
542
|
+
- Related spec documents
|
|
543
|
+
- External references (if any)"""
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def main():
|
|
547
|
+
try:
|
|
548
|
+
input_data = json.load(sys.stdin)
|
|
549
|
+
except json.JSONDecodeError:
|
|
550
|
+
sys.exit(0)
|
|
551
|
+
|
|
552
|
+
tool_name = input_data.get("tool_name", "")
|
|
553
|
+
|
|
554
|
+
if tool_name != "Task":
|
|
555
|
+
sys.exit(0)
|
|
556
|
+
|
|
557
|
+
tool_input = input_data.get("tool_input", {})
|
|
558
|
+
subagent_type = tool_input.get("subagent_type", "")
|
|
559
|
+
original_prompt = tool_input.get("prompt", "")
|
|
560
|
+
cwd = input_data.get("cwd", os.getcwd())
|
|
561
|
+
|
|
562
|
+
# Only handle subagent types we care about
|
|
563
|
+
if subagent_type not in AGENTS_ALL:
|
|
564
|
+
sys.exit(0)
|
|
565
|
+
|
|
566
|
+
# Find repo root
|
|
567
|
+
repo_root = find_repo_root(cwd)
|
|
568
|
+
if not repo_root:
|
|
569
|
+
sys.exit(0)
|
|
570
|
+
|
|
571
|
+
# Get current feature directory (research doesn't require it)
|
|
572
|
+
feature_dir = get_current_feature(repo_root)
|
|
573
|
+
|
|
574
|
+
# implement/check/debug need feature directory
|
|
575
|
+
if subagent_type in AGENTS_REQUIRE_FEATURE:
|
|
576
|
+
if not feature_dir:
|
|
577
|
+
sys.exit(0)
|
|
578
|
+
# Check if feature directory exists
|
|
579
|
+
feature_dir_full = os.path.join(repo_root, feature_dir)
|
|
580
|
+
if not os.path.exists(feature_dir_full):
|
|
581
|
+
sys.exit(0)
|
|
582
|
+
|
|
583
|
+
# Get context and build prompt based on subagent type
|
|
584
|
+
if subagent_type == AGENT_IMPLEMENT:
|
|
585
|
+
assert feature_dir is not None # validated above
|
|
586
|
+
context = get_implement_context(repo_root, feature_dir)
|
|
587
|
+
new_prompt = build_implement_prompt(original_prompt, context)
|
|
588
|
+
elif subagent_type == AGENT_CHECK:
|
|
589
|
+
assert feature_dir is not None # validated above
|
|
590
|
+
context = get_check_context(repo_root, feature_dir)
|
|
591
|
+
new_prompt = build_check_prompt(original_prompt, context)
|
|
592
|
+
elif subagent_type == AGENT_DEBUG:
|
|
593
|
+
assert feature_dir is not None # validated above
|
|
594
|
+
context = get_debug_context(repo_root, feature_dir)
|
|
595
|
+
new_prompt = build_debug_prompt(original_prompt, context)
|
|
596
|
+
elif subagent_type == AGENT_RESEARCH:
|
|
597
|
+
# Research can work without feature directory
|
|
598
|
+
context = get_research_context(repo_root, feature_dir)
|
|
599
|
+
new_prompt = build_research_prompt(original_prompt, context)
|
|
600
|
+
else:
|
|
601
|
+
sys.exit(0)
|
|
602
|
+
|
|
603
|
+
if not context:
|
|
604
|
+
sys.exit(0)
|
|
605
|
+
|
|
606
|
+
# Return updated input
|
|
607
|
+
output = {
|
|
608
|
+
"hookSpecificOutput": {
|
|
609
|
+
"hookEventName": "PreToolUse",
|
|
610
|
+
"permissionDecision": "allow",
|
|
611
|
+
"updatedInput": {**tool_input, "prompt": new_prompt},
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
print(json.dumps(output, ensure_ascii=False))
|
|
616
|
+
sys.exit(0)
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
if __name__ == "__main__":
|
|
620
|
+
main()
|