@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.
- package/LICENSE +21 -0
- package/README.md +659 -0
- package/agents/architect_agent.md +175 -0
- package/agents/code_agent.md +178 -0
- package/agents/compression_agent.md +226 -0
- package/agents/execution_agent.md +157 -0
- package/agents/orchestrator.md +406 -0
- package/agents/reviewer_agent.md +134 -0
- package/agents/ui_agent.md +147 -0
- package/agents/ux_agent.md +144 -0
- package/bin/cli.js +79 -0
- package/config/agents.yaml +388 -0
- package/config/domain_profiles.yaml +394 -0
- package/config/project.yaml.example +16 -0
- package/config/token_policies.yaml +325 -0
- package/context_loaders/__init__.py +15 -0
- 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/context_loaders/file_chunker.py +252 -0
- package/context_loaders/project_summary.py +369 -0
- package/context_loaders/repo_tree.py +203 -0
- package/package.json +46 -0
- package/quickstart.ps1 +252 -0
- package/quickstart.sh +232 -0
- package/requirements.txt +3 -0
- package/skills/analyze_repo.md +86 -0
- package/skills/animated_website.md +285 -0
- package/skills/compare_approaches.md +86 -0
- package/skills/define_data_schema.md +99 -0
- package/skills/design_api.md +126 -0
- package/skills/improve_copy.md +69 -0
- package/skills/review_code.md +83 -0
- package/skills/review_layout.md +89 -0
- package/skills/suggest_next.md +105 -0
- package/skills/summarize_history.md +89 -0
- package/skills/write_process_map.md +105 -0
- package/skills/write_tests.md +97 -0
- package/token_optimization/__pycache__/compressor.cpython-314.pyc +0 -0
- package/token_optimization/compressor.py +502 -0
- package/token_optimization/output_styles.md +80 -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
- package/tools/domain_utils.py +141 -0
- package/tools/generate_claude_md.py +269 -0
- package/tools/generate_platform_configs.py +467 -0
- package/tools/generate_prompt.py +461 -0
- package/tools/init_project.py +454 -0
- package/tools/test_kit.py +363 -0
- 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()
|