@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.
- package/PROMPT_TEMPLATE.md +143 -0
- package/README.md +92 -25
- package/agents/brain.md +90 -0
- package/agents/orchestrator.md +24 -3
- package/agents/planpro.md +91 -0
- package/bin/cli.js +40 -4
- package/claude/agents/architect.md +182 -0
- package/claude/agents/brain.md +97 -0
- package/claude/agents/code.md +185 -0
- package/claude/agents/compress.md +233 -0
- package/claude/agents/execute.md +164 -0
- package/claude/agents/orchestrate.md +434 -0
- package/claude/agents/planpro.md +98 -0
- package/claude/agents/review.md +141 -0
- package/claude/agents/ui.md +154 -0
- package/claude/agents/ux.md +151 -0
- package/claude/commands/architect.md +15 -0
- package/claude/commands/brain.md +15 -0
- package/claude/commands/code.md +15 -0
- package/claude/commands/compress.md +15 -0
- package/claude/commands/execute.md +15 -0
- package/claude/commands/orchestrate.md +15 -0
- package/claude/commands/planpro.md +15 -0
- package/claude/commands/review.md +15 -0
- package/claude/commands/ui.md +15 -0
- package/claude/commands/ux.md +15 -0
- package/config/agents.yaml +56 -0
- package/context_loaders/project_summary.py +5 -0
- package/docs/architecture.md +318 -0
- package/docs/domains.md +154 -0
- package/docs/workflows.md +548 -0
- package/examples/generic_project_lifecycle.md +518 -0
- package/examples/proof/README.md +138 -0
- package/examples/proof/implement-a-python-function-that-parses-an-iso-8/grade.md +14 -0
- package/examples/proof/implement-a-python-function-that-parses-an-iso-8/naive_output.md +211 -0
- package/examples/proof/implement-a-python-function-that-parses-an-iso-8/naive_prompt.txt +1 -0
- package/examples/proof/implement-a-python-function-that-parses-an-iso-8/structured_output.md +210 -0
- package/examples/proof/implement-a-python-function-that-parses-an-iso-8/structured_user.txt +35 -0
- package/examples/proof/write-a-runbook-for-recovering-from-a-redis-prim/grade.md +14 -0
- package/examples/proof/write-a-runbook-for-recovering-from-a-redis-prim/naive_output.md +274 -0
- package/examples/proof/write-a-runbook-for-recovering-from-a-redis-prim/naive_prompt.txt +1 -0
- package/examples/proof/write-a-runbook-for-recovering-from-a-redis-prim/structured_output.md +127 -0
- package/examples/proof/write-a-runbook-for-recovering-from-a-redis-prim/structured_user.txt +35 -0
- package/package.json +10 -2
- package/platforms/antigravity.md +195 -0
- package/platforms/claude.md +305 -0
- package/platforms/openai.md +333 -0
- package/skills/analyze_repo.md +86 -86
- package/token_optimization/compressor.py +4 -7
- package/tools/_core.py +102 -0
- package/tools/compare_prompts.py +196 -0
- package/tools/generate_claude_agents.py +162 -0
- package/tools/generate_claude_md.py +14 -80
- package/tools/generate_platform_configs.py +26 -36
- package/tools/generate_prompt.py +79 -79
- package/tools/grade_proof.py +118 -0
- package/tools/init_project.py +11 -58
- package/tools/routing.py +103 -0
- package/tools/test_kit.py +392 -363
- package/tools/validate_kit.py +15 -43
- package/context_loaders/__pycache__/__init__.cpython-314.pyc +0 -0
- package/context_loaders/__pycache__/file_chunker.cpython-314.pyc +0 -0
- package/context_loaders/__pycache__/project_summary.cpython-314.pyc +0 -0
- package/context_loaders/__pycache__/repo_tree.cpython-314.pyc +0 -0
- package/token_optimization/__pycache__/compressor.cpython-314.pyc +0 -0
- package/tools/__pycache__/domain_utils.cpython-314.pyc +0 -0
- package/tools/__pycache__/generate_claude_md.cpython-314.pyc +0 -0
- package/tools/__pycache__/validate_kit.cpython-314.pyc +0 -0
|
@@ -14,10 +14,8 @@ Usage:
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import argparse
|
|
17
|
-
import os
|
|
18
17
|
import re
|
|
19
18
|
import sys
|
|
20
|
-
import tempfile
|
|
21
19
|
from pathlib import Path
|
|
22
20
|
|
|
23
21
|
__version__ = "1.0.0"
|
|
@@ -27,25 +25,21 @@ KIT_DIR = SCRIPT_DIR.parent # agents-maker/
|
|
|
27
25
|
sys.path.insert(0, str(KIT_DIR))
|
|
28
26
|
|
|
29
27
|
try:
|
|
30
|
-
import
|
|
28
|
+
from tools._core import atomic_write, load_yaml, py_invocation
|
|
29
|
+
from tools.routing import AGENT_ROLES, PHASE_AGENTS, PHASE_LABELS, phase_agents
|
|
31
30
|
except ImportError:
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
from _core import atomic_write, load_yaml, py_invocation
|
|
32
|
+
from routing import AGENT_ROLES, PHASE_AGENTS, PHASE_LABELS, phase_agents
|
|
33
|
+
|
|
34
|
+
# Backward-compatible aliases (generate_platform_configs imports these names).
|
|
35
|
+
_PHASE_AGENTS = PHASE_AGENTS
|
|
36
|
+
_PHASE_LABELS = PHASE_LABELS
|
|
37
|
+
_AGENT_ROLES = AGENT_ROLES
|
|
34
38
|
|
|
35
39
|
# ---------------------------------------------------------------------------
|
|
36
40
|
# Helpers
|
|
37
41
|
# ---------------------------------------------------------------------------
|
|
38
42
|
|
|
39
|
-
def _load_yaml(path: Path) -> dict:
|
|
40
|
-
if not path.exists():
|
|
41
|
-
return {}
|
|
42
|
-
try:
|
|
43
|
-
with open(path, encoding="utf-8") as f:
|
|
44
|
-
return yaml.safe_load(f) or {}
|
|
45
|
-
except Exception:
|
|
46
|
-
return {}
|
|
47
|
-
|
|
48
|
-
|
|
49
43
|
def _parse_phase(state_text: str) -> str:
|
|
50
44
|
m = re.search(r"##\s+Current Phase\s*\n+(\S+)", state_text)
|
|
51
45
|
if m:
|
|
@@ -53,65 +47,8 @@ def _parse_phase(state_text: str) -> str:
|
|
|
53
47
|
return "task_framing"
|
|
54
48
|
|
|
55
49
|
|
|
56
|
-
# Phase → domain → active agents mapping (derived from agents.yaml lifecycle)
|
|
57
|
-
_PHASE_AGENTS: dict[str, dict[str, list[str]]] = {
|
|
58
|
-
"task_framing": {"_all": ["orchestrator"]},
|
|
59
|
-
"requirements": {"_all": ["orchestrator", "architect_agent"]},
|
|
60
|
-
"solution_design": {
|
|
61
|
-
"_all": ["architect_agent"],
|
|
62
|
-
"software": ["architect_agent", "ui_agent"],
|
|
63
|
-
"product_design":["architect_agent", "ui_agent", "ux_agent"],
|
|
64
|
-
"marketing": ["architect_agent", "ux_agent"],
|
|
65
|
-
},
|
|
66
|
-
"implementation": {
|
|
67
|
-
"_all": ["execution_agent"],
|
|
68
|
-
"software": ["code_agent"],
|
|
69
|
-
"data_analytics":["code_agent"],
|
|
70
|
-
},
|
|
71
|
-
"review_refinement":{
|
|
72
|
-
"_all": ["reviewer_agent"],
|
|
73
|
-
"software": ["reviewer_agent", "code_agent"],
|
|
74
|
-
"product_design":["reviewer_agent", "ui_agent", "ux_agent"],
|
|
75
|
-
"marketing": ["reviewer_agent", "ux_agent"],
|
|
76
|
-
},
|
|
77
|
-
"handoff": {"_all": ["orchestrator", "execution_agent"]},
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
_PHASE_LABELS: dict[str, str] = {
|
|
81
|
-
"task_framing": "Task Framing",
|
|
82
|
-
"requirements": "Requirements",
|
|
83
|
-
"solution_design": "Solution Design",
|
|
84
|
-
"implementation": "Implementation",
|
|
85
|
-
"review_refinement":"Review & Refinement",
|
|
86
|
-
"handoff": "Handoff",
|
|
87
|
-
# aliases accepted by generate_prompt.py
|
|
88
|
-
"framing": "Task Framing",
|
|
89
|
-
"design": "Solution Design",
|
|
90
|
-
"implement": "Implementation",
|
|
91
|
-
"review": "Review & Refinement",
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
_AGENT_ROLES: dict[str, str] = {
|
|
95
|
-
"orchestrator": "routing",
|
|
96
|
-
"architect_agent": "design",
|
|
97
|
-
"code_agent": "implementation",
|
|
98
|
-
"execution_agent": "execution",
|
|
99
|
-
"ui_agent": "UI",
|
|
100
|
-
"ux_agent": "UX",
|
|
101
|
-
"reviewer_agent": "QA",
|
|
102
|
-
"compression_agent": "compression",
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def _py(kit_rel: str, tool: str) -> str:
|
|
107
|
-
"""Return a shell-safe 'python ...' invocation. Quotes the full path when it contains spaces."""
|
|
108
|
-
path = f"{kit_rel}/tools/{tool}"
|
|
109
|
-
return f'python "{path}"' if " " in path else f"python {path}"
|
|
110
|
-
|
|
111
|
-
|
|
112
50
|
def _active_agents(domain: str, phase: str) -> list[str]:
|
|
113
|
-
|
|
114
|
-
return phase_map.get(domain, phase_map["_all"])
|
|
51
|
+
return phase_agents(phase, domain)
|
|
115
52
|
|
|
116
53
|
|
|
117
54
|
# ---------------------------------------------------------------------------
|
|
@@ -132,8 +69,8 @@ def build_claude_md(
|
|
|
132
69
|
agent_list = ", ".join(
|
|
133
70
|
f"{a} ({_AGENT_ROLES.get(a, 'specialist')})" for a in agents
|
|
134
71
|
)
|
|
135
|
-
regen_cmd =
|
|
136
|
-
prompt_cmd =
|
|
72
|
+
regen_cmd = py_invocation(kit_rel_path, "generate_claude_md.py")
|
|
73
|
+
prompt_cmd = py_invocation(kit_rel_path, "generate_prompt.py")
|
|
137
74
|
|
|
138
75
|
return (
|
|
139
76
|
f"# agents-maker — Project AI Config\n"
|
|
@@ -207,7 +144,7 @@ def main() -> None:
|
|
|
207
144
|
sys.exit(1)
|
|
208
145
|
|
|
209
146
|
# Load project config
|
|
210
|
-
project_cfg =
|
|
147
|
+
project_cfg = load_yaml(KIT_DIR / "config" / "project.yaml")
|
|
211
148
|
if not project_cfg:
|
|
212
149
|
print(
|
|
213
150
|
"[WARN] config/project.yaml not found — run init_project.py first.",
|
|
@@ -250,10 +187,7 @@ def main() -> None:
|
|
|
250
187
|
return
|
|
251
188
|
|
|
252
189
|
out_path = project_root / "CLAUDE.md"
|
|
253
|
-
|
|
254
|
-
f.write(content)
|
|
255
|
-
tmp = f.name
|
|
256
|
-
os.replace(tmp, out_path)
|
|
190
|
+
atomic_write(out_path, content)
|
|
257
191
|
|
|
258
192
|
print(f"\nWritten: {out_path}")
|
|
259
193
|
print(f" Domain : {domain} (confidence: {confidence})")
|
|
@@ -24,9 +24,7 @@ from __future__ import annotations
|
|
|
24
24
|
__version__ = "1.0.0"
|
|
25
25
|
|
|
26
26
|
import argparse
|
|
27
|
-
import os
|
|
28
27
|
import sys
|
|
29
|
-
import tempfile
|
|
30
28
|
from pathlib import Path
|
|
31
29
|
|
|
32
30
|
SCRIPT_DIR = Path(__file__).resolve().parent # agents-maker/tools/
|
|
@@ -34,12 +32,7 @@ KIT_DIR = SCRIPT_DIR.parent # agents-maker/
|
|
|
34
32
|
sys.path.insert(0, str(KIT_DIR))
|
|
35
33
|
|
|
36
34
|
try:
|
|
37
|
-
|
|
38
|
-
except ImportError:
|
|
39
|
-
print("[ERROR] pyyaml is required: pip install pyyaml", file=sys.stderr)
|
|
40
|
-
sys.exit(1)
|
|
41
|
-
|
|
42
|
-
try:
|
|
35
|
+
from tools._core import atomic_write, load_yaml, py_invocation
|
|
43
36
|
from tools.generate_claude_md import (
|
|
44
37
|
_AGENT_ROLES,
|
|
45
38
|
_PHASE_AGENTS,
|
|
@@ -48,6 +41,7 @@ try:
|
|
|
48
41
|
build_claude_md,
|
|
49
42
|
)
|
|
50
43
|
except ImportError:
|
|
44
|
+
from _core import atomic_write, load_yaml, py_invocation
|
|
51
45
|
from generate_claude_md import (
|
|
52
46
|
_AGENT_ROLES,
|
|
53
47
|
_PHASE_AGENTS,
|
|
@@ -56,12 +50,7 @@ except ImportError:
|
|
|
56
50
|
build_claude_md,
|
|
57
51
|
)
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
from tools.domain_utils import _load_yaml
|
|
61
|
-
except ImportError:
|
|
62
|
-
from domain_utils import _load_yaml
|
|
63
|
-
|
|
64
|
-
PLATFORMS = ["claude", "copilot", "cursor", "antigravity"]
|
|
53
|
+
PLATFORMS = ["claude", "claude_agents", "copilot", "cursor", "antigravity"]
|
|
65
54
|
|
|
66
55
|
# ---------------------------------------------------------------------------
|
|
67
56
|
# Helpers shared across builders
|
|
@@ -76,12 +65,6 @@ def _agent_list_str(agents: list[str]) -> str:
|
|
|
76
65
|
return ", ".join(f"{a} ({_AGENT_ROLES.get(a, 'specialist')})" for a in agents)
|
|
77
66
|
|
|
78
67
|
|
|
79
|
-
def _py(kit_rel: str, tool: str) -> str:
|
|
80
|
-
"""Return a shell-safe 'python ...' invocation. Quotes the full path when it contains spaces."""
|
|
81
|
-
path = f"{kit_rel}/tools/{tool}"
|
|
82
|
-
return f'python "{path}"' if " " in path else f"python {path}"
|
|
83
|
-
|
|
84
|
-
|
|
85
68
|
def _yaml_str(value: str) -> str:
|
|
86
69
|
"""Return a YAML-safe scalar: quoted if it contains spaces or YAML special characters."""
|
|
87
70
|
if " " in value or any(c in value for c in ":{}[]#&*!|>'\"%@`"):
|
|
@@ -89,16 +72,6 @@ def _yaml_str(value: str) -> str:
|
|
|
89
72
|
return value
|
|
90
73
|
|
|
91
74
|
|
|
92
|
-
def _atomic_write(path: Path, content: str) -> None:
|
|
93
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
94
|
-
with tempfile.NamedTemporaryFile(
|
|
95
|
-
"w", dir=path.parent, delete=False, suffix=".tmp", encoding="utf-8"
|
|
96
|
-
) as f:
|
|
97
|
-
f.write(content)
|
|
98
|
-
tmp = f.name
|
|
99
|
-
os.replace(tmp, path)
|
|
100
|
-
|
|
101
|
-
|
|
102
75
|
# ---------------------------------------------------------------------------
|
|
103
76
|
# Builder: GitHub Copilot — .github/copilot-instructions.md
|
|
104
77
|
# ---------------------------------------------------------------------------
|
|
@@ -114,7 +87,7 @@ def build_copilot_md(
|
|
|
114
87
|
stack_str = ", ".join(stack) if stack else "unknown"
|
|
115
88
|
phase_label = _PHASE_LABELS.get(phase, phase)
|
|
116
89
|
agents = _active_agents(domain, phase)
|
|
117
|
-
regen_cmd =
|
|
90
|
+
regen_cmd = py_invocation(kit_rel_path, "generate_platform_configs.py")
|
|
118
91
|
|
|
119
92
|
all_agents = [
|
|
120
93
|
"orchestrator (routing — always active)",
|
|
@@ -173,7 +146,7 @@ def build_cursor_rules(
|
|
|
173
146
|
stack_str = ", ".join(stack) if stack else "unknown"
|
|
174
147
|
phase_label = _PHASE_LABELS.get(phase, phase)
|
|
175
148
|
agents = _active_agents(domain, phase)
|
|
176
|
-
regen_cmd =
|
|
149
|
+
regen_cmd = py_invocation(kit_rel_path, "generate_platform_configs.py")
|
|
177
150
|
|
|
178
151
|
return (
|
|
179
152
|
f"# agents-maker — Cursor Rules\n"
|
|
@@ -219,6 +192,8 @@ _AGENT_DESCRIPTIONS: dict[str, str] = {
|
|
|
219
192
|
"ux_agent": "Flow critique, onboarding sequences, funnel analysis, friction ID",
|
|
220
193
|
"reviewer_agent": "Severity-rated QA review for any domain (CRITICAL/HIGH/MEDIUM/LOW)",
|
|
221
194
|
"compression_agent": "Token budget enforcement, context compression, cross-session resumption",
|
|
195
|
+
"brain": "Project brainstorming — 3+ approaches with trade-offs + a recommendation",
|
|
196
|
+
"planpro": "Implementation planning — short, specific, dependency-ordered plan file",
|
|
222
197
|
}
|
|
223
198
|
|
|
224
199
|
_AGENT_PHASES: dict[str, list[str]] = {
|
|
@@ -230,6 +205,8 @@ _AGENT_PHASES: dict[str, list[str]] = {
|
|
|
230
205
|
"ux_agent": ["solution_design", "review_refinement"],
|
|
231
206
|
"reviewer_agent": ["review_refinement"],
|
|
232
207
|
"compression_agent": ["handoff"],
|
|
208
|
+
"brain": ["task_framing", "solution_design"],
|
|
209
|
+
"planpro": ["task_framing", "requirements", "solution_design"],
|
|
233
210
|
}
|
|
234
211
|
|
|
235
212
|
_AGENT_DOMAINS: dict[str, list[str]] = {
|
|
@@ -241,10 +218,12 @@ _AGENT_DOMAINS: dict[str, list[str]] = {
|
|
|
241
218
|
"ux_agent": ["product_design", "marketing"],
|
|
242
219
|
"reviewer_agent": ["software", "content", "research", "data_analytics", "product_design", "marketing", "ops_process", "general"],
|
|
243
220
|
"compression_agent": ["software", "content", "research", "data_analytics", "product_design", "marketing", "ops_process", "general"],
|
|
221
|
+
"brain": ["software", "content", "research", "data_analytics", "product_design", "marketing", "ops_process", "general"],
|
|
222
|
+
"planpro": ["software", "content", "research", "data_analytics", "product_design", "marketing", "ops_process", "general"],
|
|
244
223
|
}
|
|
245
224
|
|
|
246
225
|
_AGENT_ORDER = [
|
|
247
|
-
"orchestrator", "architect_agent", "code_agent", "execution_agent",
|
|
226
|
+
"orchestrator", "brain", "planpro", "architect_agent", "code_agent", "execution_agent",
|
|
248
227
|
"ui_agent", "ux_agent", "reviewer_agent", "compression_agent",
|
|
249
228
|
]
|
|
250
229
|
|
|
@@ -256,7 +235,7 @@ def build_agkit_yaml(
|
|
|
256
235
|
phase: str,
|
|
257
236
|
kit_rel_path: str,
|
|
258
237
|
) -> str:
|
|
259
|
-
regen_cmd =
|
|
238
|
+
regen_cmd = py_invocation(kit_rel_path, "generate_platform_configs.py")
|
|
260
239
|
stack_list = stack if stack else ["unknown"]
|
|
261
240
|
|
|
262
241
|
lines: list[str] = [
|
|
@@ -346,7 +325,7 @@ def generate_all(
|
|
|
346
325
|
platforms: list[str],
|
|
347
326
|
dry_run: bool,
|
|
348
327
|
) -> None:
|
|
349
|
-
project_cfg =
|
|
328
|
+
project_cfg = load_yaml(kit_dir / "config" / "project.yaml")
|
|
350
329
|
if not project_cfg:
|
|
351
330
|
print("[WARN] config/project.yaml not found — run init_project.py first.", file=sys.stderr)
|
|
352
331
|
|
|
@@ -384,6 +363,17 @@ def generate_all(
|
|
|
384
363
|
content = build_agkit_yaml(project_name, domain, stack, phase, kit_rel_path)
|
|
385
364
|
builders["antigravity"] = (_PLATFORM_PATHS["antigravity"], content)
|
|
386
365
|
|
|
366
|
+
# Claude Code subagents + slash commands (.claude/agents, .claude/commands).
|
|
367
|
+
if "claude_agents" in platforms:
|
|
368
|
+
try:
|
|
369
|
+
from tools.generate_claude_agents import generate as _gen_claude
|
|
370
|
+
except ImportError:
|
|
371
|
+
from generate_claude_agents import generate as _gen_claude
|
|
372
|
+
cw, cs = _gen_claude(project_root / ".claude", kit_dir, force=False, dry_run=dry_run)
|
|
373
|
+
tag = "[dry-run] would write" if dry_run else " [DONE]"
|
|
374
|
+
print(f"{tag} {len(cw)} .claude/ file(s) — subagents + slash commands"
|
|
375
|
+
+ (f"; kept {len(cs)} existing" if cs else ""))
|
|
376
|
+
|
|
387
377
|
if dry_run:
|
|
388
378
|
for platform, (rel_path, content) in builders.items():
|
|
389
379
|
print(f"\n{'='*60}")
|
|
@@ -397,7 +387,7 @@ def generate_all(
|
|
|
397
387
|
for platform, (rel_path, content) in builders.items():
|
|
398
388
|
out_path = project_root / rel_path
|
|
399
389
|
try:
|
|
400
|
-
|
|
390
|
+
atomic_write(out_path, content)
|
|
401
391
|
print(f" [DONE] {rel_path} ({platform})")
|
|
402
392
|
written.append(rel_path)
|
|
403
393
|
except OSError as e:
|
package/tools/generate_prompt.py
CHANGED
|
@@ -21,10 +21,8 @@ Usage:
|
|
|
21
21
|
from __future__ import annotations
|
|
22
22
|
|
|
23
23
|
import argparse
|
|
24
|
-
import os
|
|
25
24
|
import re
|
|
26
25
|
import sys
|
|
27
|
-
import tempfile
|
|
28
26
|
from datetime import date
|
|
29
27
|
from pathlib import Path
|
|
30
28
|
|
|
@@ -36,12 +34,6 @@ SCRIPT_DIR = Path(__file__).resolve().parent # agents-maker/tools/
|
|
|
36
34
|
KIT_DIR = SCRIPT_DIR.parent # agents-maker/
|
|
37
35
|
sys.path.insert(0, str(KIT_DIR))
|
|
38
36
|
|
|
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
37
|
__version__ = "1.0.0"
|
|
46
38
|
|
|
47
39
|
MAX_PROBLEM_LENGTH = 5000
|
|
@@ -51,11 +43,13 @@ MAX_PROBLEM_LENGTH = 5000
|
|
|
51
43
|
# ---------------------------------------------------------------------------
|
|
52
44
|
|
|
53
45
|
try:
|
|
54
|
-
from tools.
|
|
46
|
+
from tools._core import atomic_write_yaml, load_yaml
|
|
55
47
|
from tools.domain_utils import detect_domain as _du_detect
|
|
48
|
+
from tools.routing import active_agents, domain_agents
|
|
56
49
|
except ImportError:
|
|
57
|
-
from
|
|
50
|
+
from _core import atomic_write_yaml, load_yaml
|
|
58
51
|
from domain_utils import detect_domain as _du_detect
|
|
52
|
+
from routing import active_agents, domain_agents
|
|
59
53
|
|
|
60
54
|
|
|
61
55
|
def detect_domain(problem: str) -> tuple[str, str, float]:
|
|
@@ -118,53 +112,14 @@ def infer_phase(state_path: Path) -> str:
|
|
|
118
112
|
# Agent selection per phase × domain
|
|
119
113
|
# ---------------------------------------------------------------------------
|
|
120
114
|
|
|
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
115
|
def select_agents(phase: str, domain: str) -> list[str]:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
116
|
+
# Routing is centralized in tools/routing.py (single source of truth) so the
|
|
117
|
+
# generated prompt, CLAUDE.md, and platform configs all agree.
|
|
118
|
+
return active_agents(phase, domain)
|
|
164
119
|
|
|
165
120
|
|
|
166
121
|
def select_skills(agents: list[str]) -> list[str]:
|
|
167
|
-
agents_cfg =
|
|
122
|
+
agents_cfg = load_yaml(KIT_DIR / "config" / "agents.yaml").get("agents", {})
|
|
168
123
|
seen: set[str] = set()
|
|
169
124
|
skills: list[str] = []
|
|
170
125
|
for agent in agents:
|
|
@@ -199,6 +154,42 @@ def load_skill_md(skill_key: str) -> str:
|
|
|
199
154
|
return ""
|
|
200
155
|
|
|
201
156
|
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
# Task-scoped system prompt (lazy-load: only the routed agents + skills)
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
COMPANION_DIRECTIVE = (
|
|
162
|
+
"# agents-maker — active kit (task-scoped)\n"
|
|
163
|
+
"You are operating under the agents-maker system below, loaded with only the "
|
|
164
|
+
"agents and skills routed for this task. The Orchestrator leads.\n"
|
|
165
|
+
"After every response append a [Companion] block:\n"
|
|
166
|
+
" ---\n"
|
|
167
|
+
" [Companion] Phase: <phase> | Domain: <domain> | Est. token budget used: ~N%\n"
|
|
168
|
+
" What to do next (pick one):\n"
|
|
169
|
+
" [Recommended] A: <action> Command: python agents-maker/tools/generate_prompt.py \"...\"\n"
|
|
170
|
+
" B: <action>\n"
|
|
171
|
+
" C: <action>\n"
|
|
172
|
+
" ---\n"
|
|
173
|
+
"In Direct Task Mode (a single self-contained task) deliver the finished artifact "
|
|
174
|
+
"first, then the [Companion] block — do not open the phase lifecycle."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def build_scoped_system(agents: list[str], skills: list[str]) -> str:
|
|
179
|
+
"""Assemble a task-scoped system prompt: companion directive + only the routed
|
|
180
|
+
agent and skill specs. Far smaller than the full 8-agent / 12-skill system_prompt.md."""
|
|
181
|
+
parts: list[str] = [COMPANION_DIRECTIVE, "=" * 60]
|
|
182
|
+
for agent in agents:
|
|
183
|
+
parts.append(load_agent_md(agent))
|
|
184
|
+
parts.append("---")
|
|
185
|
+
for skill in skills:
|
|
186
|
+
content = load_skill_md(skill)
|
|
187
|
+
if content:
|
|
188
|
+
parts.append(content)
|
|
189
|
+
parts.append("---")
|
|
190
|
+
return "\n\n".join(parts)
|
|
191
|
+
|
|
192
|
+
|
|
202
193
|
# ---------------------------------------------------------------------------
|
|
203
194
|
# Token estimation
|
|
204
195
|
# ---------------------------------------------------------------------------
|
|
@@ -223,28 +214,16 @@ def build_prompt(
|
|
|
223
214
|
project_cfg: dict,
|
|
224
215
|
state_text: str,
|
|
225
216
|
include_system: bool = False,
|
|
217
|
+
system_agents: list[str] | None = None,
|
|
218
|
+
system_skills: list[str] | None = None,
|
|
226
219
|
) -> str:
|
|
227
220
|
parts: list[str] = []
|
|
228
221
|
|
|
229
222
|
if include_system:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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("---")
|
|
223
|
+
# Task-scoped: inline only the domain's routed agents + skills (not the full
|
|
224
|
+
# 8-agent / 12-skill system_prompt.md) to keep token cost low.
|
|
225
|
+
parts.append(build_scoped_system(system_agents or agents, system_skills or skills))
|
|
226
|
+
parts.append("\n" + "=" * 60 + "\n")
|
|
248
227
|
|
|
249
228
|
project_name = project_cfg.get("project_name", "unknown")
|
|
250
229
|
proj_domain = project_cfg.get("primary_domain", domain)
|
|
@@ -266,7 +245,13 @@ def build_prompt(
|
|
|
266
245
|
session_num = project_cfg.get("session_count", 0) + 1
|
|
267
246
|
parts.append(f"## Session State\nSession {session_num} — starting fresh")
|
|
268
247
|
|
|
269
|
-
parts.append(
|
|
248
|
+
parts.append(
|
|
249
|
+
f"## Task\n{problem}\n\n"
|
|
250
|
+
"Execute this task now. If it is self-contained, deliver the finished artifact "
|
|
251
|
+
"immediately in Direct Task Mode — do not greet, do not restate this configuration, "
|
|
252
|
+
"do not ask for project context, and do not open the phase lifecycle unless the task "
|
|
253
|
+
"is clearly multi-phase. End with the [Companion] next-steps block."
|
|
254
|
+
)
|
|
270
255
|
|
|
271
256
|
routing_lines = [
|
|
272
257
|
"## Domain & Routing",
|
|
@@ -320,6 +305,11 @@ def main() -> None:
|
|
|
320
305
|
action="store_true",
|
|
321
306
|
help="Apply token policy for the detected phase/domain: appends output style instruction and reports token budget.",
|
|
322
307
|
)
|
|
308
|
+
parser.add_argument(
|
|
309
|
+
"--system-only",
|
|
310
|
+
action="store_true",
|
|
311
|
+
help="Print only the task-scoped system prompt (routed agents + skills) and exit. Use as the `system` field of an API call.",
|
|
312
|
+
)
|
|
323
313
|
args = parser.parse_args()
|
|
324
314
|
|
|
325
315
|
# Validate problem statement
|
|
@@ -332,7 +322,7 @@ def main() -> None:
|
|
|
332
322
|
|
|
333
323
|
# Load project config
|
|
334
324
|
project_yaml_path = KIT_DIR / "config" / "project.yaml"
|
|
335
|
-
project_cfg =
|
|
325
|
+
project_cfg = load_yaml(project_yaml_path)
|
|
336
326
|
if not project_cfg:
|
|
337
327
|
print(
|
|
338
328
|
"[WARN] project.yaml not found or empty. Run init_project.py first for best results.",
|
|
@@ -378,6 +368,19 @@ def main() -> None:
|
|
|
378
368
|
agents = select_agents(phase, domain)
|
|
379
369
|
skills = select_skills(agents)
|
|
380
370
|
|
|
371
|
+
# A self-contained system prompt (--full / --system-only) must carry the whole
|
|
372
|
+
# domain's specialists so Direct Task Mode can deliver — scope by domain, not phase.
|
|
373
|
+
if args.full or args.system_only:
|
|
374
|
+
sys_agents = domain_agents(domain)
|
|
375
|
+
sys_skills = select_skills(sys_agents)
|
|
376
|
+
else:
|
|
377
|
+
sys_agents, sys_skills = agents, skills
|
|
378
|
+
|
|
379
|
+
# --system-only: emit just the task-scoped system prompt (for the API `system` field).
|
|
380
|
+
if args.system_only:
|
|
381
|
+
print(build_scoped_system(sys_agents, sys_skills))
|
|
382
|
+
return
|
|
383
|
+
|
|
381
384
|
prompt_text = build_prompt(
|
|
382
385
|
problem=args.problem,
|
|
383
386
|
domain=domain,
|
|
@@ -389,6 +392,8 @@ def main() -> None:
|
|
|
389
392
|
project_cfg=project_cfg,
|
|
390
393
|
state_text=state_text,
|
|
391
394
|
include_system=args.full,
|
|
395
|
+
system_agents=sys_agents,
|
|
396
|
+
system_skills=sys_skills,
|
|
392
397
|
)
|
|
393
398
|
|
|
394
399
|
# Apply token policy when --compress is requested
|
|
@@ -447,12 +452,7 @@ def main() -> None:
|
|
|
447
452
|
project_cfg["session_count"] = project_cfg.get("session_count", 0) + 1
|
|
448
453
|
project_cfg["last_session"] = date.today().isoformat()
|
|
449
454
|
try:
|
|
450
|
-
|
|
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)
|
|
455
|
+
atomic_write_yaml(project_yaml_path, project_cfg)
|
|
456
456
|
except (OSError, PermissionError) as e:
|
|
457
457
|
print(f"[WARN] Could not update session_count in project.yaml: {e}", file=sys.stderr)
|
|
458
458
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
grade_proof.py — Blind adversarial grading for compare_prompts.py output.
|
|
4
|
+
|
|
5
|
+
Takes a proof folder (naive_prompt.txt + naive_output.md + structured_output.md),
|
|
6
|
+
anonymizes the two responses as A/B (order decided by a stable per-folder seed,
|
|
7
|
+
so it is blind but reproducible), and asks an impartial judge model to score both
|
|
8
|
+
on completeness / correctness / actionability / structure and pick a winner.
|
|
9
|
+
|
|
10
|
+
The judge is a DIFFERENT model from the one that produced the outputs, to reduce
|
|
11
|
+
self-preference bias. Judge input excludes the system prompt, so it stays well
|
|
12
|
+
under tight input-token rate limits.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
16
|
+
python tools/grade_proof.py examples/proof/<folder>
|
|
17
|
+
python tools/grade_proof.py examples/proof/<folder> --judge claude-sonnet-5
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import hashlib
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
import urllib.error
|
|
29
|
+
import urllib.request
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
CRITERIA = ["completeness", "correctness", "actionability", "structure"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def call_judge(prompt: str, model: str, max_tokens: int = 1200) -> str:
|
|
36
|
+
key = os.environ.get("ANTHROPIC_API_KEY")
|
|
37
|
+
if not key:
|
|
38
|
+
raise RuntimeError("ANTHROPIC_API_KEY is not set.")
|
|
39
|
+
body = {"model": model, "max_tokens": max_tokens, "messages": [{"role": "user", "content": prompt}]}
|
|
40
|
+
req = urllib.request.Request(
|
|
41
|
+
"https://api.anthropic.com/v1/messages",
|
|
42
|
+
data=json.dumps(body).encode("utf-8"), method="POST",
|
|
43
|
+
headers={"x-api-key": key, "anthropic-version": "2023-06-01", "content-type": "application/json"},
|
|
44
|
+
)
|
|
45
|
+
try:
|
|
46
|
+
with urllib.request.urlopen(req, timeout=120) as resp:
|
|
47
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
48
|
+
except urllib.error.HTTPError as e:
|
|
49
|
+
raise RuntimeError(f"judge api error {e.code}: {e.read().decode('utf-8','replace')[:300]}") from None
|
|
50
|
+
return "".join(b.get("text", "") for b in data.get("content", [])).strip()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def build_prompt(task: str, resp_a: str, resp_b: str) -> str:
|
|
54
|
+
return (
|
|
55
|
+
"You are a strict, impartial evaluator. A user made this request:\n\n"
|
|
56
|
+
f"<request>\n{task}\n</request>\n\n"
|
|
57
|
+
"Two assistants answered. Judge only how well each fulfills the request "
|
|
58
|
+
"(ignore length; longer is not better).\n\n"
|
|
59
|
+
f"### Response A\n{resp_a}\n\n### Response B\n{resp_b}\n\n"
|
|
60
|
+
"Score each response 1-5 on completeness, correctness, actionability, structure. "
|
|
61
|
+
"Then pick the overall winner. Respond with ONLY JSON, no prose:\n"
|
|
62
|
+
'{"A":{"completeness":0,"correctness":0,"actionability":0,"structure":0},'
|
|
63
|
+
'"B":{"completeness":0,"correctness":0,"actionability":0,"structure":0},'
|
|
64
|
+
'"winner":"A|B|tie","reason":"one sentence"}'
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def main() -> None:
|
|
69
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
70
|
+
ap = argparse.ArgumentParser(description="Blind-grade a compare_prompts.py proof folder.")
|
|
71
|
+
ap.add_argument("folder", help="Proof folder containing naive_/structured_ files.")
|
|
72
|
+
ap.add_argument("--judge", default="claude-sonnet-5", help="Judge model id (default: claude-sonnet-5).")
|
|
73
|
+
args = ap.parse_args()
|
|
74
|
+
|
|
75
|
+
d = Path(args.folder)
|
|
76
|
+
task = (d / "naive_prompt.txt").read_text(encoding="utf-8").strip()
|
|
77
|
+
naive = (d / "naive_output.md").read_text(encoding="utf-8").strip()
|
|
78
|
+
structured = (d / "structured_output.md").read_text(encoding="utf-8").strip()
|
|
79
|
+
|
|
80
|
+
# Stable, reproducible blind assignment: structured is A iff folder-hash is even.
|
|
81
|
+
seed = int(hashlib.sha256(d.name.encode()).hexdigest(), 16)
|
|
82
|
+
structured_is_a = (seed % 2 == 0)
|
|
83
|
+
resp_a, resp_b = (structured, naive) if structured_is_a else (naive, structured)
|
|
84
|
+
|
|
85
|
+
raw = call_judge(build_prompt(task, resp_a, resp_b), args.judge)
|
|
86
|
+
m = re.search(r"\{.*\}", raw, re.DOTALL)
|
|
87
|
+
if not m:
|
|
88
|
+
raise RuntimeError(f"Judge did not return JSON:\n{raw[:300]}")
|
|
89
|
+
verdict = json.loads(m.group(0))
|
|
90
|
+
|
|
91
|
+
label = {"A": "structured" if structured_is_a else "naive",
|
|
92
|
+
"B": "naive" if structured_is_a else "structured"}
|
|
93
|
+
winner = label.get(verdict.get("winner", ""), verdict.get("winner", "tie"))
|
|
94
|
+
scores = {label[k]: verdict[k] for k in ("A", "B")}
|
|
95
|
+
|
|
96
|
+
lines = [
|
|
97
|
+
f"# Blind grade — {d.name}",
|
|
98
|
+
"",
|
|
99
|
+
f"- **Judge:** `{args.judge}` (different model than the one that produced the outputs)",
|
|
100
|
+
f"- **Blind mapping:** structured = Response {'A' if structured_is_a else 'B'}",
|
|
101
|
+
f"- **Winner:** **{winner}**",
|
|
102
|
+
f"- **Reason:** {verdict.get('reason','')}",
|
|
103
|
+
"",
|
|
104
|
+
"| Criterion | naive | structured |",
|
|
105
|
+
"|---|---|---|",
|
|
106
|
+
]
|
|
107
|
+
for c in CRITERIA:
|
|
108
|
+
lines.append(f"| {c} | {scores['naive'].get(c,'?')} | {scores['structured'].get(c,'?')} |")
|
|
109
|
+
n_tot = sum(scores["naive"].get(c, 0) for c in CRITERIA)
|
|
110
|
+
s_tot = sum(scores["structured"].get(c, 0) for c in CRITERIA)
|
|
111
|
+
lines.append(f"| **total** | **{n_tot}** | **{s_tot}** |")
|
|
112
|
+
(d / "grade.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
113
|
+
|
|
114
|
+
print(f"[graded] {d.name}: winner={winner} | naive={n_tot} structured={s_tot} (judge={args.judge})")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
main()
|