@piecemaker-legal/piecemaker 1.0.6 → 1.0.8

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 (217) hide show
  1. package/dist/assets/{abnfDiagram-VCTEODGH-DF7LFtT-.js → abnfDiagram-VCTEODGH-K8VucbQ5.js} +1 -1
  2. package/dist/assets/{arc-xOmjWeVH.js → arc-DUmCdRoC.js} +1 -1
  3. package/dist/assets/{architectureDiagram-5GKGNRK7-CESzNPwa.js → architectureDiagram-5GKGNRK7-DYhs5vqD.js} +1 -1
  4. package/dist/assets/{blockDiagram-I7D4REHJ-BjdN78Ad.js → blockDiagram-I7D4REHJ-DaXJ9nve.js} +1 -1
  5. package/dist/assets/{c4Diagram-7LVT6UL2-BcK9EDWT.js → c4Diagram-7LVT6UL2-BLbi-zPT.js} +1 -1
  6. package/dist/assets/channel-CJIq1vqf.js +1 -0
  7. package/dist/assets/{chunk-2Q5K7J3B-DGUN1JFP.js → chunk-2Q5K7J3B-Bzs3gxaq.js} +1 -1
  8. package/dist/assets/{chunk-5VM5RSS4-BG3BBcFr.js → chunk-5VM5RSS4-CQNzml7k.js} +1 -1
  9. package/dist/assets/{chunk-F27PBJKO-CLIOfrF5.js → chunk-F27PBJKO-B8xedWWH.js} +1 -1
  10. package/dist/assets/{chunk-IMKFNOWR-D6lGkqij.js → chunk-IMKFNOWR-CjBGlNOu.js} +1 -1
  11. package/dist/assets/{chunk-JWPE2WC7-BmF9vLBT.js → chunk-JWPE2WC7-vfnYEpgA.js} +1 -1
  12. package/dist/assets/{chunk-POPQ4Y6H-C_0SR3ak.js → chunk-POPQ4Y6H-Btl98tE8.js} +1 -1
  13. package/dist/assets/{chunk-SVP7TREG-rT20Wfpp.js → chunk-SVP7TREG-Bp6-pLjR.js} +1 -1
  14. package/dist/assets/{chunk-TICWLB2K-Cn2hkmcf.js → chunk-TICWLB2K-DvmznW2q.js} +1 -1
  15. package/dist/assets/{chunk-XXDRQBXY-Dm_eP1_v.js → chunk-XXDRQBXY-QSHJTsI3.js} +1 -1
  16. package/dist/assets/classDiagram-ZZMXUADV-BYkEWggn.js +1 -0
  17. package/dist/assets/classDiagram-v2-VYDZK3BY-BYkEWggn.js +1 -0
  18. package/dist/assets/{cose-bilkent-JH36ORCC-cJeeJsep.js → cose-bilkent-JH36ORCC-DxpyZoe2.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-DX2ePIXe.js → cynefin-OW5HDTMX-B9GRp2Gn.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-5FMLGOSQ-BvWXN-RR.js → cynefinDiagram-5FMLGOSQ-Q-2tz5GW.js} +1 -1
  21. package/dist/assets/{dagre-GXQ25YYZ-CkwPRhGR.js → dagre-GXQ25YYZ-3Rl3NqNe.js} +1 -1
  22. package/dist/assets/{diagram-S7CK7UJ4-CBGNzYyv.js → diagram-S7CK7UJ4-CreeWCO1.js} +1 -1
  23. package/dist/assets/{diagram-UQ7AKVKN-CiwCU0SJ.js → diagram-UQ7AKVKN-DNBoIyDJ.js} +1 -1
  24. package/dist/assets/{diagram-VSXAHHWV-Aekg4nTk.js → diagram-VSXAHHWV-ZkcE_cRk.js} +1 -1
  25. package/dist/assets/{diagram-VX7I27RA-DPFP5K7r.js → diagram-VX7I27RA-Dzdf7N2Q.js} +1 -1
  26. package/dist/assets/{diagram-Z3DM3KII-BHJkxoey.js → diagram-Z3DM3KII-M6b-nr1u.js} +1 -1
  27. package/dist/assets/{ebnfDiagram-PWID7BFC-2rV1P32T.js → ebnfDiagram-PWID7BFC-DunIw64n.js} +1 -1
  28. package/dist/assets/{erDiagram-RLTQ6QDP-C4BjyS_H.js → erDiagram-RLTQ6QDP-kN0Se5uc.js} +1 -1
  29. package/dist/assets/{flowDiagram-HODETNUW-DL1TBlAG.js → flowDiagram-HODETNUW-24YCovut.js} +1 -1
  30. package/dist/assets/{ganttDiagram-EL5Y4UJY-MKjvzCWz.js → ganttDiagram-EL5Y4UJY-DTUUEEy6.js} +1 -1
  31. package/dist/assets/{gitGraphDiagram-WWUBYQGX-CS_oTGK5.js → gitGraphDiagram-WWUBYQGX-BitrqhMr.js} +1 -1
  32. package/dist/assets/{index-XOGQDqU3.js → index-CG9hW1GU.js} +120 -120
  33. package/dist/assets/index-D_Dc_nQv.css +1 -0
  34. package/dist/assets/{infoDiagram-27XIBGKW-ihUr2GjN.js → infoDiagram-27XIBGKW-BNHN5Mby.js} +1 -1
  35. package/dist/assets/{ishikawaDiagram-5VMMS53U-D2YAnuir.js → ishikawaDiagram-5VMMS53U-De3IQKso.js} +1 -1
  36. package/dist/assets/{journeyDiagram-3NMN7TZE-Bc2hSZGD.js → journeyDiagram-3NMN7TZE-CWlkZ25T.js} +1 -1
  37. package/dist/assets/{kanban-definition-UXKFOSKX-LRMGNonX.js → kanban-definition-UXKFOSKX-2o_ax-Nk.js} +1 -1
  38. package/dist/assets/{layout-CcfWOlPV.js → layout-Btga9ZYP.js} +1 -1
  39. package/dist/assets/{linear-7JoS2gQO.js → linear-BoGGmqPG.js} +1 -1
  40. package/dist/assets/{mermaid.core-ijh_SsuN.js → mermaid.core-M308jnLQ.js} +6 -6
  41. package/dist/assets/{mindmap-definition-YA3MSWOX-DCE4nzfF.js → mindmap-definition-YA3MSWOX-BJ6C_grh.js} +1 -1
  42. package/dist/assets/{pegDiagram-XKGWAZYB-SjaGa7lE.js → pegDiagram-XKGWAZYB-qcdYX0Xe.js} +1 -1
  43. package/dist/assets/{pieDiagram-E7YTZNPT-1xcRidSZ.js → pieDiagram-E7YTZNPT-XPrSlt1M.js} +1 -1
  44. package/dist/assets/{quadrantDiagram-AXDQQJYC-obHAHnYV.js → quadrantDiagram-AXDQQJYC-COhHhra5.js} +1 -1
  45. package/dist/assets/{railroadDiagram-O6MQD6OU-BsCVcRZb.js → railroadDiagram-O6MQD6OU-Mw_KF6gb.js} +1 -1
  46. package/dist/assets/{requirementDiagram-BXWQKSXE-CUNkhvqb.js → requirementDiagram-BXWQKSXE-DPbt7jUM.js} +1 -1
  47. package/dist/assets/{sankeyDiagram-P5KCCOFB-DhezXMKm.js → sankeyDiagram-P5KCCOFB-B6HzL6Gh.js} +1 -1
  48. package/dist/assets/{sequenceDiagram-WJ2MYXX4-CL9VswXH.js → sequenceDiagram-WJ2MYXX4-B_dXXCC0.js} +1 -1
  49. package/dist/assets/{sizeCapture-INFHLROL-t0g9jiEz.js → sizeCapture-INFHLROL-DwODubWN.js} +1 -1
  50. package/dist/assets/{stateDiagram-D77RDMKH-BW-g-IOz.js → stateDiagram-D77RDMKH-5AUnOL3S.js} +1 -1
  51. package/dist/assets/stateDiagram-v2-MP3YSRHH-CFM_nDL9.js +1 -0
  52. package/dist/assets/{swimlanes-42K2YHIH-LsuyeBte.js → swimlanes-42K2YHIH-BLnIwbnd.js} +1 -1
  53. package/dist/assets/swimlanesDiagram-VR7AAH4N-BRQJYnDN.js +8 -0
  54. package/dist/assets/{timeline-definition-24CTP7MA-B0cFka1R.js → timeline-definition-24CTP7MA-B1Yc7-na.js} +1 -1
  55. package/dist/assets/{vendor-codemirror-C5qqu4R7.js → vendor-codemirror-CjW0lHR5.js} +5 -5
  56. package/dist/assets/{vennDiagram-4TSXK5OY-D3HNCDR3.js → vennDiagram-4TSXK5OY-BFRtl-NI.js} +1 -1
  57. package/dist/assets/{wardleyDiagram-VM6X3IG4-DJ3Co8nb.js → wardleyDiagram-VM6X3IG4-BsR8iaD_.js} +1 -1
  58. package/dist/assets/{xychartDiagram-S5SC5T6Z-Cz5E9New.js → xychartDiagram-S5SC5T6Z-Crh1SwrA.js} +1 -1
  59. package/dist/favicon.png +0 -0
  60. package/dist/favicon.svg +1 -9
  61. package/dist/generate-icons.js +47 -46
  62. package/dist/icons/icon-128x128.png +0 -0
  63. package/dist/icons/icon-128x128.svg +1 -12
  64. package/dist/icons/icon-144x144.png +0 -0
  65. package/dist/icons/icon-144x144.svg +1 -12
  66. package/dist/icons/icon-152x152.png +0 -0
  67. package/dist/icons/icon-152x152.svg +1 -12
  68. package/dist/icons/icon-192x192.png +0 -0
  69. package/dist/icons/icon-192x192.svg +1 -12
  70. package/dist/icons/icon-384x384.png +0 -0
  71. package/dist/icons/icon-384x384.svg +1 -12
  72. package/dist/icons/icon-512x512.png +0 -0
  73. package/dist/icons/icon-512x512.svg +1 -12
  74. package/dist/icons/icon-72x72.png +0 -0
  75. package/dist/icons/icon-72x72.svg +1 -12
  76. package/dist/icons/icon-96x96.png +0 -0
  77. package/dist/icons/icon-96x96.svg +1 -12
  78. package/dist/index.html +3 -3
  79. package/dist/logo-128.png +0 -0
  80. package/dist/logo-256.png +0 -0
  81. package/dist/logo-32.png +0 -0
  82. package/dist/logo-512.png +0 -0
  83. package/dist/logo-64.png +0 -0
  84. package/dist/logo-sources/logo-black.png +0 -0
  85. package/dist/logo-sources/logo-white.png +0 -0
  86. package/dist/logo.svg +1 -17
  87. package/dist/sw.js +1 -1
  88. package/dist-server/plugins/piecemaker-dossier/src/institutional-terms.js +91 -0
  89. package/dist-server/plugins/piecemaker-dossier/src/institutional-terms.js.map +1 -0
  90. package/dist-server/plugins/piecemaker-dossier/src/knowledge.js +112 -10
  91. package/dist-server/plugins/piecemaker-dossier/src/knowledge.js.map +1 -1
  92. package/dist-server/plugins/piecemaker-dossier/src/scan-result.js +5 -1
  93. package/dist-server/plugins/piecemaker-dossier/src/scan-result.js.map +1 -1
  94. package/dist-server/server/index.js +60 -26
  95. package/dist-server/server/index.js.map +1 -1
  96. package/dist-server/server/modules/cli/desktop-shortcut.js +80 -0
  97. package/dist-server/server/modules/cli/desktop-shortcut.js.map +1 -0
  98. package/dist-server/server/modules/cli/pwa-launcher.js +27 -4
  99. package/dist-server/server/modules/cli/pwa-launcher.js.map +1 -1
  100. package/dist-server/server/modules/database/repositories/projects.db.js +28 -6
  101. package/dist-server/server/modules/database/repositories/projects.db.js.map +1 -1
  102. package/dist-server/server/modules/database/tests/projects.db.integration.test.js +58 -1
  103. package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -1
  104. package/dist-server/server/modules/plugins/plugins.routes.js +8 -1
  105. package/dist-server/server/modules/plugins/plugins.routes.js.map +1 -1
  106. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +2 -0
  107. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
  108. package/dist-server/server/piecemaker/bodacc-search.js +191 -0
  109. package/dist-server/server/piecemaker/bodacc-search.js.map +1 -0
  110. package/dist-server/server/piecemaker/company-search.js +145 -0
  111. package/dist-server/server/piecemaker/company-search.js.map +1 -0
  112. package/dist-server/server/piecemaker/company-search.test.js +18 -0
  113. package/dist-server/server/piecemaker/company-search.test.js.map +1 -0
  114. package/dist-server/server/piecemaker/index.js +6 -2
  115. package/dist-server/server/piecemaker/index.js.map +1 -1
  116. package/dist-server/server/piecemaker/knowledge/index.js +9 -4
  117. package/dist-server/server/piecemaker/knowledge/index.js.map +1 -1
  118. package/dist-server/server/piecemaker/knowledge/pipeline.js +68 -58
  119. package/dist-server/server/piecemaker/knowledge/pipeline.js.map +1 -1
  120. package/dist-server/server/piecemaker/knowledge/routes.js +2 -4
  121. package/dist-server/server/piecemaker/knowledge/routes.js.map +1 -1
  122. package/dist-server/server/piecemaker/knowledge/scan-jobs.js +91 -0
  123. package/dist-server/server/piecemaker/knowledge/scan-jobs.js.map +1 -0
  124. package/dist-server/server/piecemaker/knowledge/service.js +18 -36
  125. package/dist-server/server/piecemaker/knowledge/service.js.map +1 -1
  126. package/dist-server/server/piecemaker/library/connector-installation.js +224 -0
  127. package/dist-server/server/piecemaker/library/connector-installation.js.map +1 -0
  128. package/dist-server/server/piecemaker/library/index.js +11 -1
  129. package/dist-server/server/piecemaker/library/index.js.map +1 -1
  130. package/dist-server/server/piecemaker/library/marketplace.js +102 -43
  131. package/dist-server/server/piecemaker/library/marketplace.js.map +1 -1
  132. package/dist-server/server/piecemaker/library/migrate.js +8 -1
  133. package/dist-server/server/piecemaker/library/migrate.js.map +1 -1
  134. package/dist-server/server/piecemaker/library/provider-agents.js +50 -0
  135. package/dist-server/server/piecemaker/library/provider-agents.js.map +1 -0
  136. package/dist-server/server/piecemaker/library/provider-connectors.js +80 -0
  137. package/dist-server/server/piecemaker/library/provider-connectors.js.map +1 -0
  138. package/dist-server/server/piecemaker/library/provider-skills.js +11 -2
  139. package/dist-server/server/piecemaker/library/provider-skills.js.map +1 -1
  140. package/dist-server/server/piecemaker/library/store.js +125 -35
  141. package/dist-server/server/piecemaker/library/store.js.map +1 -1
  142. package/dist-server/server/piecemaker/library/workspace-installation.js +53 -29
  143. package/dist-server/server/piecemaker/library/workspace-installation.js.map +1 -1
  144. package/dist-server/server/piecemaker/timesheet/service.js +4 -21
  145. package/dist-server/server/piecemaker/timesheet/service.js.map +1 -1
  146. package/package.json +3 -2
  147. package/product.config.json +1 -0
  148. package/scripts/piecemaker/cli/install-command.mjs +35 -9
  149. package/scripts/piecemaker/cli/lib/app-window.mjs +47 -0
  150. package/scripts/piecemaker/cli/lib/config.mjs +1 -0
  151. package/scripts/piecemaker/cli/lib/piecemaker-anonymizer.mjs +78 -0
  152. package/scripts/piecemaker/cli/lib/plugins.mjs +2 -0
  153. package/scripts/piecemaker/cli/lib/pwa.mjs +49 -13
  154. package/scripts/piecemaker/cli/lib/services.mjs +65 -4
  155. package/scripts/piecemaker/cli/piecemaker-command.test.mjs +25 -0
  156. package/scripts/piecemaker/cli/piecemaker.mjs +60 -45
  157. package/scripts/piecemaker/cli/piecemaker.ps1 +8 -0
  158. package/scripts/piecemaker/cli/piecemaker.sh +6 -1
  159. package/server/index.ts +52 -23
  160. package/server/modules/cli/desktop-shortcut.ts +89 -0
  161. package/server/modules/cli/pwa-launcher.ts +29 -4
  162. package/server/modules/database/repositories/projects.db.ts +28 -6
  163. package/server/modules/database/tests/projects.db.integration.test.ts +63 -1
  164. package/server/modules/plugins/plugins.routes.ts +8 -1
  165. package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +5 -0
  166. package/server/piecemaker/anonymizer/client-config.cjs +10 -2
  167. package/server/piecemaker/anonymizer/dictionary.cjs +6 -72
  168. package/server/piecemaker/bodacc-search.ts +205 -0
  169. package/server/piecemaker/company-search.test.ts +26 -0
  170. package/server/piecemaker/company-search.ts +183 -0
  171. package/server/piecemaker/index.ts +8 -2
  172. package/server/piecemaker/knowledge/index.ts +9 -4
  173. package/server/piecemaker/knowledge/pipeline.ts +89 -57
  174. package/server/piecemaker/knowledge/routes.ts +2 -4
  175. package/server/piecemaker/knowledge/scan-jobs.ts +111 -0
  176. package/server/piecemaker/knowledge/service.ts +18 -33
  177. package/server/piecemaker/library/connector-installation.ts +248 -0
  178. package/server/piecemaker/library/index.ts +5 -1
  179. package/server/piecemaker/library/marketplace.ts +104 -39
  180. package/server/piecemaker/library/migrate.ts +8 -1
  181. package/server/piecemaker/library/provider-agents.ts +49 -0
  182. package/server/piecemaker/library/provider-connectors.ts +89 -0
  183. package/server/piecemaker/library/provider-skills.ts +10 -1
  184. package/server/piecemaker/library/store.ts +129 -36
  185. package/server/piecemaker/library/workspace-installation.ts +54 -29
  186. package/server/piecemaker/router.cjs +2 -1
  187. package/server/piecemaker/timesheet/service.ts +4 -16
  188. package/server/piecemaker/vendor/installer/steps/03-python-gliner.mjs +0 -27
  189. package/server/piecemaker/vendor/installer/steps/14-mxc-sandbox.mjs +1 -1
  190. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +2 -3
  191. package/server/piecemaker/vendor/piecemaker-plugin/skills/conversion-md/SKILL.md +3 -3
  192. package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +3 -10
  193. package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +135 -4
  194. package/server/piecemaker/vendor/websocket-server/process-group.cjs +95 -0
  195. package/server/piecemaker/vendor/websocket-server/scripts/__pycache__/scan_utils.cpython-312.pyc +0 -0
  196. package/server/piecemaker/vendor/websocket-server/scripts/convert_and_scan_pipeline.py +280 -149
  197. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/model_config.cpython-312.pyc +0 -0
  198. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/scanner_worker.cpython-312.pyc +0 -0
  199. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/scanner_worker.py +35 -29
  200. package/server/piecemaker/vendor/websocket-server/scripts/requirements.txt +0 -5
  201. package/server/shared/types.ts +1 -0
  202. package/dist/assets/channel-Dz3eE5vU.js +0 -1
  203. package/dist/assets/classDiagram-ZZMXUADV-BC1eKV2j.js +0 -1
  204. package/dist/assets/classDiagram-v2-VYDZK3BY-BC1eKV2j.js +0 -1
  205. package/dist/assets/index-DhwJpHCq.css +0 -1
  206. package/dist/assets/stateDiagram-v2-MP3YSRHH-BXuT4zDG.js +0 -1
  207. package/dist/assets/swimlanesDiagram-VR7AAH4N-B0eYrB4U.js +0 -8
  208. package/server/piecemaker/vendor/websocket-server/case-instructions.cjs +0 -111
  209. package/server/piecemaker/vendor/websocket-server/scripts/ADDRESS_ANONYMIZATION.md +0 -216
  210. package/server/piecemaker/vendor/websocket-server/scripts/DEDUPLICATION_CHANGES.md +0 -134
  211. package/server/piecemaker/vendor/websocket-server/scripts/GLINER2_README.md +0 -138
  212. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/AUDIT_PERF_QUALITE.md +0 -246
  213. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/MODELE_COREML.md +0 -62
  214. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/coreml_runtime.cpython-312.pyc +0 -0
  215. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/build_coreml.py +0 -118
  216. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/coreml_runtime.py +0 -164
  217. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/presidio-gliner.py +0 -479
@@ -6,6 +6,7 @@ import { spawn } from 'node:child_process';
6
6
  import { APP, LOG_DIR, PIECEMAKER_HOME, PORTS } from './config.mjs';
7
7
  import { waitUntil } from './exec.mjs';
8
8
  import { npmPath, runtimeEnv } from './node-runtime.mjs';
9
+ import { freePort } from './ports.mjs';
9
10
 
10
11
  export const APP_LOG = path.join(LOG_DIR, 'application.log');
11
12
  export const APP_PID_FILE = path.join(PIECEMAKER_HOME, 'application.pid');
@@ -32,6 +33,53 @@ export function appClientReachable() {
32
33
  return httpReachable(`http://127.0.0.1:${PORTS.appClient}/`);
33
34
  }
34
35
 
36
+ function anonymizerPort() {
37
+ try {
38
+ const port = Number.parseInt(JSON.parse(fs.readFileSync(path.join(PIECEMAKER_HOME, 'config.json'), 'utf8')).mikePiiPort, 10);
39
+ if (Number.isInteger(port) && port > 0) return port;
40
+ } catch {
41
+ }
42
+ return PORTS.anonymizer;
43
+ }
44
+
45
+ function stopPid(pid) {
46
+ if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
47
+ if (process.platform === 'win32') {
48
+ try { process.kill(pid, 'SIGTERM'); return true; } catch { return false; }
49
+ }
50
+ try { process.kill(-pid, 'SIGTERM'); return true; } catch {
51
+ try { process.kill(pid, 'SIGTERM'); return true; } catch { return false; }
52
+ }
53
+ }
54
+
55
+ export async function stopApplication() {
56
+ const killed = [];
57
+ const occupied = [];
58
+ try {
59
+ const pid = Number.parseInt(fs.readFileSync(APP_PID_FILE, 'utf8'), 10);
60
+ if (stopPid(pid)) killed.push(pid);
61
+ } catch {
62
+ }
63
+
64
+ for (const port of [PORTS.appClient, PORTS.appServer, anonymizerPort()]) {
65
+ const result = await freePort(port);
66
+ killed.push(...result.killed);
67
+ if (!result.released) occupied.push(port);
68
+ }
69
+
70
+ try { fs.rmSync(APP_PID_FILE, { force: true }); } catch { }
71
+ return { killed: [...new Set(killed)], occupied };
72
+ }
73
+
74
+ function readLogTail(file, maxLines = 40) {
75
+ try {
76
+ const lines = fs.readFileSync(file, 'utf8').trimEnd().split(/\n/);
77
+ return lines.slice(-maxLines).join('\n');
78
+ } catch {
79
+ return '';
80
+ }
81
+ }
82
+
35
83
  export async function startApplication(runtime, report) {
36
84
  const alreadyRunning = await appClientReachable() && await appServerReachable();
37
85
  if (alreadyRunning) {
@@ -61,11 +109,24 @@ export async function startApplication(runtime, report) {
61
109
  fs.closeSync(logHandle);
62
110
  }
63
111
 
64
- child.unref();
65
112
  fs.writeFileSync(APP_PID_FILE, String(child.pid), 'utf8');
66
113
 
67
- const serverUp = await waitUntil(appServerReachable, { timeoutMs: 120_000 });
68
- const clientUp = await waitUntil(appClientReachable, { timeoutMs: 120_000 });
114
+ const death = new Promise((resolve) => {
115
+ child.once('error', () => resolve('died'));
116
+ child.once('exit', () => resolve('died'));
117
+ });
118
+
119
+ await Promise.race([
120
+ waitUntil(appServerReachable, { timeoutMs: 120_000 }),
121
+ death,
122
+ ]);
123
+ const serverUp = await appServerReachable();
124
+ if (!serverUp) {
125
+ child.unref();
126
+ return { started: true, pid: child.pid, serverUp: false, clientUp: false, logTail: readLogTail(APP_LOG) };
127
+ }
69
128
 
70
- return { started: true, pid: child.pid, serverUp, clientUp };
129
+ const clientUp = await waitUntil(appClientReachable, { timeoutMs: 120_000 });
130
+ child.unref();
131
+ return { started: true, pid: child.pid, serverUp: true, clientUp, logTail: clientUp ? '' : readLogTail(APP_LOG) };
71
132
  }
@@ -0,0 +1,25 @@
1
+ import assert from 'node:assert/strict';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import test from 'node:test';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ import { BIN_DIR } from './lib/config.mjs';
8
+ import { piecemakerExecutable } from './install-command.mjs';
9
+
10
+ const here = path.dirname(fileURLToPath(import.meta.url));
11
+
12
+ test('the curl bootstrap installs the piecemaker command before launching', () => {
13
+ const posix = fs.readFileSync(path.join(here, 'piecemaker.sh'), 'utf8');
14
+ const windows = fs.readFileSync(path.join(here, 'piecemaker.ps1'), 'utf8');
15
+ assert.match(posix, /install-command\.mjs/);
16
+ assert.match(windows, /install-command\.mjs/);
17
+ });
18
+
19
+ test('the desktop launcher binds to the installed piecemaker binary', () => {
20
+ const executable = piecemakerExecutable();
21
+ assert.equal(path.basename(executable).startsWith('piecemaker'), true);
22
+ if (executable !== 'piecemaker') {
23
+ assert.equal(executable.startsWith(BIN_DIR) || executable.includes(`${path.sep}node${path.sep}` ) || executable.includes('versions/node'), true);
24
+ }
25
+ });
@@ -1,50 +1,36 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { installPiecemakerCommand, piecemakerExecutable } from './install-command.mjs';
3
4
  import { APP, APP_URL, PORTS } from './lib/config.mjs';
5
+ import { repairClientProxies } from './lib/piecemaker-anonymizer.mjs';
4
6
  import { installComponents } from './lib/composants.mjs';
5
7
  import { installPlugins } from './lib/plugins.mjs';
6
8
  import { gitAvailable, resolveNodeRuntime } from './lib/node-runtime.mjs';
7
- import { freePort } from './lib/ports.mjs';
8
9
  import { ensureDependencies, ensureRepository, rebuildNativeModules } from './lib/repos.mjs';
9
- import { APP_LOG, appClientReachable, appServerReachable, startApplication } from './lib/services.mjs';
10
- import { installApplicationEntry, openApplication, verifyPwaAssets } from './lib/pwa.mjs';
10
+ import { APP_LOG, appClientReachable, appServerReachable, startApplication, stopApplication } from './lib/services.mjs';
11
+ import { applicationWindowIsOpen, installApplicationEntry, openApplication, verifyPwaAssets } from './lib/pwa.mjs';
12
+ import { runApplicationWindow } from './lib/app-window.mjs';
11
13
  import { banner, blank, c, detail, fail, ok, step, warn } from './lib/ui.mjs';
12
14
 
13
15
  const report = { step, ok, warn, detail };
14
16
 
15
- const HELP = `${c.bold('piecemaker')} — installe, met à jour et lance toute la plateforme.
17
+ const HELP = `${c.bold('piecemaker')} — installe, met à jour, répare et lance toute la plateforme.
16
18
 
17
- piecemaker tout faire : ports, dépôts, dépendances, PWA, serveurs
18
- piecemaker --launch-only ne relance que les serveurs manquants
19
- piecemaker --no-open ne pas ouvrir l application à la fin
19
+ piecemaker reset propre, puis installation et lancement
20
+ piecemaker --launch-only répare et relance seulement si l'application est arrêtée
21
+ piecemaker --window relance si besoin, ouvre la fenêtre et arrête tout à sa fermeture
20
22
  piecemaker --help cette aide
21
23
  `;
22
24
 
23
25
  function parseArguments(argv) {
26
+ const window = argv.includes('--window');
24
27
  return {
25
28
  help: argv.includes('--help') || argv.includes('-h'),
26
- launchOnly: argv.includes('--launch-only'),
27
- open: !argv.includes('--no-open'),
29
+ launchOnly: argv.includes('--launch-only') || window,
30
+ window,
28
31
  };
29
32
  }
30
33
 
31
- async function cleanPorts(launchOnly) {
32
- step('Libération des ports');
33
- const targets = launchOnly
34
- ? [PORTS.appServer, PORTS.appClient]
35
- : [PORTS.appClient, PORTS.appServer];
36
-
37
- for (const port of targets) {
38
- const result = await freePort(port);
39
- if (!result.released) {
40
- warn(`port ${port} toujours occupé`);
41
- continue;
42
- }
43
- if (result.killed.length > 0) detail(`port ${port} libéré (${result.killed.join(', ')})`);
44
- }
45
- ok('Ports disponibles');
46
- }
47
-
48
34
  async function synchroniseRepositories(runtime) {
49
35
  if (!gitAvailable()) {
50
36
  throw new Error('git est requis et introuvable.');
@@ -63,32 +49,43 @@ async function synchroniseRepositories(runtime) {
63
49
  rebuildNativeModules(APP, runtime, report, ['better-sqlite3', 'node-pty']);
64
50
  }
65
51
 
66
- async function launchServices(runtime) {
67
- const applicationAlreadyRunning = await appServerReachable() && await appClientReachable();
68
-
69
- if (applicationAlreadyRunning) {
70
- ok(`Application déjà active — ${APP_URL}`);
71
- detail('socle laissé en l état : le routage des clients IA appartient à l application en cours');
72
- return true;
52
+ function printLogTail(logTail) {
53
+ if (!logTail) {
54
+ detail(`journal : ${APP_LOG}`);
55
+ return;
56
+ }
57
+ detail(`journal : ${APP_LOG}`);
58
+ for (const line of logTail.split('\n')) {
59
+ if (line.trim()) detail(line);
73
60
  }
61
+ }
62
+
63
+ async function applicationIsRunning() {
64
+ return await appServerReachable() && await appClientReachable();
65
+ }
66
+
67
+ async function resetAndLaunch(runtime) {
68
+ step('Reset de l application');
69
+ const stopped = await stopApplication();
70
+ if (stopped.killed.length) detail(`arrêtés : ${stopped.killed.join(', ')}`);
71
+ for (const port of stopped.occupied) warn(`port ${port} toujours occupé`);
72
+ ok('Processus précédents arrêtés');
74
73
 
75
74
  const application = await startApplication(runtime, report);
76
75
  if (application.alreadyRunning) {
77
76
  ok(`Application déjà active — ${APP_URL}`);
78
77
  return true;
79
78
  }
80
-
81
79
  if (!application.serverUp) {
82
80
  fail(`Serveur applicatif injoignable sur le port ${PORTS.appServer}`);
83
- detail(`journal : ${APP_LOG}`);
81
+ printLogTail(application.logTail);
84
82
  return false;
85
83
  }
86
84
  if (!application.clientUp) {
87
85
  fail(`Client applicatif injoignable sur le port ${PORTS.appClient}`);
88
- detail(`journal : ${APP_LOG}`);
86
+ printLogTail(application.logTail);
89
87
  return false;
90
88
  }
91
-
92
89
  ok(`Application active — serveur :${PORTS.appServer}, client ${APP_URL}`);
93
90
  return true;
94
91
  }
@@ -111,6 +108,7 @@ async function installPwa() {
111
108
  for (const location of entry.locations || []) {
112
109
  if (location !== entry.location) detail(`également posé : ${location}`);
113
110
  }
111
+ detail(`icône liée à ${piecemakerExecutable()}`);
114
112
  if (!entry.standalone) detail('navigateur Chromium absent : ouverture en onglet classique');
115
113
  if (!entry.verified) warn('installation incomplète : un emplacement attendu est introuvable après coup');
116
114
  }
@@ -127,7 +125,19 @@ async function main() {
127
125
  const runtime = resolveNodeRuntime();
128
126
  detail(`Node ${runtime.version}`);
129
127
 
130
- await cleanPorts(options.launchOnly);
128
+ step('Commande piecemaker');
129
+ const command = installPiecemakerCommand();
130
+ if (command.shims.length) ok(`installée : ${command.executable}`);
131
+ else warn('commande absente du PATH — relancez node scripts/piecemaker/cli/install-command.mjs');
132
+
133
+ await repairClientProxies({ appDir: APP.directory, report });
134
+
135
+ if (!options.launchOnly || !(await applicationIsRunning())) {
136
+ const running = await resetAndLaunch(runtime);
137
+ if (!running) return 1;
138
+ } else {
139
+ ok(`Application déjà active — ${APP_URL}`);
140
+ }
131
141
 
132
142
  if (!options.launchOnly) {
133
143
  await synchroniseRepositories(runtime);
@@ -135,19 +145,24 @@ async function main() {
135
145
  await installPlugins(runtime, report);
136
146
  }
137
147
 
138
- const running = await launchServices(runtime);
139
- if (!running) return 1;
140
-
141
- if (!options.launchOnly) {
142
- await installPwa();
143
- }
148
+ await installPwa();
144
149
 
145
150
  blank();
146
151
  ok(`Application ${c.bold(APP_URL)}`);
147
152
  detail('Configuration du socle : onglet Dossier › Configuration.');
148
153
  blank();
149
154
 
150
- if (options.open) openApplication();
155
+ if (options.window) {
156
+ const session = await runApplicationWindow();
157
+ if (!session.supervised) {
158
+ warn(`Fenêtre non supervisée : ${session.reason}`);
159
+ return 0;
160
+ }
161
+ ok('Fenêtre fermée — application arrêtée');
162
+ return 0;
163
+ }
164
+
165
+ if (!options.launchOnly && !applicationWindowIsOpen()) openApplication();
151
166
  return 0;
152
167
  }
153
168
 
@@ -59,5 +59,13 @@ if (-not (Test-Path $EntryPath)) {
59
59
  exit 1
60
60
  }
61
61
 
62
+ $Installer = Join-Path $AppDir 'scripts\piecemaker\cli\install-command.mjs'
63
+ if (Test-Path $Installer) {
64
+ & $NodeBin $Installer
65
+ }
66
+
67
+ $BinDir = Join-Path $HOME '.piecemaker\bin'
68
+ $env:PATH = "$(Split-Path $NodeBin -Parent);$BinDir;$env:PATH"
69
+
62
70
  & $NodeBin $EntryPath @args
63
71
  exit $LASTEXITCODE
@@ -54,7 +54,12 @@ if [ ! -f "$APP_DIR/$ENTRY" ]; then
54
54
  exit 1
55
55
  fi
56
56
 
57
- PATH="$(dirname "$NODE_BIN"):$PATH"
57
+ INSTALLER="$APP_DIR/scripts/piecemaker/cli/install-command.mjs"
58
+ if [ -f "$INSTALLER" ]; then
59
+ "$NODE_BIN" "$INSTALLER" || true
60
+ fi
61
+
62
+ PATH="$(dirname "$NODE_BIN"):${HOME}/.piecemaker/bin:${PATH:-}"
58
63
  export PATH
59
64
 
60
65
  exec "$NODE_BIN" "$APP_DIR/$ENTRY" "$@"
package/server/index.ts CHANGED
@@ -30,7 +30,7 @@ import { commandsRoutes } from './modules/commands/index.js';
30
30
  import { settingsRoutes } from './modules/settings/index.js';
31
31
  import { createSystemModule } from './modules/system/index.js';
32
32
  import { createAgentModule } from './modules/agent/index.js';
33
- import { createPieceMakerRouter } from './piecemaker/index.js';
33
+ import { createPieceMakerRouter, stopOriginalsJobs } from './piecemaker/index.js';
34
34
  import projectModuleRoutes from './modules/projects/projects.routes.js';
35
35
  import notificationRoutes from './modules/notifications/notifications.routes.js';
36
36
  import { userRoutes } from './modules/user/index.js';
@@ -378,29 +378,58 @@ async function startServer() {
378
378
  });
379
379
  });
380
380
 
381
- await closeSessionsWatcher();
382
- closeScheduledMessageDispatcher();
383
- // Clean up plugin processes on shutdown
384
- const shutdownRuntimeServices = async () => {
385
- try {
386
- await browserUseService.stopAllSessions();
387
- } catch (err) {
388
- console.error('[Browser] Error stopping sessions during shutdown:', getErrorMessage(err));
389
- }
390
- try {
391
- await stopAllPlugins();
392
- } catch (err) {
393
- console.error('[Plugins] Error stopping plugins during shutdown:', getErrorMessage(err));
394
- }
395
- try {
396
- await removeLocalServerMarker();
397
- } catch (err) {
398
- console.error('[Local Server] Error removing server marker during shutdown:', getErrorMessage(err));
399
- }
400
- process.exit(0);
381
+ let shutdownPromise: Promise<void> | null = null;
382
+ const shutdownRuntimeServices = () => {
383
+ if (shutdownPromise) return shutdownPromise;
384
+ shutdownPromise = (async () => {
385
+ // Stop accepting HTTP work first. Existing requests may finish while
386
+ // the process pipelines and background services are being drained.
387
+ const serverClosed = new Promise<void>((resolve, reject) => {
388
+ server.close((error) => {
389
+ if (error && getErrorCode(error) !== 'ERR_SERVER_NOT_RUNNING') reject(error);
390
+ else resolve();
391
+ });
392
+ }).catch((err) => {
393
+ console.error('[Server] Error closing HTTP server during shutdown:', getErrorMessage(err));
394
+ });
395
+
396
+ try {
397
+ await closeSessionsWatcher();
398
+ } catch (err) {
399
+ console.error('[Sessions] Error stopping watchers during shutdown:', getErrorMessage(err));
400
+ }
401
+ try {
402
+ closeScheduledMessageDispatcher();
403
+ } catch (err) {
404
+ console.error('[Scheduled messages] Error stopping dispatcher during shutdown:', getErrorMessage(err));
405
+ }
406
+ try {
407
+ await stopOriginalsJobs();
408
+ } catch (err) {
409
+ console.error('[PieceMaker] Error stopping pipeline jobs during shutdown:', getErrorMessage(err));
410
+ }
411
+ try {
412
+ await browserUseService.stopAllSessions();
413
+ } catch (err) {
414
+ console.error('[Browser] Error stopping sessions during shutdown:', getErrorMessage(err));
415
+ }
416
+ try {
417
+ await stopAllPlugins();
418
+ } catch (err) {
419
+ console.error('[Plugins] Error stopping plugins during shutdown:', getErrorMessage(err));
420
+ }
421
+ await serverClosed;
422
+ try {
423
+ await removeLocalServerMarker();
424
+ } catch (err) {
425
+ console.error('[Local Server] Error removing server marker during shutdown:', getErrorMessage(err));
426
+ }
427
+ process.exit(0);
428
+ })();
429
+ return shutdownPromise;
401
430
  };
402
- process.on('SIGTERM', () => void shutdownRuntimeServices());
403
- process.on('SIGINT', () => void shutdownRuntimeServices());
431
+ process.on('SIGTERM', () => { void shutdownRuntimeServices(); });
432
+ process.on('SIGINT', () => { void shutdownRuntimeServices(); });
404
433
  } catch (error) {
405
434
  console.error('[ERROR] Failed to start server:', error);
406
435
  process.exit(1);
@@ -0,0 +1,89 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+
6
+ type ShortcutOutput = {
7
+ log(message: string): void;
8
+ };
9
+
10
+ const SHORTCUT_NAME = 'PieceMaker';
11
+
12
+ function desktopDirectory(): string {
13
+ return process.env.PIECEMAKER_DESKTOP_DIR || path.join(os.homedir(), 'Desktop');
14
+ }
15
+
16
+ function launchCommand(): { executable: string; argument: string } {
17
+ return { executable: process.execPath, argument: process.argv[1] || '' };
18
+ }
19
+
20
+ function createMacShortcut(desktop: string): string | null {
21
+ const { executable, argument } = launchCommand();
22
+ if (!argument) return null;
23
+ const bundle = path.join(desktop, `${SHORTCUT_NAME}.app`);
24
+ if (fs.existsSync(bundle)) return null;
25
+
26
+ const macOsDirectory = path.join(bundle, 'Contents', 'MacOS');
27
+ fs.mkdirSync(macOsDirectory, { recursive: true });
28
+ fs.writeFileSync(
29
+ path.join(bundle, 'Contents', 'Info.plist'),
30
+ `<?xml version="1.0" encoding="UTF-8"?>
31
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
32
+ <plist version="1.0">
33
+ <dict>
34
+ <key>CFBundleName</key><string>${SHORTCUT_NAME}</string>
35
+ <key>CFBundleIdentifier</key><string>legal.piecemaker.launcher</string>
36
+ <key>CFBundleExecutable</key><string>${SHORTCUT_NAME}</string>
37
+ <key>CFBundlePackageType</key><string>APPL</string>
38
+ <key>LSUIElement</key><true/>
39
+ </dict>
40
+ </plist>
41
+ `,
42
+ );
43
+
44
+ const launcher = path.join(macOsDirectory, SHORTCUT_NAME);
45
+ fs.writeFileSync(launcher, `#!/bin/sh\nexec ${JSON.stringify(executable)} ${JSON.stringify(argument)} start\n`);
46
+ fs.chmodSync(launcher, 0o755);
47
+ return bundle;
48
+ }
49
+
50
+ function createWindowsShortcut(desktop: string): string | null {
51
+ const { executable, argument } = launchCommand();
52
+ if (!argument) return null;
53
+ const shortcut = path.join(desktop, `${SHORTCUT_NAME}.lnk`);
54
+ if (fs.existsSync(shortcut)) return null;
55
+
56
+ const script = [
57
+ '$shell = New-Object -ComObject WScript.Shell',
58
+ `$shortcut = $shell.CreateShortcut(${JSON.stringify(shortcut)})`,
59
+ `$shortcut.TargetPath = ${JSON.stringify(executable)}`,
60
+ `$shortcut.Arguments = ${JSON.stringify(`${JSON.stringify(argument)} start`)}`,
61
+ `$shortcut.WorkingDirectory = ${JSON.stringify(path.dirname(executable))}`,
62
+ '$shortcut.WindowStyle = 7',
63
+ `$shortcut.Description = ${JSON.stringify('Ouvre PieceMaker')}`,
64
+ '$shortcut.Save()',
65
+ ].join('; ');
66
+ execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], { stdio: 'ignore' });
67
+ return shortcut;
68
+ }
69
+
70
+ /**
71
+ * Creates the PieceMaker desktop icon once, right after a fresh install, so the
72
+ * application can be reopened without a terminal. Existing shortcuts are left
73
+ * untouched and any failure is reported without interrupting the launch.
74
+ */
75
+ export function ensureDesktopShortcut(output: ShortcutOutput): void {
76
+ if (process.platform !== 'darwin' && process.platform !== 'win32') return;
77
+
78
+ const desktop = desktopDirectory();
79
+ if (!fs.existsSync(desktop)) return;
80
+
81
+ try {
82
+ const created = process.platform === 'darwin'
83
+ ? createMacShortcut(desktop)
84
+ : createWindowsShortcut(desktop);
85
+ if (created) output.log(`[OK] PieceMaker desktop icon created at ${created}`);
86
+ } catch {
87
+ output.log('[WARN] Could not create the PieceMaker desktop icon.');
88
+ }
89
+ }
@@ -4,6 +4,8 @@ import fs from 'node:fs';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
6
 
7
+ import { ensureDesktopShortcut } from './desktop-shortcut.js';
8
+
7
9
  type PwaOutput = {
8
10
  log(message: string): void;
9
11
  };
@@ -102,7 +104,22 @@ async function waitForServer(url: string): Promise<boolean> {
102
104
  return false;
103
105
  }
104
106
 
105
- /** Starts PieceMaker in an isolated Chromium app window after its backend is ready. */
107
+ const SHUTDOWN_GRACE_MS = 10_000;
108
+ const DELEGATED_LAUNCH_MS = 3_000;
109
+
110
+ function shutdownAfterWindowClosed(output: PwaOutput): void {
111
+ output.log('[OK] PieceMaker window closed; stopping the local server.');
112
+ const forced = setTimeout(() => process.exit(0), SHUTDOWN_GRACE_MS);
113
+ forced.unref();
114
+ if (process.listenerCount('SIGTERM') > 0) process.emit('SIGTERM', 'SIGTERM');
115
+ else process.exit(0);
116
+ }
117
+
118
+ /**
119
+ * Starts PieceMaker in an isolated Chromium app window after its backend is
120
+ * ready, and ties the server's lifetime to that window: closing it shuts the
121
+ * local server down through the same graceful path as SIGTERM.
122
+ */
106
123
  export async function launchPwa(output: PwaOutput = DEFAULT_OUTPUT): Promise<void> {
107
124
  const url = pwaUrl();
108
125
  if (!(await waitForServer(url))) {
@@ -116,16 +133,24 @@ export async function launchPwa(output: PwaOutput = DEFAULT_OUTPUT): Promise<voi
116
133
  return;
117
134
  }
118
135
 
136
+ ensureDesktopShortcut(output);
137
+
119
138
  const profileDirectory = pwaProfileDirectory();
120
139
  fs.mkdirSync(profileDirectory, { recursive: true });
140
+ const openedAt = Date.now();
121
141
  const child = spawn(browser, [`--app=${url}`, `--user-data-dir=${profileDirectory}`], {
122
- detached: true,
123
142
  stdio: 'ignore',
124
143
  windowsHide: true,
125
144
  });
126
145
  child.on('error', () => {
127
146
  output.log(`[WARN] Could not open the PieceMaker PWA; open ${url} manually.`);
128
147
  });
129
- child.unref();
130
- output.log(`[OK] PieceMaker PWA opened at ${url}`);
148
+ child.on('exit', () => {
149
+ if (Date.now() - openedAt < DELEGATED_LAUNCH_MS) {
150
+ output.log(`[WARN] The PieceMaker window is owned by another browser process; the server keeps running.`);
151
+ return;
152
+ }
153
+ shutdownAfterWindowClosed(output);
154
+ });
155
+ output.log(`[OK] PieceMaker PWA opened at ${url} — closing the window stops the server.`);
131
156
  }
@@ -15,6 +15,22 @@ function normalizeProjectDisplayName(projectPath: string, customProjectName: str
15
15
  return directoryName || projectPath;
16
16
  }
17
17
 
18
+ function ensureAnonymizationStatusTable(db: ReturnType<typeof getConnection>): void {
19
+ db.exec(`
20
+ CREATE TABLE IF NOT EXISTS piecemaker_anonymization_status (
21
+ project_id TEXT PRIMARY KEY NOT NULL,
22
+ completed_at TEXT NOT NULL,
23
+ FOREIGN KEY (project_id) REFERENCES projects(project_id) ON DELETE CASCADE
24
+ )
25
+ `);
26
+ const mappingsExist = db.prepare(`SELECT 1 AS ok FROM sqlite_master WHERE type='table' AND name='piecemaker_mappings'`).get() as { ok: number } | undefined;
27
+ if (!mappingsExist) return;
28
+ db.prepare(`
29
+ INSERT OR IGNORE INTO piecemaker_anonymization_status(project_id, completed_at)
30
+ SELECT DISTINCT project_id, ? FROM piecemaker_mappings
31
+ `).run(new Date().toISOString());
32
+ }
33
+
18
34
  export const projectsDb = {
19
35
  createProjectPath(projectPath: string, customProjectName: string | null = null): CreateProjectPathResult {
20
36
  const db = getConnection();
@@ -88,10 +104,13 @@ export const projectsDb = {
88
104
 
89
105
  getProjectPaths(): ProjectRepositoryRow[] {
90
106
  const db = getConnection();
107
+ ensureAnonymizationStatusTable(db);
91
108
  return db.prepare(`
92
- SELECT project_id, project_path, custom_project_name, isStarred, isArchived
93
- FROM projects
94
- WHERE isArchived = 0
109
+ SELECT p.project_id, p.project_path, p.custom_project_name, p.isStarred, p.isArchived,
110
+ CASE WHEN s.project_id IS NULL THEN 0 ELSE 1 END AS anonymization_complete
111
+ FROM projects p
112
+ LEFT JOIN piecemaker_anonymization_status s ON s.project_id = p.project_id
113
+ WHERE p.isArchived = 0
95
114
  `).all() as ProjectRepositoryRow[];
96
115
  },
97
116
 
@@ -101,10 +120,13 @@ export const projectsDb = {
101
120
  */
102
121
  getArchivedProjectPaths(): ProjectRepositoryRow[] {
103
122
  const db = getConnection();
123
+ ensureAnonymizationStatusTable(db);
104
124
  return db.prepare(`
105
- SELECT project_id, project_path, custom_project_name, isStarred, isArchived
106
- FROM projects
107
- WHERE isArchived = 1
125
+ SELECT p.project_id, p.project_path, p.custom_project_name, p.isStarred, p.isArchived,
126
+ CASE WHEN s.project_id IS NULL THEN 0 ELSE 1 END AS anonymization_complete
127
+ FROM projects p
128
+ LEFT JOIN piecemaker_anonymization_status s ON s.project_id = p.project_id
129
+ WHERE p.isArchived = 1
108
130
  `).all() as ProjectRepositoryRow[];
109
131
  },
110
132