@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
@@ -0,0 +1,183 @@
1
+ import express, { type Request, type Response } from 'express';
2
+
3
+ const REGISTRE_PUBLIC_MCP_URL = 'https://registre-public.com/api/mcp';
4
+ const MCP_PROTOCOL_VERSION = '2025-03-26';
5
+ const MCP_TIMEOUT_MS = 20_000;
6
+
7
+ type JsonRpcPayload = {
8
+ result?: {
9
+ content?: Array<{ type?: string; text?: string }>;
10
+ isError?: boolean;
11
+ };
12
+ error?: { message?: string };
13
+ };
14
+
15
+ export type CompanyDirector = { name: string; role: string };
16
+
17
+ export type CompanySearchFields = {
18
+ legalName: string;
19
+ legalForm: string;
20
+ status: string;
21
+ siren: string;
22
+ siret: string;
23
+ vat: string;
24
+ legalFormCode: string;
25
+ naf: string;
26
+ creationDate: string;
27
+ category: string;
28
+ address: string;
29
+ directors: CompanyDirector[];
30
+ finances: string[];
31
+ source: string;
32
+ };
33
+
34
+ export type CompanySearchResult = {
35
+ name: string;
36
+ siren: string;
37
+ summary: string;
38
+ url: string;
39
+ details: string;
40
+ fields: CompanySearchFields;
41
+ };
42
+
43
+ function parseMcpPayload(body: string): JsonRpcPayload {
44
+ const trimmed = body.trim();
45
+ if (trimmed.startsWith('{')) return JSON.parse(trimmed) as JsonRpcPayload;
46
+ const payloads = trimmed
47
+ .split(/\r?\n/)
48
+ .filter((line) => line.startsWith('data:'))
49
+ .map((line) => line.slice(5).trim())
50
+ .filter(Boolean)
51
+ .map((line) => JSON.parse(line) as JsonRpcPayload);
52
+ const payload = payloads.at(-1);
53
+ if (!payload) throw new Error('Réponse PERS_MORALE_1 invalide.');
54
+ return payload;
55
+ }
56
+
57
+ async function mcpRequest(id: number, method: string, params: Record<string, unknown>, sessionId?: string): Promise<{ payload: JsonRpcPayload; sessionId?: string }> {
58
+ const response = await fetch(REGISTRE_PUBLIC_MCP_URL, {
59
+ method: 'POST',
60
+ headers: {
61
+ Accept: 'application/json, text/event-stream',
62
+ 'Content-Type': 'application/json',
63
+ ...(sessionId ? { 'Mcp-Session-Id': sessionId } : {}),
64
+ },
65
+ body: JSON.stringify({ jsonrpc: '2.0', id, method, params }),
66
+ signal: AbortSignal.timeout(MCP_TIMEOUT_MS),
67
+ });
68
+ const body = await response.text();
69
+ if (!response.ok) throw new Error(`Registre Public a répondu ${response.status}.`);
70
+ const payload = parseMcpPayload(body);
71
+ if (payload.error?.message) throw new Error(payload.error.message);
72
+ return { payload, sessionId: response.headers.get('mcp-session-id') || sessionId };
73
+ }
74
+
75
+ async function mcpNotification(method: string, params: Record<string, unknown>, sessionId?: string): Promise<void> {
76
+ const response = await fetch(REGISTRE_PUBLIC_MCP_URL, {
77
+ method: 'POST',
78
+ headers: {
79
+ Accept: 'application/json, text/event-stream',
80
+ 'Content-Type': 'application/json',
81
+ ...(sessionId ? { 'Mcp-Session-Id': sessionId } : {}),
82
+ },
83
+ body: JSON.stringify({ jsonrpc: '2.0', method, params }),
84
+ signal: AbortSignal.timeout(MCP_TIMEOUT_MS),
85
+ });
86
+ if (!response.ok) throw new Error(`Registre Public a répondu ${response.status}.`);
87
+ }
88
+
89
+ function resultText(payload: JsonRpcPayload): string {
90
+ const text = payload.result?.content?.filter((entry) => entry.type === 'text' && typeof entry.text === 'string').map((entry) => entry.text).join('\n').trim();
91
+ if (!text || payload.result?.isError) throw new Error('Registre Public n’a renvoyé aucun résultat exploitable.');
92
+ return text;
93
+ }
94
+
95
+ function lineValue(text: string, label: string): string {
96
+ const line = text.split('\n').find((entry) => entry.startsWith(label));
97
+ return line ? line.slice(label.length).trim() : '';
98
+ }
99
+
100
+ export function parseSearchLine(line: string): { name: string; siren: string; summary: string } | null {
101
+ const match = /^-\s+(.+?)(?:\s+\(([^)]+)\))?\s+\|\s+SIREN\s+(\d{9})\s+\|\s+(.+?)\s+\|\s+NAF\s+([^|]+)\s+\|\s+(.+)$/.exec(line.trim());
102
+ if (!match) return null;
103
+ return { name: match[1].trim(), siren: match[3], summary: [match[2]?.trim(), match[4].trim(), `NAF ${match[5].trim()}`, match[6].trim()].filter(Boolean).join(' · ') };
104
+ }
105
+
106
+ function parseDirectors(text: string): CompanyDirector[] {
107
+ const start = text.indexOf('Dirigeants:');
108
+ if (start < 0) return [];
109
+ const block = text.slice(start + 'Dirigeants:'.length).split(/\n(?=Finances\b|$)/)[0];
110
+ return block.split('\n').map((line) => /^-\s+(.+?)\s+\(([^)]+)\)$/.exec(line.trim())).filter((match): match is RegExpExecArray => Boolean(match)).map((match) => ({ name: match[1].trim(), role: match[2].trim() }));
111
+ }
112
+
113
+ function parseCompanyDetails(text: string, fallback: { name: string; siren: string }): CompanySearchFields {
114
+ const heading = /^#\s+(.+?)\s+\(([^)]+)\)\s+\(([^)]+)\)/m.exec(text);
115
+ const formAndActivity = lineValue(text, 'Forme juridique (code) :');
116
+ const [legalFormCode, nafPart] = formAndActivity.split('|').map((value) => value.trim());
117
+ const creation = lineValue(text, 'Création :');
118
+ const [creationDate, categoryPart] = creation.split('|').map((value) => value.trim());
119
+ return {
120
+ legalName: heading?.[1]?.trim() || fallback.name,
121
+ legalForm: '',
122
+ status: heading?.[3]?.trim() || '',
123
+ siren: lineValue(text, 'SIREN :').split('|')[0].trim() || fallback.siren,
124
+ siret: (lineValue(text, 'SIREN :').match(/SIRET siège\s*:\s*([^|]+)/)?.[1] || '').trim(),
125
+ vat: lineValue(text, 'N° TVA intracommunautaire :'),
126
+ legalFormCode: legalFormCode || '',
127
+ naf: nafPart?.replace(/^NAF\s*:\s*/, '') || '',
128
+ creationDate: creationDate || '',
129
+ category: categoryPart?.replace(/^Catégorie\s*:\s*/, '') || '',
130
+ address: lineValue(text, 'Adresse du siège :'),
131
+ directors: parseDirectors(text),
132
+ finances: text.split('\n').filter((line) => line.startsWith('Finances ')).map((line) => line.trim()),
133
+ source: lineValue(text, 'Fiche web :'),
134
+ };
135
+ }
136
+
137
+ async function callTool(tool: string, args: Record<string, unknown>): Promise<string> {
138
+ let sessionId: string | undefined;
139
+ const initialized = await mcpRequest(1, 'initialize', {
140
+ protocolVersion: MCP_PROTOCOL_VERSION,
141
+ capabilities: {},
142
+ clientInfo: { name: 'piecemaker-dossier', version: '0.1.0' },
143
+ });
144
+ sessionId = initialized.sessionId;
145
+ await mcpNotification('notifications/initialized', {}, sessionId);
146
+ const result = await mcpRequest(2, 'tools/call', { name: tool, arguments: args }, sessionId);
147
+ return resultText(result.payload);
148
+ }
149
+
150
+ async function searchCompanies(query: string): Promise<CompanySearchResult[]> {
151
+ const searchText = await callTool('rechercher_entreprise', { query });
152
+ const candidates = searchText.split('\n').map(parseSearchLine).filter((entry): entry is { name: string; siren: string; summary: string } => Boolean(entry));
153
+ const results = await Promise.all(candidates.map(async (candidate) => {
154
+ const details = await callTool('fiche_entreprise', { siren: candidate.siren });
155
+ const fields = parseCompanyDetails(details, candidate);
156
+ return {
157
+ name: fields.legalName || candidate.name,
158
+ siren: fields.siren || candidate.siren,
159
+ summary: candidate.summary,
160
+ url: fields.source,
161
+ details,
162
+ fields,
163
+ };
164
+ }));
165
+ return results;
166
+ }
167
+
168
+ export function createCompanySearchRouter() {
169
+ const router = express.Router();
170
+ router.post('/company-search', async (request: Request, response: Response) => {
171
+ const query = typeof request.body?.query === 'string' ? request.body.query.trim().slice(0, 200) : '';
172
+ if (!query) {
173
+ response.status(400).json({ error: 'Un nom ou un SIREN est requis.' });
174
+ return;
175
+ }
176
+ try {
177
+ response.json({ query, results: await searchCompanies(query) });
178
+ } catch (error) {
179
+ response.status(502).json({ error: error instanceof Error ? error.message : 'Recherche Registre Public impossible.' });
180
+ }
181
+ });
182
+ return router;
183
+ }
@@ -6,6 +6,7 @@ import type { Router } from 'express';
6
6
  import { providerRuntimeService, sessionsService } from '@/modules/providers/index.js';
7
7
  import { findApplicationRoot, getModuleDirectory } from '@/shared/utils.js';
8
8
 
9
+
9
10
  import { startRequiredAnonymizer } from './anonymizer/lifecycle.js';
10
11
  import { createCitationStore } from './harness/citation-store.js';
11
12
  import { installChatCitationHarness } from './harness/chat-harness.js';
@@ -13,6 +14,8 @@ import { createCitationsRouter } from './harness/citations.routes.js';
13
14
  import { startLibraryBackend, installLibraryRuntime } from './library/index.js';
14
15
  import { createKnowledgeBackend } from './knowledge/index.js';
15
16
  import { createTimesheetBackend } from './timesheet/index.js';
17
+ import { createCompanySearchRouter } from './company-search.js';
18
+ import { createBodaccSearchRouter } from './bodacc-search.js';
16
19
 
17
20
  /**
18
21
  * Point d'entrée PieceMaker. Les modules de `server/piecemaker/vendor/` sont du
@@ -35,11 +38,12 @@ type PieceMakerRuntimeStatus = {
35
38
  type PieceMakerVendorModule = {
36
39
  createPieceMakerRouter(options?: { getRuntimeStatus?: () => PieceMakerRuntimeStatus; anonymizer?: unknown }): Router;
37
40
  piecemakerHome(): string;
41
+ stopOriginalsJobs(): Promise<void>;
38
42
  };
39
43
 
40
44
  const vendor = createRequire(import.meta.url)(routerPath) as PieceMakerVendorModule;
41
45
 
42
- export const { piecemakerHome } = vendor;
46
+ export const { piecemakerHome, stopOriginalsJobs } = vendor;
43
47
  const { createAnonymizerService } = createRequire(import.meta.url)(path.join(applicationRoot, 'server/piecemaker/anonymizer/service.cjs'));
44
48
  const anonymizer = createAnonymizerService({ homeDir: piecemakerHome(), required: true });
45
49
  const ensureProxy = await startRequiredAnonymizer(anonymizer);
@@ -56,7 +60,9 @@ export function createPieceMakerRouter(options: { getRuntimeStatus?: () => Piece
56
60
  try { sessionsService.getSessionDetailsById(id); return true; } catch { return false; }
57
61
  }));
58
62
  router.use(timesheet);
59
- router.use(knowledge);
63
+ router.use(knowledge.router);
64
+ router.use(createCompanySearchRouter());
65
+ router.use(createBodaccSearchRouter());
60
66
  return router;
61
67
  }
62
68
  export type { PieceMakerRuntimeStatus };
@@ -3,6 +3,7 @@ import express from 'express';
3
3
  import { getConnection, projectsDb } from '@/modules/database/index.js';
4
4
 
5
5
  import { KnowledgeStore } from '../../../plugins/piecemaker-dossier/src/knowledge.js';
6
+
6
7
  import { createKnowledgePipeline } from './pipeline.js';
7
8
  import { createKnowledgeRouter } from './routes.js';
8
9
  import { createKnowledgeService } from './service.js';
@@ -10,13 +11,17 @@ import { createKnowledgeService } from './service.js';
10
11
  export function createKnowledgeBackend(applicationRoot: string) {
11
12
  const router = express.Router();
12
13
  let knowledgeRouter: express.Router | null = null;
14
+ let store: KnowledgeStore | null = null;
15
+ const getStore = () => {
16
+ if (!store) store = new KnowledgeStore(getConnection());
17
+ return store;
18
+ };
13
19
  router.use((request, response, next) => {
14
20
  if (!knowledgeRouter) {
15
- const store = new KnowledgeStore(getConnection());
16
- const pipeline = createKnowledgePipeline({ applicationRoot, projects: projectsDb, store });
17
- knowledgeRouter = createKnowledgeRouter(createKnowledgeService(store, projectsDb, pipeline));
21
+ const pipeline = createKnowledgePipeline({ applicationRoot, projects: projectsDb, store: getStore() });
22
+ knowledgeRouter = createKnowledgeRouter(createKnowledgeService(getStore(), projectsDb, pipeline));
18
23
  }
19
24
  knowledgeRouter(request, response, next);
20
25
  });
21
- return router;
26
+ return { router };
22
27
  }
@@ -1,12 +1,14 @@
1
+ import { createRequire } from 'node:module';
1
2
  import crypto from 'node:crypto';
2
3
  import fs from 'node:fs';
3
4
  import os from 'node:os';
4
5
  import path from 'node:path';
5
- import { spawn } from 'node:child_process';
6
6
 
7
7
  import type { KnowledgeStore } from '../../../plugins/piecemaker-dossier/src/knowledge.js';
8
- import type { GlinerDocument, GlinerMappingDocument, JsonData } from '../../../plugins/piecemaker-dossier/src/types.js';
9
8
  import { persistScanResult, scanResultOperations } from '../../../plugins/piecemaker-dossier/src/scan-result.js';
9
+ import type { GlinerDocument, GlinerMappingDocument, JsonData } from '../../../plugins/piecemaker-dossier/src/types.js';
10
+
11
+ import type { KnowledgeScanProgress } from './scan-jobs.js';
10
12
 
11
13
  type ProjectLookup = {
12
14
  getProjectById(projectId: string): { project_id: string; project_path: string } | null;
@@ -19,6 +21,29 @@ type PipelineOptions = {
19
21
  pythonPath?: string;
20
22
  };
21
23
 
24
+ type OriginalFile = { path: string; resource?: boolean };
25
+
26
+ type OriginalsPipeline = {
27
+ caseMappingFile(caseRoot: string): string;
28
+ listOriginals(caseRoot: string): Promise<OriginalFile[]>;
29
+ runManagedPythonJob(options: {
30
+ action: 'convert' | 'anonymize';
31
+ script: string;
32
+ args: string[];
33
+ onProgress?: (progress: KnowledgeScanProgress) => void;
34
+ signal?: AbortSignal;
35
+ }): Promise<unknown>;
36
+ writeCaseMapping(caseRoot: string, document: GlinerMappingDocument): unknown;
37
+ };
38
+
39
+ const require = createRequire(import.meta.url);
40
+ const {
41
+ caseMappingFile,
42
+ listOriginals,
43
+ runManagedPythonJob,
44
+ writeCaseMapping,
45
+ } = require('../vendor/websocket-server/originals-pipeline.cjs') as OriginalsPipeline;
46
+
22
47
  const SUPPORTED_EXTENSIONS = new Set(['.pdf', '.docx', '.doc', '.odt', '.rtf', '.txt', '.md', '.png', '.jpg', '.jpeg', '.tif', '.tiff', '.bmp']);
23
48
 
24
49
  function relativeKey(projectPath: string, filePath: string): string {
@@ -26,15 +51,18 @@ function relativeKey(projectPath: string, filePath: string): string {
26
51
  return crypto.createHash('sha256').update(relative).digest('hex');
27
52
  }
28
53
 
29
- function listedFiles(projectPath: string): string[] {
30
- return fs.readdirSync(projectPath, { withFileTypes: true })
31
- .filter((entry) => entry.isFile() && SUPPORTED_EXTENSIONS.has(path.extname(entry.name).toLowerCase()))
32
- .map((entry) => path.join(projectPath, entry.name));
54
+ /** Pièces scannées par défaut : même liste que l'administration, récursive, hors Markdown généré. */
55
+ export async function defaultScanFiles(projectPath: string): Promise<string[]> {
56
+ const root = fs.realpathSync(projectPath);
57
+ const originals = await listOriginals(root);
58
+ return originals
59
+ .filter((file) => !file.resource)
60
+ .map((file) => path.resolve(root, file.path));
33
61
  }
34
62
 
35
- function safeFiles(projectPath: string, requested: unknown): string[] {
63
+ async function safeFiles(projectPath: string, requested: unknown): Promise<string[]> {
36
64
  const root = fs.realpathSync(projectPath);
37
- const candidates = Array.isArray(requested) && requested.length ? requested : listedFiles(root);
65
+ const candidates = Array.isArray(requested) && requested.length ? requested : await defaultScanFiles(root);
38
66
  return [...new Set(candidates.map((candidate) => {
39
67
  const raw = String(candidate || '');
40
68
  const resolved = path.resolve(root, raw);
@@ -45,29 +73,18 @@ function safeFiles(projectPath: string, requested: unknown): string[] {
45
73
  }))];
46
74
  }
47
75
 
48
- function runProcess(executable: string, args: string[], cwd: string): Promise<{ stdout: string; stderr: string }> {
49
- return new Promise((resolve, reject) => {
50
- const child = spawn(executable, args, { cwd, env: process.env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
51
- let stdout = '';
52
- let stderr = '';
53
- child.stdout.setEncoding('utf8');
54
- child.stderr.setEncoding('utf8');
55
- child.stdout.on('data', (chunk) => { stdout += chunk; });
56
- child.stderr.on('data', (chunk) => { stderr += chunk; });
57
- child.once('error', reject);
58
- child.once('close', (code) => {
59
- if (code === 0) resolve({ stdout, stderr });
60
- else reject(new Error(stderr.trim() || stdout.trim() || `Pipeline failed with code ${code ?? 1}.`));
61
- });
62
- });
63
- }
64
-
65
76
  function parseObject(filePath: string): JsonData {
66
- const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^/, '')) as unknown;
77
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '')) as unknown;
67
78
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('Invalid pipeline output.');
68
79
  return parsed as JsonData;
69
80
  }
70
81
 
82
+ function readDocumentIndex(projectPath: string): JsonData {
83
+ const indexPath = path.join(projectPath, '.piecemaker', 'document-index.json');
84
+ if (!fs.existsSync(indexPath)) return { documents: {} };
85
+ return parseObject(indexPath);
86
+ }
87
+
71
88
  function documentsFromIndex(projectPath: string, files: string[], documentIndex: JsonData): GlinerDocument[] {
72
89
  const entries = documentIndex.documents && typeof documentIndex.documents === 'object' && !Array.isArray(documentIndex.documents)
73
90
  ? documentIndex.documents as Record<string, JsonData>
@@ -128,46 +145,61 @@ function textValue(value: unknown): string {
128
145
  return typeof value === 'string' ? value.trim() : '';
129
146
  }
130
147
 
148
+ async function withPythonPath<T>(pythonPath: string, run: () => Promise<T>): Promise<T> {
149
+ const previous = process.env.PYTHON_PATH;
150
+ process.env.PYTHON_PATH = pythonPath;
151
+ try {
152
+ return await run();
153
+ } finally {
154
+ if (previous === undefined) delete process.env.PYTHON_PATH;
155
+ else process.env.PYTHON_PATH = previous;
156
+ }
157
+ }
158
+
131
159
  export function createKnowledgePipeline(options: PipelineOptions) {
132
160
  const script = path.join(options.applicationRoot, 'server', 'piecemaker', 'vendor', 'websocket-server', 'scripts', 'convert_and_scan_pipeline.py');
133
161
  return {
134
- async scan(projectId: string, requestedFiles?: unknown) {
162
+ async scan(projectId: string, requestedFiles?: unknown, onProgress?: (progress: KnowledgeScanProgress) => void, signal?: AbortSignal) {
135
163
  const project = options.projects.getProjectById(projectId);
136
164
  if (!project) throw new Error('Project not found.');
137
165
  const projectPath = fs.realpathSync(project.project_path);
138
- const files = safeFiles(projectPath, requestedFiles);
166
+ const explicitFiles = Array.isArray(requestedFiles) && requestedFiles.length > 0;
167
+ const files = await safeFiles(projectPath, requestedFiles);
139
168
  if (!files.length) throw new Error('No supported file to scan.');
140
- const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'piecemaker-knowledge-'));
141
- const mappingFile = path.join(temporaryRoot, 'mapping.json');
142
- const stateFile = path.join(temporaryRoot, 'state.json');
169
+ const mappingFile = caseMappingFile(projectPath);
170
+ const stateFile = path.join(projectPath, '.piecemaker', 'anonymization-state.json');
143
171
  const outputDirectory = path.join(projectPath, 'Fichiers convertis PieceMaker');
172
+ fs.mkdirSync(path.dirname(mappingFile), { recursive: true });
173
+ fs.mkdirSync(path.dirname(stateFile), { recursive: true });
144
174
  fs.mkdirSync(outputDirectory, { recursive: true });
145
- try {
146
- fs.writeFileSync(mappingFile, JSON.stringify(mappingSeed(projectId, projectPath, options.store)), { mode: 0o600 });
147
- const args = [
148
- script,
149
- ...files,
150
- '-o',
151
- outputDirectory,
152
- '--mapping-file',
153
- mappingFile,
154
- '--case-root',
155
- projectPath,
156
- '--state-file',
157
- stateFile,
158
- ];
159
- const processResult = await runProcess(pythonExecutable(options.pythonPath), args, projectPath);
160
- const mapping = parseObject(mappingFile) as GlinerMappingDocument;
161
- const documentIndex = parseObject(path.join(temporaryRoot, 'document-index.json'));
162
- const documents = documentsFromIndex(projectPath, files, documentIndex);
163
- const scanResult = { projectId, mapping, documents };
164
- const result = Array.isArray(requestedFiles) && requestedFiles.length
165
- ? options.store.update({ projectId, operations: scanResultOperations(scanResult) })
166
- : persistScanResult(scanResult, options.store);
167
- return { ...result, documents: documents.length, stdout: processResult.stdout.trim(), stderr: processResult.stderr.trim() };
168
- } finally {
169
- fs.rmSync(temporaryRoot, { recursive: true, force: true });
170
- }
175
+ fs.writeFileSync(mappingFile, JSON.stringify(mappingSeed(projectId, projectPath, options.store)), { mode: 0o600 });
176
+ const args = [
177
+ ...files,
178
+ '-o',
179
+ outputDirectory,
180
+ '--mapping-file',
181
+ mappingFile,
182
+ '--case-root',
183
+ projectPath,
184
+ '--state-file',
185
+ stateFile,
186
+ ];
187
+ if (!explicitFiles) args.push('--skip-existing');
188
+ await withPythonPath(pythonExecutable(options.pythonPath), () => runManagedPythonJob({
189
+ action: 'anonymize',
190
+ script,
191
+ args,
192
+ onProgress,
193
+ signal,
194
+ }));
195
+ const mapping = parseObject(mappingFile) as GlinerMappingDocument;
196
+ writeCaseMapping(projectPath, mapping);
197
+ const documents = documentsFromIndex(projectPath, files, readDocumentIndex(projectPath));
198
+ const scanResult = { projectId, mapping, documents };
199
+ const result = explicitFiles
200
+ ? options.store.update({ projectId, operations: scanResultOperations(scanResult) })
201
+ : persistScanResult(scanResult, options.store);
202
+ return { ...result, documents: documents.length };
171
203
  },
172
204
  };
173
205
  }
@@ -17,13 +17,11 @@ export function createKnowledgeRouter(service: KnowledgeService) {
17
17
  response.status(statusFor(error)).json({ error: error instanceof Error ? error.message : 'Knowledge operation failed.' });
18
18
  });
19
19
  };
20
- router.get('/knowledge/overview', (request, response) => respond(() => service.overview(request.query.projectId), response));
21
- router.get('/knowledge/mapping', (request, response) => respond(() => service.mapping(request.query.projectId), response));
22
- router.get('/knowledge/chronology', (request, response) => respond(() => service.chronology(request.query.projectId), response));
23
20
  router.get('/knowledge/graph', (request, response) => respond(() => service.graph(request.query.projectId), response));
24
- router.get('/knowledge/agents', (request, response) => respond(() => service.agents(request.query.projectId), response));
25
21
  router.post('/knowledge/query', (request, response) => respond(() => service.query(request.body), response));
26
22
  router.post('/knowledge/update', (request, response) => respond(() => service.update(request.body), response));
27
23
  router.post('/knowledge/scan', (request, response) => respond(() => service.scan(request.body?.projectId, request.body?.files), response));
24
+ router.get('/knowledge/scan/job', (request, response) => respond(() => service.scanJob(request.query.id, request.query.projectId), response));
25
+ router.post('/knowledge/scan/cancel', (request, response) => respond(() => service.cancelScan(request.body?.id, request.body?.projectId), response));
28
26
  return router;
29
27
  }
@@ -0,0 +1,111 @@
1
+ import crypto from 'node:crypto';
2
+
3
+ export type KnowledgeScanPhase = 'convert' | 'scan' | 'commit';
4
+
5
+ export type KnowledgeScanProgress = {
6
+ phase: KnowledgeScanPhase;
7
+ percent: number;
8
+ processed: number;
9
+ total: number;
10
+ };
11
+
12
+ export type KnowledgeScanJob = {
13
+ id: string;
14
+ projectId: string;
15
+ source: 'knowledge';
16
+ action: 'anonymize';
17
+ state: 'running' | 'done' | 'error' | 'cancelled';
18
+ phase: KnowledgeScanPhase;
19
+ percent: number;
20
+ processed: number;
21
+ total: number;
22
+ error: string | null;
23
+ startedAt: string;
24
+ finishedAt: string | null;
25
+ };
26
+
27
+ const COMPLETED_RETENTION_MS = 5 * 60_000;
28
+
29
+ function isPending(job: KnowledgeScanJob): boolean {
30
+ return job.state === 'running';
31
+ }
32
+
33
+ export function createKnowledgeScanJobs() {
34
+ const jobs = new Map<string, KnowledgeScanJob>();
35
+ const controllers = new Map<string, AbortController>();
36
+
37
+ const prune = () => {
38
+ const deadline = Date.now() - COMPLETED_RETENTION_MS;
39
+ for (const [id, job] of jobs) {
40
+ if (isPending(job)) continue;
41
+ if (Date.parse(job.finishedAt || job.startedAt) < deadline) jobs.delete(id);
42
+ }
43
+ };
44
+
45
+ const runningForProject = (projectId: string): KnowledgeScanJob | null => {
46
+ for (const job of jobs.values()) if (job.projectId === projectId && isPending(job)) return job;
47
+ return null;
48
+ };
49
+
50
+ return {
51
+ start(projectId: string, run: (report: (progress: KnowledgeScanProgress) => void, signal: AbortSignal) => Promise<unknown>): KnowledgeScanJob {
52
+ prune();
53
+ const running = runningForProject(projectId);
54
+ if (running) return running;
55
+ const job: KnowledgeScanJob = {
56
+ id: crypto.randomUUID(),
57
+ projectId,
58
+ source: 'knowledge',
59
+ action: 'anonymize',
60
+ state: 'running',
61
+ phase: 'convert',
62
+ percent: 0,
63
+ processed: 0,
64
+ total: 0,
65
+ error: null,
66
+ startedAt: new Date().toISOString(),
67
+ finishedAt: null,
68
+ };
69
+ jobs.set(job.id, job);
70
+ const report = (progress: KnowledgeScanProgress) => {
71
+ if (job.state !== 'running') return;
72
+ job.phase = progress.phase;
73
+ job.percent = Math.max(job.percent, Math.min(100, progress.percent));
74
+ job.processed = progress.processed;
75
+ job.total = progress.total || job.total;
76
+ };
77
+ const controller = new AbortController();
78
+ controllers.set(job.id, controller);
79
+ run(report, controller.signal).then(() => {
80
+ if (job.state !== 'running') return;
81
+ job.state = 'done';
82
+ job.phase = 'commit';
83
+ job.percent = 100;
84
+ }, (error: unknown) => {
85
+ if (job.state !== 'running') return;
86
+ job.state = 'error';
87
+ job.error = error instanceof Error ? error.message : String(error);
88
+ }).finally(() => {
89
+ controllers.delete(job.id);
90
+ job.finishedAt = job.finishedAt || new Date().toISOString();
91
+ });
92
+ return job;
93
+ },
94
+ get(id: unknown): KnowledgeScanJob | null {
95
+ prune();
96
+ return typeof id === 'string' ? jobs.get(id) || null : null;
97
+ },
98
+ runningForProject(projectId: string): KnowledgeScanJob | null {
99
+ prune();
100
+ return runningForProject(projectId);
101
+ },
102
+ cancel(job: KnowledgeScanJob | null): KnowledgeScanJob | null {
103
+ if (!job || job.state !== 'running') return job;
104
+ job.state = 'cancelled';
105
+ job.finishedAt = new Date().toISOString();
106
+ controllers.get(job.id)?.abort();
107
+ controllers.delete(job.id);
108
+ return job;
109
+ },
110
+ };
111
+ }
@@ -1,11 +1,10 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
-
4
1
  import type { KnowledgeStore } from '../../../plugins/piecemaker-dossier/src/knowledge.js';
5
2
  import { exclusionNodeOperation } from '../../../plugins/piecemaker-dossier/src/scan-result.js';
6
3
  import type { KnowledgeQueryInput, KnowledgeUpdateInput } from '../../../plugins/piecemaker-dossier/src/types.js';
4
+
7
5
  import { legacyExclusions } from './pipeline.js';
8
6
  import type { createKnowledgePipeline } from './pipeline.js';
7
+ import { createKnowledgeScanJobs } from './scan-jobs.js';
9
8
 
10
9
  type ProjectLookup = {
11
10
  getProjectById(projectId: string): { project_id: string; project_path: string } | null;
@@ -17,6 +16,7 @@ function projectId(value: unknown): string {
17
16
  }
18
17
 
19
18
  export function createKnowledgeService(store: KnowledgeStore, projects: ProjectLookup, pipeline: ReturnType<typeof createKnowledgePipeline>) {
19
+ const scanJobs = createKnowledgeScanJobs();
20
20
  const ensureProject = (value: unknown): string => {
21
21
  const id = projectId(value);
22
22
  if (!projects.getProjectById(id)) throw new Error('Project not found.');
@@ -39,42 +39,27 @@ export function createKnowledgeService(store: KnowledgeStore, projects: ProjectL
39
39
  update(input: KnowledgeUpdateInput) {
40
40
  return store.update({ ...input, projectId: ensureProject(input.projectId) });
41
41
  },
42
- overview(value: unknown) {
43
- const id = ensureProject(value);
44
- const nodes = snapshot(id).nodes;
45
- const counts = nodes.reduce<Record<string, number>>((result, node) => {
46
- result[node.kind] = (result[node.kind] || 0) + 1;
47
- return result;
48
- }, {});
49
- return { projectId: id, counts, total: nodes.length };
50
- },
51
- mapping(value: unknown) {
52
- const id = ensureProject(value);
53
- const current = snapshot(id);
54
- return { projectId: id, nodes: current.nodes.filter((node) => node.kind !== 'document'), mappings: current.mappings, exclusions: current.exclusions || [] };
55
- },
56
- chronology(value: unknown) {
57
- const id = ensureProject(value);
58
- const current = snapshot(id);
59
- return { projectId: id, documents: current.nodes.filter((node) => node.kind === 'document'), links: current.links.filter((link) => link.relation === 'mentions') };
60
- },
61
42
  graph(value: unknown) {
62
43
  const id = ensureProject(value);
63
44
  return snapshot(id);
64
45
  },
65
- agents(value: unknown) {
46
+ scan(value: unknown, files?: unknown) {
66
47
  const id = ensureProject(value);
67
- const project = projects.getProjectById(id);
68
- if (!project) throw new Error('Project not found.');
69
- const root = fs.realpathSync(project.project_path);
70
- const candidate = path.join(root, 'AGENTS.md');
71
- if (!fs.existsSync(candidate)) return { projectId: id, content: '', exists: false };
72
- const real = fs.realpathSync(candidate);
73
- if (real !== root && !real.startsWith(`${root}${path.sep}`)) throw new Error('Invalid AGENTS.md path.');
74
- return { projectId: id, content: fs.readFileSync(real, 'utf8').slice(0, 2_000_000), exists: true };
48
+ return {
49
+ job: scanJobs.start(id, async (report, signal) => {
50
+ const result = await pipeline.scan(id, files, report, signal);
51
+ store.markAnonymizationComplete(id);
52
+ return result;
53
+ }),
54
+ };
75
55
  },
76
- scan(value: unknown, files?: unknown) {
77
- return pipeline.scan(ensureProject(value), files);
56
+ scanJob(jobId: unknown, value?: unknown) {
57
+ const job = scanJobs.get(jobId) || (value ? scanJobs.runningForProject(ensureProject(value)) : null);
58
+ return { job };
59
+ },
60
+ cancelScan(jobId: unknown, value?: unknown) {
61
+ const job = scanJobs.get(jobId) || (value ? scanJobs.runningForProject(ensureProject(value)) : null);
62
+ return { job: scanJobs.cancel(job) };
78
63
  },
79
64
  };
80
65
  }