@piecemaker-legal/piecemaker 2.0.9 → 2.0.11

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 (177) hide show
  1. package/dist/assets/FindReplaceDialog-KU5TEIBJ-BucOHLdm.js +1 -0
  2. package/dist/assets/FootnotePropertiesDialog-W3YHM4BV-D5UqbelV.js +1 -0
  3. package/dist/assets/HyperlinkDialog-ZYH7ZHRF-8yEhLigd.js +144 -0
  4. package/dist/assets/HyperlinkDialog-ZYH7ZHRF-qIPBOssF.css +1 -0
  5. package/dist/assets/ImagePositionDialog-HEWYR6Q3-wGIBYxCm.js +1 -0
  6. package/dist/assets/ImagePropertiesDialog-RAB47C6M-D86PckZ2.js +1 -0
  7. package/dist/assets/PageSetupDialog-AIGIXO3E-DLETP-7v.js +1 -0
  8. package/dist/assets/SplitCellDialog-IFBKYCD6-CwdQBXeb.js +1 -0
  9. package/dist/assets/TablePropertiesDialog-TMLKGOOW-j2nA7KJs.js +1 -0
  10. package/dist/assets/WatermarkDialog-KRL7WMIS-XFJ9gTkC.js +1 -0
  11. package/dist/assets/{abnfDiagram-VCTEODGH-WrsSAHOe.js → abnfDiagram-VCTEODGH-DiCrND_Y.js} +1 -1
  12. package/dist/assets/arc-DOjgbMYz.js +1 -0
  13. package/dist/assets/architectureDiagram-5GKGNRK7-brzIFVhl.js +36 -0
  14. package/dist/assets/{blockDiagram-I7D4REHJ-zGMnF7Ox.js → blockDiagram-I7D4REHJ-C8JhTjQQ.js} +1 -1
  15. package/dist/assets/{c4Diagram-7LVT6UL2-ClWw3bqt.js → c4Diagram-7LVT6UL2-jfYxccyk.js} +1 -1
  16. package/dist/assets/channel-Dajzu2W1.js +1 -0
  17. package/dist/assets/{chunk-2Q5K7J3B-C-9ICPHv.js → chunk-2Q5K7J3B-CrlEShp9.js} +1 -1
  18. package/dist/assets/{chunk-5VM5RSS4-Bawy3teT.js → chunk-5VM5RSS4-DzPlUHGq.js} +1 -1
  19. package/dist/assets/{chunk-F27PBJKO-DQ375RQO.js → chunk-F27PBJKO-Cp9cpeWU.js} +1 -1
  20. package/dist/assets/{chunk-IMKFNOWR-CflNF2f6.js → chunk-IMKFNOWR-BifUldEN.js} +1 -1
  21. package/dist/assets/{chunk-JWPE2WC7-CIXQv0gc.js → chunk-JWPE2WC7-ggPEIkWu.js} +1 -1
  22. package/dist/assets/{chunk-POPQ4Y6H-Cza5j6Qw.js → chunk-POPQ4Y6H-3h_nayRX.js} +1 -1
  23. package/dist/assets/{chunk-SVP7TREG-GQ_VvWZR.js → chunk-SVP7TREG-CH29aG6J.js} +1 -1
  24. package/dist/assets/{chunk-TICWLB2K-BEmU16W5.js → chunk-TICWLB2K-PluJUM5k.js} +1 -1
  25. package/dist/assets/{chunk-XXDRQBXY-C1KpkLYH.js → chunk-XXDRQBXY-DXU39Vwg.js} +1 -1
  26. package/dist/assets/classDiagram-ZZMXUADV-dDEP_giv.js +1 -0
  27. package/dist/assets/classDiagram-v2-VYDZK3BY-dDEP_giv.js +1 -0
  28. package/dist/assets/{cose-bilkent-JH36ORCC-CZE48TRG.js → cose-bilkent-JH36ORCC-Dz6roYrN.js} +1 -1
  29. package/dist/assets/{cynefin-OW5HDTMX-L-tx1eC-.js → cynefin-OW5HDTMX-BLzrpSit.js} +1 -1
  30. package/dist/assets/{cynefinDiagram-5FMLGOSQ-Coa8oJwR.js → cynefinDiagram-5FMLGOSQ-kfxnERDZ.js} +1 -1
  31. package/dist/assets/{dagre-GXQ25YYZ-Bu_3Y75K.js → dagre-GXQ25YYZ-DP57bp-N.js} +1 -1
  32. package/dist/assets/{diagram-S7CK7UJ4-CFiiV4es.js → diagram-S7CK7UJ4-DAlsrQVW.js} +1 -1
  33. package/dist/assets/{diagram-UQ7AKVKN-CCXheGAA.js → diagram-UQ7AKVKN-By5yrMer.js} +1 -1
  34. package/dist/assets/{diagram-VSXAHHWV-B70m4lr_.js → diagram-VSXAHHWV-Dx89kryN.js} +1 -1
  35. package/dist/assets/{diagram-VX7I27RA-CHkiMOVA.js → diagram-VX7I27RA-B9fARitZ.js} +1 -1
  36. package/dist/assets/{diagram-Z3DM3KII-_awpc4Bs.js → diagram-Z3DM3KII-DC15CEH1.js} +1 -1
  37. package/dist/assets/{ebnfDiagram-PWID7BFC-CZzjH86H.js → ebnfDiagram-PWID7BFC-BKQzghGF.js} +1 -1
  38. package/dist/assets/{erDiagram-RLTQ6QDP-9DgiHZg9.js → erDiagram-RLTQ6QDP-CPY6RM7G.js} +1 -1
  39. package/dist/assets/{flowDiagram-HODETNUW-DHY_503b.js → flowDiagram-HODETNUW-4dL4GJcf.js} +1 -1
  40. package/dist/assets/{ganttDiagram-EL5Y4UJY-DPiiYZ_l.js → ganttDiagram-EL5Y4UJY-D4-cVcfb.js} +1 -1
  41. package/dist/assets/{gitGraphDiagram-WWUBYQGX-Ddd_evm7.js → gitGraphDiagram-WWUBYQGX-BMeVIEiX.js} +1 -1
  42. package/dist/assets/{index-tibHq7Qp.js → index-DNtFDOni.js} +140 -140
  43. package/dist/assets/index-Dc9wNuS4.css +1 -0
  44. package/dist/assets/{infoDiagram-27XIBGKW-BPRNhfwb.js → infoDiagram-27XIBGKW-D5qMSUEU.js} +1 -1
  45. package/dist/assets/{ishikawaDiagram-5VMMS53U-BXx6aqkq.js → ishikawaDiagram-5VMMS53U-BDnyKRHm.js} +1 -1
  46. package/dist/assets/{journeyDiagram-3NMN7TZE-BK8TZM29.js → journeyDiagram-3NMN7TZE-dGz5ZGzq.js} +1 -1
  47. package/dist/assets/{kanban-definition-UXKFOSKX-CHioRmxz.js → kanban-definition-UXKFOSKX-D7UAz7o_.js} +6 -6
  48. package/dist/assets/layout-DKrmEGrz.js +1 -0
  49. package/dist/assets/{linear-CTEhUcKG.js → linear-CrTdMY-p.js} +1 -1
  50. package/dist/assets/{mermaid.core-DYUb_UUB.js → mermaid.core-CXQOT5G6.js} +7 -7
  51. package/dist/assets/{mindmap-definition-YA3MSWOX-D7ODZ4oS.js → mindmap-definition-YA3MSWOX-VgEiYfay.js} +1 -1
  52. package/dist/assets/{pegDiagram-XKGWAZYB-8J-_2_kw.js → pegDiagram-XKGWAZYB-9IqT8gGx.js} +1 -1
  53. package/dist/assets/{pieDiagram-E7YTZNPT-ebg0yfMi.js → pieDiagram-E7YTZNPT-D98pRE-t.js} +1 -1
  54. package/dist/assets/processTemplate-GPFDLQMX-B1sVV2O0.js +22 -0
  55. package/dist/assets/{quadrantDiagram-AXDQQJYC-BoWkDiFb.js → quadrantDiagram-AXDQQJYC-b9PwkYzL.js} +1 -1
  56. package/dist/assets/{railroadDiagram-O6MQD6OU-BRJ77ldz.js → railroadDiagram-O6MQD6OU-Dz87hsPE.js} +1 -1
  57. package/dist/assets/{requirementDiagram-BXWQKSXE-Z0JJgktA.js → requirementDiagram-BXWQKSXE-CkcV73dK.js} +1 -1
  58. package/dist/assets/sankeyDiagram-P5KCCOFB-UBN31Yrs.js +40 -0
  59. package/dist/assets/{sequenceDiagram-WJ2MYXX4-BPAjtXwp.js → sequenceDiagram-WJ2MYXX4-CpQxd5Za.js} +1 -1
  60. package/dist/assets/{server.browser-BzI1jrZw.js → server.browser-BY3bL-IX.js} +1 -1
  61. package/dist/assets/{sizeCapture-INFHLROL-BGZPCzM4.js → sizeCapture-INFHLROL-mCsFmyzw.js} +1 -1
  62. package/dist/assets/stateDiagram-D77RDMKH-B0MTBN-3.js +1 -0
  63. package/dist/assets/stateDiagram-v2-MP3YSRHH-DzBPN39W.js +1 -0
  64. package/dist/assets/{swimlanes-42K2YHIH-DvuB15qF.js → swimlanes-42K2YHIH-Dzydjm0e.js} +1 -1
  65. package/dist/assets/swimlanesDiagram-VR7AAH4N-BXQpbUMD.js +8 -0
  66. package/dist/assets/{timeline-definition-24CTP7MA-ZIziYCIF.js → timeline-definition-24CTP7MA-Dz0toOLu.js} +3 -3
  67. package/dist/assets/{vendor-codemirror-CjW0lHR5.js → vendor-codemirror-CN-sJePf.js} +2 -2
  68. package/dist/assets/{vendor-react-D809iwXR.js → vendor-react-B22wqN3J.js} +5 -5
  69. package/dist/assets/{vennDiagram-4TSXK5OY-C5_B9gWa.js → vennDiagram-4TSXK5OY-DOWp7dkf.js} +4 -4
  70. package/dist/assets/{wardleyDiagram-VM6X3IG4-DMlH9Afw.js → wardleyDiagram-VM6X3IG4-CUxsztUB.js} +1 -1
  71. package/dist/assets/{xychartDiagram-S5SC5T6Z-BldhJt7i.js → xychartDiagram-S5SC5T6Z-DS96vNED.js} +1 -1
  72. package/dist/index.html +4 -4
  73. package/dist/sw.js +1 -1
  74. package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js +4 -53
  75. package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js.map +1 -1
  76. package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js +2 -3
  77. package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js.map +1 -1
  78. package/dist-server/server/modules/providers/list/mistral/mistral-vibe-models.js +30 -0
  79. package/dist-server/server/modules/providers/list/mistral/mistral-vibe-models.js.map +1 -0
  80. package/dist-server/server/modules/providers/tests/mistral-vibe-models.test.js +24 -0
  81. package/dist-server/server/modules/providers/tests/mistral-vibe-models.test.js.map +1 -0
  82. package/dist-server/server/piecemaker/agent-instructions.js +148 -0
  83. package/dist-server/server/piecemaker/agent-instructions.js.map +1 -0
  84. package/dist-server/server/piecemaker/anonymizer/lifecycle.js +2 -3
  85. package/dist-server/server/piecemaker/anonymizer/lifecycle.js.map +1 -1
  86. package/dist-server/server/piecemaker/docx-document.js +50 -0
  87. package/dist-server/server/piecemaker/docx-document.js.map +1 -0
  88. package/dist-server/server/piecemaker/index.js +13 -2
  89. package/dist-server/server/piecemaker/index.js.map +1 -1
  90. package/dist-server/server/piecemaker/model-discovery/codex-catalog.js +46 -0
  91. package/dist-server/server/piecemaker/model-discovery/codex-catalog.js.map +1 -0
  92. package/dist-server/server/piecemaker/model-discovery/index.js +42 -0
  93. package/dist-server/server/piecemaker/model-discovery/index.js.map +1 -0
  94. package/dist-server/server/piecemaker/model-discovery/mistral-catalog.js +19 -0
  95. package/dist-server/server/piecemaker/model-discovery/mistral-catalog.js.map +1 -0
  96. package/dist-server/server/piecemaker/model-discovery/stdio-rpc.js +61 -0
  97. package/dist-server/server/piecemaker/model-discovery/stdio-rpc.js.map +1 -0
  98. package/dist-server/server/piecemaker/project-registry.js +29 -0
  99. package/dist-server/server/piecemaker/project-registry.js.map +1 -0
  100. package/package.json +12 -1
  101. package/scripts/piecemaker/cli/disable-anonymizer.mjs +19 -0
  102. package/scripts/piecemaker/cli/lib/config.mjs +0 -1
  103. package/scripts/piecemaker/cli/lib/plugins.mjs +1 -0
  104. package/scripts/piecemaker/cli/lib/services.mjs +1 -10
  105. package/scripts/piecemaker/cli/piecemaker.mjs +0 -3
  106. package/server/modules/providers/list/mistral/mistral-models.provider.ts +4 -53
  107. package/server/modules/providers/list/mistral/mistral-runtime.provider.ts +2 -3
  108. package/server/modules/providers/list/mistral/mistral-vibe-models.ts +43 -0
  109. package/server/modules/providers/tests/mistral-vibe-models.test.ts +27 -0
  110. package/server/piecemaker/agent-instructions.ts +148 -0
  111. package/server/piecemaker/anonymizer/client-config.cjs +92 -92
  112. package/server/piecemaker/anonymizer/dictionary.cjs +34 -3
  113. package/server/piecemaker/anonymizer/hudsucker-fixture.cjs +9 -16
  114. package/server/piecemaker/anonymizer/hudsucker-proxy/Cargo.toml +1 -1
  115. package/server/piecemaker/anonymizer/hudsucker-proxy/src/main.rs +251 -268
  116. package/server/piecemaker/anonymizer/hudsucker-smoke.cjs +343 -0
  117. package/server/piecemaker/anonymizer/lifecycle.ts +2 -3
  118. package/server/piecemaker/anonymizer/rewriter-bridge.cjs +13 -4
  119. package/server/piecemaker/anonymizer/rewriter-worker.cjs +85 -0
  120. package/server/piecemaker/anonymizer/service.cjs +109 -179
  121. package/server/piecemaker/anonymizer/sqlite-dictionary.cjs +12 -1
  122. package/server/piecemaker/docx-document.ts +54 -0
  123. package/server/piecemaker/harness/decisions.cjs +1 -1
  124. package/server/piecemaker/harness/index.cjs +2 -3
  125. package/server/piecemaker/harness/verification.cjs +1 -1
  126. package/server/piecemaker/index.ts +15 -3
  127. package/server/piecemaker/model-discovery/codex-catalog.ts +67 -0
  128. package/server/piecemaker/model-discovery/index.ts +69 -0
  129. package/server/piecemaker/model-discovery/mistral-catalog.ts +36 -0
  130. package/server/piecemaker/model-discovery/stdio-rpc.ts +79 -0
  131. package/server/piecemaker/project-registry.ts +34 -0
  132. package/server/piecemaker/router.cjs +6 -8
  133. package/server/piecemaker/vendor/installer/bin/piecemaker.mjs +31 -77
  134. package/server/piecemaker/vendor/installer/lib/codex-skills.mjs +2 -84
  135. package/server/piecemaker/vendor/installer/lib/service.mjs +4 -24
  136. package/server/piecemaker/vendor/installer/steps/02-dependances-node.mjs +5 -5
  137. package/server/piecemaker/vendor/installer/steps/06-hooks.mjs +10 -21
  138. package/server/piecemaker/vendor/installer/steps/09-claude-assets.mjs +0 -11
  139. package/server/piecemaker/vendor/installer/steps/09-codex-plugin.mjs +6 -36
  140. package/server/piecemaker/vendor/installer/steps/13-garde-secrets.mjs +30 -12
  141. package/server/piecemaker/vendor/piecemaker-plugin/README.md +2 -9
  142. package/server/piecemaker/vendor/piecemaker-plugin/scripts/billing-track.mjs +5 -5
  143. package/server/piecemaker/vendor/piecemaker-plugin/scripts/classify-ai-documents.mjs +8 -8
  144. package/server/piecemaker/vendor/piecemaker-plugin/scripts/commit-track.mjs +2 -2
  145. package/server/piecemaker/vendor/piecemaker-plugin/scripts/compile-recherche.mjs +4 -6
  146. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folder-structure.cjs +2 -90
  147. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs +71 -38
  148. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs +8 -38
  149. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs +82 -61
  150. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +189 -41
  151. package/server/piecemaker/vendor/piecemaker-plugin/scripts/protect-originals.mjs +4 -55
  152. package/server/piecemaker/vendor/piecemaker-plugin/scripts/statusline.mjs +24 -117
  153. package/server/piecemaker/vendor/piecemaker-plugin/skills/ajout-de-fonctionnalites/SKILL.md +4 -18
  154. package/server/piecemaker/vendor/piecemaker-plugin/skills/tamponnage/SKILL.md +2 -2
  155. package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +14 -148
  156. package/server/piecemaker/vendor/websocket-server/case-registry.cjs +16 -42
  157. package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +0 -73
  158. package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +0 -7
  159. package/server/piecemaker/vendor/websocket-server/stamping-routes.cjs +3 -12
  160. package/server/piecemaker/vendor/websocket-server/workspace-paths.cjs +6 -6
  161. package/dist/assets/arc-DAA6wH-C.js +0 -1
  162. package/dist/assets/architectureDiagram-5GKGNRK7-BGNHr7ef.js +0 -36
  163. package/dist/assets/channel-BAQyfMll.js +0 -1
  164. package/dist/assets/classDiagram-ZZMXUADV-BNGCuvrR.js +0 -1
  165. package/dist/assets/classDiagram-v2-VYDZK3BY-BNGCuvrR.js +0 -1
  166. package/dist/assets/index-WwknP0l4.css +0 -1
  167. package/dist/assets/layout-DEnkyfTY.js +0 -1
  168. package/dist/assets/sankeyDiagram-P5KCCOFB-4PW5HTOP.js +0 -40
  169. package/dist/assets/stateDiagram-D77RDMKH-o-vEcKwz.js +0 -1
  170. package/dist/assets/stateDiagram-v2-MP3YSRHH-BsuDaKkN.js +0 -1
  171. package/dist/assets/swimlanesDiagram-VR7AAH4N-DcCAyKlT.js +0 -8
  172. package/scripts/piecemaker/cli/lib/piecemaker-anonymizer.mjs +0 -78
  173. package/scripts/piecemaker/hooks/proxy-guard.mjs +0 -251
  174. package/server/piecemaker/anonymizer/providers.cjs +0 -120
  175. package/server/piecemaker/vendor/installer/steps/08-telegram.mjs +0 -482
  176. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/central-mapping.cjs +0 -0
  177. package/server/piecemaker/vendor/websocket-server/telegram-admin.cjs +0 -602
@@ -1,20 +1,3 @@
1
- /**
2
- * Cycle de vie du proxy PII et point d'injection dans CloudCLI.
3
- *
4
- * Deux leviers, aucun ne touchant un fichier upstream.
5
- *
6
- * **L'environnement du serveur.** Les quatre chemins de lancement de CloudCLI —
7
- * chat Claude, Codex, Cursor, opencode — étalent `process.env` dans le
8
- * sous-processus qu'ils créent, tout comme le terminal intégré. Poser des
9
- * variables ici les atteint donc tous les cinq sans ajouter une ligne ailleurs.
10
- *
11
- * **Les configurations utilisateur des clients.** Codex ne lit pas de variable
12
- * de base d'URL, opencode préfère sa configuration ; `providers.cjs` s'en
13
- * charge, et pose le garde-fou de Cursor, seul client non interceptable.
14
- *
15
- * Le proxy vit et meurt avec le serveur : à l'arrêt, les bases écrites sont
16
- * retirées pour ne pas laisser les clients pointer un port fermé.
17
- */
18
1
  const { spawn } = require('node:child_process');
19
2
  const fs = require('node:fs');
20
3
  const os = require('node:os');
@@ -22,20 +5,22 @@ const path = require('node:path');
22
5
  const tls = require('node:tls');
23
6
 
24
7
  const { createSqliteDictionaryLoader } = require('./sqlite-dictionary.cjs');
25
- const { startRewriterBridge } = require('./rewriter-bridge.cjs');
26
- const { bypassProviders, configureProviders } = require('./providers.cjs');
27
- const { createHarnessJuridique } = require('../harness/index.cjs');
8
+ const { startRewriterWorker } = require('./rewriter-worker.cjs');
9
+ const { removeLegacyProxyConfig } = require('./client-config.cjs');
28
10
 
29
- const ENV_VAR = 'ANTHROPIC_BASE_URL';
30
- const OPENAI_ENV_VAR = 'OPENAI_BASE_URL';
31
11
  const DEFAULT_UPSTREAM = 'https://api.anthropic.com';
32
- const DEFAULT_PORT = 4111;
33
- const PROXY_ENV_KEYS = [
34
- 'HTTPS_PROXY', 'HTTP_PROXY', 'ALL_PROXY', 'NO_PROXY', 'NODE_USE_ENV_PROXY',
35
- 'NODE_EXTRA_CA_CERTS', 'SSL_CERT_FILE', 'CODEX_CA_CERTIFICATE', 'REQUESTS_CA_BUNDLE',
36
- ];
12
+ const INTERCEPTED_HOSTS = ['api.anthropic.com', 'api.openai.com', 'chatgpt.com'];
13
+ const COVERAGE = {
14
+ claude: { state: 'filtered', detail: null, file: null },
15
+ codex: { state: 'filtered', detail: null, file: null },
16
+ opencode: { state: 'filtered', detail: null, file: null },
17
+ cursor: { state: 'blocked', detail: 'not-interceptable', file: null },
18
+ };
19
+
20
+ const CURSOR_REFUSAL = 'PieceMaker : cursor-agent ne peut pas être filtré par le proxy d’anonymisation '
21
+ + '(son protocole n’accepte aucune base d’URL). Tant qu’un mapping existe, il est bloqué pour éviter '
22
+ + 'que des noms de parties quittent la machine en clair. Utilisez Claude, Codex ou opencode.';
37
23
 
38
- /** Interrupteur : `PIECEMAKER_ANONYMIZER=off`, ou `anonymizer.enabled: false` dans `config.json`. */
39
24
  function isDisabled(homeDir) {
40
25
  const flag = String(process.env.PIECEMAKER_ANONYMIZER || '').toLowerCase();
41
26
  if (flag === 'off' || flag === '0' || flag === 'false') return true;
@@ -47,13 +32,6 @@ function isDisabled(homeDir) {
47
32
  }
48
33
  }
49
34
 
50
- /**
51
- * Cible du relais Anthropic. Une base distante déjà configurée (passerelle
52
- * d'entreprise, point de terminaison alternatif) est respectée : on se chaîne
53
- * derrière elle. Une base en boucle locale ne peut être qu'une instance
54
- * précédente de ce proxy, et serait un relais mort : on l'ignore au profit de
55
- * l'API officielle.
56
- */
57
35
  function resolveUpstream(configured) {
58
36
  if (!configured) return DEFAULT_UPSTREAM;
59
37
  try {
@@ -65,35 +43,17 @@ function resolveUpstream(configured) {
65
43
  }
66
44
  }
67
45
 
68
- /**
69
- * Lecture du rapport de `configureProviders` en une couverture par client, la
70
- * seule chose que l'interface a besoin de montrer.
71
- *
72
- * `filtered` : les échanges passent par le proxy et sont anonymisés.
73
- * `blocked` : le client ne peut pas être filtré et est donc empêché de partir.
74
- * `unconfigured` : le câblage a échoué ou est en conflit — le client peut
75
- * encore parler en direct, et c'est précisément ce que le statut doit crier.
76
- */
77
- function summarizeCoverage(report) {
78
- const coverage = {};
79
- for (const [name, result] of Object.entries(report || {})) {
80
- if (result?.blocked) coverage[name] = { state: 'blocked', detail: result.reason, file: result.file || null };
81
- else if (result?.configured) coverage[name] = { state: 'filtered', detail: null, file: result.file || null };
82
- else coverage[name] = { state: 'unconfigured', detail: result?.reason || 'unknown', file: result?.file || null };
83
- }
84
- return coverage;
85
- }
86
-
87
46
  function hudsuckerBinary() {
88
47
  if (process.env.PIECEMAKER_HUDSUCKER_BIN) return process.env.PIECEMAKER_HUDSUCKER_BIN;
89
48
  const name = process.platform === 'win32' ? 'piecemaker-hudsucker.exe' : 'piecemaker-hudsucker';
90
- const packaged = path.join(__dirname, 'bin', name);
91
- const release = path.join(__dirname, 'hudsucker-proxy', 'target', 'release', name);
92
- const debug = path.join(__dirname, 'hudsucker-proxy', 'target', 'debug', name);
93
- if (fs.existsSync(packaged)) return packaged;
94
- if (fs.existsSync(release)) return release;
95
- if (fs.existsSync(debug)) return debug;
96
- return packaged;
49
+ const candidates = [
50
+ path.join(__dirname, 'bin', name),
51
+ path.join(__dirname, 'hudsucker-proxy', 'target', 'release', name),
52
+ path.join(__dirname, 'hudsucker-proxy', 'target', 'debug', name),
53
+ ];
54
+ const present = candidates.filter((candidate) => fs.existsSync(candidate));
55
+ if (!present.length) return candidates[0];
56
+ return present.reduce((newest, candidate) => (fs.statSync(candidate).mtimeMs > fs.statSync(newest).mtimeMs ? candidate : newest));
97
57
  }
98
58
 
99
59
  function certificatePaths() {
@@ -113,28 +73,52 @@ function writeTrustBundle(caFile, bundleFile) {
113
73
  return bundleFile;
114
74
  }
115
75
 
116
- function waitForOutput(child, needle, timeoutMs) {
76
+ function removeLegacyCentralMapping(homeDir) {
77
+ let entries = [];
78
+ try {
79
+ entries = fs.readdirSync(homeDir);
80
+ } catch {
81
+ return;
82
+ }
83
+ for (const name of entries) {
84
+ if (name.startsWith('central-mapping.json')) fs.rmSync(path.join(homeDir, name), { force: true });
85
+ }
86
+ }
87
+
88
+ function installCursorGuard({ binDir, mappingFile }) {
89
+ fs.mkdirSync(binDir, { recursive: true });
90
+ const file = path.join(binDir, 'cursor-agent');
91
+ const script = `#!/bin/sh
92
+ if [ -s ${JSON.stringify(mappingFile)} ]; then
93
+ printf '%s\\n' ${JSON.stringify(CURSOR_REFUSAL)} >&2
94
+ exit 78
95
+ fi
96
+ PATH=$(printf '%s' "$PATH" | sed -e "s|^${binDir.replace(/[|\\]/g, '\\$&')}:||")
97
+ export PATH
98
+ exec cursor-agent "$@"
99
+ `;
100
+ fs.writeFileSync(file, script, { encoding: 'utf8', mode: 0o755 });
101
+ return { file };
102
+ }
103
+
104
+ function waitForListening(child, timeoutMs) {
117
105
  return new Promise((resolve, reject) => {
118
106
  let buffer = '';
119
- let settled = false;
120
- const finish = (failed, value) => {
121
- if (settled) return;
122
- settled = true;
107
+ const finish = (error, port) => {
123
108
  clearTimeout(timer);
124
- child.stdout?.off('data', take);
125
- child.stderr?.off('data', take);
109
+ child.stdout.off('data', take);
126
110
  child.off('exit', onExit);
127
- if (failed) reject(value);
128
- else resolve(value);
111
+ if (error) reject(error);
112
+ else resolve(port);
129
113
  };
130
- const timer = setTimeout(() => finish(true, new Error(`hudsucker n'a pas écouté : ${buffer}`)), timeoutMs);
114
+ const timer = setTimeout(() => finish(new Error(`hudsucker n'a pas écouté : ${buffer}`)), timeoutMs);
131
115
  const take = (chunk) => {
132
116
  buffer += chunk.toString();
133
- if (buffer.includes(needle)) finish(false, buffer);
117
+ const match = buffer.match(/listening 127\.0\.0\.1:(\d+)/);
118
+ if (match) finish(null, Number(match[1]));
134
119
  };
135
- const onExit = (code) => finish(true, new Error(`hudsucker sorti (${code}) : ${buffer}`));
136
- child.stdout?.on('data', take);
137
- child.stderr?.on('data', take);
120
+ const onExit = (code) => finish(new Error(`hudsucker sorti (${code}) : ${buffer}`));
121
+ child.stdout.on('data', take);
138
122
  child.once('exit', onExit);
139
123
  });
140
124
  }
@@ -142,8 +126,7 @@ function waitForOutput(child, needle, timeoutMs) {
142
126
  function proxyEnvironment(proxyUrl, caFile, trustBundle) {
143
127
  return {
144
128
  HTTPS_PROXY: proxyUrl,
145
- HTTP_PROXY: proxyUrl,
146
- ALL_PROXY: proxyUrl,
129
+ https_proxy: proxyUrl,
147
130
  NO_PROXY: 'localhost,127.0.0.1,::1',
148
131
  NODE_USE_ENV_PROXY: '1',
149
132
  NODE_EXTRA_CA_CERTS: caFile,
@@ -153,32 +136,20 @@ function proxyEnvironment(proxyUrl, caFile, trustBundle) {
153
136
  };
154
137
  }
155
138
 
156
- function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = console, required = false }) {
157
- const dictionary = createSqliteDictionaryLoader({ databasePath: process.env.DATABASE_PATH || path.join(homeDir, 'auth.db') });
158
- const legacyMappingFile = path.join(homeDir, 'central-mapping.json');
159
- // Harnais de citations vérifiées (décisions Légifrance + bloc <CITATIONS>) :
160
- // son propre interrupteur (`PIECEMAKER_CITATIONS=off`) est géré à
161
- // l'intérieur, pas ici.
162
- const harness = createHarnessJuridique({ homeDir, verifyResponses: false });
163
- // Le shim Cursor vit sous le répertoire de données PieceMaker : il n'a rien à
164
- // faire dans un répertoire appartenant à un fournisseur.
139
+ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = console }) {
140
+ const databasePath = process.env.DATABASE_PATH || path.join(homeDir, 'auth.db');
141
+ const dictionary = createSqliteDictionaryLoader({ databasePath });
165
142
  const binDir = path.join(homeDir, 'bin');
166
- const state = {
167
- enabled: false,
168
- reason: 'not_started',
169
- origin: null,
170
- upstream: null,
171
- startedAt: null,
172
- routes: [],
173
- coverage: {},
174
- };
143
+ const state = { enabled: false, reason: 'not_started', origin: null, upstream: null, coverage: {} };
175
144
  let proxy = null;
176
145
  let bridge = null;
177
146
  let previousEnv = null;
147
+ const killProxy = () => proxy?.kill('SIGKILL');
178
148
 
179
149
  async function start() {
180
150
  if (proxy) return state;
181
- if (!required && isDisabled(homeDir)) {
151
+ removeLegacyProxyConfig({ userHome, homeDir });
152
+ if (isDisabled(homeDir)) {
182
153
  state.reason = 'disabled';
183
154
  return state;
184
155
  }
@@ -191,50 +162,41 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
191
162
  return state;
192
163
  }
193
164
 
194
- const claudeUpstream = resolveUpstream(process.env[ENV_VAR]);
195
- const hosts = ['api.anthropic.com', 'api.openai.com', 'chatgpt.com'];
196
- try {
197
- const extra = new URL(claudeUpstream).hostname;
198
- if (extra && !hosts.includes(extra)) hosts.push(extra);
199
- } catch {
200
- // l'amont par défaut reste la liste fixe
201
- }
165
+ const upstream = resolveUpstream(process.env.ANTHROPIC_BASE_URL);
166
+ const hosts = [...new Set([...INTERCEPTED_HOSTS, new URL(upstream).hostname])];
202
167
 
203
168
  try {
204
- bridge = await startRewriterBridge({ dictionary, harness });
169
+ bridge = await startRewriterWorker({ databasePath, homeDir });
205
170
  } catch (error) {
206
171
  state.reason = `listen_failed: ${error.message}`;
207
172
  logger.warn?.(`[piecemaker] réécriture PII non démarrée : ${error.message}`);
208
173
  return state;
209
174
  }
210
175
 
211
- const listenedPort = DEFAULT_PORT;
212
- const origin = `http://127.0.0.1:${listenedPort}`;
213
176
  const child = spawn(binary, [
214
- '--listen', `127.0.0.1:${listenedPort}`,
177
+ '--listen', '127.0.0.1:0',
215
178
  '--rewriter', `http://127.0.0.1:${bridge.port}`,
216
179
  '--ca-cert', certificates.cert,
217
180
  '--ca-key', certificates.key,
218
181
  '--hosts', hosts.join(','),
219
182
  ], {
220
- stdio: ['ignore', 'pipe', 'pipe'],
221
- env: {
222
- ...process.env,
223
- HTTPS_PROXY: '',
224
- HTTP_PROXY: '',
225
- ALL_PROXY: '',
226
- https_proxy: '',
227
- http_proxy: '',
228
- all_proxy: '',
229
- },
183
+ stdio: ['pipe', 'pipe', 'pipe'],
184
+ env: { ...process.env, HTTPS_PROXY: '', HTTP_PROXY: '', ALL_PROXY: '', https_proxy: '', http_proxy: '', all_proxy: '' },
230
185
  });
231
186
  proxy = child;
232
- child.stdout?.on('data', (chunk) => logger.log?.(String(chunk).trim()));
233
- child.stderr?.on('data', (chunk) => logger.warn?.(`[piecemaker] hudsucker : ${String(chunk).trim()}`));
187
+ process.once('exit', killProxy);
188
+ child.stderr.on('data', (chunk) => logger.warn?.(`[piecemaker] hudsucker : ${String(chunk).trim()}`));
189
+ child.once('exit', (code, signal) => {
190
+ if (proxy === child && state.enabled) logger.warn?.(`[piecemaker] hudsucker arrêté (${code ?? signal}) : requêtes IA refusées`);
191
+ });
192
+
193
+ let port;
234
194
  try {
235
- await waitForOutput(child, `listening 127.0.0.1:${listenedPort}`, 15000);
195
+ port = await waitForListening(child, 15000);
196
+ child.stdout.resume();
236
197
  } catch (error) {
237
198
  child.kill('SIGKILL');
199
+ process.off('exit', killProxy);
238
200
  proxy = null;
239
201
  await bridge.close();
240
202
  bridge = null;
@@ -243,72 +205,44 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
243
205
  return state;
244
206
  }
245
207
 
246
- // Publie le port dans la config partagée pour que les consommateurs
247
- // indépendants du process serveur puissent le retrouver.
248
- try {
249
- const configFile = path.join(homeDir, 'config.json');
250
- const current = JSON.parse(fs.readFileSync(configFile, 'utf8'));
251
- if (current.mikePiiPort !== listenedPort) {
252
- fs.writeFileSync(configFile, `${JSON.stringify({ ...current, mikePiiPort: listenedPort }, null, 2)}\n`, 'utf8');
253
- }
254
- } catch (error) {
255
- logger.warn?.(`[piecemaker] impossible de publier mikePiiPort dans config.json : ${error.message}`);
256
- }
257
-
258
- previousEnv = {
259
- [ENV_VAR]: process.env[ENV_VAR],
260
- [OPENAI_ENV_VAR]: process.env[OPENAI_ENV_VAR],
261
- PATH: process.env.PATH,
262
- };
263
- for (const key of PROXY_ENV_KEYS) previousEnv[key] = process.env[key];
264
- const trustBundle = writeTrustBundle(certificates.cert, certificates.trustBundle);
265
- Object.assign(process.env, proxyEnvironment(origin, certificates.cert, trustBundle));
208
+ const origin = `http://127.0.0.1:${port}`;
209
+ const environment = proxyEnvironment(origin, certificates.cert, writeTrustBundle(certificates.cert, certificates.trustBundle));
210
+ previousEnv = Object.fromEntries(['PATH', 'HTTP_PROXY', 'http_proxy', 'ALL_PROXY', 'all_proxy', ...Object.keys(environment)]
211
+ .map((key) => [key, process.env[key]]));
212
+ for (const key of ['HTTP_PROXY', 'http_proxy', 'ALL_PROXY', 'all_proxy']) delete process.env[key];
213
+ Object.assign(process.env, environment);
214
+ removeLegacyCentralMapping(homeDir);
215
+ installCursorGuard({ binDir, mappingFile: path.join(homeDir, 'central-mapping.json') });
266
216
  process.env.PATH = `${binDir}${path.delimiter}${process.env.PATH || ''}`;
267
217
 
268
- const report = await configureProviders({
269
- origin,
270
- caFile: certificates.cert,
271
- trustBundle,
272
- userHome,
273
- binDir,
274
- mappingFile: legacyMappingFile,
275
- });
276
218
  Object.assign(state, {
277
219
  enabled: true,
278
220
  reason: 'running',
279
221
  origin,
280
- upstream: claudeUpstream,
281
- startedAt: new Date().toISOString(),
282
- routes: hosts.map((host) => ({ provider: host, upstream: `https://${host}` })),
283
- coverage: summarizeCoverage(report),
222
+ upstream,
223
+ coverage: COVERAGE,
284
224
  });
285
-
286
- const uncovered = Object.entries(state.coverage)
287
- .filter(([, value]) => value.state === 'unconfigured')
288
- .map(([name]) => name);
289
225
  logger.log?.(`[piecemaker] anonymisation active sur ${origin} (claude, codex, opencode ; cursor bloqué)`);
290
- if (uncovered.length) {
291
- logger.warn?.(`[piecemaker] clients non filtrés, à vérifier : ${uncovered.join(', ')}`);
292
- }
293
226
  return state;
294
227
  }
295
228
 
296
229
  async function stop() {
297
- if (!proxy && !bridge) {
298
- dictionary.close();
299
- return;
300
- }
301
- if (proxy && proxy.exitCode === null) {
302
- proxy.kill('SIGTERM');
303
- await Promise.race([
304
- new Promise((resolve) => proxy.once('exit', resolve)),
305
- new Promise((resolve) => setTimeout(resolve, 2000)),
306
- ]);
307
- if (proxy.exitCode === null) proxy.kill('SIGKILL');
230
+ if (proxy) {
231
+ const child = proxy;
232
+ proxy = null;
233
+ process.off('exit', killProxy);
234
+ if (child.exitCode === null) {
235
+ child.kill('SIGTERM');
236
+ await Promise.race([
237
+ new Promise((resolve) => child.once('exit', resolve)),
238
+ new Promise((resolve) => setTimeout(resolve, 2000)),
239
+ ]);
240
+ if (child.exitCode === null) child.kill('SIGKILL');
241
+ }
308
242
  }
309
243
  if (bridge) await bridge.close();
310
244
  bridge = null;
311
- await bypassProviders({ userHome, binDir });
245
+ fs.rmSync(path.join(binDir, 'cursor-agent'), { force: true });
312
246
  if (previousEnv) {
313
247
  for (const [key, value] of Object.entries(previousEnv)) {
314
248
  if (value === undefined) delete process.env[key];
@@ -316,16 +250,15 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
316
250
  }
317
251
  previousEnv = null;
318
252
  }
319
- proxy = null;
320
253
  dictionary.close();
321
- Object.assign(state, { enabled: false, reason: 'stopped', origin: null, routes: [], coverage: {} });
254
+ Object.assign(state, { enabled: false, reason: 'stopped', origin: null, upstream: null, coverage: {} });
322
255
  }
323
256
 
324
257
  function status() {
325
258
  const current = dictionary.get();
326
259
  return {
327
260
  ...state,
328
- listening: Boolean(proxy && proxy.exitCode === null && !proxy.killed),
261
+ listening: Boolean(proxy && proxy.exitCode === null && proxy.signalCode === null),
329
262
  dictionary: {
330
263
  file: dictionary.file,
331
264
  exists: dictionary.exists(),
@@ -342,11 +275,8 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
342
275
  }
343
276
 
344
277
  module.exports = {
345
- ENV_VAR,
346
- OPENAI_ENV_VAR,
347
278
  createAnonymizerService,
348
- isDisabled,
279
+ installCursorGuard,
349
280
  resolveUpstream,
350
- summarizeCoverage,
351
281
  writeTrustBundle,
352
282
  };
@@ -1,3 +1,4 @@
1
+ const fs = require('node:fs');
1
2
  const path = require('node:path');
2
3
 
3
4
  const Database = require('better-sqlite3');
@@ -62,6 +63,7 @@ function createSqliteDictionaryLoader({ databasePath }) {
62
63
  let mappingSignature = null;
63
64
  let generation = 0;
64
65
  let current = EMPTY_DICTIONARY;
66
+ let failure = null;
65
67
 
66
68
  function connection() {
67
69
  if (!database) {
@@ -97,15 +99,24 @@ function createSqliteDictionaryLoader({ databasePath }) {
97
99
  generation += 1;
98
100
  current = buildDictionary(buildMappingDocument(rows), generation);
99
101
  dataVersion = nextDataVersion;
102
+ failure = null;
100
103
  return current;
101
- } catch {
104
+ } catch (error) {
105
+ failure = fs.existsSync(file) ? error : null;
102
106
  return current;
103
107
  }
104
108
  }
105
109
 
110
+ function loadForOutbound() {
111
+ const dictionary = load(false);
112
+ if (failure) throw new Error(`dictionnaire illisible : ${failure.message}`);
113
+ return dictionary;
114
+ }
115
+
106
116
  return {
107
117
  file,
108
118
  get: () => load(false),
119
+ getForOutbound: loadForOutbound,
109
120
  refresh: () => load(true),
110
121
  exists: () => !load(false).empty,
111
122
  close: () => {
@@ -0,0 +1,54 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { rename, rm, stat, writeFile } from 'fs/promises';
3
+ import path from 'path';
4
+
5
+ import express, { type Request, type Response } from 'express';
6
+
7
+ import { projectsDb } from '@/modules/database/index.js';
8
+
9
+ const MAX_DOCX_BYTES = '100mb';
10
+
11
+ function resolveDocxPath(request: Request): string | null {
12
+ const projectId = typeof request.query.projectId === 'string' ? request.query.projectId : '';
13
+ const filePath = typeof request.query.path === 'string' ? request.query.path : '';
14
+ const projectRoot = projectId ? projectsDb.getProjectPathById(projectId) : null;
15
+ if (!projectRoot || !filePath.toLowerCase().endsWith('.docx')) return null;
16
+ const resolved = path.resolve(projectRoot, filePath);
17
+ return resolved.startsWith(path.resolve(projectRoot) + path.sep) ? resolved : null;
18
+ }
19
+
20
+ async function readVersion(filePath: string): Promise<number> {
21
+ return (await stat(filePath)).mtimeMs;
22
+ }
23
+
24
+ export function createDocxDocumentRouter() {
25
+ const router = express.Router();
26
+
27
+ router.get('/docx-document/version', async (request: Request, response: Response) => {
28
+ const filePath = resolveDocxPath(request);
29
+ if (!filePath) return response.status(400).json({ error: 'Invalid docx path' });
30
+ try {
31
+ response.json({ version: await readVersion(filePath) });
32
+ } catch {
33
+ response.status(404).json({ error: 'File not found' });
34
+ }
35
+ });
36
+
37
+ router.put('/docx-document', express.raw({ type: () => true, limit: MAX_DOCX_BYTES }), async (request: Request, response: Response) => {
38
+ const filePath = resolveDocxPath(request);
39
+ if (!filePath || !Buffer.isBuffer(request.body) || request.body.length === 0) {
40
+ return response.status(400).json({ error: 'Invalid docx payload' });
41
+ }
42
+ const temporaryPath = `${filePath}.${randomUUID()}.tmp`;
43
+ try {
44
+ await writeFile(temporaryPath, request.body);
45
+ await rename(temporaryPath, filePath);
46
+ response.json({ version: await readVersion(filePath) });
47
+ } catch (error) {
48
+ await rm(temporaryPath, { force: true });
49
+ response.status(500).json({ error: error instanceof Error ? error.message : String(error) });
50
+ }
51
+ });
52
+
53
+ return router;
54
+ }
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * Capture des décisions Légifrance lues via l'outil MCP `consulter_decision`,
5
5
  * repérées dans un corps de requête SORTANTE déjà analysé en JSON par le
6
- * proxy PII (`server/piecemaker/anonymizer/proxy.cjs`).
6
+ * proxy PII (`server/piecemaker/anonymizer/rewriter-bridge.cjs`).
7
7
  *
8
8
  * Équivalent serveur du hook `decision-cache.mjs` de l'autre dépôt
9
9
  * (`piecemaker-plugin/scripts/decision-cache.mjs`, PostToolUse Claude Code) :
@@ -1,9 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * Façade du harnais de citations vérifiées, à brancher sur le proxy PII
5
- * local (`server/piecemaker/anonymizer/proxy.cjs`) — câblage laissé à une
6
- * tâche ultérieure, ce module ne modifie rien de `proxy.cjs`.
4
+ * Façade du harnais de citations vérifiées, branchée sur le réécrivain du
5
+ * proxy PII (`server/piecemaker/anonymizer/rewriter-bridge.cjs`).
7
6
  *
8
7
  * Deux observateurs, symétriques au sens et au retour du proxy lui-même :
9
8
  * - `observerRequete` regarde ce qui PART vers le fournisseur (avant
@@ -6,7 +6,7 @@
6
6
  * (`piecemaker-plugin/scripts/verify-citations.mjs`).
7
7
  *
8
8
  * Ce dépôt n'a pas de hook Claude Code : tout le trafic IA passe par le
9
- * proxy PII local (`server/piecemaker/anonymizer/proxy.cjs`), qui est donc
9
+ * proxy PII local (`server/piecemaker/anonymizer/rewriter-bridge.cjs`), qui est donc
10
10
  * l'unique point d'observation possible pour un texte produit par le modèle.
11
11
  * Contrairement au hook d'origine, ce module n'a pas le pouvoir de bloquer
12
12
  * un tour (il n'y a pas de conversation Claude Code à interrompre côté
@@ -3,10 +3,13 @@ import path from 'path';
3
3
 
4
4
  import type { Router } from 'express';
5
5
 
6
+ import { projectsDb } from '@/modules/database/index.js';
6
7
  import { providerRuntimeService, sessionsService } from '@/modules/providers/index.js';
8
+ import { providerRegistry } from '@/modules/providers/provider.registry.js';
7
9
  import { findApplicationRoot, getModuleDirectory } from '@/shared/utils.js';
8
10
 
9
11
 
12
+ import { createAgentInstructionsRouter, startAgentInstructionsMirror } from './agent-instructions.js';
10
13
  import { startRequiredAnonymizer } from './anonymizer/lifecycle.js';
11
14
  import { createCitationStore } from './harness/citation-store.js';
12
15
  import { installChatCitationHarness } from './harness/chat-harness.js';
@@ -16,7 +19,10 @@ import { createKnowledgeBackend } from './knowledge/index.js';
16
19
  import { createTimesheetBackend } from './timesheet/index.js';
17
20
  import { createCompanySearchRouter } from './company-search.js';
18
21
  import { createBodaccSearchRouter } from './bodacc-search.js';
22
+ import { createDocxDocumentRouter } from './docx-document.js';
19
23
  import { createShellEnvironmentRouter, resolveDesktopShellEnvironment } from './shell-environment.js';
24
+ import { installModelDiscovery } from './model-discovery/index.js';
25
+ import { startProjectRegistry } from './project-registry.js';
20
26
 
21
27
  /**
22
28
  * Point d'entrée PieceMaker. Les modules de `server/piecemaker/vendor/` sont du
@@ -37,8 +43,9 @@ type PieceMakerRuntimeStatus = {
37
43
  };
38
44
 
39
45
  type PieceMakerVendorModule = {
40
- createPieceMakerRouter(options?: { getRuntimeStatus?: () => PieceMakerRuntimeStatus; anonymizer?: unknown }): Router;
46
+ createPieceMakerRouter(options?: { getRuntimeStatus?: () => PieceMakerRuntimeStatus; anonymizer?: unknown; publishProjects?: () => void }): Router;
41
47
  piecemakerHome(): string;
48
+ publishProjectSource(sourceId: string, folders: string[] | null): void;
42
49
  stopOriginalsJobs(): Promise<void>;
43
50
  };
44
51
 
@@ -46,22 +53,25 @@ const shellEnvironment = await resolveDesktopShellEnvironment();
46
53
  const vendor = createRequire(import.meta.url)(routerPath) as PieceMakerVendorModule;
47
54
 
48
55
  export const { piecemakerHome, stopOriginalsJobs } = vendor;
56
+ const publishProjects = startProjectRegistry(vendor.publishProjectSource);
49
57
  const { createAnonymizerService } = createRequire(import.meta.url)(path.join(applicationRoot, 'server/piecemaker/anonymizer/service.cjs'));
50
- const anonymizer = createAnonymizerService({ homeDir: piecemakerHome(), required: true });
58
+ const anonymizer = createAnonymizerService({ homeDir: piecemakerHome() });
51
59
  const ensureProxy = await startRequiredAnonymizer(anonymizer);
52
60
  const citations = createCitationStore(piecemakerHome());
53
61
  installChatCitationHarness({ runtime: providerRuntimeService, sessions: sessionsService, store: citations, ensureProxy });
54
62
  const library = await openLibrary(piecemakerHome(), applicationRoot);
55
63
  installLibraryRuntime(providerRuntimeService, sessionsService, library.store);
64
+ installModelDiscovery(providerRegistry);
56
65
  const timesheet = createTimesheetBackend(piecemakerHome());
57
66
  const knowledge = createKnowledgeBackend(applicationRoot);
67
+ startAgentInstructionsMirror(() => projectsDb.getProjectPaths().map((row) => row.project_path));
58
68
 
59
69
  export function createPieceMakerLocalRouter() {
60
70
  return knowledge.localRouter;
61
71
  }
62
72
 
63
73
  export function createPieceMakerRouter(options: { getRuntimeStatus?: () => PieceMakerRuntimeStatus } = {}) {
64
- const router = vendor.createPieceMakerRouter({ ...options, anonymizer });
74
+ const router = vendor.createPieceMakerRouter({ ...options, anonymizer, publishProjects });
65
75
  router.use(createCitationsRouter(citations, (id) => {
66
76
  try { sessionsService.getSessionDetailsById(id); return true; } catch { return false; }
67
77
  }));
@@ -70,6 +80,8 @@ export function createPieceMakerRouter(options: { getRuntimeStatus?: () => Piece
70
80
  router.use(knowledge.router);
71
81
  router.use(createCompanySearchRouter());
72
82
  router.use(createBodaccSearchRouter());
83
+ router.use(createDocxDocumentRouter());
84
+ router.use(createAgentInstructionsRouter(path.join(piecemakerHome(), 'CLAUDE.md')));
73
85
  router.use(createShellEnvironmentRouter(shellEnvironment));
74
86
  return router;
75
87
  }