all-hands-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/.allhands/README.md +75 -0
  2. package/.allhands/agents/compounder.yaml +15 -0
  3. package/.allhands/agents/coordinator.yaml +17 -0
  4. package/.allhands/agents/documentor.yaml +15 -0
  5. package/.allhands/agents/e2e-test-planner.yaml +17 -0
  6. package/.allhands/agents/emergent.yaml +22 -0
  7. package/.allhands/agents/executor.yaml +14 -0
  8. package/.allhands/agents/ideation.yaml +11 -0
  9. package/.allhands/agents/initiative-steering.yaml +19 -0
  10. package/.allhands/agents/judge.yaml +13 -0
  11. package/.allhands/agents/planner.yaml +19 -0
  12. package/.allhands/agents/pr-reviewer.yaml +15 -0
  13. package/.allhands/docs.json +5 -0
  14. package/.allhands/docs.local.json +26 -0
  15. package/.allhands/flows/COMPOUNDING.md +203 -0
  16. package/.allhands/flows/COORDINATION.md +89 -0
  17. package/.allhands/flows/CORE.md +87 -0
  18. package/.allhands/flows/DOCUMENTATION.md +218 -0
  19. package/.allhands/flows/E2E_TEST_PLAN_BUILDING.md +140 -0
  20. package/.allhands/flows/EMERGENT_PLANNING.md +57 -0
  21. package/.allhands/flows/IDEATION_SCOPING.md +154 -0
  22. package/.allhands/flows/INITIATIVE_STEERING.md +110 -0
  23. package/.allhands/flows/JUDGE_REVIEWING.md +79 -0
  24. package/.allhands/flows/PROMPT_TASK_EXECUTION.md +68 -0
  25. package/.allhands/flows/PR_REVIEWING.md +43 -0
  26. package/.allhands/flows/SPEC_PLANNING.md +216 -0
  27. package/.allhands/flows/harness/WRITING_HARNESS_FLOWS.md +27 -0
  28. package/.allhands/flows/harness/WRITING_HARNESS_KNOWLEDGE.md +27 -0
  29. package/.allhands/flows/harness/WRITING_HARNESS_ORCHESTRATION.md +27 -0
  30. package/.allhands/flows/harness/WRITING_HARNESS_SKILLS.md +27 -0
  31. package/.allhands/flows/harness/WRITING_HARNESS_TOOLS.md +27 -0
  32. package/.allhands/flows/harness/WRITING_HARNESS_VALIDATION_TOOLING.md +27 -0
  33. package/.allhands/flows/shared/CODEBASE_UNDERSTANDING.md +72 -0
  34. package/.allhands/flows/shared/CREATE_HARNESS_SPEC.md +48 -0
  35. package/.allhands/flows/shared/CREATE_SPEC.md +41 -0
  36. package/.allhands/flows/shared/CREATE_VALIDATION_TOOLING_SPEC.md +70 -0
  37. package/.allhands/flows/shared/DOCUMENTATION_DISCOVERY.md +123 -0
  38. package/.allhands/flows/shared/DOCUMENTATION_WRITER.md +101 -0
  39. package/.allhands/flows/shared/EMERGENT_REFINEMENT_ANALYSIS.md +76 -0
  40. package/.allhands/flows/shared/EXTERNAL_TECH_GUIDANCE.md +97 -0
  41. package/.allhands/flows/shared/IDEATION_CODEBASE_GROUNDING.md +49 -0
  42. package/.allhands/flows/shared/PLAN_DEEPENING.md +152 -0
  43. package/.allhands/flows/shared/PROMPT_TASKS_CURATION.md +113 -0
  44. package/.allhands/flows/shared/PROMPT_VALIDATION_REVIEW.MD +99 -0
  45. package/.allhands/flows/shared/QUICK_PREMORTEM.md +70 -0
  46. package/.allhands/flows/shared/RESEARCH_GUIDANCE.md +38 -0
  47. package/.allhands/flows/shared/REVIEW_OPTIONS_BREAKDOWN.md +68 -0
  48. package/.allhands/flows/shared/SKILL_EXTRACTION.md +84 -0
  49. package/.allhands/flows/shared/SPEC_FLOW_ANALYSIS.md +119 -0
  50. package/.allhands/flows/shared/TDD_WORKFLOW.md +109 -0
  51. package/.allhands/flows/shared/UTILIZE_VALIDATION_TOOLING.md +84 -0
  52. package/.allhands/flows/shared/WRITING_HARNESS_FLOWS.md +11 -0
  53. package/.allhands/flows/shared/WRITING_HARNESS_MCP_TOOLS.md +84 -0
  54. package/.allhands/flows/shared/jury/ARCHITECTURE_REVIEW.md +91 -0
  55. package/.allhands/flows/shared/jury/BEST_PRACTICES_REVIEW.md +80 -0
  56. package/.allhands/flows/shared/jury/CLAIM_VERIFICATION_REVIEW.md +101 -0
  57. package/.allhands/flows/shared/jury/EXPECTATIONS_FIT_REVIEW.md +78 -0
  58. package/.allhands/flows/shared/jury/MAINTAINABILITY_REVIEW.md +110 -0
  59. package/.allhands/flows/shared/jury/PROMPTS_EXPECTATIONS_FIT.md +74 -0
  60. package/.allhands/flows/shared/jury/PROMPTS_FLOW_ANALYSIS.md +92 -0
  61. package/.allhands/flows/shared/jury/PROMPTS_YAGNI.md +78 -0
  62. package/.allhands/flows/shared/jury/PROMPT_PREMORTEM.md +125 -0
  63. package/.allhands/flows/shared/jury/SECURITY_REVIEW.md +86 -0
  64. package/.allhands/flows/shared/jury/YAGNI_REVIEW.md +82 -0
  65. package/.allhands/flows/wip/DEBUG_INVESTIGATION.md +162 -0
  66. package/.allhands/flows/wip/MEMORY_RECALL.md +62 -0
  67. package/.allhands/harness/ah +131 -0
  68. package/.allhands/harness/package-lock.json +5292 -0
  69. package/.allhands/harness/package.json +52 -0
  70. package/.allhands/harness/src/__tests__/e2e/commands.test.ts +307 -0
  71. package/.allhands/harness/src/__tests__/e2e/event-loop.test.ts +539 -0
  72. package/.allhands/harness/src/__tests__/e2e/hooks.test.ts +427 -0
  73. package/.allhands/harness/src/__tests__/e2e/new-initiative-routing.test.ts +137 -0
  74. package/.allhands/harness/src/__tests__/e2e/run-e2e.ts +109 -0
  75. package/.allhands/harness/src/__tests__/e2e/specs-type.test.ts +210 -0
  76. package/.allhands/harness/src/__tests__/e2e/validation-hooks.test.ts +669 -0
  77. package/.allhands/harness/src/__tests__/e2e/validation-path-consistency.test.ts +354 -0
  78. package/.allhands/harness/src/__tests__/e2e/validation.test.ts +528 -0
  79. package/.allhands/harness/src/__tests__/harness/assertions.ts +318 -0
  80. package/.allhands/harness/src/__tests__/harness/cli-runner.ts +359 -0
  81. package/.allhands/harness/src/__tests__/harness/fixture.ts +384 -0
  82. package/.allhands/harness/src/__tests__/harness/hook-runner.ts +411 -0
  83. package/.allhands/harness/src/__tests__/harness/index.ts +122 -0
  84. package/.allhands/harness/src/cli.ts +36 -0
  85. package/.allhands/harness/src/commands/complexity.ts +177 -0
  86. package/.allhands/harness/src/commands/context7.ts +202 -0
  87. package/.allhands/harness/src/commands/docs.ts +557 -0
  88. package/.allhands/harness/src/commands/hooks.ts +24 -0
  89. package/.allhands/harness/src/commands/index.ts +51 -0
  90. package/.allhands/harness/src/commands/knowledge.ts +382 -0
  91. package/.allhands/harness/src/commands/memories.ts +302 -0
  92. package/.allhands/harness/src/commands/notify.ts +61 -0
  93. package/.allhands/harness/src/commands/oracle.ts +158 -0
  94. package/.allhands/harness/src/commands/perplexity.ts +220 -0
  95. package/.allhands/harness/src/commands/planning.ts +245 -0
  96. package/.allhands/harness/src/commands/schema.ts +73 -0
  97. package/.allhands/harness/src/commands/skills.ts +128 -0
  98. package/.allhands/harness/src/commands/solutions.ts +353 -0
  99. package/.allhands/harness/src/commands/spawn.ts +158 -0
  100. package/.allhands/harness/src/commands/specs.ts +532 -0
  101. package/.allhands/harness/src/commands/tavily.ts +226 -0
  102. package/.allhands/harness/src/commands/tools.ts +579 -0
  103. package/.allhands/harness/src/commands/trace.ts +327 -0
  104. package/.allhands/harness/src/commands/tui.ts +960 -0
  105. package/.allhands/harness/src/commands/validate.ts +143 -0
  106. package/.allhands/harness/src/commands/validation-tools.ts +108 -0
  107. package/.allhands/harness/src/hooks/context.ts +1442 -0
  108. package/.allhands/harness/src/hooks/enforcement.ts +170 -0
  109. package/.allhands/harness/src/hooks/index.ts +54 -0
  110. package/.allhands/harness/src/hooks/lifecycle.ts +229 -0
  111. package/.allhands/harness/src/hooks/notification.ts +104 -0
  112. package/.allhands/harness/src/hooks/observability.ts +551 -0
  113. package/.allhands/harness/src/hooks/session.ts +88 -0
  114. package/.allhands/harness/src/hooks/shared.ts +815 -0
  115. package/.allhands/harness/src/hooks/transcript-parser.ts +208 -0
  116. package/.allhands/harness/src/hooks/validation.ts +617 -0
  117. package/.allhands/harness/src/lib/__tests__/ctags.test.ts +244 -0
  118. package/.allhands/harness/src/lib/__tests__/docs-validation.test.ts +344 -0
  119. package/.allhands/harness/src/lib/__tests__/mcp-runtime.test.ts +190 -0
  120. package/.allhands/harness/src/lib/__tests__/schema.test.ts +861 -0
  121. package/.allhands/harness/src/lib/base-command.ts +198 -0
  122. package/.allhands/harness/src/lib/cli-daemon.ts +343 -0
  123. package/.allhands/harness/src/lib/compaction.ts +313 -0
  124. package/.allhands/harness/src/lib/ctags.ts +497 -0
  125. package/.allhands/harness/src/lib/docs-validation.ts +907 -0
  126. package/.allhands/harness/src/lib/event-loop.ts +662 -0
  127. package/.allhands/harness/src/lib/flows.ts +155 -0
  128. package/.allhands/harness/src/lib/git.ts +276 -0
  129. package/.allhands/harness/src/lib/knowledge-worker.ts +72 -0
  130. package/.allhands/harness/src/lib/knowledge.ts +810 -0
  131. package/.allhands/harness/src/lib/llm.ts +255 -0
  132. package/.allhands/harness/src/lib/mcp-client.ts +432 -0
  133. package/.allhands/harness/src/lib/mcp-daemon.ts +486 -0
  134. package/.allhands/harness/src/lib/mcp-runtime.ts +418 -0
  135. package/.allhands/harness/src/lib/notification.ts +115 -0
  136. package/.allhands/harness/src/lib/opencode/index.ts +70 -0
  137. package/.allhands/harness/src/lib/opencode/profiles.ts +300 -0
  138. package/.allhands/harness/src/lib/opencode/prompts/codesearch.md +98 -0
  139. package/.allhands/harness/src/lib/opencode/prompts/knowledge-aggregator.md +67 -0
  140. package/.allhands/harness/src/lib/opencode/runner.ts +281 -0
  141. package/.allhands/harness/src/lib/oracle.ts +926 -0
  142. package/.allhands/harness/src/lib/planning-utils.ts +150 -0
  143. package/.allhands/harness/src/lib/planning.ts +605 -0
  144. package/.allhands/harness/src/lib/pr-review.ts +225 -0
  145. package/.allhands/harness/src/lib/prompts.ts +522 -0
  146. package/.allhands/harness/src/lib/schema.ts +418 -0
  147. package/.allhands/harness/src/lib/schemas/agent-profile.ts +141 -0
  148. package/.allhands/harness/src/lib/schemas/template-vars.ts +138 -0
  149. package/.allhands/harness/src/lib/session.ts +164 -0
  150. package/.allhands/harness/src/lib/specs.ts +348 -0
  151. package/.allhands/harness/src/lib/tldr.ts +829 -0
  152. package/.allhands/harness/src/lib/tmux.ts +1051 -0
  153. package/.allhands/harness/src/lib/trace-store.ts +714 -0
  154. package/.allhands/harness/src/mcp/__tests__/index.test.ts +46 -0
  155. package/.allhands/harness/src/mcp/_template.ts +47 -0
  156. package/.allhands/harness/src/mcp/filesystem.ts +33 -0
  157. package/.allhands/harness/src/mcp/index.ts +69 -0
  158. package/.allhands/harness/src/mcp/playwright.ts +34 -0
  159. package/.allhands/harness/src/mcp/xcodebuild.ts +29 -0
  160. package/.allhands/harness/src/schemas/docs.schema.json +44 -0
  161. package/.allhands/harness/src/schemas/settings.schema.json +214 -0
  162. package/.allhands/harness/src/tui/actions.ts +227 -0
  163. package/.allhands/harness/src/tui/file-viewer-modal.ts +270 -0
  164. package/.allhands/harness/src/tui/index.ts +1574 -0
  165. package/.allhands/harness/src/tui/modal.ts +232 -0
  166. package/.allhands/harness/src/tui/prompts-pane.ts +186 -0
  167. package/.allhands/harness/src/tui/status-pane.ts +434 -0
  168. package/.allhands/harness/tsconfig.json +22 -0
  169. package/.allhands/harness/vitest.config.ts +13 -0
  170. package/.allhands/pillars.md +33 -0
  171. package/.allhands/principles.md +88 -0
  172. package/.allhands/schemas/alignment.yaml +51 -0
  173. package/.allhands/schemas/documentation.yaml +10 -0
  174. package/.allhands/schemas/prompt.yaml +92 -0
  175. package/.allhands/schemas/skill.yaml +34 -0
  176. package/.allhands/schemas/solution.yaml +131 -0
  177. package/.allhands/schemas/spec.yaml +67 -0
  178. package/.allhands/schemas/validation-suite.yaml +49 -0
  179. package/.allhands/schemas/workflow.yaml +51 -0
  180. package/.allhands/settings.json +57 -0
  181. package/.allhands/skills/claude-code-patterns/SKILL.md +60 -0
  182. package/.allhands/skills/claude-code-patterns/docs/context-hygiene.md +19 -0
  183. package/.allhands/skills/harness-maintenance/SKILL.md +449 -0
  184. package/.allhands/skills/harness-maintenance/references/core-architecture.md +187 -0
  185. package/.allhands/skills/harness-maintenance/references/harness-skills.md +87 -0
  186. package/.allhands/skills/harness-maintenance/references/knowledge-compounding.md +78 -0
  187. package/.allhands/skills/harness-maintenance/references/tools-commands-mcp-hooks.md +115 -0
  188. package/.allhands/skills/harness-maintenance/references/validation-tooling.md +77 -0
  189. package/.allhands/skills/harness-maintenance/references/writing-flows.md +84 -0
  190. package/.allhands/validation/browser-automation.md +109 -0
  191. package/.allhands/validation/xcode-automation.md +195 -0
  192. package/.allhands/workflows/documentation.md +86 -0
  193. package/.allhands/workflows/investigation.md +81 -0
  194. package/.allhands/workflows/milestone.md +91 -0
  195. package/.allhands/workflows/optimization.md +85 -0
  196. package/.allhands/workflows/refactor.md +99 -0
  197. package/.allhands/workflows/triage.md +81 -0
  198. package/.claude/README.md +1 -0
  199. package/.claude/agents/explorer.md +10 -0
  200. package/.claude/agents/researcher.md +11 -0
  201. package/.claude/agents/task-runner.md +8 -0
  202. package/.claude/settings.json +231 -0
  203. package/.env.ai.example +7 -0
  204. package/.github/workflows/npm-publish.yml +69 -0
  205. package/.internal.json +45 -0
  206. package/.tldr/config.json +11 -0
  207. package/.tldrignore +90 -0
  208. package/CLAUDE.md +6 -0
  209. package/README.md +98 -0
  210. package/bin/sync-cli.js +7552 -0
  211. package/concerns.md +7 -0
  212. package/docs/README.md +41 -0
  213. package/docs/agents/README.md +24 -0
  214. package/docs/agents/agent-configuration-system.md +86 -0
  215. package/docs/agents/execution-agents.md +50 -0
  216. package/docs/agents/knowledge-agents.md +61 -0
  217. package/docs/agents/orchestration-agent.md +57 -0
  218. package/docs/agents/planning-agents.md +84 -0
  219. package/docs/agents/quality-review-agents.md +67 -0
  220. package/docs/agents/workflow-agent-orchestration.md +69 -0
  221. package/docs/flows/README.md +44 -0
  222. package/docs/flows/compounding.md +126 -0
  223. package/docs/flows/coordination.md +72 -0
  224. package/docs/flows/core-harness-integration.md +63 -0
  225. package/docs/flows/documentation-orchestration.md +98 -0
  226. package/docs/flows/e2e-test-plan-building.md +83 -0
  227. package/docs/flows/emergent-refinement.md +104 -0
  228. package/docs/flows/flow-authoring-and-mcp-tools.md +89 -0
  229. package/docs/flows/judge-reviewing.md +112 -0
  230. package/docs/flows/plan-deepening-and-research.md +107 -0
  231. package/docs/flows/plan-review-jury.md +114 -0
  232. package/docs/flows/pr-reviewing.md +54 -0
  233. package/docs/flows/prompt-task-execution.md +119 -0
  234. package/docs/flows/spec-planning.md +162 -0
  235. package/docs/flows/type-specific-scoping-flows.md +49 -0
  236. package/docs/flows/validation-and-skills-integration.md +145 -0
  237. package/docs/flows/wip/wip-flows.md +102 -0
  238. package/docs/harness/README.md +23 -0
  239. package/docs/harness/agent-profiles.md +84 -0
  240. package/docs/harness/cli/README.md +24 -0
  241. package/docs/harness/cli/cli-entry-and-command-discovery.md +91 -0
  242. package/docs/harness/cli/docs-command.md +87 -0
  243. package/docs/harness/cli/knowledge-command.md +91 -0
  244. package/docs/harness/cli/minor-cli-commands.md +65 -0
  245. package/docs/harness/cli/oracle-command.md +113 -0
  246. package/docs/harness/cli/planning-command.md +95 -0
  247. package/docs/harness/cli/schema-and-validation-commands.md +154 -0
  248. package/docs/harness/cli/search-commands.md +97 -0
  249. package/docs/harness/cli/spawn-command.md +136 -0
  250. package/docs/harness/cli/specs-command.md +102 -0
  251. package/docs/harness/cli/tools-command.md +122 -0
  252. package/docs/harness/cli/trace-command.md +122 -0
  253. package/docs/harness/cli-daemon.md +92 -0
  254. package/docs/harness/event-loop.md +184 -0
  255. package/docs/harness/hooks/README.md +15 -0
  256. package/docs/harness/hooks/context-hooks.md +96 -0
  257. package/docs/harness/hooks/lifecycle-and-observability-hooks.md +135 -0
  258. package/docs/harness/hooks/validation-hooks.md +97 -0
  259. package/docs/harness/test-harness.md +149 -0
  260. package/docs/harness/tui.md +176 -0
  261. package/docs/memories.md +20 -0
  262. package/docs/solutions/agentic-issues/premature-agent-deletion-tui-action-dependency-20260130.md +49 -0
  263. package/docs/solutions/agentic-issues/ref-anchor-scope-mismatch-skill-references-20260131.md +55 -0
  264. package/docs/solutions/agentic-issues/tautological-tests-routing-20260131.md +52 -0
  265. package/docs/solutions/integration_issue/blocktool-output-format-mismatch-hook-runner-20260130.md +52 -0
  266. package/docs/solutions/integration_issue/dual-validation-path-divergence-schema-20260130.md +66 -0
  267. package/docs/solutions/security-issues/unsanitized-domain-path-join-20260131.md +52 -0
  268. package/docs/solutions/test-failures/event-loop-mock-ordering-checkAgentWindows-20260130.md +63 -0
  269. package/docs/sync-cli/README.md +19 -0
  270. package/docs/sync-cli/cli-entrypoint-and-commands.md +39 -0
  271. package/docs/sync-cli/commands/README.md +11 -0
  272. package/docs/sync-cli/commands/pull-manifest-command.md +36 -0
  273. package/docs/sync-cli/commands/push-command.md +84 -0
  274. package/docs/sync-cli/commands/sync-command.md +71 -0
  275. package/docs/sync-cli/systems/README.md +14 -0
  276. package/docs/sync-cli/systems/git-and-github-integration.md +49 -0
  277. package/docs/sync-cli/systems/interactive-ui.md +43 -0
  278. package/docs/sync-cli/systems/manifest-and-distribution.md +51 -0
  279. package/docs/sync-cli/systems/path-resolution.md +42 -0
  280. package/package.json +46 -0
  281. package/scripts/install-shim.sh +40 -0
  282. package/scripts/pre-pack.sh +25 -0
  283. package/specs/harness-maintenance-skill.spec.md +138 -0
  284. package/specs/roadmap/git-spec-lifecycle-management.spec.md +113 -0
  285. package/specs/sync-init-flag.spec.md +117 -0
  286. package/specs/unified-workflow-orchestration.spec.md +250 -0
  287. package/specs/validation-tooling-practice.spec.md +98 -0
  288. package/specs/workflow-domain-configuration.spec.md +265 -0
  289. package/src/commands/pull-manifest.ts +31 -0
  290. package/src/commands/push.ts +344 -0
  291. package/src/commands/sync.ts +289 -0
  292. package/src/lib/constants.ts +10 -0
  293. package/src/lib/dotfiles.ts +36 -0
  294. package/src/lib/fs-utils.ts +18 -0
  295. package/src/lib/gh.ts +40 -0
  296. package/src/lib/git.ts +63 -0
  297. package/src/lib/gitignore.ts +167 -0
  298. package/src/lib/manifest.ts +121 -0
  299. package/src/lib/marker-sync.ts +39 -0
  300. package/src/lib/paths.ts +38 -0
  301. package/src/lib/target-lines.ts +66 -0
  302. package/src/lib/ui.ts +78 -0
  303. package/src/sync-cli.ts +120 -0
  304. package/target-lines.json +23 -0
  305. package/tsconfig.json +20 -0
@@ -0,0 +1,497 @@
1
+ /**
2
+ * Ctags utilities for symbol lookup and validation.
3
+ *
4
+ * Uses universal-ctags to generate a symbol index for fast O(1) lookups.
5
+ * This enables documentation reference validation without AST parsing.
6
+ */
7
+
8
+ import { spawn, spawnSync } from "child_process";
9
+ import { existsSync } from "fs";
10
+
11
+ /**
12
+ * Single ctags entry representing a symbol in a file.
13
+ */
14
+ export interface CtagsEntry {
15
+ name: string;
16
+ path: string;
17
+ line: number;
18
+ kind: string;
19
+ signature?: string;
20
+ }
21
+
22
+ /**
23
+ * Indexed ctags data for O(1) lookups.
24
+ * Structure: Map<file, Map<symbol, CtagsEntry[]>>
25
+ * Multiple entries per symbol possible (overloads, same name in different scopes).
26
+ */
27
+ export type CtagsIndex = Map<string, Map<string, CtagsEntry[]>>;
28
+
29
+ /**
30
+ * Common paths where universal-ctags might be installed.
31
+ * Checked in order before falling back to PATH.
32
+ */
33
+ const CTAGS_PATHS = [
34
+ "/opt/homebrew/bin/ctags", // macOS ARM homebrew
35
+ "/usr/local/bin/ctags", // macOS Intel homebrew / Linux
36
+ "/usr/bin/ctags", // System PATH (may be BSD ctags)
37
+ "ctags", // Fall back to PATH
38
+ ];
39
+
40
+ /** Cached path to universal-ctags binary */
41
+ let cachedCtagsPath: string | null = null;
42
+
43
+ /**
44
+ * Find the universal-ctags binary, checking common paths first.
45
+ */
46
+ function findUniversalCtags(): { path: string; version: string } | null {
47
+ for (const ctagsPath of CTAGS_PATHS) {
48
+ // Skip absolute paths that don't exist
49
+ if (ctagsPath.startsWith("/") && !existsSync(ctagsPath)) {
50
+ continue;
51
+ }
52
+
53
+ const result = spawnSync(ctagsPath, ["--version"], { encoding: "utf-8" });
54
+
55
+ if (result.status !== 0) {
56
+ continue;
57
+ }
58
+
59
+ const output = result.stdout || "";
60
+ if (output.includes("Universal Ctags") || output.includes("universal-ctags")) {
61
+ const versionMatch = output.match(/Universal Ctags\s+([\d.]+)/i);
62
+ const version = versionMatch ? versionMatch[1] : "unknown";
63
+ return { path: ctagsPath, version };
64
+ }
65
+ }
66
+
67
+ return null;
68
+ }
69
+
70
+ /**
71
+ * Get the path to universal-ctags (cached).
72
+ */
73
+ export function getCtagsPath(): string | null {
74
+ if (cachedCtagsPath !== null) {
75
+ return cachedCtagsPath;
76
+ }
77
+
78
+ const found = findUniversalCtags();
79
+ if (found) {
80
+ cachedCtagsPath = found.path;
81
+ return cachedCtagsPath;
82
+ }
83
+
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * Check if ctags (universal-ctags) is available.
89
+ */
90
+ export function checkCtagsAvailable(): { available: boolean; version?: string; error?: string; path?: string } {
91
+ const found = findUniversalCtags();
92
+
93
+ if (!found) {
94
+ return {
95
+ available: false,
96
+ error:
97
+ "Universal Ctags not found. Install with: brew install universal-ctags\n" +
98
+ "Note: The BSD ctags that ships with macOS is not compatible.",
99
+ };
100
+ }
101
+
102
+ cachedCtagsPath = found.path;
103
+
104
+ return {
105
+ available: true,
106
+ version: found.version,
107
+ path: found.path,
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Parse a single line of ctags JSON output.
113
+ */
114
+ function parseCtagsLine(line: string): CtagsEntry | null {
115
+ try {
116
+ const entry = JSON.parse(line);
117
+
118
+ // Skip ptag entries (pseudo-tags with metadata)
119
+ if (entry._type === "ptag") {
120
+ return null;
121
+ }
122
+
123
+ if (entry._type !== "tag" || !entry.name || !entry.path) {
124
+ return null;
125
+ }
126
+
127
+ return {
128
+ name: entry.name,
129
+ path: entry.path,
130
+ line: entry.line || 0,
131
+ kind: entry.kind || "unknown",
132
+ signature: entry.signature,
133
+ };
134
+ } catch {
135
+ return null;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Generate a ctags index for a directory.
141
+ *
142
+ * Uses ctags with JSON output format for reliable parsing.
143
+ * Excludes common non-source directories.
144
+ */
145
+ export function generateCtagsIndex(
146
+ cwd: string,
147
+ options?: {
148
+ /** Additional exclude patterns */
149
+ exclude?: string[];
150
+ /** Specific file or directory to index (relative to cwd) */
151
+ target?: string;
152
+ }
153
+ ): { index: CtagsIndex; success: boolean; error?: string; entryCount: number } {
154
+ const check = checkCtagsAvailable();
155
+ if (!check.available) {
156
+ return {
157
+ index: new Map(),
158
+ success: false,
159
+ error: check.error,
160
+ entryCount: 0,
161
+ };
162
+ }
163
+
164
+ const excludes = [
165
+ "node_modules",
166
+ ".git",
167
+ "dist",
168
+ "build",
169
+ ".next",
170
+ "coverage",
171
+ "__pycache__",
172
+ ".venv",
173
+ "venv",
174
+ ...(options?.exclude || []),
175
+ ];
176
+
177
+ const args = [
178
+ "-R",
179
+ "--output-format=json",
180
+ "--fields=+nKS", // +n=line number, +K=kind, +S=signature
181
+ "-o",
182
+ "-", // Output to stdout
183
+ ];
184
+
185
+ // Add exclude patterns
186
+ for (const exclude of excludes) {
187
+ args.push(`--exclude=${exclude}`);
188
+ }
189
+
190
+ // Add target if specified, otherwise index current directory
191
+ const target = options?.target || ".";
192
+ args.push(target);
193
+
194
+ const ctagsPath = getCtagsPath()!;
195
+ const result = spawnSync(ctagsPath, args, {
196
+ encoding: "utf-8",
197
+ cwd,
198
+ maxBuffer: 50 * 1024 * 1024, // 50MB buffer for large repos
199
+ });
200
+
201
+ if (result.status !== 0) {
202
+ return {
203
+ index: new Map(),
204
+ success: false,
205
+ error: `ctags failed: ${result.stderr || "unknown error"}`,
206
+ entryCount: 0,
207
+ };
208
+ }
209
+
210
+ // Parse JSON output into index
211
+ const index: CtagsIndex = new Map();
212
+ let entryCount = 0;
213
+
214
+ const lines = result.stdout.split("\n");
215
+ for (const line of lines) {
216
+ if (!line.trim()) continue;
217
+
218
+ const entry = parseCtagsLine(line);
219
+ if (!entry) continue;
220
+
221
+ entryCount++;
222
+
223
+ // Get or create file map
224
+ let fileMap = index.get(entry.path);
225
+ if (!fileMap) {
226
+ fileMap = new Map();
227
+ index.set(entry.path, fileMap);
228
+ }
229
+
230
+ // Get or create symbol array
231
+ let symbols = fileMap.get(entry.name);
232
+ if (!symbols) {
233
+ symbols = [];
234
+ fileMap.set(entry.name, symbols);
235
+ }
236
+
237
+ symbols.push(entry);
238
+ }
239
+
240
+ return { index, success: true, entryCount };
241
+ }
242
+
243
+ /**
244
+ * Look up a symbol in a specific file.
245
+ *
246
+ * Returns all matching entries (may have multiple for overloads).
247
+ */
248
+ export function lookupSymbol(
249
+ index: CtagsIndex,
250
+ filePath: string,
251
+ symbolName: string
252
+ ): CtagsEntry[] {
253
+ const fileMap = index.get(filePath);
254
+ if (!fileMap) {
255
+ return [];
256
+ }
257
+
258
+ return fileMap.get(symbolName) || [];
259
+ }
260
+
261
+ /**
262
+ * Look up a symbol in any file (for searching).
263
+ *
264
+ * Returns all matching entries across all files.
265
+ */
266
+ export function searchSymbol(
267
+ index: CtagsIndex,
268
+ symbolName: string
269
+ ): Array<CtagsEntry & { file: string }> {
270
+ const results: Array<CtagsEntry & { file: string }> = [];
271
+
272
+ for (const [file, fileMap] of index) {
273
+ const entries = fileMap.get(symbolName);
274
+ if (entries) {
275
+ for (const entry of entries) {
276
+ results.push({ ...entry, file });
277
+ }
278
+ }
279
+ }
280
+
281
+ return results;
282
+ }
283
+
284
+ /**
285
+ * Get all symbols in a file.
286
+ */
287
+ export function getFileSymbols(index: CtagsIndex, filePath: string): CtagsEntry[] {
288
+ const fileMap = index.get(filePath);
289
+ if (!fileMap) {
290
+ return [];
291
+ }
292
+
293
+ const symbols: CtagsEntry[] = [];
294
+ for (const entries of fileMap.values()) {
295
+ symbols.push(...entries);
296
+ }
297
+
298
+ // Sort by line number
299
+ return symbols.sort((a, b) => a.line - b.line);
300
+ }
301
+
302
+ /**
303
+ * Generate ctags for a single file (faster for format-reference command).
304
+ */
305
+ export function generateFileCtags(
306
+ filePath: string,
307
+ cwd: string
308
+ ): { entries: CtagsEntry[]; success: boolean; error?: string } {
309
+ if (!existsSync(filePath)) {
310
+ return { entries: [], success: false, error: "File not found" };
311
+ }
312
+
313
+ const check = checkCtagsAvailable();
314
+ if (!check.available) {
315
+ return { entries: [], success: false, error: check.error };
316
+ }
317
+
318
+ const args = [
319
+ "--output-format=json",
320
+ "--fields=+nKS",
321
+ "-o",
322
+ "-",
323
+ filePath,
324
+ ];
325
+
326
+ const ctagsPath = getCtagsPath()!;
327
+ const result = spawnSync(ctagsPath, args, {
328
+ encoding: "utf-8",
329
+ cwd,
330
+ maxBuffer: 10 * 1024 * 1024, // 10MB buffer
331
+ });
332
+
333
+ if (result.status !== 0) {
334
+ return {
335
+ entries: [],
336
+ success: false,
337
+ error: `ctags failed: ${result.stderr || "unknown error"}`,
338
+ };
339
+ }
340
+
341
+ const entries: CtagsEntry[] = [];
342
+ const lines = result.stdout.split("\n");
343
+
344
+ for (const line of lines) {
345
+ if (!line.trim()) continue;
346
+
347
+ const entry = parseCtagsLine(line);
348
+ if (entry) {
349
+ entries.push(entry);
350
+ }
351
+ }
352
+
353
+ return { entries, success: true };
354
+ }
355
+
356
+ /**
357
+ * Find a specific symbol in a file (convenience function).
358
+ * Uses single-file ctags for efficiency.
359
+ */
360
+ export function findSymbolInFile(
361
+ filePath: string,
362
+ symbolName: string,
363
+ cwd: string
364
+ ): CtagsEntry | null {
365
+ const { entries, success } = generateFileCtags(filePath, cwd);
366
+ if (!success) {
367
+ return null;
368
+ }
369
+
370
+ // Return first match (usually there's only one)
371
+ return entries.find((e) => e.name === symbolName) || null;
372
+ }
373
+
374
+ /**
375
+ * Generate a ctags index asynchronously (non-blocking).
376
+ *
377
+ * Same behavior as generateCtagsIndex but uses spawn instead of spawnSync,
378
+ * keeping the event loop free during the ctags process.
379
+ */
380
+ export async function generateCtagsIndexAsync(
381
+ cwd: string,
382
+ options?: {
383
+ /** Additional exclude patterns */
384
+ exclude?: string[];
385
+ /** Specific file or directory to index (relative to cwd) */
386
+ target?: string;
387
+ }
388
+ ): Promise<{ index: CtagsIndex; success: boolean; error?: string; entryCount: number }> {
389
+ const check = checkCtagsAvailable();
390
+ if (!check.available) {
391
+ return {
392
+ index: new Map(),
393
+ success: false,
394
+ error: check.error,
395
+ entryCount: 0,
396
+ };
397
+ }
398
+
399
+ const excludes = [
400
+ "node_modules",
401
+ ".git",
402
+ "dist",
403
+ "build",
404
+ ".next",
405
+ "coverage",
406
+ "__pycache__",
407
+ ".venv",
408
+ "venv",
409
+ ...(options?.exclude || []),
410
+ ];
411
+
412
+ const args = [
413
+ "-R",
414
+ "--output-format=json",
415
+ "--fields=+nKS",
416
+ "-o",
417
+ "-",
418
+ ];
419
+
420
+ for (const exclude of excludes) {
421
+ args.push(`--exclude=${exclude}`);
422
+ }
423
+
424
+ const target = options?.target || ".";
425
+ args.push(target);
426
+
427
+ const ctagsPath = getCtagsPath()!;
428
+
429
+ return new Promise((resolve) => {
430
+ const child = spawn(ctagsPath, args, {
431
+ cwd,
432
+ stdio: ["pipe", "pipe", "pipe"],
433
+ });
434
+
435
+ const chunks: Buffer[] = [];
436
+ let stderrOutput = "";
437
+
438
+ child.stdout?.on("data", (data: Buffer) => {
439
+ chunks.push(data);
440
+ });
441
+
442
+ child.stderr?.on("data", (data: Buffer) => {
443
+ stderrOutput += data.toString();
444
+ });
445
+
446
+ child.on("close", (code) => {
447
+ if (code !== 0) {
448
+ resolve({
449
+ index: new Map(),
450
+ success: false,
451
+ error: `ctags failed: ${stderrOutput || "unknown error"}`,
452
+ entryCount: 0,
453
+ });
454
+ return;
455
+ }
456
+
457
+ const stdout = Buffer.concat(chunks).toString("utf-8");
458
+ const index: CtagsIndex = new Map();
459
+ let entryCount = 0;
460
+
461
+ const lines = stdout.split("\n");
462
+ for (const line of lines) {
463
+ if (!line.trim()) continue;
464
+
465
+ const entry = parseCtagsLine(line);
466
+ if (!entry) continue;
467
+
468
+ entryCount++;
469
+
470
+ let fileMap = index.get(entry.path);
471
+ if (!fileMap) {
472
+ fileMap = new Map();
473
+ index.set(entry.path, fileMap);
474
+ }
475
+
476
+ let symbols = fileMap.get(entry.name);
477
+ if (!symbols) {
478
+ symbols = [];
479
+ fileMap.set(entry.name, symbols);
480
+ }
481
+
482
+ symbols.push(entry);
483
+ }
484
+
485
+ resolve({ index, success: true, entryCount });
486
+ });
487
+
488
+ child.on("error", (err) => {
489
+ resolve({
490
+ index: new Map(),
491
+ success: false,
492
+ error: `ctags spawn error: ${err.message}`,
493
+ entryCount: 0,
494
+ });
495
+ });
496
+ });
497
+ }