@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,1309 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { readJsonIfExists, writeJson } from '../fileOps.js';
4
+ import { recordCompaction } from '../compact/index.js';
5
+ import { buildRuntimePaths } from '../runtimePaths.js';
6
+ import {
7
+ buildCompactMachineKey,
8
+ compressLine,
9
+ compressMarkdownLines,
10
+ estimateTokenCount,
11
+ readPromptCacheEntry,
12
+ writePromptCacheEntry,
13
+ } from '../token/index.js';
14
+
15
+ export const DEFAULT_OUTPUT_HISTORY_MAX_ENTRIES = 25;
16
+ const ANSI_RE = /\u001b\[[0-9;]*m/g;
17
+ const FRESH_OUTPUT_WINDOW_MS = 30 * 60 * 1000;
18
+ const RAW_OUTPUT_SAVE_MIN_TOKENS = 600;
19
+ const RAW_OUTPUT_SAVE_MIN_SAVED_TOKENS = 250;
20
+ const GENERIC_NOISE_PATTERNS = [
21
+ /^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/,
22
+ /^\[[=\-#>\s]{6,}\]/,
23
+ /^progress[:\s]/i,
24
+ /^download(ed|ing)?\b/i,
25
+ /^debug\b/i,
26
+ /^[.+-]{16,}$/,
27
+ /^[╭╰│─└┌┬┴├┤┼\s]+$/u,
28
+ /^\.+$/,
29
+ ];
30
+ const GENERIC_IMPORTANT_PATTERNS = [
31
+ /\b(error|fail(?:ed)?|warning|assertionerror|exception|fatal)\b/i,
32
+ /(^|\s)(tests?|specs?)\//i,
33
+ /(^|\s)(src|app|lib|docs|ukit|\.claude|\.codex)\//i,
34
+ /(^|\s)at\s+.*:\d+:\d+/i,
35
+ /\b(Test Files|Tests|Duration|Start at|Summary|Ran all)\b/i,
36
+ /^stderr\s*\|/i,
37
+ /^stdout\s*\|/i,
38
+ /^\s*[✓✗×❯]/,
39
+ /^\s*FAIL(?:ED)?\s+/i,
40
+ /^\s*E\s+/,
41
+ ];
42
+ const GENERIC_ANCHOR_PATTERNS = [
43
+ /\b(error|fail(?:ed)?|assertionerror|exception|fatal)\b/i,
44
+ /(^|\s)(tests?|specs?)\//i,
45
+ /(^|\s)(src|app|lib|docs|ukit|\.claude|\.codex)\//i,
46
+ /(^|\s)at\s+.*:\d+:\d+/i,
47
+ /\b(Test Files|Tests|Duration|Start at|Summary|Ran all)\b/i,
48
+ /^stderr\s*\|/i,
49
+ /^\s*[✗×❯]/,
50
+ /^\s*FAIL(?:ED)?\s+/i,
51
+ /^\s*AssertionError:/i,
52
+ ];
53
+ const GENERIC_TAIL_PATTERNS = [
54
+ /^\s*(Test Files|Tests|Duration|Start at)\b/i,
55
+ /^\s*=+ .* in [0-9.]+s =+\s*$/i,
56
+ /^\s*short test summary info\b/i,
57
+ ];
58
+ const OUTPUT_PROFILES = [
59
+ {
60
+ id: 'paths',
61
+ commandPatterns: [
62
+ /\brg\s+--files\b/i,
63
+ /\bgit\s+ls-files\b/i,
64
+ /(?:^|\s)(?:find|fd)\b/i,
65
+ /(?:^|\s)tree\b/i,
66
+ /\bls\b[^\n]*\s-R(?:\s|$)/i,
67
+ ],
68
+ importantPatterns: [],
69
+ anchorPatterns: [],
70
+ tailPatterns: [],
71
+ noisePatterns: [],
72
+ },
73
+ {
74
+ id: 'search',
75
+ commandPatterns: [
76
+ /(?:^|\s)(?:rg|ripgrep)\b/i,
77
+ /(?:^|\s)grep\b/i,
78
+ /\bgit\s+grep\b/i,
79
+ ],
80
+ importantPatterns: [],
81
+ anchorPatterns: [],
82
+ tailPatterns: [],
83
+ noisePatterns: [],
84
+ },
85
+ {
86
+ id: 'package-install',
87
+ commandPatterns: [
88
+ /\b(?:npm|pnpm|yarn|bun)\b.*\b(?:install|add|ci|update)\b/i,
89
+ ],
90
+ importantPatterns: [
91
+ /\b(?:deprecated|unmet peer|peer dependenc(?:y|ies)|ignored build scripts|approve-builds)\b/i,
92
+ /\b(?:vulnerab(?:ility|ilities)|audited \d+ packages|added \d+ packages|removed \d+ packages|changed \d+ packages)\b/i,
93
+ /\b(?:npm ERR!|ERR_PNPM|ELIFECYCLE|ERESOLVE|ENOENT|EACCES)\b/i,
94
+ /^Packages:\s*[+~-]?\d+/i,
95
+ /^Done in\b/i,
96
+ ],
97
+ anchorPatterns: [
98
+ /\b(?:deprecated|unmet peer|peer dependenc(?:y|ies)|ignored build scripts)\b/i,
99
+ /\b(?:vulnerab(?:ility|ilities)|npm ERR!|ERR_PNPM|ELIFECYCLE|ERESOLVE|ENOENT|EACCES)\b/i,
100
+ /^Packages:\s*[+~-]?\d+/i,
101
+ /^Done in\b/i,
102
+ ],
103
+ tailPatterns: [
104
+ /^Done in\b/i,
105
+ ],
106
+ noisePatterns: [
107
+ /^Progress:\s/i,
108
+ /^\+\+\++$/,
109
+ /^\s*(?:dependencies|devDependencies|optionalDependencies):\s*$/i,
110
+ /^[+]\s+\S+/,
111
+ ],
112
+ },
113
+ {
114
+ id: 'test-runner',
115
+ commandPatterns: [
116
+ /\b(?:vitest|jest|mocha|ava|pytest|py\.test)\b/i,
117
+ /\b(?:npm|pnpm|yarn|bun)\s+test\b/i,
118
+ ],
119
+ importantPatterns: [
120
+ /^\s*FAIL(?:ED)?\b/i,
121
+ /^\s*PASS\b/i,
122
+ /^\s*AssertionError:/i,
123
+ /^\s*short test summary info\b/i,
124
+ ],
125
+ anchorPatterns: [
126
+ /^\s*FAIL(?:ED)?\b/i,
127
+ /^\s*AssertionError:/i,
128
+ /^\s*short test summary info\b/i,
129
+ ],
130
+ tailPatterns: [
131
+ /^\s*short test summary info\b/i,
132
+ /^\s*=+ .* in [0-9.]+s =+\s*$/i,
133
+ ],
134
+ noisePatterns: [
135
+ /^\s*collected \d+ items?\s*$/i,
136
+ ],
137
+ },
138
+ {
139
+ id: 'git',
140
+ commandPatterns: [
141
+ /\bgit\s+status\b/i,
142
+ /\bgit\s+diff\b/i,
143
+ ],
144
+ importantPatterns: [
145
+ /^(?:M|A|D|R|C|UU|\?\?)\s+/,
146
+ /\bfiles? changed\b/i,
147
+ /\binsertions?\(\+\)/i,
148
+ /\bdeletions?\(-\)/i,
149
+ /^diff --git\b/i,
150
+ ],
151
+ anchorPatterns: [
152
+ /^(?:M|A|D|R|C|UU|\?\?)\s+/,
153
+ /\bfiles? changed\b/i,
154
+ /\binsertions?\(\+\)/i,
155
+ /\bdeletions?\(-\)/i,
156
+ ],
157
+ tailPatterns: [
158
+ /\bfiles? changed\b/i,
159
+ /\binsertions?\(\+\)/i,
160
+ /\bdeletions?\(-\)/i,
161
+ ],
162
+ noisePatterns: [],
163
+ },
164
+ {
165
+ id: 'docker-logs',
166
+ commandPatterns: [
167
+ /\bdocker(?:\s+compose)?\s+logs\b/i,
168
+ ],
169
+ importantPatterns: [
170
+ /\b(error|exception|warn|fatal|traceback|exited with code)\b/i,
171
+ /^[a-z0-9_.-]+(?:-\d+)?\s+\|/i,
172
+ ],
173
+ anchorPatterns: [
174
+ /\b(error|exception|fatal|traceback|exited with code)\b/i,
175
+ /^[a-z0-9_.-]+(?:-\d+)?\s+\|.*\b(error|exception|fatal|traceback)\b/i,
176
+ ],
177
+ tailPatterns: [],
178
+ noisePatterns: [
179
+ /^Attaching to\b/i,
180
+ ],
181
+ },
182
+ ];
183
+
184
+ function stripAnsi(value) {
185
+ return String(value ?? '').replace(ANSI_RE, '');
186
+ }
187
+
188
+ function splitLines(value) {
189
+ return stripAnsi(value)
190
+ .split(/\r?\n/)
191
+ .map((line) => line.replace(/\s+$/g, ''));
192
+ }
193
+
194
+ function normalizeLineForDedupe(line) {
195
+ return String(line ?? '').trim().replace(/\s+/g, ' ').toLowerCase();
196
+ }
197
+
198
+ function matchesAnyPattern(line, patterns = []) {
199
+ return patterns.some((pattern) => pattern.test(line));
200
+ }
201
+
202
+ function detectOutputProfile(command) {
203
+ const commandText = String(command ?? '').trim();
204
+ return OUTPUT_PROFILES.find((profile) => matchesAnyPattern(commandText, profile.commandPatterns)) ?? null;
205
+ }
206
+
207
+ function escapeRegExp(value) {
208
+ return String(value ?? '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
209
+ }
210
+
211
+ function buildProjectRootVariants(projectRoot) {
212
+ const trimmed = String(projectRoot ?? '').trim();
213
+ if (!trimmed) {
214
+ return [];
215
+ }
216
+
217
+ const resolved = path.resolve(trimmed).replace(/[\\/]+$/g, '');
218
+ return [...new Set([
219
+ resolved,
220
+ resolved.replace(/\\/g, '/'),
221
+ resolved.replace(/\//g, '\\'),
222
+ ].filter(Boolean))];
223
+ }
224
+
225
+ function shortenProjectRootSegments(value, projectRoot) {
226
+ let next = String(value ?? '');
227
+ if (!next.trim()) {
228
+ return next;
229
+ }
230
+
231
+ for (const variant of buildProjectRootVariants(projectRoot)) {
232
+ const escaped = escapeRegExp(variant);
233
+ next = next
234
+ .replace(new RegExp(`${escaped}(?:[\\\\/])`, 'g'), '')
235
+ .replace(new RegExp(`${escaped}(?=$|[\\s)'":])`, 'g'), '.');
236
+ }
237
+
238
+ return next;
239
+ }
240
+
241
+ function normalizeRuntimeRelativePath(filePath) {
242
+ return String(filePath ?? '')
243
+ .trim()
244
+ .replace(/\\/g, '/')
245
+ .replace(/^\.\//, '');
246
+ }
247
+
248
+ function buildRawOutputRecoveryReason({ exitCode = null, tokensBefore = 0, savedTokens = 0 } = {}) {
249
+ if (Number.isFinite(Number(exitCode)) && Number(exitCode) !== 0) {
250
+ return 'failure';
251
+ }
252
+ if (Number(tokensBefore) >= RAW_OUTPUT_SAVE_MIN_TOKENS) {
253
+ return 'large-output';
254
+ }
255
+ if (Number(savedTokens) >= RAW_OUTPUT_SAVE_MIN_SAVED_TOKENS) {
256
+ return 'high-savings';
257
+ }
258
+ return null;
259
+ }
260
+
261
+ function sanitizeFileComponent(value, { fallback = 'output', maxLength = 48 } = {}) {
262
+ const normalized = String(value ?? '')
263
+ .trim()
264
+ .toLowerCase()
265
+ .replace(/[^a-z0-9]+/g, '-')
266
+ .replace(/^-+|-+$/g, '')
267
+ .slice(0, maxLength);
268
+ return normalized || fallback;
269
+ }
270
+
271
+ function buildRecoveryFileName({
272
+ command = '',
273
+ summary = '',
274
+ profile = 'generic',
275
+ exitCode = null,
276
+ } = {}) {
277
+ const slug = sanitizeFileComponent(String(command).split(/\s+/).slice(0, 3).join('-'), {
278
+ fallback: sanitizeFileComponent(profile, { fallback: 'output', maxLength: 16 }),
279
+ maxLength: 24,
280
+ });
281
+ const fingerprint = buildCompactMachineKey('tool-output-recovery-v1', {
282
+ command: String(command ?? '').trim(),
283
+ summary: String(summary ?? '').trim(),
284
+ profile: String(profile ?? 'generic').trim(),
285
+ exitCode: Number.isFinite(Number(exitCode)) ? Number(exitCode) : null,
286
+ }).split(':').at(-1)?.replace(/[^a-z0-9_-]/gi, '-').slice(0, 16) || 'recovery';
287
+ return `${slug}-${fingerprint}.log`;
288
+ }
289
+
290
+ function buildRawOutputText({ command = '', stdout = '', stderr = '', exitCode = null } = {}) {
291
+ const sections = [
292
+ `Command: ${String(command ?? '').trim() || 'unknown command'}`,
293
+ ];
294
+
295
+ if (Number.isFinite(Number(exitCode))) {
296
+ sections.push(`Exit code: ${Number(exitCode)}`);
297
+ }
298
+
299
+ sections.push('');
300
+
301
+ if (String(stdout ?? '').length > 0) {
302
+ sections.push('--- stdout ---');
303
+ sections.push(String(stdout));
304
+ sections.push('');
305
+ }
306
+
307
+ if (String(stderr ?? '').length > 0) {
308
+ sections.push('--- stderr ---');
309
+ sections.push(String(stderr));
310
+ sections.push('');
311
+ }
312
+
313
+ return `${sections.join('\n').trimEnd()}\n`;
314
+ }
315
+
316
+ function buildRecoveryHintSummary(summary, rawPath, { tokensBefore = 0 } = {}) {
317
+ const recoveryLine = `- Full output: ${rawPath}`;
318
+ const summaryLines = String(summary ?? '')
319
+ .split(/\r?\n/)
320
+ .map((line) => line.trim())
321
+ .filter(Boolean);
322
+ if (summaryLines.length === 0) {
323
+ return null;
324
+ }
325
+
326
+ const directCandidate = [...summaryLines, recoveryLine].join('\n');
327
+ if (estimateTokenCount(directCandidate) < tokensBefore) {
328
+ return directCandidate;
329
+ }
330
+
331
+ const removalPatterns = [
332
+ /^-\s*stderr\s*\|/i,
333
+ /^-\s*FAIL(?:ED)?\b/i,
334
+ /^-\s*PASS\b/i,
335
+ /^-\s*(?:Test Files|Tests|Duration|Start at)\b/i,
336
+ ];
337
+
338
+ for (const pattern of removalPatterns) {
339
+ const index = summaryLines.findIndex((line, lineIndex) => lineIndex > 0 && pattern.test(line));
340
+ if (index < 0) continue;
341
+ const candidate = [...summaryLines.slice(0, index), ...summaryLines.slice(index + 1), recoveryLine].join('\n');
342
+ if (estimateTokenCount(candidate) < tokensBefore) {
343
+ return candidate;
344
+ }
345
+ }
346
+
347
+ for (let index = summaryLines.length - 1; index >= 1; index -= 1) {
348
+ const candidate = [...summaryLines.slice(0, index), ...summaryLines.slice(index + 1), recoveryLine].join('\n');
349
+ if (estimateTokenCount(candidate) < tokensBefore) {
350
+ return candidate;
351
+ }
352
+ }
353
+
354
+ return null;
355
+ }
356
+
357
+ async function persistRawOutput(projectRoot, {
358
+ command = '',
359
+ summary = '',
360
+ profile = 'generic',
361
+ stdout = '',
362
+ stderr = '',
363
+ exitCode = null,
364
+ } = {}) {
365
+ const runtimePaths = buildRuntimePaths(projectRoot);
366
+ const teeCacheDir = runtimePaths.teeCacheDir ?? path.join(runtimePaths.cacheRoot, 'tee');
367
+ const fileName = buildRecoveryFileName({
368
+ command,
369
+ summary,
370
+ profile,
371
+ exitCode,
372
+ });
373
+ const absolutePath = path.join(teeCacheDir, fileName);
374
+ const rawOutputText = buildRawOutputText({
375
+ command,
376
+ stdout,
377
+ stderr,
378
+ exitCode,
379
+ });
380
+
381
+ await fs.mkdir(teeCacheDir, { recursive: true });
382
+ await fs.writeFile(absolutePath, rawOutputText, 'utf8');
383
+
384
+ return {
385
+ rawSaved: true,
386
+ rawPath: normalizeRuntimeRelativePath(path.relative(projectRoot, absolutePath)),
387
+ rawBytes: Buffer.byteLength(rawOutputText, 'utf8'),
388
+ };
389
+ }
390
+
391
+ function isProgressNoise(line, profile = null) {
392
+ const normalized = String(line ?? '').trim();
393
+ if (!normalized) return true;
394
+ return matchesAnyPattern(normalized, [
395
+ ...GENERIC_NOISE_PATTERNS,
396
+ ...(profile?.noisePatterns ?? []),
397
+ ]);
398
+ }
399
+
400
+ function isImportantLine(line, profile = null) {
401
+ const normalized = String(line ?? '').trim();
402
+ if (!normalized) return false;
403
+ return matchesAnyPattern(normalized, [
404
+ ...GENERIC_IMPORTANT_PATTERNS,
405
+ ...(profile?.importantPatterns ?? []),
406
+ ]);
407
+ }
408
+
409
+ function isAnchorLine(line, profile = null) {
410
+ const normalized = String(line ?? '').trim();
411
+ if (!normalized) return false;
412
+ return matchesAnyPattern(normalized, [
413
+ ...GENERIC_ANCHOR_PATTERNS,
414
+ ...(profile?.anchorPatterns ?? []),
415
+ ]);
416
+ }
417
+
418
+ function isTailSummaryLine(line, profile = null) {
419
+ const normalized = String(line ?? '').trim();
420
+ if (!normalized) return false;
421
+ return matchesAnyPattern(normalized, [
422
+ ...GENERIC_TAIL_PATTERNS,
423
+ ...(profile?.tailPatterns ?? []),
424
+ ]);
425
+ }
426
+
427
+ function buildCompactedSummaryLines(lines, { maxTokens = 180, maxLines = 10, forceFirstCount = 1 } = {}) {
428
+ const sourceLines = Array.isArray(lines) ? lines : [];
429
+ const selected = [];
430
+ const seen = new Set();
431
+ let usedTokens = 0;
432
+ let nonEmptyCount = 0;
433
+
434
+ for (const line of sourceLines) {
435
+ const compressed = compressLine(line);
436
+ if (!compressed) continue;
437
+
438
+ const dedupeKey = compressed.toLowerCase();
439
+ if (seen.has(dedupeKey)) continue;
440
+
441
+ const tokens = estimateTokenCount(compressed);
442
+ const forceLine = nonEmptyCount < forceFirstCount;
443
+ const wouldOverflow = selected.length > 0 && (usedTokens + tokens) > maxTokens;
444
+ const wouldExceedLines = nonEmptyCount >= maxLines;
445
+
446
+ if (!forceLine && (wouldOverflow || wouldExceedLines)) {
447
+ break;
448
+ }
449
+ if (forceLine && wouldExceedLines) {
450
+ break;
451
+ }
452
+
453
+ selected.push(compressed);
454
+ seen.add(dedupeKey);
455
+ usedTokens += tokens;
456
+ nonEmptyCount += 1;
457
+ }
458
+
459
+ if (selected.length === 0 && sourceLines.length > 0) {
460
+ return compressMarkdownLines(sourceLines, { maxTokens, maxLines, preserveFirstLine: true });
461
+ }
462
+
463
+ return selected;
464
+ }
465
+
466
+ function findMissingAnchors(summary, anchorLines) {
467
+ const summaryText = String(summary ?? '').toLowerCase();
468
+ return anchorLines.filter((line) => {
469
+ const compressed = compressLine(line).toLowerCase();
470
+ return compressed && !summaryText.includes(compressed);
471
+ });
472
+ }
473
+
474
+ function uniqueLines(lines) {
475
+ const seen = new Set();
476
+ const unique = [];
477
+ for (const line of lines) {
478
+ const normalized = normalizeLineForDedupe(line);
479
+ if (!normalized || seen.has(normalized)) continue;
480
+ seen.add(normalized);
481
+ unique.push(String(line ?? '').trim());
482
+ }
483
+ return unique;
484
+ }
485
+
486
+ function looksLikeSearchPath(filePath) {
487
+ const normalized = String(filePath ?? '').trim();
488
+ if (!normalized) return false;
489
+ return /[\\/]/.test(normalized) || normalized.startsWith('.') || /\.(?:[a-z0-9]{1,10})(?:$|:)/i.test(normalized);
490
+ }
491
+
492
+ function parseSearchMatchLine(line) {
493
+ const trimmed = String(line ?? '').trim().replace(/^stderr:\s*/i, '');
494
+ if (!trimmed) return null;
495
+
496
+ let match = trimmed.match(/^(.+?):(\d+):(\d+):(.*)$/);
497
+ if (match && looksLikeSearchPath(match[1])) {
498
+ return {
499
+ rawLine: trimmed,
500
+ filePath: match[1],
501
+ anchor: `${match[1]}:${match[2]}:${match[3]}`,
502
+ };
503
+ }
504
+
505
+ match = trimmed.match(/^(.+?):(\d+):(.*)$/);
506
+ if (match && looksLikeSearchPath(match[1])) {
507
+ return {
508
+ rawLine: trimmed,
509
+ filePath: match[1],
510
+ anchor: `${match[1]}:${match[2]}`,
511
+ };
512
+ }
513
+
514
+ match = trimmed.match(/^(.+?):(.*)$/);
515
+ if (match && looksLikeSearchPath(match[1])) {
516
+ return {
517
+ rawLine: trimmed,
518
+ filePath: match[1],
519
+ anchor: match[1],
520
+ };
521
+ }
522
+
523
+ return null;
524
+ }
525
+
526
+ function buildSearchResultSelection(lines) {
527
+ const matches = [];
528
+ const extras = [];
529
+ const seenRaw = new Set();
530
+ for (const line of lines) {
531
+ const trimmed = String(line ?? '').trim();
532
+ if (!trimmed) continue;
533
+ const parsed = parseSearchMatchLine(trimmed);
534
+ if (parsed) {
535
+ const key = normalizeLineForDedupe(parsed.rawLine);
536
+ if (!seenRaw.has(key)) {
537
+ seenRaw.add(key);
538
+ matches.push(parsed);
539
+ }
540
+ continue;
541
+ }
542
+
543
+ if (/^Binary file .* matches$/i.test(trimmed) || /^\d+\s+matches?$/i.test(trimmed)) {
544
+ extras.push(trimmed);
545
+ }
546
+ }
547
+ if (matches.length === 0) {
548
+ return null;
549
+ }
550
+ const detailedMatches = [];
551
+ const usedDetailedKeys = new Set();
552
+ const seenDetailedFiles = new Set();
553
+ for (const match of matches) {
554
+ if (detailedMatches.length >= 2) break;
555
+ if (seenDetailedFiles.has(match.filePath)) continue;
556
+ detailedMatches.push(match);
557
+ usedDetailedKeys.add(normalizeLineForDedupe(match.rawLine));
558
+ seenDetailedFiles.add(match.filePath);
559
+ }
560
+
561
+ for (const match of matches) {
562
+ if (detailedMatches.length >= 2) break;
563
+ const key = normalizeLineForDedupe(match.rawLine);
564
+ if (usedDetailedKeys.has(key)) continue;
565
+ detailedMatches.push(match);
566
+ usedDetailedKeys.add(key);
567
+ }
568
+ const remainingAnchors = uniqueLines(
569
+ matches
570
+ .filter((match) => !usedDetailedKeys.has(normalizeLineForDedupe(match.rawLine)))
571
+ .map((match) => match.anchor),
572
+ );
573
+ const shownAnchors = remainingAnchors.slice(0, 3);
574
+ const remainingAnchorCount = Math.max(0, remainingAnchors.length - shownAnchors.length);
575
+
576
+ return {
577
+ anchorLines: uniqueLines([
578
+ ...detailedMatches.map((match) => match.rawLine),
579
+ ...remainingAnchors.slice(0, 6),
580
+ ]),
581
+ candidateLines: uniqueLines([
582
+ ...detailedMatches.map((match) => match.rawLine),
583
+ shownAnchors.length > 0
584
+ ? `Match anchors: ${shownAnchors.join(', ')}${remainingAnchorCount > 0 ? `, +${remainingAnchorCount} more` : ''}`
585
+ : null,
586
+ ...extras.slice(0, 1),
587
+ ].filter(Boolean)),
588
+ };
589
+ }
590
+
591
+ function parsePathListLine(line) {
592
+ const trimmed = String(line ?? '').trim().replace(/^stderr:\s*/i, '');
593
+ if (!trimmed || /[:*?"<>|]$/.test(trimmed) || /\s{2,}/.test(trimmed)) return null;
594
+ if (!(/[\\/]/.test(trimmed) || trimmed.startsWith('.') || /\.[a-z0-9]{1,10}$/i.test(trimmed))) return null;
595
+ return trimmed;
596
+ }
597
+
598
+ function cleanStructuredPathSegment(value) {
599
+ return String(value ?? '')
600
+ .trim()
601
+ .replace(/^['"]|['"]$/g, '')
602
+ .replace(/[\\/]+$/g, '');
603
+ }
604
+
605
+ function normalizeStructuredPath(parts) {
606
+ return (Array.isArray(parts) ? parts : [parts])
607
+ .flatMap((part) => String(part ?? '').split(/[\\/]/))
608
+ .map((segment) => segment.trim())
609
+ .filter(Boolean)
610
+ .join('/');
611
+ }
612
+
613
+ function filterLeafStructuredPaths(paths) {
614
+ const uniquePaths = uniqueLines(paths);
615
+ const parentPaths = new Set();
616
+
617
+ for (const filePath of uniquePaths) {
618
+ const parts = String(filePath ?? '').split('/').filter(Boolean);
619
+ while (parts.length > 1) {
620
+ parts.pop();
621
+ parentPaths.add(parts.join('/'));
622
+ }
623
+ }
624
+
625
+ const leafPaths = uniquePaths.filter((filePath) => !parentPaths.has(filePath));
626
+ return leafPaths.length > 0 ? leafPaths : uniquePaths;
627
+ }
628
+
629
+ function buildTreePathList(lines) {
630
+ const paths = [];
631
+ const stack = [];
632
+ let rootOffset = 0;
633
+
634
+ for (const line of lines) {
635
+ const raw = String(line ?? '')
636
+ .replace(/^stderr:\s*/i, '')
637
+ .replace(/\s+$/g, '');
638
+ const trimmed = raw.trim();
639
+ if (!trimmed || /^\d+\s+directories?,\s+\d+\s+files?$/i.test(trimmed)) continue;
640
+
641
+ if (trimmed === '.' || trimmed === './') {
642
+ stack.length = 0;
643
+ rootOffset = 0;
644
+ continue;
645
+ }
646
+
647
+ const branchMatch = raw.match(/^((?:[│ ]{4})*)(?:[├└]── )(.+)$/u);
648
+ if (branchMatch) {
649
+ const depth = Math.floor(branchMatch[1].length / 4) + rootOffset;
650
+ const name = cleanStructuredPathSegment(branchMatch[2]);
651
+ if (!name || name === '.' || name === '..') continue;
652
+ stack[depth] = name;
653
+ stack.length = depth + 1;
654
+ const filePath = normalizeStructuredPath(stack);
655
+ if (filePath) paths.push(filePath);
656
+ continue;
657
+ }
658
+
659
+ if (/^[│├└]/u.test(trimmed) || /:$/.test(trimmed) || /^total\s+\d+/i.test(trimmed)) {
660
+ continue;
661
+ }
662
+
663
+ const name = cleanStructuredPathSegment(trimmed);
664
+ if (!name || name === '.' || name === '..') continue;
665
+ stack[0] = name;
666
+ stack.length = 1;
667
+ rootOffset = 1;
668
+ const filePath = normalizeStructuredPath(stack);
669
+ if (filePath) paths.push(filePath);
670
+ }
671
+
672
+ return filterLeafStructuredPaths(paths);
673
+ }
674
+
675
+ function buildRecursiveListingPathList(lines) {
676
+ const sectionDirs = new Set();
677
+ const paths = [];
678
+ let currentDir = null;
679
+
680
+ for (const line of lines) {
681
+ const trimmed = String(line ?? '')
682
+ .trim()
683
+ .replace(/^stderr:\s*/i, '');
684
+ if (!trimmed || /^total\s+\d+/i.test(trimmed)) continue;
685
+
686
+ const sectionMatch = trimmed.match(/^(.+):$/);
687
+ if (sectionMatch) {
688
+ const sectionPath = cleanStructuredPathSegment(sectionMatch[1]);
689
+ if (!sectionPath) continue;
690
+ currentDir = sectionPath === '.'
691
+ ? '.'
692
+ : normalizeStructuredPath(sectionPath);
693
+ if (currentDir && currentDir !== '.') {
694
+ sectionDirs.add(currentDir);
695
+ }
696
+ continue;
697
+ }
698
+
699
+ if (!currentDir || trimmed === '.' || trimmed === '..') continue;
700
+
701
+ const entryName = cleanStructuredPathSegment(trimmed);
702
+ if (!entryName) continue;
703
+
704
+ const filePath = currentDir === '.'
705
+ ? normalizeStructuredPath([entryName])
706
+ : normalizeStructuredPath([currentDir, entryName]);
707
+ if (filePath) {
708
+ paths.push(filePath);
709
+ }
710
+ }
711
+
712
+ const leafPaths = uniqueLines(paths.filter((filePath) => !sectionDirs.has(filePath)));
713
+ return leafPaths.length > 0 ? leafPaths : uniqueLines(paths);
714
+ }
715
+
716
+ function extractPathCandidates(lines) {
717
+ const treePaths = buildTreePathList(lines);
718
+ const recursivePaths = buildRecursiveListingPathList(lines);
719
+ const directPaths = uniqueLines(lines.map((line) => parsePathListLine(line)).filter(Boolean));
720
+ const hasRecursiveSections = lines.some((line) => /^.+:\s*$/.test(String(line ?? '').trim().replace(/^stderr:\s*/i, '')));
721
+
722
+ if (hasRecursiveSections && recursivePaths.length > 0) {
723
+ return recursivePaths;
724
+ }
725
+ if (treePaths.length > 0 && treePaths.length >= recursivePaths.length && treePaths.length >= directPaths.length) {
726
+ return treePaths;
727
+ }
728
+ if (recursivePaths.length > 0 && recursivePaths.length >= directPaths.length) {
729
+ return recursivePaths;
730
+ }
731
+ return directPaths;
732
+ }
733
+
734
+ function getPathAreaKey(filePath) {
735
+ const parts = String(filePath ?? '').split(/[\\/]/).filter(Boolean);
736
+ if (parts[0] === '.') parts.shift();
737
+ if (parts.length === 0) return String(filePath ?? '');
738
+ if (parts[0] === 'docs') return 'docs';
739
+ if (parts.length >= 2 && ['src', 'tests', 'test', 'app', 'lib'].includes(parts[0])) return `${parts[0]}/${parts[1]}`;
740
+ return parts[0];
741
+ }
742
+
743
+ function getPathTopLevelKey(filePath) {
744
+ const parts = String(filePath ?? '').split(/[\\/]/).filter(Boolean);
745
+ if (parts[0] === '.') parts.shift();
746
+ return parts[0] || String(filePath ?? '');
747
+ }
748
+
749
+ function buildPathListSelection(lines) {
750
+ const paths = extractPathCandidates(lines);
751
+ if (paths.length === 0) return null;
752
+ if (paths.length <= 6) return { anchorLines: paths, candidateLines: paths };
753
+
754
+ const detailedPaths = [];
755
+ const usedAreas = new Set();
756
+ for (const filePath of paths) {
757
+ const areaKey = getPathAreaKey(filePath);
758
+ if (usedAreas.has(areaKey)) continue;
759
+ detailedPaths.push(filePath);
760
+ usedAreas.add(areaKey);
761
+ if (detailedPaths.length >= 4) break;
762
+ }
763
+ for (const filePath of paths) {
764
+ if (detailedPaths.length >= 4) break;
765
+ if (detailedPaths.includes(filePath)) continue;
766
+ detailedPaths.push(filePath);
767
+ }
768
+
769
+ const remainingPaths = paths.filter((filePath) => !detailedPaths.includes(filePath));
770
+ const shownAnchors = [];
771
+ const shownAnchorTopLevels = new Set();
772
+ for (const filePath of remainingPaths) {
773
+ const topLevelKey = getPathTopLevelKey(filePath);
774
+ if (shownAnchorTopLevels.has(topLevelKey)) continue;
775
+ shownAnchors.push(filePath);
776
+ shownAnchorTopLevels.add(topLevelKey);
777
+ if (shownAnchors.length >= 2) break;
778
+ }
779
+ for (const filePath of remainingPaths) {
780
+ if (shownAnchors.length >= 2) break;
781
+ if (shownAnchors.includes(filePath)) continue;
782
+ shownAnchors.push(filePath);
783
+ }
784
+
785
+ return {
786
+ anchorLines: uniqueLines([...detailedPaths, ...shownAnchors]),
787
+ candidateLines: uniqueLines([
788
+ ...detailedPaths,
789
+ shownAnchors.length > 0
790
+ ? `Path anchors: ${shownAnchors.join(', ')}${remainingPaths.length > shownAnchors.length ? `, +${remainingPaths.length - shownAnchors.length} more` : ''}`
791
+ : null,
792
+ ].filter(Boolean)),
793
+ };
794
+ }
795
+
796
+ function parseGitChangeLine(line) {
797
+ const trimmed = String(line ?? '').trim().replace(/^stderr:\s*/i, '');
798
+ if (!trimmed) return null;
799
+
800
+ const statusMatch = trimmed.match(/^([ MADRCU?]{1,2})\s+(.+)$/);
801
+ if (statusMatch && looksLikeSearchPath(statusMatch[2].split(' -> ').at(-1))) {
802
+ const status = statusMatch[1].replace(/\s+/g, ' ').trim() || '?';
803
+ const filePath = statusMatch[2].split(' -> ').at(-1);
804
+ return { rawLine: trimmed, anchor: `${status} ${filePath}`, filePath, kind: 'status' };
805
+ }
806
+
807
+ const statMatch = trimmed.match(/^(.+?)\s+\|\s+\d+\s+[+\-]+$/);
808
+ if (statMatch && looksLikeSearchPath(statMatch[1].trim())) {
809
+ const filePath = statMatch[1].trim();
810
+ return { rawLine: trimmed, anchor: filePath, filePath, kind: 'stat' };
811
+ }
812
+
813
+ const filePath = parsePathListLine(trimmed);
814
+ return filePath ? { rawLine: filePath, anchor: filePath, filePath, kind: 'path' } : null;
815
+ }
816
+
817
+ function buildGitSelection(lines) {
818
+ const summaryLines = uniqueLines(lines.filter((line) => /\bfiles? changed\b|\binsertions?\(\+\)|\bdeletions?\(-\)/i.test(String(line ?? '').trim())));
819
+ const entries = [];
820
+ const seen = new Set();
821
+ for (const line of lines) {
822
+ const entry = parseGitChangeLine(line);
823
+ if (!entry) continue;
824
+ const key = normalizeLineForDedupe(entry.rawLine);
825
+ if (seen.has(key)) continue;
826
+ seen.add(key);
827
+ entries.push(entry);
828
+ }
829
+ if (entries.length === 0 || (entries.length <= 4 && summaryLines.length <= 1)) return null;
830
+
831
+ const detailed = [];
832
+ const addDetailed = (predicate) => {
833
+ const entry = entries.find((item) => !detailed.includes(item) && predicate(item));
834
+ if (entry) detailed.push(entry);
835
+ };
836
+ addDetailed((item) => /^(?:src|app|lib)\//.test(item.filePath));
837
+ if (summaryLines.length > 0) addDetailed((item) => /^(?:src|app|lib)\//.test(item.filePath));
838
+ addDetailed((item) => /^(?:tests?|specs?)\//.test(item.filePath));
839
+ if (summaryLines.length === 0) addDetailed((item) => /^docs\//.test(item.filePath));
840
+ while (detailed.length < 3) {
841
+ const entry = entries.find((item) => !detailed.includes(item));
842
+ if (!entry) break;
843
+ detailed.push(entry);
844
+ }
845
+
846
+ const remaining = entries.filter((item) => !detailed.includes(item));
847
+ const shownAnchor = remaining.find((item) => /^docs\//.test(item.filePath)) || remaining[0] || null;
848
+ return {
849
+ anchorLines: uniqueLines([...detailed.map((item) => item.rawLine), ...(shownAnchor ? [shownAnchor.anchor] : []), ...summaryLines]),
850
+ candidateLines: uniqueLines([
851
+ ...detailed.map((item) => item.rawLine),
852
+ shownAnchor ? `Changed paths: ${shownAnchor.anchor}${remaining.length > 1 ? `, +${remaining.length - 1} more` : ''}` : null,
853
+ ...summaryLines,
854
+ ].filter(Boolean)),
855
+ };
856
+ }
857
+
858
+ function selectCandidateLines(lines, profile = null) {
859
+ if (profile?.id === 'git') {
860
+ const gitSelection = buildGitSelection(lines);
861
+ if (gitSelection) return gitSelection;
862
+ }
863
+ if (profile?.id === 'paths') {
864
+ const pathSelection = buildPathListSelection(lines);
865
+ if (pathSelection) return pathSelection;
866
+ }
867
+ if (profile?.id === 'search') {
868
+ const searchSelection = buildSearchResultSelection(lines);
869
+ if (searchSelection) {
870
+ return searchSelection;
871
+ }
872
+ }
873
+
874
+ const important = [];
875
+ const anchors = [];
876
+ const fallback = [];
877
+ const tail = [];
878
+ const seen = new Set();
879
+ const hasFailureSignal = lines.some((line) => /\b(error|fail(?:ed)?|assertionerror|exception|fatal)\b/i.test(String(line ?? '')));
880
+
881
+ for (const line of lines) {
882
+ const trimmed = String(line ?? '').trim();
883
+ const normalized = normalizeLineForDedupe(trimmed);
884
+ if (!normalized || seen.has(normalized)) continue;
885
+
886
+ const progressNoise = isProgressNoise(trimmed, profile);
887
+ const importantLine = isImportantLine(trimmed, profile);
888
+ const anchorLine = isAnchorLine(trimmed, profile);
889
+ const shouldDropOnFailure = hasFailureSignal && (/^\s*RUN\b/i.test(trimmed) || /^\s*✓\s+/i.test(trimmed) || /^stdout\s*\|/i.test(trimmed));
890
+
891
+ if (anchorLine && !shouldDropOnFailure) {
892
+ anchors.push(trimmed);
893
+ important.push(trimmed);
894
+ seen.add(normalized);
895
+ continue;
896
+ }
897
+
898
+ if (importantLine && !shouldDropOnFailure) {
899
+ important.push(trimmed);
900
+ seen.add(normalized);
901
+ continue;
902
+ }
903
+
904
+ if (!progressNoise && !shouldDropOnFailure && fallback.length < 3) {
905
+ fallback.push(trimmed);
906
+ seen.add(normalized);
907
+ }
908
+ }
909
+
910
+ for (const line of [...lines].reverse()) {
911
+ const trimmed = String(line ?? '').trim();
912
+ const normalized = normalizeLineForDedupe(trimmed);
913
+ if (!normalized || seen.has(normalized) || isProgressNoise(trimmed, profile)) continue;
914
+
915
+ if (isTailSummaryLine(trimmed, profile)) {
916
+ tail.unshift(trimmed);
917
+ seen.add(normalized);
918
+ }
919
+ if (tail.length >= 4) break;
920
+ }
921
+
922
+ return {
923
+ anchorLines: uniqueLines([...anchors, ...tail]).slice(0, 6),
924
+ candidateLines: uniqueLines([...important, ...tail, ...fallback]),
925
+ };
926
+ }
927
+
928
+ export function compressToolOutput({
929
+ command = '',
930
+ stdout = '',
931
+ stderr = '',
932
+ exitCode = null,
933
+ maxTokens = 180,
934
+ projectRoot = null,
935
+ } = {}) {
936
+ const commandText = String(command ?? '').trim();
937
+ const displayCommand = shortenProjectRootSegments(commandText, projectRoot).trim() || commandText;
938
+ const profile = detectOutputProfile(displayCommand);
939
+ const stdoutLines = splitLines(stdout).map((line) => shortenProjectRootSegments(line, projectRoot));
940
+ const stderrLines = splitLines(stderr).map((line) => shortenProjectRootSegments(line, projectRoot));
941
+ const allLines = [
942
+ ...stdoutLines,
943
+ ...stderrLines.map((line) => (line.trim() ? `stderr: ${line}` : line)),
944
+ ];
945
+ const rawText = [commandText, stdout, stderr].filter(Boolean).join('\n');
946
+ const { candidateLines, anchorLines } = selectCandidateLines(allLines, profile);
947
+ const headerLine = `- Recent command: ${displayCommand || 'unknown command'}${exitCode === null || exitCode === undefined ? '' : ` (exit ${exitCode})`}`;
948
+ const compactedLines = buildCompactedSummaryLines([headerLine, ...candidateLines.map((line) => `- ${line}`)], {
949
+ maxTokens,
950
+ maxLines: 10,
951
+ });
952
+ let summary = compactedLines.join('\n').trim();
953
+ let validationMode = 'default';
954
+ const missingAnchors = findMissingAnchors(summary, anchorLines);
955
+
956
+ if (missingAnchors.length > 0) {
957
+ const anchorFirstLines = buildCompactedSummaryLines([
958
+ headerLine,
959
+ ...anchorLines.map((line) => `- ${line}`),
960
+ ...candidateLines.map((line) => `- ${line}`),
961
+ ], {
962
+ maxTokens,
963
+ maxLines: 10,
964
+ forceFirstCount: Math.min(1 + anchorLines.length, 5),
965
+ });
966
+ const anchorFirstSummary = anchorFirstLines.join('\n').trim();
967
+
968
+ if (findMissingAnchors(anchorFirstSummary, anchorLines).length === 0) {
969
+ summary = anchorFirstSummary;
970
+ validationMode = 'anchor-first';
971
+ } else {
972
+ const compactHeaderLine = `- Cmd: ${displayCommand || 'unknown'}${exitCode === null || exitCode === undefined ? '' : ` (exit ${exitCode})`}`;
973
+ summary = buildCompactedSummaryLines([
974
+ compactHeaderLine,
975
+ ...anchorLines.map((line) => `- ${line}`),
976
+ ], {
977
+ maxTokens,
978
+ maxLines: 10,
979
+ forceFirstCount: Math.min(1 + anchorLines.length, 5),
980
+ }).join('\n').trim();
981
+ validationMode = 'forced-anchors';
982
+ }
983
+ }
984
+
985
+ const tokensBefore = estimateTokenCount(rawText);
986
+ const tokensAfter = estimateTokenCount(summary);
987
+
988
+ return {
989
+ summary,
990
+ tokensBefore,
991
+ tokensAfter,
992
+ savedTokens: Math.max(0, tokensBefore - tokensAfter),
993
+ rawLineCount: allLines.filter((line) => line.trim()).length,
994
+ keptLineCount: summary.split(/\r?\n/).filter((line) => line.trim()).length,
995
+ command: displayCommand,
996
+ exitCode,
997
+ profile: profile?.id ?? 'generic',
998
+ validationMode,
999
+ };
1000
+ }
1001
+
1002
+ function normalizeOutputHistoryEntry(entry) {
1003
+ if (!entry || typeof entry !== 'object') {
1004
+ return null;
1005
+ }
1006
+
1007
+ const timestamp = Number(entry.timestamp);
1008
+ const tokensBefore = Number(entry.tokensBefore);
1009
+ const tokensAfter = Number(entry.tokensAfter);
1010
+ const savedTokens = Number(entry.savedTokens);
1011
+ const command = typeof entry.command === 'string' ? entry.command.trim() : '';
1012
+ const summary = typeof entry.summary === 'string' ? entry.summary : '';
1013
+ const rawPath = typeof entry.rawPath === 'string' ? normalizeRuntimeRelativePath(entry.rawPath) : '';
1014
+ const rawBytes = Number(entry.rawBytes);
1015
+ const recoveryReason = typeof entry.recoveryReason === 'string' ? entry.recoveryReason.trim() : '';
1016
+ const truncated = typeof entry.truncated === 'boolean' ? entry.truncated : false;
1017
+
1018
+ return {
1019
+ ...entry,
1020
+ command,
1021
+ summary,
1022
+ timestamp: Number.isFinite(timestamp) ? timestamp : Date.now(),
1023
+ tokensBefore: Number.isFinite(tokensBefore) ? tokensBefore : 0,
1024
+ tokensAfter: Number.isFinite(tokensAfter) ? tokensAfter : 0,
1025
+ savedTokens: Number.isFinite(savedTokens) ? savedTokens : 0,
1026
+ rawSaved: Boolean(entry.rawSaved),
1027
+ rawPath,
1028
+ rawBytes: Number.isFinite(rawBytes) ? rawBytes : 0,
1029
+ recoveryReason: recoveryReason || null,
1030
+ truncated,
1031
+ };
1032
+ }
1033
+
1034
+ function normalizeOutputSummaryForDedupe(summary) {
1035
+ return String(summary ?? '')
1036
+ .split(/\r?\n/)
1037
+ .map((line) => String(line ?? '').trim())
1038
+ .filter((line) => line && !/^- Full output:\s+/i.test(line))
1039
+ .join('\n');
1040
+ }
1041
+
1042
+ function buildOutputHistoryDedupeKey(entry) {
1043
+ return [
1044
+ String(entry?.command ?? '').trim(),
1045
+ normalizeOutputSummaryForDedupe(entry?.summary),
1046
+ ].join('\n').trim().toLowerCase();
1047
+ }
1048
+
1049
+ function normalizeOutputHistoryDocument(raw, { maxEntries = DEFAULT_OUTPUT_HISTORY_MAX_ENTRIES } = {}) {
1050
+ const candidates = Array.isArray(raw?.entries)
1051
+ ? raw.entries
1052
+ : Array.isArray(raw)
1053
+ ? raw
1054
+ : [];
1055
+
1056
+ return {
1057
+ entries: candidates
1058
+ .map((entry) => normalizeOutputHistoryEntry(entry))
1059
+ .filter(Boolean)
1060
+ .sort((left, right) => (right.timestamp ?? 0) - (left.timestamp ?? 0))
1061
+ .slice(0, maxEntries),
1062
+ };
1063
+ }
1064
+
1065
+ export async function readOutputHistory(projectRoot, options = {}) {
1066
+ const runtimePaths = buildRuntimePaths(projectRoot);
1067
+ const raw = await readJsonIfExists(runtimePaths.outputHistoryPath);
1068
+ return normalizeOutputHistoryDocument(raw, options);
1069
+ }
1070
+
1071
+ export async function appendOutputHistory(projectRoot, entry, options = {}) {
1072
+ const normalizedEntry = normalizeOutputHistoryEntry(entry);
1073
+ if (!normalizedEntry || !normalizedEntry.command || !normalizedEntry.summary) {
1074
+ return readOutputHistory(projectRoot, options);
1075
+ }
1076
+
1077
+ const runtimePaths = buildRuntimePaths(projectRoot);
1078
+ const history = await readOutputHistory(projectRoot, options);
1079
+ const normalizedKey = buildOutputHistoryDedupeKey(normalizedEntry);
1080
+ const dedupedExistingEntries = history.entries.filter((candidate) => !(
1081
+ buildOutputHistoryDedupeKey(candidate) === normalizedKey
1082
+ ));
1083
+ const nextDocument = normalizeOutputHistoryDocument({
1084
+ entries: [normalizedEntry, ...dedupedExistingEntries],
1085
+ }, options);
1086
+ await writeJson(runtimePaths.outputHistoryPath, nextDocument);
1087
+ return nextDocument;
1088
+ }
1089
+
1090
+ export function summarizeOutputHistory(rawHistory) {
1091
+ const history = normalizeOutputHistoryDocument(rawHistory);
1092
+ if (history.entries.length === 0) {
1093
+ return {
1094
+ lastCompressedAt: 'never',
1095
+ savingsLabel: 'n/a',
1096
+ entryCount: 0,
1097
+ recoveryCount: 0,
1098
+ byProfile: [],
1099
+ };
1100
+ }
1101
+
1102
+ const totalBefore = history.entries.reduce((sum, entry) => sum + (entry.tokensBefore ?? 0), 0);
1103
+ const totalSaved = history.entries.reduce((sum, entry) => sum + (entry.savedTokens ?? 0), 0);
1104
+ const recoveryCount = history.entries.reduce((sum, entry) => sum + (entry.rawSaved ? 1 : 0), 0);
1105
+ const latest = history.entries[0];
1106
+ const percentage = totalBefore > 0 ? Math.trunc((totalSaved / totalBefore) * 100) : 0;
1107
+ const byProfileMap = new Map();
1108
+
1109
+ for (const entry of history.entries) {
1110
+ const profile = typeof entry.profile === 'string' && entry.profile.trim()
1111
+ ? entry.profile.trim()
1112
+ : (detectOutputProfile(String(entry.command ?? ''))?.id ?? 'generic');
1113
+ const bucket = byProfileMap.get(profile) ?? {
1114
+ profile,
1115
+ entryCount: 0,
1116
+ totalBefore: 0,
1117
+ totalSaved: 0,
1118
+ recoveryCount: 0,
1119
+ };
1120
+ bucket.entryCount += 1;
1121
+ bucket.totalBefore += Number(entry.tokensBefore ?? 0);
1122
+ bucket.totalSaved += Number(entry.savedTokens ?? 0);
1123
+ bucket.recoveryCount += entry.rawSaved ? 1 : 0;
1124
+ byProfileMap.set(profile, bucket);
1125
+ }
1126
+
1127
+ const byProfile = [...byProfileMap.values()]
1128
+ .map((bucket) => ({
1129
+ ...bucket,
1130
+ savingsLabel: bucket.entryCount > 0 && bucket.totalBefore > 0
1131
+ ? `${Math.trunc((bucket.totalSaved / bucket.totalBefore) * 100)}% avg`
1132
+ : 'n/a',
1133
+ }))
1134
+ .sort((left, right) => (
1135
+ right.totalSaved - left.totalSaved
1136
+ || right.entryCount - left.entryCount
1137
+ || left.profile.localeCompare(right.profile)
1138
+ ));
1139
+
1140
+ return {
1141
+ lastCompressedAt: latest.timestamp ? new Date(latest.timestamp).toISOString() : 'unknown',
1142
+ savingsLabel: `${percentage}% avg`,
1143
+ entryCount: history.entries.length,
1144
+ recoveryCount,
1145
+ byProfile,
1146
+ };
1147
+ }
1148
+
1149
+ export async function captureCompressedToolOutput(
1150
+ projectRoot,
1151
+ {
1152
+ command = '',
1153
+ stdout = '',
1154
+ stderr = '',
1155
+ exitCode = null,
1156
+ promptCache = true,
1157
+ maxTokens = 180,
1158
+ } = {},
1159
+ ) {
1160
+ const requestKey = buildCompactMachineKey('tool-output-v1', {
1161
+ command: String(command ?? '').trim(),
1162
+ stdoutHash: buildCompactMachineKey('stdout', stripAnsi(stdout)),
1163
+ stderrHash: buildCompactMachineKey('stderr', stripAnsi(stderr)),
1164
+ exitCode,
1165
+ maxTokens,
1166
+ });
1167
+
1168
+ if (promptCache) {
1169
+ const cached = await readPromptCacheEntry(projectRoot, requestKey, { touch: true });
1170
+ if (cached?.content) {
1171
+ const cachedCommand = String(cached.metadata?.command ?? command ?? '').trim();
1172
+ const cachedDisplayCommand = shortenProjectRootSegments(cachedCommand, projectRoot).trim() || cachedCommand;
1173
+ const cachedProfile = typeof cached.metadata?.profile === 'string' && cached.metadata.profile.trim()
1174
+ ? cached.metadata.profile.trim()
1175
+ : (detectOutputProfile(cachedDisplayCommand)?.id ?? 'generic');
1176
+ await appendOutputHistory(projectRoot, {
1177
+ timestamp: Date.now(),
1178
+ command: cachedCommand,
1179
+ profile: cachedProfile,
1180
+ summary: String(cached.content),
1181
+ tokensBefore: cached.tokensBefore ?? 0,
1182
+ tokensAfter: cached.tokensAfter ?? 0,
1183
+ savedTokens: cached.savedTokens ?? 0,
1184
+ exitCode,
1185
+ rawSaved: Boolean(cached.metadata?.rawSaved),
1186
+ rawPath: cached.metadata?.rawPath ?? '',
1187
+ rawBytes: cached.metadata?.rawBytes ?? 0,
1188
+ recoveryReason: cached.metadata?.recoveryReason ?? null,
1189
+ truncated: Boolean(cached.metadata?.truncated),
1190
+ });
1191
+ return {
1192
+ requestKey,
1193
+ summary: cached.content,
1194
+ tokensBefore: cached.tokensBefore ?? 0,
1195
+ tokensAfter: cached.tokensAfter ?? 0,
1196
+ savedTokens: cached.savedTokens ?? 0,
1197
+ command: String(command ?? '').trim(),
1198
+ exitCode,
1199
+ rawSaved: Boolean(cached.metadata?.rawSaved),
1200
+ rawPath: cached.metadata?.rawPath ?? '',
1201
+ rawBytes: cached.metadata?.rawBytes ?? 0,
1202
+ recoveryReason: cached.metadata?.recoveryReason ?? null,
1203
+ fromCache: true,
1204
+ };
1205
+ }
1206
+ }
1207
+
1208
+ const result = compressToolOutput({
1209
+ command,
1210
+ stdout,
1211
+ stderr,
1212
+ exitCode,
1213
+ maxTokens,
1214
+ projectRoot,
1215
+ });
1216
+ const recoveryReason = buildRawOutputRecoveryReason({
1217
+ exitCode,
1218
+ tokensBefore: result.tokensBefore,
1219
+ savedTokens: result.savedTokens,
1220
+ });
1221
+ let rawOutputMetadata = {
1222
+ rawSaved: false,
1223
+ rawPath: '',
1224
+ rawBytes: 0,
1225
+ recoveryReason: null,
1226
+ truncated: false,
1227
+ };
1228
+
1229
+ if (recoveryReason) {
1230
+ rawOutputMetadata = {
1231
+ ...rawOutputMetadata,
1232
+ ...(await persistRawOutput(projectRoot, {
1233
+ command,
1234
+ summary: result.summary,
1235
+ profile: result.profile,
1236
+ stdout,
1237
+ stderr,
1238
+ exitCode,
1239
+ })),
1240
+ recoveryReason,
1241
+ };
1242
+ const summaryWithRecoveryHint = buildRecoveryHintSummary(result.summary, rawOutputMetadata.rawPath, {
1243
+ tokensBefore: result.tokensBefore,
1244
+ });
1245
+ if (summaryWithRecoveryHint) {
1246
+ result.summary = summaryWithRecoveryHint;
1247
+ result.tokensAfter = estimateTokenCount(result.summary);
1248
+ result.savedTokens = Math.max(0, result.tokensBefore - result.tokensAfter);
1249
+ result.keptLineCount = result.summary.split(/\r?\n/).filter((line) => line.trim()).length;
1250
+ }
1251
+ }
1252
+ await appendOutputHistory(projectRoot, {
1253
+ timestamp: Date.now(),
1254
+ command: result.command,
1255
+ profile: result.profile,
1256
+ summary: result.summary,
1257
+ tokensBefore: result.tokensBefore,
1258
+ tokensAfter: result.tokensAfter,
1259
+ savedTokens: result.savedTokens,
1260
+ exitCode,
1261
+ ...rawOutputMetadata,
1262
+ });
1263
+
1264
+ await recordCompaction(projectRoot, {
1265
+ source: 'tool-output',
1266
+ beforeText: [command, stdout, stderr].filter(Boolean).join('\n'),
1267
+ afterText: result.summary,
1268
+ metadata: {
1269
+ command: result.command,
1270
+ exitCode,
1271
+ },
1272
+ });
1273
+
1274
+ if (promptCache) {
1275
+ await writePromptCacheEntry(projectRoot, {
1276
+ requestKey,
1277
+ kind: 'tool-output',
1278
+ updatedAt: Date.now(),
1279
+ tokensBefore: result.tokensBefore,
1280
+ tokensAfter: result.tokensAfter,
1281
+ savedTokens: result.savedTokens,
1282
+ content: result.summary,
1283
+ metadata: {
1284
+ command: result.command,
1285
+ exitCode,
1286
+ profile: result.profile,
1287
+ rawSaved: rawOutputMetadata.rawSaved,
1288
+ rawPath: rawOutputMetadata.rawPath,
1289
+ rawBytes: rawOutputMetadata.rawBytes,
1290
+ recoveryReason: rawOutputMetadata.recoveryReason,
1291
+ truncated: rawOutputMetadata.truncated,
1292
+ },
1293
+ });
1294
+ }
1295
+
1296
+ return {
1297
+ requestKey,
1298
+ ...result,
1299
+ ...rawOutputMetadata,
1300
+ fromCache: false,
1301
+ };
1302
+ }
1303
+
1304
+ export function selectFreshOutputEntries(rawHistory, { now = Date.now(), limit = 1 } = {}) {
1305
+ const history = normalizeOutputHistoryDocument(rawHistory);
1306
+ return history.entries
1307
+ .filter((entry) => typeof entry.timestamp === 'number' && (now - entry.timestamp) <= FRESH_OUTPUT_WINDOW_MS)
1308
+ .slice(0, limit);
1309
+ }