borgmcp 1.1.15 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +15 -0
- package/LICENSE +201 -33
- package/NOTICE +2 -0
- package/README.md +85 -36
- package/SECURITY.md +7 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/agent-runtime.js +63 -1
- package/dist/agent-runtime.js.map +1 -0
- package/dist/assimilate-cmd.d.ts +96 -17
- package/dist/assimilate-cmd.d.ts.map +1 -0
- package/dist/assimilate-cmd.js +1396 -48
- package/dist/assimilate-cmd.js.map +1 -0
- package/dist/assimilate-deps.d.ts.map +1 -0
- package/dist/assimilate-deps.js +384 -3
- package/dist/assimilate-deps.js.map +1 -0
- package/dist/assimilate-guard.d.ts +5 -7
- package/dist/assimilate-guard.d.ts.map +1 -0
- package/dist/assimilate-guard.js +60 -2
- package/dist/assimilate-guard.js.map +1 -0
- package/dist/assimilate-welcome.d.ts +1 -1
- package/dist/assimilate-welcome.d.ts.map +1 -0
- package/dist/assimilate-welcome.js +32 -2
- package/dist/assimilate-welcome.js.map +1 -0
- package/dist/auth-env.d.ts +7 -50
- package/dist/auth-env.d.ts.map +1 -0
- package/dist/auth-env.js +22 -1
- package/dist/auth-env.js.map +1 -0
- package/dist/backends/launch-all-pastelist.d.ts.map +1 -0
- package/dist/backends/launch-all-pastelist.js +11 -4
- package/dist/backends/launch-all-pastelist.js.map +1 -0
- package/dist/backends/launch-all-tmux.d.ts.map +1 -0
- package/dist/backends/launch-all-tmux.js +49 -1
- package/dist/backends/launch-all-tmux.js.map +1 -0
- package/dist/backends/launch-all-windows.d.ts.map +1 -0
- package/dist/backends/launch-all-windows.js +86 -12
- package/dist/backends/launch-all-windows.js.map +1 -0
- package/dist/bare-launch-menu.d.ts.map +1 -0
- package/dist/bare-launch-menu.js +83 -6
- package/dist/bare-launch-menu.js.map +1 -0
- package/dist/claude-launch-args.d.ts.map +1 -0
- package/dist/claude-launch-args.js +34 -1
- package/dist/claude-launch-args.js.map +1 -0
- package/dist/claude.d.ts.map +1 -0
- package/dist/claude.js +486 -14
- package/dist/claude.js.map +1 -0
- package/dist/cleanup-cmd.d.ts +5 -5
- package/dist/cleanup-cmd.d.ts.map +1 -0
- package/dist/cleanup-cmd.js +453 -14
- package/dist/cleanup-cmd.js.map +1 -0
- package/dist/clear-rewake-core.d.ts.map +1 -0
- package/dist/clear-rewake-core.js +23 -2
- package/dist/clear-rewake-core.js.map +1 -0
- package/dist/clear-rewake.d.ts.map +1 -0
- package/dist/clear-rewake.js +19 -1
- package/dist/clear-rewake.js.map +1 -0
- package/dist/cli-help.d.ts +5 -0
- package/dist/cli-help.d.ts.map +1 -0
- package/dist/cli-help.js +104 -56
- package/dist/cli-help.js.map +1 -0
- package/dist/cli-platform.d.ts.map +1 -0
- package/dist/cli-platform.js +106 -4
- package/dist/cli-platform.js.map +1 -0
- package/dist/cli-tool-approval.d.ts +84 -0
- package/dist/cli-tool-approval.d.ts.map +1 -0
- package/dist/cli-tool-approval.js +583 -0
- package/dist/cli-tool-approval.js.map +1 -0
- package/dist/codex-app-server.d.ts.map +1 -0
- package/dist/codex-app-server.js +228 -4
- package/dist/codex-app-server.js.map +1 -0
- package/dist/codex-app-wake.d.ts.map +1 -0
- package/dist/codex-app-wake.js +432 -2
- package/dist/codex-app-wake.js.map +1 -0
- package/dist/codex-launch.d.ts.map +1 -0
- package/dist/codex-launch.js +102 -2
- package/dist/codex-launch.js.map +1 -0
- package/dist/codex-remote.d.ts +6 -1
- package/dist/codex-remote.d.ts.map +1 -0
- package/dist/codex-remote.js +394 -1
- package/dist/codex-remote.js.map +1 -0
- package/dist/codex-wake-resolve.d.ts.map +1 -0
- package/dist/codex-wake-resolve.js +127 -1
- package/dist/codex-wake-resolve.js.map +1 -0
- package/dist/config-utils.d.ts +2 -2
- package/dist/config-utils.d.ts.map +1 -0
- package/dist/config-utils.js +766 -3
- package/dist/config-utils.js.map +1 -0
- package/dist/config.d.ts +98 -61
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +426 -1
- package/dist/config.js.map +1 -0
- package/dist/console-prefix.d.ts.map +1 -0
- package/dist/console-prefix.js +96 -1
- package/dist/console-prefix.js.map +1 -0
- package/dist/cube-name.d.ts.map +1 -0
- package/dist/cube-name.js +67 -1
- package/dist/cube-name.js.map +1 -0
- package/dist/cubes.d.ts +126 -18
- package/dist/cubes.d.ts.map +1 -0
- package/dist/cubes.js +412 -4
- package/dist/cubes.js.map +1 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +71 -1
- package/dist/debug.js.map +1 -0
- package/dist/direct-log.d.ts.map +1 -0
- package/dist/direct-log.js +11 -1
- package/dist/direct-log.js.map +1 -0
- package/dist/docs-sections.d.ts +11 -10
- package/dist/docs-sections.d.ts.map +1 -0
- package/dist/docs-sections.js +113 -3
- package/dist/docs-sections.js.map +1 -0
- package/dist/drone-lifecycle.d.ts +5 -21
- package/dist/drone-lifecycle.d.ts.map +1 -0
- package/dist/drone-lifecycle.js +57 -8
- package/dist/drone-lifecycle.js.map +1 -0
- package/dist/drone-management.d.ts +29 -0
- package/dist/drone-management.d.ts.map +1 -0
- package/dist/drone-management.js +109 -0
- package/dist/drone-management.js.map +1 -0
- package/dist/ensure-mcp-config.d.ts.map +1 -0
- package/dist/ensure-mcp-config.js +36 -1
- package/dist/ensure-mcp-config.js.map +1 -0
- package/dist/evict-drone.d.ts +10 -2
- package/dist/evict-drone.d.ts.map +1 -0
- package/dist/evict-drone.js +60 -1
- package/dist/evict-drone.js.map +1 -0
- package/dist/gc-orphan-inboxes.d.ts.map +1 -0
- package/dist/gc-orphan-inboxes.js +177 -1
- package/dist/gc-orphan-inboxes.js.map +1 -0
- package/dist/inbox-monitor.d.ts +1 -1
- package/dist/inbox-monitor.d.ts.map +1 -0
- package/dist/inbox-monitor.js +856 -3
- package/dist/inbox-monitor.js.map +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1034 -37
- package/dist/index.js.map +1 -0
- package/dist/launch-all-cmd.d.ts.map +1 -0
- package/dist/launch-all-cmd.js +377 -37
- package/dist/launch-all-cmd.js.map +1 -0
- package/dist/launch-all-command.d.ts.map +1 -0
- package/dist/launch-all-command.js +24 -1
- package/dist/launch-all-command.js.map +1 -0
- package/dist/launch-all-deps.d.ts +3 -8
- package/dist/launch-all-deps.d.ts.map +1 -0
- package/dist/launch-all-deps.js +99 -1
- package/dist/launch-all-deps.js.map +1 -0
- package/dist/launch-all-discovery.d.ts +1 -0
- package/dist/launch-all-discovery.d.ts.map +1 -0
- package/dist/launch-all-discovery.js +90 -4
- package/dist/launch-all-discovery.js.map +1 -0
- package/dist/launch-all-locks.d.ts.map +1 -0
- package/dist/launch-all-locks.js +54 -1
- package/dist/launch-all-locks.js.map +1 -0
- package/dist/launch-gate.d.ts +2 -2
- package/dist/launch-gate.d.ts.map +1 -0
- package/dist/launch-gate.js +73 -1
- package/dist/launch-gate.js.map +1 -0
- package/dist/lifecycle-log-guard.d.ts.map +1 -0
- package/dist/lifecycle-log-guard.js +93 -2
- package/dist/lifecycle-log-guard.js.map +1 -0
- package/dist/list-roles-render.d.ts.map +1 -0
- package/dist/list-roles-render.js +40 -6
- package/dist/list-roles-render.js.map +1 -0
- package/dist/local-log-routing.d.ts +19 -0
- package/dist/local-log-routing.d.ts.map +1 -0
- package/dist/local-log-routing.js +67 -0
- package/dist/local-log-routing.js.map +1 -0
- package/dist/local-manage-tool-result.d.ts +8 -0
- package/dist/local-manage-tool-result.d.ts.map +1 -0
- package/dist/local-manage-tool-result.js +10 -0
- package/dist/local-manage-tool-result.js.map +1 -0
- package/dist/local-server-cursor.d.ts +34 -0
- package/dist/local-server-cursor.d.ts.map +1 -0
- package/dist/local-server-cursor.js +151 -0
- package/dist/local-server-cursor.js.map +1 -0
- package/dist/log-audit.d.ts.map +1 -0
- package/dist/log-audit.js +194 -3
- package/dist/log-audit.js.map +1 -0
- package/dist/log-stream.d.ts +31 -18
- package/dist/log-stream.d.ts.map +1 -0
- package/dist/log-stream.js +1214 -12
- package/dist/log-stream.js.map +1 -0
- package/dist/model-presets.d.ts.map +1 -0
- package/dist/model-presets.js +19 -1
- package/dist/model-presets.js.map +1 -0
- package/dist/name-validator.d.ts.map +1 -0
- package/dist/name-validator.js +22 -1
- package/dist/name-validator.js.map +1 -0
- package/dist/opencode-drone.d.ts.map +1 -0
- package/dist/opencode-drone.js +438 -4
- package/dist/opencode-drone.js.map +1 -0
- package/dist/opencode-plugin.d.ts +1 -1
- package/dist/opencode-plugin.d.ts.map +1 -0
- package/dist/opencode-plugin.js +29 -4
- package/dist/opencode-plugin.js.map +1 -0
- package/dist/parse-assimilate-args.d.ts +1 -1
- package/dist/parse-assimilate-args.d.ts.map +1 -0
- package/dist/parse-assimilate-args.js +125 -1
- package/dist/parse-assimilate-args.js.map +1 -0
- package/dist/parse-launch-all-args.d.ts.map +1 -0
- package/dist/parse-launch-all-args.js +70 -1
- package/dist/parse-launch-all-args.js.map +1 -0
- package/dist/postinstall-banner.d.ts.map +1 -0
- package/dist/postinstall-banner.js +27 -2
- package/dist/postinstall-banner.js.map +1 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +32 -5
- package/dist/postinstall.js.map +1 -0
- package/dist/readiness-probe.d.ts.map +1 -0
- package/dist/readiness-probe.js +9 -1
- package/dist/readiness-probe.js.map +1 -0
- package/dist/regen-format.d.ts.map +1 -0
- package/dist/regen-format.js +545 -13
- package/dist/regen-format.js.map +1 -0
- package/dist/regen.d.ts.map +1 -0
- package/dist/regen.js +141 -5
- package/dist/regen.js.map +1 -0
- package/dist/remote-client.d.ts +65 -175
- package/dist/remote-client.d.ts.map +1 -0
- package/dist/remote-client.js +942 -1
- package/dist/remote-client.js.map +1 -0
- package/dist/reset-local-seat-cmd.d.ts +50 -0
- package/dist/reset-local-seat-cmd.d.ts.map +1 -0
- package/dist/reset-local-seat-cmd.js +169 -0
- package/dist/reset-local-seat-cmd.js.map +1 -0
- package/dist/role-match.d.ts.map +1 -0
- package/dist/role-match.js +23 -1
- package/dist/role-match.js.map +1 -0
- package/dist/role-resolver.d.ts.map +1 -0
- package/dist/role-resolver.js +60 -1
- package/dist/role-resolver.js.map +1 -0
- package/dist/roster-render.d.ts.map +1 -0
- package/dist/roster-render.js +121 -3
- package/dist/roster-render.js.map +1 -0
- package/dist/seat-probe.d.ts +32 -13
- package/dist/seat-probe.d.ts.map +1 -0
- package/dist/seat-probe.js +78 -1
- package/dist/seat-probe.js.map +1 -0
- package/dist/seat-store.d.ts +76 -0
- package/dist/seat-store.d.ts.map +1 -0
- package/dist/seat-store.js +309 -0
- package/dist/seat-store.js.map +1 -0
- package/dist/seats.d.ts +303 -0
- package/dist/seats.d.ts.map +1 -0
- package/dist/seats.js +551 -0
- package/dist/seats.js.map +1 -0
- package/dist/self-path.d.ts +27 -0
- package/dist/self-path.d.ts.map +1 -0
- package/dist/self-path.js +43 -0
- package/dist/self-path.js.map +1 -0
- package/dist/server-endpoint.d.ts +9 -0
- package/dist/server-endpoint.d.ts.map +1 -0
- package/dist/server-endpoint.js +38 -0
- package/dist/server-endpoint.js.map +1 -0
- package/dist/server-errors.d.ts +50 -0
- package/dist/server-errors.d.ts.map +1 -0
- package/dist/server-errors.js +79 -0
- package/dist/server-errors.js.map +1 -0
- package/dist/server-handshake.d.ts +197 -0
- package/dist/server-handshake.d.ts.map +1 -0
- package/dist/server-handshake.js +505 -0
- package/dist/server-handshake.js.map +1 -0
- package/dist/server-response.d.ts +3 -0
- package/dist/server-response.d.ts.map +1 -0
- package/dist/server-response.js +67 -0
- package/dist/server-response.js.map +1 -0
- package/dist/server-trust.d.ts +14 -0
- package/dist/server-trust.d.ts.map +1 -0
- package/dist/server-trust.js +225 -0
- package/dist/server-trust.js.map +1 -0
- package/dist/setup-confirm.d.ts +3 -3
- package/dist/setup-confirm.d.ts.map +1 -0
- package/dist/setup-confirm.js +126 -2
- package/dist/setup-confirm.js.map +1 -0
- package/dist/setup.d.ts +5 -3
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +169 -46
- package/dist/setup.js.map +1 -0
- package/dist/shell-escape.d.ts.map +1 -0
- package/dist/shell-escape.js +22 -1
- package/dist/shell-escape.js.map +1 -0
- package/dist/spawn.d.ts +2 -2
- package/dist/spawn.d.ts.map +1 -0
- package/dist/spawn.js +29 -10
- package/dist/spawn.js.map +1 -0
- package/dist/startup-services.d.ts +0 -1
- package/dist/startup-services.d.ts.map +1 -0
- package/dist/startup-services.js +26 -1
- package/dist/startup-services.js.map +1 -0
- package/dist/stream-owner.d.ts +6 -2
- package/dist/stream-owner.d.ts.map +1 -0
- package/dist/stream-owner.js +547 -2
- package/dist/stream-owner.js.map +1 -0
- package/dist/stream-status.d.ts +1 -1
- package/dist/stream-status.d.ts.map +1 -0
- package/dist/stream-status.js +268 -3
- package/dist/stream-status.js.map +1 -0
- package/dist/sync-roles-render.d.ts.map +1 -0
- package/dist/sync-roles-render.js +118 -3
- package/dist/sync-roles-render.js.map +1 -0
- package/dist/sync.d.ts +1 -1
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +286 -22
- package/dist/sync.js.map +1 -0
- package/dist/terminal-title.d.ts.map +1 -0
- package/dist/terminal-title.js +68 -1
- package/dist/terminal-title.js.map +1 -0
- package/dist/token-store.d.ts +17 -111
- package/dist/token-store.d.ts.map +1 -0
- package/dist/token-store.js +81 -1
- package/dist/token-store.js.map +1 -0
- package/dist/tool-manifest.d.ts +4 -8
- package/dist/tool-manifest.d.ts.map +1 -0
- package/dist/tool-manifest.js +554 -1
- package/dist/tool-manifest.js.map +1 -0
- package/dist/tool-scope.d.ts +8 -12
- package/dist/tool-scope.d.ts.map +1 -0
- package/dist/tool-scope.js +97 -1
- package/dist/tool-scope.js.map +1 -0
- package/dist/types.d.ts +1 -5
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/unknown-subcommand.d.ts +1 -1
- package/dist/unknown-subcommand.d.ts.map +1 -0
- package/dist/unknown-subcommand.js +37 -1
- package/dist/unknown-subcommand.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +78 -2
- package/dist/version.js.map +1 -0
- package/dist/working-repo.d.ts.map +1 -0
- package/dist/working-repo.js +81 -1
- package/dist/working-repo.js.map +1 -0
- package/dist/worktree-lifecycle.d.ts.map +1 -0
- package/dist/worktree-lifecycle.js +205 -2
- package/dist/worktree-lifecycle.js.map +1 -0
- package/docs/EXTRACTION_PROVENANCE.md +40 -0
- package/docs/LOCAL_SERVER.md +103 -0
- package/docs/RELEASING.md +120 -0
- package/package.json +52 -34
- package/src/agent-runtime.ts +69 -0
- package/src/assimilate-cmd.ts +1864 -0
- package/src/assimilate-deps.ts +444 -0
- package/src/assimilate-guard.ts +84 -0
- package/src/assimilate-welcome.ts +39 -0
- package/src/auth-env.ts +21 -0
- package/src/backends/launch-all-pastelist.ts +18 -0
- package/src/backends/launch-all-tmux.ts +75 -0
- package/src/backends/launch-all-windows.ts +110 -0
- package/src/bare-launch-menu.ts +148 -0
- package/src/claude-launch-args.ts +35 -0
- package/src/claude.ts +561 -0
- package/src/cleanup-cmd.ts +581 -0
- package/src/clear-rewake-core.ts +34 -0
- package/src/clear-rewake.ts +18 -0
- package/src/cli-help.ts +116 -0
- package/src/cli-platform.ts +127 -0
- package/src/cli-tool-approval.ts +716 -0
- package/src/codex-app-server.ts +238 -0
- package/src/codex-app-wake.ts +512 -0
- package/src/codex-launch.ts +140 -0
- package/src/codex-remote.ts +500 -0
- package/src/codex-wake-resolve.ts +155 -0
- package/src/config-utils.ts +811 -0
- package/src/config.ts +595 -0
- package/src/console-prefix.ts +99 -0
- package/src/cube-name.ts +69 -0
- package/src/cubes.ts +583 -0
- package/src/debug.ts +73 -0
- package/src/direct-log.ts +9 -0
- package/src/docs-sections.ts +129 -0
- package/src/drone-lifecycle.ts +62 -0
- package/src/drone-management.ts +162 -0
- package/src/ensure-mcp-config.ts +54 -0
- package/src/evict-drone.ts +82 -0
- package/src/gc-orphan-inboxes.ts +229 -0
- package/src/inbox-monitor.ts +1005 -0
- package/src/index.ts +1201 -0
- package/src/launch-all-cmd.ts +450 -0
- package/src/launch-all-command.ts +30 -0
- package/src/launch-all-deps.ts +188 -0
- package/src/launch-all-discovery.ts +118 -0
- package/src/launch-all-locks.ts +74 -0
- package/src/launch-gate.ts +80 -0
- package/src/lifecycle-log-guard.ts +144 -0
- package/src/list-roles-render.ts +52 -0
- package/src/local-log-routing.ts +106 -0
- package/src/local-manage-tool-result.ts +10 -0
- package/src/local-server-cursor.ts +188 -0
- package/src/log-audit.ts +207 -0
- package/src/log-stream.ts +1505 -0
- package/src/model-presets.ts +22 -0
- package/src/name-validator.ts +23 -0
- package/src/opencode-drone.ts +528 -0
- package/src/opencode-plugin.ts +38 -0
- package/src/parse-assimilate-args.ts +125 -0
- package/src/parse-launch-all-args.ts +100 -0
- package/src/postinstall-banner.ts +37 -0
- package/src/postinstall.ts +36 -0
- package/src/readiness-probe.ts +12 -0
- package/src/regen-format.ts +651 -0
- package/src/regen.ts +150 -0
- package/src/remote-client.ts +1379 -0
- package/src/reset-local-seat-cmd.ts +227 -0
- package/src/role-match.ts +29 -0
- package/src/role-resolver.ts +93 -0
- package/src/roster-render.ts +196 -0
- package/src/seat-probe.ts +115 -0
- package/src/seat-store.ts +333 -0
- package/src/seats.ts +741 -0
- package/src/self-path.ts +51 -0
- package/src/server-endpoint.ts +38 -0
- package/src/server-errors.ts +93 -0
- package/src/server-handshake.ts +783 -0
- package/src/server-response.ts +76 -0
- package/src/server-trust.ts +245 -0
- package/src/setup-confirm.ts +171 -0
- package/src/setup.ts +186 -0
- package/src/shell-escape.ts +22 -0
- package/src/spawn.ts +31 -0
- package/src/startup-services.ts +36 -0
- package/src/stream-owner.ts +686 -0
- package/src/stream-status.ts +337 -0
- package/src/sync-roles-render.ts +163 -0
- package/src/sync.ts +363 -0
- package/src/terminal-title.ts +76 -0
- package/src/token-store.ts +102 -0
- package/src/tool-manifest.ts +610 -0
- package/src/tool-scope.ts +110 -0
- package/src/types.ts +5 -0
- package/src/unknown-subcommand.ts +35 -0
- package/src/version.ts +82 -0
- package/src/working-repo.ts +99 -0
- package/src/worktree-lifecycle.ts +254 -0
- package/dist/auth-recovery.d.ts +0 -33
- package/dist/auth-recovery.js +0 -1
- package/dist/auth.d.ts +0 -103
- package/dist/auth.js +0 -39
- package/dist/device-auth.d.ts +0 -75
- package/dist/device-auth.js +0 -1
- package/dist/drone-address.d.ts +0 -26
- package/dist/drone-address.js +0 -1
- package/dist/get-started.d.ts +0 -20
- package/dist/get-started.js +0 -2
- package/dist/health-beat.d.ts +0 -134
- package/dist/health-beat.js +0 -1
- package/dist/role-section.d.ts +0 -110
- package/dist/role-section.js +0 -8
- package/dist/setup-action.d.ts +0 -20
- package/dist/setup-action.js +0 -1
- package/dist/stale-version-check.d.ts +0 -40
- package/dist/stale-version-check.js +0 -1
- package/dist/subscription-retry.d.ts +0 -40
- package/dist/subscription-retry.js +0 -1
- package/dist/templates.d.ts +0 -107
- package/dist/templates.js +0 -359
- package/dist/token-crypto.d.ts +0 -50
- package/dist/token-crypto.js +0 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gh#899: role-scope the NATIVE MCP tool surface to cut the per-call tool tax
|
|
3
|
+
* for simpler models. This is a token-surface UX / context optimization ONLY —
|
|
4
|
+
* it carries ZERO authorization semantics. Server enforcement uses the selected
|
|
5
|
+
* local client's live per-cube `read|write|manage` grant, independent of which
|
|
6
|
+
* tool schemas are pre-loaded. Reaching a management tool through the dispatcher
|
|
7
|
+
* bypasses no server check because it uses the identical CallTool path.
|
|
8
|
+
* Never treat this filter as a security boundary.
|
|
9
|
+
*
|
|
10
|
+
* Deferred (filtered-out) tools are never lost — they remain reachable through
|
|
11
|
+
* the always-present dispatcher (`borg_tool` / `borg_describe-tool`), so a
|
|
12
|
+
* scoped role can never dead-end.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Always-present escape hatch — reaches any borg tool regardless of scope. */
|
|
16
|
+
export const DISPATCHER_TOOLS = ['borg_tool', 'borg_describe-tool'] as const;
|
|
17
|
+
|
|
18
|
+
/** Every role needs these; pre-loaded natively for all roles. */
|
|
19
|
+
export const UNIVERSAL_TOOLS = [
|
|
20
|
+
'borg_regen',
|
|
21
|
+
'borg_log',
|
|
22
|
+
'borg_read-log',
|
|
23
|
+
'borg_roster',
|
|
24
|
+
'borg_stream-status',
|
|
25
|
+
'borg_whoami',
|
|
26
|
+
'borg_ack',
|
|
27
|
+
'borg_version',
|
|
28
|
+
'borg_cube',
|
|
29
|
+
'borg_role',
|
|
30
|
+
'borg_role-rationale',
|
|
31
|
+
'borg_assimilate',
|
|
32
|
+
'borg_playbook', // gh#912: on-demand operating-playbook chapter — every role needs it
|
|
33
|
+
'borg_docs', // gh#docs-site B: in-product docs lookup — every role can answer "how does borgmcp work"
|
|
34
|
+
...DISPATCHER_TOOLS,
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
/** Cube/role/drone management — native for management seats, deferred for workers. */
|
|
38
|
+
export const MANAGEMENT_TOOLS = [
|
|
39
|
+
'borg_create-cube',
|
|
40
|
+
'borg_update-cube',
|
|
41
|
+
'borg_delete-cube',
|
|
42
|
+
'borg_create-role',
|
|
43
|
+
'borg_update-role',
|
|
44
|
+
'borg_delete-role',
|
|
45
|
+
'borg_patch-role-section',
|
|
46
|
+
'borg_patch-taxonomy-class',
|
|
47
|
+
'borg_reassign-drone',
|
|
48
|
+
'borg_evict-drone',
|
|
49
|
+
'borg_sync-roles',
|
|
50
|
+
'borg_apply-template',
|
|
51
|
+
'borg_list-cubes',
|
|
52
|
+
'borg_list-drones',
|
|
53
|
+
'borg_list-roles',
|
|
54
|
+
'borg_list-templates',
|
|
55
|
+
'borg_remove-decision',
|
|
56
|
+
] as const;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Highest-stakes subset of the management set — filtered from a worker's native
|
|
60
|
+
* surface for context economy only. NOT an auth list: server access is enforced
|
|
61
|
+
* by live per-client cube grants (`read|write|manage`) and is independent
|
|
62
|
+
* of this client-side UX filter. Role labels never grant server permission.
|
|
63
|
+
* Named here only to focus reviewer attention.
|
|
64
|
+
*/
|
|
65
|
+
export const AUTH_SENSITIVE_TOOLS = [
|
|
66
|
+
'borg_evict-drone',
|
|
67
|
+
'borg_delete-cube',
|
|
68
|
+
'borg_delete-role',
|
|
69
|
+
'borg_reassign-drone',
|
|
70
|
+
] as const;
|
|
71
|
+
|
|
72
|
+
export interface RoleScope {
|
|
73
|
+
/** The drone's role name, as persisted at assimilate. Absent → role unknown. */
|
|
74
|
+
roleName?: string | null;
|
|
75
|
+
roleClass?: 'queen' | 'worker' | null;
|
|
76
|
+
isHumanSeat?: boolean | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Management seats (human-seat roles + queen-class) get the full surface. */
|
|
80
|
+
export function isManagementSeat(scope: RoleScope): boolean {
|
|
81
|
+
return scope.isHumanSeat === true || scope.roleClass === 'queen';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Names to HIDE from the native surface for this scope. Empty (hide nothing)
|
|
86
|
+
* when the role is unknown (no roleName — old cubes.json entry or pre-assimilate)
|
|
87
|
+
* or the caller is a management seat. Only a known worker role defers the
|
|
88
|
+
* management + billing sets. Fail-safe: anything not explicitly in a deferred
|
|
89
|
+
* set stays native.
|
|
90
|
+
*/
|
|
91
|
+
export function deferredToolNames(scope: RoleScope | null): Set<string> {
|
|
92
|
+
if (!scope || !scope.roleName) return new Set(); // role unknown → full set
|
|
93
|
+
if (isManagementSeat(scope)) return new Set(); // management seat → full set
|
|
94
|
+
return new Set<string>([...MANAGEMENT_TOOLS]); // worker → defer
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Filter a tool-definition array down to the native surface for this scope.
|
|
99
|
+
* Tools whose name is in the deferred set are removed; everything else
|
|
100
|
+
* (universal, dispatcher, unmapped) stays. The deferred tools remain reachable
|
|
101
|
+
* via the dispatcher.
|
|
102
|
+
*/
|
|
103
|
+
export function filterToolsForRole<T extends { name: string }>(
|
|
104
|
+
tools: T[],
|
|
105
|
+
scope: RoleScope | null
|
|
106
|
+
): T[] {
|
|
107
|
+
const hidden = deferredToolNames(scope);
|
|
108
|
+
if (hidden.size === 0) return tools;
|
|
109
|
+
return tools.filter((t) => !hidden.has(t.name));
|
|
110
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gh#911: top-level CLI footgun guard.
|
|
3
|
+
*
|
|
4
|
+
* `borg <unknown>` (e.g. `borg evict-drone X`) used to fall through the
|
|
5
|
+
* subcommand router, get ignored by parseCliFlag (which validates only
|
|
6
|
+
* `--cli`), and silently LAUNCH a Claude Code session with the typo'd word as
|
|
7
|
+
* its prompt — surprising + wasteful, and the trailing args were dropped.
|
|
8
|
+
*
|
|
9
|
+
* This pure predicate lets claude.ts reject an unknown NON-FLAG argv[2] before
|
|
10
|
+
* the default launch. Bare `borg` (no argv[2]) and recognized flags
|
|
11
|
+
* (`--cli`, `--remote`, agent passthrough, …) still fall through to launch.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** The subcommands the claude.ts router dispatches on (lines 107-176). */
|
|
15
|
+
export const KNOWN_SUBCOMMANDS = [
|
|
16
|
+
'setup',
|
|
17
|
+
'assimilate',
|
|
18
|
+
'reset-local-seat',
|
|
19
|
+
'spawn',
|
|
20
|
+
'sync',
|
|
21
|
+
'cleanup',
|
|
22
|
+
'launch-all',
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Returns the offending command string when argv[2] is an unknown non-flag
|
|
27
|
+
* positional (caller should error + exit 1), or null when it's bare `borg`,
|
|
28
|
+
* a flag, or a known subcommand (caller falls through to existing handling).
|
|
29
|
+
*/
|
|
30
|
+
export function unknownSubcommand(argv2: string | undefined): string | null {
|
|
31
|
+
if (argv2 === undefined) return null; // bare `borg`
|
|
32
|
+
if (argv2.startsWith('-')) return null; // a flag → handled downstream
|
|
33
|
+
if ((KNOWN_SUBCOMMANDS as readonly string[]).includes(argv2)) return null;
|
|
34
|
+
return argv2; // unknown non-flag positional → footgun
|
|
35
|
+
}
|
package/src/version.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime version reader.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the borgmcp client version — read at runtime
|
|
5
|
+
* from `package.json` relative to `import.meta.url`, NOT hardcoded.
|
|
6
|
+
*
|
|
7
|
+
* Consumers:
|
|
8
|
+
* - `index.ts` — passed into the MCP `Server({ name, version })`
|
|
9
|
+
* constructor so Claude Code's `/mcp` view shows the real version
|
|
10
|
+
* instead of the long-standing hardcoded "0.1.0".
|
|
11
|
+
* - `claude.ts` / `setup.ts` / `regen.ts` / `log-audit.ts` — each
|
|
12
|
+
* binary supports a `--version` flag that prints `borgmcp X.Y.Z`
|
|
13
|
+
* and exits 0 before any side-effecting work begins.
|
|
14
|
+
*
|
|
15
|
+
* Implementation notes:
|
|
16
|
+
* - Uses `readFileSync` on the resolved path relative to this module's
|
|
17
|
+
* `import.meta.url`. The compiled `dist/version.js` sits one level
|
|
18
|
+
* above `package.json` at the package root, so `../package.json` is
|
|
19
|
+
* the relative resolution. This works under both `node dist/...`
|
|
20
|
+
* and `npm run start`; the path resolution is independent of CWD.
|
|
21
|
+
* - Result is cached at module-eval time. The package.json is part of
|
|
22
|
+
* the published tarball and immutable for any given install.
|
|
23
|
+
* - Falls back to `'unknown'` if the read fails (corrupted install,
|
|
24
|
+
* someone deleted package.json, etc.) — never throws, so a fresh
|
|
25
|
+
* `--version` invocation can't kill a CLI launch.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { readFileSync } from 'node:fs';
|
|
29
|
+
import { fileURLToPath } from 'node:url';
|
|
30
|
+
import { dirname, join } from 'node:path';
|
|
31
|
+
|
|
32
|
+
function readPackageVersion(): string {
|
|
33
|
+
try {
|
|
34
|
+
const here = fileURLToPath(import.meta.url);
|
|
35
|
+
const pkgPath = join(dirname(here), '..', 'package.json');
|
|
36
|
+
const raw = readFileSync(pkgPath, 'utf-8');
|
|
37
|
+
const parsed = JSON.parse(raw);
|
|
38
|
+
if (typeof parsed.version === 'string' && parsed.version.length > 0) {
|
|
39
|
+
return parsed.version;
|
|
40
|
+
}
|
|
41
|
+
return 'unknown';
|
|
42
|
+
} catch {
|
|
43
|
+
return 'unknown';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const VERSION = readPackageVersion();
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Return the installed borgmcp version (the same string as
|
|
51
|
+
* `client/package.json`'s `version` field). Cached at module load.
|
|
52
|
+
*/
|
|
53
|
+
export function getPackageVersion(): string {
|
|
54
|
+
return VERSION;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Standard `--version` handler — call near the top of any CLI entry
|
|
59
|
+
* point. If `process.argv` contains `--version` or `-v`, prints
|
|
60
|
+
* `borgmcp X.Y.Z` to stdout and exits 0. Otherwise returns silently
|
|
61
|
+
* so the caller can continue with normal CLI work.
|
|
62
|
+
*
|
|
63
|
+
* Examples:
|
|
64
|
+
* - `borg --version` → "borgmcp 0.6.0"
|
|
65
|
+
* - `borg-mcp -v` → "borgmcp 0.6.0"
|
|
66
|
+
* - `borg assimilate` → continues to assimilation flow
|
|
67
|
+
* - `borg-setup` → continues to interactive setup wizard
|
|
68
|
+
*/
|
|
69
|
+
/**
|
|
70
|
+
* gh#285: read the on-disk package.json version fresh (not cached).
|
|
71
|
+
* Used by the regen handler to detect post-upgrade version mismatch.
|
|
72
|
+
*/
|
|
73
|
+
export function getOnDiskVersion(): string {
|
|
74
|
+
return readPackageVersion();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function handleVersionFlag(): void {
|
|
78
|
+
if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
79
|
+
process.stdout.write(`borgmcp ${VERSION}\n`);
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the repository identity for the directory hosting this MCP child.
|
|
3
|
+
*
|
|
4
|
+
* The report carries only a canonical repository identity (`host/org/repo`)
|
|
5
|
+
* and its derived name. Local filesystem paths and raw remote URLs are never
|
|
6
|
+
* included in lifecycle metadata.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
export interface WorkingRepo {
|
|
11
|
+
name: string | null;
|
|
12
|
+
/** Canonical host/path identity, never a raw Git remote URL. */
|
|
13
|
+
origin: string | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface WorkingRepoDeps {
|
|
17
|
+
runGit?: (
|
|
18
|
+
cwd: string,
|
|
19
|
+
args: string[]
|
|
20
|
+
) => { status: number | null; stdout?: string | null };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function defaultRunGit(cwd: string, args: string[]) {
|
|
24
|
+
const result = spawnSync('git', args, { cwd, encoding: 'utf-8' });
|
|
25
|
+
return { status: result.status, stdout: result.stdout };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function trimmed(value: string | null | undefined): string | null {
|
|
29
|
+
const normalized = value?.trim();
|
|
30
|
+
return normalized ? normalized : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function nameFromIdentity(identity: string): string | null {
|
|
34
|
+
const lastPathSegment = identity.replace(/\/$/, '').split('/').pop();
|
|
35
|
+
const name = lastPathSegment?.replace(/\.git$/i, '').trim();
|
|
36
|
+
return name || null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convert a Git remote to a non-secret `host/org/repo` identity.
|
|
41
|
+
*
|
|
42
|
+
* URL userinfo, query strings, fragments, scheme, and SCP-style user prefixes
|
|
43
|
+
* are deliberately discarded. Inputs that cannot identify a host and path are
|
|
44
|
+
* treated as unreportable rather than forwarded verbatim.
|
|
45
|
+
*/
|
|
46
|
+
export function canonicalizeWorkingRepoIdentity(origin: string): string | null {
|
|
47
|
+
const raw = origin.trim();
|
|
48
|
+
if (!raw) return null;
|
|
49
|
+
// Remote clients send this canonical form on subsequent lifecycle calls.
|
|
50
|
+
const canonical = raw.match(/^([A-Za-z0-9.-]+)\/([^?#\s]+)$/);
|
|
51
|
+
if (canonical) {
|
|
52
|
+
const host = canonical[1].toLowerCase();
|
|
53
|
+
const path = canonical[2].replace(/^\/+|\/+$/g, '').replace(/\.git$/i, '');
|
|
54
|
+
return host && path ? `${host}/${path}` : null;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const url = new URL(origin);
|
|
58
|
+
if (!['http:', 'https:', 'ssh:', 'git:'].includes(url.protocol)) return null;
|
|
59
|
+
const path = url.pathname.replace(/^\/+|\/+$/g, '').replace(/\.git$/i, '');
|
|
60
|
+
return url.hostname && path ? `${url.hostname.toLowerCase()}/${path}` : null;
|
|
61
|
+
} catch {
|
|
62
|
+
// SCP-style SSH remote: discard its optional user prefix and URL-like
|
|
63
|
+
// query/fragment suffix before accepting only host + repository path.
|
|
64
|
+
const match = raw.match(/^(?:[^@\s/:]+@)?([A-Za-z0-9.-]+):\/?([^?#\s]+)(?:[?#].*)?$/);
|
|
65
|
+
if (match) {
|
|
66
|
+
const host = match[1].toLowerCase();
|
|
67
|
+
const path = match[2].replace(/^\/+|\/+$/g, '').replace(/\.git$/i, '');
|
|
68
|
+
return host && path ? `${host}/${path}` : null;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Return a reportable identity for the caller's current directory.
|
|
76
|
+
*
|
|
77
|
+
* `git rev-parse --show-toplevel` supports both ordinary clones and linked
|
|
78
|
+
* worktrees. Any git failure reports no repository identity instead of
|
|
79
|
+
* disclosing the caller's local filesystem layout.
|
|
80
|
+
*/
|
|
81
|
+
export function resolveWorkingRepo(
|
|
82
|
+
cwd: string = process.cwd(),
|
|
83
|
+
deps: WorkingRepoDeps = {}
|
|
84
|
+
): WorkingRepo {
|
|
85
|
+
const runGit = deps.runGit ?? defaultRunGit;
|
|
86
|
+
const rootResult = runGit(cwd, ['rev-parse', '--show-toplevel']);
|
|
87
|
+
const root = rootResult.status === 0 ? trimmed(rootResult.stdout) : null;
|
|
88
|
+
if (!root) {
|
|
89
|
+
return { name: null, origin: null };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const originResult = runGit(cwd, ['config', '--get', 'remote.origin.url']);
|
|
93
|
+
const originRaw = originResult.status === 0 ? trimmed(originResult.stdout) : null;
|
|
94
|
+
const origin = originRaw ? canonicalizeWorkingRepoIdentity(originRaw) : null;
|
|
95
|
+
return {
|
|
96
|
+
name: origin ? nameFromIdentity(origin) : null,
|
|
97
|
+
origin,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gh#33 — worktree lifecycle as product behavior.
|
|
3
|
+
*
|
|
4
|
+
* Pure git-decision helpers behind an injected `runSync` seam (matching
|
|
5
|
+
* the `AssimilateDeps.runSync` shape), so every branch is unit-testable
|
|
6
|
+
* without a live repo. This module DECIDES + emits git command sequences;
|
|
7
|
+
* it never launches agents and never touches the cube API.
|
|
8
|
+
*
|
|
9
|
+
* Design spec: docs/superpowers/specs/2026-05-29-worktree-lifecycle-design.md
|
|
10
|
+
* Q-resolutions baked in (SPEC-APPROVED 3a80412d):
|
|
11
|
+
* Q1 branch naming — `wt-<suffix>` prefix-stripped, full-basename fallback.
|
|
12
|
+
* Q2 idle-sync — ff-only, clean-gated; never merge/rebase; never over dirty.
|
|
13
|
+
* Q3 post-merge — auto-return to wt-<basename>; ANNOUNCE the prunable
|
|
14
|
+
* merged branch, prune only when explicitly requested.
|
|
15
|
+
* Q4 uniform — no primary-worktree carve-out; main is never a working branch.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
|
|
20
|
+
/** Injected subprocess runner — matches AssimilateDeps.runSync. */
|
|
21
|
+
export type RunSync = (
|
|
22
|
+
cmd: string,
|
|
23
|
+
args: string[],
|
|
24
|
+
cwd?: string
|
|
25
|
+
) => { status: number | null; stdout: string; stderr: string };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Per-worktree branch name (Q1). Strips the repo basename prefix from the
|
|
29
|
+
* worktree dir basename for readability (`borg-mcp-codex-builder` ->
|
|
30
|
+
* `wt-codex-builder`); falls back to the full dir basename when there is
|
|
31
|
+
* no shared prefix (`myrepo-feature` under repo `otherrepo` ->
|
|
32
|
+
* `wt-myrepo-feature`).
|
|
33
|
+
*/
|
|
34
|
+
export function perWorktreeBranchName(worktreeBasename: string, repoBasename: string): string {
|
|
35
|
+
const prefix = `${repoBasename}-`;
|
|
36
|
+
const suffix = worktreeBasename.startsWith(prefix)
|
|
37
|
+
? worktreeBasename.slice(prefix.length)
|
|
38
|
+
: worktreeBasename;
|
|
39
|
+
return `wt-${suffix}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* gh#556 Part 1 — the home for NEW drone worktrees: `<homeDir>/.borg/worktrees`.
|
|
44
|
+
* (`~/.borg` is the established borg home — it already holds the encrypted
|
|
45
|
+
* credentials file, see config.ts.)
|
|
46
|
+
*/
|
|
47
|
+
export function worktreesHome(homeDir: string): string {
|
|
48
|
+
return join(homeDir, '.borg', 'worktrees');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* gh#556 Part 1 — where a NEW drone worktree lives:
|
|
53
|
+
* `<homeDir>/.borg/worktrees/<repoBase>/<suffix>` (collision variant `<suffix>-<n>`
|
|
54
|
+
* for n>=2; the caller loops n until the path is free).
|
|
55
|
+
*
|
|
56
|
+
* Pure (homeDir injected) so the path scheme + collision dedup + containment are
|
|
57
|
+
* unit-testable without touching $HOME or spawning git.
|
|
58
|
+
*
|
|
59
|
+
* Path-safety / no-traversal: `suffix` is validated upstream BEFORE it reaches here —
|
|
60
|
+
* `--worktree` via validateName (NAME_RE excludes `.`/`/`) or the role default via
|
|
61
|
+
* roleSlug (strips everything but `[a-z0-9-]`); `repoBase` is a single `basename(...)`
|
|
62
|
+
* component. So the result is always CONTAINED under `worktreesHome(homeDir)`.
|
|
63
|
+
* As defense-in-depth this throws on an EMPTY suffix — an empty leaf would let
|
|
64
|
+
* `join` collapse the path up to the repo-level dir (the degenerate-path bug); the
|
|
65
|
+
* caller also guards empty before calling, fail-loud.
|
|
66
|
+
*/
|
|
67
|
+
export function computeWorktreePath(
|
|
68
|
+
homeDir: string,
|
|
69
|
+
repoBase: string,
|
|
70
|
+
suffix: string,
|
|
71
|
+
n?: number
|
|
72
|
+
): string {
|
|
73
|
+
if (suffix.length === 0) {
|
|
74
|
+
throw new Error('computeWorktreePath: suffix must be non-empty (empty leaf would collapse the path to the repo-level dir)');
|
|
75
|
+
}
|
|
76
|
+
const leaf = n !== undefined && n >= 2 ? `${suffix}-${n}` : suffix;
|
|
77
|
+
return join(worktreesHome(homeDir), repoBase, leaf);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** True iff the working tree is clean (`git status --porcelain` empty). */
|
|
81
|
+
export function isCleanTree(runSync: RunSync, cwd: string): boolean {
|
|
82
|
+
const r = runSync('git', ['status', '--porcelain'], cwd);
|
|
83
|
+
return r.status === 0 && r.stdout.trim() === '';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface DirtyClassification {
|
|
87
|
+
staged: string[];
|
|
88
|
+
unstaged: string[];
|
|
89
|
+
untracked: string[];
|
|
90
|
+
/** Subset that are local-only config (e.g. `.claude/...`) — likely safe to set aside. */
|
|
91
|
+
localConfig: string[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const LOCAL_CONFIG_RE = /^\.claude\//;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Classify a dirty tree into staged / unstaged / untracked buckets, and
|
|
98
|
+
* flag local-config files separately. The STAGED bucket is load-bearing:
|
|
99
|
+
* the live UNBLOCK case (b15894be) had a *staged* leftover diff that
|
|
100
|
+
* blocked `pull --ff-only`, which an unstaged-only check would miss.
|
|
101
|
+
*/
|
|
102
|
+
export function classifyDirty(runSync: RunSync, cwd: string): DirtyClassification {
|
|
103
|
+
const r = runSync('git', ['status', '--porcelain'], cwd);
|
|
104
|
+
const out: DirtyClassification = { staged: [], unstaged: [], untracked: [], localConfig: [] };
|
|
105
|
+
if (r.status !== 0) return out;
|
|
106
|
+
for (const line of r.stdout.split('\n')) {
|
|
107
|
+
if (!line.trim()) continue;
|
|
108
|
+
const path = line.slice(3);
|
|
109
|
+
if (line.startsWith('??')) {
|
|
110
|
+
out.untracked.push(path);
|
|
111
|
+
} else {
|
|
112
|
+
const x = line[0]; // staged (index) column
|
|
113
|
+
const y = line[1]; // unstaged (work-tree) column
|
|
114
|
+
if (x !== ' ' && x !== '?') out.staged.push(path);
|
|
115
|
+
if (y !== ' ' && y !== '?') out.unstaged.push(path);
|
|
116
|
+
}
|
|
117
|
+
if (LOCAL_CONFIG_RE.test(path)) out.localConfig.push(path);
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** True iff `branch` is an ancestor of `ref` — i.e. a clean fast-forward target. */
|
|
123
|
+
export function isFastForward(runSync: RunSync, cwd: string, branch: string, ref: string): boolean {
|
|
124
|
+
return runSync('git', ['merge-base', '--is-ancestor', branch, ref], cwd).status === 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** True iff `branch`'s tip is an ancestor of `ref` — i.e. fully merged into it. */
|
|
128
|
+
export function isMerged(runSync: RunSync, cwd: string, branch: string, ref: string): boolean {
|
|
129
|
+
return runSync('git', ['merge-base', '--is-ancestor', branch, ref], cwd).status === 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface SyncResult {
|
|
133
|
+
action: 'fast-forwarded' | 'already-current' | 'skipped-dirty' | 'skipped-diverged';
|
|
134
|
+
message?: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Idle-sync the current per-worktree branch to `ref` (Q2). NEVER discards
|
|
139
|
+
* work: dirty -> skipped-dirty (no mutation). Only fast-forwards (no
|
|
140
|
+
* merge/rebase): diverged -> skipped-diverged. The caller fetches first.
|
|
141
|
+
*
|
|
142
|
+
* `already-current` when the branch tip already equals `ref` (the common
|
|
143
|
+
* no-op case on every launch).
|
|
144
|
+
*/
|
|
145
|
+
export function syncWorktree(runSync: RunSync, cwd: string, branch: string, ref: string): SyncResult {
|
|
146
|
+
if (!isCleanTree(runSync, cwd)) {
|
|
147
|
+
return {
|
|
148
|
+
action: 'skipped-dirty',
|
|
149
|
+
message: 'uncommitted changes present; sync skipped (nothing discarded)',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (!isFastForward(runSync, cwd, branch, ref)) {
|
|
153
|
+
return {
|
|
154
|
+
action: 'skipped-diverged',
|
|
155
|
+
message: `${branch} has diverged from ${ref}; resolve manually (no auto-merge/rebase)`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
// Already at ref? merge --ff-only is a no-op but we report it distinctly
|
|
159
|
+
// so callers can stay quiet on the common case.
|
|
160
|
+
const ahead = runSync('git', ['rev-list', '--count', `${branch}..${ref}`], cwd);
|
|
161
|
+
if (ahead.status === 0 && ahead.stdout.trim() === '0') {
|
|
162
|
+
return { action: 'already-current' };
|
|
163
|
+
}
|
|
164
|
+
const ff = runSync('git', ['merge', '--ff-only', ref], cwd);
|
|
165
|
+
if (ff.status !== 0) {
|
|
166
|
+
return { action: 'skipped-diverged', message: 'ff-only merge unexpectedly failed' };
|
|
167
|
+
}
|
|
168
|
+
return { action: 'fast-forwarded' };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface AdoptResult {
|
|
172
|
+
action: 'adopted' | 'blocked-unmerged' | 'blocked-target-unmerged' | 'skipped-dirty';
|
|
173
|
+
message?: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** True iff a local branch named `branch` already exists. */
|
|
177
|
+
export function localBranchExists(runSync: RunSync, cwd: string, branch: string): boolean {
|
|
178
|
+
return runSync('git', ['rev-parse', '--verify', '--quiet', `refs/heads/${branch}`], cwd).status === 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Migration (Q4/Q5/§4.5): bring a detached/stale worktree onto
|
|
183
|
+
* `wt-<basename>` at `ref`. Idempotent: re-running on an already-adopted
|
|
184
|
+
* clean worktree is a lossless reset to `ref`. Never discards:
|
|
185
|
+
* - dirty work tree -> skipped-dirty (surface)
|
|
186
|
+
* - current HEAD unmerged -> blocked-unmerged (surface)
|
|
187
|
+
* - TARGET `branch` exists with commits not on `ref` -> blocked-target-
|
|
188
|
+
* unmerged (surface). This is load-bearing: the switch uses `-C`
|
|
189
|
+
* (force-create/reset), which would ORPHAN commits on a pre-existing
|
|
190
|
+
* `wt-` branch. The HEAD-merged check alone misses this when the
|
|
191
|
+
* target branch != HEAD (e.g. on `main` while a prior `wt-x` holds
|
|
192
|
+
* committed-but-unmerged work). gh#33 CR-v2 blocker 078d1630.
|
|
193
|
+
*/
|
|
194
|
+
export function adoptWorktree(runSync: RunSync, cwd: string, branch: string, ref: string): AdoptResult {
|
|
195
|
+
if (!isCleanTree(runSync, cwd)) {
|
|
196
|
+
return {
|
|
197
|
+
action: 'skipped-dirty',
|
|
198
|
+
message: 'uncommitted changes present; not switching (nothing discarded)',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (!isMerged(runSync, cwd, 'HEAD', ref)) {
|
|
202
|
+
return {
|
|
203
|
+
action: 'blocked-unmerged',
|
|
204
|
+
message: `current HEAD has commits not on ${ref}; commit/push or set aside before adopting`,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
// Guard the TARGET branch ref before the `switch -C` force-reset. If
|
|
208
|
+
// `branch` already exists and is NOT an ancestor of `ref`, it carries
|
|
209
|
+
// committed-unmerged work that `-C` would discard — block instead.
|
|
210
|
+
// (Absent, or an ancestor of `ref` = a clean reset target → proceed.)
|
|
211
|
+
if (localBranchExists(runSync, cwd, branch) && !isMerged(runSync, cwd, branch, ref)) {
|
|
212
|
+
return {
|
|
213
|
+
action: 'blocked-target-unmerged',
|
|
214
|
+
message: `branch ${branch} exists with commits not on ${ref}; resolve before adopting (a force-switch would discard them)`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
runSync('git', ['switch', '-C', branch, ref], cwd);
|
|
218
|
+
return { action: 'adopted' };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface CleanupResult {
|
|
222
|
+
action: 'pruned' | 'announced' | 'not-merged';
|
|
223
|
+
/** The feature branch this result concerns (for the announce message). */
|
|
224
|
+
branch?: string;
|
|
225
|
+
message?: string;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Post-merge cleanup (Q3): when `feature` is fully merged into `ref`,
|
|
230
|
+
* either ANNOUNCE it as prunable (default) or actually prune it with the
|
|
231
|
+
* safe `git branch -d` (which itself refuses to delete an unmerged
|
|
232
|
+
* branch — defense in depth against a stale local ref). Unmerged ->
|
|
233
|
+
* not-merged (never touched).
|
|
234
|
+
*/
|
|
235
|
+
export function cleanupMerged(
|
|
236
|
+
runSync: RunSync,
|
|
237
|
+
cwd: string,
|
|
238
|
+
feature: string,
|
|
239
|
+
ref: string,
|
|
240
|
+
opts: { prune: boolean } = { prune: false }
|
|
241
|
+
): CleanupResult {
|
|
242
|
+
if (!isMerged(runSync, cwd, feature, ref)) {
|
|
243
|
+
return { action: 'not-merged', branch: feature };
|
|
244
|
+
}
|
|
245
|
+
if (!opts.prune) {
|
|
246
|
+
return {
|
|
247
|
+
action: 'announced',
|
|
248
|
+
branch: feature,
|
|
249
|
+
message: `${feature} is merged into ${ref} and can be pruned: \`git branch -d ${feature}\` (or re-run with --prune)`,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
runSync('git', ['branch', '-d', feature], cwd);
|
|
253
|
+
return { action: 'pruned', branch: feature };
|
|
254
|
+
}
|
package/dist/auth-recovery.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* gh#780 companion fix: classify auth-class failures into the RIGHT
|
|
3
|
-
* recovery advice for an in-session agent.
|
|
4
|
-
*
|
|
5
|
-
* Root-cause context: the pre-gh#780 funnel answered every auth failure by
|
|
6
|
-
* pointing the user at `borg assimilate` (the wrong remedy; gh#794 now also
|
|
7
|
-
* differentiates a dead saved login from never-signed-in, both → `borg setup`).
|
|
8
|
-
* An in-session agent's only reachable assimilate is the borg_assimilate MCP tool, which
|
|
9
|
-
* minted a brand-new drones row — so each auth blip spawned an orphan seat
|
|
10
|
-
* (the gh#780 class). Neither failure mode is fixable by assimilating:
|
|
11
|
-
* assimilate rides the same broken Bearer token.
|
|
12
|
-
*
|
|
13
|
-
* - REVOKED / re-consent required (RefreshTokenInvalidError surfaced as
|
|
14
|
-
* "Authentication required/expired"): only `borg setup` in a terminal
|
|
15
|
-
* re-consents.
|
|
16
|
-
* - TRANSIENT refresh failure (RefreshTransientError, "Failed to
|
|
17
|
-
* refresh"): keychain intact by design (gh#34) — the next call
|
|
18
|
-
* retries; waiting IS the recovery.
|
|
19
|
-
*/
|
|
20
|
-
interface AuthErrorLike {
|
|
21
|
-
name?: string;
|
|
22
|
-
message?: string;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Returns the agent-facing recovery message for an auth-class error, or
|
|
26
|
-
* null when the error is not auth-related (caller falls through to its
|
|
27
|
-
* generic error rendering). Transient classification wins over the generic
|
|
28
|
-
* auth-text match — a transient error's message may also mention
|
|
29
|
-
* "Authentication".
|
|
30
|
-
*/
|
|
31
|
-
export declare function authRecoveryMessage(error: AuthErrorLike): string | null;
|
|
32
|
-
export {};
|
|
33
|
-
//# sourceMappingURL=auth-recovery.d.ts.map
|
package/dist/auth-recovery.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
function t(n){const e=n.message??"";return n.name==="RefreshTransientError"||e.includes("Failed to refresh")?"\u25FC Transient auth-refresh failure (network/Google hiccup). Your session is intact and auth self-recovers \u2014 retry the tool call in a moment. Do NOT re-assimilate: borg_assimilate cannot fix auth and would mint a duplicate drone seat.":e.includes("Authentication required")||e.includes("Authentication expired")?"\u25FC Authentication expired \u2014 re-consent needed. Run `borg setup` in a terminal to sign in again. Do NOT re-assimilate: borg_assimilate cannot fix auth and would mint a duplicate drone seat.":null}export{t as authRecoveryMessage};
|