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 @@
1
+ {"version":3,"sources":["../src/lib/squad-parser.ts","../src/lib/mcp-config.ts"],"sourcesContent":["import { readFileSync, existsSync, readdirSync, writeFileSync } from 'fs';\nimport { join, basename, dirname } from 'path';\nimport matter from 'gray-matter';\nimport { resolveMcpConfig, type McpResolution } from './mcp-config.js';\n\nexport type EffortLevel = 'high' | 'medium' | 'low';\n\n// Context schema for frontmatter\nexport interface SquadContext {\n mcp?: string[];\n skills?: string[];\n memory?: {\n load?: string[];\n };\n model?: {\n default?: string;\n expensive?: string;\n cheap?: string;\n };\n budget?: {\n daily?: number;\n weekly?: number;\n perExecution?: number;\n };\n /** Cooldown between executions in seconds */\n cooldown?: number;\n}\n\n/**\n * Resolved skill with path and source information.\n */\nexport interface ResolvedSkill {\n /** Skill name (directory or reference name) */\n name: string;\n /** Absolute path to the skill directory */\n path: string;\n /** Where the skill was found */\n source: 'squad-local' | 'project' | 'global';\n}\n\n// Multi-LLM provider configuration\nexport interface SquadProviders {\n /** Default provider for all agents (default: anthropic) */\n default?: string;\n /** Provider for vision/image tasks */\n vision?: string;\n /** Provider for real-time data access */\n realtime?: string;\n /** Provider for high-volume/cheap operations */\n cheap?: string;\n /** Custom provider mappings by purpose */\n [key: string]: string | undefined;\n}\n\n// Frontmatter schema\nexport interface SquadFrontmatter {\n name?: string;\n mission?: string;\n repo?: string;\n stack?: string;\n context?: SquadContext;\n effort?: EffortLevel;\n /** Multi-LLM provider configuration */\n providers?: SquadProviders;\n /** Squad names this squad must wait for before executing (phase ordering) */\n depends_on?: string[];\n}\n\nexport interface Agent {\n name: string;\n role: string;\n trigger: string;\n status?: string;\n filePath?: string;\n squad?: string;\n effort?: EffortLevel;\n /** LLM provider override (from agent file frontmatter) */\n provider?: string;\n /** Agent purpose (short description) */\n purpose?: string;\n /** Cron schedule for scheduled agents */\n schedule?: string;\n /** Output destinations */\n outputs?: string[];\n}\n\nexport interface Pipeline {\n name: string;\n agents: string[];\n}\n\nexport interface Goal {\n description: string;\n completed: boolean;\n progress?: string;\n metrics?: string[];\n}\n\n/**\n * Routine definition for autonomous scheduled execution.\n * Defined in SQUAD.md under ### Routines yaml block.\n */\nexport interface Routine {\n /** Unique name for the routine */\n name: string;\n /** Cron schedule (e.g., \"0 8 * * *\" for daily 8am) */\n schedule: string;\n /** Agents to run in this batch */\n agents: string[];\n /** Model to use (defaults to squad default or sonnet) */\n model?: string;\n /** Whether the routine is enabled */\n enabled?: boolean;\n /** Priority for execution ordering (lower = higher priority) */\n priority?: number;\n /** Minimum cooldown between runs (e.g., \"6 hours\") */\n cooldown?: string;\n}\n\nexport interface Squad {\n name: string;\n /** Directory name for file path resolution (e.g., \"engineering\") */\n dir: string;\n mission: string;\n agents: Agent[];\n pipelines: Pipeline[];\n triggers: {\n scheduled: string[];\n event: string[];\n manual: string[];\n };\n /** Autonomous routines for scheduled batch execution */\n routines: Routine[];\n dependencies: string[];\n outputPath: string;\n goals: Goal[];\n effort?: EffortLevel; // Squad-level default effort\n context?: SquadContext; // Frontmatter context block\n repo?: string;\n stack?: string;\n /** Multi-LLM provider configuration */\n providers?: SquadProviders;\n /** Domain this squad operates in */\n domain?: string;\n /** Permissions for this squad */\n permissions?: Record<string, boolean>;\n /** Raw frontmatter for accessing KPIs and other custom fields */\n frontmatter?: Record<string, unknown>;\n /** Squad names this squad must wait for (phase ordering) */\n depends_on?: string[];\n}\n\n/**\n * Resolved execution context with paths and metadata.\n * Extends SquadContext with resolved paths for MCP, skills, and memory.\n */\nexport interface ExecutionContext extends SquadContext {\n /** Squad name this context belongs to */\n squadName: string;\n /** Resolved paths and metadata */\n resolved: {\n /** Path to MCP config file to use */\n mcpConfigPath: string;\n /** Source of MCP config resolution */\n mcpSource: 'user-override' | 'generated' | 'fallback' | 'squad-local';\n /** List of MCP servers in the config */\n mcpServers: string[];\n /** Resolved skill directory paths (deprecated, use skills instead) */\n skillPaths: string[];\n /** Resolved skills with source information */\n skills: ResolvedSkill[];\n /** Resolved memory file paths */\n memoryPaths: string[];\n };\n}\n\n/**\n * Find the .agents/squads directory by searching current directory and parents.\n * Searches up to 5 parent directories.\n * @returns Path to squads directory or null if not found\n */\nexport function findSquadsDir(): string | null {\n // Look for .agents/squads in current directory or parent directories\n let dir = process.cwd();\n\n for (let i = 0; i < 5; i++) {\n const squadsPath = join(dir, '.agents', 'squads');\n if (existsSync(squadsPath)) {\n return squadsPath;\n }\n const parent = join(dir, '..');\n if (parent === dir) break;\n dir = parent;\n }\n\n return null;\n}\n\n/**\n * Find the root directory of the squads project (where .agents/ lives).\n * @returns Path to project root or null if not in a squads project\n */\nexport function findProjectRoot(): string | null {\n // Find the root of the squads project (where .agents/ lives)\n const squadsDir = findSquadsDir();\n if (!squadsDir) return null;\n // squadsDir is /path/to/.agents/squads, so go up 2 levels\n return join(squadsDir, '..', '..');\n}\n\n/**\n * Check if the project has local infrastructure configuration.\n * Looks for .env file with infra-related keys (LANGFUSE_, SQUADS_BRIDGE, etc).\n * @returns True if local infra config exists\n */\nexport function hasLocalInfraConfig(): boolean {\n // Check if the project has a local .env file with infra config\n const projectRoot = findProjectRoot();\n if (!projectRoot) return false;\n\n const envPath = join(projectRoot, '.env');\n if (!existsSync(envPath)) return false;\n\n // Check if .env has any infra-related keys\n const content = readFileSync(envPath, 'utf-8');\n const infraKeys = ['LANGFUSE_', 'SQUADS_BRIDGE', 'SQUADS_POSTGRES', 'SQUADS_REDIS'];\n return infraKeys.some(key => content.includes(key));\n}\n\n/**\n * List all squad names in the given squads directory.\n * Only includes directories containing a SQUAD.md file.\n * @param squadsDir - Path to the .agents/squads directory\n * @returns Array of squad directory names\n */\nexport function listSquads(squadsDir: string): string[] {\n const squads: string[] = [];\n\n const entries = readdirSync(squadsDir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory() && !entry.name.startsWith('_')) {\n const squadFile = join(squadsDir, entry.name, 'SQUAD.md');\n if (existsSync(squadFile)) {\n squads.push(entry.name);\n }\n }\n }\n\n return squads;\n}\n\n/**\n * Find squad names similar to the input using Levenshtein distance.\n * Returns up to 3 close matches, or empty array if none are close enough.\n */\nexport function findSimilarSquads(input: string, squads: string[]): string[] {\n const lower = input.toLowerCase();\n\n function levenshtein(a: string, b: string): number {\n const m = a.length, n = b.length;\n const dp: number[][] = Array.from({ length: m + 1 }, (_, i) =>\n Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))\n );\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n dp[i][j] = a[i - 1] === b[j - 1]\n ? dp[i - 1][j - 1]\n : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);\n }\n }\n return dp[m][n];\n }\n\n const threshold = Math.max(2, Math.floor(input.length / 3));\n\n return squads\n .map(s => ({ name: s, dist: levenshtein(lower, s.toLowerCase()) }))\n .filter(({ name, dist }) => dist <= threshold || name.toLowerCase().includes(lower) || lower.includes(name.toLowerCase()))\n .sort((a, b) => a.dist - b.dist)\n .slice(0, 3)\n .map(({ name }) => name);\n}\n\n/**\n * List all agents in the squads directory or a specific squad.\n * Agents are markdown files (excluding SQUAD.md) in squad directories.\n * @param squadsDir - Path to the .agents/squads directory\n * @param squadName - Optional squad name to filter agents\n * @returns Array of Agent objects with basic metadata\n */\nexport function listAgents(squadsDir: string, squadName?: string): Agent[] {\n const agents: Agent[] = [];\n\n const dirs = squadName\n ? [squadName]\n : readdirSync(squadsDir, { withFileTypes: true })\n .filter(e => e.isDirectory() && !e.name.startsWith('_'))\n .map(e => e.name);\n\n for (const dir of dirs) {\n const squadPath = join(squadsDir, dir);\n if (!existsSync(squadPath)) continue;\n\n const files = readdirSync(squadPath);\n for (const file of files) {\n if (file.endsWith('.md') && file !== 'SQUAD.md') {\n const agentName = file.replace('.md', '');\n agents.push({\n name: agentName,\n role: `Agent in ${dir}`,\n trigger: 'manual',\n filePath: join(squadPath, file)\n });\n }\n }\n }\n\n return agents;\n}\n\n/**\n * Parse a SQUAD.md file into a Squad object.\n * Extracts frontmatter metadata, agents, pipelines, goals, and routines.\n * @param filePath - Path to the SQUAD.md file\n * @returns Parsed Squad object with all extracted data\n */\nexport function parseSquadFile(filePath: string): Squad {\n const rawContent = readFileSync(filePath, 'utf-8');\n\n // Parse frontmatter with gray-matter\n const { data: frontmatter, content: bodyContent } = matter(rawContent);\n const fm = frontmatter as SquadFrontmatter;\n\n const lines = bodyContent.split('\\n');\n\n // Directory name is used for file paths (e.g., \"engineering\", \"marketing\")\n const dirName = basename(dirname(filePath));\n\n const squad: Squad = {\n // Display name can be different from dir (e.g., \"Engineering Squad\")\n name: fm.name || dirName,\n // Directory name for file path resolution\n dir: dirName,\n mission: fm.mission || '',\n agents: [],\n pipelines: [],\n triggers: { scheduled: [], event: [], manual: [] },\n routines: [],\n dependencies: [],\n outputPath: '',\n goals: [],\n // Apply frontmatter fields\n effort: fm.effort,\n context: fm.context,\n repo: fm.repo,\n stack: fm.stack,\n providers: fm.providers,\n // Preserve raw frontmatter for KPIs and other custom fields\n frontmatter: frontmatter as Record<string, unknown>,\n // Phase ordering: which squads must complete before this one\n depends_on: Array.isArray(fm.depends_on) ? fm.depends_on : undefined,\n };\n\n let currentSection = '';\n let inTable = false;\n let tableHeaders: string[] = [];\n\n for (const line of lines) {\n // Extract squad name from title\n if (line.startsWith('# Squad:')) {\n squad.name = line.replace('# Squad:', '').trim().toLowerCase();\n continue;\n }\n\n // Track sections\n if (line.startsWith('## ')) {\n currentSection = line.replace('## ', '').trim().toLowerCase();\n inTable = false;\n continue;\n }\n\n // Extract mission\n if (currentSection === 'mission' && line.trim() && !line.startsWith('#')) {\n if (!squad.mission) {\n squad.mission = line.trim();\n }\n }\n\n // Extract squad-level effort (e.g., \"effort: medium\" in Context section)\n const effortMatch = line.match(/^effort:\\s*(high|medium|low)/i);\n if (effortMatch && !squad.effort) {\n squad.effort = effortMatch[1].toLowerCase() as EffortLevel;\n }\n\n // Parse agent tables\n if (currentSection.includes('agent') || currentSection.includes('orchestrator') ||\n currentSection.includes('evaluator') || currentSection.includes('builder') ||\n currentSection.includes('priority')) {\n\n if (line.includes('|') && line.includes('Agent')) {\n inTable = true;\n tableHeaders = line.split('|').map(h => h.trim().toLowerCase());\n continue;\n }\n\n if (inTable && line.includes('|') && !line.includes('---')) {\n const cells = line.split('|').map(c => c.trim().replace(/`/g, '').replace(/\\*\\*/g, ''));\n const agentIdx = tableHeaders.findIndex(h => h === 'agent');\n const roleIdx = tableHeaders.findIndex(h => h === 'role');\n const triggerIdx = tableHeaders.findIndex(h => h === 'trigger');\n const statusIdx = tableHeaders.findIndex(h => h === 'status');\n const effortIdx = tableHeaders.findIndex(h => h === 'effort');\n\n if (agentIdx >= 0 && cells[agentIdx]) {\n const effortValue = effortIdx >= 0 ? cells[effortIdx]?.toLowerCase() : undefined;\n const effort = ['high', 'medium', 'low'].includes(effortValue || '')\n ? effortValue as EffortLevel\n : undefined;\n\n squad.agents.push({\n name: cells[agentIdx],\n role: roleIdx >= 0 ? cells[roleIdx] : '',\n trigger: triggerIdx >= 0 ? cells[triggerIdx] : 'manual',\n status: statusIdx >= 0 ? cells[statusIdx] : 'active',\n effort\n });\n }\n }\n }\n\n // Parse pipelines (looking for patterns like: agent1 → agent2 → agent3)\n if (line.includes('→') && line.includes('`')) {\n const pipelineMatch = line.match(/`([^`]+)`\\s*→\\s*`([^`]+)`/g);\n if (pipelineMatch) {\n const agentNames = line.match(/`([^`]+)`/g)?.map(m => m.replace(/`/g, '')) || [];\n if (agentNames.length >= 2) {\n squad.pipelines.push({\n name: 'default',\n agents: agentNames\n });\n }\n }\n }\n\n // Also look for Pipeline: format\n if (line.toLowerCase().includes('pipeline:')) {\n const pipelineContent = line.split(':')[1];\n if (pipelineContent && pipelineContent.includes('→')) {\n const agentNames = pipelineContent.match(/`([^`]+)`/g)?.map(m => m.replace(/`/g, '')) || [];\n if (agentNames.length >= 2) {\n squad.pipelines.push({\n name: 'default',\n agents: agentNames\n });\n }\n }\n }\n\n // Extract output path\n if (line.toLowerCase().includes('primary') && line.includes('`')) {\n const match = line.match(/`([^`]+)`/);\n if (match) {\n squad.outputPath = match[1].replace(/\\/$/, '');\n }\n }\n\n // Parse goals (checkbox format: - [ ] or - [x])\n if (currentSection === 'goals') {\n const goalMatch = line.match(/^-\\s*\\[([ x])\\]\\s*(.+)$/);\n if (goalMatch) {\n const completed = goalMatch[1] === 'x';\n let description = goalMatch[2].trim();\n let progress: string | undefined;\n\n // Check for progress annotation\n const progressMatch = description.match(/\\(progress:\\s*([^)]+)\\)/i);\n if (progressMatch) {\n progress = progressMatch[1];\n description = description.replace(progressMatch[0], '').trim();\n }\n\n squad.goals.push({\n description,\n completed,\n progress\n });\n }\n }\n }\n\n return squad;\n}\n\n/**\n * Load and parse a squad by name.\n * Convenience function that finds the squads directory and parses the squad file.\n * @param squadName - Name of the squad directory (e.g., \"engineering\")\n * @returns Parsed Squad object or null if not found\n */\nexport function loadSquad(squadName: string): Squad | null {\n const squadsDir = findSquadsDir();\n if (!squadsDir) return null;\n\n const squadFile = join(squadsDir, squadName, 'SQUAD.md');\n if (!existsSync(squadFile)) return null;\n\n return parseSquadFile(squadFile);\n}\n\n/**\n * Load raw content of an agent definition file.\n * @param agentPath - Path to the agent markdown file\n * @returns Raw file content or empty string if file doesn't exist\n */\nexport function loadAgentDefinition(agentPath: string): string {\n if (!existsSync(agentPath)) return '';\n return readFileSync(agentPath, 'utf-8');\n}\n\n/**\n * Parse provider from an agent definition file.\n *\n * Looks for:\n * 1. Frontmatter: `provider: xai`\n * 2. Header syntax: `## Provider\\nxai`\n *\n * @returns Provider ID or undefined if not specified\n */\nexport function parseAgentProvider(agentPath: string): string | undefined {\n if (!existsSync(agentPath)) return undefined;\n\n const content = readFileSync(agentPath, 'utf-8');\n\n // Try parsing frontmatter\n try {\n const { data: frontmatter } = matter(content);\n if (frontmatter?.provider && typeof frontmatter.provider === 'string') {\n return frontmatter.provider.toLowerCase();\n }\n } catch {\n // Ignore frontmatter parsing errors\n }\n\n // Try header syntax: ## Provider\\n<provider>\n const providerHeaderMatch = content.match(/##\\s*Provider\\s*\\n+([a-zA-Z0-9_-]+)/i);\n if (providerHeaderMatch) {\n return providerHeaderMatch[1].toLowerCase();\n }\n\n return undefined;\n}\n\n/**\n * Add a new goal to a squad's SQUAD.md file.\n * Creates the Goals section if it doesn't exist.\n * @param squadName - Name of the squad directory\n * @param goal - Goal description text\n * @returns True if goal was added successfully\n */\nexport function addGoalToSquad(squadName: string, goal: string): boolean {\n const squadsDir = findSquadsDir();\n if (!squadsDir) return false;\n\n const squadFile = join(squadsDir, squadName, 'SQUAD.md');\n if (!existsSync(squadFile)) return false;\n\n let content = readFileSync(squadFile, 'utf-8');\n\n // Check if Goals section exists\n if (!content.includes('## Goals')) {\n // Add Goals section before Dependencies or at end\n const insertPoint = content.indexOf('## Dependencies');\n if (insertPoint > 0) {\n content = content.slice(0, insertPoint) + `## Goals\\n\\n- [ ] ${goal}\\n\\n` + content.slice(insertPoint);\n } else {\n content += `\\n## Goals\\n\\n- [ ] ${goal}\\n`;\n }\n } else {\n // Add to existing Goals section\n const goalsIdx = content.indexOf('## Goals');\n const nextSectionIdx = content.indexOf('\\n## ', goalsIdx + 1);\n const endIdx = nextSectionIdx > 0 ? nextSectionIdx : content.length;\n\n // Find last goal line or section header\n const goalsSection = content.slice(goalsIdx, endIdx);\n const lastGoalMatch = goalsSection.match(/^-\\s*\\[[ x]\\].+$/gm);\n\n if (lastGoalMatch) {\n // Add after last goal\n const lastGoal = lastGoalMatch[lastGoalMatch.length - 1];\n const lastGoalIdx = content.lastIndexOf(lastGoal, endIdx);\n const insertPos = lastGoalIdx + lastGoal.length;\n content = content.slice(0, insertPos) + `\\n- [ ] ${goal}` + content.slice(insertPos);\n } else {\n // No goals yet, add after section header\n const headerEnd = goalsIdx + '## Goals'.length;\n content = content.slice(0, headerEnd) + `\\n\\n- [ ] ${goal}` + content.slice(headerEnd);\n }\n }\n\n writeFileSync(squadFile, content);\n return true;\n}\n\n/**\n * Update an existing goal in a squad's SQUAD.md file.\n * Can mark goal as completed or update progress text.\n * @param squadName - Name of the squad directory\n * @param goalIndex - Zero-based index of the goal to update\n * @param updates - Object with optional completed and progress fields\n * @returns True if goal was updated successfully\n */\nexport function updateGoalInSquad(\n squadName: string,\n goalIndex: number,\n updates: { completed?: boolean; progress?: string }\n): boolean {\n const squadsDir = findSquadsDir();\n if (!squadsDir) return false;\n\n const squadFile = join(squadsDir, squadName, 'SQUAD.md');\n if (!existsSync(squadFile)) return false;\n\n const content = readFileSync(squadFile, 'utf-8');\n const lines = content.split('\\n');\n\n let currentSection = '';\n let goalCount = 0;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n\n if (line.startsWith('## ')) {\n currentSection = line.replace('## ', '').trim().toLowerCase();\n continue;\n }\n\n if (currentSection === 'goals') {\n const goalMatch = line.match(/^-\\s*\\[([ x])\\]\\s*(.+)$/);\n if (goalMatch) {\n if (goalCount === goalIndex) {\n let newLine = '- [' + (updates.completed ? 'x' : ' ') + '] ' + goalMatch[2];\n\n // Handle progress update\n if (updates.progress !== undefined) {\n // Remove existing progress annotation\n newLine = newLine.replace(/\\s*\\(progress:\\s*[^)]+\\)/i, '');\n if (updates.progress) {\n newLine += ` (progress: ${updates.progress})`;\n }\n }\n\n lines[i] = newLine;\n writeFileSync(squadFile, lines.join('\\n'));\n return true;\n }\n goalCount++;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Find the project-level skills directory (.claude/skills)\n */\nfunction findProjectSkillsDir(): string | null {\n const projectRoot = findProjectRoot();\n if (!projectRoot) return null;\n\n const skillsDir = join(projectRoot, '.claude', 'skills');\n return existsSync(skillsDir) ? skillsDir : null;\n}\n\n/**\n * Find the global skills directory (~/.claude/skills)\n */\nfunction findGlobalSkillsDir(): string | null {\n const home = process.env.HOME || process.env.USERPROFILE || '';\n if (!home) return null;\n\n const skillsDir = join(home, '.claude', 'skills');\n return existsSync(skillsDir) ? skillsDir : null;\n}\n\n/**\n * Find squad-local skills directory (.agents/squads/<squad>/skills)\n */\nfunction findSquadLocalSkillsDir(squadDir: string): string | null {\n const squadsDir = findSquadsDir();\n if (!squadsDir) return null;\n\n const skillsDir = join(squadsDir, squadDir, 'skills');\n return existsSync(skillsDir) ? skillsDir : null;\n}\n\n/**\n * List all skills in a directory.\n * Skills are subdirectories containing SKILL.md or .md files.\n */\nfunction listSkillsInDir(skillsDir: string): string[] {\n if (!existsSync(skillsDir)) return [];\n\n try {\n const entries = readdirSync(skillsDir, { withFileTypes: true });\n const skills: string[] = [];\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n // Check if directory contains SKILL.md\n const skillMdPath = join(skillsDir, entry.name, 'SKILL.md');\n if (existsSync(skillMdPath)) {\n skills.push(entry.name);\n }\n } else if (entry.isFile() && entry.name.endsWith('.md') && entry.name !== 'README.md') {\n // Single-file skill\n skills.push(entry.name.replace('.md', ''));\n }\n }\n\n return skills;\n } catch {\n return [];\n }\n}\n\n/**\n * Find the memory directory (.agents/memory)\n */\nfunction findMemoryDir(): string | null {\n const projectRoot = findProjectRoot();\n if (!projectRoot) return null;\n\n const memoryDir = join(projectRoot, '.agents', 'memory');\n return existsSync(memoryDir) ? memoryDir : null;\n}\n\n/**\n * Resolve a skill name to its path using three-tier resolution:\n * 1. Squad-local: .agents/squads/<squad>/skills/<skill>\n * 2. Project: .claude/skills/<skill>\n * 3. Global: ~/.claude/skills/<skill>\n *\n * @param skillName - Name of the skill to resolve\n * @param squadDir - Squad directory name for squad-local lookup\n * @returns Resolved skill with path and source, or null if not found\n */\nfunction resolveSkill(skillName: string, squadDir?: string): ResolvedSkill | null {\n // 1. Squad-local skills (highest priority)\n if (squadDir) {\n const squadSkillsDir = findSquadLocalSkillsDir(squadDir);\n if (squadSkillsDir) {\n // Check for directory-style skill\n const dirPath = join(squadSkillsDir, skillName);\n if (existsSync(dirPath) && existsSync(join(dirPath, 'SKILL.md'))) {\n return { name: skillName, path: dirPath, source: 'squad-local' };\n }\n // Check for single-file skill\n const filePath = join(squadSkillsDir, `${skillName}.md`);\n if (existsSync(filePath)) {\n return { name: skillName, path: filePath, source: 'squad-local' };\n }\n }\n }\n\n // 2. Project-level skills\n const projectSkillsDir = findProjectSkillsDir();\n if (projectSkillsDir) {\n const dirPath = join(projectSkillsDir, skillName);\n if (existsSync(dirPath) && existsSync(join(dirPath, 'SKILL.md'))) {\n return { name: skillName, path: dirPath, source: 'project' };\n }\n const filePath = join(projectSkillsDir, `${skillName}.md`);\n if (existsSync(filePath)) {\n return { name: skillName, path: filePath, source: 'project' };\n }\n }\n\n // 3. Global skills\n const globalSkillsDir = findGlobalSkillsDir();\n if (globalSkillsDir) {\n const dirPath = join(globalSkillsDir, skillName);\n if (existsSync(dirPath) && existsSync(join(dirPath, 'SKILL.md'))) {\n return { name: skillName, path: dirPath, source: 'global' };\n }\n const filePath = join(globalSkillsDir, `${skillName}.md`);\n if (existsSync(filePath)) {\n return { name: skillName, path: filePath, source: 'global' };\n }\n }\n\n return null;\n}\n\n/**\n * Get all available squad-local skills for a squad.\n * Scans .agents/squads/<squad>/skills/ directory.\n */\nexport function getSquadLocalSkills(squadDir: string): ResolvedSkill[] {\n const squadSkillsDir = findSquadLocalSkillsDir(squadDir);\n if (!squadSkillsDir) return [];\n\n const skillNames = listSkillsInDir(squadSkillsDir);\n const skills: ResolvedSkill[] = [];\n\n for (const name of skillNames) {\n const resolved = resolveSkill(name, squadDir);\n if (resolved && resolved.source === 'squad-local') {\n skills.push(resolved);\n }\n }\n\n return skills;\n}\n\n/**\n * Resolve memory glob patterns to actual file paths.\n */\nfunction resolveMemoryPaths(patterns: string[]): string[] {\n const memoryDir = findMemoryDir();\n if (!memoryDir) return [];\n\n const resolved: string[] = [];\n\n for (const pattern of patterns) {\n // Handle simple patterns like \"intelligence/*\" or \"research/*\"\n if (pattern.endsWith('/*')) {\n const subdir = pattern.slice(0, -2);\n const subdirPath = join(memoryDir, subdir);\n if (existsSync(subdirPath)) {\n // Add all .md files in the subdirectory\n try {\n const files = readdirSync(subdirPath);\n for (const file of files) {\n if (file.endsWith('.md')) {\n resolved.push(join(subdirPath, file));\n }\n }\n } catch {\n // Ignore read errors\n }\n }\n } else {\n // Direct path\n const fullPath = join(memoryDir, pattern);\n if (existsSync(fullPath)) {\n resolved.push(fullPath);\n }\n }\n }\n\n return resolved;\n}\n\n/**\n * Check for squad-local MCP config (.agents/squads/<squad>/mcp.json)\n */\nfunction findSquadLocalMcpConfig(squadDir: string): string | null {\n const squadsDir = findSquadsDir();\n if (!squadsDir) return null;\n\n const mcpConfigPath = join(squadsDir, squadDir, 'mcp.json');\n return existsSync(mcpConfigPath) ? mcpConfigPath : null;\n}\n\n/**\n * Resolve execution context for a squad.\n *\n * Takes a Squad object and resolves all context references to actual paths:\n * - MCP config path (four-tier resolution: squad-local, user-override, generated, fallback)\n * - Skill directory paths (three-tier: squad-local, project, global)\n * - Memory file paths\n *\n * @param squad - The squad to resolve context for\n * @param forceRegenerate - Force MCP config regeneration\n * @returns Resolved execution context with all paths\n */\nexport function resolveExecutionContext(\n squad: Squad,\n forceRegenerate = false\n): ExecutionContext {\n const ctx = squad.context || {};\n\n // Check for squad-local MCP config first (highest priority)\n const squadLocalMcpConfig = findSquadLocalMcpConfig(squad.dir);\n\n let mcpConfigPath: string;\n let mcpSource: 'squad-local' | 'user-override' | 'generated' | 'fallback';\n let mcpServers: string[] = [];\n\n if (squadLocalMcpConfig) {\n // Use squad-local MCP config\n mcpConfigPath = squadLocalMcpConfig;\n mcpSource = 'squad-local';\n\n // Try to read server names from the config\n try {\n const content = readFileSync(squadLocalMcpConfig, 'utf-8');\n const config = JSON.parse(content);\n mcpServers = Object.keys(config.mcpServers || {});\n } catch {\n // Ignore parse errors\n }\n } else {\n // Fall back to existing resolution (user-override, generated, fallback)\n const mcpResolution: McpResolution = resolveMcpConfig(\n squad.name,\n ctx.mcp,\n forceRegenerate\n );\n mcpConfigPath = mcpResolution.path;\n mcpSource = mcpResolution.source;\n mcpServers = mcpResolution.servers || [];\n }\n\n // Resolve skills with three-tier resolution\n const resolvedSkills: ResolvedSkill[] = [];\n const skillPaths: string[] = []; // For backward compatibility\n\n // First, add any squad-local skills that exist (auto-discovered)\n const squadLocalSkills = getSquadLocalSkills(squad.dir);\n for (const skill of squadLocalSkills) {\n resolvedSkills.push(skill);\n skillPaths.push(skill.path);\n }\n\n // Then, resolve explicitly listed skills from context\n if (ctx.skills) {\n for (const skillName of ctx.skills) {\n // Check if already resolved as squad-local\n if (resolvedSkills.some(s => s.name === skillName)) {\n continue;\n }\n\n const resolved = resolveSkill(skillName, squad.dir);\n if (resolved) {\n resolvedSkills.push(resolved);\n skillPaths.push(resolved.path);\n }\n }\n }\n\n // Resolve memory paths\n const memoryPaths = ctx.memory?.load\n ? resolveMemoryPaths(ctx.memory.load)\n : [];\n\n return {\n // Copy all SquadContext fields\n ...ctx,\n // Add squad name\n squadName: squad.name,\n // Add resolved paths\n resolved: {\n mcpConfigPath,\n mcpSource,\n mcpServers,\n skillPaths, // Backward compatible\n skills: resolvedSkills, // New detailed skill info\n memoryPaths,\n },\n };\n}\n","/**\n * MCP Config Generation and Resolution\n *\n * Provides dynamic MCP config generation based on squad context.\n * Three-tier resolution:\n * 1. User override: ~/.claude/mcp-configs/{squad}.json\n * 2. Generated from context.mcp: ~/.claude/contexts/{squad}.mcp.json\n * 3. Fallback: ~/.claude.json\n */\n\nimport { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';\nimport { join, dirname } from 'path';\n\n/**\n * MCP server definition structure (matches Claude's .mcp.json format)\n */\nexport interface McpServerDef {\n type: 'stdio';\n command: string;\n args: string[];\n env?: Record<string, string>;\n}\n\n/**\n * Full MCP config structure\n */\nexport interface McpConfig {\n mcpServers: Record<string, McpServerDef>;\n}\n\n/**\n * Registry of known MCP servers with their configurations.\n * These can be referenced by name in SQUAD.md context.mcp arrays.\n *\n * NOTE: We prefer CLI tools over MCP when possible (less complexity).\n * MCP is reserved for APIs that don't have good CLI alternatives.\n */\n/**\n * MCP Registry is now empty - we prefer CLI tools over MCP.\n * If you need to add MCP servers, add them here.\n */\nconst SERVER_REGISTRY: Record<string, McpServerDef> = {};\n\n/**\n * Get the home directory path.\n */\nfunction getHome(): string {\n return process.env.HOME || process.env.USERPROFILE || '';\n}\n\n/**\n * Get the path to generated contexts directory.\n */\nexport function getContextsDir(): string {\n return join(getHome(), '.claude', 'contexts');\n}\n\n/**\n * Get the path to user MCP configs directory.\n */\nexport function getMcpConfigsDir(): string {\n return join(getHome(), '.claude', 'mcp-configs');\n}\n\n/**\n * Check if a server is in the registry.\n */\nexport function isKnownServer(serverName: string): boolean {\n return serverName in SERVER_REGISTRY;\n}\n\n/**\n * Get server definition from registry.\n */\nexport function getServerDef(serverName: string): McpServerDef | undefined {\n return SERVER_REGISTRY[serverName];\n}\n\n/**\n * List all known servers in the registry.\n */\nexport function listKnownServers(): string[] {\n return Object.keys(SERVER_REGISTRY);\n}\n\n/**\n * Generate an MCP config from a list of server names.\n *\n * @param mcpServers - Array of server names to include\n * @returns Generated MCP config object\n */\nexport function generateMcpConfig(mcpServers: string[]): McpConfig {\n const config: McpConfig = { mcpServers: {} };\n\n for (const server of mcpServers) {\n const def = SERVER_REGISTRY[server];\n if (def) {\n config.mcpServers[server] = def;\n }\n // Unknown servers are silently skipped - they may be custom user servers\n }\n\n return config;\n}\n\n/**\n * Write an MCP config to a file.\n *\n * @param config - MCP config to write\n * @param path - Destination path\n */\nexport function writeMcpConfig(config: McpConfig, path: string): void {\n const dir = dirname(path);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(path, JSON.stringify(config, null, 2));\n}\n\n/**\n * Read an existing MCP config file.\n *\n * @param path - Path to config file\n * @returns Parsed config or null if not found\n */\nexport function readMcpConfig(path: string): McpConfig | null {\n if (!existsSync(path)) return null;\n try {\n const content = readFileSync(path, 'utf-8');\n return JSON.parse(content) as McpConfig;\n } catch {\n return null;\n }\n}\n\n/**\n * Resolution result with metadata.\n */\nexport interface McpResolution {\n /** Path to the MCP config to use */\n path: string;\n /** How the config was resolved */\n source: 'user-override' | 'generated' | 'fallback';\n /** Servers included (if generated or read) */\n servers?: string[];\n /** Whether config was freshly generated */\n generated?: boolean;\n}\n\n/**\n * Resolve the MCP config path for a squad using three-tier resolution:\n *\n * 1. User override: ~/.claude/mcp-configs/{squad}.json\n * 2. Generated from context.mcp: ~/.claude/contexts/{squad}.mcp.json\n * 3. Fallback: ~/.claude.json\n *\n * @param squadName - Name of the squad\n * @param mcpServers - Array of MCP server names from squad context (optional)\n * @param forceRegenerate - Force regeneration even if file exists\n * @returns Resolution result with path and metadata\n */\nexport function resolveMcpConfig(\n squadName: string,\n mcpServers?: string[],\n forceRegenerate = false\n): McpResolution {\n const home = getHome();\n\n // Tier 1: User override\n const userOverride = join(getMcpConfigsDir(), `${squadName}.json`);\n if (existsSync(userOverride)) {\n const config = readMcpConfig(userOverride);\n return {\n path: userOverride,\n source: 'user-override',\n servers: config ? Object.keys(config.mcpServers) : undefined,\n };\n }\n\n // Tier 2: Generate from context.mcp\n if (mcpServers && mcpServers.length > 0) {\n const generatedPath = join(getContextsDir(), `${squadName}.mcp.json`);\n\n // Check if we need to regenerate\n const shouldGenerate = forceRegenerate || !existsSync(generatedPath);\n\n if (shouldGenerate) {\n const config = generateMcpConfig(mcpServers);\n writeMcpConfig(config, generatedPath);\n\n return {\n path: generatedPath,\n source: 'generated',\n servers: Object.keys(config.mcpServers),\n generated: true,\n };\n }\n\n // Use existing generated config\n const config = readMcpConfig(generatedPath);\n return {\n path: generatedPath,\n source: 'generated',\n servers: config ? Object.keys(config.mcpServers) : mcpServers,\n generated: false,\n };\n }\n\n // Tier 3: Fallback to default\n return {\n path: join(home, '.claude.json'),\n source: 'fallback',\n };\n}\n\n/**\n * Convenience function to just get the path (for backward compatibility).\n */\nexport function resolveMcpConfigPath(\n squadName: string,\n mcpServers?: string[]\n): string {\n return resolveMcpConfig(squadName, mcpServers).path;\n}\n"],"mappings":"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","names":["readFileSync","existsSync","writeFileSync","join","dirname","config","join","existsSync","readFileSync","_","dirname","writeFileSync"]}
@@ -0,0 +1,420 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/git.ts
4
+ import { execSync, exec } from "child_process";
5
+ import { existsSync } from "fs";
6
+ import { join, basename } from "path";
7
+ import { promisify } from "util";
8
+ var execAsync = promisify(exec);
9
+ function checkGitStatus(cwd = process.cwd()) {
10
+ const status = {
11
+ isGitRepo: false,
12
+ hasRemote: false,
13
+ isDirty: false,
14
+ uncommittedCount: 0
15
+ };
16
+ if (!existsSync(join(cwd, ".git"))) {
17
+ return status;
18
+ }
19
+ status.isGitRepo = true;
20
+ try {
21
+ const combined = execSync(
22
+ 'echo "BRANCH:" && git rev-parse --abbrev-ref HEAD && echo "REMOTES:" && git remote -v && echo "STATUS:" && git status --porcelain',
23
+ { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
24
+ );
25
+ const branchMatch = combined.match(/BRANCH:\n(.+)\n/);
26
+ if (branchMatch) {
27
+ status.branch = branchMatch[1].trim();
28
+ }
29
+ const remotesMatch = combined.match(/REMOTES:\n([\s\S]*?)STATUS:/);
30
+ if (remotesMatch) {
31
+ const remotes = remotesMatch[1].trim();
32
+ if (remotes) {
33
+ status.hasRemote = true;
34
+ const lines = remotes.split("\n");
35
+ if (lines.length > 0) {
36
+ const parts = lines[0].split(/\s+/);
37
+ status.remoteName = parts[0];
38
+ status.remoteUrl = parts[1];
39
+ }
40
+ }
41
+ }
42
+ const statusMatch = combined.match(/STATUS:\n([\s\S]*?)$/);
43
+ if (statusMatch) {
44
+ const statusOutput = statusMatch[1].trim();
45
+ if (statusOutput) {
46
+ status.isDirty = true;
47
+ status.uncommittedCount = statusOutput.split("\n").filter((l) => l.trim()).length;
48
+ }
49
+ }
50
+ } catch {
51
+ }
52
+ return status;
53
+ }
54
+ function getRepoName(remoteUrl) {
55
+ if (!remoteUrl) return null;
56
+ const match = remoteUrl.match(/[:/]([^/]+\/[^/]+?)(?:\.git)?$/);
57
+ return match ? match[1] : null;
58
+ }
59
+ var SQUAD_REPOS = ["hq", "agents-squads-web", "squads-cli", "company", "product", "engineering", "research", "intelligence", "customer", "finance", "marketing"];
60
+ var SQUAD_REPO_MAP = {
61
+ website: ["agents-squads-web"],
62
+ product: ["squads-cli"],
63
+ engineering: ["hq", "squads-cli"],
64
+ research: ["research"],
65
+ intelligence: ["intelligence"],
66
+ customer: ["customer"],
67
+ finance: ["finance"],
68
+ company: ["company", "hq"],
69
+ marketing: ["marketing", "agents-squads-web"]
70
+ };
71
+ var SQUAD_LABELS = {
72
+ website: ["website", "web", "frontend", "ui"],
73
+ product: ["product", "cli", "feature"],
74
+ engineering: ["engineering", "infra", "backend", "bug"],
75
+ research: ["research", "analysis"],
76
+ intelligence: ["intel", "monitoring"],
77
+ customer: ["customer", "sales", "lead"],
78
+ finance: ["finance", "cost", "billing"],
79
+ company: ["company", "strategy"],
80
+ marketing: ["marketing", "content", "seo"]
81
+ };
82
+ function getGitHubStatsOptimized(basePath, days = 30) {
83
+ const stats = {
84
+ prsOpened: 0,
85
+ prsMerged: 0,
86
+ issuesClosed: 0,
87
+ issuesOpen: 0,
88
+ bySquad: /* @__PURE__ */ new Map()
89
+ };
90
+ for (const squad of Object.keys(SQUAD_REPO_MAP)) {
91
+ stats.bySquad.set(squad, {
92
+ prsOpened: 0,
93
+ prsMerged: 0,
94
+ issuesClosed: 0,
95
+ issuesOpen: 0,
96
+ commits: 0,
97
+ recentIssues: [],
98
+ recentPRs: []
99
+ });
100
+ }
101
+ const repos = ["hq", "agents-squads-web"];
102
+ const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
103
+ const results = [];
104
+ for (const repo of repos) {
105
+ const repoPath = join(basePath, repo);
106
+ if (!existsSync(repoPath)) continue;
107
+ try {
108
+ const output = execSync(
109
+ `echo '{"prs":' && gh pr list --state all --json number,title,createdAt,mergedAt,labels --limit 50 2>/dev/null && echo ',"issues":' && gh issue list --state all --json number,title,state,closedAt,labels --limit 50 2>/dev/null && echo '}'`,
110
+ { cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 }
111
+ );
112
+ const prsMatch = output.match(/"prs":(\[.*?\]),"issues":/s);
113
+ const issuesMatch = output.match(/"issues":(\[.*?\])\s*\}/s);
114
+ const prs = prsMatch ? JSON.parse(prsMatch[1]) : [];
115
+ const issues = issuesMatch ? JSON.parse(issuesMatch[1]) : [];
116
+ results.push({ repo, prs, issues });
117
+ } catch {
118
+ try {
119
+ const prsOutput = execSync(
120
+ `gh pr list --state all --json number,title,createdAt,mergedAt,labels --limit 50 2>/dev/null`,
121
+ { cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
122
+ );
123
+ const issuesOutput = execSync(
124
+ `gh issue list --state all --json number,title,state,closedAt,labels --limit 50 2>/dev/null`,
125
+ { cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
126
+ );
127
+ results.push({
128
+ repo,
129
+ prs: JSON.parse(prsOutput || "[]"),
130
+ issues: JSON.parse(issuesOutput || "[]")
131
+ });
132
+ } catch {
133
+ }
134
+ }
135
+ }
136
+ for (const { repo, prs, issues } of results) {
137
+ for (const pr of prs) {
138
+ const created = new Date(pr.createdAt);
139
+ if (created < new Date(since)) continue;
140
+ stats.prsOpened++;
141
+ if (pr.mergedAt) stats.prsMerged++;
142
+ const squad = detectSquadFromPR(pr, repo);
143
+ const squadStats = stats.bySquad.get(squad);
144
+ if (squadStats) {
145
+ squadStats.prsOpened++;
146
+ if (pr.mergedAt) squadStats.prsMerged++;
147
+ if (squadStats.recentPRs.length < 3) {
148
+ squadStats.recentPRs.push({
149
+ title: pr.title,
150
+ number: pr.number,
151
+ merged: !!pr.mergedAt
152
+ });
153
+ }
154
+ }
155
+ }
156
+ for (const issue of issues) {
157
+ const squad = detectSquadFromIssue(issue, repo);
158
+ const squadStats = stats.bySquad.get(squad);
159
+ if (issue.state === "CLOSED") {
160
+ const closed = new Date(issue.closedAt || 0);
161
+ if (closed >= new Date(since)) {
162
+ stats.issuesClosed++;
163
+ if (squadStats) {
164
+ squadStats.issuesClosed++;
165
+ }
166
+ }
167
+ } else {
168
+ stats.issuesOpen++;
169
+ if (squadStats) {
170
+ squadStats.issuesOpen++;
171
+ if (squadStats.recentIssues.length < 3) {
172
+ squadStats.recentIssues.push({
173
+ title: issue.title,
174
+ number: issue.number,
175
+ state: issue.state
176
+ });
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ return stats;
183
+ }
184
+ function detectSquadFromPR(pr, repo) {
185
+ for (const label of pr.labels || []) {
186
+ const labelLower = label.name.toLowerCase();
187
+ for (const [squad, patterns] of Object.entries(SQUAD_LABELS)) {
188
+ if (patterns.some((p) => labelLower.includes(p))) {
189
+ return squad;
190
+ }
191
+ }
192
+ }
193
+ const titleLower = pr.title.toLowerCase();
194
+ for (const [squad, patterns] of Object.entries(SQUAD_LABELS)) {
195
+ if (patterns.some((p) => titleLower.includes(p))) {
196
+ return squad;
197
+ }
198
+ }
199
+ if (repo === "agents-squads-web") return "website";
200
+ if (repo === "squads-cli") return "product";
201
+ return "engineering";
202
+ }
203
+ function detectSquadFromIssue(issue, repo) {
204
+ for (const label of issue.labels || []) {
205
+ const labelLower = label.name.toLowerCase();
206
+ if (labelLower.startsWith("squad:")) {
207
+ return labelLower.replace("squad:", "");
208
+ }
209
+ for (const [squad, patterns] of Object.entries(SQUAD_LABELS)) {
210
+ if (patterns.some((p) => labelLower.includes(p))) {
211
+ return squad;
212
+ }
213
+ }
214
+ }
215
+ const titleLower = issue.title.toLowerCase();
216
+ for (const [squad, patterns] of Object.entries(SQUAD_LABELS)) {
217
+ if (patterns.some((p) => titleLower.includes(p))) {
218
+ return squad;
219
+ }
220
+ }
221
+ if (repo === "agents-squads-web") return "website";
222
+ if (repo === "squads-cli") return "product";
223
+ return "engineering";
224
+ }
225
+ async function getMultiRepoGitStats(basePath, days = 30) {
226
+ const stats = {
227
+ totalCommits: 0,
228
+ commitsByDay: /* @__PURE__ */ new Map(),
229
+ commitsByAuthor: /* @__PURE__ */ new Map(),
230
+ commitsByRepo: /* @__PURE__ */ new Map(),
231
+ activeDays: 0,
232
+ avgCommitsPerDay: 0,
233
+ peakDay: null,
234
+ repos: [],
235
+ recentCommits: []
236
+ };
237
+ const allCommits = [];
238
+ const repoSources = [];
239
+ for (const repo of SQUAD_REPOS) {
240
+ const repoPath = join(basePath, repo);
241
+ if (existsSync(repoPath) && existsSync(join(repoPath, ".git"))) {
242
+ repoSources.push({ name: repo, path: repoPath });
243
+ }
244
+ }
245
+ if (existsSync(join(basePath, ".git")) && !repoSources.some((s) => s.path === basePath)) {
246
+ repoSources.push({ name: basename(basePath), path: basePath });
247
+ }
248
+ const repoResults = await Promise.all(
249
+ repoSources.map(async ({ name: repo, path: repoPath }) => {
250
+ try {
251
+ const { stdout } = await execAsync(
252
+ `git log --since="${days} days ago" --format="%H|%aN|%ad|%s" --date=short 2>/dev/null`,
253
+ { cwd: repoPath, encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 }
254
+ );
255
+ return { repo, repoPath, output: stdout.trim() };
256
+ } catch {
257
+ return { repo, repoPath, output: "" };
258
+ }
259
+ })
260
+ );
261
+ for (const { repo, repoPath, output } of repoResults) {
262
+ if (!output) continue;
263
+ const commits = output.split("\n").filter((l) => l.trim());
264
+ const authors = /* @__PURE__ */ new Set();
265
+ let lastCommit = "";
266
+ for (const line of commits) {
267
+ const parts = line.split("|");
268
+ const [hash, author, date, ...messageParts] = parts;
269
+ const message = messageParts.join("|");
270
+ if (!hash) continue;
271
+ stats.totalCommits++;
272
+ authors.add(author);
273
+ if (!lastCommit) lastCommit = date;
274
+ const dayCount = stats.commitsByDay.get(date) || 0;
275
+ stats.commitsByDay.set(date, dayCount + 1);
276
+ const authorCount = stats.commitsByAuthor.get(author) || 0;
277
+ stats.commitsByAuthor.set(author, authorCount + 1);
278
+ const repoCount = stats.commitsByRepo.get(repo) || 0;
279
+ stats.commitsByRepo.set(repo, repoCount + 1);
280
+ allCommits.push({ hash, author, date, message, repo });
281
+ }
282
+ stats.repos.push({
283
+ name: repo,
284
+ path: repoPath,
285
+ commits: commits.length,
286
+ lastCommit,
287
+ authors: Array.from(authors)
288
+ });
289
+ }
290
+ stats.activeDays = stats.commitsByDay.size;
291
+ stats.avgCommitsPerDay = stats.activeDays > 0 ? Math.round(stats.totalCommits / days * 10) / 10 : 0;
292
+ let peakCount = 0;
293
+ let peakDate = "";
294
+ for (const [date, count] of stats.commitsByDay) {
295
+ if (count > peakCount) {
296
+ peakCount = count;
297
+ peakDate = date;
298
+ }
299
+ }
300
+ if (peakDate) {
301
+ stats.peakDay = { date: peakDate, count: peakCount };
302
+ }
303
+ stats.recentCommits = allCommits.sort((a, b) => b.date.localeCompare(a.date) || b.hash.localeCompare(a.hash)).slice(0, 5);
304
+ return stats;
305
+ }
306
+ async function fetchOperationalStatus(repos) {
307
+ const result = { milestones: [], openPRs: [], error: null };
308
+ if (repos.length === 0) {
309
+ return result;
310
+ }
311
+ try {
312
+ execSync("gh auth status 2>/dev/null", { stdio: "pipe", timeout: 3e3 });
313
+ } catch {
314
+ result.error = "gh not authenticated";
315
+ return result;
316
+ }
317
+ const repoResults = await Promise.all(
318
+ repos.map(async (fullRepo) => {
319
+ const repoShort = fullRepo.split("/").pop() || fullRepo;
320
+ const milestones = [];
321
+ const openPRs = [];
322
+ const [msResult, prResult] = await Promise.allSettled([
323
+ execAsync(
324
+ `gh api "repos/${fullRepo}/milestones?state=open" --jq '.[] | [.title, .open_issues, .closed_issues, .due_on] | @tsv' 2>/dev/null`,
325
+ { encoding: "utf-8", timeout: 8e3 }
326
+ ),
327
+ execAsync(
328
+ `gh pr list --repo "${fullRepo}" --state open --json number,title,baseRefName --jq '.[] | [.number, .baseRefName, .title] | @tsv' 2>/dev/null`,
329
+ { encoding: "utf-8", timeout: 8e3 }
330
+ )
331
+ ]);
332
+ if (msResult.status === "fulfilled") {
333
+ for (const line of msResult.value.stdout.trim().split("\n").filter((l) => l.trim())) {
334
+ const [title, open, closed, dueOn] = line.split(" ");
335
+ const openIssues = parseInt(open) || 0;
336
+ const closedIssues = parseInt(closed) || 0;
337
+ const totalIssues = openIssues + closedIssues;
338
+ milestones.push({
339
+ repo: repoShort,
340
+ title,
341
+ openIssues,
342
+ closedIssues,
343
+ totalIssues,
344
+ percent: totalIssues > 0 ? Math.floor(closedIssues / totalIssues * 100) : 0,
345
+ dueOn: dueOn && dueOn !== "null" ? dueOn : null
346
+ });
347
+ }
348
+ }
349
+ if (prResult.status === "fulfilled") {
350
+ for (const line of prResult.value.stdout.trim().split("\n").filter((l) => l.trim())) {
351
+ const [num, base, ...titleParts] = line.split(" ");
352
+ openPRs.push({
353
+ repo: repoShort,
354
+ number: parseInt(num) || 0,
355
+ title: titleParts.join(" "),
356
+ base
357
+ });
358
+ }
359
+ }
360
+ return { milestones, openPRs };
361
+ })
362
+ );
363
+ for (const { milestones, openPRs } of repoResults) {
364
+ result.milestones.push(...milestones);
365
+ result.openPRs.push(...openPRs);
366
+ }
367
+ return result;
368
+ }
369
+ async function getActivitySparkline(basePath, days = 7) {
370
+ const activity = [];
371
+ const now = /* @__PURE__ */ new Date();
372
+ for (let i = days - 1; i >= 0; i--) {
373
+ activity.push(0);
374
+ }
375
+ const sparklineRepos = [];
376
+ for (const repo of SQUAD_REPOS) {
377
+ const repoPath = join(basePath, repo);
378
+ if (existsSync(repoPath) && existsSync(join(repoPath, ".git"))) {
379
+ sparklineRepos.push(repoPath);
380
+ }
381
+ }
382
+ if (existsSync(join(basePath, ".git")) && !sparklineRepos.includes(basePath)) {
383
+ sparklineRepos.push(basePath);
384
+ }
385
+ const results = await Promise.all(
386
+ sparklineRepos.map(async (repoPath) => {
387
+ try {
388
+ const { stdout } = await execAsync(
389
+ `git log --since="${days} days ago" --format="%ad" --date=short 2>/dev/null`,
390
+ { cwd: repoPath, encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 }
391
+ );
392
+ return stdout.trim();
393
+ } catch {
394
+ return "";
395
+ }
396
+ })
397
+ );
398
+ for (const output of results) {
399
+ if (!output) continue;
400
+ for (const dateStr of output.split("\n")) {
401
+ const commitDate = new Date(dateStr);
402
+ const daysAgo = Math.floor((now.getTime() - commitDate.getTime()) / (1e3 * 60 * 60 * 24));
403
+ const index = days - 1 - daysAgo;
404
+ if (index >= 0 && index < days) {
405
+ activity[index]++;
406
+ }
407
+ }
408
+ }
409
+ return activity;
410
+ }
411
+
412
+ export {
413
+ checkGitStatus,
414
+ getRepoName,
415
+ getGitHubStatsOptimized,
416
+ getMultiRepoGitStats,
417
+ fetchOperationalStatus,
418
+ getActivitySparkline
419
+ };
420
+ //# sourceMappingURL=chunk-TZXD6WFN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/git.ts"],"sourcesContent":["import { execSync, exec } from 'child_process';\nimport { existsSync } from 'fs';\nimport { join, basename } from 'path';\nimport { promisify } from 'util';\n\nconst execAsync = promisify(exec);\n\nexport interface GitStatus {\n isGitRepo: boolean;\n hasRemote: boolean;\n remoteName?: string;\n remoteUrl?: string;\n branch?: string;\n isDirty: boolean;\n uncommittedCount: number;\n}\n\n/**\n * Check git status - synchronous version for backwards compatibility\n */\nexport function checkGitStatus(cwd: string = process.cwd()): GitStatus {\n const status: GitStatus = {\n isGitRepo: false,\n hasRemote: false,\n isDirty: false,\n uncommittedCount: 0,\n };\n\n // Check if .git directory exists\n if (!existsSync(join(cwd, '.git'))) {\n return status;\n }\n\n status.isGitRepo = true;\n\n try {\n // Run all git commands in parallel using a single combined command\n // This reduces 3 sequential execSync calls to 1\n const combined = execSync(\n 'echo \"BRANCH:\" && git rev-parse --abbrev-ref HEAD && echo \"REMOTES:\" && git remote -v && echo \"STATUS:\" && git status --porcelain',\n { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 5000 }\n );\n\n // Parse combined output\n const branchMatch = combined.match(/BRANCH:\\n(.+)\\n/);\n if (branchMatch) {\n status.branch = branchMatch[1].trim();\n }\n\n const remotesMatch = combined.match(/REMOTES:\\n([\\s\\S]*?)STATUS:/);\n if (remotesMatch) {\n const remotes = remotesMatch[1].trim();\n if (remotes) {\n status.hasRemote = true;\n const lines = remotes.split('\\n');\n if (lines.length > 0) {\n const parts = lines[0].split(/\\s+/);\n status.remoteName = parts[0];\n status.remoteUrl = parts[1];\n }\n }\n }\n\n const statusMatch = combined.match(/STATUS:\\n([\\s\\S]*?)$/);\n if (statusMatch) {\n const statusOutput = statusMatch[1].trim();\n if (statusOutput) {\n status.isDirty = true;\n status.uncommittedCount = statusOutput.split('\\n').filter(l => l.trim()).length;\n }\n }\n\n } catch {\n // Git commands failed, but we know it's a git repo\n }\n\n return status;\n}\n\n/**\n * Check git status - async version with parallel git commands\n */\nexport async function checkGitStatusAsync(cwd: string = process.cwd()): Promise<GitStatus> {\n const status: GitStatus = {\n isGitRepo: false,\n hasRemote: false,\n isDirty: false,\n uncommittedCount: 0,\n };\n\n // Check if .git directory exists\n if (!existsSync(join(cwd, '.git'))) {\n return status;\n }\n\n status.isGitRepo = true;\n\n try {\n // Run all git commands in parallel\n const [branchResult, remotesResult, statusResult] = await Promise.all([\n execAsync('git rev-parse --abbrev-ref HEAD', { cwd, timeout: 5000 }).catch(() => ({ stdout: '' })),\n execAsync('git remote -v', { cwd, timeout: 5000 }).catch(() => ({ stdout: '' })),\n execAsync('git status --porcelain', { cwd, timeout: 5000 }).catch(() => ({ stdout: '' })),\n ]);\n\n status.branch = branchResult.stdout.trim();\n\n const remotes = remotesResult.stdout.trim();\n if (remotes) {\n status.hasRemote = true;\n const lines = remotes.split('\\n');\n if (lines.length > 0) {\n const parts = lines[0].split(/\\s+/);\n status.remoteName = parts[0];\n status.remoteUrl = parts[1];\n }\n }\n\n const statusOutput = statusResult.stdout.trim();\n if (statusOutput) {\n status.isDirty = true;\n status.uncommittedCount = statusOutput.split('\\n').filter(l => l.trim()).length;\n }\n\n } catch {\n // Git commands failed, but we know it's a git repo\n }\n\n return status;\n}\n\nexport function initGitRepo(cwd: string = process.cwd()): boolean {\n try {\n execSync('git init', { cwd, stdio: 'pipe' });\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getRepoName(remoteUrl?: string): string | null {\n if (!remoteUrl) return null;\n\n // Handle various remote URL formats\n // git@github.com:user/repo.git\n // https://github.com/user/repo.git\n const match = remoteUrl.match(/[:/]([^/]+\\/[^/]+?)(?:\\.git)?$/);\n return match ? match[1] : null;\n}\n\n// Multi-repo git performance stats\nexport interface GitPerformanceStats {\n totalCommits: number;\n commitsByDay: Map<string, number>; // date string -> count\n commitsByAuthor: Map<string, number>;\n commitsByRepo: Map<string, number>;\n activeDays: number;\n avgCommitsPerDay: number;\n peakDay: { date: string; count: number } | null;\n repos: RepoStats[];\n recentCommits: CommitInfo[]; // Most recent commits across all repos\n}\n\nexport interface RepoStats {\n name: string;\n path: string;\n commits: number;\n lastCommit: string;\n authors: string[];\n}\n\nexport interface CommitInfo {\n hash: string;\n author: string;\n date: string;\n message: string;\n repo: string;\n}\n\nconst SQUAD_REPOS = ['hq', 'agents-squads-web', 'squads-cli', 'company', 'product', 'engineering', 'research', 'intelligence', 'customer', 'finance', 'marketing'];\n\n// Squad to repo mapping for GitHub stats\nconst SQUAD_REPO_MAP: Record<string, string[]> = {\n website: ['agents-squads-web'],\n product: ['squads-cli'],\n engineering: ['hq', 'squads-cli'],\n research: ['research'],\n intelligence: ['intelligence'],\n customer: ['customer'],\n finance: ['finance'],\n company: ['company', 'hq'],\n marketing: ['marketing', 'agents-squads-web'],\n};\n\n// Label patterns that map to squads\nconst SQUAD_LABELS: Record<string, string[]> = {\n website: ['website', 'web', 'frontend', 'ui'],\n product: ['product', 'cli', 'feature'],\n engineering: ['engineering', 'infra', 'backend', 'bug'],\n research: ['research', 'analysis'],\n intelligence: ['intel', 'monitoring'],\n customer: ['customer', 'sales', 'lead'],\n finance: ['finance', 'cost', 'billing'],\n company: ['company', 'strategy'],\n marketing: ['marketing', 'content', 'seo'],\n};\n\nexport interface GitHubStats {\n prsOpened: number;\n prsMerged: number;\n issuesClosed: number;\n issuesOpen: number;\n bySquad: Map<string, SquadGitHubStats>;\n}\n\nexport interface SquadGitHubStats {\n prsOpened: number;\n prsMerged: number;\n issuesClosed: number;\n issuesOpen: number;\n commits: number;\n recentIssues: { title: string; number: number; state: string }[];\n recentPRs: { title: string; number: number; merged: boolean }[];\n}\n\nexport async function getGitHubStats(basePath: string, days: number = 30): Promise<GitHubStats> {\n const stats: GitHubStats = {\n prsOpened: 0,\n prsMerged: 0,\n issuesClosed: 0,\n issuesOpen: 0,\n bySquad: new Map(),\n };\n\n // Initialize squad stats\n for (const squad of Object.keys(SQUAD_REPO_MAP)) {\n stats.bySquad.set(squad, {\n prsOpened: 0,\n prsMerged: 0,\n issuesClosed: 0,\n issuesOpen: 0,\n commits: 0,\n recentIssues: [],\n recentPRs: [],\n });\n }\n\n const repos = ['hq', 'agents-squads-web'];\n const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();\n\n for (const repo of repos) {\n const repoPath = join(basePath, repo);\n if (!existsSync(repoPath)) continue;\n\n try {\n // Get PRs\n const prsOutput = execSync(\n `gh pr list --state all --json number,title,createdAt,mergedAt,labels --limit 100 2>/dev/null`,\n { cwd: repoPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }\n );\n const prs = JSON.parse(prsOutput || '[]');\n\n for (const pr of prs) {\n const created = new Date(pr.createdAt);\n if (created < new Date(since)) continue;\n\n stats.prsOpened++;\n if (pr.mergedAt) stats.prsMerged++;\n\n // Detect squad from labels or title\n const squad = detectSquadFromPR(pr, repo);\n const squadStats = stats.bySquad.get(squad);\n if (squadStats) {\n squadStats.prsOpened++;\n if (pr.mergedAt) squadStats.prsMerged++;\n if (squadStats.recentPRs.length < 3) {\n squadStats.recentPRs.push({\n title: pr.title,\n number: pr.number,\n merged: !!pr.mergedAt,\n });\n }\n }\n }\n\n // Get Issues\n const issuesOutput = execSync(\n `gh issue list --state all --json number,title,state,closedAt,labels --limit 100 2>/dev/null`,\n { cwd: repoPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }\n );\n const issues = JSON.parse(issuesOutput || '[]');\n\n for (const issue of issues) {\n const squad = detectSquadFromIssue(issue, repo);\n const squadStats = stats.bySquad.get(squad);\n\n if (issue.state === 'CLOSED') {\n const closed = new Date(issue.closedAt);\n if (closed >= new Date(since)) {\n stats.issuesClosed++;\n if (squadStats) {\n squadStats.issuesClosed++;\n }\n }\n } else {\n stats.issuesOpen++;\n if (squadStats) {\n squadStats.issuesOpen++;\n if (squadStats.recentIssues.length < 3) {\n squadStats.recentIssues.push({\n title: issue.title,\n number: issue.number,\n state: issue.state,\n });\n }\n }\n }\n }\n } catch {\n // gh not available or not in repo\n }\n }\n\n // Add commit counts per squad\n const gitStats = await getMultiRepoGitStats(basePath, days);\n for (const [repo, commits] of gitStats.commitsByRepo) {\n // Map repo to squad\n for (const [squad, repos] of Object.entries(SQUAD_REPO_MAP)) {\n if (repos.includes(repo)) {\n const squadStats = stats.bySquad.get(squad);\n if (squadStats) {\n squadStats.commits += commits;\n }\n }\n }\n }\n\n return stats;\n}\n\n/**\n * Optimized GitHub stats - fetches PRs and issues in parallel across repos\n * Uses a single combined gh api call per repo for better performance\n */\nexport function getGitHubStatsOptimized(basePath: string, days: number = 30): GitHubStats {\n const stats: GitHubStats = {\n prsOpened: 0,\n prsMerged: 0,\n issuesClosed: 0,\n issuesOpen: 0,\n bySquad: new Map(),\n };\n\n // Initialize squad stats\n for (const squad of Object.keys(SQUAD_REPO_MAP)) {\n stats.bySquad.set(squad, {\n prsOpened: 0,\n prsMerged: 0,\n issuesClosed: 0,\n issuesOpen: 0,\n commits: 0,\n recentIssues: [],\n recentPRs: [],\n });\n }\n\n const repos = ['hq', 'agents-squads-web'];\n const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();\n\n // Fetch all data in parallel using a single combined command\n const results: { repo: string; prs: unknown[]; issues: unknown[] }[] = [];\n\n for (const repo of repos) {\n const repoPath = join(basePath, repo);\n if (!existsSync(repoPath)) continue;\n\n try {\n // Use a single shell command to get both PRs and issues\n // This reduces the number of gh CLI invocations from 4 to 2\n const output = execSync(\n `echo '{\"prs\":' && gh pr list --state all --json number,title,createdAt,mergedAt,labels --limit 50 2>/dev/null && echo ',\"issues\":' && gh issue list --state all --json number,title,state,closedAt,labels --limit 50 2>/dev/null && echo '}'`,\n { cwd: repoPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000 }\n );\n\n // Parse the combined output (handle edge cases)\n const prsMatch = output.match(/\"prs\":(\\[.*?\\]),\"issues\":/s);\n const issuesMatch = output.match(/\"issues\":(\\[.*?\\])\\s*\\}/s);\n\n const prs = prsMatch ? JSON.parse(prsMatch[1]) : [];\n const issues = issuesMatch ? JSON.parse(issuesMatch[1]) : [];\n\n results.push({ repo, prs, issues });\n } catch {\n // Fallback: try individual calls with short timeout\n try {\n const prsOutput = execSync(\n `gh pr list --state all --json number,title,createdAt,mergedAt,labels --limit 50 2>/dev/null`,\n { cwd: repoPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 5000 }\n );\n const issuesOutput = execSync(\n `gh issue list --state all --json number,title,state,closedAt,labels --limit 50 2>/dev/null`,\n { cwd: repoPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 5000 }\n );\n results.push({\n repo,\n prs: JSON.parse(prsOutput || '[]'),\n issues: JSON.parse(issuesOutput || '[]'),\n });\n } catch {\n // Skip this repo\n }\n }\n }\n\n // Process results\n for (const { repo, prs, issues } of results) {\n // Process PRs\n for (const pr of prs as { createdAt: string; mergedAt?: string; title: string; number: number; labels: { name: string }[] }[]) {\n const created = new Date(pr.createdAt);\n if (created < new Date(since)) continue;\n\n stats.prsOpened++;\n if (pr.mergedAt) stats.prsMerged++;\n\n const squad = detectSquadFromPR(pr, repo);\n const squadStats = stats.bySquad.get(squad);\n if (squadStats) {\n squadStats.prsOpened++;\n if (pr.mergedAt) squadStats.prsMerged++;\n if (squadStats.recentPRs.length < 3) {\n squadStats.recentPRs.push({\n title: pr.title,\n number: pr.number,\n merged: !!pr.mergedAt,\n });\n }\n }\n }\n\n // Process Issues\n for (const issue of issues as { state: string; closedAt?: string; title: string; number: number; labels: { name: string }[] }[]) {\n const squad = detectSquadFromIssue(issue, repo);\n const squadStats = stats.bySquad.get(squad);\n\n if (issue.state === 'CLOSED') {\n const closed = new Date(issue.closedAt || 0);\n if (closed >= new Date(since)) {\n stats.issuesClosed++;\n if (squadStats) {\n squadStats.issuesClosed++;\n }\n }\n } else {\n stats.issuesOpen++;\n if (squadStats) {\n squadStats.issuesOpen++;\n if (squadStats.recentIssues.length < 3) {\n squadStats.recentIssues.push({\n title: issue.title,\n number: issue.number,\n state: issue.state,\n });\n }\n }\n }\n }\n }\n\n // Note: commit counts are added separately by the caller using cached git stats\n return stats;\n}\n\nfunction detectSquadFromPR(pr: { title: string; labels: { name: string }[] }, repo: string): string {\n // Check labels first\n for (const label of pr.labels || []) {\n const labelLower = label.name.toLowerCase();\n for (const [squad, patterns] of Object.entries(SQUAD_LABELS)) {\n if (patterns.some(p => labelLower.includes(p))) {\n return squad;\n }\n }\n }\n\n // Check title\n const titleLower = pr.title.toLowerCase();\n for (const [squad, patterns] of Object.entries(SQUAD_LABELS)) {\n if (patterns.some(p => titleLower.includes(p))) {\n return squad;\n }\n }\n\n // Default based on repo\n if (repo === 'agents-squads-web') return 'website';\n if (repo === 'squads-cli') return 'product';\n return 'engineering';\n}\n\nfunction detectSquadFromIssue(issue: { title: string; labels: { name: string }[] }, repo: string): string {\n // Check labels first\n for (const label of issue.labels || []) {\n const labelLower = label.name.toLowerCase();\n\n // Direct squad label match\n if (labelLower.startsWith('squad:')) {\n return labelLower.replace('squad:', '');\n }\n\n for (const [squad, patterns] of Object.entries(SQUAD_LABELS)) {\n if (patterns.some(p => labelLower.includes(p))) {\n return squad;\n }\n }\n }\n\n // Check title\n const titleLower = issue.title.toLowerCase();\n for (const [squad, patterns] of Object.entries(SQUAD_LABELS)) {\n if (patterns.some(p => titleLower.includes(p))) {\n return squad;\n }\n }\n\n // Default based on repo\n if (repo === 'agents-squads-web') return 'website';\n if (repo === 'squads-cli') return 'product';\n return 'engineering';\n}\n\nexport async function getMultiRepoGitStats(basePath: string, days: number = 30): Promise<GitPerformanceStats> {\n const stats: GitPerformanceStats = {\n totalCommits: 0,\n commitsByDay: new Map(),\n commitsByAuthor: new Map(),\n commitsByRepo: new Map(),\n activeDays: 0,\n avgCommitsPerDay: 0,\n peakDay: null,\n repos: [],\n recentCommits: [],\n };\n\n // Collect all commits with full info for sorting\n const allCommits: CommitInfo[] = [];\n\n // Build list of valid repo sources\n const repoSources: Array<{ name: string; path: string }> = [];\n\n // Check SQUAD_REPOS subdirectories\n for (const repo of SQUAD_REPOS) {\n const repoPath = join(basePath, repo);\n if (existsSync(repoPath) && existsSync(join(repoPath, '.git'))) {\n repoSources.push({ name: repo, path: repoPath });\n }\n }\n\n // Also check basePath itself (for single-project users where cwd IS the project)\n if (existsSync(join(basePath, '.git')) && !repoSources.some(s => s.path === basePath)) {\n repoSources.push({ name: basename(basePath), path: basePath });\n }\n\n // Fetch git logs from all repos in parallel\n const repoResults = await Promise.all(\n repoSources.map(async ({ name: repo, path: repoPath }) => {\n try {\n const { stdout } = await execAsync(\n `git log --since=\"${days} days ago\" --format=\"%H|%aN|%ad|%s\" --date=short 2>/dev/null`,\n { cwd: repoPath, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }\n );\n return { repo, repoPath, output: stdout.trim() };\n } catch {\n return { repo, repoPath, output: '' };\n }\n })\n );\n\n // Process results\n for (const { repo, repoPath, output } of repoResults) {\n if (!output) continue;\n\n const commits = output.split('\\n').filter(l => l.trim());\n const authors = new Set<string>();\n let lastCommit = '';\n\n for (const line of commits) {\n const parts = line.split('|');\n const [hash, author, date, ...messageParts] = parts;\n const message = messageParts.join('|'); // Handle | in commit messages\n if (!hash) continue;\n\n stats.totalCommits++;\n authors.add(author);\n if (!lastCommit) lastCommit = date;\n\n // By day\n const dayCount = stats.commitsByDay.get(date) || 0;\n stats.commitsByDay.set(date, dayCount + 1);\n\n // By author\n const authorCount = stats.commitsByAuthor.get(author) || 0;\n stats.commitsByAuthor.set(author, authorCount + 1);\n\n // By repo\n const repoCount = stats.commitsByRepo.get(repo) || 0;\n stats.commitsByRepo.set(repo, repoCount + 1);\n\n // Collect for recent commits\n allCommits.push({ hash, author, date, message, repo });\n }\n\n stats.repos.push({\n name: repo,\n path: repoPath,\n commits: commits.length,\n lastCommit,\n authors: Array.from(authors),\n });\n }\n\n // Calculate derived stats\n stats.activeDays = stats.commitsByDay.size;\n stats.avgCommitsPerDay = stats.activeDays > 0\n ? Math.round((stats.totalCommits / days) * 10) / 10\n : 0;\n\n // Find peak day\n let peakCount = 0;\n let peakDate = '';\n for (const [date, count] of stats.commitsByDay) {\n if (count > peakCount) {\n peakCount = count;\n peakDate = date;\n }\n }\n if (peakDate) {\n stats.peakDay = { date: peakDate, count: peakCount };\n }\n\n // Sort commits by date (most recent first) and take top 5\n stats.recentCommits = allCommits\n .sort((a, b) => b.date.localeCompare(a.date) || b.hash.localeCompare(a.hash))\n .slice(0, 5);\n\n return stats;\n}\n\nexport interface MilestoneInfo {\n repo: string;\n title: string;\n openIssues: number;\n closedIssues: number;\n totalIssues: number;\n percent: number;\n dueOn: string | null;\n}\n\nexport interface OpenPR {\n repo: string;\n number: number;\n title: string;\n base: string;\n}\n\nexport interface OperationalStatus {\n milestones: MilestoneInfo[];\n openPRs: OpenPR[];\n error: string | null;\n}\n\n/**\n * Fetch operational status: milestones + open PRs across repos.\n * Repos are discovered from squad definitions (SQUAD.md `repo` field).\n * Uses gh CLI — gracefully returns empty if gh is unavailable.\n * Fetches all repos in parallel for performance.\n *\n * @param repos - Array of \"owner/repo\" strings (e.g., [\"agents-squads/squads-cli\"])\n */\nexport async function fetchOperationalStatus(repos: string[]): Promise<OperationalStatus> {\n const result: OperationalStatus = { milestones: [], openPRs: [], error: null };\n\n if (repos.length === 0) {\n return result;\n }\n\n try {\n execSync('gh auth status 2>/dev/null', { stdio: 'pipe', timeout: 3000 });\n } catch {\n result.error = 'gh not authenticated';\n return result;\n }\n\n // Fetch all repos in parallel\n const repoResults = await Promise.all(\n repos.map(async (fullRepo) => {\n const repoShort = fullRepo.split('/').pop() || fullRepo;\n const milestones: OperationalStatus['milestones'] = [];\n const openPRs: OperationalStatus['openPRs'] = [];\n\n // Fetch milestones and PRs concurrently per repo\n const [msResult, prResult] = await Promise.allSettled([\n execAsync(\n `gh api \"repos/${fullRepo}/milestones?state=open\" --jq '.[] | [.title, .open_issues, .closed_issues, .due_on] | @tsv' 2>/dev/null`,\n { encoding: 'utf-8', timeout: 8000 }\n ),\n execAsync(\n `gh pr list --repo \"${fullRepo}\" --state open --json number,title,baseRefName --jq '.[] | [.number, .baseRefName, .title] | @tsv' 2>/dev/null`,\n { encoding: 'utf-8', timeout: 8000 }\n ),\n ]);\n\n if (msResult.status === 'fulfilled') {\n for (const line of msResult.value.stdout.trim().split('\\n').filter(l => l.trim())) {\n const [title, open, closed, dueOn] = line.split('\\t');\n const openIssues = parseInt(open) || 0;\n const closedIssues = parseInt(closed) || 0;\n const totalIssues = openIssues + closedIssues;\n milestones.push({\n repo: repoShort,\n title,\n openIssues,\n closedIssues,\n totalIssues,\n percent: totalIssues > 0 ? Math.floor((closedIssues / totalIssues) * 100) : 0,\n dueOn: dueOn && dueOn !== 'null' ? dueOn : null,\n });\n }\n }\n\n if (prResult.status === 'fulfilled') {\n for (const line of prResult.value.stdout.trim().split('\\n').filter(l => l.trim())) {\n const [num, base, ...titleParts] = line.split('\\t');\n openPRs.push({\n repo: repoShort,\n number: parseInt(num) || 0,\n title: titleParts.join('\\t'),\n base,\n });\n }\n }\n\n return { milestones, openPRs };\n })\n );\n\n for (const { milestones, openPRs } of repoResults) {\n result.milestones.push(...milestones);\n result.openPRs.push(...openPRs);\n }\n\n return result;\n}\n\n// Get recent activity sparkline data (last 7 days)\nexport async function getActivitySparkline(basePath: string, days: number = 7): Promise<number[]> {\n const activity: number[] = [];\n const now = new Date();\n\n // Initialize activity array with zeros for each day\n for (let i = days - 1; i >= 0; i--) {\n activity.push(0);\n }\n\n // Build list of valid repo sources\n const sparklineRepos: Array<string> = [];\n for (const repo of SQUAD_REPOS) {\n const repoPath = join(basePath, repo);\n if (existsSync(repoPath) && existsSync(join(repoPath, '.git'))) {\n sparklineRepos.push(repoPath);\n }\n }\n // Also check basePath itself (for single-project users where cwd IS the project)\n if (existsSync(join(basePath, '.git')) && !sparklineRepos.includes(basePath)) {\n sparklineRepos.push(basePath);\n }\n\n // Fetch git logs from all repos in parallel\n const results = await Promise.all(\n sparklineRepos.map(async (repoPath) => {\n try {\n const { stdout } = await execAsync(\n `git log --since=\"${days} days ago\" --format=\"%ad\" --date=short 2>/dev/null`,\n { cwd: repoPath, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }\n );\n return stdout.trim();\n } catch {\n return '';\n }\n })\n );\n\n // Process results\n for (const output of results) {\n if (!output) continue;\n\n for (const dateStr of output.split('\\n')) {\n const commitDate = new Date(dateStr);\n const daysAgo = Math.floor((now.getTime() - commitDate.getTime()) / (1000 * 60 * 60 * 24));\n const index = days - 1 - daysAgo;\n if (index >= 0 && index < days) {\n activity[index]++;\n }\n }\n }\n\n return activity;\n}\n"],"mappings":"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","names":[]}