@prateek_ai/agents-maker 1.0.0 → 1.0.2

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 (68) hide show
  1. package/PROMPT_TEMPLATE.md +143 -0
  2. package/README.md +92 -25
  3. package/agents/brain.md +90 -0
  4. package/agents/orchestrator.md +24 -3
  5. package/agents/planpro.md +91 -0
  6. package/bin/cli.js +40 -4
  7. package/claude/agents/architect.md +182 -0
  8. package/claude/agents/brain.md +97 -0
  9. package/claude/agents/code.md +185 -0
  10. package/claude/agents/compress.md +233 -0
  11. package/claude/agents/execute.md +164 -0
  12. package/claude/agents/orchestrate.md +434 -0
  13. package/claude/agents/planpro.md +98 -0
  14. package/claude/agents/review.md +141 -0
  15. package/claude/agents/ui.md +154 -0
  16. package/claude/agents/ux.md +151 -0
  17. package/claude/commands/architect.md +15 -0
  18. package/claude/commands/brain.md +15 -0
  19. package/claude/commands/code.md +15 -0
  20. package/claude/commands/compress.md +15 -0
  21. package/claude/commands/execute.md +15 -0
  22. package/claude/commands/orchestrate.md +15 -0
  23. package/claude/commands/planpro.md +15 -0
  24. package/claude/commands/review.md +15 -0
  25. package/claude/commands/ui.md +15 -0
  26. package/claude/commands/ux.md +15 -0
  27. package/config/agents.yaml +56 -0
  28. package/context_loaders/project_summary.py +5 -0
  29. package/docs/architecture.md +318 -0
  30. package/docs/domains.md +154 -0
  31. package/docs/workflows.md +548 -0
  32. package/examples/generic_project_lifecycle.md +518 -0
  33. package/examples/proof/README.md +138 -0
  34. package/examples/proof/implement-a-python-function-that-parses-an-iso-8/grade.md +14 -0
  35. package/examples/proof/implement-a-python-function-that-parses-an-iso-8/naive_output.md +211 -0
  36. package/examples/proof/implement-a-python-function-that-parses-an-iso-8/naive_prompt.txt +1 -0
  37. package/examples/proof/implement-a-python-function-that-parses-an-iso-8/structured_output.md +210 -0
  38. package/examples/proof/implement-a-python-function-that-parses-an-iso-8/structured_user.txt +35 -0
  39. package/examples/proof/write-a-runbook-for-recovering-from-a-redis-prim/grade.md +14 -0
  40. package/examples/proof/write-a-runbook-for-recovering-from-a-redis-prim/naive_output.md +274 -0
  41. package/examples/proof/write-a-runbook-for-recovering-from-a-redis-prim/naive_prompt.txt +1 -0
  42. package/examples/proof/write-a-runbook-for-recovering-from-a-redis-prim/structured_output.md +127 -0
  43. package/examples/proof/write-a-runbook-for-recovering-from-a-redis-prim/structured_user.txt +35 -0
  44. package/package.json +10 -2
  45. package/platforms/antigravity.md +195 -0
  46. package/platforms/claude.md +305 -0
  47. package/platforms/openai.md +333 -0
  48. package/skills/analyze_repo.md +86 -86
  49. package/token_optimization/compressor.py +4 -7
  50. package/tools/_core.py +102 -0
  51. package/tools/compare_prompts.py +196 -0
  52. package/tools/generate_claude_agents.py +162 -0
  53. package/tools/generate_claude_md.py +14 -80
  54. package/tools/generate_platform_configs.py +26 -36
  55. package/tools/generate_prompt.py +79 -79
  56. package/tools/grade_proof.py +118 -0
  57. package/tools/init_project.py +11 -58
  58. package/tools/routing.py +103 -0
  59. package/tools/test_kit.py +392 -363
  60. package/tools/validate_kit.py +15 -43
  61. package/context_loaders/__pycache__/__init__.cpython-314.pyc +0 -0
  62. package/context_loaders/__pycache__/file_chunker.cpython-314.pyc +0 -0
  63. package/context_loaders/__pycache__/project_summary.cpython-314.pyc +0 -0
  64. package/context_loaders/__pycache__/repo_tree.cpython-314.pyc +0 -0
  65. package/token_optimization/__pycache__/compressor.cpython-314.pyc +0 -0
  66. package/tools/__pycache__/domain_utils.cpython-314.pyc +0 -0
  67. package/tools/__pycache__/generate_claude_md.cpython-314.pyc +0 -0
  68. package/tools/__pycache__/validate_kit.cpython-314.pyc +0 -0
@@ -1,86 +1,86 @@
1
- # Skill: analyze_repo
2
-
3
- ## Description
4
-
5
- Walk a repository's file tree and produce a compact, structured summary of the project: detected stack, primary services or modules, main entrypoints, test structure, and key configuration files. Used by the Orchestrator and Architect Agent to build the Project State block at the start of a session.
6
-
7
- ---
8
-
9
- ## When to invoke
10
-
11
- - Session start, when no project summary is available.
12
- - When routing a task that requires understanding the broader project structure beyond the provided snippets.
13
- - When the Architect Agent needs a service map before designing a new component.
14
-
15
- ---
16
-
17
- ## Input expectations
18
-
19
- | Input | Required | Description |
20
- |---|---|---|
21
- | `repo_path` | Yes | Root path of the repository |
22
- | `filter_paths` | No | List of subdirectory prefixes to include (e.g., `src/`, `app/`, `services/`) |
23
- | `exclude_patterns` | No | Patterns to exclude (e.g., `node_modules/`, `__pycache__/`, `.git/`) |
24
- | `max_depth` | No | Maximum directory depth to traverse (default: 4) |
25
-
26
- In a conversational context, the user provides these via the `context_loaders/repo_tree.py` and `context_loaders/project_summary.py` scripts, and pastes the output.
27
-
28
- **If required input is missing:**
29
- - `repo_path` — if no path is provided and no repo context has been pasted, ask: "Please paste the output of `python agents-maker/context_loaders/repo_tree.py` or describe your project's directory structure." Do not produce a summary from nothing.
30
- - `filter_paths` — default to scanning the entire repo up to `max_depth`.
31
- - `exclude_patterns` — default to excluding `node_modules/`, `__pycache__/`, `.git/`, `dist/`, `build/`.
32
- - `max_depth` — default to 4.
33
-
34
- ---
35
-
36
- ## Output format
37
-
38
- The skill produces a structured text block:
39
-
40
- ```
41
- ## Project Summary
42
-
43
- **Stack**: <language(s), runtime version(s), primary framework(s)>
44
- **Build tool**: <e.g., pip + setuptools, npm + webpack, gradle>
45
- **Test framework**: <e.g., pytest, jest, JUnit>
46
- **Containerization**: <Docker | none | Kubernetes manifests present>
47
-
48
- ## Services / Modules
49
-
50
- | Name | Path | Responsibility |
51
- |---|---|---|
52
- | <name> | <path> | <one-line description> |
53
-
54
- ## Main Entrypoints
55
-
56
- | File | Purpose |
57
- |---|---|
58
- | <path> | <what it starts or exports> |
59
-
60
- ## Key Config Files
61
-
62
- | File | Purpose |
63
- |---|---|
64
- | <path> | <what it configures> |
65
-
66
- ## Test Structure
67
-
68
- | Path | Type | Coverage scope |
69
- |---|---|---|
70
- | <path> | unit | integration | e2e | <scope> |
71
- ```
72
-
73
- ---
74
-
75
- ## Token cost tier
76
-
77
- **Medium.** Involves reading file tree and inspecting key files. Output is typically 300–600 tokens.
78
-
79
- Compression hint: the output is already compact. Do not summarize it further — it is the basis for all other context in the session.
80
-
81
- ---
82
-
83
- ## Notes
84
-
85
- - This skill is implemented as `context_loaders/project_summary.py`. In agent sessions without tool access, the user runs it locally and pastes the output.
86
- - If the repo has no recognizable structure, return the raw tree truncated at `max_depth` and note: "Could not detect stack — please specify language and framework manually."
1
+ # Skill: analyze_repo
2
+
3
+ ## Description
4
+
5
+ Walk a repository's file tree and produce a compact, structured summary of the project: detected stack, primary services or modules, main entrypoints, test structure, and key configuration files. Used by the Orchestrator and Architect Agent to build the Project State block at the start of a session.
6
+
7
+ ---
8
+
9
+ ## When to invoke
10
+
11
+ - Session start, when no project summary is available.
12
+ - When routing a task that requires understanding the broader project structure beyond the provided snippets.
13
+ - When the Architect Agent needs a service map before designing a new component.
14
+
15
+ ---
16
+
17
+ ## Input expectations
18
+
19
+ | Input | Required | Description |
20
+ |---|---|---|
21
+ | `repo_path` | Yes | Root path of the repository |
22
+ | `filter_paths` | No | List of subdirectory prefixes to include (e.g., `src/`, `app/`, `services/`) |
23
+ | `exclude_patterns` | No | Patterns to exclude (e.g., `node_modules/`, `__pycache__/`, `.git/`) |
24
+ | `max_depth` | No | Maximum directory depth to traverse (default: 4) |
25
+
26
+ In a conversational context, the user provides these via the `context_loaders/repo_tree.py` and `context_loaders/project_summary.py` scripts, and pastes the output.
27
+
28
+ **If required input is missing:**
29
+ - `repo_path` — if no path is provided and no repo context has been pasted, ask: "Please paste the output of `python agents-maker/context_loaders/repo_tree.py` or describe your project's directory structure." Do not produce a summary from nothing.
30
+ - `filter_paths` — default to scanning the entire repo up to `max_depth`.
31
+ - `exclude_patterns` — default to excluding `node_modules/`, `__pycache__/`, `.git/`, `dist/`, `build/`.
32
+ - `max_depth` — default to 4.
33
+
34
+ ---
35
+
36
+ ## Output format
37
+
38
+ The skill produces a structured text block:
39
+
40
+ ```
41
+ ## Project Summary
42
+
43
+ **Stack**: <language(s), runtime version(s), primary framework(s)>
44
+ **Build tool**: <e.g., pip + setuptools, npm + webpack, gradle>
45
+ **Test framework**: <e.g., pytest, jest, JUnit>
46
+ **Containerization**: <Docker | none | Kubernetes manifests present>
47
+
48
+ ## Services / Modules
49
+
50
+ | Name | Path | Responsibility |
51
+ |---|---|---|
52
+ | <name> | <path> | <one-line description> |
53
+
54
+ ## Main Entrypoints
55
+
56
+ | File | Purpose |
57
+ |---|---|
58
+ | <path> | <what it starts or exports> |
59
+
60
+ ## Key Config Files
61
+
62
+ | File | Purpose |
63
+ |---|---|
64
+ | <path> | <what it configures> |
65
+
66
+ ## Test Structure
67
+
68
+ | Path | Type | Coverage scope |
69
+ |---|---|---|
70
+ | <path> | unit | integration | e2e | <scope> |
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Token cost tier
76
+
77
+ **Medium.** Involves reading file tree and inspecting key files. Output is typically 300–600 tokens.
78
+
79
+ Compression hint: the output is already compact. Do not summarize it further — it is the basis for all other context in the session.
80
+
81
+ ---
82
+
83
+ ## Notes
84
+
85
+ - This skill is implemented as `context_loaders/project_summary.py`. In agent sessions without tool access, the user runs it locally and pastes the output.
86
+ - If the repo has no recognizable structure, return the raw tree truncated at `max_depth` and note: "Could not detect stack — please specify language and framework manually."
@@ -126,11 +126,13 @@ class RelevanceFilter:
126
126
  See token_optimization/relevance_filter.md for the full scoring model.
127
127
  """
128
128
 
129
+ # Weights sum to 1.0. (A prior "recency" weight was removed: it had no data
130
+ # source and was hardwired to 0.0, so it silently contributed nothing; its
131
+ # budget is folded into lexical_overlap, the primary textual signal.)
129
132
  weights = {
130
- "lexical_overlap": 0.35,
133
+ "lexical_overlap": 0.45,
131
134
  "direct_reference": 0.30,
132
135
  "symbol_mention": 0.20,
133
- "recency": 0.10,
134
136
  "structural_importance": 0.05,
135
137
  }
136
138
 
@@ -177,15 +179,10 @@ class RelevanceFilter:
177
179
  symbol = self._symbol_mention(content_lower, raw_query)
178
180
  structural = 1.0 if any(p in path_lower for p in self._structural_patterns) else 0.0
179
181
 
180
- # recency: caller must set f.relevance_score > 0 before calling this method
181
- # to use it as a recency hint; otherwise defaults to 0.
182
- recency = 0.0
183
-
184
182
  return (
185
183
  self.weights["lexical_overlap"] * lexical
186
184
  + self.weights["direct_reference"] * direct
187
185
  + self.weights["symbol_mention"] * symbol
188
- + self.weights["recency"] * recency
189
186
  + self.weights["structural_importance"] * structural
190
187
  )
191
188
 
package/tools/_core.py ADDED
@@ -0,0 +1,102 @@
1
+ """
2
+ tools/_core.py
3
+
4
+ Shared low-level primitives for the agents-maker tools: YAML loading,
5
+ crash-safe atomic writes, source-hash computation, and shell-invocation
6
+ formatting. Centralizing these removes the copy-pasted helpers that were
7
+ previously duplicated across init_project.py, generate_prompt.py,
8
+ generate_claude_md.py, generate_platform_configs.py, and validate_kit.py.
9
+
10
+ Behavior is intentionally identical to the helpers it replaces.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ __version__ = "1.0.0"
16
+
17
+ import hashlib
18
+ import os
19
+ import sys
20
+ import tempfile
21
+ from pathlib import Path
22
+
23
+ # agents-maker/ (parent of tools/)
24
+ KIT_DIR = Path(__file__).resolve().parent.parent
25
+
26
+ try:
27
+ import yaml
28
+ except ImportError: # pragma: no cover - environment guard
29
+ print("[ERROR] pyyaml is required: pip install pyyaml", file=sys.stderr)
30
+ sys.exit(1)
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # YAML loading
35
+ # ---------------------------------------------------------------------------
36
+
37
+ def load_yaml(path: Path) -> dict:
38
+ """Load a YAML file, returning {} on missing file and logging parse/permission errors."""
39
+ try:
40
+ with open(path, encoding="utf-8") as f:
41
+ return yaml.safe_load(f) or {}
42
+ except FileNotFoundError:
43
+ return {}
44
+ except yaml.YAMLError as e:
45
+ print(f"[ERROR] YAML parse error in {path}: {e}", file=sys.stderr)
46
+ return {}
47
+ except PermissionError:
48
+ print(f"[ERROR] Permission denied reading {path}", file=sys.stderr)
49
+ return {}
50
+
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # Atomic writes (write to temp then os.replace — crash-safe)
54
+ # ---------------------------------------------------------------------------
55
+
56
+ def atomic_write(path: Path, content: str) -> None:
57
+ """Atomically write text to path (temp file + os.replace)."""
58
+ path.parent.mkdir(parents=True, exist_ok=True)
59
+ with tempfile.NamedTemporaryFile(
60
+ "w", dir=path.parent, delete=False, suffix=".tmp", encoding="utf-8"
61
+ ) as f:
62
+ f.write(content)
63
+ tmp = f.name
64
+ os.replace(tmp, path)
65
+
66
+
67
+ def atomic_write_yaml(path: Path, data: dict) -> None:
68
+ """Atomically dump a dict to a YAML file (temp file + os.replace)."""
69
+ path.parent.mkdir(parents=True, exist_ok=True)
70
+ with tempfile.NamedTemporaryFile(
71
+ "w", dir=path.parent, delete=False, suffix=".tmp", encoding="utf-8"
72
+ ) as f:
73
+ yaml.dump(data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
74
+ tmp = f.name
75
+ os.replace(tmp, path)
76
+
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # Source hash — lets validate_kit.py detect a stale system_prompt.md
80
+ # ---------------------------------------------------------------------------
81
+
82
+ def source_hash(kit_dir: Path = KIT_DIR) -> str:
83
+ """SHA-256 (first 16 hex chars) over all agent + skill markdown, line-ending normalized."""
84
+ h = hashlib.sha256()
85
+ agent_files = sorted((kit_dir / "agents").glob("*.md")) if (kit_dir / "agents").is_dir() else []
86
+ skill_files = sorted((kit_dir / "skills").glob("*.md")) if (kit_dir / "skills").is_dir() else []
87
+ for path in agent_files + skill_files:
88
+ try:
89
+ h.update(path.read_bytes().replace(b"\r\n", b"\n"))
90
+ except (OSError, PermissionError):
91
+ pass
92
+ return h.hexdigest()[:16]
93
+
94
+
95
+ # ---------------------------------------------------------------------------
96
+ # Shell-invocation formatting
97
+ # ---------------------------------------------------------------------------
98
+
99
+ def py_invocation(kit_rel: str, tool: str) -> str:
100
+ """Return a shell-safe 'python ...' invocation, quoting the path when it contains spaces."""
101
+ path = f"{kit_rel}/tools/{tool}"
102
+ return f'python "{path}"' if " " in path else f"python {path}"
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ compare_prompts.py — Proof-of-value harness for agents-maker.
4
+
5
+ For a given task it builds TWO prompts and runs BOTH through the same model,
6
+ so you can see what the structuring actually changes:
7
+
8
+ 1. NAIVE — the raw task, exactly as a user would type it into a chat box.
9
+ 2. STRUCTURED — the agents-maker prompt (system prompt + domain routing +
10
+ project context + Companion Mode), via generate_prompt.py --full.
11
+
12
+ Outputs (prompts + raw model responses) are written to examples/proof/<slug>/
13
+ so the comparison is reproducible and auditable — no cherry-picking.
14
+
15
+ Model runner
16
+ ------------
17
+ By default it shells out to the Claude Code CLI in non-interactive mode
18
+ (`claude -p`), which reuses your existing auth (no API key needed). Override
19
+ with --runner to use any command that reads a prompt on stdin and prints the
20
+ completion on stdout, e.g.:
21
+
22
+ python tools/compare_prompts.py "..." --runner "llm -m gpt-4o"
23
+
24
+ Usage
25
+ -----
26
+ python tools/compare_prompts.py "add rate limiting to the auth service"
27
+ python tools/compare_prompts.py "[domain: ops_process] write a Redis failover runbook"
28
+ python tools/compare_prompts.py "write a launch tweet" --skip-run # build prompts only
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import argparse
34
+ import json
35
+ import os
36
+ import re
37
+ import subprocess
38
+ import sys
39
+ import tempfile
40
+ import urllib.request
41
+ from pathlib import Path
42
+
43
+ SCRIPT_DIR = Path(__file__).resolve().parent
44
+ KIT_DIR = SCRIPT_DIR.parent
45
+ PROOF_DIR = KIT_DIR / "examples" / "proof"
46
+
47
+ _SEP = "=" * 60
48
+
49
+
50
+ def _slug(task: str) -> str:
51
+ s = re.sub(r"\[domain:[^\]]*\]", "", task).strip().lower()
52
+ s = re.sub(r"[^a-z0-9]+", "-", s).strip("-")
53
+ return (s[:48] or "task").rstrip("-")
54
+
55
+
56
+ def build_structured_prompt(task: str, full: bool = True) -> str:
57
+ """Run generate_prompt.py and extract the pasteable prompt body.
58
+
59
+ full=True inlines the whole system prompt (single-message / free-tier paste).
60
+ full=False returns only the context+task+routing block — use it as the `user`
61
+ message when the system prompt is supplied separately in the `system` field.
62
+ """
63
+ cmd = [sys.executable, str(SCRIPT_DIR / "generate_prompt.py"), task]
64
+ if full:
65
+ cmd.append("--full")
66
+ proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace")
67
+ if proc.returncode != 0:
68
+ raise RuntimeError(f"generate_prompt.py failed: {proc.stderr[:300]}")
69
+ # Output is: SEP / header / SEP / <body> / SEP / footer / SEP
70
+ parts = proc.stdout.split(_SEP)
71
+ if len(parts) < 4:
72
+ raise RuntimeError("Unexpected generate_prompt.py output format.")
73
+ return parts[2].strip()
74
+
75
+
76
+ def run_anthropic(system: str | None, user: str, model: str, max_tokens: int = 3000) -> str:
77
+ """Call the Anthropic Messages API with proper role separation. Key from env."""
78
+ key = os.environ.get("ANTHROPIC_API_KEY")
79
+ if not key:
80
+ raise RuntimeError("ANTHROPIC_API_KEY is not set.")
81
+ body: dict = {"model": model, "max_tokens": max_tokens, "messages": [{"role": "user", "content": user}]}
82
+ if system:
83
+ # Mark the (large, stable) system prompt as cacheable: the first call writes
84
+ # the cache, later calls read it at ~0.1x input cost.
85
+ body["system"] = [{"type": "text", "text": system, "cache_control": {"type": "ephemeral"}}]
86
+ req = urllib.request.Request(
87
+ "https://api.anthropic.com/v1/messages",
88
+ data=json.dumps(body).encode("utf-8"),
89
+ method="POST",
90
+ headers={"x-api-key": key, "anthropic-version": "2023-06-01", "content-type": "application/json"},
91
+ )
92
+ try:
93
+ with urllib.request.urlopen(req, timeout=180) as resp:
94
+ data = json.loads(resp.read().decode("utf-8"))
95
+ except urllib.error.HTTPError as e:
96
+ return f"[api error {e.code}] {e.read().decode('utf-8', 'replace')[:400]}"
97
+ u = data.get("usage", {})
98
+ print(
99
+ f" [usage] input={u.get('input_tokens',0)} "
100
+ f"cache_write={u.get('cache_creation_input_tokens',0)} "
101
+ f"cache_read={u.get('cache_read_input_tokens',0)} "
102
+ f"output={u.get('output_tokens',0)}",
103
+ file=sys.stderr,
104
+ )
105
+ return "".join(b.get("text", "") for b in data.get("content", [])).strip()
106
+
107
+
108
+ def run_model(prompt: str, runner: str, cwd: str) -> str:
109
+ """Feed a prompt to the model runner (stdin) and return the completion text.
110
+
111
+ Runs in an isolated empty `cwd` so an agent-style runner (e.g. claude -p)
112
+ answers the prompt as text instead of inspecting the current repository —
113
+ otherwise the comparison is confounded by whatever project it is run from.
114
+ """
115
+ cmd = runner.split() + ([] if runner.split()[0] != "claude" else ["-p"])
116
+ proc = subprocess.run(
117
+ cmd, input=prompt, capture_output=True, text=True,
118
+ encoding="utf-8", errors="replace", cwd=cwd,
119
+ )
120
+ if proc.returncode != 0:
121
+ return f"[runner error rc={proc.returncode}]\n{proc.stderr[:500]}"
122
+ return proc.stdout.strip()
123
+
124
+
125
+ def main() -> None:
126
+ sys.stdout.reconfigure(encoding="utf-8")
127
+ ap = argparse.ArgumentParser(description="Compare a naive vs agents-maker structured prompt on the same model.")
128
+ ap.add_argument("task", help="The task to test (supports a leading [domain: X] prefix).")
129
+ ap.add_argument("--runner", default="claude",
130
+ help='CLI mode: model command reading stdin, printing stdout (default: "claude" -> claude -p).')
131
+ ap.add_argument("--api", action="store_true",
132
+ help="Use the Anthropic Messages API with proper system/user role separation (needs ANTHROPIC_API_KEY).")
133
+ ap.add_argument("--model", default="claude-haiku-4-5-20251001", help="Model id for --api mode.")
134
+ ap.add_argument("--skip-run", action="store_true", help="Only build the two prompts; do not call the model.")
135
+ ap.add_argument("--out", default=None, help="Output directory (default: examples/proof/<slug>/).")
136
+ args = ap.parse_args()
137
+
138
+ out_dir = Path(args.out) if args.out else PROOF_DIR / _slug(args.task)
139
+ out_dir.mkdir(parents=True, exist_ok=True)
140
+
141
+ if args.api:
142
+ # Role-separated: task-scoped system prompt in `system`, task+context as `user`.
143
+ sp = subprocess.run(
144
+ [sys.executable, str(SCRIPT_DIR / "generate_prompt.py"), args.task, "--system-only"],
145
+ capture_output=True, text=True, encoding="utf-8", errors="replace",
146
+ )
147
+ if sp.returncode != 0:
148
+ raise RuntimeError(f"generate_prompt --system-only failed: {sp.stderr[:300]}")
149
+ system_prompt = sp.stdout.strip()
150
+ naive = args.task
151
+ structured_user = build_structured_prompt(args.task, full=False)
152
+ (out_dir / "naive_prompt.txt").write_text(naive + "\n", encoding="utf-8")
153
+ (out_dir / "structured_user.txt").write_text(structured_user + "\n", encoding="utf-8")
154
+ print(f"[built] {out_dir} (api mode, model={args.model})")
155
+ print(f" naive user: {len(naive)} chars | structured system: {len(system_prompt)} + user: {len(structured_user)} chars")
156
+ if args.skip_run:
157
+ print("[skip-run] prompts written; model not called.")
158
+ return
159
+ print("[run] naive (no system prompt)...")
160
+ naive_out = run_anthropic(None, naive, args.model)
161
+ (out_dir / "naive_output.md").write_text(naive_out + "\n", encoding="utf-8")
162
+ print("[run] structured (system=system_prompt.md, user=task+context)...")
163
+ structured_out = run_anthropic(system_prompt, structured_user, args.model)
164
+ (out_dir / "structured_output.md").write_text(structured_out + "\n", encoding="utf-8")
165
+ print(f"[done] outputs written to {out_dir}")
166
+ print(f" naive_output: {len(naive_out)} chars | structured_output: {len(structured_out)} chars")
167
+ return
168
+
169
+ naive = args.task
170
+ structured = build_structured_prompt(args.task, full=True)
171
+
172
+ (out_dir / "naive_prompt.txt").write_text(naive + "\n", encoding="utf-8")
173
+ (out_dir / "structured_prompt.txt").write_text(structured + "\n", encoding="utf-8")
174
+ print(f"[built] {out_dir}")
175
+ print(f" naive: {len(naive)} chars | structured: {len(structured)} chars")
176
+
177
+ if args.skip_run:
178
+ print("[skip-run] prompts written; model not called.")
179
+ return
180
+
181
+ sandbox = tempfile.mkdtemp(prefix="am-proof-")
182
+
183
+ print(f"[run] naive via '{args.runner}' (isolated cwd)...")
184
+ naive_out = run_model(naive, args.runner, sandbox)
185
+ (out_dir / "naive_output.md").write_text(naive_out + "\n", encoding="utf-8")
186
+
187
+ print(f"[run] structured via '{args.runner}' (isolated cwd)...")
188
+ structured_out = run_model(structured, args.runner, sandbox)
189
+ (out_dir / "structured_output.md").write_text(structured_out + "\n", encoding="utf-8")
190
+
191
+ print(f"[done] outputs written to {out_dir}")
192
+ print(f" naive_output: {len(naive_out)} chars | structured_output: {len(structured_out)} chars")
193
+
194
+
195
+ if __name__ == "__main__":
196
+ main()
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ generate_claude_agents.py — Emit Claude Code subagents + slash commands.
4
+
5
+ Turns the agents-maker roster into native Claude Code files so a user can clone
6
+ the kit into their project and immediately invoke named agents:
7
+
8
+ /brain brainstorm the whole project (options + trade-offs + recommendation)
9
+ /planpro produce the best-possible implementation plan
10
+ /architect /code /execute /ui /ux /review /orchestrate /compress
11
+
12
+ For each agent it writes:
13
+ <dest>/.claude/agents/<name>.md — subagent (frontmatter + the agent spec body)
14
+ <dest>/.claude/commands/<name>.md — slash command that invokes the subagent
15
+
16
+ Non-destructive by default: existing same-named files are skipped (never clobber
17
+ a user's own agents/commands). Use --force to overwrite.
18
+
19
+ Usage:
20
+ python agents-maker/tools/generate_claude_agents.py # -> <project>/.claude
21
+ python agents-maker/tools/generate_claude_agents.py --project /path # -> /path/.claude
22
+ python agents-maker/tools/generate_claude_agents.py --template # -> agents-maker/claude (shipped copy)
23
+ python agents-maker/tools/generate_claude_agents.py --dry-run
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ __version__ = "1.0.0"
29
+
30
+ import argparse
31
+ import sys
32
+ from pathlib import Path
33
+
34
+ SCRIPT_DIR = Path(__file__).resolve().parent # agents-maker/tools/
35
+ KIT_DIR = SCRIPT_DIR.parent # agents-maker/
36
+ sys.path.insert(0, str(KIT_DIR))
37
+
38
+ try:
39
+ from tools._core import atomic_write, load_yaml
40
+ except ImportError:
41
+ from _core import atomic_write, load_yaml
42
+
43
+ # Full autonomy: agents may read, edit, write, and run commands.
44
+ AGENT_TOOLS = "Read, Grep, Glob, Edit, Write, Bash"
45
+
46
+ # command name -> agent id (filename stem in agents/). The command/subagent use
47
+ # the short, clean name; the body comes from the (possibly _agent-suffixed) spec.
48
+ COMMANDS: dict[str, str] = {
49
+ "brain": "brain",
50
+ "planpro": "planpro",
51
+ "orchestrate": "orchestrator",
52
+ "architect": "architect_agent",
53
+ "code": "code_agent",
54
+ "execute": "execution_agent",
55
+ "ui": "ui_agent",
56
+ "ux": "ux_agent",
57
+ "review": "reviewer_agent",
58
+ "compress": "compression_agent",
59
+ }
60
+
61
+
62
+ def _flatten(text: str, limit: int = 300) -> str:
63
+ """Collapse whitespace to a single line (YAML-frontmatter-safe) and cap length."""
64
+ one = " ".join(text.split())
65
+ return one[: limit - 1] + "…" if len(one) > limit else one
66
+
67
+
68
+ def _description(agent_id: str, agents_cfg: dict) -> str:
69
+ desc = _flatten(str(agents_cfg.get(agent_id, {}).get("description", "")))
70
+ return desc or f"agents-maker {agent_id} agent"
71
+
72
+
73
+ def build_subagent(cmd: str, agent_id: str, agents_cfg: dict, kit_dir: Path) -> str:
74
+ body = (kit_dir / "agents" / f"{agent_id}.md").read_text(encoding="utf-8").strip()
75
+ return (
76
+ "---\n"
77
+ f"name: {cmd}\n"
78
+ f"description: {_description(agent_id, agents_cfg)}\n"
79
+ f"tools: {AGENT_TOOLS}\n"
80
+ "model: inherit\n"
81
+ "---\n\n"
82
+ f"{body}\n"
83
+ )
84
+
85
+
86
+ def build_command(cmd: str, agent_id: str, agents_cfg: dict) -> str:
87
+ return (
88
+ "---\n"
89
+ f"description: {_description(agent_id, agents_cfg)}\n"
90
+ "---\n"
91
+ f"# /{cmd}\n\n"
92
+ "$ARGUMENTS\n\n"
93
+ "## Task\n"
94
+ f"Use the `{cmd}` subagent (agents-maker) to handle the request above.\n\n"
95
+ "CONTEXT:\n"
96
+ "- Project config, if present: `agents-maker/config/project.yaml` — read it for "
97
+ "domain, stack, and constraints before acting.\n"
98
+ "- User request: $ARGUMENTS\n\n"
99
+ f"Follow the `{cmd}` agent's output contract. If the request is a self-contained "
100
+ "task, deliver the finished artifact directly (Direct Task Mode) — do not ask for "
101
+ "project context you can infer. End with the [Companion] next-steps block.\n"
102
+ )
103
+
104
+
105
+ def generate(dest_claude: Path, kit_dir: Path, *, force: bool = False, dry_run: bool = False) -> tuple[list[str], list[str]]:
106
+ """Write subagents + commands under dest_claude. Returns (written, skipped) rel paths."""
107
+ agents_cfg = load_yaml(kit_dir / "config" / "agents.yaml").get("agents", {})
108
+ written: list[str] = []
109
+ skipped: list[str] = []
110
+
111
+ for cmd, agent_id in COMMANDS.items():
112
+ targets = [
113
+ (dest_claude / "agents" / f"{cmd}.md", build_subagent(cmd, agent_id, agents_cfg, kit_dir)),
114
+ (dest_claude / "commands" / f"{cmd}.md", build_command(cmd, agent_id, agents_cfg)),
115
+ ]
116
+ for path, content in targets:
117
+ rel = f"{path.parent.name}/{path.name}"
118
+ if path.exists() and not force:
119
+ skipped.append(rel)
120
+ continue
121
+ if dry_run:
122
+ written.append(rel)
123
+ continue
124
+ atomic_write(path, content)
125
+ written.append(rel)
126
+
127
+ return written, skipped
128
+
129
+
130
+ def main() -> None:
131
+ sys.stdout.reconfigure(encoding="utf-8")
132
+ ap = argparse.ArgumentParser(description="Generate Claude Code subagents + slash commands from the agents-maker roster.")
133
+ ap.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
134
+ ap.add_argument("--project", help="Project root; writes <root>/.claude/ (default: parent of agents-maker/).")
135
+ ap.add_argument("--template", action="store_true", help="Write the shipped template copy to agents-maker/claude/ instead of a project's .claude/.")
136
+ ap.add_argument("--force", action="store_true", help="Overwrite existing files (default: skip existing).")
137
+ ap.add_argument("--dry-run", action="store_true", help="List what would be written without writing.")
138
+ args = ap.parse_args()
139
+
140
+ if args.template:
141
+ dest = KIT_DIR / "claude"
142
+ else:
143
+ root = Path(args.project).resolve() if args.project else KIT_DIR.parent
144
+ dest = root / ".claude"
145
+
146
+ written, skipped = generate(dest, KIT_DIR, force=args.force, dry_run=args.dry_run)
147
+
148
+ tag = "[dry-run] would write" if args.dry_run else "wrote"
149
+ print(f"{tag} {len(written)} file(s) under {dest}")
150
+ for r in written:
151
+ print(f" + {r}")
152
+ if skipped:
153
+ print(f"skipped {len(skipped)} existing file(s) (use --force to overwrite):")
154
+ for r in skipped:
155
+ print(f" = {r}")
156
+ if not args.dry_run and not args.template:
157
+ cmds = ", ".join(f"/{c}" for c in COMMANDS)
158
+ print(f"\nCommands available in this project: {cmds}")
159
+
160
+
161
+ if __name__ == "__main__":
162
+ main()