@ngockhoale/ukit 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +179 -0
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/bin/ukit +30 -0
- package/manifests/platform.full.yaml +1194 -0
- package/package.json +71 -0
- package/scripts/bug/triage.mjs +37 -0
- package/scripts/index/build-index.mjs +35 -0
- package/scripts/index/query-index.mjs +92 -0
- package/scripts/index/refresh-index.mjs +85 -0
- package/scripts/release/verify-release.mjs +56 -0
- package/src/bug/triageBug.js +123 -0
- package/src/cli/adapters.js +148 -0
- package/src/cli/commands/diff.js +51 -0
- package/src/cli/commands/doctor.js +125 -0
- package/src/cli/commands/indexArgs.js +73 -0
- package/src/cli/commands/indexTools.js +509 -0
- package/src/cli/commands/install.js +293 -0
- package/src/cli/commands/memory.js +126 -0
- package/src/cli/commands/status.js +8 -0
- package/src/cli/commands/uninstall.js +51 -0
- package/src/cli/index.js +109 -0
- package/src/context/detectProjectContext.js +49 -0
- package/src/context/detectProviders.js +12 -0
- package/src/core/applyPlan.js +89 -0
- package/src/core/buildPlan.js +228 -0
- package/src/core/compact/index.js +294 -0
- package/src/core/compact/threshold.js +936 -0
- package/src/core/diffPlan.js +73 -0
- package/src/core/ensureGitignore.js +117 -0
- package/src/core/fileOps.js +188 -0
- package/src/core/memory/hygiene.js +160 -0
- package/src/core/memory/index.js +2 -0
- package/src/core/memory/retrieval.js +476 -0
- package/src/core/memory/store.js +202 -0
- package/src/core/metadata.js +132 -0
- package/src/core/migrateLegacy.js +139 -0
- package/src/core/output/index.js +1309 -0
- package/src/core/paths.js +13 -0
- package/src/core/report.js +17 -0
- package/src/core/router/advisor.js +42 -0
- package/src/core/router/index.js +2 -0
- package/src/core/router/router.js +164 -0
- package/src/core/runInstallPipeline.js +365 -0
- package/src/core/runtimeConfig.js +190 -0
- package/src/core/runtimePaths.js +24 -0
- package/src/core/status.js +186 -0
- package/src/core/token/index.js +328 -0
- package/src/core/uninstall.js +246 -0
- package/src/core/validation/confidence.js +89 -0
- package/src/core/validation/index.js +2 -0
- package/src/core/validation/validator.js +165 -0
- package/src/index/buildIndex.js +1392 -0
- package/src/index/gitHooks.js +109 -0
- package/src/index/importResolution.js +377 -0
- package/src/index/languageTools.js +127 -0
- package/src/index/paths.js +27 -0
- package/src/index/queryIndex.js +637 -0
- package/src/index/relatedTests.js +237 -0
- package/src/index/resolveContext.js +345 -0
- package/src/index/routeCatalog.js +258 -0
- package/src/index/taskRouting.js +677 -0
- package/src/index/verificationPlan.js +437 -0
- package/src/manifest/loadManifest.js +22 -0
- package/src/manifest/selectItems.js +78 -0
- package/src/manifest/validateManifest.js +115 -0
- package/src/render/buildVariables.js +39 -0
- package/src/render/renderTemplate.js +44 -0
- package/src/stack/detectStack.js +213 -0
- package/templates/.claude/agents/bug-debugger.md +57 -0
- package/templates/.claude/agents/feature-implementer.md +55 -0
- package/templates/.claude/config/providers.md +25 -0
- package/templates/.claude/hooks/auto-allow-bash.sh +155 -0
- package/templates/.claude/hooks/auto-prune-bash.sh +75 -0
- package/templates/.claude/hooks/block-dangerous.sh +54 -0
- package/templates/.claude/hooks/compress-output.sh +17 -0
- package/templates/.claude/hooks/protect-files.sh +37 -0
- package/templates/.claude/hooks/reinject-context.sh +28 -0
- package/templates/.claude/hooks/session-start.md +13 -0
- package/templates/.claude/hooks/skill-router.sh +1681 -0
- package/templates/.claude/hooks/verification-guard.sh +271 -0
- package/templates/.claude/settings.json +144 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/mce/mc.xsd +75 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/pack.py +159 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/unpack.py +29 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validate.py +69 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/__init__.py +15 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/base.py +951 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/docx.py +274 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/pptx.py +315 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/redlining.py +279 -0
- package/templates/.claude/skills/backend-api/SKILL.md +26 -0
- package/templates/.claude/skills/canvas-design/LICENSE.txt +202 -0
- package/templates/.claude/skills/canvas-design/SKILL.md +130 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/templates/.claude/skills/code-review/SKILL.md +97 -0
- package/templates/.claude/skills/debugging-toolkit/SKILL.md +156 -0
- package/templates/.claude/skills/delivery/SKILL.md +92 -0
- package/templates/.claude/skills/discover-security/SKILL.md +86 -0
- package/templates/.claude/skills/docker-packaging/SKILL.md +60 -0
- package/templates/.claude/skills/docs-manager/SKILL.md +465 -0
- package/templates/.claude/skills/docs-manager/init-project-docs.sh +70 -0
- package/templates/.claude/skills/docs-manager/templates/README.md.template +50 -0
- package/templates/.claude/skills/docs-manager/templates/agent-roles.md.template +24 -0
- package/templates/.claude/skills/docs-manager/templates/coding-conventions.md.template +28 -0
- package/templates/.claude/skills/docs-manager/templates/memory.md.template +30 -0
- package/templates/.claude/skills/docs-manager/templates/onboarding.md.template +20 -0
- package/templates/.claude/skills/docs-manager/templates/project.md.template +26 -0
- package/templates/.claude/skills/docs-quality/SKILL.md +148 -0
- package/templates/.claude/skills/docx/LICENSE.txt +30 -0
- package/templates/.claude/skills/docx/SKILL.md +197 -0
- package/templates/.claude/skills/docx/docx-js.md +350 -0
- package/templates/.claude/skills/docx/ooxml.md +610 -0
- package/templates/.claude/skills/docx/scripts/__init__.py +1 -0
- package/templates/.claude/skills/docx/scripts/document.py +1276 -0
- package/templates/.claude/skills/docx/scripts/templates/comments.xml +3 -0
- package/templates/.claude/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/templates/.claude/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/templates/.claude/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/templates/.claude/skills/docx/scripts/templates/people.xml +3 -0
- package/templates/.claude/skills/docx/scripts/utilities.py +374 -0
- package/templates/.claude/skills/duraone/SKILL.md +204 -0
- package/templates/.claude/skills/duraone/references/backend.md +636 -0
- package/templates/.claude/skills/duraone/references/frontend.md +1506 -0
- package/templates/.claude/skills/duraone/references/sql.md +631 -0
- package/templates/.claude/skills/duraone/references/workflow.md +520 -0
- package/templates/.claude/skills/executing-plans/SKILL.md +76 -0
- package/templates/.claude/skills/file-organizer/SKILL.md +433 -0
- package/templates/.claude/skills/frontend/SKILL.md +26 -0
- package/templates/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/templates/.claude/skills/frontend-design/SKILL.md +42 -0
- package/templates/.claude/skills/frontend-vue/SKILL.md +127 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Box.vue +137 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Button.vue +93 -0
- package/templates/.claude/skills/frontend-vue/components/Control/ButtonBar.vue +29 -0
- package/templates/.claude/skills/frontend-vue/components/Control/ButtonFloat.vue +62 -0
- package/templates/.claude/skills/frontend-vue/components/Control/CheckButton.vue +75 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Checkbox.vue +58 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Datetime.vue +148 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Dropdownlist.vue +156 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Input.vue +106 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Label.vue +38 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Master/BoxColumn.vue +24 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Popup/Confirm.vue +33 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Popup/Info.vue +32 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Popup/ModalInfo.vue +39 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Popup/Reject.vue +64 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Tag.vue +82 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Upload.vue +61 -0
- package/templates/.claude/skills/frontend-vue/components/ControlMobile/Dropdownlist.vue +103 -0
- package/templates/.claude/skills/frontend-vue/components/ControlMobile/PagingBar.vue +108 -0
- package/templates/.claude/skills/frontend-vue/components/ControlMobile/UploadImage.vue +137 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/AG.vue +806 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/AntTable.vue +253 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/CustomDropdownEditor.vue +43 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/CustomDropdownEditorEnable.vue +55 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/HtmlTable.vue +40 -0
- package/templates/.claude/skills/frontend-vue/components/PDFViewer.vue +25 -0
- package/templates/.claude/skills/frontend-vue/components/Panel/FormView.vue +309 -0
- package/templates/.claude/skills/frontend-vue/components/Partial/Footer.vue +23 -0
- package/templates/.claude/skills/frontend-vue/components/Partial/Header.vue +265 -0
- package/templates/.claude/skills/frontend-vue/components/Partial/Sidebar.vue +122 -0
- package/templates/.claude/skills/frontend-vue/components/Template.vue +16 -0
- package/templates/.claude/skills/frontend-vue/components/View/Form.vue +89 -0
- package/templates/.claude/skills/frontend-vue/composables/indexDBStore.js +140 -0
- package/templates/.claude/skills/frontend-vue/composables/masterApi.js +362 -0
- package/templates/.claude/skills/frontend-vue/composables/state.js +578 -0
- package/templates/.claude/skills/frontend-vue/composables/useRequest.js +221 -0
- package/templates/.claude/skills/frontend-vue/composables/useSession.js +179 -0
- package/templates/.claude/skills/frontend-vue/composables/useTranslation.js +54 -0
- package/templates/.claude/skills/frontend-vue/composables/useWebSocket.js +257 -0
- package/templates/.claude/skills/frontend-vue/composables/userObj.js +111 -0
- package/templates/.claude/skills/frontend-vue/composables/utils.js +322 -0
- package/templates/.claude/skills/frontend-vue/reference/composables-example.vue +320 -0
- package/templates/.claude/skills/frontend-vue/reference/form-example.vue +183 -0
- package/templates/.claude/skills/frontend-vue/reference/grid-example.vue +147 -0
- package/templates/.claude/skills/frontend-vue/reference/masterdata-example/[id].vue +106 -0
- package/templates/.claude/skills/frontend-vue/reference/masterdata-example/index.vue +58 -0
- package/templates/.claude/skills/frontend-vue/reference/popup-example.vue +159 -0
- package/templates/.claude/skills/pdf/LICENSE.txt +30 -0
- package/templates/.claude/skills/pdf/SKILL.md +294 -0
- package/templates/.claude/skills/pdf/forms.md +205 -0
- package/templates/.claude/skills/pdf/reference.md +612 -0
- package/templates/.claude/skills/pdf/scripts/check_bounding_boxes.py +70 -0
- package/templates/.claude/skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
- package/templates/.claude/skills/pdf/scripts/check_fillable_fields.py +12 -0
- package/templates/.claude/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
- package/templates/.claude/skills/pdf/scripts/create_validation_image.py +41 -0
- package/templates/.claude/skills/pdf/scripts/extract_form_field_info.py +152 -0
- package/templates/.claude/skills/pdf/scripts/fill_fillable_fields.py +114 -0
- package/templates/.claude/skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/templates/.claude/skills/pdf-processing/SKILL.md +107 -0
- package/templates/.claude/skills/pdf-processing-pro/FORMS.md +610 -0
- package/templates/.claude/skills/pdf-processing-pro/OCR.md +137 -0
- package/templates/.claude/skills/pdf-processing-pro/SKILL.md +296 -0
- package/templates/.claude/skills/pdf-processing-pro/TABLES.md +626 -0
- package/templates/.claude/skills/pdf-processing-pro/scripts/analyze_form.py +307 -0
- package/templates/.claude/skills/postgres/SKILL.md +69 -0
- package/templates/.claude/skills/postgres/reference/fn_get_examples.sql +208 -0
- package/templates/.claude/skills/postgres/reference/fn_rpt_examples.sql +239 -0
- package/templates/.claude/skills/postgres/reference/utility_functions.sql +94 -0
- package/templates/.claude/skills/pptx/LICENSE.txt +30 -0
- package/templates/.claude/skills/pptx/SKILL.md +484 -0
- package/templates/.claude/skills/pptx/html2pptx.md +625 -0
- package/templates/.claude/skills/pptx/ooxml.md +427 -0
- package/templates/.claude/skills/pptx/scripts/html2pptx.js +979 -0
- package/templates/.claude/skills/pptx/scripts/inventory.py +1020 -0
- package/templates/.claude/skills/pptx/scripts/rearrange.py +231 -0
- package/templates/.claude/skills/pptx/scripts/replace.py +385 -0
- package/templates/.claude/skills/pptx/scripts/thumbnail.py +450 -0
- package/templates/.claude/skills/repo-maintenance/SKILL.md +97 -0
- package/templates/.claude/skills/research/EXAMPLES.md +434 -0
- package/templates/.claude/skills/research/REFERENCE.md +399 -0
- package/templates/.claude/skills/research/SKILL.md +136 -0
- package/templates/.claude/skills/root-cause-tracing/SKILL.md +174 -0
- package/templates/.claude/skills/root-cause-tracing/find-polluter.sh +63 -0
- package/templates/.claude/skills/sharing-skills/SKILL.md +194 -0
- package/templates/.claude/skills/sql-optimization-patterns/SKILL.md +493 -0
- package/templates/.claude/skills/subagent-driven-development/SKILL.md +189 -0
- package/templates/.claude/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/templates/.claude/skills/systematic-debugging/SKILL.md +295 -0
- package/templates/.claude/skills/systematic-debugging/test-academic.md +14 -0
- package/templates/.claude/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/templates/.claude/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/templates/.claude/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/templates/.claude/skills/test-driven-development/SKILL.md +364 -0
- package/templates/.claude/skills/testing-anti-patterns/SKILL.md +302 -0
- package/templates/.claude/skills/testing-quality/SKILL.md +97 -0
- package/templates/.claude/skills/verification-before-completion/SKILL.md +139 -0
- package/templates/.claude/skills/webapp-testing/LICENSE.txt +202 -0
- package/templates/.claude/skills/webapp-testing/SKILL.md +96 -0
- package/templates/.claude/skills/webapp-testing/examples/console_logging.py +35 -0
- package/templates/.claude/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/templates/.claude/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/templates/.claude/skills/webapp-testing/scripts/with_server.py +106 -0
- package/templates/.claude/ukit/index/build-index.mjs +28 -0
- package/templates/.claude/ukit/index/cache-utils.mjs +140 -0
- package/templates/.claude/ukit/index/lib/index-core.mjs +2800 -0
- package/templates/.claude/ukit/index/query-index.mjs +150 -0
- package/templates/.claude/ukit/index/refresh-index.mjs +57 -0
- package/templates/.claude/ukit/index/reset-auto-permissions.mjs +76 -0
- package/templates/.claude/ukit/index/resolve-context.mjs +279 -0
- package/templates/.claude/ukit/index/route-catalog.mjs +258 -0
- package/templates/.claude/ukit/index/route-task.mjs +1994 -0
- package/templates/.claude/ukit/index/triage.mjs +133 -0
- package/templates/.claude/ukit/index/verify-context.mjs +689 -0
- package/templates/.claude/ukit/runtime/compact-threshold.mjs +1013 -0
- package/templates/.claude/ukit/runtime/output-compression.mjs +1340 -0
- package/templates/.claude/ukit/runtime/reinject-context.mjs +874 -0
- package/templates/.claude/ukit/runtime/token-utils.mjs +500 -0
- package/templates/.codex/README.md +83 -0
- package/templates/.codex/settings.json +187 -0
- package/templates/.gitignore +75 -0
- package/templates/AGENTS.md +116 -0
- package/templates/CLAUDE.md +93 -0
- package/templates/adapter-presets/antigravity/README.md +22 -0
- package/templates/adapter-presets/antigravity/rules.md +49 -0
- package/templates/adapter-presets/claude/settings.local.json +42 -0
- package/templates/adapter-presets/codex/settings.local.json +6 -0
- package/templates/adapter-presets/opencode/opencode.template.json +1 -0
- package/templates/docs/BUGFIX.md +20 -0
- package/templates/docs/BUG_INDEX.md +12 -0
- package/templates/docs/BUG_METRICS.md +7 -0
- package/templates/docs/BUG_TEMPLATE.md +13 -0
- package/templates/docs/CODE_MAP.md +35 -0
- package/templates/docs/INSTALL.md +113 -0
- package/templates/docs/MEMORY.md +49 -0
- package/templates/docs/PROJECT.md +50 -0
- package/templates/docs/UKIT_USAGE_GUIDE.md +147 -0
- package/templates/docs/WORKLOG.md +10 -0
- package/templates/ukit/README.md +14 -0
- package/templates/ukit/storage/cache/compact-history.json +3 -0
- package/templates/ukit/storage/cache/compact-pressure.json +1 -0
- package/templates/ukit/storage/cache/output-history.json +3 -0
- package/templates/ukit/storage/cache/prompt-cache.json +3 -0
- package/templates/ukit/storage/config.json +37 -0
- package/templates/ukit/storage/memory/projects/.gitkeep +2 -0
- package/templates/ukit/storage/memory/sessions/.gitkeep +0 -0
- package/templates/ukit/storage/memory/user.json +5 -0
|
@@ -0,0 +1,1994 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import * as indexCore from './lib/index-core.mjs';
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_RECENT_CACHE_MAX_ENTRIES,
|
|
8
|
+
readRecentCacheEntry,
|
|
9
|
+
writeRecentCacheEntry,
|
|
10
|
+
} from './cache-utils.mjs';
|
|
11
|
+
import { ROUTE_CATALOG } from './route-catalog.mjs';
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
resolveContext,
|
|
15
|
+
buildCodeIndex,
|
|
16
|
+
DEFAULT_INDEX_CACHE_MAX_AGE_MS,
|
|
17
|
+
getIndexArtifactGeneratedAt,
|
|
18
|
+
} = indexCore;
|
|
19
|
+
|
|
20
|
+
const MAX_ACTIVE_ROUTE_SKILLS = 2;
|
|
21
|
+
const STOPWORDS = new Set([
|
|
22
|
+
'the', 'a', 'an', 'and', 'or', 'to', 'for', 'of', 'with', 'in', 'on', 'is', 'are',
|
|
23
|
+
'this', 'that', 'it', 'as', 'by', 'be', 'use', 'using', 'implement', 'fix', 'task',
|
|
24
|
+
'cần', 'và', 'là', 'cho', 'một', 'những', 'dùng',
|
|
25
|
+
]);
|
|
26
|
+
const INLINE_SUMMARY_MAX_SEGMENTS = 5;
|
|
27
|
+
const INLINE_SUMMARY_SIGNAL_PATTERNS = [
|
|
28
|
+
/\b(error|fail(?:ed)?|warning|assertionerror|exception|fatal|deprecated|unmet peer|ignored build scripts)\b/i,
|
|
29
|
+
/(^|\s)(src|app|lib|docs|ukit|\.claude|\.codex|tests?)\//i,
|
|
30
|
+
/^\s*FAIL\b/i,
|
|
31
|
+
/^\s*stderr\s*\|/i,
|
|
32
|
+
/^\s*stdout\s*\|/i,
|
|
33
|
+
];
|
|
34
|
+
const INLINE_SUMMARY_TAIL_PATTERNS = [
|
|
35
|
+
/\b(Test Files|Tests|Packages:|Done in|short test summary info)\b/i,
|
|
36
|
+
/\b(Duration|Start at)\b/i,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const buildNormalizedRouteSignalText = typeof indexCore.buildRouteSignalText === 'function'
|
|
40
|
+
? indexCore.buildRouteSignalText
|
|
41
|
+
: (...values) => values
|
|
42
|
+
.map((value) => String(value || '').trim())
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
.join('\n')
|
|
45
|
+
.toLowerCase();
|
|
46
|
+
|
|
47
|
+
async function main() {
|
|
48
|
+
const args = process.argv.slice(2);
|
|
49
|
+
const rootDir = getRootDir(args);
|
|
50
|
+
const sharedStatePath = path.join(rootDir, '.claude', 'ukit', 'skill-router-state.json');
|
|
51
|
+
const routeCachePath = path.join(rootDir, '.claude', 'ukit', 'route-cache.json');
|
|
52
|
+
const previousState = await readJson(sharedStatePath, {});
|
|
53
|
+
const targetFile = readFlagValue(args, '--target');
|
|
54
|
+
const taskType = readFlagValue(args, '--type');
|
|
55
|
+
const commandText = readFlagValue(args, '--tool-command') ?? '';
|
|
56
|
+
const lastPrompt = readFlagValue(args, '--last-prompt')
|
|
57
|
+
?? previousState?.routingContext?.lastExplicitUserPromptText
|
|
58
|
+
?? '';
|
|
59
|
+
const adapter = readFlagValue(args, '--adapter') ?? inferAdapterFromInvocation(process.argv[1] || '');
|
|
60
|
+
const commandNamespace = adapter === 'codex'
|
|
61
|
+
? '.codex'
|
|
62
|
+
: (adapter === 'antigravity' ? '.antigravity' : '.claude');
|
|
63
|
+
const promptText = args
|
|
64
|
+
.filter((arg, index) => !isFlagOrValue(args, index))
|
|
65
|
+
.join(' ')
|
|
66
|
+
.trim();
|
|
67
|
+
|
|
68
|
+
if (!promptText && !commandText && !targetFile) {
|
|
69
|
+
console.error('Usage: node .codex/ukit/index/route-task.mjs "<prompt>" [--tool-command <cmd>] [--target <file>] [--type trivial|simple|non-trivial] [--adapter codex|claude|antigravity|opencode]');
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const preparedRoute = await prepareTaskRoute({
|
|
75
|
+
rootDir,
|
|
76
|
+
promptText,
|
|
77
|
+
commandText,
|
|
78
|
+
targetFile,
|
|
79
|
+
taskType,
|
|
80
|
+
adapter,
|
|
81
|
+
lastExplicitUserPromptText: lastPrompt,
|
|
82
|
+
commandNamespace,
|
|
83
|
+
});
|
|
84
|
+
const useIndexedContext = shouldUseIndexedContext({
|
|
85
|
+
activeSkills: preparedRoute.activeSkills,
|
|
86
|
+
targetFile: preparedRoute.routingContext?.targetFile ?? null,
|
|
87
|
+
});
|
|
88
|
+
const recentOutputSnapshot = shouldIncludeRecentOutput({
|
|
89
|
+
activeSkills: preparedRoute.activeSkills,
|
|
90
|
+
routingContext: preparedRoute?.routingContext ?? {},
|
|
91
|
+
useIndexedContext,
|
|
92
|
+
})
|
|
93
|
+
? await buildRecentOutputSnapshot({
|
|
94
|
+
rootDir,
|
|
95
|
+
})
|
|
96
|
+
: null;
|
|
97
|
+
const previousContextSnapshot = shouldIncludePreviousContext({
|
|
98
|
+
routingContext: preparedRoute?.routingContext ?? {},
|
|
99
|
+
useIndexedContext,
|
|
100
|
+
})
|
|
101
|
+
? await buildPreviousContextSnapshot({
|
|
102
|
+
rootDir,
|
|
103
|
+
routingContext: preparedRoute?.routingContext ?? {},
|
|
104
|
+
})
|
|
105
|
+
: null;
|
|
106
|
+
const indexGeneratedAtMs = useIndexedContext
|
|
107
|
+
? await ensureFreshIndex({
|
|
108
|
+
rootDir,
|
|
109
|
+
logPrefix: 'route',
|
|
110
|
+
})
|
|
111
|
+
: null;
|
|
112
|
+
const projectVerification = useIndexedContext
|
|
113
|
+
? await readProjectVerificationFingerprint(rootDir)
|
|
114
|
+
: null;
|
|
115
|
+
const requestKey = buildRouteRequestKey({
|
|
116
|
+
commandNamespace,
|
|
117
|
+
indexGeneratedAtMs,
|
|
118
|
+
projectFingerprint: projectVerification?.fingerprint ?? null,
|
|
119
|
+
activeSkills: preparedRoute.activeSkills,
|
|
120
|
+
routingContext: preparedRoute.routingContext,
|
|
121
|
+
previousContextSnapshot,
|
|
122
|
+
recentOutputSnapshot,
|
|
123
|
+
});
|
|
124
|
+
const reusableState = reuseSharedRouteState({
|
|
125
|
+
previousState,
|
|
126
|
+
requestKey,
|
|
127
|
+
});
|
|
128
|
+
if (reusableState) {
|
|
129
|
+
printRouteState(reusableState);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const cachedRouteState = reuseSharedRouteState({
|
|
134
|
+
previousState: await readRecentCacheEntry(routeCachePath, requestKey, {
|
|
135
|
+
maxEntries: DEFAULT_RECENT_CACHE_MAX_ENTRIES,
|
|
136
|
+
touch: true,
|
|
137
|
+
}),
|
|
138
|
+
requestKey,
|
|
139
|
+
});
|
|
140
|
+
if (cachedRouteState) {
|
|
141
|
+
const sharedState = {
|
|
142
|
+
...cachedRouteState,
|
|
143
|
+
source: 'task-route',
|
|
144
|
+
ts: Date.now(),
|
|
145
|
+
requestKey,
|
|
146
|
+
};
|
|
147
|
+
await writeJson(sharedStatePath, sharedState);
|
|
148
|
+
printRouteState(sharedState);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const helperCacheSnapshot = await readHelperCacheSnapshot({
|
|
153
|
+
rootDir,
|
|
154
|
+
indexGeneratedAtMs,
|
|
155
|
+
projectFingerprint: projectVerification?.fingerprint ?? '',
|
|
156
|
+
routingContext: preparedRoute.routingContext,
|
|
157
|
+
useIndexedContext,
|
|
158
|
+
});
|
|
159
|
+
const route = await finalizeTaskRoute({
|
|
160
|
+
rootDir,
|
|
161
|
+
commandNamespace,
|
|
162
|
+
preparedRoute,
|
|
163
|
+
projectVerification,
|
|
164
|
+
cachedContextResult: helperCacheSnapshot.contextResult,
|
|
165
|
+
cachedVerificationPlan: helperCacheSnapshot.verificationPlan,
|
|
166
|
+
useIndexedContext,
|
|
167
|
+
previousContextSnapshot,
|
|
168
|
+
recentOutputSnapshot,
|
|
169
|
+
});
|
|
170
|
+
await seedHelperCaches({
|
|
171
|
+
rootDir,
|
|
172
|
+
indexGeneratedAtMs,
|
|
173
|
+
projectFingerprint: projectVerification?.fingerprint ?? '',
|
|
174
|
+
helperCacheSnapshot,
|
|
175
|
+
route,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const sharedState = createSharedRouteState({
|
|
179
|
+
route,
|
|
180
|
+
source: 'task-route',
|
|
181
|
+
requestKey,
|
|
182
|
+
indexGeneratedAtMs,
|
|
183
|
+
});
|
|
184
|
+
await writeJson(sharedStatePath, sharedState);
|
|
185
|
+
await writeRecentCacheEntry(routeCachePath, compactRouteCacheState(sharedState), {
|
|
186
|
+
maxEntries: DEFAULT_RECENT_CACHE_MAX_ENTRIES,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
printRouteState(sharedState);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function ensureFreshIndex({ rootDir, logPrefix }) {
|
|
193
|
+
const lastRefreshMs = await getIndexArtifactGeneratedAt({ rootDir });
|
|
194
|
+
const stale = lastRefreshMs === null
|
|
195
|
+
? true
|
|
196
|
+
: typeof indexCore.isIndexStale === 'function'
|
|
197
|
+
? await indexCore.isIndexStale({
|
|
198
|
+
rootDir,
|
|
199
|
+
maxAgeMs: DEFAULT_INDEX_CACHE_MAX_AGE_MS,
|
|
200
|
+
now: Date.now(),
|
|
201
|
+
generatedAtMs: lastRefreshMs,
|
|
202
|
+
})
|
|
203
|
+
: (Date.now() - lastRefreshMs) > DEFAULT_INDEX_CACHE_MAX_AGE_MS;
|
|
204
|
+
|
|
205
|
+
if (!stale) {
|
|
206
|
+
return lastRefreshMs;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const summary = await buildCodeIndex({ rootDir });
|
|
210
|
+
if (lastRefreshMs !== null) {
|
|
211
|
+
console.log(`[ukit:${logPrefix}] stale index refreshed (last=${new Date(lastRefreshMs).toISOString()})`);
|
|
212
|
+
} else {
|
|
213
|
+
console.log(`[ukit:${logPrefix}] stale index refreshed (no previous cache timestamp)`);
|
|
214
|
+
}
|
|
215
|
+
return Number.isFinite(summary?.generatedAtMs)
|
|
216
|
+
? summary.generatedAtMs
|
|
217
|
+
: getIndexArtifactGeneratedAt({ rootDir });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function prepareTaskRoute({
|
|
221
|
+
rootDir = process.cwd(),
|
|
222
|
+
promptText = '',
|
|
223
|
+
commandText = '',
|
|
224
|
+
targetFile = null,
|
|
225
|
+
taskType = null,
|
|
226
|
+
adapter = 'claude',
|
|
227
|
+
lastExplicitUserPromptText = '',
|
|
228
|
+
} = {}) {
|
|
229
|
+
const absoluteRoot = path.resolve(rootDir);
|
|
230
|
+
const normalizedPrompt = String(promptText || '').trim();
|
|
231
|
+
const normalizedCommand = String(commandText || '').trim();
|
|
232
|
+
const normalizedTarget = normalizeRelativeFile(absoluteRoot, targetFile);
|
|
233
|
+
const activeSkills = await selectActiveSkills({
|
|
234
|
+
rootDir: absoluteRoot,
|
|
235
|
+
promptText: normalizedPrompt,
|
|
236
|
+
commandText: normalizedCommand,
|
|
237
|
+
targetFile: normalizedTarget,
|
|
238
|
+
});
|
|
239
|
+
const selectedIds = activeSkills.map((entry) => entry.id);
|
|
240
|
+
const contextIntent = deriveContextIntent({
|
|
241
|
+
promptText: normalizedPrompt,
|
|
242
|
+
commandText: normalizedCommand,
|
|
243
|
+
targetFile: normalizedTarget,
|
|
244
|
+
selectedIds,
|
|
245
|
+
});
|
|
246
|
+
const inferredTaskType = taskType ?? inferTaskType({
|
|
247
|
+
promptText: normalizedPrompt,
|
|
248
|
+
commandText: normalizedCommand,
|
|
249
|
+
selectedIds,
|
|
250
|
+
});
|
|
251
|
+
const preservedPrompt = normalizedPrompt || String(lastExplicitUserPromptText || '').trim();
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
rootDir: absoluteRoot,
|
|
255
|
+
activeSkills,
|
|
256
|
+
routingContext: {
|
|
257
|
+
adapter: String(adapter || 'claude').trim() || 'claude',
|
|
258
|
+
promptText: normalizedPrompt,
|
|
259
|
+
lastExplicitUserPromptText: preservedPrompt,
|
|
260
|
+
commandText: normalizedCommand,
|
|
261
|
+
targetFile: normalizedTarget,
|
|
262
|
+
contextIntent,
|
|
263
|
+
taskType: inferredTaskType,
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function finalizeTaskRoute({
|
|
269
|
+
rootDir = process.cwd(),
|
|
270
|
+
commandNamespace = '.claude',
|
|
271
|
+
preparedRoute,
|
|
272
|
+
projectVerification = null,
|
|
273
|
+
cachedContextResult = null,
|
|
274
|
+
cachedVerificationPlan = null,
|
|
275
|
+
useIndexedContext = true,
|
|
276
|
+
previousContextSnapshot = null,
|
|
277
|
+
recentOutputSnapshot = null,
|
|
278
|
+
} = {}) {
|
|
279
|
+
const absoluteRoot = path.resolve(rootDir);
|
|
280
|
+
const activeSkills = preparedRoute?.activeSkills ?? [];
|
|
281
|
+
const routingContext = preparedRoute?.routingContext ?? {};
|
|
282
|
+
const selectedIds = activeSkills.map((entry) => entry.id);
|
|
283
|
+
const {
|
|
284
|
+
contextIntent,
|
|
285
|
+
targetFile: normalizedTarget,
|
|
286
|
+
taskType: inferredTaskType,
|
|
287
|
+
} = routingContext;
|
|
288
|
+
const contextResult = useIndexedContext
|
|
289
|
+
? cachedContextResult ?? (
|
|
290
|
+
contextIntent || normalizedTarget
|
|
291
|
+
? await resolveContext({
|
|
292
|
+
rootDir: absoluteRoot,
|
|
293
|
+
intent: contextIntent,
|
|
294
|
+
targetFile: normalizedTarget,
|
|
295
|
+
taskType: inferredTaskType,
|
|
296
|
+
})
|
|
297
|
+
: null
|
|
298
|
+
)
|
|
299
|
+
: null;
|
|
300
|
+
const enrichedContextResult = expandRouteContext(contextResult);
|
|
301
|
+
const verificationSnapshot = useIndexedContext
|
|
302
|
+
? projectVerification ?? await readProjectVerificationFingerprint(absoluteRoot)
|
|
303
|
+
: null;
|
|
304
|
+
const verificationPlan = useIndexedContext
|
|
305
|
+
? cachedVerificationPlan ?? await deriveVerificationPlan({
|
|
306
|
+
rootDir: absoluteRoot,
|
|
307
|
+
intent: contextIntent,
|
|
308
|
+
targetFile: normalizedTarget,
|
|
309
|
+
taskType: inferredTaskType,
|
|
310
|
+
contextResult: enrichedContextResult,
|
|
311
|
+
skillIds: selectedIds,
|
|
312
|
+
fingerprintEntries: verificationSnapshot.entries,
|
|
313
|
+
pkg: verificationSnapshot.pkg,
|
|
314
|
+
})
|
|
315
|
+
: null;
|
|
316
|
+
const contextCommand = buildHelperCommand({ commandNamespace, scriptName: 'resolve-context.mjs', intent: contextIntent, targetFile: normalizedTarget, taskType: inferredTaskType });
|
|
317
|
+
const verifyCommand = buildHelperCommand({ commandNamespace, scriptName: 'verify-context.mjs', intent: contextIntent, targetFile: normalizedTarget, taskType: inferredTaskType });
|
|
318
|
+
const contextRecommendation = useIndexedContext
|
|
319
|
+
? {
|
|
320
|
+
command: contextCommand,
|
|
321
|
+
preview: enrichedContextResult ? summarizeContext(enrichedContextResult) : null,
|
|
322
|
+
}
|
|
323
|
+
: null;
|
|
324
|
+
const verificationRecommendation = verificationPlan
|
|
325
|
+
? {
|
|
326
|
+
...verificationPlan,
|
|
327
|
+
helperCommand: verifyCommand,
|
|
328
|
+
}
|
|
329
|
+
: null;
|
|
330
|
+
const nextAction = deriveNextAction({
|
|
331
|
+
activeSkills,
|
|
332
|
+
contextRecommendation,
|
|
333
|
+
verificationRecommendation,
|
|
334
|
+
});
|
|
335
|
+
const routeSummary = buildRouteSummary({
|
|
336
|
+
activeSkills,
|
|
337
|
+
routingContext,
|
|
338
|
+
contextRecommendation,
|
|
339
|
+
verificationRecommendation,
|
|
340
|
+
nextAction,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
activeSkills,
|
|
345
|
+
routingContext,
|
|
346
|
+
previousContext: previousContextSnapshot,
|
|
347
|
+
recentOutput: recentOutputSnapshot,
|
|
348
|
+
contextRecommendation,
|
|
349
|
+
verificationRecommendation,
|
|
350
|
+
resolvedContextResult: contextResult,
|
|
351
|
+
resolvedVerificationPlan: verificationPlan,
|
|
352
|
+
nextAction,
|
|
353
|
+
routeSummary,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function printRouteState(state) {
|
|
358
|
+
const compactSummary = formatDisplayRouteSummary(state.routeSummary, state.routingContext);
|
|
359
|
+
const nextDisplay = formatDisplayNextAction(state.routeSummary);
|
|
360
|
+
const previousContextDisplay = formatDisplayPreviousContext(state.previousContext);
|
|
361
|
+
const recentOutputDisplay = formatDisplayRecentOutput(state.recentOutput);
|
|
362
|
+
const helperDisplay = formatDisplayHelperHint(state.routeSummary, compactSummary);
|
|
363
|
+
console.log('[ukit:route]');
|
|
364
|
+
console.log(`skills: ${(state.activeSkills ?? []).map((item) => item.id).join(', ') || 'none'}`);
|
|
365
|
+
if (previousContextDisplay) {
|
|
366
|
+
console.log(`previous-context: ${previousContextDisplay}`);
|
|
367
|
+
}
|
|
368
|
+
if (recentOutputDisplay) {
|
|
369
|
+
console.log(`recent-output: ${recentOutputDisplay}`);
|
|
370
|
+
}
|
|
371
|
+
console.log(`summary: ${compactSummary}`);
|
|
372
|
+
if (nextDisplay) {
|
|
373
|
+
console.log(`next: ${nextDisplay}`);
|
|
374
|
+
}
|
|
375
|
+
if (state.routeSummary?.delegateHint) {
|
|
376
|
+
console.log(`delegate: ${state.routeSummary.delegateHint}`);
|
|
377
|
+
}
|
|
378
|
+
if (helperDisplay) {
|
|
379
|
+
console.log(`helper: ${helperDisplay}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function hasRouteSummaryField(routeSummary, field) {
|
|
384
|
+
return Boolean(
|
|
385
|
+
routeSummary
|
|
386
|
+
&& typeof routeSummary === 'object'
|
|
387
|
+
&& Object.prototype.hasOwnProperty.call(routeSummary, field),
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function extractRouteLineSegment(line, label) {
|
|
392
|
+
if (typeof line !== 'string' || !line.trim()) {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const match = line.match(new RegExp(`(?:^|\\|\\s*)${label}=([^|]+)`, 'i'));
|
|
397
|
+
return match ? `${label}=${String(match[1] || '').trim()}` : null;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function extractRouteLineValue(line, label) {
|
|
401
|
+
const segment = extractRouteLineSegment(line, label);
|
|
402
|
+
return segment ? String(segment.slice(label.length + 1) || '').trim() : '';
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function formatDisplayRouteSummary(routeSummary = null, routingContext = {}) {
|
|
406
|
+
const line = String(routeSummary?.line ?? '').trim();
|
|
407
|
+
const taskSegment = routingContext?.taskType
|
|
408
|
+
? `task=${routingContext.taskType}`
|
|
409
|
+
: extractRouteLineSegment(line, 'task');
|
|
410
|
+
const policySegment = routeSummary?.policyMode
|
|
411
|
+
? `policy=${routeSummary.policyMode}`
|
|
412
|
+
: (
|
|
413
|
+
hasRouteSummaryField(routeSummary, 'policyMode')
|
|
414
|
+
? null
|
|
415
|
+
: extractRouteLineSegment(line, 'policy')
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
const segments = [
|
|
419
|
+
taskSegment,
|
|
420
|
+
extractRouteLineSegment(line, 'targets'),
|
|
421
|
+
extractRouteLineSegment(line, 'tests'),
|
|
422
|
+
extractRouteLineSegment(line, 'styles'),
|
|
423
|
+
policySegment,
|
|
424
|
+
].filter(Boolean);
|
|
425
|
+
|
|
426
|
+
return segments.join(' | ') || 'none';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function formatDisplayNextAction(routeSummary = null) {
|
|
430
|
+
if (!routeSummary || typeof routeSummary !== 'object') {
|
|
431
|
+
return '';
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const nextActionCommand = String(routeSummary.nextActionCommand ?? '').trim();
|
|
435
|
+
if (nextActionCommand) {
|
|
436
|
+
return nextActionCommand;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const nextActionType = String(routeSummary.nextActionType ?? '').trim();
|
|
440
|
+
if (!nextActionType || nextActionType === 'read-skill-instructions') {
|
|
441
|
+
return '';
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return nextActionType;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function formatDisplayPreviousContext(previousContext = null) {
|
|
448
|
+
const line = typeof previousContext?.line === 'string' ? previousContext.line.trim() : '';
|
|
449
|
+
if (!line) {
|
|
450
|
+
return '';
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return line
|
|
454
|
+
.split(/\s+\|\s+/)
|
|
455
|
+
.map((segment) => String(segment || '').trim())
|
|
456
|
+
.filter(Boolean)
|
|
457
|
+
.map((segment) => segment
|
|
458
|
+
.replace(/^\[(project|session|user)\]\s*/i, (_, label) => `${String(label || '').toLowerCase()}=`)
|
|
459
|
+
.replace(/\s+—\s+/g, '; ')
|
|
460
|
+
.replace(/\b(decisions|rules|outcome|actions|next):\s*/gi, '$1=')
|
|
461
|
+
.replace(/\(\+(\d+)\s+more\)/gi, '+$1 more'))
|
|
462
|
+
.join(' | ');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function formatDisplayRecentOutput(recentOutput = null) {
|
|
466
|
+
const line = typeof recentOutput?.line === 'string' ? recentOutput.line.trim() : '';
|
|
467
|
+
if (!line) {
|
|
468
|
+
return '';
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return line
|
|
472
|
+
.replace(/^-\s*/, '')
|
|
473
|
+
.replace(/^Recent command:\s*/i, 'cmd=')
|
|
474
|
+
.replace(/\s+\|\s+-\s+/g, ' | ')
|
|
475
|
+
.trim();
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function formatDisplayHelperHint(routeSummary = null, compactSummary = '') {
|
|
479
|
+
if (!routeSummary || typeof routeSummary !== 'object') {
|
|
480
|
+
return '';
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (String(routeSummary.nextActionCommand ?? '').trim()) {
|
|
484
|
+
return '';
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
let helperHint = String(routeSummary.helperHint ?? '').trim().replace(/\s+/g, ' ');
|
|
488
|
+
if (!helperHint) {
|
|
489
|
+
return '';
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const helperTargetMatch = helperHint.match(/(?:^|\s)target=([^\s]+)/);
|
|
493
|
+
const summaryTargets = extractRouteLineValue(compactSummary, 'targets');
|
|
494
|
+
if (helperTargetMatch && summaryTargets && summaryTargets === String(helperTargetMatch[1] || '').trim()) {
|
|
495
|
+
helperHint = helperHint.replace(` target=${helperTargetMatch[1]}`, '');
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return helperHint.trim();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function seedHelperCaches({
|
|
502
|
+
rootDir = process.cwd(),
|
|
503
|
+
indexGeneratedAtMs = null,
|
|
504
|
+
projectFingerprint = '',
|
|
505
|
+
helperCacheSnapshot = null,
|
|
506
|
+
route = null,
|
|
507
|
+
} = {}) {
|
|
508
|
+
const absoluteRoot = path.resolve(rootDir);
|
|
509
|
+
const routingContext = route?.routingContext ?? {};
|
|
510
|
+
const normalizedIntent = String(routingContext.contextIntent ?? '').trim();
|
|
511
|
+
const normalizedTargetFile = String(routingContext.targetFile ?? '').trim();
|
|
512
|
+
const normalizedTaskType = String(routingContext.taskType ?? '').trim();
|
|
513
|
+
|
|
514
|
+
if (!normalizedIntent && !normalizedTargetFile) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const updatedAt = Date.now();
|
|
519
|
+
const resolveContextResult = route?.resolvedContextResult ?? null;
|
|
520
|
+
const verificationPlan = route?.resolvedVerificationPlan ?? null;
|
|
521
|
+
const contextCacheHit = Boolean(helperCacheSnapshot?.contextCacheHit);
|
|
522
|
+
const verificationCacheHit = Boolean(helperCacheSnapshot?.verificationCacheHit);
|
|
523
|
+
|
|
524
|
+
if (resolveContextResult && !contextCacheHit) {
|
|
525
|
+
const resolveContextCachePath = path.join(absoluteRoot, '.claude', 'ukit', 'resolve-context-cache.json');
|
|
526
|
+
await writeRecentCacheEntry(resolveContextCachePath, {
|
|
527
|
+
requestKey: buildResolveContextRequestKey({
|
|
528
|
+
indexGeneratedAtMs,
|
|
529
|
+
intent: normalizedIntent,
|
|
530
|
+
targetFile: normalizedTargetFile,
|
|
531
|
+
taskType: normalizedTaskType,
|
|
532
|
+
}),
|
|
533
|
+
updatedAt,
|
|
534
|
+
result: resolveContextResult,
|
|
535
|
+
}, {
|
|
536
|
+
maxEntries: DEFAULT_RECENT_CACHE_MAX_ENTRIES,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (verificationPlan && !verificationCacheHit) {
|
|
541
|
+
const verifyContextCachePath = path.join(absoluteRoot, '.claude', 'ukit', 'verify-context-cache.json');
|
|
542
|
+
await writeRecentCacheEntry(verifyContextCachePath, {
|
|
543
|
+
requestKey: buildVerifyContextRequestKey({
|
|
544
|
+
indexGeneratedAtMs,
|
|
545
|
+
projectFingerprint,
|
|
546
|
+
intent: normalizedIntent,
|
|
547
|
+
targetFile: normalizedTargetFile,
|
|
548
|
+
taskType: normalizedTaskType,
|
|
549
|
+
}),
|
|
550
|
+
updatedAt,
|
|
551
|
+
result: verificationPlan,
|
|
552
|
+
}, {
|
|
553
|
+
maxEntries: DEFAULT_RECENT_CACHE_MAX_ENTRIES,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async function readHelperCacheSnapshot({
|
|
559
|
+
rootDir = process.cwd(),
|
|
560
|
+
indexGeneratedAtMs = null,
|
|
561
|
+
projectFingerprint = '',
|
|
562
|
+
routingContext = {},
|
|
563
|
+
useIndexedContext = true,
|
|
564
|
+
} = {}) {
|
|
565
|
+
if (!useIndexedContext) {
|
|
566
|
+
return {
|
|
567
|
+
contextCacheHit: false,
|
|
568
|
+
verificationCacheHit: false,
|
|
569
|
+
contextResult: null,
|
|
570
|
+
verificationPlan: null,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const absoluteRoot = path.resolve(rootDir);
|
|
575
|
+
const normalizedIntent = String(routingContext.contextIntent ?? '').trim();
|
|
576
|
+
const normalizedTargetFile = String(routingContext.targetFile ?? '').trim();
|
|
577
|
+
const normalizedTaskType = String(routingContext.taskType ?? '').trim();
|
|
578
|
+
|
|
579
|
+
if (!normalizedIntent && !normalizedTargetFile) {
|
|
580
|
+
return {
|
|
581
|
+
contextCacheHit: false,
|
|
582
|
+
verificationCacheHit: false,
|
|
583
|
+
contextResult: null,
|
|
584
|
+
verificationPlan: null,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const resolveContextCachePath = path.join(absoluteRoot, '.claude', 'ukit', 'resolve-context-cache.json');
|
|
589
|
+
const verifyContextCachePath = path.join(absoluteRoot, '.claude', 'ukit', 'verify-context-cache.json');
|
|
590
|
+
const [cachedContextEntry, cachedVerificationEntry] = await Promise.all([
|
|
591
|
+
readRecentCacheEntry(
|
|
592
|
+
resolveContextCachePath,
|
|
593
|
+
buildResolveContextRequestKey({
|
|
594
|
+
indexGeneratedAtMs,
|
|
595
|
+
intent: normalizedIntent,
|
|
596
|
+
targetFile: normalizedTargetFile,
|
|
597
|
+
taskType: normalizedTaskType,
|
|
598
|
+
}),
|
|
599
|
+
{
|
|
600
|
+
maxEntries: DEFAULT_RECENT_CACHE_MAX_ENTRIES,
|
|
601
|
+
},
|
|
602
|
+
),
|
|
603
|
+
readRecentCacheEntry(
|
|
604
|
+
verifyContextCachePath,
|
|
605
|
+
buildVerifyContextRequestKey({
|
|
606
|
+
indexGeneratedAtMs,
|
|
607
|
+
projectFingerprint,
|
|
608
|
+
intent: normalizedIntent,
|
|
609
|
+
targetFile: normalizedTargetFile,
|
|
610
|
+
taskType: normalizedTaskType,
|
|
611
|
+
}),
|
|
612
|
+
{
|
|
613
|
+
maxEntries: DEFAULT_RECENT_CACHE_MAX_ENTRIES,
|
|
614
|
+
},
|
|
615
|
+
),
|
|
616
|
+
]);
|
|
617
|
+
|
|
618
|
+
return {
|
|
619
|
+
contextCacheHit: Boolean(cachedContextEntry?.result),
|
|
620
|
+
verificationCacheHit: Boolean(cachedVerificationEntry?.result),
|
|
621
|
+
contextResult: cachedContextEntry?.result ?? null,
|
|
622
|
+
verificationPlan: cachedVerificationEntry?.result ?? null,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
async function selectActiveSkills({ rootDir, promptText, commandText, targetFile }) {
|
|
627
|
+
const routeSignals = {
|
|
628
|
+
promptRawText: String(promptText || '').toLowerCase(),
|
|
629
|
+
promptNormalizedText: buildNormalizedRouteSignalText(promptText),
|
|
630
|
+
commandRawText: String(commandText || '').toLowerCase(),
|
|
631
|
+
commandNormalizedText: buildNormalizedRouteSignalText(commandText),
|
|
632
|
+
fileText: String(targetFile || '').toLowerCase(),
|
|
633
|
+
};
|
|
634
|
+
const scoredEntries = ROUTE_CATALOG
|
|
635
|
+
.map((entry) => scoreSkillRouteEntry(entry, routeSignals))
|
|
636
|
+
.filter((entry) => entry.score > 0)
|
|
637
|
+
.sort((a, b) => b.score - a.score || a.order - b.order);
|
|
638
|
+
const active = [];
|
|
639
|
+
|
|
640
|
+
for (const entry of scoredEntries) {
|
|
641
|
+
if (await pathExists(path.join(rootDir, entry.path))) {
|
|
642
|
+
active.push(entry);
|
|
643
|
+
if (active.length >= MAX_ACTIVE_ROUTE_SKILLS) {
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
return active.map(({ order, ...rest }) => rest);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function scoreSkillRouteEntry(entry, routeSignals = {}) {
|
|
653
|
+
let score = 0;
|
|
654
|
+
const reasons = [];
|
|
655
|
+
|
|
656
|
+
for (const signal of entry.signals) {
|
|
657
|
+
const { directText, normalizedText } = getSignalTexts(signal.type, routeSignals);
|
|
658
|
+
signal.regex.lastIndex = 0;
|
|
659
|
+
const directMatch = signal.regex.test(directText);
|
|
660
|
+
signal.regex.lastIndex = 0;
|
|
661
|
+
const normalizedMatch = !directMatch && Boolean(normalizedText) && signal.regex.test(normalizedText);
|
|
662
|
+
if (!directMatch && !normalizedMatch) continue;
|
|
663
|
+
score += directMatch
|
|
664
|
+
? signal.score
|
|
665
|
+
: Math.max(1, signal.score - 1);
|
|
666
|
+
reasons.push(signal.type);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
score += deriveSkillPriorityBoost(entry.id, routeSignals);
|
|
670
|
+
|
|
671
|
+
return {
|
|
672
|
+
id: entry.id,
|
|
673
|
+
path: entry.path,
|
|
674
|
+
contextMode: entry.contextMode ?? 'indexed',
|
|
675
|
+
reasons: [...new Set(reasons)],
|
|
676
|
+
score,
|
|
677
|
+
order: entry.order,
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function deriveSkillPriorityBoost(skillId, routeSignals = {}) {
|
|
682
|
+
const combinedPromptSignals = [
|
|
683
|
+
routeSignals.promptRawText ?? '',
|
|
684
|
+
routeSignals.promptNormalizedText ?? '',
|
|
685
|
+
routeSignals.commandRawText ?? '',
|
|
686
|
+
routeSignals.commandNormalizedText ?? '',
|
|
687
|
+
].join('\n');
|
|
688
|
+
|
|
689
|
+
if (
|
|
690
|
+
skillId === 'testing-quality'
|
|
691
|
+
&& /\b(test|tests|spec|coverage|mock|fixture|assert|vitest|jest|playwright)\b/.test(combinedPromptSignals)
|
|
692
|
+
) {
|
|
693
|
+
return 1;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return 0;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function getSignalTexts(signalType, routeSignals = {}) {
|
|
700
|
+
if (signalType === 'command') {
|
|
701
|
+
return {
|
|
702
|
+
directText: routeSignals.commandRawText ?? '',
|
|
703
|
+
normalizedText: routeSignals.commandNormalizedText ?? '',
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (signalType === 'file') {
|
|
708
|
+
return {
|
|
709
|
+
directText: routeSignals.fileText ?? '',
|
|
710
|
+
normalizedText: '',
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
return {
|
|
715
|
+
directText: routeSignals.promptRawText ?? '',
|
|
716
|
+
normalizedText: routeSignals.promptNormalizedText ?? '',
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function shouldUseIndexedContext({ activeSkills = [], targetFile = null } = {}) {
|
|
721
|
+
if (activeSkills.length === 0) {
|
|
722
|
+
return Boolean(targetFile);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const primarySkill = activeSkills[0] ?? null;
|
|
726
|
+
const primaryIsStandalone = primarySkill?.contextMode === 'standalone';
|
|
727
|
+
if (!primaryIsStandalone) {
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
if (isStandaloneNonIndexedTarget(primarySkill, targetFile)) {
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (activeSkills.some((skill) => skill?.contextMode !== 'standalone')) {
|
|
736
|
+
return true;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return isCodeIndexedTarget(targetFile);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function isStandaloneNonIndexedTarget(primarySkill, targetFile) {
|
|
743
|
+
if (typeof targetFile !== 'string' || !targetFile.trim()) {
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const normalized = targetFile.replaceAll('\\', '/');
|
|
748
|
+
if (primarySkill?.id === 'docker-packaging') {
|
|
749
|
+
return /(?:^|\/)(?:Dockerfile|docker-compose(?:\.ya?ml)?|compose\.ya?ml|\.dockerignore)$/i.test(normalized);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function isCodeIndexedTarget(filePath) {
|
|
756
|
+
if (typeof filePath !== 'string' || !filePath.trim()) {
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const normalized = filePath.replaceAll('\\', '/');
|
|
761
|
+
if (/\.(?:[cm]?js|jsx|tsx?|vue|py|rb|go|rs|java|kt|swift|php|cs|c|cc|cpp|cxx|h|hpp|m|mm|scala|sh|bash|zsh|sql|psql|css|scss|sass|less|html|xml|json|ya?ml|toml)$/i.test(normalized)) {
|
|
762
|
+
return true;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return /(?:^|\/)(?:src|test|tests|app|lib|components|pages|screens|layouts|composables|api|routes?|controllers?|services?|db|database|migrations?|prisma|SQL)\//i.test(normalized);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
async function deriveVerificationPlan({
|
|
769
|
+
rootDir = process.cwd(),
|
|
770
|
+
intent = '',
|
|
771
|
+
targetFile = null,
|
|
772
|
+
taskType = null,
|
|
773
|
+
contextResult = null,
|
|
774
|
+
skillIds = [],
|
|
775
|
+
fingerprintEntries = [],
|
|
776
|
+
pkg = null,
|
|
777
|
+
} = {}) {
|
|
778
|
+
const absoluteRoot = path.resolve(rootDir);
|
|
779
|
+
const context = contextResult ?? await resolveContext({
|
|
780
|
+
rootDir: absoluteRoot,
|
|
781
|
+
intent,
|
|
782
|
+
targetFile,
|
|
783
|
+
taskType,
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
const resolvedPkg = pkg ?? await readPackageJson(absoluteRoot);
|
|
787
|
+
const scripts = resolvedPkg?.scripts && typeof resolvedPkg.scripts === 'object' ? resolvedPkg.scripts : {};
|
|
788
|
+
const packageManager = detectPackageManager({ fingerprintEntries, pkg: resolvedPkg });
|
|
789
|
+
const primaryTargets = unique([
|
|
790
|
+
...(context?.primaryTargets ?? []),
|
|
791
|
+
...(targetFile ? [targetFile] : []),
|
|
792
|
+
]);
|
|
793
|
+
const relatedTests = unique([
|
|
794
|
+
...(context?.relatedTests ?? []),
|
|
795
|
+
...(targetFile && /\.(test|spec)\./.test(targetFile) ? [targetFile] : []),
|
|
796
|
+
]);
|
|
797
|
+
const commands = [];
|
|
798
|
+
const fallbackCommands = [];
|
|
799
|
+
const reasons = [];
|
|
800
|
+
const notes = [];
|
|
801
|
+
const docsOnly = primaryTargets.length > 0
|
|
802
|
+
&& primaryTargets.every((filePath) => filePath.startsWith('docs/'));
|
|
803
|
+
const risky = context?.taskType === 'non-trivial'
|
|
804
|
+
|| skillIds.some((skillId) => ['discover-security', 'repo-maintenance'].includes(skillId));
|
|
805
|
+
|
|
806
|
+
let mode = 'minimal';
|
|
807
|
+
|
|
808
|
+
if (docsOnly) {
|
|
809
|
+
mode = 'docs-only';
|
|
810
|
+
reasons.push('docsOnly');
|
|
811
|
+
if (scripts.lint) {
|
|
812
|
+
fallbackCommands.push(buildScriptCommand(packageManager, 'lint'));
|
|
813
|
+
}
|
|
814
|
+
notes.push('Docs-focused change: prefer docs syntax/format verification if available; broad test runs are not the default.');
|
|
815
|
+
} else {
|
|
816
|
+
if (relatedTests.length > 0) {
|
|
817
|
+
mode = 'targeted-tests-first';
|
|
818
|
+
reasons.push('relatedTests');
|
|
819
|
+
if (scripts.test) {
|
|
820
|
+
for (const testFile of relatedTests.slice(0, 2)) {
|
|
821
|
+
commands.push(buildScriptCommand(packageManager, 'test', [testFile]));
|
|
822
|
+
}
|
|
823
|
+
} else {
|
|
824
|
+
notes.push(`Run related tests first: ${relatedTests.slice(0, 2).join(', ')}`);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (risky) {
|
|
829
|
+
reasons.push('riskySkillOrTask');
|
|
830
|
+
if (scripts.test) {
|
|
831
|
+
fallbackCommands.push(buildScriptCommand(packageManager, 'test'));
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (scripts.lint) {
|
|
836
|
+
fallbackCommands.push(buildScriptCommand(packageManager, 'lint'));
|
|
837
|
+
}
|
|
838
|
+
if (scripts.typecheck) {
|
|
839
|
+
fallbackCommands.push(buildScriptCommand(packageManager, 'typecheck'));
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (commands.length === 0 && scripts.test && risky) {
|
|
843
|
+
mode = 'broader-verification';
|
|
844
|
+
reasons.push('highRiskFallback');
|
|
845
|
+
commands.push(buildScriptCommand(packageManager, 'test'));
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
if (commands.length === 0 && fallbackCommands.length === 0) {
|
|
850
|
+
notes.push('Match verification effort to scope; no project test/lint/typecheck scripts were detected.');
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const summarizedContext = summarizeContext(context);
|
|
854
|
+
const primaryCommandList = unique(commands);
|
|
855
|
+
const fallbackCommandList = unique(fallbackCommands.filter((command) => !primaryCommandList.includes(command)));
|
|
856
|
+
const reasonList = unique(reasons);
|
|
857
|
+
|
|
858
|
+
return {
|
|
859
|
+
taskType: context?.taskType ?? taskType ?? 'simple',
|
|
860
|
+
packageManager,
|
|
861
|
+
mode,
|
|
862
|
+
commands: primaryCommandList,
|
|
863
|
+
fallbackCommands: fallbackCommandList,
|
|
864
|
+
reasons: reasonList,
|
|
865
|
+
notes: unique(notes),
|
|
866
|
+
executionPolicy: deriveExecutionPolicy({
|
|
867
|
+
taskType: context?.taskType ?? taskType ?? 'simple',
|
|
868
|
+
mode,
|
|
869
|
+
commands: primaryCommandList,
|
|
870
|
+
fallbackCommands: fallbackCommandList,
|
|
871
|
+
reasons: reasonList,
|
|
872
|
+
context: summarizedContext,
|
|
873
|
+
}),
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function summarizeContext(contextResult) {
|
|
878
|
+
return {
|
|
879
|
+
primaryTargets: contextResult?.primaryTargets ?? [],
|
|
880
|
+
analogFiles: contextResult?.analogFiles ?? [],
|
|
881
|
+
sharedAbstractions: contextResult?.sharedAbstractions ?? [],
|
|
882
|
+
relatedTests: contextResult?.relatedTests ?? [],
|
|
883
|
+
styleFiles: contextResult?.styleFiles ?? [],
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function expandRouteContext(contextResult) {
|
|
888
|
+
if (!contextResult) return contextResult;
|
|
889
|
+
const directTestTargets = (contextResult.primaryTargets ?? []).filter(isTestLikeFile);
|
|
890
|
+
if (directTestTargets.length === 0) return contextResult;
|
|
891
|
+
return {
|
|
892
|
+
...contextResult,
|
|
893
|
+
relatedTests: unique([
|
|
894
|
+
...(contextResult.relatedTests ?? []),
|
|
895
|
+
...directTestTargets,
|
|
896
|
+
]),
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function deriveNextAction({ activeSkills = [], contextRecommendation = null, verificationRecommendation = null }) {
|
|
901
|
+
const policy = verificationRecommendation?.executionPolicy ?? null;
|
|
902
|
+
const primaryCommands = verificationRecommendation?.commands ?? [];
|
|
903
|
+
const fallbackCommands = verificationRecommendation?.fallbackCommands ?? [];
|
|
904
|
+
|
|
905
|
+
if (policy?.requiresHumanConfirmationForBroadOrRisky) {
|
|
906
|
+
return {
|
|
907
|
+
type: 'ask-user-confirmation',
|
|
908
|
+
reason: 'Ask before broad verification because index evidence could not localize a safe targeted lane.',
|
|
909
|
+
followUpCommands: [...primaryCommands, ...fallbackCommands],
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (policy?.shouldAutoRunPrimaryCommands && primaryCommands.length > 0) {
|
|
914
|
+
return {
|
|
915
|
+
type: 'run-primary-verification',
|
|
916
|
+
command: primaryCommands[0],
|
|
917
|
+
reason: 'Targeted related verification is available from indexed context.',
|
|
918
|
+
followUpCommands: fallbackCommands,
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if (policy?.shouldAutoRunFallbacks && fallbackCommands.length > 0) {
|
|
923
|
+
return {
|
|
924
|
+
type: 'run-fallback-verification',
|
|
925
|
+
command: fallbackCommands[0],
|
|
926
|
+
reason: 'No primary verification command was auto-runnable, but routed fallbacks are allowed.',
|
|
927
|
+
followUpCommands: fallbackCommands.slice(1),
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (contextRecommendation?.command) {
|
|
932
|
+
return {
|
|
933
|
+
type: 'pull-indexed-context',
|
|
934
|
+
command: contextRecommendation.command,
|
|
935
|
+
reason: 'Resolve the minimal indexed file bundle before widening reads.',
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if (activeSkills.length > 0) {
|
|
940
|
+
return {
|
|
941
|
+
type: 'read-skill-instructions',
|
|
942
|
+
skillPaths: activeSkills.map((item) => item.path),
|
|
943
|
+
reason: 'Open the matching project-local skill before acting.',
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
return {
|
|
948
|
+
type: 'inspect-structure',
|
|
949
|
+
reason: 'No strong route found; inspect repository structure and indexed files first.',
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function deriveDelegationRecommendation({
|
|
954
|
+
activeSkills = [],
|
|
955
|
+
routingContext = {},
|
|
956
|
+
contextRecommendation = null,
|
|
957
|
+
verificationRecommendation = null,
|
|
958
|
+
} = {}) {
|
|
959
|
+
const skillIds = activeSkills.map((item) => item.id);
|
|
960
|
+
const lower = `${routingContext.promptText ?? ''}\n${routingContext.commandText ?? ''}`.toLowerCase();
|
|
961
|
+
const preview = contextRecommendation?.preview ?? {};
|
|
962
|
+
const contextBreadth = countDelegationContextBreadth(preview);
|
|
963
|
+
const hasExplicitTarget = Boolean(routingContext.targetFile);
|
|
964
|
+
const localizedIndexedLane = hasExplicitTarget && contextBreadth > 0 && contextBreadth <= 3;
|
|
965
|
+
const hasRelatedTests = (preview.relatedTests ?? []).length > 0;
|
|
966
|
+
const when = contextRecommendation?.command ? 'after-context' : 'now';
|
|
967
|
+
|
|
968
|
+
if (routingContext.taskType === 'trivial') {
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
if (
|
|
973
|
+
skillIds.includes('executing-plans')
|
|
974
|
+
|| /\b(execute this plan|follow this plan|implementation plan|rollout plan|controlled batches?|review checkpoints?|batch execution|execute in batches)\b/.test(lower)
|
|
975
|
+
) {
|
|
976
|
+
return {
|
|
977
|
+
hint: 'subagent-driven-development',
|
|
978
|
+
when,
|
|
979
|
+
reason: 'Explicit plan/batch execution is separable enough for deliberate subagent passes.',
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const noisyDebugLane = skillIds.includes('debugging-toolkit') && (
|
|
984
|
+
Boolean(String(routingContext.commandText ?? '').trim())
|
|
985
|
+
|| /\b(debug|error|crash|stack(?: trace)?|failing|flake|flaky|timeout|triage|root cause)\b/.test(lower)
|
|
986
|
+
|| verificationRecommendation?.executionPolicy?.policyMode === 'confirm-then-broad'
|
|
987
|
+
);
|
|
988
|
+
if (noisyDebugLane && (!localizedIndexedLane || !hasRelatedTests || contextBreadth >= 4)) {
|
|
989
|
+
return {
|
|
990
|
+
hint: 'bug-debugger',
|
|
991
|
+
when,
|
|
992
|
+
reason: 'A noisy reproduce/trace/fix loop benefits from isolated debugging context.',
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
const hasImplementationSkill = skillIds.some((id) => DELEGATABLE_IMPLEMENTATION_SKILL_IDS.has(id));
|
|
997
|
+
const clearImplementationSignal = /\b(implement|build|create|add|ship|deliver|refactor|integrat(?:e|ion)|scaffold|feature)\b/.test(lower);
|
|
998
|
+
const multiLaneSignal = /\b(multiple|several|parallel|independent|across files|across modules|batch)\b/.test(lower);
|
|
999
|
+
if (
|
|
1000
|
+
hasImplementationSkill
|
|
1001
|
+
&& clearImplementationSignal
|
|
1002
|
+
&& routingContext.taskType === 'non-trivial'
|
|
1003
|
+
&& (!localizedIndexedLane || !hasExplicitTarget || contextBreadth >= 4 || multiLaneSignal)
|
|
1004
|
+
) {
|
|
1005
|
+
return {
|
|
1006
|
+
hint: 'feature-implementer',
|
|
1007
|
+
when,
|
|
1008
|
+
reason: 'A broader implementation lane can be delegated without dragging all details through the main context.',
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
return null;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function buildRouteSummary({
|
|
1016
|
+
activeSkills = [],
|
|
1017
|
+
routingContext = {},
|
|
1018
|
+
contextRecommendation = null,
|
|
1019
|
+
verificationRecommendation = null,
|
|
1020
|
+
nextAction = null,
|
|
1021
|
+
} = {}) {
|
|
1022
|
+
const delegationRecommendation = deriveDelegationRecommendation({
|
|
1023
|
+
activeSkills,
|
|
1024
|
+
routingContext,
|
|
1025
|
+
contextRecommendation,
|
|
1026
|
+
verificationRecommendation,
|
|
1027
|
+
});
|
|
1028
|
+
const preview = contextRecommendation?.preview ?? {};
|
|
1029
|
+
const primaryTargets = summarizeCompactList(preview.primaryTargets ?? [], 2);
|
|
1030
|
+
const relatedTests = summarizeCompactList(preview.relatedTests ?? [], 2);
|
|
1031
|
+
const styleFiles = summarizeCompactList(preview.styleFiles ?? [], 1);
|
|
1032
|
+
const primaryCommands = unique(verificationRecommendation?.commands ?? []);
|
|
1033
|
+
const fallbackCommands = unique(verificationRecommendation?.fallbackCommands ?? []);
|
|
1034
|
+
const preferredOrder = unique(
|
|
1035
|
+
verificationRecommendation?.executionPolicy?.preferredOrder
|
|
1036
|
+
?? [...primaryCommands, ...fallbackCommands],
|
|
1037
|
+
);
|
|
1038
|
+
const policyMode = verificationRecommendation?.executionPolicy?.policyMode ?? null;
|
|
1039
|
+
const compactHelperLane = nextAction?.type === 'pull-indexed-context'
|
|
1040
|
+
&& typeof contextRecommendation?.command === 'string'
|
|
1041
|
+
&& contextRecommendation.command.trim();
|
|
1042
|
+
const helperHint = compactHelperHint(
|
|
1043
|
+
compactHelperLane
|
|
1044
|
+
? contextRecommendation?.command
|
|
1045
|
+
: (
|
|
1046
|
+
nextAction?.command
|
|
1047
|
+
? null
|
|
1048
|
+
: verificationRecommendation?.helperCommand
|
|
1049
|
+
),
|
|
1050
|
+
);
|
|
1051
|
+
const nextActionCommand = compactHelperLane ? null : nextAction?.command ?? null;
|
|
1052
|
+
const line = [
|
|
1053
|
+
routingContext.taskType ? `task=${routingContext.taskType}` : null,
|
|
1054
|
+
formatCompactSegment('targets', primaryTargets),
|
|
1055
|
+
formatCompactSegment('tests', relatedTests),
|
|
1056
|
+
formatCompactSegment('styles', styleFiles),
|
|
1057
|
+
policyMode ? `policy=${policyMode}` : null,
|
|
1058
|
+
].filter(Boolean).join(' | ');
|
|
1059
|
+
|
|
1060
|
+
return {
|
|
1061
|
+
primaryCommands,
|
|
1062
|
+
fallbackCommands,
|
|
1063
|
+
preferredOrder,
|
|
1064
|
+
policyMode,
|
|
1065
|
+
delegateHint: delegationRecommendation?.hint ?? null,
|
|
1066
|
+
nextActionType: nextAction?.type ?? null,
|
|
1067
|
+
nextActionCommand,
|
|
1068
|
+
helperHint,
|
|
1069
|
+
line: line || 'task=unknown',
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
function deriveExecutionPolicy({
|
|
1074
|
+
taskType = 'simple',
|
|
1075
|
+
mode = 'minimal',
|
|
1076
|
+
commands = [],
|
|
1077
|
+
fallbackCommands = [],
|
|
1078
|
+
reasons = [],
|
|
1079
|
+
context = {},
|
|
1080
|
+
} = {}) {
|
|
1081
|
+
const relatedTests = context.relatedTests ?? [];
|
|
1082
|
+
const sharedAbstractions = context.sharedAbstractions ?? [];
|
|
1083
|
+
const docsOnly = mode === 'docs-only' || reasons.includes('docsOnly');
|
|
1084
|
+
const risky = taskType === 'non-trivial'
|
|
1085
|
+
|| reasons.includes('riskySkillOrTask')
|
|
1086
|
+
|| reasons.includes('highRiskFallback');
|
|
1087
|
+
const hasPrimaryCommands = commands.length > 0;
|
|
1088
|
+
const hasTargetedPrimaryCommands = mode === 'targeted-tests-first' && hasPrimaryCommands;
|
|
1089
|
+
const sharedScope = sharedAbstractions.length > 0;
|
|
1090
|
+
const noRelatedTests = relatedTests.length === 0;
|
|
1091
|
+
const broadOnlyPrimary = hasPrimaryCommands && !hasTargetedPrimaryCommands && noRelatedTests;
|
|
1092
|
+
const requiresHumanConfirmationForBroadOrRisky = risky && broadOnlyPrimary;
|
|
1093
|
+
const shouldAutoRunPrimaryCommands = hasPrimaryCommands && !docsOnly && !requiresHumanConfirmationForBroadOrRisky;
|
|
1094
|
+
const shouldEscalateToFallbacks = !docsOnly && fallbackCommands.length > 0 && (risky || sharedScope);
|
|
1095
|
+
const shouldAutoRunFallbacks = shouldEscalateToFallbacks && !requiresHumanConfirmationForBroadOrRisky;
|
|
1096
|
+
|
|
1097
|
+
let policyMode = 'recommend-only';
|
|
1098
|
+
if (docsOnly) {
|
|
1099
|
+
policyMode = 'docs-light';
|
|
1100
|
+
} else if (hasTargetedPrimaryCommands && shouldEscalateToFallbacks) {
|
|
1101
|
+
policyMode = 'auto-run-targeted-then-fallback';
|
|
1102
|
+
} else if (hasTargetedPrimaryCommands) {
|
|
1103
|
+
policyMode = 'auto-run-targeted';
|
|
1104
|
+
} else if (requiresHumanConfirmationForBroadOrRisky) {
|
|
1105
|
+
policyMode = 'confirm-then-broad';
|
|
1106
|
+
} else if (shouldAutoRunPrimaryCommands) {
|
|
1107
|
+
policyMode = 'auto-run-primary';
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
return {
|
|
1111
|
+
policyMode,
|
|
1112
|
+
shouldAutoRunPrimaryCommands,
|
|
1113
|
+
shouldAutoRunTargetedVerification: hasTargetedPrimaryCommands && shouldAutoRunPrimaryCommands,
|
|
1114
|
+
shouldEscalateToFallbacks,
|
|
1115
|
+
shouldAutoRunFallbacks,
|
|
1116
|
+
requiresHumanConfirmationForBroadOrRisky,
|
|
1117
|
+
preferredOrder: [...commands, ...fallbackCommands],
|
|
1118
|
+
reasons: unique([
|
|
1119
|
+
...reasons,
|
|
1120
|
+
sharedScope ? 'sharedScope' : null,
|
|
1121
|
+
noRelatedTests ? 'noRelatedTests' : null,
|
|
1122
|
+
]),
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function deriveContextIntent({ promptText, commandText, targetFile, selectedIds }) {
|
|
1127
|
+
if (promptText.trim()) return promptText.trim();
|
|
1128
|
+
if (commandText.includes('triage.mjs')) return `debug ${commandText.trim()}`.trim();
|
|
1129
|
+
if (commandText.includes('query-index.mjs')) return `investigate ${commandText.trim()}`.trim();
|
|
1130
|
+
if (/\b(test|vitest|jest|playwright|cypress)\b/i.test(commandText)) {
|
|
1131
|
+
return targetFile ? `fix failing test around ${targetFile}` : 'fix failing test';
|
|
1132
|
+
}
|
|
1133
|
+
if (targetFile) {
|
|
1134
|
+
if (/^docs\//i.test(targetFile)) return `update docs for ${targetFile}`;
|
|
1135
|
+
return `work on ${targetFile}`;
|
|
1136
|
+
}
|
|
1137
|
+
if (selectedIds.includes('docs-quality')) return 'update documentation';
|
|
1138
|
+
if (selectedIds.includes('debugging-toolkit')) return 'debug failing issue';
|
|
1139
|
+
if (selectedIds.includes('testing-quality')) return 'write targeted test';
|
|
1140
|
+
if (selectedIds.includes('code-review')) return 'review change for regressions';
|
|
1141
|
+
if (selectedIds.includes('repo-maintenance')) return 'refresh stale workspace';
|
|
1142
|
+
if (selectedIds.includes('discover-security')) return 'review security-sensitive change';
|
|
1143
|
+
return '';
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function inferTaskType({ promptText, commandText, selectedIds }) {
|
|
1147
|
+
const lower = buildNormalizedRouteSignalText(promptText, commandText);
|
|
1148
|
+
if (
|
|
1149
|
+
selectedIds.includes('discover-security')
|
|
1150
|
+
|| selectedIds.includes('repo-maintenance')
|
|
1151
|
+
|| /\b(auth|security|token|permission|secret|migration|uninstall|delete all|drop table|race|flaky|timeout)\b/.test(lower)
|
|
1152
|
+
) {
|
|
1153
|
+
return 'non-trivial';
|
|
1154
|
+
}
|
|
1155
|
+
if (/\b(typo|label|text|rename|color|spacing|toggle|comment)\b/.test(lower)) {
|
|
1156
|
+
return 'trivial';
|
|
1157
|
+
}
|
|
1158
|
+
return 'simple';
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
function buildHelperCommand({ commandNamespace = '.claude', scriptName, intent = '', targetFile = null, taskType = null }) {
|
|
1162
|
+
const parts = ['node', `${commandNamespace}/ukit/index/${scriptName}`];
|
|
1163
|
+
if (intent) parts.push(JSON.stringify(intent));
|
|
1164
|
+
if (targetFile) parts.push('--target', JSON.stringify(targetFile));
|
|
1165
|
+
if (taskType) parts.push('--type', taskType);
|
|
1166
|
+
return parts.join(' ');
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function isTestLikeFile(filePath) {
|
|
1170
|
+
return /\.(test|spec)\.[a-z0-9]+$/i.test(filePath)
|
|
1171
|
+
|| /(^|\/)(?:__tests__|tests?|specs?)\//i.test(filePath);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
function summarizeCompactList(values, limit = 2) {
|
|
1175
|
+
const normalized = unique(values);
|
|
1176
|
+
return {
|
|
1177
|
+
items: normalized.slice(0, limit),
|
|
1178
|
+
total: normalized.length,
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function countDelegationContextBreadth(preview = {}) {
|
|
1183
|
+
return unique([
|
|
1184
|
+
...(preview.primaryTargets ?? []),
|
|
1185
|
+
...(preview.analogFiles ?? []),
|
|
1186
|
+
...(preview.sharedAbstractions ?? []),
|
|
1187
|
+
...(preview.relatedTests ?? []),
|
|
1188
|
+
]).length;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
function formatCompactSegment(label, summary) {
|
|
1192
|
+
const items = summary?.items ?? [];
|
|
1193
|
+
if (items.length === 0) return null;
|
|
1194
|
+
const extraCount = Math.max((summary?.total ?? items.length) - items.length, 0);
|
|
1195
|
+
return `${label}=${items.join(',')}${extraCount > 0 ? `,+${extraCount}` : ''}`;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function compactHelperHint(command) {
|
|
1199
|
+
if (typeof command !== 'string' || !command.trim()) return null;
|
|
1200
|
+
|
|
1201
|
+
const normalized = command.trim().replace(/\s+/g, ' ');
|
|
1202
|
+
const scriptMatch = normalized.match(/^node\s+([^\s]+)(?:\s+(.*))?$/i);
|
|
1203
|
+
if (!scriptMatch) {
|
|
1204
|
+
return normalized.length > 96 ? `${normalized.slice(0, 93)}...` : normalized;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
const scriptName = scriptMatch[1].split('/').pop() ?? scriptMatch[1];
|
|
1208
|
+
const rest = scriptMatch[2] ?? '';
|
|
1209
|
+
const targetMatch = rest.match(/--target\s+("[^"]+"|'[^']+'|\S+)/);
|
|
1210
|
+
const typeMatch = rest.match(/--type\s+(\S+)/);
|
|
1211
|
+
const parts = [scriptName];
|
|
1212
|
+
|
|
1213
|
+
if (targetMatch) {
|
|
1214
|
+
parts.push(`target=${stripShellQuotes(targetMatch[1])}`);
|
|
1215
|
+
}
|
|
1216
|
+
if (typeMatch) {
|
|
1217
|
+
parts.push(`type=${typeMatch[1]}`);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
return parts.join(' ');
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
function stripShellQuotes(value) {
|
|
1224
|
+
return String(value ?? '').replace(/^['"]|['"]$/g, '');
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
async function readPackageJson(rootDir) {
|
|
1228
|
+
try {
|
|
1229
|
+
const raw = await fs.readFile(path.join(rootDir, 'package.json'), 'utf8');
|
|
1230
|
+
return JSON.parse(raw);
|
|
1231
|
+
} catch {
|
|
1232
|
+
return null;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
function detectPackageManager({ fingerprintEntries = [], pkg = null } = {}) {
|
|
1237
|
+
const declaredPackageManager = getDeclaredPackageManager(pkg);
|
|
1238
|
+
if (declaredPackageManager) return declaredPackageManager;
|
|
1239
|
+
|
|
1240
|
+
const existingFiles = new Set(
|
|
1241
|
+
fingerprintEntries
|
|
1242
|
+
.filter((entry) => entry && entry.mtimeMs !== null)
|
|
1243
|
+
.map((entry) => entry.filePath),
|
|
1244
|
+
);
|
|
1245
|
+
|
|
1246
|
+
const checks = [
|
|
1247
|
+
['pnpm-lock.yaml', 'pnpm'],
|
|
1248
|
+
['yarn.lock', 'yarn'],
|
|
1249
|
+
['bun.lockb', 'bun'],
|
|
1250
|
+
['bun.lock', 'bun'],
|
|
1251
|
+
['package-lock.json', 'npm'],
|
|
1252
|
+
];
|
|
1253
|
+
|
|
1254
|
+
for (const [lockFile, pm] of checks) {
|
|
1255
|
+
if (existingFiles.has(lockFile)) {
|
|
1256
|
+
return pm;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
return 'npm';
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
async function readProjectVerificationFingerprint(rootDir) {
|
|
1264
|
+
const entries = [];
|
|
1265
|
+
let pkg = null;
|
|
1266
|
+
|
|
1267
|
+
try {
|
|
1268
|
+
const packageJsonPath = path.join(rootDir, 'package.json');
|
|
1269
|
+
const [raw, stat] = await Promise.all([
|
|
1270
|
+
fs.readFile(packageJsonPath, 'utf8'),
|
|
1271
|
+
fs.stat(packageJsonPath),
|
|
1272
|
+
]);
|
|
1273
|
+
pkg = JSON.parse(raw);
|
|
1274
|
+
entries.push({
|
|
1275
|
+
filePath: 'package.json',
|
|
1276
|
+
size: stat.size,
|
|
1277
|
+
mtimeMs: stat.mtimeMs,
|
|
1278
|
+
});
|
|
1279
|
+
} catch {
|
|
1280
|
+
entries.push({
|
|
1281
|
+
filePath: 'package.json',
|
|
1282
|
+
size: null,
|
|
1283
|
+
mtimeMs: null,
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
const trackedFiles = getDeclaredPackageManager(pkg)
|
|
1288
|
+
? []
|
|
1289
|
+
: [
|
|
1290
|
+
'package-lock.json',
|
|
1291
|
+
'pnpm-lock.yaml',
|
|
1292
|
+
'yarn.lock',
|
|
1293
|
+
'bun.lockb',
|
|
1294
|
+
'bun.lock',
|
|
1295
|
+
];
|
|
1296
|
+
|
|
1297
|
+
for (const relativePath of trackedFiles) {
|
|
1298
|
+
const absolutePath = path.join(rootDir, relativePath);
|
|
1299
|
+
try {
|
|
1300
|
+
const stat = await fs.stat(absolutePath);
|
|
1301
|
+
entries.push({
|
|
1302
|
+
filePath: relativePath,
|
|
1303
|
+
size: stat.size,
|
|
1304
|
+
mtimeMs: stat.mtimeMs,
|
|
1305
|
+
});
|
|
1306
|
+
} catch {
|
|
1307
|
+
entries.push({
|
|
1308
|
+
filePath: relativePath,
|
|
1309
|
+
size: null,
|
|
1310
|
+
mtimeMs: null,
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
return {
|
|
1316
|
+
pkg,
|
|
1317
|
+
entries,
|
|
1318
|
+
fingerprint: JSON.stringify(entries),
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
function getDeclaredPackageManager(pkg = null) {
|
|
1323
|
+
const declared = String(pkg?.packageManager ?? '').toLowerCase();
|
|
1324
|
+
if (declared.startsWith('pnpm')) return 'pnpm';
|
|
1325
|
+
if (declared.startsWith('yarn')) return 'yarn';
|
|
1326
|
+
if (declared.startsWith('bun')) return 'bun';
|
|
1327
|
+
if (declared.startsWith('npm')) return 'npm';
|
|
1328
|
+
return null;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function buildScriptCommand(packageManager, scriptName, args = []) {
|
|
1332
|
+
const filteredArgs = args.filter(Boolean);
|
|
1333
|
+
if (packageManager === 'npm') {
|
|
1334
|
+
if (scriptName === 'test') {
|
|
1335
|
+
return ['npm', 'test', filteredArgs.length > 0 ? '--' : '', ...filteredArgs].filter(Boolean).join(' ');
|
|
1336
|
+
}
|
|
1337
|
+
return ['npm', 'run', scriptName, filteredArgs.length > 0 ? '--' : '', ...filteredArgs].filter(Boolean).join(' ');
|
|
1338
|
+
}
|
|
1339
|
+
return [packageManager, scriptName, ...filteredArgs].join(' ');
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
function normalizeRelativeFile(rootDir, rawFilePath) {
|
|
1343
|
+
if (typeof rawFilePath !== 'string' || !rawFilePath.trim()) return null;
|
|
1344
|
+
|
|
1345
|
+
const trimmed = rawFilePath.trim();
|
|
1346
|
+
if (path.isAbsolute(trimmed)) {
|
|
1347
|
+
const relative = path.relative(rootDir, trimmed);
|
|
1348
|
+
if (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) {
|
|
1349
|
+
return relative.replaceAll('\\', '/');
|
|
1350
|
+
}
|
|
1351
|
+
return trimmed.replaceAll('\\', '/');
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
return trimmed.replace(/^\.\/+/, '').replaceAll('\\', '/');
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
async function pathExists(filePath) {
|
|
1358
|
+
try {
|
|
1359
|
+
await fs.access(filePath);
|
|
1360
|
+
return true;
|
|
1361
|
+
} catch {
|
|
1362
|
+
return false;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
function unique(values) {
|
|
1367
|
+
return [...new Set(values.filter(Boolean))];
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
const DELEGATABLE_IMPLEMENTATION_SKILL_IDS = new Set([
|
|
1371
|
+
'delivery',
|
|
1372
|
+
'frontend',
|
|
1373
|
+
'frontend-vue',
|
|
1374
|
+
'backend-api',
|
|
1375
|
+
'postgres',
|
|
1376
|
+
'sql-optimization-patterns',
|
|
1377
|
+
]);
|
|
1378
|
+
|
|
1379
|
+
function getRootDir(argv) {
|
|
1380
|
+
const value = readFlagValue(argv, '--root');
|
|
1381
|
+
return value ? path.resolve(value) : process.cwd();
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
function readFlagValue(argv, flag) {
|
|
1385
|
+
const exact = argv.indexOf(flag);
|
|
1386
|
+
if (exact >= 0 && argv[exact + 1]) return argv[exact + 1];
|
|
1387
|
+
const withEquals = argv.find((item) => item.startsWith(`${flag}=`));
|
|
1388
|
+
return withEquals ? withEquals.slice(flag.length + 1) : null;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
function isFlagOrValue(argv, index) {
|
|
1392
|
+
const arg = argv[index];
|
|
1393
|
+
if (!arg.startsWith('--')) {
|
|
1394
|
+
const prev = argv[index - 1];
|
|
1395
|
+
return Boolean(prev && ['--root', '--target', '--type', '--tool-command', '--last-prompt', '--adapter'].includes(prev));
|
|
1396
|
+
}
|
|
1397
|
+
return true;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
function inferAdapterFromInvocation(invocationPath) {
|
|
1401
|
+
if (invocationPath.includes('.codex/')) return 'codex';
|
|
1402
|
+
if (invocationPath.includes('.antigravity/')) return 'antigravity';
|
|
1403
|
+
return 'claude';
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
function buildResolveContextRequestKey({
|
|
1407
|
+
indexGeneratedAtMs = null,
|
|
1408
|
+
intent = '',
|
|
1409
|
+
targetFile = null,
|
|
1410
|
+
taskType = null,
|
|
1411
|
+
} = {}) {
|
|
1412
|
+
return buildCompactMachineKey('resolve-v1', {
|
|
1413
|
+
indexGeneratedAtMs,
|
|
1414
|
+
intent: String(intent || '').trim(),
|
|
1415
|
+
targetFile: String(targetFile || '').trim(),
|
|
1416
|
+
taskType: String(taskType || '').trim(),
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
function buildVerifyContextRequestKey({
|
|
1421
|
+
indexGeneratedAtMs = null,
|
|
1422
|
+
projectFingerprint = '',
|
|
1423
|
+
intent = '',
|
|
1424
|
+
targetFile = null,
|
|
1425
|
+
taskType = null,
|
|
1426
|
+
} = {}) {
|
|
1427
|
+
return buildCompactMachineKey('verify-v1', {
|
|
1428
|
+
indexGeneratedAtMs,
|
|
1429
|
+
projectFingerprint,
|
|
1430
|
+
intent: String(intent || '').trim(),
|
|
1431
|
+
targetFile: String(targetFile || '').trim(),
|
|
1432
|
+
taskType: String(taskType || '').trim(),
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
function buildRouteRequestKey({
|
|
1437
|
+
commandNamespace = '.claude',
|
|
1438
|
+
indexGeneratedAtMs = null,
|
|
1439
|
+
projectFingerprint = null,
|
|
1440
|
+
activeSkills = [],
|
|
1441
|
+
routingContext = {},
|
|
1442
|
+
previousContextSnapshot = null,
|
|
1443
|
+
recentOutputSnapshot = null,
|
|
1444
|
+
} = {}) {
|
|
1445
|
+
return buildCompactMachineKey('route-v2', {
|
|
1446
|
+
commandNamespace,
|
|
1447
|
+
indexGeneratedAtMs,
|
|
1448
|
+
projectFingerprint,
|
|
1449
|
+
previousContextFingerprint: previousContextSnapshot?.fingerprint ?? null,
|
|
1450
|
+
recentOutputFingerprint: recentOutputSnapshot?.fingerprint ?? null,
|
|
1451
|
+
skillIds: activeSkills.map((item) => item.id),
|
|
1452
|
+
skillPaths: activeSkills.map((item) => item.path),
|
|
1453
|
+
skillContextModes: activeSkills.map((item) => item.contextMode ?? 'indexed'),
|
|
1454
|
+
promptText: routingContext.promptText ?? '',
|
|
1455
|
+
lastExplicitUserPromptText: routingContext.lastExplicitUserPromptText ?? '',
|
|
1456
|
+
adapter: routingContext.adapter ?? 'claude',
|
|
1457
|
+
commandText: routingContext.commandText ?? '',
|
|
1458
|
+
targetFile: routingContext.targetFile ?? '',
|
|
1459
|
+
contextIntent: routingContext.contextIntent ?? '',
|
|
1460
|
+
taskType: routingContext.taskType ?? '',
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
function buildRouteStateFingerprint(route = {}) {
|
|
1465
|
+
return buildCompactMachineKey('routefp-v3', buildStructuredRouteFingerprintPayload(route));
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
function buildStructuredRouteFingerprintPayload(route = {}) {
|
|
1469
|
+
const routeSummary = route.routeSummary ?? null;
|
|
1470
|
+
return {
|
|
1471
|
+
skills: (route.activeSkills ?? []).map((item) => item.id),
|
|
1472
|
+
adapter: route.routingContext?.adapter ?? null,
|
|
1473
|
+
taskType: route.routingContext?.taskType ?? null,
|
|
1474
|
+
previousContextLine: route.previousContext?.line ?? null,
|
|
1475
|
+
previousContextIds: unique(route.previousContext?.selectedIds ?? []),
|
|
1476
|
+
recentOutputLine: route.recentOutput?.line ?? null,
|
|
1477
|
+
policyMode: routeSummary?.policyMode ?? null,
|
|
1478
|
+
delegateHint: routeSummary?.delegateHint ?? null,
|
|
1479
|
+
nextActionType: routeSummary?.nextActionType ?? null,
|
|
1480
|
+
nextActionCommand: routeSummary?.nextActionCommand ?? null,
|
|
1481
|
+
helperHint: routeSummary?.helperHint ?? null,
|
|
1482
|
+
primaryCommands: unique(routeSummary?.primaryCommands ?? []),
|
|
1483
|
+
fallbackCommands: unique(routeSummary?.fallbackCommands ?? []),
|
|
1484
|
+
preferredOrder: unique(routeSummary?.preferredOrder ?? []),
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
function buildCompactMachineKey(prefix, payload) {
|
|
1489
|
+
return `${prefix}:${stableMachineDigest(payload)}`;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
function stableMachineDigest(payload) {
|
|
1493
|
+
return crypto
|
|
1494
|
+
.createHash('sha256')
|
|
1495
|
+
.update(JSON.stringify(payload))
|
|
1496
|
+
.digest('base64url');
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
function reuseSharedRouteState({ previousState, requestKey }) {
|
|
1500
|
+
if (!previousState || previousState.requestKey !== requestKey) {
|
|
1501
|
+
return null;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
if (!previousState.routeSummary || !Array.isArray(previousState.activeSkills)) {
|
|
1505
|
+
return null;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
return previousState;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function createSharedRouteState({
|
|
1512
|
+
route,
|
|
1513
|
+
source = 'task-route',
|
|
1514
|
+
requestKey = null,
|
|
1515
|
+
}) {
|
|
1516
|
+
const helpers = compactHelpers({
|
|
1517
|
+
verificationRecommendation: route.verificationRecommendation ?? null,
|
|
1518
|
+
routeSummary: route.routeSummary ?? null,
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
return {
|
|
1522
|
+
requestKey,
|
|
1523
|
+
fingerprint: buildRouteStateFingerprint(route),
|
|
1524
|
+
ts: Date.now(),
|
|
1525
|
+
source,
|
|
1526
|
+
activeSkills: compactActiveSkills(route.activeSkills ?? []),
|
|
1527
|
+
routingContext: compactRoutingContext(route.routingContext ?? {}),
|
|
1528
|
+
...(route.previousContext ? { previousContext: compactPreviousContext(route.previousContext) } : {}),
|
|
1529
|
+
...(route.recentOutput ? { recentOutput: compactRecentOutput(route.recentOutput) } : {}),
|
|
1530
|
+
...(helpers ? { helpers } : {}),
|
|
1531
|
+
routeSummary: compactRouteSummary(route.routeSummary ?? null),
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
function compactRouteCacheState(sharedState = {}) {
|
|
1536
|
+
if (!sharedState || typeof sharedState !== 'object') {
|
|
1537
|
+
return sharedState;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
const compactState = { ...sharedState };
|
|
1541
|
+
delete compactState.source;
|
|
1542
|
+
delete compactState.ts;
|
|
1543
|
+
return compactState;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
function compactActiveSkills(activeSkills = []) {
|
|
1547
|
+
return activeSkills.map((item) => ({
|
|
1548
|
+
id: item.id,
|
|
1549
|
+
}));
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
function compactRoutingContext(routingContext = {}) {
|
|
1553
|
+
return {
|
|
1554
|
+
adapter: routingContext.adapter ?? 'claude',
|
|
1555
|
+
lastExplicitUserPromptText: routingContext.lastExplicitUserPromptText ?? '',
|
|
1556
|
+
taskType: routingContext.taskType ?? null,
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
function compactHelpers({
|
|
1561
|
+
verificationRecommendation = null,
|
|
1562
|
+
routeSummary = null,
|
|
1563
|
+
} = {}) {
|
|
1564
|
+
const verificationHelperHint = routeSummary?.helperHint
|
|
1565
|
+
? null
|
|
1566
|
+
: compactHelperHint(verificationRecommendation?.helperCommand ?? null);
|
|
1567
|
+
|
|
1568
|
+
if (!verificationHelperHint) {
|
|
1569
|
+
return null;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
return {
|
|
1573
|
+
verificationHelperHint,
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
function compactPreviousContext(previousContext = null) {
|
|
1578
|
+
if (!previousContext || typeof previousContext !== 'object') {
|
|
1579
|
+
return null;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
const line = typeof previousContext.line === 'string' ? previousContext.line.trim() : '';
|
|
1583
|
+
if (!line) {
|
|
1584
|
+
return null;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
return {
|
|
1588
|
+
line,
|
|
1589
|
+
selectedIds: unique(previousContext.selectedIds ?? []).slice(0, 3),
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
function compactRecentOutput(recentOutput = null) {
|
|
1594
|
+
if (!recentOutput || typeof recentOutput !== 'object') {
|
|
1595
|
+
return null;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
const line = typeof recentOutput.line === 'string' ? recentOutput.line.trim() : '';
|
|
1599
|
+
if (!line) {
|
|
1600
|
+
return null;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
return {
|
|
1604
|
+
line,
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
function compactRouteSummary(routeSummary = null) {
|
|
1609
|
+
if (!routeSummary) {
|
|
1610
|
+
return null;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
return {
|
|
1614
|
+
line: routeSummary.line ?? 'task=unknown',
|
|
1615
|
+
primaryCommands: unique(routeSummary.primaryCommands ?? []).slice(0, 2),
|
|
1616
|
+
fallbackCommands: unique(routeSummary.fallbackCommands ?? []).slice(0, 3),
|
|
1617
|
+
preferredOrder: unique(routeSummary.preferredOrder ?? []).slice(0, 5),
|
|
1618
|
+
policyMode: routeSummary.policyMode ?? null,
|
|
1619
|
+
delegateHint: routeSummary.delegateHint ?? null,
|
|
1620
|
+
nextActionType: routeSummary.nextActionType ?? null,
|
|
1621
|
+
nextActionCommand: routeSummary.nextActionCommand ?? null,
|
|
1622
|
+
helperHint: routeSummary.helperHint ?? null,
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function shouldIncludePreviousContext({
|
|
1627
|
+
routingContext = {},
|
|
1628
|
+
useIndexedContext = true,
|
|
1629
|
+
} = {}) {
|
|
1630
|
+
const taskType = String(routingContext?.taskType ?? '').trim();
|
|
1631
|
+
const hasExplicitTarget = Boolean(String(routingContext?.targetFile ?? '').trim());
|
|
1632
|
+
|
|
1633
|
+
if (!hasExplicitTarget) {
|
|
1634
|
+
return true;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
if (!useIndexedContext) {
|
|
1638
|
+
return true;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
return taskType !== 'simple' && taskType !== 'trivial';
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
function shouldIncludeRecentOutput({
|
|
1645
|
+
activeSkills = [],
|
|
1646
|
+
routingContext = {},
|
|
1647
|
+
useIndexedContext = true,
|
|
1648
|
+
} = {}) {
|
|
1649
|
+
const taskType = String(routingContext?.taskType ?? '').trim();
|
|
1650
|
+
const hasExplicitTarget = Boolean(String(routingContext?.targetFile ?? '').trim());
|
|
1651
|
+
const primarySkillId = String(activeSkills?.[0]?.id ?? '').trim();
|
|
1652
|
+
|
|
1653
|
+
if (!useIndexedContext) {
|
|
1654
|
+
return false;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
if (!hasExplicitTarget) {
|
|
1658
|
+
return true;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
if (
|
|
1662
|
+
(taskType === 'simple' || taskType === 'trivial')
|
|
1663
|
+
&& primarySkillId === 'testing-quality'
|
|
1664
|
+
) {
|
|
1665
|
+
return false;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
return true;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
async function readJson(filePath, fallback = null) {
|
|
1672
|
+
try {
|
|
1673
|
+
return JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
1674
|
+
} catch {
|
|
1675
|
+
return fallback;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
async function writeJson(filePath, value) {
|
|
1680
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
1681
|
+
await fs.writeFile(filePath, JSON.stringify(value), 'utf8');
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
function normalize(text) {
|
|
1685
|
+
return String(text ?? '')
|
|
1686
|
+
.toLowerCase()
|
|
1687
|
+
.replace(/[^\p{L}\p{N}\s./:_-]/gu, ' ')
|
|
1688
|
+
.replace(/\s+/g, ' ')
|
|
1689
|
+
.trim();
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
function tokenize(text) {
|
|
1693
|
+
return normalize(text)
|
|
1694
|
+
.split(/\s+/)
|
|
1695
|
+
.filter((token) => token && !STOPWORDS.has(token) && token.length > 1);
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
function getMemoryTimestamp(item) {
|
|
1699
|
+
return item.content?.updatedAt
|
|
1700
|
+
?? item.content?.endedAt
|
|
1701
|
+
?? item.content?.startedAt
|
|
1702
|
+
?? 0;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
function buildMemorySegments(item) {
|
|
1706
|
+
const content = item.content ?? {};
|
|
1707
|
+
if (item.type === 'project') {
|
|
1708
|
+
return [
|
|
1709
|
+
{ text: content.name, weight: 2 },
|
|
1710
|
+
{ text: content.architecture, weight: 3 },
|
|
1711
|
+
{ text: (content.techStack ?? []).join(' '), weight: 2 },
|
|
1712
|
+
{ text: (content.activeRules ?? []).join(' '), weight: 2 },
|
|
1713
|
+
{ text: (content.decisions ?? []).map((decision) => `${decision.what} ${decision.why}`).join(' '), weight: 7 },
|
|
1714
|
+
];
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
if (item.type === 'session') {
|
|
1718
|
+
return [
|
|
1719
|
+
{ text: content.taskDescription, weight: 4 },
|
|
1720
|
+
{ text: content.outcome, weight: 1 },
|
|
1721
|
+
{ text: (content.keyActions ?? []).join(' '), weight: 3 },
|
|
1722
|
+
{ text: (content.nextSteps ?? []).join(' '), weight: 2 },
|
|
1723
|
+
{ text: (content.filesChanged ?? []).join(' '), weight: 1 },
|
|
1724
|
+
{ text: (content.decisionsMade ?? []).map((decision) => `${decision.what} ${decision.why}`).join(' '), weight: 4 },
|
|
1725
|
+
];
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
return [
|
|
1729
|
+
{ text: JSON.stringify(content.preferences ?? {}), weight: 1 },
|
|
1730
|
+
{ text: (content.rules ?? []).join(' '), weight: 2 },
|
|
1731
|
+
];
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
function scoreMemoryItem(item, queryTokens) {
|
|
1735
|
+
if (queryTokens.length === 0) {
|
|
1736
|
+
return 0;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
let score = 0;
|
|
1740
|
+
for (const segment of buildMemorySegments(item)) {
|
|
1741
|
+
const segmentTokens = new Set(tokenize(segment.text));
|
|
1742
|
+
for (const token of queryTokens) {
|
|
1743
|
+
if (segmentTokens.has(token)) {
|
|
1744
|
+
score += segment.weight;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
if (score === 0) {
|
|
1750
|
+
return 0;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
const recencyBonus = getMemoryTimestamp(item) > 0 ? Math.min(1, getMemoryTimestamp(item) / Date.now()) : 0;
|
|
1754
|
+
return score + recencyBonus;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
async function readDirectoryJsonItems(dirPath) {
|
|
1758
|
+
let entries = [];
|
|
1759
|
+
try {
|
|
1760
|
+
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
1761
|
+
} catch {
|
|
1762
|
+
return [];
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
const items = [];
|
|
1766
|
+
for (const entry of entries) {
|
|
1767
|
+
if (!entry.isFile() || !entry.name.endsWith('.json')) {
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
1772
|
+
const content = await readJson(fullPath, null);
|
|
1773
|
+
if (content && typeof content === 'object') {
|
|
1774
|
+
items.push({
|
|
1775
|
+
fileName: entry.name,
|
|
1776
|
+
filePath: fullPath,
|
|
1777
|
+
content,
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
return items.sort((left, right) => left.fileName.localeCompare(right.fileName));
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
async function detectProjectId(rootDir) {
|
|
1786
|
+
const pkg = await readJson(path.join(rootDir, 'package.json'), null);
|
|
1787
|
+
if (typeof pkg?.name === 'string' && pkg.name.trim()) {
|
|
1788
|
+
return pkg.name.trim();
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
return path.basename(rootDir);
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
async function listMemoryItems(rootDir) {
|
|
1795
|
+
const runtimeRoot = path.join(rootDir, '.ukit', 'storage', 'memory');
|
|
1796
|
+
const userMemory = (await readJson(path.join(runtimeRoot, 'user.json'), null)) ?? { preferences: {}, rules: [] };
|
|
1797
|
+
const projectMemories = await readDirectoryJsonItems(path.join(runtimeRoot, 'projects'));
|
|
1798
|
+
const sessionMemories = await readDirectoryJsonItems(path.join(runtimeRoot, 'sessions'));
|
|
1799
|
+
|
|
1800
|
+
return [
|
|
1801
|
+
{
|
|
1802
|
+
id: 'user:user',
|
|
1803
|
+
type: 'user',
|
|
1804
|
+
content: userMemory,
|
|
1805
|
+
},
|
|
1806
|
+
...projectMemories.map((item) => ({
|
|
1807
|
+
id: `project:${item.content.id ?? item.fileName.replace(/\.json$/, '')}`,
|
|
1808
|
+
type: 'project',
|
|
1809
|
+
content: item.content,
|
|
1810
|
+
})),
|
|
1811
|
+
...sessionMemories.map((item) => ({
|
|
1812
|
+
id: `session:${item.content.id ?? item.fileName.replace(/\.json$/, '')}`,
|
|
1813
|
+
type: 'session',
|
|
1814
|
+
content: item.content,
|
|
1815
|
+
})),
|
|
1816
|
+
];
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
function buildPreviousContextSnippet(item) {
|
|
1820
|
+
const content = item.content ?? {};
|
|
1821
|
+
if (item.type === 'project') {
|
|
1822
|
+
const decisions = compactPhraseList((content.decisions ?? []).map((decision) => decision.what), { limit: 1 });
|
|
1823
|
+
const rules = compactPhraseList(content.activeRules ?? [], { limit: 1 });
|
|
1824
|
+
return `[project] ${content.name ?? content.id ?? 'project'} — decisions: ${decisions || 'n/a'}${rules ? ` — rules: ${rules}` : ''}`;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
if (item.type === 'session') {
|
|
1828
|
+
const actions = compactPhraseList(content.keyActions ?? [], { limit: 1 });
|
|
1829
|
+
const nextSteps = compactPhraseList(content.nextSteps ?? [], { limit: 1 });
|
|
1830
|
+
return `[session] ${content.taskDescription ?? item.id} — outcome: ${content.outcome ?? 'unknown'}${actions ? ` — actions: ${actions}` : ''}${nextSteps ? ` — next: ${nextSteps}` : ''}`;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
const rules = compactPhraseList(content.rules ?? [], { limit: 1 });
|
|
1834
|
+
return `[user] rules: ${rules || 'n/a'}`;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
function compactPhraseList(values, { limit = 1, fallback = 'n/a' } = {}) {
|
|
1838
|
+
const normalized = unique(
|
|
1839
|
+
(values ?? [])
|
|
1840
|
+
.map((value) => String(value ?? '').trim())
|
|
1841
|
+
.filter(Boolean),
|
|
1842
|
+
);
|
|
1843
|
+
|
|
1844
|
+
if (normalized.length === 0) {
|
|
1845
|
+
return fallback;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
const shown = normalized.slice(0, limit);
|
|
1849
|
+
const remaining = normalized.length - shown.length;
|
|
1850
|
+
return remaining > 0
|
|
1851
|
+
? `${shown.join('; ')} (+${remaining} more)`
|
|
1852
|
+
: shown.join('; ');
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
async function buildPreviousContextSnapshot({ rootDir = process.cwd(), routingContext = {} } = {}) {
|
|
1856
|
+
const taskQuery = String(
|
|
1857
|
+
routingContext?.lastExplicitUserPromptText
|
|
1858
|
+
|| routingContext?.promptText
|
|
1859
|
+
|| routingContext?.targetFile
|
|
1860
|
+
|| ''
|
|
1861
|
+
).trim();
|
|
1862
|
+
if (!taskQuery) {
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
const projectId = await detectProjectId(rootDir);
|
|
1867
|
+
const items = await listMemoryItems(rootDir);
|
|
1868
|
+
const queryTokens = tokenize(taskQuery);
|
|
1869
|
+
const rankedItems = items
|
|
1870
|
+
.filter((item) => item.type !== 'user')
|
|
1871
|
+
.filter((item) => (
|
|
1872
|
+
item.type === 'project'
|
|
1873
|
+
? (item.content?.id === projectId)
|
|
1874
|
+
: (item.type === 'session' ? item.content?.projectId === projectId : true)
|
|
1875
|
+
))
|
|
1876
|
+
.map((item) => ({
|
|
1877
|
+
item,
|
|
1878
|
+
score: scoreMemoryItem(item, queryTokens),
|
|
1879
|
+
}))
|
|
1880
|
+
.filter((entry) => entry.score > 0)
|
|
1881
|
+
.sort((left, right) => (
|
|
1882
|
+
right.score - left.score
|
|
1883
|
+
|| getMemoryTimestamp(right.item) - getMemoryTimestamp(left.item)
|
|
1884
|
+
))
|
|
1885
|
+
.slice(0, 2)
|
|
1886
|
+
.map((entry) => entry.item);
|
|
1887
|
+
|
|
1888
|
+
if (rankedItems.length === 0) {
|
|
1889
|
+
return null;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
return {
|
|
1893
|
+
line: rankedItems.map((item) => buildPreviousContextSnippet(item)).join(' | '),
|
|
1894
|
+
selectedIds: rankedItems.map((item) => item.id),
|
|
1895
|
+
fingerprint: buildCompactMachineKey('route-memory-v1', {
|
|
1896
|
+
taskQuery: normalize(taskQuery),
|
|
1897
|
+
projectId,
|
|
1898
|
+
items: rankedItems.map((item) => ({
|
|
1899
|
+
id: item.id,
|
|
1900
|
+
timestamp: getMemoryTimestamp(item),
|
|
1901
|
+
})),
|
|
1902
|
+
}),
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
async function buildRecentOutputSnapshot({ rootDir = process.cwd() } = {}) {
|
|
1907
|
+
const outputHistoryPath = path.join(rootDir, '.ukit', 'storage', 'cache', 'output-history.json');
|
|
1908
|
+
const outputHistory = await readJson(outputHistoryPath, { entries: [] });
|
|
1909
|
+
const entries = Array.isArray(outputHistory?.entries) ? outputHistory.entries : [];
|
|
1910
|
+
const freshEntries = entries
|
|
1911
|
+
.filter((entry) => (
|
|
1912
|
+
typeof entry?.timestamp === 'number'
|
|
1913
|
+
&& (Date.now() - entry.timestamp) <= (30 * 60 * 1000)
|
|
1914
|
+
&& typeof entry?.summary === 'string'
|
|
1915
|
+
&& entry.summary.trim()
|
|
1916
|
+
))
|
|
1917
|
+
.sort((left, right) => Number(right?.timestamp || 0) - Number(left?.timestamp || 0));
|
|
1918
|
+
const entry = freshEntries[0];
|
|
1919
|
+
if (!entry) {
|
|
1920
|
+
return null;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
return {
|
|
1924
|
+
line: compactInlineSummary(entry.summary),
|
|
1925
|
+
fingerprint: buildCompactMachineKey('route-output-v1', {
|
|
1926
|
+
command: String(entry.command || ''),
|
|
1927
|
+
summary: String(entry.summary || ''),
|
|
1928
|
+
timestamp: entry.timestamp,
|
|
1929
|
+
savedTokens: Number.isFinite(entry.savedTokens) ? entry.savedTokens : null,
|
|
1930
|
+
}),
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
function compactInlineSummary(summary, { maxSegments = INLINE_SUMMARY_MAX_SEGMENTS } = {}) {
|
|
1935
|
+
const lines = unique(
|
|
1936
|
+
String(summary || '')
|
|
1937
|
+
.split(/\r?\n/)
|
|
1938
|
+
.map((line) => line.trim())
|
|
1939
|
+
.filter(Boolean),
|
|
1940
|
+
);
|
|
1941
|
+
if (lines.length === 0) {
|
|
1942
|
+
return '';
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
if (lines.length <= maxSegments) {
|
|
1946
|
+
return lines.join(' | ');
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
const firstLine = lines[0];
|
|
1950
|
+
const middleLines = lines.slice(1, -1);
|
|
1951
|
+
const selectedMiddle = [];
|
|
1952
|
+
|
|
1953
|
+
for (const line of middleLines) {
|
|
1954
|
+
if (selectedMiddle.length >= Math.max(0, maxSegments - 2)) {
|
|
1955
|
+
break;
|
|
1956
|
+
}
|
|
1957
|
+
if (!INLINE_SUMMARY_SIGNAL_PATTERNS.some((pattern) => pattern.test(line))) {
|
|
1958
|
+
continue;
|
|
1959
|
+
}
|
|
1960
|
+
if (!selectedMiddle.includes(line)) {
|
|
1961
|
+
selectedMiddle.push(line);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
for (const line of middleLines) {
|
|
1966
|
+
if (selectedMiddle.length >= Math.max(0, maxSegments - 2)) {
|
|
1967
|
+
break;
|
|
1968
|
+
}
|
|
1969
|
+
if (!selectedMiddle.includes(line)) {
|
|
1970
|
+
selectedMiddle.push(line);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
const tailLine = selectInlineSummaryTail(lines);
|
|
1975
|
+
const selected = unique([firstLine, ...selectedMiddle, tailLine]).slice(0, maxSegments);
|
|
1976
|
+
const remaining = Math.max(0, lines.length - selected.length);
|
|
1977
|
+
return remaining > 0
|
|
1978
|
+
? `${selected.join(' | ')} | +${remaining} more`
|
|
1979
|
+
: selected.join(' | ');
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
function selectInlineSummaryTail(lines = []) {
|
|
1983
|
+
for (const pattern of INLINE_SUMMARY_TAIL_PATTERNS) {
|
|
1984
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
1985
|
+
if (pattern.test(lines[index])) {
|
|
1986
|
+
return lines[index];
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
return lines[lines.length - 1] ?? '';
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
await main();
|