@prateek_ai/agents-maker 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +659 -0
  3. package/agents/architect_agent.md +175 -0
  4. package/agents/code_agent.md +178 -0
  5. package/agents/compression_agent.md +226 -0
  6. package/agents/execution_agent.md +157 -0
  7. package/agents/orchestrator.md +406 -0
  8. package/agents/reviewer_agent.md +134 -0
  9. package/agents/ui_agent.md +147 -0
  10. package/agents/ux_agent.md +144 -0
  11. package/bin/cli.js +79 -0
  12. package/config/agents.yaml +388 -0
  13. package/config/domain_profiles.yaml +394 -0
  14. package/config/project.yaml.example +16 -0
  15. package/config/token_policies.yaml +325 -0
  16. package/context_loaders/__init__.py +15 -0
  17. package/context_loaders/__pycache__/__init__.cpython-314.pyc +0 -0
  18. package/context_loaders/__pycache__/file_chunker.cpython-314.pyc +0 -0
  19. package/context_loaders/__pycache__/project_summary.cpython-314.pyc +0 -0
  20. package/context_loaders/__pycache__/repo_tree.cpython-314.pyc +0 -0
  21. package/context_loaders/file_chunker.py +252 -0
  22. package/context_loaders/project_summary.py +369 -0
  23. package/context_loaders/repo_tree.py +203 -0
  24. package/package.json +46 -0
  25. package/quickstart.ps1 +252 -0
  26. package/quickstart.sh +232 -0
  27. package/requirements.txt +3 -0
  28. package/skills/analyze_repo.md +86 -0
  29. package/skills/animated_website.md +285 -0
  30. package/skills/compare_approaches.md +86 -0
  31. package/skills/define_data_schema.md +99 -0
  32. package/skills/design_api.md +126 -0
  33. package/skills/improve_copy.md +69 -0
  34. package/skills/review_code.md +83 -0
  35. package/skills/review_layout.md +89 -0
  36. package/skills/suggest_next.md +105 -0
  37. package/skills/summarize_history.md +89 -0
  38. package/skills/write_process_map.md +105 -0
  39. package/skills/write_tests.md +97 -0
  40. package/token_optimization/__pycache__/compressor.cpython-314.pyc +0 -0
  41. package/token_optimization/compressor.py +502 -0
  42. package/token_optimization/output_styles.md +80 -0
  43. package/tools/__pycache__/domain_utils.cpython-314.pyc +0 -0
  44. package/tools/__pycache__/generate_claude_md.cpython-314.pyc +0 -0
  45. package/tools/__pycache__/validate_kit.cpython-314.pyc +0 -0
  46. package/tools/domain_utils.py +141 -0
  47. package/tools/generate_claude_md.py +269 -0
  48. package/tools/generate_platform_configs.py +467 -0
  49. package/tools/generate_prompt.py +461 -0
  50. package/tools/init_project.py +454 -0
  51. package/tools/test_kit.py +363 -0
  52. package/tools/validate_kit.py +504 -0
@@ -0,0 +1,504 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ validate_kit.py — Multi-Agent Assistant Kit integrity checker.
4
+
5
+ Run after any change to config files, agents, skills, or domains:
6
+ python tools/validate_kit.py
7
+
8
+ Runs 12 checks:
9
+ 1. YAML parse — all 3 config files
10
+ 2. Agent .md files exist and have content
11
+ 3. Skill .md files exist and have content
12
+ 4. Domain coverage in token_policies.yaml
13
+ 5. Domain primary_agents reference valid agent_ids
14
+ 6. Output style references are defined
15
+ 7. Domain detection scoring (8 test messages)
16
+ 8. File inventory matches README repository map
17
+ 9. Compressor dry-run (PolicyLoader loads 3 workflows)
18
+ 10. Skill markdown structure (input, output, token cost sections)
19
+ 11. Agent markdown structure (role, goals, context sections)
20
+ 12. system_prompt.md freshness (source hash matches current agents + skills)
21
+
22
+ Exit code 0 = all checks pass. Exit code 1 = one or more failures.
23
+ Requires pyyaml (pip install pyyaml).
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import hashlib
29
+ import re
30
+ import sys
31
+ from pathlib import Path
32
+
33
+ __version__ = "1.0.0"
34
+
35
+ ROOT = Path(__file__).parent.parent
36
+
37
+
38
+ # ---------------------------------------------------------------------------
39
+ # Helpers
40
+ # ---------------------------------------------------------------------------
41
+
42
+ def load_yaml(path: str) -> dict:
43
+ import yaml
44
+ full_path = ROOT / path
45
+ try:
46
+ with full_path.open(encoding="utf-8") as f:
47
+ return yaml.safe_load(f) or {}
48
+ except yaml.YAMLError as e:
49
+ fail(f"YAML parse error in {path}: {e}")
50
+ return {}
51
+ except FileNotFoundError:
52
+ fail(f"File not found: {path}")
53
+ return {}
54
+
55
+
56
+ def ok(msg: str) -> None:
57
+ print(f" [PASS] {msg}")
58
+
59
+
60
+ def fail(msg: str) -> None:
61
+ print(f" [FAIL] {msg}")
62
+ FAILURES.append(msg)
63
+
64
+
65
+ FAILURES: list[str] = []
66
+
67
+
68
+ # ---------------------------------------------------------------------------
69
+ # Check 1 — YAML parses
70
+ # ---------------------------------------------------------------------------
71
+
72
+ def check_yaml_parse() -> tuple[dict, dict, dict]:
73
+ print("\n--- YAML Parse ---")
74
+ agents_cfg = domain_cfg = policy_cfg = {}
75
+ for path in ["config/agents.yaml", "config/domain_profiles.yaml", "config/token_policies.yaml"]:
76
+ try:
77
+ data = load_yaml(path)
78
+ ok(f"YAML: {path}")
79
+ if "agents" in path:
80
+ agents_cfg = data
81
+ elif "domain" in path:
82
+ domain_cfg = data
83
+ else:
84
+ policy_cfg = data
85
+ except Exception as e:
86
+ fail(f"YAML: {path} — {e}")
87
+ return agents_cfg, domain_cfg, policy_cfg
88
+
89
+
90
+ # ---------------------------------------------------------------------------
91
+ # Check 2 — Agent .md files exist
92
+ # ---------------------------------------------------------------------------
93
+
94
+ def check_agent_files(agents_cfg: dict) -> None:
95
+ print("\n--- Agent Files ---")
96
+ registered = list((agents_cfg.get("agents") or {}).keys())
97
+ if not registered:
98
+ fail("agents.yaml has no agents registered")
99
+ return
100
+ missing = []
101
+ empty = []
102
+ for agent_id in registered:
103
+ md = ROOT / "agents" / f"{agent_id}.md"
104
+ if not md.exists():
105
+ missing.append(agent_id)
106
+ else:
107
+ content = md.read_text(encoding="utf-8").strip()
108
+ if len(content) < 100:
109
+ empty.append(f"{agent_id}.md ({len(content)} chars)")
110
+ if missing:
111
+ fail(f"Agent files missing for: {missing}")
112
+ elif empty:
113
+ fail(f"Agent files suspiciously short (< 100 chars): {empty}")
114
+ else:
115
+ ok(f"Agent files: {len(registered)}/{len(registered)} present ({', '.join(registered)})")
116
+
117
+
118
+ # ---------------------------------------------------------------------------
119
+ # Check 3 — Skill .md files exist
120
+ # ---------------------------------------------------------------------------
121
+
122
+ def check_skill_files(agents_cfg: dict) -> None:
123
+ print("\n--- Skill Files ---")
124
+ agents = agents_cfg.get("agents") or {}
125
+ all_skills: set[str] = set()
126
+ for cfg in agents.values():
127
+ for s in (cfg.get("skills") or []):
128
+ all_skills.add(s)
129
+ if not all_skills:
130
+ ok("No skills referenced in agents.yaml (skip)")
131
+ return
132
+ missing = []
133
+ empty = []
134
+ for s in sorted(all_skills):
135
+ skill_path = ROOT / "skills" / f"{s}.md"
136
+ if not skill_path.exists():
137
+ missing.append(s)
138
+ else:
139
+ content = skill_path.read_text(encoding="utf-8").strip()
140
+ if len(content) < 50:
141
+ empty.append(f"{s}.md ({len(content)} chars)")
142
+ if missing:
143
+ fail(f"Skill files missing: {missing}")
144
+ elif empty:
145
+ fail(f"Skill files suspiciously short (< 50 chars): {empty}")
146
+ else:
147
+ ok(f"Skill files: {len(all_skills)}/{len(all_skills)} present")
148
+
149
+
150
+ # ---------------------------------------------------------------------------
151
+ # Check 4 — Domain coverage in token_policies.yaml
152
+ # ---------------------------------------------------------------------------
153
+
154
+ def check_domain_coverage(domain_cfg: dict, policy_cfg: dict) -> None:
155
+ print("\n--- Domain Coverage in token_policies.yaml ---")
156
+ domains = list((domain_cfg.get("domains") or {}).keys())
157
+ if not domains:
158
+ fail("domain_profiles.yaml has no domains")
159
+ return
160
+ # generic_project_lifecycle is nested under workflows:
161
+ lifecycle = (policy_cfg.get("workflows") or {}).get("generic_project_lifecycle") or {}
162
+ covered = set((lifecycle.get("domains") or {}).keys())
163
+ missing = [d for d in domains if d not in covered]
164
+ if missing:
165
+ fail(f"Domains missing from token_policies generic_project_lifecycle.domains: {missing}")
166
+ else:
167
+ ok(f"Domain coverage: all {len(domains)} domains have overrides ({', '.join(domains)})")
168
+
169
+
170
+ # ---------------------------------------------------------------------------
171
+ # Check 5 — domain primary_agents reference valid agent_ids
172
+ # ---------------------------------------------------------------------------
173
+
174
+ def check_primary_agents(domain_cfg: dict, agents_cfg: dict) -> None:
175
+ print("\n--- Domain primary_agents Validity ---")
176
+ valid_ids = set((agents_cfg.get("agents") or {}).keys())
177
+ domains = domain_cfg.get("domains") or {}
178
+ errors = []
179
+ for d, cfg in domains.items():
180
+ for phase, agent_id in (cfg.get("primary_agents") or {}).items():
181
+ if agent_id not in valid_ids:
182
+ errors.append(f"domain={d} phase={phase}: unknown agent '{agent_id}'")
183
+ if errors:
184
+ for e in errors:
185
+ fail(e)
186
+ else:
187
+ ok("All domain primary_agents reference valid agent_ids")
188
+
189
+
190
+ # ---------------------------------------------------------------------------
191
+ # Check 6 — output_style references exist in output_styles block
192
+ # ---------------------------------------------------------------------------
193
+
194
+ def check_output_styles(policy_cfg: dict) -> None:
195
+ print("\n--- Output Style References ---")
196
+ defined_styles = set((policy_cfg.get("output_styles") or {}).keys())
197
+ if not defined_styles:
198
+ fail("No output_styles defined in token_policies.yaml")
199
+ return
200
+
201
+ refs: set[str] = set()
202
+ # Scan workflows and phases for output_style keys
203
+ for key, val in (policy_cfg.get("workflows") or {}).items():
204
+ if isinstance(val, dict) and "output_style" in val:
205
+ refs.add(val["output_style"])
206
+
207
+ lifecycle = policy_cfg.get("generic_project_lifecycle") or {}
208
+ for phase_key, phase_val in (lifecycle.get("phases") or {}).items():
209
+ if isinstance(phase_val, dict) and "output_style" in phase_val:
210
+ refs.add(phase_val["output_style"])
211
+ for d_key, d_val in (lifecycle.get("domains") or {}).items():
212
+ for phase_key, phase_val in (d_val or {}).items():
213
+ if isinstance(phase_val, dict) and "output_style" in phase_val:
214
+ refs.add(phase_val["output_style"])
215
+
216
+ missing_styles = refs - defined_styles
217
+ if missing_styles:
218
+ fail(f"output_style values referenced but not defined: {sorted(missing_styles)}")
219
+ else:
220
+ ok(f"Output styles: all {len(refs)} referenced styles are defined")
221
+
222
+
223
+ # ---------------------------------------------------------------------------
224
+ # Check 7 — Domain detection scoring smoke test
225
+ # ---------------------------------------------------------------------------
226
+
227
+ SCORING_TESTS = [
228
+ ("software", "high", "Help me refactor the UserService and add unit tests for the API endpoints."),
229
+ ("content", "high", "Write a blog post about AI trends for our marketing newsletter."),
230
+ ("data_analytics", "high", "Analyze our sales funnel conversion data and build a dashboard."),
231
+ ("general", "low", "Help me document the onboarding process for new engineers."),
232
+ ("product_design", "high", "Design a mobile checkout flow for our e-commerce app."),
233
+ ("research", "high", "Write a literature review on transformer architectures."),
234
+ ("marketing", "high", "Create a go-to-market strategy for our new SaaS product."),
235
+ ("general", "low", "I need help with something."),
236
+ ]
237
+
238
+
239
+ def _score(message: str, domains: dict, settings: dict) -> tuple[str, str]:
240
+ msg_lower = message.lower()
241
+ scores: dict[str, float] = {}
242
+ for d, cfg in domains.items():
243
+ if d == "general":
244
+ continue
245
+ strong = cfg.get("detection_signals", {}).get("strong", [])
246
+ weak = cfg.get("detection_signals", {}).get("weak", [])
247
+ s = sum(1.0 for sig in strong if sig in msg_lower)
248
+ w = sum(0.4 for sig in weak if sig in msg_lower)
249
+ scores[d] = (s + w) / 3
250
+
251
+ ranked = sorted(scores.items(), key=lambda x: -x[1])
252
+ top_d, top_s = ranked[0] if ranked else ("general", 0.0)
253
+ sec_d, sec_s = ranked[1] if len(ranked) > 1 else ("general", 0.0)
254
+
255
+ conf_t = settings.get("confidence_threshold", 0.40)
256
+ amb_t = settings.get("ambiguity_threshold", 0.10)
257
+
258
+ if top_s < conf_t:
259
+ return "general", "low"
260
+ elif (top_s - sec_s) < amb_t:
261
+ return top_d, "medium"
262
+ else:
263
+ return top_d, "high"
264
+
265
+
266
+ def check_domain_scoring(domain_cfg: dict) -> None:
267
+ print("\n--- Domain Detection Scoring ---")
268
+ domains = domain_cfg.get("domains") or {}
269
+ settings = domain_cfg.get("settings") or {}
270
+ passed = 0
271
+ for exp_domain, exp_conf, msg in SCORING_TESTS:
272
+ got_domain, got_conf = _score(msg, domains, settings)
273
+ if got_domain == exp_domain and got_conf == exp_conf:
274
+ passed += 1
275
+ else:
276
+ fail(
277
+ f"Scoring mismatch: expected ({exp_domain}, {exp_conf}), "
278
+ f"got ({got_domain}, {got_conf}) for: \"{msg[:55]}...\""
279
+ )
280
+ if passed == len(SCORING_TESTS):
281
+ ok(f"Domain scoring: {passed}/{len(SCORING_TESTS)} test messages routed correctly")
282
+
283
+
284
+ # ---------------------------------------------------------------------------
285
+ # Check 8 — File inventory from README.md repository map
286
+ # ---------------------------------------------------------------------------
287
+
288
+ # (defined below — keep check order consistent with main())
289
+
290
+ # ---------------------------------------------------------------------------
291
+ # Check 9 — Compressor dry-run
292
+ # ---------------------------------------------------------------------------
293
+
294
+ def check_compressor() -> None:
295
+ print("\n--- Compressor Dry-Run ---")
296
+ try:
297
+ import sys as _sys
298
+ _sys.path.insert(0, str(ROOT))
299
+ from token_optimization.compressor import PolicyLoader
300
+ loader = PolicyLoader(ROOT / "config" / "token_policies.yaml")
301
+ loader.load()
302
+ for workflow in ["feature_implementation", "code_review", "feature_design"]:
303
+ policy = loader.get_workflow_policy(workflow)
304
+ if not policy.output_style:
305
+ fail(f"PolicyLoader.get_workflow_policy('{workflow}') returned empty output_style")
306
+ return
307
+ if policy.max_input_tokens <= 0:
308
+ fail(f"Policy '{workflow}' has invalid max_input_tokens: {policy.max_input_tokens}")
309
+ return
310
+ ok("Compressor: PolicyLoader loads and returns valid policies for 3 workflows")
311
+ except ImportError as e:
312
+ fail(f"Compressor import failed: {e}")
313
+ except Exception as e:
314
+ fail(f"Compressor dry-run failed: {e}")
315
+
316
+
317
+ # ---------------------------------------------------------------------------
318
+ # Check 10 — Skill markdown structure
319
+ # ---------------------------------------------------------------------------
320
+
321
+ _REQUIRED_SKILL_SECTIONS = ["input", "output", "token cost"]
322
+
323
+
324
+ def check_skill_structure(agents_cfg: dict) -> None:
325
+ print("\n--- Skill Markdown Structure ---")
326
+ agents = agents_cfg.get("agents") or {}
327
+ all_skills: set[str] = set()
328
+ for cfg in agents.values():
329
+ for s in (cfg.get("skills") or []):
330
+ all_skills.add(s)
331
+
332
+ if not all_skills:
333
+ ok("No skills to check (skip)")
334
+ return
335
+
336
+ issues: list[str] = []
337
+ for skill_key in sorted(all_skills):
338
+ path = ROOT / "skills" / f"{skill_key}.md"
339
+ if not path.exists():
340
+ continue # already caught by Check 3
341
+ content = path.read_text(encoding="utf-8").lower()
342
+ missing = [s for s in _REQUIRED_SKILL_SECTIONS if s not in content]
343
+ if missing:
344
+ issues.append(f"{skill_key}.md missing sections: {missing}")
345
+
346
+ if issues:
347
+ for issue in issues:
348
+ fail(f"Skill structure: {issue}")
349
+ else:
350
+ ok(f"Skill structure: all {len(all_skills)} skill cards have required sections (input, output, token cost)")
351
+
352
+
353
+ # ---------------------------------------------------------------------------
354
+ # Check 11 — Agent markdown structure
355
+ # ---------------------------------------------------------------------------
356
+
357
+ _REQUIRED_AGENT_SECTIONS = ["## role", "## goals", "context"]
358
+
359
+
360
+ def check_agent_structure(agents_cfg: dict) -> None:
361
+ print("\n--- Agent Markdown Structure ---")
362
+ registered = list((agents_cfg.get("agents") or {}).keys())
363
+
364
+ if not registered:
365
+ ok("No agents to check (skip)")
366
+ return
367
+
368
+ issues: list[str] = []
369
+ for agent_id in registered:
370
+ path = ROOT / "agents" / f"{agent_id}.md"
371
+ if not path.exists():
372
+ continue # already caught by Check 2
373
+ content = path.read_text(encoding="utf-8").lower()
374
+ missing = [s for s in _REQUIRED_AGENT_SECTIONS if s not in content]
375
+ if missing:
376
+ issues.append(f"{agent_id}.md missing sections: {missing}")
377
+
378
+ if issues:
379
+ for issue in issues:
380
+ fail(f"Agent structure: {issue}")
381
+ else:
382
+ ok(f"Agent structure: all {len(registered)} agent specs have required sections (role, goals, context)")
383
+
384
+
385
+ # ---------------------------------------------------------------------------
386
+ # Check 8 — File inventory from README.md repository map (continued below)
387
+ # ---------------------------------------------------------------------------
388
+
389
+ def check_file_inventory() -> None:
390
+ print("\n--- File Inventory (README.md repository map) ---")
391
+ readme = (ROOT / "README.md").read_text(encoding="utf-8")
392
+ # Find the repository map code block and extract file names
393
+ block = re.search(r"Repository Map.*?```(.*?)```", readme, re.DOTALL)
394
+ if not block:
395
+ ok("No repository map block found in README.md (skip)")
396
+ return
397
+
398
+ files_in_readme: list[str] = []
399
+ for line in block.group(1).splitlines():
400
+ # Allow optional emoji/unicode between tree indicator and filename
401
+ m = re.search(r"(?:├──|└──)\s*[^\w\s./\\-]*\s*([\w][\w.\-]*\.\w+)", line)
402
+ if m:
403
+ files_in_readme.append(m.group(1))
404
+
405
+ if not files_in_readme:
406
+ ok("Repository map found but no file entries extracted (skip)")
407
+ return
408
+
409
+ missing = [f for f in files_in_readme if not list(ROOT.rglob(f))]
410
+ if missing:
411
+ fail(f"Files in README map not found on disk: {missing}")
412
+ else:
413
+ ok(f"File inventory: all {len(files_in_readme)} files from README map exist on disk")
414
+
415
+
416
+ # ---------------------------------------------------------------------------
417
+ # Check 12 — system_prompt.md freshness
418
+ # ---------------------------------------------------------------------------
419
+
420
+ def _compute_source_hash(root: Path) -> str:
421
+ h = hashlib.sha256()
422
+ agent_files = sorted((root / "agents").glob("*.md")) if (root / "agents").is_dir() else []
423
+ skill_files = sorted((root / "skills").glob("*.md")) if (root / "skills").is_dir() else []
424
+ for path in agent_files + skill_files:
425
+ try:
426
+ h.update(path.read_bytes().replace(b"\r\n", b"\n"))
427
+ except (OSError, PermissionError):
428
+ pass
429
+ return h.hexdigest()[:16]
430
+
431
+
432
+ def check_system_prompt_freshness() -> None:
433
+ print("\n--- system_prompt.md Freshness ---")
434
+ sp_path = ROOT / "system_prompt.md"
435
+ if not sp_path.exists():
436
+ ok("system_prompt.md not present — skipping freshness check (run init_project.py to generate)")
437
+ return
438
+
439
+ try:
440
+ content = sp_path.read_text(encoding="utf-8")
441
+ except (OSError, PermissionError) as e:
442
+ fail(f"Could not read system_prompt.md: {e}")
443
+ return
444
+
445
+ m = re.search(r"Source hash:\s*([a-f0-9]{16})", content)
446
+ if not m:
447
+ fail("system_prompt.md has no source hash — regenerate with: python tools/init_project.py --update")
448
+ return
449
+
450
+ stored = m.group(1)
451
+ current = _compute_source_hash(ROOT)
452
+ if stored != current:
453
+ fail(
454
+ f"system_prompt.md is stale (stored hash {stored} != current {current}). "
455
+ "Regenerate: python tools/init_project.py --update"
456
+ )
457
+ else:
458
+ ok(f"system_prompt.md is current (source hash: {current})")
459
+
460
+
461
+ # ---------------------------------------------------------------------------
462
+ # Main
463
+ # ---------------------------------------------------------------------------
464
+
465
+ def main() -> int:
466
+ if "--version" in sys.argv or "-V" in sys.argv:
467
+ print(f"validate_kit.py {__version__}")
468
+ return 0
469
+ FAILURES.clear()
470
+ print("=" * 60)
471
+ print(" Multi-Agent Assistant Kit — Integrity Checker")
472
+ print(f" Root: {ROOT}")
473
+ print("=" * 60)
474
+
475
+ agents_cfg, domain_cfg, policy_cfg = check_yaml_parse()
476
+ check_agent_files(agents_cfg)
477
+ check_skill_files(agents_cfg)
478
+ check_domain_coverage(domain_cfg, policy_cfg)
479
+ check_primary_agents(domain_cfg, agents_cfg)
480
+ check_output_styles(policy_cfg)
481
+ check_domain_scoring(domain_cfg)
482
+ check_file_inventory()
483
+ check_compressor()
484
+ check_skill_structure(agents_cfg)
485
+ check_agent_structure(agents_cfg)
486
+ check_system_prompt_freshness()
487
+
488
+ total_checks = 12
489
+ failed = len(FAILURES)
490
+ passed = total_checks - failed
491
+
492
+ print("\n" + "=" * 60)
493
+ if failed == 0:
494
+ print(f" Result: ALL {total_checks} checks PASSED")
495
+ else:
496
+ print(f" Result: {passed}/{total_checks} checks passed. {failed} FAILURE(s):")
497
+ for f in FAILURES:
498
+ print(f" - {f}")
499
+ print("=" * 60)
500
+ return 1 if failed else 0
501
+
502
+
503
+ if __name__ == "__main__":
504
+ sys.exit(main())