@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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +659 -0
  3. package/agents/architect_agent.md +175 -0
  4. package/agents/code_agent.md +178 -0
  5. package/agents/compression_agent.md +226 -0
  6. package/agents/execution_agent.md +157 -0
  7. package/agents/orchestrator.md +406 -0
  8. package/agents/reviewer_agent.md +134 -0
  9. package/agents/ui_agent.md +147 -0
  10. package/agents/ux_agent.md +144 -0
  11. package/bin/cli.js +79 -0
  12. package/config/agents.yaml +388 -0
  13. package/config/domain_profiles.yaml +394 -0
  14. package/config/project.yaml.example +16 -0
  15. package/config/token_policies.yaml +325 -0
  16. package/context_loaders/__init__.py +15 -0
  17. package/context_loaders/__pycache__/__init__.cpython-314.pyc +0 -0
  18. package/context_loaders/__pycache__/file_chunker.cpython-314.pyc +0 -0
  19. package/context_loaders/__pycache__/project_summary.cpython-314.pyc +0 -0
  20. package/context_loaders/__pycache__/repo_tree.cpython-314.pyc +0 -0
  21. package/context_loaders/file_chunker.py +252 -0
  22. package/context_loaders/project_summary.py +369 -0
  23. package/context_loaders/repo_tree.py +203 -0
  24. package/package.json +46 -0
  25. package/quickstart.ps1 +252 -0
  26. package/quickstart.sh +232 -0
  27. package/requirements.txt +3 -0
  28. package/skills/analyze_repo.md +86 -0
  29. package/skills/animated_website.md +285 -0
  30. package/skills/compare_approaches.md +86 -0
  31. package/skills/define_data_schema.md +99 -0
  32. package/skills/design_api.md +126 -0
  33. package/skills/improve_copy.md +69 -0
  34. package/skills/review_code.md +83 -0
  35. package/skills/review_layout.md +89 -0
  36. package/skills/suggest_next.md +105 -0
  37. package/skills/summarize_history.md +89 -0
  38. package/skills/write_process_map.md +105 -0
  39. package/skills/write_tests.md +97 -0
  40. package/token_optimization/__pycache__/compressor.cpython-314.pyc +0 -0
  41. package/token_optimization/compressor.py +502 -0
  42. package/token_optimization/output_styles.md +80 -0
  43. package/tools/__pycache__/domain_utils.cpython-314.pyc +0 -0
  44. package/tools/__pycache__/generate_claude_md.cpython-314.pyc +0 -0
  45. package/tools/__pycache__/validate_kit.cpython-314.pyc +0 -0
  46. package/tools/domain_utils.py +141 -0
  47. package/tools/generate_claude_md.py +269 -0
  48. package/tools/generate_platform_configs.py +467 -0
  49. package/tools/generate_prompt.py +461 -0
  50. package/tools/init_project.py +454 -0
  51. package/tools/test_kit.py +363 -0
  52. package/tools/validate_kit.py +504 -0
@@ -0,0 +1,363 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ test_kit.py — Comprehensive edge-case test suite for agents-maker.
4
+
5
+ Run from the repo root:
6
+ python tools/test_kit.py
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import subprocess
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ sys.stdout.reconfigure(encoding="utf-8")
16
+ sys.stderr.reconfigure(encoding="utf-8")
17
+ ROOT = Path(__file__).resolve().parent.parent
18
+ PY = sys.executable
19
+
20
+ PASS_COUNT = 0
21
+ FAIL_COUNT = 0
22
+ RESULTS = []
23
+
24
+
25
+ def run(cmd: list[str], cwd: Path = ROOT, input_text: str | None = None) -> tuple[int, str, str]:
26
+ result = subprocess.run(
27
+ cmd, cwd=str(cwd), capture_output=True, text=True,
28
+ input=input_text, encoding="utf-8", errors="replace"
29
+ )
30
+ return result.returncode, result.stdout, result.stderr
31
+
32
+
33
+ def check(n: int, desc: str, passed: bool, reason: str = "") -> None:
34
+ global PASS_COUNT, FAIL_COUNT
35
+ if passed:
36
+ PASS_COUNT += 1
37
+ tag = f"PASS [{n:02d}]"
38
+ else:
39
+ FAIL_COUNT += 1
40
+ tag = f"FAIL [{n:02d}]"
41
+ msg = f"{tag} {desc}"
42
+ if not passed and reason:
43
+ msg += f"\n Reason: {reason}"
44
+ print(msg)
45
+ RESULTS.append((n, passed, desc, reason))
46
+
47
+
48
+ def section(title: str) -> None:
49
+ print(f"\n{'-' * 60}")
50
+ print(f" {title}")
51
+ print('-' * 60)
52
+
53
+
54
+ # -------------------------------------------------------------
55
+ # A. generate_prompt.py — normal cases
56
+ # -------------------------------------------------------------
57
+ section("A. generate_prompt.py — normal cases")
58
+
59
+ rc, out, err = run([PY, "tools/generate_prompt.py", "add rate limiting to the auth service"])
60
+ check(1, "Basic run exits 0", rc == 0, err[:200] if rc != 0 else "")
61
+ check(2, "Output contains ## Project Context", "## Project Context" in out, "")
62
+ check(3, "Software domain auto-detected", "software" in out.lower(), out[:300])
63
+
64
+ rc, out, err = run([PY, "tools/generate_prompt.py", "add rate limiting", "--phase", "implementation"])
65
+ check(4, "--phase implementation -> output contains 'implementation'", "implementation" in out, "")
66
+
67
+ rc, out, err = run([PY, "tools/generate_prompt.py", "add rate limiting", "--phase", "review"])
68
+ check(5, "--phase review -> output contains 'review'", "review" in out, "")
69
+
70
+ rc, out, err = run([PY, "tools/generate_prompt.py", "add rate limiting", "--full"])
71
+ check(6, "--full flag -> output contains orchestrator content", "Orchestrator" in out or "orchestrator" in out, "")
72
+
73
+ rc, out, err = run([PY, "tools/generate_prompt.py", "add rate limiting", "--compress"])
74
+ check(7, "--compress flag -> output contains ## Output Policy", "## Output Policy" in out, out[-300:])
75
+
76
+ rc, out, err = run([PY, "tools/generate_prompt.py", "[domain: content] write a blog post about GraphQL"])
77
+ check(8, "[domain: content] prefix -> domain is content", "content" in out.lower(), "")
78
+
79
+ rc, out, err = run([PY, "tools/generate_prompt.py", "[domain: ops_process] write a runbook for failover"])
80
+ check(9, "[domain: ops_process] prefix -> execution_agent or ops_process in output", "ops_process" in out.lower() or "execution_agent" in out.lower(), out[:300])
81
+
82
+ # -------------------------------------------------------------
83
+ # B. generate_prompt.py — edge cases / error handling
84
+ # -------------------------------------------------------------
85
+ section("B. generate_prompt.py — edge cases / error handling")
86
+
87
+ rc, out, err = run([PY, "tools/generate_prompt.py", ""])
88
+ check(10, "Empty problem -> non-zero exit", rc != 0, f"exit={rc}")
89
+ check(11, "Empty problem -> stderr contains 'empty'", "empty" in err.lower(), err[:200])
90
+
91
+ long_input = "x" * 5001
92
+ rc, out, err = run([PY, "tools/generate_prompt.py", long_input])
93
+ check(12, "Problem > 5000 chars -> non-zero exit", rc != 0, f"exit={rc}")
94
+ check(13, "Problem > 5000 chars -> stderr contains 'too long'", "too long" in err.lower(), err[:200])
95
+
96
+ rc, out, err = run([PY, "tools/generate_prompt.py", "task", "--phase", "invalid_phase"])
97
+ check(14, "Invalid --phase value -> non-zero exit", rc != 0, f"exit={rc}")
98
+
99
+ valid_phases = ["task_framing", "requirements", "design", "solution_design",
100
+ "implementation", "implement", "review", "review_refinement", "handoff", "framing"]
101
+ all_phases_ok = True
102
+ failed_phase = ""
103
+ for ph in valid_phases:
104
+ rc2, _, err2 = run([PY, "tools/generate_prompt.py", "some task", "--phase", ph])
105
+ if rc2 != 0:
106
+ all_phases_ok = False
107
+ failed_phase = f"phase={ph} exit={rc2} err={err2[:100]}"
108
+ break
109
+ check(15, "All valid --phase values exit 0", all_phases_ok, failed_phase)
110
+
111
+ # -------------------------------------------------------------
112
+ # C. Domain detection — all 8 domains
113
+ # -------------------------------------------------------------
114
+ section("C. Domain detection — all 8 domains")
115
+
116
+ domain_tests = [
117
+ (16, "software", "Refactor the UserRepository class and add unit tests for the API endpoints"),
118
+ (17, "content", "Write a technical blog post about our REST to GraphQL migration for developers"),
119
+ (18, "research", "Write a literature review on transformer model architectures for NLP"),
120
+ (19, "data_analytics", "Build a sales funnel dashboard with conversion metrics and pipeline analytics"),
121
+ (20, "product_design", "Design a mobile onboarding flow for our e-commerce app with persona mapping"),
122
+ (21, "marketing", "Create a go-to-market strategy and campaign brief for our SaaS product launch"),
123
+ (22, "ops_process", "Write a runbook SOP for our database failover and disaster recovery procedure"),
124
+ (23, "general", "I need help with something"),
125
+ ]
126
+
127
+ for n, expected_domain, task in domain_tests:
128
+ rc, out, err = run([PY, "tools/generate_prompt.py", task])
129
+ combined = out.lower() + err.lower()
130
+ if expected_domain == "general":
131
+ # Low-confidence tasks fall back to project domain when project.yaml has primary_domain set.
132
+ # Accept either "general" OR "from-project" (scoring fell below threshold, used project default).
133
+ found = "general" in combined or "from-project" in combined
134
+ else:
135
+ found = expected_domain in combined
136
+ check(n, f"Domain detection -> {expected_domain}", found, f"task='{task[:50]}...' output snippet: {out[:200]}")
137
+
138
+ # -------------------------------------------------------------
139
+ # D. validate_kit.py — all 12 checks pass
140
+ # -------------------------------------------------------------
141
+ section("D. validate_kit.py — all 12 checks pass normally")
142
+
143
+ rc, out, err = run([PY, "tools/validate_kit.py"])
144
+ check(24, "validate_kit.py exits 0", rc == 0, err[:200])
145
+ check(25, "ALL 12 checks PASSED in output", "ALL 12 checks PASSED" in out, out[-300:])
146
+
147
+ # -------------------------------------------------------------
148
+ # E. validate_kit.py — failure detection (temporarily corrupt files)
149
+ # -------------------------------------------------------------
150
+ section("E. validate_kit.py — failure detection")
151
+
152
+ orch_path = ROOT / "agents" / "orchestrator.md"
153
+ orch_backup = orch_path.read_text(encoding="utf-8")
154
+
155
+ try:
156
+ orch_path.rename(ROOT / "agents" / "orchestrator.md.bak")
157
+ rc, out, err = run([PY, "tools/validate_kit.py"])
158
+ check(26, "Missing agent file -> FAIL in output", "FAIL" in out, out[-300:])
159
+ finally:
160
+ bak = ROOT / "agents" / "orchestrator.md.bak"
161
+ if bak.exists():
162
+ bak.rename(orch_path)
163
+
164
+ skill_path = ROOT / "skills" / "analyze_repo.md"
165
+ skill_backup = skill_path.read_text(encoding="utf-8")
166
+ try:
167
+ skill_path.write_text("short", encoding="utf-8")
168
+ rc, out, err = run([PY, "tools/validate_kit.py"])
169
+ check(27, "Stub skill file (< 50 chars) -> FAIL in output", "FAIL" in out, out[-300:])
170
+ finally:
171
+ skill_path.write_text(skill_backup, encoding="utf-8")
172
+
173
+ try:
174
+ skill_path.write_text("# Skill: test\n\nNo input or output section here.\n\nToken budget: low\n", encoding="utf-8")
175
+ rc, out, err = run([PY, "tools/validate_kit.py"])
176
+ check(28, "Skill missing input/output headings -> FAIL in output", "FAIL" in out, out[-300:])
177
+ finally:
178
+ skill_path.write_text(skill_backup, encoding="utf-8")
179
+
180
+ agent_path = ROOT / "agents" / "orchestrator.md"
181
+ agent_backup = agent_path.read_text(encoding="utf-8")
182
+ try:
183
+ agent_path.write_text("# Orchestrator\n\nSome content without required sections.\n" * 5, encoding="utf-8")
184
+ rc, out, err = run([PY, "tools/validate_kit.py"])
185
+ check(29, "Agent missing ## Role/Goals/Context -> FAIL in output", "FAIL" in out, out[-300:])
186
+ finally:
187
+ agent_path.write_text(agent_backup, encoding="utf-8")
188
+
189
+ # Confirm restore worked
190
+ rc, out, err = run([PY, "tools/validate_kit.py"])
191
+ check(30, "All files restored -> validator passes again", "ALL 12 checks PASSED" in out, out[-200:])
192
+
193
+ # -------------------------------------------------------------
194
+ # F. Compressor / PolicyLoader — direct import tests
195
+ # -------------------------------------------------------------
196
+ section("F. Compressor / PolicyLoader — direct import tests")
197
+
198
+ sys.path.insert(0, str(ROOT))
199
+
200
+ try:
201
+ from token_optimization.compressor import (
202
+ CompressionReport,
203
+ Compressor,
204
+ ContextBlock,
205
+ PolicyLoader,
206
+ )
207
+
208
+ loader = PolicyLoader(ROOT / "config" / "token_policies.yaml")
209
+ loader.load()
210
+ check(31, "PolicyLoader imports and loads without error", True, "")
211
+
212
+ for wf in ["feature_implementation", "code_review", "feature_design"]:
213
+ policy = loader.get_workflow_policy(wf)
214
+ ok_wf = policy.max_input_tokens > 0 and bool(policy.output_style)
215
+ check({"feature_implementation": 32, "code_review": 33, "feature_design": 34}[wf],
216
+ f"get_workflow_policy('{wf}') returns valid policy",
217
+ ok_wf, f"max_input_tokens={policy.max_input_tokens} output_style={policy.output_style}")
218
+
219
+ unknown_policy = loader.get_workflow_policy("completely_unknown_workflow_xyz")
220
+ check(35, "Unknown workflow -> fallback policy, no crash",
221
+ unknown_policy is not None and unknown_policy.max_input_tokens >= 0, "")
222
+
223
+ policy = loader.get_workflow_policy("feature_implementation")
224
+ compressor = Compressor(policy)
225
+ empty_block = ContextBlock()
226
+ compressed, report = compressor.compress(empty_block)
227
+ check(36, "compress(empty ContextBlock) returns without crash",
228
+ isinstance(compressed, str) and isinstance(report, CompressionReport), "")
229
+
230
+ block_with_state = ContextBlock(
231
+ project_state="Name: test-project\nStack: Python, FastAPI\nDomain: software",
232
+ active_query="add rate limiting to the auth service",
233
+ conversation_state="User asked about rate limiting. We discussed Redis sliding window.",
234
+ )
235
+ compressed2, report2 = compressor.compress(block_with_state)
236
+ check(37, "compress(ContextBlock with content) returns non-empty string",
237
+ isinstance(compressed2, str) and len(compressed2) > 0, f"len={len(compressed2)}")
238
+
239
+ loader_bad = PolicyLoader("/nonexistent/path/token_policies.yaml")
240
+ try:
241
+ loader_bad.load()
242
+ check(38, "PolicyLoader with bad path -> graceful fallback, no exception", True, "")
243
+ except Exception as e:
244
+ check(38, "PolicyLoader with bad path -> graceful fallback, no exception", False, str(e))
245
+
246
+ except ImportError as e:
247
+ for n in range(31, 39):
248
+ check(n, "Compressor import test", False, str(e))
249
+
250
+ # -------------------------------------------------------------
251
+ # G. Context loaders — smoke tests
252
+ # -------------------------------------------------------------
253
+ section("G. Context loaders — smoke tests")
254
+
255
+ rc, out, err = run([PY, "context_loaders/project_summary.py", "--path", str(ROOT)])
256
+ check(39, "project_summary.py exits 0", rc == 0, err[:200])
257
+ check(40, "project_summary.py output contains 'Stack' or 'Language'",
258
+ "stack" in out.lower() or "language" in out.lower(), out[:300])
259
+
260
+ rc, out, err = run([PY, "context_loaders/repo_tree.py", "--path", str(ROOT)])
261
+ check(41, "repo_tree.py exits 0", rc == 0, err[:200])
262
+ check(42, "repo_tree.py output contains directory structure",
263
+ "agents" in out.lower() or "skills" in out.lower(), out[:300])
264
+
265
+ rc, out, err = run([PY, "context_loaders/file_chunker.py", "--path", str(ROOT), "--files", "README.md"])
266
+ check(43, "file_chunker.py exits 0", rc == 0, err[:200])
267
+ check(44, "file_chunker.py output contains README content",
268
+ "agents-maker" in out.lower() or "readme" in out.lower(), out[:300])
269
+
270
+ # -------------------------------------------------------------
271
+ # H. init_project.py — smoke tests
272
+ # -------------------------------------------------------------
273
+ section("H. init_project.py — smoke tests")
274
+
275
+ rc, out, err = run([PY, "tools/init_project.py", "--path", str(ROOT), "--update"])
276
+ check(45, "init_project.py --update exits 0", rc == 0, err[:300])
277
+ check(46, "system_prompt.md still exists after --update", (ROOT / "system_prompt.md").exists(), "")
278
+
279
+ # -------------------------------------------------------------
280
+ # I. generate_prompt.py — missing project.yaml
281
+ # -------------------------------------------------------------
282
+ section("I. generate_prompt.py — missing project.yaml")
283
+
284
+ proj_yaml = ROOT / "config" / "project.yaml"
285
+ proj_backup = None
286
+ renamed = False
287
+
288
+ if proj_yaml.exists():
289
+ proj_backup = proj_yaml.read_text(encoding="utf-8")
290
+ proj_yaml.rename(ROOT / "config" / "project.yaml.bak")
291
+ renamed = True
292
+
293
+ try:
294
+ rc, out, err = run([PY, "tools/generate_prompt.py", "add rate limiting"])
295
+ check(47, "Missing project.yaml -> tool still exits 0", rc == 0, err[:200])
296
+ check(48, "Missing project.yaml -> [WARN] printed to stderr", "[WARN]" in err, err[:200])
297
+ finally:
298
+ bak = ROOT / "config" / "project.yaml.bak"
299
+ if bak.exists():
300
+ bak.rename(proj_yaml)
301
+ elif proj_backup and not proj_yaml.exists():
302
+ proj_yaml.write_text(proj_backup, encoding="utf-8")
303
+
304
+ # -------------------------------------------------------------
305
+ # J. system_prompt.md integrity
306
+ # -------------------------------------------------------------
307
+ section("J. system_prompt.md integrity")
308
+
309
+ sp = ROOT / "system_prompt.md"
310
+ check(49, "system_prompt.md exists", sp.exists(), "")
311
+ if sp.exists():
312
+ sp_text = sp.read_text(encoding="utf-8")
313
+ check(50, "system_prompt.md > 10,000 chars", len(sp_text) > 10000, f"actual: {len(sp_text)}")
314
+ check(51, "system_prompt.md contains orchestrator content (## Role or ## Goals)",
315
+ "## role" in sp_text.lower() or "## goals" in sp_text.lower(), "")
316
+ check(52, "system_prompt.md contains version header",
317
+ "agents-maker system_prompt.md" in sp_text, sp_text[:200])
318
+ check(53, "system_prompt.md contains [Companion] instruction",
319
+ "[Companion]" in sp_text, sp_text[:500])
320
+ else:
321
+ for n in range(50, 54):
322
+ check(n, "system_prompt.md content check", False, "file missing")
323
+
324
+ # -------------------------------------------------------------
325
+ # K. generate_prompt.py — --compress with all phases
326
+ # -------------------------------------------------------------
327
+ section("K. --compress with all phases")
328
+
329
+ compress_phases = ["task_framing", "implementation", "review_refinement", "handoff", "solution_design"]
330
+ for i, ph in enumerate(compress_phases):
331
+ rc, out, err = run([PY, "tools/generate_prompt.py", "some task", "--phase", ph, "--compress"])
332
+ n = 54 + i
333
+ check(n, f"--compress --phase {ph} -> exits 0 + Output Policy in output",
334
+ rc == 0 and "## Output Policy" in out,
335
+ f"exit={rc} stderr={err[:100]}")
336
+
337
+ # -------------------------------------------------------------
338
+ # L. --full + --compress combined
339
+ # -------------------------------------------------------------
340
+ section("L. --full + --compress combined")
341
+
342
+ rc, out, err = run([PY, "tools/generate_prompt.py", "add feature", "--full", "--compress"])
343
+ check(59, "--full --compress combined -> exits 0", rc == 0, err[:200])
344
+ check(60, "--full --compress -> contains both system prompt and Output Policy",
345
+ ("Orchestrator" in out or "orchestrator" in out) and "## Output Policy" in out, "")
346
+
347
+ # -------------------------------------------------------------
348
+ # Summary
349
+ # -------------------------------------------------------------
350
+ total = PASS_COUNT + FAIL_COUNT
351
+ print(f"\n{'=' * 60}")
352
+ print(f" RESULTS: {PASS_COUNT}/{total} tests passed | {FAIL_COUNT} failed")
353
+ print('=' * 60)
354
+
355
+ if FAIL_COUNT > 0:
356
+ print("\nFailed tests:")
357
+ for n, passed, desc, reason in RESULTS:
358
+ if not passed:
359
+ print(f" [{n:02d}] {desc}")
360
+ if reason:
361
+ print(f" {reason[:300]}")
362
+
363
+ sys.exit(0 if FAIL_COUNT == 0 else 1)