@pjmendonca/devflow 1.9.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/CHANGELOG.md +526 -0
- package/LICENSE +21 -0
- package/README.md +620 -0
- package/bin/devflow-checkpoint.js +10 -0
- package/bin/devflow-collab.js +10 -0
- package/bin/devflow-cost.js +10 -0
- package/bin/devflow-create-persona.js +10 -0
- package/bin/devflow-init.js +10 -0
- package/bin/devflow-memory.js +10 -0
- package/bin/devflow-new-doc.js +10 -0
- package/bin/devflow-personalize.js +10 -0
- package/bin/devflow-setup-checkpoint.js +10 -0
- package/bin/devflow-story.js +10 -0
- package/bin/devflow-tech-debt.js +10 -0
- package/bin/devflow-validate-overrides.js +10 -0
- package/bin/devflow-validate.js +10 -0
- package/bin/devflow-version.js +10 -0
- package/lib/constants.js +30 -0
- package/lib/exec-python.js +78 -0
- package/lib/python-check.js +178 -0
- package/package.json +64 -0
- package/tooling/.automation/agents/architect.md +135 -0
- package/tooling/.automation/agents/ba.md +70 -0
- package/tooling/.automation/agents/dev.md +79 -0
- package/tooling/.automation/agents/maintainer.md +97 -0
- package/tooling/.automation/agents/pm.md +116 -0
- package/tooling/.automation/agents/reviewer.md +141 -0
- package/tooling/.automation/agents/sm.md +61 -0
- package/tooling/.automation/agents/writer.md +193 -0
- package/tooling/.automation/config.ps1.template +61 -0
- package/tooling/.automation/config.sh.template +48 -0
- package/tooling/.automation/memory/.gitkeep +6 -0
- package/tooling/.automation/memory/knowledge/kg_integration-test.json +94 -0
- package/tooling/.automation/memory/knowledge/kg_test-story.json +300 -0
- package/tooling/.automation/memory/shared/shared_integration-test.json +30 -0
- package/tooling/.automation/memory/shared/shared_test-story.json +78 -0
- package/tooling/.automation/overrides/templates/README.md +113 -0
- package/tooling/.automation/overrides/templates/architect/README.md +27 -0
- package/tooling/.automation/overrides/templates/architect/cloud-native.yaml +92 -0
- package/tooling/.automation/overrides/templates/architect/enterprise-architect.yaml +85 -0
- package/tooling/.automation/overrides/templates/architect/pragmatic-minimalist.yaml +88 -0
- package/tooling/.automation/overrides/templates/ba/README.md +27 -0
- package/tooling/.automation/overrides/templates/ba/agile-storyteller.yaml +86 -0
- package/tooling/.automation/overrides/templates/ba/domain-expert.yaml +91 -0
- package/tooling/.automation/overrides/templates/ba/requirements-engineer.yaml +89 -0
- package/tooling/.automation/overrides/templates/dev/README.md +32 -0
- package/tooling/.automation/overrides/templates/dev/junior-mentored.yaml +39 -0
- package/tooling/.automation/overrides/templates/dev/performance-engineer.yaml +43 -0
- package/tooling/.automation/overrides/templates/dev/rapid-prototyper.yaml +52 -0
- package/tooling/.automation/overrides/templates/dev/security-focused.yaml +43 -0
- package/tooling/.automation/overrides/templates/dev/senior-fullstack.yaml +39 -0
- package/tooling/.automation/overrides/templates/maintainer/README.md +27 -0
- package/tooling/.automation/overrides/templates/maintainer/devops-maintainer.yaml +113 -0
- package/tooling/.automation/overrides/templates/maintainer/legacy-steward.yaml +94 -0
- package/tooling/.automation/overrides/templates/maintainer/oss-maintainer.yaml +94 -0
- package/tooling/.automation/overrides/templates/pm/README.md +27 -0
- package/tooling/.automation/overrides/templates/pm/agile-pm.yaml +91 -0
- package/tooling/.automation/overrides/templates/pm/hybrid-delivery.yaml +87 -0
- package/tooling/.automation/overrides/templates/pm/traditional-pm.yaml +91 -0
- package/tooling/.automation/overrides/templates/reviewer/README.md +11 -0
- package/tooling/.automation/overrides/templates/reviewer/mentoring-reviewer.yaml +45 -0
- package/tooling/.automation/overrides/templates/reviewer/quick-sanity.yaml +50 -0
- package/tooling/.automation/overrides/templates/reviewer/thorough-critic.yaml +48 -0
- package/tooling/.automation/overrides/templates/sm/README.md +11 -0
- package/tooling/.automation/overrides/templates/sm/agile-coach.yaml +52 -0
- package/tooling/.automation/overrides/templates/sm/startup-pm.yaml +50 -0
- package/tooling/.automation/overrides/templates/sm/technical-lead.yaml +47 -0
- package/tooling/.automation/overrides/templates/user-profile.template.yaml +62 -0
- package/tooling/.automation/overrides/templates/writer/README.md +27 -0
- package/tooling/.automation/overrides/templates/writer/api-documentarian.yaml +99 -0
- package/tooling/.automation/overrides/templates/writer/docs-as-code.yaml +108 -0
- package/tooling/.automation/overrides/templates/writer/user-guide-author.yaml +100 -0
- package/tooling/completions/DevflowCompletion.ps1 +213 -0
- package/tooling/completions/_run-story +116 -0
- package/tooling/completions/run-story-completion.bash +136 -0
- package/tooling/docs/DOC-STANDARD.md +717 -0
- package/tooling/docs/sprint-status.yaml.template +24 -0
- package/tooling/docs/templates/bug-report.md +234 -0
- package/tooling/docs/templates/migration-spec.md +274 -0
- package/tooling/docs/templates/refactor-spec.md +86 -0
- package/tooling/docs/templates/tech-debt.md +86 -0
- package/tooling/scripts/context_checkpoint.py +556 -0
- package/tooling/scripts/cost_dashboard.py +617 -0
- package/tooling/scripts/create-persona.py +690 -0
- package/tooling/scripts/create-persona.sh +435 -0
- package/tooling/scripts/init-project-workflow.ps1 +651 -0
- package/tooling/scripts/init-project-workflow.py +70 -0
- package/tooling/scripts/init-project-workflow.sh +746 -0
- package/tooling/scripts/lib/__init__.py +35 -0
- package/tooling/scripts/lib/agent_handoff.py +526 -0
- package/tooling/scripts/lib/agent_router.py +698 -0
- package/tooling/scripts/lib/checkpoint-integration.ps1 +245 -0
- package/tooling/scripts/lib/checkpoint-integration.sh +191 -0
- package/tooling/scripts/lib/claude-cli.ps1 +952 -0
- package/tooling/scripts/lib/claude-cli.sh +1293 -0
- package/tooling/scripts/lib/cost_config.py +222 -0
- package/tooling/scripts/lib/cost_display.py +443 -0
- package/tooling/scripts/lib/cost_tracker.py +710 -0
- package/tooling/scripts/lib/currency_converter.py +328 -0
- package/tooling/scripts/lib/errors.py +438 -0
- package/tooling/scripts/lib/override-loader.sh +286 -0
- package/tooling/scripts/lib/pair_programming.py +589 -0
- package/tooling/scripts/lib/shared_memory.py +637 -0
- package/tooling/scripts/lib/swarm_orchestrator.py +689 -0
- package/tooling/scripts/memory_summarize.py +324 -0
- package/tooling/scripts/new-doc.ps1 +405 -0
- package/tooling/scripts/new-doc.py +93 -0
- package/tooling/scripts/new-doc.sh +534 -0
- package/tooling/scripts/personalize_agent.py +385 -0
- package/tooling/scripts/rollback-migration.sh +540 -0
- package/tooling/scripts/run-collab.ps1 +251 -0
- package/tooling/scripts/run-collab.py +605 -0
- package/tooling/scripts/run-collab.sh +110 -0
- package/tooling/scripts/run-story.ps1 +490 -0
- package/tooling/scripts/run-story.py +387 -0
- package/tooling/scripts/run-story.sh +467 -0
- package/tooling/scripts/setup-checkpoint-service.ps1 +219 -0
- package/tooling/scripts/setup-checkpoint-service.py +87 -0
- package/tooling/scripts/setup-checkpoint-service.sh +236 -0
- package/tooling/scripts/tech-debt-tracker.py +608 -0
- package/tooling/scripts/update_version.py +244 -0
- package/tooling/scripts/validate-overrides.py +511 -0
- package/tooling/scripts/validate-overrides.sh +432 -0
- package/tooling/scripts/validate_setup.py +539 -0
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Create Persona - Custom Agent Persona Builder
|
|
4
|
+
|
|
5
|
+
This interactive tool helps create custom agent personas for the Devflow
|
|
6
|
+
workflow system. It generates properly formatted agent files that can be
|
|
7
|
+
used with the run-story.sh automation.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python create-persona.py # Interactive mode
|
|
11
|
+
python create-persona.py --name qa # Quick create with defaults
|
|
12
|
+
python create-persona.py --from-template # Create from existing template
|
|
13
|
+
python create-persona.py --list # List existing personas
|
|
14
|
+
python create-persona.py --validate <name> # Validate a persona file
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import sys
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Colors for terminal output
|
|
24
|
+
class Colors:
|
|
25
|
+
RED = "\033[0;31m"
|
|
26
|
+
GREEN = "\033[0;32m"
|
|
27
|
+
YELLOW = "\033[1;33m"
|
|
28
|
+
BLUE = "\033[0;34m"
|
|
29
|
+
CYAN = "\033[0;36m"
|
|
30
|
+
MAGENTA = "\033[0;35m"
|
|
31
|
+
BOLD = "\033[1m"
|
|
32
|
+
NC = "\033[0m"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Persona templates
|
|
36
|
+
PERSONA_TEMPLATES = {
|
|
37
|
+
"developer": {
|
|
38
|
+
"role": "Software Developer",
|
|
39
|
+
"focus": "Writing clean, maintainable code",
|
|
40
|
+
"model": "sonnet",
|
|
41
|
+
"responsibilities": [
|
|
42
|
+
"Implement features according to specifications",
|
|
43
|
+
"Write unit and integration tests",
|
|
44
|
+
"Follow coding standards and best practices",
|
|
45
|
+
"Document code appropriately",
|
|
46
|
+
],
|
|
47
|
+
"principles": [
|
|
48
|
+
"Write code that is easy to understand and maintain",
|
|
49
|
+
"Test first when possible",
|
|
50
|
+
"Keep functions small and focused",
|
|
51
|
+
"Handle errors gracefully",
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
"reviewer": {
|
|
55
|
+
"role": "Code Reviewer",
|
|
56
|
+
"focus": "Ensuring code quality and best practices",
|
|
57
|
+
"model": "sonnet",
|
|
58
|
+
"responsibilities": [
|
|
59
|
+
"Review code for correctness and quality",
|
|
60
|
+
"Identify potential bugs and security issues",
|
|
61
|
+
"Suggest improvements and optimizations",
|
|
62
|
+
"Ensure coding standards compliance",
|
|
63
|
+
],
|
|
64
|
+
"principles": [
|
|
65
|
+
"Be constructive, not critical",
|
|
66
|
+
"Focus on important issues first",
|
|
67
|
+
"Explain the 'why' behind suggestions",
|
|
68
|
+
"Acknowledge good patterns",
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
"architect": {
|
|
72
|
+
"role": "Software Architect",
|
|
73
|
+
"focus": "System design and technical decisions",
|
|
74
|
+
"model": "opus",
|
|
75
|
+
"responsibilities": [
|
|
76
|
+
"Design system architecture",
|
|
77
|
+
"Make technology decisions",
|
|
78
|
+
"Document architectural decisions (ADRs)",
|
|
79
|
+
"Ensure scalability and maintainability",
|
|
80
|
+
],
|
|
81
|
+
"principles": [
|
|
82
|
+
"Separation of concerns",
|
|
83
|
+
"Dependency inversion",
|
|
84
|
+
"Design for change",
|
|
85
|
+
"Keep it simple",
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
"tester": {
|
|
89
|
+
"role": "QA Engineer",
|
|
90
|
+
"focus": "Quality assurance and testing",
|
|
91
|
+
"model": "sonnet",
|
|
92
|
+
"responsibilities": [
|
|
93
|
+
"Design test strategies",
|
|
94
|
+
"Write automated tests",
|
|
95
|
+
"Perform exploratory testing",
|
|
96
|
+
"Report and track bugs",
|
|
97
|
+
],
|
|
98
|
+
"principles": [
|
|
99
|
+
"Test early, test often",
|
|
100
|
+
"Cover edge cases",
|
|
101
|
+
"Automate repetitive tests",
|
|
102
|
+
"Think like a user",
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
"security": {
|
|
106
|
+
"role": "Security Engineer",
|
|
107
|
+
"focus": "Application security and vulnerability prevention",
|
|
108
|
+
"model": "opus",
|
|
109
|
+
"responsibilities": [
|
|
110
|
+
"Review code for security vulnerabilities",
|
|
111
|
+
"Recommend security best practices",
|
|
112
|
+
"Audit authentication and authorization",
|
|
113
|
+
"Identify potential attack vectors",
|
|
114
|
+
],
|
|
115
|
+
"principles": [
|
|
116
|
+
"Defense in depth",
|
|
117
|
+
"Principle of least privilege",
|
|
118
|
+
"Never trust user input",
|
|
119
|
+
"Secure by default",
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
"devops": {
|
|
123
|
+
"role": "DevOps Engineer",
|
|
124
|
+
"focus": "CI/CD, infrastructure, and deployment",
|
|
125
|
+
"model": "sonnet",
|
|
126
|
+
"responsibilities": [
|
|
127
|
+
"Set up and maintain CI/CD pipelines",
|
|
128
|
+
"Manage infrastructure as code",
|
|
129
|
+
"Monitor and optimize deployments",
|
|
130
|
+
"Automate operational tasks",
|
|
131
|
+
],
|
|
132
|
+
"principles": [
|
|
133
|
+
"Automate everything",
|
|
134
|
+
"Infrastructure as code",
|
|
135
|
+
"Monitor proactively",
|
|
136
|
+
"Fail fast, recover faster",
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
"documentation": {
|
|
140
|
+
"role": "Technical Writer",
|
|
141
|
+
"focus": "Clear and comprehensive documentation",
|
|
142
|
+
"model": "sonnet",
|
|
143
|
+
"responsibilities": [
|
|
144
|
+
"Write user documentation",
|
|
145
|
+
"Create API documentation",
|
|
146
|
+
"Maintain README files",
|
|
147
|
+
"Document processes and procedures",
|
|
148
|
+
],
|
|
149
|
+
"principles": [
|
|
150
|
+
"Write for the audience",
|
|
151
|
+
"Keep it simple and clear",
|
|
152
|
+
"Include examples",
|
|
153
|
+
"Keep docs up to date",
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass
|
|
160
|
+
class PersonaConfig:
|
|
161
|
+
"""Configuration for a custom persona."""
|
|
162
|
+
|
|
163
|
+
name: str
|
|
164
|
+
role: str
|
|
165
|
+
focus: str
|
|
166
|
+
model: str = "sonnet"
|
|
167
|
+
responsibilities: list[str] = field(default_factory=list)
|
|
168
|
+
principles: list[str] = field(default_factory=list)
|
|
169
|
+
working_directories: dict[str, str] = field(default_factory=dict)
|
|
170
|
+
tech_stack: list[str] = field(default_factory=list)
|
|
171
|
+
critical_rules: list[str] = field(default_factory=list)
|
|
172
|
+
communication_style: str = ""
|
|
173
|
+
custom_sections: dict[str, str] = field(default_factory=dict)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def print_header():
|
|
177
|
+
"""Print the tool header."""
|
|
178
|
+
print()
|
|
179
|
+
print(f"{Colors.CYAN}{'═' * 70}{Colors.NC}")
|
|
180
|
+
print(f"{Colors.CYAN} CUSTOM PERSONA BUILDER{Colors.NC}")
|
|
181
|
+
print(f"{Colors.CYAN}{'═' * 70}{Colors.NC}")
|
|
182
|
+
print()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def prompt(message: str, default: str = "", required: bool = False) -> str:
|
|
186
|
+
"""Prompt for user input."""
|
|
187
|
+
if default:
|
|
188
|
+
display = f"{Colors.BLUE}{message}{Colors.NC} [{default}]: "
|
|
189
|
+
else:
|
|
190
|
+
display = f"{Colors.BLUE}{message}{Colors.NC}: "
|
|
191
|
+
|
|
192
|
+
while True:
|
|
193
|
+
value = input(display).strip()
|
|
194
|
+
if not value and default:
|
|
195
|
+
return default
|
|
196
|
+
if not value and required:
|
|
197
|
+
print(f"{Colors.YELLOW}This field is required.{Colors.NC}")
|
|
198
|
+
continue
|
|
199
|
+
return value
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def prompt_list(message: str, min_items: int = 0) -> list[str]:
|
|
203
|
+
"""Prompt for a list of items."""
|
|
204
|
+
print(f"{Colors.BLUE}{message}{Colors.NC}")
|
|
205
|
+
print(" (Enter items one per line, empty line to finish)")
|
|
206
|
+
|
|
207
|
+
items = []
|
|
208
|
+
while True:
|
|
209
|
+
item = input(f" {len(items) + 1}. ").strip()
|
|
210
|
+
if not item:
|
|
211
|
+
if len(items) >= min_items:
|
|
212
|
+
break
|
|
213
|
+
print(f"{Colors.YELLOW}Please enter at least {min_items} item(s).{Colors.NC}")
|
|
214
|
+
continue
|
|
215
|
+
items.append(item)
|
|
216
|
+
|
|
217
|
+
return items
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def prompt_choice(message: str, choices: list[str], default: str = "") -> str:
|
|
221
|
+
"""Prompt for a choice from a list."""
|
|
222
|
+
print(f"{Colors.BLUE}{message}{Colors.NC}")
|
|
223
|
+
for i, choice in enumerate(choices, 1):
|
|
224
|
+
marker = " (default)" if choice == default else ""
|
|
225
|
+
print(f" {i}. {choice}{marker}")
|
|
226
|
+
|
|
227
|
+
while True:
|
|
228
|
+
value = input("Choice: ").strip()
|
|
229
|
+
if not value and default:
|
|
230
|
+
return default
|
|
231
|
+
|
|
232
|
+
# Accept number or text
|
|
233
|
+
if value.isdigit():
|
|
234
|
+
idx = int(value) - 1
|
|
235
|
+
if 0 <= idx < len(choices):
|
|
236
|
+
return choices[idx]
|
|
237
|
+
elif value in choices:
|
|
238
|
+
return value
|
|
239
|
+
|
|
240
|
+
print(f"{Colors.YELLOW}Please enter a valid choice.{Colors.NC}")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def interactive_create() -> PersonaConfig:
|
|
244
|
+
"""Interactive persona creation wizard."""
|
|
245
|
+
print(f"{Colors.BOLD}Let's create your custom agent persona!{Colors.NC}")
|
|
246
|
+
print()
|
|
247
|
+
|
|
248
|
+
# Basic info
|
|
249
|
+
name = prompt("Persona name (e.g., 'qa', 'frontend-dev')", required=True)
|
|
250
|
+
name = name.lower().replace(" ", "-")
|
|
251
|
+
|
|
252
|
+
# Check for template
|
|
253
|
+
print()
|
|
254
|
+
print(f"{Colors.BOLD}Would you like to start from a template?{Colors.NC}")
|
|
255
|
+
templates = list(PERSONA_TEMPLATES.keys())
|
|
256
|
+
templates.insert(0, "none (start fresh)")
|
|
257
|
+
template = prompt_choice("Select template", templates, "none (start fresh)")
|
|
258
|
+
|
|
259
|
+
if template != "none (start fresh)" and template in PERSONA_TEMPLATES:
|
|
260
|
+
tpl = PERSONA_TEMPLATES[template]
|
|
261
|
+
role = prompt("Role", tpl["role"])
|
|
262
|
+
focus = prompt("Focus", tpl["focus"])
|
|
263
|
+
model = prompt_choice("Model", ["sonnet", "opus", "haiku"], tpl["model"])
|
|
264
|
+
responsibilities = tpl["responsibilities"].copy()
|
|
265
|
+
principles = tpl["principles"].copy()
|
|
266
|
+
|
|
267
|
+
print()
|
|
268
|
+
print(
|
|
269
|
+
f"{Colors.GREEN}Template loaded!{Colors.NC} Default responsibilities and principles applied."
|
|
270
|
+
)
|
|
271
|
+
modify = prompt("Would you like to modify them? (y/n)", "n")
|
|
272
|
+
|
|
273
|
+
if modify.lower() == "y":
|
|
274
|
+
print()
|
|
275
|
+
print("Current responsibilities:")
|
|
276
|
+
for r in responsibilities:
|
|
277
|
+
print(f" - {r}")
|
|
278
|
+
if prompt("Modify responsibilities? (y/n)", "n").lower() == "y":
|
|
279
|
+
responsibilities = prompt_list("Enter responsibilities", 1)
|
|
280
|
+
|
|
281
|
+
print()
|
|
282
|
+
print("Current principles:")
|
|
283
|
+
for p in principles:
|
|
284
|
+
print(f" - {p}")
|
|
285
|
+
if prompt("Modify principles? (y/n)", "n").lower() == "y":
|
|
286
|
+
principles = prompt_list("Enter principles", 1)
|
|
287
|
+
else:
|
|
288
|
+
role = prompt("Role (e.g., 'Senior QA Engineer')", required=True)
|
|
289
|
+
focus = prompt("Focus (one-line description)", required=True)
|
|
290
|
+
model = prompt_choice("Model", ["sonnet", "opus", "haiku"], "sonnet")
|
|
291
|
+
|
|
292
|
+
print()
|
|
293
|
+
responsibilities = prompt_list("What are this agent's responsibilities?", 2)
|
|
294
|
+
|
|
295
|
+
print()
|
|
296
|
+
principles = prompt_list("What principles should this agent follow?", 2)
|
|
297
|
+
|
|
298
|
+
# Communication style
|
|
299
|
+
print()
|
|
300
|
+
communication_style = prompt(
|
|
301
|
+
"Communication style (e.g., 'Technical and detailed', 'Friendly and explanatory')",
|
|
302
|
+
"Professional and clear",
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Working directories (optional)
|
|
306
|
+
print()
|
|
307
|
+
add_dirs = prompt("Add working directories? (y/n)", "n")
|
|
308
|
+
working_dirs = {}
|
|
309
|
+
if add_dirs.lower() == "y":
|
|
310
|
+
print("Enter directory mappings (name: path), empty to finish:")
|
|
311
|
+
while True:
|
|
312
|
+
dir_name = input(" Name: ").strip()
|
|
313
|
+
if not dir_name:
|
|
314
|
+
break
|
|
315
|
+
dir_path = input(" Path: ").strip()
|
|
316
|
+
working_dirs[dir_name] = dir_path
|
|
317
|
+
|
|
318
|
+
# Tech stack (optional)
|
|
319
|
+
print()
|
|
320
|
+
add_tech = prompt("Add tech stack context? (y/n)", "n")
|
|
321
|
+
tech_stack = []
|
|
322
|
+
if add_tech.lower() == "y":
|
|
323
|
+
tech_stack = prompt_list("Enter technologies this agent works with")
|
|
324
|
+
|
|
325
|
+
# Critical rules
|
|
326
|
+
print()
|
|
327
|
+
add_rules = prompt("Add critical rules (must-do actions)? (y/n)", "n")
|
|
328
|
+
critical_rules = []
|
|
329
|
+
if add_rules.lower() == "y":
|
|
330
|
+
critical_rules = prompt_list("Enter critical rules")
|
|
331
|
+
|
|
332
|
+
return PersonaConfig(
|
|
333
|
+
name=name,
|
|
334
|
+
role=role,
|
|
335
|
+
focus=focus,
|
|
336
|
+
model=model,
|
|
337
|
+
responsibilities=responsibilities,
|
|
338
|
+
principles=principles,
|
|
339
|
+
working_directories=working_dirs,
|
|
340
|
+
tech_stack=tech_stack,
|
|
341
|
+
critical_rules=critical_rules,
|
|
342
|
+
communication_style=communication_style,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def generate_persona_markdown(config: PersonaConfig) -> str:
|
|
347
|
+
"""Generate the persona markdown file content."""
|
|
348
|
+
lines = [
|
|
349
|
+
f"# {config.role} Agent",
|
|
350
|
+
"",
|
|
351
|
+
f"You are a {config.role}. {config.focus}",
|
|
352
|
+
"",
|
|
353
|
+
]
|
|
354
|
+
|
|
355
|
+
# Responsibilities
|
|
356
|
+
lines.extend(
|
|
357
|
+
[
|
|
358
|
+
"## Responsibilities",
|
|
359
|
+
"",
|
|
360
|
+
]
|
|
361
|
+
)
|
|
362
|
+
for i, resp in enumerate(config.responsibilities, 1):
|
|
363
|
+
lines.append(f"{i}. {resp}")
|
|
364
|
+
lines.append("")
|
|
365
|
+
|
|
366
|
+
# Working directories
|
|
367
|
+
if config.working_directories:
|
|
368
|
+
lines.extend(
|
|
369
|
+
[
|
|
370
|
+
"## Working Directory",
|
|
371
|
+
"",
|
|
372
|
+
]
|
|
373
|
+
)
|
|
374
|
+
for name, path in config.working_directories.items():
|
|
375
|
+
lines.append(f"- **{name}**: `{path}`")
|
|
376
|
+
lines.append("")
|
|
377
|
+
|
|
378
|
+
# Tech stack
|
|
379
|
+
if config.tech_stack:
|
|
380
|
+
lines.extend(
|
|
381
|
+
[
|
|
382
|
+
"## Tech Stack",
|
|
383
|
+
"",
|
|
384
|
+
]
|
|
385
|
+
)
|
|
386
|
+
for tech in config.tech_stack:
|
|
387
|
+
lines.append(f"- {tech}")
|
|
388
|
+
lines.append("")
|
|
389
|
+
|
|
390
|
+
# Principles
|
|
391
|
+
lines.extend(
|
|
392
|
+
[
|
|
393
|
+
"## Principles",
|
|
394
|
+
"",
|
|
395
|
+
]
|
|
396
|
+
)
|
|
397
|
+
for i, principle in enumerate(config.principles, 1):
|
|
398
|
+
lines.append(
|
|
399
|
+
f"{i}. **{principle.split(':')[0]}**"
|
|
400
|
+
+ (f": {':'.join(principle.split(':')[1:])}" if ":" in principle else "")
|
|
401
|
+
)
|
|
402
|
+
lines.append("")
|
|
403
|
+
|
|
404
|
+
# Communication style
|
|
405
|
+
if config.communication_style:
|
|
406
|
+
lines.extend(
|
|
407
|
+
[
|
|
408
|
+
"## Communication Style",
|
|
409
|
+
"",
|
|
410
|
+
config.communication_style,
|
|
411
|
+
"",
|
|
412
|
+
]
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Critical rules
|
|
416
|
+
if config.critical_rules:
|
|
417
|
+
lines.extend(
|
|
418
|
+
[
|
|
419
|
+
"## Critical Rules",
|
|
420
|
+
"",
|
|
421
|
+
]
|
|
422
|
+
)
|
|
423
|
+
for rule in config.critical_rules:
|
|
424
|
+
lines.append(f"- {rule}")
|
|
425
|
+
lines.append("")
|
|
426
|
+
|
|
427
|
+
# Context management (standard section)
|
|
428
|
+
lines.extend(
|
|
429
|
+
[
|
|
430
|
+
"## Context Management",
|
|
431
|
+
"",
|
|
432
|
+
"You are running in an automated pipeline with limited context window. To avoid losing work:",
|
|
433
|
+
"",
|
|
434
|
+
"1. **Work incrementally** - Complete and save files one at a time",
|
|
435
|
+
"2. **Checkpoint frequently** - After each significant change, ensure the file is written",
|
|
436
|
+
"3. **Monitor your progress** - If you notice you've been working for a while, prioritize critical items",
|
|
437
|
+
"4. **Self-assess context usage** - If you estimate you're past 80% of your context:",
|
|
438
|
+
" - Finish the current file you're working on",
|
|
439
|
+
" - Write a summary of remaining work",
|
|
440
|
+
" - Complete what you can rather than leaving partial work",
|
|
441
|
+
"",
|
|
442
|
+
"If you sense context is running low, output a warning:",
|
|
443
|
+
"```",
|
|
444
|
+
"⚠️ CONTEXT WARNING: Approaching context limit. Prioritizing completion of current task.",
|
|
445
|
+
"```",
|
|
446
|
+
"",
|
|
447
|
+
]
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# Model recommendation
|
|
451
|
+
lines.extend(
|
|
452
|
+
[
|
|
453
|
+
"## Model",
|
|
454
|
+
"",
|
|
455
|
+
f"Recommended model: `{config.model}`",
|
|
456
|
+
"",
|
|
457
|
+
]
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
return "\n".join(lines)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def generate_override_yaml(config: PersonaConfig) -> str:
|
|
464
|
+
"""Generate an override YAML file for the persona."""
|
|
465
|
+
lines = [
|
|
466
|
+
f"# {config.name.upper()} Agent Override",
|
|
467
|
+
"# Customize this agent's behavior without modifying the core agent file",
|
|
468
|
+
"# These settings survive updates to the core agent",
|
|
469
|
+
"",
|
|
470
|
+
"# Additional rules (appended to base agent rules)",
|
|
471
|
+
"additional_rules:",
|
|
472
|
+
]
|
|
473
|
+
|
|
474
|
+
for rule in config.critical_rules or config.principles[:2]:
|
|
475
|
+
lines.append(f' - "{rule}"')
|
|
476
|
+
|
|
477
|
+
lines.extend(
|
|
478
|
+
[
|
|
479
|
+
"",
|
|
480
|
+
"# Memories - facts this agent should always remember",
|
|
481
|
+
"memories:",
|
|
482
|
+
' - "Example memory: add project-specific context here"',
|
|
483
|
+
"",
|
|
484
|
+
"# Critical actions - must be done before completing any task",
|
|
485
|
+
"critical_actions:",
|
|
486
|
+
]
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
for rule in config.critical_rules or ["Verify work meets requirements"]:
|
|
490
|
+
lines.append(f' - "{rule}"')
|
|
491
|
+
|
|
492
|
+
lines.extend(
|
|
493
|
+
[
|
|
494
|
+
"",
|
|
495
|
+
f"# Model override (recommended: {config.model})",
|
|
496
|
+
f'# model: "{config.model}"',
|
|
497
|
+
"",
|
|
498
|
+
"# Budget override (optional)",
|
|
499
|
+
"# max_budget_usd: 10.00",
|
|
500
|
+
"",
|
|
501
|
+
]
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
return "\n".join(lines)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def save_persona(config: PersonaConfig, project_root: Path) -> tuple:
|
|
508
|
+
"""Save the persona files."""
|
|
509
|
+
agents_dir = project_root / "tooling" / ".automation" / "agents"
|
|
510
|
+
overrides_dir = project_root / "tooling" / ".automation" / "overrides"
|
|
511
|
+
|
|
512
|
+
agents_dir.mkdir(parents=True, exist_ok=True)
|
|
513
|
+
overrides_dir.mkdir(parents=True, exist_ok=True)
|
|
514
|
+
|
|
515
|
+
# Save agent file
|
|
516
|
+
agent_file = agents_dir / f"{config.name}.md"
|
|
517
|
+
agent_content = generate_persona_markdown(config)
|
|
518
|
+
agent_file.write_text(agent_content)
|
|
519
|
+
|
|
520
|
+
# Save override template
|
|
521
|
+
override_file = overrides_dir / f"{config.name}.override.yaml"
|
|
522
|
+
if not override_file.exists():
|
|
523
|
+
override_content = generate_override_yaml(config)
|
|
524
|
+
override_file.write_text(override_content)
|
|
525
|
+
|
|
526
|
+
return agent_file, override_file
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def list_personas(project_root: Path):
|
|
530
|
+
"""List existing personas."""
|
|
531
|
+
agents_dir = project_root / "tooling" / ".automation" / "agents"
|
|
532
|
+
|
|
533
|
+
print(f"{Colors.BOLD}Available Agent Personas:{Colors.NC}")
|
|
534
|
+
print()
|
|
535
|
+
|
|
536
|
+
if not agents_dir.exists():
|
|
537
|
+
print(f" {Colors.YELLOW}No agents directory found.{Colors.NC}")
|
|
538
|
+
return
|
|
539
|
+
|
|
540
|
+
for agent_file in sorted(agents_dir.glob("*.md")):
|
|
541
|
+
name = agent_file.stem
|
|
542
|
+
|
|
543
|
+
# Extract first line (role)
|
|
544
|
+
content = agent_file.read_text()
|
|
545
|
+
first_line = content.split("\n")[0]
|
|
546
|
+
role = first_line.replace("# ", "").replace(" Agent", "")
|
|
547
|
+
|
|
548
|
+
# Check for override
|
|
549
|
+
override_file = (
|
|
550
|
+
project_root / "tooling" / ".automation" / "overrides" / f"{name}.override.yaml"
|
|
551
|
+
)
|
|
552
|
+
has_override = "✓" if override_file.exists() else " "
|
|
553
|
+
|
|
554
|
+
print(f" {Colors.GREEN}{name:15}{Colors.NC} │ {role:25} │ Override: {has_override}")
|
|
555
|
+
|
|
556
|
+
print()
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def validate_persona(name: str, project_root: Path) -> bool:
|
|
560
|
+
"""Validate a persona file."""
|
|
561
|
+
agent_file = project_root / "tooling" / ".automation" / "agents" / f"{name}.md"
|
|
562
|
+
|
|
563
|
+
if not agent_file.exists():
|
|
564
|
+
print(f"{Colors.RED}✗ Agent file not found: {agent_file}{Colors.NC}")
|
|
565
|
+
return False
|
|
566
|
+
|
|
567
|
+
print(f"{Colors.BOLD}Validating persona: {name}{Colors.NC}")
|
|
568
|
+
print()
|
|
569
|
+
|
|
570
|
+
content = agent_file.read_text()
|
|
571
|
+
errors = []
|
|
572
|
+
warnings = []
|
|
573
|
+
|
|
574
|
+
# Check for required sections
|
|
575
|
+
required_sections = ["Responsibilities", "Principles"]
|
|
576
|
+
for section in required_sections:
|
|
577
|
+
if f"## {section}" not in content:
|
|
578
|
+
errors.append(f"Missing required section: {section}")
|
|
579
|
+
|
|
580
|
+
# Check for role definition
|
|
581
|
+
if not content.startswith("# "):
|
|
582
|
+
errors.append("Missing role heading (should start with '# Role Agent')")
|
|
583
|
+
|
|
584
|
+
# Check for context management
|
|
585
|
+
if "Context Management" not in content:
|
|
586
|
+
warnings.append("Missing Context Management section (recommended)")
|
|
587
|
+
|
|
588
|
+
# Print results
|
|
589
|
+
for error in errors:
|
|
590
|
+
print(f" {Colors.RED}✗ ERROR:{Colors.NC} {error}")
|
|
591
|
+
|
|
592
|
+
for warning in warnings:
|
|
593
|
+
print(f" {Colors.YELLOW}⚠ WARNING:{Colors.NC} {warning}")
|
|
594
|
+
|
|
595
|
+
if not errors and not warnings:
|
|
596
|
+
print(f" {Colors.GREEN}✓ Persona is valid!{Colors.NC}")
|
|
597
|
+
|
|
598
|
+
print()
|
|
599
|
+
return len(errors) == 0
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def main():
|
|
603
|
+
parser = argparse.ArgumentParser(description="Create custom agent personas")
|
|
604
|
+
parser.add_argument("--name", help="Quick create with this name")
|
|
605
|
+
parser.add_argument(
|
|
606
|
+
"--template", help="Use this template (developer, reviewer, architect, etc.)"
|
|
607
|
+
)
|
|
608
|
+
parser.add_argument("--list", action="store_true", help="List existing personas")
|
|
609
|
+
parser.add_argument("--validate", help="Validate a persona file")
|
|
610
|
+
parser.add_argument(
|
|
611
|
+
"--from-template", action="store_true", help="Create from template selection"
|
|
612
|
+
)
|
|
613
|
+
args = parser.parse_args()
|
|
614
|
+
|
|
615
|
+
# Find project root
|
|
616
|
+
script_dir = Path(__file__).parent
|
|
617
|
+
project_root = script_dir.parent.parent
|
|
618
|
+
|
|
619
|
+
print_header()
|
|
620
|
+
|
|
621
|
+
if args.list:
|
|
622
|
+
list_personas(project_root)
|
|
623
|
+
return
|
|
624
|
+
|
|
625
|
+
if args.validate:
|
|
626
|
+
valid = validate_persona(args.validate, project_root)
|
|
627
|
+
sys.exit(0 if valid else 1)
|
|
628
|
+
|
|
629
|
+
if args.name and args.template:
|
|
630
|
+
# Quick create with template
|
|
631
|
+
if args.template not in PERSONA_TEMPLATES:
|
|
632
|
+
print(f"{Colors.RED}Unknown template: {args.template}{Colors.NC}")
|
|
633
|
+
print(f"Available: {', '.join(PERSONA_TEMPLATES.keys())}")
|
|
634
|
+
sys.exit(1)
|
|
635
|
+
|
|
636
|
+
tpl = PERSONA_TEMPLATES[args.template]
|
|
637
|
+
config = PersonaConfig(
|
|
638
|
+
name=args.name,
|
|
639
|
+
role=tpl["role"],
|
|
640
|
+
focus=tpl["focus"],
|
|
641
|
+
model=tpl["model"],
|
|
642
|
+
responsibilities=tpl["responsibilities"],
|
|
643
|
+
principles=tpl["principles"],
|
|
644
|
+
)
|
|
645
|
+
elif args.from_template:
|
|
646
|
+
# Template selection mode
|
|
647
|
+
print(f"{Colors.BOLD}Available Templates:{Colors.NC}")
|
|
648
|
+
print()
|
|
649
|
+
for name, tpl in PERSONA_TEMPLATES.items():
|
|
650
|
+
print(f" {Colors.GREEN}{name:15}{Colors.NC} - {tpl['role']}: {tpl['focus']}")
|
|
651
|
+
print()
|
|
652
|
+
|
|
653
|
+
template = prompt_choice(
|
|
654
|
+
"Select a template",
|
|
655
|
+
list(PERSONA_TEMPLATES.keys()),
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
persona_name = prompt("Persona name", template)
|
|
659
|
+
tpl = PERSONA_TEMPLATES[template]
|
|
660
|
+
|
|
661
|
+
config = PersonaConfig(
|
|
662
|
+
name=persona_name,
|
|
663
|
+
role=tpl["role"],
|
|
664
|
+
focus=tpl["focus"],
|
|
665
|
+
model=tpl["model"],
|
|
666
|
+
responsibilities=tpl["responsibilities"],
|
|
667
|
+
principles=tpl["principles"],
|
|
668
|
+
)
|
|
669
|
+
else:
|
|
670
|
+
# Interactive mode
|
|
671
|
+
config = interactive_create()
|
|
672
|
+
|
|
673
|
+
# Save files
|
|
674
|
+
print()
|
|
675
|
+
agent_file, override_file = save_persona(config, project_root)
|
|
676
|
+
|
|
677
|
+
print(f"{Colors.GREEN}✓ Persona created successfully!{Colors.NC}")
|
|
678
|
+
print()
|
|
679
|
+
print(f" Agent file: {agent_file}")
|
|
680
|
+
print(f" Override file: {override_file}")
|
|
681
|
+
print()
|
|
682
|
+
print(f"{Colors.BOLD}Next steps:{Colors.NC}")
|
|
683
|
+
print(f" 1. Review and customize: {agent_file}")
|
|
684
|
+
print(f" 2. Add project-specific memories: {override_file}")
|
|
685
|
+
print(f" 3. Use with: ./run-story.sh <story> --agent {config.name}")
|
|
686
|
+
print()
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
if __name__ == "__main__":
|
|
690
|
+
main()
|