@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,454 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ init_project.py — One-time project bootstrap for agents-maker Companion Mode.
4
+
5
+ Usage:
6
+ python agents-maker/tools/init_project.py
7
+ python agents-maker/tools/init_project.py --path /your/project
8
+ python agents-maker/tools/init_project.py --update # regenerate system_prompt.md
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import hashlib
15
+ import os
16
+ import sys
17
+ import tempfile
18
+ from datetime import date
19
+ from pathlib import Path
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Path setup — allow imports from context_loaders and config
23
+ # ---------------------------------------------------------------------------
24
+
25
+ __version__ = "1.0.0"
26
+
27
+ SCRIPT_DIR = Path(__file__).resolve().parent # agents-maker/tools/
28
+ KIT_DIR = SCRIPT_DIR.parent # agents-maker/
29
+ sys.path.insert(0, str(KIT_DIR))
30
+
31
+ try:
32
+ import yaml
33
+ except ImportError:
34
+ print("[ERROR] pyyaml is required: pip install pyyaml", file=sys.stderr)
35
+ sys.exit(1)
36
+
37
+ try:
38
+ from context_loaders.project_summary import build_summary
39
+ from context_loaders.repo_tree import walk_tree
40
+ except ImportError as e:
41
+ print(f"[ERROR] Could not import context_loaders: {e}", file=sys.stderr)
42
+ print("[ERROR] Make sure you're running from the project root and agents-maker/ is present.", file=sys.stderr)
43
+ sys.exit(1)
44
+
45
+ try:
46
+ from tools.domain_utils import _load_yaml
47
+ from tools.domain_utils import detect_domain as _detect_domain
48
+ except ImportError:
49
+ from domain_utils import _load_yaml
50
+ from domain_utils import detect_domain as _detect_domain
51
+
52
+
53
+ def detect_domain(summary_text: str) -> tuple[str, str]:
54
+ return _detect_domain(summary_text, kit_dir=KIT_DIR) # type: ignore[return-value]
55
+
56
+
57
+ def detect_stack_from_summary(summary_text: str) -> list[str]:
58
+ for line in summary_text.splitlines():
59
+ if line.startswith("**Stack**:"):
60
+ parts = line.split(":", 1)[1].strip()
61
+ if parts and parts != "Unknown":
62
+ return [p.strip() for p in parts.split(",") if p.strip()]
63
+ return []
64
+
65
+
66
+ # ---------------------------------------------------------------------------
67
+ # YAML injection guard — reject strings with characters that break yaml.dump
68
+ # ---------------------------------------------------------------------------
69
+
70
+ _YAML_UNSAFE = frozenset({'"', "'", ':', '!', '{', '}', '[', ']', '\\', '\n', '\r'})
71
+
72
+
73
+ def _sanitize_yaml_str(value: str, field: str) -> str:
74
+ bad = [c for c in value if c in _YAML_UNSAFE]
75
+ if bad:
76
+ safe = "".join(c for c in value if c not in _YAML_UNSAFE)
77
+ print(
78
+ f"[WARN] {field!r} contained unsafe YAML characters {sorted(set(bad))} — stripped.",
79
+ file=sys.stderr,
80
+ )
81
+ return safe
82
+ return value
83
+
84
+
85
+ # ---------------------------------------------------------------------------
86
+ # Source hash — lets validate_kit.py detect stale system_prompt.md
87
+ # ---------------------------------------------------------------------------
88
+
89
+ def _compute_source_hash(kit_dir: Path) -> str:
90
+ h = hashlib.sha256()
91
+ agent_files = sorted((kit_dir / "agents").glob("*.md")) if (kit_dir / "agents").is_dir() else []
92
+ skill_files = sorted((kit_dir / "skills").glob("*.md")) if (kit_dir / "skills").is_dir() else []
93
+ for path in agent_files + skill_files:
94
+ try:
95
+ h.update(path.read_bytes().replace(b"\r\n", b"\n"))
96
+ except (OSError, PermissionError):
97
+ pass
98
+ return h.hexdigest()[:16]
99
+
100
+
101
+ # ---------------------------------------------------------------------------
102
+ # Atomic write helpers (write to temp then os.replace — crash-safe)
103
+ # ---------------------------------------------------------------------------
104
+
105
+ def _atomic_write_text(path: Path, content: str) -> None:
106
+ path.parent.mkdir(parents=True, exist_ok=True)
107
+ with tempfile.NamedTemporaryFile(
108
+ "w", dir=path.parent, delete=False, suffix=".tmp", encoding="utf-8"
109
+ ) as f:
110
+ f.write(content)
111
+ tmp = f.name
112
+ os.replace(tmp, path)
113
+
114
+
115
+ def _atomic_write_yaml(path: Path, data: dict) -> None:
116
+ path.parent.mkdir(parents=True, exist_ok=True)
117
+ with tempfile.NamedTemporaryFile(
118
+ "w", dir=path.parent, delete=False, suffix=".tmp", encoding="utf-8"
119
+ ) as f:
120
+ yaml.dump(data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
121
+ tmp = f.name
122
+ os.replace(tmp, path)
123
+
124
+
125
+ # ---------------------------------------------------------------------------
126
+ # system_prompt.md builder
127
+ # ---------------------------------------------------------------------------
128
+
129
+ def build_system_prompt(project_name: str, domain: str, stack: list[str]) -> str:
130
+ agents_dir = KIT_DIR / "agents"
131
+ agent_order = [
132
+ "orchestrator.md",
133
+ "architect_agent.md",
134
+ "code_agent.md",
135
+ "execution_agent.md",
136
+ "ui_agent.md",
137
+ "ux_agent.md",
138
+ "reviewer_agent.md",
139
+ "compression_agent.md",
140
+ ]
141
+ skill_count = len(list((KIT_DIR / "skills").glob("*.md"))) if (KIT_DIR / "skills").exists() else 0
142
+ source_hash = _compute_source_hash(KIT_DIR)
143
+ version_header = (
144
+ f"# agents-maker system_prompt.md\n"
145
+ f"# Version: 1.0 | Generated: {date.today().isoformat()} | Source hash: {source_hash}\n"
146
+ f"# Regenerate: python agents-maker/tools/init_project.py --update\n"
147
+ f"# Contains: {len(agent_order)} agents + {skill_count} skills\n"
148
+ f"#\n"
149
+ f"# [Companion] INSTRUCTION (always active):\n"
150
+ f"# After every response append a [Companion] block:\n"
151
+ f"# ---\n"
152
+ f"# [Companion] Phase: <phase> | Domain: <domain> | Est. token budget used: ~N%\n"
153
+ f"# What to do next (pick one):\n"
154
+ f"# [Recommended] A: <action> Command: python agents-maker/tools/generate_prompt.py \"...\"\n"
155
+ f"# B: <action>\n"
156
+ f"# C: <action>\n"
157
+ f"# ---\n"
158
+ )
159
+ sections: list[str] = [version_header]
160
+
161
+ for fname in agent_order:
162
+ fpath = agents_dir / fname
163
+ if fpath.exists():
164
+ try:
165
+ content = fpath.read_text(encoding="utf-8").strip()
166
+ sections.append(content)
167
+ sections.append("---")
168
+ except (PermissionError, OSError) as e:
169
+ print(f"[WARN] Could not read {fpath}: {e}", file=sys.stderr)
170
+
171
+ skills_dir = KIT_DIR / "skills"
172
+ if skills_dir.exists():
173
+ for skill_file in sorted(skills_dir.glob("*.md")):
174
+ try:
175
+ content = skill_file.read_text(encoding="utf-8").strip()
176
+ sections.append(content)
177
+ sections.append("---")
178
+ except (PermissionError, OSError) as e:
179
+ print(f"[WARN] Could not read {skill_file}: {e}", file=sys.stderr)
180
+
181
+ stack_str = ", ".join(stack) if stack else "unknown"
182
+ context_block = (
183
+ f"## Project Context\n\n"
184
+ f"Project name: {project_name} \n"
185
+ f"Primary domain: {domain} \n"
186
+ f"Stack: {stack_str} \n"
187
+ f"Initialized: {date.today().isoformat()} \n"
188
+ )
189
+ sections.append(context_block)
190
+
191
+ return "\n\n".join(sections)
192
+
193
+
194
+ # ---------------------------------------------------------------------------
195
+ # project_state.md template
196
+ # ---------------------------------------------------------------------------
197
+
198
+ STATE_TEMPLATE = """\
199
+ # Project State
200
+ schema_version: "1.0"
201
+
202
+ ## Current Phase
203
+ task_framing
204
+
205
+ ## Domain
206
+ (detected at init — override here if needed)
207
+
208
+ ## Approved Artifacts
209
+ (none yet)
210
+
211
+ ## Open Decisions
212
+ (none yet)
213
+
214
+ ## Build Log
215
+ (empty)
216
+
217
+ ## Session Notes
218
+ (add notes after each session)
219
+ """
220
+
221
+ VALID_DOMAINS = [
222
+ "software", "content", "research", "data_analytics",
223
+ "product_design", "marketing", "ops_process", "general",
224
+ ]
225
+
226
+
227
+ # ---------------------------------------------------------------------------
228
+ # Main
229
+ # ---------------------------------------------------------------------------
230
+
231
+ def main() -> None:
232
+ sys.stdout.reconfigure(encoding="utf-8")
233
+
234
+ parser = argparse.ArgumentParser(
235
+ description="Bootstrap agents-maker Companion Mode for a project.",
236
+ formatter_class=argparse.RawDescriptionHelpFormatter,
237
+ epilog=(
238
+ "Examples:\n"
239
+ " python agents-maker/tools/init_project.py\n"
240
+ " python agents-maker/tools/init_project.py --path /my/project\n"
241
+ " python agents-maker/tools/init_project.py --update # regenerate system_prompt.md\n"
242
+ ),
243
+ )
244
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
245
+ parser.add_argument(
246
+ "--path",
247
+ help="Project root directory (default: parent of agents-maker/)",
248
+ )
249
+ parser.add_argument(
250
+ "--update",
251
+ action="store_true",
252
+ help="Regenerate system_prompt.md even if it already exists.",
253
+ )
254
+ parser.add_argument(
255
+ "--claude-md",
256
+ action="store_true",
257
+ help="Also generate CLAUDE.md in the project root (Claude Code integration).",
258
+ )
259
+ parser.add_argument(
260
+ "--platforms",
261
+ action="store_true",
262
+ help=(
263
+ "Generate config files for ALL supported platforms: "
264
+ "Claude Code (CLAUDE.md), GitHub Copilot (.github/copilot-instructions.md), "
265
+ "Cursor (.cursor/rules), Antigravity (.agkit/agents.yaml). "
266
+ "Supersedes --claude-md."
267
+ ),
268
+ )
269
+ args = parser.parse_args()
270
+
271
+ # Resolve project root
272
+ if args.path:
273
+ project_root = Path(args.path).resolve()
274
+ else:
275
+ project_root = KIT_DIR.parent
276
+
277
+ if not project_root.exists():
278
+ print(f"[ERROR] Project root does not exist: {project_root}", file=sys.stderr)
279
+ sys.exit(1)
280
+ if not project_root.is_dir():
281
+ print(f"[ERROR] Path is not a directory: {project_root}", file=sys.stderr)
282
+ sys.exit(1)
283
+
284
+ project_name = project_root.name
285
+ print(f"\nInitializing agents-maker for: {project_root}")
286
+ print("-" * 60)
287
+
288
+ # Step 1 — Scan project
289
+ print("Scanning project...")
290
+ try:
291
+ summary_text = build_summary(project_root)
292
+ walk_tree(project_root, max_depth=3, show_all=False)
293
+ except Exception as e:
294
+ print(f"[WARN] Project scan encountered an error: {e}", file=sys.stderr)
295
+ summary_text = ""
296
+
297
+ # Step 2 — Detect domain
298
+ detected_domain, confidence = detect_domain(summary_text)
299
+ stack = detect_stack_from_summary(summary_text)
300
+
301
+ print(f"Detected domain : {detected_domain} (confidence: {confidence})")
302
+ print(f"Detected stack : {', '.join(stack) if stack else 'unknown'}")
303
+
304
+ # Step 3 — Confirm or override domain
305
+ print(f"\nValid domains: {', '.join(VALID_DOMAINS)}")
306
+ try:
307
+ user_input = input(
308
+ f"Press Enter to accept '{detected_domain}', or type a domain to override: "
309
+ ).strip().lower()
310
+ except (EOFError, KeyboardInterrupt):
311
+ user_input = ""
312
+
313
+ # Validate user input strictly
314
+ if not user_input:
315
+ final_domain = detected_domain
316
+ elif len(user_input) > 50:
317
+ print(f"[WARN] Input too long — keeping '{detected_domain}'", file=sys.stderr)
318
+ final_domain = detected_domain
319
+ elif not user_input.replace("_", "").isalpha():
320
+ print(f"[WARN] Invalid domain name (letters and underscores only) — keeping '{detected_domain}'", file=sys.stderr)
321
+ final_domain = detected_domain
322
+ elif user_input in VALID_DOMAINS:
323
+ final_domain = user_input
324
+ print(f"Using domain: {final_domain}")
325
+ else:
326
+ print(f"[WARN] '{user_input}' is not a recognized domain — keeping '{detected_domain}'", file=sys.stderr)
327
+ final_domain = detected_domain
328
+
329
+ # Step 4 — Write config/project.yaml
330
+ config_dir = KIT_DIR / "config"
331
+ config_dir.mkdir(parents=True, exist_ok=True)
332
+ project_yaml_path = config_dir / "project.yaml"
333
+
334
+ # Preserve session_count if updating
335
+ existing_cfg = _load_yaml(project_yaml_path)
336
+ session_count = existing_cfg.get("session_count", 0) if args.update else 0
337
+ created_at = existing_cfg.get("created_at", date.today().isoformat()) if args.update else date.today().isoformat()
338
+
339
+ safe_name = _sanitize_yaml_str(project_name, "project_name")
340
+ safe_stack = [_sanitize_yaml_str(s, f"stack[{i}]") for i, s in enumerate(stack)]
341
+
342
+ project_cfg = {
343
+ "project_name": safe_name,
344
+ "created_at": created_at,
345
+ "primary_domain": final_domain,
346
+ "stack": safe_stack,
347
+ "key_constraints": existing_cfg.get("key_constraints", []) if args.update else [],
348
+ "session_count": session_count,
349
+ "last_session": existing_cfg.get("last_session") if args.update else None,
350
+ }
351
+ try:
352
+ _atomic_write_yaml(project_yaml_path, project_cfg)
353
+ except OSError as e:
354
+ print(f"[ERROR] Could not write project.yaml: {e}", file=sys.stderr)
355
+ sys.exit(1)
356
+
357
+ # Step 5 — Generate system_prompt.md
358
+ system_prompt_path = KIT_DIR / "system_prompt.md"
359
+ if not system_prompt_path.exists() or args.update:
360
+ system_prompt_text = build_system_prompt(project_name, final_domain, stack)
361
+ try:
362
+ _atomic_write_text(system_prompt_path, system_prompt_text)
363
+ except OSError as e:
364
+ print(f"[ERROR] Could not write system_prompt.md: {e}", file=sys.stderr)
365
+ sys.exit(1)
366
+ char_count = len(system_prompt_text)
367
+ token_estimate = char_count // 4
368
+ system_prompt_status = f"(~{char_count:,} chars, ~{token_estimate:,} tokens)"
369
+ system_prompt_done = True
370
+ else:
371
+ system_prompt_status = "(already exists — use --update to regenerate)"
372
+ system_prompt_done = False
373
+
374
+ # Step 6 — Create project_state.md if absent
375
+ state_path = KIT_DIR / "project_state.md"
376
+ if not state_path.exists():
377
+ try:
378
+ _atomic_write_text(state_path, STATE_TEMPLATE)
379
+ state_created = True
380
+ except OSError as e:
381
+ print(f"[ERROR] Could not write project_state.md: {e}", file=sys.stderr)
382
+ sys.exit(1)
383
+ else:
384
+ state_created = False
385
+
386
+ # Step 7 — Print summary
387
+ print()
388
+ print("=" * 60)
389
+ try:
390
+ print(f" [DONE] {project_yaml_path.relative_to(KIT_DIR.parent)}")
391
+ tag = "[DONE]" if system_prompt_done else "[SKIP]"
392
+ print(f" {tag} {system_prompt_path.relative_to(KIT_DIR.parent)} {system_prompt_status}")
393
+ if state_created:
394
+ print(f" [DONE] {state_path.relative_to(KIT_DIR.parent)} (template created)")
395
+ else:
396
+ print(" [SKIP] project_state.md (already exists — not overwritten)")
397
+ except ValueError:
398
+ print(f" [DONE] {project_yaml_path}")
399
+ print(f" [DONE] {system_prompt_path}")
400
+ print("=" * 60)
401
+ print()
402
+ print("Next: Paste system_prompt.md into your AI tool as the system prompt (do this once).")
403
+ print('Then: python agents-maker/tools/generate_prompt.py "your first task"')
404
+ print()
405
+
406
+ # --platforms: generate configs for all supported AI platforms
407
+ if args.platforms:
408
+ try:
409
+ from tools.generate_platform_configs import PLATFORMS, generate_all
410
+ except ImportError:
411
+ from generate_platform_configs import PLATFORMS, generate_all
412
+ print("Generating platform configs (Claude Code, Copilot, Cursor, Antigravity)...")
413
+ generate_all(project_root, KIT_DIR, PLATFORMS, dry_run=False)
414
+
415
+ # --claude-md: generate CLAUDE.md only (kept for backward compatibility)
416
+ elif args.claude_md:
417
+ try:
418
+ from tools.generate_claude_md import _parse_phase, build_claude_md
419
+ except ImportError:
420
+ from generate_claude_md import _parse_phase, build_claude_md
421
+
422
+ state_path = KIT_DIR / "project_state.md"
423
+ phase = _parse_phase(state_path.read_text(encoding="utf-8")) if state_path.exists() else "task_framing"
424
+ try:
425
+ kit_rel = KIT_DIR.relative_to(project_root)
426
+ kit_rel_path = str(kit_rel).replace("\\", "/")
427
+ except ValueError:
428
+ kit_rel_path = "agents-maker"
429
+
430
+ claude_md_content = build_claude_md(
431
+ project_name=project_name,
432
+ domain=final_domain,
433
+ confidence="high",
434
+ stack=stack,
435
+ phase=phase,
436
+ kit_rel_path=kit_rel_path,
437
+ )
438
+ claude_md_path = project_root / "CLAUDE.md"
439
+ try:
440
+ _atomic_write_text(claude_md_path, claude_md_content)
441
+ print(f" [DONE] CLAUDE.md written to {claude_md_path}")
442
+ print(" Claude Code will auto-load domain/phase/stack on every session.")
443
+ print(" Commit CLAUDE.md to git — it is project config, not private state.")
444
+ except OSError as e:
445
+ print(f" [WARN] Could not write CLAUDE.md: {e}", file=sys.stderr)
446
+ print()
447
+ else:
448
+ print("Tip: Wire agents-maker into Claude Code, Copilot, Cursor, and Antigravity:")
449
+ print(" python agents-maker/tools/generate_platform_configs.py")
450
+ print()
451
+
452
+
453
+ if __name__ == "__main__":
454
+ main()