@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,461 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ generate_prompt.py — Daily-use tool for agents-maker Companion Mode.
4
+
5
+ Assembles a domain-routed, phase-aware prompt block to paste into any AI tool
6
+ (Claude, Codex, Antigravity). Selects the right agents and skills for the
7
+ current lifecycle phase, inlines project context and session state, and
8
+ prints the result to stdout.
9
+
10
+ Note: This tool does not run the token compression pipeline from compressor.py.
11
+ Use the --full flag to include the full system prompt; otherwise the system
12
+ prompt should be loaded separately as a persistent system instruction.
13
+
14
+ Usage:
15
+ python agents-maker/tools/generate_prompt.py "add rate limiting to the auth service"
16
+ python agents-maker/tools/generate_prompt.py "compare JWT vs Redis sessions"
17
+ python agents-maker/tools/generate_prompt.py "add tests" --phase implementation
18
+ python agents-maker/tools/generate_prompt.py "add tests" --full
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import os
25
+ import re
26
+ import sys
27
+ import tempfile
28
+ from datetime import date
29
+ from pathlib import Path
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Path setup
33
+ # ---------------------------------------------------------------------------
34
+
35
+ SCRIPT_DIR = Path(__file__).resolve().parent # agents-maker/tools/
36
+ KIT_DIR = SCRIPT_DIR.parent # agents-maker/
37
+ sys.path.insert(0, str(KIT_DIR))
38
+
39
+ try:
40
+ import yaml
41
+ except ImportError:
42
+ print("[ERROR] pyyaml is required: pip install pyyaml", file=sys.stderr)
43
+ sys.exit(1)
44
+
45
+ __version__ = "1.0.0"
46
+
47
+ MAX_PROBLEM_LENGTH = 5000
48
+
49
+ # ---------------------------------------------------------------------------
50
+ # Domain scoring — delegated to shared domain_utils module
51
+ # ---------------------------------------------------------------------------
52
+
53
+ try:
54
+ from tools.domain_utils import _load_yaml
55
+ from tools.domain_utils import detect_domain as _du_detect
56
+ except ImportError:
57
+ from domain_utils import _load_yaml
58
+ from domain_utils import detect_domain as _du_detect
59
+
60
+
61
+ def detect_domain(problem: str) -> tuple[str, str, float]:
62
+ return _du_detect(problem, kit_dir=KIT_DIR, include_score=True) # type: ignore[return-value]
63
+
64
+
65
+ # ---------------------------------------------------------------------------
66
+ # Phase inference
67
+ # ---------------------------------------------------------------------------
68
+
69
+ PHASES = [
70
+ "task_framing",
71
+ "requirements",
72
+ "solution_design",
73
+ "implementation",
74
+ "review_refinement",
75
+ "handoff",
76
+ ]
77
+
78
+ PHASE_ALIASES = {
79
+ "framing": "task_framing",
80
+ "task_framing": "task_framing",
81
+ "requirements": "requirements",
82
+ "design": "solution_design",
83
+ "solution_design": "solution_design",
84
+ "implementation": "implementation",
85
+ "implement": "implementation",
86
+ "review": "review_refinement",
87
+ "review_refinement": "review_refinement",
88
+ "handoff": "handoff",
89
+ }
90
+
91
+
92
+ def infer_phase(state_path: Path) -> str:
93
+ if not state_path.exists():
94
+ return "task_framing"
95
+ try:
96
+ text = state_path.read_text(encoding="utf-8")
97
+ except (OSError, PermissionError):
98
+ return "task_framing"
99
+
100
+ # Look for "## Current Phase" section and read the next non-empty line
101
+ in_phase_section = False
102
+ for line in text.splitlines():
103
+ stripped = line.strip()
104
+ if stripped == "## Current Phase":
105
+ in_phase_section = True
106
+ continue
107
+ if in_phase_section:
108
+ if stripped and not stripped.startswith("#"):
109
+ for phase in PHASES:
110
+ if phase in stripped.lower():
111
+ return phase
112
+ # Line found but didn't match — stop looking
113
+ break
114
+ return "task_framing"
115
+
116
+
117
+ # ---------------------------------------------------------------------------
118
+ # Agent selection per phase × domain
119
+ # ---------------------------------------------------------------------------
120
+
121
+ PHASE_AGENTS: dict[str, list[str]] = {
122
+ "task_framing": ["orchestrator"],
123
+ "requirements": ["orchestrator", "architect_agent"],
124
+ "solution_design": ["orchestrator", "architect_agent"],
125
+ "implementation": ["orchestrator"],
126
+ "review_refinement": ["orchestrator", "reviewer_agent"],
127
+ "handoff": ["orchestrator"],
128
+ }
129
+
130
+ IMPLEMENTATION_AGENT: dict[str, str] = {
131
+ "software": "code_agent",
132
+ "data_analytics": "code_agent",
133
+ "content": "execution_agent",
134
+ "research": "execution_agent",
135
+ "product_design": "execution_agent",
136
+ "marketing": "execution_agent",
137
+ "ops_process": "execution_agent",
138
+ "general": "execution_agent",
139
+ }
140
+
141
+ DESIGN_AGENTS: dict[str, list[str]] = {
142
+ "product_design": ["ui_agent", "ux_agent"],
143
+ "marketing": ["ux_agent"],
144
+ }
145
+
146
+
147
+ def select_agents(phase: str, domain: str) -> list[str]:
148
+ agents = list(PHASE_AGENTS.get(phase, ["orchestrator"]))
149
+
150
+ if phase == "implementation":
151
+ impl_agent = IMPLEMENTATION_AGENT.get(domain, "execution_agent")
152
+ if impl_agent not in agents:
153
+ agents.append(impl_agent)
154
+ for extra in DESIGN_AGENTS.get(domain, []):
155
+ if extra not in agents:
156
+ agents.append(extra)
157
+
158
+ if phase == "solution_design":
159
+ for extra in DESIGN_AGENTS.get(domain, []):
160
+ if extra not in agents:
161
+ agents.append(extra)
162
+
163
+ return agents
164
+
165
+
166
+ def select_skills(agents: list[str]) -> list[str]:
167
+ agents_cfg = _load_yaml(KIT_DIR / "config" / "agents.yaml").get("agents", {})
168
+ seen: set[str] = set()
169
+ skills: list[str] = []
170
+ for agent in agents:
171
+ for skill in agents_cfg.get(agent, {}).get("skills", []):
172
+ if skill not in seen:
173
+ seen.add(skill)
174
+ skills.append(skill)
175
+ return skills
176
+
177
+
178
+ # ---------------------------------------------------------------------------
179
+ # Load agent + skill markdown content
180
+ # ---------------------------------------------------------------------------
181
+
182
+ def load_agent_md(agent_name: str) -> str:
183
+ path = KIT_DIR / "agents" / f"{agent_name}.md"
184
+ if path.exists():
185
+ try:
186
+ return path.read_text(encoding="utf-8").strip()
187
+ except (OSError, PermissionError):
188
+ pass
189
+ return f"# {agent_name}\n(agent file not found)"
190
+
191
+
192
+ def load_skill_md(skill_key: str) -> str:
193
+ path = KIT_DIR / "skills" / f"{skill_key}.md"
194
+ if path.exists():
195
+ try:
196
+ return path.read_text(encoding="utf-8").strip()
197
+ except (OSError, PermissionError):
198
+ pass
199
+ return ""
200
+
201
+
202
+ # ---------------------------------------------------------------------------
203
+ # Token estimation
204
+ # ---------------------------------------------------------------------------
205
+
206
+ def _token_est(text: str) -> int:
207
+ """Rough estimate: 1 token ≈ 4 chars. For accuracy use anthropic SDK count_tokens."""
208
+ return max(1, len(text) // 4)
209
+
210
+
211
+ # ---------------------------------------------------------------------------
212
+ # Core prompt builder
213
+ # ---------------------------------------------------------------------------
214
+
215
+ def build_prompt(
216
+ problem: str,
217
+ domain: str,
218
+ confidence: str,
219
+ score: float,
220
+ phase: str,
221
+ agents: list[str],
222
+ skills: list[str],
223
+ project_cfg: dict,
224
+ state_text: str,
225
+ include_system: bool = False,
226
+ ) -> str:
227
+ parts: list[str] = []
228
+
229
+ if include_system:
230
+ sys_path = KIT_DIR / "system_prompt.md"
231
+ if sys_path.exists():
232
+ try:
233
+ parts.append(sys_path.read_text(encoding="utf-8").strip())
234
+ parts.append("\n" + "=" * 60 + "\n")
235
+ except (OSError, PermissionError):
236
+ print("[WARN] Could not read system_prompt.md — inlining agents instead.", file=sys.stderr)
237
+ include_system = False
238
+
239
+ if not sys_path.exists() or not include_system:
240
+ for agent in agents:
241
+ parts.append(load_agent_md(agent))
242
+ parts.append("---")
243
+ for skill in skills:
244
+ content = load_skill_md(skill)
245
+ if content:
246
+ parts.append(content)
247
+ parts.append("---")
248
+
249
+ project_name = project_cfg.get("project_name", "unknown")
250
+ proj_domain = project_cfg.get("primary_domain", domain)
251
+ stack = project_cfg.get("stack", [])
252
+ stack_str = ", ".join(stack) if stack else "unknown"
253
+ key_constraints = project_cfg.get("key_constraints", [])
254
+
255
+ context_lines = [
256
+ "## Project Context",
257
+ f"Name: {project_name} | Stack: {stack_str} | Domain: {proj_domain}",
258
+ ]
259
+ if key_constraints:
260
+ context_lines.append(f"Constraints: {'; '.join(str(c) for c in key_constraints)}")
261
+ parts.append("\n".join(context_lines))
262
+
263
+ if state_text.strip():
264
+ parts.append(f"## Session State\n{state_text.strip()}")
265
+ else:
266
+ session_num = project_cfg.get("session_count", 0) + 1
267
+ parts.append(f"## Session State\nSession {session_num} — starting fresh")
268
+
269
+ parts.append(f"## Task\n{problem}")
270
+
271
+ routing_lines = [
272
+ "## Domain & Routing",
273
+ f"Domain: {domain} (confidence: {confidence}, score: {score:.2f})",
274
+ f"Suggested phase: {phase}",
275
+ f"Active agents: {', '.join(agents)}",
276
+ f"Active skills: {', '.join(skills)}",
277
+ ]
278
+ if domain == "general":
279
+ routing_lines.append(
280
+ "\nNote: Domain confidence is low. Consider prefixing your task with "
281
+ "`[domain: software]` (or another domain) to force routing."
282
+ )
283
+ parts.append("\n".join(routing_lines))
284
+
285
+ return "\n\n".join(parts)
286
+
287
+
288
+ # ---------------------------------------------------------------------------
289
+ # Main
290
+ # ---------------------------------------------------------------------------
291
+
292
+ def main() -> None:
293
+ sys.stdout.reconfigure(encoding="utf-8")
294
+
295
+ parser = argparse.ArgumentParser(
296
+ description="Generate a domain-routed prompt for your next AI session.",
297
+ formatter_class=argparse.RawDescriptionHelpFormatter,
298
+ epilog=(
299
+ "Examples:\n"
300
+ ' python agents-maker/tools/generate_prompt.py "add rate limiting"\n'
301
+ ' python agents-maker/tools/generate_prompt.py "add tests" --phase implementation\n'
302
+ ' python agents-maker/tools/generate_prompt.py "add tests" --full\n'
303
+ ),
304
+ )
305
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
306
+ parser.add_argument("problem", help="What you want to work on this session.")
307
+ parser.add_argument(
308
+ "--phase",
309
+ choices=list(PHASE_ALIASES.keys()),
310
+ default=None,
311
+ help="Force a specific lifecycle phase (default: inferred from project_state.md).",
312
+ )
313
+ parser.add_argument(
314
+ "--full",
315
+ action="store_true",
316
+ help="Prepend full system prompt content (for platforms without persistent system prompts).",
317
+ )
318
+ parser.add_argument(
319
+ "--compress",
320
+ action="store_true",
321
+ help="Apply token policy for the detected phase/domain: appends output style instruction and reports token budget.",
322
+ )
323
+ args = parser.parse_args()
324
+
325
+ # Validate problem statement
326
+ if not args.problem.strip():
327
+ print("[ERROR] Problem statement cannot be empty.", file=sys.stderr)
328
+ sys.exit(1)
329
+ if len(args.problem) > MAX_PROBLEM_LENGTH:
330
+ print(f"[ERROR] Problem statement too long ({len(args.problem)} chars, max {MAX_PROBLEM_LENGTH}).", file=sys.stderr)
331
+ sys.exit(1)
332
+
333
+ # Load project config
334
+ project_yaml_path = KIT_DIR / "config" / "project.yaml"
335
+ project_cfg = _load_yaml(project_yaml_path)
336
+ if not project_cfg:
337
+ print(
338
+ "[WARN] project.yaml not found or empty. Run init_project.py first for best results.",
339
+ file=sys.stderr,
340
+ )
341
+
342
+ # Load session state
343
+ state_path = KIT_DIR / "project_state.md"
344
+ try:
345
+ state_text = state_path.read_text(encoding="utf-8") if state_path.exists() else ""
346
+ except (OSError, PermissionError):
347
+ state_text = ""
348
+
349
+ # Detect domain: honour explicit [domain: X] prefix first
350
+ _VALID_DOMAINS = [
351
+ "software", "content", "research", "data_analytics",
352
+ "product_design", "marketing", "ops_process", "general",
353
+ ]
354
+ _prefix_m = re.match(r"^\[domain:\s*([a-z_]+)\]", args.problem.strip(), re.IGNORECASE)
355
+ if _prefix_m and _prefix_m.group(1).lower() in _VALID_DOMAINS:
356
+ domain = _prefix_m.group(1).lower()
357
+ confidence = "forced"
358
+ score = 1.0
359
+ else:
360
+ if _prefix_m:
361
+ print(
362
+ f"[WARN] '[domain: {_prefix_m.group(1)}]' is not a recognized domain — "
363
+ f"running auto-detection. Valid: {', '.join(_VALID_DOMAINS)}",
364
+ file=sys.stderr,
365
+ )
366
+ domain, confidence, score = detect_domain(args.problem)
367
+ if confidence == "low" and project_cfg.get("primary_domain"):
368
+ domain = project_cfg["primary_domain"]
369
+ confidence = "from-project"
370
+ score = 0.0
371
+
372
+ # Determine phase
373
+ if args.phase:
374
+ phase = PHASE_ALIASES.get(args.phase, args.phase)
375
+ else:
376
+ phase = infer_phase(state_path)
377
+
378
+ agents = select_agents(phase, domain)
379
+ skills = select_skills(agents)
380
+
381
+ prompt_text = build_prompt(
382
+ problem=args.problem,
383
+ domain=domain,
384
+ confidence=confidence,
385
+ score=score,
386
+ phase=phase,
387
+ agents=agents,
388
+ skills=skills,
389
+ project_cfg=project_cfg,
390
+ state_text=state_text,
391
+ include_system=args.full,
392
+ )
393
+
394
+ # Apply token policy when --compress is requested
395
+ if args.compress:
396
+ _phase_to_workflow = {
397
+ "task_framing": "generic_project_lifecycle",
398
+ "requirements": "generic_project_lifecycle",
399
+ "solution_design": "feature_design",
400
+ "implementation": "feature_implementation",
401
+ "review_refinement": "code_review",
402
+ "handoff": "generic_project_lifecycle",
403
+ }
404
+ try:
405
+ from token_optimization.compressor import PolicyLoader
406
+ _loader = PolicyLoader(KIT_DIR / "config" / "token_policies.yaml")
407
+ _loader.load()
408
+ _workflow = _phase_to_workflow.get(phase, "feature_implementation")
409
+ _policy = _loader.get_workflow_policy(_workflow)
410
+ prompt_text += (
411
+ f"\n\n## Output Policy\n"
412
+ f"Workflow: {_workflow}\n"
413
+ f"Output style: {_policy.output_style}\n"
414
+ f"Max input tokens: {_policy.max_input_tokens:,}\n"
415
+ f"Max files in context: {_policy.max_input_files}\n"
416
+ f"History compress after: {_policy.history_summarize_after_turns} turns"
417
+ )
418
+ print(
419
+ f" [Compress] Workflow: {_workflow} | Style: {_policy.output_style} "
420
+ f"| Budget: {_policy.max_input_tokens:,} tokens",
421
+ file=sys.stderr,
422
+ )
423
+ except ImportError as e:
424
+ print(f"[WARN] --compress skipped (import error): {e}", file=sys.stderr)
425
+ except Exception as e:
426
+ print(f"[WARN] --compress skipped: {e}", file=sys.stderr)
427
+
428
+ token_est = _token_est(prompt_text)
429
+ project_name = project_cfg.get("project_name", "unknown")
430
+
431
+ print("=" * 60)
432
+ print(" PASTE THIS AS YOUR NEXT MESSAGE")
433
+ print(f" Project: {project_name} | Domain: {domain} ({confidence}) | Phase: {phase}")
434
+ print(f" Est. tokens: ~{token_est:,} | Agents: {', '.join(agents)}")
435
+ print("=" * 60)
436
+ print()
437
+ print(prompt_text)
438
+ print()
439
+ print("=" * 60)
440
+ print(" After the AI responds:")
441
+ print(" -> Save any project_state.md the AI produces to agents-maker/project_state.md")
442
+ print(' -> python agents-maker/tools/generate_prompt.py "your next task"')
443
+ print("=" * 60)
444
+
445
+ # Silently update session_count + last_session (atomic write — crash-safe)
446
+ if project_yaml_path.exists():
447
+ project_cfg["session_count"] = project_cfg.get("session_count", 0) + 1
448
+ project_cfg["last_session"] = date.today().isoformat()
449
+ try:
450
+ with tempfile.NamedTemporaryFile(
451
+ "w", dir=project_yaml_path.parent, delete=False, suffix=".tmp", encoding="utf-8"
452
+ ) as _f:
453
+ yaml.dump(project_cfg, _f, default_flow_style=False, allow_unicode=True, sort_keys=False)
454
+ _tmp = _f.name
455
+ os.replace(_tmp, project_yaml_path)
456
+ except (OSError, PermissionError) as e:
457
+ print(f"[WARN] Could not update session_count in project.yaml: {e}", file=sys.stderr)
458
+
459
+
460
+ if __name__ == "__main__":
461
+ main()