circuschief 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.
- package/package.json +33 -0
- package/packages/server/bin/cli.js +4 -0
- package/packages/server/src/agents/AgentGateway.js +64 -0
- package/packages/server/src/agents/BaseAgent.js +41 -0
- package/packages/server/src/agents/LoggingAgentWrapper.js +73 -0
- package/packages/server/src/agents/adapters/ClaudeCodeAdapter.js +33 -0
- package/packages/server/src/agents/adapters/CodexAdapter.js +26 -0
- package/packages/server/src/agents/types.js +43 -0
- package/packages/server/src/agents/vcr/CassetteStore.js +111 -0
- package/packages/server/src/agents/vcr/VCRAgentAdapter.js +126 -0
- package/packages/server/src/agents/vcr/VCRSummaryWrapper.js +71 -0
- package/packages/server/src/api/canvas-helpers.js +249 -0
- package/packages/server/src/api/canvas-trash-routes.js +205 -0
- package/packages/server/src/api/canvas.js +331 -0
- package/packages/server/src/api/commandButtons.js +312 -0
- package/packages/server/src/api/commands.js +169 -0
- package/packages/server/src/api/filesystem.js +62 -0
- package/packages/server/src/api/git.js +85 -0
- package/packages/server/src/api/index.js +44 -0
- package/packages/server/src/api/kanban.js +342 -0
- package/packages/server/src/api/metrics.js +194 -0
- package/packages/server/src/api/projects-helpers.js +43 -0
- package/packages/server/src/api/projects-session-helpers.js +295 -0
- package/packages/server/src/api/projects.js +384 -0
- package/packages/server/src/api/providers.js +249 -0
- package/packages/server/src/api/quickResponses.js +129 -0
- package/packages/server/src/api/sessions-archive.js +69 -0
- package/packages/server/src/api/sessions-commands.js +220 -0
- package/packages/server/src/api/sessions-conversations.js +168 -0
- package/packages/server/src/api/sessions-draft.js +72 -0
- package/packages/server/src/api/sessions-lifecycle.js +190 -0
- package/packages/server/src/api/sessions-messages.js +141 -0
- package/packages/server/src/api/sessions-notes.js +51 -0
- package/packages/server/src/api/sessions-patch.js +252 -0
- package/packages/server/src/api/sessions-streaming.js +86 -0
- package/packages/server/src/api/sessions.js +269 -0
- package/packages/server/src/api/settings.js +194 -0
- package/packages/server/src/api/templates.js +63 -0
- package/packages/server/src/app.js +51 -0
- package/packages/server/src/database.js +58 -0
- package/packages/server/src/db/AgentCallLogRepository.js +322 -0
- package/packages/server/src/db/AttachmentRepository.js +191 -0
- package/packages/server/src/db/BaseRepository.js +39 -0
- package/packages/server/src/db/CanvasItemRepository.js +315 -0
- package/packages/server/src/db/CommandButtonRepository.js +75 -0
- package/packages/server/src/db/CommandRunRepository.js +219 -0
- package/packages/server/src/db/ConversationRepository.js +379 -0
- package/packages/server/src/db/DatabaseManager.js +91 -0
- package/packages/server/src/db/KanbanBoardRepository.js +92 -0
- package/packages/server/src/db/KanbanCardRepository.js +286 -0
- package/packages/server/src/db/KanbanLaneRepository.js +279 -0
- package/packages/server/src/db/MessageRepository.js +156 -0
- package/packages/server/src/db/ProjectDefaultsRepository.js +173 -0
- package/packages/server/src/db/ProjectRepository.js +110 -0
- package/packages/server/src/db/ProviderRepository.js +307 -0
- package/packages/server/src/db/QuickResponseRepository.js +186 -0
- package/packages/server/src/db/SessionNoteRepository.js +60 -0
- package/packages/server/src/db/SessionRepository.js +314 -0
- package/packages/server/src/db/SessionSummaryRepository.js +200 -0
- package/packages/server/src/db/SessionTemplateRepository.js +171 -0
- package/packages/server/src/db/SettingsRepository.js +211 -0
- package/packages/server/src/db/TodoRepository.js +132 -0
- package/packages/server/src/db/WorkLogRepository.js +122 -0
- package/packages/server/src/db/conversation-helpers.js +119 -0
- package/packages/server/src/db/index.js +100 -0
- package/packages/server/src/db/migrations/canvasItemsMigrations.js +109 -0
- package/packages/server/src/db/migrations/conversationsMigrations.js +183 -0
- package/packages/server/src/db/migrations/index.js +199 -0
- package/packages/server/src/db/migrations/kanbanMigrations.js +99 -0
- package/packages/server/src/db/migrations/migrationUtils.js +55 -0
- package/packages/server/src/db/migrations/miscMigrations.js +242 -0
- package/packages/server/src/db/migrations/projectsMigrations.js +95 -0
- package/packages/server/src/db/migrations/sessionsMigrations.js +282 -0
- package/packages/server/src/db/session-helpers.js +150 -0
- package/packages/server/src/index.js +106 -0
- package/packages/server/src/logger.js +22 -0
- package/packages/server/src/middleware/sessionLookup.js +57 -0
- package/packages/server/src/middleware/upload.js +94 -0
- package/packages/server/src/schema.sql +363 -0
- package/packages/server/src/services/agentCallLogger.js +116 -0
- package/packages/server/src/services/canvasStore.js +56 -0
- package/packages/server/src/services/childSessionContext.js +61 -0
- package/packages/server/src/services/commandRunner.js +422 -0
- package/packages/server/src/services/conversationContext.js +72 -0
- package/packages/server/src/services/diffService.js +172 -0
- package/packages/server/src/services/draftSessionService.js +181 -0
- package/packages/server/src/services/encryption.js +134 -0
- package/packages/server/src/services/ghService.js +169 -0
- package/packages/server/src/services/gitService.js +520 -0
- package/packages/server/src/services/gitSessionSetup.js +48 -0
- package/packages/server/src/services/hookService.js +60 -0
- package/packages/server/src/services/kanbanService.js +262 -0
- package/packages/server/src/services/kanbanTriggers.js +273 -0
- package/packages/server/src/services/nodeSpawnHelper.js +63 -0
- package/packages/server/src/services/prStatusService.js +204 -0
- package/packages/server/src/services/prUrlService.js +224 -0
- package/packages/server/src/services/providerTestService.js +81 -0
- package/packages/server/src/services/scheduleService.js +110 -0
- package/packages/server/src/services/schedulerService.js +281 -0
- package/packages/server/src/services/sessionDuplicator.js +63 -0
- package/packages/server/src/services/sessionErrors.js +173 -0
- package/packages/server/src/services/sessionExecution.js +378 -0
- package/packages/server/src/services/sessionManager.js +356 -0
- package/packages/server/src/services/sessionPrompts.js +427 -0
- package/packages/server/src/services/sessionProvider.js +107 -0
- package/packages/server/src/services/slashCommandDiscovery.js +258 -0
- package/packages/server/src/services/slashCommandPluginDiscovery.js +216 -0
- package/packages/server/src/services/slashCommandService.js +306 -0
- package/packages/server/src/services/streamEventCallbacks.js +170 -0
- package/packages/server/src/services/streamEventHandler.js +488 -0
- package/packages/server/src/services/streamUsageHandler.js +228 -0
- package/packages/server/src/services/summaryBroadcast.js +61 -0
- package/packages/server/src/services/summaryClaudeClient.js +180 -0
- package/packages/server/src/services/summaryPrompts.js +169 -0
- package/packages/server/src/services/summaryService.js +552 -0
- package/packages/server/src/services/summaryStaleCheck.js +35 -0
- package/packages/server/src/services/systemMonitor.js +281 -0
- package/packages/server/src/services/templateTriggerService.js +197 -0
- package/packages/server/src/services/terminalOutput.js +160 -0
- package/packages/server/src/services/todoStore.js +58 -0
- package/packages/server/src/services/usageTracker.js +69 -0
- package/packages/server/src/services/withConcurrencyGuard.js +110 -0
- package/packages/server/src/websocket.js +10 -0
- package/packages/server/src/ws/WebSocketManager.js +240 -0
- package/packages/server/src/ws/index.js +50 -0
- package/packages/shared/package.json +27 -0
- package/packages/shared/src/constants.js +44 -0
- package/packages/shared/src/contracts/canvas.js +25 -0
- package/packages/shared/src/contracts/commandButtons.js +36 -0
- package/packages/shared/src/contracts/kanban.js +142 -0
- package/packages/shared/src/contracts/projects.js +63 -0
- package/packages/shared/src/contracts/providers.js +81 -0
- package/packages/shared/src/contracts/quickResponses.js +44 -0
- package/packages/shared/src/contracts/sessions.js +112 -0
- package/packages/shared/src/contracts/templates.js +51 -0
- package/packages/shared/src/index.js +5 -0
- package/packages/shared/src/protocol.js +76 -0
- package/packages/shared/src/routeParams.js +36 -0
- package/packages/shared/src/types.js +167 -0
- package/packages/shared/src/utils.js +101 -0
- package/packages/web/dist/assets/ActiveSessionsView-BQc76Jc8.js +1 -0
- package/packages/web/dist/assets/ActiveSessionsView-ofSvx-K1.css +1 -0
- package/packages/web/dist/assets/AgentLogsView-CTCjHjsu.js +2 -0
- package/packages/web/dist/assets/AgentLogsView-D90PnQVk.css +1 -0
- package/packages/web/dist/assets/ApiClient-Dbs1H78V.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-CCxSZ52u.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-CQZeuYBz.css +1 -0
- package/packages/web/dist/assets/CommandButtonDetailView-CF_-LXpU.js +1 -0
- package/packages/web/dist/assets/CommandButtonDetailView-DBm3rzhw.css +1 -0
- package/packages/web/dist/assets/EffortLevelSelector-BQaQmU2d.css +1 -0
- package/packages/web/dist/assets/EffortLevelSelector-DPofLvm-.js +1 -0
- package/packages/web/dist/assets/GeneralSettingsView-BCf53fpC.css +1 -0
- package/packages/web/dist/assets/GeneralSettingsView-BY1G-Kv8.js +1 -0
- package/packages/web/dist/assets/InterpolationHelp-CgdbNcJB.js +1 -0
- package/packages/web/dist/assets/InterpolationHelp-iNxTxmhs.css +1 -0
- package/packages/web/dist/assets/MarkdownEditor-CqT1U8lo.js +2 -0
- package/packages/web/dist/assets/MarkdownEditor-enuH2yvP.css +1 -0
- package/packages/web/dist/assets/ModelSelector-BBn_Ve0D.js +1 -0
- package/packages/web/dist/assets/ModelSelector-DPPD-92R.css +1 -0
- package/packages/web/dist/assets/NewSessionView-Bo5l49nu.js +3 -0
- package/packages/web/dist/assets/NewSessionView-Byoi1XdQ.css +1 -0
- package/packages/web/dist/assets/PathChooser-BoMGzeg2.css +1 -0
- package/packages/web/dist/assets/PathChooser-Cx9gQ-Qt.js +1 -0
- package/packages/web/dist/assets/ProjectEditView-BFuscj-V.js +1 -0
- package/packages/web/dist/assets/ProjectEditView-DNwBUNRk.css +1 -0
- package/packages/web/dist/assets/ProjectListView-C55H1JHQ.css +1 -0
- package/packages/web/dist/assets/ProjectListView-Dj0jBZ46.js +1 -0
- package/packages/web/dist/assets/ProjectNewView-Brdp-xUu.js +1 -0
- package/packages/web/dist/assets/ProjectNewView-CpgE4R-l.css +1 -0
- package/packages/web/dist/assets/ProvidersView-B_QQF3RM.css +1 -0
- package/packages/web/dist/assets/ProvidersView-Cxc-1skq.js +1 -0
- package/packages/web/dist/assets/QuickResponseSettings-B2eVAtHW.js +1 -0
- package/packages/web/dist/assets/QuickResponseSettings-B8188A1D.css +1 -0
- package/packages/web/dist/assets/QuickResponsesPanel-DIBQFj0W.css +1 -0
- package/packages/web/dist/assets/QuickResponsesPanel-lU8pW2B0.js +1 -0
- package/packages/web/dist/assets/ResizableTextarea-B5nAA0RV.css +1 -0
- package/packages/web/dist/assets/ResizableTextarea-DSy1mWGY.js +1 -0
- package/packages/web/dist/assets/SessionCard-BvjLwVYg.js +1 -0
- package/packages/web/dist/assets/SessionCard-D20G3bX8.css +1 -0
- package/packages/web/dist/assets/SessionDetailView-BQbPg-RJ.js +36 -0
- package/packages/web/dist/assets/SessionDetailView-BrMG4p2-.css +1 -0
- package/packages/web/dist/assets/SessionFormOptions-BgqFR-5f.js +1 -0
- package/packages/web/dist/assets/SessionFormOptions-BuLlDF-7.css +1 -0
- package/packages/web/dist/assets/SessionListView-BAIBtJF7.css +1 -0
- package/packages/web/dist/assets/SessionListView-CYIHI8qF.js +1 -0
- package/packages/web/dist/assets/SessionLogStream-B-FwUMJQ.js +18 -0
- package/packages/web/dist/assets/SessionLogStream-zPUTiGbe.css +1 -0
- package/packages/web/dist/assets/SettingsView-DC8-hTQ-.css +1 -0
- package/packages/web/dist/assets/SettingsView-fZxpiGp7.js +1 -0
- package/packages/web/dist/assets/SlashCommandWizard-BB30cSvo.css +1 -0
- package/packages/web/dist/assets/SlashCommandWizard-BgaOw9W3.js +1 -0
- package/packages/web/dist/assets/SummarySettingsView-DcsmSVJI.css +1 -0
- package/packages/web/dist/assets/SummarySettingsView-eeu1Xq86.js +1 -0
- package/packages/web/dist/assets/TemplateDetailView-DEPKSwDo.js +1 -0
- package/packages/web/dist/assets/TemplateDetailView-DT2m06W7.css +1 -0
- package/packages/web/dist/assets/apl-B4CMkyY2.js +1 -0
- package/packages/web/dist/assets/asciiarmor-Df11BRmG.js +1 -0
- package/packages/web/dist/assets/asn1-EdZsLKOL.js +1 -0
- package/packages/web/dist/assets/asterisk-B-8jnY81.js +1 -0
- package/packages/web/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/packages/web/dist/assets/clike-B9uivgTg.js +1 -0
- package/packages/web/dist/assets/clojure-BMjYHr_A.js +1 -0
- package/packages/web/dist/assets/cmake-BQqOBYOt.js +1 -0
- package/packages/web/dist/assets/cobol-CWcv1MsR.js +1 -0
- package/packages/web/dist/assets/coffeescript-S37ZYGWr.js +1 -0
- package/packages/web/dist/assets/commandButtons-DNSHH8IA.js +4 -0
- package/packages/web/dist/assets/commonlisp-DBKNyK5s.js +1 -0
- package/packages/web/dist/assets/crystal-SjHAIU92.js +1 -0
- package/packages/web/dist/assets/css-BnMrqG3P.js +1 -0
- package/packages/web/dist/assets/cypher-C_CwsFkJ.js +1 -0
- package/packages/web/dist/assets/d-pRatUO7H.js +1 -0
- package/packages/web/dist/assets/diff-DbItnlRl.js +1 -0
- package/packages/web/dist/assets/dockerfile-BKs6k2Af.js +1 -0
- package/packages/web/dist/assets/dtd-DF_7sFjM.js +1 -0
- package/packages/web/dist/assets/dylan-DwRh75JA.js +1 -0
- package/packages/web/dist/assets/ebnf-CDyGwa7X.js +1 -0
- package/packages/web/dist/assets/ecl-Cabwm37j.js +1 -0
- package/packages/web/dist/assets/eiffel-CnydiIhH.js +1 -0
- package/packages/web/dist/assets/elm-vLlmbW-K.js +1 -0
- package/packages/web/dist/assets/erlang-BNw1qcRV.js +1 -0
- package/packages/web/dist/assets/factor-kuTfRLto.js +1 -0
- package/packages/web/dist/assets/fcl-Kvtd6kyn.js +1 -0
- package/packages/web/dist/assets/forth-Ffai-XNe.js +1 -0
- package/packages/web/dist/assets/fortran-DYz_wnZ1.js +1 -0
- package/packages/web/dist/assets/gas-Bneqetm1.js +1 -0
- package/packages/web/dist/assets/gherkin-heZmZLOM.js +1 -0
- package/packages/web/dist/assets/groovy-D9Dt4D0W.js +1 -0
- package/packages/web/dist/assets/haskell-BWDZoCOh.js +1 -0
- package/packages/web/dist/assets/haxe-H-WmDvRZ.js +1 -0
- package/packages/web/dist/assets/http-DBlCnlav.js +1 -0
- package/packages/web/dist/assets/idl-BEugSyMb.js +1 -0
- package/packages/web/dist/assets/index-BZlHgDSz.js +1 -0
- package/packages/web/dist/assets/index-BhWX8AfE.js +2 -0
- package/packages/web/dist/assets/index-Bi3XvF_f.js +1 -0
- package/packages/web/dist/assets/index-BqXoPf_D.js +1 -0
- package/packages/web/dist/assets/index-CAuTOZSD.js +1 -0
- package/packages/web/dist/assets/index-CKYk-fkb.js +1 -0
- package/packages/web/dist/assets/index-CTumW_tV.js +318 -0
- package/packages/web/dist/assets/index-CVOJVSsC.js +82 -0
- package/packages/web/dist/assets/index-CXK2Z3_z.js +1 -0
- package/packages/web/dist/assets/index-CYllQ3Vd.js +1 -0
- package/packages/web/dist/assets/index-CpsfI08O.js +1 -0
- package/packages/web/dist/assets/index-DQkhDeTA.js +3 -0
- package/packages/web/dist/assets/index-DWP8iCBp.js +1 -0
- package/packages/web/dist/assets/index-DkVb9W_J.js +1 -0
- package/packages/web/dist/assets/index-DmKHPbIa.js +1 -0
- package/packages/web/dist/assets/index-DrlQi03X.js +1 -0
- package/packages/web/dist/assets/index-gmCCsCQ1.css +1 -0
- package/packages/web/dist/assets/index-prTEzzgO.js +1 -0
- package/packages/web/dist/assets/index-wqgejMCM.js +1 -0
- package/packages/web/dist/assets/index-yh0ZHIWw.js +7 -0
- package/packages/web/dist/assets/javascript-qCveANmP.js +1 -0
- package/packages/web/dist/assets/julia-DuME0IfC.js +1 -0
- package/packages/web/dist/assets/livescript-BwQOo05w.js +1 -0
- package/packages/web/dist/assets/lua-BgMRiT3U.js +1 -0
- package/packages/web/dist/assets/mathematica-DTrFuWx2.js +1 -0
- package/packages/web/dist/assets/mbox-CNhZ1qSd.js +1 -0
- package/packages/web/dist/assets/mirc-CjQqDB4T.js +1 -0
- package/packages/web/dist/assets/mllike-CXdrOF99.js +1 -0
- package/packages/web/dist/assets/modelica-Dc1JOy9r.js +1 -0
- package/packages/web/dist/assets/mscgen-BA5vi2Kp.js +1 -0
- package/packages/web/dist/assets/mumps-BT43cFF4.js +1 -0
- package/packages/web/dist/assets/nginx-DdIZxoE0.js +1 -0
- package/packages/web/dist/assets/nsis-LdVXkNf5.js +1 -0
- package/packages/web/dist/assets/ntriples-BfvgReVJ.js +1 -0
- package/packages/web/dist/assets/octave-Ck1zUtKM.js +1 -0
- package/packages/web/dist/assets/oz-BzwKVEFT.js +1 -0
- package/packages/web/dist/assets/pascal--L3eBynH.js +1 -0
- package/packages/web/dist/assets/perl-CdXCOZ3F.js +1 -0
- package/packages/web/dist/assets/pig-CevX1Tat.js +1 -0
- package/packages/web/dist/assets/powershell-CFHJl5sT.js +1 -0
- package/packages/web/dist/assets/projects-DbBQQH-V.js +1 -0
- package/packages/web/dist/assets/properties-C78fOPTZ.js +1 -0
- package/packages/web/dist/assets/protobuf-ChK-085T.js +1 -0
- package/packages/web/dist/assets/providers-ceCc4xRU.js +1 -0
- package/packages/web/dist/assets/pug-DukmZTjD.js +1 -0
- package/packages/web/dist/assets/puppet-DMA9R1ak.js +1 -0
- package/packages/web/dist/assets/python-BuPzkPfP.js +1 -0
- package/packages/web/dist/assets/q-pXgVlZs6.js +1 -0
- package/packages/web/dist/assets/r-DUYO_cvP.js +1 -0
- package/packages/web/dist/assets/rpm-CTu-6PCP.js +1 -0
- package/packages/web/dist/assets/ruby-B2Rjki9n.js +1 -0
- package/packages/web/dist/assets/sas-B4kiWyti.js +1 -0
- package/packages/web/dist/assets/scheme-C41bIUwD.js +1 -0
- package/packages/web/dist/assets/sessions-D681M81k.js +1 -0
- package/packages/web/dist/assets/settings-D0evez2V.js +1 -0
- package/packages/web/dist/assets/shell-CjFT_Tl9.js +1 -0
- package/packages/web/dist/assets/sieve-C3Gn_uJK.js +1 -0
- package/packages/web/dist/assets/simple-mode-GW_nhZxv.js +1 -0
- package/packages/web/dist/assets/smalltalk-CnHTOXQT.js +1 -0
- package/packages/web/dist/assets/solr-DehyRSwq.js +1 -0
- package/packages/web/dist/assets/sparql-DkYu6x3z.js +1 -0
- package/packages/web/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/packages/web/dist/assets/sql-D0XecflT.js +1 -0
- package/packages/web/dist/assets/stex-C3f8Ysf7.js +1 -0
- package/packages/web/dist/assets/style-BTin-zR_.css +1 -0
- package/packages/web/dist/assets/stylus-B533Al4x.js +1 -0
- package/packages/web/dist/assets/swift-BzpIVaGY.js +1 -0
- package/packages/web/dist/assets/tcl-DVfN8rqt.js +1 -0
- package/packages/web/dist/assets/textile-CnDTJFAw.js +1 -0
- package/packages/web/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/packages/web/dist/assets/tiki-DGYXhP31.js +1 -0
- package/packages/web/dist/assets/toml-Bm5Em-hy.js +1 -0
- package/packages/web/dist/assets/troff-wAsdV37c.js +1 -0
- package/packages/web/dist/assets/ttcn-CfJYG6tj.js +1 -0
- package/packages/web/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/packages/web/dist/assets/turtle-B1tBg_DP.js +1 -0
- package/packages/web/dist/assets/vb-CmGdzxic.js +1 -0
- package/packages/web/dist/assets/vbscript-BuJXcnF6.js +1 -0
- package/packages/web/dist/assets/velocity-D8B20fx6.js +1 -0
- package/packages/web/dist/assets/verilog-C6RDOZhf.js +1 -0
- package/packages/web/dist/assets/vhdl-lSbBsy5d.js +1 -0
- package/packages/web/dist/assets/webidl-ZXfAyPTL.js +1 -0
- package/packages/web/dist/assets/xquery-CQfU5ijd.js +1 -0
- package/packages/web/dist/assets/yacas-BJ4BC0dw.js +1 -0
- package/packages/web/dist/assets/z80-Hz9HOZM7.js +1 -0
- package/packages/web/dist/favicon.png +0 -0
- package/packages/web/dist/index.html +17 -0
- package/packages/web/dist/logo.png +0 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
|
|
6
|
+
// Cache for default branch detection per repository
|
|
7
|
+
// Key: directory path, Value: { branch: string, timestamp: number }
|
|
8
|
+
const defaultBranchCache = new Map();
|
|
9
|
+
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
10
|
+
const MAX_CACHE_SIZE = 100; // Maximum number of repositories to cache
|
|
11
|
+
|
|
12
|
+
// Configurable logger for warning messages
|
|
13
|
+
// Can be overridden via setLogger() for custom logging behavior
|
|
14
|
+
let logger = {
|
|
15
|
+
warn: (...args) => console.warn(...args),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Set a custom logger for git service warnings.
|
|
20
|
+
* Useful for integrating with application logging or suppressing warnings in tests.
|
|
21
|
+
* @param {Object} customLogger - Logger object with a warn method
|
|
22
|
+
* @param {Function} customLogger.warn - Function to handle warning messages
|
|
23
|
+
*/
|
|
24
|
+
export function setLogger(customLogger) {
|
|
25
|
+
logger = customLogger;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Evict oldest entries from cache if it exceeds MAX_CACHE_SIZE.
|
|
30
|
+
* Uses LRU-like eviction based on timestamp.
|
|
31
|
+
*/
|
|
32
|
+
function evictOldestCacheEntries() {
|
|
33
|
+
if (defaultBranchCache.size <= MAX_CACHE_SIZE) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Sort entries by timestamp and remove oldest ones
|
|
38
|
+
const entries = [...defaultBranchCache.entries()];
|
|
39
|
+
entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
40
|
+
|
|
41
|
+
const entriesToRemove = entries.slice(0, entries.length - MAX_CACHE_SIZE);
|
|
42
|
+
for (const [key] of entriesToRemove) {
|
|
43
|
+
defaultBranchCache.delete(key);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Safely fetch from origin remote.
|
|
49
|
+
* Logs a warning if fetch fails but does not throw.
|
|
50
|
+
* @param {string} directory - The git repository directory
|
|
51
|
+
* @returns {Promise<boolean>} - True if fetch succeeded, false otherwise
|
|
52
|
+
*/
|
|
53
|
+
async function safeFetchOrigin(directory) {
|
|
54
|
+
try {
|
|
55
|
+
await git(directory, 'fetch origin');
|
|
56
|
+
return true;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
// No origin or network unavailable, proceed without fetch
|
|
59
|
+
logger.warn('Could not fetch from origin, proceeding with local refs:', err.message);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Execute a git command in a directory
|
|
66
|
+
* @param {string} directory
|
|
67
|
+
* @param {string} command
|
|
68
|
+
* @returns {Promise<string>}
|
|
69
|
+
*/
|
|
70
|
+
async function git(directory, command, opts = {}) {
|
|
71
|
+
const execOpts = { cwd: directory };
|
|
72
|
+
if (opts.env) execOpts.env = opts.env;
|
|
73
|
+
const { stdout } = await execAsync(`git ${command}`, execOpts);
|
|
74
|
+
return stdout.trim();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Detect the default branch using git commands (symbolic-ref, rev-parse).
|
|
79
|
+
* @param {string} directory
|
|
80
|
+
* @returns {Promise<string>}
|
|
81
|
+
*/
|
|
82
|
+
async function detectDefaultBranchFromGit(directory) {
|
|
83
|
+
// Try to get the default branch from the remote HEAD
|
|
84
|
+
try {
|
|
85
|
+
const ref = await git(directory, 'symbolic-ref refs/remotes/origin/HEAD');
|
|
86
|
+
// Returns something like "refs/remotes/origin/main"
|
|
87
|
+
return ref.replace('refs/remotes/', '');
|
|
88
|
+
} catch {
|
|
89
|
+
// Fallback: check if origin/main exists, otherwise try origin/master
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await git(directory, 'rev-parse --verify origin/main');
|
|
94
|
+
return 'origin/main';
|
|
95
|
+
} catch {
|
|
96
|
+
// origin/main doesn't exist
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
await git(directory, 'rev-parse --verify origin/master');
|
|
101
|
+
return 'origin/master';
|
|
102
|
+
} catch {
|
|
103
|
+
// No origin remote available, fall back to HEAD
|
|
104
|
+
return 'HEAD';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get the default branch from origin remote
|
|
110
|
+
* Uses GitHub CLI if available, falls back to git commands
|
|
111
|
+
* Results are cached per repository with a 5-minute TTL
|
|
112
|
+
* Falls back to HEAD if no origin remote is configured
|
|
113
|
+
* @param {string} directory
|
|
114
|
+
* @returns {Promise<string>}
|
|
115
|
+
*/
|
|
116
|
+
export async function getOriginDefaultBranch(directory) {
|
|
117
|
+
// Check cache first
|
|
118
|
+
const cached = defaultBranchCache.get(directory);
|
|
119
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
|
|
120
|
+
return cached.branch;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let branch;
|
|
124
|
+
|
|
125
|
+
// Try GitHub CLI first - most accurate method
|
|
126
|
+
try {
|
|
127
|
+
const { stdout } = await execAsync(
|
|
128
|
+
'gh repo view --json defaultBranchRef --jq ".defaultBranchRef.name"',
|
|
129
|
+
{ cwd: directory }
|
|
130
|
+
);
|
|
131
|
+
const branchName = stdout.trim();
|
|
132
|
+
if (branchName) {
|
|
133
|
+
branch = `origin/${branchName}`;
|
|
134
|
+
}
|
|
135
|
+
} catch {
|
|
136
|
+
// gh CLI not available or failed, fall back to git commands
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!branch) {
|
|
140
|
+
branch = await detectDefaultBranchFromGit(directory);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Cache the result and evict old entries if needed
|
|
144
|
+
defaultBranchCache.set(directory, { branch, timestamp: Date.now() });
|
|
145
|
+
evictOldestCacheEntries();
|
|
146
|
+
return branch;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Clear the default branch cache for all repositories.
|
|
151
|
+
* Useful for testing or when repository remote configuration has changed.
|
|
152
|
+
*/
|
|
153
|
+
export function clearDefaultBranchCache() {
|
|
154
|
+
defaultBranchCache.clear();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get the current cache size (for testing purposes).
|
|
159
|
+
* @returns {number} The number of entries in the cache
|
|
160
|
+
*/
|
|
161
|
+
export function getCacheSize() {
|
|
162
|
+
return defaultBranchCache.size;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if a directory is a git repository
|
|
167
|
+
* @param {string} directory
|
|
168
|
+
* @returns {Promise<boolean>}
|
|
169
|
+
*/
|
|
170
|
+
export async function isGitRepo(directory) {
|
|
171
|
+
try {
|
|
172
|
+
await git(directory, 'rev-parse --git-dir');
|
|
173
|
+
return true;
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get list of worktrees
|
|
181
|
+
* @param {string} directory
|
|
182
|
+
* @returns {Promise<Array<{path: string, branch: string, commit: string}>>}
|
|
183
|
+
*/
|
|
184
|
+
export async function getWorktrees(directory) {
|
|
185
|
+
const output = await git(directory, 'worktree list --porcelain');
|
|
186
|
+
const worktrees = [];
|
|
187
|
+
let current = {};
|
|
188
|
+
|
|
189
|
+
for (const line of output.split('\n')) {
|
|
190
|
+
if (line.startsWith('worktree ')) {
|
|
191
|
+
if (current.path) worktrees.push(current);
|
|
192
|
+
current = { path: line.slice(9) };
|
|
193
|
+
} else if (line.startsWith('HEAD ')) {
|
|
194
|
+
current.commit = line.slice(5);
|
|
195
|
+
} else if (line.startsWith('branch ')) {
|
|
196
|
+
current.branch = line.slice(7).replace('refs/heads/', '');
|
|
197
|
+
} else if (line === 'detached') {
|
|
198
|
+
current.branch = null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (current.path) worktrees.push(current);
|
|
203
|
+
return worktrees;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get list of branches
|
|
208
|
+
* @param {string} directory
|
|
209
|
+
* @returns {Promise<Array<{name: string, ref: string}>>}
|
|
210
|
+
*/
|
|
211
|
+
export async function getBranches(directory) {
|
|
212
|
+
const output = await git(directory, 'branch -a --format="%(refname:short)|%(refname)"');
|
|
213
|
+
return output
|
|
214
|
+
.split('\n')
|
|
215
|
+
.filter((line) => line.trim())
|
|
216
|
+
.map((line) => {
|
|
217
|
+
const [name, ref] = line.split('|');
|
|
218
|
+
return { name, ref };
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get current branch name
|
|
224
|
+
* @param {string} directory
|
|
225
|
+
* @returns {Promise<string|null>}
|
|
226
|
+
*/
|
|
227
|
+
export async function getCurrentBranch(directory) {
|
|
228
|
+
try {
|
|
229
|
+
// Use rev-parse which works in older git versions (--show-current was added in 2.22)
|
|
230
|
+
const branch = await git(directory, 'rev-parse --abbrev-ref HEAD');
|
|
231
|
+
// Returns 'HEAD' when in detached HEAD state
|
|
232
|
+
return branch === 'HEAD' ? null : branch;
|
|
233
|
+
} catch {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Create a new worktree
|
|
240
|
+
* @param {string} directory
|
|
241
|
+
* @param {string} branch
|
|
242
|
+
* @param {string} path
|
|
243
|
+
* @param {Object} options
|
|
244
|
+
* @param {boolean} options.skipFetch - Skip fetching from origin (default: false)
|
|
245
|
+
* @returns {Promise<{path: string, branch: string}>}
|
|
246
|
+
*/
|
|
247
|
+
export async function createWorktree(directory, branch, path, options = {}) {
|
|
248
|
+
const { skipFetch = false } = options;
|
|
249
|
+
|
|
250
|
+
// Fetch latest from origin to ensure we have up-to-date default branch
|
|
251
|
+
if (!skipFetch) {
|
|
252
|
+
await safeFetchOrigin(directory);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Get the default branch from origin (main or master)
|
|
256
|
+
const defaultBranch = await getOriginDefaultBranch(directory);
|
|
257
|
+
// Base new branch on origin's default branch to avoid including unrelated commits from HEAD
|
|
258
|
+
// Use --no-track to prevent the new branch from tracking the start-point (main/master)
|
|
259
|
+
await git(directory, `worktree add --no-track "${path}" -b "${branch}" ${defaultBranch}`);
|
|
260
|
+
return { path, branch };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Remove a worktree
|
|
265
|
+
* @param {string} directory
|
|
266
|
+
* @param {string} path
|
|
267
|
+
* @param {boolean} force - Force removal even if worktree has uncommitted changes
|
|
268
|
+
*/
|
|
269
|
+
export async function removeWorktree(directory, path, force = false) {
|
|
270
|
+
const forceFlag = force ? '--force' : '';
|
|
271
|
+
await git(directory, `worktree remove ${forceFlag} "${path}"`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Get diff for a directory
|
|
276
|
+
* @param {string} directory
|
|
277
|
+
* @returns {Promise<string>}
|
|
278
|
+
*/
|
|
279
|
+
export async function getDiff(directory) {
|
|
280
|
+
try {
|
|
281
|
+
return await git(directory, 'diff');
|
|
282
|
+
} catch {
|
|
283
|
+
return '';
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get staged diff for a directory
|
|
289
|
+
* @param {string} directory
|
|
290
|
+
* @returns {Promise<string>}
|
|
291
|
+
*/
|
|
292
|
+
export async function getStagedDiff(directory) {
|
|
293
|
+
try {
|
|
294
|
+
return await git(directory, 'diff --cached');
|
|
295
|
+
} catch {
|
|
296
|
+
return '';
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Check if a branch exists
|
|
302
|
+
* @param {string} directory
|
|
303
|
+
* @param {string} branch
|
|
304
|
+
* @returns {Promise<boolean>}
|
|
305
|
+
*/
|
|
306
|
+
export async function branchExists(directory, branch) {
|
|
307
|
+
try {
|
|
308
|
+
await git(directory, `rev-parse --verify refs/heads/${branch}`);
|
|
309
|
+
return true;
|
|
310
|
+
} catch {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Checkout a branch, creating it if it doesn't exist
|
|
317
|
+
* @param {string} directory
|
|
318
|
+
* @param {string} branch
|
|
319
|
+
* @returns {Promise<void>}
|
|
320
|
+
*/
|
|
321
|
+
export async function checkoutBranch(directory, branch) {
|
|
322
|
+
const exists = await branchExists(directory, branch);
|
|
323
|
+
if (exists) {
|
|
324
|
+
await git(directory, `checkout "${branch}"`);
|
|
325
|
+
} else {
|
|
326
|
+
await git(directory, `checkout -b "${branch}"`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Create a worktree for a branch (creates branch if it doesn't exist)
|
|
332
|
+
* @param {string} directory - Main repo directory
|
|
333
|
+
* @param {string} branch - Branch name
|
|
334
|
+
* @param {string} worktreePath - Path for the new worktree
|
|
335
|
+
* @param {Object} options
|
|
336
|
+
* @param {boolean} options.skipFetch - Skip fetching from origin (default: false)
|
|
337
|
+
* @returns {Promise<{path: string, branch: string}>}
|
|
338
|
+
*/
|
|
339
|
+
export async function createWorktreeForBranch(directory, branch, worktreePath, options = {}) {
|
|
340
|
+
const { skipFetch = false } = options;
|
|
341
|
+
|
|
342
|
+
// Fetch latest from origin to ensure we have up-to-date default branch
|
|
343
|
+
if (!skipFetch) {
|
|
344
|
+
await safeFetchOrigin(directory);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const exists = await branchExists(directory, branch);
|
|
348
|
+
if (exists) {
|
|
349
|
+
await git(directory, `worktree add "${worktreePath}" "${branch}"`);
|
|
350
|
+
} else {
|
|
351
|
+
// Get the default branch from origin (main or master)
|
|
352
|
+
const defaultBranch = await getOriginDefaultBranch(directory);
|
|
353
|
+
// Base new branch on origin's default branch to avoid including unrelated commits from HEAD
|
|
354
|
+
// Use --no-track to prevent the new branch from tracking the start-point (main/master)
|
|
355
|
+
await git(directory, `worktree add --no-track -b "${branch}" "${worktreePath}" ${defaultBranch}`);
|
|
356
|
+
}
|
|
357
|
+
return { path: worktreePath, branch };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Get list of untracked files
|
|
362
|
+
* @param {string} directory
|
|
363
|
+
* @returns {Promise<string[]>}
|
|
364
|
+
*/
|
|
365
|
+
export async function getUntrackedFiles(directory) {
|
|
366
|
+
try {
|
|
367
|
+
const output = await git(directory, 'ls-files --others --exclude-standard');
|
|
368
|
+
if (!output) return [];
|
|
369
|
+
return output.split('\n').filter((line) => line.trim());
|
|
370
|
+
} catch {
|
|
371
|
+
return [];
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Get diff for a directory compared to a specific branch
|
|
377
|
+
* @param {string} directory
|
|
378
|
+
* @param {string} branch - Branch to compare against (e.g., 'origin/main')
|
|
379
|
+
* @returns {Promise<string>}
|
|
380
|
+
*/
|
|
381
|
+
export async function getDiffAgainstBranch(directory, branch) {
|
|
382
|
+
try {
|
|
383
|
+
return await git(directory, `diff ${branch}`);
|
|
384
|
+
} catch {
|
|
385
|
+
return '';
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Get staged diff for a directory compared to a specific branch
|
|
391
|
+
* @param {string} directory
|
|
392
|
+
* @param {string} branch - Branch to compare against (e.g., 'origin/main')
|
|
393
|
+
* @returns {Promise<string>}
|
|
394
|
+
*/
|
|
395
|
+
export async function getStagedDiffAgainstBranch(directory, branch) {
|
|
396
|
+
try {
|
|
397
|
+
return await git(directory, `diff --cached ${branch}`);
|
|
398
|
+
} catch {
|
|
399
|
+
return '';
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Get diff between two git refs (e.g., comparing HEAD to origin/main)
|
|
405
|
+
* This shows the committed changes between two refs, ignoring working tree state
|
|
406
|
+
* @param {string} directory
|
|
407
|
+
* @param {string} fromRef - Base ref (e.g., 'origin/main')
|
|
408
|
+
* @param {string} toRef - Target ref (e.g., 'HEAD')
|
|
409
|
+
* @returns {Promise<string>}
|
|
410
|
+
*/
|
|
411
|
+
export async function getDiffBetweenRefs(directory, fromRef, toRef) {
|
|
412
|
+
try {
|
|
413
|
+
return await git(directory, `diff ${fromRef} ${toRef}`);
|
|
414
|
+
} catch {
|
|
415
|
+
return '';
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Get count of files modified/added compared to a branch
|
|
421
|
+
* Includes committed changes + staged + unstaged + untracked files
|
|
422
|
+
* @param {string} directory - The git repository directory
|
|
423
|
+
* @param {string} branch - Branch to compare against (e.g., 'origin/main')
|
|
424
|
+
* @returns {Promise<number>} - Total count of unique files modified/added
|
|
425
|
+
*/
|
|
426
|
+
export async function getModifiedFilesCount(directory, branch) {
|
|
427
|
+
try {
|
|
428
|
+
// Get all modified files in one command using --name-only
|
|
429
|
+
// This includes: committed changes vs branch + staged
|
|
430
|
+
const committedAndStaged = await git(
|
|
431
|
+
directory,
|
|
432
|
+
`diff --name-only ${branch}...HEAD`
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
// Get unstaged changes (working tree vs index)
|
|
436
|
+
const unstaged = await git(directory, 'diff --name-only');
|
|
437
|
+
|
|
438
|
+
// Get untracked files
|
|
439
|
+
const untracked = await getUntrackedFiles(directory);
|
|
440
|
+
|
|
441
|
+
// Combine all files into a Set to get unique count
|
|
442
|
+
const allFiles = new Set();
|
|
443
|
+
|
|
444
|
+
// Parse committed+staged files
|
|
445
|
+
if (committedAndStaged) {
|
|
446
|
+
committedAndStaged.split('\n').forEach(f => {
|
|
447
|
+
if (f.trim()) allFiles.add(f.trim());
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Parse unstaged files
|
|
452
|
+
if (unstaged) {
|
|
453
|
+
unstaged.split('\n').forEach(f => {
|
|
454
|
+
if (f.trim()) allFiles.add(f.trim());
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Add untracked files
|
|
459
|
+
untracked.forEach(f => allFiles.add(f));
|
|
460
|
+
|
|
461
|
+
return allFiles.size;
|
|
462
|
+
} catch (error) {
|
|
463
|
+
logger.warn(`Failed to get modified files count for ${directory}:`, error.message);
|
|
464
|
+
return 0;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Get the git author info from the global config (~/.gitconfig).
|
|
470
|
+
*
|
|
471
|
+
* Uses `--global` so that a contaminated local config (e.g. one that
|
|
472
|
+
* already has Claude Code's identity) is bypassed.
|
|
473
|
+
*
|
|
474
|
+
* @param {string} directory
|
|
475
|
+
* @param {Object} [options]
|
|
476
|
+
* @param {Object} [options.env] - Custom environment variables (useful for tests)
|
|
477
|
+
* @returns {Promise<{name: string, email: string} | null>}
|
|
478
|
+
*/
|
|
479
|
+
export async function getGitAuthor(directory, { env } = {}) {
|
|
480
|
+
try {
|
|
481
|
+
const name = await git(directory, 'config --global user.name', { env });
|
|
482
|
+
const email = await git(directory, 'config --global user.email', { env });
|
|
483
|
+
if (name && email) {
|
|
484
|
+
return { name, email };
|
|
485
|
+
}
|
|
486
|
+
return null;
|
|
487
|
+
} catch {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Pin the human developer's git identity in a worktree's config.
|
|
494
|
+
*
|
|
495
|
+
* Reads user.name/user.email from the main project directory and writes
|
|
496
|
+
* them into the worktree-specific config (--worktree). This ensures the
|
|
497
|
+
* human is always the commit Author, even if the session's environment
|
|
498
|
+
* tries to override it. Claude Code already adds its own Co-Authored-By
|
|
499
|
+
* trailer via its system prompt, so no hook is needed.
|
|
500
|
+
*
|
|
501
|
+
* Only call this for worktree directories, not the main repo.
|
|
502
|
+
*
|
|
503
|
+
* @param {string} worktreePath - The worktree directory
|
|
504
|
+
* @param {string} projectDir - The main project directory (to read author from)
|
|
505
|
+
* @returns {Promise<boolean>} - True if author was pinned
|
|
506
|
+
*/
|
|
507
|
+
export async function pinAuthorInWorktree(worktreePath, projectDir, { env } = {}) {
|
|
508
|
+
const author = await getGitAuthor(projectDir || worktreePath, { env });
|
|
509
|
+
if (!author) return false;
|
|
510
|
+
|
|
511
|
+
// Enable worktree-specific config (required for --worktree flag)
|
|
512
|
+
await git(worktreePath, 'config extensions.worktreeConfig true');
|
|
513
|
+
|
|
514
|
+
// Pin the human's identity in the worktree config so they are always
|
|
515
|
+
// the commit Author, regardless of what the session does later
|
|
516
|
+
await git(worktreePath, `config --worktree user.name "${author.name}"`);
|
|
517
|
+
await git(worktreePath, `config --worktree user.email "${author.email}"`);
|
|
518
|
+
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import * as gitService from './gitService.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Setup git environment for a session based on git mode
|
|
6
|
+
* @param {Object} options
|
|
7
|
+
* @param {string} options.projectDir - Project working directory
|
|
8
|
+
* @param {string|null} options.gitMode - 'branch', 'worktree', or null
|
|
9
|
+
* @param {string|null} options.gitBranch - Branch name
|
|
10
|
+
* @param {string} options.sessionId - Session ID
|
|
11
|
+
* @returns {Promise<{workingDirectory: string, gitWorktree: string|null}>}
|
|
12
|
+
*/
|
|
13
|
+
export async function setupGitForSession({ projectDir, gitMode, gitBranch, sessionId }) {
|
|
14
|
+
// No git operations if gitMode is not specified
|
|
15
|
+
if (!gitMode || !gitBranch) {
|
|
16
|
+
return {
|
|
17
|
+
workingDirectory: projectDir,
|
|
18
|
+
gitWorktree: null,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (gitMode === 'branch') {
|
|
23
|
+
// Checkout (or create) the branch in the project directory
|
|
24
|
+
await gitService.checkoutBranch(projectDir, gitBranch);
|
|
25
|
+
return {
|
|
26
|
+
workingDirectory: projectDir,
|
|
27
|
+
gitWorktree: null,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (gitMode === 'worktree') {
|
|
32
|
+
// Create a worktree in .worktrees/{sessionId}
|
|
33
|
+
const worktreePath = join(projectDir, '.worktrees', sessionId);
|
|
34
|
+
await gitService.createWorktreeForBranch(projectDir, gitBranch, worktreePath);
|
|
35
|
+
// Pin the human developer's git identity so they are the commit Author
|
|
36
|
+
await gitService.pinAuthorInWorktree(worktreePath, projectDir);
|
|
37
|
+
return {
|
|
38
|
+
workingDirectory: worktreePath,
|
|
39
|
+
gitWorktree: worktreePath,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Unknown git mode, fallback to project directory
|
|
44
|
+
return {
|
|
45
|
+
workingDirectory: projectDir,
|
|
46
|
+
gitWorktree: null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Execute a hook script asynchronously (fire-and-forget)
|
|
8
|
+
* @param {string} hookCommand - The shell command to execute
|
|
9
|
+
* @param {string} workingDirectory - The directory to run the command in
|
|
10
|
+
* @param {Object} context - Context variables to pass as environment variables
|
|
11
|
+
* @param {string} context.sessionId - The session ID
|
|
12
|
+
* @param {string} context.projectId - The project ID
|
|
13
|
+
* @param {string} [context.sessionName] - The session name
|
|
14
|
+
*/
|
|
15
|
+
export function executeHookAsync(hookCommand, workingDirectory, context = {}) {
|
|
16
|
+
if (!hookCommand) return;
|
|
17
|
+
|
|
18
|
+
const env = {
|
|
19
|
+
...process.env,
|
|
20
|
+
CIRCUSCHIEF_SESSION_ID: context.sessionId || '',
|
|
21
|
+
CIRCUSCHIEF_PROJECT_ID: context.projectId || '',
|
|
22
|
+
CIRCUSCHIEF_SESSION_NAME: context.sessionName || '',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
execAsync(hookCommand, { cwd: workingDirectory, env, shell: true })
|
|
26
|
+
.then(({ stdout, stderr }) => {
|
|
27
|
+
if (stdout) console.log(`[Hook] stdout: ${stdout.trim()}`);
|
|
28
|
+
if (stderr) console.warn(`[Hook] stderr: ${stderr.trim()}`);
|
|
29
|
+
})
|
|
30
|
+
.catch((error) => {
|
|
31
|
+
console.error(`[Hook] Execution failed: ${error.message}`);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Execute a hook script and wait for the result
|
|
37
|
+
* @param {string} hookCommand - The shell command to execute
|
|
38
|
+
* @param {string} workingDirectory - The directory to run the command in
|
|
39
|
+
* @param {Object} context - Context variables to pass as environment variables
|
|
40
|
+
* @returns {Promise<{success: boolean, stdout?: string, stderr?: string, error?: string}>}
|
|
41
|
+
*/
|
|
42
|
+
export async function executeHook(hookCommand, workingDirectory, context = {}) {
|
|
43
|
+
if (!hookCommand) {
|
|
44
|
+
return { success: true, stdout: '', stderr: '' };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const env = {
|
|
48
|
+
...process.env,
|
|
49
|
+
CIRCUSCHIEF_SESSION_ID: context.sessionId || '',
|
|
50
|
+
CIRCUSCHIEF_PROJECT_ID: context.projectId || '',
|
|
51
|
+
CIRCUSCHIEF_SESSION_NAME: context.sessionName || '',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const { stdout, stderr } = await execAsync(hookCommand, { cwd: workingDirectory, env, shell: true });
|
|
56
|
+
return { success: true, stdout: stdout.trim(), stderr: stderr.trim() };
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return { success: false, error: error.message };
|
|
59
|
+
}
|
|
60
|
+
}
|