@moretea-labs/forge 1.6.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/CHANGELOG.md +39 -0
- package/CLAUDE.md +4 -0
- package/README.md +2 -2
- package/README.zh-CN.md +1 -1
- 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/prompt-guard.sh +5 -81
- package/assets/hooks/session-start-context.sh +56 -1
- package/assets/partials/05-workflow.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +2 -0
- package/assets/partials-agents/03-orchestration.partial.md +1 -1
- package/assets/plugin-registry.v1.json +3 -3
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/harness-overview.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-architecture-sync.sh +17 -0
- 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 +11 -36
- package/assets/templates/helpers/inspect-project-state.ts +8 -6
- package/assets/templates/helpers/migrate-project-template.sh +22 -45
- package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
- 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/verify-contract.sh +1 -111
- package/assets/templates/helpers/verify-sprint.sh +3 -180
- package/assets/templates/helpers/workflow-contract.ts +5 -1
- 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 +178 -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-plugin-management.md +1 -1
- package/docs/forge-runtime-self-healing-loop.md +1 -1
- package/docs/operations/controller-browser-plugin.md +26 -6
- package/docs/operations/controller-performance-and-502.md +143 -0
- package/docs/operations/controller-reliability-runbook.md +1 -1
- package/docs/operations/features.md +2 -2
- 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/wiki/Architecture.md +6 -6
- package/docs/wiki/Implementation.md +2 -2
- package/docs/wiki/Integrations.md +2 -2
- package/docs/wiki/Work-Lifecycle.md +1 -1
- package/package.json +33 -11
- package/scripts/activate-source-baseline.ts +17 -13
- package/scripts/architecture-event.ts +2 -820
- package/scripts/architecture-queue.sh +2 -645
- package/scripts/archive-architecture-request.sh +2 -242
- package/scripts/archive-workflow.sh +1 -291
- 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/capability-config.ts +2 -392
- package/scripts/capability-resolver.ts +2 -503
- package/scripts/capture-plan.sh +1 -332
- package/scripts/check-agent-tooling.sh +2 -1558
- package/scripts/check-architecture-sync.sh +2 -371
- package/scripts/check-brain-manifest.sh +2 -253
- package/scripts/check-context-files.sh +2 -47
- package/scripts/check-deploy-sql-order.sh +2 -115
- package/scripts/check-mcp-compatibility.ts +14 -0
- package/scripts/check-runtime-architecture.mjs +349 -79
- package/scripts/check-skill-version.ts +3 -191
- package/scripts/check-source-duplication.mjs +61 -0
- package/scripts/check-stable-baseline.ts +92 -0
- package/scripts/check-task-sync.sh +2 -63
- package/scripts/check-task-workflow.sh +2 -1000
- package/scripts/codex-handoff-resume.sh +2 -267
- package/scripts/context-contract-sync.sh +2 -514
- package/scripts/contract-run.ts +2 -463
- package/scripts/contract-worktree.sh +1 -722
- package/scripts/ensure-task-workflow.sh +1 -1225
- package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
- package/scripts/inspect-project-state.ts +4 -279
- package/scripts/install-standalone-recovery.ts +24 -0
- package/scripts/lib/project-init-lib.sh +22 -46
- package/scripts/lib/run-forge-helper.sh +19 -0
- package/scripts/lib/run-forge-helper.ts +26 -0
- package/scripts/migrate-project-template.sh +1 -1189
- package/scripts/migrate-workflow-docs.ts +4 -412
- package/scripts/new-plan.sh +1 -180
- package/scripts/new-spec.sh +1 -47
- package/scripts/new-sprint.sh +1 -46
- package/scripts/plan-to-todo.sh +1 -796
- package/scripts/prepare-codex-handoff.sh +2 -201
- package/scripts/prepare-handoff.sh +1 -78
- package/scripts/refresh-current-status.sh +2 -467
- package/scripts/run-governed-gate.ts +5 -1
- package/scripts/select-agent-context-blocks.sh +2 -64
- package/scripts/ship-worktrees.sh +1 -558
- 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 -834
- package/scripts/stage-runtime-release.ts +2 -1
- package/scripts/summarize-failures.sh +2 -123
- package/scripts/switch-plan.sh +1 -150
- package/scripts/sync-brain-docs.sh +2 -379
- package/scripts/sync-generated-authority.mjs +175 -0
- package/scripts/verify-contract.sh +2 -703
- package/scripts/verify-forge-runtime.sh +0 -1
- package/scripts/verify-sprint.sh +1 -677
- package/scripts/workflow-contract.ts +4 -205
- package/scripts/workstream-sync.sh +2 -261
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/commands/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +12 -3
- 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 +113 -5
- 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 +828 -487
- 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/contracts.ts +16 -0
- package/src/cli/github/github.ts +135 -16
- package/src/cli/github/session.ts +2 -1
- package/src/cli/installer/managed-entries.ts +15 -1
- package/src/cli/local-bridge/console-automations.ts +54 -78
- package/src/cli/local-bridge/console-view-models.ts +1 -35
- package/src/cli/local-bridge/facade-api.ts +37 -264
- package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
- package/src/cli/local-bridge/frontend/src/styles.css +1 -1
- package/src/cli/local-bridge/frontend/src/types.ts +5 -8
- package/src/cli/local-bridge/job-store.ts +1 -490
- package/src/cli/local-bridge/server.ts +2 -515
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
- package/src/cli/mcp/auth.ts +5 -5
- package/src/cli/mcp/instructions.ts +3 -2
- package/src/cli/mcp/legacy-tool-service.ts +10 -222
- package/src/cli/mcp/multi-repository.ts +16 -5
- package/src/cli/mcp/oauth.ts +175 -26
- package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
- package/src/cli/mcp/repository-tools.ts +282 -146
- package/src/cli/mcp/server.ts +586 -116
- package/src/cli/mcp/tool-capability-cache.ts +1 -1
- package/src/cli/mcp/tool-contract.ts +14 -0
- package/src/cli/mcp/toolset-selection.ts +1 -1
- package/src/cli/mcp/toolset.ts +44 -7
- package/src/cli/mcp/transports/http.ts +124 -14
- package/src/cli/mcp/transports/session-registry.ts +43 -5
- package/src/cli/mcp/types.ts +3 -3
- package/src/cli/repositories/command-classifier.ts +103 -21
- package/src/cli/repositories/command-executor.ts +78 -526
- 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 +124 -0
- package/src/cli/repositories/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +118 -6
- package/src/cli/repository/inspector.ts +563 -65
- 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-contract.ts +61 -0
- package/src/runtime/context/semantic-navigation.ts +223 -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 +48 -16
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
- package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
- package/src/runtime/control-plane/execution/session-store.ts +18 -0
- package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
- package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
- package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
- package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
- package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
- 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-operation-service.ts +468 -0
- package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
- package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
- package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +22 -18
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +156 -11
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
- package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
- package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +90 -9
- package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
- package/src/runtime/control-plane/facade/controller-round-relay.ts +1118 -0
- package/src/runtime/control-plane/facade/controller-session-store.ts +302 -12
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +17 -1
- package/src/runtime/control-plane/facade/goal-workloop.ts +928 -190
- package/src/runtime/control-plane/facade/index.ts +3 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +275 -42
- package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
- package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
- 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 +90 -10
- package/src/runtime/control-plane/facade/work-contract-store.ts +157 -261
- package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
- 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 +152 -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 +278 -543
- 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 +63 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +365 -117
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +89 -0
- package/src/runtime/control-plane/launcher/thin-launcher.ts +244 -11
- package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
- package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
- 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/repo-actor/registry.ts +4 -2
- package/src/runtime/control-plane/routing/route-policy.ts +24 -21
- package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
- package/src/runtime/control-plane/runtime-status-client.ts +0 -2
- package/src/runtime/diagnostics/mcp-timing.ts +13 -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/evidence/process-check-execution.ts +18 -0
- package/src/runtime/execution/jobs/child-reference.ts +6 -2
- 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 +51 -4
- package/src/runtime/execution/process-runtime/check-result.ts +88 -0
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +367 -40
- package/src/runtime/execution/process-runtime/gc.ts +38 -6
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1201 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +51 -5
- package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
- package/src/runtime/execution/process-runtime/runtime.ts +53 -3
- package/src/runtime/execution/process-runtime/store.ts +1 -0
- package/src/runtime/execution/process-runtime/types.ts +4 -12
- 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/work-activity.ts +21 -0
- package/src/runtime/execution/workers/executor.ts +18 -61
- package/src/runtime/gateway/mcp/execution-tools.ts +35 -1852
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +186 -84
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +565 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2378 -1947
- package/src/runtime/plugins/app-store-connect-adapter.ts +216 -47
- package/src/runtime/plugins/browser-adapter.ts +2662 -829
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff-host.ts +2 -25
- package/src/runtime/plugins/browser-handoff.ts +110 -5
- package/src/runtime/plugins/browser-macos-bridge.ts +701 -63
- package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
- package/src/runtime/plugins/browser-native-session-service.ts +222 -0
- 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 +154 -0
- package/src/runtime/plugins/browser-runtime.ts +193 -0
- package/src/runtime/plugins/browser-session-authority.ts +332 -0
- package/src/runtime/plugins/browser-session-store.ts +121 -0
- package/src/runtime/plugins/browser-session-types.ts +98 -0
- package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +10 -4
- package/src/runtime/plugins/external-adapter.ts +358 -3
- package/src/runtime/plugins/external-unix-socket.ts +7 -1
- package/src/runtime/plugins/first-party-registry.ts +2 -0
- package/src/runtime/plugins/github-adapter.ts +125 -0
- 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 +123 -6
- package/src/runtime/plugins/macos-capability-broker.ts +47 -9
- package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
- package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
- package/src/runtime/plugins/resend-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +330 -33
- package/src/runtime/plugins/types.ts +38 -3
- 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/maintenance-executor.ts +426 -25
- package/src/runtime/root/mcp-transport.ts +180 -10
- package/src/runtime/root/package-connector-service.ts +230 -10
- package/src/runtime/root/package-runtime-release.ts +143 -17
- package/src/runtime/root/package-runtime-service.ts +461 -22
- package/src/runtime/root/release-manifest.ts +144 -18
- package/src/runtime/root/release-materialize.ts +125 -32
- package/src/runtime/root/release-store.ts +48 -11
- package/src/runtime/root/runtime.ts +23 -5
- package/src/runtime/root/service.ts +100 -29
- package/src/runtime/root/types.ts +12 -0
- package/src/runtime/safe-tooling/ios-development.ts +321 -63
- 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 +1079 -240
- package/src/runtime/standalone-recovery/entry.ts +100 -24
- package/src/runtime/standalone-recovery/installer.ts +20 -4
- 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 +81 -3
- package/src/runtime/workflow/schedules/engine.ts +364 -202
- package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
- package/src/runtime/workflow/schedules/settlement.ts +64 -17
- package/src/runtime/workflow/schedules/store.ts +209 -19
- package/src/runtime/workflow/schedules/types.ts +2 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
- 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/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 -459
- package/docs/architecture/current/codegraph-context-provider.md +0 -238
- 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 -373
- package/docs/architecture/current/entity-model.md +0 -612
- 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 -169
- 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 -321
- 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 -93
- 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/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 { execFile } from 'child_process';
|
|
2
3
|
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from 'fs';
|
|
3
|
-
import {
|
|
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[];
|
|
@@ -272,6 +403,18 @@ export interface SearchRepositoryManyOptions {
|
|
|
272
403
|
maxFiles?: number;
|
|
273
404
|
caseSensitive?: boolean;
|
|
274
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;
|
|
275
418
|
}
|
|
276
419
|
|
|
277
420
|
export interface SearchRepositoryManyResult {
|
|
@@ -282,10 +425,76 @@ export interface SearchRepositoryManyResult {
|
|
|
282
425
|
skippedLargeFiles: number;
|
|
283
426
|
skippedBinaryFiles: number;
|
|
284
427
|
truncated: boolean;
|
|
285
|
-
truncationReason?: 'max_results' | 'max_files';
|
|
428
|
+
truncationReason?: 'max_results' | 'max_files' | 'discovery_budget';
|
|
286
429
|
cacheHit?: boolean;
|
|
287
430
|
}
|
|
288
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
|
+
|
|
289
498
|
function searchInventory(
|
|
290
499
|
repoRoot: string,
|
|
291
500
|
includes: string[],
|
|
@@ -313,6 +522,157 @@ function searchInventory(
|
|
|
313
522
|
return { files: candidates.slice(0, maxFiles), candidateCount: candidates.length, cacheHit };
|
|
314
523
|
}
|
|
315
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
|
+
|
|
316
676
|
/**
|
|
317
677
|
* Internal Context Plane batch search. Every candidate file is policy-checked
|
|
318
678
|
* and read at most once while all lexical terms are matched in the same pass.
|
|
@@ -323,16 +683,27 @@ export function searchRepositoryMany(
|
|
|
323
683
|
policy: McpPolicy,
|
|
324
684
|
opts: SearchRepositoryManyOptions,
|
|
325
685
|
): SearchRepositoryManyResult {
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
+
}
|
|
336
707
|
const inventory = directCandidates && directCandidates.length > 0
|
|
337
708
|
? {
|
|
338
709
|
files: directCandidates.slice(0, maxFiles),
|
|
@@ -340,9 +711,15 @@ export function searchRepositoryMany(
|
|
|
340
711
|
cacheHit: false,
|
|
341
712
|
}
|
|
342
713
|
: searchInventory(repoRoot, includes, excludes, maxFiles, opts.cacheKey);
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
714
|
+
const matchState = createSearchManyMatchState(
|
|
715
|
+
queries,
|
|
716
|
+
maxResultsPerQuery,
|
|
717
|
+
completionMode,
|
|
718
|
+
discoveryTargetFiles,
|
|
719
|
+
discoveryMinQueryCoverage,
|
|
720
|
+
requiredQueries,
|
|
721
|
+
opts.caseSensitive === true,
|
|
722
|
+
);
|
|
346
723
|
let scannedFiles = 0;
|
|
347
724
|
let policyDeniedFiles = 0;
|
|
348
725
|
let skippedLargeFiles = 0;
|
|
@@ -350,13 +727,15 @@ export function searchRepositoryMany(
|
|
|
350
727
|
const canonicalRepoRoot = realpathSync(repoRoot);
|
|
351
728
|
|
|
352
729
|
for (const path of inventory.files) {
|
|
353
|
-
if (
|
|
730
|
+
if (searchManyComplete(matchState)) break;
|
|
354
731
|
const decision = resolveMcpPath(repoRoot, path, policy, 'read', canonicalRepoRoot);
|
|
355
732
|
if (!decision.ok || !decision.absolutePath) {
|
|
356
733
|
policyDeniedFiles += 1;
|
|
357
734
|
continue;
|
|
358
735
|
}
|
|
359
|
-
const
|
|
736
|
+
const info = statSync(decision.absolutePath);
|
|
737
|
+
if (!info.isFile()) continue;
|
|
738
|
+
const size = info.size;
|
|
360
739
|
if (size > policy.maxFileBytes) {
|
|
361
740
|
skippedLargeFiles += 1;
|
|
362
741
|
continue;
|
|
@@ -367,46 +746,163 @@ export function searchRepositoryMany(
|
|
|
367
746
|
continue;
|
|
368
747
|
}
|
|
369
748
|
scannedFiles += 1;
|
|
370
|
-
|
|
371
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
372
|
-
const text = lines[index]!;
|
|
373
|
-
const haystack = opts.caseSensitive ? text : text.toLowerCase();
|
|
374
|
-
for (const { query, needle } of needles) {
|
|
375
|
-
if ((counts.get(query) ?? 0) >= maxResultsPerQuery || !haystack.includes(needle)) continue;
|
|
376
|
-
results.push({ query, path, line: index + 1, text: text.slice(0, 500) });
|
|
377
|
-
counts.set(query, (counts.get(query) ?? 0) + 1);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
749
|
+
matchSearchManySource(matchState, path, bytes);
|
|
380
750
|
}
|
|
381
751
|
|
|
382
752
|
// Consumers may bound distinct candidate files after this call. Preserve the
|
|
383
753
|
// caller's query priority so an exact symbol/phrase cannot be displaced by
|
|
384
754
|
// earlier alphabetic files that only matched broad recall tokens.
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
755
|
+
const payload = finalizeSearchManyResult({
|
|
756
|
+
state: matchState,
|
|
757
|
+
scannedFiles,
|
|
758
|
+
policyDeniedFiles,
|
|
759
|
+
skippedLargeFiles,
|
|
760
|
+
skippedBinaryFiles,
|
|
761
|
+
candidateCount: inventory.candidateCount,
|
|
762
|
+
inventoryFileCount: inventory.files.length,
|
|
763
|
+
inventoryCacheHit: inventory.cacheHit,
|
|
391
764
|
});
|
|
765
|
+
if (sessionCache) {
|
|
766
|
+
sessionCache.putSearch({
|
|
767
|
+
query: batchQueryKey,
|
|
768
|
+
includeKey,
|
|
769
|
+
result: payload,
|
|
770
|
+
scannedFiles,
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
return payload;
|
|
774
|
+
}
|
|
392
775
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
776
|
+
// File reads are already bounded by policy/maxFiles. A wider batch keeps broad
|
|
777
|
+
// Context scans from serializing dozens of filesystem waves while still
|
|
778
|
+
// preserving original-order matching and early-stop semantics below.
|
|
779
|
+
const ASYNC_SEARCH_READ_BATCH_SIZE = 64;
|
|
780
|
+
|
|
781
|
+
type AsyncSearchCandidate =
|
|
782
|
+
| { path: string; kind: 'denied' }
|
|
783
|
+
| { path: string; kind: 'large' }
|
|
784
|
+
| { path: string; kind: 'binary' }
|
|
785
|
+
| { path: string; kind: 'nonfile' }
|
|
786
|
+
| { path: string; kind: 'source'; bytes: Buffer }
|
|
787
|
+
| { path: string; kind: 'error'; error: unknown };
|
|
788
|
+
|
|
789
|
+
async function readSearchCandidateAsync(
|
|
790
|
+
repoRoot: string,
|
|
791
|
+
policy: McpPolicy,
|
|
792
|
+
canonicalRepoRoot: string,
|
|
793
|
+
path: string,
|
|
794
|
+
): Promise<AsyncSearchCandidate> {
|
|
795
|
+
try {
|
|
796
|
+
const decision = resolveMcpPath(repoRoot, path, policy, 'read', canonicalRepoRoot);
|
|
797
|
+
if (!decision.ok || !decision.absolutePath) return { path, kind: 'denied' };
|
|
798
|
+
const info = await statAsync(decision.absolutePath);
|
|
799
|
+
if (!info.isFile()) return { path, kind: 'nonfile' };
|
|
800
|
+
const size = info.size;
|
|
801
|
+
if (size > policy.maxFileBytes) return { path, kind: 'large' };
|
|
802
|
+
const bytes = await readFileAsync(decision.absolutePath);
|
|
803
|
+
if (binary(bytes)) return { path, kind: 'binary' };
|
|
804
|
+
return { path, kind: 'source', bytes };
|
|
805
|
+
} catch (error) {
|
|
806
|
+
return { path, kind: 'error', error };
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Async Context Plane batch search with result semantics matching
|
|
812
|
+
* searchRepositoryMany. Candidate reads happen in bounded batches, while policy
|
|
813
|
+
* accounting, early-stop behavior, matching, and result ordering are consumed in
|
|
814
|
+
* original repository order on the caller thread.
|
|
815
|
+
*/
|
|
816
|
+
export async function searchRepositoryManyAsync(
|
|
817
|
+
repoRoot: string,
|
|
818
|
+
policy: McpPolicy,
|
|
819
|
+
opts: SearchRepositoryManyOptions,
|
|
820
|
+
): Promise<SearchRepositoryManyResult> {
|
|
821
|
+
const {
|
|
400
822
|
queries,
|
|
401
|
-
|
|
823
|
+
maxResultsPerQuery,
|
|
824
|
+
maxFiles,
|
|
825
|
+
includes,
|
|
826
|
+
excludes,
|
|
827
|
+
directCandidates,
|
|
828
|
+
completionMode,
|
|
829
|
+
discoveryTargetFiles,
|
|
830
|
+
discoveryMinQueryCoverage,
|
|
831
|
+
requiredQueries,
|
|
832
|
+
includeKey,
|
|
833
|
+
batchQueryKey,
|
|
834
|
+
} = searchRepositoryManyCacheIdentity(opts);
|
|
835
|
+
const sessionCache = bindSessionCache(repoRoot, opts.session);
|
|
836
|
+
if (sessionCache) {
|
|
837
|
+
const cached = sessionCache.getSearch(batchQueryKey, includeKey);
|
|
838
|
+
if (cached?.result && typeof cached.result === 'object') {
|
|
839
|
+
return { ...(cached.result as SearchRepositoryManyResult), cacheHit: true };
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
const inventory = directCandidates && directCandidates.length > 0
|
|
843
|
+
? {
|
|
844
|
+
files: directCandidates.slice(0, maxFiles),
|
|
845
|
+
candidateCount: directCandidates.length,
|
|
846
|
+
cacheHit: false,
|
|
847
|
+
}
|
|
848
|
+
: await searchInventoryAsync(repoRoot, includes, excludes, maxFiles, opts.cacheKey);
|
|
849
|
+
const matchState = createSearchManyMatchState(
|
|
850
|
+
queries,
|
|
851
|
+
maxResultsPerQuery,
|
|
852
|
+
completionMode,
|
|
853
|
+
discoveryTargetFiles,
|
|
854
|
+
discoveryMinQueryCoverage,
|
|
855
|
+
requiredQueries,
|
|
856
|
+
opts.caseSensitive === true,
|
|
857
|
+
);
|
|
858
|
+
let scannedFiles = 0;
|
|
859
|
+
let policyDeniedFiles = 0;
|
|
860
|
+
let skippedLargeFiles = 0;
|
|
861
|
+
let skippedBinaryFiles = 0;
|
|
862
|
+
const canonicalRepoRoot = realpathSync(repoRoot);
|
|
863
|
+
for (let offset = 0; offset < inventory.files.length && !searchManyComplete(matchState); offset += ASYNC_SEARCH_READ_BATCH_SIZE) {
|
|
864
|
+
const batchPaths = inventory.files.slice(offset, offset + ASYNC_SEARCH_READ_BATCH_SIZE);
|
|
865
|
+
const batch = await Promise.all(batchPaths.map((path) => readSearchCandidateAsync(repoRoot, policy, canonicalRepoRoot, path)));
|
|
866
|
+
for (const candidate of batch) {
|
|
867
|
+
if (searchManyComplete(matchState)) break;
|
|
868
|
+
if (candidate.kind === 'denied') {
|
|
869
|
+
policyDeniedFiles += 1;
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
if (candidate.kind === 'large') {
|
|
873
|
+
skippedLargeFiles += 1;
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
if (candidate.kind === 'binary') {
|
|
877
|
+
skippedBinaryFiles += 1;
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
if (candidate.kind === 'nonfile') continue;
|
|
881
|
+
if (candidate.kind === 'error') throw candidate.error;
|
|
882
|
+
scannedFiles += 1;
|
|
883
|
+
matchSearchManySource(matchState, candidate.path, candidate.bytes);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const payload = finalizeSearchManyResult({
|
|
888
|
+
state: matchState,
|
|
402
889
|
scannedFiles,
|
|
403
890
|
policyDeniedFiles,
|
|
404
891
|
skippedLargeFiles,
|
|
405
892
|
skippedBinaryFiles,
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
};
|
|
893
|
+
candidateCount: inventory.candidateCount,
|
|
894
|
+
inventoryFileCount: inventory.files.length,
|
|
895
|
+
inventoryCacheHit: inventory.cacheHit,
|
|
896
|
+
});
|
|
897
|
+
if (sessionCache) {
|
|
898
|
+
sessionCache.putSearch({
|
|
899
|
+
query: batchQueryKey,
|
|
900
|
+
includeKey,
|
|
901
|
+
result: payload,
|
|
902
|
+
scannedFiles,
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
return payload;
|
|
410
906
|
}
|
|
411
907
|
|
|
412
908
|
export function searchRepository(repoRoot: string, policy: McpPolicy, opts: SearchRepositoryOptions & {
|
|
@@ -460,7 +956,9 @@ export function searchRepository(repoRoot: string, policy: McpPolicy, opts: Sear
|
|
|
460
956
|
policyDeniedFiles += 1;
|
|
461
957
|
continue;
|
|
462
958
|
}
|
|
463
|
-
const
|
|
959
|
+
const info = statSync(decision.absolutePath);
|
|
960
|
+
if (!info.isFile()) continue;
|
|
961
|
+
const size = info.size;
|
|
464
962
|
if (size > policy.maxFileBytes) {
|
|
465
963
|
skippedLargeFiles += 1;
|
|
466
964
|
continue;
|