squads-cli 0.2.0 → 0.2.1

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 (223) hide show
  1. package/README.md +521 -288
  2. package/dist/auth-YW3UPFSB.js +23 -0
  3. package/dist/auth-YW3UPFSB.js.map +1 -0
  4. package/dist/autonomy-PSVZVX7A.js +105 -0
  5. package/dist/autonomy-PSVZVX7A.js.map +1 -0
  6. package/dist/chunk-67RO2HKR.js +174 -0
  7. package/dist/chunk-67RO2HKR.js.map +1 -0
  8. package/dist/chunk-7OCVIDC7.js +12 -0
  9. package/dist/chunk-7OCVIDC7.js.map +1 -0
  10. package/dist/chunk-BODLDQY7.js +452 -0
  11. package/dist/chunk-BODLDQY7.js.map +1 -0
  12. package/dist/chunk-EHQJHRIW.js +103 -0
  13. package/dist/chunk-EHQJHRIW.js.map +1 -0
  14. package/dist/chunk-FFFCFZ6A.js +121 -0
  15. package/dist/chunk-FFFCFZ6A.js.map +1 -0
  16. package/dist/chunk-FIWT2NMM.js +165 -0
  17. package/dist/chunk-FIWT2NMM.js.map +1 -0
  18. package/dist/chunk-HF4WR7RA.js +154 -0
  19. package/dist/chunk-HF4WR7RA.js.map +1 -0
  20. package/dist/chunk-J6QF4ZQX.js +230 -0
  21. package/dist/chunk-J6QF4ZQX.js.map +1 -0
  22. package/dist/chunk-LOA3KWYJ.js +294 -0
  23. package/dist/chunk-LOA3KWYJ.js.map +1 -0
  24. package/dist/chunk-M5FXNY6Y.js +384 -0
  25. package/dist/chunk-M5FXNY6Y.js.map +1 -0
  26. package/dist/chunk-QHNUMM4V.js +87 -0
  27. package/dist/chunk-QHNUMM4V.js.map +1 -0
  28. package/dist/chunk-QJ7C7CMB.js +223 -0
  29. package/dist/chunk-QJ7C7CMB.js.map +1 -0
  30. package/dist/chunk-RM6BWILN.js +74 -0
  31. package/dist/chunk-RM6BWILN.js.map +1 -0
  32. package/dist/chunk-TYFTF53O.js +613 -0
  33. package/dist/chunk-TYFTF53O.js.map +1 -0
  34. package/dist/chunk-TZXD6WFN.js +420 -0
  35. package/dist/chunk-TZXD6WFN.js.map +1 -0
  36. package/dist/chunk-WVOIY5GW.js +621 -0
  37. package/dist/chunk-WVOIY5GW.js.map +1 -0
  38. package/dist/chunk-Z2UKDBNL.js +162 -0
  39. package/dist/chunk-Z2UKDBNL.js.map +1 -0
  40. package/dist/chunk-ZTQ7ISUR.js +338 -0
  41. package/dist/chunk-ZTQ7ISUR.js.map +1 -0
  42. package/dist/cli.js +2483 -5902
  43. package/dist/cli.js.map +1 -1
  44. package/dist/context-GWPF4SEY.js +291 -0
  45. package/dist/context-GWPF4SEY.js.map +1 -0
  46. package/dist/context-feed-AJGVAR6H.js +394 -0
  47. package/dist/context-feed-AJGVAR6H.js.map +1 -0
  48. package/dist/cost-XBCDJ7XC.js +275 -0
  49. package/dist/cost-XBCDJ7XC.js.map +1 -0
  50. package/dist/create-BLFGG6PF.js +286 -0
  51. package/dist/create-BLFGG6PF.js.map +1 -0
  52. package/dist/dashboard-LGT2B2BL.js +951 -0
  53. package/dist/dashboard-LGT2B2BL.js.map +1 -0
  54. package/dist/dashboard-RMK2BOD2.js +794 -0
  55. package/dist/dashboard-RMK2BOD2.js.map +1 -0
  56. package/dist/doctor-XPUIIBHJ.js +374 -0
  57. package/dist/doctor-XPUIIBHJ.js.map +1 -0
  58. package/dist/env-config-SQEI3Y7Y.js +21 -0
  59. package/dist/env-config-SQEI3Y7Y.js.map +1 -0
  60. package/dist/exec-OUXM7JBF.js +223 -0
  61. package/dist/exec-OUXM7JBF.js.map +1 -0
  62. package/dist/feedback-KNAOG5QK.js +229 -0
  63. package/dist/feedback-KNAOG5QK.js.map +1 -0
  64. package/dist/github-UQTM5KMS.js +23 -0
  65. package/dist/github-UQTM5KMS.js.map +1 -0
  66. package/dist/goal-BVHV5573.js +168 -0
  67. package/dist/goal-BVHV5573.js.map +1 -0
  68. package/dist/health-4UXN44PF.js +218 -0
  69. package/dist/health-4UXN44PF.js.map +1 -0
  70. package/dist/history-ILH3SWHB.js +232 -0
  71. package/dist/history-ILH3SWHB.js.map +1 -0
  72. package/dist/index.d.ts +736 -8
  73. package/dist/index.js +1312 -6
  74. package/dist/index.js.map +1 -1
  75. package/dist/init-XQZ7BOGT.js +812 -0
  76. package/dist/init-XQZ7BOGT.js.map +1 -0
  77. package/dist/kpi-RQIU7WGK.js +413 -0
  78. package/dist/kpi-RQIU7WGK.js.map +1 -0
  79. package/dist/learn-OIFUVZAS.js +269 -0
  80. package/dist/learn-OIFUVZAS.js.map +1 -0
  81. package/dist/login-DXZANWZY.js +155 -0
  82. package/dist/login-DXZANWZY.js.map +1 -0
  83. package/dist/memory-T3ACCS7E.js +560 -0
  84. package/dist/memory-T3ACCS7E.js.map +1 -0
  85. package/dist/memory-VNF2VFRB.js +23 -0
  86. package/dist/memory-VNF2VFRB.js.map +1 -0
  87. package/dist/progress-DAUZMT3N.js +202 -0
  88. package/dist/progress-DAUZMT3N.js.map +1 -0
  89. package/dist/providers-3P5D2XL5.js +65 -0
  90. package/dist/providers-3P5D2XL5.js.map +1 -0
  91. package/dist/results-UECWGLTB.js +224 -0
  92. package/dist/results-UECWGLTB.js.map +1 -0
  93. package/dist/run-I6KAXU6U.js +4049 -0
  94. package/dist/run-I6KAXU6U.js.map +1 -0
  95. package/dist/session-HBU6KZOD.js +64 -0
  96. package/dist/session-HBU6KZOD.js.map +1 -0
  97. package/dist/sessions-CK25VGPL.js +333 -0
  98. package/dist/sessions-CK25VGPL.js.map +1 -0
  99. package/dist/squad-parser-DCG65BJS.js +35 -0
  100. package/dist/squad-parser-DCG65BJS.js.map +1 -0
  101. package/dist/stats-G6NAU5BD.js +334 -0
  102. package/dist/stats-G6NAU5BD.js.map +1 -0
  103. package/dist/status-AQNLDZVN.js +352 -0
  104. package/dist/status-AQNLDZVN.js.map +1 -0
  105. package/dist/sync-ZI3MHA4G.js +836 -0
  106. package/dist/sync-ZI3MHA4G.js.map +1 -0
  107. package/dist/templates/core/AGENTS.md.template +51 -0
  108. package/dist/templates/core/BUSINESS_BRIEF.md.template +29 -0
  109. package/dist/templates/core/CLAUDE.md.template +48 -0
  110. package/dist/templates/core/provider.yaml.template +5 -0
  111. package/dist/templates/first-squad/SQUAD.md.template +23 -0
  112. package/dist/templates/first-squad/lead.md.template +44 -0
  113. package/dist/templates/memory/getting-started/state.md.template +19 -0
  114. package/dist/templates/seed/BUSINESS_BRIEF.md.template +27 -0
  115. package/dist/templates/seed/CLAUDE.md.template +119 -0
  116. package/dist/templates/seed/README.md.template +42 -0
  117. package/dist/templates/seed/config/SYSTEM.md +52 -0
  118. package/dist/templates/seed/config/provider.yaml +4 -0
  119. package/dist/templates/seed/hooks/settings.json.template +31 -0
  120. package/dist/templates/seed/memory/company/directives.md +37 -0
  121. package/dist/templates/seed/memory/company/manager/state.md +16 -0
  122. package/dist/templates/seed/memory/engineering/issue-solver/state.md +12 -0
  123. package/dist/templates/seed/memory/intelligence/intel-lead/state.md +9 -0
  124. package/dist/templates/seed/memory/marketing/content-drafter/state.md +12 -0
  125. package/dist/templates/seed/memory/operations/ops-lead/state.md +12 -0
  126. package/dist/templates/seed/memory/product/lead/state.md +14 -0
  127. package/dist/templates/seed/memory/research/lead/state.md +14 -0
  128. package/dist/templates/seed/skills/gh/SKILL.md +57 -0
  129. package/dist/templates/seed/skills/squads-cli/SKILL.md +84 -0
  130. package/dist/templates/seed/squads/company/SQUAD.md +51 -0
  131. package/dist/templates/seed/squads/company/company-critic.md +49 -0
  132. package/dist/templates/seed/squads/company/company-eval.md +49 -0
  133. package/dist/templates/seed/squads/company/event-dispatcher.md +43 -0
  134. package/dist/templates/seed/squads/company/goal-tracker.md +43 -0
  135. package/dist/templates/seed/squads/company/manager.md +54 -0
  136. package/dist/templates/seed/squads/engineering/SQUAD.md +48 -0
  137. package/dist/templates/seed/squads/engineering/code-reviewer.md +57 -0
  138. package/dist/templates/seed/squads/engineering/issue-solver.md +58 -0
  139. package/dist/templates/seed/squads/engineering/test-writer.md +50 -0
  140. package/dist/templates/seed/squads/intelligence/SQUAD.md +38 -0
  141. package/dist/templates/seed/squads/intelligence/intel-critic.md +36 -0
  142. package/dist/templates/seed/squads/intelligence/intel-eval.md +31 -0
  143. package/dist/templates/seed/squads/intelligence/intel-lead.md +71 -0
  144. package/dist/templates/seed/squads/marketing/SQUAD.md +47 -0
  145. package/dist/templates/seed/squads/marketing/content-drafter.md +71 -0
  146. package/dist/templates/seed/squads/marketing/growth-analyst.md +49 -0
  147. package/dist/templates/seed/squads/marketing/social-poster.md +44 -0
  148. package/dist/templates/seed/squads/operations/SQUAD.md +45 -0
  149. package/dist/templates/seed/squads/operations/finance-tracker.md +47 -0
  150. package/dist/templates/seed/squads/operations/goal-tracker.md +48 -0
  151. package/dist/templates/seed/squads/operations/ops-lead.md +58 -0
  152. package/dist/templates/seed/squads/product/SQUAD.md +41 -0
  153. package/dist/templates/seed/squads/product/lead.md +56 -0
  154. package/dist/templates/seed/squads/product/scanner.md +50 -0
  155. package/dist/templates/seed/squads/product/worker.md +55 -0
  156. package/dist/templates/seed/squads/research/SQUAD.md +38 -0
  157. package/dist/templates/seed/squads/research/analyst.md +50 -0
  158. package/dist/templates/seed/squads/research/lead.md +52 -0
  159. package/dist/templates/seed/squads/research/synthesizer.md +59 -0
  160. package/dist/templates/skills/squads-learn/SKILL.md +86 -0
  161. package/dist/templates/skills/squads-workflow/instruction.md +70 -0
  162. package/dist/terminal-FBQFQTKZ.js +55 -0
  163. package/dist/terminal-FBQFQTKZ.js.map +1 -0
  164. package/dist/update-D7CGIZ3M.js +18 -0
  165. package/dist/update-D7CGIZ3M.js.map +1 -0
  166. package/dist/update-STU276HR.js +83 -0
  167. package/dist/update-STU276HR.js.map +1 -0
  168. package/package.json +31 -13
  169. package/templates/core/AGENTS.md.template +51 -0
  170. package/templates/core/BUSINESS_BRIEF.md.template +29 -0
  171. package/templates/core/CLAUDE.md.template +48 -0
  172. package/templates/core/provider.yaml.template +5 -0
  173. package/templates/first-squad/SQUAD.md.template +23 -0
  174. package/templates/first-squad/lead.md.template +44 -0
  175. package/templates/memory/getting-started/state.md.template +19 -0
  176. package/templates/seed/BUSINESS_BRIEF.md.template +27 -0
  177. package/templates/seed/CLAUDE.md.template +119 -0
  178. package/templates/seed/README.md.template +42 -0
  179. package/templates/seed/config/SYSTEM.md +52 -0
  180. package/templates/seed/config/provider.yaml +4 -0
  181. package/templates/seed/hooks/settings.json.template +31 -0
  182. package/templates/seed/memory/company/directives.md +37 -0
  183. package/templates/seed/memory/company/manager/state.md +16 -0
  184. package/templates/seed/memory/engineering/issue-solver/state.md +12 -0
  185. package/templates/seed/memory/intelligence/intel-lead/state.md +9 -0
  186. package/templates/seed/memory/marketing/content-drafter/state.md +12 -0
  187. package/templates/seed/memory/operations/ops-lead/state.md +12 -0
  188. package/templates/seed/memory/product/lead/state.md +14 -0
  189. package/templates/seed/memory/research/lead/state.md +14 -0
  190. package/templates/seed/skills/gh/SKILL.md +57 -0
  191. package/templates/seed/skills/squads-cli/SKILL.md +84 -0
  192. package/templates/seed/squads/company/SQUAD.md +51 -0
  193. package/templates/seed/squads/company/company-critic.md +49 -0
  194. package/templates/seed/squads/company/company-eval.md +49 -0
  195. package/templates/seed/squads/company/event-dispatcher.md +43 -0
  196. package/templates/seed/squads/company/goal-tracker.md +43 -0
  197. package/templates/seed/squads/company/manager.md +54 -0
  198. package/templates/seed/squads/engineering/SQUAD.md +48 -0
  199. package/templates/seed/squads/engineering/code-reviewer.md +57 -0
  200. package/templates/seed/squads/engineering/issue-solver.md +58 -0
  201. package/templates/seed/squads/engineering/test-writer.md +50 -0
  202. package/templates/seed/squads/intelligence/SQUAD.md +38 -0
  203. package/templates/seed/squads/intelligence/intel-critic.md +36 -0
  204. package/templates/seed/squads/intelligence/intel-eval.md +31 -0
  205. package/templates/seed/squads/intelligence/intel-lead.md +71 -0
  206. package/templates/seed/squads/marketing/SQUAD.md +47 -0
  207. package/templates/seed/squads/marketing/content-drafter.md +71 -0
  208. package/templates/seed/squads/marketing/growth-analyst.md +49 -0
  209. package/templates/seed/squads/marketing/social-poster.md +44 -0
  210. package/templates/seed/squads/operations/SQUAD.md +45 -0
  211. package/templates/seed/squads/operations/finance-tracker.md +47 -0
  212. package/templates/seed/squads/operations/goal-tracker.md +48 -0
  213. package/templates/seed/squads/operations/ops-lead.md +58 -0
  214. package/templates/seed/squads/product/SQUAD.md +41 -0
  215. package/templates/seed/squads/product/lead.md +56 -0
  216. package/templates/seed/squads/product/scanner.md +50 -0
  217. package/templates/seed/squads/product/worker.md +55 -0
  218. package/templates/seed/squads/research/SQUAD.md +38 -0
  219. package/templates/seed/squads/research/analyst.md +50 -0
  220. package/templates/seed/squads/research/lead.md +52 -0
  221. package/templates/seed/squads/research/synthesizer.md +59 -0
  222. package/templates/skills/squads-learn/SKILL.md +86 -0
  223. package/templates/skills/squads-workflow/instruction.md +70 -0
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/templates.ts
4
+ import { readFileSync, existsSync, readdirSync, statSync } from "fs";
5
+ import { join, dirname } from "path";
6
+ import { fileURLToPath } from "url";
7
+ import { homedir } from "os";
8
+ var __filename = fileURLToPath(import.meta.url);
9
+ var __dirname = dirname(__filename);
10
+ function findTemplatesDir() {
11
+ const repoLocations = [
12
+ join(process.cwd(), "..", "agents-squads"),
13
+ // Sibling directory
14
+ join(homedir(), "agents-squads", "agents-squads"),
15
+ // Common clone location
16
+ join(homedir(), "code", "agents-squads"),
17
+ // Alternative
18
+ join(homedir(), "projects", "agents-squads")
19
+ // Alternative
20
+ ];
21
+ for (const repoPath of repoLocations) {
22
+ const templatePath = join(repoPath, ".agents", "squads", "_template");
23
+ if (existsSync(templatePath) && existsSync(join(templatePath, "SQUAD.md"))) {
24
+ return {
25
+ type: "repo",
26
+ path: repoPath,
27
+ description: `agents-squads repo at ${repoPath}`
28
+ };
29
+ }
30
+ }
31
+ const globalTemplates = join(homedir(), ".squads", "templates");
32
+ if (existsSync(globalTemplates)) {
33
+ return {
34
+ type: "global",
35
+ path: globalTemplates,
36
+ description: "User global templates (~/.squads/templates/)"
37
+ };
38
+ }
39
+ const bundledLocations = [
40
+ join(__dirname, "..", "templates"),
41
+ // dist/templates (production)
42
+ join(__dirname, "..", "..", "templates"),
43
+ // src/../templates (dev)
44
+ join(__dirname, "..", "..", "..", "templates")
45
+ // npm installed
46
+ ];
47
+ for (const bundled of bundledLocations) {
48
+ if (existsSync(bundled)) {
49
+ return {
50
+ type: "bundled",
51
+ path: bundled,
52
+ description: "CLI bundled templates"
53
+ };
54
+ }
55
+ }
56
+ return {
57
+ type: "bundled",
58
+ path: join(__dirname, "..", "..", "templates"),
59
+ description: "CLI bundled templates (not found)"
60
+ };
61
+ }
62
+ var TEMPLATE_SOURCE = findTemplatesDir();
63
+ function resolveTemplatePath(templatePath) {
64
+ if (TEMPLATE_SOURCE.type === "repo") {
65
+ const mappings = {
66
+ "first-squad/SQUAD.md.template": ".agents/squads/_template/SQUAD.md",
67
+ "first-squad/lead.md.template": ".agents/squads/_template/agent.md",
68
+ "core/AGENTS.md.template": "AGENTS.md",
69
+ // Use repo's AGENTS.md? Or skip
70
+ "core/CLAUDE.md.template": "CLAUDE.md"
71
+ };
72
+ const mapped = mappings[templatePath];
73
+ if (mapped) {
74
+ return join(TEMPLATE_SOURCE.path, mapped);
75
+ }
76
+ return join(TEMPLATE_SOURCE.path, templatePath);
77
+ }
78
+ return join(TEMPLATE_SOURCE.path, templatePath);
79
+ }
80
+ function loadTemplate(templatePath, variables = {}) {
81
+ const fullPath = resolveTemplatePath(templatePath);
82
+ if (!existsSync(fullPath)) {
83
+ if (TEMPLATE_SOURCE.type === "repo") {
84
+ const bundledPath = join(__dirname, "..", "templates", templatePath);
85
+ if (existsSync(bundledPath)) {
86
+ return loadTemplateFromPath(bundledPath, variables);
87
+ }
88
+ }
89
+ throw new Error(`Template not found: ${templatePath} (source: ${TEMPLATE_SOURCE.description})`);
90
+ }
91
+ return loadTemplateFromPath(fullPath, variables);
92
+ }
93
+ function loadTemplateFromPath(fullPath, variables) {
94
+ let content = readFileSync(fullPath, "utf-8");
95
+ for (const [key, value] of Object.entries(variables)) {
96
+ if (value !== void 0) {
97
+ const pattern = new RegExp(`\\{\\{${key}\\}\\}`, "g");
98
+ content = content.replace(pattern, value);
99
+ }
100
+ }
101
+ for (const [key, value] of Object.entries(variables)) {
102
+ if (value !== void 0) {
103
+ const pattern = new RegExp(`\\{${key}\\}`, "g");
104
+ content = content.replace(pattern, value);
105
+ }
106
+ }
107
+ return content;
108
+ }
109
+ function toKebabCase(name) {
110
+ return name.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
111
+ }
112
+ function toTitleCase(name) {
113
+ return name.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
114
+ }
115
+
116
+ export {
117
+ loadTemplate,
118
+ toKebabCase,
119
+ toTitleCase
120
+ };
121
+ //# sourceMappingURL=chunk-FFFCFZ6A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/templates.ts"],"sourcesContent":["/**\n * Template loading and variable substitution for squads setup\n *\n * Template sources (checked in order):\n * 1. Local agents-squads repo clone (if in agents-squads directory)\n * 2. User's global templates (~/.squads/templates/)\n * 3. Bundled templates (dist/templates/)\n */\n\nimport { readFileSync, existsSync, readdirSync, statSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { homedir } from 'os';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// GitHub repo for templates\nexport const AGENTS_SQUADS_REPO = 'https://github.com/agents-squads/agents-squads.git';\n\n/**\n * Template source info\n */\nexport interface TemplateSource {\n type: 'repo' | 'global' | 'bundled';\n path: string;\n description: string;\n}\n\n/**\n * Find templates directory with priority:\n * 1. Local agents-squads repo (best - has domain starters)\n * 2. User's global templates (~/.squads/templates/)\n * 3. Bundled templates in CLI package\n */\nfunction findTemplatesDir(): TemplateSource {\n // 1. Check for local agents-squads repo (look for .agents/squads/_template)\n const repoLocations = [\n join(process.cwd(), '..', 'agents-squads'), // Sibling directory\n join(homedir(), 'agents-squads', 'agents-squads'), // Common clone location\n join(homedir(), 'code', 'agents-squads'), // Alternative\n join(homedir(), 'projects', 'agents-squads'), // Alternative\n ];\n\n for (const repoPath of repoLocations) {\n const templatePath = join(repoPath, '.agents', 'squads', '_template');\n if (existsSync(templatePath) && existsSync(join(templatePath, 'SQUAD.md'))) {\n return {\n type: 'repo',\n path: repoPath,\n description: `agents-squads repo at ${repoPath}`,\n };\n }\n }\n\n // 2. Check user's global templates\n const globalTemplates = join(homedir(), '.squads', 'templates');\n if (existsSync(globalTemplates)) {\n return {\n type: 'global',\n path: globalTemplates,\n description: 'User global templates (~/.squads/templates/)',\n };\n }\n\n // 3. Fall back to bundled templates\n const bundledLocations = [\n join(__dirname, '..', 'templates'), // dist/templates (production)\n join(__dirname, '..', '..', 'templates'), // src/../templates (dev)\n join(__dirname, '..', '..', '..', 'templates'), // npm installed\n ];\n\n for (const bundled of bundledLocations) {\n if (existsSync(bundled)) {\n return {\n type: 'bundled',\n path: bundled,\n description: 'CLI bundled templates',\n };\n }\n }\n\n // Default fallback\n return {\n type: 'bundled',\n path: join(__dirname, '..', '..', 'templates'),\n description: 'CLI bundled templates (not found)',\n };\n}\n\nconst TEMPLATE_SOURCE = findTemplatesDir();\n\n/**\n * Get current template source info\n */\nexport function getTemplateSource(): TemplateSource {\n return TEMPLATE_SOURCE;\n}\n\n/**\n * Check if using agents-squads repo templates\n */\nexport function isUsingRepoTemplates(): boolean {\n return TEMPLATE_SOURCE.type === 'repo';\n}\n\n/**\n * Template variables for substitution\n */\nexport interface TemplateVariables {\n // Squad variables\n SQUAD_NAME?: string; // Human-readable name: \"My First Squad\"\n SQUAD_ID?: string; // Kebab-case ID: \"my-first-squad\"\n SQUAD_DESCRIPTION?: string;\n GOAL?: string;\n\n // Provider variables\n PROVIDER?: string; // Provider ID: \"claude\", \"gemini\", etc.\n PROVIDER_NAME?: string; // Display name: \"Claude Code (Anthropic)\"\n\n // Custom variables\n [key: string]: string | undefined;\n}\n\n/**\n * Resolve template path based on source type\n */\nfunction resolveTemplatePath(templatePath: string): string {\n if (TEMPLATE_SOURCE.type === 'repo') {\n // For repo source, map template paths to repo structure\n // CLI templates are in different locations than repo templates\n const mappings: Record<string, string> = {\n 'first-squad/SQUAD.md.template': '.agents/squads/_template/SQUAD.md',\n 'first-squad/lead.md.template': '.agents/squads/_template/agent.md',\n 'core/AGENTS.md.template': 'AGENTS.md', // Use repo's AGENTS.md? Or skip\n 'core/CLAUDE.md.template': 'CLAUDE.md',\n };\n\n const mapped = mappings[templatePath];\n if (mapped) {\n return join(TEMPLATE_SOURCE.path, mapped);\n }\n\n // For unmapped, try direct path\n return join(TEMPLATE_SOURCE.path, templatePath);\n }\n\n // For bundled/global, use direct path\n return join(TEMPLATE_SOURCE.path, templatePath);\n}\n\n/**\n * Load a template file and substitute variables\n */\nexport function loadTemplate(templatePath: string, variables: TemplateVariables = {}): string {\n const fullPath = resolveTemplatePath(templatePath);\n\n if (!existsSync(fullPath)) {\n // Try bundled fallback if repo template not found\n if (TEMPLATE_SOURCE.type === 'repo') {\n const bundledPath = join(__dirname, '..', 'templates', templatePath);\n if (existsSync(bundledPath)) {\n return loadTemplateFromPath(bundledPath, variables);\n }\n }\n throw new Error(`Template not found: ${templatePath} (source: ${TEMPLATE_SOURCE.description})`);\n }\n\n return loadTemplateFromPath(fullPath, variables);\n}\n\nfunction loadTemplateFromPath(fullPath: string, variables: TemplateVariables): string {\n let content = readFileSync(fullPath, 'utf-8');\n\n // Substitute all {{VARIABLE}} patterns\n for (const [key, value] of Object.entries(variables)) {\n if (value !== undefined) {\n const pattern = new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, 'g');\n content = content.replace(pattern, value);\n }\n }\n\n // Also substitute {VARIABLE} patterns (used in repo templates)\n for (const [key, value] of Object.entries(variables)) {\n if (value !== undefined) {\n const pattern = new RegExp(`\\\\{${key}\\\\}`, 'g');\n content = content.replace(pattern, value);\n }\n }\n\n return content;\n}\n\n/**\n * Check if a template exists\n */\nexport function templateExists(templatePath: string): boolean {\n return existsSync(resolveTemplatePath(templatePath));\n}\n\n/**\n * Get domain starters from agents-squads repo\n * Returns available domains if using repo templates\n */\nexport function getDomainStarters(): string[] {\n if (TEMPLATE_SOURCE.type !== 'repo') {\n return [];\n }\n\n const domainsDir = join(TEMPLATE_SOURCE.path, 'domains');\n if (!existsSync(domainsDir)) {\n return [];\n }\n\n return readdirSync(domainsDir, { withFileTypes: true })\n .filter(d => d.isDirectory())\n .map(d => d.name);\n}\n\n/**\n * Copy a domain starter to current directory\n */\nexport function getDomainStarterPath(domain: string): string | null {\n if (TEMPLATE_SOURCE.type !== 'repo') {\n return null;\n }\n\n const domainPath = join(TEMPLATE_SOURCE.path, 'domains', domain);\n if (!existsSync(domainPath)) {\n return null;\n }\n\n return domainPath;\n}\n\n/**\n * List all templates in a directory\n */\nexport function listTemplates(dir: string): string[] {\n let fullDir: string;\n\n if (TEMPLATE_SOURCE.type === 'repo') {\n fullDir = join(TEMPLATE_SOURCE.path, '.agents', 'squads', '_template');\n } else {\n fullDir = join(TEMPLATE_SOURCE.path, dir);\n }\n\n if (!existsSync(fullDir)) {\n return [];\n }\n\n return readdirSync(fullDir).filter(f => {\n const stat = statSync(join(fullDir, f));\n return stat.isFile() && (f.endsWith('.template') || f.endsWith('.md'));\n });\n}\n\n/**\n * Get the templates directory path\n */\nexport function getTemplatesDir(): string {\n return TEMPLATE_SOURCE.path;\n}\n\n/**\n * Load all templates from a directory with variables substituted\n */\nexport function loadTemplateDirectory(\n dir: string,\n variables: TemplateVariables = {}\n): Map<string, string> {\n const result = new Map<string, string>();\n const fullDir = join(TEMPLATE_SOURCE.path, dir);\n\n if (!existsSync(fullDir)) {\n return result;\n }\n\n const processDir = (currentDir: string, prefix: string) => {\n const entries = readdirSync(currentDir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(currentDir, entry.name);\n const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;\n\n if (entry.isDirectory()) {\n processDir(fullPath, relativePath);\n } else if (entry.isFile()) {\n let content = readFileSync(fullPath, 'utf-8');\n\n // Substitute variables\n for (const [key, value] of Object.entries(variables)) {\n if (value !== undefined) {\n const pattern = new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, 'g');\n content = content.replace(pattern, value);\n }\n }\n\n // Remove .template suffix for output filename\n const outputName = relativePath.replace(/\\.template$/, '');\n result.set(outputName, content);\n }\n }\n };\n\n processDir(fullDir, '');\n return result;\n}\n\n/**\n * Convert a name to kebab-case for use as ID\n */\nexport function toKebabCase(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n\n/**\n * Convert a name to Title Case for display\n */\nexport function toTitleCase(name: string): string {\n return name\n .split(/[-_\\s]+/)\n .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(' ');\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/github.ts
4
+ import { execSync } from "child_process";
5
+ import { createSign } from "crypto";
6
+ import { readFileSync, existsSync } from "fs";
7
+ import { join } from "path";
8
+ import { homedir } from "os";
9
+ var APP_CONFIG_PATH = join(homedir(), ".squads", "secrets", "github-app.json");
10
+ var BOT_NAME = "agents-squads[bot]";
11
+ var BOT_EMAIL = "266303152+agents-squads[bot]@users.noreply.github.com";
12
+ var AI_COAUTHORS = {
13
+ anthropic: "Co-Authored-By: claude[bot] <209825114+claude[bot]@users.noreply.github.com>",
14
+ claude: "Co-Authored-By: claude[bot] <209825114+claude[bot]@users.noreply.github.com>",
15
+ gemini: "Co-Authored-By: gemini-code-assist <200291788+gemini-code-assist@users.noreply.github.com>",
16
+ google: "Co-Authored-By: gemini-code-assist <200291788+gemini-code-assist@users.noreply.github.com>",
17
+ openai: "Co-Authored-By: GPT <noreply@openai.com>"
18
+ };
19
+ function getCoAuthorTrailer(provider) {
20
+ const key = provider.toLowerCase().replace(/-.*$/, "");
21
+ return AI_COAUTHORS[key] || `Co-Authored-By: ${provider} <noreply@agents-squads.com>`;
22
+ }
23
+ var cachedToken = null;
24
+ function loadAppConfig() {
25
+ if (!existsSync(APP_CONFIG_PATH)) return null;
26
+ try {
27
+ const config = JSON.parse(readFileSync(APP_CONFIG_PATH, "utf-8"));
28
+ if (!config.app_id || !config.installation_id || !config.pem_path) return null;
29
+ return config;
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+ function generateJWT(appId, pemPath) {
35
+ const resolvedPath = pemPath.replace(/^~/, homedir());
36
+ const pem = readFileSync(resolvedPath, "utf-8");
37
+ const now = Math.floor(Date.now() / 1e3);
38
+ const header = Buffer.from(JSON.stringify({ alg: "RS256", typ: "JWT" })).toString("base64url");
39
+ const payload = Buffer.from(JSON.stringify({
40
+ iat: now - 60,
41
+ exp: now + 600,
42
+ iss: String(appId)
43
+ })).toString("base64url");
44
+ const sign = createSign("RSA-SHA256");
45
+ sign.update(`${header}.${payload}`);
46
+ const signature = sign.sign(pem, "base64url");
47
+ return `${header}.${payload}.${signature}`;
48
+ }
49
+ async function getGitHubAppToken() {
50
+ if (cachedToken && cachedToken.expiresAt > Date.now() + 5 * 60 * 1e3) {
51
+ return cachedToken.token;
52
+ }
53
+ const config = loadAppConfig();
54
+ if (!config) return null;
55
+ try {
56
+ const jwt = generateJWT(config.app_id, config.pem_path);
57
+ const response = await fetch(
58
+ `https://api.github.com/app/installations/${config.installation_id}/access_tokens`,
59
+ {
60
+ method: "POST",
61
+ headers: {
62
+ Authorization: `Bearer ${jwt}`,
63
+ Accept: "application/vnd.github+json"
64
+ }
65
+ }
66
+ );
67
+ if (!response.ok) return null;
68
+ const data = await response.json();
69
+ cachedToken = {
70
+ token: data.token,
71
+ expiresAt: new Date(data.expires_at).getTime()
72
+ };
73
+ return data.token;
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+ async function getBotGitEnv() {
79
+ const token = await getGitHubAppToken();
80
+ if (!token) return {};
81
+ return {
82
+ GIT_AUTHOR_NAME: BOT_NAME,
83
+ GIT_AUTHOR_EMAIL: BOT_EMAIL,
84
+ GIT_COMMITTER_NAME: BOT_NAME,
85
+ GIT_COMMITTER_EMAIL: BOT_EMAIL
86
+ };
87
+ }
88
+ async function getBotGhEnv() {
89
+ const token = await getGitHubAppToken();
90
+ if (!token) return {};
91
+ return { GH_TOKEN: token };
92
+ }
93
+ async function getBotPushUrl(repo) {
94
+ const token = await getGitHubAppToken();
95
+ if (!token) return null;
96
+ return `https://x-access-token:${token}@github.com/${repo}.git`;
97
+ }
98
+ function detectGitHubOrg(cwd = process.cwd()) {
99
+ try {
100
+ const remote = execSync("git remote get-url origin", {
101
+ cwd,
102
+ encoding: "utf-8",
103
+ stdio: ["pipe", "pipe", "pipe"]
104
+ }).trim();
105
+ const match = remote.match(/github\.com[:/]([^/]+)\//);
106
+ return match ? match[1] : void 0;
107
+ } catch {
108
+ return void 0;
109
+ }
110
+ }
111
+ function detectGitHubRepo(cwd = process.cwd()) {
112
+ try {
113
+ const remote = execSync("git remote get-url origin", {
114
+ cwd,
115
+ encoding: "utf-8",
116
+ stdio: ["pipe", "pipe", "pipe"]
117
+ }).trim();
118
+ const match = remote.match(/github\.com[:/]([^/]+\/[^/.]+)/);
119
+ return match ? match[1] : void 0;
120
+ } catch {
121
+ return void 0;
122
+ }
123
+ }
124
+ function createGitHubRepo(name, options = {}) {
125
+ const { org, description, isPrivate = true } = options;
126
+ const fullName = org ? `${org}/${name}` : name;
127
+ try {
128
+ execSync("gh --version", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
129
+ } catch {
130
+ throw new Error("gh CLI not found. Install it from https://cli.github.com/");
131
+ }
132
+ try {
133
+ execSync(`gh repo view ${fullName} --json name`, {
134
+ encoding: "utf-8",
135
+ stdio: ["pipe", "pipe", "pipe"]
136
+ });
137
+ throw new Error(`Repository "${fullName}" already exists on GitHub`);
138
+ } catch (err) {
139
+ if (err instanceof Error && err.message.includes("already exists")) {
140
+ throw err;
141
+ }
142
+ }
143
+ const args = ["gh", "repo", "create", fullName, isPrivate ? "--private" : "--public"];
144
+ if (description) {
145
+ args.push("--description", description);
146
+ }
147
+ const output = execSync(args.join(" "), {
148
+ encoding: "utf-8",
149
+ stdio: ["pipe", "pipe", "pipe"]
150
+ }).trim();
151
+ const url = output || `https://github.com/${fullName}`;
152
+ return { url, fullName };
153
+ }
154
+
155
+ export {
156
+ getCoAuthorTrailer,
157
+ getGitHubAppToken,
158
+ getBotGitEnv,
159
+ getBotGhEnv,
160
+ getBotPushUrl,
161
+ detectGitHubOrg,
162
+ detectGitHubRepo,
163
+ createGitHubRepo
164
+ };
165
+ //# sourceMappingURL=chunk-FIWT2NMM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/github.ts"],"sourcesContent":["import { execSync } from 'child_process';\nimport { createSign } from 'crypto';\nimport { readFileSync, existsSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\nexport interface GitHubRepoResult {\n url: string;\n fullName: string;\n}\n\n// ── GitHub App Authentication ─────────────────────────────────────────\n\ninterface GitHubAppConfig {\n app_id: number;\n installation_id: number;\n pem_path: string;\n}\n\nconst APP_CONFIG_PATH = join(homedir(), '.squads', 'secrets', 'github-app.json');\nconst BOT_NAME = 'agents-squads[bot]';\nconst BOT_EMAIL = '266303152+agents-squads[bot]@users.noreply.github.com';\n\n// Co-author trailers that resolve to real GitHub profiles with avatars.\n// These show up in the Contributors section of repos.\nconst AI_COAUTHORS: Record<string, string> = {\n anthropic: 'Co-Authored-By: claude[bot] <209825114+claude[bot]@users.noreply.github.com>',\n claude: 'Co-Authored-By: claude[bot] <209825114+claude[bot]@users.noreply.github.com>',\n gemini: 'Co-Authored-By: gemini-code-assist <200291788+gemini-code-assist@users.noreply.github.com>',\n google: 'Co-Authored-By: gemini-code-assist <200291788+gemini-code-assist@users.noreply.github.com>',\n openai: 'Co-Authored-By: GPT <noreply@openai.com>',\n};\n\n/**\n * Get the Co-Authored-By trailer for the model that wrote the code.\n * Uses GitHub's noreply emails so avatars show in contributor graph.\n */\nexport function getCoAuthorTrailer(provider: string): string {\n const key = provider.toLowerCase().replace(/-.*$/, ''); // \"claude-sonnet\" → \"claude\"\n return AI_COAUTHORS[key] || `Co-Authored-By: ${provider} <noreply@agents-squads.com>`;\n}\n\nlet cachedToken: { token: string; expiresAt: number } | null = null;\n\nfunction loadAppConfig(): GitHubAppConfig | null {\n if (!existsSync(APP_CONFIG_PATH)) return null;\n try {\n const config = JSON.parse(readFileSync(APP_CONFIG_PATH, 'utf-8'));\n if (!config.app_id || !config.installation_id || !config.pem_path) return null;\n return config;\n } catch {\n return null;\n }\n}\n\nfunction generateJWT(appId: number, pemPath: string): string {\n const resolvedPath = pemPath.replace(/^~/, homedir());\n const pem = readFileSync(resolvedPath, 'utf-8');\n const now = Math.floor(Date.now() / 1000);\n\n const header = Buffer.from(JSON.stringify({ alg: 'RS256', typ: 'JWT' })).toString('base64url');\n const payload = Buffer.from(JSON.stringify({\n iat: now - 60,\n exp: now + 600,\n iss: String(appId),\n })).toString('base64url');\n\n const sign = createSign('RSA-SHA256');\n sign.update(`${header}.${payload}`);\n const signature = sign.sign(pem, 'base64url');\n\n return `${header}.${payload}.${signature}`;\n}\n\n/**\n * Get a GitHub App installation token.\n * Caches the token until 5 minutes before expiry.\n * Returns null if the app is not configured.\n */\nexport async function getGitHubAppToken(): Promise<string | null> {\n // Return cached token if still valid (5 min buffer)\n if (cachedToken && cachedToken.expiresAt > Date.now() + 5 * 60 * 1000) {\n return cachedToken.token;\n }\n\n const config = loadAppConfig();\n if (!config) return null;\n\n try {\n const jwt = generateJWT(config.app_id, config.pem_path);\n const response = await fetch(\n `https://api.github.com/app/installations/${config.installation_id}/access_tokens`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${jwt}`,\n Accept: 'application/vnd.github+json',\n },\n },\n );\n\n if (!response.ok) return null;\n\n const data = await response.json() as { token: string; expires_at: string };\n cachedToken = {\n token: data.token,\n expiresAt: new Date(data.expires_at).getTime(),\n };\n return data.token;\n } catch {\n return null;\n }\n}\n\n/**\n * Git environment variables for bot-authored commits.\n * Falls back to empty object if app not configured (uses user's git config).\n */\nexport async function getBotGitEnv(): Promise<Record<string, string>> {\n const token = await getGitHubAppToken();\n if (!token) return {};\n\n return {\n GIT_AUTHOR_NAME: BOT_NAME,\n GIT_AUTHOR_EMAIL: BOT_EMAIL,\n GIT_COMMITTER_NAME: BOT_NAME,\n GIT_COMMITTER_EMAIL: BOT_EMAIL,\n };\n}\n\n/**\n * Environment for gh CLI commands authenticated as the bot.\n * Falls back to empty object (uses user's gh auth).\n */\nexport async function getBotGhEnv(): Promise<Record<string, string>> {\n const token = await getGitHubAppToken();\n if (!token) return {};\n return { GH_TOKEN: token };\n}\n\n/**\n * Get the git push URL with bot token embedded for authentication.\n * Returns null if app not configured.\n */\nexport async function getBotPushUrl(repo: string): Promise<string | null> {\n const token = await getGitHubAppToken();\n if (!token) return null;\n return `https://x-access-token:${token}@github.com/${repo}.git`;\n}\n\n\n/**\n * Detect GitHub org from the current project's git remote.\n * Falls back to undefined if not in a git repo or remote is not GitHub.\n */\nexport function detectGitHubOrg(cwd: string = process.cwd()): string | undefined {\n try {\n const remote = execSync('git remote get-url origin', {\n cwd,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n // Match github.com/<org>/<repo>\n const match = remote.match(/github\\.com[:/]([^/]+)\\//);\n return match ? match[1] : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Detect full GitHub repo (org/name) from git remote.\n */\nexport function detectGitHubRepo(cwd: string = process.cwd()): string | undefined {\n try {\n const remote = execSync('git remote get-url origin', {\n cwd,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n const match = remote.match(/github\\.com[:/]([^/]+\\/[^/.]+)/);\n return match ? match[1] : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Create a GitHub repository using the gh CLI.\n * Requires gh CLI to be installed and authenticated.\n */\nexport function createGitHubRepo(\n name: string,\n options: {\n org?: string;\n description?: string;\n isPrivate?: boolean;\n } = {}\n): GitHubRepoResult {\n const { org, description, isPrivate = true } = options;\n const fullName = org ? `${org}/${name}` : name;\n\n // Verify gh is available\n try {\n execSync('gh --version', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });\n } catch {\n throw new Error('gh CLI not found. Install it from https://cli.github.com/');\n }\n\n // Check if repo already exists\n try {\n execSync(`gh repo view ${fullName} --json name`, {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n throw new Error(`Repository \"${fullName}\" already exists on GitHub`);\n } catch (err) {\n if (err instanceof Error && err.message.includes('already exists')) {\n throw err;\n }\n // Repo doesn't exist — proceed\n }\n\n // Build gh repo create command\n const args = ['gh', 'repo', 'create', fullName, isPrivate ? '--private' : '--public'];\n if (description) {\n args.push('--description', description);\n }\n\n const output = execSync(args.join(' '), {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n const url = output || `https://github.com/${fullName}`;\n return { url, fullName };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/outcomes.ts
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
5
+ import { join } from "path";
6
+ import { homedir } from "os";
7
+ var OUTCOMES_DIR = join(homedir(), ".squads", "daemon");
8
+ var OUTCOMES_FILE = join(OUTCOMES_DIR, "outcomes.json");
9
+ function loadOutcomes() {
10
+ if (!existsSync(OUTCOMES_DIR)) mkdirSync(OUTCOMES_DIR, { recursive: true });
11
+ if (!existsSync(OUTCOMES_FILE)) {
12
+ return { records: [], scorecards: [], lastUpdated: "" };
13
+ }
14
+ try {
15
+ return JSON.parse(readFileSync(OUTCOMES_FILE, "utf-8"));
16
+ } catch {
17
+ return { records: [], scorecards: [], lastUpdated: "" };
18
+ }
19
+ }
20
+ function saveOutcomes(data) {
21
+ if (!existsSync(OUTCOMES_DIR)) mkdirSync(OUTCOMES_DIR, { recursive: true });
22
+ data.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
23
+ writeFileSync(OUTCOMES_FILE, JSON.stringify(data, null, 2));
24
+ }
25
+ function computeScorecard(squad, agent, period) {
26
+ const data = loadOutcomes();
27
+ const periodMs = period === "7d" ? 7 * 24 * 60 * 60 * 1e3 : 30 * 24 * 60 * 60 * 1e3;
28
+ const cutoff = Date.now() - periodMs;
29
+ const records = data.records.filter(
30
+ (r) => r.squad === squad && r.agent === agent && new Date(r.completedAt).getTime() > cutoff
31
+ );
32
+ if (records.length === 0) return null;
33
+ const totalPRs = records.reduce((sum, r) => sum + r.artifacts.prsCreated.length, 0);
34
+ const mergedPRs = records.reduce((sum, r) => sum + r.outcomes.prsMerged, 0);
35
+ const unmergedPRs = records.reduce((sum, r) => sum + r.outcomes.prsClosedUnmerged, 0);
36
+ const totalIssues = records.reduce((sum, r) => sum + r.artifacts.issuesCreated.length, 0);
37
+ const closedIssues = records.reduce((sum, r) => sum + r.outcomes.issuesClosed, 0);
38
+ const totalCost = records.reduce((sum, r) => sum + r.costUsd, 0);
39
+ const wasteRuns = records.filter(
40
+ (r) => r.artifacts.prsCreated.length === 0 && r.artifacts.issuesCreated.length === 0 && r.artifacts.commits === 0
41
+ ).length;
42
+ const ciRecords = records.filter((r) => r.outcomes.ciPassFirstPush !== null);
43
+ const ciPassed = ciRecords.filter((r) => r.outcomes.ciPassFirstPush === true).length;
44
+ const reviewCycles = records.filter((r) => r.outcomes.reviewCycleHours !== null).map((r) => r.outcomes.reviewCycleHours);
45
+ const avgReviewCycleHours = reviewCycles.length > 0 ? reviewCycles.reduce((a, b) => a + b, 0) / reviewCycles.length : 0;
46
+ const outcomes = closedIssues + mergedPRs;
47
+ return {
48
+ squad,
49
+ agent,
50
+ period,
51
+ executions: records.length,
52
+ wasteRate: records.length > 0 ? wasteRuns / records.length : 0,
53
+ mergeRate: totalPRs > 0 ? mergedPRs / totalPRs : 0,
54
+ issueResolutionRate: totalIssues > 0 ? closedIssues / totalIssues : 0,
55
+ ciPassRate: ciRecords.length > 0 ? ciPassed / ciRecords.length : 0,
56
+ avgReviewCycleHours,
57
+ costPerOutcome: outcomes > 0 ? totalCost / outcomes : totalCost
58
+ };
59
+ }
60
+ function computeAllScorecards(period = "7d") {
61
+ const data = loadOutcomes();
62
+ const periodMs = period === "7d" ? 7 * 24 * 60 * 60 * 1e3 : 30 * 24 * 60 * 60 * 1e3;
63
+ const cutoff = Date.now() - periodMs;
64
+ const agents = /* @__PURE__ */ new Set();
65
+ for (const r of data.records) {
66
+ if (new Date(r.completedAt).getTime() > cutoff) {
67
+ agents.add(`${r.squad}/${r.agent}`);
68
+ }
69
+ }
70
+ const scorecards = [];
71
+ for (const key of agents) {
72
+ const [squad, agent] = key.split("/");
73
+ const card = computeScorecard(squad, agent, period);
74
+ if (card) scorecards.push(card);
75
+ }
76
+ scorecards.sort((a, b) => b.executions - a.executions);
77
+ data.scorecards = scorecards;
78
+ saveOutcomes(data);
79
+ return scorecards;
80
+ }
81
+ function getScorecards() {
82
+ return loadOutcomes().scorecards;
83
+ }
84
+ function getOutcomeRecords() {
85
+ return loadOutcomes().records;
86
+ }
87
+ function gradeExecution(record) {
88
+ const { artifacts, outcomes } = record;
89
+ const hasArtifacts = artifacts.prsCreated.length > 0 || artifacts.issuesCreated.length > 0 || artifacts.commits > 0;
90
+ if (!hasArtifacts) {
91
+ return { grade: "F", reason: "No artifacts produced" };
92
+ }
93
+ if (outcomes.prsMerged > 0) {
94
+ if (outcomes.ciPassFirstPush === true) {
95
+ return { grade: "A", reason: `${outcomes.prsMerged} PR(s) merged, CI passed first push` };
96
+ }
97
+ return { grade: "A", reason: `${outcomes.prsMerged} PR(s) merged` };
98
+ }
99
+ if (outcomes.issuesClosed > 0) {
100
+ return { grade: "B", reason: `${outcomes.issuesClosed} issue(s) closed` };
101
+ }
102
+ if (artifacts.prsCreated.length > 0 && outcomes.prsOpen > 0) {
103
+ return { grade: "B", reason: `${outcomes.prsOpen} PR(s) open, awaiting review` };
104
+ }
105
+ if (outcomes.prsClosedUnmerged > 0) {
106
+ return { grade: "D", reason: `${outcomes.prsClosedUnmerged} PR(s) closed without merge` };
107
+ }
108
+ if (artifacts.commits > 0 && artifacts.prsCreated.length === 0) {
109
+ return { grade: "C", reason: `${artifacts.commits} commits, no PR created` };
110
+ }
111
+ if (artifacts.issuesCreated.length > 0 && artifacts.prsCreated.length === 0) {
112
+ return { grade: "C", reason: `${artifacts.issuesCreated.length} issue(s) filed, no code fix` };
113
+ }
114
+ return { grade: "C", reason: "Artifacts produced but no clear outcome yet" };
115
+ }
116
+ function getAgentQualityScore(squad, agent) {
117
+ const data = loadOutcomes();
118
+ const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
119
+ const records = data.records.filter(
120
+ (r) => r.squad === squad && r.agent === agent && new Date(r.completedAt).getTime() > cutoff && r.settled
121
+ );
122
+ if (records.length < 2) return null;
123
+ const gradeValues = { A: 4, B: 3, C: 2, D: 1, F: 0 };
124
+ let total = 0;
125
+ for (const record of records) {
126
+ const { grade } = gradeExecution(record);
127
+ total += gradeValues[grade];
128
+ }
129
+ return total / records.length;
130
+ }
131
+ function getOutcomeScoreModifier(squad, agent) {
132
+ const scorecards = getScorecards();
133
+ const card = scorecards.find((s) => s.squad === squad && s.agent === agent);
134
+ if (!card || card.executions < 3) return 0;
135
+ let modifier = 0;
136
+ if (card.wasteRate > 0.5) modifier -= 30;
137
+ if (card.mergeRate < 0.3 && card.executions >= 3) modifier -= 20;
138
+ if (card.mergeRate > 0.7 && card.issueResolutionRate > 0.5) modifier += 15;
139
+ if (card.costPerOutcome > 5) modifier -= 10;
140
+ const qualityScore = getAgentQualityScore(squad, agent);
141
+ if (qualityScore !== null) {
142
+ if (qualityScore >= 3) modifier += 10;
143
+ else if (qualityScore < 1.5) modifier -= 25;
144
+ else if (qualityScore < 2) modifier -= 15;
145
+ }
146
+ return modifier;
147
+ }
148
+
149
+ export {
150
+ computeAllScorecards,
151
+ getOutcomeRecords,
152
+ getOutcomeScoreModifier
153
+ };
154
+ //# sourceMappingURL=chunk-HF4WR7RA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/outcomes.ts"],"sourcesContent":["/**\n * Outcome tracking — observes GitHub for artifact outcomes.\n *\n * Polls issues/PRs created by agent runs to determine if work\n * was productive (merged, closed) or wasteful (abandoned, unmerged).\n * Uses `gh` CLI for GitHub queries — no API keys needed.\n */\n\nimport { execSync } from 'child_process';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\n// ── Types ────────────────────────────────────────────────────────────\n\nexport interface ArtifactRef {\n repo: string;\n number: number;\n}\n\nexport interface OutcomeRecord {\n executionId: string;\n squad: string;\n agent: string;\n completedAt: string;\n costUsd: number;\n artifacts: {\n issuesCreated: ArtifactRef[];\n prsCreated: ArtifactRef[];\n commits: number;\n };\n outcomes: {\n issuesClosed: number;\n issuesOpen: number;\n prsMerged: number;\n prsClosedUnmerged: number;\n prsOpen: number;\n ciPassFirstPush: boolean | null;\n reviewCycleHours: number | null;\n };\n lastPolledAt: string;\n settled: boolean;\n}\n\nexport interface AgentScorecard {\n squad: string;\n agent: string;\n period: '7d' | '30d';\n executions: number;\n wasteRate: number;\n mergeRate: number;\n issueResolutionRate: number;\n ciPassRate: number;\n avgReviewCycleHours: number;\n costPerOutcome: number;\n}\n\n// ── Storage ──────────────────────────────────────────────────────────\n\nconst OUTCOMES_DIR = join(homedir(), '.squads', 'daemon');\nconst OUTCOMES_FILE = join(OUTCOMES_DIR, 'outcomes.json');\n\ninterface OutcomesData {\n records: OutcomeRecord[];\n scorecards: AgentScorecard[];\n lastUpdated: string;\n}\n\nfunction loadOutcomes(): OutcomesData {\n if (!existsSync(OUTCOMES_DIR)) mkdirSync(OUTCOMES_DIR, { recursive: true });\n if (!existsSync(OUTCOMES_FILE)) {\n return { records: [], scorecards: [], lastUpdated: '' };\n }\n try {\n return JSON.parse(readFileSync(OUTCOMES_FILE, 'utf-8'));\n } catch {\n return { records: [], scorecards: [], lastUpdated: '' };\n }\n}\n\nfunction saveOutcomes(data: OutcomesData): void {\n if (!existsSync(OUTCOMES_DIR)) mkdirSync(OUTCOMES_DIR, { recursive: true });\n data.lastUpdated = new Date().toISOString();\n writeFileSync(OUTCOMES_FILE, JSON.stringify(data, null, 2));\n}\n\n// ── GitHub helpers ───────────────────────────────────────────────────\n\nfunction ghExec(cmd: string, env?: Record<string, string>): string | null {\n try {\n return execSync(cmd, {\n encoding: 'utf-8',\n timeout: 15000,\n stdio: ['pipe', 'pipe', 'pipe'],\n env: { ...process.env, ...env },\n }).trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Find PRs created by the bot in the last N minutes for a repo.\n */\nfunction findRecentBotPRs(\n repo: string,\n sinceMins: number,\n ghEnv?: Record<string, string>,\n): ArtifactRef[] {\n const raw = ghExec(\n `gh pr list -R ${repo} --author \"agents-squads[bot]\" --state all --json number,createdAt --limit 10`,\n ghEnv,\n );\n if (!raw) return [];\n\n try {\n const prs = JSON.parse(raw) as Array<{ number: number; createdAt: string }>;\n const cutoff = Date.now() - sinceMins * 60 * 1000;\n return prs\n .filter(pr => new Date(pr.createdAt).getTime() > cutoff)\n .map(pr => ({ repo, number: pr.number }));\n } catch {\n return [];\n }\n}\n\n/**\n * Find issues created by the bot in the last N minutes for a repo.\n */\nfunction findRecentBotIssues(\n repo: string,\n sinceMins: number,\n ghEnv?: Record<string, string>,\n): ArtifactRef[] {\n const raw = ghExec(\n `gh issue list -R ${repo} --author \"agents-squads[bot]\" --state all --json number,createdAt --limit 10`,\n ghEnv,\n );\n if (!raw) return [];\n\n try {\n const issues = JSON.parse(raw) as Array<{ number: number; createdAt: string }>;\n const cutoff = Date.now() - sinceMins * 60 * 1000;\n return issues\n .filter(i => new Date(i.createdAt).getTime() > cutoff)\n .map(i => ({ repo, number: i.number }));\n } catch {\n return [];\n }\n}\n\n/**\n * Count commits on the default branch in the last N minutes.\n */\nfunction countRecentCommits(\n repo: string,\n sinceMins: number,\n ghEnv?: Record<string, string>,\n): number {\n const since = new Date(Date.now() - sinceMins * 60 * 1000).toISOString();\n const raw = ghExec(\n `gh api repos/${repo}/commits --jq 'length' -f since=\"${since}\" -f per_page=50`,\n ghEnv,\n );\n return raw ? parseInt(raw, 10) || 0 : 0;\n}\n\n// ── Core functions ───────────────────────────────────────────────────\n\n/**\n * Record artifacts created by a completed agent run.\n * Called after each `squads run` finishes in the daemon.\n */\nexport function recordArtifacts(\n exec: {\n executionId: string;\n squad: string;\n agent: string;\n completedAt: string;\n costUsd: number;\n repo?: string;\n },\n ghEnv?: Record<string, string>,\n): OutcomeRecord | null {\n if (!exec.repo) return null;\n\n const data = loadOutcomes();\n\n // Don't double-record\n if (data.records.some(r => r.executionId === exec.executionId)) return null;\n\n // Look for artifacts created in the last 30 minutes (typical agent run window)\n const prs = findRecentBotPRs(exec.repo, 30, ghEnv);\n const issues = findRecentBotIssues(exec.repo, 30, ghEnv);\n const commits = countRecentCommits(exec.repo, 30, ghEnv);\n\n const record: OutcomeRecord = {\n executionId: exec.executionId,\n squad: exec.squad,\n agent: exec.agent,\n completedAt: exec.completedAt,\n costUsd: exec.costUsd,\n artifacts: {\n issuesCreated: issues,\n prsCreated: prs,\n commits,\n },\n outcomes: {\n issuesClosed: 0,\n issuesOpen: issues.length,\n prsMerged: 0,\n prsClosedUnmerged: 0,\n prsOpen: prs.length,\n ciPassFirstPush: null,\n reviewCycleHours: null,\n },\n lastPolledAt: new Date().toISOString(),\n settled: prs.length === 0 && issues.length === 0, // No artifacts = settled immediately\n };\n\n data.records.push(record);\n\n // Trim to last 200 records\n if (data.records.length > 200) {\n data.records = data.records.slice(-200);\n }\n\n saveOutcomes(data);\n return record;\n}\n\n/**\n * Poll GitHub for outcome updates on unsettled records.\n * Rate-limited to 30 API calls per cycle.\n */\nexport function pollOutcomes(ghEnv?: Record<string, string>): {\n polled: number;\n settled: number;\n} {\n const data = loadOutcomes();\n const unsettled = data.records.filter(r => !r.settled);\n let apiCalls = 0;\n let newlySettled = 0;\n const MAX_CALLS = 30;\n\n for (const record of unsettled) {\n if (apiCalls >= MAX_CALLS) break;\n\n let allTerminal = true;\n\n // Check PRs\n for (const pr of record.artifacts.prsCreated) {\n if (apiCalls >= MAX_CALLS) break;\n apiCalls++;\n\n const raw = ghExec(\n `gh pr view ${pr.number} -R ${pr.repo} --json state,mergedAt,createdAt,statusCheckRollup`,\n ghEnv,\n );\n if (!raw) { allTerminal = false; continue; }\n\n try {\n const prData = JSON.parse(raw) as {\n state: string;\n mergedAt: string | null;\n createdAt: string;\n statusCheckRollup: Array<{ conclusion: string }> | null;\n };\n\n if (prData.state === 'MERGED') {\n record.outcomes.prsMerged++;\n record.outcomes.prsOpen = Math.max(0, record.outcomes.prsOpen - 1);\n\n // Calculate review cycle hours\n if (prData.mergedAt && prData.createdAt) {\n const created = new Date(prData.createdAt).getTime();\n const merged = new Date(prData.mergedAt).getTime();\n record.outcomes.reviewCycleHours = (merged - created) / (1000 * 60 * 60);\n }\n\n // CI pass on first push\n if (record.outcomes.ciPassFirstPush === null && prData.statusCheckRollup) {\n record.outcomes.ciPassFirstPush = prData.statusCheckRollup.every(\n c => c.conclusion === 'SUCCESS',\n );\n }\n } else if (prData.state === 'CLOSED') {\n record.outcomes.prsClosedUnmerged++;\n record.outcomes.prsOpen = Math.max(0, record.outcomes.prsOpen - 1);\n } else {\n allTerminal = false; // Still open\n }\n } catch {\n allTerminal = false;\n }\n }\n\n // Check issues\n for (const issue of record.artifacts.issuesCreated) {\n if (apiCalls >= MAX_CALLS) break;\n apiCalls++;\n\n const raw = ghExec(\n `gh issue view ${issue.number} -R ${issue.repo} --json state`,\n ghEnv,\n );\n if (!raw) { allTerminal = false; continue; }\n\n try {\n const issueData = JSON.parse(raw) as { state: string };\n if (issueData.state === 'CLOSED') {\n record.outcomes.issuesClosed++;\n record.outcomes.issuesOpen = Math.max(0, record.outcomes.issuesOpen - 1);\n } else {\n allTerminal = false;\n }\n } catch {\n allTerminal = false;\n }\n }\n\n // Mark settled if all artifacts reached terminal state\n if (allTerminal && record.artifacts.prsCreated.length + record.artifacts.issuesCreated.length > 0) {\n record.settled = true;\n newlySettled++;\n }\n\n // Also settle records older than 30 days regardless\n const age = Date.now() - new Date(record.completedAt).getTime();\n if (age > 30 * 24 * 60 * 60 * 1000) {\n record.settled = true;\n if (!allTerminal) newlySettled++;\n }\n\n record.lastPolledAt = new Date().toISOString();\n }\n\n saveOutcomes(data);\n return { polled: apiCalls, settled: newlySettled };\n}\n\n/**\n * Compute scorecard for an agent over a time period.\n */\nexport function computeScorecard(\n squad: string,\n agent: string,\n period: '7d' | '30d',\n): AgentScorecard | null {\n const data = loadOutcomes();\n const periodMs = period === '7d' ? 7 * 24 * 60 * 60 * 1000 : 30 * 24 * 60 * 60 * 1000;\n const cutoff = Date.now() - periodMs;\n\n const records = data.records.filter(\n r => r.squad === squad && r.agent === agent &&\n new Date(r.completedAt).getTime() > cutoff,\n );\n\n if (records.length === 0) return null;\n\n const totalPRs = records.reduce((sum, r) => sum + r.artifacts.prsCreated.length, 0);\n const mergedPRs = records.reduce((sum, r) => sum + r.outcomes.prsMerged, 0);\n const unmergedPRs = records.reduce((sum, r) => sum + r.outcomes.prsClosedUnmerged, 0);\n const totalIssues = records.reduce((sum, r) => sum + r.artifacts.issuesCreated.length, 0);\n const closedIssues = records.reduce((sum, r) => sum + r.outcomes.issuesClosed, 0);\n const totalCost = records.reduce((sum, r) => sum + r.costUsd, 0);\n\n // Waste = runs with zero artifacts\n const wasteRuns = records.filter(\n r => r.artifacts.prsCreated.length === 0 &&\n r.artifacts.issuesCreated.length === 0 &&\n r.artifacts.commits === 0,\n ).length;\n\n // CI pass rate\n const ciRecords = records.filter(r => r.outcomes.ciPassFirstPush !== null);\n const ciPassed = ciRecords.filter(r => r.outcomes.ciPassFirstPush === true).length;\n\n // Avg review cycle\n const reviewCycles = records\n .filter(r => r.outcomes.reviewCycleHours !== null)\n .map(r => r.outcomes.reviewCycleHours!);\n const avgReviewCycleHours = reviewCycles.length > 0\n ? reviewCycles.reduce((a, b) => a + b, 0) / reviewCycles.length\n : 0;\n\n // Cost per outcome (issues closed + PRs merged)\n const outcomes = closedIssues + mergedPRs;\n\n return {\n squad,\n agent,\n period,\n executions: records.length,\n wasteRate: records.length > 0 ? wasteRuns / records.length : 0,\n mergeRate: totalPRs > 0 ? mergedPRs / totalPRs : 0,\n issueResolutionRate: totalIssues > 0 ? closedIssues / totalIssues : 0,\n ciPassRate: ciRecords.length > 0 ? ciPassed / ciRecords.length : 0,\n avgReviewCycleHours,\n costPerOutcome: outcomes > 0 ? totalCost / outcomes : totalCost,\n };\n}\n\n/**\n * Compute scorecards for all agents that have outcome data.\n */\nexport function computeAllScorecards(period: '7d' | '30d' = '7d'): AgentScorecard[] {\n const data = loadOutcomes();\n const periodMs = period === '7d' ? 7 * 24 * 60 * 60 * 1000 : 30 * 24 * 60 * 60 * 1000;\n const cutoff = Date.now() - periodMs;\n\n // Find unique squad/agent combos in period\n const agents = new Set<string>();\n for (const r of data.records) {\n if (new Date(r.completedAt).getTime() > cutoff) {\n agents.add(`${r.squad}/${r.agent}`);\n }\n }\n\n const scorecards: AgentScorecard[] = [];\n for (const key of agents) {\n const [squad, agent] = key.split('/');\n const card = computeScorecard(squad, agent, period);\n if (card) scorecards.push(card);\n }\n\n // Sort by executions descending\n scorecards.sort((a, b) => b.executions - a.executions);\n\n // Persist\n data.scorecards = scorecards;\n saveOutcomes(data);\n\n return scorecards;\n}\n\n/**\n * Get cached scorecards (no recompute).\n */\nexport function getScorecards(): AgentScorecard[] {\n return loadOutcomes().scorecards;\n}\n\n/**\n * Get all outcome records.\n */\nexport function getOutcomeRecords(): OutcomeRecord[] {\n return loadOutcomes().records;\n}\n\n// ── Quality Grading ─────────────────────────────────────────────\n\nexport type QualityGrade = 'A' | 'B' | 'C' | 'D' | 'F';\n\nexport interface GradeResult {\n grade: QualityGrade;\n reason: string;\n}\n\n/**\n * Grade an execution's output quality using heuristics.\n * No LLM call needed — rules-based on observable artifacts.\n *\n * A = Real deliverable (merged PR with code changes)\n * B = Useful output (open PR with code, or closed issue)\n * C = Template/report (PR with only markdown, or commits with no PR)\n * D = Slop (large PR with no tests, or blocked agent that produced output anyway)\n * F = Wasted run (no artifacts, or agent hit escalation)\n */\nexport function gradeExecution(record: OutcomeRecord): GradeResult {\n const { artifacts, outcomes } = record;\n const hasArtifacts = artifacts.prsCreated.length > 0 ||\n artifacts.issuesCreated.length > 0 ||\n artifacts.commits > 0;\n\n // F: No artifacts at all\n if (!hasArtifacts) {\n return { grade: 'F', reason: 'No artifacts produced' };\n }\n\n // A: PR merged\n if (outcomes.prsMerged > 0) {\n if (outcomes.ciPassFirstPush === true) {\n return { grade: 'A', reason: `${outcomes.prsMerged} PR(s) merged, CI passed first push` };\n }\n return { grade: 'A', reason: `${outcomes.prsMerged} PR(s) merged` };\n }\n\n // B: PR open or issues closed\n if (outcomes.issuesClosed > 0) {\n return { grade: 'B', reason: `${outcomes.issuesClosed} issue(s) closed` };\n }\n if (artifacts.prsCreated.length > 0 && outcomes.prsOpen > 0) {\n return { grade: 'B', reason: `${outcomes.prsOpen} PR(s) open, awaiting review` };\n }\n\n // D: PR closed unmerged (rejected work)\n if (outcomes.prsClosedUnmerged > 0) {\n return { grade: 'D', reason: `${outcomes.prsClosedUnmerged} PR(s) closed without merge` };\n }\n\n // C: Only commits, no PRs\n if (artifacts.commits > 0 && artifacts.prsCreated.length === 0) {\n return { grade: 'C', reason: `${artifacts.commits} commits, no PR created` };\n }\n\n // C: Only issues created (reports, not fixes)\n if (artifacts.issuesCreated.length > 0 && artifacts.prsCreated.length === 0) {\n return { grade: 'C', reason: `${artifacts.issuesCreated.length} issue(s) filed, no code fix` };\n }\n\n return { grade: 'C', reason: 'Artifacts produced but no clear outcome yet' };\n}\n\n/**\n * Compute average quality grade for an agent as a numeric score.\n * A=4, B=3, C=2, D=1, F=0\n */\nexport function getAgentQualityScore(squad: string, agent: string): number | null {\n const data = loadOutcomes();\n const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;\n const records = data.records.filter(\n r => r.squad === squad && r.agent === agent &&\n new Date(r.completedAt).getTime() > cutoff &&\n r.settled,\n );\n\n if (records.length < 2) return null; // Need at least 2 settled records\n\n const gradeValues: Record<QualityGrade, number> = { A: 4, B: 3, C: 2, D: 1, F: 0 };\n let total = 0;\n for (const record of records) {\n const { grade } = gradeExecution(record);\n total += gradeValues[grade];\n }\n\n return total / records.length;\n}\n\n/**\n * Apply outcome-based score modifiers to daemon squad scoring.\n * Returns a score adjustment (positive or negative).\n */\nexport function getOutcomeScoreModifier(squad: string, agent: string): number {\n const scorecards = getScorecards();\n const card = scorecards.find(s => s.squad === squad && s.agent === agent);\n\n // Minimum data threshold: need 3+ executions for modifiers\n if (!card || card.executions < 3) return 0;\n\n let modifier = 0;\n\n // High waste rate penalty\n if (card.wasteRate > 0.5) modifier -= 30;\n\n // Low merge rate penalty\n if (card.mergeRate < 0.3 && card.executions >= 3) modifier -= 20;\n\n // High performance bonus\n if (card.mergeRate > 0.7 && card.issueResolutionRate > 0.5) modifier += 15;\n\n // Expensive + low-scoring penalty\n if (card.costPerOutcome > 5) modifier -= 10;\n\n // Quality grade modifier (heuristic grading)\n const qualityScore = getAgentQualityScore(squad, agent);\n if (qualityScore !== null) {\n if (qualityScore >= 3.0) modifier += 10; // A/B average → boost\n else if (qualityScore < 1.5) modifier -= 25; // D/F average → strong deprioritize\n else if (qualityScore < 2.0) modifier -= 15; // C/D average → deprioritize\n }\n\n return modifier;\n}\n"],"mappings":"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","names":[]}