@moretea-labs/forge 1.5.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +46 -0
- package/CLAUDE.md +2 -0
- package/DESIGN.md +7 -2
- package/README.md +4 -4
- package/README.zh-CN.md +2 -2
- package/SKILL.md +2 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/hooks/hook-input.sh +11 -8
- package/assets/hooks/lib/session-state.sh +10 -7
- package/assets/hooks/lib/workflow-state.sh +13 -177
- package/assets/hooks/post-tool-observer.sh +8 -2
- package/assets/hooks/pre-edit-guard.sh +8 -39
- package/assets/hooks/prompt-guard.sh +7 -83
- package/assets/hooks/session-start-context.sh +56 -1
- package/assets/partials/05-workflow.partial.md +1 -1
- package/assets/partials-agents/03-orchestration.partial.md +1 -1
- package/assets/plugin-registry.v1.json +54 -3
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/harness-overview.md +1 -1
- package/assets/reference-configs/hook-operations.md +1 -1
- package/assets/reference-configs/sprint-contracts.md +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/contract.template.md +2 -7
- package/assets/templates/helpers/capture-plan.sh +2 -2
- package/assets/templates/helpers/check-task-workflow.sh +4 -4
- package/assets/templates/helpers/contract-worktree.sh +1 -21
- package/assets/templates/helpers/ensure-task-workflow.sh +13 -38
- package/assets/templates/helpers/migrate-project-template.sh +22 -45
- package/assets/templates/helpers/new-plan.sh +1 -1
- package/assets/templates/helpers/plan-to-todo.sh +12 -37
- package/assets/templates/helpers/ship-worktrees.sh +2 -29
- package/assets/templates/helpers/sprint-backlog.sh +6 -6
- package/assets/templates/helpers/verify-contract.sh +1 -111
- package/assets/templates/helpers/verify-sprint.sh +3 -180
- package/assets/templates/plan.template.md +1 -1
- package/assets/templates/review.template.md +9 -34
- package/assets/workflow-contract.v1.json +10 -19
- package/docs/README.md +10 -10
- package/docs/architecture/CURRENT.md +170 -0
- package/docs/architecture/EVOLUTION.md +42 -0
- package/docs/architecture/current/README.md +4 -156
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
- package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
- package/docs/architecture/history/README.md +7 -0
- package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
- package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
- package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
- package/docs/architecture/history.md +3 -34
- package/docs/architecture/index.md +15 -98
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
- package/docs/architecture/versions/1.6.md +38 -0
- package/docs/architecture/versions/1.7.md +41 -0
- package/docs/forge-chatgpt-browser-engine.md +2 -2
- package/docs/forge-chatgpt-mcp-setup.md +4 -28
- package/docs/forge-chatgpt-supervised-automation.md +3 -3
- package/docs/forge-github-issue-launcher.md +1 -1
- package/docs/forge-runtime-self-healing-loop.md +1 -1
- package/docs/operations/controller-browser-plugin.md +38 -22
- package/docs/operations/controller-performance-and-502.md +124 -0
- package/docs/operations/controller-reliability-runbook.md +1 -1
- package/docs/operations/features.md +3 -3
- package/docs/operations/github-repository.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +5 -3
- package/docs/tutorials/01-install-and-start.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/wiki/Architecture.md +6 -6
- package/docs/wiki/Implementation.md +2 -2
- package/docs/wiki/Installation.md +1 -1
- package/docs/wiki/Integrations.md +2 -2
- package/docs/wiki/Quick-Start.md +1 -1
- package/docs/wiki/Work-Lifecycle.md +1 -1
- package/package.json +29 -10
- package/scripts/activate-source-baseline.ts +17 -13
- package/scripts/benchmark-background-check-overlap.ts +228 -0
- package/scripts/benchmark-mcp-host.sh +117 -0
- package/scripts/benchmark-mcp-transport.ts +170 -0
- package/scripts/benchmark-quality-harness.ts +146 -0
- package/scripts/benchmark-route-session-concurrency.ts +207 -6
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
- package/scripts/benchmark-typescript-navigation.ts +186 -0
- package/scripts/benchmark-work-efficiency.ts +334 -0
- package/scripts/capture-plan.sh +3 -3
- package/scripts/check-agent-tooling.sh +8 -35
- package/scripts/check-architecture-sync.sh +17 -86
- package/scripts/check-mcp-compatibility.ts +14 -0
- package/scripts/check-public-docs.sh +14 -1
- package/scripts/check-runtime-architecture.mjs +107 -64
- package/scripts/check-stable-baseline.ts +92 -0
- package/scripts/check-task-workflow.sh +4 -4
- package/scripts/contract-worktree.sh +1 -21
- package/scripts/ensure-task-workflow.sh +22 -55
- package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
- package/scripts/install-standalone-recovery.ts +24 -0
- package/scripts/lib/project-init-lib.sh +15 -44
- package/scripts/migrate-chatgpt-automation-schedules.ts +64 -0
- package/scripts/migrate-project-template.sh +23 -1179
- package/scripts/new-plan.sh +1 -1
- package/scripts/plan-to-todo.sh +11 -36
- package/scripts/run-governed-gate.ts +2 -1
- package/scripts/ship-worktrees.sh +2 -29
- package/scripts/smoke-ios-physical-device.ts +4 -2
- package/scripts/smoke-runtime-recovery.ts +1 -35
- package/scripts/smoke-schedule-engine.ts +1 -20
- package/scripts/sprint-backlog.sh +1 -1
- package/scripts/verify-contract.sh +1 -111
- package/scripts/verify-forge-runtime.sh +0 -1
- package/scripts/verify-sprint.sh +3 -180
- package/scripts/workflow-contract.ts +1 -5
- package/src/cli/commands/chatgpt.ts +31 -2
- package/src/cli/commands/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +37 -10
- package/src/cli/commands/recovery.ts +25 -3
- package/src/cli/commands/runtime.ts +55 -3
- package/src/cli/commands/security.ts +12 -1
- package/src/cli/commands/setup-profile.ts +31 -21
- package/src/cli/controller/check-runner.ts +106 -3
- package/src/cli/controller/composite-result.ts +1 -1
- package/src/cli/controller/context/focus.ts +51 -0
- package/src/cli/controller/context/instruction-context.ts +114 -0
- package/src/cli/controller/context/known-paths.ts +76 -0
- package/src/cli/controller/context/query-planning.ts +110 -0
- package/src/cli/controller/context/rule-resolution.ts +532 -0
- package/src/cli/controller/context/source-materializer.ts +278 -0
- package/src/cli/controller/context/structural.ts +16 -0
- package/src/cli/controller/context/types.ts +286 -0
- package/src/cli/controller/context-pack.ts +898 -430
- package/src/cli/controller/launch-agents.ts +77 -2
- package/src/cli/controller/operational-plan.ts +2 -2
- package/src/cli/controller/work-mode.ts +6 -4
- package/src/cli/editing/edit-session.ts +60 -7
- package/src/cli/github/github.ts +133 -0
- package/src/cli/hook/prompt-guard-decision.ts +17 -18
- package/src/cli/hook/prompt-intents.ts +1 -1
- package/src/cli/installer/managed-entries.ts +15 -1
- package/src/cli/local-bridge/console-automations.ts +225 -67
- package/src/cli/local-bridge/console-view-models.ts +3 -35
- package/src/cli/local-bridge/facade-api.ts +2 -217
- package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +153 -3
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +183 -3
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +4 -3
- package/src/cli/local-bridge/frontend/src/lib/api.ts +18 -11
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +4 -3
- package/src/cli/local-bridge/frontend/src/styles.css +27 -11
- package/src/cli/local-bridge/frontend/src/types.ts +8 -7
- package/src/cli/local-bridge/job-store.ts +1 -490
- package/src/cli/local-bridge/server.ts +29 -532
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +5101 -4967
- package/src/cli/mcp/auth.ts +5 -5
- package/src/cli/mcp/instructions.ts +3 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -189
- package/src/cli/mcp/multi-repository.ts +16 -5
- package/src/cli/mcp/oauth.ts +175 -26
- package/src/cli/mcp/paths.ts +8 -2
- package/src/cli/mcp/repository-tools.ts +356 -101
- package/src/cli/mcp/server.ts +625 -54
- package/src/cli/mcp/tool-capability-cache.ts +1 -1
- package/src/cli/mcp/toolset-selection.ts +1 -1
- package/src/cli/mcp/toolset.ts +42 -5
- package/src/cli/mcp/transports/http.ts +125 -15
- package/src/cli/mcp/transports/session-registry.ts +67 -5
- package/src/cli/mcp/types.ts +3 -3
- package/src/cli/repositories/command-classifier.ts +209 -14
- package/src/cli/repositories/command-executor.ts +55 -521
- package/src/cli/repositories/command-process.ts +108 -0
- package/src/cli/repositories/command-scope.ts +99 -41
- package/src/cli/repositories/controller-home.ts +7 -8
- package/src/cli/repositories/git-command-executor.ts +2 -0
- package/src/cli/repositories/locks.ts +53 -16
- package/src/cli/repositories/repository-snapshot.ts +121 -0
- package/src/cli/repositories/safe-patch.ts +6 -4
- package/src/cli/repositories/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +110 -10
- package/src/cli/repository/inspector.ts +565 -62
- package/src/cli/repository/session-cache.ts +58 -17
- package/src/cli/v81-entry.ts +1 -4
- package/src/effects/fs-transaction.ts +85 -17
- package/src/runtime/context/codegraph-read-provider.ts +233 -2
- package/src/runtime/context/codegraph-sidecar.cjs +74 -40
- package/src/runtime/context/generic-lsp-provider.ts +355 -0
- package/src/runtime/context/lsp-client.ts +283 -0
- package/src/runtime/context/semantic-navigation.ts +275 -0
- package/src/runtime/context/swift-navigation.ts +565 -0
- package/src/runtime/context/typescript-navigation.ts +226 -0
- package/src/runtime/control-plane/dispatch-priority.ts +28 -0
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +196 -2
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- package/src/runtime/control-plane/execution/execution-identity.ts +65 -14
- package/src/runtime/control-plane/execution/session-store.ts +18 -0
- package/src/runtime/control-plane/execution/validation.ts +15 -2
- package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
- package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +175 -8
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -21
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +87 -9
- package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
- package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
- package/src/runtime/control-plane/facade/controller-session-store.ts +144 -14
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +9 -2
- package/src/runtime/control-plane/facade/goal-workloop.ts +885 -105
- package/src/runtime/control-plane/facade/index.ts +1 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +211 -26
- package/src/runtime/control-plane/facade/requirement-board.ts +24 -8
- package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
- package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
- package/src/runtime/control-plane/facade/types.ts +55 -11
- package/src/runtime/control-plane/facade/work-contract-store.ts +204 -73
- package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
- package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
- package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
- package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
- package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
- package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
- package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
- package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
- package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
- package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +642 -51
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
- package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
- package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
- package/src/runtime/control-plane/persistence/requirement-store.ts +15 -11
- package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
- package/src/runtime/control-plane/release-retention.ts +62 -2
- package/src/runtime/control-plane/routing/route-policy.ts +7 -16
- package/src/runtime/control-plane/runtime-status-client.ts +0 -2
- package/src/runtime/diagnostics/mcp-timing.ts +19 -0
- package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
- package/src/runtime/diagnostics/performance.ts +24 -1
- package/src/runtime/evidence/event-ledger.ts +1 -1
- package/src/runtime/execution/jobs/store.ts +58 -4
- package/src/runtime/execution/jobs/types.ts +1 -1
- package/src/runtime/execution/managed-workspace.ts +203 -1
- package/src/runtime/execution/process-runtime/check-facade.ts +52 -14
- package/src/runtime/execution/process-runtime/check-result.ts +63 -2
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +346 -40
- package/src/runtime/execution/process-runtime/gc.ts +98 -12
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +57 -6
- package/src/runtime/execution/process-runtime/resource-claims.ts +97 -8
- package/src/runtime/execution/process-runtime/runtime.ts +59 -4
- package/src/runtime/execution/process-runtime/store.ts +7 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -0
- package/src/runtime/execution/thin-harness/async-process.ts +12 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
- package/src/runtime/execution/workers/executor.ts +0 -48
- package/src/runtime/gateway/mcp/execution-tools.ts +1219 -126
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +185 -83
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2555 -1616
- package/src/runtime/model-clients/deepseek-adapter.ts +0 -32
- package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
- package/src/runtime/plugins/browser-adapter.ts +2925 -318
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff-host.ts +6 -31
- package/src/runtime/plugins/browser-handoff.ts +92 -5
- package/src/runtime/plugins/browser-macos-bridge.ts +586 -45
- package/src/runtime/plugins/browser-node-bridge.ts +6 -34
- package/src/runtime/plugins/browser-provider-registry.ts +109 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
- package/src/runtime/plugins/browser-runtime.ts +183 -0
- package/src/runtime/plugins/browser-session-authority.ts +332 -0
- package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +15 -12
- package/src/runtime/plugins/external-adapter.ts +324 -3
- package/src/runtime/plugins/external-registration.ts +84 -5
- package/src/runtime/plugins/external-unix-socket.ts +7 -1
- package/src/runtime/plugins/first-party-registry.ts +4 -2
- package/src/runtime/plugins/github-adapter.ts +125 -0
- package/src/runtime/plugins/gmail-adapter.ts +28 -2
- package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
- package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
- package/src/runtime/plugins/ios-adapter.ts +106 -16
- package/src/runtime/plugins/ios-agent-device.ts +234 -64
- package/src/runtime/plugins/ios-physical-device.ts +1143 -392
- package/src/runtime/plugins/lightweight-action.ts +153 -0
- package/src/runtime/plugins/local-system-adapter.ts +125 -8
- package/src/runtime/plugins/macos-capability-broker.ts +55 -9
- package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
- package/src/runtime/plugins/plugin-management-adapter.ts +181 -0
- package/src/runtime/plugins/resend-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +366 -24
- package/src/runtime/plugins/types.ts +39 -2
- package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
- package/src/runtime/projections/git-status-sampler.ts +16 -16
- package/src/runtime/projections/materialized-view.ts +28 -10
- package/src/runtime/recovery/actions.ts +0 -10
- package/src/runtime/recovery/classifier.ts +0 -9
- package/src/runtime/recovery/maintenance-executor.ts +415 -18
- package/src/runtime/recovery/probe.ts +0 -1
- package/src/runtime/recovery/types.ts +0 -1
- package/src/runtime/root/mcp-transport.ts +180 -10
- package/src/runtime/root/package-connector-service.ts +186 -8
- package/src/runtime/root/package-runtime-release.ts +143 -17
- package/src/runtime/root/package-runtime-service.ts +430 -22
- package/src/runtime/root/release-manifest.ts +7 -2
- package/src/runtime/root/release-materialize.ts +162 -19
- package/src/runtime/root/release-store.ts +48 -11
- package/src/runtime/root/runtime.ts +23 -5
- package/src/runtime/root/service.ts +100 -27
- package/src/runtime/root/types.ts +2 -0
- package/src/runtime/safe-tooling/index.ts +0 -1
- package/src/runtime/safe-tooling/ios-development.ts +321 -63
- package/src/runtime/safe-tooling/types.ts +0 -30
- package/src/runtime/shared/trusted-executable.ts +62 -0
- package/src/runtime/shared/trusted-node-executable.ts +48 -0
- package/src/runtime/standalone-recovery/core.ts +702 -94
- package/src/runtime/standalone-recovery/entry.ts +99 -23
- package/src/runtime/standalone-recovery/installer.ts +19 -3
- package/src/runtime/standalone-recovery/service-labels.ts +2 -0
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
- package/src/runtime/workflow/schedules/browser-probe.ts +258 -0
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +40 -0
- package/src/runtime/workflow/schedules/engine.ts +509 -80
- package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
- package/src/runtime/workflow/schedules/settlement.ts +48 -4
- package/src/runtime/workflow/schedules/store.ts +131 -10
- package/src/runtime/workflow/schedules/types.ts +6 -3
- package/src/runtime/workflow/schedules/work-continuation.ts +120 -34
- package/src/testing/test-governance.ts +8 -40
- package/assets/reference-configs/heartbeat-triage.md +0 -57
- package/assets/templates/helpers/harness-trace-grade.sh +0 -150
- package/assets/templates/helpers/heartbeat-triage.sh +0 -361
- package/assets/templates/helpers/maintenance-triage.sh +0 -167
- package/bin/forge-desktop-helper.mjs +0 -204
- package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
- package/docs/architecture/current/architecture-invariants.md +0 -331
- package/docs/architecture/current/automation-and-schedule-engine.md +0 -453
- package/docs/architecture/current/codegraph-context-provider.md +0 -226
- package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
- package/docs/architecture/current/controller-repository-decoupling.md +0 -148
- package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -354
- package/docs/architecture/current/entity-model.md +0 -592
- package/docs/architecture/current/external-plugin-governance.md +0 -396
- package/docs/architecture/current/failure-recovery.md +0 -447
- package/docs/architecture/current/governance.md +0 -213
- package/docs/architecture/current/human-interaction-plane.md +0 -87
- package/docs/architecture/current/implementation-status.md +0 -128
- package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
- package/docs/architecture/current/legacy-governance.md +0 -58
- package/docs/architecture/current/local-workspace-target-grants.md +0 -70
- package/docs/architecture/current/migration-roadmap.md +0 -105
- package/docs/architecture/current/multi-repository-execution.md +0 -371
- package/docs/architecture/current/operations-runbook.md +0 -45
- package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
- package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
- package/docs/architecture/current/repository-access-modes.md +0 -102
- package/docs/architecture/current/route-policy.md +0 -15
- package/docs/architecture/current/route-session-concurrency.md +0 -85
- package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
- package/docs/architecture/current/runtime-directory-map.md +0 -49
- package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
- package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
- package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
- package/docs/architecture/current/system-overview.md +0 -144
- package/docs/architecture/current/target-requirements-traceability.md +0 -202
- package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
- package/docs/architecture/current/thin-harness-v1.md +0 -311
- package/docs/architecture/current/verification-and-release-gates.md +0 -391
- package/docs/forge-autonomous-goal-loop.md +0 -9
- package/docs/forge-provider-configuration.md +0 -7
- package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -83
- package/docs/operations/controller-desktop-plugin.md +0 -112
- package/docs/operations/gmail-assistant-routines.md +0 -186
- package/docs/operations/personal-assistant-operator-guide.md +0 -61
- package/docs/operations/provider-configuration.md +0 -31
- package/scripts/harness-trace-grade.sh +0 -150
- package/scripts/heartbeat-triage.sh +0 -361
- package/scripts/maintenance-triage.sh +0 -167
- package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
- package/src/cli/repositories/goal-registry.ts +0 -239
- package/src/runtime/assistant/action-proposals.ts +0 -279
- package/src/runtime/assistant/intent.ts +0 -395
- package/src/runtime/assistant/model-provider.ts +0 -460
- package/src/runtime/assistant/openapi.ts +0 -223
- package/src/runtime/assistant/policy.ts +0 -89
- package/src/runtime/assistant/readiness.ts +0 -258
- package/src/runtime/assistant/routine-runtime.ts +0 -477
- package/src/runtime/assistant/schedule-binding.ts +0 -171
- package/src/runtime/assistant/standing-grants.ts +0 -375
- package/src/runtime/assistant/store.ts +0 -243
- package/src/runtime/assistant/types.ts +0 -135
- package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
- package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
- package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
- package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
- package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
- package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
- package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
- package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
- package/src/runtime/control-plane/goal-loop/index.ts +0 -13
- package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
- package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
- package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
- package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
- package/src/runtime/control-plane/goal-loop/types.ts +0 -378
- package/src/runtime/control-plane/startup-recovery.ts +0 -198
- package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
- package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
- package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
- package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
- package/src/runtime/plugins/desktop-adapter.ts +0 -374
- package/src/runtime/safe-tooling/browser-safe.ts +0 -133
- package/src/runtime/workflow/findings/store.ts +0 -204
- package/src/runtime/workflow/findings/types.ts +0 -58
- package/src/runtime/workflow/portfolio/engine.ts +0 -20
- package/src/runtime/workflow/portfolio/store.ts +0 -185
- package/src/runtime/workflow/portfolio/types.ts +0 -29
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { execFile } from 'child_process';
|
|
3
|
+
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from 'fs';
|
|
4
|
+
import { lstat as lstatAsync, readFile as readFileAsync, readdir as readdirAsync, stat as statAsync } from 'fs/promises';
|
|
5
|
+
import { join, resolve } from 'path';
|
|
4
6
|
import { runProcess } from '../../effects/process-runner';
|
|
5
7
|
import { globMatches, resolveMcpPath } from '../mcp/paths';
|
|
6
8
|
import type { McpPolicy } from '../mcp/types';
|
|
7
9
|
import {
|
|
8
10
|
collectSessionIdentity,
|
|
9
11
|
getOrCreateSessionCache,
|
|
12
|
+
invalidateSessionCachesForRepository,
|
|
10
13
|
type RepositorySessionCache,
|
|
11
14
|
type SessionIdentity,
|
|
12
15
|
} from './session-cache';
|
|
@@ -36,6 +39,8 @@ export interface CachedGitIdentity {
|
|
|
36
39
|
sampledAt: number;
|
|
37
40
|
head: string | null;
|
|
38
41
|
branch: string | null;
|
|
42
|
+
/** Dirty state comes from the same porcelain-v2 sample as branch/HEAD. */
|
|
43
|
+
dirty: boolean;
|
|
39
44
|
workingTreeFingerprint?: string;
|
|
40
45
|
}
|
|
41
46
|
|
|
@@ -76,7 +81,7 @@ function sampleGitIdentity(repoRoot: string): Omit<CachedGitIdentity, 'sampledAt
|
|
|
76
81
|
timeoutMs: 10_000,
|
|
77
82
|
maxOutputBytes: 4 * 1024 * 1024,
|
|
78
83
|
});
|
|
79
|
-
if (!result.ok) return { head: null, branch: null };
|
|
84
|
+
if (!result.ok) return { head: null, branch: null, dirty: false };
|
|
80
85
|
|
|
81
86
|
let head: string | null = null;
|
|
82
87
|
let branch: string | null = null;
|
|
@@ -93,13 +98,43 @@ function sampleGitIdentity(repoRoot: string): Omit<CachedGitIdentity, 'sampledAt
|
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
100
|
const status = statusLines.join('\n');
|
|
101
|
+
let dirtyBytes = 0;
|
|
102
|
+
const dirtyContentIdentity = statusLines.slice(0, 500).map((line) => {
|
|
103
|
+
const path = line.startsWith('? ') || line.startsWith('! ')
|
|
104
|
+
? line.slice(2)
|
|
105
|
+
: line.startsWith('1 ')
|
|
106
|
+
? line.split(' ').slice(8).join(' ')
|
|
107
|
+
: line.startsWith('2 ')
|
|
108
|
+
? line.split(' ').slice(9).join(' ').split('\t')[0]
|
|
109
|
+
: line.startsWith('u ')
|
|
110
|
+
? line.split(' ').slice(10).join(' ')
|
|
111
|
+
: '';
|
|
112
|
+
if (!path) return line;
|
|
113
|
+
try {
|
|
114
|
+
const absolute = join(repoRoot, path);
|
|
115
|
+
const stat = lstatSync(absolute);
|
|
116
|
+
if (!stat.isFile()) return `${path}:${stat.mode}:${stat.size}:${stat.mtimeMs}`;
|
|
117
|
+
if (stat.size <= 256 * 1024 && dirtyBytes + stat.size <= 4 * 1024 * 1024) {
|
|
118
|
+
dirtyBytes += stat.size;
|
|
119
|
+
return `${path}:${createHash('sha256').update(readFileSync(absolute)).digest('hex')}`;
|
|
120
|
+
}
|
|
121
|
+
return `${path}:${stat.size}:${stat.mtimeMs}`;
|
|
122
|
+
} catch {
|
|
123
|
+
return `${path}:missing`;
|
|
124
|
+
}
|
|
125
|
+
}).join('\n');
|
|
96
126
|
const workingTreeFingerprint = createHash('sha256')
|
|
97
|
-
.update(`${head ?? ''}\n${branch ?? ''}\n${status}`)
|
|
127
|
+
.update(`${head ?? ''}\n${branch ?? ''}\n${status}\n${dirtyContentIdentity}`)
|
|
98
128
|
.digest('hex')
|
|
99
129
|
.slice(0, 24);
|
|
100
|
-
return {
|
|
130
|
+
return {
|
|
131
|
+
head,
|
|
132
|
+
branch,
|
|
133
|
+
dirty: statusLines.length > 0,
|
|
134
|
+
workingTreeFingerprint: head || status ? workingTreeFingerprint : undefined,
|
|
135
|
+
};
|
|
101
136
|
} catch {
|
|
102
|
-
return { head: null, branch: null };
|
|
137
|
+
return { head: null, branch: null, dirty: false };
|
|
103
138
|
}
|
|
104
139
|
}
|
|
105
140
|
|
|
@@ -110,18 +145,46 @@ function sampleGitIdentity(repoRoot: string): Omit<CachedGitIdentity, 'sampledAt
|
|
|
110
145
|
* fingerprint is content-based (stable across reads) and the short TTL window
|
|
111
146
|
* keeps checkout routing safe while real mutations invalidate through markers.
|
|
112
147
|
*/
|
|
148
|
+
export function freshGitIdentity(repoRoot: string): CachedGitIdentity {
|
|
149
|
+
const root = resolve(repoRoot);
|
|
150
|
+
const sampled = sampleGitIdentity(root);
|
|
151
|
+
const entry: CachedGitIdentity = { ...sampled, sampledAt: Date.now() };
|
|
152
|
+
gitIdentityCache.set(root, entry);
|
|
153
|
+
pruneGitIdentityCache();
|
|
154
|
+
return entry;
|
|
155
|
+
}
|
|
156
|
+
|
|
113
157
|
export function cachedGitIdentity(repoRoot: string): CachedGitIdentity {
|
|
158
|
+
const root = resolve(repoRoot);
|
|
114
159
|
const now = Date.now();
|
|
115
|
-
const existing = gitIdentityCache.get(
|
|
160
|
+
const existing = gitIdentityCache.get(root);
|
|
116
161
|
if (existing && now - existing.sampledAt < GIT_IDENTITY_SAMPLE_TTL_MS) {
|
|
117
162
|
gitIdentityPerformance.cacheHits += 1;
|
|
118
163
|
return existing;
|
|
119
164
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
165
|
+
return freshGitIdentity(root);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Successful Forge-owned mutations invalidate observations derived from the
|
|
170
|
+
* previous source state. This is intentionally lazy: no graph rebuild, check,
|
|
171
|
+
* or language server is started here; the next read re-samples Git/current bytes.
|
|
172
|
+
*/
|
|
173
|
+
export function invalidateRepositoryReadCaches(repoRoot: string): void {
|
|
174
|
+
const root = resolve(repoRoot);
|
|
175
|
+
gitIdentityCache.delete(root);
|
|
176
|
+
gitSnapshotCache.delete(root);
|
|
177
|
+
for (const key of [...searchFileInventoryCache.keys()]) {
|
|
178
|
+
try {
|
|
179
|
+
const identity = JSON.parse(key) as { repoRoot?: unknown };
|
|
180
|
+
if (typeof identity.repoRoot === 'string' && resolve(identity.repoRoot) === root) {
|
|
181
|
+
searchFileInventoryCache.delete(key);
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
// Cache keys are internal JSON identities. Ignore legacy/unparseable entries.
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
invalidateSessionCachesForRepository(root);
|
|
125
188
|
}
|
|
126
189
|
|
|
127
190
|
export function clearGitIdentityCacheForTest(): void {
|
|
@@ -159,14 +222,26 @@ export interface RepositoryReadSession {
|
|
|
159
222
|
|
|
160
223
|
function bindSessionCache(repoRoot: string, session?: RepositoryReadSession): RepositorySessionCache | null {
|
|
161
224
|
if (!session?.sessionId || !session.repoId || !session.checkoutId) return null;
|
|
162
|
-
const
|
|
163
|
-
|
|
225
|
+
const sampled = cachedGitIdentity(repoRoot);
|
|
226
|
+
const identity: SessionIdentity = {
|
|
164
227
|
repoId: session.repoId,
|
|
165
228
|
checkoutId: session.checkoutId,
|
|
166
|
-
|
|
229
|
+
branch: sampled.branch,
|
|
230
|
+
head: sampled.head,
|
|
231
|
+
workingTreeFingerprint: sampled.workingTreeFingerprint
|
|
232
|
+
?? createHash('sha256').update(`${sampled.head ?? ''}\n${sampled.branch ?? ''}`).digest('hex').slice(0, 24),
|
|
233
|
+
};
|
|
167
234
|
return getOrCreateSessionCache(session.sessionId, repoRoot, identity);
|
|
168
235
|
}
|
|
169
236
|
|
|
237
|
+
/** Shared non-durable cache binding for progressive Context Plane providers. */
|
|
238
|
+
export function getRepositoryReadSessionCache(
|
|
239
|
+
repoRoot: string,
|
|
240
|
+
session?: RepositoryReadSession,
|
|
241
|
+
): RepositorySessionCache | null {
|
|
242
|
+
return bindSessionCache(repoRoot, session);
|
|
243
|
+
}
|
|
244
|
+
|
|
170
245
|
export function resolveSessionIdentity(
|
|
171
246
|
repoRoot: string,
|
|
172
247
|
repoId: string,
|
|
@@ -208,6 +283,15 @@ const SEARCH_GIT_INVENTORY_MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
|
208
283
|
* output is truncated so non-Git/ephemeral workspaces retain the filesystem
|
|
209
284
|
* fallback below.
|
|
210
285
|
*/
|
|
286
|
+
function parseGitSearchFileInventory(stdout: string): string[] {
|
|
287
|
+
return stdout
|
|
288
|
+
.split('\0')
|
|
289
|
+
// NUL framing already gives exact Git paths; do not trim because leading
|
|
290
|
+
// or trailing whitespace is legal in a repository filename.
|
|
291
|
+
.filter((path) => path.length > 0)
|
|
292
|
+
.sort((left, right) => left.localeCompare(right));
|
|
293
|
+
}
|
|
294
|
+
|
|
211
295
|
function gitSearchFileInventory(repoRoot: string): string[] | null {
|
|
212
296
|
try {
|
|
213
297
|
const result = runProcess('git', ['ls-files', '-z', '--cached', '--others', '--exclude-standard'], {
|
|
@@ -216,17 +300,34 @@ function gitSearchFileInventory(repoRoot: string): string[] | null {
|
|
|
216
300
|
maxOutputBytes: SEARCH_GIT_INVENTORY_MAX_OUTPUT_BYTES,
|
|
217
301
|
});
|
|
218
302
|
if (!result.ok || result.stdout.includes('[output truncated after')) return null;
|
|
219
|
-
return result.stdout
|
|
220
|
-
.split('\0')
|
|
221
|
-
// NUL framing already gives exact Git paths; do not trim because leading
|
|
222
|
-
// or trailing whitespace is legal in a repository filename.
|
|
223
|
-
.filter((path) => path.length > 0)
|
|
224
|
-
.sort((left, right) => left.localeCompare(right));
|
|
303
|
+
return parseGitSearchFileInventory(result.stdout);
|
|
225
304
|
} catch {
|
|
226
305
|
return null;
|
|
227
306
|
}
|
|
228
307
|
}
|
|
229
308
|
|
|
309
|
+
function gitSearchFileInventoryAsync(repoRoot: string): Promise<string[] | null> {
|
|
310
|
+
return new Promise((resolve) => {
|
|
311
|
+
execFile(
|
|
312
|
+
'git',
|
|
313
|
+
['ls-files', '-z', '--cached', '--others', '--exclude-standard'],
|
|
314
|
+
{
|
|
315
|
+
cwd: repoRoot,
|
|
316
|
+
timeout: 10_000,
|
|
317
|
+
maxBuffer: SEARCH_GIT_INVENTORY_MAX_OUTPUT_BYTES,
|
|
318
|
+
encoding: 'utf8',
|
|
319
|
+
},
|
|
320
|
+
(error, stdout) => {
|
|
321
|
+
if (error || typeof stdout !== 'string') {
|
|
322
|
+
resolve(null);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
resolve(parseGitSearchFileInventory(stdout));
|
|
326
|
+
},
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
230
331
|
function binary(bytes: Buffer): boolean {
|
|
231
332
|
return bytes.subarray(0, Math.min(bytes.length, 8000)).includes(0);
|
|
232
333
|
}
|
|
@@ -251,6 +352,36 @@ function walk(repoRoot: string, root: string, maxFiles: number, excludes: string
|
|
|
251
352
|
}
|
|
252
353
|
}
|
|
253
354
|
|
|
355
|
+
async function walkAsync(repoRoot: string, root: string, maxFiles: number, excludes: string[], output: string[]): Promise<void> {
|
|
356
|
+
if (output.length >= maxFiles) return;
|
|
357
|
+
const absolute = join(repoRoot, root);
|
|
358
|
+
let info;
|
|
359
|
+
try {
|
|
360
|
+
info = await lstatAsync(absolute);
|
|
361
|
+
} catch {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (info.isSymbolicLink()) return;
|
|
365
|
+
if (info.isFile()) {
|
|
366
|
+
output.push(root);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (!info.isDirectory()) return;
|
|
370
|
+
let entries;
|
|
371
|
+
try {
|
|
372
|
+
entries = await readdirAsync(absolute, { withFileTypes: true });
|
|
373
|
+
} catch {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
377
|
+
if (output.length >= maxFiles) break;
|
|
378
|
+
const child = root ? `${root}/${entry.name}` : entry.name;
|
|
379
|
+
if (isExcluded(child, excludes) || isExcluded(`${child}/`, excludes)) continue;
|
|
380
|
+
if (entry.isDirectory()) await walkAsync(repoRoot, child, maxFiles, excludes, output);
|
|
381
|
+
else if (entry.isFile()) output.push(child);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
254
385
|
export interface SearchRepositoryOptions {
|
|
255
386
|
query: string;
|
|
256
387
|
includeGlobs?: string[];
|
|
@@ -264,12 +395,26 @@ export interface SearchRepositoryOptions {
|
|
|
264
395
|
|
|
265
396
|
export interface SearchRepositoryManyOptions {
|
|
266
397
|
queries: string[];
|
|
398
|
+
/** Pre-resolved exact candidate files. Skips repository inventory discovery. */
|
|
399
|
+
files?: string[];
|
|
267
400
|
includeGlobs?: string[];
|
|
268
401
|
excludeGlobs?: string[];
|
|
269
402
|
maxResultsPerQuery?: number;
|
|
270
403
|
maxFiles?: number;
|
|
271
404
|
caseSensitive?: boolean;
|
|
272
405
|
cacheKey?: string;
|
|
406
|
+
/**
|
|
407
|
+
* Strict search remains the default. Broad Context discovery may stop once it
|
|
408
|
+
* has enough distinct candidate files across several query hints; guessed
|
|
409
|
+
* natural-language terms are evidence hints, not completeness obligations.
|
|
410
|
+
*/
|
|
411
|
+
completionMode?: 'per_query' | 'discovery';
|
|
412
|
+
discoveryTargetFiles?: number;
|
|
413
|
+
discoveryMinQueryCoverage?: number;
|
|
414
|
+
/** Exact caller-supplied needles that discovery must not skip. */
|
|
415
|
+
requiredQueries?: string[];
|
|
416
|
+
/** MCP/controller session identity used for progressive follow-up reuse. */
|
|
417
|
+
session?: RepositoryReadSession;
|
|
273
418
|
}
|
|
274
419
|
|
|
275
420
|
export interface SearchRepositoryManyResult {
|
|
@@ -280,10 +425,76 @@ export interface SearchRepositoryManyResult {
|
|
|
280
425
|
skippedLargeFiles: number;
|
|
281
426
|
skippedBinaryFiles: number;
|
|
282
427
|
truncated: boolean;
|
|
283
|
-
truncationReason?: 'max_results' | 'max_files';
|
|
428
|
+
truncationReason?: 'max_results' | 'max_files' | 'discovery_budget';
|
|
284
429
|
cacheHit?: boolean;
|
|
285
430
|
}
|
|
286
431
|
|
|
432
|
+
export interface SearchRepositoryManyCacheIdentity {
|
|
433
|
+
queries: string[];
|
|
434
|
+
maxResultsPerQuery: number;
|
|
435
|
+
maxFiles: number;
|
|
436
|
+
includes: string[];
|
|
437
|
+
excludes: string[];
|
|
438
|
+
directCandidates?: string[];
|
|
439
|
+
completionMode: 'per_query' | 'discovery';
|
|
440
|
+
discoveryTargetFiles: number;
|
|
441
|
+
discoveryMinQueryCoverage: number;
|
|
442
|
+
requiredQueries: string[];
|
|
443
|
+
includeKey: string;
|
|
444
|
+
batchQueryKey: string;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** Shared normalization/cache identity for sync search and worker-prefetched search. */
|
|
448
|
+
export function searchRepositoryManyCacheIdentity(opts: SearchRepositoryManyOptions): SearchRepositoryManyCacheIdentity {
|
|
449
|
+
const queries = Array.from(new Set(opts.queries.map((query) => query.trim()).filter(Boolean)));
|
|
450
|
+
if (queries.length === 0) throw new Error('at least one search query is required');
|
|
451
|
+
const maxResultsPerQuery = Math.min(Math.max(opts.maxResultsPerQuery ?? 100, 1), 500);
|
|
452
|
+
const maxFiles = Math.min(Math.max(opts.maxFiles ?? 5000, 1), 20_000);
|
|
453
|
+
const includes = opts.includeGlobs ?? [];
|
|
454
|
+
const excludes = [...(includes.length === 0 ? DEFAULT_EXCLUDES : []), ...(opts.excludeGlobs ?? [])];
|
|
455
|
+
const directFiles = opts.files?.map((path) => path.trim()).filter(Boolean);
|
|
456
|
+
const directCandidates = directFiles
|
|
457
|
+
? Array.from(new Set(directFiles)).filter((path) => !isExcluded(path, excludes))
|
|
458
|
+
: undefined;
|
|
459
|
+
const completionMode = opts.completionMode === 'discovery' ? 'discovery' : 'per_query';
|
|
460
|
+
const discoveryTargetFiles = completionMode === 'discovery'
|
|
461
|
+
? Math.min(Math.max(opts.discoveryTargetFiles ?? Math.max(12, Math.ceil(maxResultsPerQuery / 2)), 1), maxFiles)
|
|
462
|
+
: maxFiles;
|
|
463
|
+
const discoveryMinQueryCoverage = completionMode === 'discovery'
|
|
464
|
+
? Math.min(Math.max(opts.discoveryMinQueryCoverage ?? Math.min(3, queries.length), 1), queries.length)
|
|
465
|
+
: queries.length;
|
|
466
|
+
const requiredQueries = completionMode === 'discovery'
|
|
467
|
+
? Array.from(new Set((opts.requiredQueries ?? []).map((query) => query.trim()).filter((query) => queries.includes(query))))
|
|
468
|
+
: [];
|
|
469
|
+
const includeKey = JSON.stringify({
|
|
470
|
+
files: directCandidates,
|
|
471
|
+
includes,
|
|
472
|
+
excludes,
|
|
473
|
+
maxResultsPerQuery,
|
|
474
|
+
maxFiles,
|
|
475
|
+
caseSensitive: opts.caseSensitive === true,
|
|
476
|
+
cacheKey: opts.cacheKey,
|
|
477
|
+
completionMode,
|
|
478
|
+
discoveryTargetFiles,
|
|
479
|
+
discoveryMinQueryCoverage,
|
|
480
|
+
requiredQueries,
|
|
481
|
+
});
|
|
482
|
+
return {
|
|
483
|
+
queries,
|
|
484
|
+
maxResultsPerQuery,
|
|
485
|
+
maxFiles,
|
|
486
|
+
includes,
|
|
487
|
+
excludes,
|
|
488
|
+
directCandidates,
|
|
489
|
+
completionMode,
|
|
490
|
+
discoveryTargetFiles,
|
|
491
|
+
discoveryMinQueryCoverage,
|
|
492
|
+
requiredQueries,
|
|
493
|
+
includeKey,
|
|
494
|
+
batchQueryKey: queries.join('\u0000'),
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
287
498
|
function searchInventory(
|
|
288
499
|
repoRoot: string,
|
|
289
500
|
includes: string[],
|
|
@@ -311,6 +522,157 @@ function searchInventory(
|
|
|
311
522
|
return { files: candidates.slice(0, maxFiles), candidateCount: candidates.length, cacheHit };
|
|
312
523
|
}
|
|
313
524
|
|
|
525
|
+
async function searchInventoryAsync(
|
|
526
|
+
repoRoot: string,
|
|
527
|
+
includes: string[],
|
|
528
|
+
excludes: string[],
|
|
529
|
+
maxFiles: number,
|
|
530
|
+
cacheKey?: string,
|
|
531
|
+
): Promise<{ files: string[]; candidateCount: number; cacheHit: boolean }> {
|
|
532
|
+
const inventoryKey = JSON.stringify({ repoRoot, excludes, cacheKey });
|
|
533
|
+
const now = Date.now();
|
|
534
|
+
const cachedInventory = searchFileInventoryCache.get(inventoryKey);
|
|
535
|
+
const cacheHit = Boolean(cachedInventory && now - cachedInventory.createdAt < SEARCH_FILE_INVENTORY_CACHE_TTL_MS);
|
|
536
|
+
let inventory: string[];
|
|
537
|
+
if (cacheHit) {
|
|
538
|
+
inventory = cachedInventory!.files;
|
|
539
|
+
} else {
|
|
540
|
+
const gitFiles = await gitSearchFileInventoryAsync(repoRoot);
|
|
541
|
+
if (gitFiles) {
|
|
542
|
+
inventory = gitFiles;
|
|
543
|
+
} else {
|
|
544
|
+
const walked: string[] = [];
|
|
545
|
+
await walkAsync(repoRoot, '', 20_000, excludes, walked);
|
|
546
|
+
inventory = walked;
|
|
547
|
+
}
|
|
548
|
+
searchFileInventoryCache.set(inventoryKey, { createdAt: now, files: inventory });
|
|
549
|
+
}
|
|
550
|
+
const candidates = inventory.filter((path) => isIncluded(path, includes) && !isExcluded(path, excludes));
|
|
551
|
+
return { files: candidates.slice(0, maxFiles), candidateCount: candidates.length, cacheHit };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
interface SearchManyMatchState {
|
|
555
|
+
queries: string[];
|
|
556
|
+
needles: Array<{ query: string; needle: string }>;
|
|
557
|
+
counts: Map<string, number>;
|
|
558
|
+
results: SearchRepositoryManyResult['results'];
|
|
559
|
+
matchedPaths: Set<string>;
|
|
560
|
+
maxResultsPerQuery: number;
|
|
561
|
+
completionMode: 'per_query' | 'discovery';
|
|
562
|
+
discoveryTargetFiles: number;
|
|
563
|
+
discoveryMinQueryCoverage: number;
|
|
564
|
+
requiredQueries: string[];
|
|
565
|
+
caseSensitive: boolean;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function createSearchManyMatchState(
|
|
569
|
+
queries: string[],
|
|
570
|
+
maxResultsPerQuery: number,
|
|
571
|
+
completionMode: 'per_query' | 'discovery',
|
|
572
|
+
discoveryTargetFiles: number,
|
|
573
|
+
discoveryMinQueryCoverage: number,
|
|
574
|
+
requiredQueries: string[],
|
|
575
|
+
caseSensitive: boolean,
|
|
576
|
+
): SearchManyMatchState {
|
|
577
|
+
return {
|
|
578
|
+
queries,
|
|
579
|
+
needles: queries.map((query) => ({ query, needle: caseSensitive ? query : query.toLowerCase() })),
|
|
580
|
+
counts: new Map(queries.map((query) => [query, 0])),
|
|
581
|
+
results: [],
|
|
582
|
+
matchedPaths: new Set<string>(),
|
|
583
|
+
maxResultsPerQuery,
|
|
584
|
+
completionMode,
|
|
585
|
+
discoveryTargetFiles,
|
|
586
|
+
discoveryMinQueryCoverage,
|
|
587
|
+
requiredQueries,
|
|
588
|
+
caseSensitive,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function searchManyComplete(state: SearchManyMatchState): boolean {
|
|
593
|
+
if (state.completionMode === 'per_query') {
|
|
594
|
+
return state.needles.every(({ query }) => (state.counts.get(query) ?? 0) >= state.maxResultsPerQuery);
|
|
595
|
+
}
|
|
596
|
+
if (state.requiredQueries.some((query) => (state.counts.get(query) ?? 0) === 0)) return false;
|
|
597
|
+
const coveredQueries = state.needles.reduce(
|
|
598
|
+
(count, { query }) => count + ((state.counts.get(query) ?? 0) > 0 ? 1 : 0),
|
|
599
|
+
0,
|
|
600
|
+
);
|
|
601
|
+
return state.matchedPaths.size >= state.discoveryTargetFiles
|
|
602
|
+
&& coveredQueries >= state.discoveryMinQueryCoverage;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function exactCodeNeedleMatches(haystack: string, needle: string): boolean {
|
|
606
|
+
const escaped = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
607
|
+
return new RegExp(`(?:^|[^\\p{L}\\p{N}_$])${escaped}(?=$|[^\\p{L}\\p{N}_$])`, 'u').test(haystack);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function matchSearchManySource(state: SearchManyMatchState, path: string, bytes: Buffer): void {
|
|
611
|
+
const raw = bytes.toString('utf-8');
|
|
612
|
+
const searchable = state.caseSensitive ? raw : raw.toLowerCase();
|
|
613
|
+
const activeNeedles = state.needles.filter(({ query, needle }) =>
|
|
614
|
+
(state.counts.get(query) ?? 0) < state.maxResultsPerQuery
|
|
615
|
+
&& (state.requiredQueries.includes(query)
|
|
616
|
+
? exactCodeNeedleMatches(searchable, needle)
|
|
617
|
+
: searchable.includes(needle)));
|
|
618
|
+
if (activeNeedles.length === 0) return;
|
|
619
|
+
|
|
620
|
+
const lines = raw.split(/\r?\n/);
|
|
621
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
622
|
+
const text = lines[index]!;
|
|
623
|
+
const haystack = state.caseSensitive ? text : text.toLowerCase();
|
|
624
|
+
for (const { query, needle } of activeNeedles) {
|
|
625
|
+
const matches = state.requiredQueries.includes(query)
|
|
626
|
+
? exactCodeNeedleMatches(haystack, needle)
|
|
627
|
+
: haystack.includes(needle);
|
|
628
|
+
if ((state.counts.get(query) ?? 0) >= state.maxResultsPerQuery || !matches) continue;
|
|
629
|
+
state.results.push({ query, path, line: index + 1, text: text.slice(0, 500) });
|
|
630
|
+
state.matchedPaths.add(path);
|
|
631
|
+
state.counts.set(query, (state.counts.get(query) ?? 0) + 1);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function finalizeSearchManyResult(input: {
|
|
637
|
+
state: SearchManyMatchState;
|
|
638
|
+
scannedFiles: number;
|
|
639
|
+
policyDeniedFiles: number;
|
|
640
|
+
skippedLargeFiles: number;
|
|
641
|
+
skippedBinaryFiles: number;
|
|
642
|
+
candidateCount: number;
|
|
643
|
+
inventoryFileCount: number;
|
|
644
|
+
inventoryCacheHit: boolean;
|
|
645
|
+
}): SearchRepositoryManyResult {
|
|
646
|
+
const { state } = input;
|
|
647
|
+
const queryPriority = new Map(state.queries.map((query, index) => [query, index]));
|
|
648
|
+
state.results.sort((left, right) => {
|
|
649
|
+
const priority = (queryPriority.get(left.query) ?? state.queries.length) - (queryPriority.get(right.query) ?? state.queries.length);
|
|
650
|
+
if (priority !== 0) return priority;
|
|
651
|
+
const pathOrder = left.path.localeCompare(right.path);
|
|
652
|
+
return pathOrder !== 0 ? pathOrder : left.line - right.line;
|
|
653
|
+
});
|
|
654
|
+
const discoveryBudgetReached = state.completionMode === 'discovery' && searchManyComplete(state);
|
|
655
|
+
const hitResultLimit = state.needles.some(({ query }) => (state.counts.get(query) ?? 0) >= state.maxResultsPerQuery);
|
|
656
|
+
const truncationReason = discoveryBudgetReached
|
|
657
|
+
? 'discovery_budget' as const
|
|
658
|
+
: hitResultLimit
|
|
659
|
+
? 'max_results' as const
|
|
660
|
+
: input.candidateCount > input.inventoryFileCount
|
|
661
|
+
? 'max_files' as const
|
|
662
|
+
: undefined;
|
|
663
|
+
return {
|
|
664
|
+
queries: state.queries,
|
|
665
|
+
results: state.results,
|
|
666
|
+
scannedFiles: input.scannedFiles,
|
|
667
|
+
policyDeniedFiles: input.policyDeniedFiles,
|
|
668
|
+
skippedLargeFiles: input.skippedLargeFiles,
|
|
669
|
+
skippedBinaryFiles: input.skippedBinaryFiles,
|
|
670
|
+
truncated: Boolean(truncationReason),
|
|
671
|
+
truncationReason,
|
|
672
|
+
cacheHit: input.inventoryCacheHit,
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
314
676
|
/**
|
|
315
677
|
* Internal Context Plane batch search. Every candidate file is policy-checked
|
|
316
678
|
* and read at most once while all lexical terms are matched in the same pass.
|
|
@@ -321,24 +683,52 @@ export function searchRepositoryMany(
|
|
|
321
683
|
policy: McpPolicy,
|
|
322
684
|
opts: SearchRepositoryManyOptions,
|
|
323
685
|
): SearchRepositoryManyResult {
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
686
|
+
const {
|
|
687
|
+
queries,
|
|
688
|
+
maxResultsPerQuery,
|
|
689
|
+
maxFiles,
|
|
690
|
+
includes,
|
|
691
|
+
excludes,
|
|
692
|
+
directCandidates,
|
|
693
|
+
completionMode,
|
|
694
|
+
discoveryTargetFiles,
|
|
695
|
+
discoveryMinQueryCoverage,
|
|
696
|
+
requiredQueries,
|
|
697
|
+
includeKey,
|
|
698
|
+
batchQueryKey,
|
|
699
|
+
} = searchRepositoryManyCacheIdentity(opts);
|
|
700
|
+
const sessionCache = bindSessionCache(repoRoot, opts.session);
|
|
701
|
+
if (sessionCache) {
|
|
702
|
+
const cached = sessionCache.getSearch(batchQueryKey, includeKey);
|
|
703
|
+
if (cached?.result && typeof cached.result === 'object') {
|
|
704
|
+
return { ...(cached.result as SearchRepositoryManyResult), cacheHit: true };
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
const inventory = directCandidates && directCandidates.length > 0
|
|
708
|
+
? {
|
|
709
|
+
files: directCandidates.slice(0, maxFiles),
|
|
710
|
+
candidateCount: directCandidates.length,
|
|
711
|
+
cacheHit: false,
|
|
712
|
+
}
|
|
713
|
+
: searchInventory(repoRoot, includes, excludes, maxFiles, opts.cacheKey);
|
|
714
|
+
const matchState = createSearchManyMatchState(
|
|
715
|
+
queries,
|
|
716
|
+
maxResultsPerQuery,
|
|
717
|
+
completionMode,
|
|
718
|
+
discoveryTargetFiles,
|
|
719
|
+
discoveryMinQueryCoverage,
|
|
720
|
+
requiredQueries,
|
|
721
|
+
opts.caseSensitive === true,
|
|
722
|
+
);
|
|
334
723
|
let scannedFiles = 0;
|
|
335
724
|
let policyDeniedFiles = 0;
|
|
336
725
|
let skippedLargeFiles = 0;
|
|
337
726
|
let skippedBinaryFiles = 0;
|
|
727
|
+
const canonicalRepoRoot = realpathSync(repoRoot);
|
|
338
728
|
|
|
339
729
|
for (const path of inventory.files) {
|
|
340
|
-
if (
|
|
341
|
-
const decision = resolveMcpPath(repoRoot, path, policy, 'read');
|
|
730
|
+
if (searchManyComplete(matchState)) break;
|
|
731
|
+
const decision = resolveMcpPath(repoRoot, path, policy, 'read', canonicalRepoRoot);
|
|
342
732
|
if (!decision.ok || !decision.absolutePath) {
|
|
343
733
|
policyDeniedFiles += 1;
|
|
344
734
|
continue;
|
|
@@ -354,46 +744,159 @@ export function searchRepositoryMany(
|
|
|
354
744
|
continue;
|
|
355
745
|
}
|
|
356
746
|
scannedFiles += 1;
|
|
357
|
-
|
|
358
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
359
|
-
const text = lines[index]!;
|
|
360
|
-
const haystack = opts.caseSensitive ? text : text.toLowerCase();
|
|
361
|
-
for (const { query, needle } of needles) {
|
|
362
|
-
if ((counts.get(query) ?? 0) >= maxResultsPerQuery || !haystack.includes(needle)) continue;
|
|
363
|
-
results.push({ query, path, line: index + 1, text: text.slice(0, 500) });
|
|
364
|
-
counts.set(query, (counts.get(query) ?? 0) + 1);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
747
|
+
matchSearchManySource(matchState, path, bytes);
|
|
367
748
|
}
|
|
368
749
|
|
|
369
750
|
// Consumers may bound distinct candidate files after this call. Preserve the
|
|
370
751
|
// caller's query priority so an exact symbol/phrase cannot be displaced by
|
|
371
752
|
// earlier alphabetic files that only matched broad recall tokens.
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
753
|
+
const payload = finalizeSearchManyResult({
|
|
754
|
+
state: matchState,
|
|
755
|
+
scannedFiles,
|
|
756
|
+
policyDeniedFiles,
|
|
757
|
+
skippedLargeFiles,
|
|
758
|
+
skippedBinaryFiles,
|
|
759
|
+
candidateCount: inventory.candidateCount,
|
|
760
|
+
inventoryFileCount: inventory.files.length,
|
|
761
|
+
inventoryCacheHit: inventory.cacheHit,
|
|
378
762
|
});
|
|
763
|
+
if (sessionCache) {
|
|
764
|
+
sessionCache.putSearch({
|
|
765
|
+
query: batchQueryKey,
|
|
766
|
+
includeKey,
|
|
767
|
+
result: payload,
|
|
768
|
+
scannedFiles,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
return payload;
|
|
772
|
+
}
|
|
379
773
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
774
|
+
// File reads are already bounded by policy/maxFiles. A wider batch keeps broad
|
|
775
|
+
// Context scans from serializing dozens of filesystem waves while still
|
|
776
|
+
// preserving original-order matching and early-stop semantics below.
|
|
777
|
+
const ASYNC_SEARCH_READ_BATCH_SIZE = 64;
|
|
778
|
+
|
|
779
|
+
type AsyncSearchCandidate =
|
|
780
|
+
| { path: string; kind: 'denied' }
|
|
781
|
+
| { path: string; kind: 'large' }
|
|
782
|
+
| { path: string; kind: 'binary' }
|
|
783
|
+
| { path: string; kind: 'source'; bytes: Buffer }
|
|
784
|
+
| { path: string; kind: 'error'; error: unknown };
|
|
785
|
+
|
|
786
|
+
async function readSearchCandidateAsync(
|
|
787
|
+
repoRoot: string,
|
|
788
|
+
policy: McpPolicy,
|
|
789
|
+
canonicalRepoRoot: string,
|
|
790
|
+
path: string,
|
|
791
|
+
): Promise<AsyncSearchCandidate> {
|
|
792
|
+
try {
|
|
793
|
+
const decision = resolveMcpPath(repoRoot, path, policy, 'read', canonicalRepoRoot);
|
|
794
|
+
if (!decision.ok || !decision.absolutePath) return { path, kind: 'denied' };
|
|
795
|
+
const size = (await statAsync(decision.absolutePath)).size;
|
|
796
|
+
if (size > policy.maxFileBytes) return { path, kind: 'large' };
|
|
797
|
+
const bytes = await readFileAsync(decision.absolutePath);
|
|
798
|
+
if (binary(bytes)) return { path, kind: 'binary' };
|
|
799
|
+
return { path, kind: 'source', bytes };
|
|
800
|
+
} catch (error) {
|
|
801
|
+
return { path, kind: 'error', error };
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Async Context Plane batch search with result semantics matching
|
|
807
|
+
* searchRepositoryMany. Candidate reads happen in bounded batches, while policy
|
|
808
|
+
* accounting, early-stop behavior, matching, and result ordering are consumed in
|
|
809
|
+
* original repository order on the caller thread.
|
|
810
|
+
*/
|
|
811
|
+
export async function searchRepositoryManyAsync(
|
|
812
|
+
repoRoot: string,
|
|
813
|
+
policy: McpPolicy,
|
|
814
|
+
opts: SearchRepositoryManyOptions,
|
|
815
|
+
): Promise<SearchRepositoryManyResult> {
|
|
816
|
+
const {
|
|
387
817
|
queries,
|
|
388
|
-
|
|
818
|
+
maxResultsPerQuery,
|
|
819
|
+
maxFiles,
|
|
820
|
+
includes,
|
|
821
|
+
excludes,
|
|
822
|
+
directCandidates,
|
|
823
|
+
completionMode,
|
|
824
|
+
discoveryTargetFiles,
|
|
825
|
+
discoveryMinQueryCoverage,
|
|
826
|
+
requiredQueries,
|
|
827
|
+
includeKey,
|
|
828
|
+
batchQueryKey,
|
|
829
|
+
} = searchRepositoryManyCacheIdentity(opts);
|
|
830
|
+
const sessionCache = bindSessionCache(repoRoot, opts.session);
|
|
831
|
+
if (sessionCache) {
|
|
832
|
+
const cached = sessionCache.getSearch(batchQueryKey, includeKey);
|
|
833
|
+
if (cached?.result && typeof cached.result === 'object') {
|
|
834
|
+
return { ...(cached.result as SearchRepositoryManyResult), cacheHit: true };
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
const inventory = directCandidates && directCandidates.length > 0
|
|
838
|
+
? {
|
|
839
|
+
files: directCandidates.slice(0, maxFiles),
|
|
840
|
+
candidateCount: directCandidates.length,
|
|
841
|
+
cacheHit: false,
|
|
842
|
+
}
|
|
843
|
+
: await searchInventoryAsync(repoRoot, includes, excludes, maxFiles, opts.cacheKey);
|
|
844
|
+
const matchState = createSearchManyMatchState(
|
|
845
|
+
queries,
|
|
846
|
+
maxResultsPerQuery,
|
|
847
|
+
completionMode,
|
|
848
|
+
discoveryTargetFiles,
|
|
849
|
+
discoveryMinQueryCoverage,
|
|
850
|
+
requiredQueries,
|
|
851
|
+
opts.caseSensitive === true,
|
|
852
|
+
);
|
|
853
|
+
let scannedFiles = 0;
|
|
854
|
+
let policyDeniedFiles = 0;
|
|
855
|
+
let skippedLargeFiles = 0;
|
|
856
|
+
let skippedBinaryFiles = 0;
|
|
857
|
+
const canonicalRepoRoot = realpathSync(repoRoot);
|
|
858
|
+
for (let offset = 0; offset < inventory.files.length && !searchManyComplete(matchState); offset += ASYNC_SEARCH_READ_BATCH_SIZE) {
|
|
859
|
+
const batchPaths = inventory.files.slice(offset, offset + ASYNC_SEARCH_READ_BATCH_SIZE);
|
|
860
|
+
const batch = await Promise.all(batchPaths.map((path) => readSearchCandidateAsync(repoRoot, policy, canonicalRepoRoot, path)));
|
|
861
|
+
for (const candidate of batch) {
|
|
862
|
+
if (searchManyComplete(matchState)) break;
|
|
863
|
+
if (candidate.kind === 'denied') {
|
|
864
|
+
policyDeniedFiles += 1;
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
if (candidate.kind === 'large') {
|
|
868
|
+
skippedLargeFiles += 1;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
if (candidate.kind === 'binary') {
|
|
872
|
+
skippedBinaryFiles += 1;
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
if (candidate.kind === 'error') throw candidate.error;
|
|
876
|
+
scannedFiles += 1;
|
|
877
|
+
matchSearchManySource(matchState, candidate.path, candidate.bytes);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const payload = finalizeSearchManyResult({
|
|
882
|
+
state: matchState,
|
|
389
883
|
scannedFiles,
|
|
390
884
|
policyDeniedFiles,
|
|
391
885
|
skippedLargeFiles,
|
|
392
886
|
skippedBinaryFiles,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
};
|
|
887
|
+
candidateCount: inventory.candidateCount,
|
|
888
|
+
inventoryFileCount: inventory.files.length,
|
|
889
|
+
inventoryCacheHit: inventory.cacheHit,
|
|
890
|
+
});
|
|
891
|
+
if (sessionCache) {
|
|
892
|
+
sessionCache.putSearch({
|
|
893
|
+
query: batchQueryKey,
|
|
894
|
+
includeKey,
|
|
895
|
+
result: payload,
|
|
896
|
+
scannedFiles,
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
return payload;
|
|
397
900
|
}
|
|
398
901
|
|
|
399
902
|
export function searchRepository(repoRoot: string, policy: McpPolicy, opts: SearchRepositoryOptions & {
|