@hienlh/ppm 0.9.85 → 0.9.87

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 (243) hide show
  1. package/260415-0932-git-graph-stash-rebase-conflicts/reports/code-reviewer-260415-1020-stash-rebase-conflicts.md +288 -0
  2. package/260415-0932-git-graph-stash-rebase-conflicts/reports/tester-260415-1020-build-check.md +117 -0
  3. package/260415-1150-ext-silent-failure-debugging/reports/code-reviewer-260415-1159-ext-error-reporting-review.md +205 -0
  4. package/260415-1150-ext-silent-failure-debugging/reports/docs-manager-260415-1206-ext-error-reporting.md +99 -0
  5. package/260415-1150-ext-silent-failure-debugging/reports/tester-260415-1159-extension-error-reporting.md +174 -0
  6. package/CHANGELOG.md +24 -0
  7. package/dist/web/assets/{_basePickBy-D-bUmjma.js → _basePickBy-Bj0dI1ei.js} +1 -1
  8. package/dist/web/assets/{_baseUniq-BnXXIfRB.js → _baseUniq-CyzdZeQH.js} +1 -1
  9. package/dist/web/assets/ai-settings-section-Bo9lCaTd.js +1 -0
  10. package/dist/web/assets/{api-settings-Qi2xRiHa.js → api-settings-CUxg9RE5.js} +1 -1
  11. package/dist/web/assets/{arc-DB9vXGzd.js → arc-CxgHJ7Z4.js} +1 -1
  12. package/dist/web/assets/architecture-PBZL5I3N-DDFO_NKq.js +1 -0
  13. package/dist/web/assets/{architectureDiagram-2XIMDMQ5-BBV25747.js → architectureDiagram-2XIMDMQ5-D16OotsC.js} +1 -1
  14. package/dist/web/assets/arrow-up-I9-21gkR.js +1 -0
  15. package/dist/web/assets/{blockDiagram-WCTKOSBZ-BOTnY2Lq.js → blockDiagram-WCTKOSBZ-Ct57Wtfk.js} +1 -1
  16. package/dist/web/assets/{c4Diagram-IC4MRINW-D7QAUdHD.js → c4Diagram-IC4MRINW-BIymcNsg.js} +1 -1
  17. package/dist/web/assets/channel-wumTB1if.js +1 -0
  18. package/dist/web/assets/chat-tab-R4gKsnxD.js +10 -0
  19. package/dist/web/assets/chevron-right-DY_wImxB.js +1 -0
  20. package/dist/web/assets/{chunk-4BX2VUAB-BnOVw77D.js → chunk-4BX2VUAB-CENmY7Kw.js} +1 -1
  21. package/dist/web/assets/{chunk-55IACEB6-BftA8DxR.js → chunk-55IACEB6-DhZGI1l3.js} +1 -1
  22. package/dist/web/assets/{chunk-7E7YKBS2-B0vnP8v3.js → chunk-7E7YKBS2-DZcnC7Ow.js} +1 -1
  23. package/dist/web/assets/{chunk-7R4GIKGN-Czlaj26D.js → chunk-7R4GIKGN-y8bfHEy-.js} +2 -2
  24. package/dist/web/assets/{chunk-C72U2L5F-DpEbDtMo.js → chunk-C72U2L5F-BHPkfQj2.js} +1 -1
  25. package/dist/web/assets/{chunk-EGIJ26TM-BWXe6lkx.js → chunk-EGIJ26TM-nant2LXl.js} +1 -1
  26. package/dist/web/assets/{chunk-FMBD7UC4-DspqhPfk.js → chunk-FMBD7UC4-Bog4cpN-.js} +1 -1
  27. package/dist/web/assets/{chunk-GEFDOKGD-D6HHRbYk.js → chunk-GEFDOKGD-86LFbsAC.js} +1 -1
  28. package/dist/web/assets/chunk-GLR3WWYH-Re-5eSlQ.js +2 -0
  29. package/dist/web/assets/chunk-HHEYEP7N-C45i5G_3.js +1 -0
  30. package/dist/web/assets/{chunk-JSJVCQXG-BC8wnMwf.js → chunk-JSJVCQXG-23eG9mgt.js} +1 -1
  31. package/dist/web/assets/{chunk-KX2RTZJC-D3VDtyvX.js → chunk-KX2RTZJC-CHj8TnTB.js} +1 -1
  32. package/dist/web/assets/{chunk-KYZI473N-Z-NBw_HS.js → chunk-KYZI473N-gqRLpJ4w.js} +1 -1
  33. package/dist/web/assets/{chunk-L3YUKLVL--RGkEh__.js → chunk-L3YUKLVL-DnSMmNFC.js} +1 -1
  34. package/dist/web/assets/{chunk-MX3YWQON-2B76t_Kx.js → chunk-MX3YWQON-B6g1ZH9X.js} +1 -1
  35. package/dist/web/assets/{chunk-NQ4KR5QH-BekY3tEi.js → chunk-NQ4KR5QH-DX32345Y.js} +1 -1
  36. package/dist/web/assets/{chunk-O4XLMI2P-2CJLfx_1.js → chunk-O4XLMI2P-Vp_V4P-b.js} +1 -1
  37. package/dist/web/assets/{chunk-OZEHJAEY-sug_L09P.js → chunk-OZEHJAEY-lKq2SWjA.js} +1 -1
  38. package/dist/web/assets/{chunk-PQ6SQG4A-_fwPRLQy.js → chunk-PQ6SQG4A-Bik13fTV.js} +1 -1
  39. package/dist/web/assets/{chunk-PU5JKC2W-BUaTFJVQ.js → chunk-PU5JKC2W-DD95Rx35.js} +1 -1
  40. package/dist/web/assets/chunk-QZHKN3VN-N3VXx1VH.js +1 -0
  41. package/dist/web/assets/{chunk-R5LLSJPH-C37xW0vj.js → chunk-R5LLSJPH-dRhXRnrb.js} +1 -1
  42. package/dist/web/assets/{chunk-WL4C6EOR-CCkt_MT6.js → chunk-WL4C6EOR-B1iIvLOG.js} +1 -1
  43. package/dist/web/assets/{chunk-XIRO2GV7-Dz2LBq7Y.js → chunk-XIRO2GV7-DZBoNl1_.js} +1 -1
  44. package/dist/web/assets/{chunk-XPW4576I-DenTbBuj.js → chunk-XPW4576I-CgLyyW03.js} +1 -1
  45. package/dist/web/assets/{chunk-XZSTWKYB-Dbp1nUSQ.js → chunk-XZSTWKYB-DjV8xl5A.js} +1 -1
  46. package/dist/web/assets/{chunk-YBOYWFTD-3OTKowjE.js → chunk-YBOYWFTD-D_ILLe6_.js} +1 -1
  47. package/dist/web/assets/classDiagram-VBA2DB6C-mr-Cb1me.js +1 -0
  48. package/dist/web/assets/classDiagram-v2-RAHNMMFH-BKe8_uda.js +1 -0
  49. package/dist/web/assets/clone--z5KLAuR.js +1 -0
  50. package/dist/web/assets/code-editor-Br0vzTOy.js +8 -0
  51. package/dist/web/assets/columns-2-IeETSfON.js +1 -0
  52. package/dist/web/assets/conflict-editor-BPgCjnNz.js +19 -0
  53. package/dist/web/assets/{cose-bilkent-S5V4N54A-MbmGZnt0.js → cose-bilkent-S5V4N54A-BGNPFv3x.js} +1 -1
  54. package/dist/web/assets/{csv-preview-uZ_7b8I7.js → csv-preview-BZRICDP0.js} +2 -2
  55. package/dist/web/assets/{dagre-CPhI6v-K.js → dagre-CkhlMHnx.js} +1 -1
  56. package/dist/web/assets/{dagre-KLK3FWXG-CmSE-oNj.js → dagre-KLK3FWXG-Cnp996VG.js} +1 -1
  57. package/dist/web/assets/database-CgTomMxt.js +1 -0
  58. package/dist/web/assets/{database-viewer-5xljX0JI.js → database-viewer-DaUoQ-oR.js} +2 -2
  59. package/dist/web/assets/{diagram-E7M64L7V-B5XG3ZT7.js → diagram-E7M64L7V-BZF0tSOr.js} +1 -1
  60. package/dist/web/assets/{diagram-IFDJBPK2-BsP248aX.js → diagram-IFDJBPK2-nUcO8sN8.js} +1 -1
  61. package/dist/web/assets/{diagram-P4PSJMXO-Cna3408N.js → diagram-P4PSJMXO-CW0eCkwC.js} +1 -1
  62. package/dist/web/assets/diff-viewer-BzvK3gAE.js +4 -0
  63. package/dist/web/assets/dist-CM0oD8tQ.js +1 -0
  64. package/dist/web/assets/{erDiagram-INFDFZHY-B7SgktiR.js → erDiagram-INFDFZHY-DSkriYZ9.js} +1 -1
  65. package/dist/web/assets/extension-webview-CGepEw-b.js +3 -0
  66. package/dist/web/assets/{flowDiagram-PKNHOUZH-FOYZZ1OB.js → flowDiagram-PKNHOUZH-CFYAfZBx.js} +1 -1
  67. package/dist/web/assets/{ganttDiagram-A5KZAMGK-CnHVYh9v.js → ganttDiagram-A5KZAMGK-KSn4XAU4.js} +1 -1
  68. package/dist/web/assets/gitGraph-HDMCJU4V-OkvBPi6H.js +1 -0
  69. package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-0G9XxZay.js → gitGraphDiagram-K3NZZRJ6-BMgjjVys.js} +1 -1
  70. package/dist/web/assets/{graphlib-CNiBwlg_.js → graphlib-BWe1iK_s.js} +1 -1
  71. package/dist/web/assets/index-CKsEzQ4f.js +26 -0
  72. package/dist/web/assets/index-Chf0otez.css +2 -0
  73. package/dist/web/assets/info-3K5VOQVL-BDU2_bYD.js +1 -0
  74. package/dist/web/assets/infoDiagram-LFFYTUFH-Diq4Cyc3.js +2 -0
  75. package/dist/web/assets/input-BHj0veau.js +45 -0
  76. package/dist/web/assets/{isEmpty-CcCb5n2-.js → isEmpty-BfLnxq-B.js} +1 -1
  77. package/dist/web/assets/{ishikawaDiagram-PHBUUO56-D4QCzh5J.js → ishikawaDiagram-PHBUUO56-CiVEvp8o.js} +1 -1
  78. package/dist/web/assets/{journeyDiagram-4ABVD52K-CnHYNfKW.js → journeyDiagram-4ABVD52K-CG_v5Aho.js} +1 -1
  79. package/dist/web/assets/jsx-runtime-BRW_vwa9.js +1 -0
  80. package/dist/web/assets/{kanban-definition-K7BYSVSG-Bh_g3EVu.js → kanban-definition-K7BYSVSG-miB0-_Zq.js} +1 -1
  81. package/dist/web/assets/keybindings-store-D5zgHod8.js +1 -0
  82. package/dist/web/assets/{line-6d3eBADm.js → line-CSuSrJ9J.js} +1 -1
  83. package/dist/web/assets/{linear-cA_2lQy7.js → linear-DFN_MPsw.js} +1 -1
  84. package/dist/web/assets/{markdown-renderer-CZ07F7T6.js → markdown-renderer-DSYnGywb.js} +6 -6
  85. package/dist/web/assets/{mermaid-parser.core-C3kd7JXM.js → mermaid-parser.core-CFdP1Z5_.js} +2 -2
  86. package/dist/web/assets/{mindmap-definition-YRQLILUH-CYiUwhr_.js → mindmap-definition-YRQLILUH-pYPWwASE.js} +1 -1
  87. package/dist/web/assets/{ordinal-XHK5vIzZ.js → ordinal-DpFn432U.js} +1 -1
  88. package/dist/web/assets/packet-RMMSAZCW-BwpIpYB3.js +1 -0
  89. package/dist/web/assets/pie-UPGHQEXC-BPgAfmes.js +1 -0
  90. package/dist/web/assets/{pieDiagram-SKSYHLDU-D0S7jeZA.js → pieDiagram-SKSYHLDU-Dovdlvhu.js} +1 -1
  91. package/dist/web/assets/plus-DQGIb4mQ.js +1 -0
  92. package/dist/web/assets/port-forwarding-tab-vmqDKmk2.js +1 -0
  93. package/dist/web/assets/{postgres-viewer-RldlAO_m.js → postgres-viewer-0lIAosrr.js} +3 -3
  94. package/dist/web/assets/{quadrantDiagram-337W2JSQ-0hNP63hW.js → quadrantDiagram-337W2JSQ-TXe6cU_F.js} +1 -1
  95. package/dist/web/assets/radar-KQ55EAFF-TqxBkWx-.js +1 -0
  96. package/dist/web/assets/refresh-cw-Clk8fdUD.js +1 -0
  97. package/dist/web/assets/{requirementDiagram-Z7DCOOCP-BVnmqFbL.js → requirementDiagram-Z7DCOOCP-CuiiuGS9.js} +1 -1
  98. package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-DVkYdCJb.js → sankeyDiagram-WA2Y5GQK-BbRmhv0t.js} +1 -1
  99. package/dist/web/assets/scroll-area-BpXCNme3.js +1 -0
  100. package/dist/web/assets/{sequenceDiagram-2WXFIKYE-B80s7sOg.js → sequenceDiagram-2WXFIKYE-B2D8IQDb.js} +1 -1
  101. package/dist/web/assets/settings-tab-CMnv1fce.js +1 -0
  102. package/dist/web/assets/{sql-query-editor-CjZ7Z6XL.js → sql-query-editor-Bc2hAwqT.js} +1 -1
  103. package/dist/web/assets/sqlite-viewer-B60MS2Dy.js +1 -0
  104. package/dist/web/assets/square-vBdqj0bF.js +1 -0
  105. package/dist/web/assets/{stateDiagram-RAJIS63D-BPLXgXRR.js → stateDiagram-RAJIS63D-ylr4HxPu.js} +1 -1
  106. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-D6zvxf3M.js +1 -0
  107. package/dist/web/assets/table-Bi27fEaN.js +1 -0
  108. package/dist/web/assets/{terminal-tab-DjzD8GLn.js → terminal-tab-CCJoLstH.js} +2 -2
  109. package/dist/web/assets/text-wrap-D_OmSzhp.js +1 -0
  110. package/dist/web/assets/{timeline-definition-YZTLITO2-fa_51u1X.js → timeline-definition-YZTLITO2-pMv1grvM.js} +1 -1
  111. package/dist/web/assets/trash-2-CNuB-htI.js +1 -0
  112. package/dist/web/assets/treemap-KZPCXAKY-Kck06FKU.js +1 -0
  113. package/dist/web/assets/{use-monaco-theme-D9XFxQuU.js → use-monaco-theme-BJK48EmK.js} +1 -1
  114. package/dist/web/assets/{vennDiagram-LZ73GAT5-kX4jJn6W.js → vennDiagram-LZ73GAT5-C-rkIUbo.js} +1 -1
  115. package/dist/web/assets/x-Dw3TjeY_.js +1 -0
  116. package/dist/web/assets/{xychartDiagram-JWTSCODW-Bzm5lZBs.js → xychartDiagram-JWTSCODW-CtpjAakO.js} +1 -1
  117. package/dist/web/index.html +18 -22
  118. package/dist/web/sw.js +1 -1
  119. package/docs/codebase-summary.md +169 -13
  120. package/docs/extension-development-guide.md +98 -1
  121. package/docs/journals/260414-1400-ext-git-graph-port-complete.md +147 -0
  122. package/docs/journals/260414-1452-git-graph-faithful-port.md +144 -0
  123. package/docs/journals/260414-1810-git-graph-ui-improvements-complete.md +261 -0
  124. package/docs/journals/260414-2001-bundled-extensions.md +219 -0
  125. package/docs/project-changelog.md +123 -21
  126. package/docs/project-roadmap.md +4 -2
  127. package/docs/system-architecture.md +77 -6
  128. package/package.json +1 -1
  129. package/packages/ext-git-graph/package.json +30 -0
  130. package/packages/ext-git-graph/src/extension-integration.test.ts +230 -0
  131. package/packages/ext-git-graph/src/extension-parsers.test.ts +193 -0
  132. package/packages/ext-git-graph/src/extension.ts +921 -0
  133. package/packages/ext-git-graph/src/git-log-parser.test.ts +271 -0
  134. package/packages/ext-git-graph/src/git-log-parser.ts +38 -0
  135. package/packages/ext-git-graph/src/types.ts +192 -0
  136. package/packages/ext-git-graph/src/webview-html.test.ts +142 -0
  137. package/packages/ext-git-graph/src/webview-html.ts +2417 -0
  138. package/packages/vscode-compat/src/index.ts +4 -0
  139. package/packages/vscode-compat/src/process.ts +25 -0
  140. package/packages/vscode-compat/src/window.ts +10 -0
  141. package/src/cli/commands/ext-cmd.ts +3 -1
  142. package/src/server/ws/extensions.ts +34 -4
  143. package/src/services/contribution-registry.ts +14 -1
  144. package/src/services/extension-host-worker.ts +12 -3
  145. package/src/services/extension-manifest.ts +18 -1
  146. package/src/services/extension-rpc-handlers.ts +68 -2
  147. package/src/services/extension.service.ts +63 -9
  148. package/src/types/extension-messages.ts +3 -1
  149. package/src/types/extension.ts +8 -0
  150. package/src/web/components/editor/code-editor.tsx +16 -4
  151. package/src/web/components/editor/conflict-editor.tsx +368 -0
  152. package/src/web/components/extensions/extension-webview.tsx +153 -12
  153. package/src/web/components/layout/command-palette.tsx +41 -17
  154. package/src/web/components/layout/editor-panel.tsx +16 -4
  155. package/src/web/components/layout/mobile-nav.tsx +6 -5
  156. package/src/web/components/layout/tab-bar.tsx +3 -3
  157. package/src/web/components/layout/tab-content.tsx +17 -5
  158. package/src/web/components/settings/keyboard-shortcuts-section.tsx +46 -1
  159. package/src/web/hooks/use-extension-ws.ts +30 -4
  160. package/src/web/hooks/use-global-keybindings.ts +24 -2
  161. package/src/web/hooks/use-url-sync.ts +8 -3
  162. package/src/web/stores/extension-store.ts +8 -0
  163. package/src/web/stores/keybindings-store.ts +2 -3
  164. package/src/web/stores/panel-store.ts +2 -2
  165. package/src/web/stores/panel-utils.ts +6 -2
  166. package/src/web/stores/tab-store.ts +3 -2
  167. package/dist/web/assets/ai-settings-section-D6d-RmR6.js +0 -1
  168. package/dist/web/assets/architecture-PBZL5I3N-DpVzOETR.js +0 -1
  169. package/dist/web/assets/arrow-up-BigIMx-e.js +0 -1
  170. package/dist/web/assets/channel-Cgy1thYT.js +0 -1
  171. package/dist/web/assets/chat-tab-DXBb9Y3U.js +0 -10
  172. package/dist/web/assets/check-ePA3ZvK4.js +0 -1
  173. package/dist/web/assets/chevron-down-EQA06nR-.js +0 -1
  174. package/dist/web/assets/chevron-right-CXzzT44u.js +0 -1
  175. package/dist/web/assets/chunk-GLR3WWYH-CxUl1sdz.js +0 -2
  176. package/dist/web/assets/chunk-HHEYEP7N-DN7ebS2Y.js +0 -1
  177. package/dist/web/assets/chunk-QZHKN3VN-C4La7oLj.js +0 -1
  178. package/dist/web/assets/classDiagram-VBA2DB6C-C3IyfqG-.js +0 -1
  179. package/dist/web/assets/classDiagram-v2-RAHNMMFH-Dcvhz2pb.js +0 -1
  180. package/dist/web/assets/clone--C7Tby8z.js +0 -1
  181. package/dist/web/assets/code-editor-Cr7JrBKC.js +0 -8
  182. package/dist/web/assets/columns-2-BZ9uqssV.js +0 -1
  183. package/dist/web/assets/createLucideIcon-PuMiQgHl.js +0 -1
  184. package/dist/web/assets/database-D1ToEV9d.js +0 -1
  185. package/dist/web/assets/diff-viewer-BBr6e_gb.js +0 -4
  186. package/dist/web/assets/dist-KUoHa6tg.js +0 -1
  187. package/dist/web/assets/extension-webview-B0klBip8.js +0 -3
  188. package/dist/web/assets/eye-CNcBU6Tx.js +0 -1
  189. package/dist/web/assets/git-graph-CDiwGa0g.js +0 -1
  190. package/dist/web/assets/gitGraph-HDMCJU4V-DcPyMEIJ.js +0 -1
  191. package/dist/web/assets/index-CkaCzNgO.css +0 -2
  192. package/dist/web/assets/index-Ic5uTu20.js +0 -26
  193. package/dist/web/assets/info-3K5VOQVL-Dw4O15cw.js +0 -1
  194. package/dist/web/assets/infoDiagram-LFFYTUFH-DFhmsucr.js +0 -2
  195. package/dist/web/assets/input-CcbTF6ih.js +0 -45
  196. package/dist/web/assets/jsx-runtime-R_NjdZtX.js +0 -1
  197. package/dist/web/assets/keybindings-store-CxE6BlG2.js +0 -1
  198. package/dist/web/assets/packet-RMMSAZCW-o3LmdL8H.js +0 -1
  199. package/dist/web/assets/pie-UPGHQEXC-BjNP0M3B.js +0 -1
  200. package/dist/web/assets/plus-Iso5r9vD.js +0 -1
  201. package/dist/web/assets/port-forwarding-tab-BPuSc6pI.js +0 -1
  202. package/dist/web/assets/radar-KQ55EAFF-gDgOiaME.js +0 -1
  203. package/dist/web/assets/refresh-cw-BgQzFNaG.js +0 -1
  204. package/dist/web/assets/scroll-area-i4EZlOl_.js +0 -1
  205. package/dist/web/assets/settings-tab-BzSSN2BQ.js +0 -1
  206. package/dist/web/assets/sqlite-viewer-CoyZOM_Y.js +0 -1
  207. package/dist/web/assets/square-pfn_LYYy.js +0 -1
  208. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-DksQJ7es.js +0 -1
  209. package/dist/web/assets/table-CHv2x_qg.js +0 -1
  210. package/dist/web/assets/tag-Bb_UFXt0.js +0 -1
  211. package/dist/web/assets/text-wrap-D8BbQYTx.js +0 -1
  212. package/dist/web/assets/trash-2-DYCa06CV.js +0 -1
  213. package/dist/web/assets/treemap-KZPCXAKY-DwFqAvnj.js +0 -1
  214. package/dist/web/assets/x-BXecj-16.js +0 -1
  215. package/src/web/components/git/git-graph-branch-label.tsx +0 -124
  216. package/src/web/components/git/git-graph-constants.ts +0 -185
  217. package/src/web/components/git/git-graph-detail.tsx +0 -107
  218. package/src/web/components/git/git-graph-dialog.tsx +0 -72
  219. package/src/web/components/git/git-graph-row.tsx +0 -167
  220. package/src/web/components/git/git-graph-settings-dialog.tsx +0 -104
  221. package/src/web/components/git/git-graph-svg.tsx +0 -54
  222. package/src/web/components/git/git-graph-toolbar.tsx +0 -195
  223. package/src/web/components/git/git-graph.tsx +0 -193
  224. package/src/web/components/git/use-column-resize.ts +0 -33
  225. package/src/web/components/git/use-git-graph.ts +0 -201
  226. /package/dist/web/assets/{api-client-wQbeUyeh.js → api-client-BvxmRZUi.js} +0 -0
  227. /package/dist/web/assets/{array-X0JlPOfd.js → array-BFDiaBgf.js} +0 -0
  228. /package/dist/web/assets/{csv-parser-CElqio6o.js → csv-parser-i7fjqP2H.js} +0 -0
  229. /package/dist/web/assets/{cytoscape.esm-BfIOPvwt.js → cytoscape.esm-C8i2jUzT.js} +0 -0
  230. /package/dist/web/assets/{defaultLocale-B6RGN4id.js → defaultLocale-ZeknFqNe.js} +0 -0
  231. /package/dist/web/assets/{dist-CK1enexV.js → dist-DZmJeHOA.js} +0 -0
  232. /package/dist/web/assets/{init-BmUWJJHz.js → init-0VJVrkRJ.js} +0 -0
  233. /package/dist/web/assets/{isArrayLikeObject-BrCM-iA1.js → isArrayLikeObject-ClzWCpcm.js} +0 -0
  234. /package/dist/web/assets/{katex-xQS_6bNb.js → katex-DR0kdMDv.js} +0 -0
  235. /package/dist/web/assets/{lib-CfWBrYll.js → lib-DSLzfeW0.js} +0 -0
  236. /package/dist/web/assets/{math-CpLFzrfV.js → math-CRc16Nj6.js} +0 -0
  237. /package/dist/web/assets/{path-CoPyR7c2.js → path-INs8XTPH.js} +0 -0
  238. /package/dist/web/assets/{preload-helper-CH6UZRzu.js → preload-helper-mr3rCizq.js} +0 -0
  239. /package/dist/web/assets/{react-j5zqhEum.js → react-0tkk-ztn.js} +0 -0
  240. /package/dist/web/assets/{rough.esm-D5NinLFK.js → rough.esm-eLccZ4OJ.js} +0 -0
  241. /package/dist/web/assets/{sql-completion-provider-D0xutVaK.js → sql-completion-provider-B8uUWWej.js} +0 -0
  242. /package/dist/web/assets/{src-j04igtQ5.js → src-CqyWLlNZ.js} +0 -0
  243. /package/dist/web/assets/{utils-CSCvNZxE.js → utils-DX8jb5qv.js} +0 -0
@@ -70,6 +70,8 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
70
70
  **v0.9.x polish (post-release):**
71
71
  - File download feature (v0.9.2) — Single-file + folder-as-zip downloads with short-lived tokens, context menu + toolbar UI
72
72
  - Agent Team UI (v0.9.9) — Real-time team monitoring dashboard: REST API + fs.watch inbox events, team activity button with unread pulse, popover/drawer with members + messages, team management in Settings
73
+ - Git-Graph UI Improvements (v0.9.85+) — ✅ Faithful SVG graph rendering (vscode-git-graph port), interactive git workflow (stage/unstage/commit/stash), branch filters, auto-fetch, mobile support, tab system safety guards, UX refinements (branch context menu, dblclick checkout, toast notifications, SVG icons)
74
+ - Git Workflow Enhancements (v0.9.86+) — ✅ Stash management (popover with Apply/Pop/Drop), rebase from context menu, conflict detection (merge/rebase/cherry-pick states), inline conflict resolution editor (Monaco with visual highlighting + Accept buttons), worktree full CRUD (create/remove/prune with project integration)
73
75
 
74
76
  **Multi-provider — v0.9 scope (reduced):**
75
77
  - Tier 1 (full agentic): Claude Agent SDK — file edit, terminal, git, full autonomy
@@ -99,12 +101,12 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
99
101
 
100
102
  ### v0.10.0 — "Enhanced Workflow" (Q3 2026)
101
103
 
102
- **Theme:** Agent collaboration + git workflow. High-impact, independent features that ship fast.
104
+ **Theme:** Agent collaboration + advanced git workflow. High-impact, independent features that ship fast.
103
105
 
104
106
  | Feature | Priority | Description |
105
107
  |---------|----------|-------------|
106
108
  | **Agent Team** | High | Multi-agent collaboration within PPM. Spawn agent teams for parallel task execution — lead agent delegates to specialist agents (coder, tester, reviewer). Task coordination, file ownership, progress tracking. |
107
- | **Worktree management** | Medium | UI to create/switch/delete git worktrees. Use different providers on different branches. Integrated with project switcher. |
109
+ | **Advanced Git Operations** | Medium | Interactive rebase UI, cherry-pick workflow, merge strategy selection. (Worktree management completed in v0.9.86) |
108
110
 
109
111
  ---
110
112
 
@@ -151,13 +151,14 @@ WS /ws/project/:name/terminal/:id → Terminal I/O
151
151
  ```
152
152
  /project/{name} → Project root (project switcher)
153
153
  /project/{name}/editor/{filePath} → Open editor tab (e.g., src/index.ts)
154
+ /project/{name}/conflict-editor/{filePath} → Open conflict resolution editor (during merge/rebase)
154
155
  /project/{name}/chat/{provider}/{sessionId} → Open chat tab
155
156
  /project/{name}/terminal/{index} → Open terminal tab
156
157
  /project/{name}/database/{connId}/{table} → Open database browser
157
158
  /project/{name}/git-graph → Git history graph (singleton)
158
159
  /project/{name}/settings → Settings panel (singleton)
159
160
  ```
160
- Tab IDs are deterministic: `{type}:{identifier}` (e.g., `editor:src/index.ts`, `chat:claude/abc123`). Deep links auto-create missing tabs.
161
+ Tab IDs are deterministic: `{type}:{identifier}` (e.g., `editor:src/index.ts`, `conflict-editor:src/file.ts`, `chat:claude/abc123`). Deep links auto-create missing tabs.
161
162
 
162
163
  ---
163
164
 
@@ -1295,16 +1296,21 @@ Browser (React) ← Zustand store + React components
1295
1296
  ```
1296
1297
 
1297
1298
  **Key components:**
1298
- - **Package Format:** npm packages (`@ppm/ext-database`, `@ppm/ext-docker`, etc.)
1299
+ - **Package Format:** npm packages (`@ppm/ext-database`, `@ppm/ext-git-graph`, `@ppm/ext-docker`, etc.)
1299
1300
  - **Installation:** `~/.ppm/extensions/node_modules/{id}/`
1300
1301
  - **Lifecycle:** Install → Enable → Activate → Deactivate → Remove
1301
1302
  - **Worker Isolation:** Each activated extension runs in a Bun Worker (crash-safe, 10s activation timeout)
1302
1303
  - **Communication:** RPC (Worker↔Main) + WebSocket (Main↔Browser)
1303
1304
  - **API Shim:** `@ppm/vscode-compat` — VSCode-compatible API (commands, window, workspace)
1305
+ - **Subprocess Access:** RPC `process:spawn` handler for extensions needing CLI commands (git, docker, npm, python, etc.)
1304
1306
  - **State Storage:** globalState + workspaceState in SQLite via Memento
1305
1307
  - **UI Bridge:** StatusBar, TreeView, WebviewPanel, QuickPick, InputBox, Notifications
1306
1308
  - **Contributions:** Commands, views, configuration contributed via manifest
1307
1309
 
1310
+ **Official Extensions:**
1311
+ - `@ppm/ext-database` — Database browser with SQLite/PostgreSQL support (tree view + query panel)
1312
+ - `@ppm/ext-git-graph` — Git commit graph visualization (faithful vscode-git-graph SVG algorithm with Bézier curves, uses process:spawn for git CLI across registered projects)
1313
+
1308
1314
  ### Manifest Format
1309
1315
 
1310
1316
  Extension metadata defined in `package.json` under `ppm` key:
@@ -1417,10 +1423,33 @@ Extension metadata defined in `package.json` under `ppm` key:
1417
1423
  }
1418
1424
  ```
1419
1425
 
1420
- **Built-in Methods:**
1421
- - `storage:get(extId, scope, key)` — Get persistent value
1422
- - `storage:set(extId, scope, key, value)` — Set persistent value
1423
- - `storage:delete(extId, scope, key)` — Delete key
1426
+ **Built-in Methods (vscode-compat API):**
1427
+ - `commands:execute(command, ...args)` — Execute command
1428
+ - `commands:list(filterInternal)` — List available commands
1429
+ - `window:showMessage(level, message, items[])` — Show dialog with buttons
1430
+ - `window:showQuickPick(items[], options)` — Quick pick menu
1431
+ - `window:showInputBox(options)` — Text input dialog
1432
+ - `window:webview:create(panelId, extensionId, viewType, title)` — Create webview panel
1433
+ - `window:webview:html(panelId, html)` — Set webview content
1434
+ - `window:webview:postMessage(panelId, message)` — Send message to webview
1435
+ - `window:tree:update(viewId, items[])` — Update tree view items
1436
+ - `window:tree:refresh(viewId)` — Refresh tree view
1437
+ - `window:statusbar:update(item)` — Update/create status bar item
1438
+ - `window:statusbar:remove(itemId)` — Remove status bar item
1439
+ - `workspace:config:get(key)` — Read config value
1440
+ - `workspace:config:update(key, value, target)` — Write config value
1441
+ - `workspace:fs:readFile(filePath)` — Read file (base64 encoded)
1442
+ - `workspace:fs:writeFile(filePath, base64Content)` — Write file
1443
+ - `workspace:fs:stat(filePath)` — Get file metadata
1444
+ - `workspace:fs:readDirectory(dirPath)` — List directory contents
1445
+
1446
+ **Subprocess Execution (extensions needing CLI access):**
1447
+ - `process:spawn(command, args[], options)` — Execute external command
1448
+ - **Allowed commands:** git, node, bun, npm, yarn, pnpm, docker, psql, sqlite3, python3, python
1449
+ - **Options:** `{ cwd?: string, timeout?: number }` (default: 30s timeout, CWD must be within registered project paths, ~/.ppm/extensions/, or current process directory)
1450
+ - **Returns:** `{ code: number, stdout: string, stderr: string, error?: string }`
1451
+ - **Example:** See ext-git-graph for real-world usage (runs `git log --all` across any registered project via path-based CWD)
1452
+
1424
1453
  - Extension can define custom RPC methods via `rpc.onRequest(method, handler)`
1425
1454
 
1426
1455
  ### State Storage
@@ -1648,6 +1677,48 @@ Main Process Worker
1648
1677
 
1649
1678
  5. Extension auto-activates based on `activationEvents`, state persists
1650
1679
 
1680
+ ### Error Handling & Debugging
1681
+
1682
+ **Activation Error Tracking:**
1683
+ - `ExtensionService.activationErrors` Map tracks `extId → error message` for all failed activations
1684
+ - Errors set during `activate()` if worker response indicates failure (`!result.ok`)
1685
+ - Errors cleared on successful activation or worker termination
1686
+ - Errors included in `contributions:update` message sent via WS to browser on client connect
1687
+
1688
+ **User Feedback (UI):**
1689
+ - **Command Errors:** When extension command fails, toast shows "Extension command failed: {error}" with error details
1690
+ - **Timeout Handling:** If webview panel doesn't load within 10s, fallback UI displays activation error (if available) + "Retry" button
1691
+ - **Retry Button:** User can click to re-trigger the command without page reload (re-dispatches `ext:command:execute`)
1692
+
1693
+ **Breadcrumb Logging (Console):**
1694
+ - **`[ExtService]`** — Main process lifecycle: activation start/success, worker lifecycle, contributions broadcast
1695
+ - **`[ExtHost]`** — Worker-side execution: command routing, handler invocation, error context
1696
+ - **`[ExtWS]`** — WebSocket bridge: client connect, message handling, error responses
1697
+ - **Extension-specific tags** — e.g., `[ext-git-graph]` for extension-specific log context
1698
+
1699
+ **Example Log Flow (normal):**
1700
+ ```
1701
+ [ExtService] startup: activating ext-git-graph...
1702
+ [ExtWS] Client connected (1 total)
1703
+ [ExtHost] activating ext-git-graph from dist/extension.js
1704
+ [ExtHost] activated ext-git-graph (1 total)
1705
+ [ExtService] activated ext-git-graph successfully
1706
+ [ExtWS] command:execute "git-graph.view"
1707
+ [ExtHost] command:execute "git-graph.view" (1 extensions active)
1708
+ [ExtHost] routing "git-graph.view" → ext-git-graph
1709
+ ```
1710
+
1711
+ **Example Log Flow (error):**
1712
+ ```
1713
+ [ExtService] startup: activating ext-git-graph...
1714
+ [ExtHost] activating ext-git-graph from dist/extension.ts
1715
+ [ExtHost] ERROR: Cannot find module 'missing-dep'
1716
+ [ExtService] Failed to activate ext-git-graph on startup: Cannot find module 'missing-dep'
1717
+ → activationErrors["ext-git-graph"] = "Cannot find module 'missing-dep'"
1718
+ → browser receives { type: "contributions:update", activationErrors: {"ext-git-graph": "..."} }
1719
+ → user sees toast: "Extension "ext-git-graph" failed to activate: Cannot find module..."
1720
+ ```
1721
+
1651
1722
  ### Crash Safety
1652
1723
 
1653
1724
  **Worker Isolation:**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.9.85",
3
+ "version": "0.9.87",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@ppm/ext-git-graph",
3
+ "version": "0.1.0",
4
+ "main": "src/extension.ts",
5
+ "engines": { "ppm": ">=0.9.0" },
6
+ "activationEvents": ["onCommand:git-graph.view"],
7
+ "contributes": {
8
+ "commands": [
9
+ { "command": "git-graph.view", "title": "Git Graph: View Repository", "icon": "git-branch" }
10
+ ],
11
+ "keybindings": [
12
+ { "command": "git-graph.view", "key": "Mod+G" }
13
+ ],
14
+ "menus": {
15
+ "commandPalette": [
16
+ { "command": "git-graph.view" }
17
+ ]
18
+ },
19
+ "configuration": {
20
+ "properties": {
21
+ "git-graph.maxCommits": { "type": "number", "default": 300, "description": "Maximum commits to load" },
22
+ "git-graph.showRemoteBranches": { "type": "boolean", "default": true, "description": "Show remote branches" }
23
+ }
24
+ }
25
+ },
26
+ "ppm": {
27
+ "displayName": "Git Graph",
28
+ "icon": "git-branch"
29
+ }
30
+ }
@@ -0,0 +1,230 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "bun:test";
2
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+
6
+ /**
7
+ * Integration tests for git-graph extension.
8
+ * Uses real git repositories to test git data fetching and parsing.
9
+ */
10
+
11
+ let testRepoDir: string;
12
+
13
+ async function spawnGit(args: string[], cwd: string) {
14
+ const env = {
15
+ GIT_AUTHOR_NAME: "Test Author",
16
+ GIT_AUTHOR_EMAIL: "test@example.com",
17
+ GIT_COMMITTER_NAME: "Test Committer",
18
+ GIT_COMMITTER_EMAIL: "committer@example.com",
19
+ };
20
+
21
+ const proc = Bun.spawn(["git", ...args], {
22
+ cwd,
23
+ env,
24
+ stdout: "pipe",
25
+ stderr: "pipe",
26
+ });
27
+
28
+ const stdout = await new Response(proc.stdout).text();
29
+ const exitCode = await proc.exited;
30
+ return { stdout, exitCode };
31
+ }
32
+
33
+ async function initGitRepo(repoPath: string, withRemote = false) {
34
+ const env = {
35
+ GIT_AUTHOR_NAME: "Test Author",
36
+ GIT_AUTHOR_EMAIL: "test@example.com",
37
+ GIT_COMMITTER_NAME: "Test Committer",
38
+ GIT_COMMITTER_EMAIL: "committer@example.com",
39
+ };
40
+
41
+ // Initialize repo
42
+ await Bun.spawn(["git", "init"], { cwd: repoPath, env, stdout: "pipe" }).exited;
43
+
44
+ // Create initial commit
45
+ writeFileSync(join(repoPath, "README.md"), "# Test Repo\n");
46
+ await Bun.spawn(["git", "add", "README.md"], { cwd: repoPath, env }).exited;
47
+ await Bun.spawn(["git", "commit", "-m", "Initial commit"], { cwd: repoPath, env }).exited;
48
+
49
+ // Create a branch
50
+ await Bun.spawn(["git", "checkout", "-b", "develop"], { cwd: repoPath, env }).exited;
51
+ writeFileSync(join(repoPath, "feature.txt"), "Feature content\n");
52
+ await Bun.spawn(["git", "add", "feature.txt"], { cwd: repoPath, env }).exited;
53
+ await Bun.spawn(["git", "commit", "-m", "Add feature"], { cwd: repoPath, env }).exited;
54
+
55
+ // Create a tag
56
+ await Bun.spawn(["git", "tag", "v1.0.0"], { cwd: repoPath, env }).exited;
57
+
58
+ // Back to main
59
+ await Bun.spawn(["git", "checkout", "main"], { cwd: repoPath, env }).exited;
60
+
61
+ if (withRemote) {
62
+ // Create a bare repo to act as remote
63
+ const remoteDir = join(tmpdir(), `remote-${Date.now()}`);
64
+ await Bun.spawn(["git", "init", "--bare"], { cwd: remoteDir, env }).exited;
65
+ await Bun.spawn(["git", "remote", "add", "origin", remoteDir], { cwd: repoPath, env }).exited;
66
+ }
67
+ }
68
+
69
+ describe("git-graph extension: integration tests", () => {
70
+ beforeEach(() => {
71
+ testRepoDir = mkdtempSync(resolve(tmpdir(), "ppm-git-graph-"));
72
+ });
73
+
74
+ afterEach(() => {
75
+ try {
76
+ rmSync(testRepoDir, { recursive: true, force: true });
77
+ } catch {}
78
+ });
79
+
80
+ it("fetches branches from real git repo", async () => {
81
+ await initGitRepo(testRepoDir);
82
+
83
+ const result = await spawnGit(
84
+ ["branch", "-a", "--format=%(refname:short)|%(objectname:short)|%(HEAD)"],
85
+ testRepoDir
86
+ );
87
+
88
+ const lines = result.stdout.trim().split("\n").filter(Boolean);
89
+ expect(lines.length).toBeGreaterThan(0);
90
+ // Should have at least main and develop
91
+ const branches = lines.map((line) => line.split("|")[0]);
92
+ expect(branches).toContain("main");
93
+ expect(branches).toContain("develop");
94
+ });
95
+
96
+ it("fetches tags from real git repo", async () => {
97
+ await initGitRepo(testRepoDir);
98
+
99
+ const result = await spawnGit(
100
+ ["tag", "-l", "--format=%(refname:short)|%(objectname:short)"],
101
+ testRepoDir
102
+ );
103
+
104
+ const lines = result.stdout.trim().split("\n").filter(Boolean);
105
+ const tags = lines.map((line) => line.split("|")[0]);
106
+ expect(tags).toContain("v1.0.0");
107
+ });
108
+
109
+ it("fetches git log with custom format", async () => {
110
+ await initGitRepo(testRepoDir);
111
+
112
+ const result = await spawnGit(
113
+ [
114
+ "log",
115
+ `--format=%H%n%P%n%an%n%ae%n%at%n%cn%n%ce%n%ct%n%D%n%s%n<END_COMMIT>`,
116
+ "--topo-order",
117
+ "--all",
118
+ ],
119
+ testRepoDir
120
+ );
121
+
122
+ expect(result.stdout).toContain("<END_COMMIT>");
123
+ const commits = result.stdout.split("<END_COMMIT>").filter((b) => b.trim());
124
+ expect(commits.length).toBeGreaterThanOrEqual(2); // At least 2 commits
125
+ });
126
+
127
+ it("gets current branch name", async () => {
128
+ await initGitRepo(testRepoDir);
129
+ await spawnGit(["checkout", "main"], testRepoDir);
130
+
131
+ const result = await spawnGit(["rev-parse", "--abbrev-ref", "HEAD"], testRepoDir);
132
+
133
+ expect(result.stdout.trim()).toBe("main");
134
+ });
135
+
136
+ it("gets HEAD commit hash", async () => {
137
+ await initGitRepo(testRepoDir);
138
+
139
+ const result = await spawnGit(["rev-parse", "HEAD"], testRepoDir);
140
+
141
+ const hash = result.stdout.trim();
142
+ expect(hash).toMatch(/^[0-9a-f]{40}$/); // Valid SHA-1
143
+ });
144
+
145
+ it("handles repo with no commits gracefully", async () => {
146
+ // Initialize but don't create commits
147
+ await Bun.spawn(["git", "init"], { cwd: testRepoDir, stdout: "pipe" }).exited;
148
+
149
+ const result = await spawnGit(["log", "--format=%H"], testRepoDir);
150
+ expect(result.exitCode).not.toBe(0); // Will fail with no commits
151
+ });
152
+
153
+ it("parses commit details with show --stat", async () => {
154
+ await initGitRepo(testRepoDir);
155
+
156
+ // Get latest commit hash
157
+ const hashResult = await spawnGit(["rev-parse", "HEAD"], testRepoDir);
158
+ const hash = hashResult.stdout.trim();
159
+
160
+ const result = await spawnGit(
161
+ [
162
+ "show",
163
+ "--stat",
164
+ `--format=%H%n%P%n%an%n%ae%n%at%n%cn%n%ce%n%ct%n%B%n<END_MSG>`,
165
+ hash,
166
+ ],
167
+ testRepoDir
168
+ );
169
+
170
+ expect(result.stdout).toContain("<END_MSG>");
171
+ expect(result.stdout).toContain(hash);
172
+ });
173
+
174
+ it("fetches stash list from repo", async () => {
175
+ await initGitRepo(testRepoDir);
176
+
177
+ // Create an unstaged change
178
+ writeFileSync(join(testRepoDir, "test.txt"), "test content");
179
+ await spawnGit(["add", "test.txt"], testRepoDir);
180
+ await spawnGit(["stash", "push", "-m", "Test stash"], testRepoDir);
181
+
182
+ const result = await spawnGit(
183
+ ["stash", "list", "--format=%gd|%H|%s"],
184
+ testRepoDir
185
+ );
186
+
187
+ expect(result.stdout).toContain("stash@{0}");
188
+ expect(result.stdout).toContain("Test stash");
189
+ });
190
+
191
+ it("handles git operations with special characters in message", async () => {
192
+ await initGitRepo(testRepoDir);
193
+
194
+ writeFileSync(join(testRepoDir, "special.txt"), "content with special chars");
195
+ await spawnGit(["add", "special.txt"], testRepoDir);
196
+ await spawnGit(
197
+ ["commit", "-m", "Fix: issue #123 & test 'quotes' \"double\""],
198
+ testRepoDir
199
+ );
200
+
201
+ const result = await spawnGit(["log", "--oneline", "-1"], testRepoDir);
202
+
203
+ expect(result.stdout).toContain("issue #123");
204
+ });
205
+
206
+ it("respects skip parameter in git log", async () => {
207
+ await initGitRepo(testRepoDir);
208
+
209
+ // Create 3 additional commits
210
+ for (let i = 0; i < 3; i++) {
211
+ writeFileSync(join(testRepoDir, `file${i}.txt`), `content ${i}`);
212
+ await spawnGit(["add", "."], testRepoDir);
213
+ await spawnGit(["commit", "-m", `Commit ${i}`], testRepoDir);
214
+ }
215
+
216
+ const resultAll = await spawnGit(
217
+ ["log", "--format=%H", "--all"],
218
+ testRepoDir
219
+ );
220
+ const allCommits = resultAll.stdout.trim().split("\n").length;
221
+
222
+ const resultSkip2 = await spawnGit(
223
+ ["log", "--format=%H", "--skip=2", "--all"],
224
+ testRepoDir
225
+ );
226
+ const skippedCommits = resultSkip2.stdout.trim().split("\n").filter(Boolean).length;
227
+
228
+ expect(skippedCommits).toBeLessThan(allCommits);
229
+ });
230
+ });
@@ -0,0 +1,193 @@
1
+ import { describe, it, expect } from "bun:test";
2
+
3
+ // Import the parser functions from extension.ts
4
+ // We'll need to export them for testing purposes
5
+ function parseBranches(stdout: string) {
6
+ return stdout.trim().split("\n").filter(Boolean).map((line) => {
7
+ const [name, hash, head] = line.split("|");
8
+ const remote = name.includes("/") ? name.split("/")[0] : undefined;
9
+ return { name, hash, current: head === "*", remote };
10
+ });
11
+ }
12
+
13
+ function parseTags(stdout: string) {
14
+ return stdout.trim().split("\n").filter(Boolean).map((line) => {
15
+ const [name, hash] = line.split("|");
16
+ return { name, hash };
17
+ });
18
+ }
19
+
20
+ function parseRemotes(stdout: string) {
21
+ const map = new Map<string, { fetchUrl: string; pushUrl: string }>();
22
+ for (const line of stdout.trim().split("\n").filter(Boolean)) {
23
+ const match = line.match(/^(\S+)\s+(\S+)\s+\((\w+)\)$/);
24
+ if (!match) continue;
25
+ const [, name, url, type] = match;
26
+ if (!map.has(name)) map.set(name, { fetchUrl: "", pushUrl: "" });
27
+ const entry = map.get(name)!;
28
+ if (type === "fetch") entry.fetchUrl = url;
29
+ else entry.pushUrl = url;
30
+ }
31
+ return [...map.entries()].map(([name, urls]) => ({ name, ...urls }));
32
+ }
33
+
34
+ function parseStashes(stdout: string) {
35
+ return stdout.trim().split("\n").filter(Boolean).map((line, i) => {
36
+ const [, hash, message] = line.split("|");
37
+ return { index: i, hash, message };
38
+ });
39
+ }
40
+
41
+ describe("extension.ts: parseBranches", () => {
42
+ it("parses current branch with asterisk", () => {
43
+ const output = "main|abc1234|*\n";
44
+ const result = parseBranches(output);
45
+ expect(result).toHaveLength(1);
46
+ expect(result[0]).toEqual({ name: "main", hash: "abc1234", current: true, remote: undefined });
47
+ });
48
+
49
+ it("parses non-current branches", () => {
50
+ const output = "develop|def5678|\nfeature|ghi9012|\n";
51
+ const result = parseBranches(output);
52
+ expect(result).toHaveLength(2);
53
+ expect(result[0]?.current).toBe(false);
54
+ expect(result[1]?.current).toBe(false);
55
+ });
56
+
57
+ it("parses remote branches with remote name", () => {
58
+ const output = "origin/main|abc1234|\nupstream/develop|def5678|\n";
59
+ const result = parseBranches(output);
60
+ expect(result[0]).toEqual({ name: "origin/main", hash: "abc1234", current: false, remote: "origin" });
61
+ expect(result[1]).toEqual({ name: "upstream/develop", hash: "def5678", current: false, remote: "upstream" });
62
+ });
63
+
64
+ it("parses mixed local and remote branches", () => {
65
+ const output = "main|abc1234|*\norigin/main|abc1234|\ndevelop|def5678|\norigin/develop|def5678|\n";
66
+ const result = parseBranches(output);
67
+ expect(result).toHaveLength(4);
68
+ expect(result[0]?.remote).toBeUndefined();
69
+ expect(result[1]?.remote).toBe("origin");
70
+ });
71
+
72
+ it("handles empty input", () => {
73
+ const result = parseBranches("");
74
+ expect(result).toEqual([]);
75
+ });
76
+
77
+ it("ignores lines with missing fields", () => {
78
+ const output = "incomplete|\nmain|abc1234|*\n";
79
+ const result = parseBranches(output);
80
+ // Both lines are parsed, but the first has undefined hash
81
+ expect(result).toHaveLength(2);
82
+ });
83
+ });
84
+
85
+ describe("extension.ts: parseTags", () => {
86
+ it("parses single tag", () => {
87
+ const output = "v1.0.0|abc1234\n";
88
+ const result = parseTags(output);
89
+ expect(result).toHaveLength(1);
90
+ expect(result[0]).toEqual({ name: "v1.0.0", hash: "abc1234" });
91
+ });
92
+
93
+ it("parses multiple tags", () => {
94
+ const output = "v1.0.0|abc1234\nv1.0.1|def5678\nv2.0.0|ghi9012\n";
95
+ const result = parseTags(output);
96
+ expect(result).toHaveLength(3);
97
+ });
98
+
99
+ it("handles empty input", () => {
100
+ const result = parseTags("");
101
+ expect(result).toEqual([]);
102
+ });
103
+
104
+ it("handles whitespace", () => {
105
+ const output = " \nv1.0.0|abc1234\n \n";
106
+ const result = parseTags(output);
107
+ expect(result).toHaveLength(1);
108
+ });
109
+ });
110
+
111
+ describe("extension.ts: parseRemotes", () => {
112
+ it("parses single remote with fetch and push URLs", () => {
113
+ const output = `origin https://github.com/user/repo.git (fetch)
114
+ origin https://github.com/user/repo.git (push)
115
+ `;
116
+ const result = parseRemotes(output);
117
+ expect(result).toHaveLength(1);
118
+ expect(result[0]).toEqual({
119
+ name: "origin",
120
+ fetchUrl: "https://github.com/user/repo.git",
121
+ pushUrl: "https://github.com/user/repo.git",
122
+ });
123
+ });
124
+
125
+ it("parses multiple remotes", () => {
126
+ const output = `origin https://github.com/user/repo.git (fetch)
127
+ origin https://github.com/user/repo.git (push)
128
+ upstream https://github.com/upstream/repo.git (fetch)
129
+ upstream https://github.com/upstream/repo.git (push)
130
+ `;
131
+ const result = parseRemotes(output);
132
+ expect(result).toHaveLength(2);
133
+ expect(result[0]?.name).toBe("origin");
134
+ expect(result[1]?.name).toBe("upstream");
135
+ });
136
+
137
+ it("handles remotes with different fetch/push URLs", () => {
138
+ const output = `origin git@github.com:user/repo.git (fetch)
139
+ origin https://github.com/user/repo.git (push)
140
+ `;
141
+ const result = parseRemotes(output);
142
+ expect(result[0]?.fetchUrl).toBe("git@github.com:user/repo.git");
143
+ expect(result[0]?.pushUrl).toBe("https://github.com/user/repo.git");
144
+ });
145
+
146
+ it("handles empty input", () => {
147
+ const result = parseRemotes("");
148
+ expect(result).toEqual([]);
149
+ });
150
+
151
+ it("ignores malformed lines", () => {
152
+ const output = `origin https://github.com/user/repo.git (fetch)
153
+ origin https://github.com/user/repo.git (push)
154
+ invalid line without brackets
155
+ `;
156
+ const result = parseRemotes(output);
157
+ expect(result).toHaveLength(1);
158
+ });
159
+ });
160
+
161
+ describe("extension.ts: parseStashes", () => {
162
+ it("parses single stash", () => {
163
+ const output = "stash@{0}|abc1234|WIP on main\n";
164
+ const result = parseStashes(output);
165
+ expect(result).toHaveLength(1);
166
+ expect(result[0]).toEqual({ index: 0, hash: "abc1234", message: "WIP on main" });
167
+ });
168
+
169
+ it("parses multiple stashes with correct indices", () => {
170
+ const output = `stash@{0}|abc1234|WIP on main: first
171
+ stash@{1}|def5678|WIP on develop: second
172
+ stash@{2}|ghi9012|Untracked files
173
+ `;
174
+ const result = parseStashes(output);
175
+ expect(result).toHaveLength(3);
176
+ expect(result[0]?.index).toBe(0);
177
+ expect(result[1]?.index).toBe(1);
178
+ expect(result[2]?.index).toBe(2);
179
+ });
180
+
181
+ it("handles empty input", () => {
182
+ const result = parseStashes("");
183
+ expect(result).toEqual([]);
184
+ });
185
+
186
+ it("handles stash messages with pipes in them", () => {
187
+ // This test verifies current behavior; note that parsing breaks with pipes in message
188
+ const output = "stash@{0}|abc1234|Message with more data\n";
189
+ const result = parseStashes(output);
190
+ expect(result).toHaveLength(1);
191
+ expect(result[0]?.hash).toBe("abc1234");
192
+ });
193
+ });