@prateek_ai/agents-maker 1.0.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 +21 -0
- package/README.md +659 -0
- package/agents/architect_agent.md +175 -0
- package/agents/code_agent.md +178 -0
- package/agents/compression_agent.md +226 -0
- package/agents/execution_agent.md +157 -0
- package/agents/orchestrator.md +406 -0
- package/agents/reviewer_agent.md +134 -0
- package/agents/ui_agent.md +147 -0
- package/agents/ux_agent.md +144 -0
- package/bin/cli.js +79 -0
- package/config/agents.yaml +388 -0
- package/config/domain_profiles.yaml +394 -0
- package/config/project.yaml.example +16 -0
- package/config/token_policies.yaml +325 -0
- package/context_loaders/__init__.py +15 -0
- package/context_loaders/__pycache__/__init__.cpython-314.pyc +0 -0
- package/context_loaders/__pycache__/file_chunker.cpython-314.pyc +0 -0
- package/context_loaders/__pycache__/project_summary.cpython-314.pyc +0 -0
- package/context_loaders/__pycache__/repo_tree.cpython-314.pyc +0 -0
- package/context_loaders/file_chunker.py +252 -0
- package/context_loaders/project_summary.py +369 -0
- package/context_loaders/repo_tree.py +203 -0
- package/package.json +46 -0
- package/quickstart.ps1 +252 -0
- package/quickstart.sh +232 -0
- package/requirements.txt +3 -0
- package/skills/analyze_repo.md +86 -0
- package/skills/animated_website.md +285 -0
- package/skills/compare_approaches.md +86 -0
- package/skills/define_data_schema.md +99 -0
- package/skills/design_api.md +126 -0
- package/skills/improve_copy.md +69 -0
- package/skills/review_code.md +83 -0
- package/skills/review_layout.md +89 -0
- package/skills/suggest_next.md +105 -0
- package/skills/summarize_history.md +89 -0
- package/skills/write_process_map.md +105 -0
- package/skills/write_tests.md +97 -0
- package/token_optimization/__pycache__/compressor.cpython-314.pyc +0 -0
- package/token_optimization/compressor.py +502 -0
- package/token_optimization/output_styles.md +80 -0
- package/tools/__pycache__/domain_utils.cpython-314.pyc +0 -0
- package/tools/__pycache__/generate_claude_md.cpython-314.pyc +0 -0
- package/tools/__pycache__/validate_kit.cpython-314.pyc +0 -0
- package/tools/domain_utils.py +141 -0
- package/tools/generate_claude_md.py +269 -0
- package/tools/generate_platform_configs.py +467 -0
- package/tools/generate_prompt.py +461 -0
- package/tools/init_project.py +454 -0
- package/tools/test_kit.py +363 -0
- package/tools/validate_kit.py +504 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# Token Policies
|
|
2
|
+
# Controls input compression and output verbosity per workflow.
|
|
3
|
+
#
|
|
4
|
+
# Fields per workflow policy:
|
|
5
|
+
# max_input_files - max number of file snippets in context
|
|
6
|
+
# max_input_tokens - soft limit (tokens); compression triggered above this
|
|
7
|
+
# history_summarize_after_turns - turn count that triggers history summarization
|
|
8
|
+
# output_style - default output verbosity preset (see output_styles section)
|
|
9
|
+
# relevance_drop_threshold - relevance score [0.0–1.0] below which files are excluded
|
|
10
|
+
# snippet_max_lines - lines per file before truncation is applied
|
|
11
|
+
# snippet_head_lines - lines kept from start of a truncated file
|
|
12
|
+
# snippet_tail_lines - lines kept from end of a truncated file
|
|
13
|
+
|
|
14
|
+
defaults: &defaults
|
|
15
|
+
max_input_files: 8
|
|
16
|
+
max_input_tokens: 24000
|
|
17
|
+
history_summarize_after_turns: 6
|
|
18
|
+
output_style: standard
|
|
19
|
+
relevance_drop_threshold: 0.35
|
|
20
|
+
snippet_max_lines: 200
|
|
21
|
+
snippet_head_lines: 40
|
|
22
|
+
snippet_tail_lines: 40
|
|
23
|
+
|
|
24
|
+
workflows:
|
|
25
|
+
|
|
26
|
+
code_review:
|
|
27
|
+
<<: *defaults
|
|
28
|
+
max_input_files: 10
|
|
29
|
+
max_input_tokens: 20000
|
|
30
|
+
output_style: review_checklist
|
|
31
|
+
relevance_drop_threshold: 0.40
|
|
32
|
+
|
|
33
|
+
feature_implementation:
|
|
34
|
+
<<: *defaults
|
|
35
|
+
max_input_files: 6
|
|
36
|
+
max_input_tokens: 18000
|
|
37
|
+
history_summarize_after_turns: 5
|
|
38
|
+
output_style: detailed_with_code
|
|
39
|
+
|
|
40
|
+
feature_design:
|
|
41
|
+
<<: *defaults
|
|
42
|
+
max_input_files: 5
|
|
43
|
+
max_input_tokens: 12000
|
|
44
|
+
output_style: design_brief
|
|
45
|
+
|
|
46
|
+
ui_improvement:
|
|
47
|
+
<<: *defaults
|
|
48
|
+
max_input_files: 4
|
|
49
|
+
max_input_tokens: 10000
|
|
50
|
+
output_style: design_brief
|
|
51
|
+
snippet_max_lines: 150
|
|
52
|
+
snippet_head_lines: 60 # keep more of component props + structure
|
|
53
|
+
snippet_tail_lines: 30
|
|
54
|
+
|
|
55
|
+
ux_critique:
|
|
56
|
+
<<: *defaults
|
|
57
|
+
max_input_files: 3
|
|
58
|
+
max_input_tokens: 8000
|
|
59
|
+
output_style: concise_bullets
|
|
60
|
+
|
|
61
|
+
refactoring:
|
|
62
|
+
<<: *defaults
|
|
63
|
+
max_input_files: 6
|
|
64
|
+
max_input_tokens: 16000
|
|
65
|
+
output_style: detailed_with_code
|
|
66
|
+
history_summarize_after_turns: 4
|
|
67
|
+
|
|
68
|
+
test_generation:
|
|
69
|
+
<<: *defaults
|
|
70
|
+
max_input_files: 5
|
|
71
|
+
max_input_tokens: 14000
|
|
72
|
+
output_style: detailed_with_code
|
|
73
|
+
|
|
74
|
+
generic_project_lifecycle:
|
|
75
|
+
<<: *defaults
|
|
76
|
+
max_input_files: 6
|
|
77
|
+
max_input_tokens: 16000
|
|
78
|
+
history_summarize_after_turns: 5
|
|
79
|
+
output_style: standard
|
|
80
|
+
run_compression_after_phase: true
|
|
81
|
+
|
|
82
|
+
phases:
|
|
83
|
+
|
|
84
|
+
task_framing:
|
|
85
|
+
max_input_files: 0
|
|
86
|
+
max_input_tokens: 4000
|
|
87
|
+
history_summarize_after_turns: 3
|
|
88
|
+
output_style: qa_brief
|
|
89
|
+
run_compression_after_phase: true
|
|
90
|
+
|
|
91
|
+
requirements:
|
|
92
|
+
max_input_files: 5
|
|
93
|
+
max_input_tokens: 14000
|
|
94
|
+
history_summarize_after_turns: 4
|
|
95
|
+
output_style: requirements_spec
|
|
96
|
+
run_compression_after_phase: true
|
|
97
|
+
|
|
98
|
+
solution_design:
|
|
99
|
+
max_input_files: 5
|
|
100
|
+
max_input_tokens: 12000
|
|
101
|
+
history_summarize_after_turns: 4
|
|
102
|
+
output_style: solution_design
|
|
103
|
+
run_compression_after_phase: true
|
|
104
|
+
|
|
105
|
+
implementation:
|
|
106
|
+
max_input_files: 6
|
|
107
|
+
max_input_tokens: 18000
|
|
108
|
+
history_summarize_after_turns: 5
|
|
109
|
+
output_style: implementation_slice
|
|
110
|
+
run_compression_after_phase: false
|
|
111
|
+
|
|
112
|
+
review_refinement:
|
|
113
|
+
max_input_files: 8
|
|
114
|
+
max_input_tokens: 20000
|
|
115
|
+
history_summarize_after_turns: 4
|
|
116
|
+
output_style: critique_summary
|
|
117
|
+
run_compression_after_phase: true
|
|
118
|
+
|
|
119
|
+
handoff:
|
|
120
|
+
max_input_files: 3
|
|
121
|
+
max_input_tokens: 8000
|
|
122
|
+
history_summarize_after_turns: 3
|
|
123
|
+
output_style: handoff_package
|
|
124
|
+
run_compression_after_phase: true
|
|
125
|
+
|
|
126
|
+
settings:
|
|
127
|
+
project_state_snapshot_after_phase: true
|
|
128
|
+
force_state_block_after_turns: 20
|
|
129
|
+
cross_session_resume_on_project_state: true
|
|
130
|
+
|
|
131
|
+
domains:
|
|
132
|
+
|
|
133
|
+
software:
|
|
134
|
+
implementation:
|
|
135
|
+
max_input_files: 8
|
|
136
|
+
max_input_tokens: 22000
|
|
137
|
+
review_refinement:
|
|
138
|
+
max_input_files: 10
|
|
139
|
+
output_style: critique_summary
|
|
140
|
+
|
|
141
|
+
content:
|
|
142
|
+
solution_design:
|
|
143
|
+
max_input_tokens: 8000
|
|
144
|
+
output_style: solution_design
|
|
145
|
+
implementation:
|
|
146
|
+
max_input_files: 3
|
|
147
|
+
max_input_tokens: 10000
|
|
148
|
+
|
|
149
|
+
research:
|
|
150
|
+
implementation:
|
|
151
|
+
max_input_files: 4
|
|
152
|
+
max_input_tokens: 12000
|
|
153
|
+
review_refinement:
|
|
154
|
+
max_input_tokens: 16000
|
|
155
|
+
|
|
156
|
+
data_analytics:
|
|
157
|
+
solution_design:
|
|
158
|
+
max_input_files: 6
|
|
159
|
+
implementation:
|
|
160
|
+
max_input_files: 8
|
|
161
|
+
|
|
162
|
+
product_design:
|
|
163
|
+
solution_design:
|
|
164
|
+
max_input_files: 4
|
|
165
|
+
max_input_tokens: 10000
|
|
166
|
+
output_style: solution_design
|
|
167
|
+
implementation:
|
|
168
|
+
max_input_files: 3
|
|
169
|
+
max_input_tokens: 10000
|
|
170
|
+
review_refinement:
|
|
171
|
+
max_input_files: 5
|
|
172
|
+
max_input_tokens: 14000
|
|
173
|
+
|
|
174
|
+
marketing:
|
|
175
|
+
solution_design:
|
|
176
|
+
max_input_tokens: 8000
|
|
177
|
+
implementation:
|
|
178
|
+
max_input_files: 3
|
|
179
|
+
max_input_tokens: 10000
|
|
180
|
+
|
|
181
|
+
ops_process:
|
|
182
|
+
solution_design:
|
|
183
|
+
max_input_files: 3
|
|
184
|
+
max_input_tokens: 8000
|
|
185
|
+
implementation:
|
|
186
|
+
max_input_files: 3
|
|
187
|
+
max_input_tokens: 10000
|
|
188
|
+
review_refinement:
|
|
189
|
+
max_input_files: 4
|
|
190
|
+
max_input_tokens: 12000
|
|
191
|
+
|
|
192
|
+
general:
|
|
193
|
+
task_framing:
|
|
194
|
+
max_input_tokens: 8000
|
|
195
|
+
history_summarize_after_turns: 10
|
|
196
|
+
requirements:
|
|
197
|
+
max_input_files: 4
|
|
198
|
+
max_input_tokens: 12000
|
|
199
|
+
solution_design:
|
|
200
|
+
max_input_files: 4
|
|
201
|
+
max_input_tokens: 12000
|
|
202
|
+
implementation:
|
|
203
|
+
max_input_files: 5
|
|
204
|
+
max_input_tokens: 20000
|
|
205
|
+
history_summarize_after_turns: 8
|
|
206
|
+
review_refinement:
|
|
207
|
+
max_input_files: 6
|
|
208
|
+
max_input_tokens: 18000
|
|
209
|
+
|
|
210
|
+
# Output verbosity styles
|
|
211
|
+
# These map to the full definitions in token_optimization/output_styles.md.
|
|
212
|
+
# Keys here must match style keys referenced in workflow policies and agent defaults.
|
|
213
|
+
|
|
214
|
+
output_styles:
|
|
215
|
+
|
|
216
|
+
concise_bullets:
|
|
217
|
+
description: Short bullet lists, no prose paragraphs. Max 300 words per response.
|
|
218
|
+
max_response_tokens: 600
|
|
219
|
+
format_hints:
|
|
220
|
+
- Use bullet lists for all findings.
|
|
221
|
+
- One sentence per bullet.
|
|
222
|
+
- No introductory or closing paragraphs.
|
|
223
|
+
- Omit explanations unless the reason is non-obvious.
|
|
224
|
+
|
|
225
|
+
standard:
|
|
226
|
+
description: Mix of prose and bullets. Balanced detail. Max 600 words per response.
|
|
227
|
+
max_response_tokens: 1200
|
|
228
|
+
format_hints:
|
|
229
|
+
- Use prose for context, bullets for lists of items.
|
|
230
|
+
- Include brief rationale for recommendations.
|
|
231
|
+
- One code snippet maximum unless more are essential.
|
|
232
|
+
|
|
233
|
+
detailed_with_code:
|
|
234
|
+
description: >
|
|
235
|
+
Full explanations with inline code patches or complete file sections.
|
|
236
|
+
Used for implementation tasks.
|
|
237
|
+
max_response_tokens: 3000
|
|
238
|
+
format_hints:
|
|
239
|
+
- Include full code blocks with language tags.
|
|
240
|
+
- Explain non-obvious decisions in 1–2 sentences.
|
|
241
|
+
- Use diff format (+ / -) when showing changes to existing code.
|
|
242
|
+
- Add a "What changed and why" summary at the end.
|
|
243
|
+
|
|
244
|
+
design_brief:
|
|
245
|
+
description: >
|
|
246
|
+
Structured design output: tables, bullet lists, short prose.
|
|
247
|
+
No inline code unless an interface or schema is being defined.
|
|
248
|
+
max_response_tokens: 1500
|
|
249
|
+
format_hints:
|
|
250
|
+
- Use tables for comparisons and API contracts.
|
|
251
|
+
- Use numbered lists for sequences and steps.
|
|
252
|
+
- Use bullet lists for options and trade-offs.
|
|
253
|
+
- Keep each section under 150 words.
|
|
254
|
+
|
|
255
|
+
review_checklist:
|
|
256
|
+
description: >
|
|
257
|
+
Tabular review output. Each finding has severity, location, and
|
|
258
|
+
recommendation. No prose paragraphs.
|
|
259
|
+
max_response_tokens: 1200
|
|
260
|
+
format_hints:
|
|
261
|
+
- Output a markdown table with columns: Severity | File:Line | Issue | Recommendation.
|
|
262
|
+
- Severity values: critical | high | medium | low | info.
|
|
263
|
+
- One row per finding.
|
|
264
|
+
- Add a one-line summary count at the top (e.g., "3 high, 2 medium, 1 low").
|
|
265
|
+
|
|
266
|
+
# --- Generic Project Lifecycle styles ---
|
|
267
|
+
|
|
268
|
+
qa_brief:
|
|
269
|
+
description: Numbered question list for task framing. Max 5 questions per turn, no prose.
|
|
270
|
+
max_response_tokens: 500
|
|
271
|
+
format_hints:
|
|
272
|
+
- Numbered list of questions only, no preamble.
|
|
273
|
+
- Each question is one sentence, max 20 words.
|
|
274
|
+
- After user answers, emit task_profile block and nothing else.
|
|
275
|
+
|
|
276
|
+
requirements_spec:
|
|
277
|
+
description: Structured tables and bullets covering goals, non-goals, deliverables, constraints, assumptions.
|
|
278
|
+
max_response_tokens: 1200
|
|
279
|
+
format_hints:
|
|
280
|
+
- Required sections: Goals, Non-Goals, Target Audience, Deliverables, Constraints, Assumptions.
|
|
281
|
+
- "Tag each constraint with its type: [technical], [time], [compliance], [resource], [tone]."
|
|
282
|
+
- No prose paragraphs longer than 2 sentences.
|
|
283
|
+
|
|
284
|
+
solution_design:
|
|
285
|
+
description: Structured design artifact with Context, Approach, Structure, Risks sections.
|
|
286
|
+
max_response_tokens: 2000
|
|
287
|
+
format_hints:
|
|
288
|
+
- Required sections: Context, Approach, Structure, Risks & Open Questions.
|
|
289
|
+
- Structure section varies by domain (see docs/domains.md).
|
|
290
|
+
- Tables for comparisons; numbered lists for sequences.
|
|
291
|
+
- No implementation code; schemas and interfaces acceptable.
|
|
292
|
+
- End with numbered Open Questions list.
|
|
293
|
+
|
|
294
|
+
implementation_slice:
|
|
295
|
+
description: Increment plan + work content + approval prompt. One increment at a time.
|
|
296
|
+
max_response_tokens: 2500
|
|
297
|
+
format_hints:
|
|
298
|
+
- Start with 3-line Increment Plan (this slice, depends on, next slice).
|
|
299
|
+
- Software uses diff format; content uses section heading + body + transition note.
|
|
300
|
+
- End with "Approve this increment / request changes / change direction?"
|
|
301
|
+
|
|
302
|
+
critique_summary:
|
|
303
|
+
description: Verdict + severity-rated findings table + positive findings + action prompt.
|
|
304
|
+
max_response_tokens: 1500
|
|
305
|
+
format_hints:
|
|
306
|
+
- Open with one-line verdict (ready_to_ship | minor_revisions | significant_revisions).
|
|
307
|
+
- Table columns: Severity | Area | Issue | Recommendation.
|
|
308
|
+
- Severity: critical, high, medium, low, info.
|
|
309
|
+
- Minimum 2 positive findings.
|
|
310
|
+
- End with "Apply all fixes / apply selected fixes / discuss?"
|
|
311
|
+
|
|
312
|
+
handoff_package:
|
|
313
|
+
description: Summary bullets + how-to steps + prioritized next steps. No prose paragraphs.
|
|
314
|
+
max_response_tokens: 1200
|
|
315
|
+
format_hints:
|
|
316
|
+
- Required sections: Summary (3-5 bullets), How to Use/Run/Deploy, What's Done, What's Next.
|
|
317
|
+
- What's Next must use P1/P2/P3 priority tiers.
|
|
318
|
+
- Domain-specific additions (env vars for software, key findings table for research, etc.).
|
|
319
|
+
|
|
320
|
+
# Compression triggers
|
|
321
|
+
# The Compression Agent is invoked automatically when:
|
|
322
|
+
compression_triggers:
|
|
323
|
+
context_token_ratio: 0.75 # context exceeds 75% of model's max input tokens
|
|
324
|
+
history_turns_exceeded: true # turns exceed history_summarize_after_turns for the workflow
|
|
325
|
+
explicit_user_request: true # user says "summarize", "compress", "reduce context"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
context_loaders
|
|
3
|
+
|
|
4
|
+
Utilities for generating compact project context for agent sessions.
|
|
5
|
+
|
|
6
|
+
Scripts:
|
|
7
|
+
repo_tree.py - Walk repo and emit a filtered file tree with descriptions.
|
|
8
|
+
file_chunker.py - Extract and truncate key files for agent context.
|
|
9
|
+
project_summary.py - Produce a compact stack/services/entrypoints summary.
|
|
10
|
+
|
|
11
|
+
Typical session setup:
|
|
12
|
+
python context_loaders/project_summary.py --path /your/repo
|
|
13
|
+
python context_loaders/repo_tree.py --path /your/repo --filter src/ app/
|
|
14
|
+
(paste both outputs as the opening message in your agent session)
|
|
15
|
+
"""
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""
|
|
2
|
+
context_loaders/file_chunker.py
|
|
3
|
+
|
|
4
|
+
Extract, read, and optionally truncate specific files for inclusion in an
|
|
5
|
+
agent context block. Designed to be used after repo_tree.py identifies
|
|
6
|
+
the relevant files.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
# Read specific files
|
|
10
|
+
python context_loaders/file_chunker.py \\
|
|
11
|
+
--path /your/repo \\
|
|
12
|
+
--files services/user_service.py models/user.py tests/test_user_service.py
|
|
13
|
+
|
|
14
|
+
# Read all files under a directory, truncated
|
|
15
|
+
python context_loaders/file_chunker.py \\
|
|
16
|
+
--path /your/repo \\
|
|
17
|
+
--dirs services/ models/ \\
|
|
18
|
+
--max-lines 200
|
|
19
|
+
|
|
20
|
+
# Output to a file
|
|
21
|
+
python context_loaders/file_chunker.py \\
|
|
22
|
+
--path /your/repo \\
|
|
23
|
+
--files services/user_service.py \\
|
|
24
|
+
--output chunks.txt
|
|
25
|
+
|
|
26
|
+
Output format (paste directly into agent context):
|
|
27
|
+
### services/user_service.py
|
|
28
|
+
```python
|
|
29
|
+
<file content or truncated snippet>
|
|
30
|
+
```
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
__version__ = "1.0.0"
|
|
36
|
+
|
|
37
|
+
import argparse
|
|
38
|
+
import sys
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
|
|
41
|
+
# Default truncation settings (matches token_optimization defaults)
|
|
42
|
+
DEFAULT_MAX_LINES = 200
|
|
43
|
+
DEFAULT_HEAD_LINES = 40
|
|
44
|
+
DEFAULT_TAIL_LINES = 40
|
|
45
|
+
|
|
46
|
+
# Extensions that have a known language tag for fenced code blocks
|
|
47
|
+
LANG_MAP: dict[str, str] = {
|
|
48
|
+
".py": "python",
|
|
49
|
+
".ts": "typescript",
|
|
50
|
+
".tsx": "tsx",
|
|
51
|
+
".js": "javascript",
|
|
52
|
+
".jsx": "jsx",
|
|
53
|
+
".go": "go",
|
|
54
|
+
".java": "java",
|
|
55
|
+
".rb": "ruby",
|
|
56
|
+
".rs": "rust",
|
|
57
|
+
".cs": "csharp",
|
|
58
|
+
".cpp": "cpp",
|
|
59
|
+
".c": "c",
|
|
60
|
+
".sql": "sql",
|
|
61
|
+
".yaml": "yaml",
|
|
62
|
+
".yml": "yaml",
|
|
63
|
+
".toml": "toml",
|
|
64
|
+
".json": "json",
|
|
65
|
+
".md": "markdown",
|
|
66
|
+
".sh": "bash",
|
|
67
|
+
".bash": "bash",
|
|
68
|
+
".graphql": "graphql",
|
|
69
|
+
".gql": "graphql",
|
|
70
|
+
".proto": "protobuf",
|
|
71
|
+
".html": "html",
|
|
72
|
+
".css": "css",
|
|
73
|
+
".scss": "scss",
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _lang_tag(path: Path) -> str:
|
|
78
|
+
return LANG_MAP.get(path.suffix.lower(), "text")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def truncate_content(content: str, max_lines: int, head_lines: int, tail_lines: int) -> tuple[str, int]:
|
|
82
|
+
"""
|
|
83
|
+
Truncate content to head + tail with a gap marker.
|
|
84
|
+
Returns (truncated_content, lines_omitted).
|
|
85
|
+
"""
|
|
86
|
+
lines = content.splitlines()
|
|
87
|
+
if len(lines) <= max_lines:
|
|
88
|
+
return content, 0
|
|
89
|
+
|
|
90
|
+
head = lines[:head_lines]
|
|
91
|
+
tail = lines[-tail_lines:]
|
|
92
|
+
omitted = len(lines) - head_lines - tail_lines
|
|
93
|
+
gap = f"# ... [{omitted} lines omitted — request full file if needed] ..."
|
|
94
|
+
return "\n".join(head + [gap] + tail), omitted
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def read_file(
|
|
98
|
+
file_path: Path,
|
|
99
|
+
max_lines: int = DEFAULT_MAX_LINES,
|
|
100
|
+
head_lines: int = DEFAULT_HEAD_LINES,
|
|
101
|
+
tail_lines: int = DEFAULT_TAIL_LINES,
|
|
102
|
+
no_truncate: bool = False,
|
|
103
|
+
) -> dict:
|
|
104
|
+
"""
|
|
105
|
+
Read a single file and return a dict with path, content, lang, and truncation info.
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
content = file_path.read_text(encoding="utf-8", errors="replace")
|
|
109
|
+
except (OSError, PermissionError) as e:
|
|
110
|
+
return {
|
|
111
|
+
"path": str(file_path),
|
|
112
|
+
"content": f"# Error reading file: {e}",
|
|
113
|
+
"lang": "text",
|
|
114
|
+
"truncated": False,
|
|
115
|
+
"lines_omitted": 0,
|
|
116
|
+
"total_lines": 0,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
total_lines = content.count("\n") + 1
|
|
120
|
+
|
|
121
|
+
if no_truncate:
|
|
122
|
+
truncated_content, omitted = content, 0
|
|
123
|
+
else:
|
|
124
|
+
truncated_content, omitted = truncate_content(content, max_lines, head_lines, tail_lines)
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
"path": str(file_path),
|
|
128
|
+
"content": truncated_content,
|
|
129
|
+
"lang": _lang_tag(file_path),
|
|
130
|
+
"truncated": omitted > 0,
|
|
131
|
+
"lines_omitted": omitted,
|
|
132
|
+
"total_lines": total_lines,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def format_chunk(chunk: dict, root: Path | None = None) -> str:
|
|
137
|
+
"""Format a single file chunk as a fenced code block for agent context."""
|
|
138
|
+
display_path = chunk["path"]
|
|
139
|
+
if root:
|
|
140
|
+
try:
|
|
141
|
+
display_path = str(Path(chunk["path"]).relative_to(root))
|
|
142
|
+
except ValueError:
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
header = f"### {display_path}"
|
|
146
|
+
if chunk["truncated"]:
|
|
147
|
+
header += f" _(truncated: {chunk['lines_omitted']} lines omitted of {chunk['total_lines']} total)_"
|
|
148
|
+
|
|
149
|
+
return f"{header}\n```{chunk['lang']}\n{chunk['content']}\n```"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def collect_from_dirs(
|
|
153
|
+
root: Path,
|
|
154
|
+
dirs: list[str],
|
|
155
|
+
extensions: set[str] | None = None,
|
|
156
|
+
max_files: int = 20,
|
|
157
|
+
) -> list[Path]:
|
|
158
|
+
"""Collect file paths from specified subdirectories."""
|
|
159
|
+
if extensions is None:
|
|
160
|
+
extensions = set(LANG_MAP.keys())
|
|
161
|
+
|
|
162
|
+
paths: list[Path] = []
|
|
163
|
+
for d in dirs:
|
|
164
|
+
target = root / d.lstrip("/")
|
|
165
|
+
if not target.is_dir():
|
|
166
|
+
print(f"Warning: {target} is not a directory — skipping.", file=sys.stderr)
|
|
167
|
+
continue
|
|
168
|
+
for p in sorted(target.rglob("*")):
|
|
169
|
+
if p.is_file() and p.suffix in extensions:
|
|
170
|
+
paths.append(p)
|
|
171
|
+
if len(paths) >= max_files:
|
|
172
|
+
break
|
|
173
|
+
return paths[:max_files]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def main() -> None:
|
|
177
|
+
parser = argparse.ArgumentParser(
|
|
178
|
+
description="Extract and format file chunks for agent context."
|
|
179
|
+
)
|
|
180
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
181
|
+
parser.add_argument("--path", required=True, help="Repository root directory.")
|
|
182
|
+
parser.add_argument(
|
|
183
|
+
"--files", nargs="*", metavar="FILE",
|
|
184
|
+
help="Relative paths to specific files (from --path root)."
|
|
185
|
+
)
|
|
186
|
+
parser.add_argument(
|
|
187
|
+
"--dirs", nargs="*", metavar="DIR",
|
|
188
|
+
help="Subdirectories to collect all key files from."
|
|
189
|
+
)
|
|
190
|
+
parser.add_argument("--max-lines", type=int, default=DEFAULT_MAX_LINES,
|
|
191
|
+
help=f"Max lines before truncation (default: {DEFAULT_MAX_LINES}).")
|
|
192
|
+
parser.add_argument("--head-lines", type=int, default=DEFAULT_HEAD_LINES,
|
|
193
|
+
help=f"Lines kept from start on truncation (default: {DEFAULT_HEAD_LINES}).")
|
|
194
|
+
parser.add_argument("--tail-lines", type=int, default=DEFAULT_TAIL_LINES,
|
|
195
|
+
help=f"Lines kept from end on truncation (default: {DEFAULT_TAIL_LINES}).")
|
|
196
|
+
parser.add_argument("--no-truncate", action="store_true",
|
|
197
|
+
help="Disable truncation (caution: may produce very large output).")
|
|
198
|
+
parser.add_argument("--max-files", type=int, default=20,
|
|
199
|
+
help="Max files when using --dirs (default: 20).")
|
|
200
|
+
parser.add_argument("--output", help="Write output to this file instead of stdout.")
|
|
201
|
+
args = parser.parse_args()
|
|
202
|
+
|
|
203
|
+
root = Path(args.path).resolve()
|
|
204
|
+
if not root.is_dir():
|
|
205
|
+
print(f"Error: {root} is not a directory.", file=sys.stderr)
|
|
206
|
+
sys.exit(1)
|
|
207
|
+
|
|
208
|
+
file_paths: list[Path] = []
|
|
209
|
+
|
|
210
|
+
if args.files:
|
|
211
|
+
root_resolved = root.resolve()
|
|
212
|
+
for f in args.files:
|
|
213
|
+
p = (root / f).resolve()
|
|
214
|
+
if not str(p).startswith(str(root_resolved)):
|
|
215
|
+
print(f"[WARN] Skipping {f} — path escapes project root.", file=sys.stderr)
|
|
216
|
+
continue
|
|
217
|
+
if p.is_file():
|
|
218
|
+
file_paths.append(p)
|
|
219
|
+
else:
|
|
220
|
+
print(f"[WARN] {p} not found — skipping.", file=sys.stderr)
|
|
221
|
+
|
|
222
|
+
if args.dirs:
|
|
223
|
+
file_paths.extend(collect_from_dirs(root, args.dirs, max_files=args.max_files))
|
|
224
|
+
|
|
225
|
+
if not file_paths:
|
|
226
|
+
print("No files found. Provide --files or --dirs.", file=sys.stderr)
|
|
227
|
+
sys.exit(1)
|
|
228
|
+
|
|
229
|
+
chunks = [
|
|
230
|
+
read_file(
|
|
231
|
+
p,
|
|
232
|
+
max_lines=args.max_lines,
|
|
233
|
+
head_lines=args.head_lines,
|
|
234
|
+
tail_lines=args.tail_lines,
|
|
235
|
+
no_truncate=args.no_truncate,
|
|
236
|
+
)
|
|
237
|
+
for p in file_paths
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
header = f"## Relevant Files ({len(chunks)} files)\n"
|
|
241
|
+
body = "\n\n".join(format_chunk(c, root=root) for c in chunks)
|
|
242
|
+
output = header + "\n" + body + "\n"
|
|
243
|
+
|
|
244
|
+
if args.output:
|
|
245
|
+
Path(args.output).write_text(output, encoding="utf-8")
|
|
246
|
+
print(f"Written to {args.output}")
|
|
247
|
+
else:
|
|
248
|
+
sys.stdout.buffer.write(output.encode("utf-8"))
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
if __name__ == "__main__":
|
|
252
|
+
main()
|