forge-server 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 (412) hide show
  1. package/.claude/hooks/worktree-create.sh +64 -0
  2. package/.claude/hooks/worktree-remove.sh +57 -0
  3. package/.claude/settings.local.json +29 -0
  4. package/.forge/knowledge/conventions.yaml +1 -0
  5. package/.forge/knowledge/decisions.yaml +1 -0
  6. package/.forge/knowledge/gotchas.yaml +1 -0
  7. package/.forge/knowledge/patterns.yaml +1 -0
  8. package/.forge/manifest.yaml +6 -0
  9. package/CLAUDE.md +144 -0
  10. package/bin/setup-forge.sh +132 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +553 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/context/codebase.d.ts +57 -0
  16. package/dist/context/codebase.d.ts.map +1 -0
  17. package/dist/context/codebase.js +301 -0
  18. package/dist/context/codebase.js.map +1 -0
  19. package/dist/context/injector.d.ts +147 -0
  20. package/dist/context/injector.d.ts.map +1 -0
  21. package/dist/context/injector.js +533 -0
  22. package/dist/context/injector.js.map +1 -0
  23. package/dist/context/memory.d.ts +32 -0
  24. package/dist/context/memory.d.ts.map +1 -0
  25. package/dist/context/memory.js +140 -0
  26. package/dist/context/memory.js.map +1 -0
  27. package/dist/context/session-index.d.ts +54 -0
  28. package/dist/context/session-index.d.ts.map +1 -0
  29. package/dist/context/session-index.js +265 -0
  30. package/dist/context/session-index.js.map +1 -0
  31. package/dist/context/session.d.ts +42 -0
  32. package/dist/context/session.d.ts.map +1 -0
  33. package/dist/context/session.js +121 -0
  34. package/dist/context/session.js.map +1 -0
  35. package/dist/index.d.ts +3 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +37 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/ingestion/chunker.d.ts +19 -0
  40. package/dist/ingestion/chunker.d.ts.map +1 -0
  41. package/dist/ingestion/chunker.js +189 -0
  42. package/dist/ingestion/chunker.js.map +1 -0
  43. package/dist/ingestion/embedder.d.ts +45 -0
  44. package/dist/ingestion/embedder.d.ts.map +1 -0
  45. package/dist/ingestion/embedder.js +152 -0
  46. package/dist/ingestion/embedder.js.map +1 -0
  47. package/dist/ingestion/git-analyzer.d.ts +77 -0
  48. package/dist/ingestion/git-analyzer.d.ts.map +1 -0
  49. package/dist/ingestion/git-analyzer.js +437 -0
  50. package/dist/ingestion/git-analyzer.js.map +1 -0
  51. package/dist/ingestion/indexer.d.ts +79 -0
  52. package/dist/ingestion/indexer.d.ts.map +1 -0
  53. package/dist/ingestion/indexer.js +766 -0
  54. package/dist/ingestion/indexer.js.map +1 -0
  55. package/dist/ingestion/markdown-chunker.d.ts +19 -0
  56. package/dist/ingestion/markdown-chunker.d.ts.map +1 -0
  57. package/dist/ingestion/markdown-chunker.js +243 -0
  58. package/dist/ingestion/markdown-chunker.js.map +1 -0
  59. package/dist/ingestion/markdown-knowledge.d.ts +21 -0
  60. package/dist/ingestion/markdown-knowledge.d.ts.map +1 -0
  61. package/dist/ingestion/markdown-knowledge.js +129 -0
  62. package/dist/ingestion/markdown-knowledge.js.map +1 -0
  63. package/dist/ingestion/parser.d.ts +20 -0
  64. package/dist/ingestion/parser.d.ts.map +1 -0
  65. package/dist/ingestion/parser.js +429 -0
  66. package/dist/ingestion/parser.js.map +1 -0
  67. package/dist/ingestion/watcher.d.ts +28 -0
  68. package/dist/ingestion/watcher.d.ts.map +1 -0
  69. package/dist/ingestion/watcher.js +147 -0
  70. package/dist/ingestion/watcher.js.map +1 -0
  71. package/dist/knowledge/hydrator.d.ts +37 -0
  72. package/dist/knowledge/hydrator.d.ts.map +1 -0
  73. package/dist/knowledge/hydrator.js +220 -0
  74. package/dist/knowledge/hydrator.js.map +1 -0
  75. package/dist/knowledge/registry.d.ts +129 -0
  76. package/dist/knowledge/registry.d.ts.map +1 -0
  77. package/dist/knowledge/registry.js +361 -0
  78. package/dist/knowledge/registry.js.map +1 -0
  79. package/dist/knowledge/search.d.ts +114 -0
  80. package/dist/knowledge/search.d.ts.map +1 -0
  81. package/dist/knowledge/search.js +428 -0
  82. package/dist/knowledge/search.js.map +1 -0
  83. package/dist/knowledge/store.d.ts +76 -0
  84. package/dist/knowledge/store.d.ts.map +1 -0
  85. package/dist/knowledge/store.js +230 -0
  86. package/dist/knowledge/store.js.map +1 -0
  87. package/dist/learning/confidence.d.ts +30 -0
  88. package/dist/learning/confidence.d.ts.map +1 -0
  89. package/dist/learning/confidence.js +165 -0
  90. package/dist/learning/confidence.js.map +1 -0
  91. package/dist/learning/patterns.d.ts +52 -0
  92. package/dist/learning/patterns.d.ts.map +1 -0
  93. package/dist/learning/patterns.js +290 -0
  94. package/dist/learning/patterns.js.map +1 -0
  95. package/dist/learning/trajectory.d.ts +55 -0
  96. package/dist/learning/trajectory.d.ts.map +1 -0
  97. package/dist/learning/trajectory.js +200 -0
  98. package/dist/learning/trajectory.js.map +1 -0
  99. package/dist/memory/memory-compat.d.ts +100 -0
  100. package/dist/memory/memory-compat.d.ts.map +1 -0
  101. package/dist/memory/memory-compat.js +146 -0
  102. package/dist/memory/memory-compat.js.map +1 -0
  103. package/dist/memory/observation-store.d.ts +57 -0
  104. package/dist/memory/observation-store.d.ts.map +1 -0
  105. package/dist/memory/observation-store.js +154 -0
  106. package/dist/memory/observation-store.js.map +1 -0
  107. package/dist/memory/session-tracker.d.ts +81 -0
  108. package/dist/memory/session-tracker.d.ts.map +1 -0
  109. package/dist/memory/session-tracker.js +262 -0
  110. package/dist/memory/session-tracker.js.map +1 -0
  111. package/dist/pipeline/engine.d.ts +179 -0
  112. package/dist/pipeline/engine.d.ts.map +1 -0
  113. package/dist/pipeline/engine.js +691 -0
  114. package/dist/pipeline/engine.js.map +1 -0
  115. package/dist/pipeline/events.d.ts +54 -0
  116. package/dist/pipeline/events.d.ts.map +1 -0
  117. package/dist/pipeline/events.js +157 -0
  118. package/dist/pipeline/events.js.map +1 -0
  119. package/dist/pipeline/parallel.d.ts +83 -0
  120. package/dist/pipeline/parallel.d.ts.map +1 -0
  121. package/dist/pipeline/parallel.js +277 -0
  122. package/dist/pipeline/parallel.js.map +1 -0
  123. package/dist/pipeline/state-machine.d.ts +65 -0
  124. package/dist/pipeline/state-machine.d.ts.map +1 -0
  125. package/dist/pipeline/state-machine.js +176 -0
  126. package/dist/pipeline/state-machine.js.map +1 -0
  127. package/dist/query/graph-queries.d.ts +84 -0
  128. package/dist/query/graph-queries.d.ts.map +1 -0
  129. package/dist/query/graph-queries.js +216 -0
  130. package/dist/query/graph-queries.js.map +1 -0
  131. package/dist/query/hybrid-search.d.ts +34 -0
  132. package/dist/query/hybrid-search.d.ts.map +1 -0
  133. package/dist/query/hybrid-search.js +263 -0
  134. package/dist/query/hybrid-search.js.map +1 -0
  135. package/dist/query/intent-detector.d.ts +35 -0
  136. package/dist/query/intent-detector.d.ts.map +1 -0
  137. package/dist/query/intent-detector.js +115 -0
  138. package/dist/query/intent-detector.js.map +1 -0
  139. package/dist/query/ranking.d.ts +57 -0
  140. package/dist/query/ranking.d.ts.map +1 -0
  141. package/dist/query/ranking.js +109 -0
  142. package/dist/query/ranking.js.map +1 -0
  143. package/dist/server.d.ts +3 -0
  144. package/dist/server.d.ts.map +1 -0
  145. package/dist/server.js +291 -0
  146. package/dist/server.js.map +1 -0
  147. package/dist/storage/falkordb-store.d.ts +73 -0
  148. package/dist/storage/falkordb-store.d.ts.map +1 -0
  149. package/dist/storage/falkordb-store.js +346 -0
  150. package/dist/storage/falkordb-store.js.map +1 -0
  151. package/dist/storage/file-cache.d.ts +32 -0
  152. package/dist/storage/file-cache.d.ts.map +1 -0
  153. package/dist/storage/file-cache.js +115 -0
  154. package/dist/storage/file-cache.js.map +1 -0
  155. package/dist/storage/interfaces.d.ts +151 -0
  156. package/dist/storage/interfaces.d.ts.map +1 -0
  157. package/dist/storage/interfaces.js +7 -0
  158. package/dist/storage/interfaces.js.map +1 -0
  159. package/dist/storage/qdrant-store.d.ts +110 -0
  160. package/dist/storage/qdrant-store.d.ts.map +1 -0
  161. package/dist/storage/qdrant-store.js +467 -0
  162. package/dist/storage/qdrant-store.js.map +1 -0
  163. package/dist/storage/schema.d.ts +4 -0
  164. package/dist/storage/schema.d.ts.map +1 -0
  165. package/dist/storage/schema.js +136 -0
  166. package/dist/storage/schema.js.map +1 -0
  167. package/dist/storage/sqlite.d.ts +35 -0
  168. package/dist/storage/sqlite.d.ts.map +1 -0
  169. package/dist/storage/sqlite.js +132 -0
  170. package/dist/storage/sqlite.js.map +1 -0
  171. package/dist/tools/collaboration-tools.d.ts +111 -0
  172. package/dist/tools/collaboration-tools.d.ts.map +1 -0
  173. package/dist/tools/collaboration-tools.js +174 -0
  174. package/dist/tools/collaboration-tools.js.map +1 -0
  175. package/dist/tools/context-tools.d.ts +293 -0
  176. package/dist/tools/context-tools.d.ts.map +1 -0
  177. package/dist/tools/context-tools.js +437 -0
  178. package/dist/tools/context-tools.js.map +1 -0
  179. package/dist/tools/graph-tools.d.ts +129 -0
  180. package/dist/tools/graph-tools.d.ts.map +1 -0
  181. package/dist/tools/graph-tools.js +237 -0
  182. package/dist/tools/graph-tools.js.map +1 -0
  183. package/dist/tools/ingestion-tools.d.ts +96 -0
  184. package/dist/tools/ingestion-tools.d.ts.map +1 -0
  185. package/dist/tools/ingestion-tools.js +90 -0
  186. package/dist/tools/ingestion-tools.js.map +1 -0
  187. package/dist/tools/learning-tools.d.ts +168 -0
  188. package/dist/tools/learning-tools.d.ts.map +1 -0
  189. package/dist/tools/learning-tools.js +158 -0
  190. package/dist/tools/learning-tools.js.map +1 -0
  191. package/dist/tools/memory-tools.d.ts +183 -0
  192. package/dist/tools/memory-tools.d.ts.map +1 -0
  193. package/dist/tools/memory-tools.js +197 -0
  194. package/dist/tools/memory-tools.js.map +1 -0
  195. package/dist/tools/phase-tools.d.ts +954 -0
  196. package/dist/tools/phase-tools.d.ts.map +1 -0
  197. package/dist/tools/phase-tools.js +1215 -0
  198. package/dist/tools/phase-tools.js.map +1 -0
  199. package/dist/tools/pipeline-tools.d.ts +140 -0
  200. package/dist/tools/pipeline-tools.d.ts.map +1 -0
  201. package/dist/tools/pipeline-tools.js +162 -0
  202. package/dist/tools/pipeline-tools.js.map +1 -0
  203. package/dist/tools/registration-tools.d.ts +220 -0
  204. package/dist/tools/registration-tools.d.ts.map +1 -0
  205. package/dist/tools/registration-tools.js +391 -0
  206. package/dist/tools/registration-tools.js.map +1 -0
  207. package/dist/util/circuit-breaker.d.ts +75 -0
  208. package/dist/util/circuit-breaker.d.ts.map +1 -0
  209. package/dist/util/circuit-breaker.js +159 -0
  210. package/dist/util/circuit-breaker.js.map +1 -0
  211. package/dist/util/config.d.ts +23 -0
  212. package/dist/util/config.d.ts.map +1 -0
  213. package/dist/util/config.js +164 -0
  214. package/dist/util/config.js.map +1 -0
  215. package/dist/util/logger.d.ts +13 -0
  216. package/dist/util/logger.d.ts.map +1 -0
  217. package/dist/util/logger.js +45 -0
  218. package/dist/util/logger.js.map +1 -0
  219. package/dist/util/token-counter.d.ts +24 -0
  220. package/dist/util/token-counter.d.ts.map +1 -0
  221. package/dist/util/token-counter.js +48 -0
  222. package/dist/util/token-counter.js.map +1 -0
  223. package/dist/util/types.d.ts +525 -0
  224. package/dist/util/types.d.ts.map +1 -0
  225. package/dist/util/types.js +5 -0
  226. package/dist/util/types.js.map +1 -0
  227. package/docker-compose.yml +20 -0
  228. package/docs/plans/2026-02-27-swarm-coordination/architecture.md +203 -0
  229. package/docs/plans/2026-02-27-swarm-coordination/vision.md +57 -0
  230. package/docs/plans/completed/2026-02-26-forge-plugin-bundling/architecture.md +1 -0
  231. package/docs/plans/completed/2026-02-26-forge-plugin-bundling/vision.md +300 -0
  232. package/docs/plans/completed/2026-02-27-forge-swarm-learning/architecture.md +480 -0
  233. package/docs/plans/completed/2026-02-27-forge-swarm-learning/verification-checklist.md +462 -0
  234. package/docs/plans/completed/2026-02-27-git-history-atlassian/git-jira-plan.md +181 -0
  235. package/package.json +39 -0
  236. package/plugin/.claude-plugin/plugin.json +8 -0
  237. package/plugin/.mcp.json +15 -0
  238. package/plugin/README.md +134 -0
  239. package/plugin/agents/architect.md +367 -0
  240. package/plugin/agents/backend-specialist.md +263 -0
  241. package/plugin/agents/brainstormer.md +122 -0
  242. package/plugin/agents/data-specialist.md +266 -0
  243. package/plugin/agents/designer.md +408 -0
  244. package/plugin/agents/frontend-specialist.md +241 -0
  245. package/plugin/agents/inspector.md +406 -0
  246. package/plugin/agents/knowledge-keeper.md +443 -0
  247. package/plugin/agents/platform-engineer.md +326 -0
  248. package/plugin/agents/product-manager.md +268 -0
  249. package/plugin/agents/product-owner.md +438 -0
  250. package/plugin/agents/pulse-checker.md +73 -0
  251. package/plugin/agents/qa-strategist.md +500 -0
  252. package/plugin/agents/self-improver.md +310 -0
  253. package/plugin/agents/strategist.md +360 -0
  254. package/plugin/agents/supervisor.md +380 -0
  255. package/plugin/commands/brainstorm.md +25 -0
  256. package/plugin/commands/forge.md +88 -0
  257. package/plugin/docs/atlassian-integration.md +110 -0
  258. package/plugin/docs/workflow.md +126 -0
  259. package/plugin/skills/agent-development/.skillfish.json +10 -0
  260. package/plugin/skills/agent-development/SKILL.md +415 -0
  261. package/plugin/skills/agent-development/examples/agent-creation-prompt.md +238 -0
  262. package/plugin/skills/agent-development/examples/complete-agent-examples.md +427 -0
  263. package/plugin/skills/agent-development/references/agent-creation-system-prompt.md +207 -0
  264. package/plugin/skills/agent-development/references/system-prompt-design.md +411 -0
  265. package/plugin/skills/agent-development/references/triggering-examples.md +491 -0
  266. package/plugin/skills/agent-development/scripts/validate-agent.sh +217 -0
  267. package/plugin/skills/agent-handoff/SKILL.md +335 -0
  268. package/plugin/skills/anti-stub/SKILL.md +317 -0
  269. package/plugin/skills/brainstorm/SKILL.md +31 -0
  270. package/plugin/skills/debugging/SKILL.md +276 -0
  271. package/plugin/skills/fix/SKILL.md +62 -0
  272. package/plugin/skills/frontend-design/.skillfish.json +10 -0
  273. package/plugin/skills/frontend-design/SKILL.md +42 -0
  274. package/plugin/skills/gotchas/SKILL.md +61 -0
  275. package/plugin/skills/graph-orchestrator/SKILL.md +38 -0
  276. package/plugin/skills/history/SKILL.md +58 -0
  277. package/plugin/skills/impact/SKILL.md +59 -0
  278. package/plugin/skills/implementation-execution/SKILL.md +291 -0
  279. package/plugin/skills/index-repo/SKILL.md +55 -0
  280. package/plugin/skills/interviewing/SKILL.md +225 -0
  281. package/plugin/skills/knowledge-curation/SKILL.md +393 -0
  282. package/plugin/skills/learn/SKILL.md +69 -0
  283. package/plugin/skills/mcp-integration/.skillfish.json +10 -0
  284. package/plugin/skills/mcp-integration/SKILL.md +554 -0
  285. package/plugin/skills/mcp-integration/examples/http-server.json +20 -0
  286. package/plugin/skills/mcp-integration/examples/sse-server.json +19 -0
  287. package/plugin/skills/mcp-integration/examples/stdio-server.json +26 -0
  288. package/plugin/skills/mcp-integration/references/authentication.md +549 -0
  289. package/plugin/skills/mcp-integration/references/server-types.md +536 -0
  290. package/plugin/skills/mcp-integration/references/tool-usage.md +538 -0
  291. package/plugin/skills/nestjs/.skillfish.json +10 -0
  292. package/plugin/skills/nestjs/SKILL.md +669 -0
  293. package/plugin/skills/nestjs/drizzle-reference.md +1894 -0
  294. package/plugin/skills/nestjs/reference.md +1447 -0
  295. package/plugin/skills/nestjs/workflow-optimization.md +229 -0
  296. package/plugin/skills/parallel-dispatch/SKILL.md +308 -0
  297. package/plugin/skills/project-discovery/SKILL.md +304 -0
  298. package/plugin/skills/search/SKILL.md +56 -0
  299. package/plugin/skills/security-audit/SKILL.md +362 -0
  300. package/plugin/skills/skill-development/.skillfish.json +10 -0
  301. package/plugin/skills/skill-development/SKILL.md +637 -0
  302. package/plugin/skills/skill-development/references/skill-creator-original.md +209 -0
  303. package/plugin/skills/tdd/SKILL.md +273 -0
  304. package/plugin/skills/terminal-presentation/SKILL.md +395 -0
  305. package/plugin/skills/test-strategy/SKILL.md +365 -0
  306. package/plugin/skills/verification-protocol/SKILL.md +256 -0
  307. package/plugin/skills/visual-explainer/CHANGELOG.md +97 -0
  308. package/plugin/skills/visual-explainer/LICENSE +21 -0
  309. package/plugin/skills/visual-explainer/README.md +137 -0
  310. package/plugin/skills/visual-explainer/SKILL.md +352 -0
  311. package/plugin/skills/visual-explainer/banner.png +0 -0
  312. package/plugin/skills/visual-explainer/package.json +11 -0
  313. package/plugin/skills/visual-explainer/prompts/diff-review.md +68 -0
  314. package/plugin/skills/visual-explainer/prompts/fact-check.md +63 -0
  315. package/plugin/skills/visual-explainer/prompts/generate-slides.md +18 -0
  316. package/plugin/skills/visual-explainer/prompts/generate-web-diagram.md +10 -0
  317. package/plugin/skills/visual-explainer/prompts/plan-review.md +86 -0
  318. package/plugin/skills/visual-explainer/prompts/project-recap.md +61 -0
  319. package/plugin/skills/visual-explainer/references/css-patterns.md +1188 -0
  320. package/plugin/skills/visual-explainer/references/libraries.md +470 -0
  321. package/plugin/skills/visual-explainer/references/responsive-nav.md +212 -0
  322. package/plugin/skills/visual-explainer/references/slide-patterns.md +1403 -0
  323. package/plugin/skills/visual-explainer/templates/architecture.html +596 -0
  324. package/plugin/skills/visual-explainer/templates/data-table.html +540 -0
  325. package/plugin/skills/visual-explainer/templates/mermaid-flowchart.html +435 -0
  326. package/plugin/skills/visual-explainer/templates/slide-deck.html +913 -0
  327. package/src/cli.ts +655 -0
  328. package/src/context/.gitkeep +0 -0
  329. package/src/context/codebase.ts +393 -0
  330. package/src/context/injector.ts +797 -0
  331. package/src/context/memory.ts +187 -0
  332. package/src/context/session-index.ts +327 -0
  333. package/src/context/session.ts +152 -0
  334. package/src/index.ts +47 -0
  335. package/src/ingestion/.gitkeep +0 -0
  336. package/src/ingestion/chunker.ts +277 -0
  337. package/src/ingestion/embedder.ts +167 -0
  338. package/src/ingestion/git-analyzer.ts +545 -0
  339. package/src/ingestion/indexer.ts +984 -0
  340. package/src/ingestion/markdown-chunker.ts +337 -0
  341. package/src/ingestion/markdown-knowledge.ts +175 -0
  342. package/src/ingestion/parser.ts +475 -0
  343. package/src/ingestion/watcher.ts +182 -0
  344. package/src/knowledge/.gitkeep +0 -0
  345. package/src/knowledge/hydrator.ts +246 -0
  346. package/src/knowledge/registry.ts +463 -0
  347. package/src/knowledge/search.ts +565 -0
  348. package/src/knowledge/store.ts +262 -0
  349. package/src/learning/.gitkeep +0 -0
  350. package/src/learning/confidence.ts +193 -0
  351. package/src/learning/patterns.ts +360 -0
  352. package/src/learning/trajectory.ts +268 -0
  353. package/src/memory/.gitkeep +0 -0
  354. package/src/memory/memory-compat.ts +233 -0
  355. package/src/memory/observation-store.ts +224 -0
  356. package/src/memory/session-tracker.ts +332 -0
  357. package/src/pipeline/.gitkeep +0 -0
  358. package/src/pipeline/engine.ts +1139 -0
  359. package/src/pipeline/events.ts +253 -0
  360. package/src/pipeline/parallel.ts +394 -0
  361. package/src/pipeline/state-machine.ts +199 -0
  362. package/src/query/.gitkeep +0 -0
  363. package/src/query/graph-queries.ts +262 -0
  364. package/src/query/hybrid-search.ts +337 -0
  365. package/src/query/intent-detector.ts +131 -0
  366. package/src/query/ranking.ts +161 -0
  367. package/src/server.ts +352 -0
  368. package/src/storage/.gitkeep +0 -0
  369. package/src/storage/falkordb-store.ts +388 -0
  370. package/src/storage/file-cache.ts +141 -0
  371. package/src/storage/interfaces.ts +201 -0
  372. package/src/storage/qdrant-store.ts +557 -0
  373. package/src/storage/schema.ts +139 -0
  374. package/src/storage/sqlite.ts +168 -0
  375. package/src/tools/.gitkeep +0 -0
  376. package/src/tools/collaboration-tools.ts +208 -0
  377. package/src/tools/context-tools.ts +493 -0
  378. package/src/tools/graph-tools.ts +295 -0
  379. package/src/tools/ingestion-tools.ts +122 -0
  380. package/src/tools/learning-tools.ts +181 -0
  381. package/src/tools/memory-tools.ts +234 -0
  382. package/src/tools/phase-tools.ts +1452 -0
  383. package/src/tools/pipeline-tools.ts +188 -0
  384. package/src/tools/registration-tools.ts +450 -0
  385. package/src/util/.gitkeep +0 -0
  386. package/src/util/circuit-breaker.ts +193 -0
  387. package/src/util/config.ts +177 -0
  388. package/src/util/logger.ts +53 -0
  389. package/src/util/token-counter.ts +52 -0
  390. package/src/util/types.ts +710 -0
  391. package/tests/context/.gitkeep +0 -0
  392. package/tests/integration/.gitkeep +0 -0
  393. package/tests/knowledge/.gitkeep +0 -0
  394. package/tests/learning/.gitkeep +0 -0
  395. package/tests/pipeline/.gitkeep +0 -0
  396. package/tests/tools/.gitkeep +0 -0
  397. package/tsconfig.json +21 -0
  398. package/vitest.config.ts +10 -0
  399. package/vscode-extension/.vscodeignore +7 -0
  400. package/vscode-extension/README.md +43 -0
  401. package/vscode-extension/out/edge-collector.js +274 -0
  402. package/vscode-extension/out/edge-collector.js.map +1 -0
  403. package/vscode-extension/out/extension.js +264 -0
  404. package/vscode-extension/out/extension.js.map +1 -0
  405. package/vscode-extension/out/forge-client.js +318 -0
  406. package/vscode-extension/out/forge-client.js.map +1 -0
  407. package/vscode-extension/package-lock.json +59 -0
  408. package/vscode-extension/package.json +71 -0
  409. package/vscode-extension/src/edge-collector.ts +320 -0
  410. package/vscode-extension/src/extension.ts +269 -0
  411. package/vscode-extension/src/forge-client.ts +364 -0
  412. package/vscode-extension/tsconfig.json +19 -0
@@ -0,0 +1,475 @@
1
+ /**
2
+ * Tree-sitter AST parser for TypeScript/TSX/JavaScript/JSX (ADR-3).
3
+ * Extracts entities: functions, classes, interfaces, type aliases, exported variables, imports, exports.
4
+ * Falls back to empty result on parse error (error-tolerant).
5
+ */
6
+
7
+ import { readFile } from 'fs/promises';
8
+ import { extname } from 'path';
9
+ import type { ParseResult, ParsedEntity, ParsedImport } from '../util/types.js';
10
+ import { logger } from '../util/logger.js';
11
+
12
+ // tree-sitter imports - native C addon
13
+ // We use dynamic import with fallback to web-tree-sitter (WASM)
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ let Parser: any = null;
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ let TypeScriptGrammar: any = null;
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ let TSXGrammar: any = null;
20
+
21
+ let parserInitialized = false;
22
+ // Eagerly kick off initialization when the module loads.
23
+ // This allows synchronous callers (parseContent) to find the parser ready
24
+ // by the time test fixtures set up via async beforeAll blocks.
25
+ let _initPromiseEager: Promise<boolean> | null = null;
26
+
27
+ // Start loading immediately (non-blocking, fire-and-forget in module scope)
28
+ // The promise is stored so callers can await it if needed.
29
+ _initPromiseEager = (async () => {
30
+ try {
31
+ const treeSitter = await import('tree-sitter');
32
+ Parser = treeSitter.default;
33
+ const tsGrammar = await import('tree-sitter-typescript');
34
+ TypeScriptGrammar = (tsGrammar.default as { typescript: unknown; tsx: unknown }).typescript;
35
+ TSXGrammar = (tsGrammar.default as { typescript: unknown; tsx: unknown }).tsx;
36
+ parserInitialized = true;
37
+ return true;
38
+ } catch {
39
+ parserInitialized = true;
40
+ return false;
41
+ }
42
+ })();
43
+
44
+ export async function initParser(): Promise<boolean> {
45
+ // Reuse the eager init promise if it's still running.
46
+ if (_initPromiseEager) {
47
+ const result = await _initPromiseEager;
48
+ _initPromiseEager = null;
49
+ if (result) {
50
+ logger.info('tree-sitter parser initialized (native)');
51
+ } else {
52
+ logger.warn('tree-sitter native addon failed to load, parser will return empty results', {});
53
+ }
54
+ return result;
55
+ }
56
+ return Parser !== null;
57
+ }
58
+
59
+ /**
60
+ * Determine language from file extension.
61
+ */
62
+ export function getLanguage(filePath: string): string | null {
63
+ const ext = extname(filePath).toLowerCase();
64
+ const map: Record<string, string> = {
65
+ '.ts': 'typescript',
66
+ '.tsx': 'tsx',
67
+ '.js': 'javascript',
68
+ '.jsx': 'tsx',
69
+ '.mts': 'typescript',
70
+ '.cts': 'typescript',
71
+ '.mjs': 'javascript',
72
+ '.cjs': 'javascript',
73
+ };
74
+ return map[ext] ?? null;
75
+ }
76
+
77
+ /**
78
+ * Parse a TypeScript/JavaScript file and extract entities.
79
+ */
80
+ export async function parseFile(filePath: string, repoId: string): Promise<ParseResult> {
81
+ await initParser();
82
+
83
+ const lang = getLanguage(filePath);
84
+ if (!lang) {
85
+ return {
86
+ entities: [],
87
+ imports: [],
88
+ filePath,
89
+ language: 'unknown',
90
+ success: false,
91
+ error: 'Unsupported file extension',
92
+ };
93
+ }
94
+
95
+ let content: string;
96
+ try {
97
+ content = await readFile(filePath, 'utf8');
98
+ } catch (err) {
99
+ return {
100
+ entities: [],
101
+ imports: [],
102
+ filePath,
103
+ language: lang,
104
+ success: false,
105
+ error: `Failed to read file: ${String(err)}`,
106
+ };
107
+ }
108
+
109
+ return parseContent(content, filePath, repoId, lang);
110
+ }
111
+
112
+ /**
113
+ * Parse content string (useful for testing without filesystem).
114
+ */
115
+ export function parseContent(
116
+ content: string,
117
+ filePath: string,
118
+ repoId: string,
119
+ language: string
120
+ ): ParseResult {
121
+ if (!Parser || !TypeScriptGrammar) {
122
+ // Parser not available - return empty but successful result
123
+ return {
124
+ entities: [],
125
+ imports: [],
126
+ filePath,
127
+ language,
128
+ success: false,
129
+ error: 'Parser not initialized (WASM fallback not implemented)',
130
+ };
131
+ }
132
+
133
+ try {
134
+ const parser = new Parser();
135
+
136
+ if (language === 'tsx') {
137
+ parser.setLanguage(TSXGrammar);
138
+ } else {
139
+ parser.setLanguage(TypeScriptGrammar);
140
+ }
141
+
142
+ const tree = parser.parse(content);
143
+ const rootNode = tree.rootNode;
144
+
145
+ const entities: ParsedEntity[] = [];
146
+ const imports: ParsedImport[] = [];
147
+
148
+ // Walk the AST
149
+ walkNode(rootNode, content, filePath, repoId, entities, imports);
150
+
151
+ return {
152
+ entities,
153
+ imports,
154
+ filePath,
155
+ language,
156
+ success: true,
157
+ };
158
+ } catch (err) {
159
+ logger.warn('AST parse error', { filePath, error: String(err) });
160
+ return {
161
+ entities: [],
162
+ imports: [],
163
+ filePath,
164
+ language,
165
+ success: false,
166
+ error: String(err),
167
+ };
168
+ }
169
+ }
170
+
171
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
+ function walkNode(
173
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
174
+ node: any,
175
+ content: string,
176
+ filePath: string,
177
+ repoId: string,
178
+ entities: ParsedEntity[],
179
+ imports: ParsedImport[]
180
+ ): void {
181
+ const type = node.type as string;
182
+
183
+ // Import declarations — tree-sitter-typescript uses `import_statement`
184
+ if (type === 'import_statement' || type === 'import_declaration') {
185
+ const importNode = extractImport(node, content);
186
+ if (importNode) imports.push(importNode);
187
+ return; // Don't recurse into imports
188
+ }
189
+
190
+ // Export statements - check what they export
191
+ // tree-sitter-typescript uses `export_statement`
192
+ if (type === 'export_statement') {
193
+ extractExportedEntities(node, content, filePath, repoId, entities);
194
+ return;
195
+ }
196
+
197
+ // Function declarations at top level
198
+ if (type === 'function_declaration' || type === 'generator_function_declaration') {
199
+ const entity = extractFunction(node, content, filePath, repoId, false);
200
+ if (entity) entities.push(entity);
201
+ return;
202
+ }
203
+
204
+ // Class declarations at top level
205
+ if (type === 'class_declaration') {
206
+ const entity = extractClass(node, content, filePath, repoId, false);
207
+ if (entity) entities.push(entity);
208
+ return;
209
+ }
210
+
211
+ // Interface declarations (TypeScript)
212
+ if (type === 'interface_declaration') {
213
+ const entity = extractInterface(node, content, filePath, repoId, false);
214
+ if (entity) entities.push(entity);
215
+ return;
216
+ }
217
+
218
+ // Type alias declarations (TypeScript)
219
+ if (type === 'type_alias_declaration') {
220
+ const entity = extractTypeAlias(node, content, filePath, repoId, false);
221
+ if (entity) entities.push(entity);
222
+ return;
223
+ }
224
+
225
+ // Lexical declarations (const/let/var) at top level - only exported ones captured in export_statement
226
+ // Variable declarations handled in export context
227
+
228
+ // Recurse into children for top-level nodes only
229
+ // We only care about top-level declarations
230
+ if (
231
+ type === 'program' ||
232
+ type === 'module' ||
233
+ type === 'statement_block'
234
+ ) {
235
+ for (let i = 0; i < node.childCount; i++) {
236
+ walkNode(node.child(i), content, filePath, repoId, entities, imports);
237
+ }
238
+ }
239
+ }
240
+
241
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
242
+ function extractImport(node: any, content: string): ParsedImport | null {
243
+ try {
244
+ // Find the string source (module specifier)
245
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
246
+ let fromPath = '';
247
+ let specifiers = '';
248
+ let isDefault = false;
249
+
250
+ for (let i = 0; i < node.childCount; i++) {
251
+ const child = node.child(i);
252
+ if (child.type === 'string') {
253
+ fromPath = content.slice(child.startIndex + 1, child.endIndex - 1); // strip quotes
254
+ }
255
+ if (child.type === 'import_clause') {
256
+ const clauseText = content.slice(child.startIndex, child.endIndex);
257
+ specifiers = clauseText.trim();
258
+ isDefault = !clauseText.includes('{');
259
+ }
260
+ }
261
+
262
+ if (!fromPath) return null;
263
+
264
+ return { fromPath, specifiers, isDefault };
265
+ } catch {
266
+ return null;
267
+ }
268
+ }
269
+
270
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
271
+ function extractExportedEntities(
272
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
273
+ node: any,
274
+ content: string,
275
+ filePath: string,
276
+ repoId: string,
277
+ entities: ParsedEntity[]
278
+ ): void {
279
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
280
+ let isDefault = false;
281
+
282
+ for (let i = 0; i < node.childCount; i++) {
283
+ const child = node.child(i);
284
+ if (child.type === 'default') {
285
+ isDefault = true;
286
+ continue;
287
+ }
288
+
289
+ if (child.type === 'function_declaration' || child.type === 'generator_function_declaration') {
290
+ const entity = extractFunction(child, content, filePath, repoId, true);
291
+ if (entity) {
292
+ entity.isDefault = isDefault;
293
+ entities.push(entity);
294
+ }
295
+ } else if (child.type === 'class_declaration') {
296
+ const entity = extractClass(child, content, filePath, repoId, true);
297
+ if (entity) {
298
+ entity.isDefault = isDefault;
299
+ entities.push(entity);
300
+ }
301
+ } else if (child.type === 'interface_declaration') {
302
+ const entity = extractInterface(child, content, filePath, repoId, true);
303
+ if (entity) entities.push(entity);
304
+ } else if (child.type === 'type_alias_declaration') {
305
+ const entity = extractTypeAlias(child, content, filePath, repoId, true);
306
+ if (entity) entities.push(entity);
307
+ } else if (child.type === 'lexical_declaration' || child.type === 'variable_declaration') {
308
+ // export const foo = ...
309
+ extractVariables(child, content, filePath, repoId, true, entities);
310
+ }
311
+ }
312
+ }
313
+
314
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
315
+ function extractFunction(node: any, content: string, filePath: string, repoId: string, isExported: boolean): ParsedEntity | null {
316
+ try {
317
+ let name = '';
318
+ let params = '';
319
+ let isAsync = false;
320
+
321
+ for (let i = 0; i < node.childCount; i++) {
322
+ const child = node.child(i);
323
+ if (child.type === 'async') isAsync = true;
324
+ if (child.type === 'identifier') name = content.slice(child.startIndex, child.endIndex);
325
+ if (child.type === 'formal_parameters') {
326
+ params = content.slice(child.startIndex, child.endIndex);
327
+ }
328
+ }
329
+
330
+ if (!name) return null;
331
+
332
+ return {
333
+ name,
334
+ type: 'function',
335
+ filePath,
336
+ repoId,
337
+ startLine: node.startPosition.row + 1,
338
+ endLine: node.endPosition.row + 1,
339
+ isExported,
340
+ isAsync,
341
+ params,
342
+ sourceText: content.slice(node.startIndex, node.endIndex),
343
+ };
344
+ } catch {
345
+ return null;
346
+ }
347
+ }
348
+
349
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
350
+ function extractClass(node: any, content: string, filePath: string, repoId: string, isExported: boolean): ParsedEntity | null {
351
+ try {
352
+ let name = '';
353
+
354
+ for (let i = 0; i < node.childCount; i++) {
355
+ const child = node.child(i);
356
+ if (child.type === 'type_identifier' || child.type === 'identifier') {
357
+ name = content.slice(child.startIndex, child.endIndex);
358
+ break;
359
+ }
360
+ }
361
+
362
+ if (!name) return null;
363
+
364
+ return {
365
+ name,
366
+ type: 'class',
367
+ filePath,
368
+ repoId,
369
+ startLine: node.startPosition.row + 1,
370
+ endLine: node.endPosition.row + 1,
371
+ isExported,
372
+ sourceText: content.slice(node.startIndex, node.endIndex),
373
+ };
374
+ } catch {
375
+ return null;
376
+ }
377
+ }
378
+
379
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
380
+ function extractInterface(node: any, content: string, filePath: string, repoId: string, isExported: boolean): ParsedEntity | null {
381
+ try {
382
+ let name = '';
383
+
384
+ for (let i = 0; i < node.childCount; i++) {
385
+ const child = node.child(i);
386
+ if (child.type === 'type_identifier' || child.type === 'identifier') {
387
+ name = content.slice(child.startIndex, child.endIndex);
388
+ break;
389
+ }
390
+ }
391
+
392
+ if (!name) return null;
393
+
394
+ return {
395
+ name,
396
+ type: 'interface',
397
+ filePath,
398
+ repoId,
399
+ startLine: node.startPosition.row + 1,
400
+ endLine: node.endPosition.row + 1,
401
+ isExported,
402
+ sourceText: content.slice(node.startIndex, node.endIndex),
403
+ };
404
+ } catch {
405
+ return null;
406
+ }
407
+ }
408
+
409
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
410
+ function extractTypeAlias(node: any, content: string, filePath: string, repoId: string, isExported: boolean): ParsedEntity | null {
411
+ try {
412
+ let name = '';
413
+
414
+ for (let i = 0; i < node.childCount; i++) {
415
+ const child = node.child(i);
416
+ if (child.type === 'type_identifier' || child.type === 'identifier') {
417
+ name = content.slice(child.startIndex, child.endIndex);
418
+ break;
419
+ }
420
+ }
421
+
422
+ if (!name) return null;
423
+
424
+ return {
425
+ name,
426
+ type: 'type_alias',
427
+ filePath,
428
+ repoId,
429
+ startLine: node.startPosition.row + 1,
430
+ endLine: node.endPosition.row + 1,
431
+ isExported,
432
+ sourceText: content.slice(node.startIndex, node.endIndex),
433
+ };
434
+ } catch {
435
+ return null;
436
+ }
437
+ }
438
+
439
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
440
+ function extractVariables(
441
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
442
+ node: any,
443
+ content: string,
444
+ filePath: string,
445
+ repoId: string,
446
+ isExported: boolean,
447
+ entities: ParsedEntity[]
448
+ ): void {
449
+ try {
450
+ const isConst = content.slice(node.startIndex, node.startIndex + 5).startsWith('const');
451
+
452
+ for (let i = 0; i < node.childCount; i++) {
453
+ const child = node.child(i);
454
+ if (child.type === 'variable_declarator') {
455
+ const nameNode = child.child(0);
456
+ if (nameNode && (nameNode.type === 'identifier' || nameNode.type === 'type_identifier')) {
457
+ const name = content.slice(nameNode.startIndex, nameNode.endIndex);
458
+ entities.push({
459
+ name,
460
+ type: 'variable',
461
+ filePath,
462
+ repoId,
463
+ startLine: node.startPosition.row + 1,
464
+ endLine: node.endPosition.row + 1,
465
+ isExported,
466
+ isConst,
467
+ sourceText: content.slice(node.startIndex, node.endIndex),
468
+ });
469
+ }
470
+ }
471
+ }
472
+ } catch {
473
+ // Skip malformed variable declarations
474
+ }
475
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * File system watcher using chokidar (ADR, section 4.5).
3
+ * Watches configured repo directories, debounces rapid saves (500ms),
4
+ * respects .gitignore patterns, and detects git HEAD changes for branch switches.
5
+ */
6
+
7
+ import { EventEmitter } from 'events';
8
+ import { join } from 'path';
9
+ import { readFile } from 'fs/promises';
10
+ import type { RepoConfig } from '../util/types.js';
11
+ import { logger } from '../util/logger.js';
12
+
13
+ // Chokidar dynamic import (ESM compatible)
14
+ type ChokidarWatcher = {
15
+ on(event: string, handler: (path: string) => void): void;
16
+ close(): Promise<void>;
17
+ };
18
+
19
+ type WatchEvent = 'add' | 'change' | 'unlink';
20
+
21
+ export interface FileChangeEvent {
22
+ type: WatchEvent;
23
+ filePath: string;
24
+ repoId: string;
25
+ }
26
+
27
+ const WATCHED_EXTENSIONS = new Set([
28
+ '.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs',
29
+ '.json', '.yaml', '.yml', '.md', '.mdx', '.graphql', '.gql', '.sql',
30
+ ]);
31
+
32
+ const DEFAULT_IGNORED = [
33
+ '**/node_modules/**',
34
+ '**/.git/**',
35
+ '**/dist/**',
36
+ '**/build/**',
37
+ '**/.cache/**',
38
+ '**/*.map',
39
+ '**/*.min.js',
40
+ '**/*.min.css',
41
+ '**/package-lock.json',
42
+ '**/pnpm-lock.yaml',
43
+ '**/.DS_Store',
44
+ '**/Thumbs.db',
45
+ ];
46
+
47
+ export class FileWatcher extends EventEmitter {
48
+ private watchers: Map<string, ChokidarWatcher> = new Map();
49
+ private debounceTimers: Map<string, ReturnType<typeof setTimeout>> = new Map();
50
+ private gitHeadValues: Map<string, string> = new Map();
51
+ private readonly debounceMs: number;
52
+
53
+ constructor(debounceMs: number = 500) {
54
+ super();
55
+ this.debounceMs = debounceMs;
56
+ }
57
+
58
+ async watch(repoConfig: RepoConfig, additionalIgnored: string[] = []): Promise<void> {
59
+ const { id: repoId, path: repoPath } = repoConfig;
60
+
61
+ try {
62
+ const { default: chokidar } = await import('chokidar');
63
+
64
+ const ignored = [...DEFAULT_IGNORED, ...additionalIgnored];
65
+
66
+ const watcher = chokidar.watch(repoPath, {
67
+ ignored,
68
+ persistent: true,
69
+ ignoreInitial: true,
70
+ awaitWriteFinish: {
71
+ stabilityThreshold: 100,
72
+ pollInterval: 50,
73
+ },
74
+ usePolling: false,
75
+ // Windows-specific: use polling as fallback for network drives
76
+ interval: 500,
77
+ binaryInterval: 1000,
78
+ });
79
+
80
+ // Watch git HEAD for branch switches
81
+ const gitHeadPath = join(repoPath, '.git', 'HEAD');
82
+ await this.initGitHead(repoId, gitHeadPath);
83
+
84
+ watcher.on('add', (filePath: string) => this.handleChange('add', filePath, repoId));
85
+ watcher.on('change', (filePath: string) => this.handleChange('change', filePath, repoId));
86
+ watcher.on('unlink', (filePath: string) => this.handleChange('unlink', filePath, repoId));
87
+ watcher.on('error', (error: unknown) => {
88
+ const msg = error instanceof Error ? error.message : String(error);
89
+ logger.error('Watcher error', { repoId, error: msg });
90
+ });
91
+
92
+ // Watch .git/HEAD separately for branch switch detection.
93
+ // The main watcher ignores .git/** so we need a dedicated watcher.
94
+ const gitHeadWatcher = chokidar.watch(gitHeadPath, {
95
+ persistent: true,
96
+ ignoreInitial: true,
97
+ awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 },
98
+ usePolling: false,
99
+ interval: 500,
100
+ });
101
+ gitHeadWatcher.on('change', () => this.handleGitHeadChange(repoId, gitHeadPath));
102
+
103
+ this.watchers.set(repoId, watcher);
104
+ logger.info('Watching repo', { repoId, path: repoPath });
105
+ } catch (err) {
106
+ logger.error('Failed to start watcher', { repoId, error: String(err) });
107
+ }
108
+ }
109
+
110
+ async stop(repoId?: string): Promise<void> {
111
+ if (repoId) {
112
+ const watcher = this.watchers.get(repoId);
113
+ if (watcher) {
114
+ await watcher.close();
115
+ this.watchers.delete(repoId);
116
+ }
117
+ } else {
118
+ for (const [id, watcher] of this.watchers) {
119
+ await watcher.close();
120
+ this.watchers.delete(id);
121
+ }
122
+ }
123
+ }
124
+
125
+ private handleChange(type: WatchEvent, filePath: string, repoId: string): void {
126
+ // Filter by extension
127
+ const ext = filePath.slice(filePath.lastIndexOf('.')).toLowerCase();
128
+ if (!WATCHED_EXTENSIONS.has(ext) && !filePath.endsWith('HEAD')) return;
129
+
130
+ // Debounce
131
+ const debounceKey = `${repoId}:${filePath}`;
132
+ const existing = this.debounceTimers.get(debounceKey);
133
+ if (existing) clearTimeout(existing);
134
+
135
+ const timer = setTimeout(() => {
136
+ this.debounceTimers.delete(debounceKey);
137
+ this.handleFileChangeDebounced(type, filePath, repoId);
138
+ }, this.debounceMs);
139
+
140
+ this.debounceTimers.set(debounceKey, timer);
141
+ }
142
+
143
+ private async handleFileChangeDebounced(
144
+ type: WatchEvent,
145
+ filePath: string,
146
+ repoId: string
147
+ ): Promise<void> {
148
+ // Check for git HEAD changes (branch switch detection)
149
+ if (filePath.endsWith('.git/HEAD') || filePath.endsWith('.git\\HEAD')) {
150
+ await this.handleGitHeadChange(repoId, filePath);
151
+ return;
152
+ }
153
+
154
+ const event: FileChangeEvent = { type, filePath, repoId };
155
+ this.emit('file-change', event);
156
+ logger.debug('File change detected', { type, filePath, repoId });
157
+ }
158
+
159
+ private async initGitHead(repoId: string, headPath: string): Promise<void> {
160
+ try {
161
+ const content = await readFile(headPath, 'utf8');
162
+ this.gitHeadValues.set(repoId, content.trim());
163
+ } catch {
164
+ // .git/HEAD may not exist for non-git repos
165
+ }
166
+ }
167
+
168
+ private async handleGitHeadChange(repoId: string, headPath: string): Promise<void> {
169
+ try {
170
+ const newHead = (await readFile(headPath, 'utf8')).trim();
171
+ const oldHead = this.gitHeadValues.get(repoId);
172
+
173
+ if (oldHead !== newHead) {
174
+ this.gitHeadValues.set(repoId, newHead);
175
+ logger.info('Git branch switch detected', { repoId, from: oldHead, to: newHead });
176
+ this.emit('branch-switch', { repoId, from: oldHead, to: newHead });
177
+ }
178
+ } catch {
179
+ // Ignore errors reading git HEAD
180
+ }
181
+ }
182
+ }
File without changes