@hienlh/ppm 0.10.5 → 0.11.0

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 (122) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/web/assets/ai-settings-section-D2vqiydT.js +1 -0
  3. package/dist/web/assets/{api-settings-C__hxGX2.js → api-settings-2eTz4SgY.js} +1 -1
  4. package/dist/web/assets/architecture-PBZL5I3N-BRW4VwMk.js +1 -0
  5. package/dist/web/assets/chat-tab-CbguR_l0.js +10 -0
  6. package/dist/web/assets/code-editor-DbZP0Dnj.js +8 -0
  7. package/dist/web/assets/{conflict-editor-Bxq4QiW1.js → conflict-editor-BzrH1UpC.js} +1 -1
  8. package/dist/web/assets/{csv-preview-BizIVMyb.js → csv-preview-D37K2LRd.js} +1 -1
  9. package/dist/web/assets/{database-viewer-CvQc1PZH.js → database-viewer-CqMOv2Sg.js} +2 -2
  10. package/dist/web/assets/{diff-viewer-x7kjfVYW.js → diff-viewer-B6a2oYYn.js} +1 -1
  11. package/dist/web/assets/{esm-K1XIK4vc.js → esm-B99v94EE.js} +1 -1
  12. package/dist/web/assets/{extension-store-3yZYn07W.js → extension-store-CkyOvGbF.js} +1 -1
  13. package/dist/web/assets/extension-webview-CZr_fvOm.js +3 -0
  14. package/dist/web/assets/gitGraph-HDMCJU4V-Bt68dqWT.js +1 -0
  15. package/dist/web/assets/index-C68PuiOm.js +26 -0
  16. package/dist/web/assets/index-iZHWllzQ.css +2 -0
  17. package/dist/web/assets/info-3K5VOQVL-ySD5z855.js +1 -0
  18. package/dist/web/assets/{input-ClhO__YM.js → input-CHRMley8.js} +1 -1
  19. package/dist/web/assets/{keybindings-store-C9KsBH7z.js → keybindings-store-CpP5_miA.js} +1 -1
  20. package/dist/web/assets/keybindings-store-qfYScgY0.js +1 -0
  21. package/dist/web/assets/{markdown-renderer-CKmmrUuy.js → markdown-renderer-BhNYbXCp.js} +3 -3
  22. package/dist/web/assets/packet-RMMSAZCW-CLxaXgIf.js +1 -0
  23. package/dist/web/assets/pie-UPGHQEXC-C9wPZfkn.js +1 -0
  24. package/dist/web/assets/port-forwarding-tab-Dw9MUu5a.js +1 -0
  25. package/dist/web/assets/{postgres-viewer-YkljtDWX.js → postgres-viewer-YKyNjTLp.js} +3 -3
  26. package/dist/web/assets/{project-store-BYmQ0fDC.js → project-store-CczGNZyf.js} +1 -1
  27. package/dist/web/assets/radar-KQ55EAFF-DxEpzVN_.js +1 -0
  28. package/dist/web/assets/{scroll-area-DW7L4Gnc.js → scroll-area-DwWF9FpN.js} +1 -1
  29. package/dist/web/assets/settings-store-CuYjM0FF.js +2 -0
  30. package/dist/web/assets/settings-tab-2tdZuQIn.js +1 -0
  31. package/dist/web/assets/{sql-query-editor-CM_qEhaX.js → sql-query-editor-CVEi0jLM.js} +1 -1
  32. package/dist/web/assets/{sqlite-viewer-f6ZJHIzh.js → sqlite-viewer-Fx9qDD4-.js} +1 -1
  33. package/dist/web/assets/{tab-store-B3M9hjho.js → tab-store-Jvy1eZGM.js} +1 -1
  34. package/dist/web/assets/{terminal-tab-CVdfvDSK.js → terminal-tab-BxljmYb7.js} +1 -1
  35. package/dist/web/assets/treemap-KZPCXAKY-yelcZZqO.js +1 -0
  36. package/dist/web/assets/{use-monaco-theme-CvV5vy_F.js → use-monaco-theme-kjiAwvOp.js} +1 -1
  37. package/dist/web/assets/{vendor-mermaid-CwOSbfhN.js → vendor-mermaid-CylkVm4U.js} +3 -3
  38. package/dist/web/index.html +16 -16
  39. package/dist/web/sw.js +1 -1
  40. package/docs/codebase-summary.md +29 -5
  41. package/docs/project-changelog.md +31 -1
  42. package/docs/system-architecture.md +106 -1
  43. package/package.json +1 -1
  44. package/packages/ext-git-graph/src/webview-html.ts +8 -7
  45. package/src/cli/commands/jira-cmd.ts +92 -0
  46. package/src/cli/commands/jira-watcher-cmd.ts +149 -0
  47. package/src/index.ts +3 -0
  48. package/src/server/index.ts +19 -0
  49. package/src/server/routes/files.ts +15 -0
  50. package/src/server/routes/fs-browse.ts +40 -1
  51. package/src/server/routes/jira-config-routes.ts +74 -0
  52. package/src/server/routes/jira-watcher-routes.ts +316 -0
  53. package/src/server/routes/jira.ts +7 -0
  54. package/src/server/ws/chat.ts +21 -0
  55. package/src/services/db.service.ts +65 -1
  56. package/src/services/file.service.ts +42 -0
  57. package/src/services/jira-api-client.ts +216 -0
  58. package/src/services/jira-config.service.ts +83 -0
  59. package/src/services/jira-debug-session.service.ts +240 -0
  60. package/src/services/jira-watcher-db.service.ts +195 -0
  61. package/src/services/jira-watcher.service.ts +159 -0
  62. package/src/services/notification.service.ts +6 -0
  63. package/src/types/jira.ts +128 -0
  64. package/src/web/app.tsx +15 -12
  65. package/src/web/components/chat/chat-tab.tsx +32 -1
  66. package/src/web/components/chat/message-input.tsx +56 -5
  67. package/src/web/components/explorer/file-tree.tsx +9 -0
  68. package/src/web/components/extensions/extension-webview.tsx +24 -10
  69. package/src/web/components/jira/jira-config-form.tsx +109 -0
  70. package/src/web/components/jira/jira-debug-prompt-dialog.tsx +58 -0
  71. package/src/web/components/jira/jira-filter-builder.tsx +197 -0
  72. package/src/web/components/jira/jira-panel.tsx +201 -0
  73. package/src/web/components/jira/jira-results-panel.tsx +184 -0
  74. package/src/web/components/jira/jira-settings-section.tsx +58 -0
  75. package/src/web/components/jira/jira-status-badge.tsx +18 -0
  76. package/src/web/components/jira/jira-ticket-card.tsx +144 -0
  77. package/src/web/components/jira/jira-ticket-detail.tsx +153 -0
  78. package/src/web/components/jira/jira-watcher-form.tsx +154 -0
  79. package/src/web/components/jira/jira-watcher-list.tsx +98 -0
  80. package/src/web/components/layout/mobile-drawer.tsx +18 -5
  81. package/src/web/components/layout/sidebar.tsx +20 -3
  82. package/src/web/components/settings/settings-tab.tsx +20 -3
  83. package/src/web/components/shared/markdown-code-block.tsx +5 -3
  84. package/src/web/components/ui/file-browser-picker.tsx +88 -1
  85. package/src/web/hooks/use-chat.ts +6 -0
  86. package/src/web/lib/ws-client.ts +10 -3
  87. package/src/web/stores/jira-store.ts +198 -0
  88. package/src/web/stores/settings-store.ts +17 -2
  89. package/src/web/styles/globals.css +7 -0
  90. package/vite.config.ts +5 -66
  91. package/bun.lock +0 -2062
  92. package/bunfig.toml +0 -2
  93. package/dist/web/assets/ai-settings-section-D2rONDPd.js +0 -1
  94. package/dist/web/assets/architecture-PBZL5I3N-DmL1WyG-.js +0 -1
  95. package/dist/web/assets/chat-tab-Dki1pz84.js +0 -10
  96. package/dist/web/assets/code-editor-D3AAT8nI.js +0 -8
  97. package/dist/web/assets/extension-webview-BFd0USXC.js +0 -3
  98. package/dist/web/assets/gitGraph-HDMCJU4V-D8vKfkjC.js +0 -1
  99. package/dist/web/assets/index-DPnjO2FY.css +0 -2
  100. package/dist/web/assets/index-DuEUN2Eg.js +0 -26
  101. package/dist/web/assets/info-3K5VOQVL-VG29MIoT.js +0 -1
  102. package/dist/web/assets/keybindings-store-BkZjvU9J.js +0 -1
  103. package/dist/web/assets/packet-RMMSAZCW-Bl_WpvPc.js +0 -1
  104. package/dist/web/assets/pie-UPGHQEXC-BVpLpAIy.js +0 -1
  105. package/dist/web/assets/port-forwarding-tab-BUH9aImG.js +0 -1
  106. package/dist/web/assets/radar-KQ55EAFF-CJGco43I.js +0 -1
  107. package/dist/web/assets/settings-store-D9CflsKU.js +0 -2
  108. package/dist/web/assets/settings-tab-DfPjX9uY.js +0 -1
  109. package/dist/web/assets/square-nsMa3iMk.js +0 -1
  110. package/dist/web/assets/treemap-KZPCXAKY-BsOrObtE.js +0 -1
  111. /package/dist/web/assets/{api-client-Bn-Pi9k5.js → api-client-C3tXCh0r.js} +0 -0
  112. /package/dist/web/assets/{csv-parser--2WJNgS7.js → csv-parser-BAa56Nnn.js} +0 -0
  113. /package/dist/web/assets/{dist-im4ynINo.js → dist-On3hz9_g.js} +0 -0
  114. /package/dist/web/assets/{katex-CKoArbIw.js → katex-Bbu770d9.js} +0 -0
  115. /package/dist/web/assets/{lib-D_kRA9p6.js → lib-BqkcKGFq.js} +0 -0
  116. /package/dist/web/assets/{react-GqWghJ-L.js → react-BkWDCPD7.js} +0 -0
  117. /package/dist/web/assets/{sql-completion-provider-C3cq9j99.js → sql-completion-provider-D3acAhav.js} +0 -0
  118. /package/dist/web/assets/{table-Dq575bPF.js → table-DbSviOmw.js} +0 -0
  119. /package/dist/web/assets/{text-wrap-Cn6BNQfq.js → text-wrap-DzvCTq_i.js} +0 -0
  120. /package/dist/web/assets/{trash-2-CJYoLw7Q.js → trash-2-BgDIBl6f.js} +0 -0
  121. /package/dist/web/assets/{utils-CTg5uAYR.js → utils-ChWX7pZv.js} +0 -0
  122. /package/dist/web/assets/{vendor-xterm-ejLe7-tK.js → vendor-xterm-B9BUAFKA.js} +0 -0
@@ -39,32 +39,32 @@
39
39
  <link rel="preconnect" href="https://fonts.googleapis.com" />
40
40
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
41
41
  <link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500;600;700&family=Geist:wght@400;500;600;700&display=swap" rel="stylesheet" />
42
- <script type="module" crossorigin src="/assets/index-DuEUN2Eg.js"></script>
42
+ <script type="module" crossorigin src="/assets/index-C68PuiOm.js"></script>
43
43
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-FhOqtrmT.js">
44
- <link rel="modulepreload" crossorigin href="/assets/vendor-mermaid-CwOSbfhN.js">
44
+ <link rel="modulepreload" crossorigin href="/assets/vendor-mermaid-CylkVm4U.js">
45
45
  <link rel="modulepreload" crossorigin href="/assets/vendor-markdown-0Mxgxy0L.js">
46
46
  <link rel="modulepreload" crossorigin href="/assets/vendor-ui-B-T_damt.js">
47
- <link rel="modulepreload" crossorigin href="/assets/utils-CTg5uAYR.js">
47
+ <link rel="modulepreload" crossorigin href="/assets/utils-ChWX7pZv.js">
48
48
  <link rel="modulepreload" crossorigin href="/assets/createLucideIcon-BjHrJDVb.js">
49
49
  <link rel="modulepreload" crossorigin href="/assets/x-DlFGzN8d.js">
50
- <link rel="modulepreload" crossorigin href="/assets/input-ClhO__YM.js">
51
- <link rel="modulepreload" crossorigin href="/assets/scroll-area-DW7L4Gnc.js">
50
+ <link rel="modulepreload" crossorigin href="/assets/input-CHRMley8.js">
51
+ <link rel="modulepreload" crossorigin href="/assets/scroll-area-DwWF9FpN.js">
52
52
  <link rel="modulepreload" crossorigin href="/assets/dist-C5IgeqrV.js">
53
53
  <link rel="modulepreload" crossorigin href="/assets/plus-51UQ45rf.js">
54
54
  <link rel="modulepreload" crossorigin href="/assets/refresh-cw-CSFrDtiu.js">
55
- <link rel="modulepreload" crossorigin href="/assets/trash-2-CJYoLw7Q.js">
56
- <link rel="modulepreload" crossorigin href="/assets/api-client-Bn-Pi9k5.js">
57
- <link rel="modulepreload" crossorigin href="/assets/api-settings-C__hxGX2.js">
58
- <link rel="modulepreload" crossorigin href="/assets/ai-settings-section-D2rONDPd.js">
55
+ <link rel="modulepreload" crossorigin href="/assets/trash-2-BgDIBl6f.js">
56
+ <link rel="modulepreload" crossorigin href="/assets/api-client-C3tXCh0r.js">
57
+ <link rel="modulepreload" crossorigin href="/assets/api-settings-2eTz4SgY.js">
58
+ <link rel="modulepreload" crossorigin href="/assets/ai-settings-section-D2vqiydT.js">
59
59
  <link rel="modulepreload" crossorigin href="/assets/chevron-right-BzAdxJRG.js">
60
60
  <link rel="modulepreload" crossorigin href="/assets/database-D4DIhgi-.js">
61
- <link rel="modulepreload" crossorigin href="/assets/react-GqWghJ-L.js">
62
- <link rel="modulepreload" crossorigin href="/assets/extension-store-3yZYn07W.js">
63
- <link rel="modulepreload" crossorigin href="/assets/keybindings-store-C9KsBH7z.js">
64
- <link rel="modulepreload" crossorigin href="/assets/tab-store-B3M9hjho.js">
65
- <link rel="modulepreload" crossorigin href="/assets/project-store-BYmQ0fDC.js">
66
- <link rel="modulepreload" crossorigin href="/assets/settings-store-D9CflsKU.js">
67
- <link rel="stylesheet" crossorigin href="/assets/index-DPnjO2FY.css">
61
+ <link rel="modulepreload" crossorigin href="/assets/react-BkWDCPD7.js">
62
+ <link rel="modulepreload" crossorigin href="/assets/extension-store-CkyOvGbF.js">
63
+ <link rel="modulepreload" crossorigin href="/assets/keybindings-store-CpP5_miA.js">
64
+ <link rel="modulepreload" crossorigin href="/assets/tab-store-Jvy1eZGM.js">
65
+ <link rel="modulepreload" crossorigin href="/assets/project-store-CczGNZyf.js">
66
+ <link rel="modulepreload" crossorigin href="/assets/settings-store-CuYjM0FF.js">
67
+ <link rel="stylesheet" crossorigin href="/assets/index-iZHWllzQ.css">
68
68
  <link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
69
69
  <body class="bg-[#0f1419] text-[#e5e7eb] font-sans antialiased">
70
70
  <div id="root"></div>
package/dist/web/sw.js CHANGED
@@ -1 +1 @@
1
- try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"8c83f3b66c8ea5cd77d7c5a79ccaa387","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"948e060affb598c339be40d69e1f6f9c","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"a5d8a1acfc29c2a4c882a54ffc93def3","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"d0f94ce046cf8cf09605ee7664dac557","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"a424156a79b9c1b907db93aa3180585a","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"b3a7f967560c9816492a1567b3f7f0dc","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/x-DlFGzN8d.js"},{"revision":null,"url":"assets/vendor-xterm-ejLe7-tK.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/vendor-ui-B-T_damt.js"},{"revision":null,"url":"assets/vendor-mermaid-CwOSbfhN.js"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/utils-CTg5uAYR.js"},{"revision":null,"url":"assets/use-monaco-theme-CvV5vy_F.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-BsOrObtE.js"},{"revision":null,"url":"assets/trash-2-CJYoLw7Q.js"},{"revision":null,"url":"assets/text-wrap-Cn6BNQfq.js"},{"revision":null,"url":"assets/terminal-tab-CVdfvDSK.js"},{"revision":null,"url":"assets/table-Dq575bPF.js"},{"revision":null,"url":"assets/tab-store-B3M9hjho.js"},{"revision":null,"url":"assets/square-nsMa3iMk.js"},{"revision":null,"url":"assets/sqlite-viewer-f6ZJHIzh.js"},{"revision":null,"url":"assets/sql-query-editor-CM_qEhaX.js"},{"revision":null,"url":"assets/sql-completion-provider-C3cq9j99.js"},{"revision":null,"url":"assets/settings-tab-DfPjX9uY.js"},{"revision":null,"url":"assets/settings-store-D9CflsKU.js"},{"revision":null,"url":"assets/scroll-area-DW7L4Gnc.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/refresh-cw-CSFrDtiu.js"},{"revision":null,"url":"assets/react-GqWghJ-L.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-CJGco43I.js"},{"revision":null,"url":"assets/project-store-BYmQ0fDC.js"},{"revision":null,"url":"assets/postgres-viewer-YkljtDWX.js"},{"revision":null,"url":"assets/port-forwarding-tab-BUH9aImG.js"},{"revision":null,"url":"assets/plus-51UQ45rf.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-BVpLpAIy.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-Bl_WpvPc.js"},{"revision":null,"url":"assets/markdown-renderer-CKmmrUuy.js"},{"revision":null,"url":"assets/lib-D_kRA9p6.js"},{"revision":null,"url":"assets/keybindings-store-C9KsBH7z.js"},{"revision":null,"url":"assets/keybindings-store-BkZjvU9J.js"},{"revision":null,"url":"assets/katex-CKoArbIw.js"},{"revision":null,"url":"assets/input-ClhO__YM.js"},{"revision":null,"url":"assets/info-3K5VOQVL-VG29MIoT.js"},{"revision":null,"url":"assets/index-DuEUN2Eg.js"},{"revision":null,"url":"assets/index-DPnjO2FY.css"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-D8vKfkjC.js"},{"revision":null,"url":"assets/extension-webview-BFd0USXC.js"},{"revision":null,"url":"assets/extension-store-3yZYn07W.js"},{"revision":null,"url":"assets/esm-K1XIK4vc.js"},{"revision":null,"url":"assets/dist-im4ynINo.js"},{"revision":null,"url":"assets/dist-C5IgeqrV.js"},{"revision":null,"url":"assets/diff-viewer-x7kjfVYW.js"},{"revision":null,"url":"assets/database-viewer-CvQc1PZH.js"},{"revision":null,"url":"assets/database-D4DIhgi-.js"},{"revision":null,"url":"assets/csv-preview-BizIVMyb.js"},{"revision":null,"url":"assets/csv-parser--2WJNgS7.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/conflict-editor-Bxq4QiW1.js"},{"revision":null,"url":"assets/columns-2-4fQcE4PF.js"},{"revision":null,"url":"assets/code-editor-D3AAT8nI.js"},{"revision":null,"url":"assets/code-CuravVys.js"},{"revision":null,"url":"assets/chevron-right-BzAdxJRG.js"},{"revision":null,"url":"assets/chat-tab-Dki1pz84.js"},{"revision":null,"url":"assets/arrow-up-Dtrfv490.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-DmL1WyG-.js"},{"revision":null,"url":"assets/api-settings-C__hxGX2.js"},{"revision":null,"url":"assets/api-client-Bn-Pi9k5.js"},{"revision":null,"url":"assets/ai-settings-section-D2rONDPd.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/KaTeX_Script-Regular-D3wIWfF6.woff2"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
1
+ try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"9970d69faf52d07dd0be5497439be2dd","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/lib-BqkcKGFq.js"},{"revision":null,"url":"assets/input-CHRMley8.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/tab-store-Jvy1eZGM.js"},{"revision":null,"url":"assets/database-D4DIhgi-.js"},{"revision":null,"url":"assets/x-DlFGzN8d.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-Bt68dqWT.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/database-viewer-CqMOv2Sg.js"},{"revision":null,"url":"assets/port-forwarding-tab-Dw9MUu5a.js"},{"revision":null,"url":"assets/ai-settings-section-D2vqiydT.js"},{"revision":null,"url":"assets/index-C68PuiOm.js"},{"revision":null,"url":"assets/vendor-ui-B-T_damt.js"},{"revision":null,"url":"assets/code-editor-DbZP0Dnj.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/settings-store-CuYjM0FF.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-C9wPZfkn.js"},{"revision":null,"url":"assets/chevron-right-BzAdxJRG.js"},{"revision":null,"url":"assets/sql-query-editor-CVEi0jLM.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-CLxaXgIf.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/trash-2-BgDIBl6f.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/settings-tab-2tdZuQIn.js"},{"revision":null,"url":"assets/dist-C5IgeqrV.js"},{"revision":null,"url":"assets/terminal-tab-BxljmYb7.js"},{"revision":null,"url":"assets/plus-51UQ45rf.js"},{"revision":null,"url":"assets/esm-B99v94EE.js"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2"},{"revision":null,"url":"assets/api-settings-2eTz4SgY.js"},{"revision":null,"url":"assets/arrow-up-Dtrfv490.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-DxEpzVN_.js"},{"revision":null,"url":"assets/refresh-cw-CSFrDtiu.js"},{"revision":null,"url":"assets/vendor-xterm-B9BUAFKA.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/api-client-C3tXCh0r.js"},{"revision":null,"url":"assets/markdown-renderer-BhNYbXCp.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/use-monaco-theme-kjiAwvOp.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2"},{"revision":null,"url":"assets/KaTeX_Script-Regular-D3wIWfF6.woff2"},{"revision":null,"url":"assets/extension-store-CkyOvGbF.js"},{"revision":null,"url":"assets/diff-viewer-B6a2oYYn.js"},{"revision":null,"url":"assets/info-3K5VOQVL-ySD5z855.js"},{"revision":null,"url":"assets/columns-2-4fQcE4PF.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/sql-completion-provider-D3acAhav.js"},{"revision":null,"url":"assets/react-BkWDCPD7.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/extension-webview-CZr_fvOm.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/architecture-PBZL5I3N-BRW4VwMk.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-yelcZZqO.js"},{"revision":null,"url":"assets/sqlite-viewer-Fx9qDD4-.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/code-CuravVys.js"},{"revision":null,"url":"assets/csv-parser-BAa56Nnn.js"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/project-store-CczGNZyf.js"},{"revision":null,"url":"assets/postgres-viewer-YKyNjTLp.js"},{"revision":null,"url":"assets/katex-Bbu770d9.js"},{"revision":null,"url":"assets/keybindings-store-qfYScgY0.js"},{"revision":null,"url":"assets/utils-ChWX7pZv.js"},{"revision":null,"url":"assets/text-wrap-DzvCTq_i.js"},{"revision":null,"url":"assets/conflict-editor-BzrH1UpC.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/scroll-area-DwWF9FpN.js"},{"revision":null,"url":"assets/index-iZHWllzQ.css"},{"revision":null,"url":"assets/chat-tab-CbguR_l0.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/keybindings-store-CpP5_miA.js"},{"revision":null,"url":"assets/csv-preview-D37K2LRd.js"},{"revision":null,"url":"assets/vendor-mermaid-CylkVm4U.js"},{"revision":null,"url":"assets/table-DbSviOmw.js"},{"revision":null,"url":"assets/dist-On3hz9_g.js"},{"revision":"d0f94ce046cf8cf09605ee7664dac557","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"a424156a79b9c1b907db93aa3180585a","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"b3a7f967560c9816492a1567b3f7f0dc","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":"a5d8a1acfc29c2a4c882a54ffc93def3","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"948e060affb598c339be40d69e1f6f9c","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
@@ -17,8 +17,10 @@
17
17
  ```
18
18
  src/
19
19
  ├── cli/
20
- │ ├── commands/ # 14 CLI commands (start, stop, init, config, chat, db, git, ext, etc.)
21
- │ │ └── ext-cmd.ts # Extension CLI (install/remove/list/enable/disable/dev)
20
+ │ ├── commands/ # 16 CLI command groups (start, stop, init, config, chat, db, git, ext, jira, etc.)
21
+ │ │ ├── ext-cmd.ts # Extension CLI (install/remove/list/enable/disable/dev)
22
+ │ │ ├── jira-cmd.ts # Jira config commands (set, show, remove, test)
23
+ │ │ └── jira-watcher-cmd.ts # Jira watcher commands (add, list, enable, disable, remove, test, pull)
22
24
  │ └── utils/
23
25
  │ └── project-resolver.ts # Resolve project name -> path
24
26
  ├── server/
@@ -36,6 +38,9 @@ src/
36
38
  │ │ ├── mcp.ts # MCP server CRUD + import (GET, POST, PUT, DELETE)
37
39
  │ │ ├── extensions.ts # Extension install/remove/list/enable/disable, contributions
38
40
  │ │ ├── upgrade.ts # Version checking, upgrade
41
+ │ │ ├── jira.ts # Jira routes barrel (config, watchers)
42
+ │ │ ├── jira-config-routes.ts # Jira config API (CRUD, test connection)
43
+ │ │ ├── jira-watcher-routes.ts # Jira watcher API (CRUD, poll, results, search, metadata)
39
44
  │ │ └── static.ts # Serve frontend (dist/web)
40
45
  │ ├── helpers/
41
46
  │ │ └── resolve-project.ts # Resolve project from request params
@@ -92,7 +97,11 @@ src/
92
97
  │ │ ├── sqlite-adapter.ts
93
98
  │ │ ├── postgres-adapter.ts
94
99
  │ │ └── readonly-check.ts # CTE-safe readonly validation
95
- └── ... (20+ other services)
100
+ ├── jira-api-client.ts # Jira Cloud REST API v3 (search, getIssue, transitions)
101
+ │ ├── jira-config.service.ts # Jira config CRUD, AES-256 token encryption
102
+ │ ├── jira-watcher-db.service.ts # Watchers + results table queries
103
+ │ ├── jira-watcher.service.ts # Poll orchestrator, timer management, result sync
104
+ │ └── ... (16+ other services)
96
105
  ├── lib/
97
106
  │ ├── account-crypto.ts # AES-256 encryption
98
107
  │ └── network-utils.ts
@@ -105,11 +114,13 @@ src/
105
114
  │ ├── mcp.ts # McpServerConfig, McpTransportType, validation
106
115
  │ ├── extension.ts # ExtensionManifest, ExtensionInfo, RpcMessage, ExtensionContext
107
116
  │ ├── ppmbot.ts # BotTask, TelegramUpdate, PPMBotCommand (coordinator types)
117
+ │ ├── jira.ts # JiraConfig, JiraWatcher, JiraWatchResult, JiraIssue, JiraCredentials
108
118
  │ ├── project.ts
109
119
  │ └── terminal.ts
110
120
  └── web/ # React frontend (Vite + React 18)
111
121
  ├── app.tsx # Root component
112
- ├── stores/ # Zustand state (6 stores)
122
+ ├── stores/ # Zustand state (7 stores)
123
+ │ └── jira-store.ts # ADDED: Jira config, watchers, results, filters state
113
124
  ├── hooks/ # Custom hooks (9 hooks)
114
125
  ├── components/
115
126
  │ ├── chat/
@@ -122,7 +133,15 @@ src/
122
133
  │ ├── settings/
123
134
  │ │ ├── ai-settings-section.tsx # UPDATED: Per-provider tabs, dynamic model dropdowns
124
135
  │ │ ├── mcp-settings-section.tsx # ADDED: MCP servers tab (list, add, edit, delete)
125
- │ │ └── mcp-server-dialog.tsx # ADDED: Add/Edit MCP server dialog
136
+ │ │ ├── mcp-server-dialog.tsx # ADDED: Add/Edit MCP server dialog
137
+ │ │ ├── settings-tab.tsx # UPDATED: Added Jira Watcher tab
138
+ │ │ └── jira/ # ADDED: Jira Watcher components
139
+ │ │ ├── jira-settings-tab.tsx
140
+ │ │ ├── jira-config-form.tsx
141
+ │ │ ├── jira-filter-builder.tsx
142
+ │ │ ├── jira-watcher-list.tsx
143
+ │ │ ├── jira-results-panel.tsx
144
+ │ │ └── jira-ticket-detail.tsx
126
145
  │ ├── database/
127
146
  │ ├── editor/
128
147
  │ ├── explorer/
@@ -222,11 +241,16 @@ src/
222
241
  │ ├── test-setup.ts # Disable auth for tests
223
242
  │ ├── unit/
224
243
  │ │ ├── providers/ # Mock provider, SDK tests
244
+ │ │ ├── jira-watcher-poll.test.ts # ADDED: Jira watcher polling, rate limit backoff
225
245
  │ │ └── services/ # Chat, config, db, session-log, push-notification tests
226
246
  │ └── integration/
227
247
  │ ├── claude-agent-sdk-integration.test.ts
228
248
  │ ├── sqlite-migration.test.ts # SQLite migration validation
249
+ │ ├── jira-config.test.ts # ADDED: Jira config CRUD, token encryption
250
+ │ ├── jira-migration.test.ts # ADDED: Schema v18 migration validation
251
+ │ ├── jira-watcher-db.test.ts # ADDED: Watcher + result queries
229
252
  │ ├── api/ # Chat route tests
253
+ │ ├── api/jira-routes.test.ts # ADDED: Jira API endpoints
230
254
  │ └── ws/ # WebSocket tests
231
255
  ├── scripts/
232
256
  │ ├── build.ts # Build CLI binary (bun build --compile)
@@ -6,9 +6,39 @@ All notable changes to PPM are documented here. Format follows [Keep a Changelog
6
6
 
7
7
  ---
8
8
 
9
- ## [Unreleased] — Frontend Memory Optimization + Git-Graph Stash Management, Rebase, Conflict Resolution + Worktree CRUD
9
+ ## [Unreleased] — Jira Debug Session Redesign + Frontend Memory Optimization + Git-Graph Stash Management, Rebase, Conflict Resolution + Worktree CRUD
10
10
 
11
11
  ### Added
12
+ - **Jira Debug Session Redesign** — Direct Claude session debug replacing bot_task flow with concurrency queue
13
+ - Replaced bot_task-based debug with direct `chatService.sendMessage()` calls (simpler, faster)
14
+ - Concurrency queue: max 2 concurrent sessions globally, max 1 per project (prevents resource exhaustion)
15
+ - Manual "Start Debug" button with editable prompt in results panel (override watcher template)
16
+ - Unread tracking: `read_at` column on `jira_watch_results`, unread badge count in UI
17
+ - WS toast notifications on debug completion (`jira:debug_complete` event)
18
+ - Prompt override support for custom debug instructions per result
19
+ - Result status flow: pending → queued → running → done/failed
20
+ - Timeout protection: 10-minute abort-on-timeout with graceful cleanup
21
+ - AI summary capture: last assistant text (max 500 chars) stored in result
22
+ - Database schema v19: added `read_at` (nullable timestamp), `triggered_by` ("auto"|"manual")
23
+ - New service: `JiraDebugSessionService` with queue management + concurrency limits
24
+ - New component: `JiraDebugPromptDialog` for manual prompt override UI
25
+ - API: `POST /api/jira/results/:id/debug` to trigger debug (with optional prompt)
26
+
27
+ - **Jira Watcher Auto-Debug (v0.9.86+)** — Poll Jira Cloud per-project, auto-debug matched tickets
28
+ - Jira Cloud REST API integration (search, get issue, transitions, metadata discovery)
29
+ - Per-project config (base URL, email, AES-256 token encryption)
30
+ - JQL-based watchers with two modes: debug (queue session) and notify-only (Telegram notification)
31
+ - Configurable poll intervals (30s–60m per watcher, interval clamping)
32
+ - Rate limit aware (tracks Jira API quota, auto-backoff 429 responses)
33
+ - Result tracking (pending/queued/running/done/failed status, AI summary persistence)
34
+ - Prompt templating ({issue_key}, {summary}, {description}, {status}, {priority} substitution)
35
+ - Soft deletes (preserve result history, don't lose tracking)
36
+ - Frontend filter builder UI (projects, issue types, priorities, statuses, custom JQL)
37
+ - CLI commands: `ppm jira config {set,show,remove,test}`, `ppm jira watch {add,list,enable,disable,remove,test,pull}`
38
+ - API routes: /api/jira/config/*, /api/jira/{watchers,results,search,ticket,metadata}
39
+ - 3 SQLite tables (v18): jira_config, jira_watchers, jira_watch_results
40
+ - 44 tests (integration + unit, JQL builder, result sync, credential encryption, rate limiting)
41
+
12
42
  - **Frontend Memory & Performance Optimizations** — Reduce re-renders, lazy-load heavy components, code splitting
13
43
  - **useShallow pattern:** All destructured Zustand store calls now use `useShallow` (36 usage sites) to prevent re-renders on object mutations
14
44
  - **React.memo wrapping:** 10 heavy components memoized (CodeEditor, MessageBubble, ProjectBar, ProjectAvatar, TerminalTab, PanelLayout, Sidebar, StatusBar, StatusBarEntry, TabBar, TreeNode)
@@ -203,6 +203,10 @@ Tab IDs are deterministic: `{type}:{identifier}` (e.g., `editor:src/index.ts`, `
203
203
  | **executeDelegation()** | Task execution in isolated session, result capture | (async function, manages ChatService + result storage) |
204
204
  | **PPMBotFormatterService** | Markdown → Telegram HTML + chunking | formatMarkdown, chunkMessage |
205
205
  | **PPMBotStreamerService** | ChatEvent → progressive Telegram edits | streamMessageEdits |
206
+ | **JiraConfigService** | Jira config CRUD, token encryption | getConfigByProjectId, upsertConfig, deleteConfig, getDecryptedCredentials |
207
+ | **JiraWatcherDbService** | Jira watchers + results queries | getAllEnabledWatchers, insertResult, updateResultStatus, getWatcherById |
208
+ | **JiraApiClient** | Jira Cloud REST API v3 integration | searchIssues, getIssue, updateIssue, testConnection, getProjects, getFieldOptions |
209
+ | **JiraWatcherService** | Poll orchestrator, timer management | startAll, startWatcher, pollWatcher, syncResultStatuses |
206
210
  | **ClawBotService** | LEGACY Telegram bot (deprecated v0.9.11) | (direct-chat model, replaced by coordinator) |
207
211
  | **ClawBotTelegramService** | LEGACY Telegram API | (deprecated v0.9.11) |
208
212
  | **ClawBotSessionService** | LEGACY chatID mapping | (deprecated v0.9.11) |
@@ -441,6 +445,107 @@ Telegram → ClawBotTelegramService (polling) → ClawBotService (orchestrator)
441
445
 
442
446
  ---
443
447
 
448
+ ### Jira Watcher Auto-Debug Service
449
+ **Component:** Jira Cloud REST API poller + direct Claude debug session orchestrator
450
+
451
+ **Responsibilities:**
452
+ - Poll Jira Cloud per-project on configurable interval (30s–60m)
453
+ - Match issues via JQL filters (status, project key, priority, etc.)
454
+ - Auto-queue or manually trigger direct Claude debug sessions (no bot_task middleman)
455
+ - Manage concurrency: max 2 concurrent, max 1 per project
456
+ - Track results (pending/queued/running/done/failed) with unread status
457
+ - Notify via WS toast + Telegram when analysis completes
458
+ - Rate-limit aware (tracks Jira API quota, auto-backoff 429 responses)
459
+
460
+ **Architecture:**
461
+ ```
462
+ Jira Cloud API ← JiraWatcherService (poller, 30s–60m intervals per watcher)
463
+ ├─ searchIssues(jql) → issue list
464
+ ├─ insertResult() → SQLite jira_watch_results
465
+ └─ jiraDebugService.enqueue() → concurrency queue
466
+
467
+ JiraDebugSessionService (concurrency queue processor)
468
+ ├─ enqueue(resultId, promptOverride?) → validate + queue
469
+ ├─ processQueue() — respects MAX_CONCURRENT=2, MAX_PER_PROJECT=1
470
+ ├─ runDebugSession()
471
+ │ ├─ chatService.createSession(projectPath) — new isolated session
472
+ │ ├─ chatService.sendMessage(prompt) — send with bypassPermissions
473
+ │ ├─ capture lastAssistantText (max 500 chars)
474
+ │ └─ updateResultStatus() + notificationService.broadcastWs("jira:debug_complete")
475
+ └─ cancelDebug(resultId) — abort running session
476
+ ```
477
+
478
+ **Services (src/services/):**
479
+ - **JiraConfigService** — Config CRUD, AES-256 token encryption/decryption, per-project setup
480
+ - **JiraWatcherDbService** — Watchers + results table queries, enabled/disabled toggle, last polled tracking
481
+ - **JiraApiClient** — Jira Cloud REST v3 (search, getIssue, transitions, test connection), rate limit state, backoff logic
482
+ - **JiraWatcherService** — Main poller, timer management (startAll, startWatcher, stopWatcher, pollWatcher), prompt templating, session enqueueing
483
+ - **JiraDebugSessionService** — Concurrency queue, session lifecycle, timeout management, abort handling
484
+
485
+ **Database Schema (v19):**
486
+ - `jira_config` — id, project_id (FK), base_url, email, api_token_encrypted, created_at
487
+ - `jira_watchers` — id, jira_config_id (FK), name, jql, prompt_template, enabled, mode ("debug"|"notify"), interval_ms, last_polled_at, created_at
488
+ - `jira_watch_results` — id, watcher_id, issue_key, issue_summary, issue_updated, session_id (FK chat_sessions.id), status ("pending"|"queued"|"running"|"done"|"failed"), ai_summary, source ("watcher"|"manual"), triggered_by ("auto"|"manual"), read_at (nullable), deleted, created_at
489
+
490
+ **API Routes (src/server/routes/jira*.ts):**
491
+ ```
492
+ POST /api/jira/config — Create/update config (baseUrl, email, token)
493
+ GET /api/jira/config — Get config for active project
494
+ DELETE /api/jira/config — Delete config
495
+ POST /api/jira/config/test — Test Jira connection
496
+ GET /api/jira/watchers — List watchers for config
497
+ POST /api/jira/watchers — Create watcher (name, jql, mode, interval)
498
+ PATCH /api/jira/watchers/:id — Update watcher
499
+ DELETE /api/jira/watchers/:id — Delete watcher (soft delete results)
500
+ POST /api/jira/watchers/:id/enable — Enable/disable watcher
501
+ POST /api/jira/watchers/:id/poll — Trigger poll now
502
+ GET /api/jira/results — List results (paginated, filterable)
503
+ POST /api/jira/results/:id/debug — Manually trigger debug for result (with optional prompt override)
504
+ POST /api/jira/results/:id/read — Mark result as read
505
+ DELETE /api/jira/results/:id — Delete result (soft delete)
506
+ GET /api/jira/search — Search Jira (for filter builder UI)
507
+ GET /api/jira/ticket/:key — Get full ticket details
508
+ GET /api/jira/metadata — Fetch projects, issue types, priorities, statuses
509
+ ```
510
+
511
+ **CLI Commands (src/cli/commands/jira*.ts):**
512
+ ```
513
+ ppm jira config set <project> --url <url> --email <email> --token <token>
514
+ ppm jira config show <project>
515
+ ppm jira config remove <project>
516
+ ppm jira config test <project>
517
+ ppm jira watch add <project> <name> --jql <jql> [--mode debug|notify] [--interval 300000]
518
+ ppm jira watch list <project>
519
+ ppm jira watch enable/disable <project> <watcherId>
520
+ ppm jira watch remove <project> <watcherId>
521
+ ppm jira watch test <project> <watcherId>
522
+ ppm jira watch pull <project> <watcherId>
523
+ ppm jira results list <project> [--limit 50]
524
+ ppm jira results delete <project> <resultId>
525
+ ppm jira track <issue-key> — Manually track ticket (insert result, queue debug)
526
+ ```
527
+
528
+ **Frontend (src/web/components/jira/):**
529
+ - **jira-settings-tab.tsx** — Config form, test button, token input
530
+ - **jira-filter-builder.tsx** — JQL builder UI (projects, issue types, priorities, statuses, custom JQL)
531
+ - **jira-watcher-list.tsx** — List watchers, enable/disable, edit, delete, poll now, interval controls
532
+ - **jira-results-panel.tsx** — Results table (issue key, status, summary, AI summary), unread badge, delete, manual debug button
533
+ - **jira-debug-prompt-dialog.tsx** — Modal for prompt override when manually triggering debug
534
+ - **jira-ticket-detail.tsx** — Modal with full ticket, AI analysis, debug status
535
+ - **jira-store.ts** — Zustand (configs, watchers, results, filters, settings, unread count)
536
+
537
+ **Key Design Decisions:**
538
+ 1. **Direct Claude sessions** — Replaced bot_task flow with direct `chatService.sendMessage()` (simpler, faster, no task overhead)
539
+ 2. **Concurrency queue** — Max 2 concurrent globally, max 1 per project (prevents resource starvation, respects project context)
540
+ 3. **Manual debug trigger** — Users can override watcher prompt and manually queue debug for any pending result
541
+ 4. **Unread tracking** — `read_at` column marks when user views result, UI shows unread badge count
542
+ 5. **Prompt templating** — Support {issue_key}, {summary}, {description}, {status}, {priority} placeholders in watcher templates
543
+ 6. **Timeout protection** — 10-minute timeout with AbortController graceful cleanup and error capture
544
+ 7. **WS notifications** — `jira:debug_complete` event streamed to UI for instant toast feedback
545
+ 8. **Soft deletes** — Results marked deleted=1 (preserve history, don't lose tracking)
546
+
547
+ ---
548
+
444
549
  ### Data Access Layer (SQLite + Filesystem + Git)
445
550
  **Components:** SQLite via bun:sqlite, direct filesystem access, simple-git wrapper
446
551
 
@@ -452,7 +557,7 @@ Telegram → ClawBotTelegramService (polling) → ClawBotService (orchestrator)
452
557
  - Enforce security (no parent directory access)
453
558
 
454
559
  **Key Patterns:**
455
- - SQLite: WAL mode, foreign keys, lazy init, schema v13 (13 tables: config, connections, accounts, usage_history, session_logs, push_subscriptions, session_map, table_metadata, workspace_state, extension_storage, mcp_servers, clawbot_sessions, clawbot_memories, clawbot_paired_chats)
560
+ - SQLite: WAL mode, foreign keys, lazy init, schema v19 (18 tables: config, connections, accounts, usage_history, session_logs, push_subscriptions, session_map, table_metadata, workspace_state, extension_storage, mcp_servers, clawbot_sessions, clawbot_memories, clawbot_paired_chats, jira_config, jira_watchers, jira_watch_results, bot_tasks)
456
561
  - Path validation: `projectPath/relativePath` only, reject `..`
457
562
  - Caching: Directory trees cached with TTL
458
563
  - Error handling: Descriptive messages (file not found, permission denied)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.10.5",
3
+ "version": "0.11.0",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -242,7 +242,7 @@ button:active { background: var(--surface); }
242
242
 
243
243
  /* Graph container */
244
244
  #graph-container { flex: 1; overflow-y: auto; overflow-x: hidden; }
245
- .commit-row { display: flex; align-items: center; cursor: pointer; min-height: 24px; padding: 0 6px; font-size: 12px; }
245
+ .commit-row { display: flex; align-items: center; cursor: pointer; height: 24px; padding: 0 6px; font-size: 12px; box-sizing: border-box; overflow: hidden; }
246
246
  .commit-row:hover { background: var(--surface-hover); }
247
247
  .commit-row.selected { background: var(--selected); }
248
248
  .commit-row.header-row { background: var(--surface); cursor: default; font-weight: 600; font-size: 10px; color: var(--subtext); text-transform: uppercase; letter-spacing: 0.5px; position: sticky; top: 0; z-index: 2; border-bottom: 1px solid var(--border); min-height: 22px; }
@@ -385,7 +385,7 @@ button:active { background: var(--surface); }
385
385
 
386
386
  /* Touch devices — compact mobile layout */
387
387
  @media (pointer: coarse) {
388
- .commit-row { min-height: 32px; }
388
+ .commit-row { height: 32px; }
389
389
  .ctx-item { padding: 8px 12px; min-height: 36px; }
390
390
  button { min-width: 32px; min-height: 32px; padding: 2px 6px; }
391
391
  #app { flex-direction: column; }
@@ -398,6 +398,7 @@ button:active { background: var(--surface); }
398
398
  #commit-list-wrapper { min-width: 700px; }
399
399
  #graph-header { min-width: 700px; }
400
400
  .commit-row.header-row { min-height: 20px; }
401
+ .detail-panel { order: 8; max-height: 35vh; }
401
402
  }
402
403
  /* Column hiding on very narrow non-touch containers (e.g. narrow desktop panel) */
403
404
  @media (max-width: 500px) and (pointer: fine) {
@@ -1331,11 +1332,11 @@ function graphRender(expandIdx) {
1331
1332
  container.innerHTML = '';
1332
1333
  if (gVertices.length === 0) { if (state.graphColWidth === null) document.documentElement.style.setProperty('--graph-col-w', '40px'); return; }
1333
1334
 
1334
- // Detect touch device: match CSS breakpoint where row height changes to 32px
1335
- const isTouch = window.matchMedia('(pointer: coarse)').matches;
1336
- const cfg = isTouch
1337
- ? { ...graphConfig, grid: { ...graphConfig.grid, y: 32, offsetY: 16 } }
1338
- : graphConfig;
1335
+ // Measure actual row height (may be fractional due to browser zoom)
1336
+ // and use it for SVG grid to prevent cumulative sub-pixel drift
1337
+ const firstRow = document.querySelector('#commit-list .commit-row');
1338
+ const rowH = firstRow ? firstRow.getBoundingClientRect().height : graphConfig.grid.y;
1339
+ const cfg = { ...graphConfig, grid: { ...graphConfig.grid, y: rowH, offsetY: rowH / 2 } };
1339
1340
 
1340
1341
  const svg = document.createElementNS(SVG_NS, 'svg');
1341
1342
  const group = document.createElementNS(SVG_NS, 'g');
@@ -0,0 +1,92 @@
1
+ import { Command } from "commander";
2
+
3
+ const C = {
4
+ reset: "\x1b[0m", bold: "\x1b[1m", green: "\x1b[32m",
5
+ red: "\x1b[31m", yellow: "\x1b[33m", cyan: "\x1b[36m", dim: "\x1b[2m",
6
+ };
7
+
8
+ export async function registerJiraCommands(program: Command): Promise<void> {
9
+ const jira = program.command("jira").description("Jira watcher utilities");
10
+ registerConfigCommands(jira);
11
+ await registerWatcherCommands(jira);
12
+ }
13
+
14
+ // ── Config commands ───────────────────────────────────────────────────
15
+
16
+ function registerConfigCommands(jira: Command): void {
17
+ const config = jira.command("config").description("Manage Jira configs");
18
+
19
+ config
20
+ .command("set <projectName>")
21
+ .description("Set Jira config for a project")
22
+ .requiredOption("--url <url>", "Jira base URL (https://...)")
23
+ .requiredOption("--email <email>", "Jira account email")
24
+ .requiredOption("--token <token>", "API token (⚠ visible in shell history)")
25
+ .action(async (projectName: string, opts: { url: string; email: string; token: string }) => {
26
+ try {
27
+ const { getDb } = await import("../../services/db.service.ts");
28
+ const project = getDb().query("SELECT id FROM projects WHERE name = ?").get(projectName) as { id: number } | null;
29
+ if (!project) { console.error(`${C.red}✗${C.reset} Project "${projectName}" not found`); process.exit(1); }
30
+ const { upsertConfig } = await import("../../services/jira-config.service.ts");
31
+ const cfg = upsertConfig(project.id, opts.url, opts.email, opts.token);
32
+ console.log(`${C.green}✓${C.reset} Jira config saved for "${projectName}" (id: ${cfg.id})`);
33
+ } catch (e: any) { console.error(`${C.red}✗${C.reset} ${e.message}`); process.exit(1); }
34
+ });
35
+
36
+ config
37
+ .command("show <projectName>")
38
+ .description("Show Jira config (token masked)")
39
+ .action(async (projectName: string) => {
40
+ try {
41
+ const { getDb } = await import("../../services/db.service.ts");
42
+ const project = getDb().query("SELECT id FROM projects WHERE name = ?").get(projectName) as { id: number } | null;
43
+ if (!project) { console.error(`${C.red}✗${C.reset} Project not found`); process.exit(1); }
44
+ const { getConfigByProjectId } = await import("../../services/jira-config.service.ts");
45
+ const cfg = getConfigByProjectId(project.id);
46
+ if (!cfg) { console.log(`${C.yellow}No Jira config for "${projectName}"${C.reset}`); return; }
47
+ console.log(` URL: ${cfg.baseUrl}`);
48
+ console.log(` Email: ${cfg.email}`);
49
+ console.log(` Token: ${cfg.hasToken ? "****" : "(none)"}`);
50
+ } catch (e: any) { console.error(`${C.red}✗${C.reset} ${e.message}`); process.exit(1); }
51
+ });
52
+
53
+ config
54
+ .command("remove <projectName>")
55
+ .description("Remove Jira config (cascades watchers + results)")
56
+ .action(async (projectName: string) => {
57
+ try {
58
+ const { getDb } = await import("../../services/db.service.ts");
59
+ const project = getDb().query("SELECT id FROM projects WHERE name = ?").get(projectName) as { id: number } | null;
60
+ if (!project) { console.error(`${C.red}✗${C.reset} Project not found`); process.exit(1); }
61
+ const { deleteConfig } = await import("../../services/jira-config.service.ts");
62
+ deleteConfig(project.id);
63
+ console.log(`${C.green}✓${C.reset} Config removed for "${projectName}"`);
64
+ } catch (e: any) { console.error(`${C.red}✗${C.reset} ${e.message}`); process.exit(1); }
65
+ });
66
+
67
+ config
68
+ .command("test <projectName>")
69
+ .description("Test Jira connection")
70
+ .action(async (projectName: string) => {
71
+ try {
72
+ const { getDb } = await import("../../services/db.service.ts");
73
+ const project = getDb().query("SELECT id FROM projects WHERE name = ?").get(projectName) as { id: number } | null;
74
+ if (!project) { console.error(`${C.red}✗${C.reset} Project not found`); process.exit(1); }
75
+ const { getConfigByProjectId, getDecryptedCredentials } = await import("../../services/jira-config.service.ts");
76
+ const cfg = getConfigByProjectId(project.id);
77
+ if (!cfg) { console.error(`${C.red}✗${C.reset} No config found`); process.exit(1); }
78
+ const creds = getDecryptedCredentials(cfg.id);
79
+ if (!creds) { console.error(`${C.red}✗${C.reset} Failed to decrypt token`); process.exit(1); }
80
+ const { testConnection } = await import("../../services/jira-api-client.ts");
81
+ await testConnection(creds);
82
+ console.log(`${C.green}✓${C.reset} Connection successful`);
83
+ } catch (e: any) { console.error(`${C.red}✗${C.reset} ${e.message}`); process.exit(1); }
84
+ });
85
+ }
86
+
87
+ // ── Watcher + result + ticket commands ────────────────────────────────
88
+
89
+ async function registerWatcherCommands(jira: Command): Promise<void> {
90
+ const { registerJiraWatcherCommands } = await import("./jira-watcher-cmd.ts");
91
+ registerJiraWatcherCommands(jira);
92
+ }