salmon-loop 0.2.3
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/LICENSE +21 -0
- package/README.md +144 -0
- package/README.zh-CN.md +144 -0
- package/dist/cli/argv/headless-detection.js +60 -0
- package/dist/cli/argv/print-mode.js +60 -0
- package/dist/cli/authorization/allowlist.js +908 -0
- package/dist/cli/authorization/non-interactive.js +166 -0
- package/dist/cli/authorization/provider.js +416 -0
- package/dist/cli/chat-interface.js +83 -0
- package/dist/cli/chat.js +492 -0
- package/dist/cli/cli-runtime-context.js +12 -0
- package/dist/cli/commander-error-adapter.js +35 -0
- package/dist/cli/commander-error-meta.js +13 -0
- package/dist/cli/commands/allowlist.js +270 -0
- package/dist/cli/commands/chat.js +120 -0
- package/dist/cli/commands/config.js +250 -0
- package/dist/cli/commands/context.js +57 -0
- package/dist/cli/commands/dispatcher.js +53 -0
- package/dist/cli/commands/exit.js +9 -0
- package/dist/cli/commands/llm-output.js +135 -0
- package/dist/cli/commands/log-mode.js +143 -0
- package/dist/cli/commands/mode.js +136 -0
- package/dist/cli/commands/new.js +18 -0
- package/dist/cli/commands/parallel.js +256 -0
- package/dist/cli/commands/queue.js +130 -0
- package/dist/cli/commands/registry.js +85 -0
- package/dist/cli/commands/restore.js +26 -0
- package/dist/cli/commands/run/assistant-message.js +14 -0
- package/dist/cli/commands/run/config-resolution.js +37 -0
- package/dist/cli/commands/run/early-errors.js +108 -0
- package/dist/cli/commands/run/execute.js +73 -0
- package/dist/cli/commands/run/extensions-resolution.js +22 -0
- package/dist/cli/commands/run/handler.js +434 -0
- package/dist/cli/commands/run/headless-error-writer.js +182 -0
- package/dist/cli/commands/run/instruction-guard.js +24 -0
- package/dist/cli/commands/run/loop-params.js +46 -0
- package/dist/cli/commands/run/mode.js +8 -0
- package/dist/cli/commands/run/parse-options.js +67 -0
- package/dist/cli/commands/run/persist-session.js +35 -0
- package/dist/cli/commands/run/preflight.js +156 -0
- package/dist/cli/commands/run/reporter-factory.js +52 -0
- package/dist/cli/commands/run/runtime-llm.js +56 -0
- package/dist/cli/commands/run/runtime-options.js +30 -0
- package/dist/cli/commands/run/session.js +19 -0
- package/dist/cli/commands/run/structured-output.js +106 -0
- package/dist/cli/commands/run/types.js +2 -0
- package/dist/cli/commands/run/validate-options.js +28 -0
- package/dist/cli/commands/run/verbose.js +36 -0
- package/dist/cli/commands/run.js +2 -0
- package/dist/cli/commands/serve.js +323 -0
- package/dist/cli/commands/session.js +77 -0
- package/dist/cli/commands/snapshot-interactive.js +165 -0
- package/dist/cli/commands/snapshot.js +159 -0
- package/dist/cli/commands/status.js +17 -0
- package/dist/cli/commands/subagent.js +178 -0
- package/dist/cli/commands/subcommand-suggestions.js +63 -0
- package/dist/cli/commands/tool-names.js +155 -0
- package/dist/cli/commands/types.js +2 -0
- package/dist/cli/commands/utils.js +42 -0
- package/dist/cli/config.js +16 -0
- package/dist/cli/crash-reporter.js +5 -0
- package/dist/cli/headless/anthropic-stream-normalized-encoder.js +164 -0
- package/dist/cli/headless/anthropic-stream-protocol.js +62 -0
- package/dist/cli/headless/json-protocol.js +124 -0
- package/dist/cli/headless/native-stream-normalized-encoder.js +206 -0
- package/dist/cli/headless/openai-responses-canonical-applier.js +94 -0
- package/dist/cli/headless/openai-responses-state.js +294 -0
- package/dist/cli/headless/openai-stream-encoder.js +152 -0
- package/dist/cli/headless/stdout-writer.js +9 -0
- package/dist/cli/headless/stream-json-protocol.js +136 -0
- package/dist/cli/index.js +8 -0
- package/dist/cli/locales/en.js +409 -0
- package/dist/cli/locales/index.js +7 -0
- package/dist/cli/program-bootstrap.js +14 -0
- package/dist/cli/program-commands.js +106 -0
- package/dist/cli/program-options.js +15 -0
- package/dist/cli/program-output-mode.js +11 -0
- package/dist/cli/program-parse.js +24 -0
- package/dist/cli/reporters/anthropic-stream.js +77 -0
- package/dist/cli/reporters/base.js +2 -0
- package/dist/cli/reporters/json.js +69 -0
- package/dist/cli/reporters/openai-stream.js +72 -0
- package/dist/cli/reporters/standard.js +226 -0
- package/dist/cli/reporters/stderr-log-reporter.js +71 -0
- package/dist/cli/reporters/stream-json.js +111 -0
- package/dist/cli/run-cli.js +25 -0
- package/dist/cli/slash/runtime.js +240 -0
- package/dist/cli/ui/App.js +273 -0
- package/dist/cli/ui/authorization/bus.js +35 -0
- package/dist/cli/ui/components/CommandInput.js +200 -0
- package/dist/cli/ui/components/CommandSuggestionList.js +20 -0
- package/dist/cli/ui/components/Markdown.js +423 -0
- package/dist/cli/ui/components/MessageList.js +34 -0
- package/dist/cli/ui/components/StatusBannerLine.js +7 -0
- package/dist/cli/ui/components/TodoDrawer.js +60 -0
- package/dist/cli/ui/components/WelcomeMessage.js +14 -0
- package/dist/cli/ui/components/animations/StretchingThinking.js +51 -0
- package/dist/cli/ui/components/animations/ThinkingWave.js +15 -0
- package/dist/cli/ui/components/animations/TypeIndicator.js +30 -0
- package/dist/cli/ui/components/layout/SplitPane.js +11 -0
- package/dist/cli/ui/components/messageList/MessageItem.js +27 -0
- package/dist/cli/ui/components/messageList/QueuePreviewList.js +11 -0
- package/dist/cli/ui/components/messageList/items/EmphasisMessageItem.js +20 -0
- package/dist/cli/ui/components/messageList/items/InterruptMessageItem.js +10 -0
- package/dist/cli/ui/components/messageList/items/LightweightMessageItem.js +12 -0
- package/dist/cli/ui/components/messageList/items/StandardMessageItem.js +23 -0
- package/dist/cli/ui/components/messageList/items/WelcomeMessageItem.js +7 -0
- package/dist/cli/ui/components/messageList/messageListLayout.js +27 -0
- package/dist/cli/ui/components/messageList/streaming.js +51 -0
- package/dist/cli/ui/components/messageList/types.js +2 -0
- package/dist/cli/ui/components/messageList/utils.js +7 -0
- package/dist/cli/ui/components/sidebar/FileContext.js +8 -0
- package/dist/cli/ui/components/sidebar/MissionControl.js +8 -0
- package/dist/cli/ui/config.js +59 -0
- package/dist/cli/ui/hooks/useCommandLifecycle.js +110 -0
- package/dist/cli/ui/hooks/useCommandSuggestions.js +87 -0
- package/dist/cli/ui/hooks/useInputHistory.js +57 -0
- package/dist/cli/ui/hooks/useLoopEvents.js +382 -0
- package/dist/cli/ui/hooks/useLoopState.js +73 -0
- package/dist/cli/ui/hooks/useOnionExit.js +31 -0
- package/dist/cli/ui/hooks/useTerminalDimensions.js +34 -0
- package/dist/cli/ui/index.js +136 -0
- package/dist/cli/ui/selection/bus.js +35 -0
- package/dist/cli/ui/status/formatStatusBanner.js +8 -0
- package/dist/cli/ui/store/context.js +17 -0
- package/dist/cli/ui/store/reducer.js +264 -0
- package/dist/cli/ui/store/types.js +81 -0
- package/dist/cli/ui/styles/theme.js +295 -0
- package/dist/cli/ui/types.js +2 -0
- package/dist/cli/ui/utils/sanitizer.js +122 -0
- package/dist/cli/ui/utils/transcript.js +28 -0
- package/dist/cli/utils/asyncQueue.js +125 -0
- package/dist/cli/utils/audit-scope.js +10 -0
- package/dist/cli/utils/detectors/index.js +38 -0
- package/dist/cli/utils/llm-output.js +34 -0
- package/dist/cli/utils/outcome-reporter.js +17 -0
- package/dist/cli/utils/safe-fs.js +184 -0
- package/dist/cli/utils/verify-resolver.js +34 -0
- package/dist/cli/utils/worktree-prepare-resolver.js +18 -0
- package/dist/core/adapters/fs/atomic-file-writer.js +129 -0
- package/dist/core/adapters/fs/file-adapter.js +95 -0
- package/dist/core/adapters/fs/filesystem.js +31 -0
- package/dist/core/adapters/fs/index.js +5 -0
- package/dist/core/adapters/fs/node-fs.js +7 -0
- package/dist/core/adapters/fs/readonly-filesystem.js +23 -0
- package/dist/core/adapters/git/git-adapter.js +704 -0
- package/dist/core/adapters/git/git-runner.js +119 -0
- package/dist/core/adapters/git/lock-manager.js +314 -0
- package/dist/core/adapters/git/types.js +2 -0
- package/dist/core/adapters/path/index.js +2 -0
- package/dist/core/adapters/path/path-adapter.js +23 -0
- package/dist/core/ast/guard.js +116 -0
- package/dist/core/ast/index.js +4 -0
- package/dist/core/ast/parser.js +284 -0
- package/dist/core/ast/validator.js +46 -0
- package/dist/core/backends/salmon-loop/task-executor.js +68 -0
- package/dist/core/checkpoint-domain/manifest-store.js +379 -0
- package/dist/core/checkpoint-domain/service.js +84 -0
- package/dist/core/checkpoint-domain/types.js +2 -0
- package/dist/core/config/defaults.js +50 -0
- package/dist/core/config/errors.js +11 -0
- package/dist/core/config/file-format.js +108 -0
- package/dist/core/config/index.js +7 -0
- package/dist/core/config/limits.js +77 -0
- package/dist/core/config/load.js +34 -0
- package/dist/core/config/normalize.js +35 -0
- package/dist/core/config/paths.js +20 -0
- package/dist/core/config/redact.js +16 -0
- package/dist/core/config/resolve-env.js +43 -0
- package/dist/core/config/resolve-llm.js +130 -0
- package/dist/core/config/resolve.js +68 -0
- package/dist/core/config/resolvers/ast-validation.js +8 -0
- package/dist/core/config/resolvers/context.js +21 -0
- package/dist/core/config/resolvers/observability.js +45 -0
- package/dist/core/config/resolvers/output.js +8 -0
- package/dist/core/config/resolvers/permission-mode.js +6 -0
- package/dist/core/config/resolvers/security.js +14 -0
- package/dist/core/config/resolvers/server.js +36 -0
- package/dist/core/config/resolvers/tool-authorization.js +39 -0
- package/dist/core/config/resolvers/ui.js +26 -0
- package/dist/core/config/types/config-file.js +2 -0
- package/dist/core/config/types/primitives.js +9 -0
- package/dist/core/config/types/resolved.js +2 -0
- package/dist/core/config/types.js +4 -0
- package/dist/core/config/validate.js +852 -0
- package/dist/core/context/assembly/default-prompt-assembler.js +7 -0
- package/dist/core/context/assembly/prompt-assembler.js +2 -0
- package/dist/core/context/ast/import-extractor.js +28 -0
- package/dist/core/context/ast/module-resolver.js +61 -0
- package/dist/core/context/ast/source-outline.js +25 -0
- package/dist/core/context/audit-constants.js +23 -0
- package/dist/core/context/audit.js +54 -0
- package/dist/core/context/budget/dynamic-adjuster.js +149 -0
- package/dist/core/context/budget/example-integration.js +49 -0
- package/dist/core/context/budget/integration.js +93 -0
- package/dist/core/context/builder.js +289 -0
- package/dist/core/context/cache/errors.js +16 -0
- package/dist/core/context/cache/incremental-updater.js +131 -0
- package/dist/core/context/cache/index.js +25 -0
- package/dist/core/context/cache/path-resolver.js +127 -0
- package/dist/core/context/cache/prompt-caching.js +207 -0
- package/dist/core/context/cache/store-factory.js +63 -0
- package/dist/core/context/cache/store.js +193 -0
- package/dist/core/context/cache/types.js +15 -0
- package/dist/core/context/compression/js-like-comments.js +139 -0
- package/dist/core/context/compression/smart-compress.js +61 -0
- package/dist/core/context/compression/whitespace.js +26 -0
- package/dist/core/context/dependencies.js +102 -0
- package/dist/core/context/effectiveness/index.js +25 -0
- package/dist/core/context/effectiveness/tracker.js +253 -0
- package/dist/core/context/effectiveness/types.js +15 -0
- package/dist/core/context/formatters/index.js +7 -0
- package/dist/core/context/formatters/json-converter.js +662 -0
- package/dist/core/context/formatters/types.js +6 -0
- package/dist/core/context/formatters/xml-context.js +296 -0
- package/dist/core/context/gatherers/architecture-gatherer.js +75 -0
- package/dist/core/context/gatherers/artifact-gatherer.js +53 -0
- package/dist/core/context/gatherers/ast-gatherer.js +370 -0
- package/dist/core/context/gatherers/ghost-dependency-gatherer.js +46 -0
- package/dist/core/context/gatherers/git-diff-gatherer.js +91 -0
- package/dist/core/context/gatherers/git-history-gatherer.js +57 -0
- package/dist/core/context/gatherers/knowledge-gatherer.js +101 -0
- package/dist/core/context/gatherers/metadata-gatherer.js +59 -0
- package/dist/core/context/gatherers/primary-text-gatherer.js +36 -0
- package/dist/core/context/gatherers/ripgrep-gatherer.js +104 -0
- package/dist/core/context/hash.js +52 -0
- package/dist/core/context/index.js +3 -0
- package/dist/core/context/keywords.js +179 -0
- package/dist/core/context/policies/budget-policy.js +36 -0
- package/dist/core/context/policies/pack-until-full.js +419 -0
- package/dist/core/context/scoring/relevance.js +191 -0
- package/dist/core/context/service-deps.js +32 -0
- package/dist/core/context/service-helpers.js +32 -0
- package/dist/core/context/service.js +265 -0
- package/dist/core/context/steps/context-budget.js +157 -0
- package/dist/core/context/steps/context-gather.js +71 -0
- package/dist/core/context/steps/context-primary.js +19 -0
- package/dist/core/context/steps/context-promotion.js +78 -0
- package/dist/core/context/steps/context-targets.js +85 -0
- package/dist/core/context/steps/types.js +2 -0
- package/dist/core/context/summarization/index.js +27 -0
- package/dist/core/context/summarization/prompts.js +80 -0
- package/dist/core/context/summarization/summarizer.js +377 -0
- package/dist/core/context/summarization/types.js +29 -0
- package/dist/core/context/targeting/churn-policy.js +27 -0
- package/dist/core/context/targeting/target-resolver.js +491 -0
- package/dist/core/context/token/adaptive-budget.js +364 -0
- package/dist/core/context/token/cache.js +163 -0
- package/dist/core/context/token/counter.js +190 -0
- package/dist/core/context/token/encoding-registry.js +173 -0
- package/dist/core/context/token/index.js +31 -0
- package/dist/core/context/token/token-budget.js +213 -0
- package/dist/core/context/token/types.js +10 -0
- package/dist/core/context/truncation/index.js +23 -0
- package/dist/core/context/truncation/semantic-truncator.js +103 -0
- package/dist/core/context/truncation/strategies/error-stack.js +94 -0
- package/dist/core/context/truncation/strategies/generic.js +48 -0
- package/dist/core/context/truncation/strategies/git-diff.js +99 -0
- package/dist/core/context/truncation/strategies/index.js +10 -0
- package/dist/core/context/truncation/strategies/json.js +142 -0
- package/dist/core/context/truncation/strategies/log.js +131 -0
- package/dist/core/context/truncation/strategies/test-result.js +140 -0
- package/dist/core/context/truncation/type-detector.js +133 -0
- package/dist/core/context/truncation/types.js +16 -0
- package/dist/core/context/types.js +2 -0
- package/dist/core/extensions/index.js +118 -0
- package/dist/core/extensions/load.js +36 -0
- package/dist/core/extensions/merge.js +29 -0
- package/dist/core/extensions/paths.js +40 -0
- package/dist/core/extensions/redact.js +37 -0
- package/dist/core/extensions/schemas.js +70 -0
- package/dist/core/extensions/types.js +2 -0
- package/dist/core/facades/cli-authorization-allowlist.js +3 -0
- package/dist/core/facades/cli-authorization-non-interactive.js +3 -0
- package/dist/core/facades/cli-authorization-provider.js +2 -0
- package/dist/core/facades/cli-chat.js +11 -0
- package/dist/core/facades/cli-command-allowlist.js +3 -0
- package/dist/core/facades/cli-command-chat.js +8 -0
- package/dist/core/facades/cli-command-checkpoint.js +3 -0
- package/dist/core/facades/cli-command-config.js +10 -0
- package/dist/core/facades/cli-command-dispatcher.js +2 -0
- package/dist/core/facades/cli-command-parallel.js +8 -0
- package/dist/core/facades/cli-command-session.js +2 -0
- package/dist/core/facades/cli-command-tool-names.js +6 -0
- package/dist/core/facades/cli-context.js +8 -0
- package/dist/core/facades/cli-headless.js +3 -0
- package/dist/core/facades/cli-observability.js +3 -0
- package/dist/core/facades/cli-program-bootstrap.js +2 -0
- package/dist/core/facades/cli-reporters.js +5 -0
- package/dist/core/facades/cli-run-execute.js +3 -0
- package/dist/core/facades/cli-run-handler.js +7 -0
- package/dist/core/facades/cli-run-headless-error-writer.js +2 -0
- package/dist/core/facades/cli-run-loop-params.js +2 -0
- package/dist/core/facades/cli-run-persist-session.js +2 -0
- package/dist/core/facades/cli-run-runtime-llm.js +5 -0
- package/dist/core/facades/cli-serve.js +21 -0
- package/dist/core/facades/cli-slash-runtime.js +9 -0
- package/dist/core/facades/cli-subagent.js +2 -0
- package/dist/core/facades/cli-ui.js +5 -0
- package/dist/core/facades/cli-utils-llm-output.js +3 -0
- package/dist/core/facades/cli-utils-path.js +2 -0
- package/dist/core/facades/cli-utils-worktree.js +2 -0
- package/dist/core/failure/diagnostics.js +221 -0
- package/dist/core/feedback/index.js +28 -0
- package/dist/core/feedback/parsers.js +59 -0
- package/dist/core/feedback/patterns.js +26 -0
- package/dist/core/feedback/types.js +2 -0
- package/dist/core/grizzco/domain/grizzco-types.js +41 -0
- package/dist/core/grizzco/dsl/DecisionEngine.js +149 -0
- package/dist/core/grizzco/dsl/MicroTaskRunner.js +39 -0
- package/dist/core/grizzco/dsl/llm-strategy.js +80 -0
- package/dist/core/grizzco/dsl/strategies.js +69 -0
- package/dist/core/grizzco/dsl/types.js +2 -0
- package/dist/core/grizzco/engine/observability/event-adapter.js +41 -0
- package/dist/core/grizzco/engine/observability/index.js +3 -0
- package/dist/core/grizzco/engine/observability/loop-telemetry.js +51 -0
- package/dist/core/grizzco/engine/outcome/index.js +2 -0
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +167 -0
- package/dist/core/grizzco/engine/pipeline/pipeline.js +335 -0
- package/dist/core/grizzco/engine/pipeline/types.js +2 -0
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +242 -0
- package/dist/core/grizzco/engine/transaction/authorization-summary.js +44 -0
- package/dist/core/grizzco/engine/transaction/index.js +3 -0
- package/dist/core/grizzco/engine/transaction/report-mapper.js +50 -0
- package/dist/core/grizzco/engine/transaction/retry-policy.js +19 -0
- package/dist/core/grizzco/engine/transaction/runner-builder.js +45 -0
- package/dist/core/grizzco/engine/transaction/session.js +58 -0
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +193 -0
- package/dist/core/grizzco/engine/transaction/types.js +2 -0
- package/dist/core/grizzco/execution/Executor.js +58 -0
- package/dist/core/grizzco/execution/RejectionManager.js +71 -0
- package/dist/core/grizzco/execution/WorkerFactory.js +31 -0
- package/dist/core/grizzco/flows/SalmonLoopFlow.js +102 -0
- package/dist/core/grizzco/runtime/apply-back-runtime.js +136 -0
- package/dist/core/grizzco/runtime/apply-back-utils.js +13 -0
- package/dist/core/grizzco/runtime/host/host-runner.js +99 -0
- package/dist/core/grizzco/runtime/host/index.js +2 -0
- package/dist/core/grizzco/runtime/host/types.js +2 -0
- package/dist/core/grizzco/services/CachedService.js +42 -0
- package/dist/core/grizzco/services/implementations/default/GitConfigService.js +38 -0
- package/dist/core/grizzco/services/implementations/mock/MockLockService.js +11 -0
- package/dist/core/grizzco/services/implementations/mock/MockUserQuotaService.js +11 -0
- package/dist/core/grizzco/services/registry.js +30 -0
- package/dist/core/grizzco/services/types.js +2 -0
- package/dist/core/grizzco/steps/answer.js +75 -0
- package/dist/core/grizzco/steps/apply-back.js +46 -0
- package/dist/core/grizzco/steps/apply.js +136 -0
- package/dist/core/grizzco/steps/ast-validate.js +37 -0
- package/dist/core/grizzco/steps/audit.js +311 -0
- package/dist/core/grizzco/steps/context.js +74 -0
- package/dist/core/grizzco/steps/display-answer.js +6 -0
- package/dist/core/grizzco/steps/display-report.js +158 -0
- package/dist/core/grizzco/steps/display-research.js +6 -0
- package/dist/core/grizzco/steps/displayReview.js +6 -0
- package/dist/core/grizzco/steps/explore.js +245 -0
- package/dist/core/grizzco/steps/extractIssues.js +27 -0
- package/dist/core/grizzco/steps/generateFixPlan.js +13 -0
- package/dist/core/grizzco/steps/generateReview.js +71 -0
- package/dist/core/grizzco/steps/patch.js +220 -0
- package/dist/core/grizzco/steps/plan.js +191 -0
- package/dist/core/grizzco/steps/preflight.js +93 -0
- package/dist/core/grizzco/steps/prepare-deps.js +49 -0
- package/dist/core/grizzco/steps/read-only-shrink.js +4 -0
- package/dist/core/grizzco/steps/research.js +188 -0
- package/dist/core/grizzco/steps/rollback.js +138 -0
- package/dist/core/grizzco/steps/shrink.js +64 -0
- package/dist/core/grizzco/steps/validate.js +40 -0
- package/dist/core/grizzco/steps/verify.js +136 -0
- package/dist/core/grizzco/validation/AstValidationService.js +133 -0
- package/dist/core/grizzco/validation/ContextValidator.js +17 -0
- package/dist/core/grizzco/validation/ast-validation-policy.js +11 -0
- package/dist/core/grizzco/workers/direct-write-worker.js +44 -0
- package/dist/core/grizzco/workers/git-apply-worker.js +75 -0
- package/dist/core/grizzco/workers/i-merge-worker.js +2 -0
- package/dist/core/grizzco/workers/mm-three-way-worker.js +117 -0
- package/dist/core/grizzco/workers/no-op-worker.js +18 -0
- package/dist/core/grizzco/workers/overwrite-binary-worker.js +29 -0
- package/dist/core/grizzco/workers/strata-sync-worker.js +69 -0
- package/dist/core/grizzco/workers/three-way-merge-worker.js +84 -0
- package/dist/core/grizzco/workers/three-way-staged-worker.js +93 -0
- package/dist/core/grizzco/workers/union-merge-worker.js +71 -0
- package/dist/core/history/input-history.js +55 -0
- package/dist/core/intent/chat-intent.js +250 -0
- package/dist/core/interaction/events/bus.js +52 -0
- package/dist/core/interaction/model/events.js +2 -0
- package/dist/core/interaction/model/index.js +3 -0
- package/dist/core/interaction/model/task-state.js +9 -0
- package/dist/core/interaction/model/transition-policy.js +50 -0
- package/dist/core/interaction/model/types.js +2 -0
- package/dist/core/interaction/orchestration/facade.js +190 -0
- package/dist/core/interaction/orchestration/index.js +2 -0
- package/dist/core/interaction/orchestration/store.js +32 -0
- package/dist/core/interaction/sync/task-sync-engine.js +57 -0
- package/dist/core/interaction/turn-stop-reason.js +27 -0
- package/dist/core/language-support/index.js +3 -0
- package/dist/core/language-support/orchestrator.js +37 -0
- package/dist/core/language-support/strategies/extension-candidate-strategy.js +27 -0
- package/dist/core/language-support/strategies/index.js +3 -0
- package/dist/core/language-support/strategies/language-query-strategy.js +26 -0
- package/dist/core/llm/ai-sdk/chat-executor.js +88 -0
- package/dist/core/llm/ai-sdk/langfuse-headers.js +28 -0
- package/dist/core/llm/ai-sdk/message-mapper.js +240 -0
- package/dist/core/llm/ai-sdk/observation-context.js +16 -0
- package/dist/core/llm/ai-sdk/provider-factory.js +29 -0
- package/dist/core/llm/ai-sdk/request-params.js +18 -0
- package/dist/core/llm/ai-sdk/request-runtime.js +168 -0
- package/dist/core/llm/ai-sdk/result-mapper.js +31 -0
- package/dist/core/llm/ai-sdk/retry-classifier.js +82 -0
- package/dist/core/llm/ai-sdk/retry-executor.js +38 -0
- package/dist/core/llm/ai-sdk.js +92 -0
- package/dist/core/llm/audit.js +2 -0
- package/dist/core/llm/base-url.js +18 -0
- package/dist/core/llm/contracts/repair.js +68 -0
- package/dist/core/llm/errors.js +172 -0
- package/dist/core/llm/factory.js +21 -0
- package/dist/core/llm/http/index.js +2 -0
- package/dist/core/llm/index.js +6 -0
- package/dist/core/llm/message-composition.js +25 -0
- package/dist/core/llm/openai.js +69 -0
- package/dist/core/llm/output-policy.js +192 -0
- package/dist/core/llm/phase-router.js +55 -0
- package/dist/core/llm/redact.js +37 -0
- package/dist/core/llm/registry.js +81 -0
- package/dist/core/llm/retry-utils.js +114 -0
- package/dist/core/llm/stream-utils.js +87 -0
- package/dist/core/llm/utils.js +82 -0
- package/dist/core/observability/audit-file.js +199 -0
- package/dist/core/observability/audit-trail.js +125 -0
- package/dist/core/observability/authorization-decisions.js +54 -0
- package/dist/core/observability/debug-artifacts.js +61 -0
- package/dist/core/observability/error-envelope.js +63 -0
- package/dist/core/observability/error-mapping.js +271 -0
- package/dist/core/observability/ignored-error.js +6 -0
- package/dist/core/observability/logger.js +457 -0
- package/dist/core/observability/loop-event-reporter.js +46 -0
- package/dist/core/observability/monitor.js +240 -0
- package/dist/core/observability/run-outcome-reporter.js +15 -0
- package/dist/core/observability/token-usage.js +36 -0
- package/dist/core/observability/ui-log-sanitize.js +35 -0
- package/dist/core/patch/aggregator.js +93 -0
- package/dist/core/patch/diff.js +298 -0
- package/dist/core/permission-gate/default-gate.js +115 -0
- package/dist/core/permission-gate/gate.js +2 -0
- package/dist/core/permission-gate/types.js +2 -0
- package/dist/core/plan/index.js +2 -0
- package/dist/core/plan/manager.js +123 -0
- package/dist/core/plan/markdown-editor.js +238 -0
- package/dist/core/plan/storage.js +75 -0
- package/dist/core/plan/types.js +2 -0
- package/dist/core/plugin/interface.js +2 -0
- package/dist/core/plugin/loader.js +130 -0
- package/dist/core/plugin/registry.js +90 -0
- package/dist/core/plugin/validator.js +98 -0
- package/dist/core/prompts/registry.js +189 -0
- package/dist/core/prompts/runtime.js +69 -0
- package/dist/core/prompts/schema.js +2 -0
- package/dist/core/prompts/templates/phases/explore_user.hbs +26 -0
- package/dist/core/prompts/templates/phases/patch_user.hbs +57 -0
- package/dist/core/prompts/templates/phases/plan_user.hbs +33 -0
- package/dist/core/prompts/templates/system/_context_json_legend.hbs +21 -0
- package/dist/core/prompts/templates/system/_tool_defs.hbs +60 -0
- package/dist/core/prompts/templates/system/explore_system.hbs +26 -0
- package/dist/core/prompts/templates/system/main_system.hbs +18 -0
- package/dist/core/prompts/templates/system/patch_system.hbs +10 -0
- package/dist/core/prompts/templates/system/plan_system.hbs +1 -0
- package/dist/core/prompts/templates/system/reflection.hbs +39 -0
- package/dist/core/protocols/a2a/agent-card.js +30 -0
- package/dist/core/protocols/a2a/mapper.js +14 -0
- package/dist/core/protocols/a2a/sdk/auth-middleware.js +31 -0
- package/dist/core/protocols/a2a/sdk/executor.js +301 -0
- package/dist/core/protocols/a2a/sdk/server.js +24 -0
- package/dist/core/protocols/a2a/task-projection.js +45 -0
- package/dist/core/protocols/acp/acp-command-runner.js +204 -0
- package/dist/core/protocols/acp/acp-filesystem.js +43 -0
- package/dist/core/protocols/acp/checkpoint-meta.js +2 -0
- package/dist/core/protocols/acp/formal-agent.js +1201 -0
- package/dist/core/protocols/acp/handlers.js +51 -0
- package/dist/core/protocols/acp/permission-provider.js +122 -0
- package/dist/core/protocols/acp/stdio-server.js +116 -0
- package/dist/core/reflection/engine.js +55 -0
- package/dist/core/reflection/types.js +2 -0
- package/dist/core/runtime/agent-server-runtime.js +88 -0
- package/dist/core/runtime/bun-runtime.js +26 -0
- package/dist/core/runtime/command-runner-context.js +16 -0
- package/dist/core/runtime/exit-codes.js +11 -0
- package/dist/core/runtime/fastify-fetch-bridge.js +51 -0
- package/dist/core/runtime/fastify-server-bundle.js +26 -0
- package/dist/core/runtime/initialize.js +132 -0
- package/dist/core/runtime/loop-finalize.js +71 -0
- package/dist/core/runtime/loop-run-lifecycle.js +73 -0
- package/dist/core/runtime/loop-run-reporter.js +19 -0
- package/dist/core/runtime/loop-runtime-config.js +26 -0
- package/dist/core/runtime/loop-session-runner.js +30 -0
- package/dist/core/runtime/loop.js +84 -0
- package/dist/core/runtime/paths.js +84 -0
- package/dist/core/runtime/process-runner.js +16 -0
- package/dist/core/runtime/process-types.js +2 -0
- package/dist/core/runtime/semaphore.js +41 -0
- package/dist/core/runtime/sidecar-fastify-plugin.js +35 -0
- package/dist/core/runtime/sidecar-paths.js +47 -0
- package/dist/core/runtime/sidecar-route-catalog.js +103 -0
- package/dist/core/runtime/spawn-command.js +392 -0
- package/dist/core/runtime/spawn-interactive.js +71 -0
- package/dist/core/security/redaction.js +160 -0
- package/dist/core/session/compression.js +323 -0
- package/dist/core/session/flow.js +85 -0
- package/dist/core/session/manager.js +313 -0
- package/dist/core/session/pruning-strategy.js +153 -0
- package/dist/core/session/session-context-builder.js +122 -0
- package/dist/core/session/summary-sync.js +82 -0
- package/dist/core/session/token-tracker.js +82 -0
- package/dist/core/session/types.js +2 -0
- package/dist/core/skills/bridge.js +33 -0
- package/dist/core/skills/index.js +8 -0
- package/dist/core/skills/loader.js +80 -0
- package/dist/core/skills/parser.js +66 -0
- package/dist/core/skills/runtime/MicroTaskRunner.js +102 -0
- package/dist/core/skills/runtime/SkillRunner.js +108 -0
- package/dist/core/skills/strategy.js +29 -0
- package/dist/core/skills/types.js +2 -0
- package/dist/core/slash/index.js +6 -0
- package/dist/core/slash/parser.js +33 -0
- package/dist/core/slash/registry.js +78 -0
- package/dist/core/slash/router.js +76 -0
- package/dist/core/slash/steps/slash-decide.js +19 -0
- package/dist/core/slash/steps/slash-execute.js +73 -0
- package/dist/core/slash/steps/types.js +2 -0
- package/dist/core/slash/strategy.js +33 -0
- package/dist/core/slash/types.js +2 -0
- package/dist/core/strata/checkpoint/manager.js +492 -0
- package/dist/core/strata/checkpoint/snapshot-audit.js +88 -0
- package/dist/core/strata/checkpoint/snapshot-create.js +79 -0
- package/dist/core/strata/checkpoint/snapshot-write-tree.js +72 -0
- package/dist/core/strata/engine/shadow-merge-engine.js +394 -0
- package/dist/core/strata/index.js +15 -0
- package/dist/core/strata/interaction/content-guardian.js +59 -0
- package/dist/core/strata/interaction/file-system-provider.js +89 -0
- package/dist/core/strata/layers/file-state-resolver.js +157 -0
- package/dist/core/strata/layers/immutable-git-layer.js +42 -0
- package/dist/core/strata/layers/shadow-driver/copy-backend.js +114 -0
- package/dist/core/strata/layers/shadow-driver/env.js +29 -0
- package/dist/core/strata/layers/shadow-driver/error-classifier.js +41 -0
- package/dist/core/strata/layers/shadow-driver/index.js +17 -0
- package/dist/core/strata/layers/shadow-driver/readonly-lock.js +221 -0
- package/dist/core/strata/layers/shadow-driver/shadow-driver.js +234 -0
- package/dist/core/strata/layers/shadow-driver/strategy.js +86 -0
- package/dist/core/strata/layers/sidecar-layer.js +96 -0
- package/dist/core/strata/layers/worktree.js +240 -0
- package/dist/core/strata/runtime/environment.js +377 -0
- package/dist/core/strata/runtime/synchronizer.js +819 -0
- package/dist/core/strata/types.js +46 -0
- package/dist/core/streaming/canonical/canonical-responses-event-emitter.js +326 -0
- package/dist/core/streaming/canonical/function-call-item-id.js +13 -0
- package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +54 -0
- package/dist/core/streaming/canonical/responses-event-emitter.js +127 -0
- package/dist/core/streaming/canonical/responses-events.js +2 -0
- package/dist/core/streaming/normalized-events.js +9 -0
- package/dist/core/streaming/normalized-from-text.js +47 -0
- package/dist/core/streaming/stream-assembler.js +347 -0
- package/dist/core/structured-output/index.js +3 -0
- package/dist/core/structured-output/json-extract.js +70 -0
- package/dist/core/structured-output/json-schema-validator.js +90 -0
- package/dist/core/structured-output/types.js +2 -0
- package/dist/core/sub-agent/artifacts/store.js +141 -0
- package/dist/core/sub-agent/artifacts/types.js +2 -0
- package/dist/core/sub-agent/controller.js +69 -0
- package/dist/core/sub-agent/core/loop.js +79 -0
- package/dist/core/sub-agent/core/manager.js +246 -0
- package/dist/core/sub-agent/registry-defaults.js +52 -0
- package/dist/core/sub-agent/registry.js +35 -0
- package/dist/core/sub-agent/tools/task-spawn.js +29 -0
- package/dist/core/sub-agent/types.js +23 -0
- package/dist/core/target-runtime/command-resolver.js +42 -0
- package/dist/core/target-runtime/index.js +3 -0
- package/dist/core/target-runtime/profile.js +73 -0
- package/dist/core/testgen/detector.js +17 -0
- package/dist/core/testgen/index.js +38 -0
- package/dist/core/testgen/templates.js +46 -0
- package/dist/core/tools/audit.js +140 -0
- package/dist/core/tools/authorization/types.js +2 -0
- package/dist/core/tools/budget.js +118 -0
- package/dist/core/tools/builtin/artifact.js +29 -0
- package/dist/core/tools/builtin/ast-grep.js +107 -0
- package/dist/core/tools/builtin/ast.js +62 -0
- package/dist/core/tools/builtin/code-search/backends/powershell.js +84 -0
- package/dist/core/tools/builtin/code-search/backends/rg.js +85 -0
- package/dist/core/tools/builtin/code-search/executor.js +87 -0
- package/dist/core/tools/builtin/code-search/parse/plain-grep.js +59 -0
- package/dist/core/tools/builtin/code-search/parse/rg-json.js +31 -0
- package/dist/core/tools/builtin/code-search/spec.js +82 -0
- package/dist/core/tools/builtin/fs.js +243 -0
- package/dist/core/tools/builtin/git.js +118 -0
- package/dist/core/tools/builtin/index.js +80 -0
- package/dist/core/tools/builtin/interaction.js +120 -0
- package/dist/core/tools/builtin/knowledge.js +98 -0
- package/dist/core/tools/builtin/plan.js +148 -0
- package/dist/core/tools/builtin/proposal.js +207 -0
- package/dist/core/tools/builtin/shell.js +71 -0
- package/dist/core/tools/builtin/verify.js +41 -0
- package/dist/core/tools/capability/executor.js +84 -0
- package/dist/core/tools/capability/runner.js +50 -0
- package/dist/core/tools/capability/types.js +2 -0
- package/dist/core/tools/dispatcher.js +80 -0
- package/dist/core/tools/headless-payload.js +37 -0
- package/dist/core/tools/loader.js +100 -0
- package/dist/core/tools/mapper.js +142 -0
- package/dist/core/tools/mcp/client.js +308 -0
- package/dist/core/tools/mcp/loader.js +110 -0
- package/dist/core/tools/mcp/schema.js +54 -0
- package/dist/core/tools/mcp/streamable-http.js +101 -0
- package/dist/core/tools/mcp/types.js +26 -0
- package/dist/core/tools/parallel/isolation.js +25 -0
- package/dist/core/tools/parallel/lock-manager.js +124 -0
- package/dist/core/tools/parallel/persistence.js +126 -0
- package/dist/core/tools/parallel/plan-builder.js +66 -0
- package/dist/core/tools/parallel/plan.js +2 -0
- package/dist/core/tools/parallel/refs.js +7 -0
- package/dist/core/tools/parallel/resolve-args.js +50 -0
- package/dist/core/tools/parallel/resource-helpers.js +35 -0
- package/dist/core/tools/parallel/resources.js +2 -0
- package/dist/core/tools/parallel/scheduler.js +372 -0
- package/dist/core/tools/parser.js +89 -0
- package/dist/core/tools/permissions/permission-rules.js +503 -0
- package/dist/core/tools/plugins/loader.js +102 -0
- package/dist/core/tools/policy.js +87 -0
- package/dist/core/tools/registry.js +29 -0
- package/dist/core/tools/router.js +514 -0
- package/dist/core/tools/sanitize.js +78 -0
- package/dist/core/tools/schema-utils.js +71 -0
- package/dist/core/tools/session.js +1105 -0
- package/dist/core/tools/streaming/ToolCallAccumulator.js +64 -0
- package/dist/core/tools/types.js +2 -0
- package/dist/core/types/authorization.js +2 -0
- package/dist/core/types/context.js +2 -0
- package/dist/core/types/errors.js +29 -0
- package/dist/core/types/execution.js +65 -0
- package/dist/core/types/index.js +9 -0
- package/dist/core/types/llm.js +9 -0
- package/dist/core/types/loop.js +2 -0
- package/dist/core/types/planning.js +2 -0
- package/dist/core/types/runtime.js +2 -0
- package/dist/core/types/usage.js +2 -0
- package/dist/core/ui/kaomoji.js +5 -0
- package/dist/core/utils/path.js +116 -0
- package/dist/core/utils/platform-shell.js +10 -0
- package/dist/core/utils/sanitizer.js +107 -0
- package/dist/core/verification/runner.js +265 -0
- package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +272 -0
- package/dist/integrations/langfuse/outcome-proxy.js +68 -0
- package/dist/interfaces/cli/task-runner.js +11 -0
- package/dist/languages/typescript/index.js +178 -0
- package/dist/locales/en.js +679 -0
- package/dist/locales/index.js +11 -0
- package/dist/utils/eol.js +35 -0
- package/package.json +153 -0
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
import { realpathSync } from 'fs';
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { text } from '../../../locales/index.js';
|
|
7
|
+
import { LIMITS } from '../../config/limits.js';
|
|
8
|
+
import { logIgnoredError } from '../../observability/ignored-error.js';
|
|
9
|
+
import { getLogger } from '../../observability/logger.js';
|
|
10
|
+
import { GitError } from '../../types/index.js';
|
|
11
|
+
import { isPathWithinDirectory, normalizePath } from '../../utils/path.js';
|
|
12
|
+
import { runGitCommand } from './git-runner.js';
|
|
13
|
+
import { FileHandleManager } from './lock-manager.js';
|
|
14
|
+
// Singleton map to ensure one lock manager per repository path
|
|
15
|
+
const lockManagers = new Map();
|
|
16
|
+
function getLockManager(repoPath) {
|
|
17
|
+
if (!lockManagers.has(repoPath)) {
|
|
18
|
+
lockManagers.set(repoPath, new FileHandleManager());
|
|
19
|
+
}
|
|
20
|
+
return lockManagers.get(repoPath);
|
|
21
|
+
}
|
|
22
|
+
function isShaLike(value) {
|
|
23
|
+
return /^[0-9a-f]{7,40}$/i.test(value);
|
|
24
|
+
}
|
|
25
|
+
function splitPathsByCharBudget(baseArgs, paths, maxChars) {
|
|
26
|
+
const batches = [];
|
|
27
|
+
let current = [];
|
|
28
|
+
const baseLen = baseArgs.reduce((sum, a) => sum + a.length + 1, 0);
|
|
29
|
+
let currentLen = baseLen;
|
|
30
|
+
for (const p of paths) {
|
|
31
|
+
const addLen = p.length + 1;
|
|
32
|
+
if (current.length > 0 && currentLen + addLen > maxChars) {
|
|
33
|
+
batches.push(current);
|
|
34
|
+
current = [];
|
|
35
|
+
currentLen = baseLen;
|
|
36
|
+
}
|
|
37
|
+
current.push(p);
|
|
38
|
+
currentLen += addLen;
|
|
39
|
+
}
|
|
40
|
+
if (current.length > 0)
|
|
41
|
+
batches.push(current);
|
|
42
|
+
return batches;
|
|
43
|
+
}
|
|
44
|
+
function joinNulTerminated(values) {
|
|
45
|
+
// Git pathspec-from-file expects elements separated by NUL when --pathspec-file-nul is set.
|
|
46
|
+
return Buffer.from(values.join('\0') + '\0', 'utf8');
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* GitAdapter: The unified security gateway for Git interactions.
|
|
50
|
+
* This is the EXCLUSIVE outlet for all Git commands in the system.
|
|
51
|
+
*/
|
|
52
|
+
export class GitAdapter {
|
|
53
|
+
repoPath;
|
|
54
|
+
lockManager;
|
|
55
|
+
constructor(repoPath) {
|
|
56
|
+
this.repoPath = repoPath;
|
|
57
|
+
this.lockManager = getLockManager(repoPath);
|
|
58
|
+
}
|
|
59
|
+
// ==================== Base Execution Layer ====================
|
|
60
|
+
/**
|
|
61
|
+
* Low-level git execution that returns structured output without throwing.
|
|
62
|
+
* Prefer higher-level helpers (`exec`, `query`) unless the caller must
|
|
63
|
+
* inspect spawn errors (e.g., ENOENT) or truncation flags.
|
|
64
|
+
*/
|
|
65
|
+
async execMeta(args, options = {}) {
|
|
66
|
+
return await runGitCommand({
|
|
67
|
+
repoRoot: this.repoPath,
|
|
68
|
+
args,
|
|
69
|
+
cwd: options.cwd,
|
|
70
|
+
env: options.env,
|
|
71
|
+
input: options.input,
|
|
72
|
+
timeoutMs: options.timeoutMs ?? LIMITS.gitTimeoutMs,
|
|
73
|
+
limits: options.limits,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Primary executor for Git commands.
|
|
78
|
+
* Standardizes environment, timeout, and handles machine-readable output.
|
|
79
|
+
*/
|
|
80
|
+
async exec(args, options = {}) {
|
|
81
|
+
const res = await this.execRaw(args, options);
|
|
82
|
+
const output = res.stdout.toString('utf8');
|
|
83
|
+
return options.trim === false ? output : output.replace(/\s+$/, '');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Raw executor for Git commands that returns Buffers.
|
|
87
|
+
* Internal use only to support binary data and stdin.
|
|
88
|
+
*/
|
|
89
|
+
async execRaw(args, options = {}) {
|
|
90
|
+
const res = await this.execMeta(args, {
|
|
91
|
+
cwd: options.cwd,
|
|
92
|
+
env: options.env,
|
|
93
|
+
input: options.input,
|
|
94
|
+
timeoutMs: options.timeoutMs,
|
|
95
|
+
limits: options.limits,
|
|
96
|
+
});
|
|
97
|
+
if (res.stdoutTruncated && !options.allowTruncatedStdout) {
|
|
98
|
+
const maxStdoutBytes = options.limits?.maxStdoutBytes;
|
|
99
|
+
const safeMaxBytes = typeof maxStdoutBytes === 'number' && Number.isFinite(maxStdoutBytes)
|
|
100
|
+
? maxStdoutBytes
|
|
101
|
+
: LIMITS.maxToolOutputBytes;
|
|
102
|
+
throw new GitError(text.git.outputTruncated(safeMaxBytes), args.join(' '), res.stderr);
|
|
103
|
+
}
|
|
104
|
+
if (res.ok || options.allowError) {
|
|
105
|
+
return { stdout: res.stdout, stderr: res.stderr, code: res.code };
|
|
106
|
+
}
|
|
107
|
+
if (res.timedOut) {
|
|
108
|
+
throw new GitError(text.git.timeout(options.timeoutMs ?? LIMITS.gitTimeoutMs), args.join(' '), res.stderr);
|
|
109
|
+
}
|
|
110
|
+
if (res.code === 128) {
|
|
111
|
+
getLogger().debug(`[GitAdapter] Code 128 Debug: Args: ${args.join(' ')}\nStderr: ${res.stderr}`);
|
|
112
|
+
}
|
|
113
|
+
if (res.error?.message) {
|
|
114
|
+
throw new GitError(text.git.processError(res.error.message), args.join(' '), res.stderr);
|
|
115
|
+
}
|
|
116
|
+
throw new GitError(text.git.commandFailed(res.code), args.join(' '), res.stderr);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Execute validated Git commands with security validation.
|
|
120
|
+
*
|
|
121
|
+
* Note: Despite the historic name, this gateway is not strictly read-only.
|
|
122
|
+
* It allows a small set of well-scoped plumbing commands used by the
|
|
123
|
+
* execution model (e.g., worktree management and ref bookkeeping).
|
|
124
|
+
*/
|
|
125
|
+
async query(args, options = {}) {
|
|
126
|
+
this.assertQueryAllowed(args);
|
|
127
|
+
return this.exec(args, options);
|
|
128
|
+
}
|
|
129
|
+
// ==================== Business Layer ====================
|
|
130
|
+
/**
|
|
131
|
+
* Generates a blob hash for the given content without writing to the object database.
|
|
132
|
+
* Useful for comparing content against git objects.
|
|
133
|
+
*/
|
|
134
|
+
async hashObject(content) {
|
|
135
|
+
const res = await this.execRaw(['hash-object', '--stdin'], { input: content });
|
|
136
|
+
return res.stdout.toString('utf8').trim();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Updates the index with the given content.
|
|
140
|
+
* Uses plumbing commands (hash-object -w + update-index) to avoid touching the working tree.
|
|
141
|
+
*/
|
|
142
|
+
async updateIndex(mode, hash, relativePath) {
|
|
143
|
+
if (!this.isShadowWorktreePath()) {
|
|
144
|
+
throw new GitError(text.git.indexWriteDenied, 'updateIndex', 'Index Write Denied');
|
|
145
|
+
}
|
|
146
|
+
await this.exec(['update-index', '--cacheinfo', mode, hash, relativePath]);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Check if a path is ignored by .gitignore rules.
|
|
150
|
+
* Uses check-ignore plumbing command.
|
|
151
|
+
*/
|
|
152
|
+
async checkIgnore(relativePath) {
|
|
153
|
+
const res = await this.execRaw(['check-ignore', '-q', '--no-index', relativePath], {
|
|
154
|
+
allowError: true,
|
|
155
|
+
});
|
|
156
|
+
return res.code === 0;
|
|
157
|
+
}
|
|
158
|
+
async getStatus(paths) {
|
|
159
|
+
const base = ['status', '--porcelain=v2'];
|
|
160
|
+
if (!paths || paths.length === 0)
|
|
161
|
+
return this.exec(base);
|
|
162
|
+
const safePaths = this.sanitizePaths(paths);
|
|
163
|
+
if (safePaths.length === 0)
|
|
164
|
+
return this.exec(base);
|
|
165
|
+
const batches = splitPathsByCharBudget([...base, '--'], safePaths, LIMITS.gitArgMaxChars);
|
|
166
|
+
const outputs = [];
|
|
167
|
+
for (const batch of batches) {
|
|
168
|
+
outputs.push(await this.exec([...base, '--', ...batch]));
|
|
169
|
+
}
|
|
170
|
+
return outputs.join('\n').replace(/\n+$/, '');
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get the status of a specific path.
|
|
174
|
+
* Uses git status --porcelain -z -- [path] to handle special characters in filenames.
|
|
175
|
+
*
|
|
176
|
+
* @param relativePath - The relative path to check
|
|
177
|
+
* @returns PathStatus object or null if path is not tracked
|
|
178
|
+
*/
|
|
179
|
+
async getStatusForPath(relativePath) {
|
|
180
|
+
const res = await this.execRaw(['status', '--porcelain', '-z', '--', relativePath], {
|
|
181
|
+
allowError: true,
|
|
182
|
+
});
|
|
183
|
+
// If no output, path is not tracked
|
|
184
|
+
if (!res.stdout || res.stdout.length === 0) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const status = res.stdout.toString('utf8');
|
|
188
|
+
const tokens = status.split('\0').filter((token) => token.length > 0);
|
|
189
|
+
if (tokens.length === 0) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
// Parse the status codes
|
|
193
|
+
let staged = false;
|
|
194
|
+
let unstaged = false;
|
|
195
|
+
let untracked = false;
|
|
196
|
+
let deleted = false;
|
|
197
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
198
|
+
const entry = tokens[i];
|
|
199
|
+
const code = entry.slice(0, 2);
|
|
200
|
+
// Handle rename/copy entries
|
|
201
|
+
if (code.startsWith('R') || code.startsWith('C')) {
|
|
202
|
+
// In -z format, renames are: XY PATH1\0PATH2\0
|
|
203
|
+
// entry (tokens[i]) is "XY PATH1", tokens[i+1] is "PATH2"
|
|
204
|
+
const originalPath = entry.slice(3);
|
|
205
|
+
const newPath = tokens[i + 1];
|
|
206
|
+
// Check if this rename/copy affects our target path
|
|
207
|
+
if (originalPath && normalizePath(originalPath) === normalizePath(relativePath)) {
|
|
208
|
+
// Original path matches - this is the "from" side of rename
|
|
209
|
+
const x = code[0]; // Index status
|
|
210
|
+
const y = code[1]; // Working tree status
|
|
211
|
+
staged = staged || (x !== ' ' && x !== '?');
|
|
212
|
+
unstaged = unstaged || y !== ' ';
|
|
213
|
+
deleted = deleted || x === 'D' || y === 'D';
|
|
214
|
+
}
|
|
215
|
+
if (newPath && normalizePath(newPath) === normalizePath(relativePath)) {
|
|
216
|
+
// New path matches - this is the "to" side of rename
|
|
217
|
+
const x = code[0]; // Index status
|
|
218
|
+
const y = code[1]; // Working tree status
|
|
219
|
+
staged = staged || (x !== ' ' && x !== '?');
|
|
220
|
+
unstaged = unstaged || y !== ' ';
|
|
221
|
+
// For the "to" side, it's not deleted (it's the new location)
|
|
222
|
+
}
|
|
223
|
+
i += 1;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
// Extract path from entry
|
|
227
|
+
let pathPart = '';
|
|
228
|
+
if (entry.length > 2) {
|
|
229
|
+
const maybeSep = entry[2];
|
|
230
|
+
if (maybeSep === ' ' || maybeSep === '\t') {
|
|
231
|
+
pathPart = entry.slice(3);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
pathPart = entry.slice(2);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (pathPart && normalizePath(pathPart) === normalizePath(relativePath)) {
|
|
238
|
+
// Correct parsing logic according to Git porcelain format
|
|
239
|
+
const x = code[0]; // Index status
|
|
240
|
+
const y = code[1]; // Working tree status
|
|
241
|
+
// Staged: X is not space or question mark
|
|
242
|
+
staged = staged || (x !== ' ' && x !== '?');
|
|
243
|
+
// Unstaged: Y is not space
|
|
244
|
+
unstaged = unstaged || y !== ' ';
|
|
245
|
+
// Untracked: both X and Y are question marks
|
|
246
|
+
untracked = untracked || (x === '?' && y === '?');
|
|
247
|
+
// Deleted: X or Y contains 'D'
|
|
248
|
+
deleted = deleted || x === 'D' || y === 'D';
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return { staged, unstaged, untracked, deleted };
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Read file content as Buffer to handle binary data safely.
|
|
255
|
+
*/
|
|
256
|
+
async show(revision, filePath) {
|
|
257
|
+
const res = await this.execRaw(['show', `${revision}:${filePath}`]);
|
|
258
|
+
return res.stdout;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Perform a three-way merge on files using git merge-file.
|
|
262
|
+
*/
|
|
263
|
+
async mergeFile(basePath, currentPath, incomingPath, options = {}) {
|
|
264
|
+
const args = ['merge-file', '-p', '-q'];
|
|
265
|
+
if (options.union)
|
|
266
|
+
args.push('--union');
|
|
267
|
+
args.push(currentPath, basePath, incomingPath);
|
|
268
|
+
const res = await this.execRaw(args, { allowError: true });
|
|
269
|
+
// git merge-file exit codes:
|
|
270
|
+
// 0: success, no conflicts
|
|
271
|
+
// 1: success, conflicts present (markers in output)
|
|
272
|
+
// other: error (invalid inputs, unreadable files, etc.)
|
|
273
|
+
// We must treat any non-(0|1) code as a hard failure; otherwise callers may write empty output to disk.
|
|
274
|
+
if (res.code !== 0 && res.code !== 1) {
|
|
275
|
+
throw new GitError('git merge-file failed', args.join(' '), res.stderr);
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
content: res.stdout,
|
|
279
|
+
hasConflict: res.code === 1,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
async applyPatch(diffText, options = {}) {
|
|
283
|
+
await this.lockManager.acquireLock(this.repoPath);
|
|
284
|
+
try {
|
|
285
|
+
// NOTE:
|
|
286
|
+
// - `git apply -3` requires valid preimage blob ids (from `index <old>..<new>` lines).
|
|
287
|
+
// - LLM-generated diffs often contain fake index lines, which triggers:
|
|
288
|
+
// "repository lacks the necessary blob to perform 3-way merge."
|
|
289
|
+
// - For safety, we dynamically decide whether 3-way is possible:
|
|
290
|
+
// - If all referenced old blobs exist, keep index lines and run -3.
|
|
291
|
+
// - Otherwise, strip index lines and fall back to non-3-way apply.
|
|
292
|
+
//
|
|
293
|
+
// FIX: Do not strip index lines if patch is binary, as it corrupts the binary payload.
|
|
294
|
+
const isBinary = /(^|\n)GIT binary patch(\r?\n|$)/.test(diffText) ||
|
|
295
|
+
/(^|\n)(literal|delta) \d+(\r?\n|$)/.test(diffText) ||
|
|
296
|
+
/(^|\n)Binary files .* differ(\r?\n|$)/.test(diffText);
|
|
297
|
+
const extractOldBlobIds = (text) => {
|
|
298
|
+
const ids = new Set();
|
|
299
|
+
const lines = text.split(/\r?\n/);
|
|
300
|
+
for (const line of lines) {
|
|
301
|
+
const m = line.match(/^\s*index\s+([0-9a-f]{7,40})\.\.[0-9a-f]{7,40}(?:\s+\d+)?\s*$/i);
|
|
302
|
+
if (m?.[1])
|
|
303
|
+
ids.add(m[1]);
|
|
304
|
+
}
|
|
305
|
+
return Array.from(ids);
|
|
306
|
+
};
|
|
307
|
+
const blobExists = async (sha) => {
|
|
308
|
+
// `git cat-file -e <sha>` exits 0 if the object exists.
|
|
309
|
+
const res = await this.execRaw(['cat-file', '-e', sha], { allowError: true });
|
|
310
|
+
return res.code === 0;
|
|
311
|
+
};
|
|
312
|
+
let useThreeWay = Boolean(options.threeWay);
|
|
313
|
+
let preserveIndexLines = Boolean(options.preserveIndexLines) || isBinary;
|
|
314
|
+
if (useThreeWay && !preserveIndexLines) {
|
|
315
|
+
const oldIds = extractOldBlobIds(diffText).filter((id) => !/^0+$/.test(id));
|
|
316
|
+
if (oldIds.length === 0) {
|
|
317
|
+
// No index lines means -3 is not possible; fall back to direct apply.
|
|
318
|
+
useThreeWay = false;
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
for (const id of oldIds) {
|
|
322
|
+
const exists = await blobExists(id);
|
|
323
|
+
if (!exists) {
|
|
324
|
+
useThreeWay = false;
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// Only preserve index lines if we can actually do 3-way.
|
|
329
|
+
preserveIndexLines = useThreeWay;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
let cleanedDiff = diffText;
|
|
333
|
+
if (!preserveIndexLines) {
|
|
334
|
+
cleanedDiff = diffText
|
|
335
|
+
.split(/\r?\n/)
|
|
336
|
+
.filter((l) => {
|
|
337
|
+
const trimmedStart = l.trimStart();
|
|
338
|
+
const lower = trimmedStart.toLowerCase();
|
|
339
|
+
return !(lower.startsWith('index ') || lower.startsWith('index\t'));
|
|
340
|
+
})
|
|
341
|
+
.join('\n');
|
|
342
|
+
}
|
|
343
|
+
const tempFile = path.join(tmpdir(), `salmon-patch-${Date.now()}-${randomBytes(4).toString('hex')}.patch`);
|
|
344
|
+
await fs.writeFile(tempFile, cleanedDiff, 'utf8');
|
|
345
|
+
try {
|
|
346
|
+
const args = ['apply', '--recount'];
|
|
347
|
+
if (useThreeWay)
|
|
348
|
+
args.push('-3');
|
|
349
|
+
if (options.ignoreWhitespace)
|
|
350
|
+
args.push('--ignore-whitespace');
|
|
351
|
+
if (options.contextLines)
|
|
352
|
+
args.push(`-C${options.contextLines}`);
|
|
353
|
+
args.push(tempFile);
|
|
354
|
+
await this.exec(args, { env: options.env });
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
await fs
|
|
358
|
+
.unlink(tempFile)
|
|
359
|
+
.catch((error) => logIgnoredError(`[GitAdapter] cleanup ${tempFile}`, error));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
finally {
|
|
363
|
+
await this.lockManager.releaseLock(this.repoPath);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Precision rollback protecting staged changes.
|
|
368
|
+
*/
|
|
369
|
+
async rollbackFiles(paths, ref) {
|
|
370
|
+
const safePaths = this.sanitizePaths(paths);
|
|
371
|
+
if (safePaths.length === 0)
|
|
372
|
+
return;
|
|
373
|
+
await this.lockManager.acquireLock(this.repoPath);
|
|
374
|
+
try {
|
|
375
|
+
// CRITICAL SAFETY: Restores from Index to Worktree to preserve user's staged changes.
|
|
376
|
+
const base = ['checkout'];
|
|
377
|
+
if (ref)
|
|
378
|
+
base.push(ref);
|
|
379
|
+
const batches = splitPathsByCharBudget([...base, '--'], safePaths, LIMITS.gitArgMaxChars);
|
|
380
|
+
if (batches.length <= 1) {
|
|
381
|
+
await this.exec([...base, '--', ...safePaths]);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
// Avoid ARG_MAX and reduce partial-rollback risk by using a single checkout invocation.
|
|
385
|
+
const tempFile = path.join(tmpdir(), `salmon-pathspec-${Date.now()}-${randomBytes(4).toString('hex')}.txt`);
|
|
386
|
+
await fs.writeFile(tempFile, joinNulTerminated(safePaths));
|
|
387
|
+
try {
|
|
388
|
+
await this.exec([...base, '--pathspec-from-file', tempFile, '--pathspec-file-nul']);
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
// Fallback for older Git versions without pathspec-from-file support.
|
|
392
|
+
const msg = error instanceof Error
|
|
393
|
+
? error instanceof Error
|
|
394
|
+
? error.message
|
|
395
|
+
: String(error)
|
|
396
|
+
: String(error);
|
|
397
|
+
if (!/pathspec-from-file/i.test(msg))
|
|
398
|
+
throw error;
|
|
399
|
+
for (const batch of batches) {
|
|
400
|
+
await this.exec([...base, '--', ...batch]);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
finally {
|
|
404
|
+
await fs
|
|
405
|
+
.unlink(tempFile)
|
|
406
|
+
.catch((error) => logIgnoredError(`[GitAdapter] cleanup ${tempFile}`, error));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
try {
|
|
411
|
+
await this.resolveConflicts();
|
|
412
|
+
}
|
|
413
|
+
catch (cleanupError) {
|
|
414
|
+
const cleanupMessage = cleanupError instanceof Error
|
|
415
|
+
? cleanupError instanceof Error
|
|
416
|
+
? cleanupError.message
|
|
417
|
+
: String(cleanupError)
|
|
418
|
+
: String(cleanupError);
|
|
419
|
+
const rollbackMessage = error instanceof Error
|
|
420
|
+
? error instanceof Error
|
|
421
|
+
? error.message
|
|
422
|
+
: String(error)
|
|
423
|
+
: String(error);
|
|
424
|
+
throw new GitError(`${cleanupMessage}; original rollback error: ${rollbackMessage}`, 'rollbackFiles', cleanupMessage);
|
|
425
|
+
}
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
finally {
|
|
429
|
+
await this.lockManager.releaseLock(this.repoPath);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Compatibility alias for rollbackFiles.
|
|
434
|
+
* If paths is a string (legacy call), it treats it as a single file rollback or handles it gracefully.
|
|
435
|
+
*/
|
|
436
|
+
async safeRollback(paths, ref) {
|
|
437
|
+
const pathArray = Array.isArray(paths) ? paths : [paths];
|
|
438
|
+
return this.rollbackFiles(pathArray, ref);
|
|
439
|
+
}
|
|
440
|
+
sanitizePaths(paths) {
|
|
441
|
+
return paths
|
|
442
|
+
.map((p) => p.trim().replace(/\\/g, '/'))
|
|
443
|
+
.filter((p) => p && !p.startsWith('/') && !p.includes('..'));
|
|
444
|
+
}
|
|
445
|
+
assertQueryAllowed(args) {
|
|
446
|
+
const cmd = args[0];
|
|
447
|
+
if (!cmd)
|
|
448
|
+
throw new Error(text.git.securityViolation(String(cmd)));
|
|
449
|
+
const allowed = new Set([
|
|
450
|
+
'diff',
|
|
451
|
+
'for-each-ref',
|
|
452
|
+
'log',
|
|
453
|
+
'ls-files',
|
|
454
|
+
'ls-tree',
|
|
455
|
+
'read-tree',
|
|
456
|
+
'rev-parse',
|
|
457
|
+
'show',
|
|
458
|
+
'status',
|
|
459
|
+
'update-index',
|
|
460
|
+
'update-ref',
|
|
461
|
+
'worktree',
|
|
462
|
+
'write-tree',
|
|
463
|
+
]);
|
|
464
|
+
if (!allowed.has(cmd))
|
|
465
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
466
|
+
if (cmd === 'diff') {
|
|
467
|
+
if (args.includes('--no-index'))
|
|
468
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
469
|
+
}
|
|
470
|
+
if (cmd === 'write-tree') {
|
|
471
|
+
if (args.length !== 1)
|
|
472
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
473
|
+
}
|
|
474
|
+
if (cmd === 'read-tree') {
|
|
475
|
+
if (args.length !== 2 || !isShaLike(args[1])) {
|
|
476
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (cmd === 'update-index') {
|
|
480
|
+
const ok = args.length === 3 && args[1] === '-q' && args[2] === '--refresh';
|
|
481
|
+
if (!ok)
|
|
482
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
483
|
+
}
|
|
484
|
+
if (cmd === 'update-ref') {
|
|
485
|
+
const hasMessage = args.length === 5 && args[1] === '-m';
|
|
486
|
+
if (!hasMessage)
|
|
487
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
488
|
+
const refName = args[3];
|
|
489
|
+
const newValue = args[4];
|
|
490
|
+
if (!refName.startsWith('refs/s8p/'))
|
|
491
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
492
|
+
if (!isShaLike(newValue))
|
|
493
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
494
|
+
}
|
|
495
|
+
if (cmd === 'for-each-ref') {
|
|
496
|
+
const refPrefix = args[args.length - 1];
|
|
497
|
+
if (!refPrefix || !refPrefix.startsWith('refs/s8p/snapshots/')) {
|
|
498
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
499
|
+
}
|
|
500
|
+
const allowedPrefixes = ['--sort=', '--format=', '--count='];
|
|
501
|
+
for (const token of args.slice(1, -1)) {
|
|
502
|
+
if (!allowedPrefixes.some((p) => token.startsWith(p))) {
|
|
503
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (cmd === 'worktree') {
|
|
508
|
+
const sub = args[1];
|
|
509
|
+
const shadowRoot = GitAdapter.resolveShadowRoot();
|
|
510
|
+
if (sub === 'list') {
|
|
511
|
+
const ok = args.length === 3 && args[2] === '--porcelain';
|
|
512
|
+
if (!ok)
|
|
513
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (sub === 'add') {
|
|
517
|
+
const allowedFlags = new Set(['--quiet', '--detach']);
|
|
518
|
+
const flags = new Set();
|
|
519
|
+
let i = 2;
|
|
520
|
+
for (; i < args.length && args[i]?.startsWith('-'); i++) {
|
|
521
|
+
const token = args[i];
|
|
522
|
+
if (!allowedFlags.has(token))
|
|
523
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
524
|
+
flags.add(token);
|
|
525
|
+
}
|
|
526
|
+
if (!flags.has('--detach'))
|
|
527
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
528
|
+
if (args.length - i !== 2)
|
|
529
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
530
|
+
const worktreePath = path.resolve(args[i]);
|
|
531
|
+
const baseRef = args[i + 1];
|
|
532
|
+
const parityRoot = GitAdapter.resolveParityShadowRoot(this.repoPath);
|
|
533
|
+
const allowed = [shadowRoot, parityRoot].some((root) => isPathWithinDirectory(root, worktreePath, { allowEqual: false }));
|
|
534
|
+
if (!allowed) {
|
|
535
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
536
|
+
}
|
|
537
|
+
if (!baseRef || baseRef.includes('..'))
|
|
538
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (sub === 'remove') {
|
|
542
|
+
const allowedFlags = new Set(['--force']);
|
|
543
|
+
const flags = new Set();
|
|
544
|
+
let i = 2;
|
|
545
|
+
for (; i < args.length && args[i]?.startsWith('-'); i++) {
|
|
546
|
+
const token = args[i];
|
|
547
|
+
if (!allowedFlags.has(token))
|
|
548
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
549
|
+
flags.add(token);
|
|
550
|
+
}
|
|
551
|
+
if (!flags.has('--force'))
|
|
552
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
553
|
+
if (args.length - i !== 1)
|
|
554
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
555
|
+
const worktreePath = path.resolve(args[i]);
|
|
556
|
+
const parityRoot = GitAdapter.resolveParityShadowRoot(this.repoPath);
|
|
557
|
+
const allowed = [shadowRoot, parityRoot].some((root) => isPathWithinDirectory(root, worktreePath, { allowEqual: false }));
|
|
558
|
+
if (!allowed) {
|
|
559
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
560
|
+
}
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
throw new Error(text.git.securityViolation(cmd));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Resolves the canonical path to the shadow worktree root directory.
|
|
568
|
+
*
|
|
569
|
+
* SECURITY: This method implements DOUBLE realpath resolution to prevent attacks:
|
|
570
|
+
* 1. Prevents symlink attacks: Even if tmpdir() is a symlink, we resolve to the real path
|
|
571
|
+
* 2. Prevents TMPDIR pollution: Attackers cannot trick the system by manipulating TMPDIR env var
|
|
572
|
+
*
|
|
573
|
+
* Why this matters:
|
|
574
|
+
* - Shadow worktrees are the ONLY safe place for destructive operations (reset --hard, clean -fd)
|
|
575
|
+
* - If an attacker could bypass this check, they could trigger data loss in the main repository
|
|
576
|
+
* - Using realpathSync ensures we compare canonical paths, not attacker-controlled symlinks
|
|
577
|
+
*
|
|
578
|
+
* @returns Canonical shadow root path (e.g., "/tmp/s8p-wt")
|
|
579
|
+
*/
|
|
580
|
+
static resolveShadowRoot() {
|
|
581
|
+
const tmpResolved = path.resolve(tmpdir());
|
|
582
|
+
let tmpReal = tmpResolved;
|
|
583
|
+
try {
|
|
584
|
+
// CRITICAL: Resolve symlinks in tmpdir() itself
|
|
585
|
+
// Example: /tmp -> /private/tmp on macOS
|
|
586
|
+
tmpReal = realpathSync(tmpResolved);
|
|
587
|
+
}
|
|
588
|
+
catch {
|
|
589
|
+
// Fall back to resolved path. If tmp is not realpath-resolvable, prefer denying shadow checks elsewhere.
|
|
590
|
+
tmpReal = tmpResolved;
|
|
591
|
+
}
|
|
592
|
+
return path.join(tmpReal, 's8p-wt');
|
|
593
|
+
}
|
|
594
|
+
static resolveParityShadowRoot(repoPath) {
|
|
595
|
+
const repoResolved = path.resolve(repoPath);
|
|
596
|
+
let repoReal = repoResolved;
|
|
597
|
+
try {
|
|
598
|
+
repoReal = realpathSync(repoResolved);
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
repoReal = repoResolved;
|
|
602
|
+
}
|
|
603
|
+
const parent = path.dirname(repoReal);
|
|
604
|
+
return path.join(parent, '.salmonloop', 'worktrees');
|
|
605
|
+
}
|
|
606
|
+
static resolveParityShadowRootFromPath(repoPath) {
|
|
607
|
+
const parsed = path.parse(repoPath);
|
|
608
|
+
const rel = repoPath.slice(parsed.root.length);
|
|
609
|
+
const parts = rel.split(path.sep).filter((part) => part.length > 0);
|
|
610
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
611
|
+
if (parts[i] === '.salmonloop' && parts[i + 1] === 'worktrees') {
|
|
612
|
+
return path.join(parsed.root, ...parts.slice(0, i + 2));
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Verifies if the current GitAdapter instance points to a shadow worktree.
|
|
619
|
+
*
|
|
620
|
+
* SECURITY: Multi-layer defense against path traversal and symlink attacks:
|
|
621
|
+
* 1. Both shadowRoot AND repoPath are resolved via realpathSync
|
|
622
|
+
* 2. Directory-aware containment check prevents partial matches (e.g., /tmp/s8p-wt-evil)
|
|
623
|
+
* 3. String prefix pitfalls are avoided by path.relative semantics
|
|
624
|
+
*
|
|
625
|
+
* Why this check is CRITICAL:
|
|
626
|
+
* - Operations like resolveConflicts() execute `git reset --hard HEAD` and `git clean -fd`
|
|
627
|
+
* - These commands PERMANENTLY DELETE uncommitted changes and untracked files
|
|
628
|
+
* - This check ensures such operations ONLY run in disposable shadow worktrees
|
|
629
|
+
* - The main repository is NEVER touched by destructive cleanup operations
|
|
630
|
+
*
|
|
631
|
+
* Attack scenarios prevented:
|
|
632
|
+
* - Symlink injection: realpathSync resolves links before comparison
|
|
633
|
+
* - Path traversal: strict prefix check prevents escape via ../
|
|
634
|
+
* - Partial path matching: trailing separator prevents /tmp/s8p-wt-malicious from matching
|
|
635
|
+
*
|
|
636
|
+
* @returns true if this.repoPath is inside the shadow worktree root, false otherwise
|
|
637
|
+
*/
|
|
638
|
+
isShadowWorktreePath() {
|
|
639
|
+
const expectedRoot = GitAdapter.resolveShadowRoot();
|
|
640
|
+
const repoResolved = path.resolve(this.repoPath);
|
|
641
|
+
let repo = repoResolved;
|
|
642
|
+
try {
|
|
643
|
+
// CRITICAL: Resolve symlinks in the repo path being checked
|
|
644
|
+
// Prevents attacker from creating a symlink to main repo inside shadow root
|
|
645
|
+
repo = realpathSync(repoResolved);
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
repo = repoResolved;
|
|
649
|
+
}
|
|
650
|
+
if (isPathWithinDirectory(expectedRoot, repo, { allowEqual: false }))
|
|
651
|
+
return true;
|
|
652
|
+
const parityRoot = GitAdapter.resolveParityShadowRootFromPath(repo);
|
|
653
|
+
if (!parityRoot)
|
|
654
|
+
return false;
|
|
655
|
+
return isPathWithinDirectory(parityRoot, repo, { allowEqual: false });
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Aggressively cleans the shadow worktree to resolve merge conflicts or corrupted state.
|
|
659
|
+
*
|
|
660
|
+
* ⚠️ DANGER: This method runs DESTRUCTIVE Git operations:
|
|
661
|
+
* - `git reset --hard HEAD`: Discards ALL uncommitted changes
|
|
662
|
+
* - `git clean -fd`: Deletes ALL untracked files and directories
|
|
663
|
+
*
|
|
664
|
+
* WHY THIS IS SAFE (not a data loss risk):
|
|
665
|
+
* 1. Protected by isShadowWorktreePath() check - throws error if not in shadow
|
|
666
|
+
* 2. Shadow worktrees are DISPOSABLE temporary directories (e.g., /tmp/s8p-wt/...)
|
|
667
|
+
* 3. User's main repository is NEVER touched - it remains in read-only state
|
|
668
|
+
* 4. Original state is preserved in snapshot commits (refs/s8p/snapshots/*)
|
|
669
|
+
* 5. Apply-back process is transactional - failures don't corrupt main workspace
|
|
670
|
+
*
|
|
671
|
+
* Design Intent (see docs/design/checkpoint.md):
|
|
672
|
+
* - "AI modifications run in a disposable 'Shadow Worktree', never polluting
|
|
673
|
+
* the user's primary workspace until verified"
|
|
674
|
+
*
|
|
675
|
+
* Common Misunderstanding:
|
|
676
|
+
* ❌ "reset --hard will lose user data"
|
|
677
|
+
* ✅ Correct: It only affects the temporary shadow, which is recreated from snapshots
|
|
678
|
+
*
|
|
679
|
+
* Error Handling:
|
|
680
|
+
* - allowError: true on stash - may have nothing to stash, that's fine
|
|
681
|
+
* - Catch block ignores errors - this is best-effort cleanup in a disposable environment
|
|
682
|
+
* - Even if cleanup fails, shadow worktree is deleted during teardown anyway
|
|
683
|
+
*
|
|
684
|
+
* @throws GitError if called on main repository (safety violation)
|
|
685
|
+
*/
|
|
686
|
+
async resolveConflicts() {
|
|
687
|
+
// SAFETY BARRIER: Absolutely refuse to run destructive operations outside shadow
|
|
688
|
+
if (!this.isShadowWorktreePath()) {
|
|
689
|
+
throw new GitError(text.git.conflictResolutionDenied, 'resolveConflicts', 'Safety Check Failed');
|
|
690
|
+
}
|
|
691
|
+
try {
|
|
692
|
+
// Best-effort cleanup sequence for disposable shadow worktree
|
|
693
|
+
await this.exec(['stash'], { allowError: true });
|
|
694
|
+
await this.exec(['reset', '--hard', 'HEAD']);
|
|
695
|
+
await this.exec(['clean', '-fd']);
|
|
696
|
+
}
|
|
697
|
+
catch (_e) {
|
|
698
|
+
// Best-effort cleanup, ignore errors
|
|
699
|
+
// Rationale: Shadow worktree will be deleted during teardown anyway
|
|
700
|
+
// Logging errors here would just create noise for expected edge cases
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
//# sourceMappingURL=git-adapter.js.map
|