@oscharko-dev/keiko-server 0.2.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/dist/.tsbuildinfo +1 -0
- package/dist/assistant-response.d.ts +6 -0
- package/dist/assistant-response.d.ts.map +1 -0
- package/dist/assistant-response.js +12 -0
- package/dist/browser.d.ts +11 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +245 -0
- package/dist/chat-handlers.d.ts +48 -0
- package/dist/chat-handlers.d.ts.map +1 -0
- package/dist/chat-handlers.js +821 -0
- package/dist/chat-stream-handlers.d.ts +4 -0
- package/dist/chat-stream-handlers.d.ts.map +1 -0
- package/dist/chat-stream-handlers.js +136 -0
- package/dist/conversation-prompt.d.ts +8 -0
- package/dist/conversation-prompt.d.ts.map +1 -0
- package/dist/conversation-prompt.js +36 -0
- package/dist/conversation-validation.d.ts +26 -0
- package/dist/conversation-validation.d.ts.map +1 -0
- package/dist/conversation-validation.js +125 -0
- package/dist/credentialPersistence.d.ts +23 -0
- package/dist/credentialPersistence.d.ts.map +1 -0
- package/dist/credentialPersistence.js +93 -0
- package/dist/credentialVault.d.ts +30 -0
- package/dist/credentialVault.d.ts.map +1 -0
- package/dist/credentialVault.js +206 -0
- package/dist/csp.d.ts +3 -0
- package/dist/csp.d.ts.map +1 -0
- package/dist/csp.js +75 -0
- package/dist/deps.d.ts +78 -0
- package/dist/deps.d.ts.map +1 -0
- package/dist/deps.js +457 -0
- package/dist/editor/agentRoutes.d.ts +7 -0
- package/dist/editor/agentRoutes.d.ts.map +1 -0
- package/dist/editor/agentRoutes.js +197 -0
- package/dist/editor/assuredGateRunner.d.ts +36 -0
- package/dist/editor/assuredGateRunner.d.ts.map +1 -0
- package/dist/editor/assuredGateRunner.js +100 -0
- package/dist/editor/assuredPreFilter.d.ts +34 -0
- package/dist/editor/assuredPreFilter.d.ts.map +1 -0
- package/dist/editor/assuredPreFilter.js +134 -0
- package/dist/editor/assuredPreFilterRunner.d.ts +31 -0
- package/dist/editor/assuredPreFilterRunner.d.ts.map +1 -0
- package/dist/editor/assuredPreFilterRunner.js +312 -0
- package/dist/editor/builtinLanguageProviders.d.ts +6 -0
- package/dist/editor/builtinLanguageProviders.d.ts.map +1 -0
- package/dist/editor/builtinLanguageProviders.js +221 -0
- package/dist/editor/codingContext.d.ts +12 -0
- package/dist/editor/codingContext.d.ts.map +1 -0
- package/dist/editor/codingContext.js +121 -0
- package/dist/editor/codingContextEvidence.d.ts +7 -0
- package/dist/editor/codingContextEvidence.d.ts.map +1 -0
- package/dist/editor/codingContextEvidence.js +52 -0
- package/dist/editor/codingContextProviders.d.ts +36 -0
- package/dist/editor/codingContextProviders.d.ts.map +1 -0
- package/dist/editor/codingContextProviders.js +348 -0
- package/dist/editor/completionModelEvidence.d.ts +16 -0
- package/dist/editor/completionModelEvidence.d.ts.map +1 -0
- package/dist/editor/completionModelEvidence.js +50 -0
- package/dist/editor/completionRoutes.d.ts +37 -0
- package/dist/editor/completionRoutes.d.ts.map +1 -0
- package/dist/editor/completionRoutes.js +411 -0
- package/dist/editor/contextRoutes.d.ts +6 -0
- package/dist/editor/contextRoutes.d.ts.map +1 -0
- package/dist/editor/contextRoutes.js +411 -0
- package/dist/editor/disposableAssuredExecution.d.ts +22 -0
- package/dist/editor/disposableAssuredExecution.d.ts.map +1 -0
- package/dist/editor/disposableAssuredExecution.js +57 -0
- package/dist/editor/editorCompletionModel.d.ts +47 -0
- package/dist/editor/editorCompletionModel.d.ts.map +1 -0
- package/dist/editor/editorCompletionModel.js +156 -0
- package/dist/editor/editorInlineCompletionModel.d.ts +34 -0
- package/dist/editor/editorInlineCompletionModel.d.ts.map +1 -0
- package/dist/editor/editorInlineCompletionModel.js +112 -0
- package/dist/editor/editorModelTokenBudget.d.ts +46 -0
- package/dist/editor/editorModelTokenBudget.d.ts.map +1 -0
- package/dist/editor/editorModelTokenBudget.js +121 -0
- package/dist/editor/inlineCompletionRateLimiter.d.ts +19 -0
- package/dist/editor/inlineCompletionRateLimiter.d.ts.map +1 -0
- package/dist/editor/inlineCompletionRateLimiter.js +46 -0
- package/dist/editor/inlineCompletionRoutes.d.ts +26 -0
- package/dist/editor/inlineCompletionRoutes.d.ts.map +1 -0
- package/dist/editor/inlineCompletionRoutes.js +404 -0
- package/dist/editor/inlineCompletionTelemetryEvidence.d.ts +5 -0
- package/dist/editor/inlineCompletionTelemetryEvidence.d.ts.map +1 -0
- package/dist/editor/inlineCompletionTelemetryEvidence.js +42 -0
- package/dist/editor/languageCancellation.d.ts +19 -0
- package/dist/editor/languageCancellation.d.ts.map +1 -0
- package/dist/editor/languageCancellation.js +48 -0
- package/dist/editor/languageProvider.d.ts +39 -0
- package/dist/editor/languageProvider.d.ts.map +1 -0
- package/dist/editor/languageProvider.js +11 -0
- package/dist/editor/languageRoutes.d.ts +15 -0
- package/dist/editor/languageRoutes.d.ts.map +1 -0
- package/dist/editor/languageRoutes.js +106 -0
- package/dist/editor/languageSanitize.d.ts +8 -0
- package/dist/editor/languageSanitize.d.ts.map +1 -0
- package/dist/editor/languageSanitize.js +101 -0
- package/dist/editor/languageService.d.ts +36 -0
- package/dist/editor/languageService.d.ts.map +1 -0
- package/dist/editor/languageService.js +93 -0
- package/dist/editor/languageServiceHost.d.ts +14 -0
- package/dist/editor/languageServiceHost.d.ts.map +1 -0
- package/dist/editor/languageServiceHost.js +242 -0
- package/dist/editor/localKnowledgeRetrieval.d.ts +21 -0
- package/dist/editor/localKnowledgeRetrieval.d.ts.map +1 -0
- package/dist/editor/localKnowledgeRetrieval.js +44 -0
- package/dist/editor/patchApplyEvidence.d.ts +21 -0
- package/dist/editor/patchApplyEvidence.d.ts.map +1 -0
- package/dist/editor/patchApplyEvidence.js +87 -0
- package/dist/editor/patchApplyRoutes.d.ts +16 -0
- package/dist/editor/patchApplyRoutes.d.ts.map +1 -0
- package/dist/editor/patchApplyRoutes.js +307 -0
- package/dist/editor/postApplyVerification.d.ts +42 -0
- package/dist/editor/postApplyVerification.d.ts.map +1 -0
- package/dist/editor/postApplyVerification.js +177 -0
- package/dist/editor/testGenerationEvidence.d.ts +6 -0
- package/dist/editor/testGenerationEvidence.d.ts.map +1 -0
- package/dist/editor/testGenerationEvidence.js +72 -0
- package/dist/editor/testGenerationPatch.d.ts +10 -0
- package/dist/editor/testGenerationPatch.d.ts.map +1 -0
- package/dist/editor/testGenerationPatch.js +66 -0
- package/dist/editor/testGenerationRoutes.d.ts +21 -0
- package/dist/editor/testGenerationRoutes.d.ts.map +1 -0
- package/dist/editor/testGenerationRoutes.js +254 -0
- package/dist/editor/testGenerationRunner.d.ts +23 -0
- package/dist/editor/testGenerationRunner.d.ts.map +1 -0
- package/dist/editor/testGenerationRunner.js +120 -0
- package/dist/editor/textOffsets.d.ts +6 -0
- package/dist/editor/textOffsets.d.ts.map +1 -0
- package/dist/editor/textOffsets.js +82 -0
- package/dist/editor/typescriptLanguageProvider.d.ts +3 -0
- package/dist/editor/typescriptLanguageProvider.d.ts.map +1 -0
- package/dist/editor/typescriptLanguageProvider.js +217 -0
- package/dist/evidence.d.ts +28 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +145 -0
- package/dist/files-deny.d.ts +3 -0
- package/dist/files-deny.d.ts.map +1 -0
- package/dist/files-deny.js +12 -0
- package/dist/files.d.ts +97 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +733 -0
- package/dist/gateway-setup.d.ts +10 -0
- package/dist/gateway-setup.d.ts.map +1 -0
- package/dist/gateway-setup.js +896 -0
- package/dist/governed-workflow.d.ts +17 -0
- package/dist/governed-workflow.d.ts.map +1 -0
- package/dist/governed-workflow.js +147 -0
- package/dist/grounded-answer.d.ts +12 -0
- package/dist/grounded-answer.d.ts.map +1 -0
- package/dist/grounded-answer.js +69 -0
- package/dist/grounded-context-index.d.ts +25 -0
- package/dist/grounded-context-index.d.ts.map +1 -0
- package/dist/grounded-context-index.js +169 -0
- package/dist/grounded-document-evidence.d.ts +28 -0
- package/dist/grounded-document-evidence.d.ts.map +1 -0
- package/dist/grounded-document-evidence.js +430 -0
- package/dist/grounded-handoff.d.ts +4 -0
- package/dist/grounded-handoff.d.ts.map +1 -0
- package/dist/grounded-handoff.js +445 -0
- package/dist/grounded-orchestrator.d.ts +43 -0
- package/dist/grounded-orchestrator.d.ts.map +1 -0
- package/dist/grounded-orchestrator.js +1445 -0
- package/dist/grounded-prompt.d.ts +2 -0
- package/dist/grounded-prompt.d.ts.map +1 -0
- package/dist/grounded-prompt.js +17 -0
- package/dist/grounded-qa-hybrid.d.ts +36 -0
- package/dist/grounded-qa-hybrid.d.ts.map +1 -0
- package/dist/grounded-qa-hybrid.js +762 -0
- package/dist/grounded-qa-multi-source.d.ts +38 -0
- package/dist/grounded-qa-multi-source.d.ts.map +1 -0
- package/dist/grounded-qa-multi-source.js +461 -0
- package/dist/grounded-qa.d.ts +45 -0
- package/dist/grounded-qa.d.ts.map +1 -0
- package/dist/grounded-qa.js +877 -0
- package/dist/grounded-rerank.d.ts +26 -0
- package/dist/grounded-rerank.d.ts.map +1 -0
- package/dist/grounded-rerank.js +72 -0
- package/dist/grounded-turn-registry.d.ts +23 -0
- package/dist/grounded-turn-registry.d.ts.map +1 -0
- package/dist/grounded-turn-registry.js +102 -0
- package/dist/headers.d.ts +3 -0
- package/dist/headers.d.ts.map +1 -0
- package/dist/headers.js +22 -0
- package/dist/host-check.d.ts +3 -0
- package/dist/host-check.d.ts.map +1 -0
- package/dist/host-check.js +58 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/load-csp.d.ts +3 -0
- package/dist/load-csp.d.ts.map +1 -0
- package/dist/load-csp.js +100 -0
- package/dist/local-knowledge-grounded-qa.d.ts +42 -0
- package/dist/local-knowledge-grounded-qa.d.ts.map +1 -0
- package/dist/local-knowledge-grounded-qa.js +678 -0
- package/dist/local-knowledge-handlers.d.ts +24 -0
- package/dist/local-knowledge-handlers.d.ts.map +1 -0
- package/dist/local-knowledge-handlers.js +1285 -0
- package/dist/local-knowledge-indexing-registry.d.ts +13 -0
- package/dist/local-knowledge-indexing-registry.d.ts.map +1 -0
- package/dist/local-knowledge-indexing-registry.js +53 -0
- package/dist/localKnowledgeKeyProvider.d.ts +11 -0
- package/dist/localKnowledgeKeyProvider.d.ts.map +1 -0
- package/dist/localKnowledgeKeyProvider.js +48 -0
- package/dist/memory-audit-event-builders.d.ts +21 -0
- package/dist/memory-audit-event-builders.d.ts.map +1 -0
- package/dist/memory-audit-event-builders.js +187 -0
- package/dist/memory-audit-handler.d.ts +23 -0
- package/dist/memory-audit-handler.d.ts.map +1 -0
- package/dist/memory-audit-handler.js +191 -0
- package/dist/memory-capture-policy.d.ts +10 -0
- package/dist/memory-capture-policy.d.ts.map +1 -0
- package/dist/memory-capture-policy.js +44 -0
- package/dist/memory-consolidation-handlers.d.ts +6 -0
- package/dist/memory-consolidation-handlers.d.ts.map +1 -0
- package/dist/memory-consolidation-handlers.js +491 -0
- package/dist/memory-consolidation-registry.d.ts +47 -0
- package/dist/memory-consolidation-registry.d.ts.map +1 -0
- package/dist/memory-consolidation-registry.js +106 -0
- package/dist/memory-conv-handlers.d.ts +8 -0
- package/dist/memory-conv-handlers.d.ts.map +1 -0
- package/dist/memory-conv-handlers.js +369 -0
- package/dist/memory-conversation-context.d.ts +13 -0
- package/dist/memory-conversation-context.d.ts.map +1 -0
- package/dist/memory-conversation-context.js +22 -0
- package/dist/memory-diagnostics.d.ts +29 -0
- package/dist/memory-diagnostics.d.ts.map +1 -0
- package/dist/memory-diagnostics.js +122 -0
- package/dist/memory-embedding.d.ts +21 -0
- package/dist/memory-embedding.d.ts.map +1 -0
- package/dist/memory-embedding.js +264 -0
- package/dist/memory-handlers.d.ts +19 -0
- package/dist/memory-handlers.d.ts.map +1 -0
- package/dist/memory-handlers.js +1204 -0
- package/dist/memory-maintenance-handlers.d.ts +35 -0
- package/dist/memory-maintenance-handlers.d.ts.map +1 -0
- package/dist/memory-maintenance-handlers.js +219 -0
- package/dist/memory-record-builders.d.ts +4 -0
- package/dist/memory-record-builders.d.ts.map +1 -0
- package/dist/memory-record-builders.js +19 -0
- package/dist/memory-retention.d.ts +31 -0
- package/dist/memory-retention.d.ts.map +1 -0
- package/dist/memory-retention.js +151 -0
- package/dist/memory-retrieval-signals.d.ts +12 -0
- package/dist/memory-retrieval-signals.d.ts.map +1 -0
- package/dist/memory-retrieval-signals.js +100 -0
- package/dist/memory-salience.d.ts +12 -0
- package/dist/memory-salience.d.ts.map +1 -0
- package/dist/memory-salience.js +154 -0
- package/dist/memory-scope-sanitizer.d.ts +6 -0
- package/dist/memory-scope-sanitizer.d.ts.map +1 -0
- package/dist/memory-scope-sanitizer.js +106 -0
- package/dist/memory-target-resolver.d.ts +4 -0
- package/dist/memory-target-resolver.d.ts.map +1 -0
- package/dist/memory-target-resolver.js +73 -0
- package/dist/memory-workflow-port.d.ts +14 -0
- package/dist/memory-workflow-port.d.ts.map +1 -0
- package/dist/memory-workflow-port.js +186 -0
- package/dist/private-json.d.ts +3 -0
- package/dist/private-json.d.ts.map +1 -0
- package/dist/private-json.js +62 -0
- package/dist/promptEnhancer/index.d.ts +3 -0
- package/dist/promptEnhancer/index.d.ts.map +1 -0
- package/dist/promptEnhancer/index.js +5 -0
- package/dist/promptEnhancer/orchestrate.d.ts +2 -0
- package/dist/promptEnhancer/orchestrate.d.ts.map +1 -0
- package/dist/promptEnhancer/orchestrate.js +5 -0
- package/dist/promptEnhancer/routes.d.ts +9 -0
- package/dist/promptEnhancer/routes.d.ts.map +1 -0
- package/dist/promptEnhancer/routes.js +205 -0
- package/dist/qualityIntelligence/capsuleAdapter.d.ts +27 -0
- package/dist/qualityIntelligence/capsuleAdapter.d.ts.map +1 -0
- package/dist/qualityIntelligence/capsuleAdapter.js +57 -0
- package/dist/qualityIntelligence/connectorAuthorization.d.ts +22 -0
- package/dist/qualityIntelligence/connectorAuthorization.d.ts.map +1 -0
- package/dist/qualityIntelligence/connectorAuthorization.js +35 -0
- package/dist/qualityIntelligence/connectorErrors.d.ts +16 -0
- package/dist/qualityIntelligence/connectorErrors.d.ts.map +1 -0
- package/dist/qualityIntelligence/connectorErrors.js +56 -0
- package/dist/qualityIntelligence/connectorRoutes.d.ts +7 -0
- package/dist/qualityIntelligence/connectorRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/connectorRoutes.js +167 -0
- package/dist/qualityIntelligence/editRoutes.d.ts +5 -0
- package/dist/qualityIntelligence/editRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/editRoutes.js +293 -0
- package/dist/qualityIntelligence/exportAssembly.d.ts +22 -0
- package/dist/qualityIntelligence/exportAssembly.d.ts.map +1 -0
- package/dist/qualityIntelligence/exportAssembly.js +352 -0
- package/dist/qualityIntelligence/exportRoutes.d.ts +5 -0
- package/dist/qualityIntelligence/exportRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/exportRoutes.js +320 -0
- package/dist/qualityIntelligence/figma/figmaConcurrency.d.ts +8 -0
- package/dist/qualityIntelligence/figma/figmaConcurrency.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaConcurrency.js +34 -0
- package/dist/qualityIntelligence/figma/figmaConnector.d.ts +65 -0
- package/dist/qualityIntelligence/figma/figmaConnector.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaConnector.js +184 -0
- package/dist/qualityIntelligence/figma/figmaConnectorAudit.d.ts +52 -0
- package/dist/qualityIntelligence/figma/figmaConnectorAudit.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaConnectorAudit.js +63 -0
- package/dist/qualityIntelligence/figma/figmaConnectorErrors.d.ts +31 -0
- package/dist/qualityIntelligence/figma/figmaConnectorErrors.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaConnectorErrors.js +220 -0
- package/dist/qualityIntelligence/figma/figmaConnectorMetrics.d.ts +44 -0
- package/dist/qualityIntelligence/figma/figmaConnectorMetrics.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaConnectorMetrics.js +49 -0
- package/dist/qualityIntelligence/figma/figmaConsent.d.ts +39 -0
- package/dist/qualityIntelligence/figma/figmaConsent.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaConsent.js +62 -0
- package/dist/qualityIntelligence/figma/figmaHttpPort.d.ts +28 -0
- package/dist/qualityIntelligence/figma/figmaHttpPort.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaHttpPort.js +70 -0
- package/dist/qualityIntelligence/figma/figmaObservedActions.d.ts +49 -0
- package/dist/qualityIntelligence/figma/figmaObservedActions.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaObservedActions.js +89 -0
- package/dist/qualityIntelligence/figma/figmaReadiness.d.ts +32 -0
- package/dist/qualityIntelligence/figma/figmaReadiness.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaReadiness.js +67 -0
- package/dist/qualityIntelligence/figma/figmaRenderPort.d.ts +29 -0
- package/dist/qualityIntelligence/figma/figmaRenderPort.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaRenderPort.js +93 -0
- package/dist/qualityIntelligence/figma/figmaResnapshot.d.ts +28 -0
- package/dist/qualityIntelligence/figma/figmaResnapshot.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaResnapshot.js +38 -0
- package/dist/qualityIntelligence/figma/figmaRetry.d.ts +31 -0
- package/dist/qualityIntelligence/figma/figmaRetry.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaRetry.js +62 -0
- package/dist/qualityIntelligence/figma/figmaScopeRef.d.ts +9 -0
- package/dist/qualityIntelligence/figma/figmaScopeRef.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaScopeRef.js +18 -0
- package/dist/qualityIntelligence/figma/figmaScopedPagination.d.ts +86 -0
- package/dist/qualityIntelligence/figma/figmaScopedPagination.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaScopedPagination.js +308 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotBuilder.d.ts +31 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotBuilder.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotBuilder.js +314 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotHash.d.ts +18 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotHash.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotHash.js +63 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotTypes.d.ts +65 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotTypes.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotTypes.js +13 -0
- package/dist/qualityIntelligence/figma/figmaTokenSource.d.ts +9 -0
- package/dist/qualityIntelligence/figma/figmaTokenSource.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaTokenSource.js +61 -0
- package/dist/qualityIntelligence/figma/figmaTokenStore.d.ts +19 -0
- package/dist/qualityIntelligence/figma/figmaTokenStore.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaTokenStore.js +156 -0
- package/dist/qualityIntelligence/figma/figmaUrl.d.ts +6 -0
- package/dist/qualityIntelligence/figma/figmaUrl.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaUrl.js +36 -0
- package/dist/qualityIntelligence/figma/index.d.ts +20 -0
- package/dist/qualityIntelligence/figma/index.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/index.js +26 -0
- package/dist/qualityIntelligence/figmaCodegenRoutes.d.ts +28 -0
- package/dist/qualityIntelligence/figmaCodegenRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/figmaCodegenRoutes.js +165 -0
- package/dist/qualityIntelligence/figmaSnapshotAdapter.d.ts +55 -0
- package/dist/qualityIntelligence/figmaSnapshotAdapter.d.ts.map +1 -0
- package/dist/qualityIntelligence/figmaSnapshotAdapter.js +219 -0
- package/dist/qualityIntelligence/figmaSnapshotOrchestration.d.ts +64 -0
- package/dist/qualityIntelligence/figmaSnapshotOrchestration.d.ts.map +1 -0
- package/dist/qualityIntelligence/figmaSnapshotOrchestration.js +203 -0
- package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts +112 -0
- package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/figmaSnapshotRoutes.js +1063 -0
- package/dist/qualityIntelligence/figmaSnapshotScreenIds.d.ts +19 -0
- package/dist/qualityIntelligence/figmaSnapshotScreenIds.d.ts.map +1 -0
- package/dist/qualityIntelligence/figmaSnapshotScreenIds.js +75 -0
- package/dist/qualityIntelligence/generationPort.d.ts +15 -0
- package/dist/qualityIntelligence/generationPort.d.ts.map +1 -0
- package/dist/qualityIntelligence/generationPort.js +185 -0
- package/dist/qualityIntelligence/handoffErrors.d.ts +9 -0
- package/dist/qualityIntelligence/handoffErrors.d.ts.map +1 -0
- package/dist/qualityIntelligence/handoffErrors.js +21 -0
- package/dist/qualityIntelligence/handoffRoutes.d.ts +15 -0
- package/dist/qualityIntelligence/handoffRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/handoffRoutes.js +341 -0
- package/dist/qualityIntelligence/index.d.ts +17 -0
- package/dist/qualityIntelligence/index.d.ts.map +1 -0
- package/dist/qualityIntelligence/index.js +36 -0
- package/dist/qualityIntelligence/judgePort.d.ts +30 -0
- package/dist/qualityIntelligence/judgePort.d.ts.map +1 -0
- package/dist/qualityIntelligence/judgePort.js +326 -0
- package/dist/qualityIntelligence/modelSelection.d.ts +58 -0
- package/dist/qualityIntelligence/modelSelection.d.ts.map +1 -0
- package/dist/qualityIntelligence/modelSelection.js +148 -0
- package/dist/qualityIntelligence/reCheckRoutes.d.ts +6 -0
- package/dist/qualityIntelligence/reCheckRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/reCheckRoutes.js +1157 -0
- package/dist/qualityIntelligence/retentionEnforcement.d.ts +13 -0
- package/dist/qualityIntelligence/retentionEnforcement.d.ts.map +1 -0
- package/dist/qualityIntelligence/retentionEnforcement.js +47 -0
- package/dist/qualityIntelligence/retentionRoutes.d.ts +8 -0
- package/dist/qualityIntelligence/retentionRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/retentionRoutes.js +74 -0
- package/dist/qualityIntelligence/reviewRoutes.d.ts +5 -0
- package/dist/qualityIntelligence/reviewRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/reviewRoutes.js +145 -0
- package/dist/qualityIntelligence/reviewStore.d.ts +75 -0
- package/dist/qualityIntelligence/reviewStore.d.ts.map +1 -0
- package/dist/qualityIntelligence/reviewStore.js +170 -0
- package/dist/qualityIntelligence/runExecution.d.ts +36 -0
- package/dist/qualityIntelligence/runExecution.d.ts.map +1 -0
- package/dist/qualityIntelligence/runExecution.js +180 -0
- package/dist/qualityIntelligence/runIngestion.d.ts +70 -0
- package/dist/qualityIntelligence/runIngestion.d.ts.map +1 -0
- package/dist/qualityIntelligence/runIngestion.js +1235 -0
- package/dist/qualityIntelligence/runRegistry.d.ts +31 -0
- package/dist/qualityIntelligence/runRegistry.d.ts.map +1 -0
- package/dist/qualityIntelligence/runRegistry.js +66 -0
- package/dist/qualityIntelligence/runRoutes.d.ts +16 -0
- package/dist/qualityIntelligence/runRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/runRoutes.js +357 -0
- package/dist/qualityIntelligence/traceabilityRoutes.d.ts +5 -0
- package/dist/qualityIntelligence/traceabilityRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/traceabilityRoutes.js +173 -0
- package/dist/qualityIntelligence/uiRoutes.d.ts +7 -0
- package/dist/qualityIntelligence/uiRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/uiRoutes.js +336 -0
- package/dist/read-handlers.d.ts +9 -0
- package/dist/read-handlers.d.ts.map +1 -0
- package/dist/read-handlers.js +265 -0
- package/dist/relationship-handlers.d.ts +191 -0
- package/dist/relationship-handlers.d.ts.map +1 -0
- package/dist/relationship-handlers.js +0 -0
- package/dist/routes.d.ts +37 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +507 -0
- package/dist/run-engine.d.ts +25 -0
- package/dist/run-engine.d.ts.map +1 -0
- package/dist/run-engine.js +385 -0
- package/dist/run-handlers.d.ts +9 -0
- package/dist/run-handlers.d.ts.map +1 -0
- package/dist/run-handlers.js +465 -0
- package/dist/run-request.d.ts +17 -0
- package/dist/run-request.d.ts.map +1 -0
- package/dist/run-request.js +219 -0
- package/dist/runs.d.ts +47 -0
- package/dist/runs.d.ts.map +1 -0
- package/dist/runs.js +100 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +152 -0
- package/dist/sink.d.ts +28 -0
- package/dist/sink.d.ts.map +1 -0
- package/dist/sink.js +80 -0
- package/dist/sse-write.d.ts +9 -0
- package/dist/sse-write.d.ts.map +1 -0
- package/dist/sse-write.js +26 -0
- package/dist/sse.d.ts +8 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +27 -0
- package/dist/static.d.ts +5 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/static.js +76 -0
- package/dist/store/chats.d.ts +17 -0
- package/dist/store/chats.d.ts.map +1 -0
- package/dist/store/chats.js +624 -0
- package/dist/store/db.d.ts +11 -0
- package/dist/store/db.d.ts.map +1 -0
- package/dist/store/db.js +203 -0
- package/dist/store/errors.d.ts +13 -0
- package/dist/store/errors.d.ts.map +1 -0
- package/dist/store/errors.js +30 -0
- package/dist/store/index.d.ts +7 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +6 -0
- package/dist/store/messages.d.ts +8 -0
- package/dist/store/messages.d.ts.map +1 -0
- package/dist/store/messages.js +149 -0
- package/dist/store/paths.d.ts +5 -0
- package/dist/store/paths.d.ts.map +1 -0
- package/dist/store/paths.js +84 -0
- package/dist/store/projects.d.ts +8 -0
- package/dist/store/projects.d.ts.map +1 -0
- package/dist/store/projects.js +59 -0
- package/dist/store/relationship-audit.d.ts +42 -0
- package/dist/store/relationship-audit.d.ts.map +1 -0
- package/dist/store/relationship-audit.js +155 -0
- package/dist/store/relationships.d.ts +191 -0
- package/dist/store/relationships.d.ts.map +1 -0
- package/dist/store/relationships.js +724 -0
- package/dist/store/schema.d.ts +4 -0
- package/dist/store/schema.d.ts.map +1 -0
- package/dist/store/schema.js +220 -0
- package/dist/store/types.d.ts +29 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/types.js +8 -0
- package/dist/store/validation.d.ts +7 -0
- package/dist/store/validation.d.ts.map +1 -0
- package/dist/store/validation.js +117 -0
- package/dist/store-handlers.d.ts +17 -0
- package/dist/store-handlers.d.ts.map +1 -0
- package/dist/store-handlers.js +872 -0
- package/dist/terminal-errors.d.ts +22 -0
- package/dist/terminal-errors.d.ts.map +1 -0
- package/dist/terminal-errors.js +45 -0
- package/dist/terminal-evidence.d.ts +21 -0
- package/dist/terminal-evidence.d.ts.map +1 -0
- package/dist/terminal-evidence.js +65 -0
- package/dist/terminal-routes.d.ts +10 -0
- package/dist/terminal-routes.d.ts.map +1 -0
- package/dist/terminal-routes.js +219 -0
- package/dist/terminal.d.ts +68 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +855 -0
- package/package.json +52 -0
|
@@ -0,0 +1,1445 @@
|
|
|
1
|
+
// Grounded repository Q&A orchestrator (Epic #177, Issue #185). Composes the connected-context
|
|
2
|
+
// layers — #181 exploration planner, #179 lexical search facade, #180 structural adapters,
|
|
3
|
+
// #182 candidate ranker, and #183 context-pack assembler — into a single linear pipeline that
|
|
4
|
+
// produces a redacted `ConnectedContextPack` plus an assistant-content string. The model call
|
|
5
|
+
// is injected through the `GroundedAnswerer` seam so production can route through the Model
|
|
6
|
+
// Gateway while tests can keep deterministic answerers.
|
|
7
|
+
//
|
|
8
|
+
// Pure orchestration: the only IO this module performs is delegated through the workspace
|
|
9
|
+
// package's already-bounded WorkspaceFs port. Path validation is enforced by every composed
|
|
10
|
+
// layer at its own boundary, so this file does not re-validate scope paths.
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { isValidScopePath, } from "@oscharko-dev/keiko-contracts/connected-context";
|
|
13
|
+
import { advanceRing, applyUsage, assembleContextPack, canContinue, classifyRetrievalIntent, complete, contextPackIndexKey, planAndGovern, rankCandidates, } from "@oscharko-dev/keiko-workflows";
|
|
14
|
+
import { DEFAULT_SEARCH_LIMITS, FileTooLargeError, RepoSearchUnsupportedFileError, detectWorkspaceAt, findFiles, gitHistoryAdapter, importGraphAdapter, readExcerpt, resolveWithinWorkspace, runStructuralAdapters, searchText, testSourcePairingAdapter, containedRealPathInfo, evidenceAtomStableId, } from "@oscharko-dev/keiko-workspace";
|
|
15
|
+
import { CancelledError } from "@oscharko-dev/keiko-model-gateway";
|
|
16
|
+
import { nodeWorkspaceFs } from "@oscharko-dev/keiko-workspace/internal/fs";
|
|
17
|
+
import { normalizeGroundedAnswerPayload } from "./grounded-answer.js";
|
|
18
|
+
import { collectConnectedDocumentEvidence, isConnectedDocumentPath, } from "./grounded-document-evidence.js";
|
|
19
|
+
// Raised when the planner asks for clarification (no anchors, too-generic prompt, etc.). The
|
|
20
|
+
// route maps this to a 400 BAD_REQUEST via clarificationUserMessage below; the Error message
|
|
21
|
+
// itself keeps the stable machine-ish form for logs and tests.
|
|
22
|
+
export class ClarificationNeededError extends Error {
|
|
23
|
+
clarification;
|
|
24
|
+
constructor(clarification) {
|
|
25
|
+
super(`clarification needed: ${clarification.reason}`);
|
|
26
|
+
this.clarification = clarification;
|
|
27
|
+
this.name = "ClarificationNeededError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Release 0.2.0 — user-facing mapping for a planner clarification. The raw reason string
|
|
31
|
+
// ("clarification needed: too-generic") told the user nothing actionable; the HTTP message now
|
|
32
|
+
// says what the planner needs and folds in the planner's own suggested questions. Static text
|
|
33
|
+
// plus planner-built suggestions only — no user/file content, so nothing to redact.
|
|
34
|
+
export function clarificationUserMessage(error) {
|
|
35
|
+
const { reason, suggestedQuestions } = error.clarification;
|
|
36
|
+
const intro = reason === "scope-empty"
|
|
37
|
+
? "Die verbundene Quelle enthält nichts Durchsuchbares."
|
|
38
|
+
: reason === "scope-invalid"
|
|
39
|
+
? "Die verbundene Quelle konnte nicht durchsucht werden."
|
|
40
|
+
: "Keiko braucht mehr Kontext, um die verbundenen Quellen gezielt zu durchsuchen.";
|
|
41
|
+
const anchorHint = reason === "no-anchors" || reason === "too-generic"
|
|
42
|
+
? " Nenne eine konkrete Datei, einen Identifier, eine Fehlermeldung oder eine exakte Phrase."
|
|
43
|
+
: "";
|
|
44
|
+
const examples = suggestedQuestions.slice(0, 2);
|
|
45
|
+
const exampleText = examples.length > 0 ? ` Zum Beispiel: ${examples.map((q) => `"${q}"`).join(" oder ")}` : "";
|
|
46
|
+
return `${intro}${anchorHint}${exampleText}`;
|
|
47
|
+
}
|
|
48
|
+
const TEXT_ENCODER = new TextEncoder();
|
|
49
|
+
function throwIfCancelled(signal) {
|
|
50
|
+
if (signal?.aborted === true) {
|
|
51
|
+
throw new CancelledError("grounded repository request cancelled");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function utf8ByteLength(value) {
|
|
55
|
+
return TEXT_ENCODER.encode(value).length;
|
|
56
|
+
}
|
|
57
|
+
function usageDelta(overrides = {}) {
|
|
58
|
+
return {
|
|
59
|
+
searchCalls: 0,
|
|
60
|
+
filesRead: 0,
|
|
61
|
+
excerptBytes: 0,
|
|
62
|
+
modelInputTokens: 0,
|
|
63
|
+
modelOutputTokens: 0,
|
|
64
|
+
elapsedMs: 0,
|
|
65
|
+
rerankCalls: 0,
|
|
66
|
+
...overrides,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function clampUsageToBudget(usage, budget) {
|
|
70
|
+
return {
|
|
71
|
+
searchCalls: Math.min(usage.searchCalls, budget.searchCallsMax),
|
|
72
|
+
filesRead: Math.min(usage.filesRead, budget.filesReadMax),
|
|
73
|
+
excerptBytes: Math.min(usage.excerptBytes, budget.excerptBytesMax),
|
|
74
|
+
modelInputTokens: Math.min(usage.modelInputTokens, budget.modelInputTokensMax),
|
|
75
|
+
modelOutputTokens: Math.min(usage.modelOutputTokens, budget.modelOutputTokensMax),
|
|
76
|
+
elapsedMs: Math.min(usage.elapsedMs, budget.elapsedMsMax),
|
|
77
|
+
rerankCalls: Math.min(usage.rerankCalls, budget.rerankCallsMax),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function budgetClipped(stopReason, nowMs) {
|
|
81
|
+
return {
|
|
82
|
+
kind: "budget-clipped",
|
|
83
|
+
claim: `repository exploration stopped: ${stopReason}`,
|
|
84
|
+
impactedAtomIds: [],
|
|
85
|
+
emittedAtMs: nowMs,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function answerBudgetClipped(dimensions, nowMs) {
|
|
89
|
+
return {
|
|
90
|
+
kind: "budget-clipped",
|
|
91
|
+
claim: `grounded answer exceeded budget: ${dimensions.join(", ")}`,
|
|
92
|
+
impactedAtomIds: [],
|
|
93
|
+
emittedAtMs: nowMs,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function noEvidence(nowMs) {
|
|
97
|
+
return {
|
|
98
|
+
kind: "no-evidence",
|
|
99
|
+
claim: "No repository evidence matched the connected scope for this question.",
|
|
100
|
+
impactedAtomIds: [],
|
|
101
|
+
emittedAtMs: nowMs,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function toolUnavailable(claim, nowMs) {
|
|
105
|
+
return {
|
|
106
|
+
kind: "tool-unavailable",
|
|
107
|
+
claim,
|
|
108
|
+
impactedAtomIds: [],
|
|
109
|
+
emittedAtMs: nowMs,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function readBudgetStopReason(budget) {
|
|
113
|
+
const exhausted = [
|
|
114
|
+
...(budget.filesReadMax <= 0 ? ["filesRead"] : []),
|
|
115
|
+
...(budget.excerptBytesMax <= 0 ? ["excerptBytes"] : []),
|
|
116
|
+
];
|
|
117
|
+
if (exhausted.length === 0) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
return `budget-exhausted on ${exhausted.join(", ")}`;
|
|
121
|
+
}
|
|
122
|
+
function omittedFromSearchCandidates(candidates, nowMs) {
|
|
123
|
+
const omitted = [];
|
|
124
|
+
for (const candidate of candidates) {
|
|
125
|
+
if (candidate.omitted === undefined) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (!isValidScopePath(candidate.scopePath, { mustBeRelative: true })) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
omitted.push({
|
|
132
|
+
scopePath: candidate.scopePath,
|
|
133
|
+
reason: candidate.omitted,
|
|
134
|
+
omittedAtMs: nowMs,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return omitted;
|
|
138
|
+
}
|
|
139
|
+
function safeAdapterName(name) {
|
|
140
|
+
const cleaned = name.replace(/[^a-zA-Z0-9._-]/g, "");
|
|
141
|
+
return cleaned.length === 0 ? "structural-adapter" : cleaned;
|
|
142
|
+
}
|
|
143
|
+
function adapterDiagnostics(result, nowMs) {
|
|
144
|
+
const markers = [];
|
|
145
|
+
const seen = new Set();
|
|
146
|
+
for (const name of result.unavailable) {
|
|
147
|
+
const safeName = safeAdapterName(name);
|
|
148
|
+
if (seen.has(`unavailable:${safeName}`)) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
seen.add(`unavailable:${safeName}`);
|
|
152
|
+
markers.push(toolUnavailable(`structural adapter unavailable: ${safeName}`, nowMs));
|
|
153
|
+
}
|
|
154
|
+
for (const error of result.errored) {
|
|
155
|
+
const safeName = safeAdapterName(error.name);
|
|
156
|
+
if (seen.has(`errored:${safeName}`)) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
seen.add(`errored:${safeName}`);
|
|
160
|
+
markers.push(toolUnavailable(`structural adapter failed safely: ${safeName}`, nowMs));
|
|
161
|
+
}
|
|
162
|
+
return markers;
|
|
163
|
+
}
|
|
164
|
+
function dedupeUncertainty(markers) {
|
|
165
|
+
const seen = new Set();
|
|
166
|
+
const out = [];
|
|
167
|
+
for (const marker of markers) {
|
|
168
|
+
const key = `${marker.kind}:${marker.claim}`;
|
|
169
|
+
if (seen.has(key)) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
seen.add(key);
|
|
173
|
+
out.push(marker);
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
function anchorKindForTerm(term, anchors) {
|
|
178
|
+
return anchors.find((anchor) => anchor.term === term)?.kind;
|
|
179
|
+
}
|
|
180
|
+
function looksPathAnchor(term) {
|
|
181
|
+
return term.includes("/") || /\.[a-z0-9]+$/i.test(term);
|
|
182
|
+
}
|
|
183
|
+
function queryForStructuralAnchor(term, kind, base) {
|
|
184
|
+
return {
|
|
185
|
+
...base,
|
|
186
|
+
kind: kind === "identifier" || (!looksPathAnchor(term) && kind !== "path")
|
|
187
|
+
? "exact-symbol"
|
|
188
|
+
: "natural-language",
|
|
189
|
+
text: term,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function structuralQueriesForRing(ring, inputs) {
|
|
193
|
+
const queries = [];
|
|
194
|
+
const seen = new Set();
|
|
195
|
+
for (const term of ring.anchorTerms) {
|
|
196
|
+
const anchorKind = anchorKindForTerm(term, inputs.anchors);
|
|
197
|
+
if (anchorKind !== "path" && anchorKind !== "identifier" && anchorKind !== "quoted") {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const query = queryForStructuralAnchor(term, anchorKind, inputs.query);
|
|
201
|
+
const key = `${query.kind}:${query.text}`;
|
|
202
|
+
if (seen.has(key)) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
seen.add(key);
|
|
206
|
+
queries.push(query);
|
|
207
|
+
}
|
|
208
|
+
return queries.length === 0 ? [inputs.query] : queries;
|
|
209
|
+
}
|
|
210
|
+
function plannedSearchCallsForRing(ring, inputs) {
|
|
211
|
+
return ring.kind === "structural" ? structuralQueriesForRing(ring, inputs).length : 1;
|
|
212
|
+
}
|
|
213
|
+
function mergeAtomsByStableId(results, cap) {
|
|
214
|
+
const atoms = [];
|
|
215
|
+
const seen = new Set();
|
|
216
|
+
for (const result of results) {
|
|
217
|
+
for (const atom of result.atoms) {
|
|
218
|
+
if (atoms.length >= cap) {
|
|
219
|
+
return atoms;
|
|
220
|
+
}
|
|
221
|
+
if (seen.has(atom.stableId)) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
seen.add(atom.stableId);
|
|
225
|
+
atoms.push(atom);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return atoms;
|
|
229
|
+
}
|
|
230
|
+
async function runRing(ring, inputs) {
|
|
231
|
+
if (ring.kind === "lexical") {
|
|
232
|
+
const result = await searchText(inputs.searchScope, inputs.query, ring.searchLimits, {
|
|
233
|
+
fs: inputs.fs,
|
|
234
|
+
nowMs: inputs.nowMs,
|
|
235
|
+
searchHints: { retrievalIntent: inputs.retrievalIntent },
|
|
236
|
+
});
|
|
237
|
+
// Lexical scanning is transient: each candidate file is read to match lines, then discarded.
|
|
238
|
+
// It does NOT consume the excerpt budget. Charging result.filesScanned against filesReadMax
|
|
239
|
+
// (Epic #177 retrieval defect) let a wide scan exhaust the budget the excerpt READ phase needs
|
|
240
|
+
// and starved multi-file scopes — the scan could only ever examine ~4 files. The reserved
|
|
241
|
+
// search call (one per ring) plus elapsedMs already bound the scan; the files whose content
|
|
242
|
+
// actually enters the pack are charged when their excerpts are read in the assembler.
|
|
243
|
+
return {
|
|
244
|
+
atoms: result.atoms,
|
|
245
|
+
omitted: omittedFromSearchCandidates(result.candidates, inputs.nowMs()),
|
|
246
|
+
uncertainty: [],
|
|
247
|
+
usage: usageDelta({ elapsedMs: result.elapsedMs }),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
// Keep the planner's ring split authoritative: the structural ring should only run the
|
|
251
|
+
// structural adapters, while the git-history ring should only run the repo-level history
|
|
252
|
+
// adapter. Reusing the full default registry for both rings duplicates atoms and inflates
|
|
253
|
+
// downstream ranking signals whenever a workspace-root query plans both rings.
|
|
254
|
+
const registry = ring.kind === "structural"
|
|
255
|
+
? { adapters: [testSourcePairingAdapter, importGraphAdapter] }
|
|
256
|
+
: { adapters: [gitHistoryAdapter] };
|
|
257
|
+
const queries = ring.kind === "structural" ? structuralQueriesForRing(ring, inputs) : [inputs.query];
|
|
258
|
+
const results = await Promise.all(queries.map((query) => runStructuralAdapters(registry, inputs.searchScope, query, ring.searchLimits, inputs.fs, {
|
|
259
|
+
nowMs: inputs.nowMs,
|
|
260
|
+
})));
|
|
261
|
+
const elapsedMs = results.reduce((sum, result) => sum + result.elapsedMs, 0);
|
|
262
|
+
const cap = Math.min(ring.searchLimits.maxMatchesReturned, inputs.query.maxResults);
|
|
263
|
+
const atoms = ring.kind === "git-history" ? [] : mergeAtomsByStableId(results, cap);
|
|
264
|
+
const uncertainty = dedupeUncertainty(results.flatMap((result) => adapterDiagnostics(result, inputs.nowMs())));
|
|
265
|
+
return {
|
|
266
|
+
atoms,
|
|
267
|
+
omitted: [],
|
|
268
|
+
uncertainty,
|
|
269
|
+
usage: usageDelta({ elapsedMs }),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function reserveRingSearchCalls(governor, ring, inputs) {
|
|
273
|
+
const reserved = applyUsage(governor, usageDelta({ searchCalls: plannedSearchCallsForRing(ring, inputs) }));
|
|
274
|
+
if (reserved.status !== "budget-exhausted") {
|
|
275
|
+
return { governor: reserved };
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
governor: reserved,
|
|
279
|
+
marker: budgetClipped(reserved.stopReason ?? "budget exhausted", inputs.nowMs()),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
async function runAllRings(rings, inputs, initialGovernor) {
|
|
283
|
+
const blockedByReadBudget = readBudgetStopReason(initialGovernor.plan.budget);
|
|
284
|
+
if (blockedByReadBudget !== undefined) {
|
|
285
|
+
return {
|
|
286
|
+
atoms: [],
|
|
287
|
+
omitted: [],
|
|
288
|
+
governor: complete(initialGovernor),
|
|
289
|
+
uncertainty: [budgetClipped(blockedByReadBudget, inputs.nowMs())],
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const atoms = [];
|
|
293
|
+
const omitted = [];
|
|
294
|
+
const uncertainty = [];
|
|
295
|
+
let governor = initialGovernor;
|
|
296
|
+
for (const ring of rings) {
|
|
297
|
+
throwIfCancelled(inputs.signal);
|
|
298
|
+
if (!canContinue(governor)) {
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
const reservation = reserveRingSearchCalls(governor, ring, inputs);
|
|
302
|
+
governor = reservation.governor;
|
|
303
|
+
if (reservation.marker !== undefined) {
|
|
304
|
+
uncertainty.push(reservation.marker);
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
const result = await runRing(ring, inputs);
|
|
308
|
+
throwIfCancelled(inputs.signal);
|
|
309
|
+
const afterRing = applyUsage(governor, result.usage);
|
|
310
|
+
atoms.push(...result.atoms);
|
|
311
|
+
omitted.push(...result.omitted);
|
|
312
|
+
uncertainty.push(...result.uncertainty);
|
|
313
|
+
if (afterRing.status === "budget-exhausted") {
|
|
314
|
+
governor = afterRing;
|
|
315
|
+
uncertainty.push(budgetClipped(afterRing.stopReason ?? "budget exhausted", inputs.nowMs()));
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
governor = advanceRing(afterRing);
|
|
319
|
+
}
|
|
320
|
+
if (governor.status === "running") {
|
|
321
|
+
governor = complete(governor);
|
|
322
|
+
}
|
|
323
|
+
return { atoms, omitted, governor, uncertainty };
|
|
324
|
+
}
|
|
325
|
+
const DEFAULT_EXCERPT_WINDOW = { startLine: 1, endLine: 200 };
|
|
326
|
+
const EXCERPT_CONTEXT_LINES = 2;
|
|
327
|
+
const MAX_EXCERPT_WINDOWS_PER_FILE = 8;
|
|
328
|
+
const PROJECT_METADATA_QUERY_TERMS = [
|
|
329
|
+
"abhängigkeit",
|
|
330
|
+
"abhängigkeiten",
|
|
331
|
+
"abhaengigkeit",
|
|
332
|
+
"abhaengigkeiten",
|
|
333
|
+
"build",
|
|
334
|
+
"cypress",
|
|
335
|
+
"dependenc",
|
|
336
|
+
"dependencies",
|
|
337
|
+
"devdependencies",
|
|
338
|
+
"framework",
|
|
339
|
+
"java-script",
|
|
340
|
+
"javascript",
|
|
341
|
+
"jest",
|
|
342
|
+
"node",
|
|
343
|
+
"node.js",
|
|
344
|
+
"npm",
|
|
345
|
+
"package",
|
|
346
|
+
"package.json",
|
|
347
|
+
"package-manager",
|
|
348
|
+
"paketmanager",
|
|
349
|
+
"playwright",
|
|
350
|
+
"pnpm",
|
|
351
|
+
"react",
|
|
352
|
+
"script",
|
|
353
|
+
"stack",
|
|
354
|
+
"tech-stack",
|
|
355
|
+
"techstack",
|
|
356
|
+
"test",
|
|
357
|
+
"test-runner",
|
|
358
|
+
"testing",
|
|
359
|
+
"testumgebung",
|
|
360
|
+
"type script",
|
|
361
|
+
"type-script",
|
|
362
|
+
"typescript",
|
|
363
|
+
"version",
|
|
364
|
+
"versionen",
|
|
365
|
+
"vite",
|
|
366
|
+
"vitest",
|
|
367
|
+
"yarn",
|
|
368
|
+
];
|
|
369
|
+
const PROJECT_METADATA_FILENAMES = [
|
|
370
|
+
"package.json",
|
|
371
|
+
"package-lock.json",
|
|
372
|
+
"pnpm-lock.yaml",
|
|
373
|
+
"yarn.lock",
|
|
374
|
+
"bun.lock",
|
|
375
|
+
"bun.lockb",
|
|
376
|
+
"vitest.config.ts",
|
|
377
|
+
"vitest.config.mts",
|
|
378
|
+
"vitest.config.js",
|
|
379
|
+
"vitest.config.mjs",
|
|
380
|
+
"vitest.setup.ts",
|
|
381
|
+
"vite.config.ts",
|
|
382
|
+
"vite.config.mts",
|
|
383
|
+
"vite.config.js",
|
|
384
|
+
"vite.config.mjs",
|
|
385
|
+
"jest.config.ts",
|
|
386
|
+
"jest.config.js",
|
|
387
|
+
"jest.config.mjs",
|
|
388
|
+
"playwright.config.ts",
|
|
389
|
+
"playwright.config.js",
|
|
390
|
+
"cypress.config.ts",
|
|
391
|
+
"cypress.config.js",
|
|
392
|
+
"next.config.ts",
|
|
393
|
+
"next.config.js",
|
|
394
|
+
"next.config.mjs",
|
|
395
|
+
"tsconfig.json",
|
|
396
|
+
"eslint.config.ts",
|
|
397
|
+
"eslint.config.js",
|
|
398
|
+
"eslint.config.mjs",
|
|
399
|
+
"postcss.config.js",
|
|
400
|
+
"postcss.config.mjs",
|
|
401
|
+
];
|
|
402
|
+
const REPOSITORY_OVERVIEW_FILENAMES = [
|
|
403
|
+
"README.md",
|
|
404
|
+
"readme.md",
|
|
405
|
+
"AGENTS.md",
|
|
406
|
+
"CLAUDE.md",
|
|
407
|
+
"CONTRIBUTING.md",
|
|
408
|
+
"docs/README.md",
|
|
409
|
+
"docs/architecture.md",
|
|
410
|
+
"docs/ARCHITECTURE.md",
|
|
411
|
+
"docs/adr/README.md",
|
|
412
|
+
];
|
|
413
|
+
const WORKSPACE_PACKAGE_DIRS = ["packages", "apps", "services", "libs"];
|
|
414
|
+
const MAX_WORKSPACE_MANIFESTS = 24;
|
|
415
|
+
const SYMBOL_FILE_EXTENSIONS = [
|
|
416
|
+
"ts",
|
|
417
|
+
"tsx",
|
|
418
|
+
"js",
|
|
419
|
+
"jsx",
|
|
420
|
+
"mts",
|
|
421
|
+
"cts",
|
|
422
|
+
"mjs",
|
|
423
|
+
"cjs",
|
|
424
|
+
"vue",
|
|
425
|
+
];
|
|
426
|
+
const SYMBOL_FILE_EXTENSION_SET = new Set(SYMBOL_FILE_EXTENSIONS);
|
|
427
|
+
const SYMBOL_FILE_SEARCH_LIMITS = {
|
|
428
|
+
maxFilesScanned: 10_000,
|
|
429
|
+
maxMatchesReturned: 32,
|
|
430
|
+
maxBytesPerFileScanned: DEFAULT_SEARCH_LIMITS.maxBytesPerFileScanned,
|
|
431
|
+
elapsedMsMax: DEFAULT_SEARCH_LIMITS.elapsedMsMax,
|
|
432
|
+
};
|
|
433
|
+
const SYMBOL_LINE_SCAN_BYTES_MAX = 2_097_152;
|
|
434
|
+
// Aggregate cap on firstSymbolLine reads across ALL terms in one question, so a vague code question
|
|
435
|
+
// on a large customer repo can never trigger an unbounded number of full-file reads even if many
|
|
436
|
+
// files match the symbol globs (each read also re-stats + splits the file — see firstSymbolLine).
|
|
437
|
+
const MAX_SYMBOL_LINE_READS = 64;
|
|
438
|
+
const LOCKFILE_NAMES = new Set([
|
|
439
|
+
"package-lock.json",
|
|
440
|
+
"pnpm-lock.yaml",
|
|
441
|
+
"yarn.lock",
|
|
442
|
+
"bun.lock",
|
|
443
|
+
"bun.lockb",
|
|
444
|
+
"cargo.lock",
|
|
445
|
+
"composer.lock",
|
|
446
|
+
"gemfile.lock",
|
|
447
|
+
]);
|
|
448
|
+
function basename(scopePath) {
|
|
449
|
+
const index = scopePath.lastIndexOf("/");
|
|
450
|
+
return index >= 0 ? scopePath.slice(index + 1) : scopePath;
|
|
451
|
+
}
|
|
452
|
+
function compareByScopePath(a, b) {
|
|
453
|
+
return a.scopePath.localeCompare(b.scopePath);
|
|
454
|
+
}
|
|
455
|
+
function isKeikoEvidenceArtifact(scopePath) {
|
|
456
|
+
return scopePath.toLowerCase().startsWith(".keiko/evidence/");
|
|
457
|
+
}
|
|
458
|
+
function isLockfilePath(scopePath) {
|
|
459
|
+
return LOCKFILE_NAMES.has(basename(scopePath).toLowerCase());
|
|
460
|
+
}
|
|
461
|
+
function dirname(scopePath) {
|
|
462
|
+
const index = scopePath.lastIndexOf("/");
|
|
463
|
+
return index <= 0 ? "" : scopePath.slice(0, index);
|
|
464
|
+
}
|
|
465
|
+
function joinScopePath(base, filename) {
|
|
466
|
+
return base.length === 0 ? filename : `${base}/${filename}`;
|
|
467
|
+
}
|
|
468
|
+
function projectMetadataQueryFingerprint(query) {
|
|
469
|
+
return createHash("sha256")
|
|
470
|
+
.update(JSON.stringify({
|
|
471
|
+
kind: "project-metadata",
|
|
472
|
+
queryKind: query.kind,
|
|
473
|
+
text: query.text,
|
|
474
|
+
caseSensitive: query.caseSensitive,
|
|
475
|
+
}))
|
|
476
|
+
.digest("hex")
|
|
477
|
+
.slice(0, 16);
|
|
478
|
+
}
|
|
479
|
+
function selectedFileQueryFingerprint(query) {
|
|
480
|
+
return createHash("sha256")
|
|
481
|
+
.update(JSON.stringify({
|
|
482
|
+
kind: "explicit-selected-file",
|
|
483
|
+
queryKind: query.kind,
|
|
484
|
+
text: query.text,
|
|
485
|
+
caseSensitive: query.caseSensitive,
|
|
486
|
+
}))
|
|
487
|
+
.digest("hex")
|
|
488
|
+
.slice(0, 16);
|
|
489
|
+
}
|
|
490
|
+
function normalizedQueryText(queryText) {
|
|
491
|
+
return queryText.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase();
|
|
492
|
+
}
|
|
493
|
+
function retrievalIntentFor(input) {
|
|
494
|
+
return classifyRetrievalIntent(input.query.text, input.scope).intent;
|
|
495
|
+
}
|
|
496
|
+
function wantsProjectMetadata(input) {
|
|
497
|
+
const intent = retrievalIntentFor(input);
|
|
498
|
+
if (intent === "project-metadata" || intent === "repository-overview") {
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
const lowered = input.query.text.toLowerCase();
|
|
502
|
+
const normalized = normalizedQueryText(input.query.text);
|
|
503
|
+
return PROJECT_METADATA_QUERY_TERMS.some((term) => lowered.includes(term) || normalized.includes(term));
|
|
504
|
+
}
|
|
505
|
+
function wantsRepositoryOverview(input) {
|
|
506
|
+
return retrievalIntentFor(input) === "repository-overview";
|
|
507
|
+
}
|
|
508
|
+
function metadataRootsForScope(scope) {
|
|
509
|
+
if (scope.relativePaths.length === 0) {
|
|
510
|
+
return [""];
|
|
511
|
+
}
|
|
512
|
+
const roots = new Set();
|
|
513
|
+
for (const entry of scope.relativePaths) {
|
|
514
|
+
if (!isValidScopePath(entry, { mustBeRelative: true })) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
roots.add(scope.kind === "files" ? dirname(entry) : entry);
|
|
518
|
+
}
|
|
519
|
+
return [...roots].sort();
|
|
520
|
+
}
|
|
521
|
+
function isRecord(value) {
|
|
522
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
523
|
+
}
|
|
524
|
+
function readWorkspacePatterns(searchScope, fs) {
|
|
525
|
+
if (!fileExistsInSearchScope(searchScope, fs, "package.json")) {
|
|
526
|
+
return [];
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
const abs = resolveWithinWorkspace(searchScope.workspace.root, "package.json");
|
|
530
|
+
const contained = containedRealPathInfo(fs, searchScope.workspace.root, abs);
|
|
531
|
+
const parsed = JSON.parse(fs.readFileUtf8(contained.path));
|
|
532
|
+
if (!isRecord(parsed)) {
|
|
533
|
+
return [];
|
|
534
|
+
}
|
|
535
|
+
const workspaces = parsed.workspaces;
|
|
536
|
+
if (Array.isArray(workspaces)) {
|
|
537
|
+
return workspaces.filter((entry) => typeof entry === "string");
|
|
538
|
+
}
|
|
539
|
+
if (isRecord(workspaces) && Array.isArray(workspaces.packages)) {
|
|
540
|
+
return workspaces.packages.filter((entry) => typeof entry === "string");
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
return [];
|
|
545
|
+
}
|
|
546
|
+
return [];
|
|
547
|
+
}
|
|
548
|
+
function normalizeWorkspacePattern(pattern) {
|
|
549
|
+
let normalized = pattern.trim().replace(/\\/gu, "/");
|
|
550
|
+
while (normalized.startsWith("./")) {
|
|
551
|
+
normalized = normalized.slice(2);
|
|
552
|
+
}
|
|
553
|
+
normalized = normalized.replace(/\/+$/u, "");
|
|
554
|
+
if (normalized.length === 0 || normalized.startsWith("../") || normalized.includes("/../")) {
|
|
555
|
+
return undefined;
|
|
556
|
+
}
|
|
557
|
+
return normalized;
|
|
558
|
+
}
|
|
559
|
+
function safeReadDir(searchScope, fs, scopePath) {
|
|
560
|
+
if (!isValidScopePath(scopePath, { mustBeRelative: true })) {
|
|
561
|
+
return [];
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
const abs = resolveWithinWorkspace(searchScope.workspace.root, scopePath);
|
|
565
|
+
const contained = containedRealPathInfo(fs, searchScope.workspace.root, abs);
|
|
566
|
+
if (!fs.stat(contained.path).isDirectory) {
|
|
567
|
+
return [];
|
|
568
|
+
}
|
|
569
|
+
return fs.readDir(contained.path);
|
|
570
|
+
}
|
|
571
|
+
catch {
|
|
572
|
+
return [];
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
function expandWorkspacePattern(pattern, searchScope, fs) {
|
|
576
|
+
const normalized = normalizeWorkspacePattern(pattern);
|
|
577
|
+
if (normalized === undefined) {
|
|
578
|
+
return [];
|
|
579
|
+
}
|
|
580
|
+
if (!normalized.includes("*")) {
|
|
581
|
+
const scopePath = normalized.endsWith("/package.json")
|
|
582
|
+
? normalized
|
|
583
|
+
: joinScopePath(normalized, "package.json");
|
|
584
|
+
return fileExistsInSearchScope(searchScope, fs, scopePath) ? [scopePath] : [];
|
|
585
|
+
}
|
|
586
|
+
if (!normalized.endsWith("/*") || normalized.slice(0, -2).includes("*")) {
|
|
587
|
+
return [];
|
|
588
|
+
}
|
|
589
|
+
const base = normalized.slice(0, -2);
|
|
590
|
+
return safeReadDir(searchScope, fs, base)
|
|
591
|
+
.filter((entry) => entry.isDirectory && !entry.isSymbolicLink)
|
|
592
|
+
.map((entry) => joinScopePath(joinScopePath(base, entry.name), "package.json"))
|
|
593
|
+
.filter((scopePath) => fileExistsInSearchScope(searchScope, fs, scopePath))
|
|
594
|
+
.sort();
|
|
595
|
+
}
|
|
596
|
+
function workspacePackageManifestPaths(input, searchScope, fs) {
|
|
597
|
+
if (input.scope.kind !== "workspace-root" || input.scope.relativePaths.length !== 0) {
|
|
598
|
+
return [];
|
|
599
|
+
}
|
|
600
|
+
const patterns = new Set(readWorkspacePatterns(searchScope, fs));
|
|
601
|
+
for (const dir of WORKSPACE_PACKAGE_DIRS) {
|
|
602
|
+
patterns.add(`${dir}/*`);
|
|
603
|
+
}
|
|
604
|
+
const paths = [];
|
|
605
|
+
const seen = new Set();
|
|
606
|
+
for (const pattern of [...patterns].sort()) {
|
|
607
|
+
for (const scopePath of expandWorkspacePattern(pattern, searchScope, fs)) {
|
|
608
|
+
if (seen.has(scopePath)) {
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
seen.add(scopePath);
|
|
612
|
+
paths.push(scopePath);
|
|
613
|
+
if (paths.length >= MAX_WORKSPACE_MANIFESTS) {
|
|
614
|
+
return paths;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return paths;
|
|
619
|
+
}
|
|
620
|
+
function metadataAtom(scope, scopePath, queryFingerprint, nowMs) {
|
|
621
|
+
return {
|
|
622
|
+
schemaVersion: scope.schemaVersion,
|
|
623
|
+
stableId: evidenceAtomStableId({
|
|
624
|
+
scopeId: scope.scopeId,
|
|
625
|
+
scopePath,
|
|
626
|
+
lineRange: undefined,
|
|
627
|
+
provenanceKind: "file-listing",
|
|
628
|
+
provenanceTool: "repo.projectMetadata",
|
|
629
|
+
queryFingerprint,
|
|
630
|
+
}),
|
|
631
|
+
scopePath,
|
|
632
|
+
lineRange: undefined,
|
|
633
|
+
score: 1,
|
|
634
|
+
provenance: {
|
|
635
|
+
kind: "file-listing",
|
|
636
|
+
tool: "repo.projectMetadata",
|
|
637
|
+
queryFingerprint,
|
|
638
|
+
},
|
|
639
|
+
redactionState: "redacted",
|
|
640
|
+
emittedAtMs: nowMs(),
|
|
641
|
+
ledgerRef: undefined,
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
function overviewAtom(scope, scopePath, queryFingerprint, nowMs) {
|
|
645
|
+
return {
|
|
646
|
+
schemaVersion: scope.schemaVersion,
|
|
647
|
+
stableId: evidenceAtomStableId({
|
|
648
|
+
scopeId: scope.scopeId,
|
|
649
|
+
scopePath,
|
|
650
|
+
lineRange: undefined,
|
|
651
|
+
provenanceKind: "file-listing",
|
|
652
|
+
provenanceTool: "repo.repositoryOverview",
|
|
653
|
+
queryFingerprint,
|
|
654
|
+
}),
|
|
655
|
+
scopePath,
|
|
656
|
+
lineRange: undefined,
|
|
657
|
+
score: 1,
|
|
658
|
+
provenance: {
|
|
659
|
+
kind: "file-listing",
|
|
660
|
+
tool: "repo.repositoryOverview",
|
|
661
|
+
queryFingerprint,
|
|
662
|
+
},
|
|
663
|
+
redactionState: "redacted",
|
|
664
|
+
emittedAtMs: nowMs(),
|
|
665
|
+
ledgerRef: undefined,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
function symbolFileQuery(input, pattern) {
|
|
669
|
+
return {
|
|
670
|
+
kind: "file-pattern",
|
|
671
|
+
text: pattern,
|
|
672
|
+
caseSensitive: false,
|
|
673
|
+
maxResults: 32,
|
|
674
|
+
emittedAtMs: input.query.emittedAtMs,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function symbolFileAnchorTerms(plan) {
|
|
678
|
+
if (plan.retrievalIntent !== "targeted-code-search" &&
|
|
679
|
+
plan.retrievalIntent !== "diagnostic-search") {
|
|
680
|
+
return [];
|
|
681
|
+
}
|
|
682
|
+
const terms = [];
|
|
683
|
+
const seen = new Set();
|
|
684
|
+
for (const anchor of plan.anchors) {
|
|
685
|
+
if (anchor.kind !== "identifier" || anchor.weight < 0.85) {
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (!/^[a-z_$][a-z0-9_$-]+$/u.test(anchor.term) || anchor.term.includes(".")) {
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
if (!seen.has(anchor.term)) {
|
|
692
|
+
seen.add(anchor.term);
|
|
693
|
+
terms.push(anchor.term);
|
|
694
|
+
}
|
|
695
|
+
if (terms.length >= 3) {
|
|
696
|
+
break;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return terms;
|
|
700
|
+
}
|
|
701
|
+
function escapeRegex(value) {
|
|
702
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
703
|
+
}
|
|
704
|
+
function lineDefinesSymbol(line, term) {
|
|
705
|
+
const escaped = escapeRegex(term);
|
|
706
|
+
const patterns = [
|
|
707
|
+
new RegExp(`\\b(?:export\\s+)?(?:async\\s+)?function\\s+${escaped}\\b`, "iu"),
|
|
708
|
+
new RegExp(`\\b(?:export\\s+)?(?:class|interface|type|enum)\\s+${escaped}\\b`, "iu"),
|
|
709
|
+
new RegExp(`\\b(?:export\\s+)?(?:const|let|var)\\s+${escaped}\\b`, "iu"),
|
|
710
|
+
];
|
|
711
|
+
return patterns.some((pattern) => pattern.test(line));
|
|
712
|
+
}
|
|
713
|
+
function firstLineIndex(lines, predicate) {
|
|
714
|
+
return lines.findIndex(predicate);
|
|
715
|
+
}
|
|
716
|
+
function firstSymbolLine(searchScope, fs, scopePath, term) {
|
|
717
|
+
try {
|
|
718
|
+
const abs = resolveWithinWorkspace(searchScope.workspace.root, scopePath);
|
|
719
|
+
const contained = containedRealPathInfo(fs, searchScope.workspace.root, abs);
|
|
720
|
+
const stat = fs.stat(contained.path);
|
|
721
|
+
if (!stat.isFile || stat.size > SYMBOL_LINE_SCAN_BYTES_MAX) {
|
|
722
|
+
return undefined;
|
|
723
|
+
}
|
|
724
|
+
const loweredTerm = term.toLowerCase();
|
|
725
|
+
const lines = fs.readFileUtf8(contained.path).split("\n");
|
|
726
|
+
const definitionIndex = firstLineIndex(lines, (line) => lineDefinesSymbol(line, term));
|
|
727
|
+
const index = definitionIndex >= 0
|
|
728
|
+
? definitionIndex
|
|
729
|
+
: firstLineIndex(lines, (line) => line.toLowerCase().includes(loweredTerm));
|
|
730
|
+
return index < 0 ? undefined : index + 1;
|
|
731
|
+
}
|
|
732
|
+
catch {
|
|
733
|
+
return undefined;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
function symbolLineAtom(scope, scopePath, lineNumber, queryFingerprint, nowMs) {
|
|
737
|
+
const lineRange = { startLine: lineNumber, endLine: lineNumber };
|
|
738
|
+
return {
|
|
739
|
+
schemaVersion: scope.schemaVersion,
|
|
740
|
+
stableId: evidenceAtomStableId({
|
|
741
|
+
scopeId: scope.scopeId,
|
|
742
|
+
scopePath,
|
|
743
|
+
lineRange,
|
|
744
|
+
provenanceKind: "lexical-search",
|
|
745
|
+
provenanceTool: "repo.symbolFileDiscovery",
|
|
746
|
+
queryFingerprint,
|
|
747
|
+
}),
|
|
748
|
+
scopePath,
|
|
749
|
+
lineRange,
|
|
750
|
+
score: 1,
|
|
751
|
+
provenance: {
|
|
752
|
+
kind: "lexical-search",
|
|
753
|
+
tool: "repo.symbolFileDiscovery",
|
|
754
|
+
queryFingerprint,
|
|
755
|
+
},
|
|
756
|
+
redactionState: "redacted",
|
|
757
|
+
emittedAtMs: nowMs(),
|
|
758
|
+
ledgerRef: undefined,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function scopePathExtension(scopePath) {
|
|
762
|
+
const name = scopePath.slice(scopePath.lastIndexOf("/") + 1);
|
|
763
|
+
const dot = name.lastIndexOf(".");
|
|
764
|
+
return dot < 0 ? "" : name.slice(dot + 1).toLowerCase();
|
|
765
|
+
}
|
|
766
|
+
// True when `scopePath` is a `<term>.<code-extension>` definition file. The single-walk symbol glob
|
|
767
|
+
// `**/term.*` also matches multi-dot names like `term.test.tsx` (which the prior per-extension globs
|
|
768
|
+
// did not), so this restores the exact contract: keep only paths ending in `term.<ext>` for a code
|
|
769
|
+
// extension — the implementation file, not its co-named spec/story. Exported for direct testing.
|
|
770
|
+
export function isSymbolDefinitionPath(scopePath, term) {
|
|
771
|
+
const extension = scopePathExtension(scopePath);
|
|
772
|
+
return (SYMBOL_FILE_EXTENSION_SET.has(extension) &&
|
|
773
|
+
scopePath.toLowerCase().endsWith(`${term.toLowerCase()}.${extension}`));
|
|
774
|
+
}
|
|
775
|
+
// Walk the tree ONCE for `**/term.*`, keep only `term.<code-ext>` definition files, and (within the
|
|
776
|
+
// shared read budget) attach the definition/first-mention line. The single walk replaces the prior
|
|
777
|
+
// per-extension globs (up to 27 redundant full-tree walks per question, ~4.7s on a 3.5k-file repo).
|
|
778
|
+
async function symbolAtomsForTerm(term, input, plan, searchScope, fs, nowMs, budget) {
|
|
779
|
+
const result = await findFiles(searchScope, symbolFileQuery(input, `**/${term}.*`), SYMBOL_FILE_SEARCH_LIMITS, {
|
|
780
|
+
fs,
|
|
781
|
+
nowMs,
|
|
782
|
+
searchHints: { retrievalIntent: plan.retrievalIntent },
|
|
783
|
+
});
|
|
784
|
+
const atoms = [];
|
|
785
|
+
for (const atom of result.atoms) {
|
|
786
|
+
if (!isSymbolDefinitionPath(atom.scopePath, term)) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
atoms.push(atom);
|
|
790
|
+
if (budget.remaining <= 0) {
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
budget.remaining -= 1;
|
|
794
|
+
const lineNumber = firstSymbolLine(searchScope, fs, atom.scopePath, term);
|
|
795
|
+
if (lineNumber !== undefined) {
|
|
796
|
+
const fp = atom.provenance.queryFingerprint;
|
|
797
|
+
atoms.push(symbolLineAtom(input.scope, atom.scopePath, lineNumber, fp, nowMs));
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return atoms;
|
|
801
|
+
}
|
|
802
|
+
async function symbolFileAtoms(input, plan, searchScope, fs, nowMs, signal) {
|
|
803
|
+
const terms = symbolFileAnchorTerms(plan);
|
|
804
|
+
if (terms.length === 0) {
|
|
805
|
+
return [];
|
|
806
|
+
}
|
|
807
|
+
const atoms = [];
|
|
808
|
+
const seen = new Set();
|
|
809
|
+
const budget = { remaining: MAX_SYMBOL_LINE_READS };
|
|
810
|
+
for (const term of terms) {
|
|
811
|
+
throwIfCancelled(signal);
|
|
812
|
+
for (const atom of await symbolAtomsForTerm(term, input, plan, searchScope, fs, nowMs, budget)) {
|
|
813
|
+
if (!seen.has(atom.stableId)) {
|
|
814
|
+
seen.add(atom.stableId);
|
|
815
|
+
atoms.push(atom);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return atoms;
|
|
820
|
+
}
|
|
821
|
+
function selectedFileAtom(scope, scopePath, queryFingerprint, nowMs) {
|
|
822
|
+
return {
|
|
823
|
+
schemaVersion: scope.schemaVersion,
|
|
824
|
+
stableId: evidenceAtomStableId({
|
|
825
|
+
scopeId: scope.scopeId,
|
|
826
|
+
scopePath,
|
|
827
|
+
lineRange: undefined,
|
|
828
|
+
provenanceKind: "file-listing",
|
|
829
|
+
provenanceTool: "repo.selectedFile",
|
|
830
|
+
queryFingerprint,
|
|
831
|
+
}),
|
|
832
|
+
scopePath,
|
|
833
|
+
lineRange: undefined,
|
|
834
|
+
score: 1,
|
|
835
|
+
provenance: {
|
|
836
|
+
kind: "file-listing",
|
|
837
|
+
tool: "repo.selectedFile",
|
|
838
|
+
queryFingerprint,
|
|
839
|
+
},
|
|
840
|
+
redactionState: "redacted",
|
|
841
|
+
emittedAtMs: nowMs(),
|
|
842
|
+
ledgerRef: undefined,
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
function fileExistsInSearchScope(searchScope, fs, scopePath) {
|
|
846
|
+
try {
|
|
847
|
+
const abs = resolveWithinWorkspace(searchScope.workspace.root, scopePath);
|
|
848
|
+
const contained = containedRealPathInfo(fs, searchScope.workspace.root, abs);
|
|
849
|
+
return fs.stat(contained.path).isFile;
|
|
850
|
+
}
|
|
851
|
+
catch {
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
function selectedFileScopeAtoms(input, searchScope, fs, nowMs) {
|
|
856
|
+
if (input.scope.explicitConnection !== true || input.scope.kind !== "files") {
|
|
857
|
+
return [];
|
|
858
|
+
}
|
|
859
|
+
const atoms = [];
|
|
860
|
+
const seen = new Set();
|
|
861
|
+
const queryFingerprint = selectedFileQueryFingerprint(input.query);
|
|
862
|
+
for (const entry of input.scope.relativePaths) {
|
|
863
|
+
if (!isValidScopePath(entry, { mustBeRelative: true })) {
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
const scopePath = entry.replace(/\\/gu, "/");
|
|
867
|
+
if (seen.has(scopePath)) {
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
seen.add(scopePath);
|
|
871
|
+
// Connected documents (supported DOCX/XLSX/PDF, or a known-unsupported document format) are not
|
|
872
|
+
// code-first excerpt files; they are handled exclusively by bounded document extraction (Issue
|
|
873
|
+
// #1285), so they must not also enter the line-window excerpt path here — that would double-count
|
|
874
|
+
// the file and leave an empty, unreadable code excerpt alongside the document evidence/diagnostic.
|
|
875
|
+
if (isConnectedDocumentPath(scopePath)) {
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
if (fileExistsInSearchScope(searchScope, fs, scopePath)) {
|
|
879
|
+
atoms.push(selectedFileAtom(input.scope, scopePath, queryFingerprint, nowMs));
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return atoms;
|
|
883
|
+
}
|
|
884
|
+
function projectMetadataAtoms(input, searchScope, fs, nowMs) {
|
|
885
|
+
if (!wantsProjectMetadata(input)) {
|
|
886
|
+
return [];
|
|
887
|
+
}
|
|
888
|
+
const atoms = [];
|
|
889
|
+
const seen = new Set();
|
|
890
|
+
const queryFingerprint = projectMetadataQueryFingerprint(input.query);
|
|
891
|
+
for (const root of metadataRootsForScope(input.scope)) {
|
|
892
|
+
for (const filename of PROJECT_METADATA_FILENAMES) {
|
|
893
|
+
const scopePath = joinScopePath(root, filename);
|
|
894
|
+
if (seen.has(scopePath) || !isValidScopePath(scopePath, { mustBeRelative: true })) {
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
seen.add(scopePath);
|
|
898
|
+
if (fileExistsInSearchScope(searchScope, fs, scopePath)) {
|
|
899
|
+
atoms.push(metadataAtom(input.scope, scopePath, queryFingerprint, nowMs));
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
for (const scopePath of workspacePackageManifestPaths(input, searchScope, fs)) {
|
|
904
|
+
if (seen.has(scopePath) || !isValidScopePath(scopePath, { mustBeRelative: true })) {
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
seen.add(scopePath);
|
|
908
|
+
atoms.push(metadataAtom(input.scope, scopePath, queryFingerprint, nowMs));
|
|
909
|
+
}
|
|
910
|
+
return atoms;
|
|
911
|
+
}
|
|
912
|
+
function repositoryOverviewAtoms(input, searchScope, fs, nowMs) {
|
|
913
|
+
if (!wantsRepositoryOverview(input)) {
|
|
914
|
+
return [];
|
|
915
|
+
}
|
|
916
|
+
const atoms = [];
|
|
917
|
+
const seen = new Set();
|
|
918
|
+
const queryFingerprint = projectMetadataQueryFingerprint(input.query);
|
|
919
|
+
for (const root of metadataRootsForScope(input.scope)) {
|
|
920
|
+
for (const filename of REPOSITORY_OVERVIEW_FILENAMES) {
|
|
921
|
+
const scopePath = joinScopePath(root, filename);
|
|
922
|
+
if (seen.has(scopePath) || !isValidScopePath(scopePath, { mustBeRelative: true })) {
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
seen.add(scopePath);
|
|
926
|
+
if (fileExistsInSearchScope(searchScope, fs, scopePath)) {
|
|
927
|
+
atoms.push(overviewAtom(input.scope, scopePath, queryFingerprint, nowMs));
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return atoms;
|
|
932
|
+
}
|
|
933
|
+
async function withDeterministicContextAtoms(rings, input, plan, searchScope, fs, nowMs, signal) {
|
|
934
|
+
const deterministicAtoms = [
|
|
935
|
+
...(await symbolFileAtoms(input, plan, searchScope, fs, nowMs, signal)),
|
|
936
|
+
...projectMetadataAtoms(input, searchScope, fs, nowMs),
|
|
937
|
+
...repositoryOverviewAtoms(input, searchScope, fs, nowMs),
|
|
938
|
+
];
|
|
939
|
+
return deterministicAtoms.length === 0
|
|
940
|
+
? rings
|
|
941
|
+
: { ...rings, atoms: [...rings.atoms, ...deterministicAtoms] };
|
|
942
|
+
}
|
|
943
|
+
function withExplicitScopeAtoms(rings, input, searchScope, fs, nowMs) {
|
|
944
|
+
const selectedAtoms = selectedFileScopeAtoms(input, searchScope, fs, nowMs);
|
|
945
|
+
return selectedAtoms.length === 0
|
|
946
|
+
? rings
|
|
947
|
+
: { ...rings, atoms: [...selectedAtoms, ...rings.atoms] };
|
|
948
|
+
}
|
|
949
|
+
function queryTerms(queryText, anchors) {
|
|
950
|
+
const terms = new Set();
|
|
951
|
+
const loweredQuery = queryText.toLowerCase();
|
|
952
|
+
for (const token of loweredQuery.split(/[^a-z0-9._/-]+/)) {
|
|
953
|
+
if (token.length > 0) {
|
|
954
|
+
terms.add(token);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
for (const anchor of anchors) {
|
|
958
|
+
const lowered = anchor.term.toLowerCase();
|
|
959
|
+
if (lowered.length > 0) {
|
|
960
|
+
terms.add(lowered);
|
|
961
|
+
}
|
|
962
|
+
for (const token of lowered.split(/[^a-z0-9._/-]+/)) {
|
|
963
|
+
if (token.length > 0) {
|
|
964
|
+
terms.add(token);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
return [...terms];
|
|
969
|
+
}
|
|
970
|
+
function explicitlyTargetsRuntimeArtifact(scopePath, queryText, anchors) {
|
|
971
|
+
if (!isKeikoEvidenceArtifact(scopePath)) {
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
const loweredQuery = queryText.toLowerCase();
|
|
975
|
+
if (loweredQuery.includes(".keiko") || loweredQuery.includes("evidence artifact")) {
|
|
976
|
+
return true;
|
|
977
|
+
}
|
|
978
|
+
return queryTerms(queryText, anchors).some((term) => scopePath.toLowerCase().includes(term));
|
|
979
|
+
}
|
|
980
|
+
function explicitlyTargetsLockfile(scopePath, queryText, anchors) {
|
|
981
|
+
if (!isLockfilePath(scopePath)) {
|
|
982
|
+
return false;
|
|
983
|
+
}
|
|
984
|
+
const loweredQuery = queryText.toLowerCase();
|
|
985
|
+
if (loweredQuery.includes("lockfile") ||
|
|
986
|
+
loweredQuery.includes("package manager") ||
|
|
987
|
+
loweredQuery.includes("packagemanager") ||
|
|
988
|
+
loweredQuery.includes("dependency version") ||
|
|
989
|
+
loweredQuery.includes("dependency versions") ||
|
|
990
|
+
loweredQuery.includes("resolved version") ||
|
|
991
|
+
loweredQuery.includes("resolved versions")) {
|
|
992
|
+
return true;
|
|
993
|
+
}
|
|
994
|
+
const path = scopePath.toLowerCase();
|
|
995
|
+
const name = basename(scopePath).toLowerCase();
|
|
996
|
+
return queryTerms(queryText, anchors).some((term) => path.includes(term) || name === term);
|
|
997
|
+
}
|
|
998
|
+
function refineCandidateOrdering(kept, omitted, queryText, anchors, nowMs) {
|
|
999
|
+
const preferred = [];
|
|
1000
|
+
const lockfiles = [];
|
|
1001
|
+
const runtimeArtifacts = [];
|
|
1002
|
+
for (const candidate of kept) {
|
|
1003
|
+
const scopePath = candidate.scopePath;
|
|
1004
|
+
if (isKeikoEvidenceArtifact(scopePath) &&
|
|
1005
|
+
!explicitlyTargetsRuntimeArtifact(scopePath, queryText, anchors)) {
|
|
1006
|
+
runtimeArtifacts.push(candidate);
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
if (isLockfilePath(scopePath) && !explicitlyTargetsLockfile(scopePath, queryText, anchors)) {
|
|
1010
|
+
lockfiles.push(candidate);
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
preferred.push(candidate);
|
|
1014
|
+
}
|
|
1015
|
+
if (preferred.length === 0) {
|
|
1016
|
+
return { kept, omitted };
|
|
1017
|
+
}
|
|
1018
|
+
const nextOmitted = [...omitted];
|
|
1019
|
+
for (const candidate of runtimeArtifacts) {
|
|
1020
|
+
nextOmitted.push({
|
|
1021
|
+
scopePath: candidate.scopePath,
|
|
1022
|
+
reason: "low-relevance",
|
|
1023
|
+
omittedAtMs: nowMs,
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
nextOmitted.sort(compareByScopePath);
|
|
1027
|
+
return {
|
|
1028
|
+
kept: [...preferred, ...lockfiles],
|
|
1029
|
+
omitted: nextOmitted,
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
function groupEvidenceAtomsByPath(atoms) {
|
|
1033
|
+
const grouped = new Map();
|
|
1034
|
+
for (const atom of atoms) {
|
|
1035
|
+
const existing = grouped.get(atom.scopePath);
|
|
1036
|
+
if (existing === undefined) {
|
|
1037
|
+
grouped.set(atom.scopePath, [atom]);
|
|
1038
|
+
}
|
|
1039
|
+
else {
|
|
1040
|
+
existing.push(atom);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
return grouped;
|
|
1044
|
+
}
|
|
1045
|
+
function lineWindowForAtom(atom) {
|
|
1046
|
+
const range = atom.lineRange;
|
|
1047
|
+
if (range === undefined) {
|
|
1048
|
+
return DEFAULT_EXCERPT_WINDOW;
|
|
1049
|
+
}
|
|
1050
|
+
return {
|
|
1051
|
+
startLine: Math.max(1, range.startLine - EXCERPT_CONTEXT_LINES),
|
|
1052
|
+
endLine: range.endLine + EXCERPT_CONTEXT_LINES,
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
function mergeLineWindows(windows) {
|
|
1056
|
+
const sorted = [...windows].sort((a, b) => a.startLine === b.startLine ? a.endLine - b.endLine : a.startLine - b.startLine);
|
|
1057
|
+
const merged = [];
|
|
1058
|
+
for (const window of sorted) {
|
|
1059
|
+
const previous = merged[merged.length - 1];
|
|
1060
|
+
if (previous === undefined || window.startLine > previous.endLine + 1) {
|
|
1061
|
+
merged.push(window);
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
merged[merged.length - 1] = {
|
|
1065
|
+
startLine: previous.startLine,
|
|
1066
|
+
endLine: Math.max(previous.endLine, window.endLine),
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
return merged;
|
|
1070
|
+
}
|
|
1071
|
+
function windowContainsAtom(window, atom) {
|
|
1072
|
+
const range = atom.lineRange;
|
|
1073
|
+
return (range === undefined || (window.startLine <= range.startLine && window.endLine >= range.endLine));
|
|
1074
|
+
}
|
|
1075
|
+
function strongestAtomScoreForWindow(window, atomsForPath) {
|
|
1076
|
+
let score = 0;
|
|
1077
|
+
for (const atom of atomsForPath) {
|
|
1078
|
+
if (windowContainsAtom(window, atom) && atom.score > score) {
|
|
1079
|
+
score = atom.score;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
return score;
|
|
1083
|
+
}
|
|
1084
|
+
function excerptLineWindows(atomsForPath) {
|
|
1085
|
+
if (atomsForPath === undefined || atomsForPath.length === 0) {
|
|
1086
|
+
return { windows: [DEFAULT_EXCERPT_WINDOW], omittedWindowCount: 0 };
|
|
1087
|
+
}
|
|
1088
|
+
const merged = mergeLineWindows(atomsForPath.map(lineWindowForAtom));
|
|
1089
|
+
const selected = [...merged]
|
|
1090
|
+
.sort((a, b) => {
|
|
1091
|
+
const scoreDelta = strongestAtomScoreForWindow(b, atomsForPath) - strongestAtomScoreForWindow(a, atomsForPath);
|
|
1092
|
+
return scoreDelta === 0 ? a.startLine - b.startLine : scoreDelta;
|
|
1093
|
+
})
|
|
1094
|
+
.slice(0, MAX_EXCERPT_WINDOWS_PER_FILE)
|
|
1095
|
+
.sort((a, b) => a.startLine === b.startLine ? a.endLine - b.endLine : a.startLine - b.startLine);
|
|
1096
|
+
return {
|
|
1097
|
+
windows: selected,
|
|
1098
|
+
omittedWindowCount: Math.max(0, merged.length - selected.length),
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
function exhaustedDimensions(remainingFiles, remainingBytes) {
|
|
1102
|
+
return [
|
|
1103
|
+
...(remainingFiles <= 0 ? ["filesRead"] : []),
|
|
1104
|
+
...(remainingBytes <= 0 ? ["excerptBytes"] : []),
|
|
1105
|
+
].join(", ");
|
|
1106
|
+
}
|
|
1107
|
+
async function readPathExcerptWindows(scopePath, inputs, remainingBytes) {
|
|
1108
|
+
const windows = [];
|
|
1109
|
+
let bytesConsumed = 0;
|
|
1110
|
+
const selection = excerptLineWindows(inputs.atomsByPath.get(scopePath));
|
|
1111
|
+
for (const window of selection.windows) {
|
|
1112
|
+
throwIfCancelled(inputs.signal);
|
|
1113
|
+
const availableBytes = remainingBytes - bytesConsumed;
|
|
1114
|
+
if (availableBytes <= 0) {
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
const maxBytes = Math.min(8192, availableBytes);
|
|
1118
|
+
const result = await readExcerpt(inputs.searchScope, { scopePath, startLine: window.startLine, endLine: window.endLine, maxBytes }, { fs: inputs.fs });
|
|
1119
|
+
throwIfCancelled(inputs.signal);
|
|
1120
|
+
windows.push({ ...window, content: result.content });
|
|
1121
|
+
bytesConsumed += utf8ByteLength(result.content);
|
|
1122
|
+
}
|
|
1123
|
+
return { windows, bytesConsumed, omittedWindowCount: selection.omittedWindowCount };
|
|
1124
|
+
}
|
|
1125
|
+
async function readKeptExcerpts(keptPaths, inputs) {
|
|
1126
|
+
const excerpts = new Map();
|
|
1127
|
+
const uncertainty = [];
|
|
1128
|
+
let remainingFiles = Math.max(0, inputs.budget.filesReadMax - inputs.initialUsage.filesRead);
|
|
1129
|
+
let remainingBytes = Math.max(0, inputs.budget.excerptBytesMax - inputs.initialUsage.excerptBytes);
|
|
1130
|
+
for (const scopePath of keptPaths) {
|
|
1131
|
+
throwIfCancelled(inputs.signal);
|
|
1132
|
+
if (remainingFiles <= 0 || remainingBytes <= 0) {
|
|
1133
|
+
const dimensions = exhaustedDimensions(remainingFiles, remainingBytes);
|
|
1134
|
+
uncertainty.push(budgetClipped(`budget-exhausted on ${dimensions}`, inputs.nowMs()));
|
|
1135
|
+
break;
|
|
1136
|
+
}
|
|
1137
|
+
try {
|
|
1138
|
+
const result = await readPathExcerptWindows(scopePath, inputs, remainingBytes);
|
|
1139
|
+
const { windows } = result;
|
|
1140
|
+
if (windows.length > 0) {
|
|
1141
|
+
excerpts.set(scopePath, windows);
|
|
1142
|
+
if (result.omittedWindowCount > 0) {
|
|
1143
|
+
uncertainty.push({
|
|
1144
|
+
kind: "scope-incomplete",
|
|
1145
|
+
claim: `excerpt window limit omitted ${String(result.omittedWindowCount)} additional matching range(s) in ${scopePath}`,
|
|
1146
|
+
impactedAtomIds: [],
|
|
1147
|
+
emittedAtMs: inputs.nowMs(),
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
remainingFiles -= 1;
|
|
1151
|
+
remainingBytes -= result.bytesConsumed;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
catch (error) {
|
|
1155
|
+
// A single unreadable file (unsupported/binary, or larger than the excerpt read cap) must
|
|
1156
|
+
// degrade to a skipped excerpt, never crash the whole grounded answer. Other kept files and
|
|
1157
|
+
// the rest of the pipeline continue; the file simply contributes no excerpt content.
|
|
1158
|
+
if (error instanceof RepoSearchUnsupportedFileError || error instanceof FileTooLargeError) {
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
throw error;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return { excerpts, uncertainty };
|
|
1165
|
+
}
|
|
1166
|
+
function buildSearchScope(scope, workspace) {
|
|
1167
|
+
return {
|
|
1168
|
+
workspace,
|
|
1169
|
+
scopeId: scope.scopeId,
|
|
1170
|
+
relativePaths: scope.relativePaths,
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
function fileStateCacheIdentity(keptPaths, searchScope, fs) {
|
|
1174
|
+
const identity = [];
|
|
1175
|
+
try {
|
|
1176
|
+
for (const scopePath of keptPaths) {
|
|
1177
|
+
const target = containedRealPathInfo(fs, searchScope.workspace.root, resolveWithinWorkspace(searchScope.workspace.root, scopePath));
|
|
1178
|
+
const stat = fs.stat(target.path);
|
|
1179
|
+
if (!stat.isFile || stat.mtimeMs === undefined) {
|
|
1180
|
+
return undefined;
|
|
1181
|
+
}
|
|
1182
|
+
identity.push(`${scopePath}:${target.realRelative}:${stat.size.toString()}:${stat.mtimeMs.toString()}`);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
catch {
|
|
1186
|
+
return undefined;
|
|
1187
|
+
}
|
|
1188
|
+
return identity.sort();
|
|
1189
|
+
}
|
|
1190
|
+
function createReadyGovernedPlan(input, nowMs) {
|
|
1191
|
+
const planned = planAndGovern(input.budget === undefined
|
|
1192
|
+
? { scope: input.scope, query: input.query }
|
|
1193
|
+
: { scope: input.scope, query: input.query, budget: input.budget }, { nowMs });
|
|
1194
|
+
const { plan } = planned;
|
|
1195
|
+
if (plan.state !== "ready") {
|
|
1196
|
+
if (plan.clarification !== undefined) {
|
|
1197
|
+
throw new ClarificationNeededError(plan.clarification);
|
|
1198
|
+
}
|
|
1199
|
+
throw new ClarificationNeededError({
|
|
1200
|
+
reason: "scope-invalid",
|
|
1201
|
+
suggestedQuestions: ["Reselect files or a directory before asking."],
|
|
1202
|
+
minimumAnchorCount: 0,
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
if (planned.governor === undefined) {
|
|
1206
|
+
throw new Error("ready exploration plan did not produce a budget governor");
|
|
1207
|
+
}
|
|
1208
|
+
return { plan, governor: planned.governor };
|
|
1209
|
+
}
|
|
1210
|
+
// Document paths are disjoint from code excerpt paths (documents are excluded from the code-first
|
|
1211
|
+
// selected-file atoms), so a plain copy-merge never overwrites a code excerpt.
|
|
1212
|
+
function mergeExcerptSources(base, documents) {
|
|
1213
|
+
if (documents.size === 0) {
|
|
1214
|
+
return base;
|
|
1215
|
+
}
|
|
1216
|
+
const merged = new Map(base);
|
|
1217
|
+
for (const [scopePath, windows] of documents) {
|
|
1218
|
+
merged.set(scopePath, windows);
|
|
1219
|
+
}
|
|
1220
|
+
return merged;
|
|
1221
|
+
}
|
|
1222
|
+
async function assembleEmptyGroundedPack({ input, deps, plan, governor, nowMs, stopReason, }) {
|
|
1223
|
+
const assembleOptions = deps.microIndex === undefined ? { nowMs } : { nowMs, microIndex: deps.microIndex };
|
|
1224
|
+
const assemble = await assembleContextPack({
|
|
1225
|
+
scope: input.scope,
|
|
1226
|
+
query: input.query,
|
|
1227
|
+
budget: plan.budget,
|
|
1228
|
+
atoms: [],
|
|
1229
|
+
ranked: [],
|
|
1230
|
+
omittedFromRanking: [],
|
|
1231
|
+
excerpts: new Map(),
|
|
1232
|
+
initialUsage: clampUsageToBudget(governor.usage, plan.budget),
|
|
1233
|
+
initialUncertainty: [budgetClipped(stopReason, nowMs())],
|
|
1234
|
+
}, assembleOptions);
|
|
1235
|
+
return assemble.pack;
|
|
1236
|
+
}
|
|
1237
|
+
function cachedGroundedPack({ input, deps, plan, rings, ordered, cacheIdentity, initialUsage, assembleOptions, }) {
|
|
1238
|
+
if (deps.microIndex === undefined || cacheIdentity === undefined) {
|
|
1239
|
+
return undefined;
|
|
1240
|
+
}
|
|
1241
|
+
const key = contextPackIndexKey({
|
|
1242
|
+
scope: input.scope,
|
|
1243
|
+
query: input.query,
|
|
1244
|
+
budget: plan.budget,
|
|
1245
|
+
atoms: rings.atoms,
|
|
1246
|
+
ranked: ordered.kept,
|
|
1247
|
+
omittedFromRanking: [...rings.omitted, ...ordered.omitted],
|
|
1248
|
+
excerpts: new Map(),
|
|
1249
|
+
cacheIdentity,
|
|
1250
|
+
initialUsage,
|
|
1251
|
+
}, assembleOptions);
|
|
1252
|
+
return deps.microIndex.get(key);
|
|
1253
|
+
}
|
|
1254
|
+
function preparePackAssembly(input, plan, rings, nowMs) {
|
|
1255
|
+
const atoms = rings.atoms;
|
|
1256
|
+
const initialUsage = clampUsageToBudget(rings.governor.usage, plan.budget);
|
|
1257
|
+
const ranking = rankCandidates({ atoms, anchors: plan.anchors }, { nowMs });
|
|
1258
|
+
const ordered = refineCandidateOrdering(ranking.kept, ranking.omitted, input.query.text, plan.anchors, nowMs());
|
|
1259
|
+
return {
|
|
1260
|
+
atoms,
|
|
1261
|
+
initialUsage,
|
|
1262
|
+
ordered,
|
|
1263
|
+
atomsByPath: groupEvidenceAtomsByPath(atoms),
|
|
1264
|
+
evidenceUncertainty: atoms.length === 0 || ordered.kept.length === 0 ? [noEvidence(nowMs())] : [],
|
|
1265
|
+
keptPaths: ordered.kept.map((c) => c.scopePath),
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
async function assemblePackFromReads({ input, plan, rings, prepared, excerptReads, documentEvidence, cacheIdentity, assembleOptions, }) {
|
|
1269
|
+
const excerpts = mergeExcerptSources(excerptReads.excerpts, documentEvidence.excerpts);
|
|
1270
|
+
const needsNoEvidenceMarker = excerpts.size === 0 &&
|
|
1271
|
+
!prepared.evidenceUncertainty.some((marker) => marker.kind === "no-evidence");
|
|
1272
|
+
// Connected documents are owned exclusively by the bounded document-extraction path: they either
|
|
1273
|
+
// surface as document evidence or as a precise document diagnostic. The code-first lexical scan
|
|
1274
|
+
// also sees them as binary candidates, so strip any document-path omission it produced to avoid a
|
|
1275
|
+
// path that is both a selected file and an omitted entry (which the pack validator rejects).
|
|
1276
|
+
const codeOmitted = [...rings.omitted, ...prepared.ordered.omitted].filter((entry) => !isConnectedDocumentPath(entry.scopePath));
|
|
1277
|
+
const assemble = await assembleContextPack({
|
|
1278
|
+
scope: input.scope,
|
|
1279
|
+
query: input.query,
|
|
1280
|
+
budget: plan.budget,
|
|
1281
|
+
atoms: [...prepared.atoms, ...documentEvidence.atoms],
|
|
1282
|
+
ranked: [...prepared.ordered.kept, ...documentEvidence.candidates],
|
|
1283
|
+
omittedFromRanking: [...codeOmitted, ...documentEvidence.omitted],
|
|
1284
|
+
excerpts,
|
|
1285
|
+
// Document evidence is request-local and not part of the file-state cache key, so a pack that
|
|
1286
|
+
// carries any document evidence — extracted atoms OR skipped-document omissions — must not be
|
|
1287
|
+
// written into the micro-index under a code-only file-state key (it would orphan an entry the
|
|
1288
|
+
// read-bypass gate never serves). Mirror the bypass condition in prepareGroundedAssembly.
|
|
1289
|
+
cacheIdentity: documentEvidence.atoms.length > 0 || documentEvidence.omitted.length > 0
|
|
1290
|
+
? undefined
|
|
1291
|
+
: cacheIdentity,
|
|
1292
|
+
initialUsage: prepared.initialUsage,
|
|
1293
|
+
initialUncertainty: [
|
|
1294
|
+
...rings.uncertainty,
|
|
1295
|
+
...excerptReads.uncertainty,
|
|
1296
|
+
...prepared.evidenceUncertainty,
|
|
1297
|
+
...documentEvidence.uncertainty,
|
|
1298
|
+
...(needsNoEvidenceMarker ? [noEvidence(assembleOptions.nowMs())] : []),
|
|
1299
|
+
],
|
|
1300
|
+
}, assembleOptions);
|
|
1301
|
+
return assemble.pack;
|
|
1302
|
+
}
|
|
1303
|
+
async function augmentRingsWithDeterministicAtoms({ input, deps, plan, rings, searchScope, fs, nowMs, }) {
|
|
1304
|
+
const scopedRings = withExplicitScopeAtoms(rings, input, searchScope, fs, nowMs);
|
|
1305
|
+
return withDeterministicContextAtoms(scopedRings, input, plan, searchScope, fs, nowMs, deps.signal);
|
|
1306
|
+
}
|
|
1307
|
+
async function prepareGroundedAssembly(args, augmentedRings, prepared) {
|
|
1308
|
+
const { input, deps, plan, searchScope, fs, nowMs } = args;
|
|
1309
|
+
// Bounded small-document extraction for explicit `files` scopes (Issue #1285). Returns empty
|
|
1310
|
+
// evidence for every other scope kind, leaving the code-first path byte-identical.
|
|
1311
|
+
const documentEvidence = await collectConnectedDocumentEvidence({
|
|
1312
|
+
scope: input.scope,
|
|
1313
|
+
query: input.query,
|
|
1314
|
+
searchScope,
|
|
1315
|
+
fs,
|
|
1316
|
+
nowMs,
|
|
1317
|
+
signal: deps.signal,
|
|
1318
|
+
});
|
|
1319
|
+
const hasDocumentEvidence = documentEvidence.atoms.length > 0 || documentEvidence.omitted.length > 0;
|
|
1320
|
+
const cacheIdentity = deps.microIndex === undefined || hasDocumentEvidence
|
|
1321
|
+
? undefined
|
|
1322
|
+
: fileStateCacheIdentity(prepared.keptPaths, searchScope, fs);
|
|
1323
|
+
const assembleOptions = deps.microIndex === undefined || hasDocumentEvidence
|
|
1324
|
+
? { nowMs }
|
|
1325
|
+
: { nowMs, microIndex: deps.microIndex };
|
|
1326
|
+
// The micro-index cache key does not model request-local document evidence, so a scope that
|
|
1327
|
+
// carried documents this run must not be served from (or written to) the shared cache.
|
|
1328
|
+
const cached = hasDocumentEvidence
|
|
1329
|
+
? undefined
|
|
1330
|
+
: cachedGroundedPack({
|
|
1331
|
+
input,
|
|
1332
|
+
deps,
|
|
1333
|
+
plan,
|
|
1334
|
+
rings: augmentedRings,
|
|
1335
|
+
ordered: prepared.ordered,
|
|
1336
|
+
cacheIdentity,
|
|
1337
|
+
initialUsage: prepared.initialUsage,
|
|
1338
|
+
assembleOptions,
|
|
1339
|
+
});
|
|
1340
|
+
return { documentEvidence, cached, cacheIdentity, assembleOptions };
|
|
1341
|
+
}
|
|
1342
|
+
async function assembleGroundedPack(args) {
|
|
1343
|
+
const { input, deps, plan, searchScope, fs, nowMs } = args;
|
|
1344
|
+
const augmentedRings = await augmentRingsWithDeterministicAtoms(args);
|
|
1345
|
+
const prepared = preparePackAssembly(input, plan, augmentedRings, nowMs);
|
|
1346
|
+
const ctx = await prepareGroundedAssembly(args, augmentedRings, prepared);
|
|
1347
|
+
if (ctx.cached !== undefined) {
|
|
1348
|
+
return ctx.cached;
|
|
1349
|
+
}
|
|
1350
|
+
const excerptReads = await readKeptExcerpts(prepared.keptPaths, {
|
|
1351
|
+
searchScope,
|
|
1352
|
+
fs,
|
|
1353
|
+
budget: plan.budget,
|
|
1354
|
+
initialUsage: prepared.initialUsage,
|
|
1355
|
+
atomsByPath: prepared.atomsByPath,
|
|
1356
|
+
nowMs,
|
|
1357
|
+
signal: deps.signal,
|
|
1358
|
+
});
|
|
1359
|
+
return await assemblePackFromReads({
|
|
1360
|
+
input,
|
|
1361
|
+
plan,
|
|
1362
|
+
rings: augmentedRings,
|
|
1363
|
+
prepared,
|
|
1364
|
+
excerptReads,
|
|
1365
|
+
documentEvidence: ctx.documentEvidence,
|
|
1366
|
+
cacheIdentity: ctx.cacheIdentity,
|
|
1367
|
+
assembleOptions: ctx.assembleOptions,
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
// ─── Public entry ─────────────────────────────────────────────────────────────
|
|
1371
|
+
// Epic #532 — retrieval-only pipeline: the ready-governed plan, workspace detection, ring run,
|
|
1372
|
+
// and pack assembly (the original steps 1–4) WITHOUT the model answer. `deps.answerer` is part of
|
|
1373
|
+
// the shared deps type but is intentionally not invoked here; the multi-source path answers once
|
|
1374
|
+
// over the merged packs rather than per source.
|
|
1375
|
+
export async function retrieveConnectedContextPack(input, deps) {
|
|
1376
|
+
const fs = deps.fs ?? nodeWorkspaceFs;
|
|
1377
|
+
const detect = deps.detectWorkspace ?? detectWorkspaceAt;
|
|
1378
|
+
const nowMs = deps.nowMs ?? Date.now;
|
|
1379
|
+
const start = nowMs();
|
|
1380
|
+
throwIfCancelled(deps.signal);
|
|
1381
|
+
const { plan, governor } = createReadyGovernedPlan(input, nowMs);
|
|
1382
|
+
deps.recordPlan?.(plan);
|
|
1383
|
+
throwIfCancelled(deps.signal);
|
|
1384
|
+
const blockedByReadBudget = readBudgetStopReason(plan.budget);
|
|
1385
|
+
if (blockedByReadBudget !== undefined) {
|
|
1386
|
+
const pack = await assembleEmptyGroundedPack({
|
|
1387
|
+
input,
|
|
1388
|
+
deps,
|
|
1389
|
+
plan,
|
|
1390
|
+
governor,
|
|
1391
|
+
nowMs,
|
|
1392
|
+
stopReason: blockedByReadBudget,
|
|
1393
|
+
});
|
|
1394
|
+
throwIfCancelled(deps.signal);
|
|
1395
|
+
return { pack, elapsedMs: Math.max(0, nowMs() - start), plan };
|
|
1396
|
+
}
|
|
1397
|
+
const workspace = detect(input.workspaceRoot, fs);
|
|
1398
|
+
const searchScope = buildSearchScope(input.scope, workspace);
|
|
1399
|
+
const rings = await runAllRings(plan.rings, {
|
|
1400
|
+
searchScope,
|
|
1401
|
+
query: input.query,
|
|
1402
|
+
anchors: plan.anchors,
|
|
1403
|
+
retrievalIntent: plan.retrievalIntent,
|
|
1404
|
+
fs,
|
|
1405
|
+
nowMs,
|
|
1406
|
+
signal: deps.signal,
|
|
1407
|
+
}, governor);
|
|
1408
|
+
throwIfCancelled(deps.signal);
|
|
1409
|
+
const pack = await assembleGroundedPack({ input, deps, plan, rings, searchScope, fs, nowMs });
|
|
1410
|
+
throwIfCancelled(deps.signal);
|
|
1411
|
+
return { pack, elapsedMs: Math.max(0, nowMs() - start), plan };
|
|
1412
|
+
}
|
|
1413
|
+
export async function runGroundedExploration(input, deps) {
|
|
1414
|
+
// AC5 (#532): the single-source path measures its OWN total wall time (retrieval + answer) so the
|
|
1415
|
+
// observable elapsedMs is byte-identical to before this split. The retrieval-only elapsed returned
|
|
1416
|
+
// by retrieveConnectedContextPack is deliberately discarded here.
|
|
1417
|
+
const nowMs = deps.nowMs ?? Date.now;
|
|
1418
|
+
const start = nowMs();
|
|
1419
|
+
const { pack, plan } = await retrieveConnectedContextPack(input, deps);
|
|
1420
|
+
const answer = normalizeGroundedAnswerPayload(await deps.answerer.answer(input.query.text, pack));
|
|
1421
|
+
const elapsedMs = Math.max(0, nowMs() - start);
|
|
1422
|
+
const exhaustedAnswerDimensions = [
|
|
1423
|
+
...(answer.usage.promptTokens > pack.budget.modelInputTokensMax ? ["modelInputTokens"] : []),
|
|
1424
|
+
...(answer.usage.completionTokens > pack.budget.modelOutputTokensMax
|
|
1425
|
+
? ["modelOutputTokens"]
|
|
1426
|
+
: []),
|
|
1427
|
+
...(elapsedMs > pack.budget.elapsedMsMax ? ["elapsedMs"] : []),
|
|
1428
|
+
];
|
|
1429
|
+
const groundedPack = {
|
|
1430
|
+
...pack,
|
|
1431
|
+
usage: {
|
|
1432
|
+
...pack.usage,
|
|
1433
|
+
modelInputTokens: Math.min(answer.usage.promptTokens, pack.budget.modelInputTokensMax),
|
|
1434
|
+
modelOutputTokens: Math.min(answer.usage.completionTokens, pack.budget.modelOutputTokensMax),
|
|
1435
|
+
elapsedMs: Math.min(Math.max(pack.usage.elapsedMs, elapsedMs), pack.budget.elapsedMsMax),
|
|
1436
|
+
},
|
|
1437
|
+
uncertainty: exhaustedAnswerDimensions.length === 0
|
|
1438
|
+
? pack.uncertainty
|
|
1439
|
+
: [...pack.uncertainty, answerBudgetClipped(exhaustedAnswerDimensions, nowMs())],
|
|
1440
|
+
};
|
|
1441
|
+
return { pack: groundedPack, assistantContent: answer.content, elapsedMs, plan };
|
|
1442
|
+
}
|
|
1443
|
+
// Re-export DEFAULT_SEARCH_LIMITS for parity with #179 callers that import limits via the
|
|
1444
|
+
// orchestrator. Keeps `grounded-qa.ts` from needing a second workspace import path.
|
|
1445
|
+
export { DEFAULT_SEARCH_LIMITS };
|