clinkx 0.2.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/LICENSE +10 -0
- package/README.md +113 -18
- package/clinkx-workflows/dist/allowed-roots.d.ts +1 -0
- package/clinkx-workflows/dist/allowed-roots.js +5 -0
- package/clinkx-workflows/dist/allowed-roots.js.map +1 -0
- package/clinkx-workflows/dist/artifacts.d.ts +20 -1
- package/clinkx-workflows/dist/artifacts.js +38 -9
- package/clinkx-workflows/dist/artifacts.js.map +1 -1
- package/clinkx-workflows/dist/backend.d.ts +4 -0
- package/clinkx-workflows/dist/citation-check.d.ts +40 -0
- package/clinkx-workflows/dist/citation-check.js +348 -0
- package/clinkx-workflows/dist/citation-check.js.map +1 -0
- package/clinkx-workflows/dist/clink-client.d.ts +2 -1
- package/clinkx-workflows/dist/clink-client.js +17 -4
- package/clinkx-workflows/dist/clink-client.js.map +1 -1
- package/clinkx-workflows/dist/config.d.ts +145 -16
- package/clinkx-workflows/dist/config.js +384 -26
- package/clinkx-workflows/dist/config.js.map +1 -1
- package/clinkx-workflows/dist/contained-file.d.ts +172 -0
- package/clinkx-workflows/dist/contained-file.js +483 -0
- package/clinkx-workflows/dist/contained-file.js.map +1 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-contract.d.ts +416 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-contract.js +328 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-contract.js.map +1 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-router.d.ts +24 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-router.js +39 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-router.js.map +1 -0
- package/clinkx-workflows/dist/context-cache-key.d.ts +53 -0
- package/clinkx-workflows/dist/context-cache-key.js +91 -0
- package/clinkx-workflows/dist/context-cache-key.js.map +1 -0
- package/clinkx-workflows/dist/context-mode-resolver.d.ts +27 -0
- package/clinkx-workflows/dist/context-mode-resolver.js +126 -0
- package/clinkx-workflows/dist/context-mode-resolver.js.map +1 -0
- package/clinkx-workflows/dist/context-preflight.d.ts +184 -0
- package/clinkx-workflows/dist/context-preflight.js +91 -0
- package/clinkx-workflows/dist/context-preflight.js.map +1 -0
- package/clinkx-workflows/dist/council-expander.d.ts +1 -1
- package/clinkx-workflows/dist/council-expander.js +2 -1
- package/clinkx-workflows/dist/council-expander.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +165 -0
- package/clinkx-workflows/dist/dashboard/activity-contract.js +105 -0
- package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/activity-lease.d.ts +25 -0
- package/clinkx-workflows/dist/dashboard/activity-lease.js +34 -0
- package/clinkx-workflows/dist/dashboard/activity-lease.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +86 -0
- package/clinkx-workflows/dist/dashboard/activity-reader.js +480 -0
- package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/api-routes.d.ts +36 -0
- package/clinkx-workflows/dist/dashboard/api-routes.js +1047 -0
- package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/artifact-index.d.ts +37 -0
- package/clinkx-workflows/dist/dashboard/artifact-index.js +145 -0
- package/clinkx-workflows/dist/dashboard/artifact-index.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/challenge-store.d.ts +31 -0
- package/clinkx-workflows/dist/dashboard/auth/challenge-store.js +65 -0
- package/clinkx-workflows/dist/dashboard/auth/challenge-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/controller.d.ts +26 -0
- package/clinkx-workflows/dist/dashboard/auth/controller.js +596 -0
- package/clinkx-workflows/dist/dashboard/auth/controller.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/credential-store.d.ts +60 -0
- package/clinkx-workflows/dist/dashboard/auth/credential-store.js +776 -0
- package/clinkx-workflows/dist/dashboard/auth/credential-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.d.ts +9 -0
- package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js +44 -0
- package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/session-store.d.ts +17 -0
- package/clinkx-workflows/dist/dashboard/auth/session-store.js +77 -0
- package/clinkx-workflows/dist/dashboard/auth/session-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/webauthn-service.d.ts +25 -0
- package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js +71 -0
- package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/context-run-liveness.d.ts +37 -0
- package/clinkx-workflows/dist/dashboard/context-run-liveness.js +49 -0
- package/clinkx-workflows/dist/dashboard/context-run-liveness.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +222 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.js +1178 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/contracts.d.ts +8936 -0
- package/clinkx-workflows/dist/dashboard/contracts.js +1157 -0
- package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/generation.d.ts +14 -0
- package/clinkx-workflows/dist/dashboard/generation.js +41 -0
- package/clinkx-workflows/dist/dashboard/generation.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/index.d.ts +28 -0
- package/clinkx-workflows/dist/dashboard/index.js +16 -0
- package/clinkx-workflows/dist/dashboard/index.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +101 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.js +748 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +303 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js +221 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-reader.d.ts +50 -0
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js +581 -0
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.d.ts +89 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js +71 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +28 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +110 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/read-model.d.ts +86 -0
- package/clinkx-workflows/dist/dashboard/read-model.js +440 -0
- package/clinkx-workflows/dist/dashboard/read-model.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/request-admission.d.ts +31 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js +126 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/scanner.d.ts +75 -0
- package/clinkx-workflows/dist/dashboard/scanner.js +1110 -0
- package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/server-factory.d.ts +50 -0
- package/clinkx-workflows/dist/dashboard/server-factory.js +431 -0
- package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +2611 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.js +480 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-identity.d.ts +21 -0
- package/clinkx-workflows/dist/dashboard/session-log-identity.js +101 -0
- package/clinkx-workflows/dist/dashboard/session-log-identity.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-journal.d.ts +43 -0
- package/clinkx-workflows/dist/dashboard/session-log-journal.js +522 -0
- package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-reader.d.ts +61 -0
- package/clinkx-workflows/dist/dashboard/session-log-reader.js +378 -0
- package/clinkx-workflows/dist/dashboard/session-log-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +6 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js +367 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-retention.d.ts +23 -0
- package/clinkx-workflows/dist/dashboard/session-log-retention.js +212 -0
- package/clinkx-workflows/dist/dashboard/session-log-retention.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/static-responses.d.ts +10 -0
- package/clinkx-workflows/dist/dashboard/static-responses.js +94 -0
- package/clinkx-workflows/dist/dashboard/static-responses.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/worker-lock-observer.d.ts +28 -0
- package/clinkx-workflows/dist/dashboard/worker-lock-observer.js +241 -0
- package/clinkx-workflows/dist/dashboard/worker-lock-observer.js.map +1 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/index-DOkMXTfB.js +25 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/style-ChJsn0KD.css +1 -0
- package/clinkx-workflows/dist/dashboard-ui/contracts/council-plan-review-input-v1.json +158 -0
- package/clinkx-workflows/dist/dashboard-ui/index.html +15 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Mono-OFL.txt +93 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Sans-OFL.txt +93 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/JetBrains-Mono-OFL.txt +93 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/NOTICE.md +32 -0
- package/clinkx-workflows/dist/dashboard-ui/theme-init.js +8 -0
- package/clinkx-workflows/dist/definition-normalizer.d.ts +19 -0
- package/clinkx-workflows/dist/definition-normalizer.js.map +1 -1
- package/clinkx-workflows/dist/delegate-validate.d.ts +78 -0
- package/clinkx-workflows/dist/delegate-validate.js +97 -0
- package/clinkx-workflows/dist/delegate-validate.js.map +1 -0
- package/clinkx-workflows/dist/engine.d.ts +141 -0
- package/clinkx-workflows/dist/engine.js +882 -28
- package/clinkx-workflows/dist/engine.js.map +1 -1
- package/clinkx-workflows/dist/errors.d.ts +2 -0
- package/clinkx-workflows/dist/errors.js.map +1 -1
- package/clinkx-workflows/dist/fingerprint.d.ts +1 -1
- package/clinkx-workflows/dist/fingerprint.js +1 -1
- package/clinkx-workflows/dist/index.d.ts +19 -0
- package/clinkx-workflows/dist/index.js +15 -0
- package/clinkx-workflows/dist/index.js.map +1 -1
- package/clinkx-workflows/dist/loader.d.ts +9 -3
- package/clinkx-workflows/dist/loader.js +92 -21
- package/clinkx-workflows/dist/loader.js.map +1 -1
- package/clinkx-workflows/dist/plan-review/admission.d.ts +160 -0
- package/clinkx-workflows/dist/plan-review/admission.js +290 -0
- package/clinkx-workflows/dist/plan-review/admission.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/context-policy.d.ts +55 -0
- package/clinkx-workflows/dist/plan-review/context-policy.js +146 -0
- package/clinkx-workflows/dist/plan-review/context-policy.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/contract.d.ts +763 -0
- package/clinkx-workflows/dist/plan-review/contract.js +289 -0
- package/clinkx-workflows/dist/plan-review/contract.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/digest.d.ts +68 -0
- package/clinkx-workflows/dist/plan-review/digest.js +241 -0
- package/clinkx-workflows/dist/plan-review/digest.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/discovery.d.ts +45 -0
- package/clinkx-workflows/dist/plan-review/discovery.js +36 -0
- package/clinkx-workflows/dist/plan-review/discovery.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/execution-contract.d.ts +298 -0
- package/clinkx-workflows/dist/plan-review/execution-contract.js +194 -0
- package/clinkx-workflows/dist/plan-review/execution-contract.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/expander.d.ts +48 -0
- package/clinkx-workflows/dist/plan-review/expander.js +243 -0
- package/clinkx-workflows/dist/plan-review/expander.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/projector.d.ts +126 -0
- package/clinkx-workflows/dist/plan-review/projector.js +437 -0
- package/clinkx-workflows/dist/plan-review/projector.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/publication.d.ts +95 -0
- package/clinkx-workflows/dist/plan-review/publication.js +182 -0
- package/clinkx-workflows/dist/plan-review/publication.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/ranking-reducer.d.ts +34 -0
- package/clinkx-workflows/dist/plan-review/ranking-reducer.js +139 -0
- package/clinkx-workflows/dist/plan-review/ranking-reducer.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/resume.d.ts +38 -0
- package/clinkx-workflows/dist/plan-review/resume.js +122 -0
- package/clinkx-workflows/dist/plan-review/resume.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/rubric.d.ts +7 -0
- package/clinkx-workflows/dist/plan-review/rubric.js +24 -0
- package/clinkx-workflows/dist/plan-review/rubric.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/run-state.d.ts +398 -0
- package/clinkx-workflows/dist/plan-review/run-state.js +183 -0
- package/clinkx-workflows/dist/plan-review/run-state.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/runtime-policy.d.ts +74 -0
- package/clinkx-workflows/dist/plan-review/runtime-policy.js +186 -0
- package/clinkx-workflows/dist/plan-review/runtime-policy.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/snapshot.d.ts +81 -0
- package/clinkx-workflows/dist/plan-review/snapshot.js +170 -0
- package/clinkx-workflows/dist/plan-review/snapshot.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/stage-error.d.ts +19 -0
- package/clinkx-workflows/dist/plan-review/stage-error.js +20 -0
- package/clinkx-workflows/dist/plan-review/stage-error.js.map +1 -0
- package/clinkx-workflows/dist/post-processors/index.d.ts +20 -0
- package/clinkx-workflows/dist/post-processors/index.js +21 -0
- package/clinkx-workflows/dist/post-processors/index.js.map +1 -0
- package/clinkx-workflows/dist/prompt-budget.d.ts +7 -0
- package/clinkx-workflows/dist/prompt-budget.js +7 -1
- package/clinkx-workflows/dist/prompt-budget.js.map +1 -1
- package/clinkx-workflows/dist/reducers/index.js +2 -0
- package/clinkx-workflows/dist/reducers/index.js.map +1 -1
- package/clinkx-workflows/dist/run-lock-heartbeat.d.ts +2 -0
- package/clinkx-workflows/dist/run-lock-heartbeat.js +3 -0
- package/clinkx-workflows/dist/run-lock-heartbeat.js.map +1 -0
- package/clinkx-workflows/dist/run-summary/cards/council-plan-review.d.ts +34 -0
- package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js +74 -0
- package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js.map +1 -0
- package/clinkx-workflows/dist/run-summary/cards/index.js +5 -0
- package/clinkx-workflows/dist/run-summary/cards/index.js.map +1 -1
- package/clinkx-workflows/dist/run-summary.d.ts +13 -0
- package/clinkx-workflows/dist/run-summary.js +29 -1
- package/clinkx-workflows/dist/run-summary.js.map +1 -1
- package/clinkx-workflows/dist/schema.d.ts +76 -21
- package/clinkx-workflows/dist/schema.js +10 -1
- package/clinkx-workflows/dist/schema.js.map +1 -1
- package/clinkx-workflows/dist/seed-terms.d.ts +30 -0
- package/clinkx-workflows/dist/seed-terms.js +176 -0
- package/clinkx-workflows/dist/seed-terms.js.map +1 -0
- package/clinkx-workflows/dist/server.d.ts +5 -2
- package/clinkx-workflows/dist/server.js +82 -52
- package/clinkx-workflows/dist/server.js.map +1 -1
- package/clinkx-workflows/dist/shutdown.d.ts +18 -20
- package/clinkx-workflows/dist/shutdown.js +43 -61
- package/clinkx-workflows/dist/shutdown.js.map +1 -1
- package/clinkx-workflows/dist/state-schema.d.ts +38 -0
- package/clinkx-workflows/dist/state.d.ts +170 -3
- package/clinkx-workflows/dist/state.js +1275 -28
- package/clinkx-workflows/dist/state.js.map +1 -1
- package/clinkx-workflows/dist/transport.d.ts +15 -0
- package/clinkx-workflows/dist/transport.js +19 -10
- package/clinkx-workflows/dist/transport.js.map +1 -1
- package/clinkx-workflows/dist/types.d.ts +14 -2
- package/clinkx-workflows/dist/validators/council.d.ts +318 -85
- package/clinkx-workflows/dist/validators/council.js +52 -13
- package/clinkx-workflows/dist/validators/council.js.map +1 -1
- package/clinkx-workflows/dist/validators/index.d.ts +1 -0
- package/clinkx-workflows/dist/validators/index.js +3 -0
- package/clinkx-workflows/dist/validators/index.js.map +1 -1
- package/clinkx-workflows/dist/validators/plan-review.d.ts +398 -0
- package/clinkx-workflows/dist/validators/plan-review.js +316 -0
- package/clinkx-workflows/dist/validators/plan-review.js.map +1 -0
- package/clinkx-workflows/dist/workflow-receipt.js +188 -4
- package/clinkx-workflows/dist/workflow-receipt.js.map +1 -1
- package/clinkx-workflows/dist/workflow-tools.d.ts +48 -3
- package/clinkx-workflows/dist/workflow-tools.js +1402 -59
- package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
- package/clinkx-workflows/dist/workspace-signature.d.ts +16 -0
- package/clinkx-workflows/dist/workspace-signature.js +388 -0
- package/clinkx-workflows/dist/workspace-signature.js.map +1 -0
- package/clinkx-workflows/templates/council/answer-specialist.txt +2 -1
- package/clinkx-workflows/templates/council/code-review-chairman.txt +13 -0
- package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +4 -1
- package/clinkx-workflows/templates/council/code-review-reviewer.txt +19 -3
- package/clinkx-workflows/templates/council/debug-debugger.txt +2 -1
- package/clinkx-workflows/templates/council/discover-analyst.txt +2 -1
- package/clinkx-workflows/templates/council/member.txt +2 -1
- package/clinkx-workflows/templates/council/plan-review-chairman.txt +68 -0
- package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +51 -0
- package/clinkx-workflows/templates/council/plan-review-reviewer.txt +60 -0
- package/clinkx-workflows/templates/plan-review/chairman.txt +48 -0
- package/clinkx-workflows/templates/plan-review/cross-review.txt +52 -0
- package/clinkx-workflows/templates/plan-review/ranking.txt +16 -0
- package/clinkx-workflows/templates/plan-review/reviewer.txt +57 -0
- package/clinkx-workflows/workflows/council-answer.yaml +97 -8
- package/clinkx-workflows/workflows/council-code-review.yaml +103 -8
- package/clinkx-workflows/workflows/council-debug.yaml +97 -8
- package/clinkx-workflows/workflows/council-default.yaml +97 -8
- package/clinkx-workflows/workflows/council-discover.yaml +97 -8
- package/clinkx-workflows/workflows/council-plan-review.yaml +250 -0
- package/clinkx-workflows/workflows/plan-review-receipts.yaml +230 -0
- package/conf/adapters/agy.json +49 -0
- package/conf/adapters/claude.json +5 -0
- package/conf/adapters/codex-app-server.json +44 -0
- package/conf/adapters/codex.json +7 -2
- package/conf/adapters/glm.json +5 -0
- package/conf/adapters/grok.json +60 -0
- package/conf/adapters/hapi/agy.json +52 -0
- package/conf/adapters/hapi/claude.json +5 -0
- package/conf/adapters/hapi/codex.json +7 -2
- package/conf/adapters/hapi/glm.json +5 -0
- package/conf/prompts/json-codereviewer.txt +1 -0
- package/conf/prompts/json.txt +1 -0
- package/dist/activity-journal.d.ts +42 -0
- package/dist/activity-journal.js +381 -0
- package/dist/activity-journal.js.map +1 -0
- package/dist/allowed-roots.d.ts +53 -0
- package/dist/allowed-roots.js +216 -0
- package/dist/allowed-roots.js.map +1 -0
- package/dist/codex/activity-session-log.d.ts +14 -0
- package/dist/codex/activity-session-log.js +206 -0
- package/dist/codex/activity-session-log.js.map +1 -0
- package/dist/codex/agent-tools.d.ts +23 -0
- package/dist/codex/agent-tools.js +1270 -0
- package/dist/codex/agent-tools.js.map +1 -0
- package/dist/codex/app-server-client.d.ts +76 -0
- package/dist/codex/app-server-client.js +582 -0
- package/dist/codex/app-server-client.js.map +1 -0
- package/dist/codex/app-server-registry.d.ts +389 -0
- package/dist/codex/app-server-registry.js +1407 -0
- package/dist/codex/app-server-registry.js.map +1 -0
- package/dist/codex/client-types.d.ts +109 -0
- package/dist/codex/client-types.js +2 -0
- package/dist/codex/client-types.js.map +1 -0
- package/dist/codex/protocol.d.ts +5375 -0
- package/dist/codex/protocol.js +614 -0
- package/dist/codex/protocol.js.map +1 -0
- package/dist/codex/session.d.ts +263 -0
- package/dist/codex/session.js +466 -0
- package/dist/codex/session.js.map +1 -0
- package/dist/config.d.ts +185 -19
- package/dist/config.js +82 -16
- package/dist/config.js.map +1 -1
- package/dist/context-builder/anchors.d.ts +12 -0
- package/dist/context-builder/anchors.js +22 -0
- package/dist/context-builder/anchors.js.map +1 -0
- package/dist/context-builder/bundle.d.ts +109 -0
- package/dist/context-builder/bundle.js +1498 -0
- package/dist/context-builder/bundle.js.map +1 -0
- package/dist/context-builder/codemap.d.ts +22 -0
- package/dist/context-builder/codemap.js +675 -0
- package/dist/context-builder/codemap.js.map +1 -0
- package/dist/context-builder/config.d.ts +61 -0
- package/dist/context-builder/config.js +216 -0
- package/dist/context-builder/config.js.map +1 -0
- package/dist/context-builder/context-run-catalog.d.ts +17 -0
- package/dist/context-builder/context-run-catalog.js +99 -0
- package/dist/context-builder/context-run-catalog.js.map +1 -0
- package/dist/context-builder/delegate-packet.d.ts +219 -0
- package/dist/context-builder/delegate-packet.js +1190 -0
- package/dist/context-builder/delegate-packet.js.map +1 -0
- package/dist/context-builder/delegate-reconstruction.d.ts +13 -0
- package/dist/context-builder/delegate-reconstruction.js +220 -0
- package/dist/context-builder/delegate-reconstruction.js.map +1 -0
- package/dist/context-builder/delegate-validation.d.ts +27 -0
- package/dist/context-builder/delegate-validation.js +479 -0
- package/dist/context-builder/delegate-validation.js.map +1 -0
- package/dist/context-builder/delegate.d.ts +26 -0
- package/dist/context-builder/delegate.js +134 -0
- package/dist/context-builder/delegate.js.map +1 -0
- package/dist/context-builder/diagnostics.d.ts +36 -0
- package/dist/context-builder/diagnostics.js +107 -0
- package/dist/context-builder/diagnostics.js.map +1 -0
- package/dist/context-builder/discovery.d.ts +146 -0
- package/dist/context-builder/discovery.js +2129 -0
- package/dist/context-builder/discovery.js.map +1 -0
- package/dist/context-builder/globs.d.ts +14 -0
- package/dist/context-builder/globs.js +175 -0
- package/dist/context-builder/globs.js.map +1 -0
- package/dist/context-builder/handler.d.ts +41 -0
- package/dist/context-builder/handler.js +2590 -0
- package/dist/context-builder/handler.js.map +1 -0
- package/dist/context-builder/lifecycle-contract.d.ts +424 -0
- package/dist/context-builder/lifecycle-contract.js +523 -0
- package/dist/context-builder/lifecycle-contract.js.map +1 -0
- package/dist/context-builder/lifecycle-journal.d.ts +62 -0
- package/dist/context-builder/lifecycle-journal.js +88 -0
- package/dist/context-builder/lifecycle-journal.js.map +1 -0
- package/dist/context-builder/lifecycle-observer.d.ts +102 -0
- package/dist/context-builder/lifecycle-observer.js +258 -0
- package/dist/context-builder/lifecycle-observer.js.map +1 -0
- package/dist/context-builder/lifecycle-reconcile.d.ts +32 -0
- package/dist/context-builder/lifecycle-reconcile.js +191 -0
- package/dist/context-builder/lifecycle-reconcile.js.map +1 -0
- package/dist/context-builder/lifecycle-recovery.d.ts +2 -0
- package/dist/context-builder/lifecycle-recovery.js +14 -0
- package/dist/context-builder/lifecycle-recovery.js.map +1 -0
- package/dist/context-builder/path-signals.d.ts +7 -0
- package/dist/context-builder/path-signals.js +70 -0
- package/dist/context-builder/path-signals.js.map +1 -0
- package/dist/context-builder/paths.d.ts +30 -0
- package/dist/context-builder/paths.js +153 -0
- package/dist/context-builder/paths.js.map +1 -0
- package/dist/context-builder/progress.d.ts +17 -0
- package/dist/context-builder/progress.js +93 -0
- package/dist/context-builder/progress.js.map +1 -0
- package/dist/context-builder/receipt.d.ts +91 -0
- package/dist/context-builder/receipt.js +180 -0
- package/dist/context-builder/receipt.js.map +1 -0
- package/dist/context-builder/refine-handler.d.ts +14 -0
- package/dist/context-builder/refine-handler.js +790 -0
- package/dist/context-builder/refine-handler.js.map +1 -0
- package/dist/context-builder/review-coverage.d.ts +84 -0
- package/dist/context-builder/review-coverage.js +437 -0
- package/dist/context-builder/review-coverage.js.map +1 -0
- package/dist/context-builder/run-id.d.ts +7 -0
- package/dist/context-builder/run-id.js +31 -0
- package/dist/context-builder/run-id.js.map +1 -0
- package/dist/context-builder/schema.d.ts +669 -0
- package/dist/context-builder/schema.js +399 -0
- package/dist/context-builder/schema.js.map +1 -0
- package/dist/context-builder/secrets.d.ts +41 -0
- package/dist/context-builder/secrets.js +1651 -0
- package/dist/context-builder/secrets.js.map +1 -0
- package/dist/context-builder/selection.d.ts +56 -0
- package/dist/context-builder/selection.js +1111 -0
- package/dist/context-builder/selection.js.map +1 -0
- package/dist/context-builder/session-log-identity.d.ts +1 -0
- package/dist/context-builder/session-log-identity.js +66 -0
- package/dist/context-builder/session-log-identity.js.map +1 -0
- package/dist/context-builder/slice-range-math.d.ts +16 -0
- package/dist/context-builder/slice-range-math.js +137 -0
- package/dist/context-builder/slice-range-math.js.map +1 -0
- package/dist/context-builder/status.d.ts +66 -0
- package/dist/context-builder/status.js +160 -0
- package/dist/context-builder/status.js.map +1 -0
- package/dist/context-builder/storage.d.ts +118 -0
- package/dist/context-builder/storage.js +1003 -0
- package/dist/context-builder/storage.js.map +1 -0
- package/dist/context-builder/token-estimate.d.ts +19 -0
- package/dist/context-builder/token-estimate.js +90 -0
- package/dist/context-builder/token-estimate.js.map +1 -0
- package/dist/context-builder/versions.d.ts +29 -0
- package/dist/context-builder/versions.js +30 -0
- package/dist/context-builder/versions.js.map +1 -0
- package/dist/context-builder/workflow-runner.d.ts +69 -0
- package/dist/context-builder/workflow-runner.js +1051 -0
- package/dist/context-builder/workflow-runner.js.map +1 -0
- package/dist/context-builder/write-watchdog.d.ts +14 -0
- package/dist/context-builder/write-watchdog.js +38 -0
- package/dist/context-builder/write-watchdog.js.map +1 -0
- package/dist/dashboard-adapter-catalog.d.ts +9 -0
- package/dist/dashboard-adapter-catalog.js +46 -0
- package/dist/dashboard-adapter-catalog.js.map +1 -0
- package/dist/dashboard-cli.d.ts +79 -0
- package/dist/dashboard-cli.js +585 -0
- package/dist/dashboard-cli.js.map +1 -0
- package/dist/dashboard-companion/client.d.ts +36 -0
- package/dist/dashboard-companion/client.js +210 -0
- package/dist/dashboard-companion/client.js.map +1 -0
- package/dist/dashboard-companion/config.d.ts +8 -0
- package/dist/dashboard-companion/config.js +52 -0
- package/dist/dashboard-companion/config.js.map +1 -0
- package/dist/dashboard-companion/http-proxy.d.ts +10 -0
- package/dist/dashboard-companion/http-proxy.js +234 -0
- package/dist/dashboard-companion/http-proxy.js.map +1 -0
- package/dist/dashboard-companion/protocol.d.ts +80 -0
- package/dist/dashboard-companion/protocol.js +455 -0
- package/dist/dashboard-companion/protocol.js.map +1 -0
- package/dist/dashboard-companion/standalone-assets.d.ts +12 -0
- package/dist/dashboard-companion/standalone-assets.js +72 -0
- package/dist/dashboard-companion/standalone-assets.js.map +1 -0
- package/dist/dashboard-companion/standalone-entry.d.ts +1 -0
- package/dist/dashboard-companion/standalone-entry.js +17 -0
- package/dist/dashboard-companion/standalone-entry.js.map +1 -0
- package/dist/dashboard-gateway/config.d.ts +9 -0
- package/dist/dashboard-gateway/config.js +44 -0
- package/dist/dashboard-gateway/config.js.map +1 -0
- package/dist/dashboard-gateway/index.d.ts +11 -0
- package/dist/dashboard-gateway/index.js +755 -0
- package/dist/dashboard-gateway/index.js.map +1 -0
- package/dist/dashboard-gateway/main.d.ts +1 -0
- package/dist/dashboard-gateway/main.js +25 -0
- package/dist/dashboard-gateway/main.js.map +1 -0
- package/dist/dashboard-pairing-token.d.ts +1 -0
- package/dist/dashboard-pairing-token.js +5 -0
- package/dist/dashboard-pairing-token.js.map +1 -0
- package/dist/elaris/client.d.ts +73 -0
- package/dist/elaris/client.js +825 -0
- package/dist/elaris/client.js.map +1 -0
- package/dist/elaris/config.d.ts +18 -0
- package/dist/elaris/config.js +75 -0
- package/dist/elaris/config.js.map +1 -0
- package/dist/elaris/errors.d.ts +41 -0
- package/dist/elaris/errors.js +205 -0
- package/dist/elaris/errors.js.map +1 -0
- package/dist/elaris/github-repo.d.ts +18 -0
- package/dist/elaris/github-repo.js +89 -0
- package/dist/elaris/github-repo.js.map +1 -0
- package/dist/elaris/index.d.ts +10 -0
- package/dist/elaris/index.js +11 -0
- package/dist/elaris/index.js.map +1 -0
- package/dist/elaris/redact.d.ts +89 -0
- package/dist/elaris/redact.js +423 -0
- package/dist/elaris/redact.js.map +1 -0
- package/dist/elaris/run-id.d.ts +5 -0
- package/dist/elaris/run-id.js +20 -0
- package/dist/elaris/run-id.js.map +1 -0
- package/dist/elaris/status.d.ts +45 -0
- package/dist/elaris/status.js +122 -0
- package/dist/elaris/status.js.map +1 -0
- package/dist/elaris/tool-schemas.d.ts +314 -0
- package/dist/elaris/tool-schemas.js +208 -0
- package/dist/elaris/tool-schemas.js.map +1 -0
- package/dist/elaris/tools.d.ts +73 -0
- package/dist/elaris/tools.js +1065 -0
- package/dist/elaris/tools.js.map +1 -0
- package/dist/elaris/types.d.ts +1541 -0
- package/dist/elaris/types.js +252 -0
- package/dist/elaris/types.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +19 -0
- package/dist/errors.js.map +1 -1
- package/dist/handler.d.ts +4 -0
- package/dist/handler.js +10 -1
- package/dist/handler.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -1
- package/dist/local-clink-backend.d.ts +3 -0
- package/dist/local-clink-backend.js +4 -0
- package/dist/local-clink-backend.js.map +1 -1
- package/dist/package-info.d.ts +2 -0
- package/dist/package-info.js +18 -0
- package/dist/package-info.js.map +1 -0
- package/dist/parsers/codex-jsonl.js +48 -3
- package/dist/parsers/codex-jsonl.js.map +1 -1
- package/dist/parsers/extract.d.ts +10 -1
- package/dist/parsers/extract.js +11 -3
- package/dist/parsers/extract.js.map +1 -1
- package/dist/paths.d.ts +8 -0
- package/dist/paths.js +31 -43
- package/dist/paths.js.map +1 -1
- package/dist/pipeline.d.ts +51 -3
- package/dist/pipeline.js +264 -47
- package/dist/pipeline.js.map +1 -1
- package/dist/run-dir.d.ts +24 -0
- package/dist/run-dir.js +201 -2
- package/dist/run-dir.js.map +1 -1
- package/dist/runner.d.ts +13 -0
- package/dist/runner.js +184 -15
- package/dist/runner.js.map +1 -1
- package/dist/safety.d.ts +4 -4
- package/dist/safety.js +4 -4
- package/dist/schema.d.ts +7 -7
- package/dist/server.d.ts +2 -0
- package/dist/server.js +396 -24
- package/dist/server.js.map +1 -1
- package/dist/session-log-stream.d.ts +18 -0
- package/dist/session-log-stream.js +319 -0
- package/dist/session-log-stream.js.map +1 -0
- package/package.json +47 -5
- package/conf/adapters/gemini.json +0 -44
- package/conf/adapters/hapi/gemini.json +0 -47
- package/dist/artifacts.d.ts +0 -9
- package/dist/artifacts.js +0 -24
- package/dist/artifacts.js.map +0 -1
- package/dist/parsers/gemini-json.d.ts +0 -2
- package/dist/parsers/gemini-json.js +0 -72
- package/dist/parsers/gemini-json.js.map +0 -1
|
@@ -0,0 +1,2129 @@
|
|
|
1
|
+
import { constants as fsConstants } from "node:fs";
|
|
2
|
+
import { lstat as fsLstat, open as fsOpen, readdir as fsReaddir, realpath as fsRealpath, stat as fsStat, } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { isSamePathOrDescendant } from "../allowed-roots.js";
|
|
5
|
+
import { CancellationError } from "../errors.js";
|
|
6
|
+
import { CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS, } from "./config.js";
|
|
7
|
+
import { CONTEXT_BUILDER_REQUESTED_EVIDENCE_OMISSION_REASON, requestedEvidenceOmission, } from "./diagnostics.js";
|
|
8
|
+
import { compileContextBuilderGlobs, CONTEXT_BUILDER_DEFAULT_EXCLUDE_GLOBS, } from "./globs.js";
|
|
9
|
+
import { normalizeContextBuilderRootRelativePath, rootRelativePathForAbsolutePath, } from "./paths.js";
|
|
10
|
+
import { directoryDistance, genericPathKind, isTestPath, normalizedPathStem, } from "./path-signals.js";
|
|
11
|
+
import { buildContextReviewObligations, } from "./review-coverage.js";
|
|
12
|
+
import { sanitizeHighConfidenceSecretContent, scanSecretPath, } from "./secrets.js";
|
|
13
|
+
import { CONTEXT_BUILDER_PUBLIC_BUDGET_HARD_LIMITS, } from "./schema.js";
|
|
14
|
+
const SEARCH_WINDOW_LINES_BEFORE = 19;
|
|
15
|
+
const SEARCH_WINDOW_LINES_AFTER = 20;
|
|
16
|
+
const SEARCH_BRIDGE_GAP_LINES = 20;
|
|
17
|
+
const SEARCH_MAX_MERGED_SPAN_LINES = 160;
|
|
18
|
+
const DEFAULT_MAX_FULL_FOCUS_SLICE_BYTES = 48_000;
|
|
19
|
+
const DEFAULT_MAX_FULL_IMPLEMENTATION_FOCUS_SLICE_BYTES = 96_000;
|
|
20
|
+
// Non-silent, material signal for an explicitly-focused file that exceeded the
|
|
21
|
+
// bounded full-file focus size and had no per-file search evidence: instead of
|
|
22
|
+
// dropping it, we include a bounded head slice and record this reason so the
|
|
23
|
+
// receipt reflects a partial inclusion (not a silent omission).
|
|
24
|
+
const CONTEXT_BUILDER_FOCUS_SLICED_REASON = "focus_sliced";
|
|
25
|
+
// Non-silent, material signal for an explicitly-focused file whose FIRST logical
|
|
26
|
+
// line alone exceeds the bounded focus-slice cap. The line-range model cannot
|
|
27
|
+
// express a partial line, so emitting a head slice would blow the byte bound.
|
|
28
|
+
// We omit the file with this explicit material reason instead — honoring both
|
|
29
|
+
// the byte bound and "never silently drop an explicitly-focused file".
|
|
30
|
+
const CONTEXT_BUILDER_FOCUS_OVER_CAP_REASON = "focus_over_cap";
|
|
31
|
+
const IMPORT_EXPANSION_PASSES = 3;
|
|
32
|
+
const MAX_EXPANDED_SEARCH_TERMS = 128;
|
|
33
|
+
const MAX_EXPANDED_SEARCH_TERM_BYTES = 16_384;
|
|
34
|
+
const MAX_DERIVED_SEARCH_TERM_LENGTH = 256;
|
|
35
|
+
const PROJECT_SUPPORT_FILENAMES = Object.freeze([
|
|
36
|
+
"package.json",
|
|
37
|
+
"vitest.config.ts",
|
|
38
|
+
"vite.config.ts",
|
|
39
|
+
]);
|
|
40
|
+
export async function discoverContextBuilderCandidates(options) {
|
|
41
|
+
const allowSecrets = options.allowSecrets ?? true;
|
|
42
|
+
const internalCaps = options.internal_caps ?? CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS;
|
|
43
|
+
const matcher = compileContextBuilderGlobs({
|
|
44
|
+
include: [...(options.include_globs ?? [])],
|
|
45
|
+
exclude: [
|
|
46
|
+
...CONTEXT_BUILDER_DEFAULT_EXCLUDE_GLOBS,
|
|
47
|
+
...(options.exclude_globs ?? []),
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
const state = {
|
|
51
|
+
tree: [],
|
|
52
|
+
contentCandidates: [],
|
|
53
|
+
omissions: [],
|
|
54
|
+
requestedEvidenceOmissionKeys: new Set(),
|
|
55
|
+
processedFileKeys: new Set(),
|
|
56
|
+
traversedDirectoryKeys: new Set(),
|
|
57
|
+
focusScanExemptFiles: 0,
|
|
58
|
+
metrics: {
|
|
59
|
+
files_seen: 0,
|
|
60
|
+
files_considered: 0,
|
|
61
|
+
scan_cap_hit: false,
|
|
62
|
+
search_cap_hit: false,
|
|
63
|
+
discovery_byte_cap_hit: false,
|
|
64
|
+
discovery_candidate_cap_hit: false,
|
|
65
|
+
candidate_cap_hit: false,
|
|
66
|
+
bytes_read: 0,
|
|
67
|
+
},
|
|
68
|
+
stopped: false,
|
|
69
|
+
};
|
|
70
|
+
for (const root of options.roots) {
|
|
71
|
+
if (state.stopped) {
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
assertNotAborted(options.signal);
|
|
75
|
+
await processExactFocusFiles({
|
|
76
|
+
root,
|
|
77
|
+
focusPaths: options.focus_paths ?? [],
|
|
78
|
+
allowSecrets,
|
|
79
|
+
matcher,
|
|
80
|
+
budget: options.budget,
|
|
81
|
+
internalCaps,
|
|
82
|
+
...(options.signal == null ? {} : { signal: options.signal }),
|
|
83
|
+
fs: {
|
|
84
|
+
readdir: options.fs?.readdir ?? fsReaddir,
|
|
85
|
+
lstat: options.fs?.lstat ?? fsLstat,
|
|
86
|
+
open: options.fs?.open ?? fsOpen,
|
|
87
|
+
realpath: options.fs?.realpath ?? fsRealpath,
|
|
88
|
+
stat: options.fs?.stat ?? fsStat,
|
|
89
|
+
},
|
|
90
|
+
state,
|
|
91
|
+
});
|
|
92
|
+
await processFocusDirectories({
|
|
93
|
+
root,
|
|
94
|
+
focusPaths: options.focus_paths ?? [],
|
|
95
|
+
allowSecrets,
|
|
96
|
+
matcher,
|
|
97
|
+
budget: options.budget,
|
|
98
|
+
internalCaps,
|
|
99
|
+
excludedRealRoots: options.excluded_real_roots ?? [],
|
|
100
|
+
...(options.signal == null ? {} : { signal: options.signal }),
|
|
101
|
+
fs: {
|
|
102
|
+
readdir: options.fs?.readdir ?? fsReaddir,
|
|
103
|
+
lstat: options.fs?.lstat ?? fsLstat,
|
|
104
|
+
open: options.fs?.open ?? fsOpen,
|
|
105
|
+
realpath: options.fs?.realpath ?? fsRealpath,
|
|
106
|
+
stat: options.fs?.stat ?? fsStat,
|
|
107
|
+
},
|
|
108
|
+
state,
|
|
109
|
+
});
|
|
110
|
+
await traverseDirectory({
|
|
111
|
+
root,
|
|
112
|
+
absoluteDir: root.path,
|
|
113
|
+
relativeDir: "",
|
|
114
|
+
matcher,
|
|
115
|
+
focusPaths: options.focus_paths ?? [],
|
|
116
|
+
allowSecrets,
|
|
117
|
+
budget: options.budget,
|
|
118
|
+
internalCaps,
|
|
119
|
+
excludedRealRoots: options.excluded_real_roots ?? [],
|
|
120
|
+
...(options.signal == null ? {} : { signal: options.signal }),
|
|
121
|
+
fs: {
|
|
122
|
+
readdir: options.fs?.readdir ?? fsReaddir,
|
|
123
|
+
lstat: options.fs?.lstat ?? fsLstat,
|
|
124
|
+
open: options.fs?.open ?? fsOpen,
|
|
125
|
+
realpath: options.fs?.realpath ?? fsRealpath,
|
|
126
|
+
stat: options.fs?.stat ?? fsStat,
|
|
127
|
+
},
|
|
128
|
+
state,
|
|
129
|
+
});
|
|
130
|
+
await processImportAndSupportFiles({
|
|
131
|
+
root,
|
|
132
|
+
matcher,
|
|
133
|
+
focusPaths: options.focus_paths ?? [],
|
|
134
|
+
allowSecrets,
|
|
135
|
+
budget: options.budget,
|
|
136
|
+
internalCaps,
|
|
137
|
+
...(options.signal == null ? {} : { signal: options.signal }),
|
|
138
|
+
fs: {
|
|
139
|
+
readdir: options.fs?.readdir ?? fsReaddir,
|
|
140
|
+
lstat: options.fs?.lstat ?? fsLstat,
|
|
141
|
+
open: options.fs?.open ?? fsOpen,
|
|
142
|
+
realpath: options.fs?.realpath ?? fsRealpath,
|
|
143
|
+
stat: options.fs?.stat ?? fsStat,
|
|
144
|
+
},
|
|
145
|
+
state,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
state.tree.sort(compareTreeFiles);
|
|
149
|
+
state.contentCandidates.sort(compareContentCandidates);
|
|
150
|
+
const expandedSearchTerms = expandSearchTerms([...(options.search_terms ?? [])]);
|
|
151
|
+
const search = buildCandidateSearchRecords({
|
|
152
|
+
candidates: state.contentCandidates,
|
|
153
|
+
focusPaths: [...(options.focus_paths ?? [])],
|
|
154
|
+
searchTerms: expandedSearchTerms.terms,
|
|
155
|
+
callerTermCount: expandedSearchTerms.callerTermCount,
|
|
156
|
+
maxSearchResults: options.budget.max_search_results,
|
|
157
|
+
maxPreviewBytes: internalCaps.max_search_preview_bytes,
|
|
158
|
+
maxCandidateBytes: internalCaps.max_candidate_bytes,
|
|
159
|
+
...(options.signal == null ? {} : { signal: options.signal }),
|
|
160
|
+
});
|
|
161
|
+
if (search.capHit) {
|
|
162
|
+
state.metrics.search_cap_hit = true;
|
|
163
|
+
}
|
|
164
|
+
const slices = buildCandidateSliceRecords({
|
|
165
|
+
candidates: state.contentCandidates,
|
|
166
|
+
focusPaths: [...(options.focus_paths ?? [])],
|
|
167
|
+
searchRecords: search.records,
|
|
168
|
+
maxCandidateSlices: internalCaps.max_candidate_slices,
|
|
169
|
+
maxCandidateBytes: Math.max(0, internalCaps.max_candidate_bytes - search.candidateBytes),
|
|
170
|
+
maxSearchSliceBytesPerCandidate: options.budget.max_bytes_per_file,
|
|
171
|
+
maxFullFocusSliceBytes: Math.min(options.budget.max_bytes_per_file, DEFAULT_MAX_FULL_IMPLEMENTATION_FOCUS_SLICE_BYTES),
|
|
172
|
+
...(options.signal == null ? {} : { signal: options.signal }),
|
|
173
|
+
});
|
|
174
|
+
state.omissions.push(...slices.omissions);
|
|
175
|
+
if (slices.capHit) {
|
|
176
|
+
state.metrics.candidate_cap_hit = true;
|
|
177
|
+
}
|
|
178
|
+
const reviewObligations = buildContextReviewObligations({
|
|
179
|
+
candidates: state.contentCandidates,
|
|
180
|
+
focusPaths: (options.focus_paths ?? []).map((focus) => ({
|
|
181
|
+
root_index: focus.root_index,
|
|
182
|
+
path: focus.root_relative_path,
|
|
183
|
+
kind: focus.kind,
|
|
184
|
+
})),
|
|
185
|
+
searchRecords: search.records,
|
|
186
|
+
slices: slices.records,
|
|
187
|
+
policy: options.coverage_policy,
|
|
188
|
+
});
|
|
189
|
+
classifyContextEvidenceCandidates({
|
|
190
|
+
candidates: state.contentCandidates,
|
|
191
|
+
focusPaths: options.focus_paths ?? [],
|
|
192
|
+
searchRecords: search.records,
|
|
193
|
+
policy: options.coverage_policy,
|
|
194
|
+
obligations: reviewObligations,
|
|
195
|
+
});
|
|
196
|
+
auditScanCapFocusedPaths({
|
|
197
|
+
focusPaths: [...(options.focus_paths ?? [])],
|
|
198
|
+
state,
|
|
199
|
+
});
|
|
200
|
+
return {
|
|
201
|
+
tree: state.tree,
|
|
202
|
+
content_candidates: state.contentCandidates,
|
|
203
|
+
search: search.records,
|
|
204
|
+
slices: slices.records,
|
|
205
|
+
omissions: state.omissions,
|
|
206
|
+
metrics: state.metrics,
|
|
207
|
+
search_coverage: search.coverage,
|
|
208
|
+
...(reviewObligations == null ? {} : { review_obligations: reviewObligations }),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
export function expandSearchTerms(searchTerms) {
|
|
212
|
+
const expanded = [];
|
|
213
|
+
const seen = new Set();
|
|
214
|
+
let aggregateBytes = 0;
|
|
215
|
+
// Caller terms are admitted first and are never displaced by heuristic
|
|
216
|
+
// expansion. Public post-parse validation owns their individual/aggregate
|
|
217
|
+
// bounds; this function only bounds derived fan-out before stream creation.
|
|
218
|
+
for (const term of searchTerms) {
|
|
219
|
+
const added = addExpandedSearchTerm(expanded, seen, term);
|
|
220
|
+
aggregateBytes += added;
|
|
221
|
+
}
|
|
222
|
+
const callerTermCount = expanded.length;
|
|
223
|
+
for (const term of searchTerms) {
|
|
224
|
+
for (const derived of deriveGenericSearchTerms(term)) {
|
|
225
|
+
const normalized = derived.trim();
|
|
226
|
+
if (normalized.length > MAX_DERIVED_SEARCH_TERM_LENGTH ||
|
|
227
|
+
expanded.length >= MAX_EXPANDED_SEARCH_TERMS) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const bytes = Buffer.byteLength(normalized, "utf8");
|
|
231
|
+
if (aggregateBytes + bytes > MAX_EXPANDED_SEARCH_TERM_BYTES) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
aggregateBytes += addExpandedSearchTerm(expanded, seen, normalized);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { terms: expanded, callerTermCount };
|
|
238
|
+
}
|
|
239
|
+
function classifyContextEvidenceCandidates(input) {
|
|
240
|
+
if (input.policy?.id == null) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const directSearchKeys = new Set(input.searchRecords.map(candidateKey));
|
|
244
|
+
const focusedFileKeys = new Set(input.focusPaths
|
|
245
|
+
.filter((focus) => focus.kind === "file")
|
|
246
|
+
.map((focus) => candidateKey({
|
|
247
|
+
root_index: focus.root_index,
|
|
248
|
+
path: focus.root_relative_path,
|
|
249
|
+
})));
|
|
250
|
+
const targetCandidateIds = new Set(input.obligations?.targets.map((target) => target.candidate_id) ?? []);
|
|
251
|
+
const regressionCandidateIds = new Set(input.obligations?.targets.flatMap((target) => target.regression_links.map((link) => link.candidate_id)) ?? []);
|
|
252
|
+
for (const candidate of input.candidates) {
|
|
253
|
+
const classes = [];
|
|
254
|
+
const key = candidateKey(candidate);
|
|
255
|
+
const packetCandidateId = `c:${String(candidate.root_index)}:${candidate.path}`;
|
|
256
|
+
const isReviewTarget = targetCandidateIds.has(packetCandidateId);
|
|
257
|
+
if (isReviewTarget) {
|
|
258
|
+
classes.push("review_target");
|
|
259
|
+
}
|
|
260
|
+
if (isTestPath(candidate.path) && regressionCandidateIds.has(packetCandidateId)) {
|
|
261
|
+
classes.push("regression_test");
|
|
262
|
+
}
|
|
263
|
+
const hasDiscoveryEvidence = directSearchKeys.has(key) ||
|
|
264
|
+
focusedFileKeys.has(key) ||
|
|
265
|
+
candidate.selection_hint != null;
|
|
266
|
+
if (genericPathKind(candidate.path) === "config" && hasDiscoveryEvidence) {
|
|
267
|
+
classes.push("configuration");
|
|
268
|
+
}
|
|
269
|
+
if (candidate.selection_hint === "local_dependency") {
|
|
270
|
+
classes.push("dependency");
|
|
271
|
+
}
|
|
272
|
+
candidate.evidence_classes = classes;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function addExpandedSearchTerm(expanded, seen, term) {
|
|
276
|
+
const normalized = term.trim();
|
|
277
|
+
if (normalized.length === 0) {
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
const key = normalized.toLowerCase();
|
|
281
|
+
if (seen.has(key)) {
|
|
282
|
+
return 0;
|
|
283
|
+
}
|
|
284
|
+
expanded.push(normalized);
|
|
285
|
+
seen.add(key);
|
|
286
|
+
return Buffer.byteLength(normalized, "utf8");
|
|
287
|
+
}
|
|
288
|
+
function deriveGenericSearchTerms(term) {
|
|
289
|
+
const tokens = tokenizeGenericSearchTerm(term);
|
|
290
|
+
const terms = new Set(tokens);
|
|
291
|
+
for (let index = 0; index < tokens.length - 1; index++) {
|
|
292
|
+
const current = tokens[index];
|
|
293
|
+
const next = tokens[index + 1];
|
|
294
|
+
if (current == null || next == null) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
terms.add(`${current}_${next}`);
|
|
298
|
+
terms.add(`${current}${next}`);
|
|
299
|
+
}
|
|
300
|
+
return [...terms].filter((value) => value.length >= 3).sort();
|
|
301
|
+
}
|
|
302
|
+
function tokenizeGenericSearchTerm(value) {
|
|
303
|
+
return value
|
|
304
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
305
|
+
.replace(/\.[A-Za-z0-9]+$/g, " ")
|
|
306
|
+
.split(/[^A-Za-z0-9]+/)
|
|
307
|
+
.map((word) => normalizeGenericSearchWord(word))
|
|
308
|
+
.filter((word) => word.length >= 3 && !GENERIC_SEARCH_STOPWORDS.has(word));
|
|
309
|
+
}
|
|
310
|
+
function normalizeGenericSearchWord(word) {
|
|
311
|
+
const lower = word.toLowerCase();
|
|
312
|
+
if (lower.length > 4 && lower.endsWith("ies")) {
|
|
313
|
+
return `${lower.slice(0, -3)}y`;
|
|
314
|
+
}
|
|
315
|
+
if (lower.length > 3 && lower.endsWith("s")) {
|
|
316
|
+
return lower.slice(0, -1);
|
|
317
|
+
}
|
|
318
|
+
return lower;
|
|
319
|
+
}
|
|
320
|
+
const GENERIC_SEARCH_STOPWORDS = new Set([
|
|
321
|
+
"the",
|
|
322
|
+
"and",
|
|
323
|
+
"for",
|
|
324
|
+
"with",
|
|
325
|
+
"that",
|
|
326
|
+
"this",
|
|
327
|
+
"from",
|
|
328
|
+
"into",
|
|
329
|
+
"without",
|
|
330
|
+
"change",
|
|
331
|
+
"fix",
|
|
332
|
+
"add",
|
|
333
|
+
"new",
|
|
334
|
+
"safe",
|
|
335
|
+
"safely",
|
|
336
|
+
"test",
|
|
337
|
+
"tests",
|
|
338
|
+
]);
|
|
339
|
+
async function processImportAndSupportFiles(input) {
|
|
340
|
+
for (let pass = 0; pass < IMPORT_EXPANSION_PASSES; pass++) {
|
|
341
|
+
const before = input.state.contentCandidates.length;
|
|
342
|
+
const dependencies = dependencyPathChoicesForCandidates(input.root, input.state.contentCandidates.filter((candidate) => candidate.root_index === input.root.root_index));
|
|
343
|
+
for (const choices of dependencies) {
|
|
344
|
+
assertNotAborted(input.signal);
|
|
345
|
+
const relativePath = await firstExistingPath(input.root, choices, input.fs);
|
|
346
|
+
if (relativePath == null) {
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
await processFile({
|
|
350
|
+
root: input.root,
|
|
351
|
+
absolutePath: path.join(input.root.path, relativePath),
|
|
352
|
+
relativePath,
|
|
353
|
+
matcher: input.matcher,
|
|
354
|
+
focusPaths: input.focusPaths,
|
|
355
|
+
allowSecrets: input.allowSecrets,
|
|
356
|
+
budget: input.budget,
|
|
357
|
+
internalCaps: input.internalCaps,
|
|
358
|
+
ignoreIncludeGlob: true,
|
|
359
|
+
ignoreScanCap: true,
|
|
360
|
+
selectionHint: "local_dependency",
|
|
361
|
+
...(input.signal == null ? {} : { signal: input.signal }),
|
|
362
|
+
fs: input.fs,
|
|
363
|
+
state: input.state,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
if (input.state.contentCandidates.length === before) {
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const supportFiles = projectSupportFilesForCandidates(input.root, input.state.contentCandidates.filter((candidate) => candidate.root_index === input.root.root_index));
|
|
371
|
+
for (const relativePath of supportFiles) {
|
|
372
|
+
assertNotAborted(input.signal);
|
|
373
|
+
if ((await firstExistingPath(input.root, [relativePath], input.fs)) == null) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
await processFile({
|
|
377
|
+
root: input.root,
|
|
378
|
+
absolutePath: path.join(input.root.path, relativePath),
|
|
379
|
+
relativePath,
|
|
380
|
+
matcher: input.matcher,
|
|
381
|
+
focusPaths: input.focusPaths,
|
|
382
|
+
allowSecrets: input.allowSecrets,
|
|
383
|
+
budget: input.budget,
|
|
384
|
+
internalCaps: input.internalCaps,
|
|
385
|
+
ignoreIncludeGlob: true,
|
|
386
|
+
ignoreScanCap: true,
|
|
387
|
+
selectionHint: "project_support",
|
|
388
|
+
...(input.signal == null ? {} : { signal: input.signal }),
|
|
389
|
+
fs: input.fs,
|
|
390
|
+
state: input.state,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function dependencyPathChoicesForCandidates(root, candidates) {
|
|
395
|
+
const choices = [];
|
|
396
|
+
const seen = new Set();
|
|
397
|
+
for (const candidate of candidates) {
|
|
398
|
+
for (const specifier of importSpecifiers(candidate.content)) {
|
|
399
|
+
const resolved = resolveImportSpecifierChoices(root, candidate.path, specifier);
|
|
400
|
+
const useful = resolved.filter((pathChoice) => isUsefulLocalDependency(candidate.path, pathChoice));
|
|
401
|
+
if (useful.length === 0) {
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
const key = useful.join("\u0000");
|
|
405
|
+
if (!seen.has(key)) {
|
|
406
|
+
choices.push(useful);
|
|
407
|
+
seen.add(key);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return choices.sort((left, right) => left[0].localeCompare(right[0]));
|
|
412
|
+
}
|
|
413
|
+
async function firstExistingPath(root, choices, fs) {
|
|
414
|
+
for (const relativePath of choices) {
|
|
415
|
+
try {
|
|
416
|
+
const stats = await fs.stat(path.join(root.path, relativePath));
|
|
417
|
+
if (stats.isFile()) {
|
|
418
|
+
return relativePath;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
if (isExpectedDescendantFilesystemError(error)) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
throw error;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
function projectSupportFilesForCandidates(root, candidates) {
|
|
431
|
+
const prefixes = new Set();
|
|
432
|
+
for (const candidate of candidates) {
|
|
433
|
+
const prefix = projectPackagePrefix(candidate.path);
|
|
434
|
+
if (prefix != null) {
|
|
435
|
+
prefixes.add(prefix);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const paths = [];
|
|
439
|
+
for (const prefix of [...prefixes].sort()) {
|
|
440
|
+
for (const filename of PROJECT_SUPPORT_FILENAMES) {
|
|
441
|
+
const relativePath = prefix.length === 0 ? filename : `${prefix}/${filename}`;
|
|
442
|
+
if (isSamePathOrDescendant(path.join(root.path, relativePath), root.path)) {
|
|
443
|
+
paths.push(relativePath);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return paths;
|
|
448
|
+
}
|
|
449
|
+
function importSpecifiers(content) {
|
|
450
|
+
const specifiers = [];
|
|
451
|
+
const patterns = [
|
|
452
|
+
/\bimport\s+(?:type\s+)?(?:[^'";]+?\s+from\s+)?['"]([^'"]+)['"]/g,
|
|
453
|
+
/\bexport\s+(?:type\s+)?[^'";]+?\s+from\s+['"]([^'"]+)['"]/g,
|
|
454
|
+
/\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
455
|
+
/^\s*from\s+([A-Za-z_][\w.]*|\.+[\w.]*)\s+import\b/gm,
|
|
456
|
+
/^\s*import\s+(app(?:\.[A-Za-z_][\w]*)+)\b/gm,
|
|
457
|
+
];
|
|
458
|
+
for (const pattern of patterns) {
|
|
459
|
+
let match;
|
|
460
|
+
while ((match = pattern.exec(content)) != null) {
|
|
461
|
+
const specifier = match[1];
|
|
462
|
+
if (specifier != null) {
|
|
463
|
+
specifiers.push(specifier);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return specifiers;
|
|
468
|
+
}
|
|
469
|
+
function resolveImportSpecifierChoices(root, importerPath, specifier) {
|
|
470
|
+
const base = resolveImportBase(importerPath, specifier);
|
|
471
|
+
if (base == null) {
|
|
472
|
+
return [];
|
|
473
|
+
}
|
|
474
|
+
const choices = [];
|
|
475
|
+
for (const candidate of importResolutionCandidates(base)) {
|
|
476
|
+
const absolute = path.join(root.path, candidate);
|
|
477
|
+
if (!isSamePathOrDescendant(absolute, root.path)) {
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
const normalized = normalizeContextBuilderRootRelativePath(candidate);
|
|
482
|
+
if (normalized === candidate) {
|
|
483
|
+
choices.push(candidate);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return choices;
|
|
491
|
+
}
|
|
492
|
+
function resolveImportBase(importerPath, specifier) {
|
|
493
|
+
if (specifier.startsWith("@/")) {
|
|
494
|
+
const srcPrefix = sourceRootPrefix(importerPath);
|
|
495
|
+
return srcPrefix == null ? null : `${srcPrefix}/${specifier.slice(2)}`;
|
|
496
|
+
}
|
|
497
|
+
if (specifier.startsWith("app.") && importerPath.endsWith(".py")) {
|
|
498
|
+
const appPrefix = pythonAppRootPrefix(importerPath);
|
|
499
|
+
return appPrefix == null
|
|
500
|
+
? null
|
|
501
|
+
: `${appPrefix}/${specifier.slice("app.".length).replaceAll(".", "/")}`;
|
|
502
|
+
}
|
|
503
|
+
if (specifier.startsWith(".") && importerPath.endsWith(".py")) {
|
|
504
|
+
return resolvePythonRelativeImportBase(importerPath, specifier);
|
|
505
|
+
}
|
|
506
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
507
|
+
return normalizeSlash(path.normalize(path.join(path.dirname(importerPath), specifier)));
|
|
508
|
+
}
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
function importResolutionCandidates(base) {
|
|
512
|
+
if (/\.(?:[cm]?[jt]sx?|json)$/.test(base)) {
|
|
513
|
+
return [base];
|
|
514
|
+
}
|
|
515
|
+
return [
|
|
516
|
+
`${base}.ts`,
|
|
517
|
+
`${base}.tsx`,
|
|
518
|
+
`${base}.js`,
|
|
519
|
+
`${base}.jsx`,
|
|
520
|
+
`${base}.py`,
|
|
521
|
+
`${base}.json`,
|
|
522
|
+
`${base}/index.ts`,
|
|
523
|
+
`${base}/index.tsx`,
|
|
524
|
+
`${base}/index.js`,
|
|
525
|
+
`${base}/index.jsx`,
|
|
526
|
+
`${base}/__init__.py`,
|
|
527
|
+
];
|
|
528
|
+
}
|
|
529
|
+
// Adjacency radius (in directory hops) within which an imported file is treated
|
|
530
|
+
// as a useful local dependency regardless of stem/kind heuristics.
|
|
531
|
+
const LOCAL_DEPENDENCY_MAX_DIRECTORY_DISTANCE = 6;
|
|
532
|
+
function isUsefulLocalDependency(importerPath, dependencyPath) {
|
|
533
|
+
if (importerPath === dependencyPath) {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
if (directoryDistance(importerPath, dependencyPath) <=
|
|
537
|
+
LOCAL_DEPENDENCY_MAX_DIRECTORY_DISTANCE) {
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
if (normalizedPathStem(importerPath) !== normalizedPathStem(dependencyPath)) {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
return genericPathKind(importerPath) !== genericPathKind(dependencyPath);
|
|
544
|
+
}
|
|
545
|
+
function sourceRootPrefix(importerPath) {
|
|
546
|
+
const srcIndex = importerPath.indexOf("/src/");
|
|
547
|
+
if (srcIndex < 0) {
|
|
548
|
+
return importerPath.startsWith("src/") ? "src" : null;
|
|
549
|
+
}
|
|
550
|
+
return importerPath.slice(0, srcIndex + "/src".length);
|
|
551
|
+
}
|
|
552
|
+
function projectPackagePrefix(candidatePath) {
|
|
553
|
+
const srcIndex = candidatePath.indexOf("/src/");
|
|
554
|
+
if (srcIndex < 0) {
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
return candidatePath.slice(0, srcIndex);
|
|
558
|
+
}
|
|
559
|
+
function pythonAppRootPrefix(importerPath) {
|
|
560
|
+
const appIndex = importerPath.indexOf("/app/");
|
|
561
|
+
if (appIndex >= 0) {
|
|
562
|
+
return importerPath.slice(0, appIndex + "/app".length);
|
|
563
|
+
}
|
|
564
|
+
return importerPath.startsWith("app/") ? "app" : null;
|
|
565
|
+
}
|
|
566
|
+
function resolvePythonRelativeImportBase(importerPath, specifier) {
|
|
567
|
+
const match = /^(\.+)(.*)$/.exec(specifier);
|
|
568
|
+
if (match == null) {
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
const dots = match[1] ?? "";
|
|
572
|
+
const moduleTail = match[2] ?? "";
|
|
573
|
+
let baseDir = path.dirname(importerPath);
|
|
574
|
+
for (let index = 1; index < dots.length; index++) {
|
|
575
|
+
baseDir = path.dirname(baseDir);
|
|
576
|
+
}
|
|
577
|
+
const suffix = moduleTail.length === 0 ? "" : moduleTail.replaceAll(".", "/");
|
|
578
|
+
return normalizeSlash(path.normalize(suffix.length === 0 ? baseDir : path.join(baseDir, suffix)));
|
|
579
|
+
}
|
|
580
|
+
function normalizeSlash(value) {
|
|
581
|
+
return value.replaceAll(path.sep, "/");
|
|
582
|
+
}
|
|
583
|
+
async function processExactFocusFiles(input) {
|
|
584
|
+
const focusFiles = [...input.focusPaths]
|
|
585
|
+
.filter((focus) => focus.root_index === input.root.root_index)
|
|
586
|
+
.filter((focus) => focus.kind === "file")
|
|
587
|
+
.sort(compareFocusPathsForDiscovery);
|
|
588
|
+
for (const focus of focusFiles) {
|
|
589
|
+
assertNotAborted(input.signal);
|
|
590
|
+
await processFile({
|
|
591
|
+
root: input.root,
|
|
592
|
+
absolutePath: focus.absolute_path,
|
|
593
|
+
relativePath: focus.root_relative_path,
|
|
594
|
+
matcher: input.matcher,
|
|
595
|
+
focusPaths: input.focusPaths,
|
|
596
|
+
allowSecrets: input.allowSecrets,
|
|
597
|
+
budget: input.budget,
|
|
598
|
+
internalCaps: input.internalCaps,
|
|
599
|
+
focusScanExempt: true,
|
|
600
|
+
...(input.signal == null ? {} : { signal: input.signal }),
|
|
601
|
+
fs: input.fs,
|
|
602
|
+
state: input.state,
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
// Pre-walk explicitly-focused directories before global traversal so a focus
|
|
607
|
+
// directory late in sort order cannot be starved by max_scan_files exhaustion
|
|
608
|
+
// from unrelated trees. Files processed here are focus-scan-exempt (they never
|
|
609
|
+
// erode traversal headroom) but remain bounded by the discovery byte and
|
|
610
|
+
// candidate caps; glob/secret/binary semantics are unchanged because the walk
|
|
611
|
+
// reuses traverseDirectory/processFile verbatim.
|
|
612
|
+
async function processFocusDirectories(input) {
|
|
613
|
+
const focusDirectories = [...input.focusPaths]
|
|
614
|
+
.filter((focus) => focus.root_index === input.root.root_index)
|
|
615
|
+
.filter((focus) => focus.kind === "directory")
|
|
616
|
+
.sort(compareFocusPathsForDiscovery);
|
|
617
|
+
for (const focus of focusDirectories) {
|
|
618
|
+
assertNotAborted(input.signal);
|
|
619
|
+
// A root-level focus gains nothing from a pre-walk (global traversal
|
|
620
|
+
// starts there immediately), and pre-walking it would exempt the entire
|
|
621
|
+
// root from max_scan_files.
|
|
622
|
+
if (focus.root_relative_path === "") {
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
// Leave include-glob-pruned focus directories to global traversal, which
|
|
626
|
+
// emits the existing requested-evidence omission for them.
|
|
627
|
+
if (!input.matcher.canContainIncludedPath(focus.root_relative_path)) {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
await traverseDirectory({
|
|
631
|
+
root: input.root,
|
|
632
|
+
absoluteDir: focus.absolute_path,
|
|
633
|
+
relativeDir: focus.root_relative_path,
|
|
634
|
+
matcher: input.matcher,
|
|
635
|
+
focusPaths: input.focusPaths,
|
|
636
|
+
allowSecrets: input.allowSecrets,
|
|
637
|
+
budget: input.budget,
|
|
638
|
+
internalCaps: input.internalCaps,
|
|
639
|
+
excludedRealRoots: input.excludedRealRoots,
|
|
640
|
+
focusScanExempt: true,
|
|
641
|
+
...(input.signal == null ? {} : { signal: input.signal }),
|
|
642
|
+
fs: input.fs,
|
|
643
|
+
state: input.state,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
export function buildCandidateSearchRecords(input) {
|
|
648
|
+
const maxPreviewBytes = input.maxPreviewBytes ??
|
|
649
|
+
CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS.max_search_preview_bytes;
|
|
650
|
+
const maxCandidateBytes = input.maxCandidateBytes ??
|
|
651
|
+
CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS.max_candidate_bytes;
|
|
652
|
+
const records = [];
|
|
653
|
+
let previewBytes = 0;
|
|
654
|
+
let candidateBytes = 0;
|
|
655
|
+
let capHit = false;
|
|
656
|
+
const callerTermCount = Math.max(0, Math.min(input.callerTermCount ?? 0, input.searchTerms.length));
|
|
657
|
+
const searchTerms = input.searchTerms
|
|
658
|
+
.map((term, index) => ({ term, index }))
|
|
659
|
+
.filter(({ term }) => term.trim().length > 0);
|
|
660
|
+
const callerTerms = searchTerms.filter(({ index }) => index < callerTermCount);
|
|
661
|
+
const derivedTerms = searchTerms.filter(({ index }) => index >= callerTermCount);
|
|
662
|
+
// Candidates whose content was examined for at least one term before a hard
|
|
663
|
+
// cap ended the walk. Key set is bounded by the candidate pool; only counts
|
|
664
|
+
// are surfaced.
|
|
665
|
+
const searchedCandidateKeys = new Set();
|
|
666
|
+
const callerTermCoverage = callerTerms.map(({ term, index }) => ({
|
|
667
|
+
term,
|
|
668
|
+
search_term_index: index,
|
|
669
|
+
hit_count: 0,
|
|
670
|
+
scan_complete: false,
|
|
671
|
+
}));
|
|
672
|
+
let callerRecordCount = 0;
|
|
673
|
+
const finalize = () => ({
|
|
674
|
+
records,
|
|
675
|
+
capHit,
|
|
676
|
+
candidateBytes,
|
|
677
|
+
coverage: {
|
|
678
|
+
eligible_candidate_count: input.candidates.length,
|
|
679
|
+
searched_candidate_count: searchedCandidateKeys.size,
|
|
680
|
+
unsearched_candidate_count: input.candidates.length - searchedCandidateKeys.size,
|
|
681
|
+
caller_term_count: callerTerms.length,
|
|
682
|
+
derived_term_count: derivedTerms.length,
|
|
683
|
+
caller_record_count: callerRecordCount,
|
|
684
|
+
derived_record_count: records.length - callerRecordCount,
|
|
685
|
+
caller_coverage_complete: callerTermCoverage.every((coverage) => coverage.scan_complete),
|
|
686
|
+
caller_terms: callerTermCoverage,
|
|
687
|
+
},
|
|
688
|
+
});
|
|
689
|
+
if (searchTerms.length === 0) {
|
|
690
|
+
return finalize();
|
|
691
|
+
}
|
|
692
|
+
const focusRank = new Map();
|
|
693
|
+
for (const focus of input.focusPaths ?? []) {
|
|
694
|
+
if (focus.kind !== "file") {
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
const key = candidateKey({
|
|
698
|
+
root_index: focus.root_index,
|
|
699
|
+
path: focus.root_relative_path,
|
|
700
|
+
});
|
|
701
|
+
if (!focusRank.has(key)) {
|
|
702
|
+
focusRank.set(key, focusRank.size);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const candidates = [...input.candidates].sort((left, right) => {
|
|
706
|
+
const leftRank = focusRank.get(candidateKey(left)) ?? Number.MAX_SAFE_INTEGER;
|
|
707
|
+
const rightRank = focusRank.get(candidateKey(right)) ?? Number.MAX_SAFE_INTEGER;
|
|
708
|
+
return (leftRank - rightRank ||
|
|
709
|
+
compareContentCandidates(left, right));
|
|
710
|
+
});
|
|
711
|
+
const candidateLines = [];
|
|
712
|
+
for (const candidate of candidates) {
|
|
713
|
+
assertNotAborted(input.signal);
|
|
714
|
+
candidateLines.push(splitTextLines(candidate.content));
|
|
715
|
+
}
|
|
716
|
+
// Emits one found hit unless a hard cap rejects it. Cap semantics match the
|
|
717
|
+
// pre-phasing scheduler: the record/byte caps are checked after the hit is
|
|
718
|
+
// found, and tripping either one ends the whole search with capHit set.
|
|
719
|
+
const emitSearchHit = (stream, hit) => {
|
|
720
|
+
if (records.length >= input.maxSearchResults) {
|
|
721
|
+
capHit = true;
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
724
|
+
const preview = boundedPreview(hit.line, hit.columnIndex, stream.searchTerm.term.length);
|
|
725
|
+
const previewSize = Buffer.byteLength(preview, "utf8");
|
|
726
|
+
const bounded = previewBytes + previewSize <= maxPreviewBytes ? preview : "";
|
|
727
|
+
const record = {
|
|
728
|
+
root_index: stream.candidate.root_index,
|
|
729
|
+
path: stream.candidate.path,
|
|
730
|
+
line: hit.lineIndex + 1,
|
|
731
|
+
column: hit.columnIndex + 1,
|
|
732
|
+
search_term: stream.searchTerm.term,
|
|
733
|
+
search_term_index: stream.searchTerm.index,
|
|
734
|
+
preview: bounded,
|
|
735
|
+
};
|
|
736
|
+
const recordBytes = Buffer.byteLength(`${JSON.stringify(record)}\n`, "utf8");
|
|
737
|
+
if (candidateBytes + recordBytes > maxCandidateBytes) {
|
|
738
|
+
capHit = true;
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
previewBytes += Buffer.byteLength(bounded, "utf8");
|
|
742
|
+
candidateBytes += recordBytes;
|
|
743
|
+
records.push(record);
|
|
744
|
+
return true;
|
|
745
|
+
};
|
|
746
|
+
if (callerTerms.length > 0) {
|
|
747
|
+
// Phase 1 — exact caller terms get first claim on the record budget. One
|
|
748
|
+
// cursor per caller term walks the complete candidate list (focus rank
|
|
749
|
+
// first, one hit per candidate per pass), and the scheduler round-robins
|
|
750
|
+
// across the term cursors, so neither a common sibling term nor an early
|
|
751
|
+
// alphabetical candidate prefix can starve a rare late-path caller hit.
|
|
752
|
+
const cursors = callerTermCoverage.map((coverage) => ({
|
|
753
|
+
coverage,
|
|
754
|
+
streams: candidates.map((candidate, candidateIndex) => ({
|
|
755
|
+
candidate,
|
|
756
|
+
searchTerm: { term: coverage.term, index: coverage.search_term_index },
|
|
757
|
+
lines: candidateLines[candidateIndex] ?? [],
|
|
758
|
+
lineIndex: 0,
|
|
759
|
+
fromIndex: 0,
|
|
760
|
+
done: false,
|
|
761
|
+
})),
|
|
762
|
+
cursor: 0,
|
|
763
|
+
active: candidates.length,
|
|
764
|
+
exhausted: false,
|
|
765
|
+
}));
|
|
766
|
+
let liveCursors = cursors.length;
|
|
767
|
+
while (liveCursors > 0) {
|
|
768
|
+
for (const cursor of cursors) {
|
|
769
|
+
assertNotAborted(input.signal);
|
|
770
|
+
if (cursor.exhausted) {
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
const next = nextCallerTermHit(cursor, searchedCandidateKeys);
|
|
774
|
+
if (next == null) {
|
|
775
|
+
// Natural exhaustion: this term's cursor examined every candidate.
|
|
776
|
+
cursor.exhausted = true;
|
|
777
|
+
cursor.coverage.scan_complete = true;
|
|
778
|
+
liveCursors--;
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
if (!emitSearchHit(next.stream, next.hit)) {
|
|
782
|
+
return finalize();
|
|
783
|
+
}
|
|
784
|
+
cursor.coverage.hit_count++;
|
|
785
|
+
callerRecordCount++;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
if (derivedTerms.length > 0) {
|
|
790
|
+
// Phase 2 — derived/heuristic terms spend whatever budget remains, using
|
|
791
|
+
// the pre-existing candidate-major one-hit-per-stream round-robin.
|
|
792
|
+
const streams = [];
|
|
793
|
+
for (const [candidateIndex, candidate] of candidates.entries()) {
|
|
794
|
+
assertNotAborted(input.signal);
|
|
795
|
+
const lines = candidateLines[candidateIndex] ?? [];
|
|
796
|
+
for (const searchTerm of derivedTerms) {
|
|
797
|
+
streams.push({
|
|
798
|
+
candidate,
|
|
799
|
+
searchTerm,
|
|
800
|
+
lines,
|
|
801
|
+
lineIndex: 0,
|
|
802
|
+
fromIndex: 0,
|
|
803
|
+
done: false,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
let activeStreams = streams.length;
|
|
808
|
+
while (activeStreams > 0) {
|
|
809
|
+
let foundInRound = false;
|
|
810
|
+
for (const stream of streams) {
|
|
811
|
+
assertNotAborted(input.signal);
|
|
812
|
+
if (stream.done) {
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
searchedCandidateKeys.add(candidateKey(stream.candidate));
|
|
816
|
+
const hit = nextSearchHit(stream);
|
|
817
|
+
if (hit == null) {
|
|
818
|
+
stream.done = true;
|
|
819
|
+
activeStreams--;
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
foundInRound = true;
|
|
823
|
+
if (!emitSearchHit(stream, hit)) {
|
|
824
|
+
return finalize();
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (!foundInRound) {
|
|
828
|
+
break;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return finalize();
|
|
833
|
+
}
|
|
834
|
+
// Advances one caller term's cursor to its next hit, cycling the candidate
|
|
835
|
+
// list one hit per candidate per pass. Scanning is free — only emitted records
|
|
836
|
+
// consume the budget — so a rare term reaches late-sorting candidates even
|
|
837
|
+
// when earlier candidates are saturated with other terms' hits. Returns
|
|
838
|
+
// undefined exactly when every candidate stream is exhausted.
|
|
839
|
+
function nextCallerTermHit(cursor, searchedCandidateKeys) {
|
|
840
|
+
const total = cursor.streams.length;
|
|
841
|
+
for (let visited = 0; visited < total && cursor.active > 0; visited++) {
|
|
842
|
+
const stream = cursor.streams[cursor.cursor];
|
|
843
|
+
cursor.cursor = (cursor.cursor + 1) % total;
|
|
844
|
+
if (stream == null || stream.done) {
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
searchedCandidateKeys.add(candidateKey(stream.candidate));
|
|
848
|
+
const hit = nextSearchHit(stream);
|
|
849
|
+
if (hit == null) {
|
|
850
|
+
stream.done = true;
|
|
851
|
+
cursor.active--;
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
return { stream, hit };
|
|
855
|
+
}
|
|
856
|
+
return undefined;
|
|
857
|
+
}
|
|
858
|
+
export function buildCandidateSliceRecords(input) {
|
|
859
|
+
const maxCandidateSlices = input.maxCandidateSlices ??
|
|
860
|
+
CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS.max_candidate_slices;
|
|
861
|
+
const maxCandidateBytes = input.maxCandidateBytes ??
|
|
862
|
+
CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS.max_candidate_bytes;
|
|
863
|
+
const maxFullFocusSliceBytes = input.maxFullFocusSliceBytes ??
|
|
864
|
+
DEFAULT_MAX_FULL_FOCUS_SLICE_BYTES;
|
|
865
|
+
const candidateByIdentity = new Map(input.candidates.map((candidate) => [candidateKey(candidate), candidate]));
|
|
866
|
+
const records = [];
|
|
867
|
+
const omissions = [];
|
|
868
|
+
let bytes = 0;
|
|
869
|
+
let capHit = false;
|
|
870
|
+
const searchSliceBytesByCandidate = new Map();
|
|
871
|
+
const focusedLargeSearchCandidates = new Map();
|
|
872
|
+
const expectedFocusedSearchRanges = new Map();
|
|
873
|
+
const survivedFocusedSearchRanges = new Map();
|
|
874
|
+
const focusSliceSurvivors = new Set();
|
|
875
|
+
const slicedLargeFocusedCandidates = new Set();
|
|
876
|
+
const omittedRequestedCandidates = new Set();
|
|
877
|
+
const pushRequestedCandidateOmission = (candidate, kind, message) => {
|
|
878
|
+
const key = candidateKey(candidate);
|
|
879
|
+
if (omittedRequestedCandidates.has(key)) {
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
omissions.push(requestedEvidenceOmission({
|
|
883
|
+
root_index: candidate.root_index,
|
|
884
|
+
kind,
|
|
885
|
+
message,
|
|
886
|
+
paths: [candidate.path],
|
|
887
|
+
}));
|
|
888
|
+
omittedRequestedCandidates.add(key);
|
|
889
|
+
};
|
|
890
|
+
const pushMissingFocusedSearchOmissions = (keys) => {
|
|
891
|
+
for (const key of keys) {
|
|
892
|
+
if (focusedSearchRangesCovered(expectedFocusedSearchRanges.get(key) ?? [], survivedFocusedSearchRanges.get(key) ?? []) ||
|
|
893
|
+
omittedRequestedCandidates.has(key)) {
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
const candidate = focusedLargeSearchCandidates.get(key);
|
|
897
|
+
if (candidate == null) {
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
pushRequestedCandidateOmission(candidate, "candidate_cap", "focused file had search evidence, but no bounded search slice survived max_bytes_per_file or candidate caps");
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
const pushMissingFocusedRangeOmissions = (keys) => {
|
|
904
|
+
for (const key of keys) {
|
|
905
|
+
if (focusSliceSurvivors.has(key) || omittedRequestedCandidates.has(key)) {
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
const candidate = candidateByIdentity.get(key);
|
|
909
|
+
if (candidate == null) {
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
pushRequestedCandidateOmission(candidate, "candidate_cap", "focused file slice could not be represented within candidate slice or byte caps");
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
const append = (record) => {
|
|
916
|
+
const key = candidateKey(record);
|
|
917
|
+
if (record.source === "search" &&
|
|
918
|
+
input.maxSearchSliceBytesPerCandidate != null) {
|
|
919
|
+
const contentBytes = Buffer.byteLength(record.content, "utf8");
|
|
920
|
+
const candidateBytes = searchSliceBytesByCandidate.get(key) ?? 0;
|
|
921
|
+
if (candidateBytes + contentBytes > input.maxSearchSliceBytesPerCandidate) {
|
|
922
|
+
capHit = true;
|
|
923
|
+
return true;
|
|
924
|
+
}
|
|
925
|
+
searchSliceBytesByCandidate.set(key, candidateBytes + contentBytes);
|
|
926
|
+
}
|
|
927
|
+
const recordBytes = Buffer.byteLength(`${JSON.stringify(record)}\n`, "utf8");
|
|
928
|
+
if (records.length >= maxCandidateSlices || bytes + recordBytes > maxCandidateBytes) {
|
|
929
|
+
capHit = true;
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
records.push(record);
|
|
933
|
+
bytes += recordBytes;
|
|
934
|
+
if (record.source === "search") {
|
|
935
|
+
const survived = survivedFocusedSearchRanges.get(key);
|
|
936
|
+
if (survived != null) {
|
|
937
|
+
survived.push({
|
|
938
|
+
start_line: record.start_line,
|
|
939
|
+
end_line: record.end_line,
|
|
940
|
+
...(record.search_term_index == null
|
|
941
|
+
? {}
|
|
942
|
+
: { search_term_index: record.search_term_index }),
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
focusSliceSurvivors.add(key);
|
|
948
|
+
}
|
|
949
|
+
return true;
|
|
950
|
+
};
|
|
951
|
+
const searchRanges = new Map();
|
|
952
|
+
for (const hit of [...input.searchRecords].sort(compareSearchRecords)) {
|
|
953
|
+
const candidate = candidateByIdentity.get(searchKey(hit));
|
|
954
|
+
if (candidate == null || candidate.line_count === 0) {
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
const ranges = searchRanges.get(candidateKey(candidate)) ?? [];
|
|
958
|
+
ranges.push({
|
|
959
|
+
start_line: Math.max(1, hit.line - SEARCH_WINDOW_LINES_BEFORE),
|
|
960
|
+
end_line: Math.min(candidate.line_count, hit.line + SEARCH_WINDOW_LINES_AFTER),
|
|
961
|
+
search_term_index: hit.search_term_index,
|
|
962
|
+
});
|
|
963
|
+
searchRanges.set(candidateKey(candidate), ranges);
|
|
964
|
+
}
|
|
965
|
+
const focusRanges = new Map();
|
|
966
|
+
for (const focus of [...input.focusPaths].sort(compareFocusPathsForDiscovery)) {
|
|
967
|
+
for (const candidate of input.candidates) {
|
|
968
|
+
if (candidate.root_index === focus.root_index &&
|
|
969
|
+
(candidate.path === focus.root_relative_path ||
|
|
970
|
+
(focus.kind === "directory" &&
|
|
971
|
+
isRootRelativeDescendant(candidate.path, focus.root_relative_path)))) {
|
|
972
|
+
const key = candidateKey(candidate);
|
|
973
|
+
if (candidate.byte_length > maxFullFocusSliceBytes) {
|
|
974
|
+
if (searchRanges.has(key)) {
|
|
975
|
+
focusedLargeSearchCandidates.set(key, candidate);
|
|
976
|
+
expectedFocusedSearchRanges.set(key, mergeLineRanges(searchRanges.get(key) ?? [], {
|
|
977
|
+
bridgeGapLines: SEARCH_BRIDGE_GAP_LINES,
|
|
978
|
+
maxMergedSpanLines: SEARCH_MAX_MERGED_SPAN_LINES,
|
|
979
|
+
}));
|
|
980
|
+
survivedFocusedSearchRanges.set(key, []);
|
|
981
|
+
}
|
|
982
|
+
else if (!slicedLargeFocusedCandidates.has(key)) {
|
|
983
|
+
// Never silently drop an explicitly-focused file. With no per-file
|
|
984
|
+
// search evidence, include a bounded head slice up to the focus
|
|
985
|
+
// byte cap and record a material, non-silent partial-focus signal
|
|
986
|
+
// instead of a too_large omission.
|
|
987
|
+
if (firstLineExceedsCap(candidate.content, maxFullFocusSliceBytes)) {
|
|
988
|
+
// The first logical line alone exceeds the cap (e.g. a minified
|
|
989
|
+
// single-line file). The line-range model cannot express a partial
|
|
990
|
+
// line, so a head slice would blow the byte bound. Omit the file
|
|
991
|
+
// with an explicit material reason rather than exceeding the bound.
|
|
992
|
+
omissions.push(focusOverCapNotice(candidate, maxFullFocusSliceBytes));
|
|
993
|
+
slicedLargeFocusedCandidates.add(key);
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
const headEndLine = headSliceEndLine(candidate.content, maxFullFocusSliceBytes);
|
|
997
|
+
const ranges = focusRanges.get(key) ?? [];
|
|
998
|
+
ranges.push({ start_line: 1, end_line: headEndLine });
|
|
999
|
+
focusRanges.set(key, ranges);
|
|
1000
|
+
omissions.push(focusSlicedNotice(candidate, headEndLine));
|
|
1001
|
+
slicedLargeFocusedCandidates.add(key);
|
|
1002
|
+
}
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
const ranges = focusRanges.get(candidateKey(candidate)) ?? [];
|
|
1006
|
+
ranges.push({ start_line: 1, end_line: Math.max(1, candidate.line_count) });
|
|
1007
|
+
focusRanges.set(candidateKey(candidate), ranges);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
const dependencyRanges = new Map();
|
|
1012
|
+
for (const candidate of input.candidates) {
|
|
1013
|
+
if (candidate.selection_hint == null || candidate.line_count === 0) {
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
if (candidate.byte_length >
|
|
1017
|
+
(input.maxSearchSliceBytesPerCandidate ?? Number.MAX_SAFE_INTEGER)) {
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
dependencyRanges.set(candidateKey(candidate), [
|
|
1021
|
+
{ start_line: 1, end_line: Math.max(1, candidate.line_count) },
|
|
1022
|
+
]);
|
|
1023
|
+
}
|
|
1024
|
+
// Focus slices are appended FIRST so max_candidate_slices/max_candidate_bytes
|
|
1025
|
+
// pressure prunes search and dependency material before explicitly-focused
|
|
1026
|
+
// ranges — never the other way around.
|
|
1027
|
+
for (const [key, ranges] of [...focusRanges.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
1028
|
+
assertNotAborted(input.signal);
|
|
1029
|
+
const candidate = candidateByIdentity.get(key);
|
|
1030
|
+
if (candidate == null) {
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
for (const range of mergeLineRanges(ranges)) {
|
|
1034
|
+
if (!append({
|
|
1035
|
+
root_index: candidate.root_index,
|
|
1036
|
+
path: candidate.path,
|
|
1037
|
+
start_line: range.start_line,
|
|
1038
|
+
end_line: range.end_line,
|
|
1039
|
+
source: "focus",
|
|
1040
|
+
content: sliceContent(candidate.content, range.start_line, range.end_line),
|
|
1041
|
+
})) {
|
|
1042
|
+
pushMissingFocusedRangeOmissions(focusRanges.keys());
|
|
1043
|
+
pushMissingFocusedSearchOmissions(focusedLargeSearchCandidates.keys());
|
|
1044
|
+
return { records, capHit, omissions };
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
for (const [key, ranges] of [...searchRanges.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
1049
|
+
assertNotAborted(input.signal);
|
|
1050
|
+
const candidate = candidateByIdentity.get(key);
|
|
1051
|
+
if (candidate == null || candidate.line_count === 0) {
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
for (const range of mergeLineRanges(ranges, {
|
|
1055
|
+
bridgeGapLines: SEARCH_BRIDGE_GAP_LINES,
|
|
1056
|
+
maxMergedSpanLines: SEARCH_MAX_MERGED_SPAN_LINES,
|
|
1057
|
+
})) {
|
|
1058
|
+
if (!append({
|
|
1059
|
+
root_index: candidate.root_index,
|
|
1060
|
+
path: candidate.path,
|
|
1061
|
+
start_line: range.start_line,
|
|
1062
|
+
end_line: range.end_line,
|
|
1063
|
+
source: "search",
|
|
1064
|
+
content: sliceContent(candidate.content, range.start_line, range.end_line),
|
|
1065
|
+
...(range.search_term_index == null
|
|
1066
|
+
? {}
|
|
1067
|
+
: { search_term_index: range.search_term_index }),
|
|
1068
|
+
})) {
|
|
1069
|
+
pushMissingFocusedSearchOmissions(focusedLargeSearchCandidates.keys());
|
|
1070
|
+
pushMissingFocusedRangeOmissions(focusRanges.keys());
|
|
1071
|
+
return { records, capHit, omissions };
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
pushMissingFocusedSearchOmissions(focusedLargeSearchCandidates.keys());
|
|
1076
|
+
for (const [key, ranges] of [...dependencyRanges.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
1077
|
+
if (searchRanges.has(key) || focusRanges.has(key)) {
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
assertNotAborted(input.signal);
|
|
1081
|
+
const candidate = candidateByIdentity.get(key);
|
|
1082
|
+
if (candidate == null) {
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
for (const range of mergeLineRanges(ranges)) {
|
|
1086
|
+
if (!append({
|
|
1087
|
+
root_index: candidate.root_index,
|
|
1088
|
+
path: candidate.path,
|
|
1089
|
+
start_line: range.start_line,
|
|
1090
|
+
end_line: range.end_line,
|
|
1091
|
+
source: "search",
|
|
1092
|
+
content: sliceContent(candidate.content, range.start_line, range.end_line),
|
|
1093
|
+
search_term_index: Number.MAX_SAFE_INTEGER - 1,
|
|
1094
|
+
})) {
|
|
1095
|
+
pushMissingFocusedRangeOmissions(focusRanges.keys());
|
|
1096
|
+
return { records, capHit, omissions };
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
return { records, capHit, omissions };
|
|
1101
|
+
}
|
|
1102
|
+
export function splitTextLines(text) {
|
|
1103
|
+
if (text.length === 0) {
|
|
1104
|
+
return [];
|
|
1105
|
+
}
|
|
1106
|
+
const withoutFinalNewline = text.endsWith("\n")
|
|
1107
|
+
? text.slice(0, text.endsWith("\r\n") ? -2 : -1)
|
|
1108
|
+
: text;
|
|
1109
|
+
if (withoutFinalNewline.length === 0) {
|
|
1110
|
+
return [""];
|
|
1111
|
+
}
|
|
1112
|
+
return withoutFinalNewline.split(/\r\n|\n/);
|
|
1113
|
+
}
|
|
1114
|
+
function nextSearchHit(stream) {
|
|
1115
|
+
while (stream.lineIndex < stream.lines.length) {
|
|
1116
|
+
const line = stream.lines[stream.lineIndex] ?? "";
|
|
1117
|
+
const columnIndex = line.indexOf(stream.searchTerm.term, stream.fromIndex);
|
|
1118
|
+
if (columnIndex >= 0) {
|
|
1119
|
+
stream.fromIndex = columnIndex + stream.searchTerm.term.length;
|
|
1120
|
+
return { lineIndex: stream.lineIndex, columnIndex, line };
|
|
1121
|
+
}
|
|
1122
|
+
stream.lineIndex++;
|
|
1123
|
+
stream.fromIndex = 0;
|
|
1124
|
+
}
|
|
1125
|
+
return undefined;
|
|
1126
|
+
}
|
|
1127
|
+
export function mergeLineRanges(ranges, options = {}) {
|
|
1128
|
+
const bridgeGapLines = options.bridgeGapLines ?? 0;
|
|
1129
|
+
const maxMergedSpanLines = options.maxMergedSpanLines ?? Number.MAX_SAFE_INTEGER;
|
|
1130
|
+
const sorted = [...ranges].sort((left, right) => left.start_line - right.start_line ||
|
|
1131
|
+
left.end_line - right.end_line ||
|
|
1132
|
+
(left.search_term_index ?? Number.MAX_SAFE_INTEGER) -
|
|
1133
|
+
(right.search_term_index ?? Number.MAX_SAFE_INTEGER));
|
|
1134
|
+
const merged = [];
|
|
1135
|
+
for (const range of sorted) {
|
|
1136
|
+
const last = merged.at(-1);
|
|
1137
|
+
if (last != null &&
|
|
1138
|
+
range.start_line <= last.end_line + bridgeGapLines + 1 &&
|
|
1139
|
+
Math.max(last.end_line, range.end_line) - last.start_line + 1 <=
|
|
1140
|
+
maxMergedSpanLines) {
|
|
1141
|
+
last.end_line = Math.max(last.end_line, range.end_line);
|
|
1142
|
+
last.search_term_index = Math.min(last.search_term_index ?? Number.MAX_SAFE_INTEGER, range.search_term_index ?? Number.MAX_SAFE_INTEGER);
|
|
1143
|
+
if (last.search_term_index === Number.MAX_SAFE_INTEGER) {
|
|
1144
|
+
delete last.search_term_index;
|
|
1145
|
+
}
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
merged.push({ ...range });
|
|
1149
|
+
}
|
|
1150
|
+
return merged;
|
|
1151
|
+
}
|
|
1152
|
+
// Largest 1-based line count whose cumulative UTF-8 bytes (lines joined by
|
|
1153
|
+
// "\n", matching sliceContent) stay within capBytes. Always returns >= 1 so an
|
|
1154
|
+
// explicitly-focused file is never reduced to nothing.
|
|
1155
|
+
// CALLER COUPLING: line 1 is admitted unconditionally (the `count > 0 &&` guard
|
|
1156
|
+
// skips the cap check for the first line), so callers MUST pre-screen with
|
|
1157
|
+
// firstLineExceedsCap — otherwise an over-cap first line silently breaks the
|
|
1158
|
+
// byte bound.
|
|
1159
|
+
function headSliceEndLine(content, capBytes) {
|
|
1160
|
+
const lines = splitTextLines(content);
|
|
1161
|
+
let bytes = 0;
|
|
1162
|
+
let count = 0;
|
|
1163
|
+
for (const line of lines) {
|
|
1164
|
+
const next = bytes + (count === 0 ? 0 : 1) + Buffer.byteLength(line, "utf8");
|
|
1165
|
+
if (count > 0 && next > capBytes) {
|
|
1166
|
+
break;
|
|
1167
|
+
}
|
|
1168
|
+
bytes = next;
|
|
1169
|
+
count++;
|
|
1170
|
+
}
|
|
1171
|
+
return Math.max(1, count);
|
|
1172
|
+
}
|
|
1173
|
+
// True when even the first logical line's own UTF-8 byte length exceeds capBytes,
|
|
1174
|
+
// i.e. no head slice (which always covers at least line 1) can fit within the cap.
|
|
1175
|
+
function firstLineExceedsCap(content, capBytes) {
|
|
1176
|
+
const lines = splitTextLines(content);
|
|
1177
|
+
const first = lines[0];
|
|
1178
|
+
if (first == null) {
|
|
1179
|
+
return false;
|
|
1180
|
+
}
|
|
1181
|
+
return Buffer.byteLength(first, "utf8") > capBytes;
|
|
1182
|
+
}
|
|
1183
|
+
function focusOverCapNotice(candidate, capBytes) {
|
|
1184
|
+
return {
|
|
1185
|
+
root_index: candidate.root_index,
|
|
1186
|
+
reason: CONTEXT_BUILDER_FOCUS_OVER_CAP_REASON,
|
|
1187
|
+
message: "focused file's first line alone exceeded the bounded focus-slice cap " +
|
|
1188
|
+
`(${capBytes} bytes); omitted rather than exceeding the byte bound`,
|
|
1189
|
+
paths: [candidate.path],
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
function focusSlicedNotice(candidate, headEndLine) {
|
|
1193
|
+
return {
|
|
1194
|
+
root_index: candidate.root_index,
|
|
1195
|
+
reason: CONTEXT_BUILDER_FOCUS_SLICED_REASON,
|
|
1196
|
+
message: "focused file exceeded bounded full-file focus size and had no per-file " +
|
|
1197
|
+
`search evidence; included a bounded head slice (lines 1-${headEndLine}) ` +
|
|
1198
|
+
"instead of dropping it",
|
|
1199
|
+
paths: [candidate.path],
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
function focusedSearchRangesCovered(expected, survived) {
|
|
1203
|
+
return expected.every((range) => rangeCoveredByRanges(range, survived));
|
|
1204
|
+
}
|
|
1205
|
+
function rangeCoveredByRanges(target, ranges) {
|
|
1206
|
+
let nextLine = target.start_line;
|
|
1207
|
+
for (const range of mergeLineRanges(ranges)) {
|
|
1208
|
+
if (range.end_line < nextLine) {
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
if (range.start_line > nextLine) {
|
|
1212
|
+
return false;
|
|
1213
|
+
}
|
|
1214
|
+
nextLine = Math.max(nextLine, range.end_line + 1);
|
|
1215
|
+
if (nextLine > target.end_line) {
|
|
1216
|
+
return true;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return false;
|
|
1220
|
+
}
|
|
1221
|
+
async function traverseDirectory(input) {
|
|
1222
|
+
assertNotAborted(input.signal);
|
|
1223
|
+
if (input.state.stopped) {
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
const directoryKey = candidateKey({
|
|
1227
|
+
root_index: input.root.root_index,
|
|
1228
|
+
path: input.relativeDir,
|
|
1229
|
+
});
|
|
1230
|
+
if (input.state.traversedDirectoryKeys.has(directoryKey)) {
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
input.state.traversedDirectoryKeys.add(directoryKey);
|
|
1234
|
+
if (input.relativeDir !== "" &&
|
|
1235
|
+
(input.matcher.excludes(input.relativeDir) ||
|
|
1236
|
+
input.matcher.excludes(`${input.relativeDir}/__dir__`))) {
|
|
1237
|
+
input.state.omissions.push({
|
|
1238
|
+
root_index: input.root.root_index,
|
|
1239
|
+
reason: "exclude_glob",
|
|
1240
|
+
message: "Directory omitted by exclude globs",
|
|
1241
|
+
paths: [input.relativeDir],
|
|
1242
|
+
});
|
|
1243
|
+
pushRequestedFocusOmission({
|
|
1244
|
+
root_index: input.root.root_index,
|
|
1245
|
+
relativePath: input.relativeDir,
|
|
1246
|
+
focusPaths: input.focusPaths,
|
|
1247
|
+
state: input.state,
|
|
1248
|
+
kind: "unavailable",
|
|
1249
|
+
message: "focused path was omitted by exclude globs",
|
|
1250
|
+
});
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
const initialVerification = await verifyDirectoryStillExpected(input);
|
|
1254
|
+
if (initialVerification.kind === "omitted") {
|
|
1255
|
+
pushDirectoryOmission(input, initialVerification.omission);
|
|
1256
|
+
pushRequestedFocusOmission({
|
|
1257
|
+
root_index: input.root.root_index,
|
|
1258
|
+
relativePath: input.relativeDir,
|
|
1259
|
+
focusPaths: input.focusPaths,
|
|
1260
|
+
state: input.state,
|
|
1261
|
+
kind: "unavailable",
|
|
1262
|
+
message: sanitizedFocusedOmissionMessage(initialVerification.omission.reason),
|
|
1263
|
+
});
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
if (isExcludedRealRoot(initialVerification.realPath, input.excludedRealRoots)) {
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
let entries;
|
|
1270
|
+
try {
|
|
1271
|
+
entries = await input.fs.readdir(input.absoluteDir, {
|
|
1272
|
+
withFileTypes: true,
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
catch (error) {
|
|
1276
|
+
if (isExpectedDescendantFilesystemError(error)) {
|
|
1277
|
+
pushDirectoryOmission(input, filesystemOmission("Directory omitted after filesystem error", error));
|
|
1278
|
+
pushRequestedFocusOmission({
|
|
1279
|
+
root_index: input.root.root_index,
|
|
1280
|
+
relativePath: input.relativeDir,
|
|
1281
|
+
focusPaths: input.focusPaths,
|
|
1282
|
+
state: input.state,
|
|
1283
|
+
kind: "unavailable",
|
|
1284
|
+
message: sanitizedFocusedOmissionMessage("filesystem_error"),
|
|
1285
|
+
});
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
throw error;
|
|
1289
|
+
}
|
|
1290
|
+
assertNotAborted(input.signal);
|
|
1291
|
+
const finalVerification = await verifyDirectoryStillExpected(input);
|
|
1292
|
+
if (finalVerification.kind === "omitted") {
|
|
1293
|
+
pushDirectoryOmission(input, finalVerification.omission);
|
|
1294
|
+
pushRequestedFocusOmission({
|
|
1295
|
+
root_index: input.root.root_index,
|
|
1296
|
+
relativePath: input.relativeDir,
|
|
1297
|
+
focusPaths: input.focusPaths,
|
|
1298
|
+
state: input.state,
|
|
1299
|
+
kind: "unavailable",
|
|
1300
|
+
message: sanitizedFocusedOmissionMessage(finalVerification.omission.reason),
|
|
1301
|
+
});
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
if (isExcludedRealRoot(finalVerification.realPath, input.excludedRealRoots)) {
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
1308
|
+
for (const entry of entries) {
|
|
1309
|
+
assertNotAborted(input.signal);
|
|
1310
|
+
if (input.state.stopped) {
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
const relativePath = normalizeContextBuilderRootRelativePath(input.relativeDir === "" ? entry.name : `${input.relativeDir}/${entry.name}`);
|
|
1314
|
+
const absolutePath = path.join(input.absoluteDir, entry.name);
|
|
1315
|
+
if (entry.isSymbolicLink()) {
|
|
1316
|
+
input.state.omissions.push({
|
|
1317
|
+
root_index: input.root.root_index,
|
|
1318
|
+
reason: "symlink_omitted",
|
|
1319
|
+
message: "Symlink omitted during context_builder discovery",
|
|
1320
|
+
paths: [relativePath],
|
|
1321
|
+
});
|
|
1322
|
+
pushRequestedFocusOmission({
|
|
1323
|
+
root_index: input.root.root_index,
|
|
1324
|
+
relativePath,
|
|
1325
|
+
focusPaths: input.focusPaths,
|
|
1326
|
+
state: input.state,
|
|
1327
|
+
kind: "unavailable",
|
|
1328
|
+
message: sanitizedFocusedOmissionMessage("symlink_omitted"),
|
|
1329
|
+
});
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
if (entry.isDirectory()) {
|
|
1333
|
+
if (!input.matcher.canContainIncludedPath(relativePath)) {
|
|
1334
|
+
pushRequestedFocusOmission({
|
|
1335
|
+
root_index: input.root.root_index,
|
|
1336
|
+
relativePath,
|
|
1337
|
+
focusPaths: input.focusPaths,
|
|
1338
|
+
state: input.state,
|
|
1339
|
+
kind: "unavailable",
|
|
1340
|
+
message: sanitizedFocusedOmissionMessage("caller_include_glob"),
|
|
1341
|
+
});
|
|
1342
|
+
continue;
|
|
1343
|
+
}
|
|
1344
|
+
await traverseDirectory({
|
|
1345
|
+
...input,
|
|
1346
|
+
absoluteDir: absolutePath,
|
|
1347
|
+
relativeDir: relativePath,
|
|
1348
|
+
});
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
if (!entry.isFile()) {
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
await processFile({
|
|
1355
|
+
...input,
|
|
1356
|
+
absolutePath,
|
|
1357
|
+
relativePath,
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
function pushDirectoryOmission(input, omission) {
|
|
1362
|
+
input.state.omissions.push({
|
|
1363
|
+
root_index: input.root.root_index,
|
|
1364
|
+
reason: omission.reason,
|
|
1365
|
+
message: omission.message,
|
|
1366
|
+
...(input.relativeDir === "" ? {} : { paths: [input.relativeDir] }),
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
async function processFile(input) {
|
|
1370
|
+
const fileKey = candidateKey({
|
|
1371
|
+
root_index: input.root.root_index,
|
|
1372
|
+
path: input.relativePath,
|
|
1373
|
+
});
|
|
1374
|
+
const alreadyProcessed = input.state.processedFileKeys.has(fileKey);
|
|
1375
|
+
if (alreadyProcessed &&
|
|
1376
|
+
(input.ignoreIncludeGlob !== true ||
|
|
1377
|
+
input.state.contentCandidates.some((candidate) => candidateKey(candidate) === fileKey))) {
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
if (!alreadyProcessed) {
|
|
1381
|
+
input.state.processedFileKeys.add(fileKey);
|
|
1382
|
+
}
|
|
1383
|
+
const omitted = (reason, message, secretFindings) => {
|
|
1384
|
+
input.state.omissions.push({
|
|
1385
|
+
root_index: input.root.root_index,
|
|
1386
|
+
reason,
|
|
1387
|
+
message,
|
|
1388
|
+
paths: [input.relativePath],
|
|
1389
|
+
});
|
|
1390
|
+
pushRequestedFocusOmission({
|
|
1391
|
+
root_index: input.root.root_index,
|
|
1392
|
+
relativePath: input.relativePath,
|
|
1393
|
+
focusPaths: input.focusPaths,
|
|
1394
|
+
state: input.state,
|
|
1395
|
+
kind: requestedOmissionKindForDiscoveryReason(reason),
|
|
1396
|
+
message: sanitizedFocusedOmissionMessage(reason, secretFindings),
|
|
1397
|
+
});
|
|
1398
|
+
};
|
|
1399
|
+
const requestedOmitted = (kind, message) => {
|
|
1400
|
+
pushRequestedFocusOmission({
|
|
1401
|
+
root_index: input.root.root_index,
|
|
1402
|
+
relativePath: input.relativePath,
|
|
1403
|
+
focusPaths: input.focusPaths,
|
|
1404
|
+
state: input.state,
|
|
1405
|
+
kind,
|
|
1406
|
+
message,
|
|
1407
|
+
});
|
|
1408
|
+
};
|
|
1409
|
+
const pushTree = (file) => {
|
|
1410
|
+
input.state.tree.push({
|
|
1411
|
+
root_index: input.root.root_index,
|
|
1412
|
+
path: input.relativePath,
|
|
1413
|
+
size_bytes: file.size_bytes ?? 0,
|
|
1414
|
+
eligible: file.eligible,
|
|
1415
|
+
content_eligible: file.content_eligible,
|
|
1416
|
+
...(file.omission_reason == null
|
|
1417
|
+
? {}
|
|
1418
|
+
: { omission_reason: file.omission_reason }),
|
|
1419
|
+
});
|
|
1420
|
+
};
|
|
1421
|
+
if (!isSamePathOrDescendant(input.absolutePath, input.root.path)) {
|
|
1422
|
+
omitted("path_escape", "File path escaped root during discovery");
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
const normalizedPath = rootRelativePathForAbsolutePath(input.root, input.absolutePath);
|
|
1426
|
+
if (normalizedPath !== input.relativePath) {
|
|
1427
|
+
omitted("path_normalized_mismatch", "File path changed after normalization");
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
if (input.ignoreIncludeGlob !== true && !input.matcher.includes(input.relativePath)) {
|
|
1431
|
+
requestedOmitted("unavailable", sanitizedFocusedOmissionMessage("caller_include_glob"));
|
|
1432
|
+
pushTree({
|
|
1433
|
+
eligible: false,
|
|
1434
|
+
content_eligible: false,
|
|
1435
|
+
omission_reason: "caller_include_glob",
|
|
1436
|
+
});
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
if (input.matcher.excludes(input.relativePath)) {
|
|
1440
|
+
requestedOmitted("unavailable", sanitizedFocusedOmissionMessage("exclude_glob"));
|
|
1441
|
+
pushTree({
|
|
1442
|
+
eligible: false,
|
|
1443
|
+
content_eligible: false,
|
|
1444
|
+
omission_reason: "exclude_glob",
|
|
1445
|
+
});
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
// files_seen stays accurate for observability, but focus-processed files are
|
|
1449
|
+
// subtracted from the cap check so explicit focus never consumes the
|
|
1450
|
+
// max_scan_files headroom reserved for global traversal.
|
|
1451
|
+
if (input.ignoreScanCap !== true &&
|
|
1452
|
+
input.focusScanExempt !== true &&
|
|
1453
|
+
input.state.metrics.files_seen - input.state.focusScanExemptFiles >=
|
|
1454
|
+
input.budget.max_scan_files) {
|
|
1455
|
+
input.state.metrics.scan_cap_hit = true;
|
|
1456
|
+
pushRequestedFocusOmission({
|
|
1457
|
+
root_index: input.root.root_index,
|
|
1458
|
+
relativePath: input.relativePath,
|
|
1459
|
+
focusPaths: input.focusPaths,
|
|
1460
|
+
state: input.state,
|
|
1461
|
+
kind: "unavailable",
|
|
1462
|
+
message: "focused path was not reached before max_scan_files stopped discovery",
|
|
1463
|
+
});
|
|
1464
|
+
input.state.stopped = true;
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
input.state.metrics.files_seen++;
|
|
1468
|
+
if (input.focusScanExempt === true) {
|
|
1469
|
+
input.state.focusScanExemptFiles++;
|
|
1470
|
+
}
|
|
1471
|
+
const pathSecrets = input.allowSecrets ? [] : scanSecretPath(input.relativePath);
|
|
1472
|
+
if (pathSecrets.length > 0) {
|
|
1473
|
+
omitted("secret_path", `Secret-looking path omitted (${pathSecrets[0]?.provenance})`, pathSecrets);
|
|
1474
|
+
pushTree({
|
|
1475
|
+
eligible: false,
|
|
1476
|
+
content_eligible: false,
|
|
1477
|
+
omission_reason: "secret_path",
|
|
1478
|
+
});
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
let stats;
|
|
1482
|
+
try {
|
|
1483
|
+
stats = await input.fs.lstat(input.absolutePath);
|
|
1484
|
+
}
|
|
1485
|
+
catch (error) {
|
|
1486
|
+
if (isExpectedDescendantFilesystemError(error)) {
|
|
1487
|
+
omittedFromError("File omitted after lstat error", error);
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
throw error;
|
|
1491
|
+
}
|
|
1492
|
+
assertNotAborted(input.signal);
|
|
1493
|
+
if (stats.isSymbolicLink()) {
|
|
1494
|
+
omitted("symlink_omitted", "Symlink omitted during context_builder discovery");
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
if (!stats.isFile()) {
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
const fileVerification = await verifyFileStillExpected({
|
|
1501
|
+
root: input.root,
|
|
1502
|
+
absolutePath: input.absolutePath,
|
|
1503
|
+
relativePath: input.relativePath,
|
|
1504
|
+
expectedStats: stats,
|
|
1505
|
+
fs: input.fs,
|
|
1506
|
+
});
|
|
1507
|
+
if (fileVerification.kind === "omitted") {
|
|
1508
|
+
omitted(fileVerification.omission.reason, fileVerification.omission.message);
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
const sizeBytes = saneFileSize(stats.size);
|
|
1512
|
+
if (sizeBytes == null) {
|
|
1513
|
+
omitted("file_changed_during_read", "File size could not be verified safely");
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
input.state.metrics.files_considered++;
|
|
1517
|
+
if (isNoisyFile(input.relativePath, sizeBytes)) {
|
|
1518
|
+
omitted("noisy_file", "Noisy generated file omitted");
|
|
1519
|
+
pushTree({
|
|
1520
|
+
size_bytes: sizeBytes,
|
|
1521
|
+
eligible: false,
|
|
1522
|
+
content_eligible: false,
|
|
1523
|
+
omission_reason: "noisy_file",
|
|
1524
|
+
});
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
if (input.state.metrics.bytes_read + sizeBytes > input.internalCaps.max_discovery_bytes) {
|
|
1528
|
+
input.state.metrics.discovery_byte_cap_hit = true;
|
|
1529
|
+
requestedOmitted("unavailable", "focused file could not be read before the aggregate discovery byte cap");
|
|
1530
|
+
pushTree({
|
|
1531
|
+
size_bytes: sizeBytes,
|
|
1532
|
+
eligible: true,
|
|
1533
|
+
content_eligible: false,
|
|
1534
|
+
omission_reason: "discovery_byte_cap",
|
|
1535
|
+
});
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
let read;
|
|
1539
|
+
try {
|
|
1540
|
+
read = await readBoundedRegularFile({
|
|
1541
|
+
absolutePath: input.absolutePath,
|
|
1542
|
+
relativePath: input.relativePath,
|
|
1543
|
+
root: input.root,
|
|
1544
|
+
maxBytes: Math.max(input.budget.max_bytes_per_file, CONTEXT_BUILDER_PUBLIC_BUDGET_HARD_LIMITS.max_bytes_per_file),
|
|
1545
|
+
expectedStats: stats,
|
|
1546
|
+
fs: input.fs,
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
catch (error) {
|
|
1550
|
+
if (isExpectedDescendantFilesystemError(error)) {
|
|
1551
|
+
const omission = filesystemOmission("File omitted after open/read error", error);
|
|
1552
|
+
omitted(omission.reason, omission.message);
|
|
1553
|
+
pushTree({
|
|
1554
|
+
size_bytes: sizeBytes,
|
|
1555
|
+
eligible: true,
|
|
1556
|
+
content_eligible: false,
|
|
1557
|
+
omission_reason: omission.reason,
|
|
1558
|
+
});
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
throw error;
|
|
1562
|
+
}
|
|
1563
|
+
assertNotAborted(input.signal);
|
|
1564
|
+
if (read.kind === "omitted") {
|
|
1565
|
+
omitted(read.omission.reason, read.omission.message);
|
|
1566
|
+
requestedOmitted(read.omission.reason === "file_too_large" ? "too_large" : "unavailable", read.omission.message);
|
|
1567
|
+
pushTree({
|
|
1568
|
+
size_bytes: read.sizeBytes ?? sizeBytes,
|
|
1569
|
+
eligible: true,
|
|
1570
|
+
content_eligible: false,
|
|
1571
|
+
omission_reason: read.omission.reason,
|
|
1572
|
+
});
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
const contentBuffer = read.buffer;
|
|
1576
|
+
if (isBinaryBuffer(contentBuffer)) {
|
|
1577
|
+
omitted("binary_file", "Binary file omitted from content-bearing artifacts");
|
|
1578
|
+
pushTree({
|
|
1579
|
+
size_bytes: sizeBytes,
|
|
1580
|
+
eligible: true,
|
|
1581
|
+
content_eligible: false,
|
|
1582
|
+
omission_reason: "binary_file",
|
|
1583
|
+
});
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
const rawContent = contentBuffer.toString("utf8");
|
|
1587
|
+
input.state.metrics.bytes_read += contentBuffer.byteLength;
|
|
1588
|
+
const sanitized = input.allowSecrets
|
|
1589
|
+
? { status: "unchanged", content: rawContent, findings: [] }
|
|
1590
|
+
: sanitizeHighConfidenceSecretContent(rawContent);
|
|
1591
|
+
if (sanitized.status === "blocked") {
|
|
1592
|
+
omitted("content_secret", `Content omitted due to high-confidence secret (${summarizeSecretFindings(sanitized.findings)})`, sanitized.findings);
|
|
1593
|
+
pushTree({
|
|
1594
|
+
size_bytes: read.sizeBytes,
|
|
1595
|
+
eligible: true,
|
|
1596
|
+
content_eligible: false,
|
|
1597
|
+
omission_reason: "content_secret",
|
|
1598
|
+
});
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
const content = sanitized.content;
|
|
1602
|
+
const contentBytes = Buffer.byteLength(content, "utf8");
|
|
1603
|
+
if (input.state.contentCandidates.length >= input.internalCaps.max_discovery_candidates) {
|
|
1604
|
+
input.state.metrics.discovery_candidate_cap_hit = true;
|
|
1605
|
+
requestedOmitted("discovery_candidate_cap", "focused file could not be represented because max_discovery_candidates was reached");
|
|
1606
|
+
pushTree({
|
|
1607
|
+
size_bytes: read.sizeBytes,
|
|
1608
|
+
eligible: true,
|
|
1609
|
+
content_eligible: false,
|
|
1610
|
+
omission_reason: "discovery_candidate_cap",
|
|
1611
|
+
});
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
pushTree({
|
|
1615
|
+
size_bytes: read.sizeBytes,
|
|
1616
|
+
eligible: true,
|
|
1617
|
+
content_eligible: true,
|
|
1618
|
+
});
|
|
1619
|
+
input.state.contentCandidates.push({
|
|
1620
|
+
root_index: input.root.root_index,
|
|
1621
|
+
path: input.relativePath,
|
|
1622
|
+
absolute_path: input.absolutePath,
|
|
1623
|
+
content,
|
|
1624
|
+
byte_length: contentBytes,
|
|
1625
|
+
line_count: splitTextLines(content).length,
|
|
1626
|
+
has_final_newline: content.endsWith("\n"),
|
|
1627
|
+
...(input.selectionHint == null ? {} : { selection_hint: input.selectionHint }),
|
|
1628
|
+
});
|
|
1629
|
+
function omittedFromError(message, error) {
|
|
1630
|
+
const omission = filesystemOmission(message, error);
|
|
1631
|
+
omitted(omission.reason, omission.message);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
async function verifyDirectoryStillExpected(input) {
|
|
1635
|
+
let lstatStats;
|
|
1636
|
+
try {
|
|
1637
|
+
lstatStats = await input.fs.lstat(input.absoluteDir);
|
|
1638
|
+
}
|
|
1639
|
+
catch (error) {
|
|
1640
|
+
if (isExpectedDescendantFilesystemError(error)) {
|
|
1641
|
+
return {
|
|
1642
|
+
kind: "omitted",
|
|
1643
|
+
omission: filesystemOmission("Directory omitted after filesystem error", error),
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
throw error;
|
|
1647
|
+
}
|
|
1648
|
+
if (lstatStats.isSymbolicLink()) {
|
|
1649
|
+
return {
|
|
1650
|
+
kind: "omitted",
|
|
1651
|
+
omission: {
|
|
1652
|
+
reason: "symlink_omitted",
|
|
1653
|
+
message: "Symlink omitted during context_builder discovery",
|
|
1654
|
+
},
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
if (!lstatStats.isDirectory()) {
|
|
1658
|
+
return {
|
|
1659
|
+
kind: "omitted",
|
|
1660
|
+
omission: {
|
|
1661
|
+
reason: "file_changed_during_read",
|
|
1662
|
+
message: "Directory changed before it could be read safely",
|
|
1663
|
+
},
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
const target = await verifyPathTarget({
|
|
1667
|
+
root: input.root,
|
|
1668
|
+
absolutePath: input.absoluteDir,
|
|
1669
|
+
relativePath: input.relativeDir,
|
|
1670
|
+
expectedStats: lstatStats,
|
|
1671
|
+
expectedKind: "directory",
|
|
1672
|
+
fs: input.fs,
|
|
1673
|
+
});
|
|
1674
|
+
if (target.kind === "omitted") {
|
|
1675
|
+
return target;
|
|
1676
|
+
}
|
|
1677
|
+
return { kind: "verified", realPath: target.realPath };
|
|
1678
|
+
}
|
|
1679
|
+
async function verifyFileStillExpected(input) {
|
|
1680
|
+
return verifyPathTarget({
|
|
1681
|
+
root: input.root,
|
|
1682
|
+
absolutePath: input.absolutePath,
|
|
1683
|
+
relativePath: input.relativePath,
|
|
1684
|
+
expectedStats: input.expectedStats,
|
|
1685
|
+
expectedKind: "file",
|
|
1686
|
+
fs: input.fs,
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
async function verifyPathTarget(input) {
|
|
1690
|
+
let realPath;
|
|
1691
|
+
try {
|
|
1692
|
+
realPath = await input.fs.realpath(input.absolutePath);
|
|
1693
|
+
}
|
|
1694
|
+
catch (error) {
|
|
1695
|
+
if (isExpectedDescendantFilesystemError(error)) {
|
|
1696
|
+
return {
|
|
1697
|
+
kind: "omitted",
|
|
1698
|
+
omission: filesystemOmission("Path omitted after filesystem error", error),
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
throw error;
|
|
1702
|
+
}
|
|
1703
|
+
if (!isSamePathOrDescendant(realPath, input.root.path)) {
|
|
1704
|
+
return {
|
|
1705
|
+
kind: "omitted",
|
|
1706
|
+
omission: {
|
|
1707
|
+
reason: "path_escape",
|
|
1708
|
+
message: "Path resolved outside root during discovery",
|
|
1709
|
+
},
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
const normalizedPath = rootRelativePathForAbsolutePath(input.root, realPath);
|
|
1713
|
+
if (normalizedPath !== input.relativePath) {
|
|
1714
|
+
return {
|
|
1715
|
+
kind: "omitted",
|
|
1716
|
+
omission: {
|
|
1717
|
+
reason: "path_escape",
|
|
1718
|
+
message: "Path resolved to an unexpected root-relative target",
|
|
1719
|
+
},
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
let targetStats;
|
|
1723
|
+
try {
|
|
1724
|
+
targetStats = await input.fs.stat(realPath);
|
|
1725
|
+
}
|
|
1726
|
+
catch (error) {
|
|
1727
|
+
if (isExpectedDescendantFilesystemError(error)) {
|
|
1728
|
+
return {
|
|
1729
|
+
kind: "omitted",
|
|
1730
|
+
omission: filesystemOmission("Path omitted after filesystem error", error),
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
throw error;
|
|
1734
|
+
}
|
|
1735
|
+
const expectedKindMatches = input.expectedKind === "file"
|
|
1736
|
+
? targetStats.isFile()
|
|
1737
|
+
: targetStats.isDirectory();
|
|
1738
|
+
if (!expectedKindMatches || !sameFileIdentity(input.expectedStats, targetStats)) {
|
|
1739
|
+
return {
|
|
1740
|
+
kind: "omitted",
|
|
1741
|
+
omission: {
|
|
1742
|
+
reason: "file_changed_during_read",
|
|
1743
|
+
message: "Path changed before it could be read safely",
|
|
1744
|
+
},
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
return { kind: "verified", realPath };
|
|
1748
|
+
}
|
|
1749
|
+
function compareTreeFiles(left, right) {
|
|
1750
|
+
return left.root_index - right.root_index || left.path.localeCompare(right.path);
|
|
1751
|
+
}
|
|
1752
|
+
function compareContentCandidates(left, right) {
|
|
1753
|
+
return left.root_index - right.root_index || left.path.localeCompare(right.path);
|
|
1754
|
+
}
|
|
1755
|
+
function compareSearchRecords(left, right) {
|
|
1756
|
+
return (left.root_index - right.root_index ||
|
|
1757
|
+
left.path.localeCompare(right.path) ||
|
|
1758
|
+
left.line - right.line ||
|
|
1759
|
+
left.search_term_index - right.search_term_index ||
|
|
1760
|
+
left.column - right.column);
|
|
1761
|
+
}
|
|
1762
|
+
function compareFocusPathsForDiscovery(left, right) {
|
|
1763
|
+
return (left.root_index - right.root_index ||
|
|
1764
|
+
left.root_relative_path.localeCompare(right.root_relative_path) ||
|
|
1765
|
+
left.input_index - right.input_index);
|
|
1766
|
+
}
|
|
1767
|
+
function auditScanCapFocusedPaths(input) {
|
|
1768
|
+
if (!input.state.metrics.scan_cap_hit) {
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
for (const focus of input.focusPaths) {
|
|
1772
|
+
if (hasSurvivingFocusEvidence(input.state, focus) ||
|
|
1773
|
+
hasRequestedEvidenceOmissionForPath(input.state.omissions, focus.root_index, focus.root_relative_path)) {
|
|
1774
|
+
continue;
|
|
1775
|
+
}
|
|
1776
|
+
pushRequestedFocusOmission({
|
|
1777
|
+
root_index: focus.root_index,
|
|
1778
|
+
relativePath: focus.root_relative_path,
|
|
1779
|
+
focusPaths: input.focusPaths,
|
|
1780
|
+
state: input.state,
|
|
1781
|
+
kind: "unavailable",
|
|
1782
|
+
message: "focused path was not reached before max_scan_files stopped discovery",
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
function hasSurvivingFocusEvidence(state, focus) {
|
|
1787
|
+
return (state.contentCandidates.some((candidate) => focusPathCoversEvidencePath(focus, candidate.root_index, candidate.path)) ||
|
|
1788
|
+
state.tree.some((file) => focusPathCoversEvidencePath(focus, file.root_index, file.path)));
|
|
1789
|
+
}
|
|
1790
|
+
function focusPathCoversEvidencePath(focus, rootIndex, relativePath) {
|
|
1791
|
+
return (focus.root_index === rootIndex &&
|
|
1792
|
+
(relativePath === focus.root_relative_path ||
|
|
1793
|
+
(focus.kind === "directory" &&
|
|
1794
|
+
isRootRelativeDescendant(relativePath, focus.root_relative_path))));
|
|
1795
|
+
}
|
|
1796
|
+
function pushRequestedFocusOmission(input) {
|
|
1797
|
+
const paths = requestedEvidencePathsForFocusedOmission(input.root_index, input.relativePath, input.focusPaths).filter((requestedPath) => {
|
|
1798
|
+
const key = requestedEvidenceOmissionKey(input.root_index, requestedPath);
|
|
1799
|
+
if (input.state.requestedEvidenceOmissionKeys.has(key)) {
|
|
1800
|
+
return false;
|
|
1801
|
+
}
|
|
1802
|
+
if (hasRequestedEvidenceOmissionForPath(input.state.omissions, input.root_index, requestedPath)) {
|
|
1803
|
+
input.state.requestedEvidenceOmissionKeys.add(key);
|
|
1804
|
+
return false;
|
|
1805
|
+
}
|
|
1806
|
+
return true;
|
|
1807
|
+
});
|
|
1808
|
+
if (paths.length === 0) {
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
input.state.omissions.push(requestedEvidenceOmission({
|
|
1812
|
+
root_index: input.root_index,
|
|
1813
|
+
kind: input.kind,
|
|
1814
|
+
message: input.message,
|
|
1815
|
+
paths,
|
|
1816
|
+
}));
|
|
1817
|
+
for (const requestedPath of paths) {
|
|
1818
|
+
input.state.requestedEvidenceOmissionKeys.add(requestedEvidenceOmissionKey(input.root_index, requestedPath));
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
function requestedEvidencePathsForFocusedOmission(rootIndex, relativePath, focusPaths) {
|
|
1822
|
+
const paths = new Set();
|
|
1823
|
+
for (const focus of focusPaths) {
|
|
1824
|
+
if (focus.root_index !== rootIndex) {
|
|
1825
|
+
continue;
|
|
1826
|
+
}
|
|
1827
|
+
if (relativePath === focus.root_relative_path) {
|
|
1828
|
+
paths.add(relativePath);
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
if (focus.kind === "directory" &&
|
|
1832
|
+
isRootRelativeDescendant(relativePath, focus.root_relative_path)) {
|
|
1833
|
+
paths.add(relativePath);
|
|
1834
|
+
continue;
|
|
1835
|
+
}
|
|
1836
|
+
if (isRootRelativeDescendant(focus.root_relative_path, relativePath)) {
|
|
1837
|
+
paths.add(focus.root_relative_path);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
return [...paths].sort();
|
|
1841
|
+
}
|
|
1842
|
+
function hasRequestedEvidenceOmissionForPath(omissions, rootIndex, relativePath) {
|
|
1843
|
+
return omissions.some((omission) => omission.reason === CONTEXT_BUILDER_REQUESTED_EVIDENCE_OMISSION_REASON &&
|
|
1844
|
+
omission.root_index === rootIndex &&
|
|
1845
|
+
(omission.paths ?? []).includes(relativePath));
|
|
1846
|
+
}
|
|
1847
|
+
function requestedEvidenceOmissionKey(rootIndex, relativePath) {
|
|
1848
|
+
return `${rootIndex}\u0000${relativePath}`;
|
|
1849
|
+
}
|
|
1850
|
+
function requestedOmissionKindForDiscoveryReason(reason) {
|
|
1851
|
+
return reason === "file_too_large" ? "too_large" : "unavailable";
|
|
1852
|
+
}
|
|
1853
|
+
function sanitizedFocusedOmissionMessage(reason, secretFindings) {
|
|
1854
|
+
switch (reason) {
|
|
1855
|
+
case "caller_include_glob":
|
|
1856
|
+
return "focused path was outside include globs";
|
|
1857
|
+
case "exclude_glob":
|
|
1858
|
+
return "focused path was omitted by exclude globs";
|
|
1859
|
+
case "secret_path":
|
|
1860
|
+
return withSecretFindingProvenance("focused path was omitted because it matched a secret-path rule", secretFindings);
|
|
1861
|
+
case "symlink_omitted":
|
|
1862
|
+
return "focused path was omitted because symlinks are not followed";
|
|
1863
|
+
case "path_escape":
|
|
1864
|
+
case "path_normalized_mismatch":
|
|
1865
|
+
return "focused path could not be safely resolved within the root";
|
|
1866
|
+
case "filesystem_error":
|
|
1867
|
+
return "focused path could not be read due to an expected filesystem error";
|
|
1868
|
+
case "file_changed_during_read":
|
|
1869
|
+
return "focused path changed before it could be read safely";
|
|
1870
|
+
case "noisy_file":
|
|
1871
|
+
return "focused generated file was omitted as noisy";
|
|
1872
|
+
case "binary_file":
|
|
1873
|
+
return "focused binary file cannot be represented as text";
|
|
1874
|
+
case "content_secret":
|
|
1875
|
+
return withSecretFindingProvenance("focused file content was omitted because secret-like content was detected", secretFindings);
|
|
1876
|
+
case "file_too_large":
|
|
1877
|
+
return "focused file exceeded max_bytes_per_file";
|
|
1878
|
+
default:
|
|
1879
|
+
return "focused path could not be represented as local evidence";
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
const MAX_SECRET_FINDING_PROVENANCE_DETAILS = 3;
|
|
1883
|
+
// Appends detector provenance labels and line numbers (never matched secret
|
|
1884
|
+
// text or values) to a sanitized focused-omission message, e.g.
|
|
1885
|
+
// "... (aws_access_key_id at line 12)".
|
|
1886
|
+
function withSecretFindingProvenance(base, secretFindings) {
|
|
1887
|
+
const details = [];
|
|
1888
|
+
for (const finding of secretFindings ?? []) {
|
|
1889
|
+
const detail = finding.line == null
|
|
1890
|
+
? finding.provenance
|
|
1891
|
+
: `${finding.provenance} at line ${finding.line}`;
|
|
1892
|
+
if (!details.includes(detail)) {
|
|
1893
|
+
details.push(detail);
|
|
1894
|
+
}
|
|
1895
|
+
if (details.length >= MAX_SECRET_FINDING_PROVENANCE_DETAILS) {
|
|
1896
|
+
break;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
return details.length === 0 ? base : `${base} (${details.join(", ")})`;
|
|
1900
|
+
}
|
|
1901
|
+
function candidateKey(candidate) {
|
|
1902
|
+
return `${candidate.root_index}\u0000${candidate.path}`;
|
|
1903
|
+
}
|
|
1904
|
+
function searchKey(search) {
|
|
1905
|
+
return `${search.root_index}\u0000${search.path}`;
|
|
1906
|
+
}
|
|
1907
|
+
function isRootRelativeDescendant(child, parent) {
|
|
1908
|
+
if (parent === "") {
|
|
1909
|
+
return true;
|
|
1910
|
+
}
|
|
1911
|
+
return child === parent || child.startsWith(`${parent}/`);
|
|
1912
|
+
}
|
|
1913
|
+
function isExcludedRealRoot(realPath, excludedRealRoots) {
|
|
1914
|
+
return excludedRealRoots.some((root) => isSamePathOrDescendant(realPath, root));
|
|
1915
|
+
}
|
|
1916
|
+
function sliceContent(text, startLine, endLine) {
|
|
1917
|
+
return splitTextLines(text)
|
|
1918
|
+
.slice(startLine - 1, endLine)
|
|
1919
|
+
.join("\n");
|
|
1920
|
+
}
|
|
1921
|
+
function boundedPreview(line, columnIndex, termLength) {
|
|
1922
|
+
const radius = 80;
|
|
1923
|
+
const start = Math.max(0, columnIndex - radius);
|
|
1924
|
+
const end = Math.min(line.length, columnIndex + termLength + radius);
|
|
1925
|
+
const prefix = start > 0 ? "..." : "";
|
|
1926
|
+
const suffix = end < line.length ? "..." : "";
|
|
1927
|
+
return `${prefix}${line.slice(start, end)}${suffix}`;
|
|
1928
|
+
}
|
|
1929
|
+
function isBinaryBuffer(buffer) {
|
|
1930
|
+
if (buffer.length === 0) {
|
|
1931
|
+
return false;
|
|
1932
|
+
}
|
|
1933
|
+
if (buffer.includes(0)) {
|
|
1934
|
+
return true;
|
|
1935
|
+
}
|
|
1936
|
+
let suspicious = 0;
|
|
1937
|
+
const sampleLength = Math.min(buffer.length, 8_192);
|
|
1938
|
+
for (let index = 0; index < sampleLength; index++) {
|
|
1939
|
+
const byte = buffer[index] ?? 0;
|
|
1940
|
+
const allowedControl = byte === 0x09 || byte === 0x0a || byte === 0x0d || byte === 0x1b;
|
|
1941
|
+
if (byte < 0x20 && !allowedControl) {
|
|
1942
|
+
suspicious++;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
return suspicious / sampleLength > 0.05;
|
|
1946
|
+
}
|
|
1947
|
+
function isNoisyFile(rootRelativePath, sizeBytes) {
|
|
1948
|
+
const basename = path.posix.basename(rootRelativePath);
|
|
1949
|
+
return sizeBytes > 20_000 && (basename.endsWith(".min.js") || basename.endsWith(".map"));
|
|
1950
|
+
}
|
|
1951
|
+
function summarizeSecretFindings(findings) {
|
|
1952
|
+
return findings.map((finding) => finding.provenance).join(", ");
|
|
1953
|
+
}
|
|
1954
|
+
async function readBoundedRegularFile(input) {
|
|
1955
|
+
const initialVerification = await verifyFileStillExpected({
|
|
1956
|
+
root: input.root,
|
|
1957
|
+
absolutePath: input.absolutePath,
|
|
1958
|
+
relativePath: input.relativePath,
|
|
1959
|
+
expectedStats: input.expectedStats,
|
|
1960
|
+
fs: input.fs,
|
|
1961
|
+
});
|
|
1962
|
+
if (initialVerification.kind === "omitted") {
|
|
1963
|
+
return {
|
|
1964
|
+
kind: "omitted",
|
|
1965
|
+
omission: initialVerification.omission,
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
let handle;
|
|
1969
|
+
try {
|
|
1970
|
+
handle = await input.fs.open(input.absolutePath, noFollowReadFlags());
|
|
1971
|
+
}
|
|
1972
|
+
catch (error) {
|
|
1973
|
+
if (filesystemErrorCode(error) === "ELOOP") {
|
|
1974
|
+
return {
|
|
1975
|
+
kind: "omitted",
|
|
1976
|
+
omission: {
|
|
1977
|
+
reason: "symlink_omitted",
|
|
1978
|
+
message: "Symlink omitted during context_builder discovery",
|
|
1979
|
+
},
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
throw error;
|
|
1983
|
+
}
|
|
1984
|
+
try {
|
|
1985
|
+
const openedStats = await handle.stat();
|
|
1986
|
+
const openedVerification = await verifyFileStillExpected({
|
|
1987
|
+
root: input.root,
|
|
1988
|
+
absolutePath: input.absolutePath,
|
|
1989
|
+
relativePath: input.relativePath,
|
|
1990
|
+
expectedStats: openedStats,
|
|
1991
|
+
fs: input.fs,
|
|
1992
|
+
});
|
|
1993
|
+
if (openedVerification.kind === "omitted") {
|
|
1994
|
+
return {
|
|
1995
|
+
kind: "omitted",
|
|
1996
|
+
omission: openedVerification.omission,
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
const openedSize = saneFileSize(openedStats.size);
|
|
2000
|
+
if (!openedStats.isFile() || openedSize == null) {
|
|
2001
|
+
return {
|
|
2002
|
+
kind: "omitted",
|
|
2003
|
+
omission: {
|
|
2004
|
+
reason: "file_changed_during_read",
|
|
2005
|
+
message: "File changed before it could be read safely",
|
|
2006
|
+
},
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
if (!sameFileIdentity(input.expectedStats, openedStats)) {
|
|
2010
|
+
return {
|
|
2011
|
+
kind: "omitted",
|
|
2012
|
+
omission: {
|
|
2013
|
+
reason: "file_changed_during_read",
|
|
2014
|
+
message: "File changed before it could be read safely",
|
|
2015
|
+
},
|
|
2016
|
+
sizeBytes: openedSize,
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
if (openedSize > input.maxBytes) {
|
|
2020
|
+
return {
|
|
2021
|
+
kind: "omitted",
|
|
2022
|
+
omission: {
|
|
2023
|
+
reason: "file_too_large",
|
|
2024
|
+
message: "File exceeded max_bytes_per_file while opening",
|
|
2025
|
+
},
|
|
2026
|
+
sizeBytes: openedSize,
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
const readLength = Math.min(input.maxBytes + 1, openedSize + 1);
|
|
2030
|
+
const buffer = Buffer.alloc(readLength);
|
|
2031
|
+
const { bytesRead } = await handle.read(buffer, 0, readLength, 0);
|
|
2032
|
+
if (bytesRead !== openedSize) {
|
|
2033
|
+
return {
|
|
2034
|
+
kind: "omitted",
|
|
2035
|
+
omission: {
|
|
2036
|
+
reason: "file_changed_during_read",
|
|
2037
|
+
message: "File changed before it could be read safely",
|
|
2038
|
+
},
|
|
2039
|
+
sizeBytes: openedSize,
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
const finalVerification = await verifyFileStillExpected({
|
|
2043
|
+
root: input.root,
|
|
2044
|
+
absolutePath: input.absolutePath,
|
|
2045
|
+
relativePath: input.relativePath,
|
|
2046
|
+
expectedStats: openedStats,
|
|
2047
|
+
fs: input.fs,
|
|
2048
|
+
});
|
|
2049
|
+
if (finalVerification.kind === "omitted") {
|
|
2050
|
+
return {
|
|
2051
|
+
kind: "omitted",
|
|
2052
|
+
omission: finalVerification.omission,
|
|
2053
|
+
sizeBytes: openedSize,
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
return {
|
|
2057
|
+
kind: "read",
|
|
2058
|
+
buffer: buffer.subarray(0, bytesRead),
|
|
2059
|
+
sizeBytes: openedSize,
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
finally {
|
|
2063
|
+
await handle.close();
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
let _noFollowAvailable;
|
|
2067
|
+
function noFollowReadFlags() {
|
|
2068
|
+
if (_noFollowAvailable === undefined) {
|
|
2069
|
+
_noFollowAvailable = typeof fsConstants.O_NOFOLLOW === "number";
|
|
2070
|
+
}
|
|
2071
|
+
return _noFollowAvailable
|
|
2072
|
+
? fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW
|
|
2073
|
+
: fsConstants.O_RDONLY;
|
|
2074
|
+
}
|
|
2075
|
+
function sameFileIdentity(left, right) {
|
|
2076
|
+
if (left.dev !== right.dev || left.ino !== right.ino) {
|
|
2077
|
+
return false;
|
|
2078
|
+
}
|
|
2079
|
+
if (left.isFile() || right.isFile()) {
|
|
2080
|
+
return left.isFile() && right.isFile()
|
|
2081
|
+
&& left.size === right.size
|
|
2082
|
+
&& left.mtimeMs === right.mtimeMs;
|
|
2083
|
+
}
|
|
2084
|
+
if (left.isDirectory() || right.isDirectory()) {
|
|
2085
|
+
return left.isDirectory() && right.isDirectory();
|
|
2086
|
+
}
|
|
2087
|
+
return false;
|
|
2088
|
+
}
|
|
2089
|
+
function saneFileSize(size) {
|
|
2090
|
+
return Number.isSafeInteger(size) && size >= 0 ? size : undefined;
|
|
2091
|
+
}
|
|
2092
|
+
const EXPECTED_DESCENDANT_FILESYSTEM_ERROR_CODES = new Set([
|
|
2093
|
+
"EACCES",
|
|
2094
|
+
"EPERM",
|
|
2095
|
+
"ENOENT",
|
|
2096
|
+
"ENOTDIR",
|
|
2097
|
+
"ELOOP",
|
|
2098
|
+
]);
|
|
2099
|
+
function isExpectedDescendantFilesystemError(error) {
|
|
2100
|
+
const code = filesystemErrorCode(error);
|
|
2101
|
+
return (code != null &&
|
|
2102
|
+
EXPECTED_DESCENDANT_FILESYSTEM_ERROR_CODES.has(code));
|
|
2103
|
+
}
|
|
2104
|
+
function filesystemOmission(message, error) {
|
|
2105
|
+
const code = filesystemErrorCode(error);
|
|
2106
|
+
if (code === "ELOOP") {
|
|
2107
|
+
return {
|
|
2108
|
+
reason: "symlink_omitted",
|
|
2109
|
+
message: "Symlink omitted during context_builder discovery",
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
return {
|
|
2113
|
+
reason: "filesystem_error",
|
|
2114
|
+
message: code == null ? message : `${message} (${code})`,
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
function filesystemErrorCode(error) {
|
|
2118
|
+
if (typeof error !== "object" || error == null || !("code" in error)) {
|
|
2119
|
+
return undefined;
|
|
2120
|
+
}
|
|
2121
|
+
const code = error.code;
|
|
2122
|
+
return typeof code === "string" ? code : undefined;
|
|
2123
|
+
}
|
|
2124
|
+
function assertNotAborted(signal) {
|
|
2125
|
+
if (signal?.aborted === true) {
|
|
2126
|
+
throw new CancellationError();
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
//# sourceMappingURL=discovery.js.map
|