@moretea-labs/forge 1.8.1 → 1.8.2
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 +5 -2
- package/CHANGELOG.md +20 -0
- package/README.en.md +24 -3
- package/README.md +10 -6
- package/README.zh-CN.md +5 -3
- package/THIRD_PARTY_NOTICES.md +1 -2
- package/adapters/chatgpt/browser-delivery-runtime.ts +5 -3
- package/adapters/chatgpt/provider-delivery.ts +1 -1
- package/adapters/chatgpt/work-conversation-binding-store.ts +20 -1
- package/adapters/computer/chatgpt-extension-target.ts +295 -50
- package/adapters/computer/chrome-extension/background.js +227 -71
- package/adapters/computer/chrome-extension/content.js +181 -157
- package/adapters/computer/chrome-extension/manifest.json +3 -1
- package/adapters/computer/desktop-operator-provider.ts +18 -6
- package/adapters/computer/interaction-target-authority.ts +39 -9
- package/adapters/mcp/access-mode.ts +18 -39
- package/adapters/mcp/auth.ts +2 -6
- package/adapters/mcp/instructions.ts +2 -2
- package/adapters/mcp/multi-repository.ts +7 -28
- package/adapters/mcp/runtime-gateway/context-adapter.ts +79 -40
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +7 -15
- package/adapters/mcp/runtime-gateway/execution-tools.ts +7 -13
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +16 -3
- package/adapters/mcp/runtime-gateway/process-tools.ts +3 -21
- package/adapters/mcp/runtime-gateway/router.ts +3 -5
- package/adapters/mcp/runtime-gateway/routing-policy.ts +17 -10
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1 -1
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +14 -9
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +1 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +36 -1
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +58 -4
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +7 -3
- package/adapters/mcp/runtime-gateway/work-adapter.ts +16 -16
- package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +0 -17
- package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +69 -12
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +26 -2
- package/adapters/mcp/server.ts +24 -3
- package/adapters/mcp/setup.ts +4 -10
- package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +672 -2935
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +1 -1
- package/adapters/mcp/tool-mapping/repository-tools.ts +52 -86
- package/adapters/mcp/toolset-names.ts +11 -42
- package/adapters/mcp/toolset.ts +16 -90
- package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
- package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
- package/adapters/mcp/transports/http-observation.ts +18 -6
- package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
- package/adapters/mcp/transports/http.ts +54 -14
- package/adapters/mcp/types.ts +0 -6
- package/assets/hooks/lib/session-state.sh +0 -38
- package/assets/hooks/post-bash.sh +1 -1
- package/assets/hooks/post-tool-observer.sh +0 -5
- package/assets/hooks/prompt-guard.sh +0 -55
- package/assets/partials/04-project-structure.partial.md +1 -1
- package/assets/partials/07-footer.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +1 -1
- package/assets/partials-agents/08-deep-docs.partial.md +1 -1
- package/assets/reference-configs/agentic-development-flow.md +1 -1
- package/assets/reference-configs/external-tooling.md +0 -76
- package/assets/reference-configs/release-deploy.md +1 -1
- package/assets/templates/helpers/architecture-queue.sh +1 -1
- package/assets/templates/helpers/check-agent-tooling.sh +1 -327
- package/assets/templates/helpers/check-task-workflow.sh +1 -1
- package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
- package/assets/templates/helpers/migrate-project-template.sh +2 -2
- package/assets/workflow-contract.v1.json +0 -2
- package/docs/README.md +5 -2
- package/docs/architecture/CURRENT.md +114 -30
- package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +15 -2
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +4 -2
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +109 -4
- package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
- package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
- package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
- package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
- package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
- package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
- package/docs/architecture/domains/public-surface.md +3 -5
- package/docs/architecture/domains/verification.md +5 -5
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +2 -2
- package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
- package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
- package/docs/forge-plugin-management.md +14 -2
- package/docs/operations/controller-browser-plugin.md +23 -0
- package/docs/operations/features.md +6 -7
- package/docs/operations/features.zh-CN.md +1 -1
- package/docs/operations/github-repository.md +4 -4
- package/docs/operations/github-semantic-release-projection.md +20 -0
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +13 -54
- package/docs/operations/runtime-performance-diagnostics.md +8 -0
- package/docs/operations/standalone-disaster-recovery.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/tutorials/03-first-repository-task.md +2 -0
- package/docs/wiki/Computer-and-Plugins.md +78 -0
- package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
- package/docs/wiki/Home.md +16 -2
- package/docs/wiki/Implementation.md +24 -12
- package/docs/wiki/Installation.md +28 -32
- package/docs/wiki/Integrations.md +4 -0
- package/docs/wiki/Quick-Start.md +35 -22
- package/docs/wiki/Releases-and-Upgrades.md +15 -25
- package/docs/wiki/_Sidebar.md +5 -1
- package/package.json +1 -12
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +63 -38
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +10 -0
- package/packages/kernel/scheduler/application/eligibility.ts +4 -1
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +13 -10
- package/packages/kernel/work/application/work-service.ts +1 -0
- package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
- package/packages/kernel/work/domain/state-machine.ts +9 -2
- package/packages/kernel/work/infrastructure/work-contract-store.ts +28 -1
- package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
- package/packages/plugin-runtime/computer/conversation-target.ts +17 -7
- package/scripts/assemble-template.ts +11 -72
- package/scripts/check-mcp-compatibility.ts +55 -74
- package/scripts/check-package-identity.mjs +0 -5
- package/scripts/check-runtime-architecture.mjs +305 -28
- package/scripts/init-project.sh +0 -46
- package/scripts/install-ios-hid-toolchain.sh +60 -0
- package/scripts/install-standalone-recovery.ts +1 -1
- package/scripts/lib/project-init-lib.sh +3 -3
- package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
- package/scripts/open-source-audit-allowlist.txt +0 -1
- package/scripts/run-governed-gate.ts +2 -0
- package/scripts/smoke-ios-physical-device.ts +38 -10
- package/scripts/smoke-mcp-http-runtime.ts +7 -8
- package/scripts/smoke-mcp-tool-surface.ts +27 -41
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +6 -3
- package/scripts/sync-generated-authority.mjs +0 -1
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/agent-jobs/integration.ts +2 -2
- package/src/cli/chatgpt-browser/file-policy.ts +2 -2
- package/src/cli/commands/doctor.ts +1 -143
- package/src/cli/commands/global-runtime.ts +3 -196
- package/src/cli/commands/init-hook.ts +6 -23
- package/src/cli/commands/init.ts +3 -252
- package/src/cli/commands/mcp.ts +0 -5
- package/src/cli/commands/prompt-guard-decision.ts +0 -2
- package/src/cli/commands/recovery.ts +2 -14
- package/src/cli/commands/repository.ts +17 -1
- package/src/cli/commands/setup-execution.ts +41 -0
- package/src/cli/controller/completion-orchestrator.ts +1 -1
- package/src/cli/controller/composite-operations.ts +1 -1
- package/src/cli/controller/context/instruction-context.ts +1 -1
- package/src/cli/controller/context/known-paths.ts +2 -2
- package/src/cli/controller/context/query-planning.ts +11 -4
- package/src/cli/controller/context/rule-resolution.ts +2 -2
- package/src/cli/controller/context/source-materializer.ts +2 -2
- package/src/cli/controller/context/structural-provider.ts +84 -0
- package/src/cli/controller/context/structural.ts +6 -6
- package/src/cli/controller/context/types.ts +8 -9
- package/src/cli/controller/context-pack.ts +97 -76
- package/src/cli/controller/operational-plan.ts +1 -1
- package/src/cli/controller/systemd-user.ts +4 -10
- package/src/cli/editing/edit-session.ts +17 -5
- package/src/cli/github/github.ts +4 -4
- package/src/cli/github/semantic-projection.ts +174 -0
- package/src/cli/hook/prompt-intents.ts +0 -9
- package/src/cli/index.ts +1 -43
- package/src/cli/local-bridge/connector-freshness.ts +1 -12
- package/src/cli/local-bridge/console-automations.ts +1 -1
- package/src/cli/local-bridge/console-view-models.ts +1 -1
- package/src/cli/local-bridge/facade-api.ts +0 -2
- package/src/cli/repositories/command-classifier.ts +0 -9
- package/src/cli/repositories/command-scope.ts +2 -2
- package/src/cli/repositories/locks.ts +20 -0
- package/src/cli/repositories/registry.ts +2 -1
- package/src/cli/repositories/safe-patch.ts +2 -12
- package/src/cli/repositories/structured-git.ts +39 -3
- package/src/cli/repository/inspector.ts +185 -343
- package/src/effects/process-runner.ts +17 -1
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +7 -0
- package/src/runtime/context/semantic-navigation.ts +1 -1
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +4 -4
- package/src/runtime/control-plane/execution/work-delivery-service.ts +150 -31
- package/src/runtime/control-plane/execution/work-handle-authority.ts +12 -4
- package/src/runtime/control-plane/execution/work-handle-store.ts +53 -1
- package/src/runtime/control-plane/execution/work-operation-service.ts +20 -10
- package/src/runtime/control-plane/execution/work-preparation-service.ts +108 -4
- package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +28 -13
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +5 -1
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +1 -1
- package/src/runtime/control-plane/facade/capability-registry.ts +2 -0
- package/src/runtime/control-plane/facade/work-continuation.ts +1 -1
- package/src/runtime/control-plane/generated-cache-retention.ts +114 -63
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +20 -8
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -11
- package/src/runtime/control-plane/governance/external-effects.ts +1 -1
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +1 -0
- package/src/runtime/control-plane/runtime-cleanup.ts +12 -93
- package/src/runtime/evidence/sensitive-output.ts +22 -2
- package/src/runtime/execution/jobs/child-reference.ts +11 -0
- package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
- package/src/runtime/execution/process-runtime/command-facade.ts +3 -3
- package/src/runtime/execution/process-runtime/runtime.ts +20 -16
- package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
- package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
- package/src/runtime/execution/workers/executor.ts +1 -2
- package/src/runtime/maintenance/cleanup.ts +41 -5
- package/src/runtime/plugins/action-application.ts +12 -1
- package/src/runtime/plugins/app-store-connect-adapter.ts +252 -4
- package/src/runtime/plugins/browser-adapter.ts +381 -313
- package/src/runtime/plugins/browser-macos-bridge.ts +114 -112
- package/src/runtime/plugins/browser-manifest-surface.ts +2 -1
- package/src/runtime/plugins/browser-registration.ts +2 -0
- package/src/runtime/plugins/browser-runtime.ts +26 -35
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +11 -2
- package/src/runtime/plugins/computer-registration.ts +76 -9
- package/src/runtime/plugins/github-adapter.ts +30 -3
- package/src/runtime/plugins/gmail-adapter.ts +166 -1
- package/src/runtime/plugins/google-shared.ts +10 -5
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
- package/src/runtime/plugins/ios-adapter.ts +45 -40
- package/src/runtime/plugins/ios-agent-device.ts +54 -4
- package/src/runtime/plugins/ios-device-ownership.ts +103 -0
- package/src/runtime/plugins/ios-physical-device.ts +497 -79
- package/src/runtime/plugins/local-system-adapter.ts +1 -99
- package/src/runtime/plugins/store.ts +80 -7
- package/src/runtime/plugins/types.ts +4 -0
- package/src/runtime/projections/git-status-sampler.ts +2 -19
- package/src/runtime/recovery/actions.ts +1 -1
- package/src/runtime/recovery/lifecycle-inventory.ts +1 -11
- package/src/runtime/recovery/probe.ts +2 -2
- package/src/runtime/release/release-coordinator.ts +20 -4
- package/src/runtime/root/controller-progression-composition.ts +67 -5
- package/src/runtime/root/package-connector-service.ts +123 -11
- package/src/runtime/root/package-runtime-release.ts +6 -18
- package/src/runtime/root/package-runtime-service.ts +7 -13
- package/src/runtime/root/release-execution-canary.ts +18 -5
- package/src/runtime/root/release-manifest.ts +0 -34
- package/src/runtime/root/release-materialize.ts +0 -78
- package/src/runtime/root/runtime.ts +1 -2
- package/src/runtime/root/scheduled-controller-composition.ts +2 -1
- package/src/runtime/root/service.ts +90 -11
- package/src/runtime/root/types.ts +0 -6
- package/src/runtime/root/workflow-supervisor-composition.ts +207 -19
- package/src/runtime/root/workflow-supervisor-runtime.ts +365 -10
- package/src/runtime/safe-tooling/google-credential-store.ts +1 -11
- package/src/runtime/safe-tooling/ios-development.ts +22 -8
- package/src/runtime/safe-tooling/job-summary.ts +1 -1
- package/src/runtime/safe-tooling/workspace-auth.ts +4 -1
- package/src/runtime/shared/atomic-text-file.ts +21 -0
- package/src/runtime/shared/json-files.ts +8 -3
- package/src/runtime/standalone-recovery/core.ts +189 -174
- package/src/runtime/standalone-recovery/entry.ts +62 -30
- package/src/runtime/standalone-recovery/operation-lock.ts +11 -1
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -0
- package/src/runtime/workflow/schedules/engine.ts +36 -14
- package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +51 -6
- package/supervisor/client.ts +31 -14
- package/supervisor/control-plane.ts +210 -115
- package/supervisor/forge-validators.ts +88 -21
- package/supervisor/native-browser-adapter.ts +393 -28
- package/supervisor/native-messaging/host.ts +21 -2
- package/supervisor/protocol.ts +74 -26
- package/supervisor/server.ts +16 -8
- package/supervisor/store.ts +476 -57
- package/supervisor/types.ts +19 -13
- package/SKILL.md +0 -427
- package/adapters/computer/chatgpt-target-router.ts +0 -103
- package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +0 -87
- package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
- package/adapters/mcp/toolset-selection.ts +0 -41
- package/agents/openai.yaml +0 -4
- package/assets/skill-hooks.json +0 -41
- package/assets/skill-version.json +0 -189
- package/assets/skills/claude-review/SKILL.md +0 -231
- package/assets/skills/codex-review/SKILL.md +0 -103
- package/assets/templates/helpers/check-skill-version.ts +0 -193
- package/scripts/check-skill-version.ts +0 -5
- package/scripts/ensure-codegraph.sh +0 -16
- package/scripts/run-skill-hook.ts +0 -244
- package/scripts/sync-codex-installed-copies.sh +0 -182
- package/src/cli/commands/tools.ts +0 -167
- package/src/cli/mcp/access-mode.ts +0 -2
- package/src/cli/mcp/access-tools.ts +0 -2
- package/src/cli/mcp/audit.ts +0 -2
- package/src/cli/mcp/auth.ts +0 -2
- package/src/cli/mcp/instructions.ts +0 -2
- package/src/cli/mcp/legacy-context.ts +0 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -2
- package/src/cli/mcp/multi-repository.ts +0 -2
- package/src/cli/mcp/oauth.ts +0 -2
- package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
- package/src/cli/mcp/paths.ts +0 -2
- package/src/cli/mcp/policy.ts +0 -2
- package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
- package/src/cli/mcp/proxy-env.ts +0 -2
- package/src/cli/mcp/readiness-projection.ts +0 -2
- package/src/cli/mcp/redaction.ts +0 -2
- package/src/cli/mcp/repo.ts +0 -2
- package/src/cli/mcp/repository-tools.ts +0 -2
- package/src/cli/mcp/server.ts +0 -2
- package/src/cli/mcp/setup.ts +0 -2
- package/src/cli/mcp/tool-capability-cache.ts +0 -2
- package/src/cli/mcp/tool-contract.ts +0 -2
- package/src/cli/mcp/tools.ts +0 -2
- package/src/cli/mcp/toolset-names.ts +0 -2
- package/src/cli/mcp/toolset-selection.ts +0 -2
- package/src/cli/mcp/toolset.ts +0 -2
- package/src/cli/mcp/transports/http.ts +0 -2
- package/src/cli/mcp/transports/session-registry.ts +0 -2
- package/src/cli/mcp/transports/stdio.ts +0 -2
- package/src/cli/mcp/types.ts +0 -2
- package/src/cli/mcp/umbrella-tools.ts +0 -2
- package/src/cli/tools/codegraph.ts +0 -695
- package/src/core/adoption/helper-wrapper-plan.ts +0 -159
- package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
- package/src/runtime/context/codegraph-read-provider.ts +0 -611
- package/src/runtime/context/codegraph-sidecar.cjs +0 -248
- package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
- package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
- package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
- package/src/runtime/gateway/mcp/process-tools.ts +0 -2
- package/src/runtime/gateway/mcp/router.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
- package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
- package/src/runtime/plugins/browser-provider-registry.ts +0 -2
- package/src/runtime/plugins/computer-chatgpt-macos-target.ts +0 -891
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
-
import { execFile } from 'child_process';
|
|
3
2
|
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from 'fs';
|
|
4
|
-
import {
|
|
5
|
-
import { join, resolve } from 'path';
|
|
3
|
+
import { delimiter, join, resolve } from 'path';
|
|
6
4
|
import { runProcess } from '../../effects/process-runner';
|
|
7
|
-
import { globMatches, resolveMcpPath } from '
|
|
8
|
-
import type { McpPolicy } from '
|
|
5
|
+
import { globMatches, resolveMcpPath } from '../../../adapters/mcp/paths';
|
|
6
|
+
import type { McpPolicy } from '../../../adapters/mcp/types';
|
|
9
7
|
import {
|
|
10
8
|
collectSessionIdentity,
|
|
11
9
|
getOrCreateSessionCache,
|
|
@@ -306,28 +304,6 @@ function gitSearchFileInventory(repoRoot: string): string[] | null {
|
|
|
306
304
|
}
|
|
307
305
|
}
|
|
308
306
|
|
|
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
|
-
|
|
331
307
|
function binary(bytes: Buffer): boolean {
|
|
332
308
|
return bytes.subarray(0, Math.min(bytes.length, 8000)).includes(0);
|
|
333
309
|
}
|
|
@@ -352,36 +328,6 @@ function walk(repoRoot: string, root: string, maxFiles: number, excludes: string
|
|
|
352
328
|
}
|
|
353
329
|
}
|
|
354
330
|
|
|
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
|
-
|
|
385
331
|
export interface SearchRepositoryOptions {
|
|
386
332
|
query: string;
|
|
387
333
|
includeGlobs?: string[];
|
|
@@ -499,7 +445,6 @@ function searchInventory(
|
|
|
499
445
|
repoRoot: string,
|
|
500
446
|
includes: string[],
|
|
501
447
|
excludes: string[],
|
|
502
|
-
maxFiles: number,
|
|
503
448
|
cacheKey?: string,
|
|
504
449
|
): { files: string[]; candidateCount: number; cacheHit: boolean } {
|
|
505
450
|
const inventoryKey = JSON.stringify({ repoRoot, excludes, cacheKey });
|
|
@@ -519,36 +464,10 @@ function searchInventory(
|
|
|
519
464
|
return files;
|
|
520
465
|
})();
|
|
521
466
|
const candidates = inventory.filter((path) => isIncluded(path, includes) && !isExcluded(path, excludes));
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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 };
|
|
467
|
+
// A lexical hit beyond the first N alphabetic Git paths is not optional.
|
|
468
|
+
// The bounded rg matcher owns result limits; inventory must retain all
|
|
469
|
+
// eligible source paths rather than silently losing the rest of the repo.
|
|
470
|
+
return { files: candidates, candidateCount: candidates.length, cacheHit };
|
|
552
471
|
}
|
|
553
472
|
|
|
554
473
|
interface SearchManyMatchState {
|
|
@@ -607,29 +526,17 @@ function exactCodeNeedleMatches(haystack: string, needle: string): boolean {
|
|
|
607
526
|
return new RegExp(`(?:^|[^\\p{L}\\p{N}_$])${escaped}(?=$|[^\\p{L}\\p{N}_$])`, 'u').test(haystack);
|
|
608
527
|
}
|
|
609
528
|
|
|
610
|
-
function
|
|
611
|
-
const
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
-
}
|
|
529
|
+
function matchSearchManyLine(state: SearchManyMatchState, path: string, line: number, text: string): void {
|
|
530
|
+
const haystack = state.caseSensitive ? text : text.toLowerCase();
|
|
531
|
+
for (const { query, needle } of state.needles) {
|
|
532
|
+
if ((state.counts.get(query) ?? 0) >= state.maxResultsPerQuery) continue;
|
|
533
|
+
const matches = state.requiredQueries.includes(query)
|
|
534
|
+
? exactCodeNeedleMatches(haystack, needle)
|
|
535
|
+
: haystack.includes(needle);
|
|
536
|
+
if (!matches) continue;
|
|
537
|
+
state.results.push({ query, path, line, text: text.slice(0, 500) });
|
|
538
|
+
state.matchedPaths.add(path);
|
|
539
|
+
state.counts.set(query, (state.counts.get(query) ?? 0) + 1);
|
|
633
540
|
}
|
|
634
541
|
}
|
|
635
542
|
|
|
@@ -674,9 +581,89 @@ function finalizeSearchManyResult(input: {
|
|
|
674
581
|
}
|
|
675
582
|
|
|
676
583
|
/**
|
|
677
|
-
*
|
|
678
|
-
*
|
|
679
|
-
*
|
|
584
|
+
* ripgrep owns literal matching and returns line-addressed evidence directly.
|
|
585
|
+
* The caller supplies only policy-checked paths; neither a repository-wide
|
|
586
|
+
* scan nor a second JavaScript file-content scan is permitted.
|
|
587
|
+
*/
|
|
588
|
+
function ripgrepCandidateLines(
|
|
589
|
+
repoRoot: string,
|
|
590
|
+
allowedPaths: readonly string[],
|
|
591
|
+
queries: readonly string[],
|
|
592
|
+
caseSensitive: boolean,
|
|
593
|
+
maxResultsPerQuery: number,
|
|
594
|
+
skippedBinaryPaths?: Set<string>,
|
|
595
|
+
): Map<string, Array<{ line: number; text: string }>> {
|
|
596
|
+
const found = new Map<string, Array<{ line: number; text: string }>>();
|
|
597
|
+
const searchDeadline = Date.now() + 15_000;
|
|
598
|
+
for (let offset = 0; offset < allowedPaths.length; offset += 128) {
|
|
599
|
+
const paths = allowedPaths.slice(offset, offset + 128);
|
|
600
|
+
const remainingMs = searchDeadline - Date.now();
|
|
601
|
+
if (remainingMs <= 0) throw new Error('RIPGREP_SEARCH_TIMEOUT: source discovery exceeded 15s total budget');
|
|
602
|
+
const result = runProcess('rg', [
|
|
603
|
+
'--null', '--with-filename', '--line-number', '--no-heading', '--color', 'never', '--fixed-strings', '--no-ignore', '--no-config', '--no-messages',
|
|
604
|
+
'--max-count', String(Math.min(Math.max(maxResultsPerQuery, 32), 500)),
|
|
605
|
+
...(caseSensitive ? [] : ['--ignore-case']),
|
|
606
|
+
...queries.flatMap((query) => ['-e', query]),
|
|
607
|
+
'--', ...paths,
|
|
608
|
+
], {
|
|
609
|
+
cwd: repoRoot,
|
|
610
|
+
// launchd/systemd often omit package-manager bins from PATH. A missing
|
|
611
|
+
// executable must never masquerade as rg exit 1 (no matches).
|
|
612
|
+
env: { PATH: [process.env.PATH, ...(process.platform === 'darwin' ? ['/opt/homebrew/bin', '/usr/local/bin'] : [])].filter(Boolean).join(delimiter) },
|
|
613
|
+
timeoutMs: Math.min(remainingMs, 5_000),
|
|
614
|
+
maxOutputBytes: 16 * 1024 * 1024,
|
|
615
|
+
});
|
|
616
|
+
if (result.error || (result.status !== 0 && result.status !== 1) || result.timedOut
|
|
617
|
+
|| result.stdout.endsWith('\n[output truncated after 16777216 bytes]')) {
|
|
618
|
+
throw new Error('RIPGREP_SEARCH_FAILED: ' + (result.error || result.stderr || String(result.status)));
|
|
619
|
+
}
|
|
620
|
+
// NUL-framed paths survive the shared process redactor. JSON-lines did
|
|
621
|
+
// not: masking source text inside a JSON string invalidated its syntax.
|
|
622
|
+
let cursor = 0;
|
|
623
|
+
while (cursor < result.stdout.length) {
|
|
624
|
+
const filenameEnd = result.stdout.indexOf('\0', cursor);
|
|
625
|
+
const firstLineEnd = result.stdout.indexOf('\n', cursor);
|
|
626
|
+
// Ripgrep prints a plain-text "path: binary file matches" notice when
|
|
627
|
+
// a candidate contains NUL bytes. This notice has no NUL-framed path
|
|
628
|
+
// or line number; discard only notices naming an exact authorized path.
|
|
629
|
+
// One binary match must not invalidate unrelated valid source evidence.
|
|
630
|
+
if (firstLineEnd >= cursor && (filenameEnd < 0 || firstLineEnd < filenameEnd)) {
|
|
631
|
+
const notice = result.stdout.slice(cursor, firstLineEnd);
|
|
632
|
+
const marker = ': binary file matches';
|
|
633
|
+
const markerAt = notice.lastIndexOf(marker);
|
|
634
|
+
const binaryPath = markerAt > 0 ? notice.slice(0, markerAt) : '';
|
|
635
|
+
const suffix = markerAt >= 0 ? notice.slice(markerAt + marker.length) : '';
|
|
636
|
+
if (!paths.includes(binaryPath) || (suffix !== '' && !/^ \(found "\\0" byte around offset \d+\)$/.test(suffix))) {
|
|
637
|
+
throw new Error('RIPGREP_SEARCH_INVALID_OUTPUT: malformed NUL/line-framed match');
|
|
638
|
+
}
|
|
639
|
+
skippedBinaryPaths?.add(binaryPath);
|
|
640
|
+
cursor = firstLineEnd + 1;
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
const lineEnd = result.stdout.indexOf('\n', filenameEnd + 1);
|
|
644
|
+
const numberEnd = result.stdout.indexOf(':', filenameEnd + 1);
|
|
645
|
+
if (filenameEnd < cursor || lineEnd < 0 || numberEnd <= filenameEnd || numberEnd >= lineEnd) {
|
|
646
|
+
throw new Error('RIPGREP_SEARCH_INVALID_OUTPUT: malformed NUL/line-framed match');
|
|
647
|
+
}
|
|
648
|
+
const path = result.stdout.slice(cursor, filenameEnd);
|
|
649
|
+
const line = Number(result.stdout.slice(filenameEnd + 1, numberEnd));
|
|
650
|
+
if (!Number.isSafeInteger(line) || line <= 0) {
|
|
651
|
+
throw new Error('RIPGREP_SEARCH_INVALID_OUTPUT: malformed source line number');
|
|
652
|
+
}
|
|
653
|
+
const text = result.stdout.slice(numberEnd + 1, lineEnd).replace(/\r$/, '');
|
|
654
|
+
const lines = found.get(path) ?? [];
|
|
655
|
+
lines.push({ line, text });
|
|
656
|
+
found.set(path, lines);
|
|
657
|
+
cursor = lineEnd + 1;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
for (const lines of found.values()) lines.sort((a, b) => a.line - b.line);
|
|
661
|
+
return found;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Internal Context Plane discovery: ripgrep selects matching authorized files,
|
|
666
|
+
* then bounded source materialization owns line-level evidence and ranking.
|
|
680
667
|
*/
|
|
681
668
|
export function searchRepositoryMany(
|
|
682
669
|
repoRoot: string,
|
|
@@ -704,13 +691,9 @@ export function searchRepositoryMany(
|
|
|
704
691
|
return { ...(cached.result as SearchRepositoryManyResult), cacheHit: true };
|
|
705
692
|
}
|
|
706
693
|
}
|
|
707
|
-
const inventory = directCandidates
|
|
708
|
-
? {
|
|
709
|
-
|
|
710
|
-
candidateCount: directCandidates.length,
|
|
711
|
-
cacheHit: false,
|
|
712
|
-
}
|
|
713
|
-
: searchInventory(repoRoot, includes, excludes, maxFiles, opts.cacheKey);
|
|
694
|
+
const inventory = directCandidates !== undefined
|
|
695
|
+
? { files: directCandidates.slice(0, maxFiles), candidateCount: directCandidates.length, cacheHit: false }
|
|
696
|
+
: searchInventory(repoRoot, includes, excludes, opts.cacheKey);
|
|
714
697
|
const matchState = createSearchManyMatchState(
|
|
715
698
|
queries,
|
|
716
699
|
maxResultsPerQuery,
|
|
@@ -723,30 +706,74 @@ export function searchRepositoryMany(
|
|
|
723
706
|
let scannedFiles = 0;
|
|
724
707
|
let policyDeniedFiles = 0;
|
|
725
708
|
let skippedLargeFiles = 0;
|
|
726
|
-
|
|
709
|
+
const skippedBinaryPaths = new Set<string>();
|
|
727
710
|
const canonicalRepoRoot = realpathSync(repoRoot);
|
|
728
711
|
|
|
712
|
+
const authorizedCandidatePaths: string[] = [];
|
|
713
|
+
// No external search process may inspect a file before MCP read authorization.
|
|
729
714
|
for (const path of inventory.files) {
|
|
730
|
-
if (searchManyComplete(matchState)) break;
|
|
731
715
|
const decision = resolveMcpPath(repoRoot, path, policy, 'read', canonicalRepoRoot);
|
|
732
|
-
if (!decision.ok || !decision.absolutePath) {
|
|
733
|
-
policyDeniedFiles += 1;
|
|
734
|
-
continue;
|
|
735
|
-
}
|
|
716
|
+
if (!decision.ok || !decision.absolutePath) { policyDeniedFiles += 1; continue; }
|
|
736
717
|
const info = statSync(decision.absolutePath);
|
|
737
718
|
if (!info.isFile()) continue;
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
719
|
+
if (info.size > policy.maxFileBytes) { skippedLargeFiles += 1; continue; }
|
|
720
|
+
authorizedCandidatePaths.push(path);
|
|
721
|
+
}
|
|
722
|
+
const exactHits = authorizedCandidatePaths.length && requiredQueries.length
|
|
723
|
+
? ripgrepCandidateLines(repoRoot, authorizedCandidatePaths, requiredQueries, opts.caseSensitive === true, maxResultsPerQuery, skippedBinaryPaths)
|
|
724
|
+
: null;
|
|
725
|
+
const hasRequiredHit = (needle: string): boolean => exactHits !== null
|
|
726
|
+
&& [...exactHits.values()].some((lines) => lines.some(({ text }) => exactCodeNeedleMatches(
|
|
727
|
+
opts.caseSensitive ? text : text.toLowerCase(),
|
|
728
|
+
opts.caseSensitive ? needle : needle.toLowerCase(),
|
|
729
|
+
)));
|
|
730
|
+
// A complete exact-symbol answer needs no broad recall-token pass. If an
|
|
731
|
+
// explicitly requested symbol is absent, fall back to ordinary discovery.
|
|
732
|
+
const hitsByPath = exactHits && requiredQueries.every(hasRequiredHit)
|
|
733
|
+
? exactHits
|
|
734
|
+
: authorizedCandidatePaths.length
|
|
735
|
+
? ripgrepCandidateLines(repoRoot, authorizedCandidatePaths, queries, opts.caseSensitive === true, maxResultsPerQuery, skippedBinaryPaths)
|
|
736
|
+
: new Map<string, Array<{ line: number; text: string }>>();
|
|
737
|
+
// Exact symbol needles must not lose to broad recall-token matches that
|
|
738
|
+
// occur in earlier alphabetic paths.
|
|
739
|
+
const exactHit = (path: string): boolean => requiredQueries.some((needle) =>
|
|
740
|
+
(hitsByPath.get(path) ?? []).some((hit) => exactCodeNeedleMatches(
|
|
741
|
+
opts.caseSensitive ? hit.text : hit.text.toLowerCase(),
|
|
742
|
+
opts.caseSensitive ? needle : needle.toLowerCase(),
|
|
743
|
+
)));
|
|
744
|
+
// Prefer the public source owner over same-named local wrappers. A bare
|
|
745
|
+
// declaration and an exported declaration are both exact hits, but they
|
|
746
|
+
// are not equally useful when only a few source files can be returned.
|
|
747
|
+
const declarationRankCache = new Map<string, number>();
|
|
748
|
+
const declarationRank = (path: string): number => {
|
|
749
|
+
const cached = declarationRankCache.get(path);
|
|
750
|
+
if (cached !== undefined) return cached;
|
|
751
|
+
let rank = 0;
|
|
752
|
+
for (const hit of hitsByPath.get(path) ?? []) {
|
|
753
|
+
const source = hit.text.trimStart();
|
|
754
|
+
const declared = /^(?:(?:export|declare|default|async)\s+)*(?:function|class|interface|type|const|let|enum)\s+([\w$]+)/.exec(source)?.[1];
|
|
755
|
+
if (declared === undefined || !requiredQueries.some((needle) =>
|
|
756
|
+
opts.caseSensitive ? declared === needle : declared.toLowerCase() === needle.toLowerCase())) continue;
|
|
757
|
+
rank = Math.max(rank, source.startsWith('export ') ? 2 : 1);
|
|
758
|
+
if (rank === 2) break;
|
|
747
759
|
}
|
|
748
|
-
|
|
749
|
-
|
|
760
|
+
declarationRankCache.set(path, rank);
|
|
761
|
+
return rank;
|
|
762
|
+
};
|
|
763
|
+
// Sorting only paths with evidence preserves the authorized inventory order
|
|
764
|
+
// while avoiding comparisons across thousands of guaranteed no-hit files.
|
|
765
|
+
const pathsWithHits = authorizedCandidatePaths.filter((path) => hitsByPath.has(path));
|
|
766
|
+
const orderedPaths = requiredQueries.length
|
|
767
|
+
? pathsWithHits.sort((left, right) =>
|
|
768
|
+
declarationRank(right) - declarationRank(left)
|
|
769
|
+
|| Number(exactHit(right)) - Number(exactHit(left)))
|
|
770
|
+
: pathsWithHits;
|
|
771
|
+
for (const path of orderedPaths) {
|
|
772
|
+
if (searchManyComplete(matchState) || matchState.matchedPaths.size >= maxFiles) break;
|
|
773
|
+
const hits = hitsByPath.get(path);
|
|
774
|
+
if (!hits) continue;
|
|
775
|
+
for (const hit of hits) matchSearchManyLine(matchState, path, hit.line, hit.text);
|
|
776
|
+
if (matchState.matchedPaths.has(path)) scannedFiles += 1;
|
|
750
777
|
}
|
|
751
778
|
|
|
752
779
|
// Consumers may bound distinct candidate files after this call. Preserve the
|
|
@@ -757,7 +784,7 @@ export function searchRepositoryMany(
|
|
|
757
784
|
scannedFiles,
|
|
758
785
|
policyDeniedFiles,
|
|
759
786
|
skippedLargeFiles,
|
|
760
|
-
skippedBinaryFiles,
|
|
787
|
+
skippedBinaryFiles: skippedBinaryPaths.size,
|
|
761
788
|
candidateCount: inventory.candidateCount,
|
|
762
789
|
inventoryFileCount: inventory.files.length,
|
|
763
790
|
inventoryCacheHit: inventory.cacheHit,
|
|
@@ -773,138 +800,20 @@ export function searchRepositoryMany(
|
|
|
773
800
|
return payload;
|
|
774
801
|
}
|
|
775
802
|
|
|
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
803
|
/**
|
|
811
|
-
* Async Context
|
|
812
|
-
*
|
|
813
|
-
*
|
|
814
|
-
* original repository order on the caller thread.
|
|
804
|
+
* Async Context Pack uses the same canonical ripgrep-backed search implementation.
|
|
805
|
+
* A second filesystem scanning implementation would diverge in policy, matching
|
|
806
|
+
* and ranking; this short-lived sidecar does not own a separate search engine.
|
|
815
807
|
*/
|
|
816
808
|
export async function searchRepositoryManyAsync(
|
|
817
809
|
repoRoot: string,
|
|
818
810
|
policy: McpPolicy,
|
|
819
811
|
opts: SearchRepositoryManyOptions,
|
|
820
812
|
): Promise<SearchRepositoryManyResult> {
|
|
821
|
-
|
|
822
|
-
queries,
|
|
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,
|
|
889
|
-
scannedFiles,
|
|
890
|
-
policyDeniedFiles,
|
|
891
|
-
skippedLargeFiles,
|
|
892
|
-
skippedBinaryFiles,
|
|
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;
|
|
813
|
+
return searchRepositoryMany(repoRoot, policy, opts);
|
|
906
814
|
}
|
|
907
815
|
|
|
816
|
+
/** Single-query facade delegates to the canonical policy-checked rg engine. */
|
|
908
817
|
export function searchRepository(repoRoot: string, policy: McpPolicy, opts: SearchRepositoryOptions & {
|
|
909
818
|
session?: RepositoryReadSession;
|
|
910
819
|
}): {
|
|
@@ -915,92 +824,25 @@ export function searchRepository(repoRoot: string, policy: McpPolicy, opts: Sear
|
|
|
915
824
|
skippedLargeFiles: number;
|
|
916
825
|
skippedBinaryFiles: number;
|
|
917
826
|
truncated: boolean;
|
|
918
|
-
truncationReason?:
|
|
827
|
+
truncationReason?: 'max_results' | 'max_files';
|
|
919
828
|
cacheHit?: boolean;
|
|
920
829
|
} {
|
|
921
|
-
const
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
const includeKey = JSON.stringify({
|
|
929
|
-
includes,
|
|
930
|
-
excludes,
|
|
931
|
-
maxResults,
|
|
932
|
-
maxFiles,
|
|
933
|
-
caseSensitive: opts.caseSensitive === true,
|
|
830
|
+
const results = searchRepositoryMany(repoRoot, policy, {
|
|
831
|
+
queries: [opts.query],
|
|
832
|
+
includeGlobs: opts.includeGlobs,
|
|
833
|
+
excludeGlobs: opts.excludeGlobs,
|
|
834
|
+
maxResultsPerQuery: opts.maxResults,
|
|
835
|
+
maxFiles: opts.maxFiles,
|
|
836
|
+
caseSensitive: opts.caseSensitive,
|
|
934
837
|
cacheKey: opts.cacheKey,
|
|
838
|
+
session: opts.session,
|
|
935
839
|
});
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
const inventory = searchInventory(repoRoot, includes, excludes, maxFiles, opts.cacheKey);
|
|
944
|
-
const files = inventory.files;
|
|
945
|
-
const needle = opts.caseSensitive ? query : query.toLowerCase();
|
|
946
|
-
const results: Array<{ path: string; line: number; text: string }> = [];
|
|
947
|
-
let scannedFiles = 0;
|
|
948
|
-
let policyDeniedFiles = 0;
|
|
949
|
-
let skippedLargeFiles = 0;
|
|
950
|
-
let skippedBinaryFiles = 0;
|
|
951
|
-
for (const path of files) {
|
|
952
|
-
if (results.length >= maxResults) break;
|
|
953
|
-
if (!isIncluded(path, includes) || isExcluded(path, excludes)) continue;
|
|
954
|
-
const decision = resolveMcpPath(repoRoot, path, policy, 'read');
|
|
955
|
-
if (!decision.ok || !decision.absolutePath) {
|
|
956
|
-
policyDeniedFiles += 1;
|
|
957
|
-
continue;
|
|
958
|
-
}
|
|
959
|
-
const info = statSync(decision.absolutePath);
|
|
960
|
-
if (!info.isFile()) continue;
|
|
961
|
-
const size = info.size;
|
|
962
|
-
if (size > policy.maxFileBytes) {
|
|
963
|
-
skippedLargeFiles += 1;
|
|
964
|
-
continue;
|
|
965
|
-
}
|
|
966
|
-
const bytes = readFileSync(decision.absolutePath);
|
|
967
|
-
if (binary(bytes)) {
|
|
968
|
-
skippedBinaryFiles += 1;
|
|
969
|
-
continue;
|
|
970
|
-
}
|
|
971
|
-
scannedFiles += 1;
|
|
972
|
-
const raw = bytes.toString('utf-8');
|
|
973
|
-
const lines = raw.split(/\r?\n/);
|
|
974
|
-
for (let index = 0; index < lines.length && results.length < maxResults; index += 1) {
|
|
975
|
-
const haystack = opts.caseSensitive ? lines[index] : lines[index].toLowerCase();
|
|
976
|
-
if (haystack.includes(needle)) results.push({ path, line: index + 1, text: lines[index].slice(0, 500) });
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
const truncationReason = results.length >= maxResults
|
|
980
|
-
? 'max_results' as const
|
|
981
|
-
: inventory.candidateCount > files.length
|
|
982
|
-
? 'max_files' as const
|
|
983
|
-
: undefined;
|
|
984
|
-
const payload = {
|
|
985
|
-
query,
|
|
986
|
-
results,
|
|
987
|
-
scannedFiles,
|
|
988
|
-
policyDeniedFiles,
|
|
989
|
-
skippedLargeFiles,
|
|
990
|
-
skippedBinaryFiles,
|
|
991
|
-
truncated: Boolean(truncationReason),
|
|
992
|
-
truncationReason,
|
|
993
|
-
cacheHit: inventory.cacheHit,
|
|
840
|
+
return {
|
|
841
|
+
...results,
|
|
842
|
+
query: opts.query,
|
|
843
|
+
results: results.results.map(({ path, line, text }) => ({ path, line, text })),
|
|
844
|
+
truncationReason: results.truncationReason === 'discovery_budget' ? undefined : results.truncationReason,
|
|
994
845
|
};
|
|
995
|
-
if (sessionCache) {
|
|
996
|
-
sessionCache.putSearch({
|
|
997
|
-
query,
|
|
998
|
-
includeKey,
|
|
999
|
-
result: payload,
|
|
1000
|
-
scannedFiles,
|
|
1001
|
-
});
|
|
1002
|
-
}
|
|
1003
|
-
return payload;
|
|
1004
846
|
}
|
|
1005
847
|
|
|
1006
848
|
export function clearSearchFileInventoryCacheForTest(): void {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, statSync } from "fs";
|
|
2
2
|
import { delimiter, extname, isAbsolute, join } from "path";
|
|
3
3
|
import { spawnSync } from "child_process";
|
|
4
|
-
import { redactSensitiveText } from "../runtime/evidence/sensitive-output";
|
|
4
|
+
import { redactSensitiveText, redactSensitiveValue } from "../runtime/evidence/sensitive-output";
|
|
5
5
|
|
|
6
6
|
export interface ProcessOutputRedaction {
|
|
7
7
|
readonly pattern: RegExp;
|
|
@@ -53,6 +53,22 @@ export function redactProcessOutput(
|
|
|
53
53
|
value: string,
|
|
54
54
|
redactions: readonly ProcessOutputRedaction[] = [],
|
|
55
55
|
): string {
|
|
56
|
+
// CLI JSON is a structured protocol, not free-form log text. Regex redaction
|
|
57
|
+
// can replace an array/object value with a bare [REDACTED] marker, destroying
|
|
58
|
+
// the JSON and causing an otherwise successful CoreDevice action to fail.
|
|
59
|
+
// Redact parsed values first, then reserialize so secrets stay hidden and
|
|
60
|
+
// downstream machine-readable contracts remain valid.
|
|
61
|
+
if (redactions.length === 0) {
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(value);
|
|
64
|
+
if (parsed !== null && typeof parsed === "object") {
|
|
65
|
+
const structured = redactSensitiveValue(parsed);
|
|
66
|
+
if (structured.changed) return JSON.stringify(structured.value);
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Ordinary command output is text, not necessarily JSON.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
56
72
|
const customRedacted = redactions.reduce(
|
|
57
73
|
(current, redaction) => current.replace(redaction.pattern, redaction.replacement),
|
|
58
74
|
value,
|
|
@@ -266,7 +266,7 @@ export class GenericLspSemanticProvider implements SemanticNavigationProvider {
|
|
|
266
266
|
return requests.map(() => ({
|
|
267
267
|
ok: false,
|
|
268
268
|
code: 'LSP_SEMANTIC_READ_SCOPE_UNSUPPORTED',
|
|
269
|
-
message: `External ${this.descriptor.id} navigation is restricted to the controller read profile; use lexical/
|
|
269
|
+
message: `External ${this.descriptor.id} navigation is restricted to the controller read profile; use bounded lexical/raw source evidence under narrower read policies.`,
|
|
270
270
|
}));
|
|
271
271
|
}
|
|
272
272
|
const outcomes: Array<SemanticNavigationOutcome | undefined> = new Array(requests.length);
|