cuelo 0.10.20 → 0.10.21
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +4 -5
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.js +2 -2
- package/.next/required-server-files.json +2 -2
- package/.next/routes-manifest.json +0 -6
- package/.next/server/app/_global-error/page.js +2 -2
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +2 -2
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +3 -3
- package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/agent/[id]/account/route.js +1 -1
- package/.next/server/app/api/agent/[id]/bash-output/route.js +1 -1
- package/.next/server/app/api/agent/[id]/events/route.js +2 -2
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/running/route.js +1 -1
- package/.next/server/app/api/attachment-settings/route.js +1 -1
- package/.next/server/app/api/attachments/[id]/transcription/route.js +2 -2
- package/.next/server/app/api/attachments/draft/route.js +1 -1
- package/.next/server/app/api/attachments/route.js +2 -2
- package/.next/server/app/api/auth/all-providers/route.js +1 -1
- package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
- package/.next/server/app/api/auth/login/[provider]/route.js +2 -2
- package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
- package/.next/server/app/api/auth/providers/route.js +1 -1
- package/.next/server/app/api/cwd/browse/route.js +1 -1
- package/.next/server/app/api/cwd/validate/route.js +1 -1
- package/.next/server/app/api/default-cwd/route.js +1 -1
- package/.next/server/app/api/display-settings/route.js +1 -1
- package/.next/server/app/api/document-text/route.js +1 -1
- package/.next/server/app/api/experiments/route.js +1 -1
- package/.next/server/app/api/file-index/route.js +1 -1
- package/.next/server/app/api/files/[...path]/route.js +1 -1
- package/.next/server/app/api/git/diff/route.js +2 -2
- package/.next/server/app/api/git/status/route.js +1 -1
- package/.next/server/app/api/github/projects/route.js +1 -1
- package/.next/server/app/api/gpt6/handles/route.js +1 -1
- package/.next/server/app/api/gpt6/reply/route.js +1 -1
- package/.next/server/app/api/gpt6/token/route.js +1 -1
- package/.next/server/app/api/gpt6/web6-session/route.js +1 -1
- package/.next/server/app/api/home/route.js +1 -1
- package/.next/server/app/api/live/call/[callId]/route.js +3 -3
- package/.next/server/app/api/live/call/[callId]/route.js.nft.json +1 -1
- package/.next/server/app/api/live/events/route.js +4 -4
- package/.next/server/app/api/live/events/route.js.nft.json +1 -1
- package/.next/server/app/api/live/interpret/route.js +3 -3
- package/.next/server/app/api/live/interpret/route.js.nft.json +1 -1
- package/.next/server/app/api/live/offer/route.js +1 -1
- package/.next/server/app/api/live/offer/route.js.nft.json +1 -1
- package/.next/server/app/api/live/voices/route.js +1 -1
- package/.next/server/app/api/live/voices/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/route.js +2 -2
- package/.next/server/app/api/model-roles/route.js +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/models-config/catalog/route.js +1 -1
- package/.next/server/app/api/models-config/discover/route.js +1 -1
- package/.next/server/app/api/models-config/route.js +3 -3
- package/.next/server/app/api/models-config/test/route.js +1 -1
- package/.next/server/app/api/office-avatars/[id]/route.js +1 -1
- package/.next/server/app/api/office-avatars/route.js +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/project-docs/route.js +1 -1
- package/.next/server/app/api/project-trust/route.js +1 -1
- package/.next/server/app/api/projects/route.js +1 -1
- package/.next/server/app/api/runs/[runId]/route.js +1 -1
- package/.next/server/app/api/runs/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/auto-name/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/search/route.js +1 -1
- package/.next/server/app/api/settings/route.js +1 -1
- package/.next/server/app/api/sidecars/[service]/[...path]/route.js +1 -1
- package/.next/server/app/api/skills/check/route.js +1 -1
- package/.next/server/app/api/skills/install/route.js +2 -2
- package/.next/server/app/api/skills/route.js +1 -1
- package/.next/server/app/api/skills/search/route.js +1 -1
- package/.next/server/app/api/skills/update/route.js +1 -1
- package/.next/server/app/api/subagents/eta/route.js +1 -1
- package/.next/server/app/api/theme/route.js +1 -1
- package/.next/server/app/api/tool-image/route.js +1 -1
- package/.next/server/app/api/update-maintenance/route.js +3 -3
- package/.next/server/app/api/updates/route.js +1 -1
- package/.next/server/app/api/web-access/recovery/route.js +1 -1
- package/.next/server/app/api/web-access/route.js +1 -1
- package/.next/server/app/api/web6/consults/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/favicon.ico/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +5 -5
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +5 -5
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/manifest.webmanifest/route.js +2 -2
- package/.next/server/app/office/page.js +2 -2
- package/.next/server/app/office/page_client-reference-manifest.js +1 -1
- package/.next/server/app/office-preview/page.js +2 -2
- package/.next/server/app/office-preview/page_client-reference-manifest.js +1 -1
- package/.next/server/app/office-preview.html +1 -1
- package/.next/server/app/office-preview.rsc +3 -3
- package/.next/server/app/office-preview.segments/_full.segment.rsc +3 -3
- package/.next/server/app/office-preview.segments/_head.segment.rsc +1 -1
- package/.next/server/app/office-preview.segments/_index.segment.rsc +3 -3
- package/.next/server/app/office-preview.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/office-preview.segments/office-preview/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/office-preview.segments/office-preview.segment.rsc +1 -1
- package/.next/server/app/office.html +1 -1
- package/.next/server/app/office.rsc +5 -5
- package/.next/server/app/office.segments/_full.segment.rsc +5 -5
- package/.next/server/app/office.segments/_head.segment.rsc +1 -1
- package/.next/server/app/office.segments/_index.segment.rsc +3 -3
- package/.next/server/app/office.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/office.segments/office/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/office.segments/office.segment.rsc +1 -1
- package/.next/server/app/page.js +2 -2
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/recover/page.js +2 -2
- package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
- package/.next/server/app/recover.html +1 -1
- package/.next/server/app/recover.rsc +3 -3
- package/.next/server/app/recover.segments/_full.segment.rsc +3 -3
- package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
- package/.next/server/app/recover.segments/_index.segment.rsc +3 -3
- package/.next/server/app/recover.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
- package/.next/server/app-paths-manifest.json +4 -5
- package/.next/server/chunks/1008.js +5 -5
- package/.next/server/chunks/5489.js +4 -4
- package/.next/server/chunks/5582.js +1 -1
- package/.next/server/chunks/6429.js +5 -5
- package/.next/server/chunks/6775.js +7 -0
- package/.next/server/chunks/7362.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/IKi2VjUzAnD5AUcDn7QNi/_buildManifest.js +1 -0
- package/.next/static/chunks/6110-7a2311ab143b31e2.js +1 -0
- package/.next/static/chunks/{9575-f018b304d7088589.js → 9575-032510d25828764f.js} +4 -4
- package/.next/static/chunks/app/_global-error/{page-95aaaafc49522a6b.js → page-571d58ede1391fc8.js} +1 -1
- package/.next/static/chunks/app/api/agent/[id]/account/{route-95aaaafc49522a6b.js → route-571d58ede1391fc8.js} +1 -1
- package/.next/static/chunks/app/api/agent/[id]/bash-output/{route-95aaaafc49522a6b.js → route-571d58ede1391fc8.js} +1 -1
- package/.next/static/chunks/app/api/agent/[id]/events/{route-95aaaafc49522a6b.js → route-571d58ede1391fc8.js} +1 -1
- package/.next/static/chunks/app/api/agent/[id]/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/agent/running/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/attachment-settings/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/attachments/[id]/transcription/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/attachments/draft/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/attachments/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/auth/all-providers/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/auth/api-key/[provider]/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/auth/login/[provider]/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/auth/logout/[provider]/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/auth/providers/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/cwd/browse/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/cwd/validate/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/default-cwd/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/display-settings/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/document-text/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/experiments/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/file-index/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/files/[...path]/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/git/diff/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/git/status/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/github/projects/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/gpt6/handles/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/gpt6/reply/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/gpt6/token/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/gpt6/web6-session/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/home/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/live/call/[callId]/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/live/events/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/live/interpret/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/live/offer/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/live/voices/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/mcp/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/model-roles/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/models/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/models-config/catalog/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/models-config/discover/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/models-config/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/models-config/test/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/office-avatars/[id]/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/office-avatars/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/project-docs/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/project-trust/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/projects/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/runs/[runId]/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/runs/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/context/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/state/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/sessions/search/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/settings/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/sidecars/[service]/[...path]/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/skills/check/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/skills/install/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/skills/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/skills/search/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/skills/update/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/subagents/eta/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/theme/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/tool-image/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/update-maintenance/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/updates/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/web-access/recovery/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/web-access/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/web6/consults/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/api/worktrees/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/app/{layout-2961d1e4cf63214e.js → layout-a7fca69a4fa9b2c2.js} +1 -1
- package/.next/static/chunks/app/manifest.webmanifest/route-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-571d58ede1391fc8.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-571d58ede1391fc8.js +1 -0
- package/.next/static/css/{949fdc99cee51026.css → cc26725ba00edca3.css} +1 -1
- package/.next/trace +108 -108
- package/.next/trace-build +1 -1
- package/.next/types/routes.d.ts +1 -2
- package/.next/types/validator.ts +0 -9
- package/README.md +8 -4
- package/Tools/CUELO_Setup/files/native-runtime-patch.js +3 -3
- package/Tools/OMP_Global_Config/agent/AGENTS.md +2 -0
- package/Tools/OMP_Global_Config/agent/extensions/docmap-route.ts +34 -0
- package/Tools/OMP_Global_Config/agent/extensions/jev-runtime.ts +12 -1
- package/Tools/OMP_Global_Config/agent/extensions/lib/docmap-search.ts +576 -0
- package/Tools/OMP_Global_Config/agent/extensions/mcp-selection.ts +48 -7
- package/Tools/OMP_Global_Config/agent/prompt-copies/AGENTS.en.md +3 -1
- package/Tools/OMP_Global_Config/agent/rules/docs-handoff.md +1 -1
- package/Tools/OMP_Global_Config/agent/rules/frontend.md +4 -2
- package/Tools/OMP_Global_Config/agent/rules/verdict.md +1 -0
- package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +981 -132
- package/package.json +7 -7
- package/.next/server/app/api/live/character-voice/route.js +0 -1
- package/.next/server/app/api/live/character-voice/route.js.nft.json +0 -1
- package/.next/server/app/api/live/character-voice/route_client-reference-manifest.js +0 -1
- package/.next/server/chunks/205.js +0 -7
- package/.next/server/chunks/7467.js +0 -3
- package/.next/static/chunks/6110-11dcb476b561c51f.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/agent/running/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/attachment-settings/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/attachments/[id]/transcription/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/attachments/draft/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/attachments/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/auth/all-providers/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/auth/api-key/[provider]/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/auth/login/[provider]/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/auth/logout/[provider]/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/auth/providers/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/cwd/browse/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/cwd/validate/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/default-cwd/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/display-settings/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/document-text/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/experiments/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/file-index/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/files/[...path]/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/git/diff/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/git/status/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/github/projects/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/gpt6/handles/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/gpt6/reply/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/gpt6/token/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/gpt6/web6-session/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/home/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/live/call/[callId]/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/live/character-voice/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/live/events/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/live/interpret/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/live/offer/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/live/voices/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/mcp/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/model-roles/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/models/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/models-config/catalog/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/models-config/discover/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/models-config/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/models-config/test/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/office-avatars/[id]/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/office-avatars/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/project-docs/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/project-trust/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/projects/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/runs/[runId]/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/runs/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/context/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/state/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/sessions/search/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/settings/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/sidecars/[service]/[...path]/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/skills/check/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/skills/install/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/skills/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/skills/search/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/skills/update/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/subagents/eta/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/theme/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/tool-image/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/update-maintenance/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/updates/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/web-access/recovery/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/web-access/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/web6/consults/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/api/worktrees/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/app/manifest.webmanifest/route-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-95aaaafc49522a6b.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-95aaaafc49522a6b.js +0 -1
- package/.next/static/fOdMIeIOVq5i7KtiCo4rg/_buildManifest.js +0 -1
- package/.next/types/app/api/live/character-voice/route.ts +0 -351
- package/public/audio/live/ACML-1.0.txt +0 -85
- package/public/audio/live/hikari-reference.wav +0 -0
- package/public/audio/live/isana-reference.wav +0 -0
- package/public/audio/live/mio-reference.wav +0 -0
- package/public/audio/live/nova-reference.wav +0 -0
- package/public/audio/live/rin-reference.wav +0 -0
- package/public/audio/live/shion-reference.wav +0 -0
- package/public/audio/live/voices.json +0 -275
- package/public/audio/live/yuki-reference.wav +0 -0
- /package/.next/static/{fOdMIeIOVq5i7KtiCo4rg → IKi2VjUzAnD5AUcDn7QNi}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
// 프로젝트 문서 지도(doc/README.md)·하위 지도와 작업 기록의 `RECORD:` 헤더만으로 요청마다 BM25를 만들고 버린다.
|
|
2
|
+
// 2026-10-16 실측(doc/history/2026/10/16-rag-reinvestigation): 격리 표본 12건에서 에이전트는 지도를 0/12 열었고,
|
|
3
|
+
// 하위 지도에만 있던 정답 기록 2건을 놓쳤다. 본문까지 색인하면 무관 질문 점수가 같이 올라 헤더 전용이 낫다
|
|
4
|
+
// (hit@3 37/45 대 31/45). 상시 색인·캐시·LLM 호출은 두지 않는다. 읽기 실패는 호출부가 조용히 버린다.
|
|
5
|
+
import { closeSync, existsSync, openSync, readdirSync, readFileSync, readSync, statSync } from "node:fs";
|
|
6
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
7
|
+
|
|
8
|
+
// 근거: doc/history/2026/10/16-rag-reinvestigation/maker-DocmapRoute.md(처음 보정),
|
|
9
|
+
// doc/history/2026/10/17-rag-fixes/maker-DocmapFloor.md(기록 수·질의 길이에 묶이지 않는 두 조건으로 재보정).
|
|
10
|
+
/**
|
|
11
|
+
* 점수 조건. 1위 BM25 점수를 「기록 하나에만 있는 낱말 하나의 IDF」로 나눈 값이다. 그래서 기록 수가 달라도 대략
|
|
12
|
+
* 「드문 낱말 몇 개가 맞았나」로 읽힌다. 옛 절대 하한 10(2026-10-16 보정, 기록 212단위)은 이 값으로 2.02였다.
|
|
13
|
+
* 절대 하한은 기록 1개일 때 정답 4.07로 떨어지고, 무관 기록 15개를 더하면 같은 정답이 22.39로 붙었다.
|
|
14
|
+
*/
|
|
15
|
+
export const DOCMAP_MIN_SCORE = 2;
|
|
16
|
+
/**
|
|
17
|
+
* 점수 조건과 함께 보는 포괄률. 질의 낱말 중 기록 말뭉치에 있는 것의 헤더 IDF 합 대비, 1위 기록이 담은 낱말의
|
|
18
|
+
* IDF 합이다. 기록 본문 절반 이상에 나오는 흔한 낱말은 분모·분자에서 뺀다. 긴 지시는 1위가 일부만 설명해 낮다.
|
|
19
|
+
* 0.3은 보정 세트(홀수 문항)에서 점수 조건으로 전달되던 답의 최솟값(d29 0.34)을 잃지 않는 가장 큰 0.05 단위다.
|
|
20
|
+
*/
|
|
21
|
+
export const DOCMAP_MIN_COVERAGE = 0.3;
|
|
22
|
+
/**
|
|
23
|
+
* 짧은 질문 조건. 「RAG에서 안된것좀찾아바」처럼 낱말 하나만 기록과 맞는 질문은 점수가 낮지만, 그 낱말이 기록
|
|
24
|
+
* 본문에서도 드물고 질의를 거의 다 설명하면 붙인다. 근거는 1위가 맞힌 낱말마다 본문 IDF ÷ 최대 IDF의 합이다.
|
|
25
|
+
* 0.8·0.6은 65문항 전체를 보고 정했다(held-out 없음): 포괄률 0.8 이상 무관 질문의 근거 최고 0.46(u06),
|
|
26
|
+
* 근거 0.5 이상 무관 질문의 포괄률 최고 0.60(u18). 짧은 정답 질문 d17 0.78은 붙고 d14 0.54는 안 붙는다. RAG 질문 0.68.
|
|
27
|
+
*/
|
|
28
|
+
export const DOCMAP_FOCUSED_COVERAGE = 0.8;
|
|
29
|
+
export const DOCMAP_FOCUSED_EVIDENCE = 0.6;
|
|
30
|
+
/** 기록 본문의 이 비율 이상에 나오는 낱말은 포괄률에서 뺀다(조사·일반어). 기록이 적으면 판단하지 않는다. */
|
|
31
|
+
const GENERIC_SHARE = 0.5;
|
|
32
|
+
/** 이 비율 이상이면 순위에서도 뺀다(「에서」「으로」). 0.5로 순위까지 빼면 평가 hit@3이 21/22에서 20/22로 떨어졌다. */
|
|
33
|
+
const PARTICLE_SHARE = 0.9;
|
|
34
|
+
const GENERIC_MIN_RECORDS = 10;
|
|
35
|
+
export const DOCMAP_MAX_CANDIDATES = 3;
|
|
36
|
+
export const DOCMAP_MAX_CHARS = 1500;
|
|
37
|
+
/** 긴 prompt는 앞과 뒤를 함께 본다. 경로·날짜·마지막 요청은 보통 끝에 있다. */
|
|
38
|
+
const QUERY_HEAD_CHARS = 1000;
|
|
39
|
+
const QUERY_TAIL_CHARS = 1000;
|
|
40
|
+
const HEAD_BYTES = 8192;
|
|
41
|
+
const BODY_MAX_BYTES = 262_144;
|
|
42
|
+
const EXCERPT_CHARS = 160;
|
|
43
|
+
const SUPERSEDE_HOPS = 3;
|
|
44
|
+
const HEADER_LINE = /^(?:#\s|RECORD:|SCOPE:|PATHS:|DATE:|STATUS:)/;
|
|
45
|
+
/** 1위 대비 이 비율 미만인 뒤 후보는 공통 낱말("기록" 등)로만 걸린 잡음이라 넣지 않는다. */
|
|
46
|
+
export const DOCMAP_CANDIDATE_RATIO = 0.5;
|
|
47
|
+
/** 시점 낱말은 주제 근거가 아니라 날짜 의도로만 쓴다. */
|
|
48
|
+
const TEMPORAL_TERMS: Record<string, true> = {
|
|
49
|
+
오늘: true, 어제: true, 그제: true, 그저: true, 저께: true, 최근: true, 요즘: true, 최신: true, 방금: true,
|
|
50
|
+
today: true, yesterday: true, recent: true, recently: true, latest: true,
|
|
51
|
+
};
|
|
52
|
+
const RECENT_INTENT = /최근|요즘|최신|방금|\brecent(?:ly)?\b|\blatest\b/i;
|
|
53
|
+
const ENGLISH_STOP: Record<string, true> = Object.fromEntries(
|
|
54
|
+
"an the and or of to in on at for from by with is are was were be been do did does what why how which who when where that this it its as into after before about can could should would will"
|
|
55
|
+
.split(" ")
|
|
56
|
+
.map((w) => [w, true] as const),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
export const DOCMAP_INSTRUCTION =
|
|
60
|
+
"아래는 질문과 맞을 수 있는 작업 기록 후보다. 답하기 전에 그 구간을 read로 열어 확인하고 맞지 않으면 버려라. 회상된 기억은 단서이지 근거가 아니다.";
|
|
61
|
+
|
|
62
|
+
export interface DocmapUnit {
|
|
63
|
+
/** 대표 파일 절대 경로(main.md, 없으면 그 폴더의 다른 기록, 또는 월별 스냅샷 파일). */
|
|
64
|
+
readonly file: string;
|
|
65
|
+
/** 단위에 속한 기록 파일. 폴더 단위면 그 폴더 바로 아래 .md, 스냅샷이면 자기 자신. */
|
|
66
|
+
readonly files: readonly string[];
|
|
67
|
+
readonly key: string;
|
|
68
|
+
readonly title: string;
|
|
69
|
+
readonly fields: Readonly<Record<string, string>>;
|
|
70
|
+
readonly mapLines: string[];
|
|
71
|
+
/** 대표 파일 앞 8KB를 소문자로. 낱말이 기록 본문에서 흔한지 셀 때만 쓴다(점수는 헤더만). */
|
|
72
|
+
readonly body: string;
|
|
73
|
+
/** RECORD `DATE:`, 없으면 경로의 YYYY/MM/DD. `YYYY-MM-DD` 또는 빈 문자열. */
|
|
74
|
+
readonly date: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface DocmapCorpus {
|
|
78
|
+
readonly root: string;
|
|
79
|
+
readonly maps: readonly string[];
|
|
80
|
+
readonly units: readonly DocmapUnit[];
|
|
81
|
+
readonly tokens: ReadonlyMap<DocmapUnit, readonly string[]>;
|
|
82
|
+
readonly termCounts: ReadonlyMap<DocmapUnit, ReadonlyMap<string, number>>;
|
|
83
|
+
readonly df: ReadonlyMap<string, number>;
|
|
84
|
+
readonly avgLen: number;
|
|
85
|
+
/** 요청 하나 동안만 쓰는 낱말별 본문 문서 빈도 메모. */
|
|
86
|
+
readonly bodyDf: Map<string, number>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface DocmapCandidate {
|
|
90
|
+
readonly unit: DocmapUnit;
|
|
91
|
+
readonly score: number;
|
|
92
|
+
/** superseded-by를 따라왔으면 원래 후보의 대표 파일. */
|
|
93
|
+
readonly supersededFrom?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface DocmapSection {
|
|
97
|
+
readonly heading: string;
|
|
98
|
+
readonly start: number;
|
|
99
|
+
readonly end: number;
|
|
100
|
+
readonly excerpt: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface DocmapRoute {
|
|
104
|
+
readonly topScore: number;
|
|
105
|
+
readonly signals: DocmapSignals;
|
|
106
|
+
readonly candidates: readonly DocmapCandidate[];
|
|
107
|
+
readonly text: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 소문자 ASCII 낱말(2자 이상)과 한글 연속 구간의 2글자 조각. */
|
|
111
|
+
export function docmapTokens(text: string): string[] {
|
|
112
|
+
const lower = text.toLowerCase();
|
|
113
|
+
const out: string[] = lower.match(/[a-z0-9_]{2,}/g) ?? [];
|
|
114
|
+
for (const run of lower.match(/[가-힣]+/g) ?? []) {
|
|
115
|
+
if (run.length === 1) { out.push(run); continue; }
|
|
116
|
+
for (let i = 0; i < run.length - 1; i++) out.push(run.slice(i, i + 2));
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isFile(path: string): boolean {
|
|
122
|
+
try { return statSync(path).isFile(); } catch { return false; }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isDir(path: string): boolean {
|
|
126
|
+
try { return statSync(path).isDirectory(); } catch { return false; }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function readHead(path: string, bytes: number): string {
|
|
130
|
+
const fd = openSync(path, "r");
|
|
131
|
+
try {
|
|
132
|
+
const buf = Buffer.alloc(bytes);
|
|
133
|
+
const n = readSync(fd, buf, 0, bytes, 0);
|
|
134
|
+
return buf.subarray(0, n).toString("utf8");
|
|
135
|
+
} finally {
|
|
136
|
+
closeSync(fd);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** cwd에서 위로 올라가 저장소 루트(.git)와 가장 가까운 HANDOFF.md 폴더를 찾는다. */
|
|
141
|
+
export function findDocmapProject(cwd: string): { root: string; project: string } | undefined {
|
|
142
|
+
let dir = resolve(cwd);
|
|
143
|
+
let project: string | undefined;
|
|
144
|
+
for (;;) {
|
|
145
|
+
if (!project && isFile(join(dir, "HANDOFF.md"))) project = dir;
|
|
146
|
+
if (existsSync(join(dir, ".git"))) return { root: dir, project: project ?? dir };
|
|
147
|
+
const parent = dirname(dir);
|
|
148
|
+
if (parent === dir) return project ? { root: project, project } : undefined;
|
|
149
|
+
dir = parent;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function mapLinks(line: string, base: string): string[] {
|
|
154
|
+
const out: string[] = [];
|
|
155
|
+
for (const m of line.matchAll(/\]\(([^)#\s]+)/g)) {
|
|
156
|
+
const href = m[1]!;
|
|
157
|
+
if (/^[a-z]+:/i.test(href)) continue;
|
|
158
|
+
out.push(resolve(base, decodeURI(href)));
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** 루트·프로젝트 지도, 그 지도가 링크한 하위 지도, 루트 아래 1~2단 `*\/doc/README.md`(옆에 history가 있는 것). */
|
|
164
|
+
export function findDocmapMaps(root: string, project: string): string[] {
|
|
165
|
+
const maps = new Set<string>();
|
|
166
|
+
const add = (path: string) => { if (isFile(path)) maps.add(path); };
|
|
167
|
+
add(join(root, "doc", "README.md"));
|
|
168
|
+
add(join(project, "doc", "README.md"));
|
|
169
|
+
for (const map of [...maps]) {
|
|
170
|
+
for (const line of readFileSync(map, "utf8").split("\n")) {
|
|
171
|
+
for (const target of mapLinks(line, dirname(map))) {
|
|
172
|
+
if (target.endsWith(`${sep}doc${sep}README.md`) && target.startsWith(root + sep)) add(target);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const skip = (name: string) => name.startsWith(".") || name === "node_modules";
|
|
177
|
+
for (const a of readdirSync(root, { withFileTypes: true })) {
|
|
178
|
+
if (!a.isDirectory() || skip(a.name)) continue;
|
|
179
|
+
const first = join(root, a.name);
|
|
180
|
+
if (isDir(join(first, "doc", "history"))) add(join(first, "doc", "README.md"));
|
|
181
|
+
for (const b of readdirSync(first, { withFileTypes: true })) {
|
|
182
|
+
if (!b.isDirectory() || skip(b.name)) continue;
|
|
183
|
+
const second = join(first, b.name);
|
|
184
|
+
if (isDir(join(second, "doc", "history"))) add(join(second, "doc", "README.md"));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return [...maps];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function parseHead(text: string): { title: string; fields: Record<string, string> } {
|
|
191
|
+
const head = text.split(/\n## /, 1)[0]!;
|
|
192
|
+
const title = /^#\s+(.+)$/m.exec(head)?.[1]?.trim() ?? "";
|
|
193
|
+
const fields: Record<string, string> = {};
|
|
194
|
+
for (const line of head.split("\n")) {
|
|
195
|
+
const m = /^(SCOPE|PATHS|DATE|STATUS):\s*(.*)$/.exec(line.trim());
|
|
196
|
+
if (m && !(m[1]! in fields)) fields[m[1]!] = m[2]!.trim();
|
|
197
|
+
}
|
|
198
|
+
return { title, fields };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function representative(files: string[]): string {
|
|
202
|
+
const names = files.map((f) => f.slice(f.lastIndexOf(sep) + 1));
|
|
203
|
+
const pick = names.indexOf("main.md") >= 0 ? names.indexOf("main.md") : names.findIndex((n) => n.startsWith("main"));
|
|
204
|
+
return files[pick >= 0 ? pick : 0]!;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function collectUnits(historyDir: string): DocmapUnit[] {
|
|
208
|
+
const units: DocmapUnit[] = [];
|
|
209
|
+
for (const year of readdirSync(historyDir)) {
|
|
210
|
+
if (!/^\d{4}$/.test(year)) continue;
|
|
211
|
+
const yearDir = join(historyDir, year);
|
|
212
|
+
if (!isDir(yearDir)) continue;
|
|
213
|
+
for (const month of readdirSync(yearDir)) {
|
|
214
|
+
if (!/^\d{2}$/.test(month)) continue;
|
|
215
|
+
const monthDir = join(yearDir, month);
|
|
216
|
+
if (!isDir(monthDir)) continue;
|
|
217
|
+
for (const entry of readdirSync(monthDir, { withFileTypes: true })) {
|
|
218
|
+
if (!/^\d{2}-/.test(entry.name)) continue;
|
|
219
|
+
const path = join(monthDir, entry.name);
|
|
220
|
+
let files: string[];
|
|
221
|
+
if (entry.isDirectory()) {
|
|
222
|
+
files = readdirSync(path, { withFileTypes: true })
|
|
223
|
+
.filter((f) => f.isFile() && f.name.endsWith(".md"))
|
|
224
|
+
.map((f) => join(path, f.name))
|
|
225
|
+
.sort();
|
|
226
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
227
|
+
files = [path];
|
|
228
|
+
} else continue;
|
|
229
|
+
if (files.length === 0) continue;
|
|
230
|
+
const file = representative(files);
|
|
231
|
+
// 같은 폴더의 maker 기록 제목·헤더까지 넣으면 hit@3이 36/45에서 33/45로 떨어졌다(2026-10-16 보정 세트).
|
|
232
|
+
const head = readHead(file, HEAD_BYTES);
|
|
233
|
+
const { title, fields } = parseHead(head);
|
|
234
|
+
const date = /^\d{4}-\d{2}-\d{2}/.exec(fields.DATE ?? "")?.[0] ?? `${year}-${month}-${entry.name.slice(0, 2)}`;
|
|
235
|
+
units.push({ file, files, key: path, title, fields, mapLines: [], body: head.toLowerCase(), date });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return units;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function unitText(unit: DocmapUnit): string {
|
|
243
|
+
const f = unit.fields;
|
|
244
|
+
return [...unit.mapLines, unit.title, f.SCOPE, f.PATHS, f.DATE, f.STATUS].filter(Boolean).join(" ");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function buildDocmapCorpus(root: string, maps: readonly string[]): DocmapCorpus {
|
|
248
|
+
const units: DocmapUnit[] = [];
|
|
249
|
+
const seen = new Set<string>();
|
|
250
|
+
for (const map of maps) {
|
|
251
|
+
const historyDir = join(dirname(map), "history");
|
|
252
|
+
if (seen.has(historyDir) || !isDir(historyDir)) continue;
|
|
253
|
+
seen.add(historyDir);
|
|
254
|
+
units.push(...collectUnits(historyDir));
|
|
255
|
+
}
|
|
256
|
+
const byKey = new Map(units.map((u) => [u.key, u] as const));
|
|
257
|
+
for (const map of maps) {
|
|
258
|
+
const base = dirname(map);
|
|
259
|
+
for (const line of readFileSync(map, "utf8").split("\n")) {
|
|
260
|
+
for (const target of mapLinks(line, base)) {
|
|
261
|
+
const unit = byKey.get(target) ?? byKey.get(dirname(target));
|
|
262
|
+
if (unit && !unit.mapLines.includes(line)) unit.mapLines.push(line);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return docmapCorpusFrom(root, maps, units);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** 단위 목록에서 토큰·문서 빈도를 만든다. 평가 스크립트가 단위 부분집합으로도 부른다. */
|
|
270
|
+
export function docmapCorpusFrom(root: string, maps: readonly string[], units: readonly DocmapUnit[]): DocmapCorpus {
|
|
271
|
+
const tokens = new Map<DocmapUnit, readonly string[]>();
|
|
272
|
+
const termCounts = new Map<DocmapUnit, Map<string, number>>();
|
|
273
|
+
const df = new Map<string, number>();
|
|
274
|
+
let total = 0;
|
|
275
|
+
for (const unit of units) {
|
|
276
|
+
const toks = docmapTokens(unitText(unit));
|
|
277
|
+
tokens.set(unit, toks);
|
|
278
|
+
total += toks.length;
|
|
279
|
+
const counts = new Map<string, number>();
|
|
280
|
+
for (const t of toks) counts.set(t, (counts.get(t) ?? 0) + 1);
|
|
281
|
+
termCounts.set(unit, counts);
|
|
282
|
+
for (const t of counts.keys()) df.set(t, (df.get(t) ?? 0) + 1);
|
|
283
|
+
}
|
|
284
|
+
return { root, maps, units, tokens, termCounts, df, avgLen: total / (units.length || 1) || 1, bodyDf: new Map() };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function idfFor(records: number, df: number): number {
|
|
288
|
+
return Math.log(1 + (records - df + 0.5) / (df + 0.5));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function docmapIdf(corpus: DocmapCorpus, term: string): number {
|
|
292
|
+
return idfFor(corpus.units.length, corpus.df.get(term) ?? 0);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* 헤더나 본문 앞 8KB에 `term`이 있는 기록 수. `stopAt`에 닿거나 닿을 수 없게 되면 일찍 멈추고, 그 부분 값은
|
|
297
|
+
* 메모하지 않는다. 한글 2글자 조각은 부분 문자열, ASCII 낱말은 낱말 경계로 센다(토큰화와 같은 뜻).
|
|
298
|
+
*/
|
|
299
|
+
function countRecords(corpus: DocmapCorpus, term: string, stopAt = Number.POSITIVE_INFINITY): number {
|
|
300
|
+
const memo = corpus.bodyDf.get(term);
|
|
301
|
+
if (memo !== undefined) return memo;
|
|
302
|
+
const word = /^[a-z0-9_]+$/.test(term) ? new RegExp(`(?<![a-z0-9_])${term}(?![a-z0-9_])`) : undefined;
|
|
303
|
+
const units = corpus.units;
|
|
304
|
+
let n = 0;
|
|
305
|
+
for (let i = 0; i < units.length; i++) {
|
|
306
|
+
const u = units[i]!;
|
|
307
|
+
if ((word ? word.test(u.body) : u.body.includes(term)) || corpus.termCounts.get(u)!.has(term)) n++;
|
|
308
|
+
if (Number.isFinite(stopAt) && (n >= stopAt || n + (units.length - 1 - i) < stopAt)) return n;
|
|
309
|
+
}
|
|
310
|
+
corpus.bodyDf.set(term, n);
|
|
311
|
+
return n;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** 기록 `share` 이상(최소 10개)에 나오는 낱말. 기록이 적으면 흔한지 판단할 수 없어 아무것도 빼지 않는다. */
|
|
315
|
+
function commonIn(corpus: DocmapCorpus, term: string, share: number): boolean {
|
|
316
|
+
const need = Math.max(GENERIC_MIN_RECORDS, share * corpus.units.length);
|
|
317
|
+
return countRecords(corpus, term, need) >= need;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** 긴 prompt는 앞 1000자와 뒤 1000자를 본다. */
|
|
321
|
+
function queryText(query: string): string {
|
|
322
|
+
return query.length <= QUERY_HEAD_CHARS + QUERY_TAIL_CHARS ? query : `${query.slice(0, QUERY_HEAD_CHARS)}\n${query.slice(-QUERY_TAIL_CHARS)}`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** 질의 낱말에서 한 글자 한글(조사·관형사), 시점 낱말, 영어 기능어를 뺀다. */
|
|
326
|
+
function queryTerms(query: string): string[] {
|
|
327
|
+
return [...new Set(docmapTokens(queryText(query)))].filter(
|
|
328
|
+
(w) => !/^[가-힣]$/.test(w) && !Object.hasOwn(TEMPORAL_TERMS, w) && !Object.hasOwn(ENGLISH_STOP, w),
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** 순위·조건에 쓰는 질의 낱말: 말뭉치에 있고, 거의 모든 기록 본문에 나오는 조사(「에서」「으로」)가 아닌 것. */
|
|
333
|
+
export function docmapQueryTerms(corpus: DocmapCorpus, query: string): string[] {
|
|
334
|
+
return queryTerms(query).filter((w) => corpus.df.has(w) && !commonIn(corpus, w, PARTICLE_SHARE));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** 점수가 같으면 RECORD 날짜가 새로운 기록을 앞에 둔다. */
|
|
338
|
+
export function rankDocmap(corpus: DocmapCorpus, query: string, terms = docmapQueryTerms(corpus, query)): DocmapCandidate[] {
|
|
339
|
+
if (corpus.units.length === 0) return [];
|
|
340
|
+
const out: DocmapCandidate[] = [];
|
|
341
|
+
for (const unit of corpus.units) {
|
|
342
|
+
const counts = corpus.termCounts.get(unit)!;
|
|
343
|
+
const len = corpus.tokens.get(unit)!.length;
|
|
344
|
+
let score = 0;
|
|
345
|
+
for (const w of terms) {
|
|
346
|
+
const f = counts.get(w);
|
|
347
|
+
if (!f) continue;
|
|
348
|
+
score += (docmapIdf(corpus, w) * f * 2.2) / (f + 1.2 * (0.25 + (0.75 * len) / corpus.avgLen));
|
|
349
|
+
}
|
|
350
|
+
if (score > 0) out.push({ unit, score });
|
|
351
|
+
}
|
|
352
|
+
return out.sort((a, b) => b.score - a.score || (a.unit.date < b.unit.date ? 1 : a.unit.date > b.unit.date ? -1 : 0));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export interface DocmapSignals {
|
|
356
|
+
/** 1위 점수 ÷ 최대 IDF. */
|
|
357
|
+
readonly score: number;
|
|
358
|
+
/** 흔한 낱말을 뺀 질의 낱말의 헤더 IDF 합 대비 1위가 담은 몫. 0~1. */
|
|
359
|
+
readonly coverage: number;
|
|
360
|
+
/** 1위가 담은 질의 낱말마다 본문 IDF ÷ 최대 IDF의 합. 기록 본문에서도 드문 낱말일수록 크다. */
|
|
361
|
+
readonly evidence: number;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function docmapSignals(corpus: DocmapCorpus, top: DocmapCandidate, terms: readonly string[]): DocmapSignals {
|
|
365
|
+
const counts = corpus.termCounts.get(top.unit)!;
|
|
366
|
+
const records = corpus.units.length;
|
|
367
|
+
// 기록 하나에만 있는 낱말의 IDF. 점수·근거를 이것으로 나눠 기록 수와 무관한 단위로 맞춘다.
|
|
368
|
+
const unit = idfFor(records, 1);
|
|
369
|
+
let all = 0;
|
|
370
|
+
let hit = 0;
|
|
371
|
+
let evidence = 0;
|
|
372
|
+
for (const w of terms) {
|
|
373
|
+
if (counts.has(w)) evidence += idfFor(records, countRecords(corpus, w)) / unit;
|
|
374
|
+
if (commonIn(corpus, w, GENERIC_SHARE)) continue;
|
|
375
|
+
const idf = docmapIdf(corpus, w);
|
|
376
|
+
all += idf;
|
|
377
|
+
if (counts.has(w)) hit += idf;
|
|
378
|
+
}
|
|
379
|
+
return { score: top.score / unit, coverage: all > 0 ? hit / all : 0, evidence };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** 드문 낱말이 여럿 맞고 1위가 질의의 일정 몫을 설명하거나, 짧은 질문을 1위가 거의 다 설명하면 붙인다. */
|
|
383
|
+
export function docmapPasses(s: DocmapSignals): boolean {
|
|
384
|
+
return (s.score >= DOCMAP_MIN_SCORE && s.coverage >= DOCMAP_MIN_COVERAGE) || (s.coverage >= DOCMAP_FOCUSED_COVERAGE && s.evidence >= DOCMAP_FOCUSED_EVIDENCE);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function supersededTarget(corpus: DocmapCorpus, unit: DocmapUnit): DocmapUnit | undefined {
|
|
388
|
+
const m = /^superseded-by\s+(\S+)/i.exec(unit.fields.STATUS ?? "");
|
|
389
|
+
if (!m) return undefined;
|
|
390
|
+
const ref = m[1]!.replace(/[`'"]/g, "");
|
|
391
|
+
for (const base of [corpus.root, dirname(unit.file)]) {
|
|
392
|
+
const target = resolve(base, ref);
|
|
393
|
+
const found = corpus.units.find((u) => u.key === target || u.files.includes(target) || u.key === dirname(target));
|
|
394
|
+
if (found && found !== unit) return found;
|
|
395
|
+
}
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** 질문이 가리키는 날짜(`days`, YYYY-MM-DD) 또는 「최근」 의도. 오늘·어제·그제, 2026-10-16, 10월 16일, 10-16, 10/16. */
|
|
400
|
+
export type DocmapDateIntent = { readonly days: readonly string[] } | { readonly recent: true };
|
|
401
|
+
|
|
402
|
+
export function docmapDateIntent(query: string, now = new Date()): DocmapDateIntent | undefined {
|
|
403
|
+
const text = queryText(query);
|
|
404
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
405
|
+
const ymd = (d: Date) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
406
|
+
const today = ymd(now);
|
|
407
|
+
const days = new Set<string>();
|
|
408
|
+
const back = (n: number) => days.add(ymd(new Date(now.getFullYear(), now.getMonth(), now.getDate() - n)));
|
|
409
|
+
if (/오늘|\btoday\b/i.test(text)) back(0);
|
|
410
|
+
if (/어제|\byesterday\b/i.test(text)) back(1);
|
|
411
|
+
if (/그제|그저께/.test(text)) back(2);
|
|
412
|
+
const add = (y: number | undefined, m: number, d: number) => {
|
|
413
|
+
if (m < 1 || m > 12 || d < 1 || d > 31) return;
|
|
414
|
+
let year = y ?? now.getFullYear();
|
|
415
|
+
// 연도 없이 쓴 날짜가 오늘 뒤면 작년을 가리킨 것으로 본다.
|
|
416
|
+
if (y === undefined && `${year}-${pad(m)}-${pad(d)}` > today) year--;
|
|
417
|
+
days.add(`${year}-${pad(m)}-${pad(d)}`);
|
|
418
|
+
};
|
|
419
|
+
for (const m of text.matchAll(/(?<!\d)(20\d{2})[-./](\d{1,2})[-./](\d{1,2})(?!\d)/g)) add(Number(m[1]), Number(m[2]), Number(m[3]));
|
|
420
|
+
for (const m of text.matchAll(/(?<!\d)(\d{1,2})\s*월\s*(\d{1,2})\s*일/g)) add(undefined, Number(m[1]), Number(m[2]));
|
|
421
|
+
for (const m of text.matchAll(/(?<![\d./-])(\d{1,2})[-/](\d{2})(?![\d./-])/g)) add(undefined, Number(m[1]), Number(m[2]));
|
|
422
|
+
if (days.size > 0) return { days: [...days] };
|
|
423
|
+
return RECENT_INTENT.test(text) ? { recent: true } : undefined;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* 상위 후보를 고르고 superseded-by를 최대 3번 따라간다. 같은 단위는 한 번만 남긴다.
|
|
428
|
+
* 날짜를 물으면 그 날짜 기록을 1위 대비 비율의 절반까지 넓혀 앞에 둔다. 「최근」을 물으면 비율을 넘은 후보 중
|
|
429
|
+
* 새로운 기록부터 고르되 1위는 남긴다. 같은 주제 기록이 여럿이면 가장 새 기록이 빠지지 않게 하려는 것이다.
|
|
430
|
+
*/
|
|
431
|
+
export function pickDocmapCandidates(
|
|
432
|
+
corpus: DocmapCorpus,
|
|
433
|
+
ranked: readonly DocmapCandidate[],
|
|
434
|
+
limit = DOCMAP_MAX_CANDIDATES,
|
|
435
|
+
ratio = DOCMAP_CANDIDATE_RATIO,
|
|
436
|
+
intent?: DocmapDateIntent,
|
|
437
|
+
): DocmapCandidate[] {
|
|
438
|
+
const top = ranked[0];
|
|
439
|
+
if (!top) return [];
|
|
440
|
+
let order = ranked.filter((c) => c.score >= top.score * ratio);
|
|
441
|
+
if (intent && "days" in intent) {
|
|
442
|
+
const dated = ranked.filter((c) => intent.days.includes(c.unit.date) && c.score >= (top.score * ratio) / 2);
|
|
443
|
+
order = [...dated, ...order.filter((c) => !dated.includes(c))];
|
|
444
|
+
} else if (intent) {
|
|
445
|
+
order = [...order].sort((a, b) => (a.unit.date < b.unit.date ? 1 : a.unit.date > b.unit.date ? -1 : 0));
|
|
446
|
+
const at = order.indexOf(top);
|
|
447
|
+
if (at >= limit) order.splice(limit - 1, 0, ...order.splice(at, 1));
|
|
448
|
+
}
|
|
449
|
+
const picked: DocmapCandidate[] = [];
|
|
450
|
+
for (const c of order) {
|
|
451
|
+
if (picked.length >= limit) break;
|
|
452
|
+
let unit = c.unit;
|
|
453
|
+
for (let hop = 0; hop < SUPERSEDE_HOPS; hop++) {
|
|
454
|
+
const next = supersededTarget(corpus, unit);
|
|
455
|
+
if (!next) break;
|
|
456
|
+
unit = next;
|
|
457
|
+
}
|
|
458
|
+
if (picked.some((p) => p.unit === unit)) continue;
|
|
459
|
+
picked.push(unit === c.unit ? c : { unit, score: c.score, supersededFrom: c.unit.file });
|
|
460
|
+
}
|
|
461
|
+
return picked;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** 후보 파일들의 `## ` 절을 한 말뭉치로 BM25해 질문과 가장 맞는 절과 그 안의 가장 맞는 줄을 고른다. */
|
|
465
|
+
export function bestDocmapSections(files: readonly string[], query: string): Map<string, DocmapSection> {
|
|
466
|
+
type Sec = { file: string; heading: string; start: number; end: number; lines: string[]; toks: string[] };
|
|
467
|
+
const secs: Sec[] = [];
|
|
468
|
+
for (const file of files) {
|
|
469
|
+
const lines = readHead(file, BODY_MAX_BYTES).split("\n");
|
|
470
|
+
let cur: Sec = { file, heading: "", start: 1, end: 1, lines: [], toks: [] };
|
|
471
|
+
lines.forEach((line, i) => {
|
|
472
|
+
if (/^##\s/.test(line) && cur.lines.length > 0) {
|
|
473
|
+
cur.end = i;
|
|
474
|
+
secs.push(cur);
|
|
475
|
+
cur = { file, heading: line.replace(/^#+\s*/, "").trim(), start: i + 1, end: i + 1, lines: [], toks: [] };
|
|
476
|
+
} else if (/^##\s/.test(line)) {
|
|
477
|
+
cur.heading = line.replace(/^#+\s*/, "").trim();
|
|
478
|
+
}
|
|
479
|
+
cur.lines.push(line);
|
|
480
|
+
});
|
|
481
|
+
cur.end = lines.length;
|
|
482
|
+
secs.push(cur);
|
|
483
|
+
}
|
|
484
|
+
// 제목·RECORD 헤더는 SCOPE로 이미 보여 주므로 구간 고르기에서 뺀다. 넣으면 머리말이 늘 이긴다.
|
|
485
|
+
for (const s of secs) s.toks = docmapTokens(s.lines.filter((l) => !HEADER_LINE.test(l.trim())).join("\n"));
|
|
486
|
+
const n = secs.length;
|
|
487
|
+
const df = new Map<string, number>();
|
|
488
|
+
for (const s of secs) for (const t of new Set(s.toks)) df.set(t, (df.get(t) ?? 0) + 1);
|
|
489
|
+
const avg = secs.reduce((a, s) => a + s.toks.length, 0) / (n || 1) || 1;
|
|
490
|
+
const q = queryTerms(query);
|
|
491
|
+
const idf = (w: string) => { const d = df.get(w) ?? 0; return Math.log(1 + (n - d + 0.5) / (d + 0.5)); };
|
|
492
|
+
const best = new Map<string, { score: number; sec: Sec }>();
|
|
493
|
+
for (const s of secs) {
|
|
494
|
+
const counts = new Map<string, number>();
|
|
495
|
+
for (const t of s.toks) counts.set(t, (counts.get(t) ?? 0) + 1);
|
|
496
|
+
let score = 0;
|
|
497
|
+
for (const w of q) {
|
|
498
|
+
const f = counts.get(w);
|
|
499
|
+
if (f) score += (idf(w) * f * 2.2) / (f + 1.2 * (0.25 + (0.75 * s.toks.length) / avg));
|
|
500
|
+
}
|
|
501
|
+
const prev = best.get(s.file);
|
|
502
|
+
if (!prev || score > prev.score) best.set(s.file, { score, sec: s });
|
|
503
|
+
}
|
|
504
|
+
const out = new Map<string, DocmapSection>();
|
|
505
|
+
for (const [file, { sec }] of best) {
|
|
506
|
+
let bestLine = "";
|
|
507
|
+
let bestLineScore = -1;
|
|
508
|
+
for (const line of sec.lines) {
|
|
509
|
+
const text = line.trim();
|
|
510
|
+
if (!text || /^#/.test(text) || HEADER_LINE.test(text)) continue;
|
|
511
|
+
const toks = new Set(docmapTokens(text));
|
|
512
|
+
let s = 0;
|
|
513
|
+
for (const w of q) if (toks.has(w)) s += idf(w);
|
|
514
|
+
if (s > bestLineScore) { bestLineScore = s; bestLine = text; }
|
|
515
|
+
}
|
|
516
|
+
const plain = bestLine.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/[*`]/g, "").replace(/^[-|>\d.)\s]+/, "").trim();
|
|
517
|
+
const excerpt = plain.length > EXCERPT_CHARS ? `${plain.slice(0, EXCERPT_CHARS - 1)}…` : plain;
|
|
518
|
+
out.set(file, { heading: sec.heading || "(머리말)", start: sec.start, end: sec.end, excerpt });
|
|
519
|
+
}
|
|
520
|
+
return out;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function rel(root: string, path: string): string {
|
|
524
|
+
return relative(root, path).split(sep).join("/");
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function clip(text: string, max: number): string {
|
|
528
|
+
const one = text.replace(/\s+/g, " ").trim();
|
|
529
|
+
return one.length > max ? `${one.slice(0, max - 1)}…` : one;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
export function renderDocmapRoute(corpus: DocmapCorpus, candidates: readonly DocmapCandidate[], sections: ReadonlyMap<string, DocmapSection>, maxChars = DOCMAP_MAX_CHARS): string {
|
|
533
|
+
const blocks = candidates.map((c, i) => {
|
|
534
|
+
const u = c.unit;
|
|
535
|
+
const lines = [`${i + 1}. ${rel(corpus.root, u.file)}${u.fields.STATUS ? ` (STATUS: ${clip(u.fields.STATUS, 60)})` : ""}`];
|
|
536
|
+
if (c.supersededFrom) lines.push(` 대체 전: ${rel(corpus.root, c.supersededFrom)}`);
|
|
537
|
+
if (u.mapLines[0]) lines.push(` 지도: ${clip(u.mapLines[0].replace(/\]\([^)]*\)/g, "]").replace(/^[-*]\s*/, "").replace(/\s*\|\s*/g, " "), 200)}`);
|
|
538
|
+
else if (u.title) lines.push(` 제목: ${clip(u.title, 160)}`);
|
|
539
|
+
if (u.fields.SCOPE) lines.push(` SCOPE: ${clip(u.fields.SCOPE, 160)}`);
|
|
540
|
+
const sec = sections.get(u.file);
|
|
541
|
+
if (sec) lines.push(` 구간: ## ${clip(sec.heading, 60)} (L${sec.start}-${sec.end})${sec.excerpt ? ` — ${sec.excerpt}` : ""}`);
|
|
542
|
+
return lines;
|
|
543
|
+
});
|
|
544
|
+
const head = `[문서 지도 후보] ${DOCMAP_INSTRUCTION}`;
|
|
545
|
+
const join2 = () => [head, ...blocks.map((b) => b.join("\n"))].join("\n");
|
|
546
|
+
let text = join2();
|
|
547
|
+
// 넘치면 뒤 후보부터 줄을 덜어 낸다: 발췌 → SCOPE → 지도 줄 순서.
|
|
548
|
+
for (const drop of [/^ {3}구간: /, /^ {3}SCOPE: /, /^ {3}(지도|제목): /]) {
|
|
549
|
+
for (let i = blocks.length - 1; i >= 0 && text.length > maxChars; i--) {
|
|
550
|
+
const idx = blocks[i]!.findIndex((l) => drop.test(l));
|
|
551
|
+
if (idx < 0) continue;
|
|
552
|
+
if (drop.source.includes("구간")) blocks[i]![idx] = blocks[i]![idx]!.replace(/ — .*$/, "");
|
|
553
|
+
else blocks[i]!.splice(idx, 1);
|
|
554
|
+
text = join2();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return text.length > maxChars ? text.slice(0, maxChars - 1) + "…" : text;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** 요청 하나에 대한 전 과정. 조건 미달이거나 지도·기록이 없으면 undefined. 예외는 호출부가 삼킨다. */
|
|
561
|
+
export function routeDocmap(cwd: string, prompt: string, now = new Date()): DocmapRoute | undefined {
|
|
562
|
+
const found = findDocmapProject(cwd);
|
|
563
|
+
if (!found) return undefined;
|
|
564
|
+
const maps = findDocmapMaps(found.root, found.project);
|
|
565
|
+
if (maps.length === 0) return undefined;
|
|
566
|
+
const corpus = buildDocmapCorpus(found.root, maps);
|
|
567
|
+
const terms = docmapQueryTerms(corpus, prompt);
|
|
568
|
+
const ranked = rankDocmap(corpus, prompt, terms);
|
|
569
|
+
const top = ranked[0];
|
|
570
|
+
if (!top) return undefined;
|
|
571
|
+
const signals = docmapSignals(corpus, top, terms);
|
|
572
|
+
if (!docmapPasses(signals)) return undefined;
|
|
573
|
+
const candidates = pickDocmapCandidates(corpus, ranked, DOCMAP_MAX_CANDIDATES, DOCMAP_CANDIDATE_RATIO, docmapDateIntent(prompt, now));
|
|
574
|
+
const sections = bestDocmapSections(candidates.map((c) => c.unit.file), prompt);
|
|
575
|
+
return { topScore: top.score, signals, candidates, text: renderDocmapRoute(corpus, candidates, sections) };
|
|
576
|
+
}
|