@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,1392 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+
5
+ import { getArtifactPath, getIndexDir, INDEX_ARTIFACTS, INDEX_SCHEMA_VERSION, normalizeRelative } from './paths.js';
6
+ import { importsNeedAliasContext, loadImportAliasContextState, resolveImportSpecifier } from './importResolution.js';
7
+ import { clearIndexArtifactCache } from './queryIndex.js';
8
+ import { clearRelatedTestArtifactCache } from './relatedTests.js';
9
+
10
+ const SOURCE_DIRS = ['src', 'tests', 'test', 'specs', 'spec', '__tests__', 'manifests'];
11
+ const EXCLUDED_DIR_NAMES = new Set([
12
+ 'node_modules',
13
+ '.git',
14
+ '.cache',
15
+ '.next',
16
+ '.nuxt',
17
+ '.output',
18
+ '.vercel',
19
+ '.turbo',
20
+ '.yarn',
21
+ 'dist',
22
+ 'build',
23
+ 'out',
24
+ 'coverage',
25
+ 'tmp',
26
+ 'temp',
27
+ 'vendor',
28
+ '.venv',
29
+ 'venv',
30
+ ]);
31
+ const CODE_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '.vue']);
32
+ const STYLE_EXTENSIONS = new Set(['.css', '.scss', '.sass', '.less']);
33
+ const TRACKED_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '.vue', '.json', '.yaml', '.yml', '.md']);
34
+ const DISCOVERED_EXTENSIONS = new Set([...TRACKED_EXTENSIONS, ...STYLE_EXTENSIONS]);
35
+ export const DEFAULT_INDEX_CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
36
+ const INDEX_PARSE_BATCH_SIZE = 8;
37
+
38
+ export async function buildCodeIndex({ rootDir = process.cwd() } = {}) {
39
+ const absoluteRoot = path.resolve(rootDir);
40
+ const indexDir = getIndexDir(absoluteRoot);
41
+
42
+ const previousFilesArtifact = await readArtifactIfExists(absoluteRoot, INDEX_ARTIFACTS.files);
43
+ const canReusePrevious = previousFilesArtifact?.schemaVersion === INDEX_SCHEMA_VERSION;
44
+ const previousCodeFileRecords = canReusePrevious
45
+ ? (previousFilesArtifact.items ?? []).filter((item) => CODE_EXTENSIONS.has(item?.ext))
46
+ : [];
47
+
48
+ const previousCodeMetaByPath = new Map(
49
+ previousCodeFileRecords.map((item) => [item.filePath, { mtimeMs: Number(item.mtimeMs ?? -1), size: Number(item.size ?? -1) }]),
50
+ );
51
+
52
+ const discoveredFiles = await collectFiles(await resolveScanRoots(absoluteRoot));
53
+ const sourceFingerprint = createSourceFingerprint(absoluteRoot, discoveredFiles);
54
+ const styleFilePaths = discoveredFiles
55
+ .map((entry) => {
56
+ const ext = path.extname(entry.absolutePath).toLowerCase();
57
+ if (!STYLE_EXTENSIONS.has(ext)) {
58
+ return null;
59
+ }
60
+ return normalizeRelative(absoluteRoot, entry.absolutePath);
61
+ })
62
+ .filter(Boolean)
63
+ .sort((a, b) => a.localeCompare(b));
64
+
65
+ const fileRecords = discoveredFiles
66
+ .map((entry) => {
67
+ const ext = path.extname(entry.absolutePath).toLowerCase();
68
+ if (!TRACKED_EXTENSIONS.has(ext)) {
69
+ return null;
70
+ }
71
+
72
+ const filePath = normalizeRelative(absoluteRoot, entry.absolutePath);
73
+ const domain = filePath.split('/')[0] ?? 'other';
74
+
75
+ return {
76
+ filePath,
77
+ domain,
78
+ ext,
79
+ mtimeMs: entry.mtimeMs,
80
+ size: entry.size,
81
+ };
82
+ })
83
+ .filter(Boolean)
84
+ .sort((a, b) => a.filePath.localeCompare(b.filePath));
85
+ const canReuseFilesArtifact = canReusePrevious
86
+ && areFileRecordSnapshotsEqual(previousFilesArtifact?.items ?? [], fileRecords);
87
+
88
+ const codeFiles = fileRecords.filter((record) => CODE_EXTENSIONS.has(record.ext));
89
+
90
+ const symbols = [];
91
+ const imports = [];
92
+ const reusableCodeFiles = [];
93
+ let filesToParse = [];
94
+ let reusedCodeFileCount = 0;
95
+ let canReuseParsedArtifacts = false;
96
+
97
+ for (const file of codeFiles) {
98
+ const previousMeta = previousCodeMetaByPath.get(file.filePath);
99
+ const isUnchanged = previousMeta
100
+ && previousMeta.mtimeMs === file.mtimeMs
101
+ && previousMeta.size === file.size;
102
+
103
+ if (isUnchanged) {
104
+ reusableCodeFiles.push(file.filePath);
105
+ continue;
106
+ }
107
+
108
+ filesToParse.push(file.filePath);
109
+ }
110
+
111
+ if (reusableCodeFiles.length > 0) {
112
+ const [previousSymbolsArtifact, previousImportsArtifact] = await Promise.all([
113
+ readArtifactIfExists(absoluteRoot, INDEX_ARTIFACTS.symbols),
114
+ readArtifactIfExists(absoluteRoot, INDEX_ARTIFACTS.imports),
115
+ ]);
116
+ canReuseParsedArtifacts = previousSymbolsArtifact?.schemaVersion === INDEX_SCHEMA_VERSION
117
+ && previousImportsArtifact?.schemaVersion === INDEX_SCHEMA_VERSION;
118
+
119
+ if (canReuseParsedArtifacts) {
120
+ const previousSymbolsByPath = groupBy(previousSymbolsArtifact.items ?? [], (item) => item.filePath);
121
+ const previousImportsByPath = groupBy(previousImportsArtifact.items ?? [], (item) => item.from);
122
+
123
+ for (const filePath of reusableCodeFiles) {
124
+ symbols.push(...(previousSymbolsByPath.get(filePath) ?? []));
125
+ imports.push(...(previousImportsByPath.get(filePath) ?? []));
126
+ reusedCodeFileCount += 1;
127
+ }
128
+ } else {
129
+ filesToParse = codeFiles.map((file) => file.filePath);
130
+ }
131
+ }
132
+ const canReuseAllParsedArtifacts = canReuseParsedArtifacts && filesToParse.length === 0 && reusableCodeFiles.length === codeFiles.length;
133
+
134
+ for (let start = 0; start < filesToParse.length; start += INDEX_PARSE_BATCH_SIZE) {
135
+ const batch = filesToParse.slice(start, start + INDEX_PARSE_BATCH_SIZE);
136
+ const parsedBatch = (await Promise.all(batch.map(async (filePath) => {
137
+ try {
138
+ const absolutePath = path.join(absoluteRoot, filePath);
139
+ const content = await fs.readFile(absolutePath, 'utf8');
140
+ const scriptContent = extractScriptContent(filePath, content);
141
+ return {
142
+ filePath,
143
+ symbols: extractSymbols(filePath, scriptContent),
144
+ imports: [
145
+ ...extractImports(filePath, scriptContent),
146
+ ...extractSupplementalImports(filePath, content),
147
+ ],
148
+ };
149
+ } catch {
150
+ return null;
151
+ }
152
+ }))).filter(Boolean);
153
+
154
+ for (const parsedFile of parsedBatch) {
155
+ symbols.push(...parsedFile.symbols);
156
+ imports.push(...parsedFile.imports);
157
+ }
158
+ }
159
+
160
+ const canReuseTestsMapCandidate = canReusePrevious
161
+ && haveStableTrackedFileIdentities(previousFilesArtifact?.items ?? [], fileRecords);
162
+ const previousTestsMapArtifact = canReuseTestsMapCandidate
163
+ ? await readArtifactIfExists(absoluteRoot, INDEX_ARTIFACTS.testsMap)
164
+ : null;
165
+ const canReuseTestsMap = canReuseTestsMapCandidate
166
+ && previousTestsMapArtifact?.schemaVersion === INDEX_SCHEMA_VERSION;
167
+ const testsMap = canReuseTestsMap
168
+ ? previousTestsMapArtifact.items
169
+ : buildTestsMap(fileRecords);
170
+ const bugIndexSnapshot = await readBugIndexSnapshot(absoluteRoot);
171
+ const previousHotspotsArtifact = canReusePrevious
172
+ ? await readArtifactIfExists(absoluteRoot, INDEX_ARTIFACTS.hotspots)
173
+ : null;
174
+ const canReuseHotspots = previousHotspotsArtifact?.schemaVersion === INDEX_SCHEMA_VERSION
175
+ && areBugIndexSnapshotsEqual(previousHotspotsArtifact?.sourceSnapshot, bugIndexSnapshot);
176
+ const hotspots = canReuseHotspots
177
+ ? previousHotspotsArtifact.items
178
+ : await buildHotspots(absoluteRoot, bugIndexSnapshot);
179
+
180
+ const currentCodePaths = new Set(codeFiles.map((f) => f.filePath));
181
+ const canReuseArchetypesCandidate = canReusePrevious
182
+ && filesToParse.length === 0
183
+ && codeFiles.every((file) => previousCodeMetaByPath.has(file.filePath));
184
+ const previousArchetypesArtifact = canReuseArchetypesCandidate
185
+ ? await readArtifactIfExists(absoluteRoot, INDEX_ARTIFACTS.archetypes)
186
+ : null;
187
+ const canReuseArchetypes = canReuseArchetypesCandidate
188
+ && previousArchetypesArtifact?.schemaVersion === INDEX_SCHEMA_VERSION
189
+ && previousArchetypesArtifact?.items?.length === codeFiles.length
190
+ && (previousArchetypesArtifact.items ?? []).every((item) => currentCodePaths.has(item.filePath));
191
+
192
+ // Reuse cached archetypes only when every indexed code file is unchanged
193
+ const archetypes = canReuseArchetypes
194
+ ? previousArchetypesArtifact.items
195
+ : classifyArchetypes(fileRecords, symbols);
196
+ const needsAliasContext = importsNeedAliasContext(imports);
197
+ const canReuseGraphArtifactsCandidate = canReusePrevious
198
+ && filesToParse.length === 0
199
+ && haveStableTrackedFileIdentities(previousCodeFileRecords, codeFiles);
200
+ const [previousRelationsArtifact, previousAnalogsArtifact] = canReuseGraphArtifactsCandidate
201
+ ? await Promise.all([
202
+ readArtifactIfExists(absoluteRoot, INDEX_ARTIFACTS.relations),
203
+ readArtifactIfExists(absoluteRoot, INDEX_ARTIFACTS.analogs),
204
+ ])
205
+ : [null, null];
206
+ const importAliasState = needsAliasContext
207
+ ? await loadImportAliasContextState({ rootDir: absoluteRoot })
208
+ : null;
209
+ const importAliasContext = importAliasState?.context ?? null;
210
+ const styleSnapshot = {
211
+ styleFiles: styleFilePaths,
212
+ importAliasSnapshot: importAliasState?.snapshot ?? [],
213
+ };
214
+ const canReuseRelations = canReuseGraphArtifactsCandidate
215
+ && previousRelationsArtifact?.schemaVersion === INDEX_SCHEMA_VERSION
216
+ && areStringArraysEqual(previousRelationsArtifact?.sourceSnapshot?.styleFiles, styleFilePaths)
217
+ && arePathSnapshotsEqual(
218
+ previousRelationsArtifact?.sourceSnapshot?.importAliasSnapshot,
219
+ styleSnapshot.importAliasSnapshot,
220
+ );
221
+ const canReuseAnalogs = canReuseGraphArtifactsCandidate
222
+ && previousAnalogsArtifact?.schemaVersion === INDEX_SCHEMA_VERSION
223
+ && areStringArraysEqual(previousAnalogsArtifact?.sourceSnapshot?.styleFiles, styleFilePaths)
224
+ && arePathSnapshotsEqual(
225
+ previousAnalogsArtifact?.sourceSnapshot?.importAliasSnapshot,
226
+ styleSnapshot.importAliasSnapshot,
227
+ );
228
+ const indexedFileSet = new Set(codeFiles.map((file) => file.filePath));
229
+ const resolvedImportsBySource = canReuseRelations && canReuseAnalogs
230
+ ? null
231
+ : buildResolvedImportMap(
232
+ absoluteRoot,
233
+ imports,
234
+ indexedFileSet,
235
+ importAliasContext,
236
+ );
237
+ const resolvedStyleImportsBySource = canReuseRelations
238
+ ? null
239
+ : buildResolvedImportMap(
240
+ absoluteRoot,
241
+ imports,
242
+ new Set(styleFilePaths),
243
+ importAliasContext,
244
+ );
245
+ const relations = canReuseRelations
246
+ ? previousRelationsArtifact.items
247
+ : buildRelations(
248
+ fileRecords,
249
+ archetypes,
250
+ testsMap,
251
+ resolvedImportsBySource,
252
+ resolvedStyleImportsBySource,
253
+ );
254
+ const analogs = canReuseAnalogs
255
+ ? previousAnalogsArtifact.items
256
+ : buildAnalogs(fileRecords, archetypes, relations, resolvedImportsBySource);
257
+
258
+ const generatedAt = new Date().toISOString();
259
+
260
+ await fs.mkdir(indexDir, { recursive: true });
261
+ if (!canReuseFilesArtifact) {
262
+ await writeArtifact(absoluteRoot, INDEX_ARTIFACTS.files, {
263
+ schemaVersion: INDEX_SCHEMA_VERSION,
264
+ generatedAt,
265
+ rootDir: absoluteRoot,
266
+ items: fileRecords,
267
+ });
268
+ }
269
+ if (!canReuseAllParsedArtifacts) {
270
+ await writeArtifact(absoluteRoot, INDEX_ARTIFACTS.symbols, {
271
+ schemaVersion: INDEX_SCHEMA_VERSION,
272
+ generatedAt,
273
+ rootDir: absoluteRoot,
274
+ items: symbols,
275
+ });
276
+ await writeArtifact(absoluteRoot, INDEX_ARTIFACTS.imports, {
277
+ schemaVersion: INDEX_SCHEMA_VERSION,
278
+ generatedAt,
279
+ rootDir: absoluteRoot,
280
+ items: imports,
281
+ });
282
+ }
283
+ if (!canReuseTestsMap) {
284
+ await writeArtifact(absoluteRoot, INDEX_ARTIFACTS.testsMap, {
285
+ schemaVersion: INDEX_SCHEMA_VERSION,
286
+ generatedAt,
287
+ rootDir: absoluteRoot,
288
+ items: testsMap,
289
+ });
290
+ }
291
+ if (!canReuseHotspots) {
292
+ await writeArtifact(absoluteRoot, INDEX_ARTIFACTS.hotspots, {
293
+ schemaVersion: INDEX_SCHEMA_VERSION,
294
+ generatedAt,
295
+ rootDir: absoluteRoot,
296
+ sourceSnapshot: bugIndexSnapshot,
297
+ items: hotspots,
298
+ });
299
+ }
300
+ if (!canReuseArchetypes) {
301
+ await writeArtifact(absoluteRoot, INDEX_ARTIFACTS.archetypes, {
302
+ schemaVersion: INDEX_SCHEMA_VERSION,
303
+ generatedAt,
304
+ rootDir: absoluteRoot,
305
+ items: archetypes,
306
+ });
307
+ }
308
+ if (!canReuseRelations) {
309
+ await writeArtifact(absoluteRoot, INDEX_ARTIFACTS.relations, {
310
+ schemaVersion: INDEX_SCHEMA_VERSION,
311
+ generatedAt,
312
+ rootDir: absoluteRoot,
313
+ sourceSnapshot: styleSnapshot,
314
+ items: relations,
315
+ });
316
+ }
317
+ if (!canReuseAnalogs) {
318
+ await writeArtifact(absoluteRoot, INDEX_ARTIFACTS.analogs, {
319
+ schemaVersion: INDEX_SCHEMA_VERSION,
320
+ generatedAt,
321
+ rootDir: absoluteRoot,
322
+ sourceSnapshot: styleSnapshot,
323
+ items: analogs,
324
+ });
325
+ }
326
+ await writeArtifact(absoluteRoot, INDEX_ARTIFACTS.meta, {
327
+ schemaVersion: INDEX_SCHEMA_VERSION,
328
+ generatedAt,
329
+ rootDir: absoluteRoot,
330
+ sourceFingerprint,
331
+ });
332
+ const preservedRuntimeCaches = canReuseFilesArtifact
333
+ && canReuseAllParsedArtifacts
334
+ && canReuseTestsMap
335
+ && canReuseHotspots
336
+ && canReuseArchetypes
337
+ && canReuseRelations
338
+ && canReuseAnalogs;
339
+ if (!preservedRuntimeCaches) {
340
+ clearIndexArtifactCache(absoluteRoot);
341
+ clearRelatedTestArtifactCache(absoluteRoot);
342
+ }
343
+
344
+ return {
345
+ indexDir,
346
+ generatedAt,
347
+ generatedAtMs: Date.parse(generatedAt),
348
+ fileCount: fileRecords.length,
349
+ symbolCount: symbols.length,
350
+ importCount: imports.length,
351
+ testsMapCount: testsMap.length,
352
+ hotspotCount: hotspots.length,
353
+ archetypeCount: archetypes.length,
354
+ relationCount: relations.length,
355
+ analogCount: analogs.length,
356
+ parsedCodeFileCount: filesToParse.length,
357
+ reusedCodeFileCount,
358
+ reusedTestsMap: canReuseTestsMap,
359
+ reusedHotspots: canReuseHotspots,
360
+ reusedRelations: canReuseRelations,
361
+ reusedAnalogs: canReuseAnalogs,
362
+ preservedRuntimeCaches,
363
+ };
364
+ }
365
+
366
+ async function collectFiles(scanRoots) {
367
+ const result = [];
368
+ const stack = [...scanRoots];
369
+ const visitedDirectoryRealPaths = new Set();
370
+
371
+ while (stack.length > 0) {
372
+ const current = stack.pop();
373
+ let currentRealPath;
374
+ try {
375
+ currentRealPath = await fs.realpath(current);
376
+ } catch {
377
+ continue;
378
+ }
379
+
380
+ if (visitedDirectoryRealPaths.has(currentRealPath)) {
381
+ continue;
382
+ }
383
+ visitedDirectoryRealPaths.add(currentRealPath);
384
+
385
+ let entries;
386
+ try {
387
+ entries = await fs.readdir(current, { withFileTypes: true });
388
+ } catch {
389
+ continue;
390
+ }
391
+
392
+ const symlinkDirectories = [];
393
+ const directDirectories = [];
394
+ const symlinkFiles = [];
395
+ for (const entry of entries) {
396
+ const fullPath = path.join(current, entry.name);
397
+ if (entry.isDirectory()) {
398
+ if (shouldSkipDirectory(entry.name)) {
399
+ continue;
400
+ }
401
+ directDirectories.push(fullPath);
402
+ } else if (entry.isSymbolicLink()) {
403
+ try {
404
+ const stat = await fs.stat(fullPath);
405
+ if (stat.isDirectory() && !shouldSkipDirectory(entry.name)) {
406
+ symlinkDirectories.push(fullPath);
407
+ continue;
408
+ }
409
+
410
+ if (stat.isFile()) {
411
+ const ext = path.extname(entry.name).toLowerCase();
412
+ if (DISCOVERED_EXTENSIONS.has(ext)) {
413
+ symlinkFiles.push({
414
+ absolutePath: fullPath,
415
+ mtimeMs: Math.floor(stat.mtimeMs),
416
+ size: stat.size,
417
+ });
418
+ }
419
+ }
420
+ } catch {
421
+ // broken symlink, skip
422
+ }
423
+ }
424
+ }
425
+ stack.push(...symlinkDirectories, ...directDirectories);
426
+
427
+ const fileEntries = entries.filter((entry) => {
428
+ if (!entry.isFile()) {
429
+ return false;
430
+ }
431
+ const ext = path.extname(entry.name).toLowerCase();
432
+ return DISCOVERED_EXTENSIONS.has(ext);
433
+ });
434
+ const fileStats = (await Promise.all(fileEntries.map(async (entry) => {
435
+ try {
436
+ const fullPath = path.join(current, entry.name);
437
+ const stat = await fs.stat(fullPath);
438
+ return {
439
+ absolutePath: fullPath,
440
+ mtimeMs: Math.floor(stat.mtimeMs),
441
+ size: stat.size,
442
+ };
443
+ } catch {
444
+ return null;
445
+ }
446
+ }))).filter(Boolean);
447
+ result.push(...fileStats, ...symlinkFiles);
448
+ }
449
+
450
+ return result;
451
+ }
452
+
453
+ function extractScriptContent(filePath, content) {
454
+ if (!filePath.endsWith('.vue')) {
455
+ return content;
456
+ }
457
+
458
+ const allMatches = [...content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/gi)];
459
+ if (allMatches.length === 0) return '';
460
+ return allMatches.map((m) => m[1]).join('\n');
461
+ }
462
+
463
+ function extractSymbols(filePath, content) {
464
+ const symbols = [];
465
+ const addSymbol = (name, type) => {
466
+ if (!name) {
467
+ return;
468
+ }
469
+ symbols.push({
470
+ name,
471
+ type,
472
+ filePath,
473
+ });
474
+ };
475
+ const addMatches = (regex, type, groupIndex = 1) => {
476
+ for (const match of content.matchAll(regex)) {
477
+ const symbolName = match[groupIndex];
478
+ if (!symbolName) {
479
+ continue;
480
+ }
481
+ addSymbol(symbolName, type);
482
+ }
483
+ };
484
+
485
+ addMatches(/export\s+(?:async\s+)?function\s+([A-Za-z_$][A-Za-z0-9_$]*)/g, 'function');
486
+ addMatches(/export\s+class\s+([A-Za-z_$][A-Za-z0-9_$]*)/g, 'class');
487
+ addMatches(/(?:const|let|var)\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*(?:async\s*)?\(/g, 'callable');
488
+ addMatches(/(?:const|let|var)\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][A-Za-z0-9_$]*)\s*=>/g, 'callable');
489
+ addMatches(/export\s+default\s+(?:async\s+)?function\s+([A-Za-z_$][A-Za-z0-9_$]*)/g, 'default-function');
490
+ addMatches(/export\s+default\s+class\s+([A-Za-z_$][A-Za-z0-9_$]*)/g, 'default-class');
491
+ addMatches(/export\s+default\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*(?:;|\n|$)/g, 'default-reference');
492
+ addMatches(/defineOptions\(\s*\{[\s\S]*?name\s*:\s*['"]([^'"]+)['"]/g, 'component-name');
493
+ addMatches(/defineComponent\(\s*\{[\s\S]*?name\s*:\s*['"]([^'"]+)['"]/g, 'component-name');
494
+ addMatches(/export\s+default\s*\{[\s\S]*?name\s*:\s*['"]([^'"]+)['"]/g, 'component-name');
495
+
496
+ if (/\bexport\s+default\b/.test(content)) {
497
+ addSymbol('default', 'default-export');
498
+ }
499
+
500
+ for (const match of content.matchAll(/export\s*\{([^}]+)\}/g)) {
501
+ const block = match[1] ?? '';
502
+ const candidates = block.split(',').map((part) => part.trim()).filter(Boolean);
503
+ for (const candidate of candidates) {
504
+ const aliasMatch = candidate.match(/^([A-Za-z_$][A-Za-z0-9_$]*)(?:\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*))?$/);
505
+ if (!aliasMatch) {
506
+ continue;
507
+ }
508
+ addSymbol(aliasMatch[2] ?? aliasMatch[1], 'named-export');
509
+ }
510
+ }
511
+
512
+ return dedupeBy(symbols, (item) => `${item.filePath}:${item.type}:${item.name}`);
513
+ }
514
+
515
+ function extractImports(filePath, content) {
516
+ // Strip comments to avoid false-positive imports from commented-out code
517
+ const stripped = content
518
+ .replace(/\/\*[\s\S]*?\*\//g, '') // block comments
519
+ .replace(/\/\/.*$/gm, ''); // line comments
520
+ const imports = [];
521
+
522
+ for (const match of stripped.matchAll(/import\s+(?:[^'";]+?\s+from\s+)?['"]([^'"]+)['"]/g)) {
523
+ imports.push({
524
+ from: filePath,
525
+ to: match[1],
526
+ kind: 'import',
527
+ });
528
+ }
529
+
530
+ for (const match of stripped.matchAll(/import\(\s*['"]([^'"]+)['"]\s*\)/g)) {
531
+ imports.push({
532
+ from: filePath,
533
+ to: match[1],
534
+ kind: 'dynamic-import',
535
+ });
536
+ }
537
+
538
+ for (const match of stripped.matchAll(/require\(\s*['"]([^'"]+)['"]\s*\)/g)) {
539
+ imports.push({
540
+ from: filePath,
541
+ to: match[1],
542
+ kind: 'require',
543
+ });
544
+ }
545
+
546
+ for (const match of stripped.matchAll(/export\s+(?:type\s+)?\{[^}]+\}\s+from\s+['"]([^'"]+)['"]/g)) {
547
+ imports.push({
548
+ from: filePath,
549
+ to: match[1],
550
+ kind: 're-export',
551
+ });
552
+ }
553
+
554
+ for (const match of stripped.matchAll(/export\s+\*(?:\s+as\s+[A-Za-z_$][A-Za-z0-9_$]*)?\s+from\s+['"]([^'"]+)['"]/g)) {
555
+ imports.push({
556
+ from: filePath,
557
+ to: match[1],
558
+ kind: 're-export-all',
559
+ });
560
+ }
561
+
562
+ return imports;
563
+ }
564
+
565
+ function extractSupplementalImports(filePath, content) {
566
+ if (!filePath.endsWith('.vue')) {
567
+ return [];
568
+ }
569
+
570
+ const imports = [];
571
+ for (const match of content.matchAll(/<style\b[^>]*\bsrc=['"]([^'"]+)['"][^>]*>/gi)) {
572
+ imports.push({
573
+ from: filePath,
574
+ to: match[1],
575
+ kind: 'style-src',
576
+ });
577
+ }
578
+
579
+ return imports;
580
+ }
581
+
582
+ function buildTestsMap(fileRecords) {
583
+ const testFiles = fileRecords.filter((file) => isLikelyTestFile(file.filePath));
584
+ const sourceFiles = fileRecords.filter((file) => CODE_EXTENSIONS.has(file.ext) && !isLikelyTestFile(file.filePath));
585
+
586
+ return sourceFiles.map((sourceFile) => {
587
+ const matches = testFiles
588
+ .map((testFile) => ({
589
+ filePath: testFile.filePath,
590
+ score: scoreTestMatch(sourceFile.filePath, testFile.filePath),
591
+ }))
592
+ .filter((match) => match.score > 0)
593
+ .sort((a, b) => b.score - a.score || a.filePath.localeCompare(b.filePath))
594
+ .map((match) => match.filePath)
595
+ .slice(0, 10);
596
+
597
+ return {
598
+ sourceFile: sourceFile.filePath,
599
+ tests: matches,
600
+ };
601
+ });
602
+ }
603
+
604
+ async function buildHotspots(rootDir, bugIndexSnapshot = null) {
605
+ const snapshot = bugIndexSnapshot ?? await readBugIndexSnapshot(rootDir);
606
+ if (!snapshot.exists) {
607
+ return [];
608
+ }
609
+ const bugIndexPath = path.join(rootDir, 'docs', 'BUG_INDEX.md');
610
+ let content;
611
+ try {
612
+ content = await fs.readFile(bugIndexPath, 'utf8');
613
+ } catch {
614
+ return [];
615
+ }
616
+
617
+ const scores = new Map();
618
+ const filePattern = /([A-Za-z0-9_@()[\].-]+(?:\/[A-Za-z0-9_@()[\].-]+)+\.(?:tsx|jsx|ts|js|mjs|cjs|vue))/g;
619
+
620
+ for (const match of content.matchAll(filePattern)) {
621
+ const filePath = match[1];
622
+ scores.set(filePath, (scores.get(filePath) ?? 0) + 1);
623
+ }
624
+
625
+ return [...scores.entries()]
626
+ .map(([filePath, count]) => ({ filePath, count }))
627
+ .sort((a, b) => b.count - a.count || a.filePath.localeCompare(b.filePath));
628
+ }
629
+
630
+ async function readBugIndexSnapshot(rootDir) {
631
+ const bugIndexPath = path.join(rootDir, 'docs', 'BUG_INDEX.md');
632
+
633
+ try {
634
+ const stat = await fs.stat(bugIndexPath);
635
+ return {
636
+ exists: true,
637
+ mtimeMs: Math.floor(stat.mtimeMs),
638
+ size: stat.size,
639
+ };
640
+ } catch {
641
+ return { exists: false };
642
+ }
643
+ }
644
+
645
+ function areBugIndexSnapshotsEqual(previousSnapshot, nextSnapshot) {
646
+ if (!previousSnapshot || !nextSnapshot) {
647
+ return false;
648
+ }
649
+
650
+ if (previousSnapshot.exists !== nextSnapshot.exists) {
651
+ return false;
652
+ }
653
+
654
+ if (!previousSnapshot.exists) {
655
+ return true;
656
+ }
657
+
658
+ return previousSnapshot.mtimeMs === nextSnapshot.mtimeMs
659
+ && previousSnapshot.size === nextSnapshot.size;
660
+ }
661
+
662
+ async function resolveScanRoots(rootDir) {
663
+ const concreteRoots = [];
664
+
665
+ for (const relativeDir of SOURCE_DIRS) {
666
+ const dirPath = path.join(rootDir, relativeDir);
667
+ try {
668
+ const stat = await fs.stat(dirPath);
669
+ if (stat.isDirectory()) {
670
+ concreteRoots.push(dirPath);
671
+ }
672
+ } catch {
673
+ // ignore
674
+ }
675
+ }
676
+
677
+ return concreteRoots.length > 0 ? concreteRoots : [rootDir];
678
+ }
679
+
680
+ function shouldSkipDirectory(name) {
681
+ if (EXCLUDED_DIR_NAMES.has(name)) {
682
+ return true;
683
+ }
684
+
685
+ return name.startsWith('.') && name !== '.claude' && name !== '.codex' && name !== '.antigravity';
686
+ }
687
+
688
+ function isLikelyTestFile(filePath) {
689
+ const lower = filePath.toLowerCase();
690
+ return (
691
+ lower.startsWith('__tests__/')
692
+ || lower.startsWith('test/')
693
+ || lower.startsWith('tests/')
694
+ || lower.startsWith('spec/')
695
+ || lower.startsWith('specs/')
696
+ || lower.includes('/__tests__/')
697
+ || lower.includes('/test/')
698
+ || lower.includes('/spec/')
699
+ || lower.includes('/specs/')
700
+ || lower.endsWith('.test.js')
701
+ || lower.endsWith('.test.ts')
702
+ || lower.endsWith('.test.vue')
703
+ || lower.endsWith('.test.tsx')
704
+ || lower.endsWith('.test.jsx')
705
+ || lower.endsWith('.test.mjs')
706
+ || lower.endsWith('.test.cjs')
707
+ || lower.endsWith('.spec.js')
708
+ || lower.endsWith('.spec.ts')
709
+ || lower.endsWith('.spec.vue')
710
+ || lower.endsWith('.spec.tsx')
711
+ || lower.endsWith('.spec.jsx')
712
+ || lower.endsWith('.spec.mjs')
713
+ || lower.endsWith('.spec.cjs')
714
+ );
715
+ }
716
+
717
+ function createSourceFingerprint(rootDir, discoveredFiles) {
718
+ const hash = crypto.createHash('sha256');
719
+ const normalizedEntries = discoveredFiles
720
+ .map((entry) => {
721
+ const filePath = normalizeRelative(rootDir, entry.absolutePath);
722
+ if (!filePath || filePath === '.' || filePath.startsWith('../') || path.isAbsolute(filePath)) {
723
+ return null;
724
+ }
725
+ return {
726
+ filePath,
727
+ mtimeMs: Number(entry.mtimeMs ?? -1),
728
+ size: Number(entry.size ?? -1),
729
+ };
730
+ })
731
+ .filter(Boolean)
732
+ .sort((a, b) => a.filePath.localeCompare(b.filePath));
733
+
734
+ let newestMtimeMs = 0;
735
+ for (const entry of normalizedEntries) {
736
+ newestMtimeMs = Math.max(newestMtimeMs, entry.mtimeMs);
737
+ hash.update(entry.filePath);
738
+ hash.update('\0');
739
+ hash.update(String(entry.mtimeMs));
740
+ hash.update('\0');
741
+ hash.update(String(entry.size));
742
+ hash.update('\n');
743
+ }
744
+
745
+ return {
746
+ fileCount: normalizedEntries.length,
747
+ newestMtimeMs,
748
+ digest: hash.digest('base64url'),
749
+ };
750
+ }
751
+
752
+ function areSourceFingerprintsEqual(previousFingerprint, nextFingerprint) {
753
+ return previousFingerprint?.fileCount === nextFingerprint?.fileCount
754
+ && previousFingerprint?.newestMtimeMs === nextFingerprint?.newestMtimeMs
755
+ && previousFingerprint?.digest === nextFingerprint?.digest;
756
+ }
757
+
758
+ async function writeArtifact(rootDir, artifactName, payload) {
759
+ const artifactPath = getArtifactPath(rootDir, artifactName);
760
+ await fs.writeFile(artifactPath, JSON.stringify(payload, null, 2));
761
+ }
762
+
763
+ async function readArtifactIfExists(rootDir, artifactName) {
764
+ try {
765
+ const artifactPath = getArtifactPath(rootDir, artifactName);
766
+ const raw = await fs.readFile(artifactPath, 'utf8');
767
+ return JSON.parse(raw);
768
+ } catch {
769
+ return null;
770
+ }
771
+ }
772
+
773
+ function groupBy(items, keyFn) {
774
+ const map = new Map();
775
+ for (const item of items) {
776
+ const key = keyFn(item);
777
+ if (!map.has(key)) {
778
+ map.set(key, []);
779
+ }
780
+ map.get(key).push(item);
781
+ }
782
+ return map;
783
+ }
784
+
785
+ function dedupeBy(list, keyFn) {
786
+ const seen = new Set();
787
+ const result = [];
788
+
789
+ for (const item of list) {
790
+ const key = keyFn(item);
791
+ if (seen.has(key)) {
792
+ continue;
793
+ }
794
+ seen.add(key);
795
+ result.push(item);
796
+ }
797
+
798
+ return result;
799
+ }
800
+
801
+ function haveStableTrackedFileIdentities(previousRecords, nextRecords) {
802
+ if (previousRecords.length !== nextRecords.length) {
803
+ return false;
804
+ }
805
+
806
+ return previousRecords.every((record, index) => {
807
+ const nextRecord = nextRecords[index];
808
+ return nextRecord
809
+ && record?.filePath === nextRecord.filePath
810
+ && record?.ext === nextRecord.ext;
811
+ });
812
+ }
813
+
814
+ function areStringArraysEqual(previousValues, nextValues) {
815
+ if (!Array.isArray(previousValues) || !Array.isArray(nextValues)) {
816
+ return false;
817
+ }
818
+
819
+ if (previousValues.length !== nextValues.length) {
820
+ return false;
821
+ }
822
+
823
+ return previousValues.every((value, index) => value === nextValues[index]);
824
+ }
825
+
826
+ function arePathSnapshotsEqual(previousSnapshot, nextSnapshot) {
827
+ if (!Array.isArray(previousSnapshot) || !Array.isArray(nextSnapshot)) {
828
+ return false;
829
+ }
830
+
831
+ if (previousSnapshot.length !== nextSnapshot.length) {
832
+ return false;
833
+ }
834
+
835
+ return previousSnapshot.every((entry, index) => {
836
+ const nextEntry = nextSnapshot[index];
837
+ return nextEntry
838
+ && entry?.filePath === nextEntry.filePath
839
+ && entry?.exists === nextEntry.exists
840
+ && entry?.size === nextEntry.size
841
+ && entry?.mtimeMs === nextEntry.mtimeMs;
842
+ });
843
+ }
844
+
845
+ function areFileRecordSnapshotsEqual(previousRecords, nextRecords) {
846
+ if (previousRecords.length !== nextRecords.length) {
847
+ return false;
848
+ }
849
+
850
+ return previousRecords.every((record, index) => {
851
+ const nextRecord = nextRecords[index];
852
+ return nextRecord
853
+ && record?.filePath === nextRecord.filePath
854
+ && record?.domain === nextRecord.domain
855
+ && record?.ext === nextRecord.ext
856
+ && Number(record?.mtimeMs ?? -1) === Number(nextRecord.mtimeMs ?? -1)
857
+ && Number(record?.size ?? -1) === Number(nextRecord.size ?? -1);
858
+ });
859
+ }
860
+
861
+ // ── Index V2: Archetype Classification ──
862
+
863
+ const ARCHETYPE_RULES = [
864
+ // Test (check first)
865
+ { pattern: /(?:^|\/)(?:__tests__|test|tests|spec|specs)\//, archetype: 'test' },
866
+ // FE page subtypes
867
+ { pattern: /(?:^|\/)pages\/.*(?:List|list)\b/, archetype: 'list-page' },
868
+ { pattern: /(?:^|\/)pages\/.*(?:Detail|detail|View|view)\b/, archetype: 'detail-page' },
869
+ { pattern: /(?:^|\/)pages\/.*(?:Form|form|Edit|edit|Create|create|Add|add)\b/, archetype: 'form-page' },
870
+ { pattern: /(?:^|\/)pages\/|^app\.vue$|^layouts\//, archetype: 'page' },
871
+ // FE component subtypes
872
+ { pattern: /(?:^|\/)components\/.*(?:Modal|modal|Popup|popup|Dialog|dialog)\b/, archetype: 'modal' },
873
+ { pattern: /(?:^|\/)components\/.*(?:Grid|grid|Table|table)\b/, archetype: 'grid' },
874
+ { pattern: /(?:^|\/)components\//, archetype: 'component' },
875
+ // Composables / hooks
876
+ { pattern: /(?:^|\/)composables\/|^use[A-Z]/, archetype: 'composable' },
877
+ { pattern: /(?:^|\/)hooks\//, archetype: 'hook' },
878
+ // API / service layers
879
+ { pattern: /(?:^|\/)(?:api|apis?|services?)\//, archetype: 'api' },
880
+ // Store / state
881
+ { pattern: /(?:^|\/)store(?:s)?\//, archetype: 'store' },
882
+ // Utils / helpers
883
+ { pattern: /(?:^|\/)(?:utils?|helpers?)\//, archetype: 'util' },
884
+ // Middleware
885
+ { pattern: /(?:^|\/)middleware\//, archetype: 'middleware' },
886
+ // Layout
887
+ { pattern: /(?:^|\/)layouts?\//, archetype: 'layout' },
888
+ // Config
889
+ { pattern: /(?:^|\/)(?:config|conf|settings)\./, archetype: 'config' },
890
+ // Migration
891
+ { pattern: /(?:^|\/)migrations?\//, archetype: 'migration' },
892
+ // Schema / model
893
+ { pattern: /(?:^|\/)(?:models?|schemas?)\//, archetype: 'schema' },
894
+ ];
895
+
896
+ function classifyArchetypes(fileRecords, symbols) {
897
+ const symbolNamesByPath = groupBy(symbols, (s) => s.filePath);
898
+
899
+ return fileRecords
900
+ .filter((f) => CODE_EXTENSIONS.has(f.ext))
901
+ .map((file) => {
902
+ if (isLikelyTestFile(file.filePath)) {
903
+ return {
904
+ filePath: file.filePath,
905
+ archetype: 'test',
906
+ };
907
+ }
908
+
909
+ let archetype = null;
910
+
911
+ for (const rule of ARCHETYPE_RULES) {
912
+ if (rule.pattern.test(file.filePath)) {
913
+ archetype = rule.archetype;
914
+ break;
915
+ }
916
+ }
917
+
918
+ if (!archetype) {
919
+ const fileSymbols = symbolNamesByPath.get(file.filePath) ?? [];
920
+ const hasDefaultExport = fileSymbols.some((s) => s.name === 'default');
921
+ const hasComponentSignal = file.ext === '.vue'
922
+ || fileSymbols.some((s) =>
923
+ s.type === 'component-name'
924
+ || s.name.toLowerCase().includes('component')
925
+ || s.name.toLowerCase().includes('page'),
926
+ );
927
+
928
+ if ((hasDefaultExport || file.ext === '.vue') && hasComponentSignal) {
929
+ archetype = 'component';
930
+ } else {
931
+ archetype = 'other';
932
+ }
933
+ }
934
+
935
+ return {
936
+ filePath: file.filePath,
937
+ archetype,
938
+ };
939
+ });
940
+ }
941
+
942
+ // ── Index V2: Feature Relations ──
943
+
944
+ function buildRelations(fileRecords, archetypes, testsMap, resolvedImportsBySource, resolvedStyleImportsBySource = new Map()) {
945
+ const archetypeMap = new Map(archetypes.map((a) => [a.filePath, a.archetype]));
946
+ const testsBySource = new Map((testsMap ?? []).map((t) => [t.sourceFile, t.tests ?? []]));
947
+ const codeFiles = fileRecords.filter((f) => CODE_EXTENSIONS.has(f.ext));
948
+ const featureDirByFile = new Map(codeFiles.map((file) => [file.filePath, getFeatureDir(file.filePath)]));
949
+ const siblingsByKey = groupBy(
950
+ codeFiles,
951
+ (file) => `${featureDirByFile.get(file.filePath)}::${archetypeMap.get(file.filePath) ?? 'other'}`,
952
+ );
953
+
954
+ return codeFiles.map((file) => {
955
+ const filePath = file.filePath;
956
+ const archetype = archetypeMap.get(filePath) ?? 'other';
957
+ const fileImports = [...(resolvedImportsBySource.get(filePath) ?? new Set())];
958
+ const styleImports = [...(resolvedStyleImportsBySource.get(filePath) ?? new Set())];
959
+ const featureDir = featureDirByFile.get(filePath) ?? '';
960
+
961
+ const relations = {};
962
+
963
+ for (const resolved of fileImports) {
964
+ const targetArchetype = archetypeMap.get(resolved) ?? guessArchetypeFromPath(resolved);
965
+ if (!targetArchetype) {
966
+ continue;
967
+ }
968
+
969
+ if (targetArchetype === 'component' || targetArchetype === 'modal' || targetArchetype === 'grid') {
970
+ relations.components ??= [];
971
+ if (!relations.components.includes(resolved)) {
972
+ relations.components.push(resolved);
973
+ }
974
+ } else if (targetArchetype === 'composable' || targetArchetype === 'hook') {
975
+ relations.composables ??= [];
976
+ if (!relations.composables.includes(resolved)) {
977
+ relations.composables.push(resolved);
978
+ }
979
+ } else if (targetArchetype === 'api' || targetArchetype === 'service') {
980
+ relations.services ??= [];
981
+ if (!relations.services.includes(resolved)) {
982
+ relations.services.push(resolved);
983
+ }
984
+ } else if (targetArchetype === 'store') {
985
+ relations.stores ??= [];
986
+ if (!relations.stores.includes(resolved)) {
987
+ relations.stores.push(resolved);
988
+ }
989
+ } else if (targetArchetype === 'util') {
990
+ relations.utils ??= [];
991
+ if (!relations.utils.includes(resolved)) {
992
+ relations.utils.push(resolved);
993
+ }
994
+ }
995
+ }
996
+
997
+ const relatedTests = testsBySource.get(filePath) ?? [];
998
+ if (relatedTests.length > 0) {
999
+ relations.tests = relatedTests;
1000
+ }
1001
+
1002
+ if (styleImports.length > 0) {
1003
+ relations.styles = styleImports;
1004
+ }
1005
+
1006
+ const siblings = (siblingsByKey.get(`${featureDir}::${archetype}`) ?? [])
1007
+ .filter((candidate) => candidate.filePath !== filePath)
1008
+ .map((candidate) => candidate.filePath);
1009
+ if (siblings.length > 0) {
1010
+ relations.siblings = siblings;
1011
+ }
1012
+
1013
+ const hasAnyRelation = Object.keys(relations).length > 0;
1014
+ return {
1015
+ filePath,
1016
+ archetype,
1017
+ ...(hasAnyRelation ? { relations } : {}),
1018
+ };
1019
+ });
1020
+ }
1021
+
1022
+ function getFeatureDir(filePath) {
1023
+ const parts = filePath.split('/');
1024
+ // e.g., "src/pages/customer/List.vue" → "src/pages/customer"
1025
+ // or "src/components/CustomerGrid.vue" → "src/components"
1026
+ if (parts.length >= 3) {
1027
+ return parts.slice(0, 3).join('/');
1028
+ }
1029
+ if (parts.length >= 2) {
1030
+ return parts.slice(0, 2).join('/');
1031
+ }
1032
+ return parts[0] ?? '';
1033
+ }
1034
+
1035
+ function guessArchetypeFromPath(filePath) {
1036
+ for (const rule of ARCHETYPE_RULES) {
1037
+ if (rule.pattern.test(filePath)) {
1038
+ return rule.archetype;
1039
+ }
1040
+ }
1041
+ return null;
1042
+ }
1043
+
1044
+ // ── Index V2: Analog Candidates ──
1045
+
1046
+ const ANALOG_RELATION_TYPES = ['components', 'composables', 'services'];
1047
+
1048
+ function buildAnalogs(fileRecords, archetypes, relations, resolvedImportsByFile) {
1049
+ const archetypeMap = new Map(archetypes.map((a) => [a.filePath, a.archetype]));
1050
+ const codeFiles = fileRecords.filter((f) => CODE_EXTENSIONS.has(f.ext));
1051
+ const relationLookupByFile = new Map(
1052
+ relations.map((relation) => [relation.filePath, buildAnalogRelationLookup(relation.relations ?? {})]),
1053
+ );
1054
+ const descriptors = codeFiles.map((file) => buildAnalogDescriptor({
1055
+ filePath: file.filePath,
1056
+ archetype: archetypeMap.get(file.filePath) ?? 'other',
1057
+ imports: resolvedImportsByFile.get(file.filePath) ?? new Set(),
1058
+ relationLookup: relationLookupByFile.get(file.filePath) ?? buildAnalogRelationLookup(),
1059
+ }));
1060
+ const analogIndexesByArchetype = buildAnalogIndexes(descriptors);
1061
+
1062
+ return descriptors.map((descriptor) => {
1063
+ const analogIndex = analogIndexesByArchetype.get(descriptor.archetype) ?? createEmptyAnalogIndex();
1064
+ const candidateScores = new Map();
1065
+
1066
+ for (const importPath of descriptor.imports) {
1067
+ accumulateAnalogCandidates({
1068
+ candidatePaths: analogIndex.byImport.get(importPath),
1069
+ selfPath: descriptor.filePath,
1070
+ candidateScores,
1071
+ scoreDelta: 3,
1072
+ applyReason: (candidate) => candidate.sharedImports.add(importPath),
1073
+ });
1074
+ }
1075
+
1076
+ if (descriptor.featureDir) {
1077
+ accumulateAnalogCandidates({
1078
+ candidatePaths: analogIndex.byFeatureDir.get(descriptor.featureDir),
1079
+ selfPath: descriptor.filePath,
1080
+ candidateScores,
1081
+ scoreDelta: 2,
1082
+ applyReason: (candidate) => {
1083
+ candidate.sameFeatureDir = true;
1084
+ },
1085
+ });
1086
+ }
1087
+
1088
+ for (const relType of ANALOG_RELATION_TYPES) {
1089
+ for (const target of descriptor.relationLookup[relType]) {
1090
+ accumulateAnalogCandidates({
1091
+ candidatePaths: analogIndex.byRelationTarget.get(`${relType}:${target}`),
1092
+ selfPath: descriptor.filePath,
1093
+ candidateScores,
1094
+ scoreDelta: 2,
1095
+ applyReason: (candidate) => {
1096
+ candidate.sharedRelations[relType].add(target);
1097
+ },
1098
+ });
1099
+ }
1100
+ }
1101
+
1102
+ if (descriptor.namePrefix3.length >= 3) {
1103
+ accumulateAnalogCandidates({
1104
+ candidatePaths: analogIndex.byNamePrefix.get(descriptor.namePrefix3),
1105
+ selfPath: descriptor.filePath,
1106
+ candidateScores,
1107
+ scoreDelta: 1,
1108
+ applyReason: (candidate) => {
1109
+ candidate.namePrefix = descriptor.namePrefix3;
1110
+ },
1111
+ });
1112
+ }
1113
+
1114
+ const analogs = [...candidateScores.entries()]
1115
+ .map(([filePath, candidate]) => ({
1116
+ filePath,
1117
+ score: Math.min(candidate.score / 10, 1),
1118
+ reason: formatAnalogReasons(candidate),
1119
+ }))
1120
+ .filter((candidate) => candidate.reason)
1121
+ .sort((a, b) => b.score - a.score || a.filePath.localeCompare(b.filePath))
1122
+ .slice(0, 5);
1123
+
1124
+ return {
1125
+ filePath: descriptor.filePath,
1126
+ archetype: descriptor.archetype,
1127
+ analogs,
1128
+ };
1129
+ });
1130
+ }
1131
+
1132
+ function buildAnalogDescriptor({ filePath, archetype, imports, relationLookup }) {
1133
+ const baseName = path.basename(filePath, path.extname(filePath)).toLowerCase();
1134
+
1135
+ return {
1136
+ filePath,
1137
+ archetype,
1138
+ featureDir: getFeatureDir(filePath),
1139
+ imports,
1140
+ relationLookup,
1141
+ namePrefix3: baseName.slice(0, 3),
1142
+ };
1143
+ }
1144
+
1145
+ function buildAnalogIndexes(descriptors) {
1146
+ const indexesByArchetype = new Map();
1147
+
1148
+ for (const descriptor of descriptors) {
1149
+ let analogIndex = indexesByArchetype.get(descriptor.archetype);
1150
+ if (!analogIndex) {
1151
+ analogIndex = createEmptyAnalogIndex();
1152
+ indexesByArchetype.set(descriptor.archetype, analogIndex);
1153
+ }
1154
+
1155
+ for (const importPath of descriptor.imports) {
1156
+ indexAnalogValue(analogIndex.byImport, importPath, descriptor.filePath);
1157
+ }
1158
+
1159
+ if (descriptor.featureDir) {
1160
+ indexAnalogValue(analogIndex.byFeatureDir, descriptor.featureDir, descriptor.filePath);
1161
+ }
1162
+
1163
+ for (const relType of ANALOG_RELATION_TYPES) {
1164
+ for (const target of descriptor.relationLookup[relType]) {
1165
+ indexAnalogValue(analogIndex.byRelationTarget, `${relType}:${target}`, descriptor.filePath);
1166
+ }
1167
+ }
1168
+
1169
+ if (descriptor.namePrefix3.length >= 3) {
1170
+ indexAnalogValue(analogIndex.byNamePrefix, descriptor.namePrefix3, descriptor.filePath);
1171
+ }
1172
+ }
1173
+
1174
+ return indexesByArchetype;
1175
+ }
1176
+
1177
+ function createEmptyAnalogIndex() {
1178
+ return {
1179
+ byImport: new Map(),
1180
+ byFeatureDir: new Map(),
1181
+ byRelationTarget: new Map(),
1182
+ byNamePrefix: new Map(),
1183
+ };
1184
+ }
1185
+
1186
+ function indexAnalogValue(indexMap, key, filePath) {
1187
+ if (!key) {
1188
+ return;
1189
+ }
1190
+
1191
+ if (!indexMap.has(key)) {
1192
+ indexMap.set(key, new Set());
1193
+ }
1194
+ indexMap.get(key).add(filePath);
1195
+ }
1196
+
1197
+ function buildAnalogRelationLookup(relations = {}) {
1198
+ return {
1199
+ components: new Set(relations.components ?? []),
1200
+ composables: new Set(relations.composables ?? []),
1201
+ services: new Set(relations.services ?? []),
1202
+ };
1203
+ }
1204
+
1205
+ function accumulateAnalogCandidates({
1206
+ candidatePaths,
1207
+ selfPath,
1208
+ candidateScores,
1209
+ scoreDelta,
1210
+ applyReason,
1211
+ }) {
1212
+ if (!candidatePaths) {
1213
+ return;
1214
+ }
1215
+
1216
+ for (const candidatePath of candidatePaths) {
1217
+ if (candidatePath === selfPath) {
1218
+ continue;
1219
+ }
1220
+
1221
+ if (!candidateScores.has(candidatePath)) {
1222
+ candidateScores.set(candidatePath, {
1223
+ score: 0,
1224
+ sharedImports: new Set(),
1225
+ sameFeatureDir: false,
1226
+ sharedRelations: buildAnalogRelationLookup(),
1227
+ namePrefix: '',
1228
+ });
1229
+ }
1230
+
1231
+ const candidate = candidateScores.get(candidatePath);
1232
+ candidate.score += scoreDelta;
1233
+ applyReason(candidate);
1234
+ }
1235
+ }
1236
+
1237
+ function formatAnalogReasons(candidate) {
1238
+ const reasons = [];
1239
+
1240
+ if (candidate.sharedImports.size > 0) {
1241
+ reasons.push(`shared imports: ${[...candidate.sharedImports].slice(0, 3).join(', ')}`);
1242
+ }
1243
+ if (candidate.sameFeatureDir) {
1244
+ reasons.push('same feature dir');
1245
+ }
1246
+ for (const relType of ANALOG_RELATION_TYPES) {
1247
+ const sharedTargets = [...candidate.sharedRelations[relType]];
1248
+ if (sharedTargets.length > 0) {
1249
+ reasons.push(`shared ${relType}: ${sharedTargets.join(', ')}`);
1250
+ }
1251
+ }
1252
+ if (candidate.namePrefix) {
1253
+ reasons.push(`similar name prefix: ${candidate.namePrefix}`);
1254
+ }
1255
+
1256
+ return reasons.join('; ');
1257
+ }
1258
+
1259
+ function buildResolvedImportMap(rootDir, imports, indexedFileSet, importAliasContext) {
1260
+ const resolvedImportsBySource = new Map();
1261
+
1262
+ for (const imp of imports) {
1263
+ if (!imp?.from || !imp?.to) {
1264
+ continue;
1265
+ }
1266
+
1267
+ const resolved = resolveImportSpecifier({
1268
+ rootDir,
1269
+ fromFilePath: imp.from,
1270
+ specifier: imp.to,
1271
+ indexedFileSet,
1272
+ aliasContext: importAliasContext,
1273
+ });
1274
+ if (!resolved) {
1275
+ continue;
1276
+ }
1277
+
1278
+ if (!resolvedImportsBySource.has(imp.from)) {
1279
+ resolvedImportsBySource.set(imp.from, new Set());
1280
+ }
1281
+ resolvedImportsBySource.get(imp.from).add(resolved);
1282
+ }
1283
+
1284
+ return resolvedImportsBySource;
1285
+ }
1286
+
1287
+ function scoreTestMatch(sourceFilePath, testFilePath) {
1288
+ const sourcePathLower = sourceFilePath.toLowerCase();
1289
+ const testPathLower = testFilePath.toLowerCase();
1290
+ const sourceBase = path.basename(sourcePathLower, path.extname(sourcePathLower));
1291
+ const sourceParentDir = path.posix.basename(path.posix.dirname(sourcePathLower));
1292
+ const sourceStem = sourceBase === 'index' ? sourceParentDir : sourceBase;
1293
+ const testBase = path.basename(testPathLower, path.extname(testPathLower));
1294
+ const testStem = normalizeTestStem(testBase);
1295
+
1296
+ let score = 0;
1297
+
1298
+ if (testStem === sourceStem) {
1299
+ score += 100;
1300
+ }
1301
+ if (testPathLower.includes(`/${sourceStem}.`) || testPathLower.includes(`/${sourceStem}/`)) {
1302
+ score += 50;
1303
+ }
1304
+ if (sourceBase === 'index' && testStem === 'index' && testPathLower.includes(`/${sourceParentDir}/`)) {
1305
+ score += 90;
1306
+ }
1307
+ if (sourceBase === 'index' && testStem === sourceParentDir) {
1308
+ score += 80;
1309
+ }
1310
+ if (
1311
+ sourceStem.length >= 4
1312
+ && testStem !== sourceStem
1313
+ && (testStem.includes(sourceStem) || sourceStem.includes(testStem))
1314
+ ) {
1315
+ score += 20;
1316
+ }
1317
+
1318
+ return score;
1319
+ }
1320
+
1321
+ function normalizeTestStem(testBase) {
1322
+ return testBase
1323
+ .replace(/(?:\.test|\.spec)+$/g, '')
1324
+ .replace(/[-_.](test|spec)$/g, '')
1325
+ .toLowerCase();
1326
+ }
1327
+
1328
+ function parseArtifactGeneratedAt(artifact) {
1329
+ const generatedAtRaw = String(artifact?.generatedAt ?? '').trim();
1330
+
1331
+ if (!generatedAtRaw) {
1332
+ return null;
1333
+ }
1334
+
1335
+ const generatedAtMs = Date.parse(generatedAtRaw);
1336
+ if (Number.isNaN(generatedAtMs)) {
1337
+ return null;
1338
+ }
1339
+
1340
+ return generatedAtMs;
1341
+ }
1342
+
1343
+ export async function getIndexArtifactGeneratedAt({
1344
+ rootDir = process.cwd(),
1345
+ artifactName = INDEX_ARTIFACTS.files,
1346
+ } = {}) {
1347
+ const absoluteRoot = path.resolve(rootDir);
1348
+ if (artifactName === INDEX_ARTIFACTS.files || artifactName === INDEX_ARTIFACTS.meta) {
1349
+ const metaArtifact = await readArtifactIfExists(absoluteRoot, INDEX_ARTIFACTS.meta);
1350
+ const metaGeneratedAtMs = parseArtifactGeneratedAt(metaArtifact);
1351
+ if (metaGeneratedAtMs !== null) {
1352
+ return metaGeneratedAtMs;
1353
+ }
1354
+ }
1355
+
1356
+ const artifact = await readArtifactIfExists(absoluteRoot, artifactName);
1357
+ return parseArtifactGeneratedAt(artifact);
1358
+ }
1359
+
1360
+ export async function isIndexStale({
1361
+ rootDir = process.cwd(),
1362
+ maxAgeMs = DEFAULT_INDEX_CACHE_MAX_AGE_MS,
1363
+ now = Date.now(),
1364
+ generatedAtMs = null,
1365
+ } = {}) {
1366
+ const absoluteRoot = path.resolve(rootDir);
1367
+ const metaArtifact = await readArtifactIfExists(absoluteRoot, INDEX_ARTIFACTS.meta);
1368
+ const effectiveGeneratedAtMs = Number.isFinite(generatedAtMs)
1369
+ ? generatedAtMs
1370
+ : parseArtifactGeneratedAt(metaArtifact);
1371
+ if (effectiveGeneratedAtMs === null) {
1372
+ return true;
1373
+ }
1374
+
1375
+ if (typeof maxAgeMs !== 'number' || Number.isNaN(maxAgeMs) || maxAgeMs < 0) {
1376
+ return true;
1377
+ }
1378
+
1379
+ if ((now - effectiveGeneratedAtMs) >= maxAgeMs) {
1380
+ return true;
1381
+ }
1382
+
1383
+ if (!metaArtifact?.sourceFingerprint) {
1384
+ return true;
1385
+ }
1386
+
1387
+ const currentSourceFingerprint = createSourceFingerprint(
1388
+ absoluteRoot,
1389
+ await collectFiles(await resolveScanRoots(absoluteRoot)),
1390
+ );
1391
+ return !areSourceFingerprintsEqual(metaArtifact.sourceFingerprint, currentSourceFingerprint);
1392
+ }