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,2590 @@
|
|
|
1
|
+
import { appendArtifactOnlyRootsToEnv, isArtifactOnlyAllowedRootEntry, isSamePathOrDescendant, parseAllowedRootsEnv, stripArtifactOnlyMarker, } from "../allowed-roots.js";
|
|
2
|
+
import { CancellationError, ExecutionError, InvalidParamsError, } from "../errors.js";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { realpath as fsRealpath } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { AdapterRegistry as RuntimeAdapterRegistry } from "../registry.js";
|
|
7
|
+
import { logger } from "../logger.js";
|
|
8
|
+
import { assertContextBuilderAnchorSufficiency } from "./anchors.js";
|
|
9
|
+
import { writeContextBuilderFinalArtifacts, } from "./bundle.js";
|
|
10
|
+
import { CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS, getSharedContextBuilderSemaphore, resolveContextBuilderRunsDir, } from "./config.js";
|
|
11
|
+
import { buildContextBuilderCuratorPacket, buildContextBuilderFinalizerPacket, CONTEXT_BUILDER_DELEGATE_PACKET_CAP_PROFILE_DEFAULTS, contextBuilderCandidateId, } from "./delegate-packet.js";
|
|
12
|
+
import { reconstructContextBuilderDelegateSelection } from "./delegate-reconstruction.js";
|
|
13
|
+
import { runContextBuilderDelegateCall, } from "./delegate.js";
|
|
14
|
+
import { validateContextBuilderCuratorOutput, validateContextBuilderDelegateCoverageSelection, validateContextBuilderFinalizerOutput, } from "./delegate-validation.js";
|
|
15
|
+
import { discoverContextBuilderCandidates, mergeLineRanges, } from "./discovery.js";
|
|
16
|
+
import { evaluateSearchCapMateriality } from "./status.js";
|
|
17
|
+
import { boundContextBuilderPathList, isRequestedEvidenceOmission, sanitizeDelegateFailureText, sanitizeDiagnosticText, } from "./diagnostics.js";
|
|
18
|
+
import { compileContextBuilderGlobs, CONTEXT_BUILDER_DEFAULT_EXCLUDE_GLOBS, validateContextBuilderGlobs, } from "./globs.js";
|
|
19
|
+
import { resolveContextBuilderFocusPaths, resolveContextBuilderRoots, } from "./paths.js";
|
|
20
|
+
import { createContextBuilderProgressReporter, } from "./progress.js";
|
|
21
|
+
import { ContextBuilderLifecycleController, ContextBuilderLifecycleObserverError, } from "./lifecycle-observer.js";
|
|
22
|
+
import { createContextBuilderLifecycleJournal } from "./lifecycle-journal.js";
|
|
23
|
+
import { recordContextRunCatalogMarker } from "./context-run-catalog.js";
|
|
24
|
+
import { appendLifecycleCompletionWarning } from "./lifecycle-recovery.js";
|
|
25
|
+
import { withContextBuilderWriteWatchdog } from "./write-watchdog.js";
|
|
26
|
+
import { createContextBuilderReceiptTextResponse, } from "./receipt.js";
|
|
27
|
+
import { generateContextBuilderRunId } from "./run-id.js";
|
|
28
|
+
import { CONTEXT_BUILDER_INTENT_PROFILE_DEFAULTS, parseContextBuilderInput, } from "./schema.js";
|
|
29
|
+
import { evaluateContextCoverage, resolveContextCoveragePolicy, } from "./review-coverage.js";
|
|
30
|
+
import { selectContextBuilderFinalContext, } from "./selection.js";
|
|
31
|
+
import { cleanupContextBuilderIncompleteRun, createContextBuilderRun, } from "./storage.js";
|
|
32
|
+
import { admitJournalContextGeneration } from "./session-log-identity.js";
|
|
33
|
+
import { estimateContextBuilderTokensFromUtf8Bytes } from "./token-estimate.js";
|
|
34
|
+
let defaultAdapterRegistry;
|
|
35
|
+
const artifactOnlyAllowedRoots = new Set();
|
|
36
|
+
const CONTEXT_BUILDER_DELEGATE_PROMPT_VERSION = "cb-delegate-v3-scored-obligations";
|
|
37
|
+
const MAX_DELEGATE_MINIMUM_OUTPUT_SELECTIONS = 24;
|
|
38
|
+
export async function handleContextBuilder(args, options = {}) {
|
|
39
|
+
const input = parseContextBuilderInput(args);
|
|
40
|
+
const coveragePolicy = resolveContextCoveragePolicy(input.profile);
|
|
41
|
+
const semaphore = options.semaphore ?? getDefaultContextBuilderSemaphore();
|
|
42
|
+
const progress = createContextBuilderProgressReporter(options.progress);
|
|
43
|
+
const ownsLifecycle = options.lifecycleController == null;
|
|
44
|
+
semaphore.acquire();
|
|
45
|
+
let lifecycle;
|
|
46
|
+
let storage;
|
|
47
|
+
let committed = false;
|
|
48
|
+
try {
|
|
49
|
+
// Resolve and validate the runs dir up front so a broad/forbidden runs_dir
|
|
50
|
+
// is rejected with its canonical error BEFORE any lifecycle transition, and
|
|
51
|
+
// so the journal reuses this one already-validated path instead of
|
|
52
|
+
// re-resolving (a second resolution would wrap that rejection as an opaque
|
|
53
|
+
// observer failure).
|
|
54
|
+
const runsDir = await resolveContextBuilderRunsDir(options.runsDir);
|
|
55
|
+
// A standalone operation that owns its lifecycle (no injected observer)
|
|
56
|
+
// persists authoritatively through the .context-runs journal; the injected
|
|
57
|
+
// path — workflow preflight — supplies its own store instead.
|
|
58
|
+
const journal = ownsLifecycle && options.lifecycle == null
|
|
59
|
+
? createContextBuilderLifecycleJournal({
|
|
60
|
+
runsDir,
|
|
61
|
+
resolveRunsDir: async () => runsDir,
|
|
62
|
+
})
|
|
63
|
+
: undefined;
|
|
64
|
+
lifecycle = options.lifecycleController ??
|
|
65
|
+
new ContextBuilderLifecycleController({
|
|
66
|
+
operationKind: "standalone",
|
|
67
|
+
execution: lifecycleExecutionConfig(input),
|
|
68
|
+
...(options.lifecycle != null
|
|
69
|
+
? {
|
|
70
|
+
observer: options.lifecycle.observer,
|
|
71
|
+
...(options.lifecycle.origin == null
|
|
72
|
+
? {}
|
|
73
|
+
: { origin: options.lifecycle.origin }),
|
|
74
|
+
}
|
|
75
|
+
: journal == null
|
|
76
|
+
? {}
|
|
77
|
+
: { observer: journal.observe }),
|
|
78
|
+
...(progress == null ? {} : { notificationObserver: progress.observe }),
|
|
79
|
+
...(options.now == null ? {} : { now: options.now }),
|
|
80
|
+
});
|
|
81
|
+
if (ownsLifecycle) {
|
|
82
|
+
await lifecycle?.transition("resolving");
|
|
83
|
+
}
|
|
84
|
+
const createdAt = (options.now?.() ?? new Date()).toISOString();
|
|
85
|
+
const runId = input.run_id ?? generateContextBuilderRunId();
|
|
86
|
+
await lifecycle?.transition("validating", {
|
|
87
|
+
...(ownsLifecycle ? { artifactRunId: runId } : {}),
|
|
88
|
+
});
|
|
89
|
+
const internalCaps = options.internalCaps ?? CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS;
|
|
90
|
+
const currentAllowedRoots = parseAllowedRootsEnv({
|
|
91
|
+
includeArtifactOnly: false,
|
|
92
|
+
});
|
|
93
|
+
const explicitlyConfiguredArtifactRoots = getExactlyConfiguredArtifactRoots();
|
|
94
|
+
const sourceAllowedRoots = currentAllowedRoots.filter((root) => !artifactOnlyAllowedRoots.has(root) ||
|
|
95
|
+
explicitlyConfiguredArtifactRoots.has(root));
|
|
96
|
+
validateContextBuilderGlobs({
|
|
97
|
+
include: input.include_globs,
|
|
98
|
+
exclude: input.exclude_globs,
|
|
99
|
+
});
|
|
100
|
+
await rejectArtifactOnlySourceRoots(input.roots, {
|
|
101
|
+
explicitlyConfiguredArtifactRoots,
|
|
102
|
+
});
|
|
103
|
+
const rootResolution = await resolveContextBuilderRoots(input.roots, {
|
|
104
|
+
allowedRoots: sourceAllowedRoots,
|
|
105
|
+
});
|
|
106
|
+
const focusPaths = await resolveContextBuilderFocusPaths(input.focus_paths, rootResolution.roots);
|
|
107
|
+
assertContextBuilderAnchorSufficiency({
|
|
108
|
+
focus_paths: focusPaths,
|
|
109
|
+
include_globs: input.include_globs,
|
|
110
|
+
search_terms: input.search_terms,
|
|
111
|
+
allowRootWideDiscovery: options.allowRootWideDiscovery ?? false,
|
|
112
|
+
});
|
|
113
|
+
assertNoContextBuilderFocusGlobConflicts({
|
|
114
|
+
focusPaths,
|
|
115
|
+
includeGlobs: input.include_globs,
|
|
116
|
+
excludeGlobs: input.exclude_globs,
|
|
117
|
+
});
|
|
118
|
+
appendArtifactOnlyRootToEnv(runsDir);
|
|
119
|
+
if (!sourceAllowedRoots.includes(runsDir)) {
|
|
120
|
+
artifactOnlyAllowedRoots.add(runsDir);
|
|
121
|
+
}
|
|
122
|
+
const excludedArtifactRoots = [...artifactOnlyAllowedRoots].filter((root) => !explicitlyConfiguredArtifactRoots.has(root));
|
|
123
|
+
const writingStorage = await createContextBuilderRun({
|
|
124
|
+
runsDir,
|
|
125
|
+
runId,
|
|
126
|
+
createdAt,
|
|
127
|
+
});
|
|
128
|
+
storage = writingStorage;
|
|
129
|
+
let sessionLogContext;
|
|
130
|
+
if (journal !== undefined) {
|
|
131
|
+
try {
|
|
132
|
+
sessionLogContext = {
|
|
133
|
+
contextRunId: lifecycle.contextRunId,
|
|
134
|
+
contextGeneration: await admitJournalContextGeneration(runsDir, lifecycle.contextRunId),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
logger.debug({ contextRunId: lifecycle.contextRunId }, "context session-log identity admission failed open");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if (options.sessionLogContext !== undefined) {
|
|
142
|
+
// Workflow preflight path: the runner minted the operation id (via the
|
|
143
|
+
// controller) and created the run-dir generation sidecar. Use that pair
|
|
144
|
+
// instead of the journal (which the injected-controller path does not
|
|
145
|
+
// create). Both flow into the context-family session-log owner emitted for
|
|
146
|
+
// the curator/finalizer delegate calls.
|
|
147
|
+
const injected = options.sessionLogContext;
|
|
148
|
+
sessionLogContext = injected.contextRunId === lifecycle.contextRunId
|
|
149
|
+
? injected
|
|
150
|
+
: { contextRunId: lifecycle.contextRunId, contextGeneration: injected.contextGeneration };
|
|
151
|
+
}
|
|
152
|
+
await lifecycle?.transition("scanning");
|
|
153
|
+
const discovery = await discoverContextBuilderCandidates({
|
|
154
|
+
roots: rootResolution.roots,
|
|
155
|
+
task: input.task,
|
|
156
|
+
allowSecrets: input.allow_secrets,
|
|
157
|
+
focus_paths: focusPaths,
|
|
158
|
+
include_globs: input.include_globs,
|
|
159
|
+
exclude_globs: input.exclude_globs,
|
|
160
|
+
search_terms: input.search_terms,
|
|
161
|
+
coverage_policy: coveragePolicy,
|
|
162
|
+
budget: input.budget,
|
|
163
|
+
internal_caps: internalCaps,
|
|
164
|
+
excluded_real_roots: excludedArtifactRoots,
|
|
165
|
+
...(options.signal == null ? {} : { signal: options.signal }),
|
|
166
|
+
});
|
|
167
|
+
await lifecycle?.transition("searching", {
|
|
168
|
+
counts: lifecycleCountsFromDiscovery(discovery),
|
|
169
|
+
});
|
|
170
|
+
await lifecycle?.transition("selecting");
|
|
171
|
+
const selection = selectContextBuilderFinalContext({
|
|
172
|
+
task: input.task,
|
|
173
|
+
tree: discovery.tree,
|
|
174
|
+
search: discovery.search,
|
|
175
|
+
slices: discovery.slices,
|
|
176
|
+
budget: input.budget,
|
|
177
|
+
coverage_policy: coveragePolicy,
|
|
178
|
+
evidence_classes_by_candidate: evidenceClassesByCandidate(discovery.content_candidates),
|
|
179
|
+
...(discovery.review_obligations == null
|
|
180
|
+
? {}
|
|
181
|
+
: { review_obligations: discovery.review_obligations }),
|
|
182
|
+
});
|
|
183
|
+
const effectiveDelegatePacketCaps = resolveEffectiveDelegatePacketCaps(input.profile, options.delegatePacketCaps);
|
|
184
|
+
const delegateResult = input.delegate.enabled
|
|
185
|
+
? await resolveDelegateSelection({
|
|
186
|
+
input,
|
|
187
|
+
discovery,
|
|
188
|
+
deterministicSelection: selection,
|
|
189
|
+
focusPaths,
|
|
190
|
+
baselineOmissions: rootResolution.omissions,
|
|
191
|
+
sourceRoots: rootResolution.roots,
|
|
192
|
+
registry: options.registry ?? getDefaultAdapterRegistry(),
|
|
193
|
+
coveragePolicy,
|
|
194
|
+
...(effectiveDelegatePacketCaps == null
|
|
195
|
+
? {}
|
|
196
|
+
: { delegatePacketCaps: effectiveDelegatePacketCaps }),
|
|
197
|
+
...(lifecycle == null ? {} : { lifecycle }),
|
|
198
|
+
...(options.signal == null ? {} : { signal: options.signal }),
|
|
199
|
+
...(sessionLogContext == null ? {} : { sessionLogContext }),
|
|
200
|
+
})
|
|
201
|
+
: undefined;
|
|
202
|
+
const effectiveDelegateResult = delegateResult;
|
|
203
|
+
const finalSelection = effectiveDelegateResult?.selection ?? selection;
|
|
204
|
+
const coverage = evaluateSelectionCoverage({
|
|
205
|
+
policy: coveragePolicy,
|
|
206
|
+
candidates: discovery.content_candidates,
|
|
207
|
+
selected: finalSelection.selected,
|
|
208
|
+
...(discovery.review_obligations == null
|
|
209
|
+
? {}
|
|
210
|
+
: { obligations: discovery.review_obligations }),
|
|
211
|
+
});
|
|
212
|
+
const rawWarnings = [...(effectiveDelegateResult?.warnings ?? [])];
|
|
213
|
+
const openQuestions = effectiveDelegateResult?.openQuestions ?? [];
|
|
214
|
+
const omissions = [
|
|
215
|
+
...rootResolution.omissions,
|
|
216
|
+
...discovery.omissions,
|
|
217
|
+
...finalSelection.omissions,
|
|
218
|
+
...(effectiveDelegateResult?.omissions ?? []),
|
|
219
|
+
];
|
|
220
|
+
const metrics = buildContextBuilderMetrics({
|
|
221
|
+
discovery: discovery.metrics,
|
|
222
|
+
selection: finalSelection.metrics,
|
|
223
|
+
omissionCount: omissions.length,
|
|
224
|
+
});
|
|
225
|
+
const reconciledWarnings = reconcileContextBuilderWarnings({
|
|
226
|
+
warnings: rawWarnings,
|
|
227
|
+
selected: finalSelection.selected,
|
|
228
|
+
omissions,
|
|
229
|
+
metrics,
|
|
230
|
+
delegate: effectiveDelegateResult?.delegate,
|
|
231
|
+
});
|
|
232
|
+
// A material search cap must be explained in the receipt, not just
|
|
233
|
+
// classified: the warning carries the bounded coverage summary while the
|
|
234
|
+
// cap event (built from metrics + search_coverage in the status reducer)
|
|
235
|
+
// carries the partial classification.
|
|
236
|
+
const searchCapWarning = searchCapCoverageWarning(metrics.search_cap_hit, discovery.search_coverage);
|
|
237
|
+
const warnings = searchCapWarning == null
|
|
238
|
+
? reconciledWarnings
|
|
239
|
+
: [...reconciledWarnings, searchCapWarning];
|
|
240
|
+
const delegateMetadata = effectiveDelegateResult?.delegate == null
|
|
241
|
+
? undefined
|
|
242
|
+
: {
|
|
243
|
+
...effectiveDelegateResult.delegate,
|
|
244
|
+
selected_count: finalSelection.selected.length,
|
|
245
|
+
warning_count: warnings.length,
|
|
246
|
+
};
|
|
247
|
+
const delegatePacketEstimatedTokens = effectiveDelegateResult?.observability.delegate_packet_size ?? 0;
|
|
248
|
+
const delegatePacketCompaction = effectiveDelegateResult?.observability.delegate_packet_compaction;
|
|
249
|
+
const observability = {
|
|
250
|
+
curator_ms: effectiveDelegateResult?.observability.curator_ms ?? null,
|
|
251
|
+
finalizer_ms: effectiveDelegateResult?.observability.finalizer_ms ?? null,
|
|
252
|
+
candidate_pool_size: discovery.content_candidates.length,
|
|
253
|
+
// Deprecated alias (see ContextBuilderObservabilityMetrics): historical
|
|
254
|
+
// name for the packet ESTIMATED-TOKEN metric. Kept byte-identical for
|
|
255
|
+
// compatibility; new readers should use delegate_packet_estimated_tokens.
|
|
256
|
+
delegate_packet_size: delegatePacketEstimatedTokens,
|
|
257
|
+
delegate_packet_estimated_tokens: delegatePacketEstimatedTokens,
|
|
258
|
+
...(delegatePacketCompaction == null
|
|
259
|
+
? {}
|
|
260
|
+
: { delegate_packet_compaction: delegatePacketCompaction }),
|
|
261
|
+
};
|
|
262
|
+
const renderInput = {
|
|
263
|
+
run_id: runId,
|
|
264
|
+
created_at: createdAt,
|
|
265
|
+
task: input.task,
|
|
266
|
+
allowSecrets: input.allow_secrets,
|
|
267
|
+
roots: rootResolution.roots.map((root) => ({
|
|
268
|
+
root_index: root.root_index,
|
|
269
|
+
input_path: root.input_path,
|
|
270
|
+
path: root.path,
|
|
271
|
+
})),
|
|
272
|
+
inputs: {
|
|
273
|
+
effective_profile: input.profile ?? null,
|
|
274
|
+
roots: input.roots,
|
|
275
|
+
focus_paths: input.focus_paths,
|
|
276
|
+
include_globs: input.include_globs,
|
|
277
|
+
exclude_globs: input.exclude_globs,
|
|
278
|
+
search_terms: input.search_terms,
|
|
279
|
+
resolved_budget: input.budget,
|
|
280
|
+
delegate: input.delegate,
|
|
281
|
+
...(input.run_id == null ? {} : { run_id: input.run_id }),
|
|
282
|
+
},
|
|
283
|
+
budget: input.budget,
|
|
284
|
+
internal_caps: internalCaps,
|
|
285
|
+
tree: finalSelection.tree,
|
|
286
|
+
search: finalSelection.search,
|
|
287
|
+
slices: finalSelection.slices,
|
|
288
|
+
selected_context: finalSelection.selected,
|
|
289
|
+
omissions,
|
|
290
|
+
warnings,
|
|
291
|
+
metrics,
|
|
292
|
+
search_coverage: discovery.search_coverage,
|
|
293
|
+
open_questions: openQuestions,
|
|
294
|
+
...(delegateMetadata == null ? {} : { delegate: delegateMetadata }),
|
|
295
|
+
observability,
|
|
296
|
+
coverage,
|
|
297
|
+
status_evidence: { coverage },
|
|
298
|
+
debug: input.debug,
|
|
299
|
+
};
|
|
300
|
+
await lifecycle?.transition("writing", {
|
|
301
|
+
...delegateLifecyclePatch(effectiveDelegateResult, lifecycle),
|
|
302
|
+
counts: lifecycleCountsFromMetrics(metrics, discovery.search.length),
|
|
303
|
+
coverage: lifecycleCoverage(coverage),
|
|
304
|
+
...(effectiveDelegateResult?.delegate.fallback_used === true
|
|
305
|
+
? {
|
|
306
|
+
fallback: {
|
|
307
|
+
status: "used",
|
|
308
|
+
reason_code: "delegate_fallback",
|
|
309
|
+
},
|
|
310
|
+
reasonCodes: ["delegate_fallback"],
|
|
311
|
+
}
|
|
312
|
+
: {}),
|
|
313
|
+
});
|
|
314
|
+
// Capture into a const so the beforeManifestCommit closure keeps the
|
|
315
|
+
// narrowed non-undefined type (the outer binding is a reassignable let).
|
|
316
|
+
const committingLifecycle = lifecycle;
|
|
317
|
+
const receipt = await withContextBuilderWriteWatchdog((signal) => writeContextBuilderFinalArtifacts(writingStorage, renderInput, {
|
|
318
|
+
signal,
|
|
319
|
+
...(writingStorage.ownerNonce == null ? {} : { ownerNonce: writingStorage.ownerNonce }),
|
|
320
|
+
...(!ownsLifecycle || committingLifecycle == null
|
|
321
|
+
? {}
|
|
322
|
+
: { beforeManifestCommit: () => committingLifecycle.transition("committing") }),
|
|
323
|
+
}), options.signal == null ? {} : { signal: options.signal });
|
|
324
|
+
committed = true;
|
|
325
|
+
if (ownsLifecycle) {
|
|
326
|
+
await recordContextRunCatalogMarker({
|
|
327
|
+
runsDir,
|
|
328
|
+
kind: "artifact",
|
|
329
|
+
id: receipt.run_id,
|
|
330
|
+
createdAt,
|
|
331
|
+
}).catch(() => {
|
|
332
|
+
logger.warn({ contextRunId: receipt.run_id }, "context run catalog marker write failed");
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
let responseReceipt = receipt;
|
|
336
|
+
if (ownsLifecycle) {
|
|
337
|
+
try {
|
|
338
|
+
await lifecycle?.complete(receipt.status, {
|
|
339
|
+
artifactRunId: receipt.run_id,
|
|
340
|
+
counts: lifecycleCountsFromMetrics(receipt.metrics, discovery.search.length),
|
|
341
|
+
coverage: lifecycleCoverage(coverage),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
if (!(error instanceof ContextBuilderLifecycleObserverError))
|
|
346
|
+
throw error;
|
|
347
|
+
logger.warn({ contextRunId: lifecycle?.contextRunId }, "context run lifecycle completion unavailable after retry");
|
|
348
|
+
responseReceipt = appendLifecycleCompletionWarning(receipt, internalCaps.max_warnings);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return createContextBuilderReceiptTextResponse(responseReceipt);
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
if (storage != null && !committed && storage.ownerNonce != null) {
|
|
355
|
+
await cleanupContextBuilderIncompleteRun(storage, storage.ownerNonce).catch(() => { });
|
|
356
|
+
}
|
|
357
|
+
if (ownsLifecycle && lifecycle != null &&
|
|
358
|
+
!(err instanceof ContextBuilderLifecycleObserverError)) {
|
|
359
|
+
const cancelled = err instanceof CancellationError || options.signal?.aborted === true;
|
|
360
|
+
const delegateFailed = input.delegate.enabled && delegateExecutionActive(lifecycle);
|
|
361
|
+
await lifecycle.fail(cancelled ? "cancelled" : "failed", {
|
|
362
|
+
...terminalExecutionPatch(lifecycle, cancelled),
|
|
363
|
+
reasonCodes: [
|
|
364
|
+
cancelled
|
|
365
|
+
? "operation_cancelled"
|
|
366
|
+
: delegateFailed
|
|
367
|
+
? "delegate_failed"
|
|
368
|
+
: "internal_failure",
|
|
369
|
+
],
|
|
370
|
+
...(!cancelled && delegateFailed
|
|
371
|
+
? {
|
|
372
|
+
fallback: {
|
|
373
|
+
status: "failed",
|
|
374
|
+
reason_code: "delegate_failed",
|
|
375
|
+
},
|
|
376
|
+
}
|
|
377
|
+
: {}),
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
throw err;
|
|
381
|
+
}
|
|
382
|
+
finally {
|
|
383
|
+
semaphore.release();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function lifecycleExecutionConfig(input) {
|
|
387
|
+
if (!input.delegate.enabled) {
|
|
388
|
+
return {
|
|
389
|
+
curator: { configured: false, enabled: false },
|
|
390
|
+
finalizer: { configured: false, enabled: false },
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
curator: { configured: true, enabled: true },
|
|
395
|
+
finalizer: {
|
|
396
|
+
configured: true,
|
|
397
|
+
enabled: contextBuilderFinalizerEnabled(input),
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function lifecycleCountsFromDiscovery(discovery) {
|
|
402
|
+
return {
|
|
403
|
+
files_seen: discovery.metrics.files_seen,
|
|
404
|
+
files_considered: discovery.metrics.files_considered,
|
|
405
|
+
files_selected: 0,
|
|
406
|
+
files_omitted: discovery.omissions.length,
|
|
407
|
+
search_results: discovery.search.length,
|
|
408
|
+
bytes_read: discovery.metrics.bytes_read,
|
|
409
|
+
bytes_written: 0,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function lifecycleCountsFromMetrics(metrics, searchResults) {
|
|
413
|
+
return {
|
|
414
|
+
files_seen: metrics.files_seen,
|
|
415
|
+
files_considered: metrics.files_considered,
|
|
416
|
+
files_selected: metrics.files_selected,
|
|
417
|
+
files_omitted: metrics.files_omitted,
|
|
418
|
+
search_results: searchResults,
|
|
419
|
+
bytes_read: metrics.bytes_read,
|
|
420
|
+
bytes_written: metrics.bytes_written,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function lifecycleCoverage(coverage) {
|
|
424
|
+
const required = coverage.required_when_available.length;
|
|
425
|
+
const missing = coverage.missing_required.length;
|
|
426
|
+
return {
|
|
427
|
+
policy: coverage.policy_id,
|
|
428
|
+
status: coverage.status,
|
|
429
|
+
required,
|
|
430
|
+
selected: Math.max(0, required - missing),
|
|
431
|
+
missing,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function delegateLifecyclePatch(result, lifecycle) {
|
|
435
|
+
if (result == null)
|
|
436
|
+
return {};
|
|
437
|
+
if (result.delegate.fallback_used) {
|
|
438
|
+
if (lifecycle?.snapshot?.phase === "finalizing") {
|
|
439
|
+
return { curatorStatus: "succeeded", finalizerStatus: "failed" };
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
curatorStatus: "failed",
|
|
443
|
+
...(lifecycle?.snapshot?.finalizer.effective_enabled === true
|
|
444
|
+
? { finalizerStatus: "skipped" }
|
|
445
|
+
: {}),
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
curatorStatus: "succeeded",
|
|
450
|
+
...(result.delegate.finalizer_score == null
|
|
451
|
+
? {}
|
|
452
|
+
: {
|
|
453
|
+
finalizerStatus: "succeeded",
|
|
454
|
+
finalizerScore: result.delegate.finalizer_score,
|
|
455
|
+
}),
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
function terminalExecutionPatch(lifecycle, cancelled) {
|
|
459
|
+
const snapshot = lifecycle.snapshot;
|
|
460
|
+
if (snapshot == null)
|
|
461
|
+
return {};
|
|
462
|
+
const terminalStatus = cancelled ? "cancelled" : "failed";
|
|
463
|
+
return {
|
|
464
|
+
...(snapshot.curator.execution_status === "running"
|
|
465
|
+
? { curatorStatus: terminalStatus }
|
|
466
|
+
: snapshot.curator.execution_status === "pending"
|
|
467
|
+
? { curatorStatus: "skipped" }
|
|
468
|
+
: {}),
|
|
469
|
+
...(snapshot.finalizer.execution_status === "running"
|
|
470
|
+
? { finalizerStatus: terminalStatus }
|
|
471
|
+
: snapshot.finalizer.execution_status === "pending"
|
|
472
|
+
? { finalizerStatus: "skipped" }
|
|
473
|
+
: {}),
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function delegateExecutionActive(lifecycle) {
|
|
477
|
+
const snapshot = lifecycle.snapshot;
|
|
478
|
+
return snapshot?.curator.execution_status === "running" ||
|
|
479
|
+
snapshot?.finalizer.execution_status === "running";
|
|
480
|
+
}
|
|
481
|
+
function evidenceClassesByCandidate(candidates) {
|
|
482
|
+
return new Map(candidates.map((candidate) => [
|
|
483
|
+
`${candidate.root_index}\u0000${candidate.path}`,
|
|
484
|
+
candidate.evidence_classes ?? [],
|
|
485
|
+
]));
|
|
486
|
+
}
|
|
487
|
+
function evaluateSelectionCoverage(input) {
|
|
488
|
+
const classesByCandidate = evidenceClassesByCandidate(input.candidates);
|
|
489
|
+
const selectedClasses = input.selected.flatMap((file) => classesByCandidate.get(`${file.root_index}\u0000${file.path}`) ?? []);
|
|
490
|
+
return evaluateContextCoverage({
|
|
491
|
+
policy: input.policy,
|
|
492
|
+
available: input.candidates.flatMap((candidate) => candidate.evidence_classes ?? []),
|
|
493
|
+
selected: selectedClasses,
|
|
494
|
+
unclassifiable: input.policy.id == null
|
|
495
|
+
? []
|
|
496
|
+
: ["public_entrypoint", "reverse_consumer"],
|
|
497
|
+
...(input.obligations == null ? {} : { obligations: input.obligations }),
|
|
498
|
+
selectedRanges: input.selected,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
function delegateCoverageSelectionFailure(selected, candidates, coveragePolicy) {
|
|
502
|
+
try {
|
|
503
|
+
validateContextBuilderDelegateCoverageSelection(selected.map((file) => ({
|
|
504
|
+
candidate_id: contextBuilderCandidateId(file.root_index, file.path),
|
|
505
|
+
ranges: file.ranges,
|
|
506
|
+
})), candidates, coveragePolicy);
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
return error instanceof Error ? error.message : String(error);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
async function rejectArtifactOnlySourceRoots(inputRoots, input) {
|
|
514
|
+
if (artifactOnlyAllowedRoots.size === 0) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const cwd = path.resolve(process.cwd());
|
|
518
|
+
for (const inputRoot of inputRoots) {
|
|
519
|
+
const lexicalRoot = path.resolve(cwd, inputRoot);
|
|
520
|
+
const resolvedRoot = await fsRealpath(lexicalRoot).catch(() => undefined);
|
|
521
|
+
if (resolvedRoot == null) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
for (const artifactRoot of artifactOnlyAllowedRoots) {
|
|
525
|
+
if (input.explicitlyConfiguredArtifactRoots.has(artifactRoot) ||
|
|
526
|
+
!isSamePathOrDescendant(resolvedRoot, artifactRoot)) {
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
throw new InvalidParamsError(`context_builder root is outside allowed roots: ${inputRoot}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Fail-fast admission check: an explicitly-focused path that the caller's own
|
|
535
|
+
* (or the default) globs would prune is a contradictory request. Discovery
|
|
536
|
+
* only surfaces these later, one gate at a time, as fatal runtime omissions,
|
|
537
|
+
* so aggregate EVERY conflict up front into one InvalidParamsError before any
|
|
538
|
+
* scanning or delegate subprocess work begins.
|
|
539
|
+
*
|
|
540
|
+
* The check mirrors discovery's glob semantics exactly: the matcher is
|
|
541
|
+
* compiled from include_globs plus CONTEXT_BUILDER_DEFAULT_EXCLUDE_GLOBS +
|
|
542
|
+
* exclude_globs (discoverContextBuilderCandidates does the same), focus FILES
|
|
543
|
+
* are matched on their own root-relative path (processFile's includes/excludes
|
|
544
|
+
* gates), and focus DIRECTORIES are flagged only when the directory itself is
|
|
545
|
+
* exclude-pruned via the same `<dir>` / `<dir>/__dir__` probes traverseDirectory
|
|
546
|
+
* uses. Ancestor-directory exclusion and directory-vs-include pruning are
|
|
547
|
+
* deliberately NOT modeled here (they depend on traversal state), so anything
|
|
548
|
+
* not flagged falls through to discovery's existing runtime handling.
|
|
549
|
+
*/
|
|
550
|
+
function assertNoContextBuilderFocusGlobConflicts(input) {
|
|
551
|
+
const matcher = compileContextBuilderGlobs({
|
|
552
|
+
include: [...input.includeGlobs],
|
|
553
|
+
exclude: [
|
|
554
|
+
...CONTEXT_BUILDER_DEFAULT_EXCLUDE_GLOBS,
|
|
555
|
+
...input.excludeGlobs,
|
|
556
|
+
],
|
|
557
|
+
});
|
|
558
|
+
const conflicts = [];
|
|
559
|
+
const seen = new Set();
|
|
560
|
+
for (const focus of input.focusPaths) {
|
|
561
|
+
const relative = focus.root_relative_path;
|
|
562
|
+
if (relative === "") {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const key = `${focus.root_index}\u0000${relative}\u0000${focus.kind}`;
|
|
566
|
+
if (seen.has(key)) {
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
seen.add(key);
|
|
570
|
+
if (focus.kind === "directory") {
|
|
571
|
+
if (matcher.excludes(relative) ||
|
|
572
|
+
matcher.excludes(`${relative}/__dir__`)) {
|
|
573
|
+
conflicts.push(`focus directory "${relative}" (root_index ${focus.root_index}) is pruned by exclude_globs`);
|
|
574
|
+
}
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (matcher.excludes(relative)) {
|
|
578
|
+
conflicts.push(`focus path "${relative}" (root_index ${focus.root_index}) is omitted by exclude_globs`);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (!matcher.includes(relative)) {
|
|
582
|
+
conflicts.push(`focus path "${relative}" (root_index ${focus.root_index}) is outside include_globs`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (conflicts.length > 0) {
|
|
586
|
+
throw new InvalidParamsError(`context_builder focus_paths conflict with the effective include/exclude globs (${conflicts.length} conflict${conflicts.length === 1 ? "" : "s"}): ${conflicts.join("; ")}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function getExactlyConfiguredArtifactRoots() {
|
|
590
|
+
const rawEntries = rawAllowedRootEntries();
|
|
591
|
+
const configured = new Set();
|
|
592
|
+
const cwd = path.resolve(process.cwd());
|
|
593
|
+
for (const artifactRoot of artifactOnlyAllowedRoots) {
|
|
594
|
+
for (const rawEntry of rawEntries) {
|
|
595
|
+
if (isArtifactOnlyAllowedRootEntry(rawEntry) &&
|
|
596
|
+
realpathSyncOrUndefined(path.resolve(cwd, stripArtifactOnlyMarker(rawEntry))) === artifactRoot) {
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const resolvedEntry = realpathSyncOrUndefined(path.resolve(cwd, rawEntry));
|
|
600
|
+
if (resolvedEntry === artifactRoot) {
|
|
601
|
+
configured.add(artifactRoot);
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return configured;
|
|
607
|
+
}
|
|
608
|
+
function appendArtifactOnlyRootToEnv(runsDir) {
|
|
609
|
+
appendArtifactOnlyRootsToEnv([runsDir], { requireExisting: false });
|
|
610
|
+
}
|
|
611
|
+
function rawAllowedRootEntries() {
|
|
612
|
+
const raw = process.env["CLINKX_ALLOWED_ROOTS"];
|
|
613
|
+
if (raw == null || raw.trim() === "") {
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
616
|
+
return raw
|
|
617
|
+
.split(",")
|
|
618
|
+
.map((root) => root.trim())
|
|
619
|
+
.filter((root) => root.length > 0);
|
|
620
|
+
}
|
|
621
|
+
function realpathSyncOrUndefined(input) {
|
|
622
|
+
try {
|
|
623
|
+
return realpathSync(input);
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
return undefined;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function getDefaultContextBuilderSemaphore() {
|
|
630
|
+
return getSharedContextBuilderSemaphore();
|
|
631
|
+
}
|
|
632
|
+
function getDefaultAdapterRegistry() {
|
|
633
|
+
if (defaultAdapterRegistry == null) {
|
|
634
|
+
defaultAdapterRegistry = RuntimeAdapterRegistry.load();
|
|
635
|
+
}
|
|
636
|
+
return defaultAdapterRegistry;
|
|
637
|
+
}
|
|
638
|
+
function resolveEffectiveDelegatePacketCaps(profile, explicitCaps) {
|
|
639
|
+
const profileCaps = profile == null
|
|
640
|
+
? undefined
|
|
641
|
+
: CONTEXT_BUILDER_DELEGATE_PACKET_CAP_PROFILE_DEFAULTS[profile];
|
|
642
|
+
if (profileCaps == null && explicitCaps == null) {
|
|
643
|
+
return undefined;
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
...(profileCaps ?? {}),
|
|
647
|
+
...(explicitCaps ?? {}),
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
function contextBuilderFinalizerEnabled(input) {
|
|
651
|
+
if (!input.delegate.enabled) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
const explicit = input.delegate.finalizer.enabled;
|
|
655
|
+
if (explicit != null) {
|
|
656
|
+
return explicit;
|
|
657
|
+
}
|
|
658
|
+
if (input.profile != null) {
|
|
659
|
+
return CONTEXT_BUILDER_INTENT_PROFILE_DEFAULTS[input.profile]
|
|
660
|
+
.finalizer_enabled;
|
|
661
|
+
}
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
function delegateFinalizerReceiptMetadata(finalizer) {
|
|
665
|
+
return {
|
|
666
|
+
cli_name: finalizer.cli_name,
|
|
667
|
+
role: finalizer.role,
|
|
668
|
+
min_score: finalizer.min_score,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
async function resolveDelegateSelection(input) {
|
|
672
|
+
const delegate = input.input.delegate;
|
|
673
|
+
if (!delegate.enabled) {
|
|
674
|
+
throw new ExecutionError("context_builder delegate orchestration requires enabled delegate config");
|
|
675
|
+
}
|
|
676
|
+
const finalizerEnabled = contextBuilderFinalizerEnabled(input.input);
|
|
677
|
+
const delegatePacketInput = {
|
|
678
|
+
task: input.input.task,
|
|
679
|
+
allowSecrets: input.input.allow_secrets,
|
|
680
|
+
discovery: {
|
|
681
|
+
tree: input.discovery.tree,
|
|
682
|
+
content_candidates: input.discovery.content_candidates,
|
|
683
|
+
search: input.discovery.search,
|
|
684
|
+
slices: input.discovery.slices,
|
|
685
|
+
omissions: [...input.baselineOmissions, ...input.discovery.omissions],
|
|
686
|
+
metrics: input.discovery.metrics,
|
|
687
|
+
...(input.discovery.review_obligations == null
|
|
688
|
+
? {}
|
|
689
|
+
: { review_obligations: input.discovery.review_obligations }),
|
|
690
|
+
},
|
|
691
|
+
selection: input.deterministicSelection,
|
|
692
|
+
budget: input.input.budget,
|
|
693
|
+
focus_paths: input.focusPaths,
|
|
694
|
+
caller_search_terms: input.input.search_terms,
|
|
695
|
+
coveragePolicy: input.coveragePolicy,
|
|
696
|
+
...(input.delegatePacketCaps == null
|
|
697
|
+
? {}
|
|
698
|
+
: { caps: input.delegatePacketCaps }),
|
|
699
|
+
};
|
|
700
|
+
const candidateFallbackCount = input.deterministicSelection.selected.length;
|
|
701
|
+
let curatorMs = null;
|
|
702
|
+
let finalizerMs = null;
|
|
703
|
+
let delegatePacketSize = 0;
|
|
704
|
+
// Compaction counters from the last-built packet (finalizer overrides
|
|
705
|
+
// curator) and bounded T3 evidence warnings, surfaced additively.
|
|
706
|
+
let delegatePacketCompaction;
|
|
707
|
+
const packetEvidenceWarnings = [];
|
|
708
|
+
const recordPacketEvidenceWarning = (warning) => {
|
|
709
|
+
if (warning == null ||
|
|
710
|
+
packetEvidenceWarnings.some((existing) => existing.code === warning.code)) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
packetEvidenceWarnings.push({
|
|
714
|
+
code: warning.code,
|
|
715
|
+
message: warning.message,
|
|
716
|
+
});
|
|
717
|
+
};
|
|
718
|
+
const currentDelegateObservability = () => ({
|
|
719
|
+
curator_ms: curatorMs,
|
|
720
|
+
finalizer_ms: finalizerMs,
|
|
721
|
+
delegate_packet_size: delegatePacketSize,
|
|
722
|
+
...(delegatePacketCompaction == null
|
|
723
|
+
? {}
|
|
724
|
+
: { delegate_packet_compaction: delegatePacketCompaction }),
|
|
725
|
+
});
|
|
726
|
+
const sessionLogOwner = (executor) => input.sessionLogContext == null
|
|
727
|
+
? undefined
|
|
728
|
+
: {
|
|
729
|
+
family: "context",
|
|
730
|
+
contextRunId: input.sessionLogContext.contextRunId,
|
|
731
|
+
contextGeneration: input.sessionLogContext.contextGeneration,
|
|
732
|
+
executor,
|
|
733
|
+
executionId: `${executor}_${input.sessionLogContext.contextRunId.slice(-32)}`,
|
|
734
|
+
attempt: 1,
|
|
735
|
+
};
|
|
736
|
+
const requestedFocusOmissions = input.discovery.omissions.filter(isRequestedEvidenceOmission);
|
|
737
|
+
if (requestedFocusOmissions.length > 0) {
|
|
738
|
+
return handleDelegateRuntimeFailure({
|
|
739
|
+
delegate,
|
|
740
|
+
reason: `requested local evidence omitted: ${formatOmissionsForDelegateFailure(requestedFocusOmissions)}`,
|
|
741
|
+
deterministicSelection: input.deterministicSelection,
|
|
742
|
+
omissionCount: input.baselineOmissions.length +
|
|
743
|
+
input.discovery.omissions.length +
|
|
744
|
+
input.deterministicSelection.omissions.length,
|
|
745
|
+
candidateFallbackCount,
|
|
746
|
+
observability: currentDelegateObservability(),
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
await input.lifecycle?.transition("curating", { curatorStatus: "running" });
|
|
750
|
+
const curatorPacket = buildContextBuilderCuratorPacket(delegatePacketInput);
|
|
751
|
+
delegatePacketSize = curatorPacket.metrics.packet_estimated_tokens;
|
|
752
|
+
delegatePacketCompaction = curatorPacket.metrics.compaction;
|
|
753
|
+
recordPacketEvidenceWarning(curatorPacket.evidence_warning);
|
|
754
|
+
const curatorPacketRequestedOmissions = curatorPacket.omissions.filter(isRequestedEvidenceOmission);
|
|
755
|
+
if (curatorPacketRequestedOmissions.length > 0) {
|
|
756
|
+
return handleDelegateRuntimeFailure({
|
|
757
|
+
delegate,
|
|
758
|
+
reason: `requested local evidence omitted: ${formatOmissionsForDelegateFailure(curatorPacketRequestedOmissions)}`,
|
|
759
|
+
deterministicSelection: input.deterministicSelection,
|
|
760
|
+
omissions: curatorPacketRequestedOmissions,
|
|
761
|
+
omissionCount: input.baselineOmissions.length +
|
|
762
|
+
input.discovery.omissions.length +
|
|
763
|
+
input.deterministicSelection.omissions.length +
|
|
764
|
+
curatorPacketRequestedOmissions.length,
|
|
765
|
+
candidateFallbackCount,
|
|
766
|
+
observability: currentDelegateObservability(),
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
if (curatorPacket.candidates.length === 0) {
|
|
770
|
+
return handleDelegateRuntimeFailure({
|
|
771
|
+
delegate,
|
|
772
|
+
reason: `curator packet contained no selectable candidates; ${delegatePromptDiagnostic(curatorPacket)}`,
|
|
773
|
+
deterministicSelection: input.deterministicSelection,
|
|
774
|
+
omissionCount: input.baselineOmissions.length +
|
|
775
|
+
input.discovery.omissions.length +
|
|
776
|
+
input.deterministicSelection.omissions.length,
|
|
777
|
+
candidateFallbackCount,
|
|
778
|
+
observability: currentDelegateObservability(),
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
const curatorStartMs = Date.now();
|
|
782
|
+
const curatorSessionLogOwner = sessionLogOwner("curator");
|
|
783
|
+
const curatorCall = await runContextBuilderDelegateCall({
|
|
784
|
+
prompt: renderDelegatePrompt("curator", curatorPacket),
|
|
785
|
+
provider: delegate.curator,
|
|
786
|
+
registry: input.registry,
|
|
787
|
+
timeout_seconds: delegate.timeout_seconds,
|
|
788
|
+
max_response_chars: delegate.max_response_chars,
|
|
789
|
+
...(input.signal == null ? {} : { signal: input.signal }),
|
|
790
|
+
...(curatorSessionLogOwner == null
|
|
791
|
+
? {}
|
|
792
|
+
: { sessionLogOwner: curatorSessionLogOwner }),
|
|
793
|
+
});
|
|
794
|
+
curatorMs = Math.max(0, Date.now() - curatorStartMs);
|
|
795
|
+
if (!curatorCall.ok) {
|
|
796
|
+
return handleDelegateRuntimeFailure({
|
|
797
|
+
delegate,
|
|
798
|
+
reason: failureReasonFromCall("curator", curatorCall),
|
|
799
|
+
failureKind: curatorCall.reason,
|
|
800
|
+
deterministicSelection: input.deterministicSelection,
|
|
801
|
+
omissionCount: input.baselineOmissions.length +
|
|
802
|
+
input.discovery.omissions.length +
|
|
803
|
+
input.deterministicSelection.omissions.length,
|
|
804
|
+
candidateFallbackCount,
|
|
805
|
+
observability: currentDelegateObservability(),
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
let curatorProposal;
|
|
809
|
+
let curatorSelection;
|
|
810
|
+
try {
|
|
811
|
+
const validatedCuratorProposal = validateContextBuilderCuratorOutput(curatorCall.json, {
|
|
812
|
+
candidates: curatorPacket.candidates,
|
|
813
|
+
budget: input.input.budget,
|
|
814
|
+
allowSecrets: input.input.allow_secrets,
|
|
815
|
+
coveragePolicy: curatorPacket.coverage_policy,
|
|
816
|
+
});
|
|
817
|
+
curatorProposal = augmentCuratorProposalWithDeterministicBaseline(validatedCuratorProposal, curatorPacket, input.input.budget.max_files);
|
|
818
|
+
curatorSelection = reconstructContextBuilderDelegateSelection({
|
|
819
|
+
selected: curatorProposal.selected,
|
|
820
|
+
candidates: curatorPacket.candidates,
|
|
821
|
+
slices: input.discovery.slices,
|
|
822
|
+
tree: input.discovery.tree,
|
|
823
|
+
search: input.discovery.search,
|
|
824
|
+
budget: input.input.budget,
|
|
825
|
+
});
|
|
826
|
+
if (curatorSelection.omissions.length > 0 &&
|
|
827
|
+
curatorProposal !== validatedCuratorProposal) {
|
|
828
|
+
const rawCuratorSelection = reconstructContextBuilderDelegateSelection({
|
|
829
|
+
selected: validatedCuratorProposal.selected,
|
|
830
|
+
candidates: curatorPacket.candidates,
|
|
831
|
+
slices: input.discovery.slices,
|
|
832
|
+
tree: input.discovery.tree,
|
|
833
|
+
search: input.discovery.search,
|
|
834
|
+
budget: input.input.budget,
|
|
835
|
+
});
|
|
836
|
+
if (rawCuratorSelection.selected.length > 0 &&
|
|
837
|
+
rawCuratorSelection.omissions.length === 0) {
|
|
838
|
+
curatorProposal = {
|
|
839
|
+
...validatedCuratorProposal,
|
|
840
|
+
warnings: [
|
|
841
|
+
...validatedCuratorProposal.warnings,
|
|
842
|
+
"server-retained deterministic baseline exceeded local reconstruction budget; using curator-selected evidence only",
|
|
843
|
+
],
|
|
844
|
+
};
|
|
845
|
+
curatorSelection = rawCuratorSelection;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (curatorSelection.selected.length > 0 &&
|
|
849
|
+
curatorSelection.omissions.length > 0) {
|
|
850
|
+
curatorProposal = pruneDelegateProposalToLocalSelection(curatorProposal, curatorPacket.candidates, curatorSelection, "curator selection exceeded local reconstruction budget; using budget-pruned selected evidence only");
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
catch (err) {
|
|
854
|
+
if (err instanceof CancellationError) {
|
|
855
|
+
throw err;
|
|
856
|
+
}
|
|
857
|
+
return handleDelegateRuntimeFailure({
|
|
858
|
+
delegate,
|
|
859
|
+
reason: failureReasonFromError("curator_validation", err, delegatePromptDiagnostic(curatorPacket)),
|
|
860
|
+
deterministicSelection: input.deterministicSelection,
|
|
861
|
+
omissionCount: input.baselineOmissions.length +
|
|
862
|
+
input.discovery.omissions.length +
|
|
863
|
+
input.deterministicSelection.omissions.length,
|
|
864
|
+
candidateFallbackCount,
|
|
865
|
+
observability: currentDelegateObservability(),
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
if (curatorSelection.selected.length === 0) {
|
|
869
|
+
return handleDelegateRuntimeFailure({
|
|
870
|
+
delegate,
|
|
871
|
+
reason: "curator produced empty local selection",
|
|
872
|
+
deterministicSelection: input.deterministicSelection,
|
|
873
|
+
omissionCount: input.baselineOmissions.length +
|
|
874
|
+
input.discovery.omissions.length +
|
|
875
|
+
input.deterministicSelection.omissions.length,
|
|
876
|
+
candidateFallbackCount,
|
|
877
|
+
observability: currentDelegateObservability(),
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
if (!finalizerEnabled) {
|
|
881
|
+
const repairedSelection = repairDelegateSelection({
|
|
882
|
+
curatorSelection,
|
|
883
|
+
contentCandidates: input.discovery.content_candidates,
|
|
884
|
+
focusPaths: input.focusPaths,
|
|
885
|
+
budget: input.input.budget,
|
|
886
|
+
expandSelectedFiles: true,
|
|
887
|
+
});
|
|
888
|
+
const warnings = curatorProposal.warnings.map((message) => delegateWarning("delegate_curator_warning", message));
|
|
889
|
+
warnings.push(...packetEvidenceWarnings);
|
|
890
|
+
appendDelegateRepairWarnings(warnings, repairedSelection);
|
|
891
|
+
const openQuestions = curatorProposal.open_questions.map((question) => sanitizeDiagnosticText(question));
|
|
892
|
+
const omissionCount = input.baselineOmissions.length +
|
|
893
|
+
input.discovery.omissions.length +
|
|
894
|
+
repairedSelection.selection.omissions.length;
|
|
895
|
+
const delegateEvidenceCoverageFailure = delegateCoverageSelectionFailure(restoreDroppedDegenerateReviewTargetsForValidation({
|
|
896
|
+
preRepairSelected: curatorSelection.selected,
|
|
897
|
+
postRepairSelected: repairedSelection.selection.selected,
|
|
898
|
+
candidates: curatorPacket.candidates,
|
|
899
|
+
coveragePolicy: curatorPacket.coverage_policy,
|
|
900
|
+
}), curatorPacket.candidates, curatorPacket.coverage_policy);
|
|
901
|
+
if (delegateEvidenceCoverageFailure != null) {
|
|
902
|
+
return handleDelegateRuntimeFailure({
|
|
903
|
+
delegate,
|
|
904
|
+
reason: `post-repair evidence coverage failed: ${delegateEvidenceCoverageFailure}`,
|
|
905
|
+
deterministicSelection: input.deterministicSelection,
|
|
906
|
+
omissionCount,
|
|
907
|
+
candidateFallbackCount,
|
|
908
|
+
observability: currentDelegateObservability(),
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
// Re-verify required-focus presence against the actual returned selection.
|
|
912
|
+
// The curator-only branch does not fold required-focus ranges, so it checks
|
|
913
|
+
// whole-file presence here; range coverage is re-verified only in the
|
|
914
|
+
// finalizer branch, which folds requested focus evidence first.
|
|
915
|
+
const curatorOnlyCoverageFailure = postRepairRequiredFocusEvictionFailure({
|
|
916
|
+
focusPaths: input.focusPaths,
|
|
917
|
+
candidates: curatorPacket.candidates,
|
|
918
|
+
preRepairSelected: curatorSelection.selected,
|
|
919
|
+
postRepairSelected: repairedSelection.selection.selected,
|
|
920
|
+
});
|
|
921
|
+
if (curatorOnlyCoverageFailure != null) {
|
|
922
|
+
return handleDelegateRuntimeFailure({
|
|
923
|
+
delegate,
|
|
924
|
+
reason: `post-repair required-focus coverage failed: ${curatorOnlyCoverageFailure}`,
|
|
925
|
+
deterministicSelection: input.deterministicSelection,
|
|
926
|
+
omissionCount: input.baselineOmissions.length +
|
|
927
|
+
input.discovery.omissions.length +
|
|
928
|
+
input.deterministicSelection.omissions.length,
|
|
929
|
+
candidateFallbackCount,
|
|
930
|
+
observability: currentDelegateObservability(),
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
return {
|
|
934
|
+
selection: repairedSelection.selection,
|
|
935
|
+
warnings,
|
|
936
|
+
omissions: [],
|
|
937
|
+
openQuestions,
|
|
938
|
+
delegate: {
|
|
939
|
+
enabled: true,
|
|
940
|
+
status: "succeeded",
|
|
941
|
+
failure_policy: delegate.failure_policy,
|
|
942
|
+
fallback_used: false,
|
|
943
|
+
curator: { ...delegate.curator },
|
|
944
|
+
finalizer: delegateFinalizerReceiptMetadata(delegate.finalizer),
|
|
945
|
+
selected_count: repairedSelection.selection.selected.length,
|
|
946
|
+
warning_count: warnings.length,
|
|
947
|
+
omission_count: omissionCount,
|
|
948
|
+
},
|
|
949
|
+
observability: currentDelegateObservability(),
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
await input.lifecycle?.transition("finalizing", {
|
|
953
|
+
curatorStatus: "succeeded",
|
|
954
|
+
finalizerStatus: "running",
|
|
955
|
+
});
|
|
956
|
+
const finalizerPacket = buildContextBuilderFinalizerPacket({
|
|
957
|
+
...delegatePacketInput,
|
|
958
|
+
selection: curatorSelection,
|
|
959
|
+
curator_proposal: curatorProposal,
|
|
960
|
+
});
|
|
961
|
+
delegatePacketSize = finalizerPacket.metrics.packet_estimated_tokens;
|
|
962
|
+
delegatePacketCompaction = finalizerPacket.metrics.compaction;
|
|
963
|
+
recordPacketEvidenceWarning(finalizerPacket.evidence_warning);
|
|
964
|
+
const finalizerPacketRequestedOmissions = finalizerPacket.omissions.filter(isRequestedEvidenceOmission);
|
|
965
|
+
if (finalizerPacketRequestedOmissions.length > 0) {
|
|
966
|
+
return handleDelegateRuntimeFailure({
|
|
967
|
+
delegate,
|
|
968
|
+
reason: `requested local evidence omitted: ${formatOmissionsForDelegateFailure(finalizerPacketRequestedOmissions)}`,
|
|
969
|
+
deterministicSelection: input.deterministicSelection,
|
|
970
|
+
omissions: finalizerPacketRequestedOmissions,
|
|
971
|
+
omissionCount: input.baselineOmissions.length +
|
|
972
|
+
input.discovery.omissions.length +
|
|
973
|
+
input.deterministicSelection.omissions.length +
|
|
974
|
+
finalizerPacketRequestedOmissions.length,
|
|
975
|
+
candidateFallbackCount,
|
|
976
|
+
observability: currentDelegateObservability(),
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
if ((finalizerPacket.curator_proposal?.selected.length ?? 0) === 0) {
|
|
980
|
+
return handleDelegateRuntimeFailure({
|
|
981
|
+
delegate,
|
|
982
|
+
reason: `finalizer packet lost curator proposal selection; ${delegatePromptDiagnostic(finalizerPacket)}`,
|
|
983
|
+
deterministicSelection: input.deterministicSelection,
|
|
984
|
+
omissionCount: input.baselineOmissions.length +
|
|
985
|
+
input.discovery.omissions.length +
|
|
986
|
+
input.deterministicSelection.omissions.length,
|
|
987
|
+
candidateFallbackCount,
|
|
988
|
+
observability: currentDelegateObservability(),
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
const finalizerStartMs = Date.now();
|
|
992
|
+
const finalizerSessionLogOwner = sessionLogOwner("finalizer");
|
|
993
|
+
const finalizerCall = await runContextBuilderDelegateCall({
|
|
994
|
+
prompt: renderDelegatePrompt("finalizer", finalizerPacket, {
|
|
995
|
+
minScore: delegate.finalizer.min_score,
|
|
996
|
+
}),
|
|
997
|
+
provider: delegate.finalizer,
|
|
998
|
+
registry: input.registry,
|
|
999
|
+
timeout_seconds: delegate.timeout_seconds,
|
|
1000
|
+
max_response_chars: delegate.max_response_chars,
|
|
1001
|
+
...(input.signal == null ? {} : { signal: input.signal }),
|
|
1002
|
+
...(finalizerSessionLogOwner == null
|
|
1003
|
+
? {}
|
|
1004
|
+
: { sessionLogOwner: finalizerSessionLogOwner }),
|
|
1005
|
+
});
|
|
1006
|
+
finalizerMs = Math.max(0, Date.now() - finalizerStartMs);
|
|
1007
|
+
if (!finalizerCall.ok) {
|
|
1008
|
+
return handleDelegateRuntimeFailure({
|
|
1009
|
+
delegate,
|
|
1010
|
+
reason: failureReasonFromCall("finalizer", finalizerCall),
|
|
1011
|
+
failureKind: finalizerCall.reason,
|
|
1012
|
+
deterministicSelection: input.deterministicSelection,
|
|
1013
|
+
omissionCount: input.baselineOmissions.length +
|
|
1014
|
+
input.discovery.omissions.length +
|
|
1015
|
+
input.deterministicSelection.omissions.length,
|
|
1016
|
+
candidateFallbackCount,
|
|
1017
|
+
observability: currentDelegateObservability(),
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
try {
|
|
1021
|
+
const finalizerProposal = validateContextBuilderFinalizerOutput(finalizerCall.json, {
|
|
1022
|
+
candidates: finalizerPacket.candidates,
|
|
1023
|
+
budget: input.input.budget,
|
|
1024
|
+
minScore: delegate.finalizer.min_score,
|
|
1025
|
+
allowSecrets: input.input.allow_secrets,
|
|
1026
|
+
coveragePolicy: finalizerPacket.coverage_policy,
|
|
1027
|
+
});
|
|
1028
|
+
if (finalizerProposal.omissions.length > 0) {
|
|
1029
|
+
return handleDelegateRuntimeFailure({
|
|
1030
|
+
delegate,
|
|
1031
|
+
reason: `finalizer reported omissions: ${finalizerProposal.omissions.join("; ")}`,
|
|
1032
|
+
deterministicSelection: input.deterministicSelection,
|
|
1033
|
+
omissionCount: input.baselineOmissions.length +
|
|
1034
|
+
input.discovery.omissions.length +
|
|
1035
|
+
input.deterministicSelection.omissions.length +
|
|
1036
|
+
finalizerProposal.omissions.length,
|
|
1037
|
+
candidateFallbackCount,
|
|
1038
|
+
observability: currentDelegateObservability(),
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
const foldedFinalizerSelection = foldRequiredFocusEvidenceIntoSelection(finalizerProposal.selected, requiredFocusEvidenceSelections({
|
|
1042
|
+
focusPaths: input.focusPaths,
|
|
1043
|
+
candidates: finalizerPacket.candidates,
|
|
1044
|
+
}));
|
|
1045
|
+
const finalSelection = reconstructContextBuilderDelegateSelection({
|
|
1046
|
+
selected: foldedFinalizerSelection.selected,
|
|
1047
|
+
candidates: finalizerPacket.candidates,
|
|
1048
|
+
slices: input.discovery.slices,
|
|
1049
|
+
tree: input.discovery.tree,
|
|
1050
|
+
search: input.discovery.search,
|
|
1051
|
+
budget: input.input.budget,
|
|
1052
|
+
});
|
|
1053
|
+
if (finalSelection.selected.length === 0) {
|
|
1054
|
+
return handleDelegateRuntimeFailure({
|
|
1055
|
+
delegate,
|
|
1056
|
+
reason: "finalizer produced empty local selection",
|
|
1057
|
+
deterministicSelection: input.deterministicSelection,
|
|
1058
|
+
omissionCount: input.baselineOmissions.length +
|
|
1059
|
+
input.discovery.omissions.length +
|
|
1060
|
+
input.deterministicSelection.omissions.length,
|
|
1061
|
+
candidateFallbackCount,
|
|
1062
|
+
observability: currentDelegateObservability(),
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
// A per-file reconstruction-budget overrun no longer fails the entire
|
|
1066
|
+
// delegate selection. Over-budget candidates (e.g. large generated/i18n
|
|
1067
|
+
// catalogs the finalizer over-selected) are dropped while the rest of the
|
|
1068
|
+
// curated bundle is retained. Completeness is re-checked against the
|
|
1069
|
+
// MATERIALIZED (post-budget) selection, so a dropped REQUIRED-FOCUS file
|
|
1070
|
+
// still fails closed below, while non-required oversized files are pruned
|
|
1071
|
+
// and surfaced as a warning further down rather than collapsing the bundle.
|
|
1072
|
+
// Required-focus coverage is additionally re-verified against the
|
|
1073
|
+
// post-repair selection below, so eviction/compaction during repair also
|
|
1074
|
+
// fails closed.
|
|
1075
|
+
const completenessFailure = delegateCompletenessFailure({
|
|
1076
|
+
focusPaths: input.focusPaths,
|
|
1077
|
+
candidates: finalizerPacket.candidates,
|
|
1078
|
+
finalizerSelected: materializeFinalizerSelection(finalSelection.selected, finalizerPacket.candidates),
|
|
1079
|
+
curatorWarnings: curatorProposal.warnings,
|
|
1080
|
+
finalizerWarnings: finalizerProposal.warnings,
|
|
1081
|
+
});
|
|
1082
|
+
if (completenessFailure != null) {
|
|
1083
|
+
return handleDelegateRuntimeFailure({
|
|
1084
|
+
delegate,
|
|
1085
|
+
reason: completenessFailure,
|
|
1086
|
+
deterministicSelection: input.deterministicSelection,
|
|
1087
|
+
omissionCount: input.baselineOmissions.length +
|
|
1088
|
+
input.discovery.omissions.length +
|
|
1089
|
+
input.deterministicSelection.omissions.length,
|
|
1090
|
+
candidateFallbackCount,
|
|
1091
|
+
observability: currentDelegateObservability(),
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
const repairedSelection = repairDelegateSelection({
|
|
1095
|
+
curatorSelection: finalSelection,
|
|
1096
|
+
contentCandidates: input.discovery.content_candidates,
|
|
1097
|
+
focusPaths: input.focusPaths,
|
|
1098
|
+
budget: input.input.budget,
|
|
1099
|
+
expandSelectedFiles: false,
|
|
1100
|
+
});
|
|
1101
|
+
const delegateEvidenceCoverageFailure = delegateCoverageSelectionFailure(repairedSelection.selection.selected, finalizerPacket.candidates, finalizerPacket.coverage_policy);
|
|
1102
|
+
if (delegateEvidenceCoverageFailure != null) {
|
|
1103
|
+
return handleDelegateRuntimeFailure({
|
|
1104
|
+
delegate,
|
|
1105
|
+
reason: `post-repair evidence coverage failed: ${delegateEvidenceCoverageFailure}`,
|
|
1106
|
+
deterministicSelection: input.deterministicSelection,
|
|
1107
|
+
omissionCount: input.baselineOmissions.length +
|
|
1108
|
+
input.discovery.omissions.length +
|
|
1109
|
+
repairedSelection.selection.omissions.length,
|
|
1110
|
+
candidateFallbackCount,
|
|
1111
|
+
observability: currentDelegateObservability(),
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
// Re-verify required-focus completeness against the actual returned
|
|
1115
|
+
// selection. This keeps the fallback contract tied to the final materialized
|
|
1116
|
+
// packet rather than the delegate's requested selection.
|
|
1117
|
+
const postRepairCoverageFailure = requiredFocusCoverageFailure({
|
|
1118
|
+
focusPaths: input.focusPaths,
|
|
1119
|
+
candidates: finalizerPacket.candidates,
|
|
1120
|
+
finalizerSelected: materializeFinalizerSelection(repairedSelection.selection.selected, finalizerPacket.candidates),
|
|
1121
|
+
});
|
|
1122
|
+
if (postRepairCoverageFailure != null) {
|
|
1123
|
+
return handleDelegateRuntimeFailure({
|
|
1124
|
+
delegate,
|
|
1125
|
+
reason: `post-repair required-focus coverage failed: ${postRepairCoverageFailure}`,
|
|
1126
|
+
deterministicSelection: input.deterministicSelection,
|
|
1127
|
+
omissionCount: input.baselineOmissions.length +
|
|
1128
|
+
input.discovery.omissions.length +
|
|
1129
|
+
input.deterministicSelection.omissions.length,
|
|
1130
|
+
candidateFallbackCount,
|
|
1131
|
+
observability: currentDelegateObservability(),
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
const warnings = [
|
|
1135
|
+
...curatorProposal.warnings.map((message) => delegateWarning("delegate_curator_warning", message)),
|
|
1136
|
+
...finalizerProposal.warnings.map((message) => delegateWarning("delegate_finalizer_warning", message)),
|
|
1137
|
+
];
|
|
1138
|
+
if (foldedFinalizerSelection.foldedPaths.length > 0) {
|
|
1139
|
+
warnings.push(delegateWarning("delegate_finalizer_warning", `finalizer omitted requested local evidence; server folded it back into the selection: ${boundContextBuilderPathList(foldedFinalizerSelection.foldedPaths).join(", ")}`));
|
|
1140
|
+
}
|
|
1141
|
+
if (finalSelection.omissions.length > 0) {
|
|
1142
|
+
warnings.push(delegateWarning("delegate_finalizer_warning", `finalizer selection pruned to fit the local reconstruction budget; omitted: ${finalSelection.omissions
|
|
1143
|
+
.map(formatOmissionForDelegateFailure)
|
|
1144
|
+
.join("; ")}`));
|
|
1145
|
+
}
|
|
1146
|
+
warnings.push(...packetEvidenceWarnings);
|
|
1147
|
+
appendDelegateRepairWarnings(warnings, repairedSelection);
|
|
1148
|
+
const openQuestions = curatorProposal.open_questions.map((question) => sanitizeDiagnosticText(question));
|
|
1149
|
+
const omissionCount = input.baselineOmissions.length +
|
|
1150
|
+
input.discovery.omissions.length +
|
|
1151
|
+
repairedSelection.selection.omissions.length;
|
|
1152
|
+
return {
|
|
1153
|
+
selection: repairedSelection.selection,
|
|
1154
|
+
warnings,
|
|
1155
|
+
omissions: [],
|
|
1156
|
+
openQuestions,
|
|
1157
|
+
delegate: {
|
|
1158
|
+
enabled: true,
|
|
1159
|
+
status: "succeeded",
|
|
1160
|
+
failure_policy: delegate.failure_policy,
|
|
1161
|
+
fallback_used: false,
|
|
1162
|
+
curator: { ...delegate.curator },
|
|
1163
|
+
finalizer: delegateFinalizerReceiptMetadata(delegate.finalizer),
|
|
1164
|
+
finalizer_score: finalizerProposal.score,
|
|
1165
|
+
selected_count: repairedSelection.selection.selected.length,
|
|
1166
|
+
warning_count: warnings.length,
|
|
1167
|
+
omission_count: omissionCount,
|
|
1168
|
+
},
|
|
1169
|
+
observability: currentDelegateObservability(),
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
catch (err) {
|
|
1173
|
+
if (err instanceof CancellationError) {
|
|
1174
|
+
throw err;
|
|
1175
|
+
}
|
|
1176
|
+
if (err instanceof ExecutionError) {
|
|
1177
|
+
throw err;
|
|
1178
|
+
}
|
|
1179
|
+
return handleDelegateRuntimeFailure({
|
|
1180
|
+
delegate,
|
|
1181
|
+
reason: failureReasonFromError("finalizer_validation", err, `${delegatePromptDiagnostic(finalizerPacket)}${finalizerScoreDiagnostic(finalizerCall.json, delegate.finalizer.min_score)}`),
|
|
1182
|
+
deterministicSelection: input.deterministicSelection,
|
|
1183
|
+
omissionCount: input.baselineOmissions.length +
|
|
1184
|
+
input.discovery.omissions.length +
|
|
1185
|
+
input.deterministicSelection.omissions.length,
|
|
1186
|
+
candidateFallbackCount,
|
|
1187
|
+
observability: currentDelegateObservability(),
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
function restoreDroppedDegenerateReviewTargetsForValidation(input) {
|
|
1192
|
+
const targetKeys = new Set((input.coveragePolicy.obligations?.targets ?? []).flatMap((target) => {
|
|
1193
|
+
const candidate = input.candidates.find((item) => item.id === target.candidate_id);
|
|
1194
|
+
return candidate == null
|
|
1195
|
+
? []
|
|
1196
|
+
: [`${candidate.root_index}\u0000${candidate.path}`];
|
|
1197
|
+
}));
|
|
1198
|
+
if (targetKeys.size === 0) {
|
|
1199
|
+
return [...input.postRepairSelected];
|
|
1200
|
+
}
|
|
1201
|
+
const postRepairKeys = new Set(input.postRepairSelected.map((file) => selectedFileKey(file)));
|
|
1202
|
+
const droppedDegenerateTargets = input.preRepairSelected.filter((file) => {
|
|
1203
|
+
const key = selectedFileKey(file);
|
|
1204
|
+
return (targetKeys.has(key) &&
|
|
1205
|
+
!postRepairKeys.has(key) &&
|
|
1206
|
+
file.content.trim().length === 0);
|
|
1207
|
+
});
|
|
1208
|
+
return [...input.postRepairSelected, ...droppedDegenerateTargets];
|
|
1209
|
+
}
|
|
1210
|
+
function repairDelegateSelection(input) {
|
|
1211
|
+
const focusExpanded = input.expandSelectedFiles
|
|
1212
|
+
? expandSelectedFocusFilesToFullCandidates(input.curatorSelection, input.contentCandidates, input.focusPaths, input.budget)
|
|
1213
|
+
: { selection: input.curatorSelection, expandedCount: 0 };
|
|
1214
|
+
const smallSlicesExpanded = input.expandSelectedFiles
|
|
1215
|
+
? expandSelectedSmallFilesToFullCandidates(focusExpanded.selection, input.contentCandidates, input.focusPaths, input.budget)
|
|
1216
|
+
: focusExpanded;
|
|
1217
|
+
const emptyDropped = dropEmptySelectedFiles(smallSlicesExpanded.selection);
|
|
1218
|
+
return {
|
|
1219
|
+
selection: emptyDropped.selection,
|
|
1220
|
+
expandedCount: focusExpanded.expandedCount + smallSlicesExpanded.expandedCount,
|
|
1221
|
+
droppedDegenerateCount: emptyDropped.droppedCount,
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
function appendDelegateRepairWarnings(warnings, repair) {
|
|
1225
|
+
if (repair.expandedCount > 0) {
|
|
1226
|
+
warnings.push(delegateWarning("delegate_curator_warning", `server-expanded ${repair.expandedCount} selected file(s) to full context`));
|
|
1227
|
+
}
|
|
1228
|
+
if (repair.droppedDegenerateCount > 0) {
|
|
1229
|
+
warnings.push(delegateWarning("delegate_curator_warning", `server-dropped ${repair.droppedDegenerateCount} degenerate selected slice(s)`));
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
function expandSelectedFocusFilesToFullCandidates(selection, candidates, focusPaths, budget) {
|
|
1233
|
+
const focusKeys = new Set(focusPaths
|
|
1234
|
+
.filter((focus) => focus.kind === "file")
|
|
1235
|
+
.map((focus) => `${focus.root_index}\u0000${focus.root_relative_path}`));
|
|
1236
|
+
if (focusKeys.size === 0) {
|
|
1237
|
+
return { selection, expandedCount: 0 };
|
|
1238
|
+
}
|
|
1239
|
+
const candidatesByKey = new Map(candidates.map((candidate) => [selectedFileKey(candidate), candidate]));
|
|
1240
|
+
let totalBytes = selection.metrics.bytes_selected;
|
|
1241
|
+
let totalTokens = selection.metrics.estimated_tokens;
|
|
1242
|
+
let expandedCount = 0;
|
|
1243
|
+
const selected = selection.selected.map((file) => {
|
|
1244
|
+
const key = selectedFileKey(file);
|
|
1245
|
+
if (!focusKeys.has(key)) {
|
|
1246
|
+
return file;
|
|
1247
|
+
}
|
|
1248
|
+
if (file.ranges.length !== 1) {
|
|
1249
|
+
return file;
|
|
1250
|
+
}
|
|
1251
|
+
const candidate = candidatesByKey.get(key);
|
|
1252
|
+
if (candidate == null) {
|
|
1253
|
+
return file;
|
|
1254
|
+
}
|
|
1255
|
+
const fullFile = selectedFileFromCandidate(candidate);
|
|
1256
|
+
if (fullFile.bytes <= file.bytes || fileCoversCandidate(file, candidate)) {
|
|
1257
|
+
return file;
|
|
1258
|
+
}
|
|
1259
|
+
const totalsWithoutFile = {
|
|
1260
|
+
totalBytes: totalBytes - file.bytes,
|
|
1261
|
+
totalTokens: totalTokens - file.estimated_tokens,
|
|
1262
|
+
};
|
|
1263
|
+
if (fullFile.bytes > budget.max_bytes_per_file ||
|
|
1264
|
+
totalsWithoutFile.totalBytes + fullFile.bytes > budget.max_total_bytes ||
|
|
1265
|
+
totalsWithoutFile.totalTokens + fullFile.estimated_tokens >
|
|
1266
|
+
budget.target_estimated_tokens) {
|
|
1267
|
+
return file;
|
|
1268
|
+
}
|
|
1269
|
+
totalBytes = totalsWithoutFile.totalBytes + fullFile.bytes;
|
|
1270
|
+
totalTokens = totalsWithoutFile.totalTokens + fullFile.estimated_tokens;
|
|
1271
|
+
expandedCount += 1;
|
|
1272
|
+
return { ...fullFile, source: file.source };
|
|
1273
|
+
});
|
|
1274
|
+
if (expandedCount === 0) {
|
|
1275
|
+
return { selection, expandedCount };
|
|
1276
|
+
}
|
|
1277
|
+
return {
|
|
1278
|
+
expandedCount,
|
|
1279
|
+
selection: {
|
|
1280
|
+
...selection,
|
|
1281
|
+
selected,
|
|
1282
|
+
metrics: {
|
|
1283
|
+
files_selected: selected.length,
|
|
1284
|
+
files_omitted: selection.metrics.files_omitted,
|
|
1285
|
+
bytes_selected: totalBytes,
|
|
1286
|
+
estimated_tokens: totalTokens,
|
|
1287
|
+
truncated: selection.metrics.truncated,
|
|
1288
|
+
},
|
|
1289
|
+
},
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
const SMALL_SELECTED_SLICE_FULL_EXPANSION_MAX_BYTES = 64_000;
|
|
1293
|
+
function expandSelectedSmallFilesToFullCandidates(selection, candidates, focusPaths, budget) {
|
|
1294
|
+
const candidatesByKey = new Map(candidates.map((candidate) => [selectedFileKey(candidate), candidate]));
|
|
1295
|
+
const focusKeys = new Set(focusPaths
|
|
1296
|
+
.filter((focus) => focus.kind === "file")
|
|
1297
|
+
.map((focus) => `${focus.root_index}\u0000${focus.root_relative_path}`));
|
|
1298
|
+
let totalBytes = selection.metrics.bytes_selected;
|
|
1299
|
+
let totalTokens = selection.metrics.estimated_tokens;
|
|
1300
|
+
let expandedCount = 0;
|
|
1301
|
+
const selected = selection.selected.map((file) => {
|
|
1302
|
+
if (focusKeys.has(selectedFileKey(file)) || file.ranges.length !== 1) {
|
|
1303
|
+
return file;
|
|
1304
|
+
}
|
|
1305
|
+
const candidate = candidatesByKey.get(selectedFileKey(file));
|
|
1306
|
+
if (candidate == null || fileCoversCandidate(file, candidate)) {
|
|
1307
|
+
return file;
|
|
1308
|
+
}
|
|
1309
|
+
if (candidate.byte_length > SMALL_SELECTED_SLICE_FULL_EXPANSION_MAX_BYTES) {
|
|
1310
|
+
return file;
|
|
1311
|
+
}
|
|
1312
|
+
const fullFile = selectedFileFromCandidate(candidate);
|
|
1313
|
+
if (fullFile.bytes <= file.bytes) {
|
|
1314
|
+
return file;
|
|
1315
|
+
}
|
|
1316
|
+
const totalsWithoutFile = {
|
|
1317
|
+
totalBytes: totalBytes - file.bytes,
|
|
1318
|
+
totalTokens: totalTokens - file.estimated_tokens,
|
|
1319
|
+
};
|
|
1320
|
+
if (fullFile.bytes > budget.max_bytes_per_file ||
|
|
1321
|
+
totalsWithoutFile.totalBytes + fullFile.bytes > budget.max_total_bytes ||
|
|
1322
|
+
totalsWithoutFile.totalTokens + fullFile.estimated_tokens >
|
|
1323
|
+
budget.target_estimated_tokens) {
|
|
1324
|
+
return file;
|
|
1325
|
+
}
|
|
1326
|
+
totalBytes = totalsWithoutFile.totalBytes + fullFile.bytes;
|
|
1327
|
+
totalTokens = totalsWithoutFile.totalTokens + fullFile.estimated_tokens;
|
|
1328
|
+
expandedCount += 1;
|
|
1329
|
+
return { ...fullFile, source: file.source };
|
|
1330
|
+
});
|
|
1331
|
+
if (expandedCount === 0) {
|
|
1332
|
+
return { selection, expandedCount };
|
|
1333
|
+
}
|
|
1334
|
+
return {
|
|
1335
|
+
expandedCount,
|
|
1336
|
+
selection: {
|
|
1337
|
+
...selection,
|
|
1338
|
+
selected,
|
|
1339
|
+
metrics: {
|
|
1340
|
+
files_selected: selected.length,
|
|
1341
|
+
files_omitted: selection.metrics.files_omitted,
|
|
1342
|
+
bytes_selected: totalBytes,
|
|
1343
|
+
estimated_tokens: totalTokens,
|
|
1344
|
+
truncated: selection.metrics.truncated,
|
|
1345
|
+
},
|
|
1346
|
+
},
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
function dropEmptySelectedFiles(selection) {
|
|
1350
|
+
const selected = selection.selected.filter((file) => file.content.trim().length > 0);
|
|
1351
|
+
const droppedCount = selection.selected.length - selected.length;
|
|
1352
|
+
if (droppedCount === 0) {
|
|
1353
|
+
return { selection, droppedCount };
|
|
1354
|
+
}
|
|
1355
|
+
return {
|
|
1356
|
+
selection: selectionWithSelectedFiles(selection, selected),
|
|
1357
|
+
droppedCount,
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
function selectionWithSelectedFiles(selection, selected) {
|
|
1361
|
+
const bytes = selected.reduce((total, file) => total + file.bytes, 0);
|
|
1362
|
+
const tokens = selected.reduce((total, file) => total + file.estimated_tokens, 0);
|
|
1363
|
+
return {
|
|
1364
|
+
...selection,
|
|
1365
|
+
selected,
|
|
1366
|
+
metrics: {
|
|
1367
|
+
files_selected: selected.length,
|
|
1368
|
+
files_omitted: selection.metrics.files_omitted,
|
|
1369
|
+
bytes_selected: bytes,
|
|
1370
|
+
estimated_tokens: tokens,
|
|
1371
|
+
truncated: selection.metrics.truncated,
|
|
1372
|
+
},
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
function selectedFileKey(file) {
|
|
1376
|
+
return `${file.root_index}\u0000${file.path}`;
|
|
1377
|
+
}
|
|
1378
|
+
function selectedFileFromCandidate(candidate) {
|
|
1379
|
+
const bytes = Buffer.byteLength(candidate.content, "utf8");
|
|
1380
|
+
return {
|
|
1381
|
+
root_index: candidate.root_index,
|
|
1382
|
+
path: candidate.path,
|
|
1383
|
+
ranges: [{ start_line: 1, end_line: Math.max(1, candidate.line_count) }],
|
|
1384
|
+
content: candidate.content,
|
|
1385
|
+
bytes,
|
|
1386
|
+
estimated_tokens: estimateContextBuilderTokensFromUtf8Bytes(bytes),
|
|
1387
|
+
source: "search",
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
function fileCoversCandidate(file, candidate) {
|
|
1391
|
+
return file.ranges.some((range) => range.start_line <= 1 &&
|
|
1392
|
+
range.end_line >= Math.max(1, candidate.line_count));
|
|
1393
|
+
}
|
|
1394
|
+
function handleDelegateRuntimeFailure(input) {
|
|
1395
|
+
// Delegate-failure reasons aggregate many per-entry details (each already
|
|
1396
|
+
// individually sanitized/redacted where assembled); the wider marked-
|
|
1397
|
+
// truncation bound keeps the tail from being silently dropped.
|
|
1398
|
+
const failureReason = sanitizeDelegateFailureText(input.reason);
|
|
1399
|
+
if (input.delegate.failure_policy === "fail") {
|
|
1400
|
+
const error = new ExecutionError(`context_builder delegate failed: ${failureReason}`);
|
|
1401
|
+
if (input.failureKind === "timeout") {
|
|
1402
|
+
error.failureClass = "transient";
|
|
1403
|
+
}
|
|
1404
|
+
throw error;
|
|
1405
|
+
}
|
|
1406
|
+
const warnings = [
|
|
1407
|
+
delegateWarning("delegate_fallback", `Delegate curation failed; deterministic selection was used. ${failureReason}`),
|
|
1408
|
+
];
|
|
1409
|
+
return {
|
|
1410
|
+
selection: input.deterministicSelection,
|
|
1411
|
+
warnings,
|
|
1412
|
+
omissions: [...(input.omissions ?? [])],
|
|
1413
|
+
openQuestions: [],
|
|
1414
|
+
delegate: {
|
|
1415
|
+
enabled: true,
|
|
1416
|
+
status: "partial",
|
|
1417
|
+
failure_policy: input.delegate.failure_policy,
|
|
1418
|
+
fallback_used: true,
|
|
1419
|
+
curator: { ...input.delegate.curator },
|
|
1420
|
+
finalizer: delegateFinalizerReceiptMetadata(input.delegate.finalizer),
|
|
1421
|
+
selected_count: input.candidateFallbackCount,
|
|
1422
|
+
warning_count: warnings.length,
|
|
1423
|
+
omission_count: input.omissionCount,
|
|
1424
|
+
failure_reason: failureReason,
|
|
1425
|
+
},
|
|
1426
|
+
observability: input.observability ?? {
|
|
1427
|
+
curator_ms: null,
|
|
1428
|
+
finalizer_ms: null,
|
|
1429
|
+
delegate_packet_size: 0,
|
|
1430
|
+
},
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
function formatOmissionForDelegateFailure(omission) {
|
|
1434
|
+
const paths = omission.paths == null || omission.paths.length === 0
|
|
1435
|
+
? "unknown focused path"
|
|
1436
|
+
: boundContextBuilderPathList(omission.paths).join(", ");
|
|
1437
|
+
return `${omission.reason} (${paths}): ${sanitizeDiagnosticText(omission.message)}`;
|
|
1438
|
+
}
|
|
1439
|
+
// Bounds for the aggregated delegate-failure omission list. Entries past the
|
|
1440
|
+
// cap are dropped with an explicit `(+N more omissions)` marker and the total
|
|
1441
|
+
// count always leads the message, so truncation is never silent.
|
|
1442
|
+
const DELEGATE_FAILURE_MAX_OMISSION_ENTRIES = 6;
|
|
1443
|
+
const DELEGATE_FAILURE_MAX_ENTRY_TEXT_LENGTH = 240;
|
|
1444
|
+
const DELEGATE_FAILURE_MAX_ENTRY_PATHS = 5;
|
|
1445
|
+
function formatOmissionsForDelegateFailure(omissions) {
|
|
1446
|
+
const total = omissions.length;
|
|
1447
|
+
const shown = omissions
|
|
1448
|
+
.slice(0, DELEGATE_FAILURE_MAX_OMISSION_ENTRIES)
|
|
1449
|
+
.map(formatOmissionEntryForDelegateFailure);
|
|
1450
|
+
const droppedSuffix = total > shown.length ? ` (+${total - shown.length} more omissions)` : "";
|
|
1451
|
+
return `${total} omission${total === 1 ? "" : "s"}: ${shown.join("; ")}${droppedSuffix}`;
|
|
1452
|
+
}
|
|
1453
|
+
function formatOmissionEntryForDelegateFailure(omission) {
|
|
1454
|
+
// Redaction happens per entry, BEFORE assembly into the aggregate message:
|
|
1455
|
+
// paths through boundContextBuilderPathList (secret-path redaction) and free
|
|
1456
|
+
// text through the marked bounded sanitizer, so no entry can smuggle secret
|
|
1457
|
+
// material into the joined reason and no entry ends in an unmarked "...".
|
|
1458
|
+
const allPaths = omission.paths ?? [];
|
|
1459
|
+
const shownPaths = boundContextBuilderPathList(allPaths, DELEGATE_FAILURE_MAX_ENTRY_PATHS);
|
|
1460
|
+
const pathText = shownPaths.length === 0
|
|
1461
|
+
? "unknown focused path"
|
|
1462
|
+
: allPaths.length > shownPaths.length
|
|
1463
|
+
? `${shownPaths.join(", ")} (+${allPaths.length - shownPaths.length} more paths)`
|
|
1464
|
+
: shownPaths.join(", ");
|
|
1465
|
+
return `${sanitizeDelegateFailureText(omission.reason, DELEGATE_FAILURE_MAX_ENTRY_TEXT_LENGTH)} (${pathText}): ${sanitizeDelegateFailureText(omission.message, DELEGATE_FAILURE_MAX_ENTRY_TEXT_LENGTH)}`;
|
|
1466
|
+
}
|
|
1467
|
+
function rangeCoveredByRanges(target, ranges) {
|
|
1468
|
+
let nextLine = target.start_line;
|
|
1469
|
+
for (const range of [...ranges].sort((left, right) => left.start_line - right.start_line || left.end_line - right.end_line)) {
|
|
1470
|
+
if (range.end_line < nextLine) {
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
if (range.start_line > nextLine) {
|
|
1474
|
+
return false;
|
|
1475
|
+
}
|
|
1476
|
+
nextLine = Math.max(nextLine, range.end_line + 1);
|
|
1477
|
+
if (nextLine > target.end_line) {
|
|
1478
|
+
return true;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
return false;
|
|
1482
|
+
}
|
|
1483
|
+
function pruneDelegateProposalToLocalSelection(proposal, candidates, selection, warning) {
|
|
1484
|
+
const candidatesByPath = new Map();
|
|
1485
|
+
for (const candidate of candidates) {
|
|
1486
|
+
candidatesByPath.set(`${candidate.root_index}:${candidate.path}`, candidate);
|
|
1487
|
+
}
|
|
1488
|
+
const selected = selection.selected.flatMap((file) => {
|
|
1489
|
+
const candidate = candidatesByPath.get(`${file.root_index}:${file.path}`);
|
|
1490
|
+
if (candidate == null) {
|
|
1491
|
+
return [];
|
|
1492
|
+
}
|
|
1493
|
+
const original = proposal.selected.find((item) => item.candidate_id === candidate.id);
|
|
1494
|
+
return [
|
|
1495
|
+
{
|
|
1496
|
+
candidate_id: candidate.id,
|
|
1497
|
+
ranges: file.ranges.map((range) => ({
|
|
1498
|
+
start_line: range.start_line,
|
|
1499
|
+
end_line: range.end_line,
|
|
1500
|
+
reason: "budget-pruned local selection range",
|
|
1501
|
+
})),
|
|
1502
|
+
reason: original?.reason ??
|
|
1503
|
+
"retained because it fit the local reconstruction budget",
|
|
1504
|
+
},
|
|
1505
|
+
];
|
|
1506
|
+
});
|
|
1507
|
+
return {
|
|
1508
|
+
...proposal,
|
|
1509
|
+
selected,
|
|
1510
|
+
warnings: proposal.warnings.includes(warning)
|
|
1511
|
+
? proposal.warnings
|
|
1512
|
+
: [...proposal.warnings, warning],
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
function delegateCompletenessFailure(input) {
|
|
1516
|
+
const materialWarnings = [
|
|
1517
|
+
...input.curatorWarnings,
|
|
1518
|
+
...input.finalizerWarnings,
|
|
1519
|
+
].filter((warning) => isMaterialDelegateEvidenceWarning(warning) &&
|
|
1520
|
+
!warningReferencesSelectedDelegateCandidate({
|
|
1521
|
+
warning,
|
|
1522
|
+
candidates: input.candidates,
|
|
1523
|
+
selected: input.finalizerSelected,
|
|
1524
|
+
}));
|
|
1525
|
+
if (materialWarnings.length > 0) {
|
|
1526
|
+
return `delegate warnings reported material evidence gaps: ${materialWarnings.join("; ")}`;
|
|
1527
|
+
}
|
|
1528
|
+
return requiredFocusCoverageFailure(input);
|
|
1529
|
+
}
|
|
1530
|
+
function requiredFocusCoverageFailure(input) {
|
|
1531
|
+
const required = requiredFocusEvidenceSelections(input);
|
|
1532
|
+
if (required.length === 0) {
|
|
1533
|
+
return undefined;
|
|
1534
|
+
}
|
|
1535
|
+
const selectedByCandidate = new Map();
|
|
1536
|
+
for (const selection of input.finalizerSelected) {
|
|
1537
|
+
selectedByCandidate.set(selection.candidate_id, [
|
|
1538
|
+
...(selectedByCandidate.get(selection.candidate_id) ?? []),
|
|
1539
|
+
...selection.ranges,
|
|
1540
|
+
]);
|
|
1541
|
+
}
|
|
1542
|
+
const missing = required.filter((selection) => {
|
|
1543
|
+
const selectedRanges = selectedByCandidate.get(selection.candidate_id) ?? [];
|
|
1544
|
+
return selection.ranges.some((range) => !rangeCoveredByRanges(range, selectedRanges));
|
|
1545
|
+
});
|
|
1546
|
+
if (missing.length === 0) {
|
|
1547
|
+
return undefined;
|
|
1548
|
+
}
|
|
1549
|
+
return `delegate finalizer omitted requested local evidence: ${missing
|
|
1550
|
+
.map((selection) => `${selection.path}#${selection.ranges
|
|
1551
|
+
.map((range) => `${range.start_line}-${range.end_line}`)
|
|
1552
|
+
.join(",")}`)
|
|
1553
|
+
.join("; ")}`;
|
|
1554
|
+
}
|
|
1555
|
+
// Curator-only post-repair guard. Unlike the finalizer branch, the curator-only
|
|
1556
|
+
// path does not fold required-focus ranges, so its baseline is the curator's
|
|
1557
|
+
// own pre-repair selection. Repair is allowed to:
|
|
1558
|
+
// - COMPACT a required-focus file's ranges to fit budget pressure (the file
|
|
1559
|
+
// stays in the bundle, just sliced — the documented focus-slicing behavior); and
|
|
1560
|
+
// - DROP a degenerate (whitespace-only) selected slice entirely (server-dropped).
|
|
1561
|
+
// The HIGH failure mode this guards is repair evicting a NON-degenerate
|
|
1562
|
+
// required-focus candidate ENTIRELY: a focus file with real content that the
|
|
1563
|
+
// curator selected and that survived reconstruction must not silently vanish
|
|
1564
|
+
// from the post-repair selection. Whole-file eviction of non-empty required
|
|
1565
|
+
// focus fails closed.
|
|
1566
|
+
function postRepairRequiredFocusEvictionFailure(input) {
|
|
1567
|
+
// Root-aware required-focus key set: a multi-root run can carry the same
|
|
1568
|
+
// relative path under two roots, only one of which is required focus. Keying
|
|
1569
|
+
// the required set by bare path would misclassify the non-required twin.
|
|
1570
|
+
const requiredKeys = new Set(input.candidates
|
|
1571
|
+
.filter((candidate) => candidate.focus_requested === true &&
|
|
1572
|
+
(candidate.required_focus_ranges?.length ?? 0) > 0 &&
|
|
1573
|
+
candidateMatchesFocusPath(candidate, input.focusPaths))
|
|
1574
|
+
.map((candidate) => `${candidate.root_index}:${candidate.path}`));
|
|
1575
|
+
if (requiredKeys.size === 0) {
|
|
1576
|
+
return undefined;
|
|
1577
|
+
}
|
|
1578
|
+
const selectedFileKey = (file) => `${file.root_index}:${file.path}`;
|
|
1579
|
+
// Pre-repair required-focus files that carried real (non-whitespace) content.
|
|
1580
|
+
// A whitespace-only focus slice is legitimately server-dropped by repair, so
|
|
1581
|
+
// exempt it — only its eviction is a no-op, never a hard failure.
|
|
1582
|
+
const preRepairRequired = new Map();
|
|
1583
|
+
for (const file of input.preRepairSelected) {
|
|
1584
|
+
if (requiredKeys.has(selectedFileKey(file)) &&
|
|
1585
|
+
file.ranges.length > 0 &&
|
|
1586
|
+
file.content.trim().length > 0) {
|
|
1587
|
+
preRepairRequired.set(selectedFileKey(file), file);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
const postRepairKeys = new Set(input.postRepairSelected
|
|
1591
|
+
.filter((file) => file.ranges.length > 0)
|
|
1592
|
+
.map(selectedFileKey));
|
|
1593
|
+
const evicted = [...preRepairRequired.values()]
|
|
1594
|
+
.filter((file) => !postRepairKeys.has(selectedFileKey(file)))
|
|
1595
|
+
.map((file) => `root ${file.root_index}: ${file.path}`);
|
|
1596
|
+
if (evicted.length === 0) {
|
|
1597
|
+
return undefined;
|
|
1598
|
+
}
|
|
1599
|
+
return `repair evicted required-focus evidence the curator selection carried: ${evicted.join("; ")}`;
|
|
1600
|
+
}
|
|
1601
|
+
export const postRepairRequiredFocusEvictionFailureForTest = postRepairRequiredFocusEvictionFailure;
|
|
1602
|
+
function warningReferencesSelectedDelegateCandidate(input) {
|
|
1603
|
+
const selectedCandidateIds = new Set(input.selected.map((selection) => selection.candidate_id));
|
|
1604
|
+
const selectedFiles = input.candidates
|
|
1605
|
+
.filter((candidate) => selectedCandidateIds.has(candidate.id))
|
|
1606
|
+
.map((candidate) => ({
|
|
1607
|
+
root_index: candidate.root_index,
|
|
1608
|
+
path: candidate.path,
|
|
1609
|
+
ranges: candidate.ranges.map((range) => ({
|
|
1610
|
+
start_line: range.start_line,
|
|
1611
|
+
end_line: range.end_line,
|
|
1612
|
+
})),
|
|
1613
|
+
content: "",
|
|
1614
|
+
bytes: candidate.size_bytes,
|
|
1615
|
+
estimated_tokens: candidate.estimated_tokens,
|
|
1616
|
+
source: candidate.discovery_reason === "focus_path" ? "focus" : "search",
|
|
1617
|
+
}));
|
|
1618
|
+
return isStaleSelectedContextOmissionWarning(input.warning.toLowerCase(), selectedFiles);
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Re-express a materialized (post-budget reconstruction) selection as
|
|
1622
|
+
* candidate-id-keyed proposal selections so completeness can be evaluated
|
|
1623
|
+
* against what actually survived the local reconstruction budget — not what
|
|
1624
|
+
* the finalizer merely requested. A candidate dropped by the budget guard is
|
|
1625
|
+
* absent here, so any required-focus range it carried reads as uncovered and
|
|
1626
|
+
* fails closed in delegateCompletenessFailure.
|
|
1627
|
+
*/
|
|
1628
|
+
function materializeFinalizerSelection(selected, candidates) {
|
|
1629
|
+
const candidateIdByKey = new Map();
|
|
1630
|
+
for (const candidate of candidates) {
|
|
1631
|
+
candidateIdByKey.set(`${candidate.root_index}:${candidate.path}`, candidate.id);
|
|
1632
|
+
}
|
|
1633
|
+
const materialized = [];
|
|
1634
|
+
for (const file of selected) {
|
|
1635
|
+
const candidateId = candidateIdByKey.get(`${file.root_index}:${file.path}`);
|
|
1636
|
+
if (candidateId == null) {
|
|
1637
|
+
continue;
|
|
1638
|
+
}
|
|
1639
|
+
materialized.push({
|
|
1640
|
+
candidate_id: candidateId,
|
|
1641
|
+
reason: "materialized post-budget selection",
|
|
1642
|
+
ranges: file.ranges.map((range) => ({
|
|
1643
|
+
start_line: range.start_line,
|
|
1644
|
+
end_line: range.end_line,
|
|
1645
|
+
reason: "materialized post-budget selection range",
|
|
1646
|
+
})),
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
return materialized;
|
|
1650
|
+
}
|
|
1651
|
+
function requiredFocusEvidenceSelections(input) {
|
|
1652
|
+
if (input.focusPaths.length === 0) {
|
|
1653
|
+
return [];
|
|
1654
|
+
}
|
|
1655
|
+
return input.candidates
|
|
1656
|
+
.filter((candidate) => candidate.focus_requested === true &&
|
|
1657
|
+
(candidate.required_focus_ranges?.length ?? 0) > 0 &&
|
|
1658
|
+
candidateMatchesFocusPath(candidate, input.focusPaths))
|
|
1659
|
+
.map((candidate) => ({
|
|
1660
|
+
candidate_id: candidate.id,
|
|
1661
|
+
path: candidate.path,
|
|
1662
|
+
ranges: candidate.required_focus_ranges.map((range) => ({
|
|
1663
|
+
start_line: range.start_line,
|
|
1664
|
+
end_line: range.end_line,
|
|
1665
|
+
})),
|
|
1666
|
+
}));
|
|
1667
|
+
}
|
|
1668
|
+
function foldRequiredFocusEvidenceIntoSelection(finalizerSelected, requiredFocus) {
|
|
1669
|
+
if (requiredFocus.length === 0) {
|
|
1670
|
+
return { selected: [...finalizerSelected], foldedPaths: [] };
|
|
1671
|
+
}
|
|
1672
|
+
const selectedByCandidate = new Map();
|
|
1673
|
+
for (const selection of finalizerSelected) {
|
|
1674
|
+
selectedByCandidate.set(selection.candidate_id, {
|
|
1675
|
+
candidate_id: selection.candidate_id,
|
|
1676
|
+
reason: selection.reason,
|
|
1677
|
+
ranges: selection.ranges.map((range) => ({ ...range })),
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
const foldedPaths = [];
|
|
1681
|
+
for (const required of requiredFocus) {
|
|
1682
|
+
const existing = selectedByCandidate.get(required.candidate_id);
|
|
1683
|
+
const existingRanges = existing?.ranges ?? [];
|
|
1684
|
+
const hasUncoveredRange = required.ranges.some((range) => !rangeCoveredByRanges(range, existingRanges));
|
|
1685
|
+
if (!hasUncoveredRange) {
|
|
1686
|
+
continue;
|
|
1687
|
+
}
|
|
1688
|
+
foldedPaths.push(required.path);
|
|
1689
|
+
const ranges = mergeLineRanges([
|
|
1690
|
+
...existingRanges.map((range) => ({
|
|
1691
|
+
start_line: range.start_line,
|
|
1692
|
+
end_line: range.end_line,
|
|
1693
|
+
})),
|
|
1694
|
+
...required.ranges,
|
|
1695
|
+
]).map((range) => ({
|
|
1696
|
+
start_line: range.start_line,
|
|
1697
|
+
end_line: range.end_line,
|
|
1698
|
+
reason: "server-folded requested local evidence omitted by finalizer",
|
|
1699
|
+
}));
|
|
1700
|
+
selectedByCandidate.set(required.candidate_id, {
|
|
1701
|
+
candidate_id: required.candidate_id,
|
|
1702
|
+
reason: existing?.reason ?? "requested local focus evidence preserved by server",
|
|
1703
|
+
ranges,
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
return { selected: [...selectedByCandidate.values()], foldedPaths };
|
|
1707
|
+
}
|
|
1708
|
+
function candidateMatchesFocusPath(candidate, focusPaths) {
|
|
1709
|
+
return focusPaths.some((focus) => {
|
|
1710
|
+
if (focus.root_index !== candidate.root_index) {
|
|
1711
|
+
return false;
|
|
1712
|
+
}
|
|
1713
|
+
if (focus.kind === "file") {
|
|
1714
|
+
return candidate.path === focus.root_relative_path;
|
|
1715
|
+
}
|
|
1716
|
+
return isSameRootRelativePathOrDescendant(candidate.path, focus.root_relative_path);
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
function isSameRootRelativePathOrDescendant(candidatePath, parentPath) {
|
|
1720
|
+
if (parentPath === "") {
|
|
1721
|
+
return true;
|
|
1722
|
+
}
|
|
1723
|
+
return candidatePath === parentPath || candidatePath.startsWith(`${parentPath}/`);
|
|
1724
|
+
}
|
|
1725
|
+
function isMaterialDelegateEvidenceWarning(message) {
|
|
1726
|
+
const text = message.toLowerCase();
|
|
1727
|
+
if (isNonBlockingDelegateWarning(text)) {
|
|
1728
|
+
return false;
|
|
1729
|
+
}
|
|
1730
|
+
return (/\b(required|requested|focus(?:ed)?)\b.{0,80}\b(missing|missed|incomplete|unavailable|insufficient|omitted|not selected|not selectable|unselectable|not present)\b/.test(text) ||
|
|
1731
|
+
/\b(missing|missed|incomplete|unavailable|insufficient|omitted|not selected|not selectable|unselectable|not present)\b.{0,80}\b(required|requested|focus(?:ed)?)\b/.test(text) ||
|
|
1732
|
+
/\bselected\b.{0,80}\b(not selectable|unselectable|not present)\b/.test(text) ||
|
|
1733
|
+
/\boutside (?:the )?selected ranges?\b/.test(text));
|
|
1734
|
+
}
|
|
1735
|
+
function isNonBlockingDelegateWarning(text) {
|
|
1736
|
+
return (/\bpacket evidence omitted full source excerpts\b/.test(text) ||
|
|
1737
|
+
/\bfull source excerpts? (?:were )?omitted\b/.test(text) ||
|
|
1738
|
+
/\bfull-file focus ranges?\b.{0,120}\b(?:excerpts?|full source)\b.{0,80}\b(?:omitted|truncated)\b/.test(text) ||
|
|
1739
|
+
/\bfull source excerpts?\b.{0,80}\bunavailable\b.{0,120}\bwhole-file focus ranges?\b/.test(text) ||
|
|
1740
|
+
isSecretSafetyOmissionWarning(text) ||
|
|
1741
|
+
/\bomitted from deterministic_selection\b.{0,120}\bbut selected\b/.test(text) ||
|
|
1742
|
+
isSpeculativeIrrelevantOmissionWarning(text) ||
|
|
1743
|
+
isFocusPreservingSkipWarning(text) ||
|
|
1744
|
+
isDeterministicExpansionWarning(text));
|
|
1745
|
+
}
|
|
1746
|
+
function isSecretSafetyOmissionWarning(text) {
|
|
1747
|
+
return (/\b(?:high-confidence secret|secret detection|secret safety|secret[- ]safe|redacted|redacted-safe|redaction)\b/.test(text) &&
|
|
1748
|
+
/\b(?:omitted|excluded|not selected|not requested|should not be requested|unavailable)\b/.test(text));
|
|
1749
|
+
}
|
|
1750
|
+
function isFocusPreservingSkipWarning(text) {
|
|
1751
|
+
return (/\b(?:unrelated|irrelevant|out-of-scope|low[- ]signal)\b.{0,120}\b(?:not selected|omitted|skipped)\b.{0,80}\bpreserve focus\b/.test(text) ||
|
|
1752
|
+
/\b(?:not selected|omitted|skipped)\b.{0,120}\b(?:unrelated|irrelevant|out-of-scope|low[- ]signal)\b.{0,80}\bpreserve focus\b/.test(text));
|
|
1753
|
+
}
|
|
1754
|
+
function isSpeculativeIrrelevantOmissionWarning(text) {
|
|
1755
|
+
if (!/\b(?:may|might|could)\b.{0,80}\b(?:omit|omits|omitted|missing)\b/.test(text)) {
|
|
1756
|
+
return false;
|
|
1757
|
+
}
|
|
1758
|
+
if (/\b(required|requested)\b/.test(text)) {
|
|
1759
|
+
return false;
|
|
1760
|
+
}
|
|
1761
|
+
return /\b(additional|broader|other|unrelated|irrelevant|repository|out-of-packet|outside (?:this|the) bounded packet)\b/.test(text);
|
|
1762
|
+
}
|
|
1763
|
+
function isDeterministicExpansionWarning(text) {
|
|
1764
|
+
return (/\bselection\b.{0,80}\b(?:intentionally\s+)?(?:exceeds|exceeded|expands beyond|expanded beyond|extends beyond|extended beyond)\b.{0,120}\bdeterministic\b.{0,160}\binsufficient\b/.test(text) &&
|
|
1765
|
+
!/\b(missing|missed|omitted|not selected|not selectable|unselectable|not present|unavailable)\b/.test(text));
|
|
1766
|
+
}
|
|
1767
|
+
function augmentCuratorProposalWithDeterministicBaseline(proposal, packet, maxSelections) {
|
|
1768
|
+
const deterministicSelected = packet.deterministic_selection?.selected ?? [];
|
|
1769
|
+
if (deterministicSelected.length === 0 ||
|
|
1770
|
+
proposal.selected.length >= maxSelections) {
|
|
1771
|
+
return proposal;
|
|
1772
|
+
}
|
|
1773
|
+
const selectedByCandidate = new Map();
|
|
1774
|
+
for (const selection of proposal.selected) {
|
|
1775
|
+
selectedByCandidate.set(selection.candidate_id, {
|
|
1776
|
+
candidate_id: selection.candidate_id,
|
|
1777
|
+
ranges: [...selection.ranges],
|
|
1778
|
+
reason: selection.reason,
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
let added = 0;
|
|
1782
|
+
for (const selection of deterministicSelected) {
|
|
1783
|
+
const existing = selectedByCandidate.get(selection.candidate_id);
|
|
1784
|
+
if (existing == null) {
|
|
1785
|
+
if (selectedByCandidate.size >= maxSelections) {
|
|
1786
|
+
break;
|
|
1787
|
+
}
|
|
1788
|
+
selectedByCandidate.set(selection.candidate_id, {
|
|
1789
|
+
candidate_id: selection.candidate_id,
|
|
1790
|
+
ranges: selection.ranges.map((range) => ({
|
|
1791
|
+
start_line: range.start_line,
|
|
1792
|
+
end_line: range.end_line,
|
|
1793
|
+
reason: "deterministic baseline retained because curator underselected local evidence",
|
|
1794
|
+
})),
|
|
1795
|
+
reason: "server-retained deterministic baseline candidate for finalizer review",
|
|
1796
|
+
});
|
|
1797
|
+
added += 1;
|
|
1798
|
+
continue;
|
|
1799
|
+
}
|
|
1800
|
+
const deterministicRanges = selection.ranges.map((range) => ({
|
|
1801
|
+
start_line: range.start_line,
|
|
1802
|
+
end_line: range.end_line,
|
|
1803
|
+
reason: "deterministic baseline range retained because curator expanded local evidence",
|
|
1804
|
+
}));
|
|
1805
|
+
if (totalSelectedRangeLines(deterministicRanges) < totalSelectedRangeLines(existing.ranges)) {
|
|
1806
|
+
existing.ranges = deterministicRanges;
|
|
1807
|
+
added += 1;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
if (added === 0) {
|
|
1811
|
+
return proposal;
|
|
1812
|
+
}
|
|
1813
|
+
return {
|
|
1814
|
+
selected: [...selectedByCandidate.values()],
|
|
1815
|
+
warnings: [
|
|
1816
|
+
...proposal.warnings,
|
|
1817
|
+
`curator selected fewer local candidates than the deterministic baseline; retained ${added} deterministic baseline candidate/range item(s) for finalizer review`,
|
|
1818
|
+
],
|
|
1819
|
+
open_questions: proposal.open_questions,
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
function totalSelectedRangeLines(ranges) {
|
|
1823
|
+
return ranges.reduce((total, range) => total + Math.max(0, range.end_line - range.start_line + 1), 0);
|
|
1824
|
+
}
|
|
1825
|
+
/**
|
|
1826
|
+
* Renders the server-enforced coverage obligations as explicit prompt rules.
|
|
1827
|
+
*
|
|
1828
|
+
* The obligations are already present in the serialized packet, but the packet
|
|
1829
|
+
* alone never told the delegate they were MANDATORY, so a schema-valid proposal
|
|
1830
|
+
* could miss one and be rejected by
|
|
1831
|
+
* {@link validateContextBuilderDelegateCoverageSelection}.
|
|
1832
|
+
*
|
|
1833
|
+
* Branch selection here tracks that validator's branch selection: it enters the
|
|
1834
|
+
* obligation branch on `obligations != null`, NOT on a non-empty target list.
|
|
1835
|
+
* Branching on `targets.length > 0` instead would render the satisfiable
|
|
1836
|
+
* evidence-class contract for a packet the validator rejects outright, i.e.
|
|
1837
|
+
* instruct the delegate to do something that provably cannot pass.
|
|
1838
|
+
*/
|
|
1839
|
+
function packetCoverageObligationRules(packet) {
|
|
1840
|
+
if (packet == null || typeof packet !== "object") {
|
|
1841
|
+
return [];
|
|
1842
|
+
}
|
|
1843
|
+
const policy = packet["coverage_policy"];
|
|
1844
|
+
if (policy == null || typeof policy !== "object") {
|
|
1845
|
+
return [];
|
|
1846
|
+
}
|
|
1847
|
+
const policyRecord = policy;
|
|
1848
|
+
if (policyRecord["policy_id"] !== "review_deep_v1") {
|
|
1849
|
+
return [];
|
|
1850
|
+
}
|
|
1851
|
+
const requiredClasses = policyRecord["required_when_available"];
|
|
1852
|
+
if (!Array.isArray(requiredClasses) || requiredClasses.length === 0) {
|
|
1853
|
+
return [];
|
|
1854
|
+
}
|
|
1855
|
+
const obligations = policyRecord["obligations"];
|
|
1856
|
+
if (obligations != null && typeof obligations === "object") {
|
|
1857
|
+
const targets = obligations["targets"];
|
|
1858
|
+
if (!Array.isArray(targets) || targets.length === 0) {
|
|
1859
|
+
// The validator rejects a present-but-empty target list unconditionally,
|
|
1860
|
+
// so no instruction rendered here could be satisfied. Stay silent rather
|
|
1861
|
+
// than describe a contract the delegate cannot meet. (The server building
|
|
1862
|
+
// a packet it will always reject is a separate defect; do not paper over
|
|
1863
|
+
// it by rendering the evidence-class rules the validator will not apply.)
|
|
1864
|
+
return [];
|
|
1865
|
+
}
|
|
1866
|
+
return [
|
|
1867
|
+
"Coverage obligations (mandatory, server-enforced):",
|
|
1868
|
+
"- packet.coverage_policy.obligations.targets is required coverage, not a suggestion. Server validation rejects your output if any obligation below is unmet, even when every other rule is satisfied.",
|
|
1869
|
+
"- For EVERY target: selected MUST contain that target's candidate_id with at least one range that overlaps one of that target's ranges.",
|
|
1870
|
+
"- For EVERY target whose regression_links is non-empty: selected MUST also contain at least ONE of those links' candidate_id with an overlapping range. One linked candidate per target is sufficient; you do not need all of them.",
|
|
1871
|
+
"- Obligations hold even when the task reads narrower than the obligation set. Add the obligation candidates alongside your task-driven picks rather than dropping them.",
|
|
1872
|
+
];
|
|
1873
|
+
}
|
|
1874
|
+
const classes = requiredClasses.filter((entry) => typeof entry === "string");
|
|
1875
|
+
if (classes.length === 0) {
|
|
1876
|
+
return [];
|
|
1877
|
+
}
|
|
1878
|
+
return [
|
|
1879
|
+
"Coverage obligations (mandatory, server-enforced):",
|
|
1880
|
+
`- packet.coverage_policy.required_when_available lists required evidence classes: ${classes.join(", ")}.`,
|
|
1881
|
+
"- For every listed class that ANY packet candidate carries in its evidence_classes, selected MUST include at least one candidate carrying that class. Server validation rejects your output otherwise.",
|
|
1882
|
+
];
|
|
1883
|
+
}
|
|
1884
|
+
/**
|
|
1885
|
+
* Renders the finalizer scoring contract.
|
|
1886
|
+
*
|
|
1887
|
+
* `min_score` is a server-side gate
|
|
1888
|
+
* ({@link validateContextBuilderFinalizerOutput}); before this it was never
|
|
1889
|
+
* sent to the model, so the finalizer was asked to clear a bar it could not
|
|
1890
|
+
* see, on a scale that was never defined. The rubric below is deliberately
|
|
1891
|
+
* anchored so that an approving score lands at or above the threshold, and it
|
|
1892
|
+
* names the dominant real-world failure mode: penalizing the packet for
|
|
1893
|
+
* evidence the packet was never meant to contain.
|
|
1894
|
+
*/
|
|
1895
|
+
function finalizerScoringRules(minScore) {
|
|
1896
|
+
if (minScore == null) {
|
|
1897
|
+
return [];
|
|
1898
|
+
}
|
|
1899
|
+
// A threshold of 0 cannot reject anything (the gate is `score < minScore`), so
|
|
1900
|
+
// the usual "below X, do not approve" band would render a range no score can
|
|
1901
|
+
// occupy and instruct the delegate to "honestly reach 0". State the degenerate
|
|
1902
|
+
// case plainly instead of rendering an impossible rule.
|
|
1903
|
+
const scoreBands = minScore <= 0
|
|
1904
|
+
? [
|
|
1905
|
+
`- score does not gate this request (any score clears a minimum of ${minScore}); express insufficiency through approved:false and concrete omissions, never through a low score.`,
|
|
1906
|
+
]
|
|
1907
|
+
: [
|
|
1908
|
+
"- score rates ONLY whether the `selected` you are returning is sufficient, correct local evidence for packet.task, judged against the evidence actually present in this packet:",
|
|
1909
|
+
// Bands are stated relative to the live threshold rather than as fixed
|
|
1910
|
+
// ranges. Fixed bands (9-10 / 6-8 / 0-5) left the shipped 8.5 default
|
|
1911
|
+
// in an undefined gap, and at a lower configured threshold they
|
|
1912
|
+
// labelled the passing score as deficient — telling the delegate to
|
|
1913
|
+
// approve while self-reporting a score whose own band calls the
|
|
1914
|
+
// selection inadequate.
|
|
1915
|
+
` - ${minScore}-10: selected is sufficient for the task; no packet evidence the task requires was left out.`,
|
|
1916
|
+
` - below ${minScore}: selected is wrong, or omits locally-available packet evidence the task needs. Do not approve.`,
|
|
1917
|
+
`- If you would approve, return a score at or above ${minScore}. If you cannot honestly reach ${minScore}, set approved:false and list concrete omissions rather than approving with a failing score.`,
|
|
1918
|
+
];
|
|
1919
|
+
return [
|
|
1920
|
+
"Scoring (server-enforced):",
|
|
1921
|
+
`- score >= ${minScore} clears the score gate; below ${minScore} fails the whole request regardless of everything else. Clearing the score gate is necessary but NOT sufficient — approved, omissions, warnings, selections, coverage, and budget are each validated separately.`,
|
|
1922
|
+
...scoreBands,
|
|
1923
|
+
"- Do NOT lower score for evidence that is absent from the packet, outside the bounded task, or that you merely suspect exists elsewhere in the repository. The packet is intentionally bounded and is the only source of truth for this call.",
|
|
1924
|
+
// The server scans warnings with a keyword-proximity matcher
|
|
1925
|
+
// (isMaterialEvidenceGapWarning) that has no negation handling, so an
|
|
1926
|
+
// affirmative all-clear is rejected exactly like a real gap report. Name the
|
|
1927
|
+
// exact fatal phrasings; a partial description reads as authoritative and is
|
|
1928
|
+
// worse than none.
|
|
1929
|
+
"- warnings carry speculative or out-of-scope notes ONLY, and the server scans them for evidence-gap phrasing. Do not mention required, requested, or focused evidence in warnings at all — not even to state that nothing is missing — and never phrase a warning as 'outside the selected range(s)' or as 'selected ... not present/not selectable/unselectable'. That scan is a keyword-proximity match with no negation handling, so an affirmative all-clear is rejected exactly like a real gap report.",
|
|
1930
|
+
"- If required evidence is genuinely missing, set approved:false with concrete omissions. Never route a real coverage gap into warnings.",
|
|
1931
|
+
];
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Single source for every score exemplar the finalizer prompt shows.
|
|
1935
|
+
*
|
|
1936
|
+
* The shape example, the rubric bands, and the minimum-output example must all
|
|
1937
|
+
* derive from the live threshold: hardcoding them let the prompt display a
|
|
1938
|
+
* score that its own rubric did not cover, and (at `min_score` above the
|
|
1939
|
+
* hardcoded value — 9.25 is schema-legal) an example that would fail the very
|
|
1940
|
+
* gate it was illustrating.
|
|
1941
|
+
*/
|
|
1942
|
+
function finalizerExampleScore(minScore) {
|
|
1943
|
+
// A non-gating threshold gets the neutral fallback rather than its own value:
|
|
1944
|
+
// "emit the lowest score that still clears the gate" is the right anti-inflation
|
|
1945
|
+
// rule wherever a gate exists, but at 0 it would render `{"score":0,"approved":true}`
|
|
1946
|
+
// and an unsure finalizer copying that reports a 0-scored approval into
|
|
1947
|
+
// `finalizer_score` observability. A higher exemplar always clears a lower gate.
|
|
1948
|
+
return minScore == null || minScore <= 0 ? 9 : minScore;
|
|
1949
|
+
}
|
|
1950
|
+
export function renderDelegatePrompt(stage, packet, options) {
|
|
1951
|
+
// Diagnostic-array exemplars are non-empty on purpose: empty arrays cannot
|
|
1952
|
+
// communicate element types, and a live finalizer failure returned
|
|
1953
|
+
// object-shaped `omissions`. The angle-bracket entries are placeholders that
|
|
1954
|
+
// show each element is one plain string.
|
|
1955
|
+
const outputShape = stage === "curator"
|
|
1956
|
+
? '{"selected":[{"candidate_id":"c:0:path","ranges":[{"start_line":1,"end_line":1,"reason":"why this range"}],"reason":"why this file"}],"warnings":["<short plain-string caveat>"],"open_questions":["<short plain-string follow-up question>"]}'
|
|
1957
|
+
: `{"score":${finalizerExampleScore(options?.minScore)},"approved":true,"selected":[{"candidate_id":"c:0:path","ranges":[{"start_line":1,"end_line":1,"reason":"why this range"}],"reason":"why this file"}],"warnings":["<short plain-string caveat>"],"omissions":["<short plain string describing an omitted file or range; must be [] when approving>"]}`;
|
|
1958
|
+
const stageRules = stage === "curator"
|
|
1959
|
+
? [
|
|
1960
|
+
"Curator-specific rules:",
|
|
1961
|
+
"- The packet is the complete local evidence. Do not run commands, read files, discover paths, or use filesystem state.",
|
|
1962
|
+
"- selected:[] is invalid when packet.candidates is non-empty. It will fail server validation.",
|
|
1963
|
+
"- If packet.candidates is non-empty, selected MUST contain at least one candidate.",
|
|
1964
|
+
"- Use packet.deterministic_selection.selected as the safe starting point; keep relevant deterministic candidates unless the task clearly excludes them.",
|
|
1965
|
+
"- Treat the minimum valid JSON example as a floor, not a complete packet. Add task-critical candidates from packet.candidates when deterministic_selection is sparse or omissions show important local evidence was budget-pruned.",
|
|
1966
|
+
"- Do not return selected:[] to express uncertainty. Select the closest relevant local candidate and put caveats in warnings or open_questions.",
|
|
1967
|
+
// The validator requires string elements (SafeTextSchema) in every
|
|
1968
|
+
// diagnostic array; empty exemplars alone let models invent object
|
|
1969
|
+
// entries, which fail schema validation.
|
|
1970
|
+
"- warnings and open_questions are arrays of plain strings. Each element is one short plain string; objects inside these arrays are invalid and fail server validation.",
|
|
1971
|
+
]
|
|
1972
|
+
: [
|
|
1973
|
+
"Finalizer-specific rules:",
|
|
1974
|
+
"- The packet is the complete local evidence. Do not run commands, read files, discover paths, or use filesystem state.",
|
|
1975
|
+
"- selected:[] is invalid when approving. It will fail server validation.",
|
|
1976
|
+
"- If approving, selected MUST contain at least one candidate.",
|
|
1977
|
+
"- Start from packet.curator_proposal.selected and keep only candidates that are locally supported by packet.candidates.",
|
|
1978
|
+
"- Treat packet.deterministic_selection.selected as server-ranked required baseline evidence. If the curator underselected or warned about sparse evidence, add relevant deterministic candidates/ranges that are present in packet.candidates instead of lowering the score for evidence the packet already provides.",
|
|
1979
|
+
"- Do not reject solely because the curator warning says broader implementation may exist when deterministic_selection and packet.candidates contain concrete task-critical ranges for that concern.",
|
|
1980
|
+
"- If the selected packet evidence is insufficient for the bounded task, set approved:false and explain concrete omissions; the server will treat that as delegate failure according to failure_policy.",
|
|
1981
|
+
// "'may contain more detail' caveats" invited exactly the phrasing
|
|
1982
|
+
// isMaterialEvidenceGapWarning rejects outright
|
|
1983
|
+
// (/\boutside (?:the )?selected ranges?\b/), so obeying this rule got
|
|
1984
|
+
// the finalizer hard-failed. Steer the caveat at evidence absent from
|
|
1985
|
+
// the packet instead of at the selected ranges.
|
|
1986
|
+
"- If approving with score at or above the requested minimum, omissions MUST be []. Put speculative concerns about evidence absent from this packet in warnings instead.",
|
|
1987
|
+
// The observed live schema failure was object-shaped `omissions`;
|
|
1988
|
+
// state the element type explicitly instead of relying on exemplars.
|
|
1989
|
+
// (No open_questions here: the finalizer schema is strict and would
|
|
1990
|
+
// reject that key as unrecognized.)
|
|
1991
|
+
"- warnings and omissions are arrays of plain strings. Each element is one short plain string; objects inside these arrays are invalid and fail server validation.",
|
|
1992
|
+
];
|
|
1993
|
+
const minimumOutput = buildMinimumDelegateOutput(stage, packet, options?.minScore);
|
|
1994
|
+
return [
|
|
1995
|
+
`You are the context_builder ${stage}.`,
|
|
1996
|
+
`Prompt version: ${CONTEXT_BUILDER_DELEGATE_PROMPT_VERSION}.`,
|
|
1997
|
+
"Return exactly one strict JSON object and no prose.",
|
|
1998
|
+
"For this tool call, the JSON packet below is the only source of truth.",
|
|
1999
|
+
stage === "curator"
|
|
2000
|
+
? "Never answer with selected:[]. If candidates exist, choose at least one candidate_id and one eligible range from packet.candidates or packet.deterministic_selection.selected."
|
|
2001
|
+
: "Never answer with selected:[]. If approving, choose at least one candidate_id and one eligible range from packet.curator_proposal.selected.",
|
|
2002
|
+
"Use only candidate_id and ranges from the packet. Do not add paths or source content.",
|
|
2003
|
+
"Every selected range must stay within one of that candidate's packet.ranges entries.",
|
|
2004
|
+
stage === "curator"
|
|
2005
|
+
? "Use the excerpt, search_hits, deterministic_selection, omissions, and task fields as evidence; the packet intentionally omits full source content."
|
|
2006
|
+
: "Use the excerpt, search_hits, curator_proposal, deterministic_selection, omissions, and task fields as evidence; the packet intentionally omits full source content.",
|
|
2007
|
+
...stageRules,
|
|
2008
|
+
...(stage === "finalizer" ? finalizerScoringRules(options?.minScore) : []),
|
|
2009
|
+
...packetCoverageObligationRules(packet),
|
|
2010
|
+
...(minimumOutput == null
|
|
2011
|
+
? []
|
|
2012
|
+
: [
|
|
2013
|
+
"If unsure, return this minimum valid JSON object rather than an empty selection:",
|
|
2014
|
+
minimumOutput,
|
|
2015
|
+
]),
|
|
2016
|
+
`Required JSON shape: ${outputShape}`,
|
|
2017
|
+
"Invalid output example: {\"selected\":[],\"warnings\":[],\"open_questions\":[]}",
|
|
2018
|
+
"",
|
|
2019
|
+
"Packet:",
|
|
2020
|
+
JSON.stringify(packet),
|
|
2021
|
+
"",
|
|
2022
|
+
"Return the JSON object now. If candidates exist, selected must be non-empty.",
|
|
2023
|
+
].join("\n");
|
|
2024
|
+
}
|
|
2025
|
+
/**
|
|
2026
|
+
* True when `selections` would clear the packet's coverage obligations.
|
|
2027
|
+
*
|
|
2028
|
+
* Mirrors {@link validateContextBuilderDelegateCoverageSelection}: every target
|
|
2029
|
+
* needs an overlapping selected range, and a target with regression links needs
|
|
2030
|
+
* an overlapping range from at least ONE of them (the link check is disjunctive).
|
|
2031
|
+
*/
|
|
2032
|
+
function selectionsSatisfyPacketObligations(selections, packet) {
|
|
2033
|
+
if (packet == null || typeof packet !== "object") {
|
|
2034
|
+
return true;
|
|
2035
|
+
}
|
|
2036
|
+
const policy = packet["coverage_policy"];
|
|
2037
|
+
if (policy == null || typeof policy !== "object") {
|
|
2038
|
+
return true;
|
|
2039
|
+
}
|
|
2040
|
+
const policyRecord = policy;
|
|
2041
|
+
if (policyRecord["policy_id"] !== "review_deep_v1") {
|
|
2042
|
+
return true;
|
|
2043
|
+
}
|
|
2044
|
+
const requiredClasses = policyRecord["required_when_available"];
|
|
2045
|
+
if (!Array.isArray(requiredClasses) || requiredClasses.length === 0) {
|
|
2046
|
+
return true;
|
|
2047
|
+
}
|
|
2048
|
+
const obligations = policyRecord["obligations"];
|
|
2049
|
+
if (obligations == null || typeof obligations !== "object") {
|
|
2050
|
+
// The validator does NOT concede this branch: it falls through to the
|
|
2051
|
+
// evidence-class check and still rejects a selection that omits an
|
|
2052
|
+
// available required class. Returning true here would advertise an example
|
|
2053
|
+
// the validator rejects. Unreachable through the current packet builder
|
|
2054
|
+
// (normalizeCoveragePolicy attaches obligations for every review_deep_v1
|
|
2055
|
+
// policy), but the two must not disagree — that divergence is the bug
|
|
2056
|
+
// generator this whole change exists to close.
|
|
2057
|
+
const candidates = packet["candidates"];
|
|
2058
|
+
if (!Array.isArray(candidates)) {
|
|
2059
|
+
return true;
|
|
2060
|
+
}
|
|
2061
|
+
const selectedIds = new Set(selections.map((selection) => selection.candidate_id));
|
|
2062
|
+
const evidenceClassesOf = (candidate) => {
|
|
2063
|
+
if (candidate == null || typeof candidate !== "object") {
|
|
2064
|
+
return [];
|
|
2065
|
+
}
|
|
2066
|
+
const classes = candidate["evidence_classes"];
|
|
2067
|
+
return Array.isArray(classes) ? classes : [];
|
|
2068
|
+
};
|
|
2069
|
+
return requiredClasses.every((evidenceClass) => {
|
|
2070
|
+
const available = candidates.some((candidate) => evidenceClassesOf(candidate).includes(evidenceClass));
|
|
2071
|
+
if (!available) {
|
|
2072
|
+
return true;
|
|
2073
|
+
}
|
|
2074
|
+
return candidates.some((candidate) => {
|
|
2075
|
+
const id = candidate != null && typeof candidate === "object"
|
|
2076
|
+
? candidate["id"]
|
|
2077
|
+
: undefined;
|
|
2078
|
+
return (typeof id === "string" &&
|
|
2079
|
+
selectedIds.has(id) &&
|
|
2080
|
+
evidenceClassesOf(candidate).includes(evidenceClass));
|
|
2081
|
+
});
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
const targets = obligations["targets"];
|
|
2085
|
+
if (!Array.isArray(targets)) {
|
|
2086
|
+
return true;
|
|
2087
|
+
}
|
|
2088
|
+
if (targets.length === 0) {
|
|
2089
|
+
// The validator rejects a present-but-empty target list unconditionally, so
|
|
2090
|
+
// no example can be valid.
|
|
2091
|
+
return false;
|
|
2092
|
+
}
|
|
2093
|
+
const selectedById = new Map(selections.map((selection) => [selection.candidate_id, selection.ranges]));
|
|
2094
|
+
const covers = (value) => {
|
|
2095
|
+
if (value == null || typeof value !== "object") {
|
|
2096
|
+
return false;
|
|
2097
|
+
}
|
|
2098
|
+
const record = value;
|
|
2099
|
+
const candidateId = record["candidate_id"];
|
|
2100
|
+
if (typeof candidateId !== "string") {
|
|
2101
|
+
return false;
|
|
2102
|
+
}
|
|
2103
|
+
const selectedRanges = selectedById.get(candidateId);
|
|
2104
|
+
if (selectedRanges == null || selectedRanges.length === 0) {
|
|
2105
|
+
return false;
|
|
2106
|
+
}
|
|
2107
|
+
const obligationRanges = record["ranges"];
|
|
2108
|
+
if (!Array.isArray(obligationRanges) || obligationRanges.length === 0) {
|
|
2109
|
+
return false;
|
|
2110
|
+
}
|
|
2111
|
+
return obligationRanges.some((range) => {
|
|
2112
|
+
if (range == null || typeof range !== "object") {
|
|
2113
|
+
return false;
|
|
2114
|
+
}
|
|
2115
|
+
const start = range["start_line"];
|
|
2116
|
+
const end = range["end_line"];
|
|
2117
|
+
if (typeof start !== "number" || typeof end !== "number") {
|
|
2118
|
+
return false;
|
|
2119
|
+
}
|
|
2120
|
+
return selectedRanges.some((selectedRange) => selectedRange.start_line <= end && start <= selectedRange.end_line);
|
|
2121
|
+
});
|
|
2122
|
+
};
|
|
2123
|
+
return targets.every((target) => {
|
|
2124
|
+
if (!covers(target)) {
|
|
2125
|
+
return false;
|
|
2126
|
+
}
|
|
2127
|
+
const links = target["regression_links"];
|
|
2128
|
+
if (!Array.isArray(links) || links.length === 0) {
|
|
2129
|
+
return true;
|
|
2130
|
+
}
|
|
2131
|
+
return links.some((link) => covers(link));
|
|
2132
|
+
});
|
|
2133
|
+
}
|
|
2134
|
+
/** The packet's `budget.max_files`, when it carries a usable one. */
|
|
2135
|
+
function packetBudgetMaxFiles(packet) {
|
|
2136
|
+
if (packet == null || typeof packet !== "object") {
|
|
2137
|
+
return null;
|
|
2138
|
+
}
|
|
2139
|
+
const budget = packet["budget"];
|
|
2140
|
+
if (budget == null || typeof budget !== "object") {
|
|
2141
|
+
return null;
|
|
2142
|
+
}
|
|
2143
|
+
const maxFiles = budget["max_files"];
|
|
2144
|
+
return typeof maxFiles === "number" && Number.isFinite(maxFiles) && maxFiles > 0
|
|
2145
|
+
? maxFiles
|
|
2146
|
+
: null;
|
|
2147
|
+
}
|
|
2148
|
+
function buildMinimumDelegateOutput(stage, packet, minScore) {
|
|
2149
|
+
const allSelections = packetMinimumSelections(stage, packet);
|
|
2150
|
+
if (allSelections.length === 0) {
|
|
2151
|
+
return null;
|
|
2152
|
+
}
|
|
2153
|
+
// The example is advertised as "minimum valid" and a delegate may copy it
|
|
2154
|
+
// verbatim, so it must satisfy every gate the copy would face.
|
|
2155
|
+
//
|
|
2156
|
+
// packetMinimumSelections bounds by MAX_DELEGATE_MINIMUM_OUTPUT_SELECTIONS,
|
|
2157
|
+
// which is unrelated to the caller's budget: validateBudget rejects a
|
|
2158
|
+
// selection longer than budget.max_files, so an unbounded example was invalid
|
|
2159
|
+
// whenever max_files was the smaller limit. Apply the budget first, then judge
|
|
2160
|
+
// coverage on exactly what is advertised.
|
|
2161
|
+
const maxFiles = packetBudgetMaxFiles(packet);
|
|
2162
|
+
const minimumSelections = maxFiles == null ? allSelections : allSelections.slice(0, maxFiles);
|
|
2163
|
+
if (minimumSelections.length === 0) {
|
|
2164
|
+
return null;
|
|
2165
|
+
}
|
|
2166
|
+
// Neither bound ranks obligations first, so a required target can fall outside
|
|
2167
|
+
// the cap — offering an example that fails coverage validation. Withhold the
|
|
2168
|
+
// example rather than advertise an invalid one.
|
|
2169
|
+
if (!selectionsSatisfyPacketObligations(minimumSelections, packet)) {
|
|
2170
|
+
return null;
|
|
2171
|
+
}
|
|
2172
|
+
const selected = minimumSelections.map((selection) => ({
|
|
2173
|
+
candidate_id: selection.candidate_id,
|
|
2174
|
+
ranges: selection.ranges.map((range) => ({
|
|
2175
|
+
start_line: range.start_line,
|
|
2176
|
+
end_line: range.end_line,
|
|
2177
|
+
reason: "locally supported range from the packet",
|
|
2178
|
+
})),
|
|
2179
|
+
reason: "minimum valid local selection for the requested context",
|
|
2180
|
+
}));
|
|
2181
|
+
const output = stage === "curator"
|
|
2182
|
+
? {
|
|
2183
|
+
selected,
|
|
2184
|
+
warnings: [
|
|
2185
|
+
"minimum deterministic selection used; review packet evidence for more context",
|
|
2186
|
+
],
|
|
2187
|
+
open_questions: [],
|
|
2188
|
+
}
|
|
2189
|
+
: {
|
|
2190
|
+
// This example is offered to an UNSURE finalizer, and it carries a
|
|
2191
|
+
// deliberately minimal selection. Stamping it `score: 10` would pair
|
|
2192
|
+
// the top of the scale with the floor of the evidence and teach
|
|
2193
|
+
// "when uncertain, emit 10" — inflating the very self-score the gate
|
|
2194
|
+
// relies on. Emit the lowest score that still clears the gate, so the
|
|
2195
|
+
// minimum selection reads as the minimum passing answer.
|
|
2196
|
+
score: finalizerExampleScore(minScore),
|
|
2197
|
+
approved: true,
|
|
2198
|
+
selected,
|
|
2199
|
+
warnings: [
|
|
2200
|
+
"minimum curator proposal selection used; review packet evidence for more context",
|
|
2201
|
+
],
|
|
2202
|
+
omissions: [],
|
|
2203
|
+
};
|
|
2204
|
+
return JSON.stringify(output);
|
|
2205
|
+
}
|
|
2206
|
+
function packetMinimumSelections(stage, packet) {
|
|
2207
|
+
if (packet == null || typeof packet !== "object") {
|
|
2208
|
+
return [];
|
|
2209
|
+
}
|
|
2210
|
+
const record = packet;
|
|
2211
|
+
if (stage === "finalizer") {
|
|
2212
|
+
const proposal = record["curator_proposal"];
|
|
2213
|
+
const proposalSelected = proposal != null && typeof proposal === "object"
|
|
2214
|
+
? proposal["selected"]
|
|
2215
|
+
: undefined;
|
|
2216
|
+
return packetSelectionsFromValues(proposalSelected);
|
|
2217
|
+
}
|
|
2218
|
+
const deterministic = record["deterministic_selection"];
|
|
2219
|
+
const deterministicSelected = deterministic != null && typeof deterministic === "object"
|
|
2220
|
+
? deterministic["selected"]
|
|
2221
|
+
: undefined;
|
|
2222
|
+
const deterministicSelections = packetSelectionsFromValues(deterministicSelected);
|
|
2223
|
+
const candidateSelections = packetCandidateSelectionsFromValues(record["candidates"]);
|
|
2224
|
+
if (deterministicSelections.length > 0) {
|
|
2225
|
+
return mergePacketSelections([
|
|
2226
|
+
...deterministicSelections,
|
|
2227
|
+
...candidateSelections,
|
|
2228
|
+
]).slice(0, MAX_DELEGATE_MINIMUM_OUTPUT_SELECTIONS);
|
|
2229
|
+
}
|
|
2230
|
+
return candidateSelections;
|
|
2231
|
+
}
|
|
2232
|
+
function packetSelectionsFromValues(values) {
|
|
2233
|
+
if (!Array.isArray(values)) {
|
|
2234
|
+
return [];
|
|
2235
|
+
}
|
|
2236
|
+
return mergePacketSelections(values
|
|
2237
|
+
.slice(0, MAX_DELEGATE_MINIMUM_OUTPUT_SELECTIONS)
|
|
2238
|
+
.map((value) => packetSelectionFromValue(value, "candidate_id"))
|
|
2239
|
+
.filter((selection) => selection != null));
|
|
2240
|
+
}
|
|
2241
|
+
function packetCandidateSelectionsFromValues(values) {
|
|
2242
|
+
if (!Array.isArray(values)) {
|
|
2243
|
+
return [];
|
|
2244
|
+
}
|
|
2245
|
+
return mergePacketSelections(values
|
|
2246
|
+
.slice(0, MAX_DELEGATE_MINIMUM_OUTPUT_SELECTIONS)
|
|
2247
|
+
.map((value) => packetSelectionFromValue(value, "id"))
|
|
2248
|
+
.filter((selection) => selection != null));
|
|
2249
|
+
}
|
|
2250
|
+
function mergePacketSelections(selections) {
|
|
2251
|
+
const merged = new Map();
|
|
2252
|
+
for (const selection of selections) {
|
|
2253
|
+
const ranges = merged.get(selection.candidate_id) ?? [];
|
|
2254
|
+
// Deduplicate by bounds. Sources overlap by construction — a candidate in
|
|
2255
|
+
// deterministic_selection is normally also present in packet.candidates —
|
|
2256
|
+
// so concatenating produced the SAME range twice, and validateSelections
|
|
2257
|
+
// rejects a duplicate range outright. That made the advertised "minimum
|
|
2258
|
+
// valid JSON" example invalid in the ordinary case: a delegate copying it
|
|
2259
|
+
// was hard-failed by the server that offered it.
|
|
2260
|
+
for (const range of selection.ranges) {
|
|
2261
|
+
if (!ranges.some((existing) => existing.start_line === range.start_line &&
|
|
2262
|
+
existing.end_line === range.end_line)) {
|
|
2263
|
+
ranges.push(range);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
merged.set(selection.candidate_id, ranges);
|
|
2267
|
+
}
|
|
2268
|
+
return [...merged].map(([candidate_id, ranges]) => ({
|
|
2269
|
+
candidate_id,
|
|
2270
|
+
ranges,
|
|
2271
|
+
}));
|
|
2272
|
+
}
|
|
2273
|
+
function packetSelectionFromValue(value, candidateIdKey) {
|
|
2274
|
+
if (value == null || typeof value !== "object") {
|
|
2275
|
+
return null;
|
|
2276
|
+
}
|
|
2277
|
+
const record = value;
|
|
2278
|
+
const candidateId = record[candidateIdKey];
|
|
2279
|
+
const ranges = record["ranges"];
|
|
2280
|
+
const validRanges = Array.isArray(ranges)
|
|
2281
|
+
? ranges.map(packetRangeBounds).filter((range) => range != null)
|
|
2282
|
+
: [];
|
|
2283
|
+
return typeof candidateId === "string" && validRanges.length > 0
|
|
2284
|
+
? { candidate_id: candidateId, ranges: validRanges }
|
|
2285
|
+
: null;
|
|
2286
|
+
}
|
|
2287
|
+
function packetRangeBounds(value) {
|
|
2288
|
+
if (value == null || typeof value !== "object") {
|
|
2289
|
+
return null;
|
|
2290
|
+
}
|
|
2291
|
+
const record = value;
|
|
2292
|
+
const startLine = record["start_line"];
|
|
2293
|
+
const endLine = record["end_line"];
|
|
2294
|
+
if (typeof startLine !== "number" || typeof endLine !== "number") {
|
|
2295
|
+
return null;
|
|
2296
|
+
}
|
|
2297
|
+
return Number.isInteger(startLine) &&
|
|
2298
|
+
Number.isInteger(endLine) &&
|
|
2299
|
+
startLine > 0 &&
|
|
2300
|
+
endLine >= startLine
|
|
2301
|
+
? { start_line: startLine, end_line: endLine }
|
|
2302
|
+
: null;
|
|
2303
|
+
}
|
|
2304
|
+
function delegatePromptDiagnostic(packet) {
|
|
2305
|
+
if (packet == null || typeof packet !== "object") {
|
|
2306
|
+
return `prompt_version=${CONTEXT_BUILDER_DELEGATE_PROMPT_VERSION}; packet=invalid`;
|
|
2307
|
+
}
|
|
2308
|
+
const record = packet;
|
|
2309
|
+
const candidateCount = Array.isArray(record["candidates"])
|
|
2310
|
+
? record["candidates"].length
|
|
2311
|
+
: 0;
|
|
2312
|
+
const deterministic = record["deterministic_selection"];
|
|
2313
|
+
const deterministicSelected = deterministic != null && typeof deterministic === "object"
|
|
2314
|
+
? deterministic["selected"]
|
|
2315
|
+
: undefined;
|
|
2316
|
+
const deterministicCount = Array.isArray(deterministicSelected)
|
|
2317
|
+
? deterministicSelected.length
|
|
2318
|
+
: 0;
|
|
2319
|
+
// `curator_proposal` only exists on a finalizer packet (curator packets are
|
|
2320
|
+
// built before any proposal exists). Reporting `curator_selected=0` for a
|
|
2321
|
+
// curator packet described the packet's missing field, not the curator's
|
|
2322
|
+
// output, so every curator-stage diagnostic claimed an empty selection that
|
|
2323
|
+
// had in fact never been observed. Omit the field unless the packet carries a
|
|
2324
|
+
// proposal to count.
|
|
2325
|
+
const proposal = record["curator_proposal"];
|
|
2326
|
+
const proposalSelected = proposal != null && typeof proposal === "object"
|
|
2327
|
+
? proposal["selected"]
|
|
2328
|
+
: undefined;
|
|
2329
|
+
// `min` must report what the prompt actually carried, not whether a minimum
|
|
2330
|
+
// selection could be computed: buildMinimumDelegateOutput also withholds the
|
|
2331
|
+
// example when it would fail budget or coverage, and a withheld floor example
|
|
2332
|
+
// is exactly the condition worth seeing next to a delegate failure. Derive it
|
|
2333
|
+
// from the same call the prompt makes, at the stage this packet belongs to.
|
|
2334
|
+
const stage = Array.isArray(proposalSelected)
|
|
2335
|
+
? "finalizer"
|
|
2336
|
+
: "curator";
|
|
2337
|
+
return [
|
|
2338
|
+
`pv=${CONTEXT_BUILDER_DELEGATE_PROMPT_VERSION}`,
|
|
2339
|
+
`candidates=${candidateCount}`,
|
|
2340
|
+
`det=${deterministicCount}`,
|
|
2341
|
+
...(Array.isArray(proposalSelected)
|
|
2342
|
+
? [`curator_selected=${proposalSelected.length}`]
|
|
2343
|
+
: []),
|
|
2344
|
+
`min=${buildMinimumDelegateOutput(stage, packet) != null}`,
|
|
2345
|
+
].join("; ");
|
|
2346
|
+
}
|
|
2347
|
+
function failureReasonFromCall(stage, call) {
|
|
2348
|
+
return `${stage} ${call.reason}: ${call.message}`;
|
|
2349
|
+
}
|
|
2350
|
+
/**
|
|
2351
|
+
* Safe-scalar score context for finalizer rejections. A not-approved rejection
|
|
2352
|
+
* previously carried no score at all; surfacing the reported score next to the
|
|
2353
|
+
* configured min_score makes rejection and under-score failures diagnosable
|
|
2354
|
+
* from the failure reason alone. Only a finite numeric `score` read from the
|
|
2355
|
+
* finalizer's raw JSON is ever included — never free-form provider text.
|
|
2356
|
+
*/
|
|
2357
|
+
function finalizerScoreDiagnostic(json, minScore) {
|
|
2358
|
+
const rawScore = json != null && typeof json === "object"
|
|
2359
|
+
? json["score"]
|
|
2360
|
+
: undefined;
|
|
2361
|
+
return typeof rawScore === "number" && Number.isFinite(rawScore)
|
|
2362
|
+
? `; finalizer_score=${rawScore}; min_score=${minScore}`
|
|
2363
|
+
: `; min_score=${minScore}`;
|
|
2364
|
+
}
|
|
2365
|
+
function failureReasonFromError(stage, err, diagnostic) {
|
|
2366
|
+
const message = err instanceof Error ? err.message : "unknown delegate error";
|
|
2367
|
+
return diagnostic == null
|
|
2368
|
+
? `${stage}: ${message}`
|
|
2369
|
+
: `${stage}: ${diagnostic}; ${message}`;
|
|
2370
|
+
}
|
|
2371
|
+
function delegateWarning(code, message) {
|
|
2372
|
+
return {
|
|
2373
|
+
code: sanitizeDiagnosticText(code),
|
|
2374
|
+
message: sanitizeDiagnosticText(message),
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
// Bounded per-term detail in the search-cap coverage warning. Full per-term
|
|
2378
|
+
// rows (up to the public 20-term cap) persist in manifest/summary
|
|
2379
|
+
// `search_coverage`; the warning message stays a compact summary.
|
|
2380
|
+
const MAX_SEARCH_CAP_WARNING_TERM_DETAILS = 6;
|
|
2381
|
+
/**
|
|
2382
|
+
* Builds the receipt-visible explanation for a MATERIAL search cap. Returns
|
|
2383
|
+
* undefined when the cap was not hit or coverage proves it non-material.
|
|
2384
|
+
* The warning `code` is the stable materiality reason string
|
|
2385
|
+
* (`search_cap_unresolved_coverage` / `search_cap_unsearched_candidates`);
|
|
2386
|
+
* classification itself rides on the cap event in the status reducer, so this
|
|
2387
|
+
* warning is explanatory and never double-counts toward `partial`.
|
|
2388
|
+
* Terms are referenced by `search_term_index`, never by value, so the warning
|
|
2389
|
+
* cannot leak caller term text; the full (sanitized) rows live in
|
|
2390
|
+
* `search_coverage`. The first unsearched candidate's rank/path is not
|
|
2391
|
+
* reported: T1's coverage counters are aggregate-only by design and the
|
|
2392
|
+
* caller-phase scheduler is term-major round-robin, so the searched set is not
|
|
2393
|
+
* an ordering prefix and no per-candidate identity is cheaply available.
|
|
2394
|
+
*/
|
|
2395
|
+
function searchCapCoverageWarning(searchCapHit, coverage) {
|
|
2396
|
+
if (!searchCapHit) {
|
|
2397
|
+
return undefined;
|
|
2398
|
+
}
|
|
2399
|
+
const materiality = evaluateSearchCapMateriality(coverage);
|
|
2400
|
+
if (!materiality.material) {
|
|
2401
|
+
return undefined;
|
|
2402
|
+
}
|
|
2403
|
+
const incompleteTerms = coverage.caller_terms.filter((term) => !term.scan_complete);
|
|
2404
|
+
const termDetails = incompleteTerms
|
|
2405
|
+
.slice(0, MAX_SEARCH_CAP_WARNING_TERM_DETAILS)
|
|
2406
|
+
.map((term) => `#${term.search_term_index}(hits=${term.hit_count})`)
|
|
2407
|
+
.join(", ");
|
|
2408
|
+
const hiddenIncomplete = incompleteTerms.length - Math.min(incompleteTerms.length, MAX_SEARCH_CAP_WARNING_TERM_DETAILS);
|
|
2409
|
+
const callerSummary = coverage.caller_term_count === 0
|
|
2410
|
+
? "caller terms 0"
|
|
2411
|
+
: `caller terms ${coverage.caller_term_count} (${incompleteTerms.length} incomplete${termDetails.length === 0 ? "" : `: ${termDetails}`}${hiddenIncomplete > 0 ? `, +${hiddenIncomplete} more` : ""}; ${coverage.caller_record_count} records)`;
|
|
2412
|
+
return {
|
|
2413
|
+
code: materiality.reason,
|
|
2414
|
+
message: `search cap ended discovery before coverage was proven: searched ` +
|
|
2415
|
+
`${coverage.searched_candidate_count}/${coverage.eligible_candidate_count} eligible candidates ` +
|
|
2416
|
+
`(${coverage.unsearched_candidate_count} unsearched); ${callerSummary}; ` +
|
|
2417
|
+
`derived terms ${coverage.derived_term_count} (${coverage.derived_record_count} records); ` +
|
|
2418
|
+
`full per-term coverage is in manifest.json search_coverage`,
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
function reconcileContextBuilderWarnings(input) {
|
|
2422
|
+
if (input.warnings.length === 0) {
|
|
2423
|
+
return [];
|
|
2424
|
+
}
|
|
2425
|
+
return input.warnings.filter((warning) => {
|
|
2426
|
+
const text = warning.message.toLowerCase();
|
|
2427
|
+
if (isStaleTruncationWarning(text, input)) {
|
|
2428
|
+
return false;
|
|
2429
|
+
}
|
|
2430
|
+
if (isStaleSelectedContextOmissionWarning(text, input.selected)) {
|
|
2431
|
+
return false;
|
|
2432
|
+
}
|
|
2433
|
+
if (isStaleSelectedContextRangeWarning(text, input.selected)) {
|
|
2434
|
+
return false;
|
|
2435
|
+
}
|
|
2436
|
+
return true;
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
function isStaleTruncationWarning(text, input) {
|
|
2440
|
+
if (!isTruncationWarning(text)) {
|
|
2441
|
+
return false;
|
|
2442
|
+
}
|
|
2443
|
+
return !contextBuilderPacketIsDegraded(input);
|
|
2444
|
+
}
|
|
2445
|
+
function contextBuilderPacketIsDegraded(input) {
|
|
2446
|
+
return (input.metrics.truncated ||
|
|
2447
|
+
input.omissions.some(isMaterialWarningOmission) ||
|
|
2448
|
+
input.delegate?.fallback_used === true ||
|
|
2449
|
+
input.delegate?.status === "partial");
|
|
2450
|
+
}
|
|
2451
|
+
function isMaterialWarningOmission(omission) {
|
|
2452
|
+
if (omission.reason === "excluded" ||
|
|
2453
|
+
omission.reason === "exclude_glob" ||
|
|
2454
|
+
omission.reason === "caller_include_glob" ||
|
|
2455
|
+
omission.reason === "content_secret" ||
|
|
2456
|
+
omission.reason === "secret_path" ||
|
|
2457
|
+
omission.reason === "symlink_omitted" ||
|
|
2458
|
+
omission.reason === "symlink_file" ||
|
|
2459
|
+
omission.reason === "binary_file" ||
|
|
2460
|
+
omission.reason === "duplicate_root" ||
|
|
2461
|
+
omission.reason === "noisy_file" ||
|
|
2462
|
+
omission.reason === "default_exclude" ||
|
|
2463
|
+
omission.reason === "default_noisy_exclude") {
|
|
2464
|
+
return false;
|
|
2465
|
+
}
|
|
2466
|
+
return true;
|
|
2467
|
+
}
|
|
2468
|
+
function isTruncationWarning(text) {
|
|
2469
|
+
return /\b(?:truncat(?:ed|ion)|token[- ]?truncated|token budget|budget[- ]limited|budget limited)\b/.test(text);
|
|
2470
|
+
}
|
|
2471
|
+
function isStaleSelectedContextOmissionWarning(text, selected) {
|
|
2472
|
+
if (selected.length === 0 ||
|
|
2473
|
+
!isOmissionWarning(text) ||
|
|
2474
|
+
isPathOrSecretOmissionWarning(text)) {
|
|
2475
|
+
return false;
|
|
2476
|
+
}
|
|
2477
|
+
const references = selectedContextWarningReferences(selected);
|
|
2478
|
+
const groupedRefs = concreteWarningReferenceGroups(text);
|
|
2479
|
+
if (groupedRefs.length > 0) {
|
|
2480
|
+
if (groupedRefs.some((group) => group.every((ref) => references.tokens.has(ref)))) {
|
|
2481
|
+
return true;
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
return warningReferencesSelectedContext(text, references);
|
|
2485
|
+
}
|
|
2486
|
+
function isStaleSelectedContextRangeWarning(text, selected) {
|
|
2487
|
+
if (selected.length === 0 ||
|
|
2488
|
+
!/\b(?:available as|available only|only available|only includes|only selected|partial|range|ranges|slice|slices|line|lines|clipped|narrow)\b/.test(text)) {
|
|
2489
|
+
return false;
|
|
2490
|
+
}
|
|
2491
|
+
return warningReferencesSelectedContext(text, selectedContextWarningReferences(selected));
|
|
2492
|
+
}
|
|
2493
|
+
function isOmissionWarning(text) {
|
|
2494
|
+
return /\b(?:missing|missed|omission|omissions|omit|omits|omitted|not selected|not be selected|not present|unavailable|insufficient)\b/.test(text);
|
|
2495
|
+
}
|
|
2496
|
+
function isPathOrSecretOmissionWarning(text) {
|
|
2497
|
+
return /\b(?:secret|sensitive|credential|private key|api key|redacted|allowed roots?|outside allowed|unsafe path|path traversal|symlink|permission denied|eacces|enoent|not readable)\b/.test(text);
|
|
2498
|
+
}
|
|
2499
|
+
function selectedContextWarningReferences(selected) {
|
|
2500
|
+
const compactTextRefs = new Set();
|
|
2501
|
+
const tokens = new Set();
|
|
2502
|
+
for (const file of selected) {
|
|
2503
|
+
const normalizedPath = normalizeWarningReference(file.path);
|
|
2504
|
+
if (normalizedPath.length > 0) {
|
|
2505
|
+
compactTextRefs.add(normalizedPath);
|
|
2506
|
+
}
|
|
2507
|
+
const basename = file.path.split("/").at(-1) ?? file.path;
|
|
2508
|
+
const basenameRef = normalizeWarningReference(basename);
|
|
2509
|
+
if (basenameRef.length > 0) {
|
|
2510
|
+
compactTextRefs.add(basenameRef);
|
|
2511
|
+
}
|
|
2512
|
+
const stem = basename.split(".")[0] ?? basename;
|
|
2513
|
+
const stemRef = normalizeWarningReference(stem);
|
|
2514
|
+
if (isConcreteWarningReference(stemRef)) {
|
|
2515
|
+
compactTextRefs.add(stemRef);
|
|
2516
|
+
tokens.add(stemRef);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
return { compactTextRefs, tokens };
|
|
2520
|
+
}
|
|
2521
|
+
function warningReferencesSelectedContext(text, references) {
|
|
2522
|
+
const compactText = normalizeWarningReference(text);
|
|
2523
|
+
for (const ref of references.compactTextRefs) {
|
|
2524
|
+
if (isConcreteWarningReference(ref) && compactText.includes(ref)) {
|
|
2525
|
+
return true;
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
return false;
|
|
2529
|
+
}
|
|
2530
|
+
function concreteWarningReferenceGroups(text) {
|
|
2531
|
+
const groups = [];
|
|
2532
|
+
const groupPatterns = [
|
|
2533
|
+
/\b([a-z0-9_.-]+(?:\/[a-z0-9_.-]+)+)\s+(?:schemas?|tests?|files?)\b/g,
|
|
2534
|
+
/\b([a-z0-9_.-]+)\s+and\s+([a-z0-9_.-]+)\s+(?:schemas?|tests?|files?)\b/g,
|
|
2535
|
+
];
|
|
2536
|
+
for (const pattern of groupPatterns) {
|
|
2537
|
+
for (const match of text.matchAll(pattern)) {
|
|
2538
|
+
const refs = match
|
|
2539
|
+
.slice(1)
|
|
2540
|
+
.flatMap((value) => value.split("/"))
|
|
2541
|
+
.map(normalizeWarningReference)
|
|
2542
|
+
.filter(isConcreteWarningReference);
|
|
2543
|
+
if (refs.length > 0) {
|
|
2544
|
+
groups.push(refs);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
return groups;
|
|
2549
|
+
}
|
|
2550
|
+
function normalizeWarningReference(value) {
|
|
2551
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
2552
|
+
}
|
|
2553
|
+
function isConcreteWarningReference(value) {
|
|
2554
|
+
if (value.length < 4) {
|
|
2555
|
+
return false;
|
|
2556
|
+
}
|
|
2557
|
+
return !new Set([
|
|
2558
|
+
"backend",
|
|
2559
|
+
"frontend",
|
|
2560
|
+
"schema",
|
|
2561
|
+
"schemas",
|
|
2562
|
+
"service",
|
|
2563
|
+
"services",
|
|
2564
|
+
"source",
|
|
2565
|
+
"test",
|
|
2566
|
+
"tests",
|
|
2567
|
+
"types",
|
|
2568
|
+
]).has(value);
|
|
2569
|
+
}
|
|
2570
|
+
function buildContextBuilderMetrics(input) {
|
|
2571
|
+
return {
|
|
2572
|
+
files_seen: input.discovery.files_seen,
|
|
2573
|
+
files_considered: input.discovery.files_considered,
|
|
2574
|
+
files_selected: input.selection.files_selected,
|
|
2575
|
+
files_omitted: input.omissionCount,
|
|
2576
|
+
scan_cap_hit: input.discovery.scan_cap_hit,
|
|
2577
|
+
search_cap_hit: input.discovery.search_cap_hit,
|
|
2578
|
+
discovery_byte_cap_hit: input.discovery.discovery_byte_cap_hit,
|
|
2579
|
+
discovery_candidate_cap_hit: input.discovery.discovery_candidate_cap_hit,
|
|
2580
|
+
candidate_cap_hit: input.discovery.candidate_cap_hit,
|
|
2581
|
+
bytes_read: input.discovery.bytes_read,
|
|
2582
|
+
bytes_written: 0,
|
|
2583
|
+
estimated_tokens: input.selection.estimated_tokens,
|
|
2584
|
+
truncated: input.selection.truncated ||
|
|
2585
|
+
input.discovery.scan_cap_hit ||
|
|
2586
|
+
input.discovery.discovery_byte_cap_hit ||
|
|
2587
|
+
input.discovery.discovery_candidate_cap_hit,
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
//# sourceMappingURL=handler.js.map
|