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